common.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. import { reactive, ref } from 'vue';
  2. import { MessagePlugin } from 'tdesign-vue-next';
  3. import localforage from 'localforage';
  4. import dayjs from 'dayjs';
  5. import axios from 'axios';
  6. import router from '@/router/index';
  7. import { useUser } from '@/services/user';
  8. import { showNotification, Meta2dBackData, checkData } from '@/services/utils';
  9. import { noLoginTip, localStorageName } from '@/services/utils';
  10. import { upload, dataURLtoBlob } from '@/services/file';
  11. import { delImage, addCollection, updateCollection } from '@/services/api';
  12. import { baseVer } from '@/services/upgrade';
  13. import { debounce } from './debouce';
  14. const assets = reactive({
  15. home: 'https://le5le.com',
  16. account: 'https://account.le5le.com',
  17. helps: [
  18. {
  19. name: '产品介绍',
  20. url: 'https://doc.le5le.com/document/118756411',
  21. },
  22. {
  23. name: '快速上手',
  24. url: 'https://doc.le5le.com/document/119363000',
  25. },
  26. {
  27. name: '使用手册',
  28. url: 'https://doc.le5le.com/document/118764244',
  29. },
  30. {
  31. name: '快捷键',
  32. url: 'https://doc.le5le.com/document/119620214',
  33. divider: true,
  34. },
  35. {
  36. name: '企业服务与支持',
  37. url: 'https://doc.le5le.com/document/119296274',
  38. divider: true,
  39. },
  40. {
  41. name: '关于我们',
  42. url: 'https://le5le.com/about.html',
  43. },
  44. ],
  45. });
  46. export const useAssets = () => {
  47. const getAssets = async () => {
  48. // 官网或安装包版本
  49. if (
  50. import.meta.env.VITE_TRIAL == undefined ||
  51. import.meta.env.VITE_TRIAL == 1
  52. ) {
  53. return;
  54. }
  55. // 企业版
  56. const ret = await axios.get('/api/assets');
  57. if (ret) {
  58. Object.assign(assets, ret);
  59. }
  60. };
  61. return {
  62. assets,
  63. getAssets,
  64. };
  65. };
  66. const dot = ref(false);
  67. export const useDot = () => {
  68. const getDot = async () => {
  69. return dot;
  70. };
  71. const setDot = async (value = true) => {
  72. dot.value = value;
  73. if (value) {
  74. tree.patch = true;
  75. debounce(autoSave, 3000);
  76. }
  77. };
  78. return {
  79. dot,
  80. getDot,
  81. setDot,
  82. };
  83. };
  84. const { user } = useUser();
  85. export enum SaveType {
  86. Save,
  87. SaveAs,
  88. }
  89. export const save = async (
  90. type: SaveType = SaveType.Save,
  91. component?: boolean,
  92. notice?: boolean
  93. ) => {
  94. meta2d.stopAnimate();
  95. const data: Meta2dBackData = meta2d.data();
  96. if (!(user && user.id)) {
  97. MessagePlugin.warning(noLoginTip);
  98. localforage.setItem(localStorageName, JSON.stringify(data));
  99. return;
  100. }
  101. checkData(data);
  102. if (!data._id && router.currentRoute.value.query.id) {
  103. data._id = router.currentRoute.value.query.id as string;
  104. }
  105. if (
  106. (globalThis as any).beforeSaveMeta2d &&
  107. !(await (globalThis as any).beforeSaveMeta2d(data))
  108. ) {
  109. return;
  110. }
  111. if (type === SaveType.SaveAs) {
  112. //另存为去掉teams信息
  113. delete data.teams;
  114. }
  115. //如果不是自己创建的团队图纸,就不去修改缩略图(没有权限去删除缩略图)
  116. if (!(data.teams && data.owner?.id !== user.id)) {
  117. let blob: Blob;
  118. try {
  119. blob = dataURLtoBlob(meta2d.toPng(10) + '');
  120. } catch (e) {
  121. MessagePlugin.error(
  122. '无法下载,宽度不合法,画布可能没有画笔/画布大小超出浏览器最大限制'
  123. );
  124. return;
  125. }
  126. if (data._id && type === SaveType.Save) {
  127. if (data.image && !(await delImage(data.image))) {
  128. return;
  129. }
  130. }
  131. const file = await upload(blob, true);
  132. if (!file) {
  133. return;
  134. }
  135. // 缩略图
  136. data.image = file.url;
  137. (meta2d.store.data as Meta2dBackData).image = data.image;
  138. }
  139. if (data.component || component) {
  140. data.component = true;
  141. // pens 存储原数据用于二次编辑 ; componentDatas 组合后的数据,用于复用
  142. data.componentDatas = meta2d.toComponent(
  143. undefined,
  144. (meta2d.store.data as Meta2dBackData).showChild,
  145. false //自定义组合节点生成默认锚点
  146. );
  147. } else {
  148. data.component = false; // 必要值
  149. }
  150. let collection = data.component ? 'le5leV-components' : 'le5leV';
  151. let ret: any;
  152. if (!data.name) {
  153. // 文件名称
  154. data.name = `meta2d.${new Date().toLocaleString()}`;
  155. (meta2d.store.data as Meta2dBackData).name = data.name;
  156. }
  157. !data.version && (data.version = baseVer);
  158. if (!data.folder) {
  159. data.folder = '';
  160. }
  161. if (type === SaveType.SaveAs) {
  162. // 另存为一定走 新增 ,由于后端 未控制 userId 等属性,清空一下
  163. const delAttrs = [
  164. 'userId',
  165. '_id',
  166. 'id',
  167. 'shared',
  168. 'star',
  169. 'view',
  170. 'username',
  171. 'editorName',
  172. 'editorId',
  173. 'createdAt',
  174. 'updatedAt',
  175. 'recommend',
  176. 'team',
  177. ];
  178. for (const k of delAttrs) {
  179. delete (data as any)[k];
  180. }
  181. ret = await addCollection(collection, data);
  182. } else {
  183. if (data._id && data.teams && data.owner?.id !== user.id) {
  184. // 团队图纸 不允许修改文件夹信息
  185. delete data.folder;
  186. ret = await updateCollection(collection, data);
  187. } else if (data._id) {
  188. ret = await updateCollection(collection, data);
  189. } else {
  190. ret = await addCollection(collection, data); // 新增
  191. }
  192. }
  193. if (ret.error) {
  194. return;
  195. }
  196. // 保存图纸之后的钩子函数
  197. globalThis.afterSaveMeta2d && (await globalThis.afterSaveMeta2d(ret));
  198. if (
  199. !data._id ||
  200. data.owner?.id !== user.id ||
  201. router.currentRoute.value.query.version ||
  202. type === SaveType.SaveAs // 另存为肯定走新增,也会产生新的 id
  203. ) {
  204. data._id = ret._id;
  205. (meta2d.store.data as Meta2dBackData)._id = data._id;
  206. router.replace({
  207. path: '/',
  208. query: {
  209. id: data._id,
  210. r: Date.now() + '',
  211. component: data.component + '',
  212. },
  213. });
  214. }
  215. notice && MessagePlugin.success('保存成功!');
  216. dot.value = false;
  217. localforage.removeItem(localStorageName);
  218. return true;
  219. };
  220. const pen = ref(false);
  221. export const drawPen = () => {
  222. meta2d.inactive();
  223. try {
  224. if (!meta2d.canvas.drawingLineName) {
  225. // 开了钢笔,需要关掉铅笔
  226. meta2d.canvas.pencil && drawingPencil();
  227. meta2d.drawLine(meta2d.store.options.drawingLineName);
  228. } else {
  229. meta2d.finishDrawLine();
  230. meta2d.drawLine();
  231. }
  232. //钢笔
  233. pen.value = !!meta2d.canvas.drawingLineName;
  234. } catch (e: any) {
  235. MessagePlugin.warning(e.message);
  236. }
  237. };
  238. const pencil = ref(false);
  239. const drawingPencil = () => {
  240. try {
  241. if (!meta2d.canvas.pencil) {
  242. // 开了铅笔需要关掉钢笔
  243. meta2d.canvas.drawingLineName && drawPen();
  244. meta2d.drawingPencil();
  245. } else {
  246. meta2d.stopPencil();
  247. }
  248. pencil.value = meta2d.canvas.pencil || false;
  249. } catch (e: any) {
  250. MessagePlugin.warning(e.message);
  251. }
  252. };
  253. export const magnifier = ref(false);
  254. export const showMagnifier = () => {
  255. if (!meta2d.canvas.magnifierCanvas.magnifier) {
  256. meta2d.showMagnifier();
  257. } else {
  258. meta2d.hideMagnifier();
  259. }
  260. magnifier.value = meta2d.canvas.magnifierCanvas.magnifier;
  261. };
  262. export const map = ref(false);
  263. export const showMap = () => {
  264. if (!meta2d.map?.isShow) {
  265. meta2d.showMap();
  266. } else {
  267. meta2d.hideMap();
  268. }
  269. map.value = meta2d.map?.isShow;
  270. };
  271. export const title = '系统可能不会保存您所做的更改,是否继续?';
  272. export const unLogin = '未登录,系统可能不会保存您的文件,是否继续?';
  273. export const unsave = '当前文件未保存,是否继续?(开通vip可享受自动保存服务)';
  274. export function autoSave(force = false) {
  275. if (!dot.value && (!force || router.currentRoute.value.query.id)) {
  276. return;
  277. }
  278. const data: any = meta2d.data();
  279. if (
  280. user &&
  281. user.id &&
  282. user.isVip &&
  283. data._id &&
  284. !data.component &&
  285. data.owner &&
  286. data.owner.id === user.id
  287. ) {
  288. save(SaveType.Save);
  289. } else {
  290. data.updateAt = dayjs().format();
  291. localforage.setItem(localStorageName, JSON.stringify(data));
  292. }
  293. }
  294. export const notificFn = async (fn: Function, params: any) => {
  295. if (!(user && user.id)) {
  296. if (await showNotification(unLogin)) {
  297. fn(params);
  298. }
  299. } else {
  300. if (dot.value) {
  301. if (user.isVip) {
  302. if (await save(SaveType.Save)) {
  303. fn(params);
  304. }
  305. } else {
  306. if (await showNotification(unsave)) {
  307. fn(params);
  308. }
  309. }
  310. } else {
  311. fn(params);
  312. }
  313. }
  314. };
  315. export const onScaleWindow = () => {
  316. // meta2d.fitView();
  317. meta2d.fitSizeView(true, 32);
  318. };
  319. export const onScaleView = () => {
  320. meta2d.scale(1);
  321. // meta2d.centerView();
  322. const { x, y, origin, center } = meta2d.store.data;
  323. meta2d.translate(-x - origin.x, -y - origin.y);
  324. meta2d.translate(meta2d.store.options.x || 0, meta2d.store.options.y || 0);
  325. };
  326. export const blank = async (save = true) => {
  327. meta2d.canvas.drawingLineName && drawPen();
  328. meta2d.canvas.pencil && drawingPencil();
  329. meta2d.canvas.magnifierCanvas.magnifier && showMagnifier();
  330. meta2d.map?.isShow && showMap();
  331. save && autoSave(true);
  332. dot.value = false;
  333. meta2d.open({ pens: [] } as any);
  334. };
  335. export const newFile = () => {
  336. blank();
  337. router.push({
  338. path: '/',
  339. query: {
  340. r: Date.now() + '',
  341. },
  342. });
  343. setTimeout(() => {
  344. autoSave(true);
  345. }, 300);
  346. };
  347. const tree = reactive({
  348. list: [],
  349. patch: true,
  350. });
  351. export const getPenTree = () => {
  352. if (tree.patch) {
  353. tree.patch = false;
  354. const list = [];
  355. for (const item of meta2d.store.data.pens) {
  356. if (item.parentId) {
  357. continue;
  358. }
  359. const elem = calcElem(item);
  360. elem && list.push(elem);
  361. }
  362. tree.list = list;
  363. }
  364. return tree.list;
  365. };
  366. export const getPenAnimations = (idOrTag?: string) => {
  367. const animations = [];
  368. let pens: any[] = meta2d.store.active || [];
  369. if (idOrTag) {
  370. pens = meta2d.find(idOrTag) || [];
  371. }
  372. for (const pen of pens) {
  373. if (pen.animations) {
  374. for (const a of pen.animations) {
  375. animations.push(a.name);
  376. }
  377. }
  378. }
  379. return Array.from(new Set(animations));
  380. };
  381. const calcElem = (node: any) => {
  382. if (!node) {
  383. return;
  384. }
  385. const elem: any = {
  386. label: (node as any).description || node.name,
  387. value: node.id,
  388. locked: node.locked,
  389. visible: node.visible,
  390. };
  391. if (!node.children) {
  392. return elem;
  393. }
  394. elem.children = [];
  395. for (const id of node.children) {
  396. const child = calcElem(meta2d.store.pens[id]);
  397. child && elem.children.push(child);
  398. }
  399. return elem;
  400. };
  401. export const setChildrenVisible = (node: any, v: boolean) => {
  402. const children = node.getChildren();
  403. if (children && children.length > 0) {
  404. for (const child of children) {
  405. child.data.visible = v;
  406. setChildrenVisible(child, v);
  407. }
  408. }
  409. };
  410. export const fonts = [
  411. '新宋体',
  412. '微软雅黑',
  413. '黑体',
  414. '楷体',
  415. '-apple-system',
  416. 'BlinkMacSystemFont',
  417. 'PingFang SC',
  418. 'Hiragino Sans GB',
  419. 'Microsoft YaHei UI',
  420. 'Microsoft YaHei',
  421. 'fangsong',
  422. 'Source Han Sans CN',
  423. 'sans-serif',
  424. 'serif',
  425. 'Apple Color Emoji',
  426. 'Segoe UI Emoji',
  427. 'Segoe UI Symbol',
  428. ];
  429. export const delAttrs = [
  430. 'userId',
  431. 'shared',
  432. 'team',
  433. 'owner',
  434. 'username',
  435. 'editor',
  436. 'editorId',
  437. 'editorName',
  438. 'createdAt',
  439. 'folder',
  440. 'image',
  441. 'id',
  442. '_id',
  443. 'view',
  444. 'updatedAt',
  445. 'star',
  446. 'recommend',
  447. ];
  448. export const typeOptions = [
  449. {
  450. label: '字符串',
  451. value: 'string',
  452. },
  453. {
  454. label: '数字',
  455. value: 'number',
  456. },
  457. {
  458. label: '布尔',
  459. value: 'bool',
  460. },
  461. {
  462. label: '对象',
  463. value: 'object',
  464. },
  465. {
  466. label: '数组',
  467. value: 'array',
  468. },
  469. ];