697 lines
16 KiB
Vue
697 lines
16 KiB
Vue
<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>
|