ananzhusen 2 years ago
parent
commit
c05bd2052b
7 changed files with 369 additions and 9 deletions
  1. 1 0
      package.json
  2. 14 0
      pnpm-lock.yaml
  3. 42 0
      src/services/api.ts
  4. 28 0
      src/services/file.ts
  5. 41 0
      src/services/utils.ts
  6. 1 1
      src/views/components/FileProps.vue
  7. 242 8
      src/views/components/View.vue

+ 1 - 0
package.json

@@ -14,6 +14,7 @@
     "fast-xml-parser": "^4.0.1",
     "file-saver": "^2.0.5",
     "jszip": "^3.10.0",
+    "localforage": "^1.10.0",
     "monaco-editor": "^0.28.1",
     "tdesign-vue-next": "^1.3.0",
     "vue": "^3.2.37",

+ 14 - 0
pnpm-lock.yaml

@@ -12,6 +12,7 @@ specifiers:
   fast-xml-parser: ^4.0.1
   file-saver: ^2.0.5
   jszip: ^3.10.0
+  localforage: ^1.10.0
   monaco-editor: ^0.28.1
   postcss: ^8.4.6
   postcss-import: ^14.1.0
@@ -30,6 +31,7 @@ dependencies:
   fast-xml-parser: 4.2.2
   file-saver: 2.0.5
   jszip: 3.10.1
+  localforage: 1.10.0
   monaco-editor: 0.28.1
   tdesign-vue-next: 1.3.1_vue@3.2.47
   vue: 3.2.47
@@ -1071,12 +1073,24 @@ packages:
       setimmediate: 1.0.5
     dev: false
 
+  /lie/3.1.1:
+    resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
+    dependencies:
+      immediate: 3.0.6
+    dev: false
+
   /lie/3.3.0:
     resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
     dependencies:
       immediate: 3.0.6
     dev: false
 
+  /localforage/1.10.0:
+    resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==}
+    dependencies:
+      lie: 3.1.1
+    dev: false
+
   /lodash/4.17.21:
     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
     dev: false

+ 42 - 0
src/services/api.ts

@@ -1,2 +1,44 @@
 //所有的接口请求
 import axios from "axios";
+export const cdnUrl = "https://drive.le5lecdn.com";
+
+export async function delImage(image: string) {
+  if (image.startsWith(cdnUrl)) {
+    await axios.delete("/file" + image.replace(cdnUrl, ""));
+  } else {
+    await axios.delete(`${image}`);
+  }
+  return true;
+}
+
+export async function getFolders(query: any) {
+  const folder: any = await axios.post("/data/folders/get", {
+    query,
+  });
+  if (folder.error) {
+    return;
+  } else {
+    return folder;
+  }
+}
+
+export async function updateFolders(data: any) {
+  const folder: any = await axios.post("/data/folders/update", data);
+  if (folder.error) {
+    return;
+  } else {
+    return folder;
+  }
+}
+
+export async function addCollection(collection: string, data: any) {
+  return await axios.post(`/data/${collection}/add`, data); // 新增
+}
+
+export async function updateCollection(collection: string, data: any) {
+  return await axios.post(`/data/${collection}/update`, data); // 新增
+}
+
+// export async function addCollection(collection: string, data: any) {
+//   return await axios.post(`/data/${collection}/add`, data); // 新增
+// }

+ 28 - 0
src/services/file.ts

@@ -69,3 +69,31 @@ export async function readFile(file: Blob) {
     reader.readAsText(file);
   });
 }
+
+export function dataURLtoBlob(base64: string) {
+  let arr: any = base64.split(","),
+    mime = arr[0].match(/:(.*?);/)[1],
+    bstr = atob(arr[1]),
+    n = bstr.length,
+    u8arr = new Uint8Array(n);
+  while (n--) {
+    u8arr[n] = bstr.charCodeAt(n);
+  }
+  return new Blob([u8arr], { type: mime });
+}
+
+/**
+ * 图片转 Blob
+ * @param img 图片
+ */
+export function saveToBlob(img: HTMLImageElement): Blob {
+  const canvas: HTMLCanvasElement = document.createElement("canvas");
+  canvas.setAttribute("origin-clean", "false");
+  canvas.width = img.width;
+  canvas.height = img.height;
+
+  const context: any = canvas.getContext("2d");
+  context.filter = window.getComputedStyle(img).filter;
+  context.drawImage(img, 0, 0, canvas.width, canvas.height);
+  return dataURLtoBlob(canvas.toDataURL());
+}

+ 41 - 0
src/services/utils.ts

@@ -29,6 +29,7 @@ export interface Meta2dBackData extends Meta2dData {
   username?: string;
   editorId?: string;
   editorName?: string;
+  teams?: { id?: string; name?: string }[];
 }
 
 const notification = ref<any>(null);
@@ -127,3 +128,43 @@ export function strictAssign(
   Object.assign(undefinedSource, target);
   Object.assign(source, undefinedSource);
 }
