Browse Source

feat: 1、小区管理员角色下拉接口调整;解绑设备变为抹掉设置;

liujia 1 tuần trước cách đây
mục cha
commit
804531737b

+ 4 - 2
src/api/system/index.ts

@@ -3,7 +3,9 @@ import request from '@/request'
 /**
  * 获取角色列表
  */
-export const queryRoleList = (): Promise<
+export const queryRoleList = (params: {
+  tenantId: string
+}): Promise<
   ResponseData<
     {
       roleName: string
@@ -11,7 +13,7 @@ export const queryRoleList = (): Promise<
     }[]
   >
 > => {
-  return request.get('/role/queryList')
+  return request.post('/role/queryList', params)
 }
 
 /**

+ 124 - 98
src/components/DetectionAreaView/index.vue

@@ -11,12 +11,9 @@
             class="furniture-item"
             :style="getFurnitureStyle(item)"
           >
-            <furniture-icon
-              :icon="item.type"
-              :width="item.width"
-              :height="item.length"
-              :draggable="false"
-            />
+            <div class="furniture-rotated-container" :style="getRotatedContainerStyle(item)">
+              <furnitureIcon :icon="item.type" :width="item.width" :height="item.length" />
+            </div>
           </div>
         </div>
 
@@ -114,10 +111,57 @@ const device = {
 }
 
 /**
- * 地理坐标转换为像素坐标
- * @param geoX - 地理坐标系x坐标(厘米)
- * @param geoY - 地理坐标系y坐标(厘米)
- * @returns 像素坐标系中的坐标点
+ * 计算旋转后的边界框(基于左上角基准)
+ */
+const calculateBoundingBox = (width: number, length: number, rotate: number) => {
+  const rad = (rotate * Math.PI) / 180
+
+  if (rotate === 0) {
+    return {
+      left: 0,
+      right: width,
+      top: 0,
+      bottom: length,
+      width: width,
+      height: length,
+    }
+  }
+
+  // 计算旋转后的四个角点(相对于左上角)
+  const corners = [
+    { x: 0, y: 0 }, // 左上
+    { x: width, y: 0 }, // 右上
+    { x: width, y: length }, // 右下
+    { x: 0, y: length }, // 左下
+  ]
+
+  // 旋转角点(围绕左上角旋转)
+  const rotatedCorners = corners.map((corner) => ({
+    x: corner.x * Math.cos(rad) - corner.y * Math.sin(rad),
+    y: corner.x * Math.sin(rad) + corner.y * Math.cos(rad),
+  }))
+
+  // 计算边界框
+  const xs = rotatedCorners.map((c) => c.x)
+  const ys = rotatedCorners.map((c) => c.y)
+
+  const minX = Math.min(...xs)
+  const maxX = Math.max(...xs)
+  const minY = Math.min(...ys)
+  const maxY = Math.max(...ys)
+
+  return {
+    left: minX,
+    right: maxX,
+    top: minY,
+    bottom: maxY,
+    width: maxX - minX,
+    height: maxY - minY,
+  }
+}
+
+/**
+ * 地理坐标转换为像素坐标(左上角基准)
  */
 const geoToPixel = (geoX: number, geoY: number): { x: number; y: number } => {
   const radarFurniture: RadarFurniture = {
@@ -135,11 +179,44 @@ const geoToPixel = (geoX: number, geoY: number): { x: number; y: number } => {
 }
 
 /**
- * 像素坐标转换为地理坐标
- * @param pixelX - 像素坐标系x坐标
- * @param pixelY - 像素坐标系y坐标
- * @returns 地理坐标系中的坐标点
+ * 获取家具容器样式 - 基于左上角定位
+ */
+const getFurnitureStyle = (item: FurnitureItem) => {
+  // 获取家具左上角的像素坐标
+  const pixelPos = geoToPixel(item?.x || 0, item?.y || 0)
+  const boundingBox = calculateBoundingBox(item.width, item.length, item.rotate || 0)
+
+  const cssObj = {
+    position: 'absolute',
+    left: `${pixelPos.x}px`,
+    top: `${pixelPos.y}px`,
+    width: `${boundingBox.width}px`,
+    height: `${boundingBox.height}px`,
+    pointerEvents: props.mode === 'edit' ? 'auto' : 'none',
+  } as CSSProperties
+
+  return cssObj
+}
+
+/**
+ * 获取旋转容器样式 - 调整位置使内容在边界框中正确显示
  */
+const getRotatedContainerStyle = (item: FurnitureItem) => {
+  const boundingBox = calculateBoundingBox(item.width, item.length, item.rotate || 0)
+
+  const cssObj = {
+    position: 'absolute',
+    left: `${-boundingBox.left}px`,
+    top: `${-boundingBox.top}px`,
+    width: `${item.width}px`,
+    height: `${item.length}px`,
+    transform: `rotate(${item.rotate || 0}deg)`,
+    transformOrigin: '0 0',
+  } as CSSProperties
+
+  return cssObj
+}
+
 const pixelToGeo = (pixelX: number, pixelY: number): { x: number; y: number } => {
   const canvasRect: CanvasRect = {
     left: pixelX,
@@ -170,14 +247,14 @@ const convertAreaByDirection = (
   const normalizedDirection = ((direction % 360) + 360) % 360
 
   switch (normalizedDirection) {
-    case 0: // 设备朝北:x轴反向,y轴保持不变
-      return [-xEnd, -xStart, yStart, yEnd]
-    case 90: // 设备朝东:保持原始坐标系
+    case 0: // 设备朝东:保持原始坐标系(x轴朝右)
       return [xStart, xEnd, yStart, yEnd]
-    case 180: // 设备朝南:x轴保持不变,y轴反向
+    case 90: // 设备朝南:y轴反向
       return [xStart, xEnd, -yEnd, -yStart]
-    case 270: // 设备朝西:x轴反向,y轴反向
+    case 180: // 设备朝西:x轴反向
       return [-xEnd, -xStart, -yEnd, -yStart]
+    case 270: // 设备朝北:x轴反向,y轴反向
+      return [-xEnd, -xStart, yStart, yEnd]
     default: // 其他角度使用任意角度转换
       return convertAreaForArbitraryAngle(area, direction)
   }
@@ -204,12 +281,10 @@ const convertAreaForArbitraryAngle = (
     { x: xEnd, y: yEnd }, // 右上角
   ]
 
-  // 计算旋转角度(逆时针旋转)
   const rotationAngle = (-direction * Math.PI) / 180
   const cosA = Math.cos(rotationAngle)
   const sinA = Math.sin(rotationAngle)
 
-  // 应用旋转矩阵变换到所有角点
   const rotatedCorners = corners.map((corner) => {
     return {
       x: corner.x * cosA - corner.y * sinA,
@@ -217,55 +292,16 @@ const convertAreaForArbitraryAngle = (
     }
   })
 
-  // 计算旋转后的边界范围
   const xValues = rotatedCorners.map((c) => c.x)
   const yValues = rotatedCorners.map((c) => c.y)
 
   return [Math.min(...xValues), Math.max(...xValues), Math.min(...yValues), Math.max(...yValues)]
 }
 
-/**
- * 计算转换后的检测区域坐标(响应式)
- * 根据当前设备方向实时计算检测区域的实际坐标
- */
 const convertedCoordinates = computed(() => {
   return convertAreaByDirection(props.coordinates, props.direction)
 })
 
-/**
- * 计算家具元素的样式
- * 将地理坐标系中的家具位置转换为画布中的像素位置和样式
- * @param item - 家具配置项
- * @returns 家具的CSS样式对象
- */
-const getFurnitureStyle = (item: FurnitureItem) => {
-  const radarFurniture: RadarFurniture = {
-    x: item?.x || 0,
-    y: item?.y || 0,
-    width: item.width,
-    height: item.length,
-  }
-
-  const canvasRect = convert_furniture_r2c(radarFurniture, radarPosition)
-
-  return {
-    position: 'absolute',
-    left: `${canvasRect.left - item.width / 2}px`, // 居中定位
-    top: `${canvasRect.top - item.length / 2}px`, // 居中定位
-    width: `${item.width}px`,
-    height: `${item.length}px`,
-    transform: `rotate(${item.rotate}deg)`, // 家具自身旋转
-    transformOrigin: 'center center',
-    pointerEvents: props.mode === 'edit' ? 'auto' : 'none', // 编辑模式下可交互
-  } as CSSProperties
-}
-
-/**
- * 计算目标点位的样式
- * 将地理坐标系中的目标位置转换为画布中的像素位置和样式
- * @param target - 目标点位数据
- * @returns 目标点位的CSS样式对象
- */
 const getTargetStyle = (target: TargetPoint) => {
   const radarFurniture: RadarFurniture = {
     x: target.x,
@@ -276,18 +312,8 @@ const getTargetStyle = (target: TargetPoint) => {
 
   const canvasRect = convert_furniture_r2c(radarFurniture, radarPosition)
 
-  // 定义红橙黄绿青蓝紫颜色序列
-  const colorPalette = [
-    'red', // 0: 红
-    'orange', // 1: 橙
-    'yellow', // 2: 黄
-    'green', // 3: 绿
-    'cyan', // 4: 青
-    'blue', // 5: 蓝
-    'purple', // 6: 紫
-  ]
+  const colorPalette = ['red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'purple']
 
-  // 如果 target.id 超出颜色数组范围,使用最后一个颜色(紫色)
   const colorIndex = target.id < colorPalette.length ? target.id : colorPalette.length - 1
   const color = colorPalette[colorIndex]
 
@@ -313,10 +339,6 @@ const getTargetStyle = (target: TargetPoint) => {
   } as CSSProperties
 }
 
-/**
- * 初始化Canvas上下文
- * 获取Canvas的2D渲染上下文并开始绘制
- */
 const initCanvas = () => {
   if (!canvasRef.value) return
 
@@ -367,7 +389,7 @@ const drawRadarDevice = () => {
 
   // 移动到设备中心并应用旋转
   ctx.translate(device.x, device.y)
-  const rotation = props.direction
+  const rotation = props.direction + 90
   ctx.rotate((rotation * Math.PI) / 180)
 
   // 绘制居中的雷达图标
@@ -406,9 +428,9 @@ const drawRadarDeviceFallback = () => {
   const indicatorWidth = device.size * 0.3
 
   ctx.beginPath()
-  ctx.moveTo(0, -device.size / 2 - indicatorLength) // 尖角顶点
-  ctx.lineTo(-indicatorWidth / 2, -device.size / 2) // 左下角
-  ctx.lineTo(indicatorWidth / 2, -device.size / 2) // 右下角
+  ctx.moveTo(0, -device.size / 2 - indicatorLength)
+  ctx.lineTo(-indicatorWidth / 2, -device.size / 2)
+  ctx.lineTo(indicatorWidth / 2, -device.size / 2)
   ctx.closePath()
   ctx.fill()
   ctx.stroke()
@@ -431,22 +453,15 @@ const drawInfoText = () => {
   ctx.font = '12px Arial'
   ctx.textAlign = 'left'
 
-  // 文本显示位置(左下角)
   const baseX = 5
   const baseY = CANVAS_SIZE - 10
 
-  if (props.direction === 90) {
-    // 默认方向只显示原始坐标
-    ctx.fillText(`检测区域: [${xStart}, ${xEnd}, ${yStart}, ${yEnd}] cm`, baseX, baseY)
-  } else {
-    // 其他方向显示原始和转换后的坐标
-    ctx.fillText(`原始区域: [${xStart}, ${xEnd}, ${yStart}, ${yEnd}] cm`, baseX, baseY - 15)
-    ctx.fillText(
-      `转换区域: [${Math.round(convertedXStart)}, ${Math.round(convertedXEnd)}, ${Math.round(convertedYStart)}, ${Math.round(convertedYEnd)}] cm`,
-      baseX,
-      baseY
-    )
-  }
+  ctx.fillText(`原始区域: [${xStart}, ${xEnd}, ${yStart}, ${yEnd}] cm`, baseX, baseY - 15)
+  ctx.fillText(
+    `转换区域: [${Math.round(convertedXStart)}, ${Math.round(convertedXEnd)}, ${Math.round(convertedYStart)}, ${Math.round(convertedYEnd)}] cm`,
+    baseX,
+    baseY
+  )
 }
 
 /**
@@ -497,13 +512,11 @@ const drawAreaBorder = () => {
 
   const [xStart, xEnd, yStart, yEnd] = convertedCoordinates.value
 
-  // 确保检测区域不超出雷达范围
   const clampedXStart = Math.max(xStart, -RADAR_RANGE / 2)
   const clampedXEnd = Math.min(xEnd, RADAR_RANGE / 2)
   const clampedYStart = Math.max(yStart, -RADAR_RANGE / 2)
   const clampedYEnd = Math.min(yEnd, RADAR_RANGE / 2)
 
-  // 转换到画布坐标系
   const radarRect: RadarRect = {
     x_cm_start: clampedXStart,
     x_cm_stop: clampedXEnd,
@@ -551,10 +564,6 @@ const drawCoordinateAxes = () => {
   ctx.setLineDash([]) // 恢复实线样式
 }
 
-/**
- * 监听属性变化
- * 当坐标、方向或模式变化时重新绘制检测区域
- */
 watch(
   () => [props.coordinates, props.direction, props.mode],
   () => {
@@ -582,6 +591,7 @@ defineExpose({
     convert_furniture_r2c(furniture, radarPosition),
   convert_furniture_c2r: (furniture: CanvasRect) => convert_furniture_c2r(furniture, radarPosition),
   radarPosition,
+  calculateBoundingBox,
 })
 </script>
 
@@ -620,6 +630,22 @@ defineExpose({
 
       .furniture-item {
         position: absolute;
+
+        .furniture-rotated-container {
+          position: absolute;
+
+          .furniture-wrapper {
+            width: 100%;
+            height: 100%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+
+            :deep(*) {
+              pointer-events: none;
+            }
+          }
+        }
       }
     }
 

+ 7 - 9
src/components/EditableSubregion/index.vue

@@ -7,8 +7,6 @@
         height: `${canvasSize}px`,
         cursor: !editable ? 'no-drop' : isCreating ? 'crosshair' : 'default',
         position: 'relative',
-        border: '1px solid #ccc',
-        background: 'rgba(242, 242, 240, 0.5)',
       }"
       @mousedown="handleMouseDown"
     >
@@ -509,13 +507,13 @@ onMounted(() => {
   gap: 20px;
 }
 
-.mapBox {
-  position: relative;
-  flex-shrink: 0;
-  border: 1px solid #ddd;
-  background-color: rgba(242, 242, 240, 0.5);
-  pointer-events: auto;
-}
+// .mapBox {
+//   position: relative;
+//   flex-shrink: 0;
+//   border: 1px solid #ddd;
+//   background-color: rgba(242, 242, 240, 0.5);
+//   pointer-events: auto;
+// }
 
 .temp-block {
   position: absolute;

+ 255 - 190
src/components/RadarEditor/index.vue

@@ -48,6 +48,7 @@
           <a-radio-button :value="1">配置面板</a-radio-button>
           <a-radio-button :value="2">信息面板</a-radio-button>
         </a-radio-group>
+        <a-button type="link" size="small" @click="syncCoordinates">同步坐标</a-button>
       </div>
 
       <div v-if="modeRadio === 1" class="config">
@@ -89,8 +90,8 @@
           <div class="panel-ct">
             <div class="subregionTool">
               <div v-if="localSubRegions.length === 0">
-                暂无区域,立即<a-button type="link" size="small" @click="createSubregion"
-                  >新建</a-button
+                暂无区域,<a-button type="link" size="small" @click="createSubregion"
+                  >新建区域</a-button
                 >
               </div>
               <div v-else>
@@ -100,7 +101,7 @@
                   type="link"
                   size="small"
                   @click="createSubregion"
-                  >继续建</a-button
+                  >继续建</a-button
                 >
               </div>
             </div>
@@ -110,221 +111,256 @@
 
       <div v-if="modeRadio === 2" class="info">
         <div class="panel">
-          <div class="panel-hd"
-            >家具列表 <span v-if="localFurniture.length">({{ localFurniture.length }})</span></div
-          >
+          <div class="panel-hd">
+            <div
+              >家具列表 <span v-if="localFurniture.length">({{ localFurniture.length }})</span></div
+            >
+            <a-space>
+              <a-popconfirm
+                v-if="localFurniture.length"
+                title="确定清空家具吗?"
+                @confirm="clearFurniture"
+              >
+                <a-button size="small" type="link">清空</a-button>
+              </a-popconfirm>
+              <a-button size="small" type="link" @click="modeRadio = 1">添加</a-button>
+            </a-space>
+          </div>
           <div class="panel-ct">
-            <div v-for="(furniture, index) in localFurniture" :key="index" class="list-item">
-              <a-collapse v-model:activeKey="furnitureActiveKey" ghost>
-                <a-collapse-panel :key="index + 2" :header="`${furniture.name} 属性`">
-                  <div class="mapConfig">
-                    <div class="mapConfig-item">
-                      <label class="mapConfig-item-label">家具尺寸:</label>
-                      <div class="mapConfig-item-content">
-                        <a-space>
-                          <a-input-number
-                            v-model:value="furniture.width"
-                            :min="10"
-                            size="small"
-                            :style="inputStyle"
-                          />
-                          <a-input-number
-                            v-model:value="furniture.length"
-                            :min="10"
-                            size="small"
-                            :style="inputStyle"
-                          />
-                        </a-space>
-                      </div>
-                    </div>
-
-                    <div class="mapConfig-item">
-                      <label class="mapConfig-item-label">家具旋转:</label>
-                      <div class="mapConfig-item-content">
-                        <a-select v-model:value="furniture.rotate" size="small" :style="inputStyle">
-                          <a-select-option :value="0">0°</a-select-option>
-                          <a-select-option :value="90">90°</a-select-option>
-                          <a-select-option :value="180">180°</a-select-option>
-                          <a-select-option :value="270">270°</a-select-option>
-                        </a-select>
+            <template v-if="localFurniture.length">
+              <div v-for="(furniture, index) in localFurniture" :key="index" class="list-item">
+                <a-collapse v-model:activeKey="furnitureActiveKey" ghost>
+                  <a-collapse-panel :key="index + 1" :header="`${furniture.name} 属性`">
+                    <div class="mapConfig">
+                      <div class="mapConfig-item">
+                        <label class="mapConfig-item-label">家具尺寸:</label>
+                        <div class="mapConfig-item-content">
+                          <a-space>
+                            <a-input-number
+                              v-model:value="furniture.width"
+                              :min="10"
+                              size="small"
+                              :style="inputStyle"
+                            />
+                            <a-input-number
+                              v-model:value="furniture.length"
+                              :min="10"
+                              size="small"
+                              :style="inputStyle"
+                            />
+                          </a-space>
+                        </div>
                       </div>
-                    </div>
-
-                    <div class="mapConfig-item">
-                      <label class="mapConfig-item-label">位置微调:</label>
-                      <div class="mapConfig-item-content"></div>
-                      <a-space>
-                        <ArrowLeftOutlined @click="nudge('left')" />
-                        <ArrowUpOutlined @click="nudge('up')" />
-                        <ArrowDownOutlined @click="nudge('down')" />
-                        <ArrowRightOutlined @click="nudge('right')" />
-                        <a-input-number
-                          v-model:value="nudgeStep"
-                          :min="1"
-                          size="small"
-                          style="width: 60px"
-                        />
-                      </a-space>
-                    </div>
 
-                    <div class="mapConfig-item">
-                      <label class="mapConfig-item-label">left/top:</label>
-                      <div class="mapConfig-item-content">
-                        <a-space>
-                          <a-input-number
-                            v-model:value="furniture.left"
-                            disabled
+                      <div class="mapConfig-item">
+                        <label class="mapConfig-item-label">家具旋转:</label>
+                        <div class="mapConfig-item-content">
+                          <a-select
+                            v-model:value="furniture.rotate"
                             size="small"
                             :style="inputStyle"
-                          />
-                          <a-input-number
-                            v-model:value="furniture.top"
-                            disabled
-                            size="small"
-                            :style="inputStyle"
-                          />
-                        </a-space>
+                          >
+                            <a-select-option :value="0">0°</a-select-option>
+                            <a-select-option :value="90">90°</a-select-option>
+                            <a-select-option :value="180">180°</a-select-option>
+                            <a-select-option :value="270">270°</a-select-option>
+                          </a-select>
+                        </div>
                       </div>
-                    </div>
 
-                    <div class="mapConfig-item">
-                      <label class="mapConfig-item-label">x/y:</label>
-                      <div class="mapConfig-item-content">
+                      <div class="mapConfig-item">
+                        <label class="mapConfig-item-label">位置微调:</label>
+                        <div class="mapConfig-item-content"></div>
                         <a-space>
+                          <ArrowLeftOutlined @click="nudge('left')" />
+                          <ArrowUpOutlined @click="nudge('up')" />
+                          <ArrowDownOutlined @click="nudge('down')" />
+                          <ArrowRightOutlined @click="nudge('right')" />
                           <a-input-number
-                            v-model:value="furniture.x"
+                            v-model:value="nudgeStep"
+                            :min="1"
                             size="small"
-                            :style="inputStyle"
-                          />
-                          <a-input-number
-                            v-model:value="furniture.y"
-                            size="small"
-                            :style="inputStyle"
+                            style="width: 60px"
                           />
                         </a-space>
                       </div>
-                    </div>
 
-                    <div class="mapConfig-item">
-                      <label class="mapConfig-item-label">操作:</label>
-                      <div class="mapConfig-item-content">
-                        <a-space>
-                          <a-popconfirm
-                            title="确定删除家具吗?"
-                            @confirm="deleteFurniture(furniture.nanoid!)"
-                          >
-                            <DeleteOutlined />
-                          </a-popconfirm>
-                        </a-space>
+                      <div class="mapConfig-item">
+                        <label class="mapConfig-item-label">left/top:</label>
+                        <div class="mapConfig-item-content">
+                          <a-space>
+                            <a-input-number
+                              v-model:value="furniture.left"
+                              disabled
+                              size="small"
+                              :style="inputStyle"
+                            />
+                            <a-input-number
+                              v-model:value="furniture.top"
+                              disabled
+                              size="small"
+                              :style="inputStyle"
+                            />
+                          </a-space>
+                        </div>
+                      </div>
+
+                      <div class="mapConfig-item">
+                        <label class="mapConfig-item-label">x/y:</label>
+                        <div class="mapConfig-item-content">
+                          <a-space>
+                            <a-input-number
+                              v-model:value="furniture.x"
+                              size="small"
+                              :style="inputStyle"
+                            />
+                            <a-input-number
+                              v-model:value="furniture.y"
+                              size="small"
+                              :style="inputStyle"
+                            />
+                          </a-space>
+                        </div>
+                      </div>
+
+                      <div class="mapConfig-item">
+                        <label class="mapConfig-item-label">操作:</label>
+                        <div class="mapConfig-item-content">
+                          <a-space>
+                            <a-popconfirm
+                              title="确定删除家具吗?"
+                              @confirm="deleteFurniture(furniture.nanoid!)"
+                            >
+                              <DeleteOutlined />
+                            </a-popconfirm>
+                          </a-space>
+                        </div>
                       </div>
                     </div>
-                  </div>
-                </a-collapse-panel>
-              </a-collapse>
-            </div>
+                  </a-collapse-panel>
+                </a-collapse>
+              </div>
+            </template>
+            <div v-else class="list-empty">暂无家具</div>
           </div>
         </div>
 
         <div class="panel">
-          <div class="panel-hd"
-            >区域列表 <span v-if="localSubRegions.length">({{ localSubRegions.length }})</span></div
-          >
+          <div class="panel-hd">
+            <div
+              >区域列表
+              <span v-if="localSubRegions.length">({{ localSubRegions.length }})</span></div
+            >
+            <a-space>
+              <a-popconfirm
+                v-if="localSubRegions.length"
+                title="确定清空子区域吗?"
+                @confirm="clearSubregions"
+              >
+                <a-button size="small" type="link">清空</a-button>
+              </a-popconfirm>
+              <a-button size="small" type="link" @click="createSubregion">新建</a-button>
+            </a-space>
+          </div>
           <div class="panel-ct">
-            <div v-for="(region, index) in localSubRegions" :key="index" class="list-item">
-              <a-collapse v-model:activeKey="regionActiveKey" ghost>
-                <a-collapse-panel :key="index + 2" :header="`子区域 ${index + 1} 属性`">
-                  <div class="mapConfig">
-                    <div class="mapConfig-item">
-                      <div class="mapConfig-item-label">X范围:</div>
-                      <div class="mapConfig-item-content">
-                        <a-space>
-                          <a-input
-                            v-model:value.trim="region.startXx"
-                            :style="inputStyle"
-                            size="small"
-                          />
-
-                          <a-input
-                            v-model:value.trim="region.stopXx"
-                            :style="inputStyle"
-                            size="small"
-                          />
-                        </a-space>
+            <template v-if="localSubRegions.length">
+              <div v-for="(region, index) in localSubRegions" :key="index" class="list-item">
+                <a-collapse v-model:activeKey="regionActiveKey" ghost>
+                  <a-collapse-panel :key="index + 1" :header="`子区域 ${index + 1} 属性`">
+                    <div class="mapConfig">
+                      <div class="mapConfig-item">
+                        <div class="mapConfig-item-label">X范围:</div>
+                        <div class="mapConfig-item-content">
+                          <a-space>
+                            <a-input
+                              v-model:value.trim="region.startXx"
+                              :style="inputStyle"
+                              size="small"
+                            />
+
+                            <a-input
+                              v-model:value.trim="region.stopXx"
+                              :style="inputStyle"
+                              size="small"
+                            />
+                          </a-space>
+                        </div>
                       </div>
-                    </div>
 
-                    <div class="mapConfig-item">
-                      <div class="mapConfig-item-label">Y范围:</div>
-                      <div class="mapConfig-item-content">
-                        <a-space>
-                          <a-input
-                            v-model:value.trim="region.startYy"
-                            :style="inputStyle"
-                            size="small"
-                          />
-
-                          <a-input
-                            v-model:value.trim="region.stopYy"
-                            :style="inputStyle"
-                            size="small"
-                          />
-                        </a-space>
+                      <div class="mapConfig-item">
+                        <div class="mapConfig-item-label">Y范围:</div>
+                        <div class="mapConfig-item-content">
+                          <a-space>
+                            <a-input
+                              v-model:value.trim="region.startYy"
+                              :style="inputStyle"
+                              size="small"
+                            />
+
+                            <a-input
+                              v-model:value.trim="region.stopYy"
+                              :style="inputStyle"
+                              size="small"
+                            />
+                          </a-space>
+                        </div>
                       </div>
-                    </div>
-
-                    <div class="mapConfig-item">
-                      <div class="mapConfig-item-label">Z范围:</div>
-                      <div class="mapConfig-item-content">
-                        <a-space>
-                          <a-input
-                            v-model:value.trim="region.startZz"
-                            :style="inputStyle"
-                            size="small"
-                          />
 
-                          <a-input
-                            v-model:value.trim="region.stopZz"
-                            :style="inputStyle"
-                            size="small"
-                          />
-                        </a-space>
+                      <div class="mapConfig-item">
+                        <div class="mapConfig-item-label">Z范围:</div>
+                        <div class="mapConfig-item-content">
+                          <a-space>
+                            <a-input
+                              v-model:value.trim="region.startZz"
+                              :style="inputStyle"
+                              size="small"
+                            />
+
+                            <a-input
+                              v-model:value.trim="region.stopZz"
+                              :style="inputStyle"
+                              size="small"
+                            />
+                          </a-space>
+                        </div>
                       </div>
-                    </div>
 
-                    <div class="mapConfig-item">
-                      <div class="mapConfig-item-label">区域跟踪:</div>
-                      <div class="mapConfig-item-content">
-                        <a-switch v-model:checked="region.isTracking" size="small" />
+                      <div class="mapConfig-item">
+                        <div class="mapConfig-item-label">区域跟踪:</div>
+                        <div class="mapConfig-item-content">
+                          <a-switch v-model:checked="region.isTracking" size="small" />
+                        </div>
                       </div>
-                    </div>
 
-                    <div class="mapConfig-item">
-                      <div class="mapConfig-item-label">区域跌倒:</div>
-                      <div class="mapConfig-item-content">
-                        <a-switch v-model:checked="region.isFalling" size="small" />
+                      <div class="mapConfig-item">
+                        <div class="mapConfig-item-label">区域跌倒:</div>
+                        <div class="mapConfig-item-content">
+                          <a-switch v-model:checked="region.isFalling" size="small" />
+                        </div>
                       </div>
-                    </div>
 
-                    <div v-if="region.isBed" class="mapConfig-item">
-                      <div class="mapConfig-item-label">呼吸检测:</div>
-                      <div class="mapConfig-item-content"> 默认开启 </div>
-                    </div>
+                      <div v-if="region.isBed" class="mapConfig-item">
+                        <div class="mapConfig-item-label">呼吸检测:</div>
+                        <div class="mapConfig-item-content"> 默认开启 </div>
+                      </div>
 
-                    <div class="mapConfig-item">
-                      <div class="mapConfig-item-label">删除区域:</div>
-                      <div class="mapConfig-item-content">
-                        <a-popconfirm
-                          title="确定删除区域吗?"
-                          @confirm="deleteBlockArea(region.id || '')"
-                        >
-                          <DeleteOutlined />
-                        </a-popconfirm>
+                      <div class="mapConfig-item">
+                        <div class="mapConfig-item-label">删除区域:</div>
+                        <div class="mapConfig-item-content">
+                          <a-popconfirm
+                            title="确定删除区域吗?"
+                            @confirm="deleteBlockArea(region.id || '')"
+                          >
+                            <DeleteOutlined />
+                          </a-popconfirm>
+                        </div>
                       </div>
                     </div>
-                  </div>
-                </a-collapse-panel>
-              </a-collapse>
-            </div>
+                  </a-collapse-panel>
+                </a-collapse>
+              </div>
+            </template>
+            <div v-else class="list-empty">暂无子区域</div>
           </div>
         </div>
       </div>
@@ -371,6 +407,8 @@ const emit = defineEmits<{
 const localFurniture = ref<FurnitureItem[]>(props.furnitureItems ?? [])
 const localSubRegions = ref<SubRegions[]>(props.subRegions ?? [])
 
+console.log('props', props)
+
 const inputStyle = computed(() => ({ width: '70px' }))
 const pixelPosition = reactive({ left: 0, top: 0 })
 const nudgeStep = ref(5)
@@ -539,7 +577,7 @@ onUnmounted(() => {
   localStorage.removeItem('subRegions')
 })
 
-const regionActiveKey = ref<number[]>([0])
+const regionActiveKey = ref<number[]>([])
 const furnitureActiveKey = ref<number[]>([])
 
 const deleteBlockArea = (id: string) => {
@@ -548,10 +586,21 @@ const deleteBlockArea = (id: string) => {
   }
 }
 
-const modeRadioChange = (e: Event) => {
-  const value = (e.target as HTMLInputElement).value as unknown as 1 | 2 | 3
-  console.log(11111111, value)
-  regionActiveKey.value = [0]
+const modeRadioChange = () => {
+  regionActiveKey.value = []
+  furnitureActiveKey.value = []
+}
+
+const syncCoordinates = () => {
+  console.log('同步坐标', localFurniture.value, localSubRegions.value)
+}
+
+const clearSubregions = () => {
+  localSubRegions.value = []
+}
+
+const clearFurniture = () => {
+  localFurniture.value = []
 }
 </script>
 
@@ -597,7 +646,6 @@ const modeRadioChange = (e: Event) => {
     .header {
       display: flex;
       align-items: center;
-      justify-content: space-between;
       margin-bottom: 12px;
     }
   }
@@ -611,11 +659,20 @@ const modeRadioChange = (e: Event) => {
     color: #666;
     line-height: 1.5;
     padding: 5px 8px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    .ant-btn-link {
+      font-size: 12px;
+      line-height: 1.5;
+      padding: 0;
+    }
   }
   &-ct {
     display: flex;
     flex-direction: column;
     gap: 8px;
+    min-height: 30px;
     max-height: 300px;
     overflow-y: auto;
 
@@ -666,6 +723,14 @@ const modeRadioChange = (e: Event) => {
     background-color: #f5f5f5;
     cursor: pointer;
   }
+
+  .list-empty {
+    padding: 8px 12px;
+    font-size: 14px;
+    color: #999;
+    border-radius: 8px;
+    background-color: #f5f5f5;
+  }
 }
 
 .mapConfig {

+ 5 - 2
src/views/community/components/add/index.vue

@@ -228,6 +228,7 @@ watch(
   (val) => {
     if (val) {
       console.log('PROPS', props)
+      fetchRoleList()
       fetchDict()
       if (Object.keys(props.data).length !== 0) {
         if (props.type === 'tenant') {
@@ -272,7 +273,9 @@ const roleList = ref<{ label: string; value: string }[]>([])
 // 查询角色下拉列表
 const fetchRoleList = async () => {
   try {
-    const res = await systemApi.queryRoleList()
+    const res = await systemApi.queryRoleList({
+      tenantId: props.data.tenantId + '',
+    })
     console.log('获取角色列表成功', res)
     const data = res.data
     roleList.value =
@@ -286,7 +289,7 @@ const fetchRoleList = async () => {
     console.log('获取角色列表失败', err)
   }
 }
-fetchRoleList()
+// fetchRoleList()
 
 const isEditAdminForm = ref<boolean>(true) // 管理员表单模式:查看、编辑
 // 查询是否存在管理员信息

+ 4 - 4
src/views/device/detail/components/deviceAreaConfig/index.vue

@@ -389,7 +389,7 @@ const fetchRoomLayout = async () => {
 }
 fetchRoomLayout().finally(() => {
   // 获取房间信息后,初始化雷达图标
-  initRadarIcon()
+  // initRadarIcon()
 })
 
 interface CanvaseItem {
@@ -702,9 +702,9 @@ const { originX, originY } = getOriginPosition(props.ranges, [
 ])
 
 // 初始化添加雷达图标
-const initRadarIcon = () => {
-  console.log('initRadarIcon', mapCanvasList.value, furnitureItems.value)
-}
+// const initRadarIcon = () => {
+//   console.log('initRadarIcon', mapCanvasList.value, furnitureItems.value)
+// }
 
 // 保存所有配置
 const saveAllConfig = () => {

+ 23 - 23
src/views/device/detail/index.vue

@@ -36,7 +36,7 @@
               detailState.yyStart,
               detailState.yyEnd,
             ]"
-            :direction="detailState.northAngle || 0"
+            :direction="270"
             :furnitureItems="furnitureItems"
             :targets="Object.values(targets)"
           ></DetectionAreaView>
@@ -313,7 +313,7 @@ import { EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
 import * as alarmApi from '@/api/alarm'
 import { Empty } from 'ant-design-vue'
 const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE
-import { getOriginPosition } from '@/utils'
+// import { getOriginPosition } from '@/utils'
 import { useDict } from '@/hooks/useDict'
 import DeviceUpgrade from './components/DeviceUpgrade/index.vue'
 
@@ -439,30 +439,30 @@ const fetchDeviceDetail = async () => {
     spinning.value = false
 
     // 获取雷达图标尺寸
-    const { radarX, radarY, radarWidth, radarHeight, originOffsetX, originOffsetY } =
-      getOriginPosition(
-        [
-          res.data.xxStart as number,
-          res.data.xxEnd as number,
-          res.data.yyStart as number,
-          res.data.yyEnd as number,
-        ],
-        [0, 0]
-      )
+    // const { radarX, radarY, radarWidth, radarHeight, originOffsetX, originOffsetY } =
+    //   getOriginPosition(
+    //     [
+    //       res.data.xxStart as number,
+    //       res.data.xxEnd as number,
+    //       res.data.yyStart as number,
+    //       res.data.yyEnd as number,
+    //     ],
+    //     [0, 0]
+    //   )
 
     furnitureItems.value = furnitureItems.value.filter((item) => item.type !== 'radar')
     // 添加雷达图标
-    furnitureItems.value.unshift({
-      name: '雷达',
-      type: 'radar',
-      width: radarWidth,
-      length: radarHeight,
-      top: radarY,
-      left: radarX,
-      x: originOffsetX,
-      y: originOffsetY,
-      rotate: 0,
-    })
+    // furnitureItems.value.unshift({
+    //   name: '雷达',
+    //   type: 'radar',
+    //   width: radarWidth,
+    //   length: radarHeight,
+    //   top: radarY,
+    //   left: radarX,
+    //   x: originOffsetX,
+    //   y: originOffsetY,
+    //   rotate: 0,
+    // })
   } catch (error) {
     console.error('❌获取设备详情失败', error)
     spinning.value = false

+ 4 - 4
src/views/device/list/index.vue

@@ -102,7 +102,7 @@
             <a-button type="link" @click="detailHandler(record.devId, record.clientId)"
               >查看详情</a-button
             >
-            <a-button type="link" @click="unbindDeviceHandler(record)">解绑设备</a-button>
+            <a-button type="link" @click="unbindDeviceHandler(record)">抹掉配置</a-button>
           </template>
         </template>
       </a-table>
@@ -132,7 +132,7 @@
 
     <OTADeviceModal v-model:open="otaDeviceOpen" title="设备OTA升级"></OTADeviceModal>
 
-    <baseModal v-model:open="unbindOpen" title="解绑设备">
+    <baseModal v-model:open="unbindOpen" title="恢复出厂">
       <a-descriptions title="" bordered :column="1" size="middle">
         <a-descriptions-item label="设备ID">{{ unbindDeviceData.devId }}</a-descriptions-item>
         <a-descriptions-item label="设备名称">{{ unbindDeviceData.devName }}</a-descriptions-item>
@@ -152,12 +152,12 @@
         <a-space class="unbindDevice-btn">
           <a-button @click="unbindOpen = false">取消</a-button>
           <a-popconfirm
-            title="确认解绑该设备吗?"
+            title="确认恢复出厂设置吗?"
             ok-text="确认"
             cancel-text="取消"
             @confirm="confirmUnbindDevice(unbindDeviceData.devId)"
           >
-            <a-button type="primary">解绑</a-button>
+            <a-button type="primary">恢复出厂</a-button>
           </a-popconfirm>
         </a-space>
       </template>