Parcourir la source

Merge branch 'main' of github.com:le5le-com/visualization-design

Alsmile il y a 2 ans
Parent
commit
2e45e29f4b

+ 2 - 1
package.json

@@ -16,7 +16,7 @@
     "jszip": "^3.10.0",
     "localforage": "^1.10.0",
     "monaco-editor": "^0.37.1",
-    "tdesign-vue-next": "^1.3.0",
+    "tdesign-vue-next": "^1.3.2",
     "vue": "^3.2.37",
     "vue-router": "^4.1.3"
   },
@@ -27,6 +27,7 @@
     "@vitejs/plugin-vue": "^4.0.0",
     "@vitejs/plugin-vue-jsx": "^3.0.0",
     "autoprefixer": "^10.4.13",
+    "formidable": "^2.0.1",
     "postcss": "^8.4.6",
     "postcss-import": "^14.1.0",
     "postcss-nested": "^6.0.1",

Fichier diff supprimé car celui-ci est trop grand
+ 187 - 195
pnpm-lock.yaml


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
public/png/废气治理/储罐.svg


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
public/png/废气治理/单向阀.svg


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
public/png/废气治理/过滤器.svg


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
public/png/智慧城市/充电桩.svg


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
public/png/智慧城市/草地.svg


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
public/png/智慧港口/堆取料机.svg


BIN
public/png/电信机房/服务器.gif


BIN
public/png/电信机房/防火墙.gif


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
public/png/采暖系统/板式换热器.svg


BIN
public/png/采暖系统/采暖系统.gif


BIN
public/rotate.cur


+ 2 - 0
src/global.d.ts

