View.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. <template>
  2. <div class="meta2d">
  3. <div class="tools">
  4. <t-tooltip content="新建" placement="bottom">
  5. <a><t-icon name="add" @click="newFile" /></a>
  6. </t-tooltip>
  7. <t-tooltip content="保存" placement="bottom">
  8. <a> <t-icon name="save" @click="save" /></a>
  9. </t-tooltip>
  10. <t-tooltip content="保存为模板" placement="bottom">
  11. <a><t-icon name="layers" @click="saveAsComponents" /></a>
  12. </t-tooltip>
  13. <t-tooltip content="格式化" placement="bottom">
  14. <a>
  15. <svg
  16. width="1em"
  17. height="1em"
  18. viewBox="0 0 256 256"
  19. xmlns="http://www.w3.org/2000/svg"
  20. xmlns:xlink="http://www.w3.org/1999/xlink"
  21. >
  22. <defs><path id="77456364" d="M0 0h256v256H0z"></path></defs>
  23. <g fill="none" fill-rule="evenodd">
  24. <mask id="3804093554b" fill="#fff">
  25. <use xlink:href="#77456364"></use>
  26. </mask>
  27. <path
  28. d="M213 77c0 14.36-11.64 26-26 26H69c-14.36 0-26-11.64-26-26V51c0-14.36 11.64-26 26-26h118c14.36 0 26 11.64 26 26v2h9.125c9.83 0 17.82 7.88 17.997 17.67l.003.33v50c0 9.83-7.88 17.82-17.67 17.997l-.33.003h-84.626v18H139c9.83 0 17.82 7.88 17.997 17.67l.003.33v35c0 16.016-12.984 29-29 29-15.856 0-28.74-12.725-28.996-28.52L99 210v-35c0-9.83 7.88-17.82 17.67-17.997L117 157h.499l.001-20c0-9.83 7.88-17.82 17.67-17.997l.33-.003h84.625V73H213Zm-76 100h-18v33a9 9 0 0 0 8.471 8.985l.264.011.265.004a9 9 0 0 0 8.996-8.735L137 210v-33Zm50-132H69a6 6 0 0 0-6 6v26a6 6 0 0 0 6 6h118a6 6 0 0 0 6-6V51a6 6 0 0 0-6-6Z"
  29. fill="currentColor"
  30. fill-rule="nonzero"
  31. mask="url(#3804093554b)"
  32. ></path>
  33. </g>
  34. </svg>
  35. </a>
  36. </t-tooltip>
  37. <t-tooltip content="清除格式" placement="bottom">
  38. <a>
  39. <svg
  40. width="1em"
  41. height="1em"
  42. viewBox="0 0 1024 1024"
  43. xmlns="http://www.w3.org/2000/svg"
  44. >
  45. <path
  46. d="M889.186 384.07 677.671 172.56c-53.063-53.063-139.094-53.063-192.157 0L134.617 523.457c-53.063 53.063-53.063 139.099 0 192.158l170.196 170.2a41.354 41.354 0 0 0 29.243 12.11h215.001a41.354 41.354 0 0 0 29.184-12.05L889.155 576.26c53.094-53.09 53.094-139.126.031-192.19zM830.7 442.558c20.48 20.472 20.764 53.492.855 74.319l-.961.984-298.618 297.358H351.185l-158.09-158.086c-20.76-20.763-20.76-54.43 0-75.193l350.901-350.897c20.764-20.764 54.43-20.764 75.193 0l211.515 211.511z"
  47. fill="currentColor"
  48. ></path>
  49. <path
  50. d="m685.505 678.754-58.19 58.77-317.587-314.43 58.191-58.774zm197.55 136.508c23.46 0 42.483 18.514 42.483 41.353 0 22.45-18.38 40.724-41.294 41.338l-1.19.016h-454.6c-23.462 0-42.485-18.514-42.485-41.354 0-22.449 18.381-40.723 41.295-41.338l1.19-.015h454.6z"
  51. fill="currentColor"
  52. ></path>
  53. </svg>
  54. </a>
  55. </t-tooltip>
  56. <div class="flex-grow"></div>
  57. <t-tooltip content="直线" placement="bottom">
  58. <a><t-icon name="slash" /></a>
  59. </t-tooltip>
  60. <t-tooltip content="文字" placement="bottom">
  61. <a>T</a>
  62. </t-tooltip>
  63. <t-tooltip content="图片" placement="bottom">
  64. <a><t-icon name="image" /></a>
  65. </t-tooltip>
  66. <t-tooltip content="视图大小" placement="bottom">
  67. <div style="line-height: 40px; margin-left: 8px">100%</div>
  68. </t-tooltip>
  69. <t-tooltip content="100%视图" placement="bottom">
  70. <a><t-icon name="refresh" /></a>
  71. </t-tooltip>
  72. <t-tooltip content="窗口大小" placement="bottom">
  73. <a><t-icon name="minus-rectangle" /></a>
  74. </t-tooltip>
  75. <t-tooltip content="数据源" placement="bottom">
  76. <a><t-icon name="server" /></a>
  77. </t-tooltip>
  78. <div class="flex-grow"></div>
  79. <t-tooltip content="预览" placement="bottom">
  80. <a><t-icon name="browse" /></a>
  81. </t-tooltip>
  82. <t-tooltip content="运行" placement="bottom">
  83. <a><t-icon name="caret-right" /></a>
  84. </t-tooltip>
  85. <t-tooltip content="手机查看" placement="bottom">
  86. <a><t-icon name="qrcode" /></a>
  87. </t-tooltip>
  88. <t-tooltip content="分享" placement="bottom">
  89. <a><t-icon name="share" /></a>
  90. </t-tooltip>
  91. <t-tooltip content="发布" placement="bottom">
  92. <a><t-icon name="cloud" /></a>
  93. </t-tooltip>
  94. </div>
  95. <div id="meta2d"></div>
  96. </div>
  97. </template>
  98. <script lang="ts" setup>
  99. import { Meta2d, Options } from "@meta2d/core";
  100. import { onMounted, onUnmounted ,watch} from "vue";
  101. import { registerBasicDiagram } from "@/services/register";
  102. import { Meta2dBackData, checkData } from "@/services/utils";
  103. import { useRouter, useRoute } from "vue-router";
  104. import { useUser } from "@/services/user";
  105. import { MessagePlugin } from "tdesign-vue-next";
  106. import localforage from "localforage";
  107. import { dataURLtoBlob, upload } from "@/services/file";
  108. import {
  109. delImage,
  110. getFolders,
  111. addCollection,
  112. updateCollection,
  113. updateFolders,
  114. } from "@/services/api";
  115. import { baseVer } from "@/services/upgrade";
  116. const router = useRouter();
  117. const route = useRoute();
  118. const { user, message, getUser, getMessage, signout } = useUser();
  119. const meta2dOptions: Options = {
  120. cdn: "https://assets.le5lecdn.com",
  121. rule: true,
  122. background: '#1e2430',
  123. x: 32,
  124. y: 32,
  125. width: 1920,
  126. height: 1080,
  127. };
  128. onMounted(() => {
  129. meta2d = new Meta2d('meta2d', meta2dOptions);
  130. registerBasicDiagram();
  131. open();
  132. });
  133. const watcher = watch(
  134. () => route.query.id,
  135. async () => {
  136. open();
  137. }
  138. );
  139. const open = async () => {
  140. if (route.query.id) {
  141. const ret: any = await axios.post('/data/le5le2d/get', {
  142. id: route.query.id,
  143. });
  144. ret && meta2d.open(ret);
  145. } else {
  146. meta2d.open();
  147. }
  148. };
  149. onUnmounted(() => {
  150. watcher();
  151. if (meta2d) {
  152. meta2d.destroy();
  153. }
  154. });
  155. enum SaveType {
  156. Save,
  157. SaveAs,
  158. }
  159. //本地保存图纸数据 key
  160. const localMeta2dDataName = "meta2dData";
  161. const save = async (type: SaveType = SaveType.Save) => {
  162. (<any>globalThis).meta2d.stopAnimate();
  163. const data: Meta2dBackData = (<any>globalThis).meta2d.data();
  164. if (!(user && user.username)) {
  165. MessagePlugin.warning("请先登录,否则无法保存!");
  166. localforage.setItem(localMeta2dDataName, JSON.stringify(data));
  167. return;
  168. }
  169. checkData(data);
  170. if (!data._id && route.query.id) {
  171. data._id = route.query.id as string;
  172. }
  173. if (
  174. (globalThis as any).beforeSaveMeta2d &&
  175. !(await (globalThis as any).beforeSaveMeta2d(data))
  176. ) {
  177. return;
  178. }
  179. if (type === SaveType.SaveAs) {
  180. //另存为去掉teams信息
  181. delete data.teams;
  182. }
  183. //如果不是自己创建的团队图纸,就不去修改缩略图(没有权限去删除缩略图)
  184. if (!((data as any).teams && data.owner?.id !== user.id)) {
  185. let blob: Blob;
  186. try {
  187. blob = dataURLtoBlob((<any>globalThis).meta2d.toPng(10));
  188. } catch (e) {
  189. MessagePlugin.error(
  190. "无法下载,宽度不合法,画布可能没有画笔/画布大小超出浏览器最大限制"
  191. );
  192. return;
  193. }
  194. if (data._id && type === SaveType.Save) {
  195. if (data.image && !(await delImage(data.image))) {
  196. return;
  197. }
  198. }
  199. const file = await upload(blob, true);
  200. if (!file) {
  201. return;
  202. }
  203. // 缩略图
  204. data.image = file.url;
  205. (<any>globalThis).meta2d.store.data.image = data.image;
  206. }
  207. if (data.component) {
  208. // pens 存储原数据用于二次编辑 ; componentDatas 组合后的数据,用于复用
  209. data.componentDatas = (<any>globalThis).meta2d.toComponent(
  210. undefined,
  211. (<any>globalThis).meta2d.store.data.showChild,
  212. false //自定义组合节点生成默认锚点
  213. );
  214. } else {
  215. data.component = false; // 必要值
  216. }
  217. let collection = data.component ? "le5le2d-components" : "le5le2d";
  218. let ret: any;
  219. if (!data.name) {
  220. // 文件名称
  221. data.name = `meta2d.${new Date().toLocaleString()}`;
  222. (<any>globalThis).meta2d.store.data.name = data.name;
  223. }
  224. !data.version && (data.version = baseVer);
  225. let list = undefined;
  226. let folder: any = undefined;
  227. let folderId = undefined;
  228. if (
  229. !data.component &&
  230. data.folder &&
  231. !(data.teams && data.owner?.id !== user.id)
  232. ) {
  233. //自己的图纸才允许去请求
  234. folder = getFolders({
  235. type: collection,
  236. name: data.folder,
  237. });
  238. if (folder) {
  239. list = folder.list; //团队图纸文件夹
  240. folderId = folder._id;
  241. }
  242. }
  243. if (!list) {
  244. list = [];
  245. }
  246. if (type === SaveType.SaveAs) {
  247. // 另存为一定走 新增 ,由于后端 未控制 userId 等属性,清空一下
  248. const delAttrs = [
  249. "userId",
  250. "id",
  251. "shared",
  252. "star",
  253. "view",
  254. "username",
  255. "editorName",
  256. "editorId",
  257. "createdAt",
  258. "updatedAt",
  259. "recommend",
  260. ];
  261. for (const k of delAttrs) {
  262. delete (data as any)[k];
  263. }
  264. ret = addCollection(collection, data); // 新增
  265. if (!data.component) {
  266. list.push({
  267. id: ret._id,
  268. image: data.image,
  269. name: data.name,
  270. component: data.component,
  271. });
  272. }
  273. } else {
  274. if (data._id && data.teams && data.owner?.id !== user.id) {
  275. // 团队图纸 不允许修改文件夹信息
  276. delete data.folder;
  277. ret = updateCollection(collection, data);
  278. } else if (data._id) {
  279. ret = updateCollection(collection, data);
  280. if (!data.component) {
  281. list.forEach((i: any) => {
  282. if (i.id === data._id) {
  283. i.image = data.image;
  284. }
  285. });
  286. }
  287. //TODO 处理老接口图纸情况
  288. let one = list.find((item: any) => item.id === data._id);
  289. if (!data.component && !one) {
  290. list.push({
  291. id: ret._id,
  292. image: data.image,
  293. name: data.name,
  294. component: data.component,
  295. });
  296. }
  297. } else {
  298. ret = addCollection(collection, data); // 新增
  299. if (!data.component) {
  300. list.push({
  301. id: ret._id,
  302. image: data.image,
  303. name: data.name,
  304. component: data.component,
  305. });
  306. }
  307. }
  308. }
  309. if (ret.error) {
  310. return null;
  311. } else {
  312. if (!data.component && folderId) {
  313. const updateRet: any = updateFolders({
  314. _id: folderId,
  315. list,
  316. });
  317. if (updateRet.error) {
  318. return null;
  319. }
  320. }
  321. // showModelSaveAsPop.value = false;
  322. }
  323. // 保存图纸之后的钩子函数
  324. (window as any).afterSaveMeta2d &&
  325. (await (window as any).afterSaveMeta2d(ret));
  326. if (
  327. !data._id ||
  328. data.owner?.id !== user.id ||
  329. route.query.version ||
  330. type === SaveType.SaveAs // 另存为肯定走新增,也会产生新的 id
  331. ) {
  332. data._id = ret._id;
  333. (<any>globalThis).meta2d.store.data._id = data._id;
  334. router.replace({
  335. path: "/",
  336. query: {
  337. id: data._id,
  338. r: Date.now() + "",
  339. component: data.component + "",
  340. },
  341. });
  342. }
  343. MessagePlugin.success("保存成功!");
  344. // 保存成功,重新请求文件夹
  345. (<any>globalThis).meta2d.emit("t-save-success", true);
  346. // 已保存,不再是新的,无需提示保存
  347. // isNew.value = false;
  348. localforage.removeItem(localMeta2dDataName);
  349. };
  350. const saveAsComponents = () => {
  351. // (<any>globalThis).meta2d.store.data.component = true;
  352. save();
  353. }
  354. </script>
  355. <style lang="postcss" scoped>
  356. .meta2d {
  357. display: flex;
  358. flex-direction: column;
  359. background-color: var(--color-background-editor);
  360. border-right: 1px solid var(--color-border);
  361. .tools {
  362. display: flex;
  363. font-size: 12px;
  364. background-color: var(--color-background);
  365. height: 40px;
  366. flex-shrink: 0;
  367. padding: 0 12px;
  368. a {
  369. display: flex;
  370. align-items: center;
  371. height: 100%;
  372. padding: 0 10px;
  373. color: var(--color);
  374. text-decoration: none;
  375. &:hover {
  376. color: var(--color-primary);
  377. }
  378. }
  379. .t-icon {
  380. font-size: 16px;
  381. }
  382. }
  383. #meta2d {
  384. border-top: 1px solid var(--color-border);
  385. height: calc(100vh - 81px);
  386. }
  387. }
  388. </style>