canvas/pages/index/gesture-canvas-page.vue

295 lines
8.3 KiB
Vue

<template>
<view class="container" @click="handleCanvasClick">
<view class="gesture-container"
@touchstart="handleTouchEvent"
@touchmove="handleTouchEvent"
@touchend="handleTouchEvent"
@touchcancel="handleTouchEvent">
<!-- 显示调试信息 -->
<view class="debug-info">
<text>缩放: {{scaleValue.toFixed(2)}} | X: {{transformMatrix.tx.toFixed(1)}} | Y: {{transformMatrix.ty.toFixed(1)}}</text>
<text>手势: {{gestureStatus}}</text>
<text>触点: {{touchPoints}}</text>
</view>
<transform-canvas
ref="canvasRef"
:width="canvasWidth"
:height="canvasHeight"
:matrix="transformMatrix"
:imgUrl="'https://assets.tech.troyrc.com/sx25/images/events/XBDT.jpg'"
:areaData="seatAreas"
/>
</view>
<!-- <view class="controls">
<button @click="resetCanvas">重置</button>
<button @click="zoomIn">放大</button>
<button @click="zoomOut">缩小</button>
</view> -->
</view>
</template>
<script>
import TransformMatrix from './transform-matrix.js'
import GestureHandler from './gesture-handler.js'
import TransformCanvas from './transform-canvas.vue'
export default {
components: {
TransformCanvas
},
data() {
return {
canvasWidth: 800,
canvasHeight: 600,
transformMatrix: new TransformMatrix(),
gestureHandler: null,
containerRect: null,
gestureStatus: '等待手势...',
scaleValue: 1,
touchPoints: 0,
lastGestureTime: 0,
seatAreas: [] // 座位区域数据
};
},
async mounted() {
await this.getContainerPosition();
this.initGestureHandler();
// 加载座位区域数据
await this.loadSeatAreas();
// 初始绘制
this.$nextTick(() => {
this.updateCanvas();
});
},
methods: {
handleCanvasClick(e) {
if (!this.containerRect) return;
// 计算相对坐标
const x = e.detail.x - this.containerRect.left;
const y = e.detail.y - this.containerRect.top;
// 检测点击区域
const hitArea = this.$refs.canvasRef.checkHitArea(x, y);
console.log('hitArea',hitArea)
if (hitArea) {
console.log('点击区域:', hitArea);
}
},
async getContainerPosition() {
// 添加重试机制
let retryCount = 0;
const maxRetries = 3;
while (retryCount < maxRetries) {
try {
const rect = await new Promise(resolve => {
const query = uni.createSelectorQuery().in(this);
query.select('.gesture-container').boundingClientRect(res => {
resolve(res);
}).exec();
});
if (rect) {
this.containerRect = {
left: rect.left,
top: rect.top,
width: rect.width,
height: rect.height
};
return;
}
} catch (e) {
console.error('获取容器位置失败:', e);
}
// 等待后重试
await new Promise(r => setTimeout(r, 100));
retryCount++;
}
},
initGestureHandler() {
try {
this.gestureHandler = new GestureHandler(this, this.transformMatrix, {
container: this.containerRect
});
} catch (e) {
console.error('手势处理器初始化失败:', e);
// 简化降级方案
this.gestureHandler = {
catchEvent: (event) => {
if (event.touches.length > 0) {
this.gestureStatus = "降级模式";
this.touchPoints = event.touches.length;
if (event.type === 'touchmove') {
const firstTouch = event.touches[0];
const x = firstTouch.clientX - this.containerRect.left;
const y = firstTouch.clientY - this.containerRect.top;
this.transformMatrix.tx = x;
this.transformMatrix.ty = y;
this.updateCanvas();
}
}
}
}
}
},
// 事件处理
async handleTouchEvent(event) {
if (!this.gestureHandler || !this.containerRect) {
await this.getContainerPosition();
this.initGestureHandler();
if (event.type === 'touchend' || event.type === 'touchcancel') {
this.gestureStatus = '结束';
this.updateCanvas();
return;
}
}
// 记录时间戳
const currentTime = Date.now();
// 更新触点数量
this.touchPoints = event.touches.length;
// 修正坐标
const correctedTouches = Array.from(event.touches).map(touch => {
return {
...touch,
x: touch.clientX - this.containerRect.left,
y: touch.clientY - this.containerRect.top
};
});
// 创建修正后的事件对象
const correctedEvent = {
...event,
touches: correctedTouches,
changedTouches: correctedTouches
};
// 传递事件给手势处理器
this.gestureHandler.catchEvent(correctedEvent);
// 更新手势状态
if (event.type === 'touchstart') {
this.gestureStatus = event.touches.length > 1 ? '双指开始' : '单指开始';
}
else if (event.type === 'touchmove') {
this.gestureStatus = event.touches.length > 1 ? '双指缩放/移动' : '单指移动';
}
else {
this.gestureStatus = '结束';
}
// 限制更新频率
if (currentTime - this.lastGestureTime > 50) {
this.lastGestureTime = currentTime;
this.updateCanvas();
}
},
// 更新Canvas
updateCanvas() {
this.$nextTick(() => {
this.scaleValue = Math.sqrt(
this.transformMatrix.a * this.transformMatrix.a +
this.transformMatrix.b * this.transformMatrix.b
);
});
},
// 重置画布
resetCanvas() {
this.transformMatrix.reset();
this.scaleValue = 1;
this.gestureStatus = "画布已重置";
this.updateCanvas();
},
// 放大
zoomIn() {
this.transformMatrix.scale(1.2, 1.2, this.canvasWidth/2, this.canvasHeight/2);
this.updateCanvas();
},
// 缩小
zoomOut() {
this.transformMatrix.scale(0.8, 0.8, this.canvasWidth/2, this.canvasHeight/2);
this.updateCanvas();
},
// 加载座位区域数据
async loadSeatAreas() {
try {
// 模拟API请求
const response = {
"code":200,
"message":"",
"data":[{"areacode":"03tkrrjukrgu","areaname":"主席台","description":"主席台","remain":100,"polygon":[262,154,262,165,262,177,262,188,314,188,365,188,417,188,417,177,417,165,417,154,365,154,314,154,262,154]},{"areacode":"ea0jg3jukrgw","areaname":"A区","description":"A区","remain":1000,"polygon":[105,94,105,125,105,158,105,189,183,189,251,189,250,147,336,147,337,125,337,94,259,94,183,94,105,94]},{"areacode":"832fe6ej0kqc","areaname":"C区","description":"C区","remain":1000,"polygon":[106,418,106,452,106,487,106,521,183,521,261,521,338,521,338,487,338,452,338,418,261,418,183,418,106,418]},{"areacode":"p5naxqej0kqd","areaname":"B区","description":"B区","remain":1000,"polygon":[345,93,345,125,344,147,425,148,425,188,499,190,576,190,576,158,576,125,576,93,499,93,422,93,345,93]},{"areacode":"uknpk3sa819j","areaname":"D区","description":"D区","remain":1000,"polygon":[347,419,347,453,347,487,347,521,423,521,499,521,575,521,575,487,575,453,575,419,499,419,423,419,347,419]}]
}
this.seatAreas = response.data;
} catch (e) {
console.error('加载区域数据失败:', e);
}
}
}
};
</script>
<style scoped>
.container {
/* padding: 20px; */
}
.gesture-container {
position: relative;
width: 100%;
/* height: 70vh; */
margin: 0 auto;
}
.debug-info {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0,0,0,0.6);
color: white;
padding: 5px;
font-size: 12px;
z-index: 10;
display: flex;
flex-direction: column;
}
.controls {
margin-top: 20px;
display: flex;
justify-content: center;
gap: 10px;
}
button {
padding: 8px 16px;
background: #4a8cff;
color: white;
border: none;
border-radius: 4px;
}
</style>