Prechádzať zdrojové kódy

blockly编程游戏
1、配置列表加入懒加载处理json数据,优化性能
2、虚拟实验室列表同步优惠

liyanbo 5 mesiacov pred
rodič
commit
91b5ef8a43

+ 97 - 0
src/components/Json/LazyJsonViewer.vue

@@ -0,0 +1,97 @@
+<template>
+  <div>
+    <el-popover
+      placement="top-start"
+      width="600"
+      trigger="manual"
+      v-model:visible="isPopoverVisible"
+      @show="handleShow"
+    >
+      <div v-if="loading">
+        <el-icon class="el-icon-loading" />
+        加载中...
+      </div>
+      <pre v-else-if="formattedJson"
+        style="
+          white-space: pre-wrap;
+          word-break: break-all;
+          background-color: #f5f5f5;
+          padding: 10px;
+          border-radius: 4px;
+          max-height: 400px;
+          overflow-y: auto;
+        "
+      >{{ formattedJson }}</pre>
+      <div v-else class="text-gray-400">无效的JSON数据</div>
+      
+      <template #reference>
+        <span @click="togglePopover" class="text-blue-500 cursor-pointer">点击查看</span>
+      </template>
+    </el-popover>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, nextTick } from 'vue'
+
+const props = defineProps({
+  jsonData: {
+    type: String,
+    default: ''
+  }
+})
+
+const isPopoverVisible = ref(false)
+const loading = ref(false)
+const formattedJson = ref('')
+const hasProcessed = ref(false)
+
+// 切换弹窗显示状态
+const togglePopover = () => {
+  isPopoverVisible.value = !isPopoverVisible.value
+}
+
+// 弹窗显示时处理JSON数据
+const handleShow = async () => {
+  // 如果已经处理过数据,直接显示
+  if (hasProcessed.value) {
+    return
+  }
+  
+  loading.value = true
+  
+  // 使用setTimeout将处理放入下一事件循环,避免阻塞UI
+  await nextTick()
+  
+  try {
+    // 模拟异步处理,减轻主线程压力
+    await new Promise(resolve => setTimeout(resolve, 50))
+    
+    // 检查数据大小,过大的数据给出提示
+    if (props.jsonData && props.jsonData.length > 50000) {
+      formattedJson.value = `数据过大(${props.jsonData.length}字符),建议在详情页查看`
+    } else if (props.jsonData) {
+      // 尝试解析和格式化JSON
+      const parsed = JSON.parse(props.jsonData)
+      formattedJson.value = JSON.stringify(parsed, null, 2)
+    }
+    
+    hasProcessed.value = true
+  } catch (error) {
+    console.error('JSON格式化错误:', error)
+    formattedJson.value = 'JSON解析失败'
+  } finally {
+    loading.value = false
+  }
+}
+
+// 监听jsonData变化,重置处理状态
+watch(() => props.jsonData, () => {
+  hasProcessed.value = false
+  formattedJson.value = ''
+})
+</script>
+
+<style scoped>
+/* 可以添加自定义样式 */
+</style>

+ 1 - 1
src/types/auto-components.d.ts