+
+export function checkData(data: Meta2dData) {
+  const pens: Pen[] = data.pens || [];
+  for (let i = 0; i < pens.length; i++) {
+    const pen: any = pens[i];
+    pen.events?.forEach((event: any) => {
+      delete event.setProps;
+    });
+
+    //处理画笔是脏数据的情况
+    if (
+      !(
+        pen.x > -Infinity &&
+        pen.x < Infinity &&
+        pen.y > -Infinity &&
+        pen.y < Infinity &&
+        pen.width > -Infinity &&
+        pen.width < Infinity &&
+        pen.height > -Infinity &&
+        pen.height < Infinity
+      )
+    ) {
+      pens.splice(i, 1);
+      --i;
+    } else if (
+      pen.x == null ||
+      pen.y == null ||
+      pen.width == null ||
+      pen.height == null
+    ) {
+      pens.splice(i, 1);
+      --i;
+    }
+  }
+
+  if (Array.isArray(data.mqttOptions)) {
+    // mqttOptions 是数组则认为是脏数据,删掉
+    data.mqttOptions = {};
+  }
+}

+ 1 - 1
src/views/components/FileProps.vue

@@ -262,7 +262,7 @@ const changeValue = (e: any, key: string) => {
 };
 
 onMounted(() => {
-  initMeta2dCanvas();
+  // initMeta2dCanvas();
   openData();
   (<any>globalThis).meta2d.on("opened", openData);
 });

+ 242 - 8
src/views/components/View.vue

@@ -2,13 +2,13 @@
   <div class="meta2d">
     <div class="tools">
       <t-tooltip content="新建" placement="bottom">
-        <a><t-icon name="add" /></a>
+        <a><t-icon name="add" @click="newFile" /></a>
       </t-tooltip>
       <t-tooltip content="保存" placement="bottom">
-        <a> <t-icon name="save" /></a>
+        <a> <t-icon name="save" @click="save" /></a>
       </t-tooltip>
       <t-tooltip content="保存为模板" placement="bottom">
-        <a><t-icon name="layers" /></a>
+        <a><t-icon name="layers" @click="saveAsComponents" /></a>
       </t-tooltip>
       <t-tooltip content="格式化" placement="bottom">
         <a>
@@ -100,16 +100,39 @@
 </template>
 
 <script lang="ts" setup>
-import { Meta2d, Options } from '@meta2d/core';
-import { onMounted, onUnmounted } from 'vue';
-import { registerBasicDiagram } from '@/services/register';
+import { Meta2d, Options } from "@meta2d/core";
+import { onMounted, onUnmounted } from "vue";
+import { registerBasicDiagram } from "@/services/register";
+import { Meta2dBackData, checkData } from "@/services/utils";
+import { useRouter, useRoute } from "vue-router";
+import { useUser } from "@/services/user";
+import { MessagePlugin } from "tdesign-vue-next";
+import localforage from "localforage";
+import { dataURLtoBlob, upload } from "@/services/file";
+import {
+  delImage,
+  getFolders,
+  addCollection,
+  updateCollection,
+  updateFolders,
+} from "@/services/api";
+import { baseVer } from "@/services/upgrade";
+
+const router = useRouter();
+const route = useRoute();
+const { user, message, getUser, getMessage, signout } = useUser();
 
 const meta2dOptions: Options = {
-  cdn: 'https://assets.le5lecdn.com',
+  cdn: "https://assets.le5lecdn.com",
   rule: true,
+  background: '#1e2430',
+  x: 32,
+  y: 32,
+  width: 1920,
+  height: 1080,
 };
 onMounted(() => {
-  new Meta2d('meta2d', meta2dOptions);
+  new Meta2d("meta2d", meta2dOptions);
   registerBasicDiagram();
 });
 
@@ -118,6 +141,217 @@ onUnmounted(() => {
     (<any>globalThis).meta2d.destroy();
   }
 });
