parts.ts 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { defineStore } from 'pinia'
  2. import { computed, ref } from 'vue'
  3. import { renamePart as apiRenamePart } from '../api/models'
  4. import { getViewer } from '../three/instance'
  5. import type { PartMeta, PartStatus } from '../types'
  6. import { useModelStore } from './model'
  7. export const usePartsStore = defineStore('parts', () => {
  8. const parts = ref<PartMeta[]>([])
  9. const selected = ref<Set<string>>(new Set())
  10. const queryOpen = ref(false)
  11. const search = ref('')
  12. const pendingSelectLabel = ref<string | null>(null)
  13. const selectedParts = computed(() => parts.value.filter((p) => selected.value.has(p.id)))
  14. const selectedCount = computed(() => selected.value.size)
  15. function byId(id: string): PartMeta | undefined {
  16. return parts.value.find((p) => p.id === id)
  17. }
  18. function setParts(list: PartMeta[]): void {
  19. parts.value = list
  20. selected.value = new Set()
  21. }
  22. function syncViewerSelection(): void {
  23. getViewer()?.setSelectedIds([...selected.value])
  24. }
  25. function selectOnly(id: string | null): void {
  26. selected.value = new Set(id ? [id] : [])
  27. syncViewerSelection()
  28. }
  29. function toggle(id: string, additive: boolean): void {
  30. if (!additive) { selectOnly(id); return }
  31. const next = new Set(selected.value)
  32. if (next.has(id)) next.delete(id)
  33. else next.add(id)
  34. selected.value = next
  35. syncViewerSelection()
  36. }
  37. function clearSelection(): void {
  38. selected.value = new Set()
  39. syncViewerSelection()
  40. }
  41. function setColor(id: string, hex: string, status: PartStatus | null): void {
  42. const part = byId(id)
  43. if (!part) return
  44. part.colorHex = hex
  45. part.status = status
  46. getViewer()?.setPartColor(id, hex, status)
  47. }
  48. function resetColor(id: string): void {
  49. const part = byId(id)
  50. if (!part) return
  51. part.colorHex = null
  52. part.status = null
  53. getViewer()?.resetPart(id)
  54. }
  55. function applyPreset(hex: string, status: PartStatus | null): void {
  56. if (!selected.value.size) return
  57. selected.value.forEach((id) => setColor(id, hex, status))
  58. }
  59. function resetSelected(): void {
  60. if (!selected.value.size) return
  61. selected.value.forEach((id) => resetColor(id))
  62. }
  63. function resetAll(): void {
  64. parts.value.forEach((p) => resetColor(p.id))
  65. }
  66. /** 重命名部件(写回 .glb 并同步映射表),随后重载模型并按新名重新选中。 */
  67. async function renamePart(label: string, newName: string): Promise<void> {
  68. const modelStore = useModelStore()
  69. const result = await apiRenamePart(modelStore.current, label, newName)
  70. pendingSelectLabel.value = result.new_label
  71. modelStore.requestReload()
  72. }
  73. return {
  74. parts, selected, queryOpen, search, pendingSelectLabel,
  75. selectedParts, selectedCount,
  76. byId, setParts, selectOnly, toggle, clearSelection,
  77. setColor, resetColor, applyPreset, resetSelected, resetAll, renamePart,
  78. }
  79. })