topfans/frontend/pages/square/debug-grid.vue
2026-04-13 16:16:41 +08:00

697 lines
16 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="debug-container"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
>
<!-- 背景条3张图并排无限滚动 -->
<view class="background-strip" :style="backgroundStripStyle">
<image
v-for="i in 3"
:key="i"
class="background-tile"
:style="{ width: tileWidth + 'px', height: '100%' }"
src="/static/background/mainbg.png"
/>
</view>
<!-- 网格点层 -->
<view class="grid-layer" :style="gridLayerStyle">
<!-- 渲染 5 tile 的网格点n=0,1,2,3,4 -->
<view
v-for="point in visiblePoints"
:key="point.key"
class="grid-point"
:class="{
'grid-point--excluded': point.isExcluded,
'grid-point--selected': selectedIndex === point.originalIndex
}"
:style="{
left: point.x + 'px',
top: point.y + 'px',
transform: 'translate(-50%, -50%)'
}"
@click="selectPoint(point.originalIndex)"
>
<view class="grid-dot"></view>
<text class="grid-label">{{ point.originalIndex }}</text>
<text class="grid-coords">({{ Math.round(point.originalX) }}, {{ Math.round(point.originalY) }})</text>
</view>
</view>
<!-- 控制面板 -->
<view class="control-panel" @click.stop>
<view class="panel-header">
<text class="panel-title">网格调试工具</text>
<button class="btn-close" @click="goBack">返回</button>
</view>
<!-- 单点调试模式 -->
<view v-if="selectedIndex !== null" class="point-edit-section">
<view class="point-edit-header">
<text class="point-edit-title">编辑点位 #{{ selectedIndex }}</text>
<button class="btn-deselect" @click="deselectPoint">取消选择</button>
</view>
<view class="point-edit-controls">
<view class="point-edit-row">
<text class="point-edit-label">X: {{ currentPoint.x }}</text>
<view class="point-edit-buttons">
<button class="btn-adjust" @click="adjustPoint('x', -10)">-10</button>
<button class="btn-adjust" @click="adjustPoint('x', -1)">-1</button>
<button class="btn-adjust" @click="adjustPoint('x', 1)">+1</button>
<button class="btn-adjust" @click="adjustPoint('x', 10)">+10</button>
</view>
</view>
<view class="point-edit-row">
<text class="point-edit-label">Y: {{ currentPoint.y }}</text>
<view class="point-edit-buttons">
<button class="btn-adjust" @click="adjustPoint('y', -10)">-10</button>
<button class="btn-adjust" @click="adjustPoint('y', -1)">-1</button>
<button class="btn-adjust" @click="adjustPoint('y', 1)">+1</button>
<button class="btn-adjust" @click="adjustPoint('y', 10)">+10</button>
</view>
</view>
<view class="point-edit-row">
<button class="btn-save-point" @click="savePointAdjustment">保存此点位调整</button>
<button class="btn-reset-point" @click="resetPoint">重置此点位</button>
</view>
</view>
</view>
<!-- 全局参数调整 -->
<view v-else class="global-controls">
<view class="control-section">
<text class="control-label">起始Y (startY): {{ config.grid.startY }}</text>
<slider
:value="config.grid.startY"
@change="updateStartY"
min="-200"
max="100"
step="10"
show-value
/>
</view>
<view class="control-section">
<text class="control-label">垂直间距 (spacingY): {{ config.grid.spacingY }}</text>
<slider
:value="config.grid.spacingY"
@change="updateSpacingY"
min="150"
max="250"
step="5"
show-value
/>
</view>
<view class="control-section">
<text class="control-label">水平间距 (spacingX): {{ config.grid.spacingX }}</text>
<slider
:value="config.grid.spacingX"
@change="updateSpacingX"
min="450"
max="600"
step="5"
show-value
/>
</view>
<view class="control-section">
<text class="control-label">交错偏移 (staggerOffsetX): {{ config.grid.staggerOffsetX }}</text>
<slider
:value="config.grid.staggerOffsetX"
@change="updateStaggerOffsetX"
min="200"
max="350"
step="5"
show-value
/>
</view>
</view>
<view class="control-section">
<button class="btn-export" @click="exportConfig">导出配置</button>
<button class="btn-reset" @click="resetConfig">重置全部</button>
</view>
<view class="coords-list">
<text class="coords-title">生成的坐标列表(点击点位编辑):</text>
<scroll-view class="coords-scroll" scroll-y>
<view
v-for="(coord, i) in originalCoords"
:key="i"
class="coord-item"
:class="{ 'coord-item--selected': selectedIndex === i }"
@click="selectPoint(i)"
>
<text class="coord-index">{{ i }}:</text>
<text class="coord-value">[{{ coord.x }}, {{ coord.y }}]</text>
<text v-if="coord.x === 0 && coord.y === 0" class="coord-excluded">(排除)</text>
<text v-if="hasAdjustment(i)" class="coord-adjusted">(已调整)</text>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import { GRID_CONFIG, generateGridCoordinates, IMAGE_W, IMAGE_H } from './config/cabin.js'
const config = ref(JSON.parse(JSON.stringify(GRID_CONFIG)))
const selectedIndex = ref(null)
// 屏幕和背景尺寸
const screenWidth = ref(375)
const screenHeight = ref(812)
const tileWidth = ref(375)
const scale = ref(1)
// 滑动相关
const bgOffsetX = ref(0)
let touchStartX = 0
let lastMoveX = 0
// 原始坐标(未缩放)
const originalCoords = computed(() => {
return generateGridCoordinates(config.value)
})
// 缩放后的坐标
const scaledCoords = computed(() => {
return originalCoords.value.map(coord => ({
x: coord.x * scale.value,
y: coord.y * scale.value,
originalX: coord.x,
originalY: coord.y,
isExcluded: coord.x === 0 && coord.y === 0
}))
})
// 可见的网格点5个tile
const visiblePoints = computed(() => {
const w = tileWidth.value
const points = []
for (let n = 0; n <= 4; n++) {
scaledCoords.value.forEach((coord, i) => {
points.push({
key: `${i}-${n}`,
x: coord.x + n * w,
y: coord.y,
originalX: coord.originalX,
originalY: coord.originalY,
originalIndex: i,
isExcluded: coord.isExcluded
})
})
}
return points
})
// 背景条样式
const backgroundStripStyle = computed(() => {
const centerOffset = (screenWidth.value - tileWidth.value) / 2
return {
width: `${tileWidth.value * 3}px`,
transform: `translateX(${-tileWidth.value + centerOffset + bgOffsetX.value}px)`
}
})
// 网格层样式
const gridLayerStyle = computed(() => {
const centerOffset = (screenWidth.value - tileWidth.value) / 2
return {
transform: `translateX(${-tileWidth.value + centerOffset + bgOffsetX.value}px)`
}
})
// 当前选中点的坐标
const currentPoint = computed(() => {
if (selectedIndex.value === null) return { x: 0, y: 0 }
return originalCoords.value[selectedIndex.value] || { x: 0, y: 0 }
})
// 归一化偏移量
const clampOffset = (offset) => {
const w = tileWidth.value
return (((offset % w) + w) % w) - w
}
// 触摸事件
const onTouchStart = (e) => {
touchStartX = e.touches[0].clientX
lastMoveX = touchStartX
}
const onTouchMove = (e) => {
e.preventDefault()
const currentX = e.touches[0].clientX
const delta = currentX - lastMoveX
lastMoveX = currentX
bgOffsetX.value = clampOffset(bgOffsetX.value + delta)
}
const onTouchEnd = () => {
// 简化处理,不添加惯性
}
const selectPoint = (index) => {
selectedIndex.value = index
console.log('选中点位:', index, originalCoords.value[index])
}
const deselectPoint = () => {
selectedIndex.value = null
}
const adjustPoint = (axis, delta) => {
if (selectedIndex.value === null) return
const idx = selectedIndex.value
if (!config.value.manualAdjustments) {
config.value.manualAdjustments = {}
}
const current = originalCoords.value[idx]
if (!config.value.manualAdjustments[idx]) {
config.value.manualAdjustments[idx] = { x: current.x, y: current.y }
}
if (axis === 'x') {
config.value.manualAdjustments[idx].x += delta
} else {
config.value.manualAdjustments[idx].y += delta
}
config.value = { ...config.value }
}
const savePointAdjustment = () => {
uni.showToast({
title: `点位 #${selectedIndex.value} 已保存`,
icon: 'success'
})
console.log('保存点位调整:', selectedIndex.value, config.value.manualAdjustments[selectedIndex.value])
}
const resetPoint = () => {
if (selectedIndex.value === null) return
if (config.value.manualAdjustments && config.value.manualAdjustments[selectedIndex.value]) {
delete config.value.manualAdjustments[selectedIndex.value]
config.value = { ...config.value }
uni.showToast({
title: '点位已重置',
icon: 'success'
})
}
}
const hasAdjustment = (index) => {
return config.value.manualAdjustments && config.value.manualAdjustments[index]
}
const updateStartY = (e) => {
config.value.grid.startY = e.detail.value
}
const updateSpacingY = (e) => {
config.value.grid.spacingY = e.detail.value
}
const updateSpacingX = (e) => {
config.value.grid.spacingX = e.detail.value
}
const updateStaggerOffsetX = (e) => {
config.value.grid.staggerOffsetX = e.detail.value
}
const exportConfig = () => {
const configStr = JSON.stringify(config.value, null, 2)
console.log('========== 当前配置 ==========')
console.log(configStr)
const coordsCode = originalCoords.value.map(c => ` [${c.x}, ${c.y}]`).join(',\n')
console.log('\n========== 坐标数组 ==========')
console.log('[\n' + coordsCode + '\n]')
if (config.value.manualAdjustments && Object.keys(config.value.manualAdjustments).length > 0) {
console.log('\n========== 手动调整 ==========')
console.log('manualAdjustments: {')
Object.entries(config.value.manualAdjustments).forEach(([idx, adj]) => {
console.log(` ${idx}: { x: ${adj.x}, y: ${adj.y} },`)
})
console.log('}')
}
uni.showModal({
title: '配置已导出',
content: '请查看控制台输出,复制到 config/cabin.js',
showCancel: false
})
}
const resetConfig = () => {
uni.showModal({
title: '确认重置',
content: '将重置所有参数和调整,是否继续?',
success: (res) => {
if (res.confirm) {
config.value = JSON.parse(JSON.stringify(GRID_CONFIG))
selectedIndex.value = null
uni.showToast({
title: '已重置',
icon: 'success'
})
}
}
})
}
const goBack = () => {
uni.navigateBack()
}
// 初始化
const init = () => {
const info = uni.getSystemInfoSync()
screenWidth.value = info.windowWidth
screenHeight.value = info.windowHeight
tileWidth.value = Math.round(info.windowHeight * (IMAGE_W / IMAGE_H))
scale.value = info.windowHeight / IMAGE_H
console.log('调试页面初始化:', {
screenWidth: screenWidth.value,
screenHeight: screenHeight.value,
tileWidth: tileWidth.value,
scale: scale.value
})
}
init()
</script>
<style scoped>
.debug-container {
position: relative;
width: 100vw;
min-height: calc(100vh + 650rpx);
overflow: hidden;
background: #1a1a1a;
padding-bottom: 650rpx;
}
.background-strip {
position: absolute;
top: 0;
left: 0;
height: 100vh;
display: flex;
z-index: 0;
will-change: transform;
}
.background-tile {
flex-shrink: 0;
height: 100%;
}
.grid-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
pointer-events: none;
}
.grid-point {
position: absolute;
display: flex;
flex-direction: column;
align-items: center;
z-index: 10;
pointer-events: auto;
}
.grid-point--excluded {
opacity: 0.3;
}
.grid-dot {
width: 20rpx;
height: 20rpx;
background: #ff0000;
border-radius: 50%;
border: 2rpx solid #ffffff;
box-shadow: 0 0 10rpx rgba(255, 0, 0, 0.8);
}
.grid-point--selected .grid-dot {
background: #00ff00;
width: 30rpx;
height: 30rpx;
box-shadow: 0 0 20rpx rgba(0, 255, 0, 1);
}
.grid-label {
font-size: 20rpx;
color: #ffffff;
background: rgba(0, 0, 0, 0.7);
padding: 2rpx 8rpx;
border-radius: 4rpx;
margin-top: 4rpx;
font-weight: bold;
}
.grid-point--selected .grid-label {
background: rgba(0, 255, 0, 0.9);
color: #000000;
font-size: 24rpx;
}
.grid-coords {
font-size: 18rpx;
color: #00ff00;
background: rgba(0, 0, 0, 0.7);
padding: 2rpx 6rpx;
border-radius: 4rpx;
margin-top: 2rpx;
}
.control-panel {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #2a2a2a;
border-top: 2rpx solid #444;
padding: 20rpx;
max-height: 600rpx;
overflow-y: auto;
z-index: 100;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.panel-title {
font-size: 32rpx;
color: #ffffff;
font-weight: bold;
}
.btn-close {
padding: 10rpx 20rpx;
background: #ff4444;
color: #ffffff;
border: none;
border-radius: 8rpx;
font-size: 24rpx;
}
.control-section {
margin-bottom: 30rpx;
}
.control-label {
display: block;
font-size: 26rpx;
color: #ffffff;
margin-bottom: 10rpx;
}
.btn-export,
.btn-reset {
width: 48%;
padding: 20rpx;
background: #4CAF50;
color: #ffffff;
border: none;
border-radius: 8rpx;
font-size: 28rpx;
margin-right: 4%;
}
.btn-reset {
background: #ff9800;
margin-right: 0;
}
.coords-list {
margin-top: 30rpx;
background: #1a1a1a;
border-radius: 8rpx;
padding: 20rpx;
}
.coords-title {
display: block;
font-size: 28rpx;
color: #ffffff;
margin-bottom: 10rpx;
font-weight: bold;
}
.coords-scroll {
max-height: 400rpx;
}
.coord-item {
display: flex;
align-items: center;
padding: 8rpx 0;
border-bottom: 1rpx solid #333;
cursor: pointer;
}
.coord-item--selected {
background: rgba(0, 255, 0, 0.2);
}
.coord-index {
font-size: 24rpx;
color: #00ff00;
width: 80rpx;
font-weight: bold;
}
.coord-value {
font-size: 24rpx;
color: #ffffff;
flex: 1;
}
.coord-excluded {
font-size: 20rpx;
color: #ff0000;
margin-left: 10rpx;
}
.coord-adjusted {
font-size: 20rpx;
color: #00ff00;
margin-left: 10rpx;
}
.point-edit-section {
background: #1a1a1a;
border-radius: 8rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.point-edit-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.point-edit-title {
font-size: 28rpx;
color: #00ff00;
font-weight: bold;
}
.btn-deselect {
padding: 8rpx 16rpx;
background: #666;
color: #ffffff;
border: none;
border-radius: 6rpx;
font-size: 22rpx;
}
.point-edit-controls {
display: flex;
flex-direction: column;
gap: 15rpx;
}
.point-edit-row {
display: flex;
flex-direction: column;
gap: 10rpx;
}
.point-edit-label {
font-size: 24rpx;
color: #ffffff;
font-weight: bold;
}
.point-edit-buttons {
display: flex;
gap: 10rpx;
}
.btn-adjust {
flex: 1;
padding: 15rpx;
background: #4CAF50;
color: #ffffff;
border: none;
border-radius: 6rpx;
font-size: 24rpx;
}
.btn-save-point,
.btn-reset-point {
width: 48%;
padding: 20rpx;
border: none;
border-radius: 8rpx;
font-size: 26rpx;
color: #ffffff;
}
.btn-save-point {
background: #2196F3;
margin-right: 4%;
}
.btn-reset-point {
background: #ff5722;
}
.global-controls {
margin-bottom: 20rpx;
}
</style>