Przeglądaj źródła

feat: 接口联调

liaojiaxing 4 tygodni temu
rodzic
commit
268e6c99bb

+ 1 - 0
components.d.ts

@@ -16,6 +16,7 @@ declare module 'vue' {
     ElForm: typeof import('element-plus/es')['ElForm']
     ElFormItem: typeof import('element-plus/es')['ElFormItem']
     ElInput: typeof import('element-plus/es')['ElInput']
+    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
     ElOption: typeof import('element-plus/es')['ElOption']
     ElRow: typeof import('element-plus/es')['ElRow']
     ElSelect: typeof import('element-plus/es')['ElSelect']

+ 9 - 2
package.json

@@ -11,16 +11,22 @@
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.1",
     "@univerjs/icons": "^0.4.6",
-    "@univerjs/preset-sheets-core": "^0.9.2",
-    "@univerjs/presets": "^0.9.2",
+    "@univerjs/preset-sheets-core": "^0.10.0",
+    "@univerjs/preset-sheets-data-validation": "^0.10.0",
+    "@univerjs/preset-sheets-drawing": "^0.10.0",
+    "@univerjs/preset-sheets-find-replace": "^0.10.0",
+    "@univerjs/preset-sheets-hyper-link": "^0.10.0",
+    "@univerjs/presets": "^0.10.0",
     "@vitejs/plugin-vue-jsx": "^5.0.1",
     "@zwight/luckyexcel": "^1.1.6",
     "element-plus": "^2.10.4",
     "file-saver": "^2.0.5",
+    "js-base64": "^3.7.7",
     "less": "^4.4.0",
     "less-loader": "^12.3.0",
     "lodash-es": "^4.17.21",
     "normalize.css": "^8.0.1",
+    "pako": "^2.1.0",
     "pinia": "^3.0.3",
     "simple-mind-map": "0.14.0-fix.1",
     "vue": "^3.5.17",
@@ -28,6 +34,7 @@
   },
   "devDependencies": {
     "@types/lodash-es": "^4.17.12",
+    "@types/pako": "^2.0.3",
     "@vitejs/plugin-vue": "^6.0.0",
     "@vue/tsconfig": "^0.7.0",
     "rollup-plugin-visualizer": "^6.0.3",

Plik diff jest za duży
+ 925 - 903
pnpm-lock.yaml


+ 27 - 2
src/App.vue

@@ -1,11 +1,36 @@
 <script setup lang="ts">
-import zhCn from 'element-plus/es/locale/lang/zh-cn'
+import { onMounted, onBeforeMount } from "vue";
+import zhCn from "element-plus/es/locale/lang/zh-cn";
+import { useEditBomStore } from "./store/editbom";
+
+const editBomStore = useEditBomStore();
+const readMessage = (event: MessageEvent) => {
+  console.log('接收到消息:', event);
+  const { data = {}} = event;
+  // {
+  //   type: "setTools",
+  //   value: any
+  // }
+  if (data?.type === "setTools" && data.value) {
+    editBomStore.BpmTools = data.value;
+  }
+};
+
+// 读取工具
+onMounted(() => {
+  window.parent.postMessage("ready", "*");
+  window.addEventListener("message", readMessage);
+});
+
+onBeforeMount(() => {
+  window.removeEventListener("message", readMessage);
+});
 </script>
 
 <template>
   <div class="app-wrapper">
     <el-config-provider :locale="zhCn">
-      <router-view/>
+      <router-view />
     </el-config-provider>
   </div>
 </template>

Plik diff jest za duży
+ 1 - 0
src/assets/warning.svg


+ 66 - 11
src/components/Sheet.vue

@@ -1,7 +1,11 @@
 <template>
   <div class="univer-wrapper">
     <slot></slot>
-    <div ref="container" v-loading="loading" style="width: 100%; height: 100%; flex: 1"></div>
+    <div
+      ref="container"
+      v-loading="loading"
+      style="width: 100%; height: 100%; flex: 1"
+    ></div>
   </div>
 </template>
 
@@ -15,9 +19,19 @@ import {
   watch,
 } from "vue";
 import { UniverSheetsCorePreset } from "@univerjs/preset-sheets-core";
-import type { Univer, FUniver, Workbook } from "@univerjs/presets";
+import type {
+  Univer,
+  FUniver,
+  Workbook,
+  IWorkbookData,
+} from "@univerjs/presets";
 import UniverPresetSheetsCoreZhCN from "@univerjs/preset-sheets-core/locales/zh-CN";
-import { createUniver, LocaleType, merge } from "@univerjs/presets";
+import {
+  createUniver,
+  LocaleType,
+  merge,
+  UniverInstanceType,
+} from "@univerjs/presets";
 import "@univerjs/preset-sheets-core/lib/index.css";
 import { ElMessage } from "element-plus";
 
@@ -26,6 +40,22 @@ import CustomImportMenu from "./plugins/controllers/menu/import.menu";
 import CustomExportMenu from "./plugins/controllers/menu/export.menu";
 // import CustomSaveMenu from "./plugins/controllers/menu/save.menu";
 
+import { UniverSheetsHyperLinkPreset } from "@univerjs/preset-sheets-hyper-link";
+import UniverPresetSheetsHyperLinkZhCN from "@univerjs/preset-sheets-hyper-link/locales/zh-CN";
+import "@univerjs/preset-sheets-hyper-link/lib/index.css";
+
+import { UniverSheetsDataValidationPreset } from "@univerjs/preset-sheets-data-validation";
+import UniverPresetSheetsDataValidationZhCN from "@univerjs/preset-sheets-data-validation/locales/zh-CN";
+import "@univerjs/preset-sheets-data-validation/lib/index.css";
+
+import { UniverSheetsFindReplacePreset } from "@univerjs/preset-sheets-find-replace";
+import UniverPresetSheetsFindReplaceZhCN from "@univerjs/preset-sheets-find-replace/locales/zh-CN";
+import "@univerjs/preset-sheets-find-replace/lib/index.css";
+
+import { UniverSheetsDrawingPreset } from "@univerjs/preset-sheets-drawing";
+import UniverPresetSheetsDrawingZhCN from "@univerjs/preset-sheets-drawing/locales/zh-CN";
+import "@univerjs/preset-sheets-drawing/lib/index.css";
+
 const container = ref<HTMLDivElement | null>(null);
 const loading = ref(false);
 
@@ -36,7 +66,7 @@ let univerAPIInstance: FUniver | null = null;
 export type UniverExpose = {
   getUniverInstance: () => Univer | null;
   getUniverAPIInstance: () => FUniver | null;
-  getUniverSnapshot: (workbook: Workbook) => void;
+  getUniverSnapshot: () => Workbook;
 };
 
 const getUniverSnapshot = () => {
@@ -81,7 +111,14 @@ onMounted(() => {
   const { univer, univerAPI } = createUniver({
     locale: LocaleType.ZH_CN,
     locales: {
-      [LocaleType.ZH_CN]: merge({}, UniverPresetSheetsCoreZhCN),
+      [LocaleType.ZH_CN]: merge(
+        {},
+        UniverPresetSheetsCoreZhCN,
+        UniverPresetSheetsHyperLinkZhCN,
+        UniverPresetSheetsDataValidationZhCN,
+        UniverPresetSheetsFindReplaceZhCN,
+        UniverPresetSheetsDrawingZhCN
+      ),
     },
     presets: [
       UniverSheetsCorePreset({
@@ -93,6 +130,19 @@ onMounted(() => {
           },
         },
       }),
+      UniverSheetsHyperLinkPreset({
+        // 自定义外部链接跳转方式
+        urlHandler: {
+          navigateToOtherWebsite: (url) =>
+            window.open(`${url}?utm_source=univer`, "_blank"),
+        },
+      }),
+      UniverSheetsDataValidationPreset({
+        // 是否在下拉菜单中显示编辑按钮
+        showEditOnDropdown: true,
+      }),
+      UniverSheetsFindReplacePreset(),
+      UniverSheetsDrawingPreset(),
     ],
   });
 
@@ -145,7 +195,10 @@ onMounted(() => {
     menu,
   });
 
-  univerAPI.createWorkbook({});
+  univer.createUnit<IWorkbookData, Workbook>(
+    UniverInstanceType.UNIVER_SHEET,
+    props?.workbook || {}
+  );
 
   univerInstance = univer;
   univerAPIInstance = univerAPI;
@@ -155,15 +208,18 @@ onMounted(() => {
 watch(
   () => props.workbook,
   (workbook) => {
-    if (workbook && univerAPIInstance) {
-      univerAPIInstance?.createWorkbook(workbook);
+    if (workbook && univerInstance) {
+      console.log("workbook", workbook, univerInstance);
+      univerInstance.createUnit<IWorkbookData, Workbook>(
+        UniverInstanceType.UNIVER_SHEET,
+        workbook
+      );
     }
   },
   {
-    immediate: true,
     deep: true,
   }
-)
+);
 
 onBeforeUnmount(() => {
   univerInstance?.dispose();
@@ -179,6 +235,5 @@ onBeforeUnmount(() => {
   height: 100%;
   display: flex;
   flex-direction: column;
-
 }
 </style>

+ 76 - 34
src/components/mindmap/Mindmap.vue

@@ -12,15 +12,22 @@ import {
   defineExpose,
   withDefaults,
   defineEmits,
+  createApp,
+  defineComponent,
 } from "vue";
 import MindMap from "simple-mind-map";
 import defaultTheme from "./defaultTheme";
 // mindmap plugins
 import Drag from "simple-mind-map/src/plugins/Drag";
 import SearchPlugin from "simple-mind-map/src/plugins/Search";
+import Watermark from "simple-mind-map/src/plugins/Watermark";
+
+import changIco from "@/assets/warning.svg";
+import { ElTooltip } from "element-plus";
 
 MindMap.usePlugin(Drag);
 MindMap.usePlugin(SearchPlugin);
+MindMap.usePlugin(Watermark);
 
 export type MindMapInstance = {
   getInstance: () => MindMap;
@@ -37,7 +44,7 @@ const props = withDefaults(
   }
 );
 
-const emit = defineEmits(["update:data"]);
+const emit = defineEmits(["update:data", "nodeDblClick"]);
 
 const mindmapRef = ref<HTMLDivElement | null>(null);
 const mindmap = ref<MindMap | null>(null);
@@ -51,8 +58,13 @@ onBeforeUnmount(() => {
 watch(
   () => props.data,
   (data) => {
-    console.log("data", data);
-    data && mindmap.value?.updateData(data);
+    if (data) {
+      mindmap.value?.updateData(data);
+      setTimeout(() => {
+        // @ts-ignore
+        mindmap.value?.view?.fit();
+      }, 100);
+    }
   },
   {
     deep: true,
@@ -70,51 +82,70 @@ watch(
 onMounted(() => {
   const instance = new MindMap({
     el: mindmapRef.value!,
-    data: props.data || {
-      data: {
-        text: "主物料",
-      },
-      children: [],
-    },
+    data: props.data,
     themeConfig: {
       ...defaultTheme,
       backgroundColor: "#eee",
     },
+    // 拖拽限制
+    beforeDragStart: ([node]: any[]) => {
+      console.log("dran start", node.nodeData);
+      const data = node?.nodeData.data;
+
+      return data?.is_disable;
+    },
     // 只读
     readonly: !!props.readonly,
     isUseCustomNodeContent: true,
     customCreateNodeContent: (node: any) => {
-      const { sourceData = {} } = node.getData();
-      console.log("node", node, sourceData);
+      const sourceData = node.nodeData;
+      const data = sourceData.data;
+
+      const App = defineComponent({
+        render() {
+          return (
+            <div class="text-#666 flex items-center">
+              <span class="mr-4px" style={{ color: node.isRoot ? "#fff" : "" }}>
+                {data?.name}
+              </span>
+              {data?.qty !== undefined && data?.qty !== '' && !node?.isRoot && (
+                <ElTooltip content="数量" placement="top">
+                  <span class="mr-4px text-white px-8px py-2px bg-#40bf8a rounded-4px">
+                    {data?.qty ?? "-"}
+                  </span>
+                </ElTooltip>
+              )}
+              {data?.type && (
+                <ElTooltip content="类型" placement="top">
+                  <span class="mr-4px text-white px-8px py-2px bg-#bf4f40 rounded-4px">
+                    {data?.type || "-"}
+                  </span>
+                </ElTooltip>
+              )}
+              {data?.is_change && (
+                <ElTooltip content={data?.change_content} placement="top">
+                  <img src={changIco} class="w-20px h-20px inline-block" />
+                </ElTooltip>
+              )}
+            </div>
+          );
+        },
+      });
+
       // return你的自定义DOM节点
       let div = document.createElement("div");
+      const app = createApp(App);
       div.className = "mx-12px my-8px";
       div.style = "user-select: none;";
-      div.innerHTML = `
-        <div class="w-200px text-#666">
-          ${
-            // <div class="h-60px w-80px overflow-hidden m-auto mb-8px">
-            //   <img src="https://picsum.photos/80/60" class="m-auto"/>
-            // </div>
-            ""
-            }
-          <div class="border-b border-b-solid flex items-center justify-between pb-4px">
-            <span class="text-sm font-light">零件号</span>
-            <span class="text-sm font-medium">123445</span>
-          </div>
-          <div class="border-b border-b-solid flex items-center justify-between pb-4px">
-            <span class="text-sm font-light">描述</span>
-            <span class="text-sm font-medium">xxxx零件</span>
-          </div>
-          <div class="border-b flex items-center justify-between pb-4px">
-            <span class="text-sm font-light">数量</span>
-            <span class="text-sm font-medium">10</span>
-          </div>
-        </div>`;
+      app.mount(div);
+
       return div;
     },
   } as any);
 
+  // @ts-ignore TODO 临时隐藏Tab新增功能
+  instance.keyCommand.removeShortcut("Tab");
+
   // 事件监听
   // 自适应
   instance.on("node_tree_render_end", () => {
@@ -123,15 +154,23 @@ onMounted(() => {
     instance?.view?.fit();
     loaded = true;
   });
+
   // 激活节点
   instance.on("node_active", (_node: any, list: any[]) => {
     activeNodeList.value = list;
   });
+
   // 数据改变
   instance.on("data_change", (data: any) => {
-    // console.log("data_change", data);
+    console.log("data_change", data);
     emit("update:data", data);
   });
+
+  // 节点双击
+  instance.on("node_dblclick", () => {
+    emit("nodeDblClick", activeNodeList.value[0]);
+  });
+
   // 视图数据改变
   // instance.on("view_data_change", (data: any) => {
   //   console.log("view_data_change", data);
@@ -147,11 +186,14 @@ defineExpose({
 });
 </script>
 
-<style scoped>
+<style lang="less" scoped>
 .mindmap-container {
   width: 100%;
   height: 100%;
   padding: 0;
   margin: 0;
+  :deep(.smm-quick-create-child-btn) {
+    display: none !important;
+  }
 }
 </style>

+ 21 - 21
src/components/mindmap/defaultTheme.ts

@@ -12,7 +12,7 @@ export default {
   // 连线的粗细
   lineWidth: 1,
   // 连线的颜色
-  lineColor: '#424242',
+  lineColor: '#549688',
   // 连线样式
   lineDasharray: 'none',
   // 连线是否开启流动效果,仅在虚线时有效(需要注册LineFlow插件)
@@ -34,7 +34,7 @@ export default {
   // 概要连线的粗细
   generalizationLineWidth: 1,
   // 概要连线的颜色
-  generalizationLineColor: '#999',
+  generalizationLineColor: '#549688',
   // 概要曲线距节点的距离
   generalizationLineMargin: 0,
   // 概要节点距节点的距离
@@ -72,19 +72,19 @@ export default {
   // 根节点样式
   root: {
     shape: 'rectangle',
-    fillColor: '#ffffffff',
+    fillColor: '#549688',
     fontFamily: '微软雅黑, Microsoft YaHei',
     color: '#fff',
     fontSize: 16,
     fontWeight: 'bold',
     fontStyle: 'normal',
-    borderColor: '#808080',
-    borderWidth: 1,
+    borderColor: 'transparent',
+    borderWidth: 0,
     borderDasharray: 'none',
     borderRadius: 5,
     textDecoration: 'none',
     gradientStyle: false,
-    startColor: '#999',
+    startColor: '#549688',
     endColor: '#fff',
     startDir: [0, 0],
     endDir: [1, 0],
@@ -114,21 +114,21 @@ export default {
   // 二级节点样式
   second: {
     shape: 'rectangle',
-    marginX: 30,
-    marginY: 30,
-    fillColor: '#fafafa',
+    marginX: 100,
+    marginY: 40,
+    fillColor: '#fff',
     fontFamily: '微软雅黑, Microsoft YaHei',
-    color: '#fff',
+    color: '#565656',
     fontSize: 16,
     fontWeight: 'normal',
     fontStyle: 'normal',
-    borderColor: '#808080',
+    borderColor: '#549688',
     borderWidth: 1,
     borderDasharray: 'none',
     borderRadius: 5,
     textDecoration: 'none',
     gradientStyle: false,
-    startColor: '#999',
+    startColor: '#549688',
     endColor: '#fff',
     startDir: [0, 0],
     endDir: [1, 0],
@@ -142,21 +142,21 @@ export default {
   // 三级及以下节点样式
   node: {
     shape: 'rectangle',
-    marginX: 30,
-    marginY: 20,
-    fillColor: '#fafafa',
+    marginX: 50,
+    marginY: 0,
+    fillColor: 'transparent',
     fontFamily: '微软雅黑, Microsoft YaHei',
-    color: '#fff',
+    color: '#6a6d6c',
     fontSize: 14,
     fontWeight: 'normal',
     fontStyle: 'normal',
-    borderColor: '#808080',
-    borderWidth: 1,
+    borderColor: 'transparent',
+    borderWidth: 0,
     borderRadius: 5,
     borderDasharray: 'none',
     textDecoration: 'none',
     gradientStyle: false,
-    startColor: '#999',
+    startColor: '#549688',
     endColor: '#fff',
     startDir: [0, 0],
     endDir: [1, 0],
@@ -178,13 +178,13 @@ export default {
     fontSize: 16,
     fontWeight: 'normal',
     fontStyle: 'normal',
-    borderColor: '#999',
+    borderColor: '#549688',
     borderWidth: 1,
     borderDasharray: 'none',
     borderRadius: 5,
     textDecoration: 'none',
     gradientStyle: false,
-    startColor: '#999',
+    startColor: '#549688',
     endColor: '#fff',
     startDir: [0, 0],
     endDir: [1, 0],

+ 6 - 3
src/components/plugins/controllers/menu/export.menu.ts

@@ -30,13 +30,16 @@ const ExportButtonOperation: (config?: ICustomMenuPulginParams) => ICommand = (
 
     if (config?.before && !before) return false;
     const univer = _accessor.get(IUniverInstanceService);
-    const snapshot = univer
+    const snapshot = config?.snapshot?.() || univer
       .getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)
       ?.getSnapshot();
 
     const postParams = {
-      snapshot: config?.snapshot?.() || snapshot,
-      fileName: config?.fileName,
+      snapshot: {
+        ...snapshot,
+        resources: []
+      },
+      fileName: config?.fileName || snapshot?.name,
       ...(before || {}),
     };
 

+ 329 - 81
src/pages/excel/ConfigDrawer.vue

@@ -1,177 +1,425 @@
 <template>
-  <el-drawer v-model="visible" title="物料配置" size="600">
-    <el-form ref="form" label-position="top">
+  <el-drawer
+    v-model="visible"
+    title="BOM详情配置"
+    size="600"
+    v-loading="loading"
+  >
+    <el-form
+      ref="form"
+      label-position="top"
+      :model="formData"
+      :disabled="nodeData?.is_disable"
+      scroll-to-error
+    >
       <el-row :gutter="12">
         <el-col :span="12">
-          <el-form-item label="SN">
-            <el-input v-model="formData.sn" />
+          <el-form-item label="BOM编号" prop="bom_code">
+            <el-input
+              placeholder="请输入"
+              v-model="formData.bom_code"
+              disabled
+            />
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="级号">
-            <el-select disabled placeholder="级号">
+          <el-form-item
+            label="零件类型"
+            prop="part_type"
+            :rules="[
+              { required: true, message: '请选择零件类型', trigger: 'change' },
+            ]"
+          >
+            <el-select v-model="formData.part_type" placeholder="请选择">
               <el-option
-                v-for="item in 10"
-                :key="item"
-                :label="item"
-                :value="item"
+                v-for="item in options"
+                :key="item.value"
+                :label="item.label"
+                :value="item.value"
               />
             </el-select>
           </el-form-item>
         </el-col>
-      </el-row>
-      <el-row :gutter="12">
+
         <el-col :span="12">
-          <el-form-item label="自制/外购/标准件/领用件/DB/CCC/NA">
-            <el-input v-model="formData.name" />
+          <el-form-item
+            label="零件名/描述"
+            prop="part_name"
+            :rules="[
+              { required: true, message: '零件名不能为空', trigger: 'blur' },
+            ]"
+          >
+            <el-input placeholder="请输入" v-model="formData.part_name" />
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="ERP号">
-            <el-input v-model="formData.desc" />
+          <el-form-item
+            label="内部零件号"
+            name="internal_part_number"
+            :rules="[
+              {
+                required: true,
+                message: '内部零件号不能为空',
+                trigger: 'blur',
+              },
+            ]"
+          >
+            <el-input
+              placeholder="请输入"
+              v-model="formData.internal_part_number"
+            />
           </el-form-item>
         </el-col>
-      </el-row>
-      <el-row :gutter="12">
+
         <el-col :span="12">
-          <el-form-item label="客户零件号">
-            <el-input v-model="formData.desc" />
+          <el-form-item
+            label="客户零件号"
+            name="customer_part_code"
+            :rules="[
+              {
+                required: true,
+                message: '客户零件号不能为空',
+                trigger: 'blur',
+              },
+            ]"
+          >
+            <el-input
+              placeholder="请输入"
+              v-model="formData.customer_part_code"
+            />
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="客户版本号">
-            <el-input v-model="formData.desc" />
+          <el-form-item label="客户版本号" name="customer_part_vesion">
+            <el-input
+              placeholder="请输入"
+              v-model="formData.customer_part_vesion"
+            />
           </el-form-item>
         </el-col>
-      </el-row>
-      <el-row :gutter="12">
+
         <el-col :span="12">
-          <el-form-item label="内部零件号">
-            <el-input v-model="formData.desc" />
+          <el-form-item label="ERP号" name="erp_code">
+            <el-input placeholder="请输入" v-model="formData.erp_code" />
           </el-form-item>
         </el-col>
+
         <el-col :span="12">
-          <el-form-item label="零件名/描述">
-            <el-input v-model="formData.desc" />
+          <el-form-item label="材料名称" name="material_name">
+            <el-input placeholder="请输入" v-model="formData.material_name" />
           </el-form-item>
         </el-col>
-      </el-row>
-      <el-row :gutter="12">
         <el-col :span="12">
-          <el-form-item label="材料名称">
-            <el-input v-model="formData.desc" />
+          <el-form-item label="材料牌号" name="material_grade">
+            <el-input placeholder="请输入" v-model="formData.material_grade" />
           </el-form-item>
         </el-col>
+
         <el-col :span="12">
-          <el-form-item label="材料牌号">
-            <el-input v-model="formData.desc" />
+          <el-form-item label="规格及标准" name="spec_standards">
+            <el-input placeholder="请输入" v-model="formData.spec_standards" />
           </el-form-item>
         </el-col>
-      </el-row>
-      <el-row :gutter="12">
         <el-col :span="12">
-          <el-form-item label="规格及标准">
-            <el-input v-model="formData.desc" />
+          <el-form-item label="生产工艺" name="process">
+            <el-input placeholder="请输入" v-model="formData.process" />
           </el-form-item>
         </el-col>
+
         <el-col :span="12">
-          <el-form-item label="生产工艺">
-            <el-input v-model="formData.desc" />
+          <el-form-item label="表面处理" name="appreance_treat">
+            <el-input placeholder="请输入" v-model="formData.appreance_treat" />
           </el-form-item>
         </el-col>
-      </el-row>
-      <el-row :gutter="12">
         <el-col :span="12">
-          <el-form-item label="表面处理">
-            <el-input v-model="formData.desc" />
+          <el-form-item label="颜色" name="color">
+            <el-input placeholder="请输入" v-model="formData.color" />
           </el-form-item>
         </el-col>
-        <el-col :span="12">
-          <el-form-item label="颜色">
-            <el-input v-model="formData.desc" />
+        <el-col :span="24">
+          <el-form-item label="颜色配置" name="color_config">
+            <el-input placeholder="请输入" v-model="formData.color_config" />
           </el-form-item>
         </el-col>
       </el-row>
-      <el-row :gutter="12">
+
+      <el-row :gutter="8">
         <el-col :span="8"
-          ><el-form-item label="长">
-            <el-input v-model="formData.desc" />
+          ><el-form-item label="长" name="length">
+            <el-input-number
+              class="inline-block w-full!"
+              placeholder="请输入"
+              v-model="formData.length"
+            />
           </el-form-item>
         </el-col>
         <el-col :span="8">
-          <el-form-item label="宽">
-            <el-input v-model="formData.desc" /> </el-form-item
+          <el-form-item label="宽" name="width">
+            <el-input-number
+              class="inline-block w-full!"
+              placeholder="请输入"
+              v-model="formData.width"
+            /> </el-form-item
         ></el-col>
         <el-col :span="8">
-          <el-form-item label="高">
-            <el-input v-model="formData.desc" /> </el-form-item
+          <el-form-item label="高" name="height">
+            <el-input-number
+              class="inline-block w-full!"
+              placeholder="请输入"
+              v-model="formData.height"
+            /> </el-form-item
         ></el-col>
       </el-row>
+
       <el-row :gutter="12">
         <el-col :span="12">
-          <el-form-item label="数量">
-            <el-input v-model="formData.desc" /> </el-form-item
+          <el-form-item label="数量" name="qty">
+            <el-input-number
+              class="inline-block w-full!"
+              placeholder="请输入"
+              v-model="formData.qty"
+            /> </el-form-item
         ></el-col>
         <el-col :span="12"
-          ><el-form-item label="单位">
-            <el-input v-model="formData.desc" />
+          ><el-form-item label="单位" name="unit">
+            <el-input placeholder="请输入" v-model="formData.unit" />
           </el-form-item>
         </el-col>
-      </el-row>
-      <el-row :gutter="12">
+
         <el-col :span="12"
-          ><el-form-item label="每件净重">
-            <el-input v-model="formData.desc" />
+          ><el-form-item label="每件净重" name="net_weight">
+            <el-input-number
+              class="inline-block w-full!"
+              placeholder="请输入"
+              v-model="formData.net_weight"
+            />
           </el-form-item>
         </el-col>
         <el-col :span="12"
-          ><el-form-item label="每件毛重">
-            <el-input v-model="formData.desc" />
+          ><el-form-item label="每件毛重" name="gross_weight">
+            <el-input-number
+              class="inline-block w-full!"
+              placeholder="请输入"
+              v-model="formData.gross_weight"
+            />
           </el-form-item>
         </el-col>
-      </el-row>
-      <el-row :gutter="12">
+
         <el-col :span="12"
-          ><el-form-item label="工艺消耗定额">
-            <el-input v-model="formData.desc" />
+          ><el-form-item label="工艺消耗定额" name="technology_consume">
+            <el-input-number
+              class="inline-block w-full!"
+              placeholder="请输入"
+              v-model="formData.technology_consume"
+            />
           </el-form-item>
         </el-col>
         <el-col :span="12">
-          <el-form-item label="利用率%">
-            <el-input v-model="formData.desc" />
+          <el-form-item label="利用率%" name="rate">
+            <el-input-number
+              class="inline-block w-full!"
+              placeholder="请输入"
+              v-model="formData.rate"
+            />
           </el-form-item>
         </el-col>
       </el-row>
-      <el-form-item label="供应商">
-        <el-input v-model="formData.desc" />
+
+      <el-form-item label="供应商" name="supplier">
+        <el-input placeholder="请输入" v-model="formData.supplier" />
       </el-form-item>
-      <el-form-item label="备注">
-        <el-input type="textarea" :rows="4" v-model="formData.desc" />
+      <el-form-item label="备注" name="note">
+        <el-input
+          placeholder="请输入"
+          type="textarea"
+          :rows="4"
+          v-model="formData.note"
+        />
       </el-form-item>
     </el-form>
     <template #footer>
       <el-button @click="visible = false">取消</el-button>
-      <el-button type="primary" @click="visible = false">确定</el-button>
+      <el-button type="primary" @click="handleSubmit">确定</el-button>
     </template>
   </el-drawer>
 </template>
 
 <script setup lang="ts">
-import { ref, defineExpose, reactive } from "vue";
+import { ref, defineExpose, defineEmits } from "vue";
+import type { FormInstance } from "element-plus";
+
+const keyMap = {
+  type: "类型",
+  bom_code: "BOM编号",
+  qty: "数量",
+  appreance_treat: "表面处理",
+  color: "颜色",
+  color_config: "颜色配置",
+  customer_part_code: "客户零件号",
+  customer_part_vesion: "客户版本号",
+  erp_code: "ERP号",
+  height: "高",
+  internal_part_number: "内部零件号",
+  length: "长",
+  material_grade: "材料牌号",
+  material_name: "材料名称",
+  net_weight: "每件净重",
+  note: "备注",
+  part_name: "零件名/描述",
+  part_type: "零件类型",
+  process: "生产工艺",
+  rate: "利用率",
+  spec_standards: "规格及标准",
+  supplier: "供应商",
+  technology_consume: "工艺消耗定额",
+  unit: "单位",
+  width: "宽",
+};
+
+const options = [
+  { label: "自制", value: "M" },
+  { label: "外购", value: "P" },
+  { label: "标准件", value: "S" },
+  { label: "领用件", value: "CSMT" },
+  { label: "DB", value: "DB" },
+  { label: "CCC", value: "CCC" },
+  { label: "NA", value: "NA" },
+];
 
 const visible = ref(false);
-const formData = reactive({
-  sn: "",
+const formData = ref<Record<string, any>>({
   name: "",
-  desc: "",
+  type: "",
+  bom_code: "",
+  qty: undefined,
+  is_change: false,
+  change_content: "",
+  is_disable: false,
+  bom_det: undefined,
 });
+const form = ref<FormInstance>();
+const loading = ref(false);
 
-const open = () => {
+// 表单数据
+let originFormData: any = null;
+const emit = defineEmits(["ok"]);
+// 节点数据
+const nodeData = ref<any>(null);
+const open = (node: any) => {
   visible.value = true;
-};
+  nodeData.value = node;
+  nodeData.value = node.data;
+  try {
+    loading.value = true;
+    // 测试接口
+    // fetch(`https://sl-yf-bommgr-admin-dev.shalu.com/api/module/Invoke`, {
+    //   method: "POST",
+    //   headers: {
+    //     Authorization: "bpm_client_1410638690438352896",
+    //     "content-type": "application/json;charset=UTF-8",
+    //   },
+    //   body: JSON.stringify({
+    //     interfaceCode: "Common.getBOMInfo",
+    //     bom_id: node.id,
+    //   }),
+    // })
+    //   .then((res) => res.json())
+    //   .then((res) => {
+    //     console.log("res", res);
+    //     originFormData = res.result;
+    //     formData.value = {
+    //       ...node.data,
+    //       ...(res?.result || {}),
+    //       bom_det: {},
+    //     };
+    //     loading.value = false;
+    //   });
 
+    window.parent?.BpmTools?.program(
+      {
+        interfaceCode: "Common.getBOMInfo",
+        bom_id: node.id,
+      },
+      (res: any) => {
+        originFormData = res;
+        formData.value = {
+          ...node.data,
+          ...(res || {}),
+          ...(node.data?.bom_det || {}),
+          bom_det: {},
+        };
+        loading.value = false;
+      }
+    );
+  } finally {
+    loading.value = false;
+  }
+};
 const close = () => {
   visible.value = false;
+  originFormData = null;
+  nodeData.value = null;
+  form.value?.resetFields();
+};
+
+// 提交表单
+const handleSubmit = async () => {
+  const valid = await form.value?.validate();
+  if (!valid) return;
+  // 获取变更之前的数据
+  if (!originFormData) return;
+  console.log(originFormData, formData.value);
+
+  let str = "";
+  Object.entries(formData.value).forEach(([key, value]) => {
+    if (
+      [
+        "is_change",
+        "change_content",
+        "changecontent",
+        "bom_det",
+        "isActive",
+        "is_disable",
+        "expand",
+        "uid",
+        "name",
+        "type",
+      ].includes(key)
+    ) {
+      return;
+    }
+    // 值发生变化
+    if (value !== originFormData[key]) {
+      if (key in keyMap)
+        str += `${str ? "," : ""}${keyMap[key as keyof typeof keyMap]}: ${
+          originFormData[key] ?? "空"
+        }->${value ?? ""}`;
+      // 更新内部数据
+      if (Object.hasOwnProperty.call(nodeData.value || {}, key)) {
+        nodeData.value[key] = value;
+      }
+      if (formData.value?.bom_det) {
+        formData.value.bom_det[key] = value;
+      } else {
+        formData.value.bom_det = { [key]: value };
+      }
+    }
+  });
+  formData.value.change_content = str;
+  if (str) {
+    formData.value.is_change = true;
+  }
+
+  emit("ok", {
+    ...(nodeData.value || {}),
+    type: formData.value.part_type,
+    name: formData.value.part_name,
+    qty: formData.value.qty,
+    is_change: formData.value.is_change,
+    change_content: formData.value.change_content,
+    bom_det: formData.value.bom_det,
+  });
+  close();
 };
 
 defineExpose({

+ 124 - 36
src/pages/excel/MindmapModal.vue

@@ -1,14 +1,19 @@
 <template>
-  <div v-if="visible" class="mindmap-modal">
+  <div v-if="visible" class="mindmap-modal" style="user-select: none">
     <div class="mindmap-modal_header">
       <div class="text-xl font-semibold">查看“BOM物料名称”</div>
-      <el-button
-        v-if="!hideClose"
-        link
-        @click="close"
-        :icon="Close"
-        style="font-size: 1.2em"
-      ></el-button>
+      <div>
+        <el-button link @click="handleSave" class="mr-8px"
+          ><img class="w-1em mr-4px" :src="saveImg" />保存</el-button
+        >
+        <el-button
+          v-if="!hideClose"
+          link
+          @click="close"
+          :icon="Close"
+          style="font-size: 1.2em"
+        ></el-button>
+      </div>
     </div>
     <div class="mindmap-modal_tool">
       <el-form ref="formRef" :model="formData" class="flex items-center" inline>
@@ -19,7 +24,12 @@
             style="width: 120px"
             @change="changeLevel"
           >
-            <el-option v-for="item in 10" :key="item" :label="`${item}级`" :value="item" />
+            <el-option
+              v-for="item in 10"
+              :key="item"
+              :label="`${item}级`"
+              :value="item"
+            />
           </el-select>
         </el-form-item>
         <el-form-item label="布局" prop="layout" class="mr-12px">
@@ -29,7 +39,8 @@
             style="width: 120px"
             @change="changeLayout"
           >
-            <el-option label="水平" value="logicalStructure" />
+            <el-option label="逻辑右" value="logicalStructure" />
+            <el-option label="逻辑左" value="logicalStructureLeft" />
             <el-option label="垂直" value="organizationStructure" />
           </el-select>
         </el-form-item>
@@ -45,14 +56,14 @@
         </el-form-item>
       </el-form>
       <div class="flex gap-4px">
-        <el-tooltip content="添加">
+        <!-- <el-tooltip content="添加">
           <el-button
             type="default"
             circle
             :icon="CirclePlusFilled"
             @click="addNode"
           />
-        </el-tooltip>
+        </el-tooltip> -->
         <el-tooltip content="详情">
           <el-button
             type="default"
@@ -89,18 +100,22 @@
     </div>
 
     <div class="mindmap-modal_body">
-      <Mindmap v-model:data="editBomStore.mindmapData" ref="mindmapRef" />
+      <Mindmap
+        v-model:data="data"
+        ref="mindmapRef"
+        @node-dbl-click="handleNodeDbClick"
+      />
     </div>
   </div>
 
-  <ConfigDrawer ref="configDrawerRef" />
+  <ConfigDrawer ref="configDrawerRef" @ok="handleConfigOk"/>
 </template>
 
 <script setup lang="ts">
-import { ref, defineExpose, reactive, defineProps } from "vue";
+import { ref, defineExpose, reactive, defineProps, watch } from "vue";
 import {
   Aim,
-  CirclePlusFilled,
+  // CirclePlusFilled,
   Close,
   InfoFilled,
   Search,
@@ -111,22 +126,33 @@ import type { MindMapInstance } from "@/components/mindmap/Mindmap.vue";
 import type { FormInstance } from "element-plus";
 import { ElMessage } from "element-plus";
 import { bfsWalk } from "simple-mind-map/src/utils";
-import { useEditBomStore } from "@/store/editbom";
+// import { useEditBomStore } from "@/store/editbom";
 
 import Mindmap from "@/components/mindmap/Mindmap.vue";
 import ConfigDrawer from "./ConfigDrawer.vue";
 import AutoIcon from "@/assets/auto.svg";
+import saveImg from "@/assets/save.svg";
 
 const props = defineProps<{
   defaultOpen?: boolean;
   hideClose?: boolean;
+  defaultData?: any;
 }>();
 
 const visible = ref(!!props.defaultOpen);
 const mindmapRef = ref<MindMapInstance>();
 const configDrawerRef = ref();
 const formRef = ref<FormInstance>();
-const editBomStore = useEditBomStore();
+// const editBomStore = useEditBomStore();
+const data = ref(
+  props?.defaultData || {
+    id: "root",
+    data: {
+      name: "根节点",
+    },
+    children: [],
+  }
+);
 const formData = reactive({
   level: undefined,
   layout: "logicalStructure",
@@ -154,8 +180,8 @@ const changeLayout = (value: string) => {
 // 切换层级
 const changeLevel = (level: number) => {
   const mindmap = mindmapRef.value?.getInstance();
-  mindmap?.execCommand('UNEXPAND_TO_LEVEL', level);
-}
+  mindmap?.execCommand("UNEXPAND_TO_LEVEL", level);
+};
 
 const autoFit = () => {
   const mindmap = mindmapRef.value?.getInstance();
@@ -167,10 +193,10 @@ const autoFit = () => {
 const openNodeDetail = () => {
   const activeList = mindmapRef.value?.getActiveNodeList();
   if (!activeList?.length) {
-    ElMessage.warning("请选择节点");
+    ElMessage.warning("请选择查看节点");
     return;
   }
-  configDrawerRef.value.open();
+  configDrawerRef.value.open(activeList[0].nodeData);
 };
 
 // 缩放
@@ -186,32 +212,93 @@ const handleZoom = (num: number) => {
 };
 
 // 添加节点
-const addNode = () => {
-  const mindmap = mindmapRef.value?.getInstance();
-  const activeList = mindmapRef.value?.getActiveNodeList();
-  if (!activeList?.length) {
-    ElMessage.warning("请选择父节点");
-    return;
-  }
+// const addNode = () => {
+//   const mindmap = mindmapRef.value?.getInstance();
+//   const activeList = mindmapRef.value?.getActiveNodeList();
+//   if (!activeList?.length) {
+//     ElMessage.warning("请选择父节点");
+//     return;
+//   }
 
-  mindmap?.execCommand("INSERT_CHILD_NODE");
-};
+//   mindmap?.execCommand("INSERT_CHILD_NODE");
+// };
 
 // 搜索
 const handleSearch = () => {
-  const result = [];
+  const result: any[] = [];
   const mindmap = mindmapRef.value?.getInstance();
   const data = mindmap?.getData(false);
-  console.log(data);
-  // TODO: 处理搜索结果
+  mindmap?.execCommand("CLEAR_ACTIVE_NODE");
   bfsWalk(data, (node: any) => {
-    console.log('遍历节点:', node);
-    if(node.data?.a?.includes(formData.search)) {
+    if (node.data?.name?.includes(formData.search)) {
       result.push(node);
     }
   });
+
+  if (result.length) {
+    result.forEach((node) => {
+      const n = mindmap?.renderer.findNodeByUid(node.data?.uid);
+      n?.active();
+    });
+  } else {
+    ElMessage.warning("未找到节点");
+  }
+};
+
+const handleNodeDbClick = (node: any) => {
+  console.log("handleNodeDbClick", node);
+  if (node.isRoot) return;
+  configDrawerRef.value.open(node.nodeData);
+};
+
+// 节点配置结束
+const handleConfigOk = (updateNodeData: any) => {
+  const mindmap = mindmapRef.value?.getInstance();
+  const allData = mindmap?.getData(false);
+  bfsWalk(allData, (node: any) => {
+    if (node.data.uid === updateNodeData.uid) {
+      node.data = updateNodeData;
+    }
+  });
+  mindmap?.updateData(allData);
+}
+// 保存
+const handleSave = () => {
+  const mindmap = mindmapRef.value?.getInstance();
+  const data = mindmap?.getData(false);
+  
+  bfsWalk(data, (node: any) => {
+    delete node.smmVersion;
+    delete node.data.uid;
+    delete node.data.expand;
+    delete node.data.isActive;
+  });
+
+  console.log("保存数据:", data);
+  try {
+    window.parent?.BpmTools?.program(
+      {
+        interfaceCode: "Common.doSaveBOMAiImageData",
+        model: data,
+      },
+      (res: any) => {
+        console.log("保存结果:", res);
+        ElMessage.success("保存成功!");
+      }
+    );
+  } catch (error) {
+    console.log(error);
+    ElMessage.error("保存失败!");
+  }
 };
 
+watch(
+  () => props?.defaultData,
+  (val) => {
+    data.value = val;
+  }
+);
+
 // 打开弹窗
 const open = () => {
   visible.value = true;
@@ -264,6 +351,7 @@ defineExpose({
     flex: 1;
     width: 100%;
     background: #fafafa;
+    overflow: hidden;
   }
 }
 

+ 80 - 13
src/pages/excel/index.vue

@@ -1,24 +1,35 @@
 <template>
   <div class="page-container">
-    <div class="header-bar">
+    <div class="custom-menu-container">
       <div>
-        <el-button link @click="openMindmap" class="mr-8px"><img class="w-1em mr-4px" :src="saveImg"/>保存</el-button>
+        <el-button link @click="handleSave" class="mr-8px"
+          ><img class="w-1em mr-4px" :src="saveImg" />保存</el-button
+        >
+        <el-button link @click="handleSaveZip" class="mr-8px"
+          ><img class="w-1em mr-4px" :src="saveImg" />压缩保存</el-button
+        >
         <!-- <el-button link @click="openMindmap"><img class="w-1em mr-4px" :src="mindmapImg"/>思维导图模式</el-button> -->
       </div>
       <div>
         <!-- <el-button link @click="openMindmap" class="mr-8px"><img class="w-1em mr-4px" :src="saveImg"/>项目比对</el-button> -->
-        <el-select placeholder="历史版本" class="w-120px" size="small"> 
+        <el-select placeholder="历史版本" style="width: 120px" size="small">
           <el-option label="2025.7.16 12:12" value="1"></el-option>
         </el-select>
       </div>
     </div>
-    <Sheet ref="sheetRef" :created="onSheetCreated" show-export-menu show-import-menu />
+    <Sheet
+      ref="sheetRef"
+      :created="onSheetCreated"
+      :workbook="workbook"
+      show-export-menu
+      show-import-menu
+    />
   </div>
-  <MindmapModal ref="mindmapModalRef"/>
+  <MindmapModal ref="mindmapModalRef" />
 </template>
 
 <script setup lang="ts">
-import { ref } from "vue";
+import { onMounted, ref } from "vue";
 import type { UniverExpose } from "@/components/Sheet.vue";
 import Sheet from "@/components/Sheet.vue";
 import MindmapModal from "./MindmapModal.vue";
@@ -26,19 +37,73 @@ import MindmapModal from "./MindmapModal.vue";
 import saveImg from "@/assets/save.svg";
 import { useEditBomStore } from "@/store/editbom";
 import type { FUniver, Univer } from "@univerjs/presets";
+import { useRoute } from "vue-router";
+import { ElMessage } from "element-plus";
+import { unzipData, zipData } from "@/utils";
 
 const sheetRef = ref<UniverExpose | null>(null);
-const mindmapModalRef = ref<any>(null);
+const mindmapModalRef = ref<any>();
 const editBomStore = useEditBomStore();
+const route = useRoute();
+const workbook = ref<any>();
 
 const onSheetCreated = (univer: Univer, univerApi: FUniver) => {
   editBomStore.univer = univer;
   editBomStore.univerApi = univerApi;
-}
+};
 
-const openMindmap = () => {
-  mindmapModalRef.value.open();
+// const openMindmap = () => {
+//   mindmapModalRef.value.open();
+// };
+
+// 正常保存
+const handleSave = () => {
+  const workbook = sheetRef.value?.getUniverSnapshot();
+  console.log(workbook, window.parent);
+  try {
+    window.parent?.BpmTools?.program(
+      {
+        interfaceCode: "Common.doSaveOnlineData",
+        model: {
+          ...workbook,
+          bom_id: route.query?.id,
+        },
+      },
+      (res: any) => {
+        console.log("保存结果:", res);
+        ElMessage.success("保存成功!");
+      }
+    );
+  } catch (error) {
+    console.log(error);
+    ElMessage.error("保存失败!");
+  }
 };
+
+// 压缩保存
+const handleSaveZip = async () => { 
+  const workbook = sheetRef.value?.getUniverSnapshot();
+  console.log(JSON.stringify(workbook), window.parent);
+  const data = zipData(workbook);
+  console.log('压缩之后:', data);
+  console.log('解压之后:', unzipData(data));
+};
+
+onMounted(() => {
+  if (!route.query?.id) return;
+
+  window.parent?.BpmTools?.program(
+    {
+      interfaceCode: "Common.getBOM2OnlineData",
+      bom_id: route.query.id,
+    },
+    (res: any) => {
+      if (res?.code) {
+        workbook.value = res?.data;
+      }
+    }
+  );
+});
 </script>
 
 <style lang="less" scoped>
@@ -51,10 +116,12 @@ const openMindmap = () => {
   height: 100%;
 }
 
-.header-bar {
+.custom-menu-container {
+  position: absolute;
+  right: 20px;
+  top: 0;
+  z-index: 99;
   height: 40px;
-  background: #f5f5f5;
-  padding: 0 12px;
   line-height: 40px;
   display: inline-flex;
   align-items: center;

+ 44 - 9
src/pages/mindmap/index.vue

@@ -1,18 +1,53 @@
 <template>
-  <MindmapModal ref="mindmapModalRef" :hide-close="true" />
+  <MindmapModal
+    ref="mindmapModalRef"
+    :hide-close="true"
+    :default-data="mindmapData"
+    defaultOpen
+  />
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref } from "vue"
-import MindmapModal from "../excel/MindmapModal.vue"
+import { onMounted, ref } from "vue";
+import MindmapModal from "../excel/MindmapModal.vue";
+import { useRoute } from "vue-router";
 
-const mindmapModalRef = ref<InstanceType<typeof MindmapModal>>()
+const mindmapModalRef = ref<InstanceType<typeof MindmapModal>>();
+const route = useRoute();
+const mindmapData = ref<any>();
 
 onMounted(() => {
-  mindmapModalRef.value?.open()
-})
-</script>
+  // 测试接口
+  // fetch(`https://sl-yf-bommgr-admin-dev.shalu.com/api/module/Invoke`, {
+  //   method: "POST",
+  //   headers: {
+  //     'Authorization': "bpm_client_1410638690438352896",
+  //     "content-type": "application/json;charset=UTF-8",
+  //   },
+  //   body: JSON.stringify({
+  //     interfaceCode: "Common.getBOMAiImageData",
+  //     bom_id: "b9be149a-ea42-4f0c-985e-77bd9adbc8f3",
+  //   }),
+  // })
+  //   .then((res) => res.json())
+  //   .then((res) => {
+  //     console.log("res", res);
+  //     mindmapData.value = res.result.data;
+  //   });
 
-<style scoped>
+  if (!route.query?.id) return;
+  window.parent?.BpmTools?.program(
+    {
+      interfaceCode: "Common.getBOMAiImageData",
+      bom_id: route.query.id,
+    },
+    (res: any) => {
+      if (res?.code) {
+        mindmapData.value = res?.data;
+      }
+    }
+  );
+});
+</script>
 
-</style>
+<style scoped></style>

+ 2 - 0
src/store/editbom.ts

@@ -9,6 +9,7 @@ import type { Workbook, FUniver, Univer } from "@univerjs/presets";
 import { ElMessage } from "element-plus";
 
 export const useEditBomStore = defineStore("editBom", () => {
+  const BpmTools = ref<any>(null);
   const workbook = ref<Workbook | null>(null);
   const univerApi = ref<FUniver | null>(null);
   const univer = ref<Univer | null>(null);
@@ -76,5 +77,6 @@ export const useEditBomStore = defineStore("editBom", () => {
     mindmapData,
     univer,
     univerApi,
+    BpmTools,
   };
 });

+ 50 - 5
src/utils/index.ts

@@ -1,3 +1,6 @@
+import { gzip, ungzip } from "pako";
+import { encode, decode } from "js-base64";
+
 // import * as XLSX from "xlsx";
 // import type { WorkBook, WorkSheet } from "xlsx";
 // import type {
@@ -42,7 +45,7 @@
 //   return cellData;
 // };
 
-// /** 
+// /**
 //  * xlsx的workbook对象转换成IWorkbookData结构
 //  * @param workbook xlsx的workbook对象
 //  */
@@ -196,7 +199,6 @@
 //   }
 // }
 
-
 // 选择文件
 export const waitUserSelectExcelFile = (params: {
   onSelect?: (result: File) => void;
@@ -204,9 +206,9 @@ export const waitUserSelectExcelFile = (params: {
   onError?: (error: any) => void;
   accept?: string;
 }) => {
-  const { onSelect, onCancel, accept = '.csv' } = params;
-  const input = document.createElement('input');
-  input.type = 'file';
+  const { onSelect, onCancel, accept = ".csv" } = params;
+  const input = document.createElement("input");
+  input.type = "file";
   input.accept = accept;
   input.click();
   input.oncancel = () => {
@@ -217,4 +219,47 @@ export const waitUserSelectExcelFile = (params: {
     if (!file) return;
     onSelect?.(file);
   };
+};
+
+/**
+ * 压缩数据
+ * @param data
+ * @returns
+ */
+export const zipData = (data: any) => {
+  if (!data) return "";
+
+  // 判断数据是否需要转为JSON
+  const dataJson =
+    typeof data !== "string" && typeof data !== "number"
+      ? JSON.stringify(data)
+      : data;
+
+  // 使用Base64.encode处理字符编码,兼容中文
+  const str = encode(dataJson.toString());
+  let binaryString = gzip(str);
+  let arr = Array.from(binaryString);
+  let s = "";
+  arr.forEach((item) => {
+    s += String.fromCharCode(item);
+  });
+  return btoa(s);
+};
+
+/**
+ * 解压数据
+ * @param {string} data 待解压数据
+ * @returns {string} 解压后的数据
+ */
+export const unzipData = (data: string) => {
+  if (!data) return "";
+  
+  const binaryString = atob(data);
+  const len = binaryString.length;
+  const bytes = new Uint8Array(len);
+  for (let i = 0; i < len; i++) {
+    bytes[i] = binaryString.charCodeAt(i);
+  }
+  const result = ungzip(bytes, { to: 'string' });
+  return JSON.parse(decode(result));
 };

+ 2 - 0
src/vite-env.d.ts

@@ -2,8 +2,10 @@
 
 interface Window {
   luckysheet: any;
+  BpmTools: any;
 }
 declare module 'luckyexcel';
 declare module 'simple-mind-map/src/plugins/Drag';
 declare module 'simple-mind-map/src/plugins/Search';
+declare module 'simple-mind-map/src/plugins/Watermark';
 declare module 'simple-mind-map/src/utils';

Plik diff jest za duży
+ 1 - 1
stats.html