if (typeof Promise !== "undefined" && !Promise.prototype.finally) { Promise.prototype.finally = function(callback) { const promise = this.constructor; return this.then( (value) => promise.resolve(callback()).then(() => value), (reason) => promise.resolve(callback()).then(() => { throw reason; }) ); }; } ; if (typeof uni !== "undefined" && uni && uni.requireGlobal) { const global = uni.requireGlobal(); ArrayBuffer = global.ArrayBuffer; Int8Array = global.Int8Array; Uint8Array = global.Uint8Array; Uint8ClampedArray = global.Uint8ClampedArray; Int16Array = global.Int16Array; Uint16Array = global.Uint16Array; Int32Array = global.Int32Array; Uint32Array = global.Uint32Array; Float32Array = global.Float32Array; Float64Array = global.Float64Array; BigInt64Array = global.BigInt64Array; BigUint64Array = global.BigUint64Array; } ; if (uni.restoreGlobal) { uni.restoreGlobal(Vue, weex, plus, setTimeout, clearTimeout, setInterval, clearInterval); } (function(vue) { "use strict"; function formatAppLog(type, filename, ...args) { if (uni.__log__) { uni.__log__(type, filename, ...args); } else { console[type].apply(console, [...args, filename]); } } const _export_sfc = (sfc, props) => { const target = sfc.__vccOpts || sfc; for (const [key, val] of props) { target[key] = val; } return target; }; const _sfc_main$2 = { data() { return { canvasWidth: 300, canvasHeight: 400, ctx: null, areas: [], // 从接口获取的区域数据 selectedArea: null, colorMap: { "A区": "#FF5252", "B区": "#4CAF50", "C区": "#2196F3", "D区": "#FFC107" } }; }, async onLoad() { await this.loadAreaData(); }, onReady() { setTimeout(() => { this.initCanvas().catch((e) => { formatAppLog("error", "at pages/index/step2.vue:47", "初始化失败:", e); uni.showToast({ title: "画布初始化失败", icon: "none" }); }); }, 300); }, methods: { // 模拟加载区域数据 async loadAreaData() { try { const data = [{ "areacode": "lg0umpjukrdn", "areaname": "C4区", "description": "C4区", "remain": 94, "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] }, { "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); } }, // 根据区域名称获取颜色 getAreaColor(name) { for (const [key, value] of Object.entries(this.colorMap)) { if (name.includes(key)) return value; } return "#9E9E9E"; }, // 解析多边形数据 parsePolygon(points) { const path = []; if (!points || !Array.isArray(points) || points.length % 2 !== 0) { formatAppLog("error", "at pages/index/step2.vue:230", "无效的多边形数据:", points); 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(); }); }, // 判断点是否在多边形内 isPointInPolygon(x, y, polygon) { if (!polygon || !Array.isArray(polygon) || polygon.length < 3) { formatAppLog("warn", "at pages/index/step2.vue:360", "无效的多边形数据", polygon); return false; } let inside = false; 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; if (isNaN(xi) || isNaN(yi) || isNaN(xj) || isNaN(yj)) { formatAppLog("warn", "at pages/index/step2.vue:371", "无效的坐标点", { xi, yi, xj, yj }); return false; } const intersect = yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi; if (intersect) inside = !inside; } return inside; }, // 处理触摸开始 async handleTouchStart(e) { const touch = e.touches[0]; const clickedArea = await this.checkAreaSelection(touch.clientX, touch.clientY); if (clickedArea) { this.selectedArea = clickedArea; this.drawAllAreas(); uni.showToast({ title: `选中: ${clickedArea.areaname}`, icon: "none" }); this.$emit("area-selected", { areacode: clickedArea.areacode, areaname: clickedArea.areaname, remain: clickedArea.remain }); } } } }; function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) { return vue.openBlock(), vue.createElementBlock("view", { class: "container" }, [ vue.createElementVNode( "canvas", { "canvas-id": "selectCanvas", id: "selectCanvas", type: "2d", "disable-scroll": "true", onTouchstart: _cache[0] || (_cache[0] = (...args) => $options.handleTouchStart && $options.handleTouchStart(...args)), style: vue.normalizeStyle({ width: $data.canvasWidth + "px", height: $data.canvasHeight + "px" }) }, null, 36 /* STYLE, NEED_HYDRATION */ ), $data.selectedArea ? (vue.openBlock(), vue.createElementBlock( "view", { key: 0, class: "selection-info" }, " 当前选中:" + vue.toDisplayString($data.selectedArea.areaname) + " (剩余:" + vue.toDisplayString($data.selectedArea.remain) + ") ", 1 /* 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 _sfc_main$1 = { 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() { 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) => { formatAppLog("log", "at pages/index/index.vue:94", "给我看看", res); formatAppLog("log", "at pages/index/index.vue:95", "给我看看", res); formatAppLog("log", "at pages/index/index.vue:96", "给我看看", res); formatAppLog("log", "at pages/index/index.vue:97", "给我看看", res); formatAppLog("log", "at pages/index/index.vue:98", "给我看看", res); formatAppLog("log", "at pages/index/index.vue:99", "给我看看", res); const canvas = res[0].node; const width = res[0].width; const height = res[0].height; 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(); } } }; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return vue.openBlock(), vue.createElementBlock("view", { class: "container" }, [ vue.createElementVNode("view", { class: "screen" }, "银幕"), vue.createElementVNode( "canvas", { "canvas-id": "seatCanvas", type: "2d", class: "seat-canvas", onTouchstart: _cache[0] || (_cache[0] = (...args) => $options.handleTouchStart && $options.handleTouchStart(...args)), onTouchmove: _cache[1] || (_cache[1] = (...args) => $options.handleTouchMove && $options.handleTouchMove(...args)), onTouchend: _cache[2] || (_cache[2] = (...args) => $options.handleTouchEnd && $options.handleTouchEnd(...args)) }, null, 32 /* NEED_HYDRATION */ ), vue.createElementVNode("view", { class: "legend" }, [ vue.createElementVNode("view", { class: "legend-item" }, [ vue.createElementVNode("view", { class: "seat-icon available" }), vue.createElementVNode("text", null, "可选") ]), vue.createElementVNode("view", { class: "legend-item" }, [ vue.createElementVNode("view", { class: "seat-icon selected" }), 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 /* TEXT */ ) ]) ]); } const PagesIndexIndex = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["render", _sfc_render], ["__file", "/Users/sunmeng/Desktop/wx/canvas/pages/index/index.vue"]]); __definePage("pages/index/step2", PagesIndexStep2); __definePage("pages/index/index", PagesIndexIndex); const _sfc_main = { onLaunch: function() { formatAppLog("log", "at App.vue:4", "App Launch"); }, onShow: function() { formatAppLog("log", "at App.vue:7", "App Show"); }, onHide: function() { formatAppLog("log", "at App.vue:10", "App Hide"); } }; const App = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "/Users/sunmeng/Desktop/wx/canvas/App.vue"]]); function createApp() { const app = vue.createVueApp(App); return { app }; } const { app: __app__, Vuex: __Vuex__, Pinia: __Pinia__ } = createApp(); uni.Vuex = __Vuex__; uni.Pinia = __Pinia__; __app__.provide("__globalStyles", __uniConfig.styles); __app__._component.mpType = "app"; __app__._component.render = () => { }; __app__.mount("#app"); })(Vue);