310 lines
8.0 KiB
Vue
310 lines
8.0 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: 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> |