|
|
@@ -37,132 +37,253 @@
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="地图方格尺寸" prop="mapTileSize">
|
|
|
- <el-input-number v-model="formData.mapTileSize" :min="10" :max="200">
|
|
|
- <template #suffix>
|
|
|
- <span>px</span>
|
|
|
- </template>
|
|
|
- </el-input-number>
|
|
|
+ <!-- 地图开始坐标 - 拆分为X轴和Y轴 -->
|
|
|
+ <el-form-item label="地图开始坐标" required>
|
|
|
+ <div class="coordinate-group">
|
|
|
+ X:
|
|
|
+ <el-input-number
|
|
|
+ v-model="mapStartPointX"
|
|
|
+ placeholder="X轴"
|
|
|
+ :min="1"
|
|
|
+ :max="mapTileX"
|
|
|
+ :step="1"
|
|
|
+ style="width: 120px"
|
|
|
+ />
|
|
|
+ Y:
|
|
|
+ <el-input-number
|
|
|
+ v-model="mapStartPointY"
|
|
|
+ placeholder="Y轴"
|
|
|
+ :min="1"
|
|
|
+ :max="mapTileY"
|
|
|
+ :step="1"
|
|
|
+ style="width: 120px"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
- </el-row>
|
|
|
-
|
|
|
- <!-- 地图开始坐标 - 拆分为X轴和Y轴 -->
|
|
|
- <el-form-item label="地图开始坐标" required>
|
|
|
- <div class="coordinate-group">
|
|
|
- X:
|
|
|
- <el-input-number
|
|
|
- v-model="mapStartPointX"
|
|
|
- placeholder="X轴"
|
|
|
- :min="1"
|
|
|
- :step="1"
|
|
|
- style="width: 120px"
|
|
|
- />
|
|
|
- Y:
|
|
|
- <el-input-number
|
|
|
- v-model="mapStartPointY"
|
|
|
- placeholder="Y轴"
|
|
|
- :min="1"
|
|
|
- :step="1"
|
|
|
- style="width: 120px"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </el-form-item>
|
|
|
-
|
|
|
- <!-- 地图结束坐标 - 拆分为X轴和Y轴 -->
|
|
|
- <el-form-item label="地图结束坐标" required>
|
|
|
- <div class="coordinate-group">
|
|
|
- X:
|
|
|
- <el-input-number
|
|
|
- v-model="mapEndPointX"
|
|
|
- placeholder="X轴"
|
|
|
- :min="1"
|
|
|
- :step="1"
|
|
|
- style="width: 120px"
|
|
|
- />
|
|
|
- Y:
|
|
|
- <el-input-number
|
|
|
- v-model="mapEndPointY"
|
|
|
- placeholder="Y轴"
|
|
|
- :min="1"
|
|
|
- :step="1"
|
|
|
- style="width: 120px"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </el-form-item>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="地图方格尺寸" required>
|
|
|
+ <div class="coordinate-group">
|
|
|
+ X:
|
|
|
+ <el-input-number
|
|
|
+ v-model="mapTileX"
|
|
|
+ placeholder="横向数量"
|
|
|
+ :min="1"
|
|
|
+ :step="1"
|
|
|
+ style="width: 120px"
|
|
|
+ />
|
|
|
+ Y:
|
|
|
+ <el-input-number
|
|
|
+ v-model="mapTileY"
|
|
|
+ placeholder="纵向数量"
|
|
|
+ :min="1"
|
|
|
+ :step="1"
|
|
|
+ style="width: 120px"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
|
|
|
- <!-- 地图可行走坐标 - 动态添加 -->
|
|
|
- <el-form-item label="地图可行走坐标" required>
|
|
|
- <div class="walkable-points-container">
|
|
|
- <div class="walkable-point-item">
|
|
|
- <span style="width: 130px"> 横坐标</span>
|
|
|
- <span style="width: 130px"> 纵坐标</span>
|
|
|
- <span style="width: 120px"> 方格类型</span>
|
|
|
- <span style="width: 120px"> 任务类型</span>
|
|
|
- <span style="width: 100px"> 方格图标</span>
|
|
|
- <span style="width: 100px"> 完成状态</span>
|
|
|
- <span style="width: 200px"> 提示语</span>
|
|
|
- </div>
|
|
|
- <div v-for="(point, index) in walkablePoints" :key="index" class="walkable-point-item">
|
|
|
- X:
|
|
|
- <el-input-number
|
|
|
- v-model="point.x"
|
|
|
- placeholder="X轴"
|
|
|
- :min="1"
|
|
|
- :step="1"
|
|
|
- style="width: 100px"
|
|
|
- />
|
|
|
- Y:
|
|
|
- <el-input-number
|
|
|
- v-model="point.y"
|
|
|
- placeholder="Y轴"
|
|
|
- :min="1"
|
|
|
- :step="1"
|
|
|
- style="width: 100px"
|
|
|
- />
|
|
|
- <el-select v-model="point.type" placeholder="类型" style="width: 120px" clearable>
|
|
|
- <el-option
|
|
|
- v-for="dict in getStrDictOptions(DICT_TYPE.AI_BLOCKLY_MAP_TYPE)"
|
|
|
- :key="dict.value"
|
|
|
- :label="dict.label"
|
|
|
- :value="dict.value"
|
|
|
+ <!-- 地图结束坐标 - 拆分为X轴和Y轴 -->
|
|
|
+ <el-form-item label="地图结束坐标" required>
|
|
|
+ <div class="coordinate-group">
|
|
|
+ X:
|
|
|
+ <el-input-number
|
|
|
+ v-model="mapEndPointX"
|
|
|
+ placeholder="X轴"
|
|
|
+ :min="1"
|
|
|
+ :max="mapTileX"
|
|
|
+ :step="1"
|
|
|
+ style="width: 120px"
|
|
|
/>
|
|
|
- </el-select>
|
|
|
- <el-select v-model="point.need" placeholder="任务" style="width: 120px" clearable>
|
|
|
- <el-option
|
|
|
- v-for="dict in getStrDictOptions(DICT_TYPE.AI_BLOCKLY_MAP_TYPE)"
|
|
|
- :key="dict.value"
|
|
|
- :label="dict.label"
|
|
|
- :value="dict.value"
|
|
|
+ Y:
|
|
|
+ <el-input-number
|
|
|
+ v-model="mapEndPointY"
|
|
|
+ placeholder="Y轴"
|
|
|
+ :min="1"
|
|
|
+ :max="mapTileY"
|
|
|
+ :step="1"
|
|
|
+ style="width: 120px"
|
|
|
/>
|
|
|
- </el-select>
|
|
|
- <UploadImg v-model="point.img" title="点击上传图标" />
|
|
|
- <UploadImg v-model="point.needImg" title="点击上传图标" />
|
|
|
- <el-input v-model="point.tip" placeholder="提示语" style="flex: 1; min-width: 200px" />
|
|
|
- <el-button
|
|
|
- @click="removeWalkablePoint(index)"
|
|
|
- type="danger"
|
|
|
- circle
|
|
|
- :disabled="walkablePoints.length <= 2"
|
|
|
- >
|
|
|
- <Icon icon="ep:delete"
|
|
|
- /></el-button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <el-button @click="addWalkablePoint" type="primary" style="margin-top: 10px"
|
|
|
- >+ 添加坐标</el-button
|
|
|
- >
|
|
|
- </el-form-item>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
|
|
|
- <!-- <el-form-item label="json数据" prop="jsonData">
|
|
|
- <el-input
|
|
|
- v-model="formData.jsonData"
|
|
|
- type="textarea"
|
|
|
- placeholder="请输入JSON格式数据"
|
|
|
- :rows="3"
|
|
|
- show-word-limit
|
|
|
- />
|
|
|
- </el-form-item>-->
|
|
|
+ <!-- 地图可行走坐标 - 动态矩阵显示 -->
|
|
|
+ <el-form-item label="地图方格配置" required>
|
|
|
+ <el-row type="flex" style="width: 100%;">
|
|
|
+ <!-- 左侧方格矩阵 -->
|
|
|
+ <el-col :span="10">
|
|
|
+ <div class="map-grid-container">
|
|
|
+ <div
|
|
|
+ class="map-grid"
|
|
|
+ :style="{ gridTemplateColumns: `repeat(${mapTileX || 5}, 1fr)`,
|
|
|
+ gridTemplateRows: `repeat(${mapTileY || 5}, 1fr)` ,
|
|
|
+ backgroundImage: formData.mapBackground ? `url(${formData.mapBackground})` : 'none', backgroundSize: 'cover',
|
|
|
+ backgroundPosition: 'center' }"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-for="(cell, index) in gridCells"
|
|
|
+ :key="index"
|
|
|
+ class="grid-cell"
|
|
|
+ :class="{
|
|
|
+ 'walkable': isWalkable(cell.x, cell.y),
|
|
|
+ 'selected': selectedPoint && selectedPoint.x === cell.x && selectedPoint.y === cell.y,
|
|
|
+ 'has-icon': getPointByXY(cell.x, cell.y)?.img
|
|
|
+ }"
|
|
|
+ @click="selectCell(cell)"
|
|
|
+ >
|
|
|
+ <div class="cell-coordinate">{{ cell.x }},{{ cell.y }}</div>
|
|
|
+ <div v-if="getPointByXY(cell.x, cell.y)?.img" class="cell-icon">
|
|
|
+ <img :src="getPointByXY(cell.x, cell.y).img" alt="图标" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <!-- 右侧配置项 -->
|
|
|
+ <el-col :span="14">
|
|
|
+ <div class="point-config-container" v-if="selectedPoint">
|
|
|
+ <el-form
|
|
|
+ label-width="100px"
|
|
|
+ :model="selectedPoint"
|
|
|
+ class="config-form"
|
|
|
+ size="large"
|
|
|
+ >
|
|
|
+ <el-card class="config-card" shadow="hover">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>坐标配置</span>
|
|
|
+ <el-tag size="small" type="info">{{ selectedPoint.x }},{{ selectedPoint.y }}</el-tag>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- 第一行:是否可行走和是否自动触发 -->
|
|
|
+ <div class="config-row">
|
|
|
+ <el-form-item label="是否可行走" class="config-item">
|
|
|
+ <el-switch
|
|
|
+ v-model="selectedPoint.walkable"
|
|
|
+ active-text="是"
|
|
|
+ inactive-text="否"
|
|
|
+ size="large"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template v-if="selectedPoint.walkable">
|
|
|
+ <!-- 第二行:地图类型和任务条件 -->
|
|
|
+ <div class="config-row">
|
|
|
+
|
|
|
+ <el-form-item label="地图类型" class="config-item">
|
|
|
+ <el-select
|
|
|
+ v-model="selectedPoint.type"
|
|
|
+ placeholder="选择地图类型"
|
|
|
+ style="width: 100%"
|
|
|
+ clearable
|
|
|
+ @change="() => {}"
|
|
|
+ size="large"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="dict in getStrDictOptions(DICT_TYPE.AI_BLOCKLY_MAP_TYPE)"
|
|
|
+ :key="dict.value"
|
|
|
+ :label="dict.label"
|
|
|
+ :value="dict.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item
|
|
|
+ label="是否必须完成"
|
|
|
+ class="config-item"
|
|
|
+ v-if="selectedPoint.type === 'task'"
|
|
|
+ >
|
|
|
+ <el-switch
|
|
|
+ v-model="selectedPoint.must"
|
|
|
+ active-text="是"
|
|
|
+ inactive-text="否"
|
|
|
+ size="large"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 第三行:两个图标 -->
|
|
|
+ <div class="config-row">
|
|
|
+ <el-form-item label="初始图标" class="config-item" v-if="selectedPoint.type !== ''">
|
|
|
+ <div class="icon-upload-wrapper">
|
|
|
+ <UploadImg
|
|
|
+ v-model="selectedPoint.img"
|
|
|
+ title="点击上传初始图标"
|
|
|
+ class="custom-upload"
|
|
|
+ />
|
|
|
+ <div v-if="selectedPoint.img" class="upload-tip">已上传</div>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item
|
|
|
+ label="完成图标"
|
|
|
+ class="config-item"
|
|
|
+ v-if="selectedPoint.type !== '' && selectedPoint.type === 'task'"
|
|
|
+ >
|
|
|
+ <div class="icon-upload-wrapper">
|
|
|
+ <UploadImg
|
|
|
+ v-model="selectedPoint.endImg"
|
|
|
+ title="点击上传完成图标"
|
|
|
+ class="custom-upload"
|
|
|
+ />
|
|
|
+ <div v-if="selectedPoint.endImg" class="upload-tip">已上传</div>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-form-item label="提示语" class="form-item-full" v-if="selectedPoint.type !== '' && selectedPoint.type !== 'task'">
|
|
|
+ <el-input
|
|
|
+ v-model="selectedPoint.tip"
|
|
|
+ placeholder="输入初始提示语"
|
|
|
+ style="width: 100%"
|
|
|
+ show-word-limit
|
|
|
+ maxlength="100"
|
|
|
+ type="textarea"
|
|
|
+ :rows="2"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="未完成提示语" class="form-item-full" v-if="selectedPoint.type !== '' && selectedPoint.type === 'task'">
|
|
|
+ <el-input
|
|
|
+ v-model="selectedPoint.unfinishedTip"
|
|
|
+ placeholder="输入未完成提示语"
|
|
|
+ style="width: 100%"
|
|
|
+ show-word-limit
|
|
|
+ maxlength="100"
|
|
|
+ type="textarea"
|
|
|
+ :rows="2"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-form-item label="完成提示语" class="form-item-full" v-if="selectedPoint.type !== '' && selectedPoint.type === 'task'">
|
|
|
+ <el-input
|
|
|
+ v-model="selectedPoint.finishedTip"
|
|
|
+ placeholder="输入完成提示语"
|
|
|
+ style="width: 100%"
|
|
|
+ show-word-limit
|
|
|
+ maxlength="100"
|
|
|
+ type="textarea"
|
|
|
+ :rows="2"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ </template>
|
|
|
+ </el-card>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-else class="no-selection">
|
|
|
+ <el-empty description="请点击左侧方格进行配置" />
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-form-item>
|
|
|
|
|
|
<el-row>
|
|
|
<el-col :span="12">
|
|
|
@@ -210,7 +331,7 @@ const formData = ref({
|
|
|
info: undefined,
|
|
|
userImage: 'https://learn-ai.com.cn/admin-api/infra/file/29/get/20251107/user_1762504554550.png',
|
|
|
userDirection: 0,
|
|
|
- mapTileSize: undefined,
|
|
|
+ mapTileSize: undefined, // 保持兼容性
|
|
|
mapStartPoint: undefined,
|
|
|
mapBackground: undefined,
|
|
|
mapEndPoint: undefined,
|
|
|
@@ -225,30 +346,84 @@ const mapStartPointX = ref<number>()
|
|
|
const mapStartPointY = ref<number>()
|
|
|
const mapEndPointX = ref<number>()
|
|
|
const mapEndPointY = ref<number>()
|
|
|
+const mapTileX = ref<number>(5) // 横向数量,默认5
|
|
|
+const mapTileY = ref<number>(5) // 纵向数量,默认5
|
|
|
+
|
|
|
+// 选中的点
|
|
|
+const selectedPoint = ref<WalkablePoint | null>(null)
|
|
|
+
|
|
|
+// 所有格子数据
|
|
|
+const gridCells = computed(() => {
|
|
|
+ const cells = []
|
|
|
+ for (let y = 1; y <= (mapTileY.value || 5); y++) {
|
|
|
+ for (let x = 1; x <= (mapTileX.value || 5); x++) {
|
|
|
+ cells.push({ x, y })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return cells
|
|
|
+})
|
|
|
|
|
|
// 可行走点接口定义
|
|
|
interface WalkablePoint {
|
|
|
x: number
|
|
|
y: number
|
|
|
+ walkable: boolean
|
|
|
type: string
|
|
|
- need: string
|
|
|
+ must: boolean
|
|
|
img: string
|
|
|
- needImg: string
|
|
|
+ endImg: string
|
|
|
tip: string
|
|
|
+ unfinishedTip: string
|
|
|
+ finishedTip: string
|
|
|
}
|
|
|
|
|
|
// 可行走点数组
|
|
|
-const walkablePoints = ref<WalkablePoint[]>([{ x: 0, y: 0, type: '', need: '', img:'', needImg:'', tip: '' }])
|
|
|
+const walkablePoints = ref<WalkablePoint[]>([])
|
|
|
|
|
|
const formRules = reactive({
|
|
|
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
|
|
|
userImage: [{ required: true, message: '人物图标不能为空', trigger: 'blur' }],
|
|
|
mapBackground: [{ required: true, message: '地图背景图不能为空', trigger: 'blur' }],
|
|
|
- mapTileSize: [{ required: true, message: '地图方格尺寸不能为空', trigger: 'blur' }],
|
|
|
userDirection: [{ required: true, message: '人物朝向不能为空', trigger: 'blur' }]
|
|
|
})
|
|
|
const formRef = ref() // 表单 Ref
|
|
|
|
|
|
+/** 获取指定坐标的点配置 */
|
|
|
+const getPointByXY = (x: number, y: number): WalkablePoint | undefined => {
|
|
|
+ return walkablePoints.value.find(point => point.x === x && point.y === y)
|
|
|
+}
|
|
|
+
|
|
|
+/** 判断指定坐标是否可行走 */
|
|
|
+const isWalkable = (x: number, y: number): boolean => {
|
|
|
+ const point = getPointByXY(x, y)
|
|
|
+ return point ? point.walkable : false
|
|
|
+}
|
|
|
+
|
|
|
+/** 选择格子 */
|
|
|
+const selectCell = (cell: { x: number, y: number }) => {
|
|
|
+ // 查找是否已有该点的配置
|
|
|
+ let point = getPointByXY(cell.x, cell.y)
|
|
|
+
|
|
|
+ // 如果没有,创建新的配置
|
|
|
+ if (!point) {
|
|
|
+ point = {
|
|
|
+ x: cell.x,
|
|
|
+ y: cell.y,
|
|
|
+ walkable: false,
|
|
|
+ type: '',
|
|
|
+ must: false,
|
|
|
+ img: '',
|
|
|
+ endImg: '',
|
|
|
+ tip: '',
|
|
|
+ unfinishedTip: '',
|
|
|
+ finishedTip: ''
|
|
|
+ }
|
|
|
+ walkablePoints.value.push(point)
|
|
|
+ }
|
|
|
+
|
|
|
+ selectedPoint.value = point
|
|
|
+}
|
|
|
+
|
|
|
/** 打开弹窗 */
|
|
|
const open = async (type: string, id?: number) => {
|
|
|
dialogVisible.value = true
|
|
|
@@ -286,12 +461,37 @@ const open = async (type: string, id?: number) => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 解析地图尺寸 - 如果有旧的mapTileSize,设置合理的默认值
|
|
|
+ if (data.mapTileSize) {
|
|
|
+ try {
|
|
|
+ const point = JSON.parse(data.mapTileSize)
|
|
|
+ mapTileX.value = point.x
|
|
|
+ mapTileY.value = point.y
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析地图结束坐标失败:', e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析地图可行走点
|
|
|
if (data.mapWalkablePoints) {
|
|
|
try {
|
|
|
- walkablePoints.value = JSON.parse(data.mapWalkablePoints)
|
|
|
+ const points = JSON.parse(data.mapWalkablePoints)
|
|
|
+ // 转换为新的格式,如果是旧格式需要兼容
|
|
|
+ walkablePoints.value = points.map((p: any) => ({
|
|
|
+ x: p.x,
|
|
|
+ y: p.y,
|
|
|
+ walkable: true,
|
|
|
+ type: p.type || '',
|
|
|
+ must: p.must || false,
|
|
|
+ img: p.img || '',
|
|
|
+ endImg: p.endImg || '',
|
|
|
+ tip: p.tip || '',
|
|
|
+ unfinishedTip: p.unfinishedTip || '',
|
|
|
+ finishedTip: p.finishedTip || ''
|
|
|
+ }))
|
|
|
} catch (e) {
|
|
|
console.error('解析地图可行走坐标失败:', e)
|
|
|
- walkablePoints.value = [{ x: 0, y: 0, type: '', need: '', img:'', needImg:'', tip: '' }]
|
|
|
+ walkablePoints.value = []
|
|
|
}
|
|
|
}
|
|
|
} finally {
|
|
|
@@ -301,18 +501,6 @@ const open = async (type: string, id?: number) => {
|
|
|
}
|
|
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
|
|
|
|
|
-/** 添加可行走点 */
|
|
|
-const addWalkablePoint = () => {
|
|
|
- walkablePoints.value.push({ x: 0, y: 0, type: '', need: '', img:'', needImg:'', tip: '' })
|
|
|
-}
|
|
|
-
|
|
|
-/** 移除可行走点 */
|
|
|
-const removeWalkablePoint = (index: number) => {
|
|
|
- if (walkablePoints.value.length > 1) {
|
|
|
- walkablePoints.value.splice(index, 1)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
/** 提交表单 */
|
|
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
|
|
const submitForm = async () => {
|
|
|
@@ -327,6 +515,11 @@ const submitForm = async () => {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
+ if (mapTileX.value === undefined || mapTileY.value === undefined) {
|
|
|
+ message.error('地图方格尺寸不能为空')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
// 检查可行走点数据
|
|
|
const hasInvalidPoint = walkablePoints.value.some(
|
|
|
(point) => point.x === undefined || point.y === undefined
|
|
|
@@ -344,9 +537,31 @@ const submitForm = async () => {
|
|
|
const submitData = { ...formData.value } as MapGameVO
|
|
|
|
|
|
// 设置坐标JSON
|
|
|
+ submitData.mapTileSize = JSON.stringify({ x: mapTileX.value, y: mapTileY.value })
|
|
|
submitData.mapStartPoint = JSON.stringify({ x: mapStartPointX.value, y: mapStartPointY.value })
|
|
|
submitData.mapEndPoint = JSON.stringify({ x: mapEndPointX.value, y: mapEndPointY.value })
|
|
|
- submitData.mapWalkablePoints = JSON.stringify(walkablePoints.value)
|
|
|
+
|
|
|
+ // 只保存可行走的点
|
|
|
+ const walkableOnlyPoints = walkablePoints.value.filter(point => point.walkable)
|
|
|
+ submitData.mapWalkablePoints = JSON.stringify(walkableOnlyPoints)
|
|
|
+
|
|
|
+ // 过滤出有效的可行走点 - 修复后的代码
|
|
|
+ const validWalkablePoints = walkablePoints.value.filter(point => {
|
|
|
+ // 同时检查:1) walkable为true 2) 坐标在有效范围内
|
|
|
+ const isWalkable = point.walkable
|
|
|
+ const isValidX = point.x <= mapTileX.value
|
|
|
+ const isValidY = point.y <= mapTileY.value
|
|
|
+ return isWalkable && isValidX && isValidY
|
|
|
+ })
|
|
|
+
|
|
|
+ // 移除重复的点(基于x和y坐标)
|
|
|
+ const uniquePoints = validWalkablePoints.filter((point, index, self) => {
|
|
|
+ return index === self.findIndex(p => p.x === point.x && p.y === point.y)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 保存有效的可行走点
|
|
|
+ submitData.mapWalkablePoints = JSON.stringify(uniquePoints)
|
|
|
+
|
|
|
|
|
|
// 提交请求
|
|
|
formLoading.value = true
|
|
|
@@ -372,8 +587,7 @@ const resetForm = () => {
|
|
|
id: undefined,
|
|
|
name: undefined,
|
|
|
info: undefined,
|
|
|
- userImage:
|
|
|
- 'https://learn-ai.com.cn/admin-api/infra/file/29/get/20251107/user_1762504554550.png',
|
|
|
+ userImage: 'https://learn-ai.com.cn/admin-api/infra/file/29/get/20251107/user_1762504554550.png',
|
|
|
userDirection: 0,
|
|
|
mapTileSize: undefined,
|
|
|
mapStartPoint: undefined,
|
|
|
@@ -385,12 +599,15 @@ const resetForm = () => {
|
|
|
status: 0
|
|
|
}
|
|
|
|
|
|
- // 重置新增的响应式数据
|
|
|
+ // 重置响应式数据
|
|
|
mapStartPointX.value = undefined
|
|
|
mapStartPointY.value = undefined
|
|
|
mapEndPointX.value = undefined
|
|
|
mapEndPointY.value = undefined
|
|
|
- walkablePoints.value = [{ x: 0, y: 0, type: '', need: '', img:'', needImg:'', tip: '' }]
|
|
|
+ mapTileX.value = 5
|
|
|
+ mapTileY.value = 5
|
|
|
+ walkablePoints.value = []
|
|
|
+ selectedPoint.value = null
|
|
|
|
|
|
formRef.value?.resetFields()
|
|
|
}
|
|
|
@@ -404,41 +621,216 @@ const resetForm = () => {
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
-.coordinate-separator {
|
|
|
- font-size: 16px;
|
|
|
- color: #606266;
|
|
|
-}
|
|
|
-
|
|
|
-/* 可行走点样式 */
|
|
|
-.walkable-points-container {
|
|
|
- max-height: 400px;
|
|
|
- overflow-y: auto;
|
|
|
+/* 地图网格样式 */
|
|
|
+.map-grid-container {
|
|
|
+ overflow: auto;
|
|
|
border: 1px solid #dcdfe6;
|
|
|
border-radius: 4px;
|
|
|
- padding: 15px;
|
|
|
+ padding: 10px;
|
|
|
background-color: #f9f9f9;
|
|
|
}
|
|
|
|
|
|
-.walkable-point-item {
|
|
|
+.map-grid {
|
|
|
+ display: grid;
|
|
|
+ gap: 2px;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.grid-cell {
|
|
|
+ aspect-ratio: 1;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ background-color: rgba(255, 255, 255, 0.9);
|
|
|
+ cursor: pointer;
|
|
|
+ position: relative;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- gap: 10px;
|
|
|
- margin-bottom: 10px;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.3s;
|
|
|
+ min-height: 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.grid-cell:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+}
|
|
|
+
|
|
|
+.grid-cell.selected {
|
|
|
+ border: 3px solid #409eff;
|
|
|
+ background-color: rgba(236, 245, 255, 0.8);
|
|
|
+ box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.3);
|
|
|
+ transform: scale(1.05);
|
|
|
+ z-index: 10;
|
|
|
+}
|
|
|
+/* 同时优化选中状态与其他状态的组合样式 */
|
|
|
+.grid-cell.selected.walkable {
|
|
|
+ border: 3px solid #409eff;
|
|
|
+ background-color: rgba(230, 247, 233, 0.8); /* 结合可行走的背景色 */
|
|
|
+ box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.grid-cell.selected.has-icon {
|
|
|
+ border: 3px solid #409eff;
|
|
|
+ background-color: rgba(255, 243, 230, 0.8); /* 结合有图标的背景色 */
|
|
|
+ box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
+.grid-cell.walkable {
|
|
|
+ background-color: rgba(240, 249, 235, 0.8);
|
|
|
+ border: 3px solid rgb(103, 194, 58);
|
|
|
+}
|
|
|
+
|
|
|
+.grid-cell.has-icon {
|
|
|
+ background-color: rgba(253, 246, 236, 0.8);
|
|
|
+}
|
|
|
+
|
|
|
+.cell-coordinate {
|
|
|
+ position: absolute;
|
|
|
+ top: 2px;
|
|
|
+ left: 4px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.cell-icon {
|
|
|
+ max-width: 80%;
|
|
|
+ max-height: 80%;
|
|
|
+}
|
|
|
+
|
|
|
+.cell-icon img {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+/* 右侧配置区域样式 */
|
|
|
+.point-config-container {
|
|
|
+ overflow-y: auto;
|
|
|
padding: 10px;
|
|
|
- background-color: #fff;
|
|
|
- border-radius: 4px;
|
|
|
- border: 1px solid #e4e7ed;
|
|
|
- text-align: center;
|
|
|
}
|
|
|
|
|
|
-.walkable-point-item:last-child {
|
|
|
+.config-form {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.config-card {
|
|
|
margin-bottom: 0;
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.card-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #303133;
|
|
|
+}
|
|
|
+
|
|
|
+.form-item-row {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.form-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
|
+ gap: 20px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.form-item-col {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.icon-upload-container {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.icon-upload-item {
|
|
|
+ margin-bottom: 15px;
|
|
|
+}
|
|
|
+
|
|
|
+.custom-upload {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-tip {
|
|
|
+ margin-left: 10px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #67c23a;
|
|
|
+}
|
|
|
+
|
|
|
+/* 优化空状态样式 */
|
|
|
+.no-selection {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 300px;
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ border-radius: 8px;
|
|
|
+ border: 1px dashed #dcdfe6;
|
|
|
}
|
|
|
|
|
|
-/* 确保上传组件预览图不超过行高 */
|
|
|
-.walkable-point-item :deep(.el-upload) {
|
|
|
- max-height: 32px;
|
|
|
- max-width: 100px;
|
|
|
- margin-bottom: -6px;
|
|
|
+/* 优化滚动条样式 */
|
|
|
+.point-config-container::-webkit-scrollbar {
|
|
|
+ width: 8px;
|
|
|
+ height: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.point-config-container::-webkit-scrollbar-thumb {
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #c0c4cc;
|
|
|
+}
|
|
|
+
|
|
|
+.point-config-container::-webkit-scrollbar-track {
|
|
|
+ background: #f0f0f0;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 配置项行布局 */
|
|
|
+.config-row {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 20px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ align-items: flex-start;
|
|
|
+}
|
|
|
+
|
|
|
+.config-item {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 200px;
|
|
|
+ margin-bottom: 0 !important;
|
|
|
+}
|
|
|
+
|
|
|
+.form-item-full {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 图标上传包装器 */
|
|
|
+.icon-upload-wrapper {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 上传提示 */
|
|
|
+.upload-tip {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #67c23a;
|
|
|
+ margin-top: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 调整标签宽度以适应行布局 */
|
|
|
+:deep(.el-form-item__label) {
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式调整 */
|
|
|
+@media screen and (max-width: 768px) {
|
|
|
+ .config-row {
|
|
|
+ flex-direction: column;
|
|
|
+ }
|
|
|
+
|
|
|
+ .config-item {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|