|
@@ -9,7 +9,7 @@
|
|
|
left: `${radar.left}px`,
|
|
|
top: `${radar.top}px`,
|
|
|
position: 'absolute',
|
|
|
- transform: `translate(-50%, -50%) rotate(${radar.rotate}deg)`,
|
|
|
+ transform: `translate(-50%, -50%) rotate(${adjustedAngle + 90}deg)`,
|
|
|
cursor: 'default',
|
|
|
}"
|
|
|
:draggable="false"
|
|
@@ -73,18 +73,28 @@
|
|
|
<div v-if="showInfo" class="info-box">
|
|
|
检测区域:{{ areaWidth }} × {{ areaHeight }} cm<br />
|
|
|
坐标范围:[{{ xxStart }}, {{ xxEnd }}, {{ yyStart }}, {{ yyEnd }}]<br />
|
|
|
- 正北夹角:{{ angle }}° {{ northArrow }}<br />
|
|
|
- 坐标参考:X轴 {{ xArrow }},Y轴 {{ yArrow }}
|
|
|
+ 电源灯朝向: {{ northArrow }} ({{ angle }}°)<br />
|
|
|
+ 坐标参考:<span style="color: red">X轴 {{ xArrow }}</span
|
|
|
+ >,<span style="color: blue">Y轴 {{ yArrow }}</span>
|
|
|
</div>
|
|
|
|
|
|
<div v-if="areaWidth > 50 && areaHeight > 50" class="info-toggle" @click="showInfo = !showInfo">
|
|
|
<QuestionCircleOutlined />
|
|
|
</div>
|
|
|
+
|
|
|
+ <div class="full-coordinate-axes">
|
|
|
+ <div class="axis-line x-axis" :style="getFullAxisStyle('x')"> </div>
|
|
|
+ <div class="axis-line y-axis" :style="getFullAxisStyle('y')"> </div>
|
|
|
+ </div>
|
|
|
+ <div class="axis-markers">
|
|
|
+ <div class="axis-dot x-dot" :style="getAxisDotStyle('x')"></div>
|
|
|
+ <div class="axis-dot y-dot" :style="getAxisDotStyle('y')"></div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { computed, ref } from 'vue'
|
|
|
+import { computed, ref, type CSSProperties } from 'vue'
|
|
|
import type { FurnitureItem, TargetPoint } from '@/types/radar'
|
|
|
import { QuestionCircleOutlined } from '@ant-design/icons-vue'
|
|
|
|
|
@@ -109,6 +119,7 @@ const areaWidth = computed(() => xxEnd.value - xxStart.value)
|
|
|
const areaHeight = computed(() => yyEnd.value - yyStart.value)
|
|
|
// 雷达角度(默认值为 0°,表示正北朝上)
|
|
|
const angle = computed(() => props.angle ?? 0)
|
|
|
+const adjustedAngle = computed(() => props.angle - 90)
|
|
|
|
|
|
/**
|
|
|
* 坐标转换函数:将雷达坐标系中的点 (x, y) 转换为 CSS 坐标系中的位置 (left, top)
|
|
@@ -120,7 +131,7 @@ const angle = computed(() => props.angle ?? 0)
|
|
|
function convertRadarToCss(x: number, y: number): { left: number; top: number } {
|
|
|
let rx = x,
|
|
|
ry = y
|
|
|
- switch (angle.value) {
|
|
|
+ switch (adjustedAngle.value) {
|
|
|
case 90:
|
|
|
;[rx, ry] = [y, -x]
|
|
|
break
|
|
@@ -185,47 +196,112 @@ const showInfo = ref(false)
|
|
|
const northArrow = computed(() => {
|
|
|
switch (angle.value) {
|
|
|
case 0:
|
|
|
- return '⬆'
|
|
|
+ return '北边'
|
|
|
case 90:
|
|
|
- return '➡'
|
|
|
+ return '东边'
|
|
|
case 180:
|
|
|
- return '⬇'
|
|
|
+ return '南边'
|
|
|
case 270:
|
|
|
- return '⬅'
|
|
|
+ return '西边'
|
|
|
default:
|
|
|
return ''
|
|
|
}
|
|
|
})
|
|
|
|
|
|
-const xArrow = computed(() => {
|
|
|
- switch (angle.value) {
|
|
|
- case 0:
|
|
|
- return '➡'
|
|
|
- case 90:
|
|
|
- return '⬆'
|
|
|
- case 180:
|
|
|
- return '⬅'
|
|
|
- case 270:
|
|
|
- return '⬇'
|
|
|
- default:
|
|
|
- return ''
|
|
|
+function getDirectionLabelWithIcon(axis: 'x' | 'y', angleDeg: number): string {
|
|
|
+ const rad = (angleDeg * Math.PI) / 180
|
|
|
+ const dx = axis === 'x' ? Math.cos(rad) : Math.sin(rad)
|
|
|
+ const dy = axis === 'x' ? -Math.sin(rad) : Math.cos(rad)
|
|
|
+
|
|
|
+ const deg = Math.atan2(dx, dy) * (180 / Math.PI)
|
|
|
+ const normalized = (deg + 360) % 360
|
|
|
+
|
|
|
+ if (normalized >= 45 && normalized < 135) return '东 ➡'
|
|
|
+ if (normalized >= 135 && normalized < 225) return '南 ⬇'
|
|
|
+ if (normalized >= 225 && normalized < 315) return '西 ⬅'
|
|
|
+ return '北 ⬆'
|
|
|
+}
|
|
|
+
|
|
|
+const xArrow = computed(() => getDirectionLabelWithIcon('x', adjustedAngle.value))
|
|
|
+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 length = axis === 'x' ? areaWidth.value : areaHeight.value
|
|
|
+
|
|
|
+ // 方向角度(以雷达为原点)
|
|
|
+ let rotateDeg = 0
|
|
|
+ if (axis === 'x') {
|
|
|
+ rotateDeg = normalized
|
|
|
+ } else {
|
|
|
+ rotateDeg = normalized - 90
|
|
|
}
|
|
|
-})
|
|
|
|
|
|
-const yArrow = computed(() => {
|
|
|
- switch (angle.value) {
|
|
|
- case 0:
|
|
|
- return '⬆'
|
|
|
- case 90:
|
|
|
- return '⬅'
|
|
|
- case 180:
|
|
|
- return '⬇'
|
|
|
- case 270:
|
|
|
- return '➡'
|
|
|
- default:
|
|
|
- return ''
|
|
|
+ return {
|
|
|
+ position: 'absolute',
|
|
|
+ left: `${originX}px`,
|
|
|
+ top: `${originY}px`,
|
|
|
+ width: `${length}px`,
|
|
|
+ height: '1px',
|
|
|
+ backgroundColor: axis === 'x' ? 'red' : 'blue',
|
|
|
+ transform: `translate(-50%, -50%) rotate(${rotateDeg}deg)`,
|
|
|
+ transformOrigin: 'center center',
|
|
|
+ zIndex: 5,
|
|
|
+ pointerEvents: 'none',
|
|
|
+ } as CSSProperties
|
|
|
+}
|
|
|
+
|
|
|
+function getAxisDirectionVector(axis: 'x' | 'y', angleDeg: 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 分量(注意负号)
|
|
|
+ }
|
|
|
}
|
|
|
-})
|
|
|
+}
|
|
|
+
|
|
|
+function getAxisDotStyle(axis: 'x' | 'y') {
|
|
|
+ const radarX = radar.value.left
|
|
|
+ const radarY = radar.value.top
|
|
|
+ const boxWidth = areaWidth.value
|
|
|
+ const boxHeight = areaHeight.value
|
|
|
+
|
|
|
+ const { dx, dy } = getAxisDirectionVector(axis, adjustedAngle.value)
|
|
|
+
|
|
|
+ // 计算最大可延伸距离,避免超出盒子
|
|
|
+ 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 x = radarX + dx * scale
|
|
|
+ const y = radarY - dy * scale
|
|
|
+
|
|
|
+ return {
|
|
|
+ position: 'absolute',
|
|
|
+ left: `${x}px`,
|
|
|
+ top: `${y}px`,
|
|
|
+ width: '8px',
|
|
|
+ height: '8px',
|
|
|
+ borderRadius: '50%',
|
|
|
+ backgroundColor: axis === 'x' ? 'red' : 'blue',
|
|
|
+ transform: 'translate(-50%, -50%)',
|
|
|
+ zIndex: 10,
|
|
|
+ pointerEvents: 'none',
|
|
|
+ } as CSSProperties
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
<style lang="less" scoped>
|
|
@@ -292,6 +368,59 @@ const yArrow = computed(() => {
|
|
|
pointer-events: none;
|
|
|
min-width: 200px;
|
|
|
}
|
|
|
+
|
|
|
+ .full-coordinate-axes {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ z-index: 5;
|
|
|
+ pointer-events: none;
|
|
|
+
|
|
|
+ .axis-line {
|
|
|
+ position: absolute;
|
|
|
+ height: 1px;
|
|
|
+ background-color: #000;
|
|
|
+ }
|
|
|
+
|
|
|
+ .x-axis {
|
|
|
+ background-color: red;
|
|
|
+ opacity: 0.6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .y-axis {
|
|
|
+ background-color: blue;
|
|
|
+ opacity: 0.6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .axis-arrow {
|
|
|
+ position: absolute;
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .axis-markers {
|
|
|
+ position: absolute;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ z-index: 10;
|
|
|
+ pointer-events: none;
|
|
|
+
|
|
|
+ .axis-dot {
|
|
|
+ position: absolute;
|
|
|
+ }
|
|
|
+
|
|
|
+ .x-dot {
|
|
|
+ background-color: red;
|
|
|
+ opacity: 0.6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .y-dot {
|
|
|
+ background-color: blue;
|
|
|
+ opacity: 0.6;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.target-dot {
|