common.ts 11 KB

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