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

310 lines
8.0 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: 0,
canvasHeight: 0,
seatSize: 30,
gap: 10,
rows: 8,
cols: 10,
seats: [],
selectedSeats: [],
touchStartX: 0,
touchStartY: 0,
offsetX: 0,
offsetY: 0,
scale: 1
}
},
onReady() {
this.initSeats()
this.initCanvas()
},
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', // available, selected, sold
x: 0,
y: 0
}
}
}
},
async initCanvas() {
// 获取canvas上下文
const { canvas, width, height } = await this.getCanvasNode('seatCanvas')
this.canvasWidth = width
this.canvasHeight = height
this.ctx = canvas.getContext('2d')
// 计算初始偏移使座位居中
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()
},
getCanvasNode(id) {
return new Promise((resolve) => {
const query = uni.createSelectorQuery().in(this)
query.select(`#${id}`)
.fields({ node: true, size: true })
.exec((res) => {
console.log('给我看看',res)
console.log('给我看看',res)
console.log('给我看看',res)
console.log('给我看看',res)
console.log('给我看看',res)
console.log('给我看看',res)
const canvas = res[0].node
const width = res[0].width
const height = res[0].height
// 设置canvas实际宽高
canvas.width = width * uni.getSystemInfoSync().pixelRatio
canvas.height = height * uni.getSystemInfoSync().pixelRatio
resolve({ canvas, width, height })
})
})
},
drawSeats() {
if (!this.ctx) return
// 清空画布
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
// 保存当前状态
this.ctx.save()
// 应用缩放和平移
this.ctx.translate(this.offsetX, this.offsetY)
this.ctx.scale(this.scale, this.scale)
// 绘制座位
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)
const y = i * (this.seatSize + this.gap)
// 更新座位坐标(用于点击检测)
seat.x = x
seat.y = y
// 设置座位颜色
switch (seat.status) {
case 'available':
this.ctx.fillStyle = '#4CAF50' // 绿色
break
case 'selected':
this.ctx.fillStyle = '#2196F3' // 蓝色
break
case 'sold':
this.ctx.fillStyle = '#9E9E9E' // 灰色
break
}
// 绘制座位
this.ctx.beginPath()
this.ctx.roundRect(x, y, this.seatSize, this.seatSize, 4)
this.ctx.fill()
// 绘制座位编号
this.ctx.fillStyle = '#FFFFFF'
this.ctx.font = '12px Arial'
this.ctx.textAlign = 'center'
this.ctx.textBaseline = 'middle'
this.ctx.fillText(
`${String.fromCharCode(65 + i)}${j + 1}`,
x + this.seatSize / 2,
y + this.seatSize / 2
)
}
}
// 恢复状态
this.ctx.restore()
},
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) * this.scale
const minY = this.canvasHeight - (this.rows * (this.seatSize + this.gap) - this.gap) * this.scale
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
// 转换为画布坐标(考虑缩放和偏移)
const canvasX = (touchX - this.offsetX) / this.scale
const canvasY = (touchY - this.offsetY) / this.scale
// 检查是否点击了座位
for (let i = 0; i < this.rows; i++) {
for (let j = 0; j < this.cols; j++) {
const seat = this.seats[i][j]
if (
canvasX >= seat.x &&
canvasX <= seat.x + this.seatSize &&
canvasY >= seat.y &&
canvasY <= 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: 500rpx;
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>