AngleSelector.vue
3.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<template>
<div class="angle-selector">
<svg viewBox="0 0 120 120" class="angle-disk">
<!-- 外圈 -->
<circle cx="60" cy="60" r="55" fill="none" stroke="#e0e0e0" stroke-width="2"/>
<!-- 36等分刻度 -->
<g v-for="i in 36" :key="i">
<line
:x1="60 + 45 * Math.cos((i - 1) * 10 * Math.PI / 180)"
:y1="60 - 45 * Math.sin((i - 1) * 10 * Math.PI / 180)"
:x2="60 + 52 * Math.cos((i - 1) * 10 * Math.PI / 180)"
:y2="60 - 52 * Math.sin((i - 1) * 10 * Math.PI / 180)"
:stroke="isAngleSelected((i - 1) * 10) ? '#1976d2' : '#bbb'"
:stroke-width="i % 3 === 1 ? 2 : 1"
/>
</g>
<!-- 选中扇区 -->
<path v-if="selectedAngles.length === 2" :d="arcPath" fill="rgba(25, 118, 210, 0.3)" stroke="#1976d2" stroke-width="1"/>
<!-- 选中点 -->
<circle
v-for="(angle, idx) in selectedAngles"
:key="'pt' + idx"
:cx="60 + 40 * Math.cos(angle * Math.PI / 180)"
:cy="60 - 40 * Math.sin(angle * Math.PI / 180)"
r="5"
fill="#1976d2"
/>
<!-- 可点击扇区 -->
<g v-for="i in 36" :key="'click' + i">
<path
:d="sectorPath((i - 1) * 10)"
fill="transparent"
class="sector-click"
@click="toggleAngle((i - 1) * 10)"
/>
</g>
<!-- 角度标签 -->
<text x="60" y="12" text-anchor="middle" font-size="8" fill="#666">90</text>
<text x="110" y="63" text-anchor="middle" font-size="8" fill="#666">0</text>
<text x="60" y="115" text-anchor="middle" font-size="8" fill="#666">-90</text>
<text x="10" y="63" text-anchor="middle" font-size="8" fill="#666">180</text>
</svg>
<div class="angle-display">{{ displayText }}</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
modelValue: { type: Array, default: () => [0] }
})
const emit = defineEmits(['update:modelValue'])
const selectedAngles = computed(() => props.modelValue || [0])
const isAngleSelected = (angle) => {
if (selectedAngles.value.length === 1) return selectedAngles.value[0] === angle
if (selectedAngles.value.length === 2) {
const [a, b] = selectedAngles.value
if (a <= b) return angle >= a && angle <= b
return angle >= a || angle <= b
}
return false
}
const toggleAngle = (angle) => {
const current = [...selectedAngles.value]
const idx = current.indexOf(angle)
if (idx >= 0) {
current.splice(idx, 1)
if (current.length === 0) current.push(0)
} else if (current.length < 2) {
current.push(angle)
current.sort((a, b) => a - b)
} else {
current[1] = angle
current.sort((a, b) => a - b)
}
emit('update:modelValue', current)
}
const sectorPath = (angle) => {
const r = 55, cx = 60, cy = 60, span = 5
const a1 = (angle - span) * Math.PI / 180
const a2 = (angle + span) * Math.PI / 180
const x1 = cx + r * Math.cos(a1), y1 = cy - r * Math.sin(a1)
const x2 = cx + r * Math.cos(a2), y2 = cy - r * Math.sin(a2)
return `M${cx},${cy} L${x1},${y1} A${r},${r} 0 0,0 ${x2},${y2} Z`
}
const arcPath = computed(() => {
if (selectedAngles.value.length !== 2) return ''
const [a, b] = selectedAngles.value
const r = 40, cx = 60, cy = 60
const a1 = a * Math.PI / 180, a2 = b * Math.PI / 180
const x1 = cx + r * Math.cos(a1), y1 = cy - r * Math.sin(a1)
const x2 = cx + r * Math.cos(a2), y2 = cy - r * Math.sin(a2)
const largeArc = (b - a) > 180 ? 1 : 0
return `M${cx},${cy} L${x1},${y1} A${r},${r} 0 ${largeArc},0 ${x2},${y2} Z`
})
const displayText = computed(() => {
if (selectedAngles.value.length === 1) return `${selectedAngles.value[0]}°`
return `${selectedAngles.value[0]}° - ${selectedAngles.value[1]}°`
})
</script>
<style scoped>
.angle-selector { display: flex; flex-direction: column; align-items: center; }
.angle-disk { width: 100px; height: 100px; cursor: pointer; }
.sector-click:hover { fill: rgba(25, 118, 210, 0.1); }
.angle-display { font-size: 12px; color: #666; margin-top: 4px; }
</style>