common.ts 11 KB

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