AngleSelector.vue 3.98 KB
<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>