+
+enum SaveType {
+  Save,
+  SaveAs,
+}
+
+//本地保存图纸数据 key
+const localMeta2dDataName = "meta2dData";
+
+const save = async (type: SaveType = SaveType.Save) => {
+  (<any>globalThis).meta2d.stopAnimate();
+  const data: Meta2dBackData = (<any>globalThis).meta2d.data();
+  if (!(user && user.username)) {
+    MessagePlugin.warning("请先登录,否则无法保存!");
+    localforage.setItem(localMeta2dDataName, JSON.stringify(data));
+    return;
+  }
+  checkData(data);
+  if (!data._id && route.query.id) {
+    data._id = route.query.id as string;
+  }
+
+  if (
+    (globalThis as any).beforeSaveMeta2d &&
+    !(await (globalThis as any).beforeSaveMeta2d(data))
+  ) {
+    return;
+  }
+  if (type === SaveType.SaveAs) {
+    //另存为去掉teams信息
+    delete data.teams;
+  }
+  //如果不是自己创建的团队图纸,就不去修改缩略图(没有权限去删除缩略图)
+  if (!((data as any).teams && data.owner?.id !== user.id)) {
+    let blob: Blob;
+    try {
+      blob = dataURLtoBlob((<any>globalThis).meta2d.toPng(10));
+    } catch (e) {
+      MessagePlugin.error(
+        "无法下载,宽度不合法,画布可能没有画笔/画布大小超出浏览器最大限制"
+      );
+      return;
+    }
+    if (data._id && type === SaveType.Save) {
+      if (data.image && !(await delImage(data.image))) {
+        return;
+      }
+    }
+
+    const file = await upload(blob, true);
+    if (!file) {
+      return;
+    }
+
+    // 缩略图
+    data.image = file.url;
+    (<any>globalThis).meta2d.store.data.image = data.image;
+  }
+
+  if (data.component) {
+    // pens 存储原数据用于二次编辑 ; componentDatas 组合后的数据,用于复用
+    data.componentDatas = (<any>globalThis).meta2d.toComponent(
+      undefined,
+      (<any>globalThis).meta2d.store.data.showChild,
+      false //自定义组合节点生成默认锚点
+    );
+  } else {
+    data.component = false; // 必要值
+  }
+  let collection = data.component ? "le5le2d-components" : "le5le2d";
+  let ret: any;
+  if (!data.name) {
+    // 文件名称
+    data.name = `meta2d.${new Date().toLocaleString()}`;
+    (<any>globalThis).meta2d.store.data.name = data.name;
+  }
+  !data.version && (data.version = baseVer);
+
+  let list = undefined;
+  let folder: any = undefined;
+  let folderId = undefined;
+  if (
+    !data.component &&
+    data.folder &&
+    !(data.teams && data.owner?.id !== user.id)
+  ) {
+    //自己的图纸才允许去请求
+    folder = getFolders({
+      type: collection,
+      name: data.folder,
+    });
+    if (folder) {
+      list = folder.list; //团队图纸文件夹
+      folderId = folder._id;
+    }
+  }
+  if (!list) {
+    list = [];
+  }
+
+  if (type === SaveType.SaveAs) {
+    // 另存为一定走 新增 ,由于后端 未控制 userId 等属性,清空一下
+    const delAttrs = [
+      "userId",
+      "id",
+      "shared",
+      "star",
+      "view",
+      "username",
+      "editorName",
+      "editorId",
+      "createdAt",
+      "updatedAt",
+      "recommend",
+    ];
+    for (const k of delAttrs) {
+      delete (data as any)[k];
+    }
+    ret = addCollection(collection, data); // 新增
+    if (!data.component) {
+      list.push({
+        id: ret._id,
+        image: data.image,
+        name: data.name,
+        component: data.component,
+      });
+    }
+  } else {
+    if (data._id && data.teams && data.owner?.id !== user.id) {
+      // 团队图纸 不允许修改文件夹信息
+      delete data.folder;
+      ret = updateCollection(collection, data);
+    } else if (data._id) {
+      ret = updateCollection(collection, data);
+      if (!data.component) {
+        list.forEach((i: any) => {
+          if (i.id === data._id) {
+            i.image = data.image;
+          }
+        });
+      }
+      //TODO 处理老接口图纸情况
+      let one = list.find((item: any) => item.id === data._id);
+      if (!data.component && !one) {
+        list.push({
+          id: ret._id,
+          image: data.image,
+          name: data.name,
+          component: data.component,
+        });
+      }
+    } else {
+      ret = addCollection(collection, data); // 新增
+      if (!data.component) {
+        list.push({
+          id: ret._id,
+          image: data.image,
+          name: data.name,
+          component: data.component,
+        });
+      }
+    }
+  }
+
+  if (ret.error) {
+    return null;
+  } else {
+    if (!data.component && folderId) {
+      const updateRet: any = updateFolders({
+        _id: folderId,
+        list,
+      });
+      if (updateRet.error) {
+        return null;
+      }
+    }
+    // showModelSaveAsPop.value = false;
+  }
+  //  保存图纸之后的钩子函数
+  (window as any).afterSaveMeta2d &&
+    (await (window as any).afterSaveMeta2d(ret));
+  if (
+    !data._id ||
+    data.owner?.id !== user.id ||
+    route.query.version ||
+    type === SaveType.SaveAs // 另存为肯定走新增,也会产生新的 id
+  ) {
+    data._id = ret._id;
+    (<any>globalThis).meta2d.store.data._id = data._id;
+    router.replace({
+      path: "/",
+      query: {
+        id: data._id,
+        r: Date.now() + "",
+        component: data.component + "",
+      },
+    });
+  }
+
+  MessagePlugin.success("保存成功!");
+  // 保存成功,重新请求文件夹
+  (<any>globalThis).meta2d.emit("t-save-success", true);
+  // 已保存,不再是新的,无需提示保存
+  // isNew.value = false;
+  localforage.removeItem(localMeta2dDataName);
+};
+
+const saveAsComponents = () => {
+  // (<any>globalThis).meta2d.store.data.component = true;
+  save();
+}
 </script>
 <style lang="postcss" scoped>
 .meta2d {