AngleSelector.vue 4.11 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>