欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue3 實現(xiàn)牙位圖選擇器的實例代碼

 更新時間:2025年04月18日 09:19:33   作者:A_ugust__  
這篇文章主要介紹了vue3 實現(xiàn)牙位圖選擇器的實例代碼,代碼簡單易懂,需要的朋友參考下吧

效果圖

封裝組件:ToothChart.vue

<template>
  <div class="tooth-chart-container">
    <div class="tooth-chart">
      <svg :width="computedWidth" :height="computedHeight" :viewBox="`0 0 ${viewBoxWidth} ${viewBoxHeight}`"
        xmlns="http://www.w3.org/2000/svg">
        <!-- 上頜牙齒 -->
        <g class="upper-jaw">
          <!-- 右上區(qū) (1-8) -->
          <g v-for="tooth in upperRightTeeth" :key="tooth.number">
            <rect :x="tooth.x" :y="tooth.y" :width="toothWidth" :height="toothHeight" :rx="toothRadius"
              :class="['tooth', { selected: selectedTeeth.includes(tooth.number) }]"
              @click="toggleTooth(tooth.number)" />
            <text :x="tooth.x + toothWidth / 2" :y="tooth.y + toothHeight / 2 + 5" class="tooth-number"
              @click="toggleTooth(tooth.number)">
              {{ tooth.number }}
            </text>
          </g>
          <!-- 左上區(qū) (9-16) -->
          <g v-for="tooth in upperLeftTeeth" :key="tooth.number">
            <rect :x="tooth.x" :y="tooth.y" :width="toothWidth" :height="toothHeight" :rx="toothRadius"
              :class="['tooth', { selected: selectedTeeth.includes(tooth.number) }]"
              @click="toggleTooth(tooth.number)" />
            <text :x="tooth.x + toothWidth / 2" :y="tooth.y + toothHeight / 2 + 5" class="tooth-number"
              @click="toggleTooth(tooth.number)">
              {{ tooth.number }}
            </text>
          </g>
        </g>
        <!-- 下頜牙齒 -->
        <g class="lower-jaw">
          <!-- 右下區(qū) (17-24) -->
          <g v-for="tooth in lowerRightTeeth" :key="tooth.number">
            <rect :x="tooth.x" :y="tooth.y" :width="toothWidth" :height="toothHeight" :rx="toothRadius"
              :class="['tooth', { selected: selectedTeeth.includes(tooth.number) }]"
              @click="toggleTooth(tooth.number)" />
            <text :x="tooth.x + toothWidth / 2" :y="tooth.y + toothHeight / 2 + 5" class="tooth-number"
              @click="toggleTooth(tooth.number)">
              {{ tooth.number }}
            </text>
          </g>
          <!-- 左下區(qū) (25-32) -->
          <g v-for="tooth in lowerLeftTeeth" :key="tooth.number">
            <rect :x="tooth.x" :y="tooth.y" :width="toothWidth" :height="toothHeight" :rx="toothRadius"
              :class="['tooth', { selected: selectedTeeth.includes(tooth.number) }]"
              @click="toggleTooth(tooth.number)" />
            <text :x="tooth.x + toothWidth / 2" :y="tooth.y + toothHeight / 2 + 5" class="tooth-number"
              @click="toggleTooth(tooth.number)">
              {{ tooth.number }}
            </text>
          </g>
        </g>
        <!-- 中線標(biāo)識 -->
        <line :x1="viewBoxWidth / 2" y1="50" :x2="viewBoxWidth / 2" :y2="viewBoxHeight - 50" stroke="#78909C" stroke-width="1"
          stroke-dasharray="5,5" />
        <!-- 分區(qū)標(biāo)識 -->
        <text :x="viewBoxWidth / 4" y="30" class="quadrant-label">右上區(qū) (1-8)</text>
        <text :x="viewBoxWidth * 3 / 4" y="30" class="quadrant-label">左上區(qū) (9-16)</text>
        <text :x="viewBoxWidth / 4" :y="viewBoxHeight - 20" class="quadrant-label">右下區(qū) (17-24)</text>
        <text :x="viewBoxWidth * 3 / 4" :y="viewBoxHeight - 20" class="quadrant-label">左下區(qū) (25-32)</text>
      </svg>
    </div>
    <!-- 備注區(qū)域 -->
    <div class="notes-section">
      <div v-if="selectedTeeth.length > 0">
        <h3>選中牙齒: {{ selectedTeethWithPosition.join(', ') }}</h3>
        <textarea v-model="notes" placeholder="請輸入治療備注..." class="notes-textarea"></textarea>
      </div>
      <div v-else class="no-selection">
        請點擊牙齒進(jìn)行選擇
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
const props = defineProps({
  modelValue: {
    type: Object,
    default: () => ({
      selectedTeeth: [],
      notes: ''
    })
  },
  width: {
    type: [Number, String],
    default: '100%'
  },
  height: {
    type: [Number, String],
    default: '600'
  },
  // 新增的尺寸相關(guān)props
  viewBoxWidth: {
    type: Number,
    default: 1000
  },
  viewBoxHeight: {
    type: Number,
    default: 600
  },
  toothWidth: {
    type: Number,
    default: 40
  },
  toothHeight: {
    type: Number,
    default: 60
  },
  toothRadius: {
    type: Number,
    default: 5
  }
})
const emit = defineEmits(['update:modelValue'])
const selectedTeeth = ref([...props.modelValue.selectedTeeth])
const notes = ref(props.modelValue.notes)
// 計算屬性
const computedWidth = computed(() => typeof props.width === 'number' ? `${props.width}px` : props.width)
const computedHeight = computed(() => typeof props.height === 'number' ? `${props.height}px` : props.height)
// 計算選中牙齒及其位置信息
const selectedTeethWithPosition = computed(() => {
  return selectedTeeth.value.map(num => {
    const tooth = getAllTeeth().find(t => t.number === num)
    return tooth ? `${num}(${getPositionName(num)})` : num
  })
})
// 獲取所有牙齒數(shù)據(jù)
const getAllTeeth = () => [...upperRightTeeth, ...upperLeftTeeth, ...lowerRightTeeth, ...lowerLeftTeeth]
// 獲取牙齒位置名稱
const getPositionName = (toothNumber) => {
  if (toothNumber >= 1 && toothNumber <= 8) return '右上'
  if (toothNumber >= 9 && toothNumber <= 16) return '左上'
  if (toothNumber >= 17 && toothNumber <= 24) return '右下'
  if (toothNumber >= 25 && toothNumber <= 32) return '左下'
  return ''
}
// 標(biāo)準(zhǔn)牙位布局?jǐn)?shù)據(jù) - 基于viewBox動態(tài)計算
const upperRightTeeth = [
  { number: 1, x: props.viewBoxWidth / 2 - props.toothWidth * 4, y: 50 },
  { number: 2, x: props.viewBoxWidth / 2 - props.toothWidth * 3, y: 50 },
  { number: 3, x: props.viewBoxWidth / 2 - props.toothWidth * 2, y: 50 },
  { number: 4, x: props.viewBoxWidth / 2 - props.toothWidth * 4, y: 50 + props.toothHeight + 10 },
  { number: 5, x: props.viewBoxWidth / 2 - props.toothWidth * 3, y: 50 + props.toothHeight + 10 },
  { number: 6, x: props.viewBoxWidth / 2 - props.toothWidth * 2, y: 50 + props.toothHeight + 10 },
  { number: 7, x: props.viewBoxWidth / 2 - props.toothWidth * 4, y: 50 + (props.toothHeight + 10) * 2 },
  { number: 8, x: props.viewBoxWidth / 2 - props.toothWidth * 3, y: 50 + (props.toothHeight + 10) * 2 }
]
const upperLeftTeeth = [
  { number: 9, x: props.viewBoxWidth / 2 + props.toothWidth * 3, y: 50 + (props.toothHeight + 10) * 2 },
  { number: 10, x: props.viewBoxWidth / 2 + props.toothWidth * 2, y: 50 + (props.toothHeight + 10) * 2 },
  { number: 11, x: props.viewBoxWidth / 2 + props.toothWidth * 3, y: 50 + props.toothHeight + 10 },
  { number: 12, x: props.viewBoxWidth / 2 + props.toothWidth * 2, y: 50 + props.toothHeight + 10 },
  { number: 13, x: props.viewBoxWidth / 2 + props.toothWidth, y: 50 + props.toothHeight + 10 },
  { number: 14, x: props.viewBoxWidth / 2 + props.toothWidth * 3, y: 50 },
  { number: 15, x: props.viewBoxWidth / 2 + props.toothWidth * 2, y: 50 },
  { number: 16, x: props.viewBoxWidth / 2 + props.toothWidth, y: 50 }
]
const lowerRightTeeth = [
  { number: 17, x: props.viewBoxWidth / 2 - props.toothWidth * 4, y: props.viewBoxHeight / 2 + 20 },
  { number: 18, x: props.viewBoxWidth / 2 - props.toothWidth * 3, y: props.viewBoxHeight / 2 + 20 },
  { number: 19, x: props.viewBoxWidth / 2 - props.toothWidth * 2, y: props.viewBoxHeight / 2 + 20 },
  { number: 20, x: props.viewBoxWidth / 2 - props.toothWidth * 4, y: props.viewBoxHeight / 2 + 20 + props.toothHeight + 10 },
  { number: 21, x: props.viewBoxWidth / 2 - props.toothWidth * 3, y: props.viewBoxHeight / 2 + 20 + props.toothHeight + 10 },
  { number: 22, x: props.viewBoxWidth / 2 - props.toothWidth * 2, y: props.viewBoxHeight / 2 + 20 + props.toothHeight + 10 },
  { number: 23, x: props.viewBoxWidth / 2 - props.toothWidth * 4, y: props.viewBoxHeight / 2 + 20 + (props.toothHeight + 10) * 2 },
  { number: 24, x: props.viewBoxWidth / 2 - props.toothWidth * 3, y: props.viewBoxHeight / 2 + 20 + (props.toothHeight + 10) * 2 }
]
const lowerLeftTeeth = [
  { number: 25, x: props.viewBoxWidth / 2 + props.toothWidth * 3, y: props.viewBoxHeight / 2 + 20 + (props.toothHeight + 10) * 2 },
  { number: 26, x: props.viewBoxWidth / 2 + props.toothWidth * 2, y: props.viewBoxHeight / 2 + 20 + (props.toothHeight + 10) * 2 },
  { number: 27, x: props.viewBoxWidth / 2 + props.toothWidth * 3, y: props.viewBoxHeight / 2 + 20 + props.toothHeight + 10 },
  { number: 28, x: props.viewBoxWidth / 2 + props.toothWidth * 2, y: props.viewBoxHeight / 2 + 20 + props.toothHeight + 10 },
  { number: 29, x: props.viewBoxWidth / 2 + props.toothWidth, y: props.viewBoxHeight / 2 + 20 + props.toothHeight + 10 },
  { number: 30, x: props.viewBoxWidth / 2 + props.toothWidth * 3, y: props.viewBoxHeight / 2 + 20 },
  { number: 31, x: props.viewBoxWidth / 2 + props.toothWidth * 2, y: props.viewBoxHeight / 2 + 20 },
  { number: 32, x: props.viewBoxWidth / 2 + props.toothWidth, y: props.viewBoxHeight / 2 + 20 }
]
// 切換牙齒選擇狀態(tài)
const toggleTooth = (toothNumber) => {
  const index = selectedTeeth.value.indexOf(toothNumber)
  if (index === -1) {
    selectedTeeth.value.push(toothNumber)
  } else {
    selectedTeeth.value.splice(index, 1)
  }
  updateModelValue()
}
// 更新模型值
const updateModelValue = () => {
  emit('update:modelValue', {
    selectedTeeth: [...selectedTeeth.value],
    notes: notes.value,
    selectedTeethWithPosition: [...selectedTeethWithPosition.value]
  })
}
// 監(jiān)聽notes變化
watch(notes, () => {
  updateModelValue()
})
// 監(jiān)聽props變化
watch(() => props.modelValue, (newVal) => {
  if (JSON.stringify(newVal.selectedTeeth) !== JSON.stringify(selectedTeeth.value)) {
    selectedTeeth.value = [...newVal.selectedTeeth]
  }
  if (newVal.notes !== notes.value) {
    notes.value = newVal.notes
  }
}, { deep: true })
// 暴露方法
defineExpose({
  clearSelection: () => {
    selectedTeeth.value = []
    notes.value = ''
    updateModelValue()
  },
  getSelectedTeeth: () => [...selectedTeeth.value],
  getSelectedTeethWithPosition: () => [...selectedTeethWithPosition.value],
  getNotes: () => notes.value
})
</script>
<style scoped>
.tooth-chart-container {
  display: flex;
  flex-direction: column;
  gap: 20px;
  font-family: 'Arial', sans-serif;
  max-width: 100%;
  margin: 0 auto;
}
.tooth-chart {
  border: 1px solid #e0e0e0;
  border-radius: 12px;
  overflow: hidden;
  background-color: #f8f9fa;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}
.tooth {
  fill: #ffffff;
  stroke: #90a4ae;
  stroke-width: 1.5;
  cursor: pointer;
  transition: all 0.3s ease;
}
.tooth:hover {
  fill: #e3f2fd;
  stroke: #42a5f5;
}
.tooth.selected {
  fill: #bbdefb;
  stroke: #1e88e5;
  stroke-width: 2;
  filter: drop-shadow(0 0 4px rgba(30, 136, 229, 0.4));
}
.tooth-number {
  font-size: 22px;
  font-weight: 600;
  text-anchor: middle;
  cursor: pointer;
  user-select: none;
  fill: #37474f;
}
.quadrant-label {
  font-size: 26px;
  fill: #78909C;
  text-anchor: middle;
  font-weight: 500;
}
.notes-section {
  padding: 20px;
  border: 1px solid #e0e0e0;
  border-radius: 12px;
  background-color: #f8f9fa;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}
.notes-section h3 {
  margin-top: 0;
  margin-bottom: 15px;
  color: #263238;
  font-size: 18px;
}
.notes-textarea {
  width: 100%;
  min-height: 120px;
  padding: 12px;
  border: 1px solid #cfd8dc;
  border-radius: 8px;
  resize: vertical;
  font-family: inherit;
  font-size: 14px;
  line-height: 1.5;
  transition: border-color 0.3s;
}
.notes-textarea:focus {
  outline: none;
  border-color: #42a5f5;
  box-shadow: 0 0 0 2px rgba(66, 165, 245, 0.2);
}
.no-selection {
  color: #90a4ae;
  text-align: center;
  padding: 30px;
  font-size: 16px;
}
</style>

使用示例:

<template>
  <div class="demo-container">
    <h1>牙位圖選擇器</h1>
    <ToothChart v-model="toothData" :width="chartWidth" :height="chartHeight" :tooth-width="toothWidth"
      :tooth-height="toothHeight" />
    <div class="actions">
      <button @click="clearSelection">清除選擇</button>
      <button @click="submitData">提交數(shù)據(jù)</button>
    </div>
  </div>
</template>
<script setup>
import { ref } from 'vue'
import ToothChart from '@/components/ToothChart.vue';
const toothData = ref({
  selectedTeeth: [],
  notes: '',
  selectedTeethWithPosition: []
})
const chartWidth = ref('100%')
const chartHeight = ref('500px')
const toothWidth = ref(40)
const toothHeight = ref(60)
const clearSelection = () => {
  toothData.value = {
    selectedTeeth: [],
    notes: '',
    selectedTeethWithPosition: []
  }
}
const submitData = () => {
  alert(`已提交數(shù)據(jù):\n選中牙齒: ${toothData.value.selectedTeethWithPosition.join(', ')}\n備注: ${toothData.value.notes}`)
}
</script>
<style scoped lang="scss">
.demo-container {
  max-width: 1000px;
  margin: 0 auto;
  padding: 20px;
  font-family: Arial, sans-serif;
}
.actions {
  display: flex;
  gap: 10px;
  margin: 20px 0;
}
.actions button {
  padding: 8px 16px;
  background: #42a5f5;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.3s;
}
.actions button:hover {
  background: #1e88e5;
}
</style>

到此這篇關(guān)于vue3 實現(xiàn)牙位圖選擇器的文章就介紹到這了,更多相關(guān)vue3 選擇器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue中window.onresize的使用解析

    vue中window.onresize的使用解析

    這篇文章主要介紹了vue中window.onresize的使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • Vue判斷數(shù)組內(nèi)是否存在某一項的兩種方法

    Vue判斷數(shù)組內(nèi)是否存在某一項的兩種方法

    這篇文章主要介紹了Vue判斷數(shù)組內(nèi)是否存在某一項,今天給大家分享兩種方法,分別是findIndex()和 indexOf()方法,本文結(jié)合實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-07-07
  • vue3.2中的vuex使用詳解

    vue3.2中的vuex使用詳解

    這篇文章主要介紹了vue3.2中的vuex使用詳解,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-04-04
  • Vue項目識別不到@別名問題及解決

    Vue項目識別不到@別名問題及解決

    這篇文章主要介紹了Vue項目識別不到@別名問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • vue 組件內(nèi)獲取actions的response方式

    vue 組件內(nèi)獲取actions的response方式

    今天小編就為大家分享一篇vue 組件內(nèi)獲取actions的response方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-11-11
  • vue.js實現(xiàn)的綁定class操作示例

    vue.js實現(xiàn)的綁定class操作示例

    這篇文章主要介紹了vue.js實現(xiàn)的綁定class操作,結(jié)合實例形式分析了vue.js綁定class常見的3種操作技巧,需要的朋友可以參考下
    2018-07-07
  • Vue實現(xiàn)開心消消樂游戲算法

    Vue實現(xiàn)開心消消樂游戲算法

    這篇文章主要介紹了使用Vue寫一個開心消消樂游戲,游戲算法在文中給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-10-10
  • vue實現(xiàn)移動端拖動排序

    vue實現(xiàn)移動端拖動排序

    這篇文章主要為大家詳細(xì)介紹了vue實現(xiàn)移動端拖動排序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-08-08
  • 解決vue3中內(nèi)存泄漏的問題

    解決vue3中內(nèi)存泄漏的問題

    在項目中會發(fā)現(xiàn)一個奇怪的現(xiàn)象,當(dāng)我們在使用element-plus中的圖標(biāo)組件時會出現(xiàn)內(nèi)存泄漏,所以本文講給大家講講如何解決vue3中的內(nèi)存泄漏的問題,需要的朋友可以參考下
    2023-07-07
  • Vue引入highCharts實現(xiàn)數(shù)據(jù)可視化

    Vue引入highCharts實現(xiàn)數(shù)據(jù)可視化

    這篇文章主要為大家詳細(xì)介紹了Vue引入highCharts實現(xiàn)數(shù)據(jù)可視化,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03

最新評論