Explorar el Código

1、blockly编程课支持多路线配置

liyanbo hace 4 meses
padre
commit
6e334ca932
Se han modificado 1 ficheros con 388 adiciones y 120 borrados
  1. 388 120
      src/views/blockly/blockly/BlocklyForm.vue

+ 388 - 120
src/views/blockly/blockly/BlocklyForm.vue

@@ -6,7 +6,7 @@
       :rules="formRules"
       label-width="120px"
       v-loading="formLoading"
-    >x
+    >
       <el-form-item label="大纲课程" prop="bcType">
         <el-tree-select
           v-model="formData.bcType"
@@ -171,42 +171,7 @@
           </el-form-item>
         </el-col>
 
-        <el-col :span="12">
-          <el-form-item label="人物朝向" prop="blocklyUserDirection">
-            <el-radio-group v-model="formData.blocklyUserDirection" 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">
-          <!-- 地图开始坐标 - 拆分为X轴和Y轴 -->
-          <el-form-item label="地图开始坐标" required>
-            <div class="coordinate-group">
-              X:
-              <el-input-number
-                v-model="blocklyStartPointX"
-                placeholder="X轴"
-                :min="1"
-                :max="blocklyTileX"
-                :step="1"
-                style="width: 120px"
-              />
-              Y:
-              <el-input-number
-                v-model="blocklyStartPointY"
-                placeholder="Y轴"
-                :min="1"
-                :max="blocklyTileY"
-                :step="1"
-                style="width: 120px"
-              />
-            </div>
-          </el-form-item>
-        </el-col>
-        <el-col :span="12">
+        <el-col :span="24">
           <el-form-item label="地图方格尺寸" required>
             <div class="coordinate-group">
               X:
@@ -228,28 +193,96 @@
             </div>
           </el-form-item>
         </el-col>
-        <el-col :span="12">
-          <!-- 地图结束坐标 - 拆分为X轴和Y轴 -->
-          <el-form-item label="地图结束坐标" required>
-            <div class="coordinate-group">
-              X:
-              <el-input-number
-                v-model="blocklyEndPointX"
-                placeholder="X轴"
-                :min="1"
-                :max="blocklyTileX"
-                :step="1"
-                style="width: 120px"
-              />
-              Y:
-              <el-input-number
-                v-model="blocklyEndPointY"
-                placeholder="Y轴"
-                :min="1"
-                :max="blocklyTileY"
-                :step="1"
-                style="width: 120px"
-              />
+
+        <!-- 路线配置 -->
+        <el-col :span="24">
+          <el-form-item label="路线配置" required>
+            <el-button type="primary" @click="addRoute" style="margin-bottom: 12px;">新增路线</el-button>
+            <div class="route-config-container">
+              <div v-for="(route, index) in blocklyRoutes" :key="route.id" class="route-config-item">
+                <div class="route-row">
+                  <div class="route-title">
+                    <span>路线 {{ index + 1 }}</span>
+                  </div>
+                  
+                  <div class="route-fields">
+
+                    <!-- 人物朝向 -->
+                    <div class="route-field">
+                      <span class="field-label">人物朝向:</span>
+                      <el-radio-group v-model="route.direction" class="direction-radio">
+                        <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>
+                    </div>
+
+                    <!-- 开始坐标 -->
+                    <div class="route-field">
+                      <span class="field-label">开始坐标:</span>
+                      <div class="coordinate-group">
+                        <el-input-number
+                          v-model="route.startPoint.x"
+                          placeholder="X"
+                          :min="1"
+                          :max="blocklyTileX"
+                          :step="1"
+                          class="coordinate-input"
+                          style="width: 100px"
+                        />
+                        <el-input-number
+                          v-model="route.startPoint.y"
+                          placeholder="Y"
+                          :min="1"
+                          :max="blocklyTileY"
+                          :step="1"
+                          class="coordinate-input"
+                          style="width: 100px"
+                        />
+                      </div>
+                    </div>
+                    
+                    <!-- 结束坐标 -->
+                    <div class="route-field">
+                      <span class="field-label">结束坐标:</span>
+                      <div class="coordinate-group">
+                        <el-input-number
+                          v-model="route.endPoint.x"
+                          placeholder="X"
+                          :min="1"
+                          :max="blocklyTileX"
+                          :step="1"
+                          class="coordinate-input"
+                          style="width: 100px"
+                        />
+                        <el-input-number
+                          v-model="route.endPoint.y"
+                          placeholder="Y"
+                          :min="1"
+                          :max="blocklyTileY"
+                          :step="1"
+                          class="coordinate-input"
+                          style="width: 100px"
+                        />
+                      </div>
+                    </div>
+                  </div>
+                  
+                  <!-- 删除按钮 -->
+                  <div class="route-delete-btn">
+                    <el-button
+                      type="danger"
+                      size="small"
+                      @click="deleteRoute(route.id)"
+                      :disabled="blocklyRoutes.length <= 1"
+                      plain
+                    >
+                      删除
+                    </el-button>
+                  </div>
+                </div>
+              </div>
             </div>
           </el-form-item>
         </el-col>
