help.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import { appConstant } from "@/app";
  2. import {
  3. SceneTypeDesc,
  4. SceneTypeDomain,
  5. SceneTypePaths,
  6. } from "@/constant/scene";
  7. import { alert } from "@/helper/message";
  8. import { getCaseSceneList, getSyncSceneInfo } from "@/store/case";
  9. import { CaseTagging } from "@/store/caseTagging";
  10. import { ModelScene, QuoteScene, Scene, SceneType } from "@/store/scene";
  11. import { transformSWToken, user } from "@/store/user";
  12. import { base64ToBlob, drawImage } from "@/util";
  13. import { ref, watchEffect } from "vue";
  14. export type MenuItem = {
  15. key: string;
  16. label: string;
  17. onClick: (caseId: number) => void;
  18. };
  19. export const getFuseCodeLink = (caseId: number, query?: boolean) => {
  20. const params: { token?: string; caseId: string; app: string } = {
  21. caseId: caseId.toString(),
  22. app: appConstant.deptId.toString(),
  23. };
  24. if (!query) {
  25. params.token = user.value.token;
  26. }
  27. const url = new URL(
  28. SceneTypePaths[SceneType.SWMX][0],
  29. SceneTypeDomain[SceneType.SWMX]
  30. );
  31. for (const [name, val] of Object.entries(params)) {
  32. url.searchParams.append(name, val || "");
  33. }
  34. return url.href;
  35. };
  36. export const getSWKKSyncLink = async (caseId: number) => {
  37. const scenes = await getCaseSceneList(caseId);
  38. const supportTypes = [
  39. SceneType.SWKK,
  40. SceneType.SWKJ,
  41. SceneType.SWSSMX,
  42. SceneType.SWYDMX,
  43. ];
  44. const kkScenes = scenes.filter((scene) =>
  45. supportTypes.includes(scene.type)
  46. ) as QuoteScene[];
  47. let msg: string | null = null;
  48. if (!scenes.length) {
  49. msg = "当前案件下无场景,请先添加场景。";
  50. } else if (!kkScenes.length) {
  51. msg = `带看仅支持${supportTypes
  52. .map((type) => SceneTypeDesc[type])
  53. .join("、")}类型场景,请添加此类型场景。`;
  54. }
  55. if (msg) {
  56. alert(msg);
  57. throw msg;
  58. }
  59. const url = new URL(
  60. SceneTypePaths[SceneType.SWKK][2],
  61. SceneTypeDomain[SceneType.SWKK]
  62. );
  63. const roomId = await getSyncSceneInfo(caseId);
  64. const params = {
  65. vruserId: user.value.info.userName,
  66. // platform: "fd",
  67. roomId,
  68. // domain: location.href,
  69. // fromMiniApp: "0",
  70. role: "leader",
  71. avatar: user.value.info.avatar,
  72. redirect: encodeURIComponent(location.href),
  73. name: user.value.info.userName,
  74. // isTour: "0",
  75. m: kkScenes[0].num,
  76. };
  77. for (const [name, val] of Object.entries(params)) {
  78. url.searchParams.append(name, val || "");
  79. }
  80. return url;
  81. };
  82. export const checkScenesOpen = async (caseId: number, url: URL | string) => {
  83. const scenes = await getCaseSceneList(caseId);
  84. if (!scenes.length) {
  85. alert("当前案件下无场景,请先添加场景。");
  86. } else {
  87. window.open(url);
  88. }
  89. };
  90. export const getQuery = (
  91. caseId: number,
  92. share: boolean = false,
  93. single: boolean = false
  94. ) =>
  95. `${getFuseCodeLink(caseId, true)}${share ? "&share=1" : ""}${
  96. single ? "&single=1" : ""
  97. }#show/summary`;
  98. // 查看
  99. export const gotoQuery = (caseId: number) => {
  100. checkScenesOpen(caseId, getQuery(caseId, true));
  101. };
  102. export enum FuseImageType {
  103. FUSE,
  104. KANKAN,
  105. LASER,
  106. }
  107. export const getSceneIframe = (iframe: HTMLIFrameElement) => {
  108. const iframeElement = iframe.contentWindow?.document.documentElement;
  109. if (!iframeElement) {
  110. return null;
  111. }
  112. const extIframe = iframeElement.querySelector(
  113. ".external"
  114. ) as HTMLIFrameElement;
  115. return extIframe || iframe;
  116. };
  117. export const getIframeSceneType = async (iframe: HTMLIFrameElement) => {
  118. const targetIframe = getSceneIframe(iframe);
  119. if (!targetIframe) {
  120. return null;
  121. }
  122. const targetWindow: any = targetIframe.contentWindow;
  123. const fuseCnavas = targetWindow.document.querySelector(
  124. ".scene-canvas > canvas"
  125. ) as HTMLElement;
  126. if (fuseCnavas) {
  127. return { type: FuseImageType.FUSE, sdk: targetWindow.sdk };
  128. }
  129. const isLaser = targetWindow.document.querySelector(".laser-layer");
  130. return {
  131. type: isLaser ? FuseImageType.LASER : FuseImageType.KANKAN,
  132. sdk: await targetWindow.__sdk,
  133. };
  134. };
  135. // 处理融合iframe页面
  136. export const fuseIframeHandler = (iframe: HTMLIFrameElement) => {
  137. const currentType = ref<FuseImageType>();
  138. const interval = setInterval(async () => {
  139. const typeMap = await getIframeSceneType(iframe);
  140. currentType.value = typeMap?.type;
  141. }, 100);
  142. const stopWatch = watchEffect((onCleanup) => {
  143. // if (currentType.value === FuseImageType.LASER) {
  144. // const $iframe = getSceneIframe(iframe)!;
  145. // const target = $iframe.contentWindow!.document.head;
  146. // const $style = document.createElement("style");
  147. // $style.type = "text/css";
  148. // var textNode = document.createTextNode(`
  149. // .mode-tab > .model-mode-tab.strengthen {
  150. // display: none
  151. // }
  152. // `);
  153. // $style.appendChild(textNode);
  154. // target.appendChild($style);
  155. // }
  156. });
  157. return () => {
  158. clearInterval(interval);
  159. stopWatch();
  160. };
  161. };
  162. // 获取fuse场景截图
  163. export type FuseImageRet = { type: FuseImageType; blob: Blob | null };
  164. export const getFuseImage = async (
  165. iframe: HTMLIFrameElement,
  166. width: number = 500,
  167. height: number = width
  168. ) => {
  169. const typeMap = await getIframeSceneType(iframe);
  170. let blob: Blob | null = null;
  171. switch (typeMap?.type) {
  172. case FuseImageType.FUSE:
  173. const dataURL = await typeMap.sdk.screenshot(width, height);
  174. const res = await fetch(dataURL);
  175. blob = await res.blob();
  176. break;
  177. case FuseImageType.KANKAN:
  178. const result = await typeMap.sdk.Camera.screenshot(
  179. [{ width: width, height: width, name: "2k" }],
  180. false
  181. );
  182. blob = base64ToBlob(result[0].data);
  183. break;
  184. case FuseImageType.LASER:
  185. blob = await new Promise<Blob | null>((resolve) => {
  186. typeMap.sdk.scene
  187. .screenshot(width, height)
  188. .done((data: { dataUrl: string }) =>
  189. resolve(base64ToBlob(data.dataUrl))
  190. );
  191. });
  192. break;
  193. }
  194. return { type: typeMap?.type, blob };
  195. };
  196. // 处理fuse融合一起的图,将热点加入图片内
  197. export const fuseImageJoinHot = async (
  198. iframe: HTMLIFrameElement,
  199. rawBlob: Blob,
  200. showTags: CaseTagging[],
  201. width = 500
  202. ) => {
  203. // fuse场景需要特别处理
  204. const img = new Image();
  205. img.src = URL.createObjectURL(rawBlob);
  206. await new Promise((resolve) => (img.onload = resolve));
  207. const $canvas = document.createElement("canvas");
  208. $canvas.width = img.width;
  209. $canvas.height = img.height;
  210. const ctx = $canvas.getContext("2d")!;
  211. ctx.drawImage(img, 0, 0, img.width, img.height);
  212. const contentDoc = iframe.contentWindow!.document;
  213. const hotItems = Array.from(
  214. contentDoc.querySelectorAll(".hot-item")
  215. ) as HTMLDivElement[];
  216. hotItems.forEach((hot) => {
  217. const hotTitle = (hot.querySelector(".tip") as HTMLDivElement).innerText;
  218. const index = showTags.findIndex(
  219. (tag) => tag.tagTitle.trim() === hotTitle.trim()
  220. );
  221. if (index !== -1) {
  222. const bound = hot.getBoundingClientRect();
  223. const size = (img.width / width) * 32;
  224. const left = bound.left + size / 2;
  225. const top = bound.top + size / 2;
  226. ctx.save();
  227. ctx.translate(left, top);
  228. ctx.beginPath();
  229. ctx.arc(0, 0, size / 2, 0, 2 * Math.PI);
  230. ctx.strokeStyle = "#000";
  231. ctx.fillStyle = "#fff";
  232. ctx.stroke();
  233. ctx.fill();
  234. ctx.beginPath();
  235. ctx.fillStyle = "#000";
  236. ctx.textAlign = "center";
  237. ctx.textBaseline = "middle";
  238. ctx.font = `normal ${size / 2}px serif`;
  239. ctx.fillText((index + 1).toString(), 0, 0);
  240. ctx.restore();
  241. }
  242. });
  243. const $ccanvas = document.createElement("canvas");
  244. $ccanvas.width = width;
  245. $ccanvas.height = width;
  246. const cctx = $ccanvas.getContext("2d")!;
  247. drawImage(
  248. cctx,
  249. $ccanvas.width,
  250. $ccanvas.height,
  251. $canvas,
  252. img.width,
  253. img.height,
  254. 0,
  255. 0
  256. );
  257. const blob = await new Promise<Blob | null>((resolve) =>
  258. $ccanvas.toBlob(resolve, "png")
  259. );
  260. return blob;
  261. };
  262. export enum OpenType {
  263. query,
  264. edit,
  265. }
  266. export const openSceneUrl = async (scene: Scene, type: OpenType) => {
  267. const pathname = SceneTypePaths[scene.type][type];
  268. const url = new URL(pathname || "", window.location.href);
  269. if (scene.type === SceneType.SWMX) {
  270. url.searchParams.append(
  271. "modelId",
  272. (scene as ModelScene).modelId.toString()
  273. );
  274. url.hash = "#sign-model";
  275. url.searchParams.append("share", "1");
  276. url.searchParams.append("app", appConstant.deptId.toString());
  277. } else {
  278. url.searchParams.append("m", (scene as QuoteScene).num);
  279. if (type === OpenType.edit) {
  280. url.searchParams.append(
  281. "token",
  282. await transformSWToken(scene as QuoteScene)
  283. );
  284. }
  285. }
  286. window.open(url);
  287. };