sign-new.vue 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. <template>
  2. <div
  3. v-if="show && posStyle"
  4. class="hot-item pc"
  5. :style="posStyle"
  6. @mouseenter="isHover = true"
  7. @mouseleave="isHover = false"
  8. >
  9. <div @click.stop>
  10. <UIBubble
  11. class="hot-bubble pc"
  12. :show="showContent"
  13. type="left"
  14. level="center"
  15. @click.stop
  16. @pointerdown.stop
  17. @pointerup.stop
  18. >
  19. <h2>
  20. {{ tagging.title }}
  21. <ui-audio
  22. v-if="tagging.audio"
  23. class="audio"
  24. :src="getResources(getFileUrl(tagging.audio))"
  25. ref="audio"
  26. />
  27. </h2>
  28. <div class="content">
  29. <template v-if="defStyleType.id !== taggingStyle?.typeId && tagging.part">
  30. <p><span>遗留部位:</span>{{ tagging.part }}</p>
  31. </template>
  32. <div class="p" v-if="tagging.desc">
  33. <span v-if="defStyleType.id !== taggingStyle?.typeId"> 特征描述: </span>
  34. <div v-html="tagging.desc"></div>
  35. </div>
  36. <template v-if="defStyleType.id !== taggingStyle?.typeId">
  37. <p v-if="tagging.method"><span>提取方法:</span>{{ tagging.method }}</p>
  38. <p v-if="tagging.tqTime"><span>提取时间:</span>{{ tagging.tqTime }}</p>
  39. <p v-if="tagging.principal"><span>提取人:</span>{{ tagging.principal }}</p>
  40. <p v-if="tagging.tqStatus"><span>委托状态:</span>{{ tagging.tqStatus }}</p>
  41. </template>
  42. </div>
  43. <Images
  44. class="images"
  45. :tagging="tagging"
  46. :in-full="true"
  47. @pull="(index) => (pullIndex = index)"
  48. />
  49. <div class="edit-hot" v-if="showDelete">
  50. <span @click="$emit('delete')" class="fun-ctrl">
  51. <ui-icon type="del" />
  52. {{ $t("sys.del") }}
  53. </span>
  54. </div>
  55. </UIBubble>
  56. <Preview
  57. @close="pullIndex = -1"
  58. :current="pullIndex"
  59. :items="queryItems"
  60. v-if="!!~pullIndex"
  61. />
  62. </div>
  63. </div>
  64. </template>
  65. <script lang="ts" setup>
  66. import { computed, markRaw, onUnmounted, ref, watch, watchEffect } from "vue";
  67. import UIBubble from "bill/components/bubble/index.vue";
  68. import Images from "@/views/tagging/hot/images.vue";
  69. import Preview from "../static-preview/index.vue";
  70. import { getTaggingStyle } from "@/store";
  71. import { getFileUrl } from "@/utils";
  72. import { sdk, TaggingPositionNode } from "@/sdk";
  73. import { custom, getResource, getResources } from "@/env";
  74. import type { Tagging, TaggingPosition } from "@/store";
  75. import { useCameraChange, usePixel } from "@/hook/use-pixel";
  76. import { inRevise } from "bill/utils";
  77. import { defStyleType } from "@/api";
  78. export type SignProps = { tagging: Tagging; scenePos: TaggingPosition; show?: boolean };
  79. const props = defineProps<SignProps>();
  80. const emit = defineEmits<{
  81. (e: "delete"): void;
  82. (e: "changeLineHeight", val: number): void;
  83. (
  84. e: "changePosition",
  85. val: { position: SceneLocalPos; modelId: string; normal: SceneLocalPos; pose?: any }
  86. ): void;
  87. }>();
  88. const audio = ref();
  89. watchEffect(() => {
  90. if (props.show && audio.value) {
  91. audio.value.play();
  92. }
  93. });
  94. const [posStyle, pos, pause, recovery] = usePixel(() => undefined);
  95. const queryItems = computed(() =>
  96. props.tagging.images.map((image) => ({
  97. url: getResource(getFileUrl(image)),
  98. }))
  99. );
  100. console.log(props.tagging.styleId);
  101. const taggingStyle = computed(() => getTaggingStyle(props.tagging.styleId));
  102. const tag = markRaw(
  103. sdk.createTagging({
  104. ...props.scenePos,
  105. title: props.tagging.title,
  106. position: props.scenePos.localPos,
  107. canMove: false,
  108. image: getFileUrl(taggingStyle.value!.icon),
  109. })
  110. ) as TaggingPositionNode;
  111. const showDelete = ref(false);
  112. tag.showDelete = (show) => {
  113. showDelete.value = show;
  114. };
  115. tag.changeCanMove(false);
  116. const changePos = () => {
  117. pos.value = { localPos: tag.getImageCenter(), modelId: props.scenePos.modelId };
  118. };
  119. watch(taggingStyle, (icon) => icon && tag.changeImage(getFileUrl(icon.icon)));
  120. watchEffect(() => tag.changeMat(props.scenePos.mat));
  121. watchEffect(() => tag.changeFontSize(props.scenePos.fontSize));
  122. watchEffect(() => tag.changeTitle(props.tagging.title));
  123. watchEffect(() => tag.visibilityTitle(props.tagging.show3dTitle));
  124. watchEffect(() => {
  125. tag.changeType(props.scenePos.type);
  126. changePos();
  127. });
  128. const getPosition = () => ({
  129. position: props.scenePos.localPos,
  130. normal: props.scenePos.normal,
  131. modelId: props.scenePos.modelId,
  132. });
  133. let currentPosition = getPosition();
  134. let changeTimeout: any;
  135. tag.bus.on("changePosition", (data) => {
  136. clearTimeout(changeTimeout);
  137. emit(
  138. "changePosition",
  139. (currentPosition = {
  140. position: { ...data.pos },
  141. normal: { ...data.normal },
  142. modelId: data.modelId,
  143. })
  144. );
  145. changePos();
  146. });
  147. tag.bus.on("scaleChanged", () => changePos());
  148. tag.bus.on("changePosition", (data) => {
  149. clearTimeout(changeTimeout);
  150. currentPosition = {
  151. position: { ...data.pos },
  152. normal: { ...data.normal },
  153. modelId: data.modelId,
  154. };
  155. emit("changePosition", {
  156. ...currentPosition,
  157. pose: sdk.getPose({ modelId: data.modelId, isFlyToTag: true }),
  158. });
  159. changePos();
  160. });
  161. tag.bus.on("changeLineHeight", (lineHeight) => {
  162. emit("changeLineHeight", lineHeight);
  163. });
  164. watch(getPosition, (p) => {
  165. changeTimeout = setTimeout(() => {
  166. if (inRevise(p, currentPosition)) {
  167. tag.changePosition(p);
  168. currentPosition = p;
  169. }
  170. }, 16);
  171. });
  172. watch(
  173. () => props.scenePos.lineHeight,
  174. () => {
  175. changeTimeout = setTimeout(() => {
  176. tag.changeLineHeight(props.scenePos.lineHeight);
  177. changePos();
  178. }, 16);
  179. },
  180. { immediate: true }
  181. );
  182. const [toCameraDistance] = useCameraChange(() => {
  183. return tag.getCameraDisSquared && tag.getCameraDisSquared();
  184. });
  185. const show = computed(
  186. () =>
  187. props.scenePos.globalVisibility ||
  188. toCameraDistance.value <= Math.pow(props.scenePos.visibilityRange, 2)
  189. );
  190. watchEffect(() => tag.visibility(show.value));
  191. const isHover = ref(false);
  192. tag.bus.on("enter", () => {
  193. isHover.value = true;
  194. });
  195. tag.bus.on("leave", () => {
  196. isHover.value = false;
  197. });
  198. tag.bus.on("click", () => iconClickHandler());
  199. const sHover = ref(isHover.value);
  200. let timeout: any;
  201. watchEffect(() => {
  202. clearTimeout(timeout);
  203. if (isHover.value) {
  204. sHover.value = true;
  205. } else {
  206. timeout = setTimeout(() => {
  207. sHover.value = false;
  208. }, 100);
  209. }
  210. });
  211. const pullIndex = ref(-1);
  212. const showContent = computed(() => {
  213. return (
  214. !~pullIndex.value && (sHover.value || custom.showTaggingPositions.has(props.scenePos))
  215. );
  216. });
  217. watchEffect(() => {
  218. if (showContent.value) {
  219. recovery();
  220. } else {
  221. pause();
  222. }
  223. });
  224. const iconClickHandler = () => {
  225. if (custom.showTaggingPositions.has(props.scenePos)) {
  226. custom.showTaggingPositions.delete(props.scenePos);
  227. } else {
  228. custom.showTaggingPositions.add(props.scenePos);
  229. }
  230. };
  231. console.log("标签 创建", props.tagging.id);
  232. onUnmounted(() => {
  233. tag.destroy();
  234. clearTimeout(timeout);
  235. clearTimeout(changeTimeout);
  236. console.error("标签 销毁", props.tagging.id);
  237. });
  238. defineExpose(tag);
  239. </script>
  240. <style lang="scss" scoped>
  241. .hot-item {
  242. pointer-events: all;
  243. position: absolute;
  244. transform: translate(-50%, -100%);
  245. cursor: pointer;
  246. .tag-img {
  247. width: 32px;
  248. height: 32px;
  249. }
  250. .hot-bubble {
  251. cursor: initial;
  252. &.pc {
  253. width: 400px;
  254. }
  255. &:not(.pc) {
  256. width: 80vw;
  257. --bottom-left: 40vw;
  258. }
  259. h2 {
  260. font-size: 20px;
  261. margin-bottom: 10px;
  262. color: #ffffff;
  263. position: relative;
  264. display: flex;
  265. justify-content: space-between;
  266. }
  267. .content {
  268. font-size: 14px;
  269. font-family: MicrosoftYaHei;
  270. color: #999999;
  271. line-height: 1.35em;
  272. margin-bottom: 20px;
  273. word-break: break-all;
  274. .p,
  275. p {
  276. margin-bottom: 10px;
  277. display: flex;
  278. span {
  279. flex: 0 0 auto;
  280. }
  281. }
  282. }
  283. }
  284. &.active,
  285. &:hover {
  286. z-index: 3;
  287. }
  288. }
  289. .edit-hot {
  290. margin-top: 20px;
  291. text-align: right;
  292. span {
  293. font-size: 14px;
  294. color: rgba(255, 255, 255, 0.6);
  295. cursor: pointer;
  296. }
  297. }
  298. .images {
  299. height: 250px;
  300. }
  301. </style>
  302. <style>
  303. .tag-tip {
  304. z-index: 8 !important;
  305. }
  306. .tag-tip p {
  307. padding: 6px 10px !important;
  308. margin: 5px 0 !important;
  309. }
  310. </style>