fix
This commit is contained in:
parent
a2f3daa2ce
commit
63f31530fc
@ -1,223 +1,406 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="container">
|
<view class="container">
|
||||||
<view class="screen">银幕</view>
|
|
||||||
<canvas
|
<canvas
|
||||||
canvas-id="seatCanvas"
|
canvas-id="selectCanvas"
|
||||||
|
id="selectCanvas"
|
||||||
type="2d"
|
type="2d"
|
||||||
class="seat-canvas"
|
disable-scroll="true"
|
||||||
@touchstart="handleTouchStart"
|
@touchstart="handleTouchStart"
|
||||||
@touchmove="handleTouchMove"
|
:style="{
|
||||||
@touchend="handleTouchEnd"
|
width: canvasWidth + 'px',
|
||||||
|
height: canvasHeight + 'px'
|
||||||
|
}"
|
||||||
></canvas>
|
></canvas>
|
||||||
<view class="legend">
|
|
||||||
<view class="legend-item">
|
<view class="selection-info" v-if="selectedArea">
|
||||||
<view class="seat-icon available"></view>
|
当前选中:{{selectedArea.areaname}} (剩余:{{selectedArea.remain}})
|
||||||
<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>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ctx: null,
|
|
||||||
canvasWidth: 300,
|
canvasWidth: 300,
|
||||||
canvasHeight: 500,
|
canvasHeight: 400,
|
||||||
seatSize: 30,
|
ctx: null,
|
||||||
gap: 10,
|
areas: [], // 从接口获取的区域数据
|
||||||
rows: 8,
|
selectedArea: null,
|
||||||
cols: 10,
|
colorMap: {
|
||||||
seats: [],
|
'A区': '#FF5252',
|
||||||
selectedSeats: [],
|
'B区': '#4CAF50',
|
||||||
touchStartX: 0,
|
'C区': '#2196F3',
|
||||||
touchStartY: 0,
|
'D区': '#FFC107'
|
||||||
offsetX: 0,
|
}
|
||||||
offsetY: 0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async onLoad() {
|
||||||
|
// 模拟API请求
|
||||||
|
await this.loadAreaData();
|
||||||
|
},
|
||||||
|
|
||||||
onReady() {
|
onReady() {
|
||||||
this.initSeats();
|
setTimeout(() => {
|
||||||
this.initAndroidCanvas();
|
this.initCanvas().catch(e => {
|
||||||
|
console.error('初始化失败:', e);
|
||||||
|
uni.showToast({
|
||||||
|
title: '画布初始化失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
initSeats() {
|
// 模拟加载区域数据
|
||||||
// 初始化座位数据
|
async loadAreaData() {
|
||||||
for (let i = 0; i < this.rows; i++) {
|
try {
|
||||||
this.seats[i] = [];
|
// 这里替换为您的实际API请求
|
||||||
for (let j = 0; j < this.cols; j++) {
|
const data = [{
|
||||||
// 随机生成一些已售座位
|
"areacode": "lg0umpjukrdn",
|
||||||
const isSold = Math.random() < 0.2;
|
"areaname": "C4区",
|
||||||
this.seats[i][j] = {
|
"description": "C4区",
|
||||||
row: i,
|
"remain": 94,
|
||||||
col: j,
|
"polygon": [131, 184, 130, 196, 146, 201, 174, 208, 174, 215, 215, 215, 258, 215, 258, 205, 257, 192, 258, 184, 215, 184, 174, 184, 131, 184]
|
||||||
status: isSold ? 'sold' : 'available',
|
}, {
|
||||||
x: 0,
|
"areacode": "pnk4022gt71m",
|
||||||
y: 0
|
"areaname": "A4区-一层",
|
||||||
};
|
"description": "A4区-一层",
|
||||||
}
|
"remain": 0,
|
||||||
|
"polygon": [490, 464, 490, 475, 490, 488, 490, 499, 532, 499, 572, 499, 614, 499, 614, 488, 599, 480, 586, 474, 572, 464, 532, 464, 490, 464]
|
||||||
|
}, {
|
||||||
|
"areacode": "e0jhx1sa8194",
|
||||||
|
"areaname": "D1区-一层",
|
||||||
|
"description": "D1区-一层",
|
||||||
|
"remain": 247,
|
||||||
|
"polygon": [604, 345, 603, 385, 604, 396, 624, 439, 643, 437, 644, 417, 643, 396, 644, 367, 644, 344, 616, 345, 644, 346, 630, 344, 604, 345]
|
||||||
|
}, {
|
||||||
|
"areacode": "qxhs4574ffrh",
|
||||||
|
"areaname": "D1区-二层",
|
||||||
|
"description": "D1区-二层",
|
||||||
|
"remain": 191,
|
||||||
|
"polygon": [648, 349, 648, 386, 648, 425, 648, 452, 668, 452, 668, 462, 674, 462, 674, 425, 674, 386, 674, 349, 666, 349, 656, 349, 648, 349]
|
||||||
|
}, {
|
||||||
|
"areacode": "ar2q2e74ffrj",
|
||||||
|
"areaname": "D2区-二层",
|
||||||
|
"description": "D2区-二层",
|
||||||
|
"remain": 122,
|
||||||
|
"polygon": [647, 240, 648, 270, 648, 309, 648, 350, 657, 350, 665, 350, 673, 350, 673, 309, 673, 270, 673, 229, 667, 229, 667, 239, 647, 240]
|
||||||
|
}, {
|
||||||
|
"areacode": "qar2r374ffrl",
|
||||||
|
"areaname": "D2区-一层",
|
||||||
|
"description": "D2区-一层",
|
||||||
|
"remain": 153,
|
||||||
|
"polygon": [617, 266, 605, 285, 606, 303, 606, 342, 626, 341, 645, 342, 643, 341, 642, 302, 642, 278, 642, 263, 642, 249, 626, 248, 617, 266]
|
||||||
|
}, {
|
||||||
|
"areacode": "fpfaxmsa816f",
|
||||||
|
"areaname": "A2区-一层",
|
||||||
|
"description": "A2区-一层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [269, 463, 269, 476, 269, 488, 269, 501, 301, 501, 332, 501, 364, 501, 364, 488, 364, 476, 364, 463, 332, 463, 301, 463, 269, 463]
|
||||||
|
}, {
|
||||||
|
"areacode": "tse56pjukrdx",
|
||||||
|
"areaname": "C3区",
|
||||||
|
"description": "C3区",
|
||||||
|
"remain": 215,
|
||||||
|
"polygon": [279, 141, 279, 174, 266, 173, 265, 180, 308, 180, 339, 180, 369, 180, 369, 167, 369, 154, 369, 141, 339, 141, 308, 141, 279, 141]
|
||||||
|
}, {
|
||||||
|
"areacode": "lpkym7ej0kmd",
|
||||||
|
"areaname": "B1区-一层",
|
||||||
|
"description": "B1区-一层",
|
||||||
|
"remain": 248,
|
||||||
|
"polygon": [104, 250, 104, 282, 104, 313, 104, 345, 118, 345, 131, 345, 145, 345, 145, 313, 145, 288, 138, 276, 131, 263, 123, 250, 104, 250]
|
||||||
|
}, {
|
||||||
|
"areacode": "r6xrfc2gt71x",
|
||||||
|
"areaname": "C3区",
|
||||||
|
"description": "C3区",
|
||||||
|
"remain": 143,
|
||||||
|
"polygon": [269, 186, 269, 196, 269, 207, 269, 217, 300, 217, 333, 217, 364, 217, 364, 207, 364, 196, 364, 186, 333, 186, 300, 186, 269, 186]
|
||||||
|
}, {
|
||||||
|
"areacode": "h84jstej0kmf",
|
||||||
|
"areaname": "C1区",
|
||||||
|
"description": "C1区",
|
||||||
|
"remain": 337,
|
||||||
|
"polygon": [477, 133, 476, 168, 491, 168, 491, 179, 519, 178, 690, 179, 689, 170, 660, 170, 659, 140, 670, 140, 670, 134, 530, 134, 477, 133]
|
||||||
|
}, {
|
||||||
|
"areacode": "tqaegcej0kmg",
|
||||||
|
"areaname": "A2区-二层",
|
||||||
|
"description": "A2区-二层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [278, 514, 277, 526, 256, 525, 256, 542, 289, 542, 311, 542, 311, 531, 364, 533, 364, 525, 352, 525, 352, 513, 303, 513, 278, 514]
|
||||||
|
}, {
|
||||||
|
"areacode": "606fa9ej0kmh",
|
||||||
|
"areaname": "A3区-一层",
|
||||||
|
"description": "A3区-一层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [384, 466, 384, 477, 384, 489, 384, 500, 416, 500, 447, 500, 479, 500, 479, 489, 479, 477, 479, 466, 447, 466, 416, 466, 384, 466]
|
||||||
|
}, {
|
||||||
|
"areacode": "2x9j4bej0km8",
|
||||||
|
"areaname": "B1区-二层",
|
||||||
|
"description": "B1区-二层",
|
||||||
|
"remain": 192,
|
||||||
|
"polygon": [74, 231, 73, 270, 73, 304, 73, 340, 84, 340, 91, 340, 101, 340, 101, 304, 101, 283, 100, 239, 80, 238, 80, 230, 74, 231]
|
||||||
|
}, {
|
||||||
|
"areacode": "81mj022gt723",
|
||||||
|
"areaname": "A4区-二层",
|
||||||
|
"description": "A4区-二层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [513, 511, 513, 525, 493, 525, 493, 547, 556, 547, 624, 547, 657, 546, 657, 513, 687, 513, 688, 501, 489, 500, 490, 511, 513, 511]
|
||||||
|
}, {
|
||||||
|
"areacode": "f217dy2gt72f",
|
||||||
|
"areaname": "C2区",
|
||||||
|
"description": "C2区",
|
||||||
|
"remain": 136,
|
||||||
|
"polygon": [372, 144, 372, 156, 372, 168, 372, 180, 403, 180, 434, 180, 481, 180, 481, 175, 467, 175, 467, 144, 434, 144, 403, 144, 372, 144]
|
||||||
|
}, {
|
||||||
|
"areacode": "crxllrjukre8",
|
||||||
|
"areaname": "A3区-二层",
|
||||||
|
"description": "A3区-二层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [386, 515, 387, 528, 376, 527, 376, 535, 436, 533, 436, 545, 486, 545, 486, 534, 486, 526, 463, 526, 464, 515, 411, 515, 386, 515]
|
||||||
|
}, {
|
||||||
|
"areacode": "kb8wtgjukre9",
|
||||||
|
"areaname": "C2区",
|
||||||
|
"description": "C2区",
|
||||||
|
"remain": 144,
|
||||||
|
"polygon": [386, 186, 386, 197, 386, 206, 386, 217, 416, 217, 448, 217, 478, 217, 478, 206, 478, 197, 478, 186, 448, 186, 416, 186, 386, 186]
|
||||||
|
}, {
|
||||||
|
"areacode": "0wrt1djukrea",
|
||||||
|
"areaname": "C1区",
|
||||||
|
"description": "C1区",
|
||||||
|
"remain": 60,
|
||||||
|
"polygon": [496, 184, 496, 194, 496, 205, 496, 215, 571, 214, 572, 204, 587, 205, 598, 202, 614, 194, 614, 184, 574, 184, 536, 184, 496, 184]
|
||||||
|
}, {
|
||||||
|
"areacode": "hya95p74ffs3",
|
||||||
|
"areaname": "B2区-二层",
|
||||||
|
"description": "B2区-二层",
|
||||||
|
"remain": 191,
|
||||||
|
"polygon": [73, 337, 73, 378, 72, 415, 73, 457, 78, 457, 78, 449, 100, 449, 101, 415, 100, 394, 101, 337, 92, 337, 82, 337, 73, 337]
|
||||||
|
}, {
|
||||||
|
"areacode": "rrw413jukre2",
|
||||||
|
"areaname": "A1区-一层",
|
||||||
|
"description": "A1区-一层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [256, 465, 256, 476, 256, 489, 256, 499, 213, 499, 172, 499, 129, 499, 129, 485, 144, 479, 158, 473, 172, 465, 213, 465, 256, 465]
|
||||||
|
}, {
|
||||||
|
"areacode": "sagsr1sa816m",
|
||||||
|
"areaname": "C4区",
|
||||||
|
"description": "C4区",
|
||||||
|
"remain": 490,
|
||||||
|
"polygon": [88, 139, 89, 171, 61, 172, 60, 181, 224, 181, 259, 182, 259, 174, 273, 173, 272, 154, 273, 131, 77, 132, 77, 139, 88, 139]
|
||||||
|
}, {
|
||||||
|
"areacode": "eqk4nyjukre4",
|
||||||
|
"areaname": "B2区一层",
|
||||||
|
"description": "B2区一层",
|
||||||
|
"remain": 247,
|
||||||
|
"polygon": [104, 344, 104, 376, 104, 406, 104, 438, 123, 438, 128, 425, 134, 415, 143, 399, 144, 376, 144, 344, 130, 344, 118, 344, 104, 344]
|
||||||
|
}, {
|
||||||
|
"areacode": "rrtc9e2gt72d",
|
||||||
|
"areaname": "A1区-二层",
|
||||||
|
"description": "A1区-二层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [62, 501, 62, 509, 90, 510, 89, 549, 250, 548, 250, 527, 231, 528, 230, 510, 254, 509, 254, 501, 191, 501, 125, 501, 62, 501]
|
||||||
|
}]
|
||||||
|
this.areas = data.map(area => ({
|
||||||
|
...area,
|
||||||
|
color: this.getAreaColor(area.areaname),
|
||||||
|
// 转换多边形为绘制路径
|
||||||
|
path: this.parsePolygon(area.polygon)
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('数据加载失败:', error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 替换原来的 initAndroidCanvas 方法
|
|
||||||
initAndroidCanvas() {
|
// 根据区域名称获取颜色
|
||||||
return new Promise((resolve) => {
|
getAreaColor(name) {
|
||||||
// 安卓平台需要使用 uni.createCanvasContext
|
for (const [key, value] of Object.entries(this.colorMap)) {
|
||||||
this.ctx = uni.createCanvasContext('seatCanvas', this);
|
if (name.includes(key)) return value;
|
||||||
console.log(this.ctx,'ctx123123123')
|
}
|
||||||
// 获取Canvas显示尺寸
|
return '#9E9E9E'; // 默认颜色
|
||||||
const query = uni.createSelectorQuery().in(this);
|
},
|
||||||
query.select('.seat-canvas')
|
|
||||||
.boundingClientRect(rect => {
|
// 解析多边形数据
|
||||||
if (!rect) {
|
parsePolygon(points) {
|
||||||
console.error('获取Canvas尺寸失败');
|
const path = [];
|
||||||
return resolve(false);
|
// 检查输入数据
|
||||||
|
if (!points || !Array.isArray(points) || points.length % 2 !== 0) {
|
||||||
|
console.error('无效的多边形数据:', points);
|
||||||
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canvasWidth = rect.width;
|
for (let i = 0; i < points.length; i += 2) {
|
||||||
this.canvasHeight = rect.height;
|
// 确保坐标是数字
|
||||||
|
const x = Number(points[i]);
|
||||||
|
const y = Number(points[i+1]);
|
||||||
|
if (!isNaN(x) && !isNaN(y)) {
|
||||||
|
path.push({ x, y });
|
||||||
|
} else {
|
||||||
|
console.warn('忽略无效坐标:', points[i], points[i+1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 计算初始偏移使座位居中
|
// 确保多边形闭合
|
||||||
const totalWidth = this.cols * (this.seatSize + this.gap) - this.gap;
|
if (path.length > 0 &&
|
||||||
const totalHeight = this.rows * (this.seatSize + this.gap) - this.gap;
|
(path[0].x !== path[path.length-1].x ||
|
||||||
this.offsetX = (this.canvasWidth - totalWidth) / 2;
|
path[0].y !== path[path.length-1].y)) {
|
||||||
this.offsetY = (this.canvasHeight - totalHeight) / 2;
|
path.push({...path[0]});
|
||||||
|
}
|
||||||
|
|
||||||
this.drawSeats();
|
return path;
|
||||||
resolve(true);
|
},
|
||||||
|
|
||||||
|
async initCanvas() {
|
||||||
|
try {
|
||||||
|
// 获取Canvas节点
|
||||||
|
const canvasNode = await new Promise((resolve, reject) => {
|
||||||
|
const query = uni.createSelectorQuery().in(this);
|
||||||
|
query.select('#selectCanvas')
|
||||||
|
.fields({ node: true, size: true })
|
||||||
|
.exec(res => {
|
||||||
|
res[0]?.node ? resolve(res[0].node) : reject('Canvas节点获取失败');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取系统信息
|
||||||
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
|
|
||||||
|
// 设置Canvas的显示尺寸(CSS像素)
|
||||||
|
this.canvasWidth = systemInfo.windowWidth;
|
||||||
|
this.canvasHeight = systemInfo.windowHeight * 0.8;
|
||||||
|
|
||||||
|
// 设置Canvas的实际像素尺寸(考虑设备像素比)
|
||||||
|
const dpr = systemInfo.pixelRatio || 1;
|
||||||
|
canvasNode.width = this.canvasWidth * dpr;
|
||||||
|
canvasNode.height = this.canvasHeight * dpr;
|
||||||
|
|
||||||
|
// 初始化上下文
|
||||||
|
this.ctx = uni.createCanvasContext('selectCanvas', this);
|
||||||
|
|
||||||
|
// 缩放上下文以匹配设备像素比
|
||||||
|
this.ctx.scale(dpr, dpr);
|
||||||
|
|
||||||
|
// 绘制内容
|
||||||
|
this.drawAllAreas();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('初始化失败:', error);
|
||||||
|
this.useFallbackRender();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// 绘制所有区域
|
||||||
|
drawAllAreas() {
|
||||||
|
// 清空画布
|
||||||
|
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||||
|
|
||||||
|
// 绘制背景
|
||||||
|
this.ctx.setFillStyle('#f5f5f5');
|
||||||
|
this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||||
|
|
||||||
|
// 绘制所有区域
|
||||||
|
this.areas.forEach(area => {
|
||||||
|
this.drawArea(area);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ctx.draw();
|
||||||
|
},
|
||||||
|
|
||||||
|
// 绘制单个区域
|
||||||
|
drawArea(area) {
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(area.path[0].x, area.path[0].y);
|
||||||
|
|
||||||
|
for (let i = 1; i < area.path.length; i++) {
|
||||||
|
this.ctx.lineTo(area.path[i].x, area.path[i].y);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ctx.closePath();
|
||||||
|
this.ctx.setFillStyle(area.color);
|
||||||
|
this.ctx.fill();
|
||||||
|
|
||||||
|
// 如果是选中状态,添加边框
|
||||||
|
if (this.selectedArea === area) {
|
||||||
|
this.ctx.setStrokeStyle('#000000');
|
||||||
|
this.ctx.setLineWidth(2);
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 检查点击了哪个区域
|
||||||
|
async checkAreaSelection(x, y) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const query = uni.createSelectorQuery().in(this);
|
||||||
|
query.select('#selectCanvas')
|
||||||
|
.boundingClientRect(rect => {
|
||||||
|
if (!rect) return resolve(null);
|
||||||
|
|
||||||
|
// 转换坐标到Canvas坐标系
|
||||||
|
const canvasX = x - rect.left;
|
||||||
|
const canvasY = y - rect.top;
|
||||||
|
|
||||||
|
// 使用isPointInPath检测(简化版)
|
||||||
|
const clickedArea = this.areas.find(area =>
|
||||||
|
this.isPointInPolygon(canvasX, canvasY, area.path)
|
||||||
|
);
|
||||||
|
|
||||||
|
resolve(clickedArea);
|
||||||
})
|
})
|
||||||
.exec();
|
.exec();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
drawSeats() {
|
// 判断点是否在多边形内
|
||||||
if (!this.ctx) return;
|
isPointInPolygon(x, y, polygon) {
|
||||||
|
// 添加参数检查
|
||||||
// 清空画布(安卓平台方式)
|
if (!polygon || !Array.isArray(polygon) || polygon.length < 3) {
|
||||||
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
console.warn('无效的多边形数据', polygon);
|
||||||
|
return false;
|
||||||
// 绘制座位
|
|
||||||
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)
|
let inside = false;
|
||||||
this.ctx.fillRect(x, y, this.seatSize, this.seatSize);
|
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||||
|
const xi = polygon[i].x, yi = polygon[i].y;
|
||||||
|
const xj = polygon[j].x, yj = polygon[j].y;
|
||||||
|
|
||||||
// 绘制座位编号
|
// 添加坐标检查
|
||||||
this.ctx.setFillStyle('#FFFFFF');
|
if (isNaN(xi) || isNaN(yi) || isNaN(xj) || isNaN(yj)) {
|
||||||
this.ctx.setFontSize(12);
|
console.warn('无效的坐标点', {xi, yi, xj, yj});
|
||||||
this.ctx.setTextAlign('center');
|
return false;
|
||||||
this.ctx.fillText(
|
|
||||||
`${String.fromCharCode(65 + i)}${j + 1}`,
|
|
||||||
x + this.seatSize / 2,
|
|
||||||
y + this.seatSize / 2 + 4 // 安卓平台文字垂直对齐需要微调
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 实际绘制(安卓平台需要显式调用draw)
|
const intersect = ((yi > y) !== (yj > y))
|
||||||
this.ctx.draw();
|
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
|
||||||
},
|
if (intersect) inside = !inside;
|
||||||
// 触摸事件处理(安卓专用优化)
|
}
|
||||||
handleTouchStart(e) {
|
return inside;
|
||||||
this.touchStartX = e.touches[0].x;
|
|
||||||
this.touchStartY = e.touches[0].y;
|
|
||||||
},
|
},
|
||||||
|
// 处理触摸开始
|
||||||
|
async handleTouchStart(e) {
|
||||||
|
const touch = e.touches[0];
|
||||||
|
const clickedArea = await this.checkAreaSelection(touch.clientX, touch.clientY);
|
||||||
|
|
||||||
handleTouchMove(e) {
|
if (clickedArea) {
|
||||||
const touchX = e.touches[0].x;
|
this.selectedArea = clickedArea;
|
||||||
const touchY = e.touches[0].y;
|
this.drawAllAreas();
|
||||||
|
|
||||||
// 计算移动距离
|
uni.showToast({
|
||||||
const dx = touchX - this.touchStartX;
|
title: `选中: ${clickedArea.areaname}`,
|
||||||
const dy = touchY - this.touchStartY;
|
icon: 'none'
|
||||||
|
});
|
||||||
|
|
||||||
// 更新偏移量
|
// 返回选中区域数据
|
||||||
this.offsetX += dx;
|
this.$emit('area-selected', {
|
||||||
this.offsetY += dy;
|
areacode: clickedArea.areacode,
|
||||||
|
areaname: clickedArea.areaname,
|
||||||
// 限制边界
|
remain: clickedArea.remain
|
||||||
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>
|
</script>
|
||||||
|
|
||||||
@ -225,70 +408,21 @@ export default {
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
|
||||||
padding: 20rpx;
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.screen {
|
canvas {
|
||||||
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%;
|
width: 100%;
|
||||||
height: 500px; /* 安卓使用固定像素单位更可靠 */
|
height: 80%;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-info {
|
||||||
|
padding: 15px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin-bottom: 20rpx;
|
border-top: 1px solid #eee;
|
||||||
border-radius: 8rpx;
|
font-size: 14px;
|
||||||
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
|
color: #666;
|
||||||
}
|
|
||||||
|
|
||||||
.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>
|
</style>
|
468
unpackage/dist/dev/app-plus/app-service.js
vendored
468
unpackage/dist/dev/app-plus/app-service.js
vendored
@ -48,188 +48,356 @@ if (uni.restoreGlobal) {
|
|||||||
const _sfc_main$2 = {
|
const _sfc_main$2 = {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ctx: null,
|
|
||||||
canvasWidth: 300,
|
canvasWidth: 300,
|
||||||
canvasHeight: 500,
|
canvasHeight: 400,
|
||||||
seatSize: 30,
|
ctx: null,
|
||||||
gap: 10,
|
areas: [],
|
||||||
rows: 8,
|
// 从接口获取的区域数据
|
||||||
cols: 10,
|
selectedArea: null,
|
||||||
seats: [],
|
colorMap: {
|
||||||
selectedSeats: [],
|
"A区": "#FF5252",
|
||||||
touchStartX: 0,
|
"B区": "#4CAF50",
|
||||||
touchStartY: 0,
|
"C区": "#2196F3",
|
||||||
offsetX: 0,
|
"D区": "#FFC107"
|
||||||
offsetY: 0
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
async onLoad() {
|
||||||
|
await this.loadAreaData();
|
||||||
|
},
|
||||||
onReady() {
|
onReady() {
|
||||||
this.initSeats();
|
setTimeout(() => {
|
||||||
this.initAndroidCanvas();
|
this.initCanvas().catch((e) => {
|
||||||
|
formatAppLog("error", "at pages/index/step2.vue:47", "初始化失败:", e);
|
||||||
|
uni.showToast({
|
||||||
|
title: "画布初始化失败",
|
||||||
|
icon: "none"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
initSeats() {
|
// 模拟加载区域数据
|
||||||
for (let i = 0; i < this.rows; i++) {
|
async loadAreaData() {
|
||||||
this.seats[i] = [];
|
try {
|
||||||
for (let j = 0; j < this.cols; j++) {
|
const data = [{
|
||||||
const isSold = Math.random() < 0.2;
|
"areacode": "lg0umpjukrdn",
|
||||||
this.seats[i][j] = {
|
"areaname": "C4区",
|
||||||
row: i,
|
"description": "C4区",
|
||||||
col: j,
|
"remain": 94,
|
||||||
status: isSold ? "sold" : "available",
|
"polygon": [131, 184, 130, 196, 146, 201, 174, 208, 174, 215, 215, 215, 258, 215, 258, 205, 257, 192, 258, 184, 215, 184, 174, 184, 131, 184]
|
||||||
x: 0,
|
}, {
|
||||||
y: 0
|
"areacode": "pnk4022gt71m",
|
||||||
};
|
"areaname": "A4区-一层",
|
||||||
}
|
"description": "A4区-一层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [490, 464, 490, 475, 490, 488, 490, 499, 532, 499, 572, 499, 614, 499, 614, 488, 599, 480, 586, 474, 572, 464, 532, 464, 490, 464]
|
||||||
|
}, {
|
||||||
|
"areacode": "e0jhx1sa8194",
|
||||||
|
"areaname": "D1区-一层",
|
||||||
|
"description": "D1区-一层",
|
||||||
|
"remain": 247,
|
||||||
|
"polygon": [604, 345, 603, 385, 604, 396, 624, 439, 643, 437, 644, 417, 643, 396, 644, 367, 644, 344, 616, 345, 644, 346, 630, 344, 604, 345]
|
||||||
|
}, {
|
||||||
|
"areacode": "qxhs4574ffrh",
|
||||||
|
"areaname": "D1区-二层",
|
||||||
|
"description": "D1区-二层",
|
||||||
|
"remain": 191,
|
||||||
|
"polygon": [648, 349, 648, 386, 648, 425, 648, 452, 668, 452, 668, 462, 674, 462, 674, 425, 674, 386, 674, 349, 666, 349, 656, 349, 648, 349]
|
||||||
|
}, {
|
||||||
|
"areacode": "ar2q2e74ffrj",
|
||||||
|
"areaname": "D2区-二层",
|
||||||
|
"description": "D2区-二层",
|
||||||
|
"remain": 122,
|
||||||
|
"polygon": [647, 240, 648, 270, 648, 309, 648, 350, 657, 350, 665, 350, 673, 350, 673, 309, 673, 270, 673, 229, 667, 229, 667, 239, 647, 240]
|
||||||
|
}, {
|
||||||
|
"areacode": "qar2r374ffrl",
|
||||||
|
"areaname": "D2区-一层",
|
||||||
|
"description": "D2区-一层",
|
||||||
|
"remain": 153,
|
||||||
|
"polygon": [617, 266, 605, 285, 606, 303, 606, 342, 626, 341, 645, 342, 643, 341, 642, 302, 642, 278, 642, 263, 642, 249, 626, 248, 617, 266]
|
||||||
|
}, {
|
||||||
|
"areacode": "fpfaxmsa816f",
|
||||||
|
"areaname": "A2区-一层",
|
||||||
|
"description": "A2区-一层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [269, 463, 269, 476, 269, 488, 269, 501, 301, 501, 332, 501, 364, 501, 364, 488, 364, 476, 364, 463, 332, 463, 301, 463, 269, 463]
|
||||||
|
}, {
|
||||||
|
"areacode": "tse56pjukrdx",
|
||||||
|
"areaname": "C3区",
|
||||||
|
"description": "C3区",
|
||||||
|
"remain": 215,
|
||||||
|
"polygon": [279, 141, 279, 174, 266, 173, 265, 180, 308, 180, 339, 180, 369, 180, 369, 167, 369, 154, 369, 141, 339, 141, 308, 141, 279, 141]
|
||||||
|
}, {
|
||||||
|
"areacode": "lpkym7ej0kmd",
|
||||||
|
"areaname": "B1区-一层",
|
||||||
|
"description": "B1区-一层",
|
||||||
|
"remain": 248,
|
||||||
|
"polygon": [104, 250, 104, 282, 104, 313, 104, 345, 118, 345, 131, 345, 145, 345, 145, 313, 145, 288, 138, 276, 131, 263, 123, 250, 104, 250]
|
||||||
|
}, {
|
||||||
|
"areacode": "r6xrfc2gt71x",
|
||||||
|
"areaname": "C3区",
|
||||||
|
"description": "C3区",
|
||||||
|
"remain": 143,
|
||||||
|
"polygon": [269, 186, 269, 196, 269, 207, 269, 217, 300, 217, 333, 217, 364, 217, 364, 207, 364, 196, 364, 186, 333, 186, 300, 186, 269, 186]
|
||||||
|
}, {
|
||||||
|
"areacode": "h84jstej0kmf",
|
||||||
|
"areaname": "C1区",
|
||||||
|
"description": "C1区",
|
||||||
|
"remain": 337,
|
||||||
|
"polygon": [477, 133, 476, 168, 491, 168, 491, 179, 519, 178, 690, 179, 689, 170, 660, 170, 659, 140, 670, 140, 670, 134, 530, 134, 477, 133]
|
||||||
|
}, {
|
||||||
|
"areacode": "tqaegcej0kmg",
|
||||||
|
"areaname": "A2区-二层",
|
||||||
|
"description": "A2区-二层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [278, 514, 277, 526, 256, 525, 256, 542, 289, 542, 311, 542, 311, 531, 364, 533, 364, 525, 352, 525, 352, 513, 303, 513, 278, 514]
|
||||||
|
}, {
|
||||||
|
"areacode": "606fa9ej0kmh",
|
||||||
|
"areaname": "A3区-一层",
|
||||||
|
"description": "A3区-一层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [384, 466, 384, 477, 384, 489, 384, 500, 416, 500, 447, 500, 479, 500, 479, 489, 479, 477, 479, 466, 447, 466, 416, 466, 384, 466]
|
||||||
|
}, {
|
||||||
|
"areacode": "2x9j4bej0km8",
|
||||||
|
"areaname": "B1区-二层",
|
||||||
|
"description": "B1区-二层",
|
||||||
|
"remain": 192,
|
||||||
|
"polygon": [74, 231, 73, 270, 73, 304, 73, 340, 84, 340, 91, 340, 101, 340, 101, 304, 101, 283, 100, 239, 80, 238, 80, 230, 74, 231]
|
||||||
|
}, {
|
||||||
|
"areacode": "81mj022gt723",
|
||||||
|
"areaname": "A4区-二层",
|
||||||
|
"description": "A4区-二层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [513, 511, 513, 525, 493, 525, 493, 547, 556, 547, 624, 547, 657, 546, 657, 513, 687, 513, 688, 501, 489, 500, 490, 511, 513, 511]
|
||||||
|
}, {
|
||||||
|
"areacode": "f217dy2gt72f",
|
||||||
|
"areaname": "C2区",
|
||||||
|
"description": "C2区",
|
||||||
|
"remain": 136,
|
||||||
|
"polygon": [372, 144, 372, 156, 372, 168, 372, 180, 403, 180, 434, 180, 481, 180, 481, 175, 467, 175, 467, 144, 434, 144, 403, 144, 372, 144]
|
||||||
|
}, {
|
||||||
|
"areacode": "crxllrjukre8",
|
||||||
|
"areaname": "A3区-二层",
|
||||||
|
"description": "A3区-二层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [386, 515, 387, 528, 376, 527, 376, 535, 436, 533, 436, 545, 486, 545, 486, 534, 486, 526, 463, 526, 464, 515, 411, 515, 386, 515]
|
||||||
|
}, {
|
||||||
|
"areacode": "kb8wtgjukre9",
|
||||||
|
"areaname": "C2区",
|
||||||
|
"description": "C2区",
|
||||||
|
"remain": 144,
|
||||||
|
"polygon": [386, 186, 386, 197, 386, 206, 386, 217, 416, 217, 448, 217, 478, 217, 478, 206, 478, 197, 478, 186, 448, 186, 416, 186, 386, 186]
|
||||||
|
}, {
|
||||||
|
"areacode": "0wrt1djukrea",
|
||||||
|
"areaname": "C1区",
|
||||||
|
"description": "C1区",
|
||||||
|
"remain": 60,
|
||||||
|
"polygon": [496, 184, 496, 194, 496, 205, 496, 215, 571, 214, 572, 204, 587, 205, 598, 202, 614, 194, 614, 184, 574, 184, 536, 184, 496, 184]
|
||||||
|
}, {
|
||||||
|
"areacode": "hya95p74ffs3",
|
||||||
|
"areaname": "B2区-二层",
|
||||||
|
"description": "B2区-二层",
|
||||||
|
"remain": 191,
|
||||||
|
"polygon": [73, 337, 73, 378, 72, 415, 73, 457, 78, 457, 78, 449, 100, 449, 101, 415, 100, 394, 101, 337, 92, 337, 82, 337, 73, 337]
|
||||||
|
}, {
|
||||||
|
"areacode": "rrw413jukre2",
|
||||||
|
"areaname": "A1区-一层",
|
||||||
|
"description": "A1区-一层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [256, 465, 256, 476, 256, 489, 256, 499, 213, 499, 172, 499, 129, 499, 129, 485, 144, 479, 158, 473, 172, 465, 213, 465, 256, 465]
|
||||||
|
}, {
|
||||||
|
"areacode": "sagsr1sa816m",
|
||||||
|
"areaname": "C4区",
|
||||||
|
"description": "C4区",
|
||||||
|
"remain": 490,
|
||||||
|
"polygon": [88, 139, 89, 171, 61, 172, 60, 181, 224, 181, 259, 182, 259, 174, 273, 173, 272, 154, 273, 131, 77, 132, 77, 139, 88, 139]
|
||||||
|
}, {
|
||||||
|
"areacode": "eqk4nyjukre4",
|
||||||
|
"areaname": "B2区一层",
|
||||||
|
"description": "B2区一层",
|
||||||
|
"remain": 247,
|
||||||
|
"polygon": [104, 344, 104, 376, 104, 406, 104, 438, 123, 438, 128, 425, 134, 415, 143, 399, 144, 376, 144, 344, 130, 344, 118, 344, 104, 344]
|
||||||
|
}, {
|
||||||
|
"areacode": "rrtc9e2gt72d",
|
||||||
|
"areaname": "A1区-二层",
|
||||||
|
"description": "A1区-二层",
|
||||||
|
"remain": 0,
|
||||||
|
"polygon": [62, 501, 62, 509, 90, 510, 89, 549, 250, 548, 250, 527, 231, 528, 230, 510, 254, 509, 254, 501, 191, 501, 125, 501, 62, 501]
|
||||||
|
}];
|
||||||
|
this.areas = data.map((area) => ({
|
||||||
|
...area,
|
||||||
|
color: this.getAreaColor(area.areaname),
|
||||||
|
// 转换多边形为绘制路径
|
||||||
|
path: this.parsePolygon(area.polygon)
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
formatAppLog("error", "at pages/index/step2.vue:213", "数据加载失败:", error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 替换原来的 initAndroidCanvas 方法
|
// 根据区域名称获取颜色
|
||||||
initAndroidCanvas() {
|
getAreaColor(name) {
|
||||||
return new Promise((resolve) => {
|
for (const [key, value] of Object.entries(this.colorMap)) {
|
||||||
this.ctx = uni.createCanvasContext("seatCanvas", this);
|
if (name.includes(key))
|
||||||
formatAppLog("log", "at pages/index/step2.vue:77", this.ctx, "ctx123123123");
|
return value;
|
||||||
const query = uni.createSelectorQuery().in(this);
|
|
||||||
query.select(".seat-canvas").boundingClientRect((rect) => {
|
|
||||||
if (!rect) {
|
|
||||||
formatAppLog("error", "at pages/index/step2.vue:83", "获取Canvas尺寸失败");
|
|
||||||
return resolve(false);
|
|
||||||
}
|
}
|
||||||
this.canvasWidth = rect.width;
|
return "#9E9E9E";
|
||||||
this.canvasHeight = rect.height;
|
},
|
||||||
const totalWidth = this.cols * (this.seatSize + this.gap) - this.gap;
|
// 解析多边形数据
|
||||||
const totalHeight = this.rows * (this.seatSize + this.gap) - this.gap;
|
parsePolygon(points) {
|
||||||
this.offsetX = (this.canvasWidth - totalWidth) / 2;
|
const path = [];
|
||||||
this.offsetY = (this.canvasHeight - totalHeight) / 2;
|
if (!points || !Array.isArray(points) || points.length % 2 !== 0) {
|
||||||
this.drawSeats();
|
formatAppLog("error", "at pages/index/step2.vue:230", "无效的多边形数据:", points);
|
||||||
resolve(true);
|
return path;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < points.length; i += 2) {
|
||||||
|
const x = Number(points[i]);
|
||||||
|
const y = Number(points[i + 1]);
|
||||||
|
if (!isNaN(x) && !isNaN(y)) {
|
||||||
|
path.push({ x, y });
|
||||||
|
} else {
|
||||||
|
formatAppLog("warn", "at pages/index/step2.vue:241", "忽略无效坐标:", points[i], points[i + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (path.length > 0 && (path[0].x !== path[path.length - 1].x || path[0].y !== path[path.length - 1].y)) {
|
||||||
|
path.push({ ...path[0] });
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
},
|
||||||
|
async initCanvas() {
|
||||||
|
try {
|
||||||
|
const canvasNode = await new Promise((resolve, reject) => {
|
||||||
|
const query = uni.createSelectorQuery().in(this);
|
||||||
|
query.select("#selectCanvas").fields({ node: true, size: true }).exec((res) => {
|
||||||
|
var _a;
|
||||||
|
((_a = res[0]) == null ? void 0 : _a.node) ? resolve(res[0].node) : reject("Canvas节点获取失败");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
|
this.canvasWidth = systemInfo.windowWidth;
|
||||||
|
this.canvasHeight = systemInfo.windowHeight * 0.8;
|
||||||
|
const dpr = systemInfo.pixelRatio || 1;
|
||||||
|
canvasNode.width = this.canvasWidth * dpr;
|
||||||
|
canvasNode.height = this.canvasHeight * dpr;
|
||||||
|
this.ctx = uni.createCanvasContext("selectCanvas", this);
|
||||||
|
this.ctx.scale(dpr, dpr);
|
||||||
|
this.drawAllAreas();
|
||||||
|
} catch (error) {
|
||||||
|
formatAppLog("error", "at pages/index/step2.vue:289", "初始化失败:", error);
|
||||||
|
this.useFallbackRender();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 绘制所有区域
|
||||||
|
drawAllAreas() {
|
||||||
|
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||||
|
this.ctx.setFillStyle("#f5f5f5");
|
||||||
|
this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||||
|
this.areas.forEach((area) => {
|
||||||
|
this.drawArea(area);
|
||||||
|
});
|
||||||
|
this.ctx.draw();
|
||||||
|
},
|
||||||
|
// 绘制单个区域
|
||||||
|
drawArea(area) {
|
||||||
|
this.ctx.beginPath();
|
||||||
|
this.ctx.moveTo(area.path[0].x, area.path[0].y);
|
||||||
|
for (let i = 1; i < area.path.length; i++) {
|
||||||
|
this.ctx.lineTo(area.path[i].x, area.path[i].y);
|
||||||
|
}
|
||||||
|
this.ctx.closePath();
|
||||||
|
this.ctx.setFillStyle(area.color);
|
||||||
|
this.ctx.fill();
|
||||||
|
if (this.selectedArea === area) {
|
||||||
|
this.ctx.setStrokeStyle("#000000");
|
||||||
|
this.ctx.setLineWidth(3);
|
||||||
|
this.ctx.stroke();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 检查点击了哪个区域
|
||||||
|
async checkAreaSelection(x, y) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const query = uni.createSelectorQuery().in(this);
|
||||||
|
query.select("#selectCanvas").boundingClientRect((rect) => {
|
||||||
|
if (!rect)
|
||||||
|
return resolve(null);
|
||||||
|
const canvasX = x - rect.left;
|
||||||
|
const canvasY = y - rect.top;
|
||||||
|
const clickedArea = this.areas.find(
|
||||||
|
(area) => this.isPointInPolygon(canvasX, canvasY, area.path)
|
||||||
|
);
|
||||||
|
resolve(clickedArea);
|
||||||
}).exec();
|
}).exec();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
drawSeats() {
|
// 判断点是否在多边形内
|
||||||
if (!this.ctx)
|
isPointInPolygon(x, y, polygon) {
|
||||||
return;
|
if (!polygon || !Array.isArray(polygon) || polygon.length < 3) {
|
||||||
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
formatAppLog("warn", "at pages/index/step2.vue:360", "无效的多边形数据", polygon);
|
||||||
for (let i = 0; i < this.rows; i++) {
|
return false;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
this.ctx.fillRect(x, y, this.seatSize, this.seatSize);
|
let inside = false;
|
||||||
this.ctx.setFillStyle("#FFFFFF");
|
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||||
this.ctx.setFontSize(12);
|
const xi = polygon[i].x, yi = polygon[i].y;
|
||||||
this.ctx.setTextAlign("center");
|
const xj = polygon[j].x, yj = polygon[j].y;
|
||||||
this.ctx.fillText(
|
if (isNaN(xi) || isNaN(yi) || isNaN(xj) || isNaN(yj)) {
|
||||||
`${String.fromCharCode(65 + i)}${j + 1}`,
|
formatAppLog("warn", "at pages/index/step2.vue:371", "无效的坐标点", { xi, yi, xj, yj });
|
||||||
x + this.seatSize / 2,
|
return false;
|
||||||
y + this.seatSize / 2 + 4
|
|
||||||
// 安卓平台文字垂直对齐需要微调
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
const intersect = yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi;
|
||||||
|
if (intersect)
|
||||||
|
inside = !inside;
|
||||||
}
|
}
|
||||||
this.ctx.draw();
|
return inside;
|
||||||
},
|
},
|
||||||
// 触摸事件处理(安卓专用优化)
|
// 处理触摸开始
|
||||||
handleTouchStart(e) {
|
async handleTouchStart(e) {
|
||||||
this.touchStartX = e.touches[0].x;
|
const touch = e.touches[0];
|
||||||
this.touchStartY = e.touches[0].y;
|
const clickedArea = await this.checkAreaSelection(touch.clientX, touch.clientY);
|
||||||
},
|
if (clickedArea) {
|
||||||
handleTouchMove(e) {
|
this.selectedArea = clickedArea;
|
||||||
const touchX = e.touches[0].x;
|
this.drawAllAreas();
|
||||||
const touchY = e.touches[0].y;
|
uni.showToast({
|
||||||
const dx = touchX - this.touchStartX;
|
title: `选中: ${clickedArea.areaname}`,
|
||||||
const dy = touchY - this.touchStartY;
|
icon: "none"
|
||||||
this.offsetX += dx;
|
});
|
||||||
this.offsetY += dy;
|
this.$emit("area-selected", {
|
||||||
const minX = this.canvasWidth - (this.cols * (this.seatSize + this.gap) - this.gap);
|
areacode: clickedArea.areacode,
|
||||||
const minY = this.canvasHeight - (this.rows * (this.seatSize + this.gap) - this.gap);
|
areaname: clickedArea.areaname,
|
||||||
this.offsetX = Math.max(minX, Math.min(0, this.offsetX));
|
remain: clickedArea.remain
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) {
|
function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) {
|
||||||
return vue.openBlock(), vue.createElementBlock("view", { class: "container" }, [
|
return vue.openBlock(), vue.createElementBlock("view", { class: "container" }, [
|
||||||
vue.createElementVNode("view", { class: "screen" }, "银幕"),
|
|
||||||
vue.createElementVNode(
|
vue.createElementVNode(
|
||||||
"canvas",
|
"canvas",
|
||||||
{
|
{
|
||||||
"canvas-id": "seatCanvas",
|
"canvas-id": "selectCanvas",
|
||||||
|
id: "selectCanvas",
|
||||||
type: "2d",
|
type: "2d",
|
||||||
class: "seat-canvas",
|
"disable-scroll": "true",
|
||||||
onTouchstart: _cache[0] || (_cache[0] = (...args) => $options.handleTouchStart && $options.handleTouchStart(...args)),
|
onTouchstart: _cache[0] || (_cache[0] = (...args) => $options.handleTouchStart && $options.handleTouchStart(...args)),
|
||||||
onTouchmove: _cache[1] || (_cache[1] = (...args) => $options.handleTouchMove && $options.handleTouchMove(...args)),
|
style: vue.normalizeStyle({
|
||||||
onTouchend: _cache[2] || (_cache[2] = (...args) => $options.handleTouchEnd && $options.handleTouchEnd(...args))
|
width: $data.canvasWidth + "px",
|
||||||
|
height: $data.canvasHeight + "px"
|
||||||
|
})
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
32
|
36
|
||||||
/* NEED_HYDRATION */
|
/* STYLE, NEED_HYDRATION */
|
||||||
),
|
),
|
||||||
vue.createElementVNode("view", { class: "legend" }, [
|
$data.selectedArea ? (vue.openBlock(), vue.createElementBlock(
|
||||||
vue.createElementVNode("view", { class: "legend-item" }, [
|
"view",
|
||||||
vue.createElementVNode("view", { class: "seat-icon available" }),
|
{
|
||||||
vue.createElementVNode("text", null, "可选")
|
key: 0,
|
||||||
]),
|
class: "selection-info"
|
||||||
vue.createElementVNode("view", { class: "legend-item" }, [
|
},
|
||||||
vue.createElementVNode("view", { class: "seat-icon selected" }),
|
" 当前选中:" + vue.toDisplayString($data.selectedArea.areaname) + " (剩余:" + vue.toDisplayString($data.selectedArea.remain) + ") ",
|
||||||
vue.createElementVNode("text", null, "已选")
|
|
||||||
]),
|
|
||||||
vue.createElementVNode("view", { class: "legend-item" }, [
|
|
||||||
vue.createElementVNode("view", { class: "seat-icon sold" }),
|
|
||||||
vue.createElementVNode("text", null, "已售")
|
|
||||||
])
|
|
||||||
]),
|
|
||||||
vue.createElementVNode("view", { class: "selected-seats" }, [
|
|
||||||
vue.createElementVNode(
|
|
||||||
"text",
|
|
||||||
null,
|
|
||||||
"已选座位:" + vue.toDisplayString($data.selectedSeats.join(", ") || "无"),
|
|
||||||
1
|
1
|
||||||
/* TEXT */
|
/* TEXT */
|
||||||
)
|
)) : vue.createCommentVNode("v-if", true)
|
||||||
])
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
const PagesIndexStep2 = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["render", _sfc_render$1], ["__file", "/Users/sunmeng/Desktop/wx/canvas/pages/index/step2.vue"]]);
|
const PagesIndexStep2 = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["render", _sfc_render$1], ["__file", "/Users/sunmeng/Desktop/wx/canvas/pages/index/step2.vue"]]);
|
||||||
|
@ -2,60 +2,18 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
|
||||||
padding: 0.625rem;
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
}
|
||||||
.screen {
|
uni-canvas {
|
||||||
width: 80%;
|
|
||||||
height: 1.25rem;
|
|
||||||
background: linear-gradient(to bottom, #ccc, #fff);
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: #333;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.seat-canvas {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 500px; /* 安卓使用固定像素单位更可靠 */
|
height: 80%;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
.selection-info {
|
||||||
|
padding: 15px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
margin-bottom: 0.625rem;
|
border-top: 1px solid #eee;
|
||||||
border-radius: 0.25rem;
|
font-size: 14px;
|
||||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.1);
|
color: #666;
|
||||||
}
|
|
||||||
.legend {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 0.625rem;
|
|
||||||
}
|
|
||||||
.legend-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin: 0 0.625rem;
|
|
||||||
}
|
|
||||||
.seat-icon {
|
|
||||||
width: 0.9375rem;
|
|
||||||
height: 0.9375rem;
|
|
||||||
margin-right: 0.3125rem;
|
|
||||||
border-radius: 0.125rem;
|
|
||||||
}
|
|
||||||
.available {
|
|
||||||
background-color: #4CAF50;
|
|
||||||
}
|
|
||||||
.selected {
|
|
||||||
background-color: #2196F3;
|
|
||||||
}
|
|
||||||
.sold {
|
|
||||||
background-color: #9E9E9E;
|
|
||||||
}
|
|
||||||
.selected-seats {
|
|
||||||
padding: 0.625rem;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
width: 90%;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user