@@ -323,7 +356,7 @@
                   <template v-if="selectedBlocklyPoint.walkable">
                     <!-- 第二行:地图类型 -->
                     <div class="config-row" style="display: flex; flex-wrap: wrap; gap: 20px;">
-                      <el-form-item label="地图类型" class="config-item" 
+                      <el-form-item label="地图类型" class="config-item"
                         :style="(selectedBlocklyPoint.type === 'task' || selectedBlocklyPoint.type === 'item') ? 'width: calc(50% - 20px);' : 'width: 100%;'">
                         <el-select
                           v-model="selectedBlocklyPoint.type"
@@ -516,16 +549,15 @@ const blocklyConfigTitle = ref('配置Blockly数据')
 const blocklyConfigFormRef = ref() // Blockly配置表单Ref
 
 // Blockly配置相关响应式数据
-const blocklyStartPointX = ref<number>()
-const blocklyStartPointY = ref<number>()
-const blocklyEndPointX = ref<number>()
-const blocklyEndPointY = ref<number>()
 const blocklyTileX = ref<number>(6) // 横向数量,默认5
 const blocklyTileY = ref<number>(5) // 纵向数量,默认5
 
 // 选中的点
 const selectedBlocklyPoint = ref<BlocklyWalkablePoint | null>(null)
 
