294 lines
7.4 KiB
Vue
294 lines
7.4 KiB
Vue
<template>
|
||
<view class="container">
|
||
<view class="screen">银幕</view>
|
||
<canvas
|
||
canvas-id="seatCanvas"
|
||
type="2d"
|
||
class="seat-canvas"
|
||
@touchstart="handleTouchStart"
|
||
@touchmove="handleTouchMove"
|
||
@touchend="handleTouchEnd"
|
||
></canvas>
|
||
<view class="legend">
|
||
<view class="legend-item">
|
||
<view class="seat-icon available"></view>
|
||
<text>可选</text>
|
||
</view>
|
||
<view class="legend-item">
|
||
<view class="seat-icon selected"></view>
|
||
<text>已选</text>
|
||
</view>
|
||
<view class="legend-item">
|
||
<view class="seat-icon sold"></view>
|
||
<text>已售</text>
|
||
</view>
|
||
</view>
|
||
<view class="selected-seats">
|
||
<text>已选座位:{{ selectedSeats.join(', ') || '无' }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
ctx: null,
|
||
canvasWidth: 300,
|
||
canvasHeight: 500,
|
||
seatSize: 30,
|
||
gap: 10,
|
||
rows: 8,
|
||
cols: 10,
|
||
seats: [],
|
||
selectedSeats: [],
|
||
touchStartX: 0,
|
||
touchStartY: 0,
|
||
offsetX: 0,
|
||
offsetY: 0
|
||
}
|
||
},
|
||
onReady() {
|
||
this.initSeats();
|
||
this.initAndroidCanvas();
|
||
},
|
||
methods: {
|
||
initSeats() {
|
||
// 初始化座位数据
|
||
for (let i = 0; i < this.rows; i++) {
|
||
this.seats[i] = [];
|
||
for (let j = 0; j < this.cols; j++) {
|
||
// 随机生成一些已售座位
|
||
const isSold = Math.random() < 0.2;
|
||
this.seats[i][j] = {
|
||
row: i,
|
||
col: j,
|
||
status: isSold ? 'sold' : 'available',
|
||
x: 0,
|
||
y: 0
|
||
};
|
||
}
|
||
}
|
||
},
|
||
// 替换原来的 initAndroidCanvas 方法
|
||
initAndroidCanvas() {
|
||
return new Promise((resolve) => {
|
||
// 安卓平台需要使用 uni.createCanvasContext
|
||
this.ctx = uni.createCanvasContext('seatCanvas', this);
|
||
console.log(this.ctx,'ctx123123123')
|
||
// 获取Canvas显示尺寸
|
||
const query = uni.createSelectorQuery().in(this);
|
||
query.select('.seat-canvas')
|
||
.boundingClientRect(rect => {
|
||
if (!rect) {
|
||
console.error('获取Canvas尺寸失败');
|
||
return resolve(false);
|
||
}
|
||
|
||
this.canvasWidth = rect.width;
|
||
this.canvasHeight = rect.height;
|
||
|
||
// 计算初始偏移使座位居中
|
||
const totalWidth = this.cols * (this.seatSize + this.gap) - this.gap;
|
||
const totalHeight = this.rows * (this.seatSize + this.gap) - this.gap;
|
||
this.offsetX = (this.canvasWidth - totalWidth) / 2;
|
||
this.offsetY = (this.canvasHeight - totalHeight) / 2;
|
||
|
||
this.drawSeats();
|
||
resolve(true);
|
||
})
|
||
.exec();
|
||
});
|
||
},
|
||
|
||
drawSeats() {
|
||
if (!this.ctx) return;
|
||
|
||
// 清空画布(安卓平台方式)
|
||
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||
|
||
// 绘制座位
|
||
for (let i = 0; i < this.rows; i++) {
|
||
for (let j = 0; j < this.cols; j++) {
|
||
const seat = this.seats[i][j];
|
||
const x = j * (this.seatSize + this.gap) + this.offsetX;
|
||
const y = i * (this.seatSize + this.gap) + this.offsetY;
|
||
|
||
// 更新座位坐标
|
||
seat.x = x;
|
||
seat.y = y;
|
||
|
||
// 设置座位颜色
|
||
switch (seat.status) {
|
||
case 'available':
|
||
this.ctx.setFillStyle('#4CAF50');
|
||
break;
|
||
case 'selected':
|
||
this.ctx.setFillStyle('#2196F3');
|
||
break;
|
||
case 'sold':
|
||
this.ctx.setFillStyle('#9E9E9E');
|
||
break;
|
||
}
|
||
|
||
// 绘制座位(安卓平台API)
|
||
this.ctx.fillRect(x, y, this.seatSize, this.seatSize);
|
||
|
||
// 绘制座位编号
|
||
this.ctx.setFillStyle('#FFFFFF');
|
||
this.ctx.setFontSize(12);
|
||
this.ctx.setTextAlign('center');
|
||
this.ctx.fillText(
|
||
`${String.fromCharCode(65 + i)}${j + 1}`,
|
||
x + this.seatSize / 2,
|
||
y + this.seatSize / 2 + 4 // 安卓平台文字垂直对齐需要微调
|
||
);
|
||
}
|
||
}
|
||
|
||
// 实际绘制(安卓平台需要显式调用draw)
|
||
this.ctx.draw();
|
||
},
|
||
// 触摸事件处理(安卓专用优化)
|
||
handleTouchStart(e) {
|
||
this.touchStartX = e.touches[0].x;
|
||
this.touchStartY = e.touches[0].y;
|
||
},
|
||
|
||
handleTouchMove(e) {
|
||
const touchX = e.touches[0].x;
|
||
const touchY = e.touches[0].y;
|
||
|
||
// 计算移动距离
|
||
const dx = touchX - this.touchStartX;
|
||
const dy = touchY - this.touchStartY;
|
||
|
||
// 更新偏移量
|
||
this.offsetX += dx;
|
||
this.offsetY += dy;
|
||
|
||
// 限制边界
|
||
const minX = this.canvasWidth - (this.cols * (this.seatSize + this.gap) - this.gap);
|
||
const minY = this.canvasHeight - (this.rows * (this.seatSize + this.gap) - this.gap);
|
||
|
||
this.offsetX = Math.max(minX, Math.min(0, this.offsetX));
|
||
this.offsetY = Math.max(minY, Math.min(0, this.offsetY));
|
||
|
||
// 更新起始位置
|
||
this.touchStartX = touchX;
|
||
this.touchStartY = touchY;
|
||
|
||
this.drawSeats();
|
||
},
|
||
|
||
handleTouchEnd(e) {
|
||
// 检测是否点击了座位
|
||
const touchX = e.changedTouches[0].x;
|
||
const touchY = e.changedTouches[0].y;
|
||
|
||
// 检查是否点击了座位
|
||
for (let i = 0; i < this.rows; i++) {
|
||
for (let j = 0; j < this.cols; j++) {
|
||
const seat = this.seats[i][j];
|
||
if (
|
||
touchX >= seat.x &&
|
||
touchX <= seat.x + this.seatSize &&
|
||
touchY >= seat.y &&
|
||
touchY <= seat.y + this.seatSize
|
||
) {
|
||
this.toggleSeatSelection(seat);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
},
|
||
|
||
toggleSeatSelection(seat) {
|
||
if (seat.status === 'sold') return;
|
||
|
||
if (seat.status === 'available') {
|
||
seat.status = 'selected';
|
||
this.selectedSeats.push(`${String.fromCharCode(65 + seat.row)}${seat.col + 1}`);
|
||
} else {
|
||
seat.status = 'available';
|
||
this.selectedSeats = this.selectedSeats.filter(
|
||
s => s !== `${String.fromCharCode(65 + seat.row)}${seat.col + 1}`
|
||
);
|
||
}
|
||
|
||
this.drawSeats();
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 20rpx;
|
||
height: 100vh;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.screen {
|
||
width: 80%;
|
||
height: 40rpx;
|
||
background: linear-gradient(to bottom, #ccc, #fff);
|
||
margin-bottom: 40rpx;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
color: #333;
|
||
border-radius: 8rpx;
|
||
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.seat-canvas {
|
||
width: 100%;
|
||
height: 500px; /* 安卓使用固定像素单位更可靠 */
|
||
background-color: #fff;
|
||
margin-bottom: 20rpx;
|
||
border-radius: 8rpx;
|
||
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.legend {
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
margin: 0 20rpx;
|
||
}
|
||
|
||
.seat-icon {
|
||
width: 30rpx;
|
||
height: 30rpx;
|
||
margin-right: 10rpx;
|
||
border-radius: 4rpx;
|
||
}
|
||
|
||
.available {
|
||
background-color: #4CAF50;
|
||
}
|
||
|
||
.selected {
|
||
background-color: #2196F3;
|
||
}
|
||
|
||
.sold {
|
||
background-color: #9E9E9E;
|
||
}
|
||
|
||
.selected-seats {
|
||
padding: 20rpx;
|
||
background-color: #fff;
|
||
border-radius: 8rpx;
|
||
width: 90%;
|
||
text-align: center;
|
||
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
</style> |