help.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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 = 390
  168. ) => {
  169. const typeMap = await getIframeSceneType(iframe);
  170. let blob: Blob | null = null;
  171. switch (typeMap?.type) {
  172. case FuseImageType.FUSE:
  173. console.error(width, height);
  174. const dataURL = await typeMap.sdk.screenshot(width, height, 0);
  175. const res = await fetch(dataURL);
  176. blob = await res.blob();
  177. break;
  178. case FuseImageType.KANKAN:
  179. console.error("截图尺寸", width, height);
  180. const result = await typeMap.sdk.Camera.screenshot(
  181. [{ width: width, height: height, name: "2k", bgOpacity: 0 }],
  182. false
  183. );
  184. blob = base64ToBlob(result[0].data);
  185. break;
  186. case FuseImageType.LASER:
  187. blob = await new Promise<Blob | null>((resolve) => {
  188. typeMap.sdk.scene
  189. .screenshot(width, height, 0)
  190. .done((data: { dataUrl: string }) =>
  191. resolve(base64ToBlob(data.dataUrl))
  192. );
  193. });
  194. break;
  195. }
  196. return { type: typeMap?.type, blob };
  197. };
  198. // 处理fuse融合一起的图,将热点加入图片内
  199. export const fuseImageJoinHot = async (
  200. iframe: HTMLIFrameElement,
  201. rawBlob: Blob,
  202. showTags: CaseTagging[],
  203. width = 500,
  204. height = 390,
  205. scale = 1
  206. ) => {
  207. console.log(width, showTags);
  208. // fuse场景需要特别处理
  209. const img = new Image();
  210. img.src = URL.createObjectURL(rawBlob);
  211. await new Promise((resolve) => (img.onload = resolve));
  212. const $canvas = document.createElement("canvas");
  213. $canvas.width = img.width;
  214. $canvas.height = img.height;
  215. const ctx = $canvas.getContext("2d")!;
  216. ctx.drawImage(img, 0, 0, img.width, img.height);
  217. const contentDoc = iframe.contentWindow!.document;
  218. const hotItems = Array.from(
  219. contentDoc.querySelectorAll(".hot-item")
  220. ) as HTMLDivElement[];
  221. hotItems.forEach((hot) => {
  222. const hotTitle = (hot.querySelector(".tip") as HTMLDivElement)?.innerText;
  223. const index = showTags.findIndex(
  224. (tag) => tag.tagTitle.trim() === hotTitle?.trim()
  225. );
  226. if (index !== -1) {
  227. const bound = hot.getBoundingClientRect();
  228. const hscale = img.width / contentDoc.body.offsetWidth;
  229. const wsize = hscale * 32;
  230. const hsize = hscale * 32;
  231. const left = bound.left * hscale + wsize / 2;
  232. const top = bound.top * hscale + hsize / 2;
  233. ctx.save();
  234. ctx.translate(left, top);
  235. ctx.beginPath();
  236. ctx.arc(0, 0, hsize / 2, 0, 2 * Math.PI);
  237. ctx.strokeStyle = "#000";
  238. ctx.fillStyle = "#fff";
  239. ctx.stroke();
  240. ctx.fill();
  241. ctx.beginPath();
  242. ctx.fillStyle = "#000";
  243. ctx.textAlign = "center";
  244. ctx.textBaseline = "middle";
  245. ctx.font = `normal ${hsize / 2}px serif`;
  246. ctx.fillText((index + 1).toString(), 0, 0);
  247. ctx.restore();
  248. }
  249. });
  250. const $ccanvas = document.createElement("canvas");
  251. $ccanvas.width = width;
  252. $ccanvas.height = height;
  253. const cctx = $ccanvas.getContext("2d")!;
  254. drawImage(
  255. cctx,
  256. $ccanvas.width,
  257. $ccanvas.height,
  258. $canvas,
  259. img.width,
  260. img.height,
  261. 0,
  262. 0
  263. );
  264. const blob = await new Promise<Blob | null>((resolve) =>
  265. $ccanvas.toBlob(resolve, "png")
  266. );
  267. return blob;
  268. };
  269. export enum OpenType {
  270. query,
  271. edit,
  272. }
  273. export const openSceneUrl = async (scene: Scene, type: OpenType) => {
  274. const pathname = SceneTypePaths[scene.type][type];
  275. const url = new URL(pathname || "", window.location.href);
  276. if (scene.type === SceneType.SWMX) {
  277. url.searchParams.append(
  278. "modelId",
  279. (scene as ModelScene).modelId.toString()
  280. );
  281. url.hash = "#sign-model";
  282. url.searchParams.append("share", "1");
  283. url.searchParams.append("app", appConstant.deptId.toString());
  284. } else {
  285. url.searchParams.append("m", (scene as QuoteScene).num);
  286. if (type === OpenType.edit) {
  287. url.searchParams.append(
  288. "token",
  289. await transformSWToken(scene as QuoteScene)
  290. );
  291. }
  292. }
  293. window.open(url);
  294. };