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 } from '@/services/api'; import { baseVer } from '@/services/upgrade'; import { debounce } from './debouce'; import { deepClone, isDomShapes } from '@meta2d/core'; import { useSelection } from '@/services/selections'; const { select } = useSelection(); const assets = reactive({ home: 'https://le5le.com', account: 'https://account.le5le.com', '3d': 'https://3d.le5le.com', '2d': 'https://2d.le5le.com', helps: [ { name: '产品介绍', url: 'https://doc.le5le.com/document/118756411', }, { name: '快速上手', url: 'https://doc.le5le.com/document/119363000', }, { name: '使用手册', url: 'https://doc.le5le.com/document/118764244', }, { name: '快捷键', url: 'https://doc.le5le.com/document/119620214', divider: true, }, { name: '企业服务与支持', url: 'https://doc.le5le.com/document/119296274', divider: true, }, { name: '关于我们', url: 'https://le5le.com/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 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 const _save = async ( type: SaveType = SaveType.Save, vType?: string, notice?: boolean ) => { if (!saveFlag) { return; } if (saveTimer) { clearTimeout(saveTimer); } saveFlag = false; saveTimer = setTimeout(() => { saveFlag = true; }, 2000); meta2d.stopAnimate(); const data: Meta2dBackData = meta2d.data(); 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, }; let blob: Blob; try { blob = dataURLtoBlob(meta2d.activeToPng(10) + ''); } catch (e) { MessagePlugin.error( '无法下载,宽度不合法,可能没有选中画笔/选中画笔大小超出浏览器最大限制' ); return; } const file:any = await upload(blob, true); if (!file) { return; } // 缩略图 comData.image = file.url; comData.component = true; const allPens = meta2d.canvas.getAllByPens(meta2d.store.active); comData.componentDatas = meta2d.toComponent( allPens, (meta2d.store.data as Meta2dBackData).showChild, false ); comData.name = (meta2d.store.active[0] as any).description || `meta2d.${new Date().toLocaleString()}`; let ret: any = await addCollection('v.component.', comData); // 新增 if (ret.error) { return; } MessagePlugin.success('成功保存为组件!'); return; } checkData(data); // if (!data._id && router.currentRoute.value.query.id) { // data._id = router.currentRoute.value.query.id as string; // } if ( (globalThis as any).beforeSaveMeta2d && !(await (globalThis as any).beforeSaveMeta2d(data)) ) { return; } if (!data.tags.includes('系统模板') && !data.tags.includes('系统方案')) { if (vType === 'v.template') { if (data.tags && !data.tags.includes('模板')) { delete data._id; delete data.id; delete data.folder; } data.tags = ['模板']; } else if (!vType) { if (data.tags && !data.tags.includes('方案')) { delete data._id; delete data.id; delete data.folder; } data.tags = ['方案']; } } if (type === SaveType.SaveAs) { //另存为去掉teams信息 delete data.teams; delete data.folder; delete data.id; } //如果不是自己创建的团队图纸,就不去修改缩略图(没有权限去删除缩略图) if (!(data.teams && data.owner?.id !== user.id)) { for (const pen of meta2d.store.data.pens) { if (['iframe'].includes(pen.name)) { //重新生成绘制图片 pen.onRenderPenRaw?.(pen); } } // setTimeout(async () => { let blob: Blob; try { blob = dataURLtoBlob(meta2d.toPng(10) + ''); } catch (e) { MessagePlugin.error( '无法下载,宽度不合法,画布可能没有画笔/画布大小超出浏览器最大限制' ); return; } if (data._id && type === SaveType.Save) { if (data.image && !(await delImage(data.image))) { return; } } const file:any = await upload(blob, true); if (!file) { return; } // 缩略图 data.image = file.url; (meta2d.store.data as Meta2dBackData).image = data.image; // }, 1000); } // setTimeout(async () => { 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'; let ret: any; if (!data.name) { // 文件名称 data.name = `meta2d.${new Date().toLocaleString()}`; (meta2d.store.data as Meta2dBackData).name = data.name; } !data.version && (data.version = baseVer); // TODO if (!data.folder) { data.folder = ''; // folder.name; } let _list: any = undefined; let _folderId: any = undefined; if (data.folder) { if (!folder._id) { let type = vType; if (!type) { type = 'v'; } let folderRet: any = await axios.post('/api/data/folders/get', { query: { type, name: data.folder, }, }); if (!folderRet.error) { _list = folderRet.list; _folderId = folderRet._id; } } else { _list = folder.list; _folderId = folder._id; } } let _temType = ''; if (type === SaveType.SaveAs) { // 另存为一定走 新增 ,由于后端 未控制 userId 等属性,清空一下 for (const k of delAttrs) { delete (data as any)[k]; } if (!data.tags || !data.tags.length) { data.tags = ['方案']; } ret = await addCollection(collection, data); } else { // if (data._id && data.teams && data.owner?.id !== user.id) { // // 团队图纸 不允许修改文件夹信息 // delete data.folder; // ret = await updateCollection(collection, data); // } else if (data._id) { ret = await updateCollection(collection, data); _temType = 'update'; } else { ret = await addCollection(collection, data); // 新增 _temType = 'add'; } } if (ret.error) { return; } if (_folderId) { if (_temType === 'add') { //文件夹 _list.push({ image: data.image, name: data.name, _id: ret._id, }); } else if (_temType === 'update') { _list.forEach((i: any) => { if (i._id === data._id) { i.image = data.image; i.name = data.name; } }); } await axios.post('/api/data/folders/update', { _id: _folderId, list: _list.map((el) => { return { name: el.name, id: el.id, _id: el._id, image: el.image, component: el.component, }; }), }); } // 保存图纸之后的钩子函数 globalThis.afterSaveMeta2d && (await globalThis.afterSaveMeta2d(ret)); if ( !data._id || data.owner?.id !== user.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: data.component ? 1 : undefined, }, }); } notice && MessagePlugin.success('保存成功!'); meta2d.emit('business-save', vType); dot.value = false; localforage.removeItem(localStorageName); return true; // }, 2000); }; // TODO 保存图纸 export const save = async ( type: SaveType = SaveType.Save, vType?: string, notice?: boolean, userFlag?:number ) => { if (!saveFlag) { return; } if (saveTimer) { clearTimeout(saveTimer); } saveFlag = false; saveTimer = setTimeout(() => { saveFlag = true; }, 2000); // 区分是模板还是方案 meta2d.stopAnimate(); if( vType === 'v.component'){ meta2d.store.data.component = true } const data: Meta2dBackData = meta2d.data(); 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 = 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 ); const body = { data:comData, image:comData.image, name:comData.name, folder: '', userFlag } 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.tags && !data.tags.includes('系统模板') && !data.tags.includes('系统方案') ) { if (vType === 'v-template') { if (!data.tags.includes('模板')) { delete data.id; delete data._id; delete data.folder; data.image = data.image?.split('_').shift();// 避免保存为不同类型会增加不必要的唯一字符串 } data.tags = ['模板']; } else if (!vType) { if (!data.tags.includes('方案')) { delete data.id; delete data._id; delete data.folder; data.image = data.image?.split('_').shift(); } data.tags = ['方案']; } else if(vType === 'v.component') { if (!data.tags.includes('组件')) { delete data.id; delete data._id; delete data.folder; data.image = data.image?.split('_').shift(); } data.tags = ['组件']; } } const thumbFolder = vType === 'v-template' ? '模板' : vType === 'v.component' ? '组件' : '方案'; 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.teams && data.owner?.id !== 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.image?.split('/').pop() || data.name, `/大屏/${thumbFolder}/${data.folder || '默认'}`, conflict ); if (!file) { 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; } 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'; let ret: any; !data.version && (data.version = baseVer); // TODO if (!data.folder) { data.folder = ''; // folder.name; } const body = { data, image:data.image, name:data.name, folder:data.folder, shared:true, userFlag } if (!data.tags || !data.tags.length) { data.tags = [thumbFolder]; } if (type === SaveType.SaveAs) { // 另存为一定走 新增 ,由于后端 未控制 userId 等属性,清空一下 for (const k of delAttrs) { delete (data as any)[k]; } if (!data.tags || !data.tags.length) { data.tags = ['方案']; } 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) { 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; }; 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.owner.id === user.id ) { let _vType = ''; if (data.tags.includes('模板')) { _vType = 'v-template'; } save(SaveType.Save, _vType); } 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; meta2d.open({ pens: [] } as any); select(); }; export const newFile = () => { blank(); router.push({ path: '/', query: { r: Date.now() + '', }, }); // setTimeout(() => { // console.log("autoSave") // 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; return tree.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.template) { tag = '模板'; } else if (node.name === 'image') { if (node.isBottom) { 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 = [ '新宋体', '微软雅黑', '黑体', '楷体', '斗鱼追光体', '庞门正道标题体', '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); }