canvas/pages/index/step2.vue
2025-06-26 18:15:12 +08:00

294 lines
7.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>