+// 路线ID计数器
+const routeIdCounter = ref(1)
+
 // 可行走点接口定义
 interface BlocklyWalkablePoint {
   x: number
@@ -542,9 +574,20 @@ interface BlocklyWalkablePoint {
   finishedTip: string
 }
 
+// 路线接口定义
+interface BlocklyRoute {
+  id: number
+  direction: number
+  startPoint: { x: number, y: number }
+  endPoint: { x: number, y: number }
+}
+
 // 可行走点数组
 const blocklyWalkablePoints = ref<BlocklyWalkablePoint[]>([])
 
+// 路线数组
+const blocklyRoutes = ref<BlocklyRoute[]>([])
+
 const formData = ref({
   id: undefined,
   bcName: undefined,
@@ -553,13 +596,11 @@ const formData = ref({
 
   blocklyInfo: undefined,
   blocklyUserImage: 'https://learn-ai.com.cn/admin-api/infra/file/29/get/20251107/user_1762504554550.png',
-  blocklyUserDirection: 0,
   blocklyTileSize: undefined,
-  blocklyStartPoint: undefined,
   blocklyBackground: undefined,
-  blocklyEndPoint: undefined,
   blocklyWalkablePoints: undefined,
   blocklySpecialBlocks: undefined,
+  blocklyRouteList: undefined,
 
   bcIsInspect: "false",
   bcType: undefined,
@@ -592,10 +633,13 @@ const blocklyGridCells = computed(() => {
 const hasBlocklyConfig = computed(() => {
   return formData.value.blocklyUserImage &&
     formData.value.blocklyBackground &&
-    blocklyStartPointX.value !== undefined &&
-    blocklyStartPointY.value !== undefined &&
-    blocklyEndPointX.value !== undefined &&
-    blocklyEndPointY.value !== undefined &&
+    blocklyRoutes.value.length > 0 &&
+    blocklyRoutes.value.every(route =>
+      route.startPoint.x !== undefined &&
+      route.startPoint.y !== undefined &&
+      route.endPoint.x !== undefined &&
+      route.endPoint.y !== undefined
+    ) &&
     blocklyTileX.value !== undefined &&
     blocklyTileY.value !== undefined
 })
@@ -621,8 +665,7 @@ const formRules = reactive({
 // Blockly配置表单验证规则
 const blocklyConfigRules = reactive({
   blocklyUserImage: [{ required: true, message: '人物图标不能为空', trigger: 'blur' }],
-  blocklyBackground: [{ required: true, message: '地图背景图不能为空', trigger: 'blur' }],
-  blocklyUserDirection: [{ required: true, message: '人物朝向不能为空', trigger: 'blur' }],
+  blocklyBackground: [{ required: true, message: '地图背景图不能为空', trigger: 'blur' }]
   // blocklyInfo: [{ required: true, message: 'Blockly信息不能为空', trigger: 'blur' }]
 })
 
@@ -680,39 +723,87 @@ const open = async (type: string, id?: number) => {
     formData.value.bcType = id ? Number(id) : undefined
   }
   // 修改时,设置数据
-  if (type === 'update' && id) {
-    formLoading.value = true
-    try {
-      const blocklyData = await BlocklyApi.getBlockly(id)
-      formData.value = {
-        ...formData.value,
-        ...blocklyData,
-        bcContent: blocklyData.bcType === "image" ? blocklyData.bcContent?.split(',') : blocklyData.bcContent,
-        bcInfo: blocklyData.bcInfo || ''
-      }
-      // 确保 bcType 为正确的 id 类型
-      formData.value.bcType = blocklyData.bcType ? Number(blocklyData.bcType) : undefined
-
-      // 处理回显数据 - 解析坐标JSON
-      if (blocklyData.blocklyStartPoint) {
+      if (type === 'update' && id) {
+        formLoading.value = true
         try {
-          const point = JSON.parse(blocklyData.blocklyStartPoint)
-          blocklyStartPointX.value = point.x
-          blocklyStartPointY.value = point.y
-        } catch (e) {
-          console.error('解析地图开始坐标失败:', e)
-        }
-      }
-
-      if (blocklyData.blocklyEndPoint) {
-        try {
-          const point = JSON.parse(blocklyData.blocklyEndPoint)
-          blocklyEndPointX.value = point.x
-          blocklyEndPointY.value = point.y
-        } catch (e) {
-          console.error('解析地图结束坐标失败:', e)
-        }
-      }
+          const blocklyData = await BlocklyApi.getBlockly(id)
+          formData.value = {
+            ...formData.value,
+            ...blocklyData,
+            bcContent: blocklyData.bcType === "image" ? blocklyData.bcContent?.split(',') : blocklyData.bcContent,
+            bcInfo: blocklyData.bcInfo || ''
+          }
+          // 确保 bcType 为正确的 id 类型
+          formData.value.bcType = blocklyData.bcType ? Number(blocklyData.bcType) : undefined
+
+          // 处理回显数据 - 解析路线JSON
+          if (blocklyData.blocklyRouteList) {
+            try {
+              blocklyRoutes.value = JSON.parse(blocklyData.blocklyRouteList)
+              routeIdCounter.value = Math.max(...blocklyRoutes.value.map(route => route.id)) + 1
+            } catch (e) {
+              console.error('解析路线数据失败:', e)
+              // 如果没有路线数据,使用旧的单路线数据
+              blocklyRoutes.value = []
+              if (blocklyData.blocklyStartPoint && blocklyData.blocklyEndPoint) {
+                try {
+                  const startPoint = JSON.parse(blocklyData.blocklyStartPoint)
+                  const endPoint = JSON.parse(blocklyData.blocklyEndPoint)
+                  blocklyRoutes.value.push({
+                    id: routeIdCounter.value++,
+                    direction: blocklyData.blocklyUserDirection || 0,
+                    startPoint: startPoint,
+                    endPoint: endPoint
+                  })
+                } catch (e) {
+                  console.error('解析旧路线数据失败:', e)
+                  blocklyRoutes.value = [{
+                    id: routeIdCounter.value++,
+                    direction: 0,
+                    startPoint: { x: undefined, y: undefined },
+                    endPoint: { x: undefined, y: undefined }
+                  }]
+                }
+              } else {
+                blocklyRoutes.value = [{
+                  id: routeIdCounter.value++,
+                  direction: 0,
+                  startPoint: { x: undefined, y: undefined },
+                  endPoint: { x: undefined, y: undefined }
+                }]
+              }
+            }
+          } else {
+            // 兼容旧数据结构
+            blocklyRoutes.value = []
+            if (blocklyData.blocklyStartPoint && blocklyData.blocklyEndPoint) {
+              try {
+                const startPoint = JSON.parse(blocklyData.blocklyStartPoint)
+                const endPoint = JSON.parse(blocklyData.blocklyEndPoint)
+                blocklyRoutes.value.push({
+                  id: routeIdCounter.value++,
+                  direction: blocklyData.blocklyUserDirection || 0,
+                  startPoint: startPoint,
+                  endPoint: endPoint
+                })
+              } catch (e) {
+                console.error('解析旧路线数据失败:', e)
+                blocklyRoutes.value = [{
+                  id: routeIdCounter.value++,
+                  direction: 0,
+                  startPoint: { x: undefined, y: undefined },
+                  endPoint: { x: undefined, y: undefined }
+                }]
+              }
+            } else {
+              blocklyRoutes.value = [{
+                id: routeIdCounter.value++,
+                direction: 0,
+                startPoint: { x: undefined, y: undefined },
+                endPoint: { x: undefined, y: undefined }
+              }]
+            }
+          }
 
       // 解析地图尺寸
       if (blocklyData.blocklyTileSize) {
@@ -767,17 +858,46 @@ const openBlocklyConfigDialog = () => {
   blocklyConfigVisible.value = true
 }
 
+// 添加新路线
+const addRoute = () => {
+  blocklyRoutes.value.push({
+    id: routeIdCounter.value++,
+    direction: 0,
+    startPoint: { x: undefined, y: undefined },
+    endPoint: { x: undefined, y: undefined }
+  })
+}
+
+// 删除路线
+const deleteRoute = (routeId: number) => {
+  if (blocklyRoutes.value.length <= 1) {
+    message.warning('不能删除最后一条路线')
+    return
+  }
+  const index = blocklyRoutes.value.findIndex(route => route.id === routeId)
+  if (index > -1) {
+    blocklyRoutes.value.splice(index, 1)
+  }
+}
+
 /** 保存Blockly配置 */
 const saveBlocklyConfig = async () => {
   // 坐标数据验证
-  if (blocklyStartPointX.value === undefined || blocklyStartPointY.value === undefined) {
-    message.error('地图开始坐标不能为空')
+  if (blocklyRoutes.value.length === 0) {
+    message.error('至少需要配置一条路线')
     return
   }
 
-  if (blocklyEndPointX.value === undefined || blocklyEndPointY.value === undefined) {
-    message.error('地图结束坐标不能为空')
-    return
+  for (let i = 0; i < blocklyRoutes.value.length; i++) {
+    const route = blocklyRoutes.value[i]
+    if (route.startPoint.x === undefined || route.startPoint.y === undefined) {
+      message.error(`路线 ${i + 1} 的开始坐标不能为空`)
+      return
+    }
+    if (route.endPoint.x === undefined || route.endPoint.y === undefined) {
+      message.error(`路线 ${i + 1} 的结束坐标不能为空`)
+      return
+    }
   }
 
   if (blocklyTileX.value === undefined || blocklyTileY.value === undefined) {
@@ -790,15 +910,14 @@ const saveBlocklyConfig = async () => {
 
   // 设置坐标JSON
   formData.value.blocklyTileSize = JSON.stringify({ x: blocklyTileX.value, y: blocklyTileY.value })
-  formData.value.blocklyStartPoint = JSON.stringify({ x: blocklyStartPointX.value, y: blocklyStartPointY.value })
-  formData.value.blocklyEndPoint = JSON.stringify({ x: blocklyEndPointX.value, y: blocklyEndPointY.value })
+  formData.value.blocklyRouteList = JSON.stringify(blocklyRoutes.value)
 
   // 只保存可行走的点
   const walkableOnlyPoints = blocklyWalkablePoints.value.filter(point => point.walkable)
   formData.value.blocklyWalkablePoints = JSON.stringify(walkableOnlyPoints)
 
   blocklyConfigVisible.value = false
-  message.success('Blockly配置已保存')
+  message.success('Blockly配置已更改!')
 }
 
 /** 提交表单 */
@@ -880,13 +999,11 @@ const resetForm = () => {
 
     blocklyInfo: undefined,
     blocklyUserImage: 'https://learn-ai.com.cn/admin-api/infra/file/29/get/20251107/user_1762504554550.png',
-    blocklyUserDirection: 0,
     blocklyTileSize: undefined,
-    blocklyStartPoint: undefined,
     blocklyBackground: undefined,
-    blocklyEndPoint: undefined,
     blocklyWalkablePoints: undefined,
     blocklySpecialBlocks: undefined,
+    blocklyRouteList: undefined,
 
     bcIsInspect: "false",
     bcType: undefined,
@@ -897,15 +1014,19 @@ const resetForm = () => {
   }
 
   // 重置Blockly配置相关数据
-  blocklyStartPointX.value = undefined
-  blocklyStartPointY.value = undefined
-  blocklyEndPointX.value = undefined
-  blocklyEndPointY.value = undefined
   blocklyTileX.value = 6
   blocklyTileY.value = 5
   blocklyWalkablePoints.value = []
   selectedBlocklyPoint.value = null
 
+  // 重置路线数据
+  blocklyRoutes.value = [{
+    id: routeIdCounter.value++,
+    direction: 0,
+    startPoint: { x: undefined, y: undefined },
+    endPoint: { x: undefined, y: undefined }
+  }]
+
   // 重置上传进度状态
   uploadProgress.value = 0
   isUploading.value = false
@@ -1061,6 +1182,153 @@ color: #303133;
   margin-bottom: 15px;
 }
 
+/* 路线配置容器 */
+.route-config-container {
+  margin-top: 10px;
+  width: 100%;
+}
+
+/* 路线配置项样式 */
+.route-config-item {
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  padding: 12px;
+  margin-bottom: 12px;
+  background-color: #ffffff;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
+  transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+}
+
+.route-config-item:hover {
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  border-color: #409eff;
+  transform: translateY(-1px);
+}
+
+/* 路线行样式 */
+.route-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.route-row:last-child {
+  margin-bottom: 0;
+}
+
+/* 路线标题样式 */
+.route-title {
+  display: inline-flex;
+  align-items: center;
+  height: 28px;
+  font-weight: 600;
+  font-size: 13px;
+  color: #409eff;
+  background-color: #ecf5ff;
+  padding: 0 10px;
+  border-radius: 4px;
+  border-left: 2px solid #409eff;
+  margin-right: 15px;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+/* 路线字段容器 */
+.route-fields {
+  display: flex;
+  align-items: center;
+  flex: 1;
+  flex-wrap: wrap;
+  gap: 20px;
+}
+
+/* 路线字段样式 */
+.route-field {
+  display: flex;
+  align-items: center;
+}
+
+/* 字段标签样式 */
+.field-label {
+  font-size: 13px;
+  color: #606266;
+  margin-right: 8px;
+  font-weight: 500;
+}
+
+/* 坐标组样式 */
+.coordinate-group {
+  display: flex;
+  align-items: center;
+  gap: 5px;
+}
+
+/* 坐标输入框样式 */
+.coordinate-input {
+  width: 60px;
+}
+
+/* 方向单选按钮样式 */
+.direction-radio {
+  display: flex;
+  align-items: center;
+  border-radius: 4px;
+  overflow: hidden;
+  border: 1px solid #dcdfe6;
+}
+
+.direction-radio .el-radio-button__inner {
+  padding: 4px 10px;
+  font-size: 12px;
+  border-radius: 0;
+  border: none;
+  transition: all 0.2s;
+}
+
+.direction-radio .el-radio-button__orig-radio:checked + .el-radio-button__inner {
+  background-color: #409eff;
+  border-color: #409eff;
+}
+
+.direction-radio .el-radio-button:first-child .el-radio-button__inner {
+  border-radius: 3px 0 0 3px;
+}
+
+.direction-radio .el-radio-button:last-child .el-radio-button__inner {
+  border-radius: 0 3px 3px 0;
+}
+
+/* 删除按钮样式 */
+.route-delete-btn {
+  margin-left: auto;
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .route-row {
+    flex-wrap: wrap;
+  }
+  
+  .route-fields {
+    width: 100%;
+    margin-top: 8px;
+  }
+  
+  .route-delete-btn {
+    margin-left: 0;
+    margin-top: 8px;
+  }
+}
+
+/* 路线头部样式 - 保留用于兼容性 */
+.route-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 15px;
+  font-weight: bold;
+  color: #303133;
+}
+
 /* 图标上传包装器 */
 .icon-upload-wrapper {
   display: flex;