selectFuseImage.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <template>
  2. <div class="house-layout">
  3. <div class="iframe-layout">
  4. <iframe :src="fuseUrl" ref="iframeRef" />
  5. </div>
  6. <div class="content-layout">
  7. <div class="house-tags">
  8. <h4>请选择要同步到现场图的标注:</h4>
  9. <div
  10. class="tagging-transfer"
  11. v-sortable="{ handle: '.value-option' }"
  12. ref="dragLayout"
  13. >
  14. <el-transfer
  15. :titles="['所有', '需要']"
  16. v-model="transferValue"
  17. :data="transferSource"
  18. target-order="push"
  19. >
  20. <template #default="{ option }">
  21. <span
  22. :class="{ 'value-option': transferValue.includes(option.key) }"
  23. :id="'o' + option.key"
  24. :draggable="transferValue.includes(option.key)"
  25. >
  26. <span>{{ option.label }}</span>
  27. <el-icon v-if="transferValue.includes(option.key)"><Rank /></el-icon>
  28. </span>
  29. </template>
  30. </el-transfer>
  31. </div>
  32. </div>
  33. <div class="house-image-layout">
  34. <h4>
  35. 户型图:
  36. <el-icon><Refresh class="icon" @click="refreshBlob" /></el-icon>
  37. </h4>
  38. <div class="house-image">
  39. <div>
  40. <img :src="coverUrl" v-if="coverUrl" />
  41. <el-empty description="暂无数据" v-else />
  42. </div>
  43. </div>
  44. </div>
  45. </div>
  46. </div>
  47. </template>
  48. <script setup lang="ts">
  49. import { CaseTagging, getCaseTaggings } from "@/store/caseTagging";
  50. import {
  51. getFuseImage,
  52. getQuery,
  53. FuseImageType,
  54. fuseImageJoinHot,
  55. fuseIframeHandler,
  56. } from "@/view/case/help";
  57. import { computed, nextTick, onMounted, ref, watchEffect } from "vue";
  58. import { QuiskExpose } from "@/helper/mount";
  59. import { imageCropper } from "@/view/system/quisk";
  60. export type FuseImage = { blob: Blob | null; taggings: CaseTagging[] };
  61. const props = defineProps<{ caseId: number }>();
  62. const fuseUrl = computed(() => getQuery(props.caseId, true, true));
  63. const taggings = ref<CaseTagging[]>([]);
  64. const transferSource = computed(() =>
  65. taggings.value.map((item) => ({
  66. key: item.tagId,
  67. data: item,
  68. label: item.tagTitle,
  69. }))
  70. );
  71. const transferValue = ref<number[]>([]);
  72. const selectTaggings = computed(() =>
  73. transferValue.value.map((tagId) => taggings.value.find((tag) => tag.tagId === tagId)!)
  74. );
  75. const iframeRef = ref<HTMLIFrameElement>();
  76. const imageBlob = ref<Blob | null>(null);
  77. const coverUrl = computed(() => imageBlob.value && URL.createObjectURL(imageBlob.value));
  78. watchEffect((onClanup) => {
  79. if (iframeRef.value) {
  80. onClanup(fuseIframeHandler(iframeRef.value));
  81. }
  82. });
  83. const refreshBlob = async () => {
  84. if (!iframeRef.value) {
  85. return;
  86. }
  87. const scale = 1.564;
  88. const width = Math.ceil(540 * scale);
  89. const height = Math.ceil(390 * scale);
  90. const fuseImage = await getFuseImage(iframeRef.value, width, height);
  91. if (fuseImage?.blob) {
  92. console.log('fuseImage', fuseImage?.blob);
  93. const cropBlob = await imageCropper({
  94. img: fuseImage?.blob,
  95. fixed: [width, height]
  96. })
  97. imageBlob.value =
  98. fuseImage.type !== FuseImageType.FUSE
  99. ? cropBlob
  100. : await fuseImageJoinHot(
  101. iframeRef.value,
  102. cropBlob,
  103. selectTaggings.value,
  104. width,
  105. height,
  106. scale
  107. );
  108. }
  109. };
  110. defineExpose<QuiskExpose>({
  111. submit() {
  112. return { blob: imageBlob.value, taggings: selectTaggings.value };
  113. },
  114. });
  115. onMounted(async () => {
  116. taggings.value = await getCaseTaggings(props.caseId);
  117. });
  118. watchEffect(async (onClanup) => {
  119. transferValue.value.join("");
  120. await nextTick();
  121. const desps: (() => void)[] = [];
  122. let repKey: number | null;
  123. for (const key of transferValue.value) {
  124. const $option = document.querySelector("#o" + key) as HTMLDivElement;
  125. const startHandler = () => {
  126. $option.classList.add("dragging");
  127. repKey = key;
  128. $option.addEventListener("dragend", function endHandler(ev) {
  129. $option.classList.remove("dragging");
  130. $option.removeEventListener("dragend", endHandler);
  131. });
  132. };
  133. const dragoverHandler = (ev: Event) => ev.preventDefault();
  134. const dropHandler = (ev: Event) => {
  135. ev.preventDefault();
  136. if (repKey && key !== repKey) {
  137. const oldIndex = transferValue.value.indexOf(key);
  138. const newIndex = transferValue.value.indexOf(repKey);
  139. transferValue.value[newIndex] = key;
  140. transferValue.value[oldIndex] = repKey;
  141. }
  142. };
  143. $option.addEventListener("dragstart", startHandler);
  144. $option.addEventListener("dragover", dragoverHandler);
  145. $option.addEventListener("drop", dropHandler);
  146. desps.push(() => {
  147. $option.removeEventListener("dragstart", startHandler);
  148. $option.removeEventListener("dragover", startHandler);
  149. $option.removeEventListener("drop", dropHandler);
  150. });
  151. }
  152. onClanup(() => {
  153. desps.forEach((desp) => desp());
  154. });
  155. });
  156. </script>
  157. <style lang="scss" scoped>
  158. .house-layout {
  159. --w: calc(540 / 390 * var(--h));
  160. --h: 610px;
  161. display: flex;
  162. height: var(--h);
  163. }
  164. .iframe-layout {
  165. height: 100%;
  166. flex: 0 0 var(--w);
  167. display: flex;
  168. align-items: center;
  169. iframe {
  170. border: none;
  171. width: 100%;
  172. height: var(--h);
  173. }
  174. }
  175. .content-layout {
  176. flex: none;
  177. width: 360px;
  178. display: flex;
  179. flex-direction: column;
  180. margin-left: 40px;
  181. height: 100%;
  182. justify-content: space-between;
  183. h4 {
  184. font-size: 14px;
  185. font-weight: 400;
  186. color: rgba(0, 0, 0, 0.85);
  187. line-height: 22px;
  188. margin-bottom: 15px;
  189. flex: none;
  190. span {
  191. float: right;
  192. }
  193. }
  194. > .house-tags {
  195. flex: none;
  196. display: flex;
  197. flex-direction: column;
  198. > .tagging-transfer {
  199. flex: 1;
  200. overflow: hidden;
  201. }
  202. }
  203. }
  204. .house-image {
  205. border: 1px solid #d9d9d9;
  206. padding-top: 68%;
  207. position: relative;
  208. > div {
  209. position: absolute;
  210. inset: 0;
  211. width: 100%;
  212. display: flex;
  213. align-items: center;
  214. justify-content: center;
  215. }
  216. img {
  217. width: 100%;
  218. height: 100%;
  219. object-fit: contain;
  220. }
  221. }
  222. </style>
  223. <style lang="scss">
  224. .tagging-transfer {
  225. .el-transfer {
  226. display: flex;
  227. --el-transfer-panel-body-height: 160px;
  228. }
  229. .el-transfer__buttons {
  230. padding: 0 8px;
  231. display: flex;
  232. flex-direction: column;
  233. justify-content: center;
  234. button {
  235. padding: 0;
  236. width: 24px;
  237. height: 24px;
  238. display: block;
  239. margin: 5px 0;
  240. }
  241. }
  242. .el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label {
  243. font-size: 14px;
  244. }
  245. }
  246. .icon {
  247. cursor: pointer;
  248. }
  249. .value-option {
  250. display: flex;
  251. align-items: center;
  252. span {
  253. flex: 1;
  254. overflow: hidden;
  255. text-overflow: ellipsis;
  256. }
  257. i {
  258. flex: 0 0 auto;
  259. }
  260. }
  261. </style>
  262. <style>
  263. .value-option.dragging {
  264. opacity: 0.5;
  265. }
  266. </style>