@@ -129,8 +129,8 @@ declare module 'vue' {
     Infotip: typeof import('./../components/Infotip/src/Infotip.vue')['default']
     InputPassword: typeof import('./../components/InputPassword/src/InputPassword.vue')['default']
     InputWithColor: typeof import('./../components/InputWithColor/index.vue')['default']
+    LazyJsonViewer: typeof import('./../components/Json/LazyJsonViewer.vue')['default']
     MagicCubeEditor: typeof import('./../components/MagicCubeEditor/index.vue')['default']
-    Mapgame: typeof import('./../api/ai/mapgame/index.ts')['default']
     MarkdownView: typeof import('./../components/MarkdownView/index.vue')['default']
     NodeHandler: typeof import('./../components/SimpleProcessDesignerV2/src/NodeHandler.vue')['default']
     OperateLogV2: typeof import('./../components/OperateLogV2/src/OperateLogV2.vue')['default']

+ 2 - 0
src/types/auto-imports.d.ts

@@ -7,6 +7,8 @@ export {}
 declare global {
   const DICT_TYPE: typeof import('@/utils/dict')['DICT_TYPE']
   const EffectScope: typeof import('vue')['EffectScope']
+  const ElMessage: typeof import('element-plus/es')['ElMessage']
+  const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
   const computed: typeof import('vue')['computed']
   const createApp: typeof import('vue')['createApp']
   const customRef: typeof import('vue')['customRef']

+ 4 - 0
src/utils/dict.ts

@@ -257,4 +257,8 @@ export enum DICT_TYPE {
   // ========== QUESTIONNAIRE - 评估模块  ==========
   BJDX_QUESTIONNAIRE_STATUS = 'bjdx_questionnaire_status', // 问卷状态
   BJDX_QUEST_IS_CORRECT = 'bjdx_quest_is_correct', // 问卷状态
+
+  // ========== Blockly - 地图编程游戏  ==========
+  AI_BLOCKLY_MAP_TYPE = 'ai_blockly_map_type', // 地图类型
+
 }

+ 245 - 65
src/views/ai/mapgame/MapGameForm.vue

@@ -1,5 +1,5 @@
 <template>
-  <Dialog :title="dialogTitle" v-model="dialogVisible">
+  <Dialog :title="dialogTitle" v-model="dialogVisible" width="900px">
     <el-form
       ref="formRef"
       :model="formData"
@@ -10,71 +10,115 @@
       <el-form-item label="名称" prop="name">
         <el-input v-model="formData.name" placeholder="请输入名称" />
       </el-form-item>
-      <el-form-item label="简介" prop="info">
+      <el-form-item label="简介" prop="info" width="100%">
         <el-input v-model="formData.info" placeholder="请输入简介" />
       </el-form-item>
-      <el-form-item label="人物图标" prop="userImage">
-        <UploadImg v-model="formData.userImage" />
-      </el-form-item>
-      <el-form-item label="地图背景图" prop="mapBackground">
-        <UploadImg v-model="formData.mapBackground" />
-      </el-form-item>
-      <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>
-      </el-form-item>
-      <el-form-item label="人物朝向" prop="userDirection">
-          <el-radio-group v-model="formData.userDirection" size="large" fill="#6cf">
-          <el-radio-button label="上" value=0 />
-          <el-radio-button label="右" value=1 />
-          <el-radio-button label="下" value=2 />
-          <el-radio-button label="左" value=3 />
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item label="地图开始坐标" prop="mapStartPoint">
-        <el-input v-model="formData.mapStartPoint" placeholder="请输入地图开始坐标" />
+
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="人物图标" prop="userImage">
+            <UploadImg v-model="formData.userImage" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="地图背景图" prop="mapBackground">
+            <UploadImg v-model="formData.mapBackground" />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12">
+          <el-form-item label="人物朝向" prop="userDirection">
+            <el-radio-group v-model="formData.userDirection" size="large" >
+              <el-radio-button label="上" :value="0" />
+              <el-radio-button label="右" :value="1" />
+              <el-radio-button label="下" :value="2" />
+              <el-radio-button label="左" :value="3" />
+            </el-radio-group>
+          </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>
+          </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" style="width: 120px;" />
+          Y:
+          <el-input-number v-model="mapStartPointY" placeholder="Y轴" :min="1" style="width: 120px;" />
+        </div>
       </el-form-item>
-      <el-form-item label="地图结束坐标" prop="mapEndPoint">
-        <el-input v-model="formData.mapEndPoint" placeholder="请输入地图结束坐标" />
+      
+      <!-- 地图结束坐标 - 拆分为X轴和Y轴 -->
+      <el-form-item label="地图结束坐标" required>
+        <div class="coordinate-group">
+          X:
+          <el-input-number v-model="mapEndPointX" placeholder="X轴" :min="1" style="width: 120px;" />
+          Y:
+          <el-input-number v-model="mapEndPointY" placeholder="Y轴" :min="1" style="width: 120px;" />
+        </div>
       </el-form-item>
-      <el-form-item label="地图可行走坐标" prop="mapWalkablePoints">
-        <el-input
-          v-model="formData.mapWalkablePoints"
-          type="textarea"
-          placeholder="请输入地图可行走坐标"
-          :rows="6"
-          show-word-limit
-        />
+      
+      <!-- 地图可行走坐标 - 动态添加 -->
+      <el-form-item label="地图可行走坐标" required>
+        <div class="walkable-points-container">
+          <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" style="width: 100px;" />
+            Y:
+            <el-input-number v-model="point.y" placeholder="Y轴" :min="1" style="width: 100px;" />
+            <el-select v-model="point.type" placeholder="类型" style="width: 120px;" clearable>
+              <el-option v-for="dict in getIntDictOptions(DICT_TYPE.AI_BLOCKLY_MAP_TYPE)" :key="String(dict.value)" :label="dict.label" :value="dict.value" />
+            </el-select>
+            <el-input v-model="point.tip" placeholder="提示语" style="flex: 1; min-width: 200px;" />
+            <el-button @click="removeWalkablePoint(index)" type="danger" circle :disabled="walkablePoints.length <= 1" >
+              <Icon icon="ep:delete" /></el-button>
+          </div>
+        </div>
+        <el-button @click="addWalkablePoint" type="primary" style="margin-top: 10px;">+ 添加坐标</el-button>
       </el-form-item>
-      <el-form-item label="json数据" prop="jsonData">
+      
+<!--      <el-form-item label="json数据" prop="jsonData">
         <el-input
           v-model="formData.jsonData"
           type="textarea"
           placeholder="请输入JSON格式数据"
-          :rows="6"
+          :rows="3"
           show-word-limit
         />
-      </el-form-item>
-      <el-form-item label="排序" prop="sort">
-        <el-input-number v-model="formData.sort" :step="1" :min="1" />
-      </el-form-item>
-      <el-form-item label="状态" prop="status">
-        <el-select
-          v-model="formData.status"
-          placeholder="请选择状态"
-          clearable
-        >
-          <el-option
-            v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
-            :key="dict.value"
-            :label="dict.label"
-            :value="dict.value"
-          />
-        </el-select>
-      </el-form-item>
+      </el-form-item>-->
+
+      <el-row>
+        <el-col :span="12">
+          <el-form-item label="排序" prop="sort">
+            <el-input-number v-model="formData.sort" :step="1" :min="1" style="width: 100%" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="状态" prop="status">
+            <el-select
+              v-model="formData.status"
+              placeholder="请选择状态"
+              clearable
+            >
+              <el-option
+                v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
     </el-form>
     <template #footer>
       <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
@@ -101,7 +145,7 @@ const formData = ref({
   name: undefined,
   info: undefined,
   userImage: "https://learn-ai.com.cn/admin-api/infra/file/29/get/20251107/user_1762504554550.png",
-  userDirection: undefined,
+  userDirection: 0,
   mapTileSize: undefined,
   mapStartPoint: undefined,
   mapBackground: undefined,
@@ -111,15 +155,32 @@ const formData = ref({
   sort: undefined,
   status: undefined
 })
+
+// 新增的响应式数据
+const mapStartPointX = ref<number>()
+const mapStartPointY = ref<number>()
+const mapEndPointX = ref<number>()
+const mapEndPointY = ref<number>()
+
+// 可行走点接口定义
+interface WalkablePoint {
+  x: number
+  y: number
+  type: string | number
+  tip: string
+}
+
+// 可行走点数组
+const walkablePoints = ref<WalkablePoint[]>([{ x: 0, y: 0, type: "", tip: "" }])
+
 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' }],
-  mapStartPoint: [{ required: true, message: '地图开始坐标不能为空', trigger: 'blur' }],
-  mapEndPoint: [{ required: true, message: '地图结束坐标不能为空', trigger: 'blur' }],
-  mapWalkablePoints: [{ required: true, message: '地图可行走坐标不能为空', trigger: 'blur' }],
+  // 注意:这里移除了对mapStartPoint、mapEndPoint和mapWalkablePoints的直接验证
+  // 因为这些值现在是通过计算得到的
 })
 const formRef = ref() // 表单 Ref
 
@@ -133,7 +194,38 @@ const open = async (type: string, id?: number) => {
   if (id) {
     formLoading.value = true
     try {
-      formData.value = await MapGameApi.getMapGame(id)
+      const data = await MapGameApi.getMapGame(id)
+      formData.value = data
+
+      // 处理回显数据 - 解析坐标JSON
+      if (data.mapStartPoint) {
+        try {
+          const point = JSON.parse(data.mapStartPoint)
+          mapStartPointX.value = point.x
+          mapStartPointY.value = point.y
+        } catch (e) {
+          console.error('解析地图开始坐标失败:', e)
+        }
+      }
+      
+      if (data.mapEndPoint) {
+        try {
+          const point = JSON.parse(data.mapEndPoint)
+          mapEndPointX.value = point.x
+          mapEndPointY.value = point.y
+        } catch (e) {
+          console.error('解析地图结束坐标失败:', e)
+        }
+      }
+      
+      if (data.mapWalkablePoints) {
+        try {
+          walkablePoints.value = JSON.parse(data.mapWalkablePoints)
+        } catch (e) {
+          console.error('解析地图可行走坐标失败:', e)
+          walkablePoints.value = [{ x: 0, y: 0, type: "", tip: "" }]
+        }
+      }
     } finally {
       formLoading.value = false
     }
@@ -141,20 +233,61 @@ const open = async (type: string, id?: number) => {
 }
 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
 
+/** 添加可行走点 */
+const addWalkablePoint = () => {
+  walkablePoints.value.push({ x: 0, y: 0, type: "", tip: "" })
+}
+
+/** 移除可行走点 */
+const removeWalkablePoint = (index: number) => {
+  if (walkablePoints.value.length > 1) {
+    walkablePoints.value.splice(index, 1)
+  }
+}
+
 /** 提交表单 */
 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
 const submitForm = async () => {
-  // 校验表单
+  // 坐标数据验证
+  if (mapStartPointX.value === undefined || mapStartPointY.value === undefined) {
+    message.error('地图开始坐标不能为空')
+    return
+  }
+  
+  if (mapEndPointX.value === undefined || mapEndPointY.value === undefined) {
+    message.error('地图结束坐标不能为空')
+    return
+  }
+  
+  // 检查可行走点数据
+  const hasInvalidPoint = walkablePoints.value.some(point => 
+    point.x === undefined || point.y === undefined
+  )
+  
+  if (hasInvalidPoint) {
+    message.error('请完善所有可行走点的数据')
+    return
+  }
+  
+  // 表单基础验证
   await formRef.value.validate()
+  
+  // 准备提交数据 - 转换坐标为JSON格式
+  const submitData = { ...formData.value } as MapGameVO
+  
+  // 设置坐标JSON
+  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)
+  
   // 提交请求
   formLoading.value = true
   try {
-    const data = formData.value as unknown as MapGameVO
     if (formType.value === 'create') {
-      await MapGameApi.createMapGame(data)
+      await MapGameApi.createMapGame(submitData)
       message.success(t('common.createSuccess'))
     } else {
-      await MapGameApi.updateMapGame(data)
+      await MapGameApi.updateMapGame(submitData)
       message.success(t('common.updateSuccess'))
     }
     dialogVisible.value = false
@@ -172,7 +305,7 @@ const resetForm = () => {
     name: undefined,
     info: undefined,
     userImage: "https://learn-ai.com.cn/admin-api/infra/file/29/get/20251107/user_1762504554550.png",
-    userDirection: undefined,
+    userDirection: 0,
     mapTileSize: undefined,
     mapStartPoint: undefined,
     mapBackground: undefined,
@@ -182,6 +315,53 @@ const resetForm = () => {
     sort: undefined,
     status: undefined
   }
+  
+  // 重置新增的响应式数据
+  mapStartPointX.value = undefined
+  mapStartPointY.value = undefined
+  mapEndPointX.value = undefined
+  mapEndPointY.value = undefined
+  walkablePoints.value = [{ x: 0, y: 0, type: "", tip: "" }]
+  
   formRef.value?.resetFields()
 }
 </script>
+
+<style scoped>
+/* 坐标输入框样式 */
+.coordinate-group {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.coordinate-separator {
+  font-size: 16px;
+  color: #606266;
+}
+
+/* 可行走点样式 */
+.walkable-points-container {
+  max-height: 400px;
+  overflow-y: auto;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  padding: 15px;
+  background-color: #f9f9f9;
+}
+
+.walkable-point-item {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  margin-bottom: 10px;
+  padding: 10px;
+  background-color: #fff;
+  border-radius: 4px;
+  border: 1px solid #e4e7ed;
+}
+
+.walkable-point-item:last-child {
+  margin-bottom: 0;
+}
+</style>

+ 34 - 77
src/views/ai/mapgame/index.vue

@@ -68,11 +68,16 @@
   <!-- 列表 -->
   <ContentWrap>
     <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
-      <el-table-column label="名称" align="center" prop="name" />
-      <el-table-column label="简介" align="center" prop="info" />
+      <el-table-column label="名称" align="center" prop="name" width="180px" />
       <el-table-column label="人物图标" align="center" prop="userImage" >
         <template #default="scope">
-          <el-image :src="scope.row.userImage" fit="contain" :preview-src-list="[scope.row.userImage]" />
+          <el-image 
+            :src="scope.row.userImage" 
+            fit="contain" 
+            :preview-src-list="[scope.row.userImage]" 
+            :lazy="true" 
+            style="max-width: 60px; max-height: 60px;"
+          />
         </template>
       </el-table-column>
       <el-table-column label="人物朝向" align="center" prop="userDirection">
@@ -96,92 +101,44 @@
       </el-table-column>
       <el-table-column label="地图开始坐标" align="center" prop="mapStartPoint">
         <template #default="scope">
-          <el-popover placement="top-start" width="600" trigger="hover" v-if="scope.row.mapStartPoint">
-            <pre
-              style="
-                white-space: pre-wrap;
-                word-break: break-all;
-                background-color: #f5f5f5;
-                padding: 10px;
-                border-radius: 4px;
-                max-height: 400px;
-                overflow-y: auto;
-              "
-            >{{ formatJson(scope.row.mapStartPoint) }}</pre
-            >
-            <template #reference>
-              <span class="text-blue-500 cursor-pointer">点击查看</span>
-            </template>
-          </el-popover>
+          <LazyJsonViewer v-if="scope.row.mapStartPoint" :json-data="scope.row.mapStartPoint" />
           <span v-else>-</span>
         </template>
       </el-table-column>
       <el-table-column label="地图结束坐标" align="center" prop="mapEndPoint">
         <template #default="scope">
-          <el-popover placement="top-start" width="600" trigger="hover" v-if="scope.row.mapEndPoint">
-            <pre
-              style="
-                white-space: pre-wrap;
-                word-break: break-all;
-                background-color: #f5f5f5;
-                padding: 10px;
-                border-radius: 4px;
-                max-height: 400px;
-                overflow-y: auto;
-              "
-            >{{ formatJson(scope.row.mapEndPoint) }}</pre
-            >
-            <template #reference>
-              <span class="text-blue-500 cursor-pointer">点击查看</span>
-            </template>
-          </el-popover>
+          <LazyJsonViewer v-if="scope.row.mapEndPoint" :json-data="scope.row.mapEndPoint" />
           <span v-else>-</span>
         </template>
       </el-table-column>
       <el-table-column label="地图可行走坐标" align="center" prop="mapWalkablePoints">
         <template #default="scope">
-          <el-popover placement="top-start" width="600" trigger="hover" v-if="scope.row.mapWalkablePoints">
-            <pre
-              style="
-                white-space: pre-wrap;
-                word-break: break-all;
-                background-color: #f5f5f5;
-                padding: 10px;
-                border-radius: 4px;
-                max-height: 400px;
-                overflow-y: auto;
-              "
-            >{{ formatJson(scope.row.mapWalkablePoints) }}</pre
-            >
-            <template #reference>
-              <span class="text-blue-500 cursor-pointer">点击查看</span>
-            </template>
-          </el-popover>
-          <span v-else>-</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="json数据" align="center" prop="jsonData">
-        <template #default="scope">
-          <el-popover placement="top-start" width="600" trigger="hover" v-if="scope.row.jsonData">
-            <pre
-              style="
-                white-space: pre-wrap;
-                word-break: break-all;
-                background-color: #f5f5f5;
-                padding: 10px;
-                border-radius: 4px;
-                max-height: 400px;
-                overflow-y: auto;
-              "
-            >{{ formatJson(scope.row.jsonData) }}</pre
-            >
-            <template #reference>
-              <span class="text-blue-500 cursor-pointer">点击查看</span>
-            </template>
-          </el-popover>
+          <LazyJsonViewer v-if="scope.row.mapWalkablePoints" :json-data="scope.row.mapWalkablePoints" />
           <span v-else>-</span>
         </template>
       </el-table-column>
+<!--      <el-table-column label="json数据" align="center" prop="jsonData">-->
+<!--        <template #default="scope">-->
+<!--          <el-popover placement="top-start" width="600" trigger="hover" v-if="scope.row.jsonData">-->
+<!--            <pre-->
+<!--              style="-->
+<!--                white-space: pre-wrap;-->
+<!--                word-break: break-all;-->
+<!--                background-color: #f5f5f5;-->
+<!--                padding: 10px;-->
+<!--                border-radius: 4px;-->
+<!--                max-height: 400px;-->
+<!--                overflow-y: auto;-->
+<!--              "-->
+<!--            >{{ formatJson(scope.row.jsonData) }}</pre-->
+<!--            >-->
+<!--            <template #reference>-->
+<!--              <span class="text-blue-500 cursor-pointer">点击查看</span>-->
+<!--            </template>-->
+<!--          </el-popover>-->
+<!--          <span v-else>-</span>-->
+<!--        </template>-->
+<!--      </el-table-column>-->
       <el-table-column label="排序" align="center" prop="sort" />
       <el-table-column label="状态" align="center" prop="status" >
       <template #default="scope">
@@ -223,7 +180,7 @@
 </template>
 
 <script setup lang="ts">
-import { dateFormatter } from '@/utils/formatTime'
+import LazyJsonViewer from '@/components/Json/LazyJsonViewer.vue'
 import download from '@/utils/download'
 import { MapGameApi, MapGameVO } from '@/api/ai/mapgame'
 import MapGameForm from './MapGameForm.vue'

+ 2 - 17
src/views/ai/virtualdevice/index.vue

@@ -82,23 +82,7 @@
       <el-table-column label="路由" align="center" prop="routePath" />
       <el-table-column label="json数据" align="center" prop="jsonData">
         <template #default="scope">
-          <el-popover placement="top-start" width="600" trigger="hover" v-if="scope.row.jsonData">
-            <pre
-              style="
-                white-space: pre-wrap;
-                word-break: break-all;
-                background-color: #f5f5f5;
-                padding: 10px;
-                border-radius: 4px;
-                max-height: 400px;
-                overflow-y: auto;
-              "
-              >{{ formatJson(scope.row.jsonData) }}</pre
-            >
-            <template #reference>
-              <span class="text-blue-500 cursor-pointer">点击查看JSON</span>
-            </template>
-          </el-popover>
+          <LazyJsonViewer v-if="scope.row.jsonData" :json-data="scope.row.jsonData" />
           <span v-else>-</span>
         </template>
       </el-table-column>
@@ -147,6 +131,7 @@ import download from '@/utils/download'
 import { VirtualDeviceApi, VirtualDeviceVO } from '@/api/ai/virtualdevice'
 import VirtualDeviceForm from './VirtualDeviceForm.vue'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+import LazyJsonViewer from '@/components/Json/LazyJsonViewer.vue'
 
 /** AI-虚拟实验室 列表 */
 defineOptions({ name: 'VirtualDevice' })