sign.vue 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. <template>
  2. <ui-group-option
  3. class="sign-tagging"
  4. :class="{active: selected, edit}"
  5. @click="edit && getTaggingIsShow(tagging) && emit('select', true)"
  6. >
  7. <div class="info">
  8. <img
  9. :src="getResource(getFileUrl(tagging.images[0]))"
  10. v-if="tagging.images.length"
  11. >
  12. <div>
  13. <p>{{ tagging.title }}</p>
  14. <span>放置:{{ positions.length }}</span>
  15. </div>
  16. </div>
  17. <div class="actions" @click.stop>
  18. <ui-icon
  19. v-if="!edit"
  20. type="pin"
  21. ctrl
  22. @click.stop="$emit('select', true)"
  23. :class="{ disabled: !getTaggingIsShow(tagging) }"
  24. />
  25. <template v-else>
  26. <ui-icon type="pin1" ctrl @click.stop="$emit('fixed')" tip="放置" />
  27. <ui-more
  28. :options="menus"
  29. style="margin-left: 20px"
  30. @click="(action: keyof typeof actions) => actions[action]()"
  31. />
  32. </template>
  33. </div>
  34. </ui-group-option>
  35. </template>
  36. <script setup lang="ts">
  37. import { getFileUrl } from '@/utils'
  38. import { computed, ref, watchEffect, nextTick } from 'vue';
  39. import { getResource, showTaggingPositionsStack } from '@/env'
  40. import { sdk } from '@/sdk'
  41. import {
  42. getTaggingStyle,
  43. getTaggingPositions,
  44. getFuseModel,
  45. getFuseModelShowVariable,
  46. getTaggingIsShow
  47. } from '@/store'
  48. import type { Tagging } from '@/store'
  49. const props = withDefaults(
  50. defineProps<{ tagging: Tagging, selected?: boolean, edit?: boolean }>(),
  51. { edit: true }
  52. )
  53. const style = computed(() => getTaggingStyle(props.tagging.styleId))
  54. const positions = computed(() => getTaggingPositions(props.tagging))
  55. const emit = defineEmits<{
  56. (e: 'delete'): void
  57. (e: 'edit'): void
  58. (e: 'select', selected: boolean): void
  59. (e: 'fixed'): void
  60. }>()
  61. const menus = [
  62. { label: '编辑', value: 'edit' },
  63. { label: '删除', value: 'delete' },
  64. ]
  65. const actions = {
  66. edit: () => emit('edit'),
  67. delete: () => emit('delete')
  68. }
  69. const flyTaggingPositions = (tagging: Tagging, callback?: () => void) => {
  70. const positions = getTaggingPositions(tagging)
  71. let isStop = false
  72. const flyIndex = (i: number) => {
  73. if (isStop || i >= positions.length) {
  74. callback && nextTick(callback)
  75. return;
  76. }
  77. const position = positions[i]
  78. const model = getFuseModel(position.modelId)
  79. if (!model || !getFuseModelShowVariable(model).value) {
  80. flyIndex(i + 1)
  81. return;
  82. }
  83. const pop = showTaggingPositionsStack.push(ref(new WeakSet([position])))
  84. sdk.comeTo({
  85. position: position.localPos,
  86. modelId: position.modelId,
  87. dur: 300,
  88. distance: 3
  89. })
  90. setTimeout(() => {
  91. pop()
  92. flyIndex(i + 1)
  93. }, 2000)
  94. }
  95. flyIndex(0)
  96. return () => isStop = true
  97. }
  98. watchEffect((onCleanup) => {
  99. if (props.selected) {
  100. const success = () => emit('select', false)
  101. const stop = flyTaggingPositions(props.tagging, success)
  102. const keyupHandler = (ev: KeyboardEvent) => ev.code === 'Escape' && success()
  103. document.documentElement.addEventListener('keyup', keyupHandler, false)
  104. onCleanup(() => {
  105. stop()
  106. document.documentElement.removeEventListener('keyup', keyupHandler, false)
  107. })
  108. }
  109. })
  110. </script>
  111. <style lang="scss" scoped src="./style.scss"></style>