canvas/pages/index/step2.vue
2025-07-01 16:20:44 +08:00

428 lines
14 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">
<canvas
canvas-id="selectCanvas"
id="selectCanvas"
type="2d"
disable-scroll="true"
@touchstart="handleTouchStart"
:style="{
width: canvasWidth + 'px',
height: canvasHeight + 'px'
}"
></canvas>
<view class="selection-info" v-if="selectedArea">
当前选中{{selectedArea.areaname}} (剩余:{{selectedArea.remain}})
</view>
</view>
</template>
<script>
export default {
data() {
return {
canvasWidth: 300,
canvasHeight: 400,
ctx: null,
areas: [], // 从接口获取的区域数据
selectedArea: null,
colorMap: {
'A区': '#FF5252',
'B区': '#4CAF50',
'C区': '#2196F3',
'D区': '#FFC107'
}
}
},
async onLoad() {
// 模拟API请求
await this.loadAreaData();
},
onReady() {
setTimeout(() => {
this.initCanvas().catch(e => {
console.error('初始化失败:', e);
uni.showToast({
title: '画布初始化失败',
icon: 'none'
});
});
}, 300);
},
methods: {
// 模拟加载区域数据
async loadAreaData() {
try {
// 这里替换为您的实际API请求
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) {
console.error('数据加载失败:', 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) {
console.error('无效的多边形数据:', 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 {
console.warn('忽略无效坐标:', 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 {
// 获取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();
});
},
// 判断点是否在多边形内
isPointInPolygon(x, y, polygon) {
// 添加参数检查
if (!polygon || !Array.isArray(polygon) || polygon.length < 3) {
console.warn('无效的多边形数据', 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)) {
console.warn('无效的坐标点', {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
});
}
}
}
}
</script>
<style>
.container {
display: flex;
flex-direction: column;
height: 100vh;
}
canvas {
width: 100%;
height: 80%;
background-color: #f5f5f5;
border: 1px solid #ccc;
}
.selection-info {
padding: 15px;
background-color: #fff;
border-top: 1px solid #eee;
font-size: 14px;
color: #666;
}
</style>