@@ -3,6 +3,8 @@ import { Meta2d } from "@meta2d/core";
 declare global {
   var meta2d: Meta2d;
   var C2S: any;
+  var folderJson: any;
+  var fileJson: any;
 }
 
 declare interface Window {

+ 18 - 16
src/http.ts

@@ -1,7 +1,7 @@
-import { MessagePlugin } from 'tdesign-vue-next';
-import axios from 'axios';
-import { getCookie } from '@/services/cookie';
-import router from './router';
+import { MessagePlugin } from "tdesign-vue-next";
+import axios from "axios";
+import { getCookie } from "@/services/cookie";
+import router from "./router";
 
 // axios 配置
 axios.defaults.timeout = 60000;
@@ -13,9 +13,11 @@ const requestThrottleSet = new Set();
 // http request 拦截器
 axios.interceptors.request.use(
   (config: any) => {
-    config.baseURL = '/api';
+    if (!config.url.startsWith("/png")) {
+      config.baseURL = "/api";
+    }
     config.headers.Authorization =
-      'Bearer ' + (localStorage.token || getCookie('token') || '');
+      "Bearer " + (localStorage.token || getCookie("token") || "");
 
     if (config.params) {
       // 防抖, 比如输入搜索
@@ -45,7 +47,7 @@ axios.interceptors.request.use(
         const url = config.method + config.url;
         // 已经存在,取消重复请求
         if (requestThrottleSet.has(url)) {
-          return Promise.reject('Repeated request.');
+          return Promise.reject("Repeated request.");
         }
         requestThrottleSet.add(url);
         setTimeout(() => {
@@ -79,7 +81,7 @@ axios.interceptors.response.use(
       return;
     }
     if (error && error.response) {
-      if (error.response.config.url === '/account/profile') {
+      if (error.response.config.url === "/account/profile") {
         return;
       }
 
@@ -95,26 +97,26 @@ axios.interceptors.response.use(
 
       switch (error.response.status) {
         case 401:
-          sessionStorage.setItem('cb', encodeURIComponent(location.href));
-          router.replace({ path: '/login' });
+          sessionStorage.setItem("cb", encodeURIComponent(location.href));
+          router.replace({ path: "/login" });
           break;
         case 403:
-          MessagePlugin.error('请求错误,不合法的请求!');
+          MessagePlugin.error("请求错误,不合法的请求!");
 
           break;
         case 404:
-          if (error.response.config.url.indexOf('/data/') !== 0) {
-            MessagePlugin.error('访问数据不存在,请检查后重试!');
+          if (error.response.config.url.indexOf("/data/") !== 0) {
+            MessagePlugin.error("访问数据不存在,请检查后重试!");
           }
           break;
         case 500:
-          MessagePlugin.error('请求服务错误,请稍后重试!');
+          MessagePlugin.error("请求服务错误,请稍后重试!");
           break;
         case 504:
-          MessagePlugin.error('网络超时,请检测你的网络!');
+          MessagePlugin.error("网络超时,请检测你的网络!");
           break;
         default:
-          MessagePlugin.error('未知网络错误!');
+          MessagePlugin.error("未知网络错误!");
           break;
       }
     }

+ 5 - 2
src/router/index.ts

@@ -1,6 +1,9 @@
-import { createRouter, createWebHistory } from 'vue-router';
+import { createRouter, createWebHistory } from "vue-router";
 
-const routes = [{ path: '/', component: () => import('@/views/Index.vue') }];
+const routes = [
+  { path: "/", component: () => import("@/views/Index.vue") },
+  { path: "/preview", component: () => import("@/views/Preview.vue") },
+];
 
 const router = createRouter({
   history: createWebHistory(import.meta.env.VITE_ROUTER_BASE),

Fichier diff supprimé car celui-ci est trop grand
+ 3441 - 0
src/services/defaults.ts


+ 71 - 0
src/services/png.ts

@@ -0,0 +1,71 @@
+import axios from "@/http";
+
+const cdn = import.meta.env.VITE_ROUTER_BASE
+  ? ""
+  : "https://assets.le5lecdn.com";
+
+const market = import.meta.env.VITE_MARKET;
+
+const normalFolder = market ? "/2d/png/" : "/png/";
+/**
+ * 请求 png 的目录
+ * @returns
+ */
+export async function getPngFolders() {
+  return await getFolders(normalFolder);
+}
+
+/**
+ * 请求 png 目录下的所有 png
+ * @param name 目录名
+ * @returns
+ */
+export async function getPngs(name: string) {
+  const files = (await axios.get(normalFolder + name + "/")) as any[];
+  if (!files || !files.map) {
+    return [];
+  }
+  return files.map((f) => {
+    let fname = filename(f.name);
+    return {
+      name: globalThis.fileJson
+        ? globalThis.fileJson[fname]
+          ? globalThis.fileJson[fname]
+          : fname
+        : fname,
+      pinyin: globalThis.fileJson ? fname : null,
+      image: cdn + normalFolder + name + "/" + f.name,
+    };
+  });
+}
+
+export async function getFolders(folderName: string) {
+  const ret = (await axios.get(folderName)) as any[];
+
+  if (!ret || !ret.map) {
+    return [];
+  }
+  return await Promise.all(
+    ret.map(async (c: any) => {
+      const files = (await axios.get(folderName + c.name + "/")) as any[];
+      return {
+        name: globalThis.folderJson
+          ? globalThis.folderJson[c.name]
+            ? globalThis.folderJson[c.name]
+            : c.name
+          : c.name,
+        pinyin: globalThis.folderJson ? c.name : null,
+        show: true,
+        list: [],
+        count: files.length,
+        // 用于区别 png 与 svg 文件夹
+        svgFolder: folderName === normalFolder ? true : false,
+      };
+    })
+  );
+}
+
+export function filename(str: string) {
+  const i = str.lastIndexOf(".");
+  return str.substring(0, i);
+}

+ 81 - 0
src/views/Preview.vue

@@ -0,0 +1,81 @@
+<template>
+  <div class="preview">
+    <div class="meta2d-canvas" ref="meta2dDom"></div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, watch, onUnmounted } from "vue";
+import localforage from "localforage";
+import { localMeta2dDataName } from "@/services/utils";
+import { registerNormalShape } from "../components/register";
+import { defaultFormat } from "@/services/defaults";
+import { useRouter, useRoute } from "vue-router";
+import { Meta2d, Options, Pen } from "@meta2d/core";
+import { registerBasicDiagram } from "@/services/register";
+
+const route = useRoute();
+
+const meta2dDom = ref("");
+
+const meta2dOptions: Options = {
+  cdn: "https://assets.le5lecdn.com",
+  //   rule: true,
+  background: "#1e2430",
+  x: 10,
+  y: 10,
+  width: 1920,
+  height: 1080,
+  //   defaultFormat: { ...defaultFormat },
+};
+
+onMounted(() => {
+  meta2d = new Meta2d(meta2dDom.value, meta2dOptions);
+  registerBasicDiagram();
+  open();
+  meta2d.on("opened", opened);
+});
+
+const watcher = watch(
+  () => route.query.id,
+  async () => {
+    open();
+  }
+);
+
+const open = async () => {
+  if (route.query.id) {
+    const ret: any = getLe5le2d(route.query.id + "");
+    ret && meta2d.open(ret);
+  } else {
+    const data: any = JSON.parse(
+      await localforage.getItem(localMeta2dDataName)
+    );
+    data&&meta2d.open(data);
+  }
+};
+
+const opened = () => {
+  meta2d.fitSizeView(true, 10);
+};
+
+onUnmounted(() => {
+  watcher();
+  if (meta2d) {
+    meta2d.off("opened", opened);
+
+    meta2d.destroy();
+  }
+});
+</script>
+<style lang="postcss" scoped>
+.preview {
+  width: 100vw;
+  height: 100vh;
+  background-color: var(--color-background-editor);
+  .meta2d-canvas {
+    width: 100%;
+    height: 100%;
+  }
+}
+</style>

+ 133 - 116
src/views/components/Graphics.vue

@@ -10,7 +10,7 @@
       <div class="sub-groups">
         <div
           v-for="group in groups"
-          :class="group.active ? 'active' : ''"
+          :class="group.name === activeGroup ? 'active' : ''"
           @click="groupChange(group.name)"
         >
           <t-icon :name="group.icon" />
@@ -18,129 +18,121 @@
         </div>
       </div>
       <div class="list">
-        <div
-          class="show-item"
-          v-for="item in showList"
-          :draggable="true"
-          @dragstart="dragStart($event, item)"
-          @drag="drag($event, item)"
-          @dragend="dragEnd()"
-        >
-          <t-image
-            v-if="item.image"
-            :src="item.image"
-            fit="cover"
-            :style="{ width: '88px', height: '88px' }"
-          />
-          <i v-else class="t-icon" :class="item.icon"></i>
-          <p>{{ item.name }}</p>
-        </div>
+        <t-collapse @change="handlePanelChange">
+          <t-collapse-panel
+            :value="item.name"
+            :header="item.name"
+            v-for="item in showList"
+          >
+            <div
+              class="show-item"
+              v-for="iItem in item.list"
+              :draggable="true"
+              @dragstart="dragStart($event, iItem)"
+              @drag="drag($event, iItem)"
+              @dragend="dragEnd()"
+            >
+              <t-image v-if="iItem.image" :src="iItem.image" fit="cover" />
+              <!-- <i v-else class="t-icon" :class="iItem.icon"></i> -->
+              <svg v-else class="l-icon" aria-hidden="true">
+                <use :xlink:href="'#' + iItem.icon"></use>
+              </svg>
+              <p>{{ iItem.name }}</p>
+            </div>
+          </t-collapse-panel>
+        </t-collapse>
       </div>
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, reactive } from 'vue';
+import { onMounted, onUnmounted, reactive, ref } from "vue";
+import { shapeLib, chartLib, formLib } from "@/services/defaults";
+import { getPngFolders } from "@/services/png";
 
+const activeGroup = ref("图形");
 const groups = reactive([
   {
-    icon: 'desktop',
-    name: '场景',
-    key: '',
-    active: false,
+    icon: "desktop",
+    name: "场景",
+    key: "",
+  },
+  {
+    icon: "root-list",
+    name: "模板",
+    key: "",
   },
   {
-    icon: 'root-list',
-    name: '模板',
-    key: '',
-    active: false,
+    icon: "chart",
+    name: "图表",
+    key: "chart",
   },
   {
-    icon: 'chart',
-    name: '图表',
-    key: '',
-    active: false,
+    icon: "control-platform",
+    name: "控件",
+    key: "",
   },
   {
-    icon: 'control-platform',
-    name: '控件',
-    key: '',
-    active: false,
+    icon: "image",
+    name: "素材",
+    key: "",
   },
   {
-    icon: 'file-icon',
-    name: '图标',
-    key: '',
-    active: false,
+    icon: "file-icon",
+    name: "图标",
+    key: "",
   },
   {
-    icon: 'chart-bubble',
-    name: '图形',
-    key: '',
-    active: true,
+    icon: "chart-bubble",
+    name: "图形",
+    key: "shape",
   },
   {
-    icon: 'app',
-    name: '我的',
-    key: '',
-    active: false,
+    icon: "app",
+    name: "我的",
+    key: "",
   },
 ]);
 
-const groupChange = (name: string) => {
-  if (name === '图形') {
+const getSceneLib = () => {};
+
+const getCommponentsLib = () => {};
+
+const getMaterialLib = async () => {
+  const [pngs] = await Promise.all([getPngFolders()]);
+  console.log("png", pngs);
+  return pngs;
+};
+let materialLib: any[] = [];
+
+const groupChange = async (name: string) => {
+  activeGroup.value = name;
+  switch (name) {
+    case "图形":
+      showList.value = shapeLib;
+      break;
+    case "图表":
+      showList.value = chartLib;
+      break;
+    case "控件":
+      showList.value = formLib;
+      break;
+    case "素材":
+      if (materialLib.length === 0) {
+        materialLib = await getMaterialLib();
+      }
+      console.log(materialLib);
+      showList.value = materialLib;
+      break;
   }
 };
 
-const showList = [
-  {
-    name: 'square',
-    icon: 't-icon t-rect',
-    id: '',
-    data: {
-      text: '正方形',
-      width: 100,
-      height: 100,
-      name: 'square',
-    },
-  },
-  {
-    name: 'rectangle',
-    icon: 't-icon t-rectangle',
-    id: 2,
-    data: {
-      text: '圆角矩形',
-      width: 200,
-      height: 50,
-      borderRadius: 0.1,
-      name: 'rectangle',
-    },
-  },
-  {
-    name: 'circle',
-    icon: 't-icon t-circle',
-    image: '',
-    id: 3,
-    data: {
-      text: '圆',
-      width: 100,
-      height: 100,
-      name: 'circle',
-    },
-  },
-  {
-    name: 'triangle',
-    icon: 't-icon t-triangle',
-    id: 4,
-    data: {
-      text: '三角形',
-      width: 100,
-      height: 100,
-      name: 'triangle',
-    },
-  },
-];
+const showList = ref<any[]>([]);
+
+const handlePanelChange = (e) => {
+  console.log("change", e);
+};
 
 const dragStart = (event: DragEvent, item: any) => {
   if (!item || !event.dataTransfer) {
@@ -148,7 +140,7 @@ const dragStart = (event: DragEvent, item: any) => {
   }
 
   event.dataTransfer.setData(
-    'Meta2d',
+    "Meta2d",
     JSON.stringify(item.componentData || item.data)
   );
   event.stopPropagation();
@@ -167,13 +159,14 @@ const dragend = (event: any) => {
 };
 
 onMounted(() => {
-  document.removeEventListener('dragstart', dragstart);
-  document.removeEventListener('dragend', dragend);
+  groupChange("图形");
+  document.removeEventListener("dragstart", dragstart);
+  document.removeEventListener("dragend", dragend);
 });
 
 onUnmounted(() => {
-  document.addEventListener('dragstart', dragstart, false);
-  document.addEventListener('dragend', dragend, false);
+  document.addEventListener("dragstart", dragstart, false);
+  document.addEventListener("dragend", dragend, false);
 });
 </script>
 <style lang="postcss" scoped>
@@ -221,32 +214,56 @@ onUnmounted(() => {
     }
 
     .list {
-      background-color: var(--color-background-active);
-      padding: 12px;
-      display: grid;
-      grid-template-columns: 112px 112px;
-      grid-template-rows: 142px;
+      overflow-y: auto;
+      max-height: calc(100vh - 100px);
+      :deep(.t-collapse) {
+        border: 0px;
+      }
+      :deep(.t-collapse-panel__header) {
+        border: 0px;
+      }
 
+      :deep(.t-collapse-panel__body) {
+        border: 0px;
+      }
+      :deep(.t-collapse-panel__content) {
+        background-color: var(--color-background-active);
+        /* padding: 12px; */
+        padding: 0px;
+        display: grid;
+        grid-template-columns: 61px 61px 61px 61px;
+        grid-template-rows: 90px;
+        padding-bottom: 20px;
+      }
       .show-item {
-        padding: 12px;
+        /* padding: 10px; */
+
         p {
-          margin-top: 10px;
-          height: 20px;
-          line-height: 20px;
+          /* margin-top: 10px; */
+          height: 10px;
+          line-height: 10px;
           text-align: center;
-          font-size: 14px;
+          font-size: 12px;
         }
 
-        i {
+        /* i {
           background-color: #fff;
           border-radius: 4px;
-          height: 88px;
-          width: 88px;
-        }
+          height: 40px;
+          width: 40px;
+          margin: 10px;
+        } */
         .t-image {
           background-color: #fff;
           border-radius: 4px;
         }
+
+        svg {
+          color: var(--color);
+          height: 32px;
+          width: 32px;
+          margin: 10px 14px;
+        }
       }
     }
   }

+ 15 - 10
src/views/components/Header.vue

@@ -944,10 +944,6 @@ const onToggleAnchorHand = () => {
   meta2d.toggleAnchorHand();
 };
 
-const onScaleWindow = () => {
-  meta2d.fitView();
-};
-
 const onScaleUp = () => {
   const _scale = meta2d.store.data.scale + 0.1;
   meta2d.scale(_scale);
@@ -958,11 +954,6 @@ const onScaleDown = () => {
   meta2d.scale(_scale);
 };
 
-const onScaleView = () => {
-  meta2d.scale(1);
-  meta2d.centerView();
-};
-
 const autoAnchor = ref(true);
 const onAutoAnchor = () => {
   meta2d.store.options.autoAnchor = !meta2d.store.options.autoAnchor;
@@ -1029,7 +1020,7 @@ const route = useRoute();
 const router = useRouter();
 const { dot ,setDot} = useDot();
 
-enum SaveType {
+export enum SaveType {
   Save,
   SaveAs,
 }
@@ -1297,6 +1288,20 @@ export const newFile = async () => {
   }
 };
 
+export const onScaleWindow = () => {
+  // meta2d.fitView();
+  meta2d.fitSizeView(true,32);
+};
+
+export const onScaleView = () => {
+  meta2d.scale(1);
+  // meta2d.centerView();
+  const { x, y, origin ,center} = meta2d.store.data;
+
+  meta2d.translate(-x - origin.x, -y - origin.y);
+  meta2d.translate(meta2d.store.options.x||0,meta2d.store.options.y||0)
+};
+
 async function newfile(noRouter: boolean = false) {
   meta2d.canvas.drawingLineName && drawPen();
   meta2d.canvas.pencil && drawingPencil();

+ 287 - 33
src/views/components/View.vue

@@ -10,11 +10,17 @@
             <t-icon name="save" @click="save(SaveType.Save)" /></t-badge
         ></a>
       </t-tooltip>
-      <t-tooltip content="保存为模板" placement="bottom">
+      <t-tooltip content="保存为我的组件" placement="bottom">
         <a><t-icon name="layers" @click="save(SaveType.Save, true)" /></a>
       </t-tooltip>
       <t-tooltip content="格式化" placement="bottom">
-        <a>
+        <a
+          @click="oneFormat"
+          @dblclick="alwaysFormat"
+          :style="{
+            color: one || always ? ' #1677ff' : '',
+          }"
+        >
           <svg
             width="1em"
             height="1em"
@@ -38,7 +44,7 @@
         </a>
       </t-tooltip>
       <t-tooltip content="清除格式" placement="bottom">
-        <a>
+        <a @click="clearFormat">
           <svg
             width="1em"
             height="1em"
@@ -59,34 +65,101 @@
       <div class="flex-grow"></div>
 
       <t-tooltip content="直线" placement="bottom">
-        <a><t-icon name="slash" /></a>
+        <a
+          :draggable="true"
+          @dragstart="onAddShape($event, 'line')"
+          @click.stop="onAddShape($event, 'line')"
+          ><t-icon name="slash"
+        /></a>
       </t-tooltip>
+      <!-- <t-tooltip content="连线" placement="top"> -->
+      <t-dropdown
+        :minColumnWidth="200"
+        :maxHeight="560"
+        :delay2="[10, 150]"
+        overlayClassName="header-dropdown"
+      >
+        <a
+          @click="oneDraw"
+          @dblclick="alwaysDraw"
+          :style="{
+            color: oneD || alwaysD ? ' #1677ff' : '',
+          }"
+        >
+          <svg
+            width="1em"
+            height="1em"
+            viewBox="0 0 1024 1024"
+            xmlns="http://www.w3.org/2000/svg"
+          >
+            <path
+              d="M192 64a128 128 0 0 1 123.968 96H384a160 160 0 0 1 159.68 149.504L544 320v384a96 96 0 0 0 86.784 95.552L640 800h68.032a128 128 0 1 1 0 64.064L640 864a160 160 0 0 1-159.68-149.504L480 704V320a96 96 0 0 0-86.784-95.552L384 224l-68.032 0.064A128 128 0 1 1 192 64z m640 704a64 64 0 1 0 0 128 64 64 0 0 0 0-128zM192 128a64 64 0 1 0 0 128 64 64 0 0 0 0-128z"
+              fill="currentColor"
+            ></path>
+          </svg>
+        </a>
+        <t-dropdown-menu>
+          <t-dropdown-item v-for="item in lineTypes">
+            <div class="flex middle" @click="changeLineType(item.value)">
+              {{ item.name }} <span class="flex-grow"></span>
+              <t-icon v-show="item.value === currentLineType" name="check" />
+            </div>
+          </t-dropdown-item>
+        </t-dropdown-menu>
+      </t-dropdown>
+      <!-- </t-tooltip> -->
       <t-tooltip content="文字" placement="bottom">
-        <a>T</a>
-      </t-tooltip>
-      <t-tooltip content="图片" placement="bottom">
-        <a><t-icon name="image" /></a>
+        <a
+          :draggable="true"
+          @dragstart="onAddShape($event, 'text')"
+          @click.stop="onAddShape($event, 'text')"
+          >T</a
+        >
       </t-tooltip>
       <t-tooltip content="视图大小" placement="bottom">
-        <div style="line-height: 40px; margin-left: 8px">100%</div>
+        <div style="line-height: 40px; margin-left: 8px">{{ scale }}%</div>
       </t-tooltip>
 
       <t-tooltip content="100%视图" placement="bottom">
-        <a><t-icon name="refresh" /></a>
+        <a @click="onScaleView"><t-icon name="refresh" /></a>
       </t-tooltip>
       <t-tooltip content="窗口大小" placement="bottom">
-        <a><t-icon name="minus-rectangle" /></a>
+        <a @click="onScaleWindow"><t-icon name="minus-rectangle" /></a>
       </t-tooltip>
       <t-tooltip content="数据源" placement="bottom">
-        <a><t-icon name="server" /></a>
+        <a @click="connectShow"><t-icon name="server" /></a>
       </t-tooltip>
 
       <div class="flex-grow"></div>
       <t-tooltip content="预览" placement="bottom">
-        <a><t-icon name="browse" /></a>
+        <a @click="preview"><t-icon name="browse" /></a>
       </t-tooltip>
-      <t-tooltip content="运行" placement="bottom">
-        <a><t-icon name="caret-right" /></a>
+      <t-tooltip
+        :content="isLock === 2 ? '锁定' : isLock === 1 ? '预览' : '编辑'"
+        placement="bottom"
+      >
+        <a>
+          <!-- <t-icon name="caret-right" /> -->
+          <svg
+            v-if="isLock === 1"
+            class="l-icon"
+            aria-hidden="true"
+            @click="onLock"
+          >
+            <use xlink:href="#l-lock"></use>
+          </svg>
+          <svg
+            v-else-if="isLock === 2"
+            class="l-icon"
+            aria-hidden="true"
+            @click="onLock"
+          >
+            <use xlink:href="#l-wufayidong"></use>
+          </svg>
+          <svg v-else class="l-icon" aria-hidden="true" @click="onLock">
+            <use xlink:href="#l-unlock"></use>
+          </svg>
+        </a>
       </t-tooltip>
       <t-tooltip content="手机查看" placement="bottom">
         <a><t-icon name="qrcode" /></a>
@@ -99,20 +172,34 @@
       </t-tooltip>
     </div>
     <div id="meta2d"></div>
+
+    <t-dialog v-model:visible="connectVisible">
+      <p>这是通信对话框</p>
+    </t-dialog>
   </div>
 </template>
 
 <script lang="ts" setup>
-import { Meta2d, Options, Pen } from '@meta2d/core';
-import { onMounted, onUnmounted, watch } from 'vue';
-import { registerBasicDiagram } from '@/services/register';
-import { useRouter, useRoute } from 'vue-router';
-import { useUser } from '@/services/user';
-import { getLe5le2d } from '@/services/api';
-import { useDot } from '@/services/common';
-import { save, newFile } from './Header.vue';
-import { useSelection, SelectionMode } from '@/services/selections';
-
+import { Meta2d, Options, Pen, deepClone, LockState } from "@meta2d/core";
+import { onMounted, onUnmounted, watch, ref, reactive } from "vue";
+import { registerBasicDiagram } from "@/services/register";
+import { useRouter, useRoute } from "vue-router";
+import { useUser } from "@/services/user";
+import { getLe5le2d } from "@/services/api";
+import { useDot } from "@/services/common";
+import {
+  save,
+  newFile,
+  SaveType,
+  onScaleView,
+  onScaleWindow,
+} from "./Header.vue";
+import { useSelection, SelectionMode } from "@/services/selections";
+import { defaultFormat } from "@/services/defaults";
+import { MessagePlugin } from "tdesign-vue-next";
+import { localMeta2dDataName } from "@/services/utils";
+import localforage from "localforage";
+import { checkData } from "@/services/utils";
 const router = useRouter();
 const route = useRoute();
 const { user, message, getUser, getMessage, signout } = useUser();
@@ -120,22 +207,27 @@ const { dot, setDot, getDot } = useDot();
 const { selections } = useSelection();
 
 const meta2dOptions: Options = {
-  cdn: 'https://assets.le5lecdn.com',
+  cdn: "https://assets.le5lecdn.com",
   rule: true,
-  background: '#1e2430',
+  background: "#1e2430",
   x: 32,
   y: 32,
   width: 1920,
   height: 1080,
+  defaultFormat: { ...defaultFormat },
 };
 onMounted(() => {
-  meta2d = new Meta2d('meta2d', meta2dOptions);
+  meta2d = new Meta2d("meta2d", meta2dOptions);
   registerBasicDiagram();
   open();
   // @ts-ignore
-  meta2d.on('active', active);
+  meta2d.on("active", active);
   // @ts-ignore
-  meta2d.on('inactive', inactive);
+  meta2d.on("inactive", inactive);
+  // @ts-ignore
+  meta2d.on("scale", scaleListener);
+  // @ts-ignore
+  meta2d.on("add", lineAdd);
 });
 
 const watcher = watch(
@@ -147,20 +239,26 @@ const watcher = watch(
 
 const open = async () => {
   if (route.query.id) {
-    const ret: any = getLe5le2d(route.query.id + '');
+    const ret: any = getLe5le2d(route.query.id + "");
     ret && meta2d.open(ret);
   } else {
     meta2d.open();
   }
+  meta2d.store.data.x = meta2d.store.options.x;
+  meta2d.store.data.y = meta2d.store.options.y;
 };
 
 onUnmounted(() => {
   watcher();
   if (meta2d) {
     // @ts-ignore
-    meta2d.off('active', active);
+    meta2d.off("active", active);
+    // @ts-ignore
+    meta2d.off("inactive", inactive);
     // @ts-ignore
-    meta2d.off('inactive', inactive);
+    meta2d.off("scale", scaleListener);
+    // @ts-ignore
+    meta2d.off("add", lineAdd);
     meta2d.destroy();
   }
 });
@@ -187,6 +285,12 @@ const active = (oldPens: Pen[]) => {
     checkPropType(pens);
     selections.pens = oldPens;
   }, 10);
+
+  //格式刷处理
+  if (one.value || always.value) {
+    meta2d.formatPainter();
+    one.value = false;
+  }
 };
 
 /**
@@ -205,6 +309,156 @@ const checkPropType = (pens: Pen[]) => {
     selections.mode = SelectionMode.File;
   }
 };
+
+const one = ref(false);
+const always = ref(false);
+
+const oneFormat = () => {
+  if (one.value) {
+    one.value = false;
+  } else {
+    one.value = true;
+    meta2d.setFormatPainter();
+  }
+  if (always.value) {
+    always.value = false;
+    one.value = false;
+  }
+};
+
+const alwaysFormat = () => {
+  always.value = true;
+};
+
+const clearFormat = () => {
+  always.value = false;
+  one.value = false;
+  meta2d.clearFormatPainter();
+};
+
+const scale = ref(100);
+function scaleListener(newScale: number) {
+  scale.value = Math.round(newScale * 100);
+}
+
+const connectVisible = ref(false);
+const connectShow = () => {
+  connectVisible.value = true;
+};
+
+const currentLineType = ref("curve");
+const lineTypes = reactive([
+  { name: "曲线", icon: "t-icon t-curve2", value: "curve" },
+  { name: "线段", icon: "t-icon t-polyline", value: "polyline" },
+  { name: "直线", icon: "t-icon t-line", value: "line" },
+  { name: "脑图曲线", icon: "t-icon t-mind", value: "mind" },
+]);
+
+const changeLineType = (value: string) => {
+  currentLineType.value = value;
+  if (meta2d) {
+    meta2d.store.options.drawingLineName = value;
+    meta2d.canvas.drawingLineName && (meta2d.canvas.drawingLineName = value);
+    meta2d.store.active?.forEach((pen) => {
+      meta2d.updateLineType(pen, value);
+    });
+  }
+};
+
+const oneD = ref<boolean>(false);
+const alwaysD = ref<boolean>(false);
+const oneDraw = () => {
+  if (oneD.value) {
+    oneD.value = false;
+  } else {
+    oneD.value = true;
+    meta2d.drawLine(meta2d.store.options.drawingLineName);
+  }
+  if (alwaysD.value) {
+    meta2d.finishDrawLine();
+    meta2d.drawLine();
+    oneD.value = false;
+    alwaysD.value = false;
+  }
+};
+const alwaysDraw = () => {
+  alwaysD.value = true;
+};
+
+const lineAdd = (pens: Pen[]) => {
+  if (pens.length === 1 && pens[0].name === "line") {
+    //连线类型
+    if (oneD.value && !alwaysD.value) {
+      if (meta2d.canvas.drawingLineName) {
+        oneD.value = false;
+        setTimeout(() => {
+          meta2d.finishDrawLine();
+          meta2d.drawLine();
+        }, 100);
+      }
+    }
+  }
+};
+
+const onAddShape = (event: DragEvent, name: string) => {
+  let data: any;
+  if (name === "text") {
+    data = {
+      text: "le5le Meta2d",
+      width: 100,
+      height: 20,
+      name: "text",
+    };
+  } else if (name === "line") {
+    data = {
+      anchors: [
+        { id: "0", x: 0, y: 0.5 },
+        { id: "1", x: 1, y: 0.5 },
+      ],
+      width: 100,
+      height: 1,
+      name: "line",
+    };
+  }
+  if (!event.dataTransfer) {
+    meta2d.canvas.addCaches = deepClone([data]);
+  } else {
+    event.dataTransfer.setData("Meta2d", JSON.stringify(data));
+  }
+  event.stopPropagation();
+};
+
+const isLock = ref(0);
+function onLock() {
+  !isLock.value && (isLock.value = 0);
+  if (isLock.value === LockState.DisableMove) {
+    isLock.value = LockState.None;
+  } else {
+    isLock.value++;
+  }
+  meta2d.lock(isLock.value);
+  meta2d.hideInput();
+}
+
+const preview = async () => {
+  meta2d.stopAnimate();
+  const data: Meta2dBackData = meta2d.data();
+  checkData(data);
+  if (dot && user && data._id) {
+    // 有 id ,是修改后保存
+    await save(SaveType.Save);
+  }
+  if (!data._id) {
+    await localforage.setItem(localMeta2dDataName, JSON.stringify(data));
+  }
+  router.push({
+    path: "/preview",
+    query: {
+      r: Date.now() + "",
+      id: data._id,
+    },
+  });
+};
 </script>
 <style lang="postcss" scoped>
 .meta2d {

+ 52 - 2
vite.config.ts

@@ -1,12 +1,14 @@
-import { defineConfig } from "vite";
+import { defineConfig, Plugin, ViteDevServer } from "vite";
 import vue from "@vitejs/plugin-vue";
 import vueJsx from "@vitejs/plugin-vue-jsx";
 import * as path from "path";
 import monacoEditorPlugin from "vite-plugin-monaco-editor";
+import * as fs from "fs";
+import formidable from "formidable";
 
 // https://vitejs.dev/config/
 export default defineConfig({
-  plugins: [vue(), vueJsx(), monacoEditorPlugin({})],
+  plugins: [vue(), vueJsx(), monacoEditorPlugin({}), fileList()],
   resolve: {
     alias: {
       "@": path.resolve(__dirname, "./src/"),
@@ -24,3 +26,51 @@ export default defineConfig({
     },
   },
 });
+
+function fileList(): Plugin {
+  return {
+    name: "vite-plugin-svg-png-files",
+    configureServer(server: ViteDevServer) {
+      server.middlewares.use((req, res, next) => {
+        const url = req.url as string;
+
+        if (
+          (url.startsWith("/svg/") || url.startsWith("/png/")) &&
+          url.endsWith("/")
+        ) {
+          const pwd = decodeURI(path.join(__dirname, "public", url));
+          const files = fs.readdirSync(pwd, {
+            withFileTypes: true,
+          });
+
+          const list: {
+            name: string;
+            type?: string;
+          }[] = [];
+          for (const item of files) {
+            if (item.isDirectory()) {
+              list.push({ name: item.name, type: "directory" });
+            } else {
+              list.push({ name: item.name });
+            }
+          }
+          res.end(JSON.stringify(list));
+        } else if (url === "/img" && req.method === "POST") {
+          const form = formidable({
+            uploadDir: decodeURI(path.join(__dirname, "public", "/img")),
+            keepExtensions: true,
+          });
+          form.parse(req, (err, fields, files) => {
+            if (!err) {
+              res.end(
+                JSON.stringify({ url: "/img/" + files.file.newFilename })
+              );
+            }
+          });
+        } else {
+          next();
+        }
+      });
+    },
+  };
+}

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff