Jelajahi Sumber

feat: 家具编辑组件开发;

liujia 4 minggu lalu
induk
melakukan
7d0a20d

+ 2 - 0
components.d.ts

@@ -65,9 +65,11 @@ declare module 'vue' {
     BaseWeather: typeof import('./src/components/baseWeather/index.vue')['default']
     Copyright: typeof import('./src/components/Copyright/index.vue')['default']
     ECharts: typeof import('./src/components/baseCard/components/e-charts/index.vue')['default']
+    EditableFurniture: typeof import('./src/components/EditableFurniture/index.vue')['default']
     FurnitureIcon: typeof import('./src/components/furnitureIcon/index.vue')['default']
     FurnitureItem: typeof import('./src/components/furnitureItem/index.vue')['default']
     FurnitureList: typeof import('./src/components/furnitureList/index.vue')['default']
+    RadarEditor: typeof import('./src/components/RadarEditor/index.vue')['default']
     RadarPointCloud: typeof import('./src/components/radarPointCloud/index.vue')['default']
     RadarView: typeof import('./src/components/RadarView/index.vue')['default']
     RangePicker: typeof import('./src/components/rangePicker/index.vue')['default']

+ 551 - 0
src/components/EditableFurniture/index.vue

@@ -0,0 +1,551 @@
+<template>
+  <div
+    class="editable-furniture"
+    :style="{
+      position: 'relative',
+      left: `${centerCss.left}px`,
+      top: `${centerCss.top}px`,
+      zIndex: 10,
+      pointerEvents: 'auto',
+    }"
+  >
+    <div
+      class="furniture-wrapper"
+      :style="wrapperStyle"
+      @mousedown.stop="startDrag"
+      @dblclick.stop="showPanel = !showPanel"
+    >
+      <furnitureIcon
+        :icon="localItem.type as FurnitureIconType"
+        :width="localItem.width"
+        :height="localItem.length"
+      />
+    </div>
+
+    <div
+      v-if="showPanel"
+      class="property-panel"
+      :style="{ left: `${panelPosition.left}px`, top: `${panelPosition.top}px` }"
+      @mousedown.stop
+    >
+      <div class="panel-header" @mousedown.prevent="startPanelDrag">
+        <div class="title">家具属性</div>
+        <div class="close" @click="showPanel = false">×</div>
+        <div class="capsule" @mousedown.prevent="startPanelDrag"></div>
+      </div>
+
+      <div class="panel-item">
+        <label>家具名称:</label>
+        <a-input v-model:value="localItem.name" size="small" />
+      </div>
+
+      <div class="panel-item">
+        <label>家具尺寸:</label>
+        <a-space>
+          <a-input-number
+            v-model:value="localItem.width"
+            :min="10"
+            size="small"
+            @change="onSizeChange"
+          />
+          <a-input-number
+            v-model:value="localItem.length"
+            :min="10"
+            size="small"
+            @change="onSizeChange"
+          />
+        </a-space>
+      </div>
+
+      <div class="panel-item">
+        <label>旋转角度:</label>
+        <a-select v-model:value="localItem.rotate" size="small">
+          <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 class="panel-item">
+        <label>位置微调:</label>
+        <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="panel-item">
+        <label>平面坐标:</label>
+        <a-space>
+          x:<a-input-number v-model:value="localItem.x" @change="updateByFlatCoords" size="small" />
+          y:<a-input-number v-model:value="localItem.y" @change="updateByFlatCoords" size="small" />
+        </a-space>
+      </div>
+
+      <div class="panel-item">
+        <label>操作:</label>
+        <a-space>
+          <a-popconfirm title="确定要删除该家具吗?" @confirm="emit('delete', localItem.nanoid!)">
+            <a-button danger size="small">删除</a-button>
+          </a-popconfirm>
+        </a-space>
+      </div>
+
+      <pre class="debug">{{ localItem }}</pre>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { reactive, watch, ref, computed, onMounted, nextTick, type CSSProperties } from 'vue'
+import type { FurnitureItem } from '@/types/radar'
+import furnitureIcon from '../furnitureIcon/index.vue'
+import type { FurnitureIconType } from '@/types/furniture'
+import {
+  ArrowLeftOutlined,
+  ArrowUpOutlined,
+  ArrowDownOutlined,
+  ArrowRightOutlined,
+} from '@ant-design/icons-vue'
+import { useRadarCoordinateTransform } from '@/hooks/useRadarCoordinateTransform'
+
+defineOptions({ name: 'EditableFurniture' })
+
+interface Props {
+  item: FurnitureItem
+  angle: number
+  coordinates: [number, number, number, number]
+  initialAtOrigin?: boolean
+}
+const props = defineProps<Props>()
+const emit = defineEmits<{
+  (e: 'update', item: FurnitureItem & { left?: number; top?: number }): void
+  (e: 'delete', nanoid: string): void
+}>()
+
+const showPanel = ref(false)
+const nudgeStep = ref(5)
+
+// 扩展类型:在组件内部我们也维护页面像素 left/top(可选)
+type UIItem = FurnitureItem & { left?: number; top?: number }
+
+// localItem 保留业务语义 x/y 为业务左上角,同时维护 left/top 为页面像素(px)
+const localItem = reactive<UIItem>({
+  ...props.item,
+  x: props.item.x ?? 0,
+  y: props.item.y ?? 0,
+  width: props.item.width ?? 100,
+  length: props.item.length ?? 100,
+  rotate: props.item.rotate ?? 0,
+  left: (props.item as UIItem).left ?? 0,
+  top: (props.item as UIItem).top ?? 0,
+})
+
+watch(
+  () => props.item,
+  (next) => {
+    if (!next) return
+
+    // 显式解构预期要合并的字段
+    const { x, y, width, length, rotate, name, type, nanoid, left, top } = next as Partial<UIItem>
+
+    if (x !== undefined) localItem.x = x
+    if (y !== undefined) localItem.y = y
+    if (width !== undefined) localItem.width = width
+    if (length !== undefined) localItem.length = length
+    if (rotate !== undefined) localItem.rotate = rotate
+    if (name !== undefined) localItem.name = name
+    if (type !== undefined) localItem.type = type
+    if (nanoid !== undefined) localItem.nanoid = nanoid
+    if (left !== undefined) localItem.left = left
+    if (top !== undefined) localItem.top = top
+  },
+  { deep: true }
+)
+
+// transforms(editor <-> radar <-> css)
+let transforms = useRadarCoordinateTransform(props.angle, props.coordinates)
+watch(
+  () => [props.angle, props.coordinates] as const,
+  () => {
+    transforms = useRadarCoordinateTransform(props.angle, props.coordinates)
+    nextTick()
+      .then(() => new Promise<void>((r) => requestAnimationFrame(() => r())))
+      .then(() => new Promise((r) => setTimeout(r, 0)))
+      .then(() => {
+        updateCssFromXY()
+        setPanelInitialPosition()
+      })
+  },
+  { deep: true }
+)
+
+// 渲染用中心点(DOM center,units: px)
+const centerCss = reactive({ left: 0, top: 0 })
+
+// 面板位置(相对于 outer 根容器,即相对于 centerCss)
+const panelPosition = reactive({ left: 0, top: 0 })
+
+const DECIMALS = 4
+const EPSILON = 1e-10
+function norm(n: number): number {
+  const r = Math.round(n * Math.pow(10, DECIMALS)) / Math.pow(10, DECIMALS)
+  return Math.abs(r) < EPSILON ? 0 : r
+}
+function normXY(x: number, y: number): { x: number; y: number } {
+  return { x: norm(x), y: norm(y) }
+}
+
+// 旋转矩阵(编辑器坐标:Y 向上为正)
+function rot(x: number, y: number, deg: number) {
+  const rad = (deg * Math.PI) / 180
+  return {
+    x: x * Math.cos(rad) - y * Math.sin(rad),
+    y: x * Math.sin(rad) + y * Math.cos(rad),
+  }
+}
+
+// 计算四角相对中心偏移(editor 坐标)
+function rotatedCornersOffsets(rotate: number, w: number, h: number) {
+  return {
+    topLeft: rot(-w / 2, +h / 2, rotate),
+    topRight: rot(+w / 2, +h / 2, rotate),
+    bottomRight: rot(+w / 2, -h / 2, rotate),
+    bottomLeft: rot(-w / 2, -h / 2, rotate),
+  }
+}
+
+// ------------- 业务坐标系 <-> 编辑器坐标系 映射 -------------
+function businessToEditor(x: number, y: number) {
+  return rot(x, y, 90)
+}
+function editorToBusiness(x: number, y: number) {
+  return rot(x, y, -90)
+}
+// -----------------------------------------------------------
+
+// helper: 把业务左上角 -> 页面左上角(cssTopLeft)
+function businessTopLeftToCssTopLeft(bizX: number, bizY: number) {
+  const editorPt = businessToEditor(bizX, bizY)
+  const radarPt = transforms.editorToRadar(editorPt.x, editorPt.y)
+  const cssTopLeft = transforms.radarToCss(radarPt.x, radarPt.y)
+  return cssTopLeft
+}
+
+// updateCssFromXY:计算 cssTopLeft, 更新 centerCss,并写回 localItem.left/top(方案 A)
+function updateCssFromXY(): void {
+  const w = localItem.width ?? 0
+  const h = localItem.length ?? 0
+
+  const bizLeft = localItem.x ?? 0
+  const bizTop = localItem.y ?? 0
+
+  const cssTopLeft = businessTopLeftToCssTopLeft(bizLeft, bizTop)
+
+  // 写回页面像素(left/top),与 RadarView 的字段语义一致
+  localItem.left = cssTopLeft.left
+  localItem.top = cssTopLeft.top
+
+  // centerCss = cssTopLeft + half size (wrapper uses translate(-50%,-50%))
+  centerCss.left = cssTopLeft.left + w / 2
+  centerCss.top = cssTopLeft.top + h / 2
+}
+
+// 拖拽回写:centerCss -> cssTopLeft -> biz -> 回写 localItem.x/y;同时更新 left/top(页面像素)
+function updateXYFromCss(): void {
+  const w = localItem.width ?? 0
+  const h = localItem.length ?? 0
+
+  const cssTopLeftX = centerCss.left - w / 2
+  const cssTopLeftY = centerCss.top - h / 2
+
+  const radarPt = transforms.cssToRadar(cssTopLeftX, cssTopLeftY)
+  const editorPt = transforms.radarToEditor(radarPt.x, radarPt.y)
+  const bizPt = editorToBusiness(editorPt.x, editorPt.y)
+
+  // 回写业务左上角
+  localItem.x = bizPt.x
+  localItem.y = bizPt.y
+  ;({ x: localItem.x, y: localItem.y } = normXY(localItem.x!, localItem.y!))
+
+  // 同步页面像素字段(保持和 RadarView 一致)
+  localItem.left = cssTopLeftX
+  localItem.top = cssTopLeftY
+}
+
+// 初始化面板位置(相对于 outer,即相对于 centerCss)
+function setPanelInitialPosition() {
+  const w = localItem.width ?? 0
+  const h = localItem.length ?? 0
+  panelPosition.left = Math.round(w / 2 + 50)
+  panelPosition.top = Math.round(-h / 2)
+}
+
+onMounted(() => {
+  // 使用父组件传入的业务左上角(若有)
+  localItem.x = props.item.x ?? localItem.x
+  localItem.y = props.item.y ?? localItem.y
+  ;({ x: localItem.x, y: localItem.y } = normXY(localItem.x!, localItem.y!))
+
+  if (props.initialAtOrigin) {
+    localItem.x = 0
+    localItem.y = 0
+  }
+
+  nextTick()
+    .then(() => new Promise<void>((r) => requestAnimationFrame(() => r())))
+    .then(() => updateCssFromXY())
+    .then(() => {
+      setPanelInitialPosition()
+      // emit initial state with left/top populated
+      emit('update', { ...localItem })
+    })
+})
+
+// wrapper style: 以 centerCss 为外层定位点,内部以中心旋转
+const wrapperStyle = computed(
+  () =>
+    ({
+      width: `${localItem.width}px`,
+      height: `${localItem.length}px`,
+      transform: `translate(-50%, -50%) rotate(${localItem.rotate}deg)`,
+      transformOrigin: '50% 50%',
+      cursor: 'move',
+      position: 'absolute',
+      left: '0px',
+      top: '0px',
+      display: 'flex',
+      alignItems: 'center',
+      justifyContent: 'center',
+    }) as CSSProperties
+)
+
+function updateByFlatCoords(): void {
+  ;({ x: localItem.x, y: localItem.y } = normXY(localItem.x!, localItem.y!))
+  updateCssFromXY()
+  emit('update', { ...localItem })
+}
+
+function onSizeChange() {
+  updateCssFromXY()
+  setPanelInitialPosition()
+  emit('update', { ...localItem })
+}
+
+// 旋转监听:保持业务中心不动并更新左上角与渲染中心,写回 left/top
+watch(
+  () => localItem.rotate,
+  (angle, oldAngle) => {
+    const w = localItem.width ?? 0
+    const h = localItem.length ?? 0
+
+    const offsetsOld = rotatedCornersOffsets(oldAngle ?? angle, w, h)
+    const topLeftOldEditor = offsetsOld.topLeft
+    const topLeftOldBiz = editorToBusiness(topLeftOldEditor.x, topLeftOldEditor.y)
+    const centerBizX = localItem.x! - topLeftOldBiz.x
+    const centerBizY = localItem.y! - topLeftOldBiz.y
+
+    const offsetsNew = rotatedCornersOffsets(angle, w, h)
+    const topLeftNewEditor = offsetsNew.topLeft
+    const topLeftNewBiz = editorToBusiness(topLeftNewEditor.x, topLeftNewEditor.y)
+
+    localItem.x = centerBizX + topLeftNewBiz.x
+    localItem.y = centerBizY + topLeftNewBiz.y
+    ;({ x: localItem.x, y: localItem.y } = normXY(localItem.x!, localItem.y!))
+
+    const editorCenter = businessToEditor(centerBizX, centerBizY)
+    const radarCenter = transforms.editorToRadar(editorCenter.x, editorCenter.y)
+    const css = transforms.radarToCss(radarCenter.x, radarCenter.y)
+
+    // 更新 centerCss + left/top
+    centerCss.left = css.left
+    centerCss.top = css.top
+    localItem.left = css.left - w / 2
+    localItem.top = css.top - h / 2
+
+    emit('update', { ...localItem })
+  }
+)
+
+// 拖拽家具:直接修改 centerCss 并回写业务坐标与 left/top
+function startDrag(e: MouseEvent): void {
+  e.preventDefault()
+  e.stopPropagation()
+  const startX = e.clientX
+  const startY = e.clientY
+  const initLeft = centerCss.left
+  const initTop = centerCss.top
+
+  const onMove = (ev: MouseEvent) => {
+    centerCss.left = initLeft + (ev.clientX - startX)
+    centerCss.top = initTop + (ev.clientY - startY)
+    updateXYFromCss()
+    // emit with updated business coords and page left/top
+    emit('update', { ...localItem })
+  }
+  const onUp = () => {
+    window.removeEventListener('mousemove', onMove)
+    window.removeEventListener('mouseup', onUp)
+    document.body.style.userSelect = ''
+  }
+  window.addEventListener('mousemove', onMove)
+  window.addEventListener('mouseup', onUp)
+  document.body.style.userSelect = 'none'
+}
+
+// 面板拖拽:只移动 panelPosition(相对于 outer,不影响家具)
+function startPanelDrag(e: MouseEvent) {
+  e.preventDefault()
+  e.stopPropagation()
+  const startX = e.clientX
+  const startY = e.clientY
+  const initLeft = panelPosition.left
+  const initTop = panelPosition.top
+
+  const onMove = (ev: MouseEvent) => {
+    panelPosition.left = initLeft + (ev.clientX - startX)
+    panelPosition.top = initTop + (ev.clientY - startY)
+  }
+  const onUp = () => {
+    window.removeEventListener('mousemove', onMove)
+    window.removeEventListener('mouseup', onUp)
+  }
+  window.addEventListener('mousemove', onMove)
+  window.addEventListener('mouseup', onUp)
+}
+
+// 微调
+function nudge(direction: 'left' | 'right' | 'up' | 'down'): void {
+  switch (direction) {
+    case 'left':
+      localItem.x! -= nudgeStep.value
+      break
+    case 'right':
+      localItem.x! += nudgeStep.value
+      break
+    case 'up':
+      localItem.y! += nudgeStep.value
+      break
+    case 'down':
+      localItem.y! -= nudgeStep.value
+      break
+  }
+  ;({ x: localItem.x, y: localItem.y } = normXY(localItem.x!, localItem.y!))
+  updateCssFromXY()
+  emit('update', { ...localItem })
+}
+</script>
+
+<style scoped lang="less">
+.editable-furniture {
+  width: 0;
+  height: 0;
+  position: relative;
+  .furniture-wrapper {
+    cursor: move;
+  }
+
+  .property-panel {
+    position: absolute;
+    top: 0;
+    left: 0;
+    background: #fff;
+    border-radius: 8px;
+    border: 1px solid #ddd;
+    padding: 5px 12px 12px;
+    font-size: 12px;
+    z-index: 1000;
+    width: 260px;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+    user-select: none;
+
+    .panel-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      font-weight: 600;
+      margin-bottom: 8px;
+      position: relative;
+      user-select: none;
+
+      .capsule {
+        cursor: move;
+        position: absolute;
+        top: -5px;
+        left: 50%;
+        transform: translateX(-50%);
+        border-radius: 10px;
+        width: 100px;
+        height: 8px;
+        background: rgba(230, 230, 230, 0.8);
+        backdrop-filter: blur(10px);
+      }
+
+      &:hover {
+        cursor: move;
+      }
+
+      .title {
+        font-size: 13px;
+      }
+
+      .close {
+        font-size: 22px;
+        color: #999;
+        cursor: pointer;
+        transition: color 0.2s;
+        &:hover {
+          color: #333;
+        }
+      }
+    }
+
+    .panel-item {
+      display: flex;
+      align-items: center;
+      margin-bottom: 6px;
+
+      label {
+        flex: 0 0 80px;
+        color: #666;
+      }
+
+      :deep(.ant-input-number),
+      :deep(.ant-select),
+      :deep(.ant-input) {
+        flex: 1;
+        width: 100%;
+      }
+
+      :deep(.ant-space) {
+        flex: 1;
+        display: flex;
+        gap: 4px;
+      }
+
+      :deep(.anticon) {
+        font-size: 16px;
+        color: #333;
+        cursor: pointer;
+        transition: color 0.2s;
+        &:hover {
+          color: #1890ff;
+        }
+      }
+    }
+
+    .panel-footer {
+      margin-top: 10px;
+      text-align: center;
+
+      :deep(.ant-btn) {
+        width: 100%;
+      }
+    }
+  }
+}
+</style>

+ 63 - 0
src/components/RadarEditor/index.vue

@@ -0,0 +1,63 @@
+<template>
+  <RadarView :coordinates="coordinates" :angle="angle">
+    <template v-for="item in localFurniture" :key="item.nanoid">
+      <EditableFurniture
+        :item="item"
+        :angle="angle"
+        :coordinates="coordinates"
+        @update="updateFurniture"
+        @delete="deleteFurniture"
+      />
+    </template>
+  </RadarView>
+</template>
+
+<script setup lang="ts">
+import { defineOptions, ref, watch } from 'vue'
+import type { FurnitureItem } from '@/types/radar'
+import RadarView from '../RadarView/index.vue'
+import EditableFurniture from '../EditableFurniture/index.vue'
+
+defineOptions({ name: 'RadarEditor' })
+
+interface Props {
+  coordinates: [number, number, number, number]
+  angle: number
+  furnitureItems?: FurnitureItem[]
+}
+const props = defineProps<Props>()
+const emit = defineEmits<{
+  (e: 'update:furnitureItems', items: FurnitureItem[]): void
+}>()
+
+const localFurniture = ref<FurnitureItem[]>(props.furnitureItems ?? [])
+
+watch(
+  () => props.furnitureItems,
+  (newVal) => {
+    if (newVal) localFurniture.value = [...newVal]
+  },
+  { deep: true }
+)
+
+function updateFurniture(updated: FurnitureItem) {
+  const list = localFurniture.value.map((i) => (i.nanoid === updated.nanoid ? updated : i))
+  localFurniture.value = list
+  emit('update:furnitureItems', list)
+}
+
+function deleteFurniture(nanoid: string) {
+  const list = localFurniture.value.filter((i) => i.nanoid !== nanoid)
+  localFurniture.value = list
+  emit('update:furnitureItems', list)
+}
+
+function addFurniture(item: FurnitureItem) {
+  localFurniture.value.push(item)
+  emit('update:furnitureItems', [...localFurniture.value])
+}
+
+defineExpose({ addFurniture })
+</script>
+
+<style scoped lang="less"></style>

+ 58 - 55
src/components/RadarView/index.vue

@@ -1,21 +1,21 @@
 <template>
   <div class="radar-view" :style="areaStyle">
+    <furniture-icon
+      icon="radar"
+      :width="radar.width"
+      :height="radar.length"
+      :style="{
+        left: `${radar.left}px`,
+        top: `${radar.top}px`,
+        position: 'absolute',
+        transform: `translate(-50%, -50%) rotate(${adjustedAngle + 90}deg)`,
+        cursor: 'default',
+        zIndex: 5,
+      }"
+      :draggable="false"
+    />
     <div class="furnitures">
       <furniture-icon
-        icon="radar"
-        :width="radar.width"
-        :height="radar.length"
-        :style="{
-          left: `${radar.left}px`,
-          top: `${radar.top}px`,
-          position: 'absolute',
-          transform: `translate(-50%, -50%) rotate(${adjustedAngle + 90}deg)`,
-          cursor: 'default',
-        }"
-        :draggable="false"
-      />
-
-      <furniture-icon
         v-for="item in filteredFurniture"
         :key="item.nanoid"
         :icon="item.type"
@@ -115,8 +115,8 @@ const yyStart = computed(() => props.coordinates[2])
 const yyEnd = computed(() => props.coordinates[3])
 
 // 区域宽高计算(单位:cm)
-const areaWidth = computed(() => xxEnd.value - xxStart.value)
-const areaHeight = computed(() => yyEnd.value - yyStart.value)
+const areaWidth = computed(() => Math.abs(xxEnd.value - xxStart.value))
+const areaHeight = computed(() => Math.abs(yyEnd.value - yyStart.value))
 // 雷达角度(默认值为 0°,表示正北朝上)
 const angle = computed(() => props.angle ?? 0)
 const adjustedAngle = computed(() => props.angle - 90)
@@ -228,24 +228,22 @@ const yArrow = computed(() => getDirectionLabelWithIcon('y', adjustedAngle.value
 function getFullAxisStyle(axis: 'x' | 'y') {
   const originX = radar.value.left
   const originY = radar.value.top
-  const normalized = ((adjustedAngle.value % 360) + 360) % 360
+  const boxWidth = areaWidth.value
+  const boxHeight = areaHeight.value
 
-  // 轴线长度:贯穿整个盒子
-  const length = axis === 'x' ? areaWidth.value : areaHeight.value
+  const angleDeg = axis === 'x' ? angle.value : (angle.value + 90) % 360
 
-  // 方向角度(以雷达为原点)
-  let rotateDeg = 0
-  if (axis === 'x') {
-    rotateDeg = normalized
-  } else {
-    rotateDeg = normalized - 90
-  }
+  const { dx, dy } = getDirectionVector(angleDeg)
+
+  // 最大延伸长度:对角线长度,确保覆盖整个盒子
+  const maxLength = Math.sqrt(boxWidth ** 2 + boxHeight ** 2)
+  const rotateDeg = Math.atan2(dy, dx) * (180 / Math.PI)
 
   return {
     position: 'absolute',
     left: `${originX}px`,
     top: `${originY}px`,
-    width: `${length}px`,
+    width: `${maxLength}px`,
     height: '1px',
     backgroundColor: axis === 'x' ? 'red' : 'blue',
     transform: `translate(-50%, -50%) rotate(${rotateDeg}deg)`,
@@ -255,39 +253,31 @@ function getFullAxisStyle(axis: 'x' | 'y') {
   } as CSSProperties
 }
 
-function getAxisDirectionVector(axis: 'x' | 'y', angleDeg: number) {
+function getDirectionVector(angleDeg: number): { dx: number; dy: number } {
   const rad = (angleDeg * Math.PI) / 180
-
-  // Y轴正方向是正北,X轴正方向是正东(默认)
-  // 所以我们旋转 Y轴 (0, 1) 和 X轴 (1, 0)
-  if (axis === 'y') {
-    return {
-      dx: Math.sin(rad), // Y轴旋转后 x 分量
-      dy: Math.cos(rad), // Y轴旋转后 y 分量
-    }
-  } else {
-    return {
-      dx: Math.cos(rad), // X轴旋转后 x 分量
-      dy: -Math.sin(rad), // X轴旋转后 y 分量(注意负号)
-    }
+  return {
+    dx: Math.sin(rad), // 东向为正
+    dy: -Math.cos(rad), // 北向为正
   }
 }
 
 function getAxisDotStyle(axis: 'x' | 'y') {
-  const radarX = radar.value.left
-  const radarY = radar.value.top
+  const originX = radar.value.left
+  const originY = radar.value.top
   const boxWidth = areaWidth.value
   const boxHeight = areaHeight.value
 
-  const { dx, dy } = getAxisDirectionVector(axis, adjustedAngle.value)
+  const angleDeg = axis === 'x' ? angle.value : (angle.value - 90) % 360
 
-  // 计算最大可延伸距离,避免超出盒子
-  const scaleX = dx !== 0 ? (dx > 0 ? boxWidth - radarX : -radarX) / dx : Infinity
-  const scaleY = dy !== 0 ? (dy > 0 ? boxHeight - radarY : -radarY) / dy : Infinity
-  const scale = Math.min(scaleX, scaleY)
+  const { dx, dy } = getDirectionVector(angleDeg)
 
-  const x = radarX + dx * scale
-  const y = radarY - dy * scale
+  // 计算终点比例,确保不超出盒子边界
+  const scaleX = dx > 0 ? (boxWidth - originX) / dx : dx < 0 ? -originX / dx : Infinity
+  const scaleY = dy > 0 ? (boxHeight - originY) / dy : dy < 0 ? -originY / dy : Infinity
+  const scale = Math.min(dx !== 0 ? scaleX : Infinity, dy !== 0 ? scaleY : Infinity)
+
+  const x = originX + dx * scale
+  const y = originY + dy * scale
 
   return {
     position: 'absolute',
@@ -307,7 +297,9 @@ function getAxisDotStyle(axis: 'x' | 'y') {
 <style lang="less" scoped>
 .radar-view {
   flex-shrink: 0;
+  z-index: 0;
 
+  // 家具层
   .furnitures {
     width: 100%;
     height: 100%;
@@ -317,6 +309,7 @@ function getAxisDotStyle(axis: 'x' | 'y') {
     z-index: 1;
   }
 
+  // 点位层
   .targets {
     width: 100%;
     height: 100%;
@@ -325,6 +318,7 @@ function getAxisDotStyle(axis: 'x' | 'y') {
     z-index: 2;
   }
 
+  // 内容层 插槽
   .content {
     width: 100%;
     height: 100%;
@@ -333,30 +327,35 @@ function getAxisDotStyle(axis: 'x' | 'y') {
     z-index: 3;
   }
 
+  // 信息展示按钮
   .info-toggle {
     position: absolute;
     right: 4px;
     bottom: 4px;
-    z-index: 100;
+    z-index: 3;
     border: 1px solid rgba(0, 0, 0, 0.2);
     padding: 4px 6px;
     font-size: 12px;
     border-radius: 4px;
     cursor: pointer;
-    color: #333;
+    color: rgba(51, 51, 51, 0.5);
     transition: background 0.2s;
     font-size: 14px;
+    opacity: 0.5;
 
     &:hover {
       background: rgba(0, 0, 0, 0.1);
-      border: 1px solid rgba(0, 0, 0, 0.2);
+      color: #222222;
     }
   }
 
+  // 信息展示框
   .info-box {
     position: absolute;
     right: 4px;
     bottom: 36px;
+    right: -210px;
+    bottom: -50px;
     font-size: 12px;
     color: #333;
     background: rgba(255, 255, 255, 0.85);
@@ -367,14 +366,17 @@ function getAxisDotStyle(axis: 'x' | 'y') {
     line-height: 1.5;
     pointer-events: none;
     min-width: 200px;
+    user-select: none;
   }
 
+  // 全屏坐标轴线和点
   .full-coordinate-axes {
     position: absolute;
     width: 100%;
     height: 100%;
-    z-index: 5;
+    z-index: 3;
     pointer-events: none;
+    overflow: hidden;
 
     .axis-line {
       position: absolute;
@@ -400,11 +402,12 @@ function getAxisDotStyle(axis: 'x' | 'y') {
     }
   }
 
+  // 坐标轴标记点
   .axis-markers {
     position: absolute;
     width: 100%;
     height: 100%;
-    z-index: 10;
+    z-index: 3;
     pointer-events: none;
 
     .axis-dot {

+ 69 - 0
src/hooks/useRadarCoordinateTransform.ts

@@ -0,0 +1,69 @@
+export function useRadarCoordinateTransform(
+  angle: number,
+  coordinates: [number, number, number, number]
+) {
+  const [xStart, , , yEnd] = coordinates
+  const adjustedAngle = angle - 90
+
+  function editorToRadar(x: number, y: number): { x: number; y: number } {
+    return { x, y }
+  }
+
+  function radarToEditor(x: number, y: number): { x: number; y: number } {
+    return { x, y }
+  }
+
+  function radarToCss(x: number, y: number): { left: number; top: number } {
+    let rx = x,
+      ry = y
+    switch (adjustedAngle) {
+      case 90:
+        ;[rx, ry] = [y, -x]
+        break
+      case 180:
+        ;[rx, ry] = [-x, -y]
+        break
+      case 270:
+        ;[rx, ry] = [-y, x]
+        break
+    }
+    return {
+      left: rx - xStart,
+      top: yEnd - ry,
+    }
+  }
+
+  function cssToRadar(left: number, top: number): { x: number; y: number } {
+    const lx = left + xStart
+    const ty = yEnd - top
+    switch (adjustedAngle) {
+      case 90:
+        return { x: -ty, y: lx }
+      case 180:
+        return { x: -lx, y: -ty }
+      case 270:
+        return { x: ty, y: -lx }
+      default:
+        return { x: lx, y: ty }
+    }
+  }
+
+  // ✅ 新增:编辑角度 → 雷达角度
+  function editorAngleToRadar(editorAngle: number): number {
+    return (editorAngle + angle) % 360
+  }
+
+  // ✅ 新增:雷达角度 → 编辑角度
+  function radarAngleToEditor(radarAngle: number): number {
+    return (radarAngle - angle + 360) % 360
+  }
+
+  return {
+    editorToRadar,
+    radarToEditor,
+    radarToCss,
+    cssToRadar,
+    editorAngleToRadar,
+    radarAngleToEditor,
+  }
+}

+ 1 - 1
src/views/alarm/history/index.vue

@@ -47,8 +47,8 @@
       <a-table :columns="columns" :data-source="tableList" :loading="loading" :pagination="false">
         <template #bodyCell="{ column, record }">
           <template v-if="column.key === 'clientId'">
-            <div>{{ record.devName }}</div>
             <div>{{ record.clientId }}</div>
+            <div>{{ record.devName }}</div>
           </template>
           <template v-if="column.key === 'pose'">
             {{ record.poseName }}

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

@@ -9,7 +9,7 @@
       v-if="isEditDraggable"
       v-model:is-edit="isEditDraggable"
       :style="{ marginTop: '30px' }"
-      @add="addHnadler"
+      @add="addHandler"
     ></furnitureCard>
 
     <div class="viewer">
@@ -40,7 +40,20 @@
           </a-space>
         </div>
       </div>
-      <div v-if="!areaAvailable" class="viewer-content">
+
+      <RadarEditor
+        v-if="!areaAvailable"
+        ref="radarEditorRef"
+        :coordinates="props.ranges"
+        :angle="props.angle"
+        :furnitureItems="furnitureItems"
+        @update:furnitureItems="mapCanvasList = $event"
+      />
+      {{ props.angle }}
+      <div>furnitureItems{{ furnitureItems }} </div>
+      <div>mapCanvasList{{ mapCanvasList }} </div>
+
+      <!-- <div v-if="!areaAvailable" class="viewer-content">
         <div
           ref="contentEl"
           class="mapBox"
@@ -163,7 +176,7 @@
             </div>
           </div>
         </div>
-      </div>
+      </div> -->
     </div>
 
     <div class="viewer">
@@ -362,18 +375,20 @@ import { useDropZone } from '@vueuse/core'
 import { furnitureIconNameMap, furnitureIconSizeMap } from '@/const/furniture'
 import type { FurnitureIconType } from '@/types/furniture'
 import furnitureCard from '../furnitureCard/index.vue'
+import RadarEditor from '@/components/RadarEditor/index.vue'
 import {
-  RedoOutlined,
-  UndoOutlined,
-  ArrowUpOutlined,
-  ArrowDownOutlined,
-  ArrowLeftOutlined,
-  ArrowRightOutlined,
+  // RedoOutlined,
+  // UndoOutlined,
+  // ArrowUpOutlined,
+  // ArrowDownOutlined,
+  // ArrowLeftOutlined,
+  // ArrowRightOutlined,
   DeleteOutlined,
-  QuestionCircleOutlined,
+  // QuestionCircleOutlined,
   CloseOutlined,
 } from '@ant-design/icons-vue'
 import { getOriginPosition } from '@/utils'
+import type { FurnitureItem } from '@/types/radar'
 
 defineOptions({
   name: 'deviceAreaConfig',
@@ -384,7 +399,8 @@ type Props = {
   // x,y的范围,用于初始化画布的大小
   length: number
   width: number
-  ranges: number[] // 区域范围
+  ranges: [number, number, number, number] // 区域范围
+  angle: number // 设备角度,用于初始化画布的旋转角度
   online?: SwitchType // 设备在线状态,用于判断是否可以保存配置
 }
 const emit = defineEmits<{
@@ -394,7 +410,7 @@ const props = withDefaults(defineProps<Props>(), {
   devId: '',
   length: 0, // 区域宽度
   width: 0, // 区域高度
-  ranges: () => [], // 区域范围
+  ranges: () => [Infinity, Infinity, Infinity, Infinity], // 区域范围
   online: 0,
 })
 
@@ -460,6 +476,7 @@ const fetchRoomLayout = async () => {
           rotate: item.rotate || 0,
           x: item.x || 0,
           y: item.y || 0,
+          nanoid: nanoid(),
         }
       })
     }
@@ -516,6 +533,7 @@ interface CanvaseItem {
   type: string // 类型,图标icon
   width: number // 家具宽度
   height: number // 家具长度
+  length?: number // 家具长度
   top: number // 距离检测范围上方位置 cm
   left: number // 	距离检测范围左边位置 cm
   rotate: number // 旋转角度: 0°,90°,180°,270°
@@ -526,13 +544,15 @@ interface CanvaseItem {
   isDragging?: boolean // 是否拖拽 本地使用
 }
 
+const radarEditorRef = ref<InstanceType<typeof RadarEditor>>()
+
 // 画布上的家具列表
 const mapCanvasList = ref<CanvaseItem[]>([])
 const isEditDraggable = ref(false)
 // 家具列表添加
-const addHnadler = (icon: FurnitureIconType) => {
-  console.log('addHnadler', icon)
-  // 检查画布上是否已经添加过了 icon 为 bed 的家具
+const addHandler = (icon: FurnitureIconType) => {
+  console.log('addHandler', icon)
+
   if (icon === 'bed') {
     const isExist = mapCanvasList.value.some((item) => item.type === icon)
     if (isExist) {
@@ -541,27 +561,29 @@ const addHnadler = (icon: FurnitureIconType) => {
     }
   }
 
-  // 家具原始宽高
   const originWidth = furnitureIconSizeMap[icon].width || 30
   const originHeight = furnitureIconSizeMap[icon].height || 30
-  mapCanvasList.value.push({
+
+  const newItem: FurnitureItem = {
     name: furnitureIconNameMap[icon],
     type: icon,
     width: originWidth,
-    height: originHeight,
+    length: originHeight,
     top: 0,
     left: 0,
     rotate: 0,
-    x: originOffsetX,
-    y: originOffsetY,
+    x: 0,
+    y: 0,
+    // x: originOffsetX,
+    // y: originOffsetY,
     nanoid: nanoid(),
-    isActice: false,
-  })
+  }
+
+  radarEditorRef.value?.addFurniture(newItem)
   message.success('已添加家具')
+
   if (icon === 'bed') {
-    // 同步添加一个子区域
     blocks.value.unshift({
-      // 本地用
       id: nanoid(),
       x: 20,
       y: 15,
@@ -575,7 +597,6 @@ const addHnadler = (icon: FurnitureIconType) => {
       isTracking: false,
       isFalling: false,
       isBed: true,
-      // 接口用
       startXx: -150,
       stopXx: -100,
       startYy: 180,
@@ -589,7 +610,6 @@ const addHnadler = (icon: FurnitureIconType) => {
       trackPresence: 0,
       excludeFalling: 0,
     })
-    console.log('blocks', blocks.value)
     message.success('已添加子区域')
   }
 }
@@ -617,136 +637,136 @@ useDropZone(contentEl, {
 })
 
 // 家具列表元素开始拖拽
-const onDragstartListItem = (event: DragEvent, item: CanvaseItem) => {
-  console.log('🔥onDragstartListItem', event, item)
-  currentDragItem.value = item
-  item.isDragging = true
-
-  // 创建拖拽镜像
-  const target = event.currentTarget as HTMLElement
-  const clone = target.cloneNode(true) as HTMLElement
-  const rect = target.getBoundingClientRect()
-  // 获取实际渲染样式
-  const computedStyle = window.getComputedStyle(target)
-  // 复制所有关键样式
-  clone.style.cssText = `
-    position: fixed;
-    left: -9999px;
-    opacity: 0.5;
-    pointer-events: none;
-    width: ${rect.width}px;
-    height: ${rect.height}px;
-    transform: ${computedStyle.transform || `rotate(${item.rotate}deg)`};
-    transform-origin: ${computedStyle.transformOrigin || 'center center'};
-    border: ${computedStyle.border};
-    box-shadow: ${computedStyle.boxShadow};
-    background: ${computedStyle.background};
-  `
-  console.log('克隆元素尺寸:', clone.style.cssText)
-  document.body.appendChild(clone)
-  // 计算中心点偏移量
-  const offsetX = rect.width / 2 // 水平中心偏移
-  const offsetY = rect.height / 2 // 垂直中心偏移
-  // 设置拖拽镜像和偏移量
-  event.dataTransfer?.setDragImage(clone, offsetX, offsetY)
-  // 隐藏原元素
-  target.style.opacity = '0'
-}
+// const onDragstartListItem = (event: DragEvent, item: CanvaseItem) => {
+//   console.log('🔥onDragstartListItem', event, item)
+//   currentDragItem.value = item
+//   item.isDragging = true
+
+//   // 创建拖拽镜像
+//   const target = event.currentTarget as HTMLElement
+//   const clone = target.cloneNode(true) as HTMLElement
+//   const rect = target.getBoundingClientRect()
+//   // 获取实际渲染样式
+//   const computedStyle = window.getComputedStyle(target)
+//   // 复制所有关键样式
+//   clone.style.cssText = `
+//     position: fixed;
+//     left: -9999px;
+//     opacity: 0.5;
+//     pointer-events: none;
+//     width: ${rect.width}px;
+//     height: ${rect.height}px;
+//     transform: ${computedStyle.transform || `rotate(${item.rotate}deg)`};
+//     transform-origin: ${computedStyle.transformOrigin || 'center center'};
+//     border: ${computedStyle.border};
+//     box-shadow: ${computedStyle.boxShadow};
+//     background: ${computedStyle.background};
+//   `
+//   console.log('克隆元素尺寸:', clone.style.cssText)
+//   document.body.appendChild(clone)
+//   // 计算中心点偏移量
+//   const offsetX = rect.width / 2 // 水平中心偏移
+//   const offsetY = rect.height / 2 // 垂直中心偏移
+//   // 设置拖拽镜像和偏移量
+//   event.dataTransfer?.setDragImage(clone, offsetX, offsetY)
+//   // 隐藏原元素
+//   target.style.opacity = '0'
+// }
 
 // 家具列表元素结束拖拽
-const onDragendEndListItem = (event: DragEvent, item: CanvaseItem) => {
-  item.isDragging = false
-  if (currentDragItem.value) {
-    currentDragItem.value.x = originOffsetX
-    currentDragItem.value.y = originOffsetY
-    currentDragItem.value.isDragging = false
-  }
-  requestAnimationFrame(() => {
-    item.isActice = true
-    clickedDragItem.value = item
-  })
-  console.log('🔥onDragendEndListItem', event, item, {
-    x: currentDragItem.value?.x,
-    y: currentDragItem.value?.y,
-  })
-  if (event.currentTarget) {
-    ;(event.currentTarget as HTMLElement).style.opacity = '1'
-  }
-}
+// const onDragendEndListItem = (event: DragEvent, item: CanvaseItem) => {
+//   item.isDragging = false
+//   if (currentDragItem.value) {
+//     currentDragItem.value.x = originOffsetX
+//     currentDragItem.value.y = originOffsetY
+//     currentDragItem.value.isDragging = false
+//   }
+//   requestAnimationFrame(() => {
+//     item.isActice = true
+//     clickedDragItem.value = item
+//   })
+//   console.log('🔥onDragendEndListItem', event, item, {
+//     x: currentDragItem.value?.x,
+//     y: currentDragItem.value?.y,
+//   })
+//   if (event.currentTarget) {
+//     ;(event.currentTarget as HTMLElement).style.opacity = '1'
+//   }
+// }
 
 const clickedDragItem = ref<CanvaseItem | null>()
-const onClickMapItem = (event: MouseEvent, item: CanvaseItem) => {
-  if (!isEditDraggable.value || item.type === 'radar' || item.isDragging) return
-  console.log('onClickMapItem', event, item)
-  item.isActice = true
-  clickedDragItem.value = item
-}
-
-const handleMouseDownMapCanvas = () => {
-  mapCanvasList.value.forEach((item) => {
-    item.isActice = false
-  })
-  clickedDragItem.value = null
-}
+// const onClickMapItem = (event: MouseEvent, item: CanvaseItem) => {
+//   if (!isEditDraggable.value || item.type === 'radar' || item.isDragging) return
+//   console.log('onClickMapItem', event, item)
+//   item.isActice = true
+//   clickedDragItem.value = item
+// }
+
+// const handleMouseDownMapCanvas = () => {
+mapCanvasList.value.forEach((item) => {
+  item.isActice = false
+})
+clickedDragItem.value = null
+// }
 
 // 家具旋转
-const rotateFurnitureIcon = (type: number, nanoid: string) => {
-  console.log('rotateFurnitureIcon', type, nanoid, mapCanvasList.value)
-  const rotateMap = [0, 90, 180, 270]
-
-  if (nanoid) {
-    mapCanvasList.value.forEach((item) => {
-      if (item.nanoid === nanoid) {
-        // 获取当前角度在rotateMap中的索引
-        const currentIndex = rotateMap.indexOf(item.rotate)
-
-        if (type === 1) {
-          // 逆时针(索引递减)
-          const newIndex = (currentIndex - 1 + rotateMap.length) % rotateMap.length
-          item.rotate = rotateMap[newIndex]
-        } else if (type === 2) {
-          // 顺时针(索引递增)
-          const newIndex = (currentIndex + 1) % rotateMap.length
-          item.rotate = rotateMap[newIndex]
-        }
-      }
-    })
-  }
-}
+// const rotateFurnitureIcon = (type: number, nanoid: string) => {
+//   console.log('rotateFurnitureIcon', type, nanoid, mapCanvasList.value)
+//   const rotateMap = [0, 90, 180, 270]
+
+//   if (nanoid) {
+//     mapCanvasList.value.forEach((item) => {
+//       if (item.nanoid === nanoid) {
+//         // 获取当前角度在rotateMap中的索引
+//         const currentIndex = rotateMap.indexOf(item.rotate)
+
+//         if (type === 1) {
+//           // 逆时针(索引递减)
+//           const newIndex = (currentIndex - 1 + rotateMap.length) % rotateMap.length
+//           item.rotate = rotateMap[newIndex]
+//         } else if (type === 2) {
+//           // 顺时针(索引递增)
+//           const newIndex = (currentIndex + 1) % rotateMap.length
+//           item.rotate = rotateMap[newIndex]
+//         }
+//       }
+//     })
+//   }
+// }
 
 // 微调距离
-const distance = ref(5)
+// const distance = ref(5)
 // 家具位置微调
-const positonFurnitureIcon = (type: string, nanoid: string, distance: number) => {
-  console.log('positonFurnitureIcon', type, nanoid, mapCanvasList.value)
-  if (nanoid) {
-    mapCanvasList.value.forEach((item) => {
-      if (item.nanoid === nanoid) {
-        if (type === 'up') {
-          item.top -= distance
-        }
-        if (type === 'down') {
-          item.top += distance
-        }
-        if (type === 'left') {
-          item.left -= distance
-        }
-        if (type === 'right') {
-          item.left += distance
-        }
-      }
-    })
-  }
-}
+// const positonFurnitureIcon = (type: string, nanoid: string, distance: number) => {
+//   console.log('positonFurnitureIcon', type, nanoid, mapCanvasList.value)
+//   if (nanoid) {
+//     mapCanvasList.value.forEach((item) => {
+//       if (item.nanoid === nanoid) {
+//         if (type === 'up') {
+//           item.top -= distance
+//         }
+//         if (type === 'down') {
+//           item.top += distance
+//         }
+//         if (type === 'left') {
+//           item.left -= distance
+//         }
+//         if (type === 'right') {
+//           item.left += distance
+//         }
+//       }
+//     })
+//   }
+// }
 
 // 删除家具
-const deleteFurnitureIcon = (nanoid: string) => {
-  console.log('deleteFurnitureIcon', clickedDragItem.value)
-  if (nanoid) {
-    mapCanvasList.value = mapCanvasList.value.filter((item) => item.nanoid !== nanoid)
-    clickedDragItem.value = null
-  }
-}
+// const deleteFurnitureIcon = (nanoid: string) => {
+//   console.log('deleteFurnitureIcon', clickedDragItem.value)
+//   if (nanoid) {
+//     mapCanvasList.value = mapCanvasList.value.filter((item) => item.nanoid !== nanoid)
+//     clickedDragItem.value = null
+//   }
+// }
 
 // 关闭子区域属性
 const closeSubregionAttr = () => {
@@ -757,13 +777,13 @@ const closeSubregionAttr = () => {
 }
 
 // 删除家具床
-const deleteFurnitureBed = (nanoid: string) => {
-  console.log('deleteFurnitureBed', nanoid)
-  // 先从家具画布移除床
-  deleteFurnitureIcon(nanoid)
-  // 再从子区域画布删除对应的子区域
-  blocks.value.shift()
-}
+// const deleteFurnitureBed = (nanoid: string) => {
+//   console.log('deleteFurnitureBed', nanoid)
+//   // 先从家具画布移除床
+//   deleteFurnitureIcon(nanoid)
+//   // 再从子区域画布删除对应的子区域
+//   blocks.value.shift()
+// }
 
 // 新增区块类型
 interface BlockItem {
@@ -957,30 +977,30 @@ const { originX, originY, originOffsetX, originOffsetY, radarX, radarY } = getOr
 const initRadarIcon = () => {
   console.log('initRadarIcon', mapCanvasList.value, furnitureItems.value)
   // 在家具地图添加雷达图标
-  mapCanvasList.value.push({
-    name: '雷达',
-    type: 'radar',
-    width: furnitureIconSizeMap['radar'].width,
-    height: furnitureIconSizeMap['radar'].height,
-    top: radarY,
-    left: radarX,
-    x: originOffsetX,
-    y: originOffsetY,
-    rotate: 0,
-    nanoid: nanoid(),
-  })
+  // mapCanvasList.value.push({
+  //   name: '雷达',
+  //   type: 'radar',
+  //   width: furnitureIconSizeMap['radar'].width,
+  //   height: furnitureIconSizeMap['radar'].height,
+  //   top: radarY,
+  //   left: radarX,
+  //   x: originOffsetX,
+  //   y: originOffsetY,
+  //   rotate: 0,
+  //   nanoid: nanoid(),
+  // })
   // 在屏蔽子区域添加雷达图标
-  furnitureItems.value.push({
-    name: '雷达',
-    type: 'radar',
-    width: furnitureIconSizeMap['radar'].width,
-    length: furnitureIconSizeMap['radar'].height,
-    top: radarY,
-    left: radarX,
-    x: originOffsetX,
-    y: originOffsetY,
-    rotate: 0,
-  })
+  // furnitureItems.value.push({
+  //   name: '雷达',
+  //   type: 'radar',
+  //   width: furnitureIconSizeMap['radar'].width,
+  //   length: furnitureIconSizeMap['radar'].height,
+  //   top: radarY,
+  //   left: radarX,
+  //   x: originOffsetX,
+  //   y: originOffsetY,
+  //   rotate: 0,
+  // })
 }
 
 // 保存所有配置
@@ -1014,7 +1034,7 @@ const saveAllConfig = () => {
             name: item.name,
             type: item.type as FurnitureType,
             width: item.width,
-            length: item.height,
+            length: item.height || item.length,
             top: item.top,
             left: item.left,
             rotate: item.rotate as 0 | 90 | 180 | 270,
@@ -1125,7 +1145,6 @@ const deleteBlockArea = (id: string) => {
   padding: 10px;
   min-width: 500px;
   flex-shrink: 0;
-  // margin-top: 10px;
 
   &-header {
     display: flex;

+ 3 - 0
src/views/device/detail/components/deviceConfig/index.vue

@@ -30,6 +30,7 @@
         :length="props.data.length"
         :width="props.data.width"
         :ranges="[props.data.xStart, props.data.xEnd, props.data.yStart, props.data.yEnd]"
+        :angle="props.angle"
         :online="props.online"
         @success="deviceAreaConfigSuccess"
       ></deviceAreaConfig>
@@ -60,6 +61,7 @@ type Props = {
     yStart: number
     yEnd: number
   }
+  angle: number
   online?: SwitchType
 }
 
@@ -81,6 +83,7 @@ const props = withDefaults(defineProps<Props>(), {
     yStart: 0,
     yEnd: 0,
   }),
+  angle: 0,
   online: 0,
 })
 

+ 1 - 0
src/views/device/detail/index.vue

@@ -221,6 +221,7 @@
         :room-id="deviceRoomId"
         :furniture-items="furnitureItems"
         :sub-region-items="subRegionItems"
+        :angle="detailState.northAngle"
         :online="detailState.online"
         @success="saveConfigSuccess"
       ></deviceConfigDrawer>