import { reactive, ref } from 'vue'; import { MessagePlugin } from 'tdesign-vue-next'; import localforage from 'localforage'; import dayjs from 'dayjs'; import axios from 'axios'; import router from '@/router/index'; import { useUser } from '@/services/user'; import { showNotification, Meta2dBackData, checkData } from '@/services/utils'; import { noLoginTip, localStorageName } from '@/services/utils'; import { upload, dataURLtoBlob } from '@/services/file'; import { delImage, addCollection, updateCollection, getLe5leV } from '@/services/api'; import { baseVer } from '@/services/upgrade'; import { debounce } from './debouce'; import { deepClone, isDomShapes } from '@meta2d/core'; import { useSelection } from '@/services/selections'; import { rootDomain } from './defaults'; import { updateObject } from './object'; const { select } = useSelection(); const assets = reactive({ home: `https://${rootDomain.slice(1)}`, account: `https://account${rootDomain}',`, '3d': `https://3d${rootDomain}`, '2d': `https://2d${rootDomain}`, helps_v: [ { name: '产品介绍', url: `https://doc${rootDomain}/document/118756411`, }, { name: '快速上手', url: `https://doc${rootDomain}/document/119363000`, }, { name: '使用手册', url: `https://doc${rootDomain}/document/118764244`, }, { name: '快捷键', url: `https://doc${rootDomain}/document/119620214`, divider: true, }, { name: '企业服务与支持', url: `https://doc${rootDomain}/document/119296274`, divider: true, }, { name: '关于我们', url: `https://${rootDomain.slice(1)}/about.html`, }, ], }); export const useAssets = () => { const getAssets = async () => { // 官网或安装包版本 // if (import.meta.env.VITE_TRIAL != 1) { // return; // } // 企业版 const ret = await axios.get('/api/assets'); if (ret) { Object.assign(assets, ret); } }; return { assets, getAssets, }; }; const meta2dData = reactive({ name:'新建项目' }); export const useMeta2dData = () => { const getMeta2dData = async () => { return meta2dData; }; const setMeta2dData = async (data:any) => { for(const key in data){ meta2d.store.data[key] = data[key]; } updateObject(meta2dData, data); }; return { meta2dData, getMeta2dData, setMeta2dData, }; }; const dot = ref(false); export const useDot = () => { const getDot = async () => { return dot; }; const setDot = async (value = true) => { dot.value = value; if (value) { tree.patch = true; debounce(autoSave, 3000); } }; return { dot, getDot, setDot, }; }; const { user } = useUser(); export enum SaveType { Save, SaveAs, } let saveTimer: any = 0; let saveFlag: boolean = true; export function queryURLParams(value?: string) { let url = value || window.location.href.split('?')[1]; const urlSearchParams = new URLSearchParams(url); const params = Object.fromEntries(urlSearchParams.entries()); return params; } // TODO 保存图纸 export const save = async ( type: SaveType = SaveType.Save, vType?: string, notice?: boolean, userFlag?:number, onlyJson?:boolean ) => { if (!saveFlag) { return; } if (saveTimer) { clearTimeout(saveTimer); } saveFlag = false; saveTimer = setTimeout(() => { saveFlag = true; }, 2000); // 区分是模板还是方案 meta2d.stopAnimate(); if( vType === 'v.component'){ // @ts-ignore meta2d.store.data.component = true } const data: Meta2dBackData = meta2d.data(); if(!data.pens.length){ MessagePlugin.warning('画布为空,无法保存!'); return; } if (!(user && user.id)) { MessagePlugin.warning(noLoginTip); localforage.setItem(localStorageName, JSON.stringify(data)); return; } // 保存为组件 if ( vType === 'v.component' && meta2d.store.active && meta2d.store.active.length === 1 && meta2d.store.active[0].name === 'combine' ) { let comData: any = { center: data.center, folder: '', origin: data.origin, scale: data.scale, x: data.x, y: data.y, }; // comData.name = // (meta2d.store.active[0] as any).description || // `meta2d.${new Date().toLocaleString()}`; comData.name = (meta2d.store.active[0] as any).description ||'新建项目'; let blob: Blob; try { blob = dataURLtoBlob(meta2d.activeToPng(10) + ''); } catch (e) { MessagePlugin.error( '无法下载,宽度不合法,可能没有选中画笔/选中画笔大小超出浏览器最大限制' ); return; } //todo 传参数 const file:any = await upload(blob, true, comData.name , `/大屏/组件/默认`,'new'); if (!file) { return; } // // 缩略图 comData.image = file.url; comData.fullname = file.fullname; comData.component = true; const allPens = meta2d.canvas.getAllByPens(meta2d.store.active); comData.componentDatas = meta2d.toComponent( allPens, (meta2d.store.data as Meta2dBackData).showChild, false ); comData.version = data.version; let ids = allPens.map(item=>item.id); comData.pens = data.pens.filter((pen)=>ids.includes(pen.id)); const body = { data:comData, image:comData.image, name:comData.name, folder: '', template:userFlag===2?true:(data.isTemplate||false), case:comData.case } let ret: any = await addCollection('v.component', body); // 新增 if (ret.error) { return; } MessagePlugin.success('成功保存为组件!'); meta2d.emit('business-save', vType); return; } //删除老图片 新图片上传 checkData(data); if ( (globalThis as any).beforeSaveMeta2d && !(await (globalThis as any).beforeSaveMeta2d(data)) ) { return; } if(!data.isSystem && data.id){ if(vType === 'v-template' && (data.component||data.isTemplate===false)){ //模版 -> 组件/方案 delete data.id; delete data._id; delete data.folder; }else if(vType === 'v.component' && !data.component){ //组件 -> 模版/方案 delete data.id; delete data._id; delete data.folder; }else if(!vType && (data.isTemplate&&data.component)){ //方案 -> 模版/组件 delete data.id; delete data._id; delete data.folder; } } const thumbFolder = vType === 'v-template' ? '模板' : vType === 'v.component' ? '组件' : '方案'; const params = queryURLParams(); if (type === SaveType.SaveAs) { //另存为去掉teams信息 delete data.teams; delete data.folder; delete data.id; } //如果不是自己创建的团队图纸,就不去修改缩略图(没有权限去删除缩略图) if (!data.name) { // 文件名称 // data.name = `meta2d.${new Date().toLocaleString()}`; data.name = `新建项目`; (meta2d.store.data as Meta2dBackData).name = data.name; } if (data.component || vType === 'v.component') { data.component = true; // pens 存储原数据用于二次编辑 ; componentDatas 组合后的数据,用于复用 data.componentDatas = meta2d.toComponent( undefined, (meta2d.store.data as Meta2dBackData).showChild, false //自定义组合节点生成默认锚点 ); } else { data.component = false; // 必要值 } let collection = data.component ? 'v.component' : 'v'; if(data.id && data.ownerId !== user.id){ //不是拥有者,判断有无编辑权限 let temRet:any = await updateCollection(collection, {id:data.id || data._id, name: data.name}); if(temRet.error){ return; } } if(!onlyJson){ // if (!data.id || (data.ownerId === user.id)) { // 图纸不是创建用户的图纸 先不改缩略图 for (const pen of meta2d.store.data.pens) { if (['iframe'].includes(pen.name)) { //重新生成绘制图片 pen.onRenderPenRaw?.(pen); } } let blob: Blob; try { blob = dataURLtoBlob(meta2d.toPng(10) + ''); } catch (e) { MessagePlugin.error( '无法下载,宽度不合法,画布可能没有画笔/画布大小超出浏览器最大限制' ); return; } let conflict = 'new'; // if (data.id && type === SaveType.Save) { // // conflict = undefined // if (data.image && !(await delImage(data.image))) { // return; // } // } const file:any = await upload( blob, true, // 更新是缩略图路径一般为/file/2023/1025/1/1/新建项目_dfa573a5 // data.image?.split('/').pop()更新操作项目名不变 data.name,// + '.' + dayjs().format('YYYYMMDDHHMMss'), `/大屏/${thumbFolder}/${data.folder || '默认'}`, conflict ); if (!file) { return; } // if (data.id && type === SaveType.Save) { // // conflict = undefined // if (data.image && !(await delImage(data.image))) { // return; // } // } // 缩略图 data.image = file.url; data.filename = file.filename; (meta2d.store.data as Meta2dBackData).image = data.image; (meta2d.store.data as Meta2dBackData).filename = data.filename; // } }else{ data.image = undefined; } let ret: any; !data.version && (data.version = baseVer); // TODO if (!data.folder) { data.folder = ''; // folder.name; } let childIds = []; data.pens.forEach((pen)=>{ if(pen.name === 'iframe'){ if(pen.iframe.indexOf(`view${rootDomain}`) !== -1){ let params = queryURLParams(pen.iframe.split('?')[1]) childIds.push(params.id); } } }); // data.childIds = childIds; const body:any = { data, image:data.image, name:data.name, folder:data.folder, // shared:true, template:userFlag===2?true:(data.isTemplate||false), case:data.case, childIds } delete data.name; delete data.image; delete data.folder; delete data.case; delete data.isSystem; delete data.isTemplate; if(params.n && data.ownerId !== user.id){ for (const k of delAttrs) { delete (data as any)[k]; } } if (type === SaveType.SaveAs) { // 另存为一定走 新增 ,由于后端 未控制 userId 等属性,清空一下 for (const k of delAttrs) { delete (data as any)[k]; } body.template = false; body.component = false; ret = await addCollection(collection, body); } else { // if (data._id && data.teams && data.owner?.id !== user.id) { // // 团队图纸 不允许修改文件夹信息 // delete data.folder; // ret = await updateCollection(collection, data); // } else if (data.id || data._id) { body.ownerId = data.ownerId; body.ownerName = data.ownerName; ret = await updateCollection(collection, {id:data.id || data._id,...body}); } else { ret = await addCollection(collection, body); // 新增 } } if (ret.error) { return; } // 保存图纸之后的钩子函数 globalThis.afterSaveMeta2d && (await globalThis.afterSaveMeta2d(ret)); if ( !data.id || router.currentRoute.value.query.version || type === SaveType.SaveAs // 另存为肯定走新增,也会产生新的 id ) { data.id = ret.id; (meta2d.store.data as Meta2dBackData).id = data.id; router.replace({ path: '/', query: { id: data.id, r: Date.now() + '', c: vType === 'v.component' ? 1 : undefined, }, }); } notice && MessagePlugin.success('保存成功!'); meta2d.emit('business-save', vType); dot.value = false; localforage.removeItem(localStorageName); return true; }; export const chargeDialogShow = ref(false); export const autoSaveAS = async(id:string)=>{ //先请求打开图纸 const ret: any = await getLe5leV(id); if(!ret.data && ret.price>0){ return; } sessionStorage.setItem('opening', '1'); router.push({ path: '/', query: { r: Date.now() + '', }, }); //去除图纸一些信息 for (const k of delAttrs) { delete ret[k]; delete ret.data[k]; } chargeDialogShow.value = false; meta2d.open(ret.data); meta2d.fitView(); //另存为付费用户图纸 setTimeout(()=>{ save(SaveType.SaveAs, '', undefined, 1); },500) } const pen = ref(false); export const drawPen = () => { meta2d.inactive(); try { if (!meta2d.canvas.drawingLineName) { // 开了钢笔,需要关掉铅笔 meta2d.canvas.pencil && drawingPencil(); meta2d.drawLine(meta2d.store.options.drawingLineName); } else { meta2d.finishDrawLine(); meta2d.drawLine(); } //钢笔 pen.value = !!meta2d.canvas.drawingLineName; } catch (e: any) { MessagePlugin.warning(e.message); } }; const pencil = ref(false); const drawingPencil = () => { try { if (!meta2d.canvas.pencil) { // 开了铅笔需要关掉钢笔 meta2d.canvas.drawingLineName && drawPen(); meta2d.drawingPencil(); } else { meta2d.stopPencil(); } pencil.value = meta2d.canvas.pencil || false; } catch (e: any) { MessagePlugin.warning(e.message); } }; export const magnifier = ref(false); export const showMagnifier = () => { if (!meta2d.canvas.magnifierCanvas.magnifier) { meta2d.showMagnifier(); } else { meta2d.hideMagnifier(); } magnifier.value = meta2d.canvas.magnifierCanvas.magnifier; }; export const map = ref(false); export const showMap = () => { if (!meta2d.map?.isShow) { meta2d.showMap(); } else { meta2d.hideMap(); } map.value = meta2d.map?.isShow; }; export const title = '系统可能不会保存您所做的更改,是否继续?'; export const unLogin = '未登录,系统可能不会保存您的文件,是否继续?'; export const unsave = '当前文件未保存,是否继续?(开通vip可享受自动保存服务)'; export function autoSave(force = false) { if (!dot.value && (!force || router.currentRoute.value.query.id)) { return; } const data: any = meta2d.data(); if ( user && user.id && user.vip && (data.id||data._id) && !data.component && // data.owner && data.ownerId === user.id ) { let _vType = ''; if (data.isTemplate) { _vType = 'v-template'; } save(SaveType.Save, _vType, false, undefined, true); } else { // data.updateAt = dayjs().format(); localforage.setItem(localStorageName, JSON.stringify(data)); } } // export const notificFn = async (fn: Function, params: any) => { // if (!(user && user.id)) { // if (await showNotification(unLogin)) { // fn(params); // } // } else { // if (dot.value) { // if (user.isVip) { // if (await save(SaveType.Save)) { // fn(params); // } // } else { // if (await showNotification(unsave)) { // fn(params); // } // } // } else { // fn(params); // } // } // }; export const onScaleWindow = () => { // meta2d.fitView(); meta2d.fitSizeView(true, 32); }; export const onScaleFull = () => { meta2d.scale(1); // meta2d.centerView(); const { x, y, origin, center } = meta2d.store.data; meta2d.translate(-x - origin.x, -y - origin.y); meta2d.translate(meta2d.store.options.x || 0, meta2d.store.options.y || 0); }; export const blank = async (save = true) => { meta2d.canvas.drawingLineName && drawPen(); meta2d.canvas.pencil && drawingPencil(); meta2d.canvas.magnifierCanvas.magnifier && showMagnifier(); meta2d.map?.isShow && showMap(); save && autoSave(true); dot.value = false; sessionStorage.removeItem('openingId'); meta2d.open({ pens: [], background: '#1e2430', color: '#bdc7db', width: 1920, height: 1080 } as any); select(); }; let lastTime = false; export const newFile = () => { if(dot.value && !lastTime){ MessagePlugin.warning('当前画布未保存,请先保存!'); lastTime = true; return; } lastTime = false; blank(); router.push({ path: '/', query: { r: Date.now() + '', }, }); // setTimeout(() => { // autoSave(true); // }, 300); meta2d.emit('business-assets',true); }; export const inTreePanel = reactive({ value: false, timer: undefined, }); const tree = reactive({ list: [], patch: true, }); export const getPenTree = () => { tree.patch = false; const list = []; for (const item of meta2d.store.data.pens) { if (item.parentId) { continue; } const elem = calcElem(item); elem && list.push(elem); } tree.list = list.reverse(); return tree.list; }; const iframeTree = reactive({ list: [], patch: true, }); export const getIframeTree = () => { iframeTree.patch = false; const list = []; for (const item of meta2d.store.data.pens) { if (item.name !== 'iframe') { continue; } const elem = calcElem(item); elem && list.push(elem); } iframeTree.list = list.reverse(); return iframeTree.list; }; export const getPenAnimations = (idOrTag?: string) => { const animations = []; let pens: any[] = meta2d.store.active || []; if (idOrTag) { pens = meta2d.find(idOrTag) || []; } for (const pen of pens) { if (pen.animations) { for (const a of pen.animations) { animations.push(a.name); } } } return Array.from(new Set(animations)); }; const calcElem = (node: any) => { if (!node) { return; } let tag = '中'; if ( isDomShapes.includes(node.name) || node.name.endsWith('Dom') || meta2d.store.options.domShapes.includes(node.name) ) { tag = 'dom'; } else if (node.canvasLayer === 1) { tag = '模板'; } else if (node.name === 'image') { if(node.canvasLayer === 2){ tag = '下层'; }else if(node.canvasLayer===3){ tag = '中层'; }else{ tag = '上层'; } } else { tag = '中层'; } const elem: any = { label: (node as any).description || node.name, value: node.id, locked: node.locked, visible: node.visible, tag, }; if (!node.children) { return elem; } elem.children = []; for (const id of node.children) { const child = calcElem(meta2d.store.pens[id]); child && elem.children.push(child); } return elem; }; export const setChildrenVisible = (node: any, v: boolean) => { const children = node.getChildren(); if (children && children.length > 0) { for (const child of children) { child.data.visible = v; setChildrenVisible(child, v); } } }; export const fonts = [ '新宋体', '微软雅黑', '黑体', '楷体', '斗鱼追光体', '庞门正道标题体', 'DS-DIGI-1', 'DS-DIGIB-2', 'DS-DIGII-3', 'DS-DIGIT-4', 'ALIBABA Regular', 'ALIBABA Bold', '-apple-system', 'BlinkMacSystemFont', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei UI', 'Microsoft YaHei', 'fangsong', 'Source Han Sans CN', 'sans-serif', 'serif', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', ]; export const delAttrs = [ 'userId', 'shared', 'team', 'owner', 'username', 'editor', 'editorId', 'editorName', 'createdAt', 'tags', 'image', 'id', '_id', 'view', 'updatedAt', 'star', 'recommend', ]; export const typeOptions = [ { label: '字符串', value: 'string', }, { label: '整数', value: 'integer', }, { label: '浮点数', value: 'float', }, { label: '布尔', value: 'bool', }, { label: '对象', value: 'object', }, { label: '数组', value: 'array', }, ]; let folder = reactive({ _id: '', }); export const useFolder = () => { const getFolder = async () => { return folder; }; const setFolder = async (value: any) => { folder = deepClone(value); }; return { folder, getFolder, setFolder, }; }; export function changeType(value: string) { if (value === 'false') { return false; } else if (value === 'true') { return true; } else if ( value === '' || isNaN(Number(value)) || (typeof value === 'string' && value.endsWith('.')) ) { // 小数也进入这个 return value; } return Number(value); } // 附加缓存数据 const extendData = reactive({ lineIntersect: false, }); export const useExtendData = () => { const setExtendData = (key: any,value:any) => { extendData[key] = value; } const getExtendData = (key: any) => { return extendData[key]; } return { extendData, getExtendData, setExtendData, }; }; export function dealDataBeforeOpen(data){ data.oldV = false; data.pens.forEach((pen)=>{ if(pen.image){ pen.image = pen.image.split('?')[0]; } if (!(pen.animations && pen.animations.length)) { if (!pen.type && pen.frames) { pen.animations = [ { name: '动画1', temType: 'id', animate: 'custom', frames: deepClone(pen.frames), animateCycle: pen.animateCycle, keepAnimateState: pen.keepAnimateState, linear: pen.linear, autoPlay: pen.autoPlay, nextAnimate: pen.nextAnimate, }, ]; data.oldV = true; } else if (pen.type && pen.lineAnimateType !== undefined) { pen.animations = [ { name: '动画1', temType: 'id', animateSpan: pen.animateSpan || 1, lineAnimateType: pen.lineAnimateType, animateColor: pen.animateColor, animateReverse: pen.animateReverse, animateCycle: pen.animateCycle, animateLineWidth: pen.animateLineWidth, autoPlay: pen.autoPlay, nextAnimate: pen.nextAnimate, }, ]; data.oldV = true; } } if (pen.events?.length) { for (let i = 0; i < pen.events.length; i++) { const event = pen.events[i]; // pen.events.forEach((event)=>{ if (event.action !== undefined) { data.oldV = true; // 老格式 if (event.name === 'valueUpdate') { if (event.where && event.where.key) { /* if (!pen.realTimes) { pen.realTimes = []; } let index = pen.realTimes.findIndex( (el) => el.key === event.where.key ); let trigger = { name: '状态1', conditionType: 'and', conditions: [ { type: event.where?.type === 'custom' ? 'fn' : '', key: event.where?.key, operator: event.where?.comparison, value: event.where?.value, fnJs: event.where?.fnJs, }, ], actions: [ { action: event.action, value: event.value, params: event.params, }, ], }; if (index !== -1) { pen.realTimes[index].triggers.push(trigger); } else { pen.realTimes.push({ label: event.where.key, key: event.where.key, triggers: [trigger], }); } pen.events.splice(i, 1); i--;*/ //new if (!pen.triggers) { pen.triggers = []; } let trigger = { name: '状态1', conditionType: 'and', conditions: [ { type: event.where?.type === 'custom' ? 'fn' : '', key: event.where?.key, operator: event.where?.comparison, value: event.where?.value, fnJs: event.where?.fnJs, }, ], actions: [ { action: event.action, value: event.value, params: event.params, }, ], }; let index = pen.triggers.findIndex( (el) => el.temKey === event.where.key ); if(index !== -1){ const state = pen.triggers[index].status; const idx = state.findIndex((el)=>el.conditions?.length&&el.conditions[0].key === trigger.conditions[0].key&&el.conditions[0].operator === trigger.conditions[0].operator&&el.conditions[0].value === trigger.conditions[0].value); if(idx!==-1){ state[idx].actions.push(...deepClone(trigger.actions)); }else{ pen.triggers[index].status.push(trigger); } }else{ pen.triggers.push({ temKey:event.where?.key, name:`场景状态${i+1}`, status:[trigger] }); } pen.events.splice(i, 1); i--; } else { //TODO 没有选择触发条件的情况 if (event.value.indexOf('pen.value') !== -1) { if (!pen.realTimes) { pen.realTimes = []; } pen.realTimes.push({ label: 'value', key: 'value', triggers: [ { name: '状态1', conditionType: 'and', conditions: [], actions: [ { action: event.action, value: event.value, params: event.params, }, ], }, ], }); pen.events.splice(i, 1); i--; } } } else { event.conditionType = 'and'; event.actions = [ { action: event.action, value: event.value, params: event.params, }, ]; if (event.where && event.where.type) { event.conditions = [ { type: event.where.type === 'custom' ? 'fn' : '', key: event.where.key, operator: event.where.comparison, //valueType target value: event.where.value, fnJs: event.where.fnJs, }, ]; } delete event.action; delete event.value; delete event.params; delete event.where; } } // }); } } //状态格式转换 if(!(pen.triggers?.length) && pen.realTimes?.length){ pen.triggers = []; pen.realTimes.forEach((realTime,index)=>{ const delIdx = []; realTime.triggers?.forEach((trigger,_idx)=>{ if(trigger.conditions?.length){ trigger.conditions.forEach((condition,_idx)=>{ condition.key = realTime.key; condition.keyLabel = realTime.label || realTime.key; }); if(trigger.conditions?.length === 1){ const _idx2 = realTime.triggers.findIndex((el)=>el.conditions?.length&&el.conditions[0].key === trigger.conditions[0].key&&el.conditions[0].operator === trigger.conditions[0].operator&&el.conditions[0].value === trigger.conditions[0].value); if(_idx !== _idx2){ realTime.triggers[_idx2].actions.push(...deepClone(realTime.triggers[_idx].actions)); delIdx.push(_idx); } } } }); delIdx.forEach((idx,index)=>{ realTime.triggers.splice((idx-index),1); }); if(realTime.triggers?.length){ pen.triggers.push({ name:`状态情况${index+1}`, status:deepClone(realTime.triggers) }); } delete realTime.triggers; }); } if(pen.triggers?.length){ pen.triggers = pen.triggers.filter((item)=>item.status?.length); } if(pen.name === 'table2'){ pen.name = 'tablePlus' } }) }