import { appConstant } from "@/app"; import { SceneTypeDesc, SceneTypeDomain, SceneTypePaths, } from "@/constant/scene"; import { alert } from "@/helper/message"; import { getCaseSceneList, getSyncSceneInfo } from "@/store/case"; import { CaseTagging } from "@/store/caseTagging"; import { ModelScene, QuoteScene, Scene, SceneType } from "@/store/scene"; import { transformSWToken, user } from "@/store/user"; import { base64ToBlob, drawImage } from "@/util"; import { ref, watchEffect } from "vue"; export type MenuItem = { key: string; label: string; onClick: (caseId: number) => void; }; export const getFuseCodeLink = (caseId: number, query?: boolean) => { const params: { token?: string; caseId: string; app: string } = { caseId: caseId.toString(), app: appConstant.deptId.toString(), }; if (!query) { params.token = user.value.token; } const url = new URL( SceneTypePaths[SceneType.SWMX][0], SceneTypeDomain[SceneType.SWMX] ); for (const [name, val] of Object.entries(params)) { url.searchParams.append(name, val || ""); } return url.href; }; export const getSWKKSyncLink = async (caseId: number) => { const scenes = await getCaseSceneList(caseId); const supportTypes = [ SceneType.SWKK, SceneType.SWKJ, SceneType.SWSSMX, SceneType.SWYDMX, ]; const kkScenes = scenes.filter((scene) => supportTypes.includes(scene.type) ) as QuoteScene[]; let msg: string | null = null; if (!scenes.length) { msg = "当前案件下无场景,请先添加场景。"; } else if (!kkScenes.length) { msg = `带看仅支持${supportTypes .map((type) => SceneTypeDesc[type]) .join("、")}类型场景,请添加此类型场景。`; } if (msg) { alert(msg); throw msg; } const url = new URL( SceneTypePaths[SceneType.SWKK][2], SceneTypeDomain[SceneType.SWKK] ); const roomId = await getSyncSceneInfo(caseId); const params = { vruserId: user.value.info.userName, // platform: "fd", roomId, // domain: location.href, // fromMiniApp: "0", role: "leader", avatar: user.value.info.avatar, redirect: encodeURIComponent(location.href), name: user.value.info.userName, // isTour: "0", m: kkScenes[0].num, }; for (const [name, val] of Object.entries(params)) { url.searchParams.append(name, val || ""); } return url; }; export const checkScenesOpen = async (caseId: number, url: URL | string) => { const scenes = await getCaseSceneList(caseId); if (!scenes.length) { alert("当前案件下无场景,请先添加场景。"); } else { window.open(url); } }; export const getQuery = ( caseId: number, share: boolean = false, single: boolean = false ) => `${getFuseCodeLink(caseId, true)}${share ? "&share=1" : ""}${ single ? "&single=1" : "" }#show/summary`; // 查看 export const gotoQuery = (caseId: number) => { checkScenesOpen(caseId, getQuery(caseId, true)); }; export enum FuseImageType { FUSE, KANKAN, LASER, } export const getSceneIframe = (iframe: HTMLIFrameElement) => { const iframeElement = iframe.contentWindow?.document.documentElement; if (!iframeElement) { return null; } const extIframe = iframeElement.querySelector( ".external" ) as HTMLIFrameElement; return extIframe || iframe; }; export const getIframeSceneType = async (iframe: HTMLIFrameElement) => { const targetIframe = getSceneIframe(iframe); if (!targetIframe) { return null; } const targetWindow: any = targetIframe.contentWindow; const fuseCnavas = targetWindow.document.querySelector( ".scene-canvas > canvas" ) as HTMLElement; if (fuseCnavas) { return { type: FuseImageType.FUSE, sdk: targetWindow.sdk }; } const isLaser = targetWindow.document.querySelector(".laser-layer"); return { type: isLaser ? FuseImageType.LASER : FuseImageType.KANKAN, sdk: await targetWindow.__sdk, }; }; // 处理融合iframe页面 export const fuseIframeHandler = (iframe: HTMLIFrameElement) => { const currentType = ref(); const interval = setInterval(async () => { const typeMap = await getIframeSceneType(iframe); currentType.value = typeMap?.type; }, 100); const stopWatch = watchEffect((onCleanup) => { // if (currentType.value === FuseImageType.LASER) { // const $iframe = getSceneIframe(iframe)!; // const target = $iframe.contentWindow!.document.head; // const $style = document.createElement("style"); // $style.type = "text/css"; // var textNode = document.createTextNode(` // .mode-tab > .model-mode-tab.strengthen { // display: none // } // `); // $style.appendChild(textNode); // target.appendChild($style); // } }); return () => { clearInterval(interval); stopWatch(); }; }; // 获取fuse场景截图 export type FuseImageRet = { type: FuseImageType; blob: Blob | null }; export const getFuseImage = async ( iframe: HTMLIFrameElement, width: number = 500, height: number = width ) => { const typeMap = await getIframeSceneType(iframe); let blob: Blob | null = null; switch (typeMap?.type) { case FuseImageType.FUSE: const dataURL = await typeMap.sdk.screenshot(width, height); const res = await fetch(dataURL); blob = await res.blob(); break; case FuseImageType.KANKAN: const result = await typeMap.sdk.Camera.screenshot( [{ width: width, height: width, name: "2k" }], false ); blob = base64ToBlob(result[0].data); break; case FuseImageType.LASER: blob = await new Promise((resolve) => { typeMap.sdk.scene .screenshot(width, height) .done((data: { dataUrl: string }) => resolve(base64ToBlob(data.dataUrl)) ); }); break; } return { type: typeMap?.type, blob }; }; // 处理fuse融合一起的图,将热点加入图片内 export const fuseImageJoinHot = async ( iframe: HTMLIFrameElement, rawBlob: Blob, showTags: CaseTagging[], width = 500 ) => { // fuse场景需要特别处理 const img = new Image(); img.src = URL.createObjectURL(rawBlob); await new Promise((resolve) => (img.onload = resolve)); const $canvas = document.createElement("canvas"); $canvas.width = img.width; $canvas.height = img.height; const ctx = $canvas.getContext("2d")!; ctx.drawImage(img, 0, 0, img.width, img.height); const contentDoc = iframe.contentWindow!.document; const hotItems = Array.from( contentDoc.querySelectorAll(".hot-item") ) as HTMLDivElement[]; hotItems.forEach((hot) => { const hotTitle = (hot.querySelector(".tip") as HTMLDivElement).innerText; const index = showTags.findIndex( (tag) => tag.tagTitle.trim() === hotTitle.trim() ); if (index !== -1) { const bound = hot.getBoundingClientRect(); const size = (img.width / width) * 32; const left = bound.left + size / 2; const top = bound.top + size / 2; ctx.save(); ctx.translate(left, top); ctx.beginPath(); ctx.arc(0, 0, size / 2, 0, 2 * Math.PI); ctx.strokeStyle = "#000"; ctx.fillStyle = "#fff"; ctx.stroke(); ctx.fill(); ctx.beginPath(); ctx.fillStyle = "#000"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.font = `normal ${size / 2}px serif`; ctx.fillText((index + 1).toString(), 0, 0); ctx.restore(); } }); const $ccanvas = document.createElement("canvas"); $ccanvas.width = width; $ccanvas.height = width; const cctx = $ccanvas.getContext("2d")!; drawImage( cctx, $ccanvas.width, $ccanvas.height, $canvas, img.width, img.height, 0, 0 ); const blob = await new Promise((resolve) => $ccanvas.toBlob(resolve, "png") ); return blob; }; export enum OpenType { query, edit, } export const openSceneUrl = async (scene: Scene, type: OpenType) => { const pathname = SceneTypePaths[scene.type][type]; const url = new URL(pathname || "", window.location.href); if (scene.type === SceneType.SWMX) { url.searchParams.append( "modelId", (scene as ModelScene).modelId.toString() ); url.hash = "#sign-model"; url.searchParams.append("share", "1"); url.searchParams.append("app", appConstant.deptId.toString()); } else { url.searchParams.append("m", (scene as QuoteScene).num); if (type === OpenType.edit) { url.searchParams.append( "token", await transformSWToken(scene as QuoteScene) ); } } window.open(url); };