zhaojinyu 1 mesiac pred
commit
d8cd56e8c8
82 zmenil súbory, kde vykonal 10658 pridanie a 0 odobranie
  1. 13 0
      .prettierrc.json
  2. 3 0
      README.md
  3. 12 0
      index.html
  4. 112 0
      package.json
  5. 372 0
      src/App.vue
  6. 4 0
      src/components/Loading/index.ts
  7. 75 0
      src/components/Loading/src/Loading.vue
  8. 65 0
      src/components/Loading/src/createLoading.ts
  9. 14 0
      src/components/Loading/src/typing.ts
  10. 50 0
      src/components/Loading/src/useLoading.ts
  11. 69 0
      src/components/Report/Design/index.ts
  12. 1308 0
      src/components/Report/Design/index.vue
  13. 745 0
      src/components/Report/Design/mock/index.ts
  14. 157 0
      src/components/Report/Design/store/index.ts
  15. 33 0
      src/components/Report/Design/univer/commands/operations/sheet-excel-file.operation.ts
  16. 66 0
      src/components/Report/Design/univer/commands/operations/sheet-float-echart.operation.ts
  17. 33 0
      src/components/Report/Design/univer/commands/operations/sheet-float-image.operation.ts
  18. 13 0
      src/components/Report/Design/univer/commands/operations/sheet-preview.operation.ts
  19. 57 0
      src/components/Report/Design/univer/components/Dialog/selectCell.vue
  20. 67 0
      src/components/Report/Design/univer/components/Echart/cell.vue
  21. 111 0
      src/components/Report/Design/univer/components/Echart/float.vue
  22. 67 0
      src/components/Report/Design/univer/components/Echart/floatVM.vue
  23. 107 0
      src/components/Report/Design/univer/components/Image/float.vue
  24. 47 0
      src/components/Report/Design/univer/controllers/menus/sheet-excel-file.menu.ts
  25. 58 0
      src/components/Report/Design/univer/controllers/menus/sheet-float-echart.menu.ts
  26. 12 0
      src/components/Report/Design/univer/controllers/menus/sheet-float-image.menu.ts
  27. 13 0
      src/components/Report/Design/univer/controllers/menus/sheet-preview.menu.ts
  28. 18 0
      src/components/Report/Design/univer/controllers/sheet-dialog.controller.ts
  29. 62 0
      src/components/Report/Design/univer/controllers/sheet-excel-file.controller.ts
  30. 88 0
      src/components/Report/Design/univer/controllers/sheet-float-echart.controller.ts
  31. 50 0
      src/components/Report/Design/univer/controllers/sheet-float-image.controller.ts
  32. 52 0
      src/components/Report/Design/univer/controllers/sheet-preview.controller.ts
  33. 179 0
      src/components/Report/Design/univer/extensions/sheet-main-fill-direction.extension.ts
  34. 184 0
      src/components/Report/Design/univer/extensions/sheet-main-relation-cell.extension.ts
  35. 9 0
      src/components/Report/Design/univer/facade/f-sheet-cell-image.ts
  36. 31 0
      src/components/Report/Design/univer/facade/f-sheet-cell.ts
  37. 17 0
      src/components/Report/Design/univer/facade/f-sheet-float-dom.ts
  38. 13 0
      src/components/Report/Design/univer/facade/f-sheet-float-echart.ts
  39. 21 0
      src/components/Report/Design/univer/facade/f-sheet-float-image.ts
  40. 9 0
      src/components/Report/Design/univer/facade/f-sheet-print.ts
  41. 9 0
      src/components/Report/Design/univer/facade/f-sheet-range.ts
  42. 132 0
      src/components/Report/Design/univer/facade/index.ts
  43. 48 0
      src/components/Report/Design/univer/locales/zh-CN.ts
  44. 28 0
      src/components/Report/Design/univer/plugins/sheet-cell-image.plugin.ts
  45. 28 0
      src/components/Report/Design/univer/plugins/sheet-cell.plugin.ts
  46. 30 0
      src/components/Report/Design/univer/plugins/sheet-dialog.plugin.ts
  47. 31 0
      src/components/Report/Design/univer/plugins/sheet-excel-file.plugin.ts
  48. 28 0
      src/components/Report/Design/univer/plugins/sheet-float-dom.plugin.ts
  49. 31 0
      src/components/Report/Design/univer/plugins/sheet-float-echart.plugin.ts
  50. 35 0
      src/components/Report/Design/univer/plugins/sheet-float-image.plugin.ts
  51. 30 0
      src/components/Report/Design/univer/plugins/sheet-preview.plugin.ts
  52. 29 0
      src/components/Report/Design/univer/plugins/sheet-print.plugin.ts
  53. 28 0
      src/components/Report/Design/univer/plugins/sheet-range.plugin.ts
  54. 206 0
      src/components/Report/Design/univer/services/sheet-cell-image.service.ts
  55. 124 0
      src/components/Report/Design/univer/services/sheet-cell.service.ts
  56. 84 0
      src/components/Report/Design/univer/services/sheet-excel-file.service.ts
  57. 53 0
      src/components/Report/Design/univer/services/sheet-float-dom.service.ts
  58. 153 0
      src/components/Report/Design/univer/services/sheet-float-echart.service.ts
  59. 140 0
      src/components/Report/Design/univer/services/sheet-float-image.service.ts
  60. 144 0
      src/components/Report/Design/univer/services/sheet-image-io-impl.service.ts
  61. 794 0
      src/components/Report/Design/univer/services/sheet-print-ui.service.ts
  62. 791 0
      src/components/Report/Design/univer/services/sheet-print.service.ts
  63. 38 0
      src/components/Report/Design/univer/services/sheet-range.service.ts
  64. 304 0
      src/components/Report/Design/univer/utils/define.ts
  65. 676 0
      src/components/Report/Design/univer/utils/index.ts
  66. 627 0
      src/components/Report/Design/univer/utils/univer.ts
  67. 45 0
      src/components/Report/Design/univer/utils/uuid.ts
  68. 254 0
      src/components/Report/Preview/index.vue
  69. 176 0
      src/components/Report/Print/Form/index.vue
  70. 87 0
      src/components/Report/Print/Render/Page/index.vue
  71. 580 0
      src/components/Report/Print/Render/index.vue
  72. 276 0
      src/components/Report/Print/index.vue
  73. 32 0
      src/components/Report/index.ts
  74. 0 0
      src/components/Report/style/index.scss
  75. 10 0
      src/directives/index.ts
  76. 40 0
      src/directives/loading.ts
  77. 17 0
      src/main.ts
  78. 2 0
      src/vite-env.d.ts
  79. 34 0
      tsconfig.app.json
  80. 4 0
      tsconfig.json
  81. 24 0
      tsconfig.node.json
  82. 40 0
      vite.config.ts

+ 13 - 0
.prettierrc.json

@@ -0,0 +1,13 @@
+{
+  "printWidth": 160,
+  "semi": true,
+  "vueIndentScriptAndStyle": true,
+  "singleQuote": true,
+  "trailingComma": "all",
+  "proseWrap": "never",
+  "htmlWhitespaceSensitivity": "strict",
+  "endOfLine": "auto",
+  "bracketSameLine": true,
+  "jsxBracketSameLine": true,
+  "arrowParens": "avoid"
+}

+ 3 - 0
README.md

@@ -0,0 +1,3 @@
+# @jnpf/univer
+
+JNPF 快速开发平台 @jnpf/univer 组件

+ 12 - 0
index.html

@@ -0,0 +1,12 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>JnpfUniver</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>

+ 112 - 0
package.json

@@ -0,0 +1,112 @@
+{
+  "name": "@jnpf/univer",
+  "version": "1.0.0",
+  "description": "JNPF快速开发平台报表",
+  "keywords": [
+    "vue",
+    "univer",
+    "jnpf-univer",
+    "jnpf",
+    "jnpfsoft",
+    "福建引迈信息技术有限公司"
+  ],
+  "author": {
+    "name": "福建引迈信息技术有限公司",
+    "email": "support@yinmaisoft.com",
+    "url": "https://www.jnpfsoft.com"
+  },
+  "type": "module",
+  "scripts": {
+    "serve": "vite",
+    "build": "vite build"
+  },
+  "files": [
+    "dist"
+  ],
+  "main": "./dist/index.umd.cjs",
+  "module": "./dist/index.js",
+  "exports": {
+    ".": {
+      "types": "./src/components/Report/index.ts",
+      "development": "./src/components/Report/index.ts",
+      "default": "./dist/index.umd.cjs"
+    },
+    "./style": {
+      "development": "./src/components/Report/style/index.scss",
+      "default": "./dist/style.css"
+    }
+  },
+  "publishConfig": {
+    "access": "public"
+  },
+  "dependencies": {
+    "@univerjs-pro/engine-pivot": "0.8.3",
+    "@univerjs-pro/sheets-pivot": "0.8.3",
+    "@univerjs-pro/sheets-pivot-ui": "0.8.3",
+    "@univerjs/core": "0.8.3",
+    "@univerjs/data-validation": "0.8.3",
+    "@univerjs/design": "0.8.3",
+    "@univerjs/docs": "0.8.3",
+    "@univerjs/docs-drawing": "0.8.3",
+    "@univerjs/docs-drawing-ui": "0.8.3",
+    "@univerjs/docs-ui": "0.8.3",
+    "@univerjs/drawing": "0.8.3",
+    "@univerjs/drawing-ui": "0.8.3",
+    "@univerjs/engine-formula": "0.8.3",
+    "@univerjs/engine-render": "0.8.3",
+    "@univerjs/facade": "0.5.5",
+    "@univerjs/find-replace": "0.8.3",
+    "@univerjs/icons": "0.4.4",
+    "@univerjs/protocol": "0.1.46",
+    "@univerjs/sheets": "0.8.3",
+    "@univerjs/sheets-conditional-formatting": "0.8.3",
+    "@univerjs/sheets-conditional-formatting-ui": "0.8.3",
+    "@univerjs/sheets-crosshair-highlight": "0.8.3",
+    "@univerjs/sheets-data-validation": "0.8.3",
+    "@univerjs/sheets-data-validation-ui": "0.8.3",
+    "@univerjs/sheets-drawing": "0.8.3",
+    "@univerjs/sheets-drawing-ui": "0.8.3",
+    "@univerjs/sheets-filter": "0.8.3",
+    "@univerjs/sheets-filter-ui": "0.8.3",
+    "@univerjs/sheets-find-replace": "0.8.3",
+    "@univerjs/sheets-formula": "0.8.3",
+    "@univerjs/sheets-formula-ui": "0.8.3",
+    "@univerjs/sheets-hyper-link": "0.8.3",
+    "@univerjs/sheets-hyper-link-ui": "0.8.3",
+    "@univerjs/sheets-numfmt": "0.8.3",
+    "@univerjs/sheets-numfmt-ui": "0.8.3",
+    "@univerjs/sheets-sort": "0.8.3",
+    "@univerjs/sheets-sort-ui": "0.8.3",
+    "@univerjs/sheets-thread-comment": "0.8.3",
+    "@univerjs/sheets-thread-comment-ui": "0.8.3",
+    "@univerjs/sheets-ui": "0.8.3",
+    "@univerjs/sheets-zen-editor": "0.8.3",
+    "@univerjs/thread-comment": "0.8.3",
+    "@univerjs/thread-comment-ui": "0.8.3",
+    "@univerjs/ui": "0.8.3",
+    "@univerjs/uniscript": "0.8.3",
+    "@univerjs/watermark": "0.8.3",
+    "ant-design-vue": "4.x",
+    "echarts": "^5.6.0",
+    "jsbarcode": "^3.12.1",
+    "pinia": "^2.3.1",
+    "qrcode": "^1.5.4",
+    "rxjs": "^7.8.2",
+    "vue": "^3.5.17"
+  },
+  "devDependencies": {
+    "@types/lodash-es": "^4.17.12",
+    "@types/node": "^22.16.0",
+    "@types/qrcode": "^1.5.5",
+    "@types/react": "^18.3.23",
+    "@types/react-dom": "^18.3.7",
+    "@univerjs/vite-plugin": "0.5.1",
+    "@vitejs/plugin-vue": "^5.2.4",
+    "lodash-es": "^4.17.21",
+    "prettier": "^3.6.2",
+    "sass": "^1.89.2",
+    "typescript": "~5.6.3",
+    "vite": "^5.4.19",
+    "vue-tsc": "^2.2.12"
+  }
+}

+ 372 - 0
src/App.vue

@@ -0,0 +1,372 @@
+<script lang="ts" setup>
+  import { onMounted, reactive, ref, toRefs, unref } from 'vue';
+
+  import JnpfUniverDesign from '@/components/Report/Design/index.vue';
+  import {
+    designMockFloatEchartData,
+    designMockFloatImageData,
+    designMockSnapshotData,
+    mockUpdateFloatEchartOption,
+    mockUpdateFloatImageOption,
+  } from '@/components/Report/Design/mock';
+  import { DefaultWatermarkConfig } from '@/components/Report/Design/univer/utils/define';
+  import JnpfUniverPreview from '@/components/Report/Preview/index.vue';
+  import JnpfUniverPrint from '@/components/Report/Print/index.vue';
+  import { IWorkbookData } from '@univerjs/core';
+  import { Button as AButton } from 'ant-design-vue';
+
+  defineOptions({ name: 'App' });
+
+  const jnpfUniverDesignRef = ref();
+  const jnpfUniverPreviewRef = ref();
+  const jnpfUniverPrintRef = ref();
+
+  const state = reactive<{
+    focusedCell: any;
+
+    focusedFloatEchartId: any;
+    focusedFloatImageId: any;
+    jnpfUniverAPI: any;
+  }>({
+    focusedCell: {
+      startColumn: null,
+      startRow: null,
+    },
+
+    focusedFloatEchartId: null,
+    focusedFloatImageId: null,
+    jnpfUniverAPI: null,
+  });
+  const { focusedCell, focusedFloatEchartId, focusedFloatImageId } = toRefs(state);
+
+  // 创建设计器实例
+  function initCreate() {
+    const { jnpfUniverAPI } = unref(jnpfUniverDesignRef)?.handleCreateDesignUnit(
+      {} || {
+        floatEcharts: {} || designMockFloatEchartData,
+        floatImages: {} || designMockFloatImageData,
+        snapshot: designMockSnapshotData,
+      },
+    );
+
+    state.jnpfUniverAPI = jnpfUniverAPI ?? null;
+  }
+
+  // 缓存悬浮图表的画布id
+  function handleFocusFloatEchart(data: any) {
+    state.focusedFloatEchartId = data.drawingId;
+  }
+
+  // 【模拟】 -  更新悬浮图表的配置
+  function handleUpdateFloatEchart() {
+    unref(jnpfUniverDesignRef)?.updateFloatEchartConfig({
+      drawingId: focusedFloatEchartId.value,
+
+      echartType: 'line',
+      option: mockUpdateFloatEchartOption,
+    });
+  }
+
+  // 缓存悬浮图片的画布id
+  function handleFocusFloatImage(data: any) {
+    state.focusedFloatImageId = data.drawingId;
+  }
+
+  //  【模拟】 -  更新悬浮图片的配置
+  function handleUpdateFloatImage() {
+    unref(jnpfUniverDesignRef)?.updateFloatImageConfig({
+      drawingId: focusedFloatImageId.value,
+      ...mockUpdateFloatImageOption,
+    });
+  }
+
+  // 缓存选中单元格的行列坐标
+  function handleChangeCellFromSelection(cellInfo: any) {
+    const { startColumn, startRow } = cellInfo ?? {};
+
+    state.focusedCell = {
+      startColumn,
+      startRow,
+    };
+  }
+
+  //  【模拟】 -  更新静态文本
+  function setCellToText() {
+    const updateCellsData = [
+      {
+        ...focusedCell.value,
+        cellData: {
+          custom: {
+            type: 'text',
+          },
+          t: 1,
+          v: '静态文本',
+        },
+      },
+    ];
+
+    unref(jnpfUniverDesignRef)?.updateCellsData(updateCellsData);
+  }
+
+  //  【模拟】 -  更新数据集
+  function setCellToDataSource(fillDirection: string) {
+    const updateCellsData = [
+      {
+        ...focusedCell.value,
+        cellData: {
+          custom: {
+            fillDirection,
+            leftParentCellCustomColName: '1',
+            leftParentCellCustomRowName: 'A',
+            leftParentCellType: 'custom',
+            topParentCellCustomColName: '1',
+            topParentCellCustomRowName: 'B',
+            topParentCellType: 'custom',
+            type: 'dataSource',
+          },
+          t: 1,
+          v: 'userSex',
+        },
+      },
+    ];
+
+    unref(jnpfUniverDesignRef)?.updateCellsData(updateCellsData);
+  }
+
+  //  【模拟】 -  单元格图表
+  function setCellToEchart(echartType: string) {
+    const updateCellsData = [
+      {
+        ...focusedCell.value,
+        cellData: {
+          custom: {
+            echartType,
+            type: 'cellEchart',
+          },
+          echartOption: {
+            series: [
+              {
+                data: [120, 200, 150, 80, 70, 110, 130],
+                type: 'bar',
+              },
+            ],
+            xAxis: {
+              data: ['周一', '周二', '周三', '周四', '周五', '周六', '周天'],
+              type: 'category',
+            },
+            yAxis: {
+              type: 'value',
+            },
+          },
+        },
+      },
+    ];
+
+    unref(jnpfUniverDesignRef)?.updateCellsData(updateCellsData);
+  }
+
+  //  【模拟】 -  二维码
+  function setCellToQrCode() {
+    const updateCellsData = [
+      {
+        ...focusedCell.value,
+        cellData: {
+          custom: {
+            type: 'qrCode',
+          },
+          qrCodeOption: {
+            color: {
+              dark: '#000000', // 前景色
+              light: '#f2f3f4', // 背景色
+            },
+            errorCorrectionLevel: 'L',
+            margin: 4,
+            type: 'static', // 这里预留动态
+          },
+          t: 1,
+          v: 'https://www.baidu.com',
+        },
+      },
+    ];
+
+    unref(jnpfUniverDesignRef)?.updateCellsData(updateCellsData);
+  }
+
+  //  【模拟】 -  条形码
+  function setCellToJsbarcode() {
+    const updateCellsData = [
+      {
+        ...focusedCell.value,
+        cellData: {
+          custom: {
+            type: 'jsbarcode',
+          },
+          jsbarcodeOption: {
+            background: '#f2f3f4', // 设置背景色为浅灰色
+            displayValue: true, // 显示数字
+            font: 'Arial', // 设置字体
+            fontSize: 18, // 设置字体大小
+            format: 'CODE128', // 设置条形码类型为 EAN13
+            lineColor: '#000000', // 设置线条为蓝色
+            margin: 15, // 设置条形码四周边距
+            textAlign: 'center', // 设置文本居中
+            textPosition: 'bottom', // 设置文本在条形码底部
+            type: 'static', // 这里预留动态
+            width: 3, // 设置条形码宽度
+          },
+          t: 1,
+          v: '1234567890',
+        },
+      },
+    ];
+
+    unref(jnpfUniverDesignRef)?.updateCellsData(updateCellsData);
+  }
+
+  // 打开页面弹窗去选单元格
+  function openCellByDialogSelect() {
+    unref(jnpfUniverDesignRef)?.getCellFromDialogSelect(focusedCell.value);
+  }
+
+  // 获取弹窗选择的结果
+  function getCellFromDialogSelect(res: any) {
+    console.log(`关联的单元格坐标为:${res}`);
+  }
+
+  // 设置水印
+  function handleSetWatermark() {
+    // 水印配置
+    // x: number;
+    // y: number;
+    // repeat: boolean;
+    // spacingX: number;
+    // spacingY: number;
+    // rotate: number;
+    // opacity: number;
+    // content?: string;
+    // fontSize: number;
+    // color: string;
+    // bold: boolean;
+    // italic: boolean;
+    // direction: 'ltr' | 'rtl' | 'inherit';
+    unref(jnpfUniverDesignRef)?.setWatermark(JSON.parse(JSON.stringify(DefaultWatermarkConfig)));
+  }
+
+  // 清除水印
+  function handleClearWatermark() {
+    unref(jnpfUniverDesignRef)?.clearWatermark();
+  }
+
+  // 执行预览
+  async function handlePreview() {
+    /**
+     * 保存时做的事情
+     */
+    await unref(jnpfUniverDesignRef)?.setCellEditVisible();
+    const designWorkbookData = unref(jnpfUniverDesignRef)?.getDesignWorkbookData() ?? {};
+
+    /**
+     * 预览时要做的事情
+     * 这里要把floatEcharts, cellEcharts, floatImages的真实数据对接成功
+     */
+    // @ts-ignore 下面的常量没用到飘红真令人讨厌
+    const { cellEcharts = {}, customs = [], floatEcharts = {}, floatImages = {}, snapshot } = designWorkbookData;
+    const activeSubUnitId = unref(jnpfUniverDesignRef)?.getActiveWorksheetId(); // 获取到当前激活的工作表,这样预览的时候,可以更直观的看到当前设计器更改的内容
+    console.log(snapshot);
+    unref(jnpfUniverPreviewRef)?.handleCreatePreviewUnit({
+      cellEcharts,
+      defaultActiveSheetId: activeSubUnitId,
+      floatEcharts,
+      floatImages,
+      snapshot,
+    });
+  }
+
+  // 执行打印
+  function handlePrint(data: { activeWorksheetId?: string; snapshot: IWorkbookData }) {
+    unref(jnpfUniverPrintRef)?.handleCreatePrintUnit(data);
+  }
+
+  // 销毁实例
+  function handleDispose() {
+    unref(jnpfUniverDesignRef)?.handleDisposeUnit();
+  }
+
+  onMounted(() => {
+    initCreate();
+  });
+</script>
+
+<template>
+  <div class="jnpf-univer-module">
+    <!--    测试按钮,随时可以删除掉    -->
+    <div class="buttons-wrap">
+      <AButton type="primary" style="background: orangered" @click="handlePreview">设计预览</AButton>
+      <AButton type="primary" @click="setCellToText">设置纯文本</AButton>
+      <AButton type="primary" @click="setCellToDataSource('landscape')">设置数据集</AButton>
+      <AButton type="primary" @click="openCellByDialogSelect">选择数据集的父格</AButton>
+      <AButton type="primary" @click="setCellToEchart('line')">设置单元格图表</AButton>
+      <AButton type="primary" @click="setCellToQrCode">设置单元格二维码</AButton>
+      <AButton type="primary" @click="setCellToJsbarcode">设置单元格条形码</AButton>
+      <AButton type="primary" style="background: blueviolet" @click="handleUpdateFloatEchart">更新悬浮图表配置</AButton>
+      <AButton type="primary" style="background: purple" @click="handleUpdateFloatImage">更新悬浮图片配置</AButton>
+      <AButton type="primary" style="background: palevioletred" @click="handleSetWatermark">添加水印</AButton>
+      <AButton type="primary" style="background: green" @click="handleClearWatermark">清除水印</AButton>
+      <AButton @click="handleDispose">销毁工作簿</AButton>
+    </div>
+
+    <!--    设计器模块-->
+    <JnpfUniverDesign
+      ref="jnpfUniverDesignRef"
+      @change-cell="handleChangeCellFromSelection"
+      @focus-float-echart="handleFocusFloatEchart"
+      @focus-float-image="handleFocusFloatImage"
+      @change-dialog-select-cell="getCellFromDialogSelect"
+      @preview="handlePreview" />
+    <!--    设计预览模块-->
+    <JnpfUniverPreview ref="jnpfUniverPreviewRef" @print="handlePrint" />
+    <!--    打印预览模块-->
+    <JnpfUniverPrint ref="jnpfUniverPrintRef" />
+  </div>
+</template>
+
+<style>
+  * {
+    padding: 0;
+    margin: 0;
+  }
+
+  html {
+    height: 100%;
+  }
+
+  body {
+    height: calc(100%);
+  }
+
+  #app {
+    height: calc(100%);
+    font-family: Avenir, Helvetica, Arial, sans-serif;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+  }
+
+  .jnpf-univer-module {
+    display: flex;
+    flex-direction: column;
+    width: 100%;
+    height: calc(100%);
+
+    .ant-btn {
+      margin-right: 10px;
+    }
+  }
+
+  .buttons-wrap {
+    display: flex;
+    align-items: center;
+    height: 60px;
+    padding: 0 10px;
+    background: #ddd;
+  }
+</style>

+ 4 - 0
src/components/Loading/index.ts

@@ -0,0 +1,4 @@
+export { createLoading } from './src/createLoading';
+export { default as Loading } from './src/Loading.vue';
+
+export { useLoading } from './src/useLoading';

+ 75 - 0
src/components/Loading/src/Loading.vue

@@ -0,0 +1,75 @@
+<script lang="ts">
+  import { defineComponent, PropType } from 'vue';
+
+  import { Spin } from 'ant-design-vue';
+
+  import { SizeEnum } from './typing';
+
+  export default defineComponent({
+    components: { Spin },
+    name: 'Loading',
+    props: {
+      absolute: {
+        default: false,
+        type: Boolean as PropType<boolean>,
+      },
+      background: {
+        type: String as PropType<string>,
+      },
+      loading: {
+        default: false,
+        type: Boolean as PropType<boolean>,
+      },
+      size: {
+        default: SizeEnum.LARGE,
+        type: String as PropType<SizeEnum>,
+        validator: (v: SizeEnum): boolean => {
+          return [SizeEnum.DEFAULT, SizeEnum.LARGE, SizeEnum.SMALL].includes(v);
+        },
+      },
+      theme: {
+        type: String as PropType<'dark' | 'light'>,
+      },
+      tip: {
+        default: '',
+        type: String as PropType<string>,
+      },
+    },
+  });
+</script>
+<template>
+  <section class="full-loading" :class="{ absolute }" :style="[background ? `background-color: ${background}` : '']" v-show="loading">
+    <Spin v-bind="$attrs" :tip="tip" :size="size" :spinning="loading" />
+  </section>
+</template>
+<style lang="scss" scoped>
+  .full-loading {
+    position: fixed;
+    top: 0;
+    left: 0;
+    z-index: 200;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    height: 100%;
+    background-color: #fff;
+
+    &.absolute {
+      position: absolute;
+      top: 0;
+      left: 0;
+      z-index: 300;
+    }
+  }
+
+  .dark {
+    .full-loading:not(.light) {
+      background-color: rgb(0 0 0 / 5%);
+    }
+  }
+
+  .full-loading.dark {
+    background-color: rgb(0 0 0 / 5%);
+  }
+</style>

+ 65 - 0
src/components/Loading/src/createLoading.ts

@@ -0,0 +1,65 @@
+import type { LoadingProps } from './typing';
+
+import { createVNode, defineComponent, h, reactive, render } from 'vue';
+
+import Loading from './Loading.vue';
+
+export function createLoading(props?: Partial<LoadingProps>, target?: HTMLElement, wait = false) {
+  let vm: any = null;
+  const data = reactive({
+    loading: true,
+    tip: '',
+    ...props,
+  });
+
+  const LoadingWrap = defineComponent({
+    render() {
+      return h(Loading, { ...data });
+    },
+  });
+
+  vm = createVNode(LoadingWrap);
+
+  if (wait) {
+    // TODO fix https://github.com/anncwb/vue-vben-admin/issues/438
+    setTimeout(() => {
+      render(vm, document.createElement('div'));
+    }, 0);
+  } else {
+    render(vm, document.createElement('div'));
+  }
+
+  function close() {
+    if (vm?.el && vm.el.parentNode) {
+      vm.el.remove();
+    }
+  }
+
+  function open(target: HTMLElement = document.body) {
+    if (!vm || !vm.el) {
+      return;
+    }
+    target.append(vm.el as HTMLElement);
+  }
+
+  if (target) {
+    open(target);
+  }
+  return {
+    get $el() {
+      return vm?.el as HTMLElement;
+    },
+    close,
+    get loading() {
+      return data.loading;
+    },
+    open,
+    setLoading: (loading: boolean) => {
+      data.loading = loading;
+    },
+    setTip: (tip: string) => {
+      data.tip = tip;
+    },
+    vm,
+  };
+}

+ 14 - 0
src/components/Loading/src/typing.ts

@@ -0,0 +1,14 @@
+export enum SizeEnum {
+  DEFAULT = 'default',
+  LARGE = 'large',
+  SMALL = 'small',
+}
+
+export interface LoadingProps {
+  absolute: boolean;
+  background: string;
+  loading: boolean;
+  size: SizeEnum;
+  theme: 'dark' | 'light';
+  tip: string;
+}

+ 50 - 0
src/components/Loading/src/useLoading.ts

@@ -0,0 +1,50 @@
+import type { Ref } from 'vue';
+
+import type { LoadingProps } from './typing';
+
+import { unref } from 'vue';
+
+import { createLoading } from './createLoading';
+
+export interface UseLoadingOptions {
+  props?: Partial<LoadingProps>;
+  target?: any;
+}
+
+interface Fn {
+  (): void;
+}
+
+export function useLoading(props: Partial<LoadingProps>): [Fn, Fn, (tip: string) => void];
+export function useLoading(opt: Partial<UseLoadingOptions>): [Fn, Fn, (tip: string) => void];
+
+export function useLoading(opt: Partial<LoadingProps> | Partial<UseLoadingOptions>): [Fn, Fn, (tip: string) => void] {
+  let props: Partial<LoadingProps>;
+  let target: HTMLElement | Ref<any> = document.body;
+
+  if (Reflect.has(opt, 'target') || Reflect.has(opt, 'props')) {
+    const options = opt as Partial<UseLoadingOptions>;
+    props = options.props || {};
+    target = options.target || document.body;
+  } else {
+    props = opt as Partial<LoadingProps>;
+  }
+
+  const instance = createLoading(props, undefined, true);
+
+  const open = (): void => {
+    const t = unref(target as Ref<any>);
+    if (!t) return;
+    instance.open(t);
+  };
+
+  const close = (): void => {
+    instance.close();
+  };
+
+  const setTip = (tip: string): void => {
+    instance.setTip(tip);
+  };
+
+  return [open, close, setTip];
+}

+ 69 - 0
src/components/Report/Design/index.ts

@@ -0,0 +1,69 @@
+import { ICellData, IWorkbookData } from '@univerjs/core';
+import * as echarts from 'echarts';
+
+export interface DesignStateProps {
+  [prop: string]: any;
+  activeWorkbook: any;
+
+  activeWorkbookId: any;
+
+  activeWorksheet: any;
+
+  activeWorksheetId: any;
+  beforeCommandExecuteMonitor: any;
+  commandExecuteMonitor: any;
+  configDialogCellTarget: null;
+
+  containerEleId: any;
+
+  dialogCellInstance: any;
+  jnpfUniverAPI: any;
+
+  loading: boolean;
+  openDialogCell: boolean;
+  selectionChangeMonitor: any;
+
+  univer: any;
+  univerCreateMode: 'design' | 'preview' | 'print';
+}
+
+export interface CreateUnitProps {
+  cellEcharts?: Record<string, any>;
+
+  defaultActiveSheetId?: string;
+  floatEcharts?: Record<string, any>;
+  floatImages?: Record<string, any>;
+  loading?: boolean;
+
+  mode?: 'design' | 'preview' | 'print';
+
+  readonly?: boolean;
+  snapshot?: IWorkbookData;
+  uiContextMenu?: boolean;
+
+  uiFooter?: boolean;
+
+  uiHeader?: boolean;
+
+  watermark?: Record<string, any>;
+
+  darkMode?: boolean;
+}
+
+export interface DeliverFloatEchartOptionProps {
+  drawingId: string;
+  echartType: 'bar' | 'line' | 'pie' | 'radar';
+  option: echarts.EChartsOption;
+}
+
+export interface DeliverFloatImageOptionProps {
+  drawingId: string;
+  imageType: 'BASE64' | 'URL';
+  option: any;
+}
+
+export interface DeliverCellDataProps {
+  cellData: ICellData;
+  startColumn: number;
+  startRow: number;
+}

+ 1308 - 0
src/components/Report/Design/index.vue

@@ -0,0 +1,1308 @@
+<!-- eslint-disable no-useless-escape -->
+<!-- eslint-disable no-console -->
+<!-- eslint-disable unicorn/prefer-structured-clone -->
+<script lang="ts" setup>
+  import { reactive, ref, toRefs, unref, watch } from 'vue';
+
+  import { IWorkbookData } from '@univerjs/core';
+  import { DeviceInputEventType, ITextWatermarkConfig } from '@univerjs/engine-render';
+  import { SetSelectionsOperation, SetWorksheetActiveOperation, WorkbookEditablePermission } from '@univerjs/sheets';
+  import JsBarcode from 'jsbarcode';
+  import { debounce } from 'lodash-es';
+  import { storeToRefs } from 'pinia';
+  import QRCode from 'qrcode';
+
+  import JnpfUniverCellEchart from '../Design/univer/components/Echart/cell.vue';
+  import { CreateUnitProps, DeliverCellDataProps, DeliverFloatEchartOptionProps, DeliverFloatImageOptionProps, DesignStateProps } from './index';
+  import { useJnpfUniverStore } from './store';
+  import { JnpfFUniver } from './univer/facade';
+  import {
+    base64ToFile,
+    getParentCellPosWhenClearCell,
+    getParentCellPosWhenDelete,
+    getParentCellPosWhenInsert,
+    getParentCellPosWhenMove,
+    getParentCellPosWhenMoveCell,
+    isEmptyObject,
+    isNullOrUndefined,
+  } from './univer/utils';
+  import {
+    DefaultDialogSelectCellConfig,
+    DefaultFloatEchartOptions,
+    DefaultFloatImageOption,
+    DefaultPrintConfig,
+    DefaultWatermarkConfig,
+    JnpfCommandIds,
+    jnpfPaperPaddingTypeOptions,
+    jnpfPaperTypeOptions,
+    jnpfPrintAreaOptions,
+    jnpfPrintDirectionOptions,
+    jnpfPrintHAlignOptions,
+    jnpfPrintScaleOptions,
+    jnpfPrintVAlignOptions,
+    JnpfUniverDialogSelectCellKey,
+  } from './univer/utils/define';
+  import {
+    analysisCreateUnitData,
+    arrangeDesignWorkbookData,
+    arrangePreviewWorkbookData,
+    createUniverInstance,
+    judgeSheetHasCustomFloatDom,
+    onInsertedFloatEchart,
+    onInsertedFloatImage,
+    updateFloatStoreId,
+  } from './univer/utils/univer';
+  import { buildUUID } from './univer/utils/uuid';
+
+  import '@univerjs/design/lib/index.css';
+  import '@univerjs/ui/lib/index.css';
+  import '@univerjs/docs-ui/lib/index.css';
+  // import '@univerjs-pro/sheets-print/lib/index.css';
+  import '@univerjs/sheets-ui/lib/index.css';
+  import '@univerjs/sheets-formula-ui/lib/index.css';
+  import '@univerjs/sheets-numfmt-ui/lib/index.css';
+  import '@univerjs/sheets-filter-ui/lib/index.css';
+  import '@univerjs/sheets-sort-ui/lib/index.css';
+  import '@univerjs/sheets-data-validation-ui/lib/index.css';
+  import '@univerjs/sheets-conditional-formatting-ui/lib/index.css';
+  import '@univerjs/sheets-hyper-link-ui/lib/index.css';
+  import '@univerjs/drawing-ui/lib/index.css';
+  import '@univerjs/sheets-drawing-ui/lib/index.css';
+  import '@univerjs/thread-comment-ui/lib/index.css';
+  import '@univerjs/find-replace/lib/index.css';
+  import '@univerjs/sheets-crosshair-highlight/lib/index.css';
+  import '@univerjs/sheets-zen-editor/lib/index.css';
+  import '@univerjs/uniscript/lib/index.css';
+
+  defineOptions({ name: 'JnpfUniverDesign' });
+  const emits = defineEmits(['preview', 'focusFloatEchart', 'focusFloatImage', 'changeCell', 'changeDialogSelectCell']);
+
+  const state = reactive<DesignStateProps>({
+    activeSelections: [],
+    activeWorkbook: null,
+
+    activeWorkbookId: null,
+
+    activeWorksheet: null,
+
+    activeWorksheetId: null,
+    beforeCommandExecuteMonitor: null,
+    commandExecuteMonitor: null,
+    configDialogCellTarget: null,
+
+    containerEleId: null,
+    dialogCellInstance: null,
+
+    jnpfUniverAPI: null,
+    loading: false,
+
+    openDialogCell: false,
+    selectionChangeMonitor: null,
+    univer: null,
+
+    univerCreateMode: 'design',
+  });
+  const {
+    activeSelections,
+    activeWorkbook,
+    activeWorkbookId,
+    activeWorksheet,
+    activeWorksheetId,
+    beforeCommandExecuteMonitor,
+    commandExecuteMonitor,
+    configDialogCellTarget,
+    containerEleId,
+    dialogCellInstance,
+    jnpfUniverAPI,
+    loading,
+    openDialogCell,
+    selectionChangeMonitor,
+    univerCreateMode,
+  } = toRefs(state);
+
+  // 创建设计器的动态id
+  const dynamicJnpfUniverStoreId = `jnpfUniver_${buildUUID()}`;
+  state.containerEleId = dynamicJnpfUniverStoreId;
+
+  const jnpfUniverStore = useJnpfUniverStore(dynamicJnpfUniverStoreId);
+  const { dialogSelectCellDataCache, dialogSelectCellStateCache } = storeToRefs(jnpfUniverStore);
+
+  const jnpfUniverCellEchartRef = ref();
+
+  // 创建工作簿
+  function handleCreateDesignUnit(options?: CreateUnitProps) {
+    // 销毁实例
+    handleDisposeUnit();
+
+    const {
+      defaultActiveSheetId,
+      floatEcharts = {},
+      floatImages = {},
+      loading: createLoading = false,
+
+      mode = 'design',
+      readonly: isReadonly = false,
+      snapshot: workbookData,
+      uiContextMenu = true,
+
+      uiFooter = true,
+
+      uiHeader = true,
+
+      watermark = {},
+
+      darkMode = false,
+    } = options ?? {};
+
+    state.univerCreateMode = mode;
+    state.loading = createLoading;
+
+    const containerEle = getDesignContainerEle();
+    if (!containerEle) {
+      console.warn('报表构建失败');
+      return;
+    }
+
+    // 更新浮动元素的storeId
+    const workbookDataCache = workbookData ? updateFloatStoreId(workbookData, dynamicJnpfUniverStoreId) : '{}';
+    // 解析创建univer实例的快照数据
+    const realWorkbookData = workbookData ? analysisCreateUnitData(workbookDataCache, univerCreateMode.value, isReadonly) : {};
+
+    // 构建实例
+    const univerInstance = createUniverInstance({
+      container: containerEle,
+      contextMenu: uiContextMenu,
+      footer: uiFooter,
+      header: isReadonly ? false : uiHeader,
+      workbookData: realWorkbookData,
+      darkMode,
+    });
+
+    state.univer = univerInstance;
+    state.jnpfUniverAPI = JnpfFUniver.newAPI(univerInstance);
+
+    state.activeWorkbook = jnpfUniverAPI.value?.getActiveWorkbook();
+    state.activeWorkbookId = activeWorkbook.value?.getId();
+
+    // 改变默认激活的工作表
+    if (defaultActiveSheetId) {
+      jnpfUniverAPI.value?.executeCommand(SetWorksheetActiveOperation.id, {
+        subUnitId: defaultActiveSheetId,
+        unitId: activeWorkbookId.value,
+      });
+    }
+
+    state.activeWorksheet = activeWorkbook.value?.getActiveSheet();
+    state.activeWorksheetId = activeWorksheet.value?.getSheetId();
+
+    // 同步数据到store
+    jnpfUniverStore?.setJnpfUniverApiCache(jnpfUniverAPI.value);
+    jnpfUniverStore?.setUniverCreateModeCache(univerCreateMode.value);
+    jnpfUniverStore?.setFloatEchartDataCaches(floatEcharts);
+    jnpfUniverStore?.setFloatImageDataCaches(floatImages);
+
+    // 把store的动态id传给底层
+    jnpfUniverAPI.value?.getSheetsFloatDom()?.savePiniaStoreId(dynamicJnpfUniverStoreId);
+    jnpfUniverAPI.value?.getSheetsFloatEchart()?.savePiniaStoreId(dynamicJnpfUniverStoreId);
+    jnpfUniverAPI.value?.getSheetsFloatImage()?.savePiniaStoreId(dynamicJnpfUniverStoreId);
+
+    setWorkbookPermission(isReadonly); // 设置报表权限
+    bindUniverLifeCycle(realWorkbookData, watermark); // 绑定生命周期
+
+    bindSelectionChange(isReadonly); // 绑定选区变更事件
+    bindBeforeCommandExecute(); // 绑定监听命令执行之前
+    bindAfterCommandExecuted(isReadonly); // 绑定监听命令执行之后
+
+    // 表结构发生了变化
+    jnpfUniverAPI.value?.addEvent(jnpfUniverAPI.value?.Event?.SheetSkeletonChanged, (params: any) => {
+      correctCellEchartSizeDebounce(params);
+    });
+
+    // 这个丢出去后,外层所有的地方都可以用univer的api了
+    return {
+      jnpfUniverAPI: jnpfUniverAPI.value,
+    };
+  }
+
+  // 设置unit权限
+  function setWorkbookPermission(isReadonly: boolean) {
+    if (univerCreateMode.value === 'design' && !isReadonly) {
+      return;
+    }
+
+    updateWorkbookPermission(false);
+  }
+
+  // 更新权限控制
+  function updateWorkbookPermission(state: boolean) {
+    const permission = jnpfUniverAPI.value?.getPermission();
+
+    permission?.setWorkbookPermissionPoint(activeWorkbookId.value, WorkbookEditablePermission, state);
+    permission?.setPermissionDialogVisible(state);
+  }
+
+  // 绑定univer的生命周期
+  function bindUniverLifeCycle(workbookData: IWorkbookData, watermarkInfo: any) {
+    if (!jnpfUniverAPI.value) {
+      return;
+    }
+
+    const univerHooks = jnpfUniverAPI.value?.getHooks();
+    const { config: watermarkConfig, show: showWatermark } = watermarkInfo ?? {};
+
+    jnpfUniverAPI.value?.addEvent(jnpfUniverAPI.value?.Event.LifeCycleChanged, (params: any) => {
+      const { stage } = params;
+
+      if (stage === 2) {
+        jnpfUniverAPI.value?.getSheetsFloatImage()?.cacheImages();
+      }
+    });
+
+    // univerHooks?.onStarting(() => {});
+    univerHooks?.onReady(() => {
+      jnpfUniverAPI.value?.deleteWatermark();
+    });
+    univerHooks?.onRendered(() => {
+      // 预览和有水印配置时才显示水印
+      if (showWatermark && univerCreateMode.value === 'preview') {
+        jnpfUniverAPI.value?.addWatermark('text', { ...watermarkConfig });
+      }
+      // 0.5.3版本开始初始的情况下,不会执行onSelectionChange事件
+      jnpfUniverAPI.value?.executeCommand(SetSelectionsOperation.id, {
+        selections: [
+          {
+            range: {
+              endColumn: 0,
+              endRow: 0,
+              rangeType: 0,
+              startColumn: 0,
+              startRow: 0,
+            },
+          },
+        ],
+        subUnitId: activeWorksheetId.value,
+        type: 2,
+        unitId: activeWorkbookId.value,
+      });
+    });
+    univerHooks?.onSteady(() => {
+      /**
+       * 判断当前激活的工作表有没有图表,有图表的话loading层慢慢关一会儿
+       */
+      jnpfUniverAPI.value?.getFormula()?.executeCalculation();
+      const { resources = [] } = workbookData ?? {};
+      const activeWorksheetHasFloatDomState = resources.some((resource: any) => {
+        if (resource?.name !== 'SHEET_DRAWING_PLUGIN') {
+          return false;
+        }
+
+        const parsedResourceData = JSON.parse(resource?.data || '{}');
+        const currentSheetResourceOption = parsedResourceData?.[activeWorksheetId.value];
+
+        return currentSheetResourceOption?.data && judgeSheetHasCustomFloatDom(currentSheetResourceOption.data);
+      });
+      if (activeWorksheetHasFloatDomState) {
+        setTimeout(() => {
+          loading.value = false;
+        }, 500);
+      } else {
+        loading.value = false;
+      }
+    });
+  }
+
+  // 绑定选区变更事件
+  function bindSelectionChange(isReadonly: boolean) {
+    if (univerCreateMode.value !== 'design' || isReadonly) {
+      state.selectionChangeMonitor = null;
+      return;
+    }
+
+    state.selectionChangeMonitor = activeWorkbook.value?.onSelectionChange((params: any) => {
+      if (!params?.length) {
+        return;
+      }
+
+      state.activeSelections = JSON.parse(JSON.stringify(params));
+
+      // 业务逻辑只选取第一选区的第一个单元格
+      const firstCell = params[0] ?? {};
+      const { startColumn, startRow } = firstCell;
+      const firstCellData = jnpfUniverAPI.value?.getSheetsCell()?.getCellData(startColumn, startRow) ?? {};
+
+      if (!isReadonly) {
+        if (openDialogCell.value) {
+          // 选择关联单元格操作
+          jnpfUniverStore?.setDialogSelectCellDataCache({
+            startColumn,
+            startRow,
+          });
+        } else {
+          // 暴露数据给业务层
+          emits('changeCell', {
+            cellData: JSON.parse(JSON.stringify(firstCellData)),
+            startColumn,
+            startRow,
+          });
+        }
+      }
+    });
+  }
+
+  // 绑定监听命令执行之前
+  function bindBeforeCommandExecute() {
+    // 等待废弃
+    // beforeCommandExecuteMonitor.value = jnpfUniverAPI.value?.onBeforeCommandExecute((_command: any) => {});
+  }
+
+  // 绑定监听命令执行之后
+  function bindAfterCommandExecuted(isReadonly: boolean) {
+    state.commandExecuteMonitor = jnpfUniverAPI.value?.onCommandExecuted((command: any) => {
+      const { id: commandId } = command ?? {};
+
+      // 切换工作本
+      if (activeWorkbook.value && commandId === 'sheet.operation.set-worksheet-active') {
+        state.activeWorksheet = activeWorkbook.value?.getActiveSheet();
+        state.activeWorksheetId = activeWorksheet.value?.getSheetId();
+      }
+
+      // 插入悬浮图表
+      if (commandId === JnpfCommandIds.insertedFloatEchart) {
+        onInsertedFloatEchart(jnpfUniverStore, command);
+      }
+
+      // 聚焦某个悬浮图表
+      if (commandId === JnpfCommandIds.focusFloatEchart) {
+        const { drawingId } = command?.params ?? {};
+        if (!drawingId) {
+          return;
+        }
+
+        const res = jnpfUniverStore?.getFloatEchartDataByDrawingId(drawingId);
+        if (!res) {
+          return;
+        }
+
+        const { echartType, option } = res ?? {};
+        emits('focusFloatEchart', { drawingId, echartType, option } as DeliverFloatEchartOptionProps);
+      } else {
+        jnpfUniverAPI.value?.getSheetsFloatEchart()?.clearFocusDrawingId();
+      }
+
+      // 插入悬浮图片
+      if (commandId === JnpfCommandIds.insertedFloatImage) {
+        onInsertedFloatImage(jnpfUniverStore, command);
+      }
+
+      // 聚焦图片
+      if (commandId === JnpfCommandIds.focusFloatImage) {
+        const { drawingId } = command?.params ?? {};
+        if (!drawingId) {
+          return;
+        }
+
+        const res = jnpfUniverStore?.getFloatImageDataByDrawingId(drawingId);
+        if (!res) {
+          return;
+        }
+
+        const { imageType, option } = res ?? {};
+        emits('focusFloatImage', { drawingId, imageType, option } as DeliverFloatImageOptionProps);
+      } else {
+        jnpfUniverAPI.value?.getSheetsFloatImage()?.clearFocusDrawingId();
+      }
+
+      // 设计预览
+      if (commandId === JnpfCommandIds.preview) {
+        emits('preview');
+      }
+
+      if (univerCreateMode.value === 'design' && !isReadonly) {
+        // 操作单元格合并
+        if (commandId === 'sheet.command.add-worksheet-merge') {
+          correctCellEchartSize(command.params?.selections);
+        }
+
+        // 取消单元格合并
+        if (commandId === 'sheet.mutation.remove-worksheet-merge') {
+          correctCellEchartSize(command.params?.ranges);
+        }
+
+        // 新增行
+        if (commandId === 'sheet.mutation.insert-row') {
+          handleInsertRowAndCol(command.params, 'row');
+        }
+
+        // 新增列
+        if (commandId === 'sheet.mutation.insert-col') {
+          handleInsertRowAndCol(command.params, 'col');
+        }
+
+        // 删除行
+        if (commandId === 'sheet.mutation.remove-rows') {
+          handleDeleteRowAndCol(command.params, 'row');
+        }
+
+        // 删除列
+        if (commandId === 'sheet.mutation.remove-col') {
+          handleDeleteRowAndCol(command.params, 'col');
+        }
+
+        // 移动行
+        if (commandId === 'sheet.mutation.move-rows') {
+          handleMoveRowAndCol(command.params, 'row');
+        }
+
+        // 移动列
+        if (commandId === 'sheet.mutation.move-columns') {
+          handleMoveRowAndCol(command.params, 'col');
+        }
+
+        // 移动单元格【包含剪切】
+        if (commandId === 'sheet.mutation.move-range') {
+          handleMoveCell(command.params);
+        }
+
+        // 清除单元格内容&全部
+        if (commandId === 'sheet.command.clear-selection-content' || commandId === 'sheet.command.clear-selection-all') {
+          handleClearCell();
+        }
+      }
+    });
+  }
+
+  // 暴露 - 获取设计的工作簿数据
+  function getDesignWorkbookData() {
+    const snapshot = activeWorkbook.value ? activeWorkbook.value?.save() : null;
+    if (!snapshot) {
+      console.warn('获取快照失败');
+      return;
+    }
+
+    const { floatEchartDataCaches = {}, floatImageDataCaches = {} } = jnpfUniverStore ?? {};
+    return arrangeDesignWorkbookData(snapshot, floatEchartDataCaches, floatImageDataCaches);
+  }
+
+  // 暴露 - 获取预览的工作簿数据
+  function getPreviewWorkbookData() {
+    const designData = getDesignWorkbookData();
+
+    const { floatEchartToUniverImgDataCaches = {}, floatImageDataCaches = {} } = jnpfUniverStore ?? {};
+    const floatImageToUniverImgDataCaches: any = {};
+
+    for (const dataKey in floatImageDataCaches) {
+      floatImageToUniverImgDataCaches[floatImageDataCaches[dataKey].domId] = floatImageDataCaches[dataKey].option.src;
+    }
+
+    return arrangePreviewWorkbookData(designData, floatEchartToUniverImgDataCaches, floatImageToUniverImgDataCaches);
+  }
+
+  // 暴露 - 更新悬浮图表配置
+  function updateFloatEchartConfig(config: DeliverFloatEchartOptionProps) {
+    const { drawingId, echartType, option } = config ?? {};
+
+    // 校验参数
+    if (!drawingId || !jnpfUniverAPI.value) {
+      return;
+    }
+
+    const originalFloatEchartData = jnpfUniverStore?.getFloatEchartDataByDrawingId(drawingId);
+    if (!originalFloatEchartData) {
+      return;
+    }
+
+    const updateFloatEchartData = {
+      ...originalFloatEchartData,
+      echartType,
+      option,
+    };
+
+    const floatEchartDataCaches = jnpfUniverStore?.floatEchartDataCaches ?? {};
+    const updateFloatEchartsData = {
+      ...floatEchartDataCaches,
+      [drawingId]: updateFloatEchartData,
+    };
+
+    jnpfUniverStore?.updateFocusedFloatEchartDataCache({
+      domId: originalFloatEchartData?.domId,
+      drawingId,
+      option,
+    });
+    jnpfUniverStore?.setFloatEchartDataCaches(updateFloatEchartsData);
+  }
+
+  // 暴露 - 更新悬浮图片配置
+  function updateFloatImageConfig(config: DeliverFloatImageOptionProps) {
+    const { drawingId, imageType, option } = config ?? {};
+
+    // 校验参数
+    if (!drawingId || !jnpfUniverAPI.value) {
+      return;
+    }
+
+    const originalFloatImageData = jnpfUniverStore?.getFloatImageDataByDrawingId(drawingId);
+    if (!originalFloatImageData) {
+      return;
+    }
+
+    const updateFloatImageData = {
+      ...originalFloatImageData,
+      imageType,
+      option,
+    };
+
+    const floatImageDataCaches = jnpfUniverStore?.floatImageDataCaches ?? {};
+    const updateFloatImagesData = {
+      ...floatImageDataCaches,
+      [drawingId]: updateFloatImageData,
+    };
+
+    jnpfUniverStore?.updateFocusedFloatImageDataCache({
+      domId: originalFloatImageData?.domId,
+      drawingId,
+      option,
+    });
+    jnpfUniverStore?.setFloatImageDataCaches(updateFloatImagesData);
+  }
+
+  // 暴露 - 更新单元格值【支持多个】
+  function updateCellsData(cellsData: DeliverCellDataProps[]) {
+    if (!cellsData?.length) {
+      return;
+    }
+
+    cellsData?.forEach((config: any) => {
+      const { cellData: configuringCellData = {}, startColumn, startRow } = config ?? {};
+
+      if (isNullOrUndefined(startColumn) || isNullOrUndefined(startRow)) {
+        return;
+      }
+
+      const oldCellData = jnpfUniverAPI.value?.getSheetsCell()?.getCellData(startColumn, startRow) ?? {};
+
+      const { custom = {}, v = '' } = configuringCellData;
+      const { type } = custom;
+
+      // 纯文本
+      if (!type || type === 'text') {
+        const newCellData = {
+          ...oldCellData,
+          custom: {
+            ...oldCellData?.custom,
+            ...custom,
+            type,
+          },
+          v,
+        };
+
+        delete newCellData?.t;
+        delete newCellData?.custom?.type;
+        // delete newCellData?.custom?.fillDirection;
+
+        if (isEmptyObject(newCellData?.custom)) {
+          delete newCellData?.custom;
+        }
+
+        handleSetCellData({ newCellData, startColumn, startRow });
+      }
+
+      // 数据集
+      if (type === 'dataSource') {
+        const newCellData = {
+          ...oldCellData,
+          custom: {
+            ...oldCellData?.custom,
+            ...custom,
+            type,
+          },
+          t: 1,
+          v: isNullOrUndefined(v) || v === '' ? '' : `\${${v}}`,
+        };
+
+        delete newCellData?.p;
+
+        handleSetCellData({ newCellData, startColumn, startRow });
+      }
+
+      // 参数
+      if (type === 'parameter') {
+        const newCellData = {
+          ...oldCellData,
+          custom: {
+            ...oldCellData?.custom,
+            ...custom,
+            type,
+          },
+          t: 1,
+          v: isNullOrUndefined(v) || v === '' ? '' : `\#{${v}}`,
+        };
+        delete newCellData?.p;
+
+        handleSetCellData({ newCellData, startColumn, startRow });
+      }
+
+      // 单元格图表
+      if (type === 'chart') {
+        cellChartToImage(configuringCellData, startColumn, startRow);
+      }
+
+      // 单元格二维码(暂不支持非静态)
+      if (type === 'qrCode') {
+        // 获取单元格的宽高
+        const targetCellSize = jnpfUniverAPI.value?.getSheetsCell()?.getTargetCellSize(startColumn, startRow);
+        if (!targetCellSize) {
+          return;
+        }
+
+        const { cellHeight, cellWidth } = targetCellSize;
+
+        // 强制生成正方形 ---- 取长宽中较小的一个数据作为边长
+        const sideLength = Math.min(cellWidth, cellHeight);
+        if (sideLength <= 0) {
+          return;
+        }
+
+        const { type: codeType, ...codeConfigRest } = custom?.qrCodeOption ?? {};
+
+        if (codeType !== 'static') {
+          return;
+        }
+
+        QRCode?.toDataURL(
+          v,
+          {
+            height: sideLength,
+            width: sideLength,
+            ...codeConfigRest,
+          },
+          (err, base64) => {
+            if (err) {
+              return;
+            }
+
+            base64ToFile(base64, 'image.jpeg', 'image/jpeg')
+              .then((file: File) => {
+                jnpfUniverAPI.value?.getSheetsCellImage()?.insertCellImage(file, startRow, startColumn, {
+                  ...configuringCellData,
+                  custom: {
+                    ...configuringCellData.custom,
+                    height: sideLength,
+                    width: sideLength,
+                  },
+                });
+              })
+              .catch((error: Error) => {
+                console.log(error);
+              });
+          },
+        );
+      }
+
+      // 单元格条形码(暂不支持非静态)
+      if (type === 'jsbarcode') {
+        if (v === '' || isNullOrUndefined(v)) {
+          return;
+        }
+
+        // 获取单元格的宽高
+        const targetCellSize = jnpfUniverAPI.value?.getSheetsCell()?.getTargetCellSize(startColumn, startRow);
+        if (!targetCellSize) {
+          return;
+        }
+
+        const { cellHeight, cellWidth } = targetCellSize;
+        if (cellWidth <= 0 || cellHeight <= 0) {
+          return;
+        }
+
+        const { type: codeType, ...codeConfigRest } = custom?.jsbarcodeOption ?? {};
+
+        if (codeType !== 'static') {
+          return;
+        }
+
+        // 创建一个临时的 canvas 元素
+        const canvas = document.createElement('canvas');
+        // 设置 canvas 的宽度和高度,确保条形码适配这个区域
+        canvas.width = cellWidth;
+        canvas.height = cellHeight;
+
+        // 使用 jsbarcode 生成条形码
+        JsBarcode(canvas, v, {
+          ...codeConfigRest,
+          height: cellHeight,
+        });
+        const base64 = canvas.toDataURL();
+
+        // 销毁临时的 canvas 元素
+        canvas.width = 0;
+        canvas.height = 0;
+        canvas.remove();
+
+        base64ToFile(base64, 'image.jpeg', 'image/jpeg')
+          .then((file: File) => {
+            jnpfUniverAPI.value?.getSheetsCellImage()?.insertCellImage(file, startRow, startColumn, {
+              ...configuringCellData,
+              custom: {
+                ...configuringCellData.custom,
+                height: cellHeight,
+                width: cellWidth,
+              },
+            });
+          })
+          .catch((error: Error) => {
+            console.log(error);
+          });
+      }
+    });
+  }
+
+  // 设置单元格值
+  function handleSetCellData(config: any) {
+    const { newCellData: cellData, startColumn, startRow } = config ?? {};
+    jnpfUniverAPI.value?.getSheetsCell()?.setCellData(startColumn, startRow, cellData); // 设置值
+
+    // 更新视图
+    jnpfUniverAPI.value?.getSheetsCell()?.refreshRangeCellsView(activeWorksheet.value?.getRange(startRow, startColumn));
+  }
+
+  // 获取设计器容器Dom节点
+  function getDesignContainerEle() {
+    if (!containerEleId.value) {
+      return false;
+    }
+
+    return document.getElementById(containerEleId.value) || false;
+  }
+
+  // 暴露 - 打开选择单元格的弹窗窗口
+  function getCellFromDialogSelect(targetCall: any) {
+    const { startColumn, startRow } = targetCall ?? {};
+    if (dialogCellInstance.value || isNullOrUndefined(startRow) || isNullOrUndefined(startColumn)) {
+      return;
+    }
+    updateWorkbookPermission(false); // 开启只读权限
+
+    configDialogCellTarget.value = { ...targetCall }; // 记录配置中的单元格
+    state.openDialogCell = true; // 打开弹窗
+
+    state.dialogCellInstance = activeWorkbook.value?.openDialog({
+      id: `dialogSelectCell${dynamicJnpfUniverStoreId}`,
+      children: {
+        label: JnpfUniverDialogSelectCellKey,
+        value: dynamicJnpfUniverStoreId,
+      },
+      ...DefaultDialogSelectCellConfig,
+    });
+  }
+
+  // 暴露 - 让聚焦中的单元格失焦(不失焦的话,最后编辑的单元格数据会缺失)
+  async function setCellEditVisible(): Promise<void> {
+    if (!jnpfUniverAPI.value) {
+      return;
+    }
+
+    // 命令层
+    jnpfUniverAPI.value?.executeCommand('sheet.operation.set-cell-edit-visible', {
+      _eventType: DeviceInputEventType.PointerUp,
+      visible: false,
+    });
+
+    return new Promise(resolve => {
+      setTimeout(() => {
+        resolve();
+      }, 200);
+    });
+  }
+
+  // 暴露 - 销毁工作簿
+  function handleDisposeUnit() {
+    const unitId = activeWorkbookId?.value; // 备份出来
+
+    if (unitId) {
+      try {
+        loading.value = true;
+
+        selectionChangeMonitor.value && selectionChangeMonitor.value?.dispose();
+        beforeCommandExecuteMonitor.value && beforeCommandExecuteMonitor.value?.dispose();
+        commandExecuteMonitor.value && commandExecuteMonitor.value?.dispose();
+        dialogCellInstance.value && dialogCellInstance.value?.dispose();
+
+        jnpfUniverAPI.value && jnpfUniverAPI.value?.disposeUnit(activeWorkbookId.value);
+
+        resetUniverState();
+        jnpfUniverStore?.resetStore();
+
+        loading.value = false;
+        console.log('工作簿已销毁');
+      } catch (error) {
+        console.warn(error);
+      }
+    }
+  }
+
+  // 暴露 - 获取动态的store
+  function getDynamicStore() {
+    return jnpfUniverStore ?? null;
+  }
+
+  // 暴露 - 获取工作簿id
+  function getActiveWorkbookId() {
+    return activeWorkbookId.value ?? null;
+  }
+
+  // 暴露 - 获取激活的工作本id
+  function getActiveWorksheetId() {
+    return activeWorksheetId.value ?? null;
+  }
+
+  // 暴露 - 获取默认的图表配置
+  function getDefaultFloatEchartOptions() {
+    return JSON.parse(JSON.stringify(DefaultFloatEchartOptions));
+  }
+
+  // 暴露 - 获取默认的图片配置
+  function getDefaultFloatImageOption() {
+    return JSON.parse(JSON.stringify(DefaultFloatImageOption));
+  }
+
+  // 暴露 - 获取默认的打印配置
+  function getPrintConfigs() {
+    return JSON.parse(
+      JSON.stringify({
+        defaultConfigForm: DefaultPrintConfig,
+        paperPaddingTypeOptions: jnpfPaperPaddingTypeOptions,
+        paperTypeOptions: jnpfPaperTypeOptions,
+        printAreaOptions: jnpfPrintAreaOptions,
+        printDirectionOptions: jnpfPrintDirectionOptions,
+        printHAlignOptions: jnpfPrintHAlignOptions,
+        printScaleOptions: jnpfPrintScaleOptions,
+        printVAlignOptions: jnpfPrintVAlignOptions,
+      }),
+    );
+  }
+
+  // 暴露 - 获取工作本的名称和id
+  function getSheetsInfo() {
+    if (!jnpfUniverAPI.value) {
+      return;
+    }
+
+    return jnpfUniverAPI.value?.getActiveWorkbook()?.save()?.sheets;
+  }
+
+  // 暴露 - 获取默认的水印配置
+  function getDefaultWatermarkConfig() {
+    return JSON.parse(JSON.stringify(DefaultWatermarkConfig));
+  }
+
+  // 暴露 - 选中工作簿
+  function setWorksheetActiveOperation(sheetId: string) {
+    if (!sheetId) {
+      return;
+    }
+
+    state.jnpfUniverAPI?.executeCommand(SetWorksheetActiveOperation.id, {
+      subUnitId: sheetId,
+      unitId: activeWorkbookId.value,
+    });
+  }
+
+  // 返回弹窗选中的单元格
+  function callbackDialogSelectCell() {
+    if (!dialogCellInstance.value) {
+      return;
+    }
+
+    dialogCellInstance.value && dialogCellInstance.value?.dispose(); // 销毁弹窗
+    dialogCellInstance.value = null;
+
+    const selectCellCache = JSON.parse(JSON.stringify(dialogSelectCellDataCache.value)); // 临时存起来,不然选区变化的时候会被同步修改
+
+    const { startColumn, startRow } = (configDialogCellTarget.value ?? {}) as any;
+    jnpfUniverAPI.value?.getSheetsRange()?.recoveryRange(startRow, startColumn);
+    configDialogCellTarget.value = null;
+
+    updateWorkbookPermission(true);
+
+    state.openDialogCell = false;
+    jnpfUniverStore?.setDialogSelectCellDataCache(null);
+
+    emits('changeDialogSelectCell', selectCellCache);
+  }
+
+  // 重置实例状态
+  function resetUniverState() {
+    state.univer = null;
+    state.jnpfUniverAPI = null;
+
+    state.containerEleId = null;
+
+    state.univerCreateMode = 'design';
+
+    state.activeWorkbook = null;
+    state.activeWorkbookId = null;
+    state.activeWorksheet = null;
+    state.activeWorksheetId = null;
+
+    state.selectionChangeMonitor = null;
+    state.activeSelections = [];
+
+    state.beforeCommandExecuteMonitor = null;
+    state.commandExecuteMonitor = null;
+
+    state.configDialogCellTarget = null;
+
+    state.openDialogCell = false;
+    state.dialogCellInstance = null;
+
+    state.loading = false;
+  }
+
+  // 处理插入行和列
+  function handleInsertRowAndCol(params: any, axis: 'col' | 'row') {
+    const { range } = params;
+    const { endColumn = 0, endRow = 0, startColumn = 0, startRow = 0 } = range ?? {};
+    const offset = axis === 'row' ? endRow - startRow + 1 : endColumn - startColumn + 1;
+
+    const sheetsCellApi = jnpfUniverAPI.value?.getSheetsCell();
+
+    const allCellData = JSON.parse(JSON.stringify(sheetsCellApi?.getAllCellData())) ?? {};
+    Object.entries(allCellData).forEach(([rowKey, rowData]: [string, any]) => {
+      Object.entries(rowData).forEach(([colKey, colData]: [string, any]) => {
+        if (
+          !colData ||
+          !colData.custom ||
+          isEmptyObject(colData.custom) ||
+          (colData.custom?.leftParentCellType !== 'custom' && colData.custom?.topParentCellType !== 'custom')
+        ) {
+          return;
+        }
+
+        const updateCellData = {
+          ...allCellData[rowKey][colKey],
+          custom: {
+            ...getParentCellPosWhenInsert(range, axis, offset, colData.custom),
+          },
+        };
+        sheetsCellApi?.setCellData(colKey, rowKey, updateCellData);
+      });
+    });
+  }
+
+  // 处理删除行和列
+  function handleDeleteRowAndCol(params: any, axis: 'col' | 'row') {
+    const { range } = params;
+    const { endColumn = 0, endRow = 0, startColumn = 0, startRow = 0 } = range ?? {};
+    const offset = axis === 'row' ? endRow - startRow + 1 : endColumn - startColumn + 1;
+
+    const sheetsCellApi = jnpfUniverAPI.value?.getSheetsCell();
+
+    const allCellData = JSON.parse(JSON.stringify(sheetsCellApi?.getAllCellData())) ?? {};
+    Object.entries(allCellData).forEach(([rowKey, rowData]: [string, any]) => {
+      Object.entries(rowData).forEach(([colKey, colData]: [string, any]) => {
+        if (
+          !colData ||
+          !colData.custom ||
+          isEmptyObject(colData.custom) ||
+          (colData.custom?.leftParentCellType !== 'custom' && colData.custom?.topParentCellType !== 'custom')
+        ) {
+          return;
+        }
+
+        const updateCellData = {
+          ...allCellData[rowKey][colKey],
+          custom: {
+            ...getParentCellPosWhenDelete(range, axis, offset, colData.custom),
+          },
+        };
+        sheetsCellApi?.setCellData(colKey, rowKey, updateCellData); // 设置值
+      });
+    });
+  }
+
+  // 处理移动行和列
+  function handleMoveRowAndCol(params: any, axis: 'col' | 'row') {
+    const { sourceRange, targetRange } = params;
+
+    const isRow = axis === 'row';
+    const startKey = isRow ? 'startRow' : 'startColumn';
+    const endKey = isRow ? 'endRow' : 'endColumn';
+    const offset =
+      targetRange[startKey] > sourceRange[startKey]
+        ? targetRange[startKey] - sourceRange[startKey] - (sourceRange[endKey] - sourceRange[startKey] + 1)
+        : targetRange[startKey] < sourceRange[startKey]
+          ? targetRange[startKey] - sourceRange[startKey]
+          : 0;
+
+    const involved = axis === 'row' ? sourceRange.endRow - sourceRange.startRow + 1 : sourceRange.endColumn - sourceRange.startColumn + 1;
+
+    const sheetsCellApi = jnpfUniverAPI.value?.getSheetsCell();
+
+    const allCellData = JSON.parse(JSON.stringify(sheetsCellApi?.getAllCellData())) ?? {};
+    Object.entries(allCellData).forEach(([rowKey, rowData]: [string, any]) => {
+      Object.entries(rowData).forEach(([colKey, colData]: [string, any]) => {
+        if (
+          !colData ||
+          !colData.custom ||
+          isEmptyObject(colData.custom) ||
+          (colData.custom?.leftParentCellType !== 'custom' && colData.custom?.topParentCellType !== 'custom')
+        ) {
+          return;
+        }
+
+        const updateCellData = {
+          ...allCellData[rowKey][colKey],
+          custom: {
+            ...getParentCellPosWhenMove(sourceRange, targetRange, axis, offset, involved, colData.custom),
+          },
+        };
+        sheetsCellApi?.setCellData(colKey, rowKey, updateCellData); // 设置值
+      });
+    });
+  }
+
+  // 处理移动单元格
+  function handleMoveCell(params: any) {
+    const { fromRange, toRange } = params ?? {};
+
+    const isChangeRow = fromRange.startRow !== toRange.startRow; // 是否改变了纵向
+    const isChangeCol = fromRange.startColumn !== toRange.startColumn; // 是否改变了横向
+
+    const offsetRow = isChangeRow ? toRange.startRow - fromRange.startRow : 0; // 纵向偏移量
+    const offsetCol = isChangeCol ? toRange.startColumn - fromRange.startColumn : 0; // 横向偏移量
+
+    const sheetsCellApi = jnpfUniverAPI.value?.getSheetsCell();
+
+    const allCellData = JSON.parse(JSON.stringify(sheetsCellApi?.getAllCellData())) ?? {};
+    Object.entries(allCellData).forEach(([rowKey, rowData]: [string, any]) => {
+      Object.entries(rowData).forEach(([colKey, colData]: [string, any]) => {
+        if (
+          !colData ||
+          !colData.custom ||
+          isEmptyObject(colData.custom) ||
+          (colData.custom?.leftParentCellType !== 'custom' && colData.custom?.topParentCellType !== 'custom')
+        ) {
+          return;
+        }
+
+        const updateCellData = {
+          ...allCellData[rowKey][colKey],
+          custom: {
+            ...getParentCellPosWhenMoveCell(
+              fromRange,
+              {
+                offsetCol,
+                offsetRow,
+              },
+              colData.custom,
+            ),
+          },
+        };
+        sheetsCellApi?.setCellData(colKey, rowKey, updateCellData);
+      });
+    });
+  }
+
+  // 清除单元格内容和全部
+  function handleClearCell() {
+    const ranges = JSON.parse(JSON.stringify(activeSelections.value));
+    if (!ranges.length) {
+      return;
+    }
+
+    const sheetsCellApi = jnpfUniverAPI.value?.getSheetsCell();
+
+    const allCellData = JSON.parse(JSON.stringify(sheetsCellApi?.getAllCellData())) ?? {};
+    Object.entries(allCellData).forEach(([rowKey, rowData]: [string, any]) => {
+      Object.entries(rowData).forEach(([colKey, colData]: [string, any]) => {
+        if (
+          !colData ||
+          !colData.custom ||
+          isEmptyObject(colData.custom) ||
+          (colData.custom?.leftParentCellType !== 'custom' && colData.custom?.topParentCellType !== 'custom')
+        ) {
+          return;
+        }
+
+        const updateCellData = {
+          ...allCellData[rowKey][colKey],
+          custom: {
+            ...getParentCellPosWhenClearCell(ranges, colData.custom),
+          },
+        };
+        sheetsCellApi?.setCellData(colKey, rowKey, updateCellData); // 设置值
+      });
+    });
+  }
+
+  // 设置水印
+  function setWatermark(config: ITextWatermarkConfig) {
+    if (!jnpfUniverAPI.value) {
+      return;
+    }
+
+    try {
+      jnpfUniverAPI.value?.addWatermark('text', config);
+    } catch (error) {
+      console.error(error);
+    }
+  }
+
+  // 清除水印
+  function clearWatermark() {
+    if (!jnpfUniverAPI.value) {
+      return;
+    }
+
+    try {
+      jnpfUniverAPI.value?.deleteWatermark();
+    } catch (error) {
+      console.error(error);
+    }
+  }
+
+  // 纠正单元格图表尺寸
+  function correctCellEchartSize(ranges: any) {
+    if (!ranges) {
+      return;
+    }
+
+    const rangeArr: number[][] = [];
+    (ranges ?? []).forEach((range: any) => {
+      for (let row = range.startRow; row <= range.endRow; row++) {
+        for (let col = range.startColumn; col <= range.endColumn; col++) {
+          rangeArr.push([row, col]);
+        }
+      }
+    });
+    const uniqueArr = [...new Set(rangeArr.map(item => JSON.stringify(item)))].map(item => JSON.parse(item));
+
+    const sheetsCellApi = jnpfUniverAPI.value?.getSheetsCell();
+    const allCellData = JSON.parse(JSON.stringify(sheetsCellApi?.getAllCellData())) ?? {};
+
+    uniqueArr.forEach(([rowKey, colKey]: [number, number]) => {
+      const cellData = allCellData?.[rowKey]?.[colKey];
+      if (!cellData) {
+        return;
+      }
+
+      const { custom } = cellData ?? {};
+      if (!custom) {
+        return;
+      }
+      const { type } = custom ?? {};
+
+      if (type !== 'chart') {
+        return;
+      }
+
+      cellChartToImage(cellData, colKey, rowKey);
+    });
+  }
+
+  // 单元图表转成图片
+  function cellChartToImage(cellData: any, startColumn: number, startRow: number) {
+    // 获取单元格的宽高
+    const targetCellSize = jnpfUniverAPI.value?.getSheetsCell()?.getTargetCellSize(startColumn, startRow);
+    if (!targetCellSize) {
+      return;
+    }
+
+    const { cellHeight, cellWidth } = targetCellSize;
+    if (cellWidth <= 0 || cellHeight <= 0) {
+      return;
+    }
+
+    const { custom } = cellData ?? {};
+
+    // 得到了echart的图片
+    const echartImg = unref(jnpfUniverCellEchartRef).render(custom, cellWidth, cellHeight);
+
+    // base64转成文件流后去插入图片
+    base64ToFile(echartImg, 'image.jpeg', 'image/jpeg')
+      .then((file: File) => {
+        jnpfUniverAPI.value?.getSheetsCellImage()?.insertCellImage(file, startRow, startColumn, {
+          ...cellData,
+          custom: {
+            ...cellData.custom,
+            height: cellHeight,
+            width: cellWidth,
+          },
+        });
+      })
+      .catch((error: Error) => {
+        console.log(error);
+      });
+  }
+
+  // 处理纠正单元格图表尺寸,带3000ms 防抖
+  const correctCellEchartSizeDebounce = debounce((params: any) => {
+    correctCellEchartSize(params?.payload?.params?.ranges);
+  }, 300);
+
+  watch(
+    () => dialogSelectCellStateCache.value,
+    () => {
+      callbackDialogSelectCell();
+    },
+  );
+
+  defineExpose({
+    clearWatermark,
+    getActiveWorkbookId,
+    getActiveWorksheetId,
+    getCellFromDialogSelect,
+    getDefaultFloatEchartOptions,
+    getDefaultFloatImageOption,
+    getDefaultWatermarkConfig,
+    getDesignWorkbookData,
+    getDynamicStore,
+    getPreviewWorkbookData,
+    getPrintConfigs,
+    getSheetsInfo,
+    handleCreateDesignUnit,
+    handleDisposeUnit,
+    setCellEditVisible,
+    setWatermark,
+    setWorksheetActiveOperation,
+    updateCellsData,
+    updateFloatEchartConfig,
+    updateFloatImageConfig,
+  });
+</script>
+
+<template>
+  <div class="jnpf-univer-design-content" v-loading="loading">
+    <div :id="containerEleId" class="univer-design-container"></div>
+  </div>
+  <div id="cellEchartsContent" class="cell-echarts-content">
+    <JnpfUniverCellEchart ref="jnpfUniverCellEchartRef" :univer-create-mode="univerCreateMode" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+  .jnpf-univer-design-content {
+    position: relative;
+    flex: 1;
+    height: 100%;
+    overflow: hidden auto;
+
+    .univer-design-container {
+      width: 100%;
+      height: calc(100%);
+    }
+  }
+
+  .cell-echarts-content {
+    position: absolute;
+    inset: 0;
+    z-index: -1;
+  }
+</style>
+
+<style lang="scss">
+  .univer-pointer-events-auto.univer-fixed {
+    z-index: 1000 !important;
+  }
+</style>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 745 - 0
src/components/Report/Design/mock/index.ts


+ 157 - 0
src/components/Report/Design/store/index.ts

@@ -0,0 +1,157 @@
+import { ref } from 'vue';
+
+import { defineStore } from 'pinia';
+
+import { getAlphabetFromIndexRule, isNullOrUndefined } from '../univer/utils';
+
+// 初始状态的默认值
+const defaultFocusFloatDomData = {
+  domId: undefined,
+  drawingId: undefined,
+  option: {},
+};
+
+export const useJnpfUniverStore = (id: string) => {
+  return defineStore(id, () => {
+    const jnpfUniverApiCache: any = ref(null); // 整个api实例
+    const univerCreateModeCache: any = ref(null); // univer创建模式
+
+    const focusedFloatEchartDataCache = ref({ ...defaultFocusFloatDomData }); // 已聚焦的悬浮图表数据
+    const floatEchartDataCaches: Record<string, any> = ref({}); // 工作表中所有悬浮图表的数据
+    const floatEchartToUniverImgDataCaches: Record<string, any> = ref({}); // 工作表中所有悬浮图表转成univer图片后的数据
+
+    const focusedFloatImageDataCache = ref({ ...defaultFocusFloatDomData }); // 已聚焦的悬浮图片数据
+    const floatImageDataCaches: Record<string, any> = ref({}); // 工作表中所有悬浮图片的数据
+    const floatImageToUniverImgDataCaches: Record<string, any> = ref({}); // 工作表中所有悬浮图片转成univer图片后的数据
+
+    const dialogSelectCellDataCache: any = ref(null); // 弹窗选择区域的单元格信息
+    const dialogSelectCellStateCache: any = ref(null); // 弹窗选择单元格状态 --- 值为时间戳,用于触发页面的状态更新
+
+    // 重置存储状态为初始值
+    const resetStore = () => {
+      jnpfUniverApiCache.value = null;
+      univerCreateModeCache.value = null;
+
+      focusedFloatEchartDataCache.value = { ...defaultFocusFloatDomData };
+      floatEchartDataCaches.value = {};
+      floatEchartToUniverImgDataCaches.value = {};
+
+      focusedFloatImageDataCache.value = { ...defaultFocusFloatDomData };
+      floatImageDataCaches.value = {};
+      floatImageToUniverImgDataCaches.value = {};
+
+      dialogSelectCellDataCache.value = null;
+      dialogSelectCellStateCache.value = null;
+    };
+
+    // 存储univer创建模式
+    const setUniverCreateModeCache = (type: string) => {
+      univerCreateModeCache.value = type;
+    };
+
+    // 存储jnpfUniverApi
+    const setJnpfUniverApiCache = (value: any) => {
+      jnpfUniverApiCache.value = value ?? null;
+    };
+
+    // 根据drawingId获取悬浮图表信息
+    const getFloatEchartDataByDrawingId = (drawingId: string) => {
+      return floatEchartDataCaches.value?.[drawingId] ?? null;
+    };
+
+    // 更新已聚焦的悬浮图表数据
+    const updateFocusedFloatEchartDataCache = (value: any = {}) => {
+      focusedFloatEchartDataCache.value = value;
+    };
+
+    // 存储工作本中所有悬浮图表的数据
+    const setFloatEchartDataCaches = (value: any = {}) => {
+      floatEchartDataCaches.value = value;
+
+      jnpfUniverApiCache.value?.getSheetsFloatDom()?.saveFloatEchartItems(value);
+    };
+
+    // 存储工作本中所有悬浮图表转成univer图片后的数据
+    const setFloatEchartToUniverImgDataCaches = (value: any = {}) => {
+      floatEchartToUniverImgDataCaches.value = value;
+    };
+
+    // 根据drawingId获取悬浮图片信息
+    const getFloatImageDataByDrawingId = (drawingId: string) => {
+      return floatImageDataCaches.value?.[drawingId] ?? null;
+    };
+
+    // 更新已聚焦的悬浮图片数据
+    const updateFocusedFloatImageDataCache = (deliverConfig: any = {}) => {
+      focusedFloatImageDataCache.value = deliverConfig;
+    };
+
+    // 存储工作本中所有悬浮图片的数据
+    const setFloatImageDataCaches = (value: any = {}) => {
+      floatImageDataCaches.value = value;
+
+      jnpfUniverApiCache.value?.getSheetsFloatDom()?.saveFloatImageItems(value);
+    };
+
+    // 存储工作本中所有悬浮图片转成univer图片后的数据
+    const setFloatImageToUniverImgDataCaches = (value: any) => {
+      floatImageToUniverImgDataCaches.value = value;
+    };
+
+    // 存储弹窗选择单元格的值
+    const setDialogSelectCellDataCache = (value: any) => {
+      if (!value || isNullOrUndefined(value?.startRow) || isNullOrUndefined(value?.startColumn)) {
+        dialogSelectCellDataCache.value = null;
+        return;
+      }
+
+      dialogSelectCellDataCache.value = `${getAlphabetFromIndexRule(value.startColumn)}${value.startRow + 1}`;
+    };
+
+    // 取消弹窗选择单元格结果
+    const cancelDialogSelectCell = () => {
+      setDialogSelectCellDataCache(null);
+
+      dialogSelectCellStateCache.value = Date.now();
+    };
+
+    // 确认弹窗选择单元格结果
+    const confirmDialogSelectCell = () => {
+      dialogSelectCellStateCache.value = Date.now();
+    };
+
+    return {
+      cancelDialogSelectCell,
+
+      confirmDialogSelectCell,
+      dialogSelectCellDataCache,
+      dialogSelectCellStateCache,
+
+      floatEchartDataCaches,
+      floatEchartToUniverImgDataCaches,
+      floatImageDataCaches,
+
+      floatImageToUniverImgDataCaches,
+      focusedFloatEchartDataCache,
+
+      focusedFloatImageDataCache,
+      getFloatEchartDataByDrawingId,
+
+      getFloatImageDataByDrawingId,
+      resetStore,
+      setDialogSelectCellDataCache,
+      setFloatEchartDataCaches,
+
+      setFloatEchartToUniverImgDataCaches,
+      setFloatImageDataCaches,
+      setFloatImageToUniverImgDataCaches,
+      setJnpfUniverApiCache,
+
+      setUniverCreateModeCache,
+      univerCreateModeCache,
+      updateFocusedFloatEchartDataCache,
+
+      updateFocusedFloatImageDataCache,
+    };
+  })();
+};

+ 33 - 0
src/components/Report/Design/univer/commands/operations/sheet-excel-file.operation.ts

@@ -0,0 +1,33 @@
+import type { IAccessor, ICommand } from '@univerjs/core';
+
+import { CommandType } from '@univerjs/core';
+
+import { JnpfSheetsExcelFileService } from '../../services/sheet-excel-file.service';
+import { JnpfCommandIds } from '../../utils/define';
+
+export const JnpfSheetsImportExcelFileOperation: ICommand = {
+  handler: async (_: IAccessor) => {
+    return true;
+  },
+  id: JnpfCommandIds.importExcelFile,
+  type: CommandType.OPERATION,
+};
+
+export const JnpfSheetsDownloadExcelFileOperation: ICommand = {
+  handler: async (_: IAccessor) => {
+    return true;
+  },
+  id: JnpfCommandIds.downloadExcelFile,
+  type: CommandType.OPERATION,
+};
+
+export const JnpfSheetsImportCsvFileOperation: ICommand = {
+  handler: async (accessor: IAccessor) => {
+    const jnpfSheetsExcelFileService = accessor.get(JnpfSheetsExcelFileService);
+    jnpfSheetsExcelFileService?.handleImportCsv();
+
+    return true;
+  },
+  id: JnpfCommandIds.importCsvFile,
+  type: CommandType.OPERATION,
+};

+ 66 - 0
src/components/Report/Design/univer/commands/operations/sheet-float-echart.operation.ts

@@ -0,0 +1,66 @@
+import type { IAccessor, ICommand } from '@univerjs/core';
+
+import { CommandType } from '@univerjs/core';
+
+import { JnpfSheetsFloatEchartService } from '../../services/sheet-float-echart.service';
+import { JnpfCommandIds } from '../../utils/define';
+
+export const JnpfSheetsInsertedFloatEchartOperation: ICommand = {
+  handler: async (_: IAccessor) => {
+    return true;
+  },
+  id: JnpfCommandIds.insertedFloatEchart,
+  type: CommandType.OPERATION,
+};
+
+export const JnpfSheetsInsertFloatBarEchartOperation: ICommand = {
+  handler: async (accessor: IAccessor) => {
+    const jnpfSheetsFloatEchartService = accessor.get(JnpfSheetsFloatEchartService);
+    jnpfSheetsFloatEchartService.insertFloatEchart('JnpfUniverFloatBarEchart');
+
+    return true;
+  },
+  id: JnpfCommandIds.insertFloatBarEchart,
+  type: CommandType.OPERATION,
+};
+
+export const JnpfSheetsInsertFloatLineEchartOperation: ICommand = {
+  handler: async (accessor: IAccessor) => {
+    const jnpfSheetsFloatEchartService = accessor.get(JnpfSheetsFloatEchartService);
+    jnpfSheetsFloatEchartService.insertFloatEchart('JnpfUniverFloatLineEchart');
+
+    return true;
+  },
+  id: JnpfCommandIds.insertFloatLineEchart,
+  type: CommandType.OPERATION,
+};
+
+export const JnpfSheetsInsertFloatPieEchartOperation: ICommand = {
+  handler: async (accessor: IAccessor) => {
+    const jnpfSheetsFloatEchartService = accessor.get(JnpfSheetsFloatEchartService);
+    jnpfSheetsFloatEchartService.insertFloatEchart('JnpfUniverFloatPieEchart');
+
+    return true;
+  },
+  id: JnpfCommandIds.insertFloatPieEchart,
+  type: CommandType.OPERATION,
+};
+
+export const JnpfSheetsInsertFloatRadarEchartOperation: ICommand = {
+  handler: async (accessor: IAccessor) => {
+    const jnpfSheetsFloatEchartService = accessor.get(JnpfSheetsFloatEchartService);
+    jnpfSheetsFloatEchartService.insertFloatEchart('JnpfUniverFloatRadarEchart');
+
+    return true;
+  },
+  id: JnpfCommandIds.insertFloatRadarEchart,
+  type: CommandType.OPERATION,
+};
+
+export const JnpfSheetsFocusEchartOperation: ICommand = {
+  handler: async (_: IAccessor) => {
+    return true;
+  },
+  id: JnpfCommandIds.focusFloatEchart,
+  type: CommandType.OPERATION,
+};

+ 33 - 0
src/components/Report/Design/univer/commands/operations/sheet-float-image.operation.ts

@@ -0,0 +1,33 @@
+import type { IAccessor, ICommand } from '@univerjs/core';
+
+import { CommandType } from '@univerjs/core';
+
+import { JnpfSheetsFloatImageService } from '../../services/sheet-float-image.service';
+import { JnpfCommandIds } from '../../utils/define';
+
+export const JnpfSheetsInsertFloatImageOperation: ICommand = {
+  handler: async (accessor: IAccessor) => {
+    const jnpfSheetsFloatImageService = accessor.get(JnpfSheetsFloatImageService);
+    jnpfSheetsFloatImageService.insertFloatImage();
+
+    return true;
+  },
+  id: JnpfCommandIds.insertFloatImage,
+  type: CommandType.OPERATION,
+};
+
+export const JnpfSheetsInsertedFloatImageOperation: ICommand = {
+  handler: async (_: IAccessor) => {
+    return true;
+  },
+  id: JnpfCommandIds.insertedFloatImage,
+  type: CommandType.OPERATION,
+};
+
+export const JnpfSheetsFocusFloatImageOperation: ICommand = {
+  handler: async (_: IAccessor) => {
+    return true;
+  },
+  id: JnpfCommandIds.focusFloatImage,
+  type: CommandType.OPERATION,
+};

+ 13 - 0
src/components/Report/Design/univer/commands/operations/sheet-preview.operation.ts

@@ -0,0 +1,13 @@
+import type { IAccessor, ICommand } from '@univerjs/core';
+
+import { CommandType } from '@univerjs/core';
+
+import { JnpfCommandIds } from '../../utils/define';
+
+export const JnpfSheetsPreviewOperation: ICommand = {
+  handler: async (_: IAccessor) => {
+    return true;
+  },
+  id: JnpfCommandIds.preview,
+  type: CommandType.OPERATION,
+};

+ 57 - 0
src/components/Report/Design/univer/components/Dialog/selectCell.vue

@@ -0,0 +1,57 @@
+<script lang="ts" setup>
+  import { storeToRefs } from 'pinia';
+
+  import { useJnpfUniverStore } from '../../../store';
+
+  defineOptions({ name: 'JnpfUniverDialogSelectCell' });
+
+  const props = defineProps<PropsType>();
+  interface PropsType {
+    value: string;
+  }
+  const jnpfUniverStore = useJnpfUniverStore(props?.value);
+  const { dialogSelectCellDataCache } = storeToRefs(jnpfUniverStore);
+
+  // 点击取消
+  const handleCancel = () => {
+    jnpfUniverStore?.cancelDialogSelectCell();
+  };
+
+  // 点击确认
+  const handleConfirm = () => {
+    jnpfUniverStore?.confirmDialogSelectCell();
+  };
+</script>
+
+<template>
+  <div class="dialog-select-cell-content">
+    <div>
+      <div class="univer-mb-2 univer-flex univer-items-center univer-gap-4">
+        <span
+          class="univer-relative univer-inline-flex univer-w-full univer-items-center univer-rounded-md univer-w-full univer-border-primary-600"
+          style="width: 100%">
+          <input
+            type="text"
+            placeholder="请选择"
+            readonly
+            class="univer-box-border univer-w-full univer-rounded-md univer-bg-white univer-transition-colors univer-duration-200 placeholder:univer-text-gray-400 focus:univer-border-primary-600 focus:univer-outline-none focus:univer-ring-2 focus:univer-ring-primary-50 dark:!univer-bg-gray-700 dark:!univer-text-white dark:focus:!univer-ring-primary-900 univer-h-8 univer-px-2 univer-text-sm univer-border-gray-200 dark:!univer-border-gray-600 univer-border-solid univer-border"
+            :value="dialogSelectCellDataCache" />
+        </span>
+      </div>
+    </div>
+    <footer class="univer-flex univer-gap-2" style="display: flex; justify-content: flex-end; margin-top: 20px">
+      <button
+        class="univer-box-border univer-inline-flex univer-cursor-pointer univer-select-none univer-items-center univer-justify-center univer-gap-2 univer-whitespace-nowrap univer-rounded-md univer-border univer-border-solid univer-text-sm univer-font-medium univer-transition-colors disabled:univer-pointer-events-none disabled:univer-cursor-not-allowed disabled:univer-opacity-50 [&_svg]:univer-pointer-events-none [&_svg]:univer-size-4 [&_svg]:univer-shrink-0 univer-border-gray-200 univer-bg-white univer-text-gray-700 hover:univer-bg-gray-100 active:univer-bg-gray-200 dark:!univer-border-gray-600 dark:!univer-bg-gray-700 dark:!univer-text-white dark:hover:!univer-bg-gray-600 dark:active:!univer-bg-gray-700 univer-h-8 univer-rounded-lg univer-px-2 univer-text-sm"
+        @click="handleCancel">
+        取消
+      </button>
+      <button
+        class="univer-box-border univer-inline-flex univer-cursor-pointer univer-select-none univer-items-center univer-justify-center univer-gap-2 univer-whitespace-nowrap univer-rounded-md univer-border univer-border-solid univer-text-sm univer-font-medium univer-transition-colors disabled:univer-pointer-events-none disabled:univer-cursor-not-allowed disabled:univer-opacity-50 [&_svg]:univer-pointer-events-none [&_svg]:univer-size-4 [&_svg]:univer-shrink-0 univer-border-primary-600 univer-bg-primary-600 univer-text-white hover:univer-bg-primary-500 active:univer-bg-primary-700 univer-h-8 univer-rounded-lg univer-px-2 univer-text-sm"
+        :disabled="!dialogSelectCellDataCache"
+        style="margin-left: 10px"
+        @click="handleConfirm">
+        确认
+      </button>
+    </footer>
+  </div>
+</template>

+ 67 - 0
src/components/Report/Design/univer/components/Echart/cell.vue

@@ -0,0 +1,67 @@
+<script lang="ts" setup>
+  import * as echarts from 'echarts';
+
+  defineOptions({ name: 'JnpfUniverCellEchart' });
+
+  const props = defineProps<PropsType>();
+  interface PropsType {
+    univerCreateMode: 'design' | 'preview' | 'print';
+  }
+  // 渲染
+  function render(option: echarts.EChartsOption, width: number, height: number) {
+    // 创建一个临时容器
+    const tempContainer = document.createElement('div');
+    tempContainer.style.width = `${width}px`;
+    tempContainer.style.height = `${height}px`;
+    tempContainer.style.position = 'absolute';
+    tempContainer.style.visibility = 'hidden'; // 避免影响页面视觉
+    document.body.append(tempContainer);
+
+    let chartToImageUrl: null | string = null;
+
+    if (props.univerCreateMode !== 'print') {
+      try {
+        // 初始化 ECharts 实例
+        const tempChartInstance = echarts.init(tempContainer);
+        const { chartType, color, drawingId, height, styleType, type, width, ...restOption } = option;
+
+        // 配置图表选项
+        const updateOption = {
+          ...restOption,
+          animation: false, // 禁用动画以提高生成效率
+        };
+
+        // 设置图表选项
+        tempChartInstance.setOption(updateOption);
+
+        // 避免失真
+        tempChartInstance.resize();
+
+        // 获取图表的 Base64 图片 URL
+        chartToImageUrl = tempChartInstance.getDataURL({
+          backgroundColor: '#f9f9f9',
+          pixelRatio: 1,
+          type: 'jpeg',
+        });
+
+        // 销毁图表实例
+        tempChartInstance.dispose();
+      } catch (error) {
+        console.error('图表生成失败:', error);
+      } finally {
+        // 移除临时容器
+        tempContainer.remove();
+      }
+
+      return chartToImageUrl;
+    }
+  }
+
+  defineExpose({
+    render,
+  });
+</script>
+
+<template>
+  <div style="display: none; width: 0; height: 0"></div>
+</template>

+ 111 - 0
src/components/Report/Design/univer/components/Echart/float.vue

@@ -0,0 +1,111 @@
+<script lang="ts" setup>
+  import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
+
+  import * as echarts from 'echarts';
+  import { debounce } from 'lodash-es';
+  import { storeToRefs } from 'pinia';
+
+  import { useJnpfUniverStore } from '../../../store';
+
+  interface Data {
+    id: string;
+    piniaStoreId: string;
+  }
+  interface PropsType {
+    data: Data;
+  }
+
+  defineOptions({ name: 'JnpfUniverFloatEchart' });
+
+  const props = defineProps<PropsType>();
+
+  const floatChartContainerRef = ref<HTMLDivElement | null>(null);
+
+  const jnpfUniverStore = useJnpfUniverStore(props?.data?.piniaStoreId);
+  const { focusedFloatEchartDataCache } = storeToRefs(jnpfUniverStore);
+
+  let floatChartInstance: echarts.ECharts | null = null;
+  let resizeObserver: null | ResizeObserver = null;
+
+  // 处理窗口大小变化的函数,带300ms 防抖
+  const handleResize = debounce(() => {
+    floatChartInstance?.resize();
+  }, 300);
+
+  // 初始化或更新图表
+  function initialize(option: echarts.EChartsOption) {
+    if (!floatChartContainerRef.value) {
+      return;
+    }
+
+    if (!floatChartInstance) {
+      floatChartInstance = echarts?.init(floatChartContainerRef.value);
+    }
+
+    floatChartInstance?.clear();
+
+    // 使用传入的 options 配置图表
+    floatChartInstance?.setOption(option);
+
+    // 设置 ResizeObserver 监听容器大小变化
+    if (!resizeObserver) {
+      resizeObserver = new ResizeObserver(handleResize);
+      resizeObserver?.observe(floatChartContainerRef.value);
+    }
+  }
+
+  onMounted(() => {
+    const { id: domId, piniaStoreId } = props.data ?? {};
+
+    if (!floatChartContainerRef.value || !domId || !piniaStoreId) {
+      return;
+    }
+
+    const floatEchartDataCaches = jnpfUniverStore?.floatEchartDataCaches ?? {};
+
+    const targetOption = Object.values(floatEchartDataCaches as Record<string, { domId: string; option: any }>)?.find(item => item?.domId === domId)?.option;
+    initialize(targetOption);
+  });
+
+  // 销毁
+  onBeforeUnmount(() => {
+    if (resizeObserver && floatChartContainerRef.value) {
+      resizeObserver?.unobserve(floatChartContainerRef.value);
+    }
+
+    resizeObserver?.disconnect();
+    floatChartInstance?.dispose();
+  });
+
+  watch(
+    () => focusedFloatEchartDataCache.value,
+    value => {
+      const { domId, drawingId, option } = value;
+
+      if (!domId && !drawingId) {
+        // store置空的情况下,不响应
+        return;
+      }
+
+      const containerId = floatChartContainerRef.value?.getAttribute('id');
+      if (domId !== containerId) {
+        return;
+      }
+
+      initialize(option);
+    },
+  );
+</script>
+
+<template>
+  <div ref="floatChartContainerRef" :id="data.id" class="jnpf-univer-float-echart-ele"></div>
+</template>
+
+<style lang="scss" scoped>
+  .jnpf-univer-float-echart-ele {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    background: #f9f9f9;
+  }
+</style>

+ 67 - 0
src/components/Report/Design/univer/components/Echart/floatVM.vue

@@ -0,0 +1,67 @@
+<script lang="ts" setup>
+  import * as echarts from 'echarts';
+
+  defineOptions({ name: 'JnpfUniverFloatEchartVM' });
+
+  const props = defineProps<PropsType>();
+  interface PropsType {
+    univerCreateMode: 'design' | 'preview' | 'print';
+  }
+  // 渲染
+  function render(domId: string, option: echarts.EChartsOption, width: number, height: number) {
+    // 创建一个临时容器
+    const tempContainer = document.createElement('div');
+    tempContainer.style.width = `${width}px`;
+    tempContainer.style.height = `${height}px`;
+    tempContainer.style.position = 'absolute';
+    tempContainer.style.visibility = 'hidden'; // 避免影响页面视觉
+    document.body.append(tempContainer);
+
+    let chartToImageUrl: null | string = null;
+
+    if (props.univerCreateMode === 'preview') {
+      try {
+        // 初始化 ECharts 实例
+        const tempChartInstance = echarts.init(tempContainer);
+
+        // 配置图表选项
+        const updateOption = {
+          ...option,
+          animation: false, // 禁用动画以提高生成效率
+        };
+
+        const { color, ...restOption } = updateOption;
+
+        // 设置图表选项
+        tempChartInstance.setOption(restOption);
+
+        // 获取图表的 Base64 图片 URL
+        chartToImageUrl = tempChartInstance.getDataURL({
+          backgroundColor: '#f9f9f9',
+          type: 'jpeg',
+        });
+
+        // 销毁图表实例
+        tempChartInstance.dispose();
+      } catch (error) {
+        console.error('图表生成失败:', error);
+      } finally {
+        // 移除临时容器
+        tempContainer.remove();
+      }
+
+      return {
+        domId,
+        source: chartToImageUrl,
+      };
+    }
+  }
+
+  defineExpose({
+    render,
+  });
+</script>
+
+<template>
+  <div style="display: none; width: 0; height: 0"></div>
+</template>

+ 107 - 0
src/components/Report/Design/univer/components/Image/float.vue

@@ -0,0 +1,107 @@
+<script lang="ts" setup>
+  import { onMounted, ref, watch } from 'vue';
+
+  import { storeToRefs } from 'pinia';
+
+  import { useJnpfUniverStore } from '../../../store';
+
+  interface Data {
+    id: string;
+    piniaStoreId: string;
+  }
+  interface PropsType {
+    data: Data;
+  }
+
+  defineOptions({ name: 'JnpfUniverFloatImage' });
+
+  const props = defineProps<PropsType>();
+
+  const floatImageContainerRef = ref<HTMLDivElement | null>(null);
+
+  const jnpfUniverStore = useJnpfUniverStore(props?.data?.piniaStoreId);
+  const { floatImageToUniverImgDataCaches, focusedFloatImageDataCache, univerCreateModeCache } = storeToRefs(jnpfUniverStore);
+
+  // 初始化或更新图表
+  function initialize(option: any) {
+    if (!floatImageContainerRef.value) {
+      return;
+    }
+
+    const imageEle = floatImageContainerRef.value?.querySelector('#imageEle') as HTMLImageElement | null;
+    if (!imageEle) {
+      return;
+    }
+
+    const { alt = '', src = '' } = option ?? {};
+    imageEle.setAttribute('src', src);
+    imageEle.setAttribute('alt', alt);
+
+    // 非设计状态下,图片直接转成univer图片
+    if (univerCreateModeCache.value !== 'design') {
+      const { id: domId } = props.data ?? {};
+
+      if (domId) {
+        const caches = {
+          ...floatImageToUniverImgDataCaches.value,
+          [domId]: src,
+        };
+
+        jnpfUniverStore?.setFloatImageToUniverImgDataCaches(caches);
+      }
+    }
+  }
+
+  onMounted(() => {
+    const { id: domId, piniaStoreId } = props.data ?? {};
+
+    if (!floatImageContainerRef.value || !domId || !piniaStoreId) {
+      return;
+    }
+
+    const floatImageDataCaches = jnpfUniverStore?.floatImageDataCaches ?? {};
+
+    const targetOption = Object.values(floatImageDataCaches as Record<string, { domId: string; option: any }>)?.find(item => item?.domId === domId)?.option;
+    initialize(targetOption);
+  });
+
+  watch(
+    () => focusedFloatImageDataCache.value,
+    value => {
+      const { domId, drawingId, option } = value;
+
+      if (!domId && !drawingId) {
+        // store置空的情况下,不响应
+        return;
+      }
+
+      const containerId = floatImageContainerRef.value?.getAttribute('id');
+      if (domId !== containerId) {
+        return;
+      }
+
+      initialize(option);
+    },
+  );
+</script>
+
+<template>
+  <div ref="floatImageContainerRef" :id="data.id" class="jnpf-univer-float-image-ele">
+    <img id="imageEle" src="" alt="" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+  .jnpf-univer-float-image-ele {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    background: #fff;
+
+    img {
+      display: block;
+      width: 100%;
+      height: 100%;
+    }
+  }
+</style>

+ 47 - 0
src/components/Report/Design/univer/controllers/menus/sheet-excel-file.menu.ts

@@ -0,0 +1,47 @@
+import { IMenuButtonItem, IMenuSelectorItem, MenuItemType } from '@univerjs/ui';
+
+import {
+  JnpfSheetsDownloadExcelFileOperation,
+  JnpfSheetsImportCsvFileOperation,
+  JnpfSheetsImportExcelFileOperation,
+} from '../../commands/operations/sheet-excel-file.operation';
+import { JnpfCommandIds } from '../../utils/define';
+
+export const JnpfSheetsExcelFileMenuFactory = (): IMenuSelectorItem => {
+  return {
+    icon: 'DirectExportSingle',
+    id: JnpfCommandIds.excelFileOperations,
+    tooltip: 'jnpfSheetExcelFileMenu.tooltip',
+    type: MenuItemType.SUBITEMS,
+  };
+};
+
+export const JnpfSheetsImportExcelFileMenuFactory = (): IMenuButtonItem => {
+  return {
+    icon: 'UploadSingle',
+    id: JnpfSheetsImportExcelFileOperation.id,
+    title: 'jnpfSheetImportExcelFileMenu.title',
+    tooltip: 'jnpfSheetImportExcelFileMenu.tooltip',
+    type: MenuItemType.BUTTON,
+  };
+};
+
+export const JnpfSheetsDownloadExcelFileMenuFactory = (): IMenuButtonItem => {
+  return {
+    icon: 'ExportSingle',
+    id: JnpfSheetsDownloadExcelFileOperation.id,
+    title: 'jnpfSheetDownloadExcelFileMenu.title',
+    tooltip: 'jnpfSheetDownloadExcelFileMenu.tooltip',
+    type: MenuItemType.BUTTON,
+  };
+};
+
+export const JnpfSheetsImportCsvFileMenuFactory = (): IMenuButtonItem => {
+  return {
+    icon: 'UpperFloorSingle',
+    id: JnpfSheetsImportCsvFileOperation.id,
+    title: 'jnpfSheetImportCsvFileMenu.title',
+    tooltip: 'jnpfSheetImportCsvFileMenu.tooltip',
+    type: MenuItemType.BUTTON,
+  };
+};

+ 58 - 0
src/components/Report/Design/univer/controllers/menus/sheet-float-echart.menu.ts

@@ -0,0 +1,58 @@
+import { IMenuButtonItem, IMenuSelectorItem, MenuItemType } from '@univerjs/ui';
+
+import {
+  JnpfSheetsInsertFloatBarEchartOperation,
+  JnpfSheetsInsertFloatLineEchartOperation,
+  JnpfSheetsInsertFloatPieEchartOperation,
+  JnpfSheetsInsertFloatRadarEchartOperation,
+} from '../../commands/operations/sheet-float-echart.operation';
+import { JnpfCommandIds } from '../../utils/define';
+
+export const JnpfSheetsFloatEchartMenuFactory = (): IMenuSelectorItem => {
+  return {
+    icon: 'SystemSingle',
+    id: JnpfCommandIds.floatEchartOperations,
+    tooltip: 'jnpfSheetFloatEchartMenu.tooltip',
+    type: MenuItemType.SUBITEMS,
+  };
+};
+
+export const JnpfSheetsInsertFloatBarEchartMenuFactory = (): IMenuButtonItem => {
+  return {
+    icon: 'ChartSingle',
+    id: JnpfSheetsInsertFloatBarEchartOperation.id,
+    title: 'jnpfSheetInsertFloatBarEchartMenu.title',
+    tooltip: 'jnpfSheetInsertFloatBarEchartMenu.tooltip',
+    type: MenuItemType.BUTTON,
+  };
+};
+
+export const JnpfSheetsInsertFloatLineEchartMenuFactory = (): IMenuButtonItem => {
+  return {
+    icon: 'LineChartSingle',
+    id: JnpfSheetsInsertFloatLineEchartOperation.id,
+    title: 'jnpfSheetInsertFloatLineEchartMenu.title',
+    tooltip: 'jnpfSheetInsertFloatLineEchartMenu.tooltip',
+    type: MenuItemType.BUTTON,
+  };
+};
+
+export const JnpfSheetsInsertFloatPieEchartMenuFactory = (): IMenuButtonItem => {
+  return {
+    icon: 'PieChartSingle',
+    id: JnpfSheetsInsertFloatPieEchartOperation.id,
+    title: 'jnpfSheetInsertFloatPieEchartMenu.title',
+    tooltip: 'jnpfSheetInsertFloatPieEchartMenu.tooltip',
+    type: MenuItemType.BUTTON,
+  };
+};
+
+export const JnpfSheetsInsertFloatRadarEchartMenuFactory = (): IMenuButtonItem => {
+  return {
+    icon: 'RadarChartSingle',
+    id: JnpfSheetsInsertFloatRadarEchartOperation.id,
+    title: 'jnpfSheetInsertFloatRadarEchartMenu.title',
+    tooltip: 'jnpfSheetInsertFloatRadarEchartMenu.tooltip',
+    type: MenuItemType.BUTTON,
+  };
+};

+ 12 - 0
src/components/Report/Design/univer/controllers/menus/sheet-float-image.menu.ts

@@ -0,0 +1,12 @@
+import { IMenuButtonItem, MenuItemType } from '@univerjs/ui';
+
+import { JnpfSheetsInsertFloatImageOperation } from '../../commands/operations/sheet-float-image.operation';
+
+export const JnpfSheetsInsertFloatImageMenuFactory = (): IMenuButtonItem => {
+  return {
+    id: JnpfSheetsInsertFloatImageOperation.id,
+    title: 'jnpfSheetInsertFloatImageMenu.title',
+    tooltip: 'jnpfSheetInsertFloatImageMenu.tooltip',
+    type: MenuItemType.BUTTON,
+  };
+};

+ 13 - 0
src/components/Report/Design/univer/controllers/menus/sheet-preview.menu.ts

@@ -0,0 +1,13 @@
+import { IMenuButtonItem, MenuItemType } from '@univerjs/ui';
+
+import { JnpfSheetsPreviewOperation } from '../../commands/operations/sheet-preview.operation';
+
+export const JnpfSheetsPreviewMenuFactory = (): IMenuButtonItem => {
+  return {
+    icon: 'ViewModeSingle',
+    id: JnpfSheetsPreviewOperation.id,
+    title: 'jnpfSheetPreviewMenu.title',
+    tooltip: 'jnpfSheetPreviewMenu.tooltip',
+    type: MenuItemType.BUTTON,
+  };
+};

+ 18 - 0
src/components/Report/Design/univer/controllers/sheet-dialog.controller.ts

@@ -0,0 +1,18 @@
+import { Disposable, Inject } from '@univerjs/core';
+import { ComponentManager } from '@univerjs/ui';
+
+import univerDialogSelectCellComponent from '../components/Dialog/selectCell.vue';
+import { JnpfUniverDialogSelectCellKey } from '../utils/define';
+
+export class JnpfSheetsDialogController extends Disposable {
+  constructor(@Inject(ComponentManager) private readonly _componentManager: ComponentManager) {
+    super();
+
+    this._registerComponents();
+  }
+
+  private _registerComponents(): void {
+    // 注册选区组件进来
+    this.disposeWithMe(this._componentManager.register(JnpfUniverDialogSelectCellKey, univerDialogSelectCellComponent, { framework: 'vue3' }));
+  }
+}

+ 62 - 0
src/components/Report/Design/univer/controllers/sheet-excel-file.controller.ts

@@ -0,0 +1,62 @@
+import { Disposable, ICommandService, Inject } from '@univerjs/core';
+import { DirectExportSingle, UploadSingle, UpperFloorSingle } from '@univerjs/icons';
+import { ComponentManager, IMenuManagerService, RibbonStartGroup } from '@univerjs/ui';
+
+import {
+  JnpfSheetsDownloadExcelFileOperation,
+  JnpfSheetsImportCsvFileOperation,
+  JnpfSheetsImportExcelFileOperation,
+} from '../commands/operations/sheet-excel-file.operation';
+import { JnpfSheetsExcelFileMenuFactory, JnpfSheetsImportExcelFileMenuFactory } from '../controllers/menus/sheet-excel-file.menu';
+import { JnpfCommandIds } from '../utils/define';
+
+export class JnpfSheetsExcelFileController extends Disposable {
+  constructor(
+    @ICommandService private readonly _commandService: ICommandService,
+    @IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
+    @Inject(ComponentManager) private readonly _componentManager: ComponentManager,
+  ) {
+    super();
+
+    this._initCommands();
+    this._registerComponents();
+    this._initMenus();
+  }
+
+  private _initCommands(): void {
+    [JnpfSheetsImportExcelFileOperation, JnpfSheetsDownloadExcelFileOperation, JnpfSheetsImportCsvFileOperation].forEach(command => {
+      this.disposeWithMe(this._commandService.registerCommand(command));
+    });
+  }
+
+  private _initMenus(): void {
+    this._menuManagerService.mergeMenu({
+      [RibbonStartGroup.OTHERS]: {
+        [JnpfCommandIds.excelFileOperations]: {
+          [JnpfSheetsImportExcelFileOperation.id]: {
+            menuItemFactory: JnpfSheetsImportExcelFileMenuFactory,
+            order: 0,
+          },
+          menuItemFactory: JnpfSheetsExcelFileMenuFactory,
+          order: 100,
+          // [JnpfSheetsDownloadExcelFileOperation.id]: {
+          //   order: 1,
+          //   menuItemFactory: JnpfSheetsDownloadExcelFileMenuFactory,
+          // },
+          // [JnpfSheetsImportCsvFileOperation.id]: {
+          //   order: 2,
+          //   menuItemFactory: JnpfSheetsImportCsvFileMenuFactory,
+          // },
+        },
+      },
+    });
+  }
+
+  private _registerComponents(): void {
+    // 注册按钮图标
+    this.disposeWithMe(this._componentManager.register('DirectExportSingle', DirectExportSingle));
+    this.disposeWithMe(this._componentManager.register('UploadSingle', UploadSingle));
+    // this.disposeWithMe(this._componentManager.register('ExportSingle', ExportSingle));
+    this.disposeWithMe(this._componentManager.register('UpperFloorSingle', UpperFloorSingle));
+  }
+}

+ 88 - 0
src/components/Report/Design/univer/controllers/sheet-float-echart.controller.ts

@@ -0,0 +1,88 @@
+import { Disposable, ICommandService, Inject } from '@univerjs/core';
+import { ChartSingle, LineChartSingle, PieChartSingle, RadarChartSingle, SystemSingle } from '@univerjs/icons';
+import { ComponentManager, IMenuManagerService, RibbonInsertGroup } from '@univerjs/ui';
+
+import {
+  JnpfSheetsFocusEchartOperation,
+  JnpfSheetsInsertedFloatEchartOperation,
+  JnpfSheetsInsertFloatBarEchartOperation,
+  JnpfSheetsInsertFloatLineEchartOperation,
+  JnpfSheetsInsertFloatPieEchartOperation,
+  JnpfSheetsInsertFloatRadarEchartOperation,
+} from '../commands/operations/sheet-float-echart.operation';
+import univerFloatEchartComponent from '../components/Echart/float.vue';
+import { JnpfCommandIds, JnpfUniverFloatEchartKey } from '../utils/define';
+import {
+  JnpfSheetsFloatEchartMenuFactory,
+  JnpfSheetsInsertFloatBarEchartMenuFactory,
+  JnpfSheetsInsertFloatLineEchartMenuFactory,
+  JnpfSheetsInsertFloatPieEchartMenuFactory,
+  JnpfSheetsInsertFloatRadarEchartMenuFactory,
+} from './menus/sheet-float-echart.menu';
+
+export class JnpfSheetsFloatEchartController extends Disposable {
+  constructor(
+    @ICommandService private readonly _commandService: ICommandService,
+    @IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
+    @Inject(ComponentManager) private readonly _componentManager: ComponentManager,
+  ) {
+    super();
+
+    this._initCommands();
+    this._registerComponents();
+    this._initMenus();
+  }
+
+  private _initCommands(): void {
+    [
+      JnpfSheetsInsertFloatBarEchartOperation,
+      JnpfSheetsInsertFloatLineEchartOperation,
+      JnpfSheetsInsertFloatPieEchartOperation,
+      JnpfSheetsInsertFloatRadarEchartOperation,
+      JnpfSheetsInsertedFloatEchartOperation,
+      JnpfSheetsFocusEchartOperation,
+    ].forEach(command => {
+      this.disposeWithMe(this._commandService.registerCommand(command));
+    });
+  }
+
+  private _initMenus(): void {
+    this._menuManagerService.mergeMenu({
+      // 主菜单
+      [RibbonInsertGroup.MEDIA]: {
+        [JnpfCommandIds.floatEchartOperations]: {
+          [JnpfSheetsInsertFloatBarEchartOperation.id]: {
+            menuItemFactory: JnpfSheetsInsertFloatBarEchartMenuFactory,
+            order: 0,
+          },
+          [JnpfSheetsInsertFloatLineEchartOperation.id]: {
+            menuItemFactory: JnpfSheetsInsertFloatLineEchartMenuFactory,
+            order: 1,
+          },
+          [JnpfSheetsInsertFloatPieEchartOperation.id]: {
+            menuItemFactory: JnpfSheetsInsertFloatPieEchartMenuFactory,
+            order: 2,
+          },
+          [JnpfSheetsInsertFloatRadarEchartOperation.id]: {
+            menuItemFactory: JnpfSheetsInsertFloatRadarEchartMenuFactory,
+            order: 3,
+          },
+          menuItemFactory: JnpfSheetsFloatEchartMenuFactory,
+          order: 1,
+        },
+      },
+    });
+  }
+
+  private _registerComponents(): void {
+    // 注册图表组件进来
+    this.disposeWithMe(this._componentManager.register(JnpfUniverFloatEchartKey, univerFloatEchartComponent, { framework: 'vue3' }));
+
+    // 注册按钮图标
+    this.disposeWithMe(this._componentManager.register('SystemSingle', SystemSingle));
+    this.disposeWithMe(this._componentManager.register('ChartSingle', ChartSingle));
+    this.disposeWithMe(this._componentManager.register('LineChartSingle', LineChartSingle));
+    this.disposeWithMe(this._componentManager.register('PieChartSingle', PieChartSingle));
+    this.disposeWithMe(this._componentManager.register('RadarChartSingle', RadarChartSingle));
+  }
+}

+ 50 - 0
src/components/Report/Design/univer/controllers/sheet-float-image.controller.ts

@@ -0,0 +1,50 @@
+import { Disposable, ICommandService, Inject } from '@univerjs/core';
+import { SHEETS_IMAGE_MENU_ID } from '@univerjs/sheets-drawing-ui';
+import { ComponentManager, IMenuManagerService, RibbonInsertGroup } from '@univerjs/ui';
+
+import {
+  JnpfSheetsFocusFloatImageOperation,
+  JnpfSheetsInsertedFloatImageOperation,
+  JnpfSheetsInsertFloatImageOperation,
+} from '../commands/operations/sheet-float-image.operation';
+import univerFloatImageComponent from '../components/Image/float.vue';
+import { JnpfUniverFloatImageKey } from '../utils/define';
+import { JnpfSheetsInsertFloatImageMenuFactory } from './menus/sheet-float-image.menu';
+
+export class JnpfSheetsFloatImageController extends Disposable {
+  constructor(
+    @ICommandService private readonly _commandService: ICommandService,
+    @IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
+    @Inject(ComponentManager) private readonly _componentManager: ComponentManager,
+  ) {
+    super();
+
+    this._initCommands();
+    this._registerComponents();
+    this._initMenus();
+  }
+
+  private _initCommands(): void {
+    [JnpfSheetsInsertFloatImageOperation, JnpfSheetsInsertedFloatImageOperation, JnpfSheetsFocusFloatImageOperation].forEach(command => {
+      this.disposeWithMe(this._commandService.registerCommand(command));
+    });
+  }
+
+  private _initMenus(): void {
+    this._menuManagerService.mergeMenu({
+      [RibbonInsertGroup.MEDIA]: {
+        [SHEETS_IMAGE_MENU_ID]: {
+          [JnpfSheetsInsertFloatImageOperation.id]: {
+            menuItemFactory: JnpfSheetsInsertFloatImageMenuFactory,
+            order: 10,
+          },
+        },
+      },
+    });
+  }
+
+  private _registerComponents(): void {
+    // 注册图片组件进来
+    this.disposeWithMe(this._componentManager.register(JnpfUniverFloatImageKey, univerFloatImageComponent, { framework: 'vue3' }));
+  }
+}

+ 52 - 0
src/components/Report/Design/univer/controllers/sheet-preview.controller.ts

@@ -0,0 +1,52 @@
+import { Disposable, ICommandService, Inject } from '@univerjs/core';
+import { ViewModeSingle } from '@univerjs/icons';
+import { ComponentManager, IMenuManagerService, RibbonStartGroup } from '@univerjs/ui';
+
+import { JnpfSheetsPreviewOperation } from '../commands/operations/sheet-preview.operation';
+import { JnpfSheetsPreviewMenuFactory } from '../controllers/menus/sheet-preview.menu';
+
+export class JnpfSheetsPreviewController extends Disposable {
+  constructor(
+    @ICommandService private readonly _commandService: ICommandService,
+    @IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
+    @Inject(ComponentManager) private readonly _componentManager: ComponentManager,
+  ) {
+    super();
+
+    this._initCommands();
+    this._registerComponents();
+    this._initMenus();
+  }
+
+  private _initCommands(): void {
+    [JnpfSheetsPreviewOperation].forEach(command => {
+      this.disposeWithMe(this._commandService.registerCommand(command));
+    });
+  }
+
+  private _initMenus(): void {
+    this._menuManagerService.mergeMenu({
+      // 主菜单
+      [RibbonStartGroup.OTHERS]: {
+        [JnpfSheetsPreviewOperation.id]: {
+          menuItemFactory: JnpfSheetsPreviewMenuFactory,
+          order: 99,
+        },
+      },
+      // 右键菜单
+      // [ContextMenuPosition.MAIN_AREA]: {
+      //   [ContextMenuGroup.OTHERS]: {
+      //     [JnpfSheetsPreviewOperation.id]: {
+      //       order: 0,
+      //       menuItemFactory: JnpfSheetsPreviewMenuFactory,
+      //     },
+      //   },
+      // },
+    });
+  }
+
+  private _registerComponents(): void {
+    // 注册按钮图标
+    this.disposeWithMe(this._componentManager.register('ViewModeSingle', ViewModeSingle));
+  }
+}

+ 179 - 0
src/components/Report/Design/univer/extensions/sheet-main-fill-direction.extension.ts

@@ -0,0 +1,179 @@
+import type { IScale } from '@univerjs/core';
+import type { SpreadsheetSkeleton, UniverRenderingContext } from '@univerjs/engine-render';
+
+import { DEFAULT_FONTFACE_PLANE, FIX_ONE_PIXEL_BLUR_OFFSET, getColor, SheetExtension } from '@univerjs/engine-render';
+
+type CellCoordinates = Array<{ col: number; row: number }>; // 单元格坐标数组
+
+interface ClearSheetFillDirectionProps {
+  key: string;
+  sheetId: string;
+}
+
+interface SetSheetFillDirectionProps extends ClearSheetFillDirectionProps {
+  downArrowCells?: CellCoordinates;
+  rightArrowCells?: CellCoordinates;
+}
+
+interface RenderArrowsProps {
+  arrow: string;
+  cells: CellCoordinates;
+  colWidths: number[];
+  ctx: UniverRenderingContext;
+  endCol: number;
+  endRow: number;
+  offsets: { xOffset: number; yOffset: number };
+  rowHeights: number[];
+  startCol: number;
+  startRow: number;
+}
+
+// 定义主要扩展类,用于在表格中绘制自定义内容
+export class SetSheetFillDirectionExtension extends SheetExtension {
+  override uKey: string;
+  // 设置扩展的层级(z-index),必须大于 50
+  override get zIndex() {
+    return 50;
+  }
+  private readonly _downArrowCells: CellCoordinates; // 渲染 ↓ 的条件数组
+  private readonly _rightArrowCells: CellCoordinates; // 渲染 → 的条件数组
+
+  private readonly _sheetId: string; // 用于存储动态传入的 SheetId
+
+  constructor(props: SetSheetFillDirectionProps) {
+    super();
+
+    const { downArrowCells, key, rightArrowCells, sheetId } = props ?? {};
+    this.uKey = key;
+    this._sheetId = sheetId;
+    this._rightArrowCells = rightArrowCells ?? [];
+    this._downArrowCells = downArrowCells ?? [];
+  }
+
+  // 重写绘制方法
+  override draw(ctx: UniverRenderingContext, _parentScale: IScale, skeleton: SpreadsheetSkeleton) {
+    if (!this.isValidSkeleton(skeleton)) {
+      return;
+    }
+
+    if (!this._rightArrowCells?.length && !this._downArrowCells?.length) {
+      return;
+    }
+
+    const { columnTotalWidth, columnWidthAccumulation, rowColumnSegment, rowHeightAccumulation, rowTotalHeight } = skeleton;
+    const { endColumn, endRow, startColumn, startRow } = rowColumnSegment;
+    if (!rowHeightAccumulation || !columnWidthAccumulation || columnTotalWidth === undefined || rowTotalHeight === undefined) {
+      return;
+    }
+
+    this.setupCanvas(ctx);
+
+    // 绘制 → 箭头
+    this.renderArrows({
+      arrow: '→',
+      cells: this._rightArrowCells,
+      colWidths: columnWidthAccumulation,
+      ctx,
+      endCol: endColumn,
+      endRow,
+      offsets: { xOffset: 2, yOffset: -2 },
+      rowHeights: rowHeightAccumulation,
+      startCol: startColumn,
+      startRow,
+    });
+
+    // 绘制 ↓ 箭头
+    this.renderArrows({
+      arrow: '↓',
+      cells: this._downArrowCells,
+      colWidths: columnWidthAccumulation,
+      ctx,
+      endCol: endColumn,
+      endRow,
+      offsets: { xOffset: 0, yOffset: 1 },
+      rowHeights: rowHeightAccumulation,
+      startCol: startColumn,
+      startRow,
+    });
+
+    // 绘制分隔线
+    ctx.stroke();
+  }
+
+  // 验证骨架和必要属性是否有效
+  private isValidSkeleton(skeleton: SpreadsheetSkeleton): boolean {
+    // 如果骨架信息不存在,则退出绘制
+    if (!skeleton) {
+      return false;
+    }
+
+    // 限制扩展仅在指定的工作表上生效
+    return skeleton?.worksheet?.getSheetId() === this._sheetId;
+  }
+
+  // 渲染箭头
+  private renderArrows(props: RenderArrowsProps) {
+    const {
+      arrow,
+      cells,
+      colWidths,
+      ctx,
+      endCol,
+      endRow,
+      offsets = { xOffset: 0, yOffset: 0 }, // 默认偏移量
+      rowHeights,
+      startCol,
+      startRow,
+    } = props ?? {};
+
+    for (const { col, row } of cells) {
+      if (row >= startRow - 1 && row <= endRow && col >= startCol - 1 && col <= endCol) {
+        const rowStart = rowHeights[row - 1] || 0;
+        const colStart = colWidths[col - 1] || 0;
+        ctx.fillText(arrow, colStart + offsets.xOffset, rowStart + offsets.yOffset);
+      }
+    }
+  }
+
+  // 设置 Canvas 的绘图属性
+  private setupCanvas(ctx: UniverRenderingContext) {
+    // 设置背景色为浅灰色
+    ctx.fillStyle = getColor([255, 255, 255]);
+    // 设置文本水平对齐方式
+    ctx.textAlign = 'left';
+    // 设置文本垂直对齐方式
+    ctx.textBaseline = 'top';
+    // 设置文本颜色为黑色
+    ctx.fillStyle = getColor([0, 0, 0])!;
+    // 开始绘制路径
+    ctx.beginPath();
+    // 设置线宽为 1
+    ctx.lineWidth = 1;
+    // 防止边界模糊的偏移值,使绘制的边界在高分辨率屏幕下更清晰
+    ctx.translateWithPrecisionRatio(FIX_ONE_PIXEL_BLUR_OFFSET, FIX_ONE_PIXEL_BLUR_OFFSET);
+    // 设置分隔线颜色为浅灰色
+    ctx.strokeStyle = getColor([217, 217, 217]);
+    // 设置字体样式
+    ctx.font = `10px ${DEFAULT_FONTFACE_PLANE}`;
+  }
+}
+
+// 定义清理扩展类,用于覆盖并清除扩展效果
+export class ClearSheetFillDirectionExtension extends SheetExtension {
+  override uKey: string;
+  private readonly _sheetId: string; // 用于存储动态传入的 SheetId
+
+  constructor(props: ClearSheetFillDirectionProps) {
+    super();
+
+    const { key, sheetId } = props ?? {};
+    this.uKey = key;
+    this._sheetId = sheetId; // 将传入的 SheetId 存储到类中
+  }
+
+  // 空的绘制方法,不进行任何绘制操作
+  override draw(_ctx: UniverRenderingContext, _parentScale: IScale, skeleton: SpreadsheetSkeleton) {
+    if (skeleton?.worksheet?.getSheetId() !== this._sheetId) {
+    }
+  }
+}

+ 184 - 0
src/components/Report/Design/univer/extensions/sheet-main-relation-cell.extension.ts

@@ -0,0 +1,184 @@
+import type { IScale } from '@univerjs/core';
+
+import {
+  DEFAULT_FONTFACE_PLANE,
+  FIX_ONE_PIXEL_BLUR_OFFSET,
+  getColor,
+  SheetExtension,
+  SpreadsheetSkeleton,
+  UniverRenderingContext,
+} from '@univerjs/engine-render';
+
+type CellCoordinates = Array<{ col: number; row: number }>; // 单元格坐标数组
+
+interface ClearSheetRelationCellProps {
+  key: string;
+  sheetId: string;
+}
+
+interface SetSheetRelationCellProps extends ClearSheetRelationCellProps {
+  downArrowCells?: CellCoordinates;
+  rightArrowCells?: CellCoordinates;
+}
+
+interface RenderArrowsProps {
+  arrow: string;
+  cells: CellCoordinates;
+  colWidths: number[];
+  ctx: UniverRenderingContext;
+  endCol: number;
+  endRow: number;
+  offsetsX?: number;
+  offsetsY?: number;
+  rowHeights: number[];
+  startCol: number;
+  startRow: number;
+}
+
+// 定义主要扩展类,用于在表格中绘制自定义内容
+export class SetSheetRelationCellExtension extends SheetExtension {
+  override uKey: string;
+  // 设置扩展的层级(z-index),必须大于 50
+  override get zIndex() {
+    return 50;
+  }
+  private readonly _downArrowCells: CellCoordinates; // 渲染 ↓ 的条件数组
+  private readonly _rightArrowCells: CellCoordinates; // 渲染 → 的条件数组
+
+  private readonly _sheetId: string; // 用于存储动态传入的 SheetId
+
+  constructor(props: SetSheetRelationCellProps) {
+    super();
+
+    const { downArrowCells, key, rightArrowCells, sheetId } = props ?? {};
+    this.uKey = key;
+    this._sheetId = sheetId;
+    this._rightArrowCells = rightArrowCells ?? [];
+    this._downArrowCells = downArrowCells ?? [];
+  }
+
+  // 重写绘制方法
+  override draw(ctx: UniverRenderingContext, _parentScale: IScale, skeleton: SpreadsheetSkeleton) {
+    if (!this.isValidSkeleton(skeleton)) {
+      return;
+    }
+
+    if (!this._rightArrowCells?.length && !this._downArrowCells?.length) {
+      return;
+    }
+
+    const { columnTotalWidth, columnWidthAccumulation, rowColumnSegment, rowHeightAccumulation, rowTotalHeight } = skeleton;
+    const { endColumn, endRow, startColumn, startRow } = rowColumnSegment;
+    if (!rowHeightAccumulation || !columnWidthAccumulation || columnTotalWidth === undefined || rowTotalHeight === undefined) {
+      return;
+    }
+
+    this.setupCanvas(ctx);
+
+    // 绘制 → 箭头
+    this.renderArrows({
+      arrow: '→',
+      cells: this._rightArrowCells,
+      colWidths: columnWidthAccumulation,
+      ctx,
+      endCol: endColumn,
+      endRow,
+      offsetsX: undefined,
+      offsetsY: -2,
+      rowHeights: rowHeightAccumulation,
+      startCol: startColumn,
+      startRow,
+    });
+
+    // 绘制 ↓ 箭头
+    this.renderArrows({
+      arrow: '↓',
+      cells: this._downArrowCells,
+      colWidths: columnWidthAccumulation,
+      ctx,
+      endCol: endColumn,
+      endRow,
+      offsetsX: 0,
+      offsetsY: 12,
+      rowHeights: rowHeightAccumulation,
+      startCol: startColumn,
+      startRow,
+    });
+
+    // 绘制分隔线
+    ctx.stroke();
+  }
+
+  // 验证骨架和必要属性是否有效
+  private isValidSkeleton(skeleton: SpreadsheetSkeleton): boolean {
+    // 如果骨架信息不存在,则退出绘制
+    if (!skeleton) {
+      return false;
+    }
+
+    // 限制扩展仅在指定的工作表上生效
+    return skeleton?.worksheet?.getSheetId() === this._sheetId;
+  }
+
+  // 渲染箭头
+  private renderArrows(props: RenderArrowsProps) {
+    const { arrow, cells, colWidths, ctx, endCol, endRow, offsetsX, offsetsY = 0, rowHeights, startCol, startRow } = props ?? {};
+
+    for (const { col, row } of cells) {
+      if (row >= startRow - 1 && row <= endRow && col >= startCol - 1 && col <= endCol) {
+        const rowStart = rowHeights[row - 1] || 0;
+        const colStart = colWidths[col - 1] || 0;
+
+        const colEnd = colWidths[col] || colWidths[col - 1] || 0;
+        const cellWidth = colEnd - colStart;
+
+        const xPosition = offsetsX === undefined ? colStart + cellWidth / 2 - 6 : colStart + offsetsX;
+        const yPosition = rowStart + offsetsY;
+
+        ctx.fillText(arrow, xPosition, yPosition);
+      }
+    }
+  }
+
+  // 设置 Canvas 的绘图属性
+  private setupCanvas(ctx: UniverRenderingContext) {
+    // 设置背景色为浅灰色
+    ctx.fillStyle = getColor([255, 255, 255]);
+    // 设置文本水平对齐方式
+    ctx.textAlign = 'left';
+    // 设置文本垂直对齐方式
+    ctx.textBaseline = 'top';
+    // 设置文本颜色为黑色
+    ctx.fillStyle = getColor([24, 144, 255])!;
+    // 开始绘制路径
+    ctx.beginPath();
+    // 设置线宽为 1
+    ctx.lineWidth = 1;
+    // 防止边界模糊的偏移值,使绘制的边界在高分辨率屏幕下更清晰
+    ctx.translateWithPrecisionRatio(FIX_ONE_PIXEL_BLUR_OFFSET, FIX_ONE_PIXEL_BLUR_OFFSET);
+    // 设置分隔线颜色为浅灰色
+    ctx.strokeStyle = getColor([217, 217, 217]);
+    // 设置字体样式
+    ctx.font = `10px ${DEFAULT_FONTFACE_PLANE}`;
+  }
+}
+
+// 定义清理扩展类,用于覆盖并清除扩展效果
+export class ClearSheetRelationCellExtension extends SheetExtension {
+  override uKey: string;
+  private readonly _sheetId: string; // 用于存储动态传入的 SheetId
+
+  constructor(props: ClearSheetRelationCellProps) {
+    super();
+
+    const { key, sheetId } = props ?? {};
+    this.uKey = key;
+    this._sheetId = sheetId; // 将传入的 SheetId 存储到类中
+  }
+
+  // 空的绘制方法,不进行任何绘制操作
+  override draw(_ctx: UniverRenderingContext, _parentScale: IScale, skeleton: SpreadsheetSkeleton) {
+    if (skeleton?.worksheet?.getSheetId() !== this._sheetId) {
+    }
+  }
+}

+ 9 - 0
src/components/Report/Design/univer/facade/f-sheet-cell-image.ts

@@ -0,0 +1,9 @@
+import { JnpfSheetsCellImageService } from '../services/sheet-cell-image.service';
+
+export class JnpfFacadeSheetsCellImage {
+  constructor(private readonly _jnpfSheetsCellImageService: JnpfSheetsCellImageService) {}
+
+  insertCellImage(file: File, row: number, col: number, cellData: any) {
+    return this._jnpfSheetsCellImageService?.insertCellImage(file, row, col, cellData);
+  }
+}

+ 31 - 0
src/components/Report/Design/univer/facade/f-sheet-cell.ts

@@ -0,0 +1,31 @@
+import { ICellData } from '@univerjs/core';
+
+import { JnpfSheetsCellService } from '../services/sheet-cell.service';
+
+export class JnpfFacadeSheetsCell {
+  constructor(private readonly _jnpfSheetsCellService: JnpfSheetsCellService) {}
+
+  getAllCellData() {
+    return this._jnpfSheetsCellService?.getAllCellData();
+  }
+
+  getCellData(col: number, row: number) {
+    return this._jnpfSheetsCellService?.getCellData(col, row);
+  }
+
+  getCurrentSheetCellsCustom() {
+    return this._jnpfSheetsCellService?.getCurrentSheetCellsCustom();
+  }
+
+  getTargetCellSize(col: number, row: number) {
+    return this._jnpfSheetsCellService?.getTargetCellSize(col, row);
+  }
+
+  refreshRangeCellsView(range: any) {
+    this._jnpfSheetsCellService?.refreshRangeCellsView(range);
+  }
+
+  setCellData(col: number, row: number, cellData: ICellData) {
+    this._jnpfSheetsCellService?.setCellData(col, row, cellData);
+  }
+}

+ 17 - 0
src/components/Report/Design/univer/facade/f-sheet-float-dom.ts

@@ -0,0 +1,17 @@
+import { JnpfSheetsFloatDomService } from '../services/sheet-float-dom.service';
+
+export class JnpfFacadeSheetsFloatDom {
+  constructor(private readonly _jnpfSheetsFloatDomService: JnpfSheetsFloatDomService) {}
+
+  saveFloatEchartItems(data: any) {
+    this._jnpfSheetsFloatDomService.saveFloatEchartItems(data);
+  }
+
+  saveFloatImageItems(data: any) {
+    this._jnpfSheetsFloatDomService.saveFloatImageItems(data);
+  }
+
+  savePiniaStoreId(value: any) {
+    this._jnpfSheetsFloatDomService.savePiniaStoreId(value);
+  }
+}

+ 13 - 0
src/components/Report/Design/univer/facade/f-sheet-float-echart.ts

@@ -0,0 +1,13 @@
+import { JnpfSheetsFloatEchartService } from '../services/sheet-float-echart.service';
+
+export class JnpfFacadeSheetsFloatEchart {
+  constructor(private readonly _jnpfSheetsFloatEchartService: JnpfSheetsFloatEchartService) {}
+
+  clearFocusDrawingId() {
+    this._jnpfSheetsFloatEchartService.clearFocusDrawingId();
+  }
+
+  savePiniaStoreId(value: any) {
+    this._jnpfSheetsFloatEchartService.savePiniaStoreId(value);
+  }
+}

+ 21 - 0
src/components/Report/Design/univer/facade/f-sheet-float-image.ts

@@ -0,0 +1,21 @@
+import { JnpfSheetsFloatImageService } from '../services/sheet-float-image.service';
+import { JnpfSheetImageIoImplService } from '../services/sheet-image-io-impl.service';
+
+export class JnpfFacadeSheetsFloatImage {
+  constructor(
+    private readonly _jnpfSheetsFloatImageService: JnpfSheetsFloatImageService,
+    private readonly _jnpfSheetImageIoImplService: JnpfSheetImageIoImplService,
+  ) {}
+
+  cacheImages() {
+    this._jnpfSheetImageIoImplService.cacheImages();
+  }
+
+  clearFocusDrawingId() {
+    this._jnpfSheetsFloatImageService.clearFocusDrawingId();
+  }
+
+  savePiniaStoreId(value: any) {
+    this._jnpfSheetsFloatImageService.savePiniaStoreId(value);
+  }
+}

+ 9 - 0
src/components/Report/Design/univer/facade/f-sheet-print.ts

@@ -0,0 +1,9 @@
+import { JnpfSheetsPrintService } from '../services/sheet-print.service';
+
+export class JnpfFacadeSheetsPrint {
+  constructor(private readonly _jnpfSheetsPrintService: JnpfSheetsPrintService) {}
+
+  getLayouts(config: any) {
+    return this._jnpfSheetsPrintService.getLayouts(config);
+  }
+}

+ 9 - 0
src/components/Report/Design/univer/facade/f-sheet-range.ts

@@ -0,0 +1,9 @@
+import { JnpfSheetsRangeService } from '../services/sheet-range.service';
+
+export class JnpfFacadeSheetsRange {
+  constructor(private readonly _jnpfSheetsRangeService: JnpfSheetsRangeService) {}
+
+  recoveryRange(row: number, col: number) {
+    return this._jnpfSheetsRangeService?.recoveryRange(row, col);
+  }
+}

+ 132 - 0
src/components/Report/Design/univer/facade/index.ts

@@ -0,0 +1,132 @@
+import { ICommandService, IImageIoService, Injector, IUniverInstanceService, LifecycleService, Univer } from '@univerjs/core';
+import { FUniver } from '@univerjs/core/facade';
+
+import { JnpfFacadeSheetsCell } from '../facade/f-sheet-cell';
+import { JnpfFacadeSheetsCellImage } from '../facade/f-sheet-cell-image';
+import { JnpfFacadeSheetsFloatDom } from '../facade/f-sheet-float-dom';
+import { JnpfFacadeSheetsPrint } from '../facade/f-sheet-print';
+import { JnpfFacadeSheetsRange } from '../facade/f-sheet-range';
+import { JnpfSheetsCellImageService } from '../services/sheet-cell-image.service';
+import { JnpfSheetsCellService } from '../services/sheet-cell.service';
+import { JnpfSheetsFloatDomService } from '../services/sheet-float-dom.service';
+import { JnpfSheetsFloatEchartService } from '../services/sheet-float-echart.service';
+import { JnpfSheetsFloatImageService } from '../services/sheet-float-image.service';
+import { JnpfSheetImageIoImplService } from '../services/sheet-image-io-impl.service';
+import { JnpfSheetsPrintService } from '../services/sheet-print.service';
+import { JnpfSheetsRangeService } from '../services/sheet-range.service';
+import { JnpfFacadeSheetsFloatEchart } from './f-sheet-float-echart';
+import { JnpfFacadeSheetsFloatImage } from './f-sheet-float-image';
+
+import '@univerjs/sheets/facade';
+import '@univerjs/ui/facade';
+import '@univerjs/docs-ui/facade';
+import '@univerjs/sheets-ui/facade';
+import '@univerjs/sheets-filter/facade';
+import '@univerjs/engine-formula/facade';
+import '@univerjs/sheets-formula/facade';
+import '@univerjs/sheets-data-validation/facade';
+import '@univerjs/sheets-hyper-link/facade';
+import '@univerjs/sheets-hyper-link-ui/facade';
+import '@univerjs/watermark/facade';
+import '@univerjs/sheets-thread-comment/facade';
+import '@univerjs/sheets-crosshair-highlight/facade';
+
+export class JnpfFUniver extends FUniver {
+  constructor(injector: Injector, commandService: ICommandService, instanceService: IUniverInstanceService) {
+    const lifecycleService = injector.get<LifecycleService>(LifecycleService);
+    if (!lifecycleService) {
+      throw new Error('LifecycleService 未正确注入');
+    }
+
+    super(injector, commandService, instanceService, lifecycleService);
+  }
+
+  static newAPI(wrapped: Injector | Univer): JnpfFUniver {
+    const injector = wrapped instanceof Univer ? wrapped.__getInjector() : wrapped;
+    const commandService = injector.get<ICommandService>(ICommandService);
+    const instanceService = injector.get<IUniverInstanceService>(IUniverInstanceService);
+
+    if (!commandService || !instanceService) {
+      throw new Error('注入ICommandService或IUniverInstanceService发生错误');
+    }
+
+    return new JnpfFUniver(injector, commandService, instanceService);
+  }
+
+  getInjector() {
+    return this._injector;
+  }
+
+  getSheetsCell(): JnpfFacadeSheetsCell | null {
+    const jnpfSheetsCellService = this._injector.get(JnpfSheetsCellService);
+
+    if (!jnpfSheetsCellService) {
+      return null;
+    }
+
+    return this._injector.createInstance(JnpfFacadeSheetsCell, jnpfSheetsCellService);
+  }
+
+  getSheetsCellImage(): JnpfFacadeSheetsCellImage | null {
+    const jnpfSheetsCellImageService = this._injector.get(JnpfSheetsCellImageService);
+
+    if (!jnpfSheetsCellImageService) {
+      return null;
+    }
+
+    return this._injector.createInstance(JnpfFacadeSheetsCellImage, jnpfSheetsCellImageService);
+  }
+
+  getSheetsFloatDom(): JnpfFacadeSheetsFloatDom | null {
+    const jnpfSheetsFloatDomService = this._injector.get(JnpfSheetsFloatDomService);
+
+    if (!jnpfSheetsFloatDomService) {
+      return null;
+    }
+
+    return this._injector.createInstance(JnpfFacadeSheetsFloatDom, jnpfSheetsFloatDomService);
+  }
+
+  getSheetsFloatEchart(): JnpfFacadeSheetsFloatEchart | null {
+    const jnpfSheetsFloatEchartService = this._injector.get(JnpfSheetsFloatEchartService);
+
+    if (!jnpfSheetsFloatEchartService) {
+      return null;
+    }
+
+    return this._injector.createInstance(JnpfFacadeSheetsFloatEchart, jnpfSheetsFloatEchartService);
+  }
+
+  getSheetsFloatImage(): JnpfFacadeSheetsFloatImage | null {
+    const jnpfSheetsFloatImageService = this._injector.get(JnpfSheetsFloatImageService);
+    const jnpfSheetImageIoImplService = this._injector.get(IImageIoService);
+    if (!jnpfSheetImageIoImplService) {
+      return null;
+    }
+    if (!jnpfSheetsFloatImageService) {
+      return null;
+    }
+
+    return this._injector.createInstance(JnpfFacadeSheetsFloatImage, jnpfSheetsFloatImageService, jnpfSheetImageIoImplService as JnpfSheetImageIoImplService);
+  }
+
+  getSheetsPrint(): JnpfFacadeSheetsPrint | null {
+    const jnpfSheetsPrintService = this._injector.get(JnpfSheetsPrintService);
+
+    if (!jnpfSheetsPrintService) {
+      return null;
+    }
+
+    return this._injector.createInstance(JnpfFacadeSheetsPrint, jnpfSheetsPrintService);
+  }
+
+  getSheetsRange(): JnpfFacadeSheetsRange | null {
+    const jnpfSheetsRangeService = this._injector.get(JnpfSheetsRangeService);
+
+    if (!jnpfSheetsRangeService) {
+      return null;
+    }
+
+    return this._injector.createInstance(JnpfFacadeSheetsRange, jnpfSheetsRangeService);
+  }
+}

+ 48 - 0
src/components/Report/Design/univer/locales/zh-CN.ts

@@ -0,0 +1,48 @@
+export default {
+  jnpfSheetDownloadExcelFileMenu: {
+    title: '导出Excel',
+    tooltip: '导出Excel',
+  },
+  jnpfSheetExcelFileMenu: {
+    tooltip: 'Excel文件',
+  },
+  jnpfSheetFloatEchartMenu: {
+    tooltip: '图表',
+  },
+  jnpfSheetImportCsvFileMenu: {
+    title: '导入CSV',
+    tooltip: '导入CSV',
+  },
+  jnpfSheetImportExcelFileMenu: {
+    title: '导入Excel',
+    tooltip: '导入Excel',
+  },
+  jnpfSheetInsertFloatBarEchartMenu: {
+    title: '柱状图',
+    tooltip: '柱状图-图表',
+  },
+  jnpfSheetInsertFloatImageMenu: {
+    title: '自定义悬浮图片',
+    tooltip: '自定义悬浮图片-图片',
+  },
+  jnpfSheetInsertFloatLineEchartMenu: {
+    title: '折线图',
+    tooltip: '折线图-图表',
+  },
+  jnpfSheetInsertFloatPieEchartMenu: {
+    title: '饼图',
+    tooltip: '饼图-图表',
+  },
+  jnpfSheetInsertFloatRadarEchartMenu: {
+    title: '雷达图',
+    tooltip: '雷达图-图表',
+  },
+  jnpfSheetPreviewMenu: {
+    title: '设计预览功能',
+    tooltip: '设计预览',
+  },
+  jnpfSheetPrintMenu: {
+    title: '打印功能',
+    tooltip: '打印',
+  },
+};

+ 28 - 0
src/components/Report/Design/univer/plugins/sheet-cell-image.plugin.ts

@@ -0,0 +1,28 @@
+import type { Dependency } from '@univerjs/core';
+
+import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
+import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
+
+import zhCN from '../locales/zh-CN';
+import { JnpfSheetsCellImageService } from '../services/sheet-cell-image.service';
+
+@DependentOn(UniverSheetsUIPlugin)
+export class JnpfSheetsCellImagePlugin extends Plugin {
+  static override pluginName = 'JNPF_SHEET_CELL_IMAGE_PLUGIN';
+  static override type = UniverInstanceType.UNIVER_SHEET;
+
+  constructor(
+    @Inject(Injector) protected readonly _injector: Injector,
+    @Inject(LocaleService) private readonly _localeService: LocaleService,
+  ) {
+    super();
+
+    this._localeService.load({
+      zhCN,
+    });
+  }
+
+  override onStarting(): void {
+    ([[JnpfSheetsCellImageService]] as Dependency[]).forEach(d => this._injector.add(d));
+  }
+}

+ 28 - 0
src/components/Report/Design/univer/plugins/sheet-cell.plugin.ts

@@ -0,0 +1,28 @@
+import type { Dependency } from '@univerjs/core';
+
+import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
+import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
+
+import zhCN from '../locales/zh-CN';
+import { JnpfSheetsCellService } from '../services/sheet-cell.service';
+
+@DependentOn(UniverSheetsUIPlugin)
+export class JnpfSheetsCellPlugin extends Plugin {
+  static override pluginName = 'JNPF_SHEET_CELL_PLUGIN';
+  static override type = UniverInstanceType.UNIVER_SHEET;
+
+  constructor(
+    @Inject(Injector) protected readonly _injector: Injector,
+    @Inject(LocaleService) private readonly _localeService: LocaleService,
+  ) {
+    super();
+
+    this._localeService.load({
+      zhCN,
+    });
+  }
+
+  override onStarting(): void {
+    ([[JnpfSheetsCellService]] as Dependency[]).forEach(d => this._injector.add(d));
+  }
+}

+ 30 - 0
src/components/Report/Design/univer/plugins/sheet-dialog.plugin.ts

@@ -0,0 +1,30 @@
+import type { Dependency } from '@univerjs/core';
+
+import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
+import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
+
+import { JnpfSheetsDialogController } from '../controllers/sheet-dialog.controller';
+import zhCN from '../locales/zh-CN';
+
+@DependentOn(UniverSheetsUIPlugin)
+export class JnpfSheetsDialogPlugin extends Plugin {
+  static override pluginName = 'JNPF_SHEET_DIALOG_PLUGIN';
+  static override type = UniverInstanceType.UNIVER_SHEET;
+
+  constructor(
+    @Inject(Injector) protected readonly _injector: Injector,
+    @Inject(LocaleService) private readonly _localeService: LocaleService,
+  ) {
+    super();
+
+    this._localeService.load({
+      zhCN,
+    });
+  }
+
+  override onStarting(): void {
+    ([[JnpfSheetsDialogController]] as Dependency[]).forEach(d => this._injector.add(d));
+
+    this._injector.get(JnpfSheetsDialogController);
+  }
+}

+ 31 - 0
src/components/Report/Design/univer/plugins/sheet-excel-file.plugin.ts

@@ -0,0 +1,31 @@
+import type { Dependency } from '@univerjs/core';
+
+import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
+import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
+
+import { JnpfSheetsExcelFileController } from '../controllers/sheet-excel-file.controller';
+import zhCN from '../locales/zh-CN';
+import { JnpfSheetsExcelFileService } from '../services/sheet-excel-file.service';
+
+@DependentOn(UniverSheetsUIPlugin)
+export class JnpfSheetsExcelFilePlugin extends Plugin {
+  static override pluginName = 'JNPF_SHEET_EXCEL_FILE_PLUGIN';
+  static override type = UniverInstanceType.UNIVER_SHEET;
+
+  constructor(
+    @Inject(Injector) protected readonly _injector: Injector,
+    @Inject(LocaleService) private readonly _localeService: LocaleService,
+  ) {
+    super();
+
+    this._localeService.load({
+      zhCN,
+    });
+  }
+
+  override onStarting(): void {
+    ([[JnpfSheetsExcelFileController], [JnpfSheetsExcelFileService]] as Dependency[]).forEach(d => this._injector.add(d));
+
+    this._injector.get(JnpfSheetsExcelFileController);
+  }
+}

+ 28 - 0
src/components/Report/Design/univer/plugins/sheet-float-dom.plugin.ts

@@ -0,0 +1,28 @@
+import type { Dependency } from '@univerjs/core';
+
+import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
+import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
+
+import zhCN from '../locales/zh-CN';
+import { JnpfSheetsFloatDomService } from '../services/sheet-float-dom.service';
+
+@DependentOn(UniverSheetsUIPlugin)
+export class JnpfSheetsFloatDomPlugin extends Plugin {
+  static override pluginName = 'JNPF_SHEET_FLOAT_DOM_PLUGIN';
+  static override type = UniverInstanceType.UNIVER_SHEET;
+
+  constructor(
+    @Inject(Injector) protected readonly _injector: Injector,
+    @Inject(LocaleService) private readonly _localeService: LocaleService,
+  ) {
+    super();
+
+    this._localeService.load({
+      zhCN,
+    });
+  }
+
+  override onStarting(): void {
+    ([[JnpfSheetsFloatDomService]] as Dependency[]).forEach(d => this._injector.add(d));
+  }
+}

+ 31 - 0
src/components/Report/Design/univer/plugins/sheet-float-echart.plugin.ts

@@ -0,0 +1,31 @@
+import type { Dependency } from '@univerjs/core';
+
+import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
+import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
+
+import { JnpfSheetsFloatEchartController } from '../controllers/sheet-float-echart.controller';
+import zhCN from '../locales/zh-CN';
+import { JnpfSheetsFloatEchartService } from '../services/sheet-float-echart.service';
+
+@DependentOn(UniverSheetsUIPlugin)
+export class JnpfSheetsFloatEchartPlugin extends Plugin {
+  static override pluginName = 'JNPF_SHEET_FLOAT_ECHART_PLUGIN';
+  static override type = UniverInstanceType.UNIVER_SHEET;
+
+  constructor(
+    @Inject(Injector) protected readonly _injector: Injector,
+    @Inject(LocaleService) private readonly _localeService: LocaleService,
+  ) {
+    super();
+
+    this._localeService.load({
+      zhCN,
+    });
+  }
+
+  override onStarting(): void {
+    ([[JnpfSheetsFloatEchartController], [JnpfSheetsFloatEchartService]] as Dependency[]).forEach(d => this._injector.add(d));
+
+    this._injector.get(JnpfSheetsFloatEchartController);
+  }
+}

+ 35 - 0
src/components/Report/Design/univer/plugins/sheet-float-image.plugin.ts

@@ -0,0 +1,35 @@
+import type { Dependency } from '@univerjs/core';
+
+import { DependentOn, IImageIoService, Inject, Injector, LocaleService, mergeOverrideWithDependencies, Plugin, UniverInstanceType } from '@univerjs/core';
+import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
+
+import { JnpfSheetsFloatImageController } from '../controllers/sheet-float-image.controller';
+import zhCN from '../locales/zh-CN';
+import { JnpfSheetsFloatImageService } from '../services/sheet-float-image.service';
+import { JnpfSheetImageIoImplService } from '../services/sheet-image-io-impl.service';
+
+@DependentOn(UniverSheetsUIPlugin)
+export class JnpfSheetsFloatImagePlugin extends Plugin {
+  static override pluginName = 'JNPF_SHEET_FLOAT_IMAGE_PLUGIN';
+  static override type = UniverInstanceType.UNIVER_SHEET;
+
+  constructor(
+    @Inject(Injector) protected readonly _injector: Injector,
+    @Inject(LocaleService) private readonly _localeService: LocaleService,
+  ) {
+    super();
+
+    this._localeService.load({
+      zhCN,
+    });
+  }
+
+  override onStarting(): void {
+    mergeOverrideWithDependencies(
+      [[JnpfSheetsFloatImageController], [JnpfSheetsFloatImageService], [IImageIoService, { useClass: JnpfSheetImageIoImplService }]] as Dependency[],
+      [],
+    ).forEach(d => this._injector.add(d));
+
+    this._injector.get(JnpfSheetsFloatImageController);
+  }
+}

+ 30 - 0
src/components/Report/Design/univer/plugins/sheet-preview.plugin.ts

@@ -0,0 +1,30 @@
+import type { Dependency } from '@univerjs/core';
+
+import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
+import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
+
+import { JnpfSheetsPreviewController } from '../controllers/sheet-preview.controller';
+import zhCN from '../locales/zh-CN';
+
+@DependentOn(UniverSheetsUIPlugin)
+export class JnpfSheetsPreviewPlugin extends Plugin {
+  static override pluginName = 'JNPF_SHEET_PREVIEW_PLUGIN';
+  static override type = UniverInstanceType.UNIVER_SHEET;
+
+  constructor(
+    @Inject(Injector) protected readonly _injector: Injector,
+    @Inject(LocaleService) private readonly _localeService: LocaleService,
+  ) {
+    super();
+
+    this._localeService.load({
+      zhCN,
+    });
+  }
+
+  override onStarting(): void {
+    ([[JnpfSheetsPreviewController]] as Dependency[]).forEach(d => this._injector.add(d));
+
+    this._injector.get(JnpfSheetsPreviewController);
+  }
+}

+ 29 - 0
src/components/Report/Design/univer/plugins/sheet-print.plugin.ts

@@ -0,0 +1,29 @@
+import type { Dependency } from '@univerjs/core';
+
+import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
+import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
+
+import zhCN from '../locales/zh-CN';
+import { JnpfSheetsPrintUiService } from '../services/sheet-print-ui.service';
+import { JnpfSheetsPrintService } from '../services/sheet-print.service';
+
+@DependentOn(UniverSheetsUIPlugin)
+export class JnpfSheetsPrintPlugin extends Plugin {
+  static override pluginName = 'JNPF_SHEET_PRINT_PLUGIN';
+  static override type = UniverInstanceType.UNIVER_SHEET;
+
+  constructor(
+    @Inject(Injector) protected readonly _injector: Injector,
+    @Inject(LocaleService) private readonly _localeService: LocaleService,
+  ) {
+    super();
+
+    this._localeService.load({
+      zhCN,
+    });
+  }
+
+  override onStarting(): void {
+    ([[JnpfSheetsPrintService], [JnpfSheetsPrintUiService]] as Dependency[]).forEach(d => this._injector.add(d));
+  }
+}

+ 28 - 0
src/components/Report/Design/univer/plugins/sheet-range.plugin.ts

@@ -0,0 +1,28 @@
+import type { Dependency } from '@univerjs/core';
+
+import { DependentOn, Inject, Injector, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core';
+import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
+
+import zhCN from '../locales/zh-CN';
+import { JnpfSheetsRangeService } from '../services/sheet-range.service';
+
+@DependentOn(UniverSheetsUIPlugin)
+export class JnpfSheetsRangePlugin extends Plugin {
+  static override pluginName = 'JNPF_SHEET_RANGE_PLUGIN';
+  static override type = UniverInstanceType.UNIVER_SHEET;
+
+  constructor(
+    @Inject(Injector) protected readonly _injector: Injector,
+    @Inject(LocaleService) private readonly _localeService: LocaleService,
+  ) {
+    super();
+
+    this._localeService.load({
+      zhCN,
+    });
+  }
+
+  override onStarting(): void {
+    ([[JnpfSheetsRangeService]] as Dependency[]).forEach(d => this._injector.add(d));
+  }
+}

+ 206 - 0
src/components/Report/Design/univer/services/sheet-cell-image.service.ts

@@ -0,0 +1,206 @@
+import {
+  BooleanNumber,
+  BuildTextUtils,
+  createDocumentModelWithStyle,
+  Disposable,
+  DrawingTypeEnum,
+  IAccessor,
+  ICommandService,
+  IImageIoService,
+  IImageIoServiceParam,
+  Inject,
+  Injector,
+  IUniverInstanceService,
+  Nullable,
+  ObjectRelativeFromH,
+  ObjectRelativeFromV,
+  PositionedObjectLayoutType,
+  UniverInstanceType,
+  Workbook,
+  WrapTextType,
+} from '@univerjs/core';
+import { docDrawingPositionToTransform } from '@univerjs/docs-ui';
+import { getImageSize } from '@univerjs/drawing';
+import { IRenderManagerService } from '@univerjs/engine-render';
+import { ISheetLocationBase, SetRangeValuesCommand } from '@univerjs/sheets';
+import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
+
+import { rotatedBoundingBox } from '../utils';
+
+export class JnpfSheetsCellImageService extends Disposable {
+  constructor(
+    @Inject(Injector) private readonly _injector: Injector,
+    @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
+    @ICommandService private readonly _commandService: ICommandService,
+    @IImageIoService private readonly _imageIoService: IImageIoService,
+  ) {
+    super();
+  }
+
+  /**
+   * 在当前选中的单元格插入图片
+   * @param file 要插入的图片文件
+   * @param row 行
+   * @param col 列
+   * @param cellData 单元格配置信息
+   * @returns 插入结果,成功返回执行命令的结果,失败返回 `false` 或 `null`
+   */
+  public async insertCellImage(file: File, row: number, col: number, cellData: any) {
+    // 获取当前的工作簿实例
+    const workbook = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
+    if (!workbook) {
+      return false;
+    }
+    const unitId = workbook.getUnitId();
+
+    // 获取当前激活的工作表
+    const worksheet = workbook.getActiveSheet();
+    if (!worksheet) {
+      return false;
+    }
+    const subUnitId = worksheet.getSheetId();
+
+    // 将图片文件保存到 ImageIoService 并获取图片参数
+    let imageParam: Nullable<IImageIoServiceParam>;
+    try {
+      imageParam = await this._imageIoService.saveImage(file);
+    } catch {
+      return false;
+    }
+    if (imageParam == null) {
+      return false;
+    }
+
+    // 获取图片相关信息
+    const { base64Cache, imageId, imageSourceType, source } = imageParam;
+    const { height, image, width } = await getImageSize(base64Cache || '');
+    // 缓存图片数据
+    this._imageIoService.addImageSourceCache(source, imageSourceType, image);
+
+    const docDataModel = createDocumentModelWithStyle('', {});
+
+    // 计算图片适应单元格后的尺寸
+    const imageSize = this._getDrawingSizeByCell(
+      this._injector,
+      {
+        col,
+        row,
+        subUnitId,
+        unitId,
+      },
+      width,
+      height,
+      0,
+    );
+    if (!imageSize) {
+      return false;
+    }
+
+    // 定义图片的文档变换参数
+    const docTransform = {
+      angle: 0,
+      positionH: {
+        posOffset: 0,
+        relativeFrom: ObjectRelativeFromH.PAGE,
+      },
+      positionV: {
+        posOffset: 0,
+        relativeFrom: ObjectRelativeFromV.PARAGRAPH,
+      },
+      size: {
+        height: imageSize.height,
+        width: imageSize.width,
+      },
+    };
+
+    // 定义文档中的绘图参数
+    const docDrawingParam = {
+      behindDoc: BooleanNumber.FALSE,
+      description: '',
+      distB: 0,
+      distL: 0,
+      distR: 0,
+      distT: 0,
+      docTransform,
+      drawingId: imageId,
+      drawingType: DrawingTypeEnum.DRAWING_IMAGE,
+      imageSourceType,
+      layoutType: PositionedObjectLayoutType.INLINE, // Insert inline drawing by default.
+      source,
+      subUnitId: docDataModel.getUnitId(),
+      title: '',
+      transform: docDrawingPositionToTransform(docTransform),
+      unitId: docDataModel.getUnitId(),
+      wrapText: WrapTextType.BOTH_SIDES,
+    };
+
+    // 生成插入绘图的 JSON 结构
+    const jsonXActions = BuildTextUtils.drawing.add({
+      documentDataModel: docDataModel,
+      drawings: [docDrawingParam],
+      selection: {
+        collapsed: true,
+        endOffset: 0,
+        startOffset: 0,
+      },
+    });
+
+    if (!jsonXActions) {
+      return false;
+    }
+
+    docDataModel.apply(jsonXActions);
+
+    return this._commandService.syncExecuteCommand(SetRangeValuesCommand.id, {
+      value: {
+        [row]: {
+          [col]: {
+            custom: {
+              ...cellData.custom,
+              drawingId: imageId,
+            },
+            p: docDataModel.getSnapshot(),
+            t: 1,
+            v: cellData.v ?? '',
+          },
+        },
+      },
+    });
+  }
+
+  /**
+   * 获取单元格内图形的大小
+   * @param accessor 访问器对象
+   * @param location 单元格位置信息
+   * @param originImageWidth 原始图像宽度
+   * @param originImageHeight 原始图像高度
+   * @param angle 旋转角度(0-360,单位:度)
+   * @returns 计算后的图像尺寸,或在获取失败时返回 `false`
+   */
+  private _getDrawingSizeByCell(accessor: IAccessor, location: ISheetLocationBase, originImageWidth: number, originImageHeight: number, angle: number) {
+    const { rotatedHeight, rotatedWidth } = rotatedBoundingBox(originImageWidth, originImageHeight, angle);
+    const renderManagerService = accessor.get(IRenderManagerService);
+    const currentRender = renderManagerService.getRenderById(location.unitId);
+    if (!currentRender) {
+      return false;
+    }
+    const skeletonManagerService = currentRender.with(SheetSkeletonManagerService);
+    const skeleton = skeletonManagerService.getWorksheetSkeleton(location.subUnitId)?.skeleton;
+    if (skeleton == null) {
+      return false;
+    }
+    const cellInfo = skeleton.getCellWithCoordByIndex(location.row, location.col);
+
+    const cellWidth = cellInfo.mergeInfo.endX - cellInfo.mergeInfo.startX - 2;
+    const cellHeight = cellInfo.mergeInfo.endY - cellInfo.mergeInfo.startY - 2;
+    const imageRatio = rotatedWidth / rotatedHeight;
+    const imageWidth = Math.ceil(Math.min(cellWidth, cellHeight * imageRatio));
+    const scale = imageWidth / rotatedWidth;
+    const realScale = !scale || Number.isNaN(scale) ? 0.001 : scale;
+
+    return {
+      height: originImageHeight * realScale,
+      width: originImageWidth * realScale,
+    };
+  }
+}

+ 124 - 0
src/components/Report/Design/univer/services/sheet-cell.service.ts

@@ -0,0 +1,124 @@
+import { Disposable, ICellData, IUniverInstanceService, UniverInstanceType, Workbook } from '@univerjs/core';
+import { IRenderManagerService } from '@univerjs/engine-render';
+import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
+
+export class JnpfSheetsCellService extends Disposable {
+  constructor(
+    @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
+    @IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
+  ) {
+    super();
+  }
+
+  // 获取所有单元格数据
+  public getAllCellData() {
+    const sheet = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet();
+    return sheet.getCellMatrix().getMatrix();
+  }
+
+  // 获取指定单元格数据
+  public getCellData(col: number, row: number) {
+    const sheet = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet();
+    const cellMatrix = sheet.getCellMatrix().getMatrix();
+
+    return cellMatrix?.[row]?.[col] ?? null; // 如果未找到匹配的单元格,返回 null
+  }
+
+  // 获取当前单元格的自定义属性
+  public getCurrentSheetCellsCustom() {
+    const sheet = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet();
+    const cellMatrix = sheet?.getCellMatrix()?.getMatrix() ?? {};
+
+    const result: { [row: string]: { [col: string]: any } } = {};
+    Object.entries(cellMatrix)?.forEach(([row, colObj]) => {
+      if (colObj && typeof colObj === 'object') {
+        const rowData: { [col: string]: any } = {};
+
+        Object.entries(colObj)?.forEach(([col, value]: any) => {
+          if (value?.custom) {
+            rowData[col] = { ...value };
+          }
+        });
+
+        if (Object.keys(rowData)?.length) {
+          result[row] = rowData;
+        }
+      }
+    });
+
+    return result;
+  }
+
+  // 获取激活单元格的尺寸信息
+  public getTargetCellSize(col: number, row: number): null | { cellHeight: number; cellWidth: number } {
+    // 获取当前的工作簿实例
+    const workbook = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
+    if (!workbook) {
+      return null;
+    }
+
+    const unitId = workbook.getUnitId();
+
+    // 获取当前激活的工作表
+    const worksheet = workbook.getActiveSheet();
+    if (!worksheet) {
+      return null;
+    }
+
+    const subUnitId = worksheet.getSheetId();
+
+    // 获取对应的骨架渲染服务
+    const skeleton = this._renderManagerService?.getRenderById(unitId)?.with(SheetSkeletonManagerService)?.getUnitSkeleton(unitId, subUnitId)?.skeleton as any;
+
+    if (!skeleton) {
+      return null;
+    }
+
+    // 获取指定单元格的位置信息
+    const cellInfo = skeleton.getCellWithCoordByIndex(row, col);
+    if (!cellInfo) {
+      return null;
+    }
+
+    // 解构单元格信息
+    const { endX, endY, isMergedMainCell, mergeInfo, startX, startY } = cellInfo;
+
+    let cellWidth: number;
+    let cellHeight: number;
+
+    if (isMergedMainCell && mergeInfo) {
+      cellWidth = mergeInfo.endX - mergeInfo.startX;
+      cellHeight = mergeInfo.endY - mergeInfo.startY;
+    } else {
+      cellWidth = endX - startX;
+      cellHeight = endY - startY;
+    }
+
+    return {
+      cellHeight: Math.floor(cellHeight) - 2,
+      cellWidth: Math.floor(cellWidth) - 2,
+    };
+  }
+
+  // 刷新所有单元格的视图
+  public refreshRangeCellsView(range: any) {
+    if (!range) {
+      return;
+    }
+
+    const originalValue = range?.getValue() ?? '';
+    range?.setValue(originalValue);
+  }
+
+  // 设置单元格数据
+  public setCellData(col: number, row: number, cellData: ICellData) {
+    const sheet = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet();
+    const cellMatrix = sheet.getCellMatrix();
+
+    try {
+      cellMatrix.setValue(row, col, cellData);
+    } catch (error) {
+      console.warn(error);
+    }
+  }
+}

+ 84 - 0
src/components/Report/Design/univer/services/sheet-excel-file.service.ts

@@ -0,0 +1,84 @@
+import { Disposable, ICellData, ICommandService, IUniverInstanceService, UniverInstanceType, Workbook } from '@univerjs/core';
+import { SetRangeValuesCommand } from '@univerjs/sheets';
+
+export class JnpfSheetsExcelFileService extends Disposable {
+  constructor(
+    @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
+    @ICommandService private readonly _commandService: ICommandService,
+  ) {
+    super();
+  }
+
+  public handleImportCsv() {
+    // 获取当前工作表
+    const sheet = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET)!.getActiveSheet();
+
+    // 等待用户选择 CSV 文件
+    this._waitUserSelectCSVFile(({ colsCount, data, rowsCount }) => {
+      // 设置工作表大小
+      sheet.setColumnCount(colsCount);
+      sheet.setRowCount(rowsCount);
+
+      // 设置工作表数据
+      this._commandService
+        .executeCommand(SetRangeValuesCommand.id, {
+          range: {
+            endColumn: colsCount - 1, // 结束列索引
+            endRow: rowsCount - 1, // 结束行索引
+            startColumn: 0, // 起始列索引
+            startRow: 0, // 起始行索引
+          },
+          value: this._parseCSV2UniverData(data),
+        })
+        .then();
+    });
+  }
+
+  /**
+   * 将 CSV 解析为 Univer 数据
+   * @param csv CSV 数据
+   * @returns { v: string }[][] 返回解析后的数据
+   */
+  private _parseCSV2UniverData(csv: string[][]): ICellData[][] {
+    return csv.map(row => {
+      return row.map(cell => {
+        return {
+          v: cell || '',
+        };
+      });
+    });
+  }
+
+  /**
+   * 等待用户选择 CSV 文件
+   */
+  private _waitUserSelectCSVFile(onSelect: (data: { colsCount: number; data: string[][]; rowsCount: number }) => void) {
+    const input = document.createElement('input');
+    input.type = 'file';
+    input.accept = '.csv';
+    input.click();
+
+    input.addEventListener('change', () => {
+      const file = input.files?.[0];
+      if (!file) return;
+      const reader = new FileReader();
+      reader.addEventListener('load', () => {
+        const text = reader.result;
+        if (typeof text !== 'string') return;
+
+        // 提示:使用 npm 包来解析 CSV
+        const rows = text.split(/\r\n|\n/);
+        const data = rows.map(line => line.split(','));
+
+        const colsCount = data.reduce((max, row) => Math.max(max, row.length), 0);
+
+        onSelect({
+          colsCount,
+          data,
+          rowsCount: data.length,
+        });
+      });
+      reader.readAsText(file);
+    });
+  }
+}

+ 53 - 0
src/components/Report/Design/univer/services/sheet-float-dom.service.ts

@@ -0,0 +1,53 @@
+import { Disposable } from '@univerjs/core';
+
+export class JnpfSheetsFloatDomService extends Disposable {
+  // private _floatEchartItems: Record<string, any> = {};
+  //
+  // private _floatImageItems: Record<string, any> = {};
+  // private _piniaStoreId: null | string = null;
+
+  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
+  constructor() {
+    super();
+
+    // this._addFloatDomHook();
+  }
+
+  // 存入图表列表数据
+  public saveFloatEchartItems(data: any) {
+    // this._floatEchartItems = data;
+  }
+
+  // 存入图片列表数据
+  public saveFloatImageItems(data: any) {
+    // this._floatImageItems = data;
+  }
+
+  // 存入store的id
+  public savePiniaStoreId(data: any) {
+    // this._piniaStoreId = data;
+  }
+
+  // 增加Hook
+  // private _addFloatDomHook() {
+  //   this.disposeWithMe(
+  //     this._sheetCanvasFloatDomManagerService.addHook({
+  //       onGetFloatDomProps: (id: string) => {
+  //         if (this._floatImageItems?.hasOwnProperty(id)) {
+  //           const { domId } = this._floatImageItems[id] ?? {};
+  //           return { id: domId, piniaStoreId: this._piniaStoreId };
+  //         }
+  //
+  //         if (this._floatEchartItems?.hasOwnProperty(id)) {
+  //           const { domId } = this._floatEchartItems[id] ?? {};
+  //           return { id: domId, piniaStoreId: this._piniaStoreId };
+  //         }
+  //
+  //         return {
+  //           id,
+  //         };
+  //       },
+  //     }),
+  //   );
+  // }
+}

+ 153 - 0
src/components/Report/Design/univer/services/sheet-float-echart.service.ts

@@ -0,0 +1,153 @@
+import { Disposable, ICommandService, Inject, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
+import { IDrawingManagerService } from '@univerjs/drawing';
+import { DeviceInputEventType, IRenderManagerService } from '@univerjs/engine-render';
+import { SheetsSelectionsService } from '@univerjs/sheets';
+import { SheetCanvasFloatDomManagerService } from '@univerjs/sheets-drawing-ui';
+import { attachRangeWithCoord, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
+
+import { DefaultFloatEchartHeight, DefaultFloatEchartOptions, DefaultFloatEchartWidth, JnpfCommandIds, JnpfUniverFloatEchartKey } from '../utils/define';
+import { buildUUID } from '../utils/uuid';
+
+const regex = /^JnpfUniverFloat(.*?)Echart$/; // 正则表达式匹配 JnpfUniver 和 Echart
+
+export class JnpfSheetsFloatEchartService extends Disposable {
+  private _focusDrawingId: null | string = null;
+  private _piniaStoreId: null | string = null;
+
+  private readonly defaultPosition = {
+    endX: DefaultFloatEchartWidth,
+    endY: DefaultFloatEchartHeight,
+    startX: 100,
+    startY: 100,
+  };
+
+  constructor(
+    @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
+    @ICommandService private readonly _commandService: ICommandService,
+    @IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
+    @IDrawingManagerService private readonly _drawingManagerService: IDrawingManagerService,
+    @Inject(SheetCanvasFloatDomManagerService) private readonly _sheetCanvasFloatDomManagerService: SheetCanvasFloatDomManagerService,
+    @Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
+  ) {
+    super();
+
+    this._subscribeToFocusEvents();
+  }
+
+  // 清空焦点对象的id
+  public clearFocusDrawingId() {
+    if (!this._focusDrawingId) {
+      return;
+    }
+
+    this._focusDrawingId = null;
+  }
+
+  // 创建并插入图表DOM
+  public insertFloatEchart(componentKey: string) {
+    this._commandService
+      .executeCommand('sheet.operation.set-cell-edit-visible', {
+        _eventType: DeviceInputEventType.PointerUp,
+        visible: false,
+      })
+      .then();
+
+    const initPosition = this._calculateInitPosition();
+    const domId = `echart_${buildUUID()}`;
+
+    const addResult = this._sheetCanvasFloatDomManagerService?.addFloatDomToPosition({
+      allowTransform: true,
+      componentKey: JnpfUniverFloatEchartKey,
+      initPosition,
+      data: {
+        id: domId,
+        piniaStoreId: this._piniaStoreId,
+      },
+    });
+
+    if (!addResult) {
+      return;
+    }
+
+    const type = componentKey?.replace(regex, '$1');
+    const echartType = type ? type.charAt(0)?.toLowerCase() + type.slice(1) : undefined;
+
+    const newParams = {
+      domId,
+      drawingId: addResult.id,
+      echartType,
+      option: JSON.parse(JSON.stringify(DefaultFloatEchartOptions?.[echartType as keyof typeof DefaultFloatEchartOptions])),
+    };
+    // 自定义命令出来,告知相关的信息
+    this._commandService.executeCommand(JnpfCommandIds?.insertedFloatEchart, newParams).then();
+  }
+
+  // 存入store的id
+  public savePiniaStoreId(data: string) {
+    this._piniaStoreId = data;
+  }
+
+  // 计算初始位置
+  private _calculateInitPosition() {
+    // 获取当前单元格数据
+    const unit = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET);
+    if (!unit) {
+      console.warn('警告:未找到单元。使用默认位置 (100, 100)。');
+      return this.defaultPosition;
+    }
+
+    const unitId = unit.getUnitId();
+    const currentSelections = this._selectionManagerService.getCurrentSelections();
+    const firstSelection = currentSelections?.[0];
+    if (!firstSelection?.range) {
+      console.warn('警告:未找到选择范围。使用默认位置 (100, 100)。');
+      return this.defaultPosition;
+    }
+
+    // 获取当前表格骨架信息
+    const sheetSkeletonService = this._renderManagerService.getRenderById(unitId)?.with(SheetSkeletonManagerService);
+    const currentSkeleton = sheetSkeletonService?.getCurrent()?.skeleton;
+    if (!currentSkeleton) {
+      console.warn('警告:未找到骨架。使用默认位置 (100, 100)。');
+      return this.defaultPosition;
+    }
+
+    // 解构表格尺寸信息
+    const { columnHeaderHeight = 0, columnTotalWidth = 0, rowHeaderWidth = 0, rowTotalHeight = 0 } = currentSkeleton;
+    const columnScreenTotalWidth = columnTotalWidth + rowHeaderWidth;
+    const columnScreenTotalHeight = rowTotalHeight + columnHeaderHeight;
+
+    // 获取范围信息并计算坐标
+    const rangeInfo = attachRangeWithCoord(currentSkeleton, firstSelection.range) || {
+      startX: 0,
+      startY: 0,
+    };
+    const { startX: rangeStartX, startY: rangeStartY } = rangeInfo;
+
+    // 计算图表的边界坐标
+    const startX = Math.min(rangeStartX + DefaultFloatEchartWidth, columnScreenTotalWidth) - DefaultFloatEchartWidth;
+    const endX = Math.min(rangeStartX + DefaultFloatEchartWidth, columnScreenTotalWidth);
+    const startY = Math.min(rangeStartY + DefaultFloatEchartHeight, columnScreenTotalHeight) - DefaultFloatEchartHeight;
+    const endY = Math.min(rangeStartY + DefaultFloatEchartHeight, columnScreenTotalHeight);
+
+    return { endX, endY, startX, startY };
+  }
+
+  // 订阅图表的焦点事件
+  private _subscribeToFocusEvents() {
+    this.disposeWithMe(
+      this._drawingManagerService.focus$.subscribe(params => {
+        if (params && params?.length) {
+          const firstParam = (params?.[0] ?? {}) as any;
+          const { componentKey, drawingId } = firstParam;
+
+          if (drawingId !== this._focusDrawingId && componentKey === JnpfUniverFloatEchartKey) {
+            this._focusDrawingId = drawingId;
+
+            this._commandService?.executeCommand(JnpfCommandIds.focusFloatEchart, firstParam);
+          }
+        }
+      }),
+    );
+  }
+}

+ 140 - 0
src/components/Report/Design/univer/services/sheet-float-image.service.ts

@@ -0,0 +1,140 @@
+import { Disposable, ICommandService, Inject, IUniverInstanceService, UniverInstanceType, Workbook } from '@univerjs/core';
+import { IDrawingManagerService } from '@univerjs/drawing';
+import { DeviceInputEventType, IRenderManagerService } from '@univerjs/engine-render';
+import { SheetsSelectionsService } from '@univerjs/sheets';
+import { SheetCanvasFloatDomManagerService } from '@univerjs/sheets-drawing-ui';
+import { attachRangeWithCoord, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
+
+import { DefaultFloatImageHeight, DefaultFloatImageOption, DefaultFloatImageWidth, JnpfCommandIds, JnpfUniverFloatImageKey } from '../utils/define';
+import { buildUUID } from '../utils/uuid';
+
+export class JnpfSheetsFloatImageService extends Disposable {
+  private _focusDrawingId: null | string = null;
+  private _piniaStoreId: null | string = null;
+
+  private readonly defaultPosition = { endX: DefaultFloatImageWidth, endY: DefaultFloatImageHeight, startX: 100, startY: 100 };
+
+  constructor(
+    @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
+    @ICommandService private readonly _commandService: ICommandService,
+    @IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
+    @IDrawingManagerService private readonly _drawingManagerService: IDrawingManagerService,
+    @Inject(SheetCanvasFloatDomManagerService) private readonly _sheetCanvasFloatDomManagerService: SheetCanvasFloatDomManagerService,
+    @Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
+  ) {
+    super();
+
+    this._subscribeToFocusEvents();
+  }
+
+  // 清空焦点对象的id
+  public clearFocusDrawingId() {
+    if (!this._focusDrawingId) {
+      return;
+    }
+
+    this._focusDrawingId = null;
+  }
+
+  // 创建并插入悬浮图片DOM
+  public insertFloatImage() {
+    this._commandService
+      .executeCommand('sheet.operation.set-cell-edit-visible', {
+        _eventType: DeviceInputEventType.PointerUp,
+        visible: false,
+      })
+      .then();
+
+    const initPosition = this._calculateInitPosition();
+    const domId = `float_image_${buildUUID()}`;
+
+    const addResult = this._sheetCanvasFloatDomManagerService?.addFloatDomToPosition({
+      allowTransform: true,
+      componentKey: JnpfUniverFloatImageKey,
+      initPosition,
+      data: {
+        id: domId,
+        piniaStoreId: this._piniaStoreId,
+      },
+    });
+
+    if (!addResult) {
+      return;
+    }
+
+    const newParams = {
+      domId,
+      drawingId: addResult.id,
+      imageType: 'BASE64',
+      option: JSON.parse(JSON.stringify(DefaultFloatImageOption)),
+    };
+
+    this._commandService.executeCommand(JnpfCommandIds?.insertedFloatImage, newParams).then();
+  }
+
+  // 存入store的id
+  public savePiniaStoreId(data: string) {
+    this._piniaStoreId = data;
+  }
+
+  // 计算初始位置
+  private _calculateInitPosition() {
+    // 获取当前单元格数据
+    const unit = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
+    if (!unit) {
+      console.warn('警告:未找到单元。使用默认位置 (100, 100)。');
+      return this.defaultPosition;
+    }
+
+    const unitId = unit!.getUnitId();
+    const currentSelections = this._selectionManagerService.getCurrentSelections();
+    const firstSelection = currentSelections?.[0] ?? {};
+    if (!firstSelection?.range) {
+      console.warn('警告:未找到选择范围。使用默认位置 (100, 100)。');
+      return this.defaultPosition;
+    }
+
+    // 获取当前表格骨架信息
+    const sheetSkeletonService = this._renderManagerService.getRenderById(unitId)?.with(SheetSkeletonManagerService);
+    const currentSkeleton = sheetSkeletonService?.getCurrent()?.skeleton;
+    if (!currentSkeleton) {
+      console.warn('警告:未找到骨架。使用默认位置 (100, 100)。');
+      return this.defaultPosition;
+    }
+
+    // 解构表格尺寸信息
+    const { columnHeaderHeight = 0, columnTotalWidth = 0, rowHeaderWidth = 0, rowTotalHeight = 0 } = currentSkeleton;
+    const columnScreenTotalWidth = columnTotalWidth + rowHeaderWidth;
+    const columnScreenTotalHeight = rowTotalHeight + columnHeaderHeight;
+
+    // 获取范围信息并计算坐标
+    const rangeInfo = attachRangeWithCoord(currentSkeleton, firstSelection.range) || { startX: 0, startY: 0 };
+    const { startX: rangeStartX, startY: rangeStartY } = rangeInfo;
+
+    // 计算图表的边界坐标
+    const startX = Math.min(rangeStartX + DefaultFloatImageWidth, columnScreenTotalWidth) - DefaultFloatImageWidth;
+    const endX = Math.min(rangeStartX + DefaultFloatImageWidth, columnScreenTotalWidth);
+    const startY = Math.min(rangeStartY + DefaultFloatImageHeight, columnScreenTotalHeight) - DefaultFloatImageHeight;
+    const endY = Math.min(rangeStartY + DefaultFloatImageHeight, columnScreenTotalHeight);
+
+    return { endX, endY, startX, startY };
+  }
+
+  // 订阅图表的焦点事件
+  private _subscribeToFocusEvents() {
+    this.disposeWithMe(
+      this._drawingManagerService.focus$.subscribe(params => {
+        if (params && params?.length) {
+          const firstParam = (params?.[0] ?? {}) as any;
+          const { componentKey, drawingId } = firstParam;
+
+          if (drawingId !== this._focusDrawingId && componentKey === JnpfUniverFloatImageKey) {
+            this._focusDrawingId = drawingId;
+
+            this._commandService?.executeCommand(JnpfCommandIds.focusFloatImage, firstParam);
+          }
+        }
+      }),
+    );
+  }
+}

+ 144 - 0
src/components/Report/Design/univer/services/sheet-image-io-impl.service.ts

@@ -0,0 +1,144 @@
+import type { Workbook, Worksheet } from '@univerjs/core';
+import type { Observable } from 'rxjs';
+
+import {
+  generateRandomId,
+  IImageIoService,
+  IImageIoServiceParam,
+  ImageSourceType,
+  ImageUploadStatusType,
+  Inject,
+  Injector,
+  IUniverInstanceService,
+  Nullable,
+  UniverInstanceType,
+} from '@univerjs/core';
+import { IRenderManagerService } from '@univerjs/engine-render';
+import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
+import { Subject } from 'rxjs';
+
+export const DRAWING_IMAGE_ALLOW_SIZE = 5 * 1024 * 1024;
+
+export const DRAWING_IMAGE_ALLOW_IMAGE_LIST = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/bmp'];
+
+export class JnpfSheetImageIoImplService implements IImageIoService {
+  private _change$ = new Subject<number>();
+
+  change$ = this._change$ as Observable<number>;
+  private _imageSourceCache: Map<string, HTMLImageElement> = new Map();
+
+  private _waitCount = 0;
+
+  constructor(
+    @Inject(Injector) private readonly _accessor: Injector,
+    @IUniverInstanceService protected readonly _univerInstanceService: IUniverInstanceService,
+  ) {}
+
+  addImageSourceCache(source: string, imageSourceType: ImageSourceType, imageSource: Nullable<HTMLImageElement>) {
+    if (imageSourceType === ImageSourceType.BASE64 || imageSource == null) {
+      return;
+    }
+    this._imageSourceCache.set(source, imageSource);
+  }
+
+  cacheImages() {
+    const { sheet, unitId, subUnitId } = this._sheet();
+    const skeleton = this._getSkeleton(unitId, subUnitId);
+    if (!skeleton) {
+      return;
+    }
+    skeleton.resetCache();
+    sheet.getCellMatrix().forValue((_row, _col, value) => {
+      if (value && value.p && value.p.drawings) {
+        Object.keys(value.p.drawings).forEach(drawing => {
+          const drawingItem: any = value.p?.drawings?.[drawing];
+          if (value.p && value.p.drawings && drawingItem && drawingItem.imageSourceType === 'BASE64' && drawingItem.source) {
+            this._imageSourceCache.set(drawingItem.imageSourceType, drawingItem.source);
+            // 强制缓存
+            skeleton.imageCacheMap.getImage(drawingItem.imageSourceType, drawingItem.source);
+          }
+        });
+      }
+    });
+  }
+
+  clearMemory() {}
+
+  async getImage(imageId: string): Promise<string> {
+    return imageId;
+  }
+
+  getImageSourceCache(source: string, imageSourceType: ImageSourceType) {
+    if (imageSourceType === ImageSourceType.BASE64) {
+      const image = new Image();
+      image.src = source;
+      return image;
+    }
+    return this._imageSourceCache.get(source);
+  }
+
+  async saveImage(imageFile: File): Promise<Nullable<IImageIoServiceParam>> {
+    return new Promise((resolve, reject) => {
+      if (!DRAWING_IMAGE_ALLOW_IMAGE_LIST.includes(imageFile.type)) {
+        reject(new Error(ImageUploadStatusType.ERROR_IMAGE_TYPE));
+        this._decreaseWaiting();
+        return;
+      }
+      if (imageFile.size > DRAWING_IMAGE_ALLOW_SIZE) {
+        reject(new Error(ImageUploadStatusType.ERROR_EXCEED_SIZE));
+        this._decreaseWaiting();
+        return;
+      }
+      // 获取上传的图片的宽高
+      const reader = new FileReader();
+      reader.readAsDataURL(imageFile);
+      reader.addEventListener('load', evt => {
+        const replaceSrc = evt.target?.result as string;
+        if (replaceSrc == null) {
+          reject(new Error(ImageUploadStatusType.ERROR_IMAGE));
+          this._decreaseWaiting();
+          return;
+        }
+
+        const imageId = generateRandomId(6);
+        resolve({
+          imageId,
+          imageSourceType: ImageSourceType.BASE64,
+          source: replaceSrc,
+          base64Cache: replaceSrc,
+          status: ImageUploadStatusType.SUCCUSS,
+        });
+
+        this._decreaseWaiting();
+      });
+    });
+  }
+
+  setWaitCount(count: number) {
+    this._waitCount = count;
+    this._change$.next(count);
+  }
+
+  private _decreaseWaiting() {
+    this._waitCount -= 1;
+    this._change$.next(this._waitCount);
+  }
+
+  private _getSkeleton(unitId: string, subUnitId: string) {
+    return this._accessor.get(IRenderManagerService)?.getRenderById(unitId)?.with(SheetSkeletonManagerService)?.getOrCreateSkeleton({ sheetId: subUnitId });
+  }
+
+  private _sheet() {
+    // 使用 `UniverInstanceService` 获取当前类型为 `UNIVER_SHEET` 的活动单元实例
+    const unit: Nullable<Workbook> = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
+    const unitId = unit!.getUnitId();
+    const sheet: Worksheet = unit!.getActiveSheet();
+    const subUnitId = sheet?.getSheetId();
+    return {
+      unit,
+      sheet,
+      unitId,
+      subUnitId,
+    };
+  }
+}

+ 794 - 0
src/components/Report/Design/univer/services/sheet-print-ui.service.ts

@@ -0,0 +1,794 @@
+import type { Workbook } from '@univerjs/core';
+
+import { Disposable, DisposableCollection, generateRandomId, Inject, Injector, IUniverInstanceService } from '@univerjs/core';
+import {
+  CanvasRenderMode,
+  DEFAULT_FONTFACE_PLANE,
+  Engine,
+  IRenderManagerService,
+  ITextWatermarkConfig,
+  Scene,
+  SHEET_VIEWPORT_KEY,
+  Spreadsheet,
+  SpreadsheetSkeleton,
+  Viewport,
+} from '@univerjs/engine-render';
+import { UniverType } from '@univerjs/protocol';
+import { SheetPrintInterceptorService, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
+
+import { translateHeaderFooterText } from '../utils';
+import { JnpfPrintAlignEnum, JnpfPrintCustomFooterEnum, JnpfPrintCustomHeaderEnum, JnpfPrintOtherParamsEnum } from '../utils/define';
+import { buildUUID } from '../utils/uuid';
+
+export class JnpfSheetsPrintUiService extends Disposable {
+  // 获取容器元素
+  get container() {
+    return this._containerEle;
+  }
+
+  get root() {
+    return this._root;
+  }
+
+  private _containerEle = document.createElement('div');
+  private _dirty: boolean = false;
+
+  private _effects: DisposableCollection = new DisposableCollection();
+  private _engine!: Engine;
+  private _key: string;
+  private _layoutConfig: any;
+
+  private _leftTopViewport!: Viewport;
+  private _leftViewport!: Viewport;
+  private _mainViewport!: Viewport;
+
+  private readonly _previewContainerScale: number;
+  private readonly _printConfig: any;
+  private _root: HTMLDivElement = document.createElement('div');
+
+  private _scene!: Scene;
+
+  private readonly _sceneKey: string;
+  private _sheetPrintInterceptorService: SheetPrintInterceptorService;
+  private readonly _skeleton!: SpreadsheetSkeleton;
+  private _spreadsheetObject: any;
+
+  private _topViewport!: Viewport;
+
+  private _totalHeight: number = 0;
+  private _totalWidth: number = 0;
+  private _univerInstanceService: IUniverInstanceService;
+  private readonly _watermarkConfig: any;
+
+  constructor(
+    @Inject(Injector) private readonly _accessor: Injector,
+    layoutConfig = {},
+    printConfig = {},
+    watermarkConfig = {},
+    previewContainerScale = 1, // 打印预览容器的伸缩比
+  ) {
+    super();
+
+    this._univerInstanceService = this._getUniverInstanceService();
+    this._sheetPrintInterceptorService = this._getSheetPrintInterceptorService();
+
+    this._layoutConfig = layoutConfig as any;
+    this._printConfig = printConfig as any;
+    this._watermarkConfig = watermarkConfig as any;
+    this._previewContainerScale = previewContainerScale;
+
+    const { subUnitId, unitId } = this._layoutConfig;
+    this._sceneKey = this._getSceneKey(unitId, subUnitId);
+
+    this._key = `${unitId}_${subUnitId}`;
+    const skeleton = this._getSkeleton(unitId, subUnitId);
+    if (!skeleton) {
+      return;
+    }
+    this._skeleton = skeleton;
+    this._root.className = '__root';
+    this._initRender(); // 初始化渲染
+  }
+
+  public _prepare() {
+    this._resizeViewport();
+    this._effects.dispose();
+    this._effects = new DisposableCollection();
+    const { offsetX, offsetY, scale } = this._getTranslateOffset();
+    const { subUnitId, unitId } = this._layoutConfig;
+    this._root.style.left = `${offsetX}px`;
+    this._root.style.top = `${offsetY}px`;
+    const width = this._totalWidth * scale;
+    const height = this._totalHeight * scale;
+    this._root.style.width = `${width}px`;
+    this._root.style.height = `${height}px`;
+    this._root.style.position = 'absolute';
+    this._root.style.overflow = 'hidden';
+    this._sheetPrintInterceptorService.interceptor.fetchThroughInterceptors(
+      this._sheetPrintInterceptorService.interceptor.getInterceptPoints().PRINTING_DOM_COLLECT,
+    )(this._effects, {
+      unitId,
+      subUnitId,
+      scene: this._scene,
+      engine: this._engine,
+      root: this._root,
+      worksheet: this._skeleton.worksheet,
+      skeleton: this._skeleton,
+      offset: this._getTranslateOffset(),
+    });
+  }
+
+  clearMemory() {
+    this._skeleton.resetCache();
+  }
+
+  /**
+   * 释放资源
+   */
+  dispose() {
+    super.dispose(); // 调用父类的dispose方法
+
+    if (this._containerEle.parentElement) {
+      // eslint-disable-next-line unicorn/prefer-dom-node-remove
+      this._containerEle.parentElement?.removeChild(this._containerEle); // 移除容器
+    }
+  }
+
+  /**
+   * 监听变换并在准备好时渲染
+   */
+  renderOnReady() {
+    const observable = this._engine?.onTransformChange$?.subscribeEvent(() => {
+      this.renderPage();
+    });
+
+    this.disposeWithMe(observable); // 订阅事件并在销毁时清理
+  }
+
+  /**
+   * 渲染页面
+   */
+  renderPage() {
+    this._clearEngineCanvas(); // 清除画布
+
+    this._renderMainScene(); // 渲染主场景(核心内容)
+    this._renderHeaderFooter(); // 渲染页眉页脚
+  }
+
+  /**
+   * 标记当前状态为脏(需要重新渲染)
+   */
+  setDirty(state: boolean) {
+    this._dirty = state;
+  }
+
+  /**
+   * 将电子表格对象添加到场景中
+   */
+  private _addSpreadsheetToScene() {
+    // 创建电子表格对象,传入唯一标识符、骨架对象,并设置初始化选项
+    const spreadsheet = new Spreadsheet('__JnpfSheetPrintRender__', this._skeleton, false);
+
+    // 将电子表格设置为打印模式,确保以打印样式进行渲染
+    spreadsheet.isPrinting = true;
+
+    // 将电子表格对象添加到当前场景中,纳入渲染流程
+    this._scene.addObject(spreadsheet);
+
+    // 缓存电子表格对象,便于后续访问或操作
+    this._spreadsheetObject = spreadsheet;
+
+    // 从布局配置中获取单元 ID 和子单元 ID,用于拦截器逻辑
+    const { subUnitId, unitId } = this._layoutConfig ?? {};
+
+    // 获取拦截器实例与拦截点,确保存在后再进行触发
+    const interceptor = this._sheetPrintInterceptorService?.interceptor;
+    const interceptPoints = interceptor?.getInterceptPoints();
+    const printCollectPoint = interceptPoints?.PRINTING_COMPONENT_COLLECT;
+
+    // 如果拦截器和拦截点存在,则触发拦截逻辑
+    if (interceptor && printCollectPoint) {
+      interceptor.fetchThroughInterceptors(printCollectPoint)(
+        undefined, // 没有特定请求参数,传入 undefined
+        {
+          scene: this._scene,
+          subUnitId,
+          unitId,
+          engine: this._engine,
+          root: this._root,
+          worksheet: this._skeleton.worksheet,
+          skeleton: this._skeleton,
+          offset: this._getTranslateOffset(),
+          spreadsheet: this._spreadsheetObject,
+        }, // 将单元 ID、子单元 ID 和场景信息传递给拦截器
+      );
+    }
+  }
+
+  /**
+   * 添加并初始化多个视口(Viewport)到场景中
+   */
+  private _addViewport() {
+    // 从视口键名常量中解构出各个视口的标识符
+    const { VIEW_MAIN, VIEW_MAIN_LEFT, VIEW_MAIN_LEFT_TOP, VIEW_MAIN_TOP } = SHEET_VIEWPORT_KEY;
+
+    // 创建主视口,负责渲染主要内容区域
+    this._mainViewport = new Viewport(VIEW_MAIN, this._scene);
+
+    // 创建左侧视口,负责渲染左侧固定区域
+    this._leftViewport = new Viewport(VIEW_MAIN_LEFT, this._scene);
+
+    // 创建顶部视口,负责渲染顶部固定区域
+    this._topViewport = new Viewport(VIEW_MAIN_TOP, this._scene);
+
+    // 创建左上角视口,负责渲染左上角交叉固定区域
+    this._leftTopViewport = new Viewport(VIEW_MAIN_LEFT_TOP, this._scene);
+  }
+
+  /**
+   * 清空引擎内画布
+   */
+  private _clearEngineCanvas() {
+    this._engine?.clearCanvas();
+  }
+
+  /**
+   * 绘制水印
+   * @param ctx Canvas 2D 渲染上下文
+   * @param config 水印配置参数
+   */
+  private _drawWatermark(ctx: CanvasRenderingContext2D, config: ITextWatermarkConfig) {
+    const { bold, color, content, direction, fontSize: fontSizeConf, italic, opacity, repeat, rotate, spacingX, spacingY, x, y } = config;
+
+    if (!content) return;
+
+    const fontSize = fontSizeConf * this._previewContainerScale;
+    const { h, w } = this._getPaperContainerSize();
+
+    ctx.save();
+    ctx.globalAlpha = opacity;
+
+    const fontWeight = bold ? 'bold' : 'normal';
+    const fontStyle = italic ? 'italic' : 'normal';
+    ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${DEFAULT_FONTFACE_PLANE}`;
+    ctx.fillStyle = color;
+    ctx.textBaseline = 'top';
+
+    // **计算文本尺寸**
+    const textWidth = ctx.measureText(content).width;
+    const textHeight = fontSize; // 大部分情况下,高度近似等于字体大小
+
+    // **方向控制**
+    let stepX = spacingX + textWidth; // 修正水平间距
+    let startX = x;
+    let textAlign: CanvasTextAlign = 'left';
+
+    if (direction === 'rtl') {
+      stepX = -(spacingX + textWidth);
+      startX = w - x;
+      textAlign = 'right';
+    }
+
+    ctx.textAlign = textAlign;
+
+    if (repeat) {
+      // **修正垂直间距**
+      const stepY = spacingY + textHeight;
+
+      // **计算水印覆盖区域,防止边缘空白**
+      const cols = Math.ceil(w / Math.abs(stepX)) + 2;
+      const rows = Math.ceil(h / stepY) + 2;
+
+      for (let col = -1; col < cols; col++) {
+        for (let row = -1; row < rows; row++) {
+          const posX = startX + col * stepX;
+          const posY = y + row * stepY;
+
+          ctx.save();
+          ctx.translate(posX, posY);
+          ctx.rotate((rotate * Math.PI) / 180);
+          ctx.fillText(content, 0, 0);
+          ctx.restore();
+        }
+      }
+    } else {
+      ctx.save();
+      ctx.translate(startX, y);
+      ctx.rotate((rotate * Math.PI) / 180);
+      ctx.fillText(content, 0, 0);
+      ctx.restore();
+    }
+
+    ctx.restore();
+  }
+
+  /**
+   * 省略号裁剪文本
+   */
+  private _ellipsisText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number) {
+    if (ctx.measureText(text).width <= maxWidth) {
+      return text;
+    }
+
+    let truncated = text;
+    while (ctx.measureText(`${truncated}...`).width > maxWidth && truncated.length > 0) {
+      truncated = truncated.slice(0, -1);
+    }
+
+    return `${truncated}...`;
+  }
+
+  /**
+   * 获取纸张容器的尺寸
+   */
+  private _getPaperContainerSize() {
+    // 从布局配置中解构出纸张尺寸(宽度 w 和高度 h)
+    const { h = 0, w = 0 } = this._layoutConfig?.paperSize ?? {};
+
+    // 检查宽度和高度是否有效
+    if (w <= 0 || h <= 0) {
+      console.warn('纸张尺寸配置无效,宽度或高度小于等于0。');
+    }
+
+    // 根据预览容器的缩放比例,计算实际渲染的尺寸
+    const scaledWidth = w * this._previewContainerScale; // 宽度按缩放比例调整
+    const scaledHeight = h * this._previewContainerScale; // 高度按缩放比例调整
+
+    // 返回计算后的尺寸对象
+    return {
+      h: scaledHeight,
+      w: scaledWidth,
+    };
+  }
+
+  /**
+   * 获取纸张边距(Padding)
+   */
+  private _getPaperPadding() {
+    const { bottom = 0, left = 0, right = 0, top = 0 } = this._layoutConfig?.paperPadding ?? {};
+    const previewContainerScale = this._previewContainerScale;
+
+    return {
+      bottom: bottom * previewContainerScale,
+      left: left * previewContainerScale,
+      right: right * previewContainerScale,
+      top: top * previewContainerScale,
+    };
+  }
+
+  /**
+   * 获得场景专一key
+   * @param unitId
+   * @param subUnitId
+   */
+  private _getSceneKey(unitId: string, subUnitId: string) {
+    return `${unitId}_${subUnitId}_${buildUUID()}`;
+  }
+
+  /**
+   * 获取到sheetPrintInterceptorService
+   */
+  private _getSheetPrintInterceptorService() {
+    return this._accessor.get(SheetPrintInterceptorService);
+  }
+
+  /**
+   * 获取到skeleton
+   * @param unitId
+   * @param subUnitId
+   */
+  private _getSkeleton(unitId: string, subUnitId: string) {
+    return this._accessor.get(IRenderManagerService)?.getRenderById(unitId)?.with(SheetSkeletonManagerService)?.getOrCreateSkeleton({ sheetId: subUnitId });
+  }
+
+  /**
+   * 获取平移偏移量,用于将打印内容定位到页面上的正确位置
+   *
+   * 根据纸张的尺寸、内边距、打印配置中的对齐方式以及总内容的宽度和高度,计算水平和垂直方向的偏移量。
+   * 最终返回的偏移量用于定位打印内容的起始位置,以便它能够根据配置进行正确的对齐。
+   */
+  private _getTranslateOffset() {
+    // 获取纸张的内边距和纸张容器的尺寸
+    const paperContainerSize = this._getPaperContainerSize();
+    const paperPadding = this._getPaperPadding();
+
+    // 解构获取打印配置中的对齐方式(水平对齐和垂直对齐)
+    const { hAlign, vAlign } = this._printConfig;
+
+    // 计算最大缩放比例,确保内容在X和Y方向上都能够缩放适应
+    const scale = Math.max(this._scene.scaleX, this._scene.scaleY);
+
+    // 计算水平偏移量(offsetX)
+    let offsetX: number;
+    if (hAlign === JnpfPrintAlignEnum.start) {
+      // 水平对齐方式为左对齐,偏移量等于左边距
+      offsetX = paperPadding.left;
+    } else if (hAlign === JnpfPrintAlignEnum.end) {
+      // 水平对齐方式为右对齐,偏移量等于纸张宽度减去内容宽度和右边距
+      offsetX = paperContainerSize.w - this._totalWidth * scale - paperPadding.right;
+    } else {
+      // 水平对齐方式为居中对齐,计算居中位置
+      offsetX = paperPadding.left + (paperContainerSize.w - this._totalWidth * scale - paperPadding.left - paperPadding.right) / 2;
+    }
+
+    // 计算垂直偏移量(offsetY)
+    let offsetY: number;
+    if (vAlign === JnpfPrintAlignEnum.start) {
+      // 垂直对齐方式为上对齐,偏移量等于上边距
+      offsetY = paperPadding.top;
+    } else if (vAlign === JnpfPrintAlignEnum.end) {
+      // 垂直对齐方式为下对齐,偏移量等于纸张高度减去内容高度和下边距
+      offsetY = paperContainerSize.h - this._totalHeight * scale - paperPadding.bottom;
+    } else {
+      // 垂直对齐方式为居中对齐,计算居中位置
+      offsetY = (paperContainerSize.h - this._totalHeight * scale - paperPadding.top - paperPadding.bottom) / 2 + paperPadding.top;
+    }
+
+    // 返回四舍五入后的偏移量,确保偏移值为整数
+    return { offsetX: Math.round(offsetX), offsetY: Math.round(offsetY), scale };
+  }
+
+  /**
+   * 获取到univerInstanceService
+   */
+  private _getUniverInstanceService() {
+    return this._accessor.get(IUniverInstanceService);
+  }
+
+  /**
+   * 初始化渲染引擎、场景、视口,并配置缩放比例和资源管理。
+   */
+  private _initRender() {
+    // 获取纸张容器的宽度和高度,确保渲染区域的尺寸准确
+    const { h: paperContainerH, w: paperContainerW } = this._getPaperContainerSize();
+
+    // 设置像素比为 1,通常用于标准屏幕显示
+    const pixelRatio = 1;
+
+    // 创建渲染引擎实例
+    // 参数:宽度、高度、像素比、渲染模式
+    this._engine = new Engine('', {
+      elementWidth: paperContainerW,
+      elementHeight: paperContainerH,
+      dpr: pixelRatio,
+      renderMode: CanvasRenderMode.Printing, // 设置渲染模式为打印模式(Printing),确保画布的内容适配打印需求
+    });
+
+    // 创建场景对象,并将其与渲染引擎关联
+    // 参数:场景的唯一标识符、渲染引擎实例
+    this._scene = new Scene(this._sceneKey, this._engine);
+
+    // 将渲染引擎绑定到指定的 DOM 容器中
+    // 参数:容器元素、是否清除现有内容(false 表示不清除)
+    this._engine.setContainer(this._containerEle, false);
+    this._engine
+      .getCanvas()
+      .getContext()
+      .setId(`${this._key}_${generateRandomId(4)}`);
+    // 从布局配置中获取打印缩放比例,若未配置则默认为 1
+    const { printScale = 1 } = this._layoutConfig;
+
+    // 计算场景的最终缩放比例,结合打印缩放比例和预览容器的缩放比例
+    const sceneScale = printScale * this._previewContainerScale;
+
+    // 将计算得到的缩放比例应用到场景中
+    // 参数:x 方向缩放比例,y 方向缩放比例
+    this._scene.scale(sceneScale, sceneScale);
+
+    // 将电子表格组件添加到场景中
+    this._addSpreadsheetToScene();
+
+    // 创建视口并与当前场景关联,确保视口正确渲染内容
+    this._addViewport();
+
+    // 注册资源销毁回调,确保在对象生命周期结束时释放资源,防止内存泄漏
+    this.disposeWithMe({
+      dispose: () => {
+        // 释放引擎资源,确保渲染引擎的内存被回收
+        this._engine.dispose();
+
+        // 释放场景资源,确保场景相关的内存被回收
+        this._scene.dispose();
+        this.clearMemory();
+      },
+    });
+  }
+
+  /**
+   * 渲染页眉和页脚
+   */
+  private _renderHeaderFooter() {
+    // 从布局配置中解构获取单元ID和子单元ID
+    const { subUnitId, unitId } = this._layoutConfig ?? {};
+
+    // 获取工作簿对象
+    const workbook = this._univerInstanceService?.getUnit<Workbook>(unitId, UniverType.UNIVER_SHEET);
+    if (!workbook) return; // 如果未找到工作簿,直接返回
+
+    // 获取工作表对象
+    const worksheet = workbook?.getSheetBySheetId(subUnitId);
+    if (!worksheet) return; // 如果未找到工作表,直接返回
+
+    // 获取Canvas上下文对象
+    const ctx = this._engine?.getCanvas()?.getContext();
+    ctx.save(); // 保存当前绘图状态
+    ctx.font = `13px ${DEFAULT_FONTFACE_PLANE}`; // 设置字体样式
+
+    // 获取纸张尺寸并计算绘制位置
+    const { h, w } = this._getPaperContainerSize();
+    const x = 20 * this._previewContainerScale;
+    const y = 20 * this._previewContainerScale;
+
+    // 获取页眉页脚的参数
+    const { headerFooterParams, headerFooterType } = this._printConfig ?? {};
+
+    if (headerFooterType === 'default') {
+      const maxTextWidth = (w - 2 * x) / 2;
+
+      // 【左上角】渲染工作簿名称
+      if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.workbookTitle)) {
+        const truncatedText = this._ellipsisText(ctx, this._printConfig.workbookTitleText, maxTextWidth);
+        ctx.fillText(truncatedText, x, y);
+      }
+
+      // 【右上角】渲染工作表名称
+      if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.worksheetTitle)) {
+        const sheetName = worksheet?.getName() ?? '';
+        const truncatedText = this._ellipsisText(ctx, sheetName, maxTextWidth);
+        const textMetrics = ctx.measureText(truncatedText);
+        const textWidth = textMetrics.width;
+        ctx.fillText(truncatedText, w - textWidth - x, y); // 右对齐
+      }
+
+      // 【左下角】渲染日期和时间
+      if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printDate) || headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printTime)) {
+        let dateStr = new Date().toLocaleString();
+        if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printDate) && !headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printTime)) {
+          dateStr = new Date().toLocaleDateString();
+        } else if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printTime) && !headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.printDate)) {
+          dateStr = new Date().toLocaleTimeString();
+        }
+
+        const truncatedText = this._ellipsisText(ctx, dateStr, maxTextWidth);
+        ctx.fillText(truncatedText, x, h - y); // 绘制时间/日期
+      }
+
+      // 【右下角】渲染页码
+      if (headerFooterParams?.includes(JnpfPrintOtherParamsEnum?.pageNumber)) {
+        const pageNumberText = `${this._layoutConfig?.sheetPageNumber}`;
+        const truncatedText = this._ellipsisText(ctx, pageNumberText, maxTextWidth);
+        const textMetrics = ctx.measureText(truncatedText);
+        const textWidth = textMetrics.width;
+        ctx.fillText(truncatedText, w - textWidth - x, h - y); // 右对齐
+      }
+    } else {
+      const maxTextWidth = (w - 3 * x) / 3;
+
+      const printConfigCache = {
+        ...this._printConfig,
+        bookPageNumber: this._layoutConfig?.bookPageNumber,
+        bookTotalPage: this._layoutConfig?.bookTotalPage,
+        sheetPageNumber: this._layoutConfig?.sheetPageNumber,
+        sheetTotalPage: this._layoutConfig?.sheetTotalPage,
+        workSheetTitleText: worksheet?.getName(),
+      };
+
+      // 左上角渲染
+      if (headerFooterParams?.includes(JnpfPrintCustomHeaderEnum?.leftTop)) {
+        const text = translateHeaderFooterText(JnpfPrintCustomHeaderEnum.leftTop, printConfigCache);
+        const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
+        ctx.fillText(truncatedText, x, y);
+      }
+
+      // 中上角渲染
+      if (headerFooterParams?.includes(JnpfPrintCustomHeaderEnum?.centerTop)) {
+        const text = translateHeaderFooterText(JnpfPrintCustomHeaderEnum.centerTop, printConfigCache);
+        const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
+        const textMetrics = ctx.measureText(truncatedText);
+        const textWidth = textMetrics.width;
+        ctx.fillText(truncatedText, (w - textWidth) / 2, y);
+      }
+
+      // 右上角渲染
+      if (headerFooterParams?.includes(JnpfPrintCustomHeaderEnum?.rightTop)) {
+        const text = translateHeaderFooterText(JnpfPrintCustomHeaderEnum.rightTop, printConfigCache);
+        const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
+        const textMetrics = ctx.measureText(truncatedText);
+        const textWidth = textMetrics.width;
+        ctx.fillText(truncatedText, w - textWidth - x, y);
+      }
+
+      // 左下角渲染
+      if (headerFooterParams?.includes(JnpfPrintCustomFooterEnum?.leftBottom)) {
+        const text = translateHeaderFooterText(JnpfPrintCustomFooterEnum.leftBottom, printConfigCache);
+        const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
+        ctx.fillText(truncatedText, x, h - y);
+      }
+
+      // 中下角渲染
+      if (headerFooterParams?.includes(JnpfPrintCustomFooterEnum?.centerBottom)) {
+        const text = translateHeaderFooterText(JnpfPrintCustomFooterEnum.centerBottom, printConfigCache);
+        const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
+        const textMetrics = ctx.measureText(truncatedText);
+        const textWidth = textMetrics.width;
+        ctx.fillText(truncatedText, (w - textWidth) / 2, h - y);
+      }
+
+      // 右下角渲染
+      if (headerFooterParams?.includes(JnpfPrintCustomFooterEnum?.rightBottom)) {
+        const text = translateHeaderFooterText(JnpfPrintCustomFooterEnum.rightBottom, printConfigCache);
+        const truncatedText = this._ellipsisText(ctx, text, maxTextWidth);
+        const textMetrics = ctx.measureText(truncatedText);
+        const textWidth = textMetrics.width;
+        ctx.fillText(truncatedText, w - textWidth - x, h - y);
+      }
+    }
+
+    ctx.restore(); // 恢复绘图状态
+  }
+
+  /**
+   * 渲染主场景
+   */
+  private _renderMainScene() {
+    // 如果场景被标记为脏,需要重新调整视口大小
+    this._dirty && this._prepare();
+
+    // 获取Canvas的绘图上下文
+    const context = this._engine.getCanvas().getContext();
+
+    // 获取打印配置中的网格线设置和偏移量
+    const { gridlines } = this._printConfig;
+    const { offsetX, offsetY } = this._getTranslateOffset();
+
+    // 根据网格线设置禁用或启用网格线
+    this._spreadsheetObject.setForceDisableGridlines(!gridlines);
+
+    // 强制标记表格为脏,以便重新渲染
+    this._spreadsheetObject.makeForceDirty();
+    this._scene.makeDirty();
+
+    // 保存当前绘图上下文状态
+    context.save();
+    // 应用偏移量
+    context.translateWithPrecision(offsetX, offsetY);
+
+    // 渲染场景
+    this._scene.render();
+
+    // 恢复绘图上下文状态
+    context.restore();
+
+    // 添加水印(判断下,如果不需要水印就不用配置了)!!!目前和0.6.2版本一致,都是盖在了内容上面
+    if (this._watermarkConfig?.show) {
+      this._drawWatermark(context, this._watermarkConfig?.config);
+    }
+  }
+
+  /**
+   * 调整视口大小和滚动值
+   */
+  private _resizeViewport() {
+    // 从布局配置中解构冻结和范围设置
+    const { freeze, printScale = 1, range } = this._layoutConfig;
+    // 从冻结设置中解构x轴和y轴的分割点以及起始行列
+    const { startColumn, startRow, xSplit, ySplit } = freeze;
+    // 从骨架配置中解构行头宽度和列头高度
+    const { columnHeaderHeight, rowHeaderWidth } = this._skeleton;
+
+    // 缩放场景以适应预览容器的缩放比例
+    const sceneScale = printScale * this._previewContainerScale;
+    this._scene.scale(sceneScale, sceneScale);
+
+    // 获取无合并单元格的位置,基于起始行列
+    const cellPosition = this._skeleton?.getNoMergeCellPositionByIndexWithNoHeader(startRow, startColumn);
+    // 获取无合并单元格的位置,基于起始行列减去分割点
+    const cellPositionSplit = this._skeleton?.getNoMergeCellPositionByIndexWithNoHeader(startRow - ySplit, startColumn - xSplit);
+
+    // 计算起始X分割位置
+    const startXSplit = xSplit > 0 ? cellPosition.startX - cellPositionSplit.startX : 0;
+    // 计算起始Y分割位置
+    const startYSplit = ySplit > 0 ? cellPosition.startY - cellPositionSplit.startY : 0;
+
+    // 获取场景的精度缩放比例
+    const precisionScale = this._scene.getPrecisionScale();
+    // 定义用于缩放X值的函数
+    const getScaleX = (value: number) => Math.round(value * precisionScale.scaleX) / precisionScale.scaleX;
+    // 定义用于缩放Y值的函数
+    const getScaleY = (value: number) => Math.round(value * precisionScale.scaleY) / precisionScale.scaleY;
+
+    // 获取范围起始单元格的位置
+    const startCellPosition = this._skeleton.getNoMergeCellPositionByIndexWithNoHeader(range.startRow, range.startColumn);
+    // 计算结束单元格的位置,并处理边界情况
+    const endCellPosition = this._skeleton.getNoMergeCellPositionByIndexWithNoHeader(range.endRow, range.endColumn);
+
+    // 定义范围对象,包含起始和结束的X、Y坐标
+    const _range = {
+      endX: xSplit > 0 ? cellPosition.startX : 0,
+      endY: ySplit > 0 ? cellPosition.startY : 0,
+      startX: xSplit > 0 ? cellPositionSplit.startX : 0,
+      startY: ySplit > 0 ? cellPositionSplit.startY : 0,
+    };
+
+    const endX = Math.max(0, getScaleX(endCellPosition.endX) - Math.max(startCellPosition.startX, _range.endX));
+    const endY = Math.max(0, getScaleY(endCellPosition.endY) - getScaleY(Math.max(startCellPosition.startY, _range.endY)));
+    // 计算缩放比例
+    const scale = 1 / Math.max(precisionScale.scaleX, precisionScale.scaleY);
+
+    this._totalWidth = startXSplit + endX + scale;
+    this._totalHeight = startYSplit + endY + scale;
+
+    const startSplit = { x: startXSplit, y: startYSplit };
+
+    const scaleXY = {
+      x: getScaleX(endCellPosition.endX) - getScaleX(endX) - getScaleX(Math.max(startCellPosition.startX, _range.endX)),
+      y: getScaleY(endCellPosition.endY) - getScaleY(endY) - getScaleY(Math.max(startCellPosition.startY, _range.endY)),
+    };
+
+    // 如果x轴和y轴都有分割点,启用左上角视口并调整其大小和滚动值
+    if (xSplit > 0 && ySplit > 0) {
+      this._leftTopViewport.enable();
+      this._leftTopViewport.resizeWhenFreezeChange({
+        height: getScaleY(startSplit.y),
+        left: 0,
+        top: 0,
+        width: getScaleX(startSplit.x),
+      });
+      this._leftTopViewport.updateScrollVal({
+        viewportScrollX: getScaleX(_range.startX) + getScaleX(rowHeaderWidth),
+        viewportScrollY: getScaleY(_range.startY) + getScaleY(columnHeaderHeight),
+      });
+    } else {
+      // 否则禁用左上角视口
+      this._leftTopViewport.disable();
+    }
+
+    // 如果x轴有分割点,启用左视口并调整其大小和滚动值
+    if (xSplit > 0) {
+      this._leftViewport.enable();
+      this._leftViewport.resizeWhenFreezeChange({
+        height: getScaleY(endY) + scale + scaleXY.y,
+        left: 0,
+        top: getScaleY(startSplit.y),
+        width: getScaleX(startSplit.x),
+      });
+      this._leftViewport.updateScrollVal({
+        viewportScrollX: getScaleX(_range.startX) + getScaleX(rowHeaderWidth),
+        viewportScrollY: getScaleY(startCellPosition.startY) + getScaleY(columnHeaderHeight) - getScaleY(startSplit.y),
+      });
+    } else {
+      // 否则禁用左视口
+      this._leftViewport.disable();
+    }
+
+    // 如果y轴有分割点,启用顶视口并调整其大小和滚动值
+    if (ySplit > 0) {
+      this._topViewport.enable();
+      this._topViewport.resizeWhenFreezeChange({
+        height: getScaleY(startSplit.y),
+        left: getScaleX(startSplit.x),
+        top: 0,
+        width: getScaleX(endX) + scale + scaleXY.x,
+      });
+      this._topViewport.updateScrollVal({
+        viewportScrollX: getScaleX(startCellPosition.startX) + getScaleX(rowHeaderWidth) - getScaleX(startSplit.x),
+        viewportScrollY: getScaleY(_range.startY) + getScaleY(columnHeaderHeight),
+      });
+    } else {
+      // 否则禁用顶视口
+      this._topViewport.disable();
+    }
+
+    // 调整主视口的大小和滚动值
+    this._mainViewport.resizeWhenFreezeChange({
+      height: getScaleY(endY) + scale + scaleXY.y,
+      left: getScaleX(startSplit.x),
+      top: getScaleY(startSplit.y),
+      width: getScaleX(endX) + scale + scaleXY.x,
+    });
+    this._mainViewport.updateScrollVal({
+      viewportScrollX: getScaleX(startCellPosition.startX) + getScaleX(rowHeaderWidth) - getScaleX(startSplit.x),
+      viewportScrollY: getScaleY(startCellPosition.startY) + getScaleY(columnHeaderHeight) - getScaleY(startSplit.y),
+    });
+  }
+}

+ 791 - 0
src/components/Report/Design/univer/services/sheet-print.service.ts

@@ -0,0 +1,791 @@
+import type { IFreeze, Workbook } from '@univerjs/core';
+
+import { Disposable, Inject, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
+import { IRenderManagerService, SpreadsheetSkeleton } from '@univerjs/engine-render';
+import { SheetsSelectionsService } from '@univerjs/sheets';
+import { SheetPrintInterceptorService, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
+
+import { isEmptyObject, isNullOrUndefined } from '../utils';
+import {
+  DefaultPrintConfig,
+  JnpfPaperPaddingForType,
+  JnpfPrintAreaEnum,
+  JnpfPrintCustomFooterEnum,
+  JnpfPrintCustomHeaderEnum,
+  JnpfPrintDirectionEnum,
+  JnpfPrintFreezeEnum,
+  JnpfPrintOtherParamsEnum,
+  JnpfPrintPaperSizeForType,
+  JnpfPrintScaleEnum,
+} from '../utils/define';
+import { buildUUID } from '../utils/uuid';
+
+export class JnpfSheetsPrintService extends Disposable {
+  private _configParams: Record<string, any> = {};
+  private _layoutHeaderFooterParams: any[] = [];
+
+  constructor(
+    @IUniverInstanceService protected readonly _univerInstanceService: IUniverInstanceService,
+    @IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
+    @Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
+    @Inject(SheetPrintInterceptorService) private readonly _sheetPrintInterceptorService: SheetPrintInterceptorService,
+  ) {
+    super();
+  }
+
+  /**
+   * 根据配置获取打印布局。
+   * @param {any} printConfig - 打印配置对象,默认为 `DefaultPrintConfig`。
+   */
+  getLayouts(printConfig: any = DefaultPrintConfig) {
+    // 分析布局的页眉和页脚配置,并将结果存储在 `_layoutHeaderFooterParams` 中
+    this._layoutHeaderFooterParams = this._analyseLayoutHeaderFooterConfig(printConfig);
+
+    // 获取参数出来判断是不是存在冻结
+    const subUnitId = this._getWorkbook()!.getActiveSheet()?.getSheetId();
+    if (!subUnitId) {
+      return;
+    }
+
+    const snapshot = this._getWorkbook()!.save();
+
+    let freeze = [];
+
+    switch (printConfig.printArea) {
+      case JnpfPrintAreaEnum.currentSheet: {
+        const { xSplit = 0, ySplit = 0 } = snapshot?.sheets?.[subUnitId]?.freeze ?? {};
+
+        // 正确的冻结状态判断
+        const colHasFreeze = xSplit > 0; // 列冻结(xSplit 对应列冻结)
+        const rowHasFreeze = ySplit > 0; // 行冻结(ySplit 对应行冻结)
+
+        // 判断冻结状态【判断是单体还是全部,全部的话判断有开就有,没开就没有】
+        freeze = [
+          rowHasFreeze && printConfig?.yFreeze && JnpfPrintFreezeEnum.row, // 行冻结
+          colHasFreeze && printConfig?.xFreeze && JnpfPrintFreezeEnum.column, // 列冻结
+        ].filter(Boolean); // 过滤掉 false/null/undefined
+
+        break;
+      }
+      case JnpfPrintAreaEnum.partSheets: {
+        const { sheets } = snapshot;
+        const { partSheets = [] } = printConfig;
+
+        const colHasFreeze =
+          Object.values(sheets).some((sheet: any) => {
+            return partSheets.includes(sheet.id) ? sheet?.freeze?.xSplit > 0 : false;
+          }) && printConfig?.xFreeze;
+        const rowHasFreeze =
+          Object.values(sheets).some((sheet: any) => {
+            return partSheets.includes(sheet.id) ? sheet?.freeze?.ySplit > 0 : false;
+          }) && printConfig?.yFreeze;
+
+        if (colHasFreeze) {
+          freeze.push('column');
+        }
+        if (rowHasFreeze) {
+          freeze.push('row');
+        }
+
+        break;
+      }
+      case JnpfPrintAreaEnum.workbook: {
+        const { sheets } = snapshot;
+        const colHasFreeze = Object.values(sheets).some((sheet: any) => sheet?.freeze?.xSplit > 0) && printConfig?.xFreeze;
+        const rowHasFreeze = Object.values(sheets).some((sheet: any) => sheet?.freeze?.ySplit > 0) && printConfig?.yFreeze;
+
+        if (colHasFreeze) {
+          freeze.push('column');
+        }
+        if (rowHasFreeze) {
+          freeze.push('row');
+        }
+
+        break;
+      }
+      // No default
+    }
+
+    // 提取剩余配置
+    const { xFreeze, yFreeze, ...restPrintConfig } = printConfig ?? {};
+
+    // 将配置参数存储在 `_configParams` 中
+    this._configParams = {
+      ...restPrintConfig,
+      freeze,
+    };
+
+    // 获取所有需要打印的范围
+    const printRanges = this._computePrintRanges();
+    // 初始化空数组,用于存储计算出的布局
+    const layouts: any[] = [];
+
+    // 遍历每个打印范围,计算对应的页面布局
+    printRanges?.forEach(range => {
+      // 如果当前范围存在,继续处理
+      if (range) {
+        // 计算页面布局
+        const pageLayout = this._computePageLayout(range);
+
+        // 如果页面布局存在,将其添加到 layouts 数组中
+        if (pageLayout) {
+          layouts.push(pageLayout);
+        }
+      }
+    });
+
+    // 获取纸张大小
+    const paperSize = this._getPaperSize();
+    // 获取纸张边距
+    const paperPadding = this._getPaperPadding();
+
+    // 整个需要打印的页码数量
+    const bookTotalPage = layouts.reduce((total, layout) => total + layout.pages.length, 0);
+
+    let bookPageNumber = 1;
+    const pagesLayout = layouts.flatMap(({ pages: pageRanges, ...info }) =>
+      pageRanges.map((range: any, rangeIndex: any) => ({
+        ...info,
+        bookPageNumber: bookPageNumber++,
+        bookTotalPage,
+        browserPreviewScale: 1,
+        layoutId: `layout_${buildUUID()}`,
+        paperPadding,
+        paperSize,
+        range,
+        sheetPageNumber: rangeIndex + 1, // layout 内部页码从 1 开始
+        sheetTotalPage: pageRanges?.length,
+      })),
+    );
+
+    return {
+      pagesLayout,
+      paperSize,
+      printConfig: {
+        ...this._configParams,
+        currentDateTime: new Date(Date.now() + 8 * 3600 * 1000).toJSON().slice(0, 19).replace('T', ' '),
+        headerFooterParams: this._layoutHeaderFooterParams,
+      },
+    };
+  }
+
+  /**
+   * 解析页面布局中的页眉和页脚配置。
+   * @param {any} printConfig - 页眉页脚的配置对象,包含多个布尔值属性来决定是否显示某些信息。
+   * @returns 返回需要在页眉或页脚中包含的参数数组。
+   */
+  private _analyseLayoutHeaderFooterConfig(printConfig: any) {
+    // 定义一个数组,用于存储需要包含的页眉和页脚参数
+    const layoutHeaderFooterParams = [];
+
+    if (printConfig.headerFooterType === 'default') {
+      if (printConfig?.workbookTitle) {
+        layoutHeaderFooterParams.push(JnpfPrintOtherParamsEnum.workbookTitle);
+      }
+      if (printConfig?.worksheetTitle) {
+        layoutHeaderFooterParams.push(JnpfPrintOtherParamsEnum.worksheetTitle);
+      }
+      if (printConfig?.printDate) {
+        layoutHeaderFooterParams.push(JnpfPrintOtherParamsEnum.printDate);
+      }
+      if (printConfig?.printTime) {
+        layoutHeaderFooterParams.push(JnpfPrintOtherParamsEnum.printTime);
+      }
+      if (printConfig?.pageNumber) {
+        layoutHeaderFooterParams.push(JnpfPrintOtherParamsEnum.pageNumber);
+      }
+    } else {
+      if (printConfig.customHeaderFooterValue?.leftTop) {
+        layoutHeaderFooterParams.push(JnpfPrintCustomHeaderEnum.leftTop);
+      }
+      if (printConfig.customHeaderFooterValue?.centerTop) {
+        layoutHeaderFooterParams.push(JnpfPrintCustomHeaderEnum.centerTop);
+      }
+      if (printConfig.customHeaderFooterValue?.rightTop) {
+        layoutHeaderFooterParams.push(JnpfPrintCustomHeaderEnum.rightTop);
+      }
+      if (printConfig.customHeaderFooterValue?.leftBottom) {
+        layoutHeaderFooterParams.push(JnpfPrintCustomFooterEnum.leftBottom);
+      }
+      if (printConfig.customHeaderFooterValue?.centerBottom) {
+        layoutHeaderFooterParams.push(JnpfPrintCustomFooterEnum.centerBottom);
+      }
+      if (printConfig.customHeaderFooterValue?.rightBottom) {
+        layoutHeaderFooterParams.push(JnpfPrintCustomFooterEnum.rightBottom);
+      }
+    }
+
+    // 返回最终解析出来的页眉页脚参数数组
+    return layoutHeaderFooterParams;
+  }
+
+  /**
+   * 计算页面布局,根据给定的打印范围和工作表的骨架信息,计算每一页的布局。
+   * @param printRange {any} 打印范围,包含工作簿信息、工作表信息、打印范围、冻结信息、缩放比例及内容尺寸
+   * @returns 返回计算出的页面布局信息
+   */
+  private _computePageLayout(printRange: any) {
+    // 解构打印范围信息
+    const { contentSize, freeze, printScale, range, subUnitId, unitId } = printRange ?? {};
+
+    // 获取工作表的骨架信息
+    const skeleton = this._getUnitSkeleton(unitId, subUnitId);
+    if (!skeleton) {
+      return;
+    }
+
+    // 解构工作表的行高和列宽累计值
+    const { columnWidthAccumulation, rowHeightAccumulation } = skeleton;
+
+    // 解构打印范围的行列信息
+    const { endColumn, endRow, startColumn, startRow } = range;
+
+    const calcRow = (start: any) => {
+      let end = start;
+      const offset = start === 0 ? 0 : rowHeightAccumulation[start - 1];
+      for (; end < endRow; end++) {
+        const current = (rowHeightAccumulation[end] - offset) * printScale;
+        const next = (rowHeightAccumulation[end + 1] - offset) * printScale;
+        if (current > contentSize.h || (current < contentSize.h && next > contentSize.h)) return { endRow: end - 1, startRow: start };
+      }
+      return { endRow: end, startRow: start };
+    };
+
+    const calcColumn = (start: any) => {
+      let end = start;
+      const offset = start === 0 ? 0 : columnWidthAccumulation[start - 1];
+      for (; end < endColumn; end++) {
+        const current = (columnWidthAccumulation[end] - offset) * printScale;
+        const next = (columnWidthAccumulation[end + 1] - offset) * printScale;
+
+        if (current >= contentSize.w || (current < contentSize.w && next > contentSize.w)) {
+          return { endColumn: end, startColumn: start };
+        }
+      }
+      return { endColumn: end, startColumn: start };
+    };
+
+    // 计算所有行的分页范围
+    const rows = [];
+    for (let start = startRow; start <= endRow; ) {
+      const row = calcRow(start);
+      rows.push(row);
+      start = row.endRow + 1; // 移动到下一个分页起始行
+    }
+
+    // 计算所有列的分页范围
+    const columns: any[] = [];
+    for (let start = startColumn; start <= endColumn; ) {
+      const column = calcColumn(start);
+      columns.push(column);
+      start = column.endColumn + 1; // 移动到下一个分页起始列
+    }
+
+    // 将行列的分页信息组合成每一页的布局
+    const pages: any[] = [];
+    rows.forEach(row => {
+      columns.forEach(column => {
+        pages.push({ ...row, ...column });
+      });
+    });
+
+    // 返回计算的页面布局信息,包括单位ID、子单位ID、页面信息、冻结信息和缩放比例
+    return { freeze, pages, printScale, subUnitId, unitId };
+  }
+
+  /**
+   * 计算并返回打印页面的范围,包括内容区域、冻结行列以及打印比例。
+   * @param {string} subUnitId - 工作表的唯一标识符。
+   * @param {any} sheetRange - 需要打印的工作表范围,包含起始和结束的行列。
+   * @returns 返回打印页面的范围、冻结信息、内容区域和打印比例。
+   */
+  private _computePrintPageRange(subUnitId: string, sheetRange: any) {
+    // 获取工作簿实例
+    const workbook = this._getWorkbook();
+
+    // 如果没有获取到工作簿,返回 undefined
+    if (!workbook) {
+      return;
+    }
+
+    // 获取工作簿的唯一标识符
+    const unitId = workbook!.getUnitId();
+
+    // 获取纸张大小
+    const paperSize = this._getPaperSize();
+    if (!paperSize) {
+      return;
+    }
+
+    // 获取纸张的边距
+    const paperPadding = this._getPaperPadding();
+    if (!paperPadding) {
+      return;
+    }
+
+    // 计算内容区域的大小,减去左右、上下的边距
+    const contentSize = {
+      h: paperSize.h - (paperPadding.top + paperPadding.bottom),
+      w: paperSize.w - (paperPadding.left + paperPadding.right),
+    };
+
+    // 如果起始行列小于等于 -1,返回默认的打印范围,表示无效的打印范围
+    if (sheetRange?.startRow <= -1 || sheetRange?.startColumn <= -1) {
+      return {
+        contentSize,
+        freeze: { startColumn: -1, startRow: -1, xSplit: 0, ySplit: 0 },
+        printScale: 1,
+        range: { endColumn: 0, endRow: 0, startColumn: 0, startRow: 0 },
+        subUnitId,
+        unitId,
+      };
+    }
+
+    // 获取工作表实例
+    const worksheet = workbook!.getSheetBySheetId(subUnitId);
+    if (!worksheet) {
+      return;
+    }
+
+    // 获取工作表的冻结信息
+    const freeze = worksheet?.getFreeze();
+    if (!freeze) {
+      return;
+    }
+
+    // 获取工作表的骨架信息(如列宽、行高等)
+    const skeleton = this._getUnitSkeleton(unitId, subUnitId);
+    if (!skeleton) {
+      return;
+    }
+
+    // 计算打印比例
+    const printScale = this._computePrintScale(skeleton, sheetRange, freeze);
+
+    // 初始化打印范围和冻结信息的默认值
+    let startColumn = -1;
+    let startRow = -1;
+    let xSplit = 0;
+    let ySplit = 0;
+
+    // 获取骨架中的列宽和行高累积数据
+    const { columnWidthAccumulation, rowHeightAccumulation } = skeleton ?? {};
+    let { endColumn: sheetRangeEndColumn, endRow: sheetRangeEndRow, startColumn: sheetRangeStartColumn, startRow: sheetRangeStartRow } = sheetRange ?? {};
+    const { startColumn: freezeStartColumn, startRow: freezeStartRow, xSplit: freezeXSplit, ySplit: freezeYSplit } = freeze ?? {};
+
+    // 处理冻结行(如果配置了冻结行)
+    if (freezeYSplit && this._configParams?.freeze.includes(JnpfPrintFreezeEnum?.row)) {
+      // 如果打印范围的结束行大于等于冻结行,则计算冻结区域的高度并调整打印区域的高度
+      if (sheetRangeEndRow >= freezeStartRow) {
+        const offsetHeight = rowHeightAccumulation[freezeStartRow] - rowHeightAccumulation[freezeStartRow - freezeYSplit];
+
+        if (offsetHeight < contentSize?.h) {
+          startRow = freezeStartRow;
+          ySplit = freezeYSplit;
+          contentSize.h -= offsetHeight;
+        }
+      } else {
+        // 如果打印范围的结束行小于冻结行,则计算冻结区域的高度并调整打印区域的高度
+        startRow = sheetRangeEndRow + 1;
+        ySplit = sheetRangeEndRow + 1 - (freezeStartRow - freezeYSplit);
+        contentSize.h = contentSize?.h - rowHeightAccumulation[freezeStartRow] - rowHeightAccumulation[freezeStartRow - freezeYSplit];
+      }
+    }
+
+    // 更新起始行,确保不小于冻结区域的起始行
+    sheetRangeStartRow = Math.max(startRow, sheetRangeStartRow);
+
+    // 处理冻结列(如果配置了冻结列)
+    if (freezeXSplit && this._configParams?.freeze.includes(JnpfPrintFreezeEnum?.column)) {
+      // 如果打印范围的结束列大于等于冻结列,则计算冻结区域的宽度并调整打印区域的宽度
+      if (sheetRangeEndColumn >= freezeStartColumn) {
+        const offsetWidth =
+          printScale * (columnWidthAccumulation[freezeStartColumn - 1] - (columnWidthAccumulation[freezeStartColumn - freezeXSplit - 1] || 0));
+
+        if (offsetWidth < contentSize?.w) {
+          startColumn = freezeStartColumn;
+          xSplit = freezeXSplit;
+          contentSize.w -= offsetWidth;
+        }
+      } else {
+        // 如果打印范围的结束列小于冻结列,则计算冻结区域的宽度并调整打印区域的宽度
+        const offsetWidth =
+          printScale * (columnWidthAccumulation[freezeStartColumn - 1] - (columnWidthAccumulation[freezeStartColumn - freezeXSplit] - 1 || 0));
+
+        if (offsetWidth < contentSize?.w) {
+          const startColumnCache = sheetRangeEndColumn + 1;
+          const xSplitCache = sheetRangeEndColumn + 1 - (freezeStartColumn - freezeXSplit);
+
+          startColumn = startColumnCache;
+          xSplit = xSplitCache;
+          contentSize.w -= offsetWidth;
+        }
+      }
+    }
+
+    // 更新起始列,确保不小于冻结区域的起始列
+    sheetRangeStartColumn = Math.max(startColumn, sheetRangeStartColumn);
+
+    // 返回计算出的打印页面范围,包含打印区域、冻结信息、内容大小和打印比例
+    return {
+      contentSize,
+      freeze: { startColumn, startRow, xSplit, ySplit },
+      printScale,
+      range: { endColumn: sheetRangeEndColumn, endRow: sheetRangeEndRow, startColumn: sheetRangeStartColumn, startRow: sheetRangeStartRow },
+      subUnitId,
+      unitId,
+    };
+  }
+
+  /**
+   * 计算打印范围,根据配置的区域选择合适的打印范围。
+   * @returns 返回包含一个或多个打印范围的数组。
+   */
+  private _computePrintRanges() {
+    // 初始化空数组 result,用于存储计算的打印范围
+    let result: any[] = [];
+
+    // 根据配置的打印区域 执行不同的处理
+    switch (this._configParams.printArea) {
+      // 如果区域是当前选择的区域
+      case JnpfPrintAreaEnum.currentSelection: {
+        // 计算当前选择区域的打印范围,并返回该范围的数组
+        result = [this._computePrintSelectionRange()];
+        break;
+      }
+
+      // 如果区域是当前工作表
+      case JnpfPrintAreaEnum.currentSheet: {
+        // 获取当前活动工作表的 ID
+        const subUnitId = this._getWorkbook()!.getActiveSheet()?.getSheetId();
+
+        // 如果工作表 ID 不存在,则跳出
+        if (!subUnitId) {
+          break;
+        }
+
+        // 计算当前工作表的打印范围
+        const range = this._computePrintSheetRange(subUnitId);
+        // 如果计算结果有效,返回一个包含该范围的数组
+        result = range ? [range] : [];
+        break;
+      }
+
+      // 如果区域是部分工作簿
+      case JnpfPrintAreaEnum.partSheets: {
+        const partSheets = new Set(this._configParams.partSheets ?? []);
+        // 获取工作簿中的所有工作表
+        const sheets = this._getWorkbook()!.getSheets();
+
+        const targetSheets = sheets.filter(sheet => partSheets.has(sheet?.getSheetId()));
+
+        // 对每个工作表计算打印范围,并过滤掉无效的范围
+        result = targetSheets
+          ?.map(sheet => {
+            const subUnitId = sheet?.getSheetId();
+            // 对每个工作表计算并返回其打印范围
+            return this._computePrintSheetRange(subUnitId);
+          })
+          .filter(Boolean); // 过滤掉无效值(如 null 或 undefined)
+        break;
+      }
+
+      // 如果区域是整个工作簿
+      case JnpfPrintAreaEnum.workbook: {
+        // 获取工作簿中的所有工作表
+        const sheets = this._getWorkbook()!.getSheets();
+
+        // 对每个工作表计算打印范围,并过滤掉无效的范围
+        result = sheets
+          ?.map(sheet => {
+            const subUnitId = sheet?.getSheetId();
+            // 对每个工作表计算并返回其打印范围
+            return this._computePrintSheetRange(subUnitId);
+          })
+          .filter(Boolean); // 过滤掉无效值(如 null 或 undefined)
+        break;
+      }
+
+      // 如果区域类型不匹配,返回空数组
+      default: {
+        break;
+      }
+    }
+
+    // 返回计算出的打印范围数组
+    return result;
+  }
+
+  /**
+   * 计算打印比例。
+   * @param {SpreadsheetSkeleton} skeleton - 工作表的骨架信息,包括行高、列宽累积数据。
+   * @param {any} sheetRange - 打印的单元格范围,包括起始行、起始列、结束行、结束列。
+   * @param {IFreeze} freeze - 冻结范围信息,包括冻结的起始行列和冻结的分割范围。
+   * @returns 返回适合的打印缩放比例,范围在 [0, 1] 之间。
+   */
+  private _computePrintScale(skeleton: SpreadsheetSkeleton, sheetRange: any, freeze: IFreeze) {
+    // 解构骨架中的列宽和行高累积信息
+    const { columnWidthAccumulation, rowHeightAccumulation } = skeleton ?? {};
+
+    // 骨架信息为空时,返回默认比例 1
+    if (isNullOrUndefined(columnWidthAccumulation) || isNullOrUndefined(rowHeightAccumulation)) {
+      return 1;
+    }
+
+    // 解构打印范围的起始和结束行列
+    const { endColumn: sheetRangeEndColumn, endRow: sheetRangeEndRow, startColumn: sheetRangeStartColumn, startRow: sheetRangeStartRow } = sheetRange ?? {};
+
+    // 打印范围信息为空时,返回默认比例 1
+    if (
+      isNullOrUndefined(sheetRangeStartRow) ||
+      isNullOrUndefined(sheetRangeStartColumn) ||
+      isNullOrUndefined(sheetRangeEndRow) ||
+      isNullOrUndefined(sheetRangeEndColumn)
+    ) {
+      return 1;
+    }
+
+    // 解构冻结范围的起始行列和分割范围
+    const { startColumn: freezeStartColumn, startRow: freezeStartRow, xSplit: freezeXSplit, ySplit: freezeYSplit } = freeze ?? {};
+
+    // 冻结范围信息为空时,返回默认比例 1
+    if (isNullOrUndefined(freezeXSplit) || isNullOrUndefined(freezeYSplit) || isNullOrUndefined(freezeStartRow) || isNullOrUndefined(freezeStartColumn)) {
+      return 1;
+    }
+
+    // 确定打印范围的起始行:对冻结范围与打印范围取交集
+    const startRow = sheetRangeEndRow < freezeStartRow ? sheetRangeStartRow : Math.max(freezeStartRow, sheetRangeStartRow);
+
+    // 确定打印范围的起始列:对冻结范围与打印范围取交集
+    const startColumn = sheetRangeEndColumn < freezeStartColumn ? sheetRangeStartColumn : Math.max(freezeStartColumn, sheetRangeStartColumn);
+
+    // 获取纸张尺寸
+    const paperSize = this._getPaperSize();
+    if (!paperSize) {
+      return 1;
+    }
+
+    // 获取纸张边距
+    const paperPadding = this._getPaperPadding();
+    if (!paperPadding) {
+      return 1;
+    }
+
+    // 计算冻结区域的宽度(列)和高度(行)
+    const xSplit = freezeXSplit > 0 ? columnWidthAccumulation[freezeStartColumn - 1] - (columnWidthAccumulation[freezeStartColumn - freezeXSplit - 1] || 0) : 0;
+    const ySplit = freezeYSplit > 0 ? rowHeightAccumulation[freezeStartRow - 1] - (rowHeightAccumulation[freezeStartRow - freezeYSplit - 1] || 0) : 0;
+
+    // 计算打印范围的高度和宽度
+    const rowHeight = rowHeightAccumulation[sheetRangeEndRow] - (rowHeightAccumulation[startRow - 1] || 0);
+    const columnWidth = columnWidthAccumulation[sheetRangeEndColumn] - (columnWidthAccumulation[startColumn - 1] || 0);
+
+    // 加上冻结区域的尺寸,得到总的打印高度和宽度
+    const xScale = columnWidth + xSplit;
+    const yScale = rowHeight + ySplit;
+
+    let result = 1;
+
+    // 根据配置参数计算打印比例
+    switch (this._configParams.printScale) {
+      case JnpfPrintScaleEnum.fitHeight: {
+        // 高度适应比例
+        result = Math.min(1, (paperSize.h - paperPadding.top - paperPadding.bottom) / yScale);
+        break;
+      }
+      case JnpfPrintScaleEnum.fitPage: {
+        // 页面完全适应比例(宽度和高度都适应)
+        result = Math.min(1, (paperSize.w - paperPadding.left - paperPadding.right) / xScale, (paperSize.h - paperPadding.top - paperPadding.bottom) / yScale);
+        break;
+      }
+      case JnpfPrintScaleEnum.fitWidth: {
+        // 宽度适应比例
+        result = Math.min(1, (paperSize.w - paperPadding.left - paperPadding.right) / xScale);
+        break;
+      }
+      default: {
+        break;
+      }
+    }
+
+    // 返回比例值,保留两位小数
+    return Math.floor(result * 100) / 100;
+  }
+
+  /**
+   * 计算并返回打印选择范围的详细信息。
+   * @returns 返回选区的打印范围信息。
+   */
+  private _computePrintSelectionRange() {
+    // 获取当前工作簿实例
+    const workbook = this._getWorkbook();
+
+    // 如果没有获取到工作簿,返回 undefined
+    if (!workbook) {
+      return;
+    }
+
+    // 获取工作簿的唯一标识符
+    const unitId = workbook!.getUnitId();
+
+    // 获取当前活动工作表
+    const worksheet = workbook!.getActiveSheet();
+
+    // 如果没有获取到工作表,返回 undefined
+    if (!worksheet) {
+      return;
+    }
+
+    // 获取当前工作表的唯一标识符(subUnitId)
+    const subUnitId = worksheet?.getSheetId();
+
+    // 获取当前工作簿的最后选择区域
+    const lastSelection = this._selectionManagerService?.getWorkbookSelections(unitId)?.getCurrentLastSelection();
+
+    // 如果没有获取到选择区域,返回 undefined
+    if (!lastSelection) {
+      return;
+    }
+
+    // 根据最后选择的范围计算打印的页面范围
+    return this._computePrintPageRange(subUnitId, lastSelection?.range);
+  }
+
+  /**
+   * 计算工作表的打印范围。
+   * @param {string} subUnitId - 工作表的唯一标识符。
+   * @returns 返回计算出的打印范围,包含页面范围、冻结信息等。
+   */
+  private _computePrintSheetRange(subUnitId: string) {
+    // 获取工作簿实例
+    const workbook = this._getWorkbook();
+
+    // 如果没有获取到工作簿,返回 undefined
+    if (!workbook) {
+      return;
+    }
+
+    // 获取工作簿的唯一标识符
+    const unitId = workbook!.getUnitId();
+
+    // 获取当前工作表
+    const worksheet = workbook!.getSheetBySheetId(subUnitId);
+
+    // 如果工作表不存在,返回 undefined
+    if (!worksheet) {
+      return;
+    }
+
+    // 获取工作表的骨架信息,包含如行列宽度、冻结状态等数据
+    const skeleton = this._getUnitSkeleton(unitId, subUnitId);
+
+    // 如果工作表的骨架信息不存在,返回 undefined
+    if (!skeleton) {
+      return;
+    }
+
+    // 获取工作表的冻结信息,包括冻结行列位置
+    const freeze = worksheet?.getFreeze();
+
+    // 如果冻结信息不存在,返回 undefined
+    if (!freeze) {
+      return;
+    }
+
+    // 获取当前工作表的单元格矩阵打印范围
+    const cellMatrixPrintRange = worksheet?.getCellMatrixPrintRange();
+
+    // 如果没有获取到单元格矩阵打印范围,返回 undefined
+    if (!cellMatrixPrintRange) {
+      return;
+    }
+
+    // 获取当前工作表骨架中的溢出缓存数据
+    const { overflowCache } = skeleton;
+    if (overflowCache) {
+      // 如果有溢出缓存,遍历其值并调整打印范围
+      overflowCache.forValue((_row: any, _col: any, value: any) => {
+        const { endColumn } = value;
+
+        // 如果溢出的单元格列超过当前打印范围的结束列,则调整打印范围
+        if (endColumn > cellMatrixPrintRange?.endColumn) {
+          cellMatrixPrintRange.endColumn = endColumn;
+        }
+      });
+    }
+
+    // 使用拦截器调整打印范围
+    const realCellMatrixPrintRange = this._sheetPrintInterceptorService.interceptor?.fetchThroughInterceptors(
+      this._sheetPrintInterceptorService.interceptor?.getInterceptPoints()?.PRINTING_RANGE,
+    )(cellMatrixPrintRange, { subUnitId, unitId });
+
+    // 返回最终计算出的打印页面范围,包括冻结行列的调整
+    return this._computePrintPageRange(subUnitId, {
+      // 调整起始行列位置,确保不小于零
+      ...realCellMatrixPrintRange,
+      startColumn: Math.max(freeze.startColumn - freeze.xSplit, 0),
+      startRow: Math.max(freeze.startRow - freeze.ySplit, 0),
+    });
+  }
+
+  /**
+   * 获取当前配置的纸张边距信息。
+   * @returns 返回包含边距信息的对象,或者返回 `null`。
+   */
+  private _getPaperPadding() {
+    // 从配置参数中解构获取边距类型(padding)
+    const { padding } = this._configParams ?? {};
+
+    // 根据边距类型从预定义的边距配置映射中查找对应的边距值
+    const paperPadding = JnpfPaperPaddingForType[padding as keyof typeof JnpfPaperPaddingForType] ?? {};
+
+    // 如果找到有效的边距配置,返回边距配置;否则返回 null
+    return isEmptyObject(paperPadding) ? null : paperPadding;
+  }
+
+  /**
+   * 获取当前配置的纸张大小。
+   * @returns 返回包含宽度和高度的纸张尺寸对象,或者返回 `null`。
+   */
+  private _getPaperSize() {
+    // 从配置参数中解构获取纸张类型(paperType)和方向(direction)
+    const { direction, paperType } = this._configParams ?? {};
+
+    // 根据纸张类型获取对应的尺寸数据
+    const paperSize = JnpfPrintPaperSizeForType[paperType as keyof typeof JnpfPrintPaperSizeForType] ?? {};
+    // 如果纸张尺寸为空,返回 null
+    if (isEmptyObject(paperSize)) {
+      return null;
+    }
+
+    // 根据打印方向返回调整后的纸张尺寸
+    return direction === JnpfPrintDirectionEnum?.portrait
+      ? { ...paperSize } // 如果是纵向模式,直接返回原始纸张尺寸
+      : {
+          h: paperSize?.w,
+          w: paperSize?.h, // 如果是横向模式,交换宽度和高度
+        };
+  }
+
+  /**
+   * 获取指定单元的骨架信息。
+   * @param {string} unitId - 单元的唯一标识符。
+   * @param {string} subUnitId - 子单元的唯一标识符。
+   * @returns 返回单元骨架信息对象,如果未找到则返回空对象。
+   */
+  private _getUnitSkeleton(unitId: string, subUnitId: string) {
+    // 从 `RenderManagerService` 中获取指定单元 ID 的渲染服务
+    return (this._renderManagerService
+      ?.getRenderById(unitId) // 根据单元 ID 获取对应的渲染服务
+      ?.with(SheetSkeletonManagerService) // 使用骨架管理服务进行扩展
+      ?.getUnitSkeleton(unitId, subUnitId)?.skeleton ?? {}) as any; // 获取单元对应的骨架信息 // 如果骨架信息不存在,则返回空对象 // 强制将返回值类型设置为 `any`
+  }
+
+  /**
+   * 获取当前活动的工作簿实例。
+   * @returns  返回当前活动的工作簿实例,如果不存在则返回 `undefined`。
+   */
+  private _getWorkbook() {
+    // 使用 `UniverInstanceService` 获取当前类型为 `UNIVER_SHEET` 的活动单元实例
+    return this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
+  }
+}

+ 38 - 0
src/components/Report/Design/univer/services/sheet-range.service.ts

@@ -0,0 +1,38 @@
+import { Disposable, ICommandService, IUniverInstanceService, UniverInstanceType, Workbook } from '@univerjs/core';
+import { SetSelectionsOperation } from '@univerjs/sheets';
+
+export class JnpfSheetsRangeService extends Disposable {
+  constructor(
+    @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
+    @ICommandService private readonly _commandService: ICommandService,
+  ) {
+    super();
+  }
+
+  // 恢复选区
+  public recoveryRange(row: number, col: number) {
+    const unit = this._univerInstanceService.getCurrentUnitForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
+    const unitId = unit!.getUnitId();
+    const sheet = unit!.getActiveSheet();
+    const subUnitId = sheet?.getSheetId();
+
+    this._commandService
+      .executeCommand(SetSelectionsOperation.id, {
+        selections: [
+          {
+            range: {
+              endColumn: col,
+              endRow: row,
+              rangeType: 0,
+              startColumn: col,
+              startRow: row,
+            },
+          },
+        ],
+        subUnitId,
+        type: 2,
+        unitId,
+      })
+      .then();
+  }
+}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 304 - 0
src/components/Report/Design/univer/utils/define.ts


+ 676 - 0
src/components/Report/Design/univer/utils/index.ts

@@ -0,0 +1,676 @@
+import { JnpfPrintDirectionEnum, JnpfPrintPaperSizeForType } from './define';
+
+/**
+ * 检查值是否是 null 或 undefined
+ * @param value 要检查的值
+ * @returns 如果值是 null 或 undefined,返回 true,否则返回 false
+ */
+export const isNullOrUndefined = (value: any) => {
+  return value === null || value === undefined;
+};
+
+/**
+ * 判断一个值是否为空对象
+ * @param obj - 待检查的值
+ * @returns 如果是空对象,返回 true;否则返回 false
+ */
+export const isEmptyObject = (obj: unknown): boolean => {
+  return obj !== null && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 0;
+};
+
+/**
+ * 将指定值从数组中移除并将其添加到数组的末尾。
+ * @param array - 要操作的数组。
+ * @param  value - 要移动到数组末尾的值。
+ * @returns - 操作后的数组(原数组被修改)。
+ */
+export const moveArrayValueToEnd = (array: any[], value: number | string) => {
+  const index = array?.indexOf(value); // 找到目标值的索引
+
+  if (index !== -1) {
+    // 如果值存在于数组中
+    const [item] = array.splice(index, 1); // 删除目标值
+    array.push(item); // 添加到数组末尾
+  }
+
+  return array;
+};
+
+/**
+ * 根据字母规则计算输入字符串的索引值。
+ * @param input {string} - 输入的字母字符串(如 "A", "B", "AA", "AB" 等)。
+ * @returns {number} - 对应的索引值。
+ */
+export const getIndexFromAlphabetRule = (input: string): null | number => {
+  if (isNullOrUndefined(input)) {
+    return null;
+  }
+
+  const base = 26; // 字母表的长度
+  const charCodeA = 'A'.charCodeAt(0);
+
+  let index = 0;
+  for (let i = 0; i < input.length; i++) {
+    index = index * base + (input.charCodeAt(i) - charCodeA + 1);
+  }
+
+  return index - 1; // 数组索引从 0 开始
+};
+
+/**
+ * 根据索引值获取字母字符串。
+ * @param index {number} - 输入的索引值(如 0, 1, 26, 27 等)。
+ * @returns {string} - 对应的字母字符串(如 "A", "B", "AA", "AB" 等)。
+ */
+export const getAlphabetFromIndexRule = (index: number): string => {
+  const base = 26; // 字母表的长度
+  const charCodeA = 'A'.charCodeAt(0);
+
+  let result = '';
+  index += 1; // 转为从 1 开始的规则
+
+  while (index > 0) {
+    const remainder = (index - 1) % base;
+    result = String.fromCharCode(charCodeA + remainder) + result;
+    index = Math.floor((index - 1) / base);
+  }
+
+  return result;
+};
+
+/**
+ * 根据类型和坐标获取箭头单元格数组
+ * @param parentCellType - 父单元格类型
+ * @param colName - 列名
+ * @param rowName - 行名
+ * @returns 单元格坐标数组
+ */
+// export const getSheetRelationCell = (parentCellType: string, colName?: string, rowName?: string): { row: number; col: number }[] => {
+//   if (parentCellType === 'none' || !colName || !rowName) return [];
+//
+//   const col = getIndexFromAlphabetRule(colName);
+//   const row = Number(rowName) - 1;
+//
+//   return [{ row, col }];
+// };
+
+/**
+ * 修正工作表中的单元格数据。
+ * 根据传入的单元格数据 (`cellData`) 和指定的行、列数量,判断是否需要修正数据。
+ * 如果所有单元格数据异常(如未定义、空字符串、或无效值),则修正为默认配置;
+ * 如果存在正常单元格,则返回原始数据。
+ *
+ */
+export const correctSheetCellData = (cellData: any = {}, rowCount: number, columnCount: number, isFloatDom: boolean = false) => {
+  // 单元格纠正配置
+  const correctCellDataConfig = {
+    t: 1,
+    v: ' ',
+  };
+
+  // 如果 cellData 是空对象,直接返回默认初始化的单元格数据
+  if (isEmptyObject(cellData)) {
+    return {
+      0: {
+        0: correctCellDataConfig,
+      },
+    };
+  }
+
+  let totalCells = 0;
+  let abnormalCells = 0;
+  // 统计单元格数据中正常与异常的数量
+  for (const rowKey in cellData) {
+    const rowValue = cellData[rowKey] ?? {};
+    for (const colKey in rowValue) {
+      const { s, t, v, p } = rowValue[colKey] ?? {};
+
+      // 判断异常单元格条件
+      if (isNullOrUndefined(s) && !p && (v === undefined || (v === '' && t === 1) || (v === 0 && (t === 2 || t === 3)))) {
+        abnormalCells++;
+      }
+
+      totalCells++;
+    }
+  }
+
+  // 如果存在正常单元格,直接返回原始数据
+  if (totalCells > abnormalCells) {
+    return cellData;
+  }
+
+  // 否则需要修正数据
+  for (let i = 0; i < rowCount; i++) {
+    for (let j = 0; j < columnCount; j++) {
+      const { custom, t, v } = cellData?.[i]?.[j] ?? {};
+
+      // 判断是否需要修正(根据 isFloatDom 区分)
+      const needsCorrection = isFloatDom
+        ? custom === undefined && (v === undefined || (v === '' && t === 1))
+        : custom === undefined && (v === undefined || (v === '' && t === 1) || (v === 0 && (t === 2 || t === 3)));
+
+      // 如果发现第一个需要修正的单元格,修正并结束所有循环
+      if (needsCorrection) {
+        if (!cellData[i]) {
+          cellData[i] = {};
+        }
+        cellData[i][j] = correctCellDataConfig;
+
+        return cellData; // 修正完成后直接返回
+      }
+    }
+  }
+
+  // 排查不出来问题,只能返回了
+  return cellData;
+};
+
+/**
+ * 将 Base64 编码的字符串转换为 File 对象
+ * @param base64String Base64 字符串(必须以 `data:` 开头)
+ * @param fileName 生成的文件名
+ * @param mimeType 文件 MIME 类型(如 "image/png", "application/pdf")
+ * @returns 返回一个 Promise,解析后得到 File 对象
+ */
+export function base64ToFile(base64String: string, fileName: string, mimeType: string): Promise<File> {
+  return fetch(base64String)
+    .then(res => res.blob()) // 将 Base64 转换为 Blob
+    .then(blob => new File([blob], fileName, { type: mimeType })); // 生成 File 对象
+}
+
+/**
+ * 计算旋转后的边界框尺寸
+ * @param {number} width 原始宽度
+ * @param {number} height 原始高度
+ * @param {number} angleDegrees 旋转角度(0-360,单位:度)
+ * @returns {{ rotatedWidth: number; rotatedHeight: number }} 旋转后的宽度和高度
+ */
+export function rotatedBoundingBox(width: number, height: number, angleDegrees: number): { rotatedHeight: number; rotatedWidth: number } {
+  const angle = (angleDegrees * Math.PI) / 180; // 角度转换为弧度
+  const rotatedWidth = Math.abs(width * Math.cos(angle)) + Math.abs(height * Math.sin(angle));
+  const rotatedHeight = Math.abs(width * Math.sin(angle)) + Math.abs(height * Math.cos(angle));
+  return { rotatedHeight, rotatedWidth };
+}
+
+/**
+ * 计算插入行和列后父格的位置
+ * @param range - 选中的区域,包含起始行、列(`startRow`、`startColumn`)以及结束行、列(`endRow`、`endColumn`)
+ * @param axis - 操作方向,`'row'` 表示插入行,`'col'` 表示插入列
+ * @param offset - 插入的行数或列数,决定偏移量
+ * @param custom - 当前单元格的数据,包含自定义父格信息(如 `topParentCellType` 和 `leftParentCellType`)
+ */
+export function getParentCellPosWhenInsert(range: any, axis: 'col' | 'row', offset: number, custom: any) {
+  const { startColumn = 0, startRow = 0 } = range ?? {};
+
+  let {
+    leftParentCellCustomColName,
+    leftParentCellCustomRowName,
+    leftParentCellType,
+    topParentCellCustomColName,
+    topParentCellCustomRowName,
+    topParentCellType,
+  } = custom ?? {};
+
+  // 上父格自定义
+  if (topParentCellType === 'custom' && !isNullOrUndefined(topParentCellCustomColName) && !isNullOrUndefined(topParentCellCustomRowName)) {
+    // 行操作
+    if (axis === 'row') {
+      const targetTopColIndex = Number(topParentCellCustomColName) - 1;
+      if (startRow <= targetTopColIndex) {
+        topParentCellCustomColName = (targetTopColIndex + offset + 1).toString();
+      }
+    }
+
+    // 列操作
+    if (axis === 'col') {
+      const targetTopRowIndex = getIndexFromAlphabetRule(topParentCellCustomRowName);
+      if (targetTopRowIndex !== null && startColumn <= targetTopRowIndex) {
+        topParentCellCustomRowName = getAlphabetFromIndexRule(targetTopRowIndex + offset);
+      }
+    }
+  }
+
+  // 左父格自定义
+  if (leftParentCellType === 'custom' && !isNullOrUndefined(leftParentCellCustomColName) && !isNullOrUndefined(leftParentCellCustomRowName)) {
+    // 行操作
+    if (axis === 'row') {
+      const targetLeftColIndex = Number(leftParentCellCustomColName) - 1;
+      if (startRow <= targetLeftColIndex) {
+        leftParentCellCustomColName = (targetLeftColIndex + offset + 1).toString();
+      }
+    }
+
+    // 列操作
+    if (axis === 'col') {
+      const targetLeftRowIndex = getIndexFromAlphabetRule(leftParentCellCustomRowName);
+      if (targetLeftRowIndex !== null && startColumn <= targetLeftRowIndex) {
+        leftParentCellCustomRowName = getAlphabetFromIndexRule(targetLeftRowIndex + offset);
+      }
+    }
+  }
+
+  return {
+    ...custom,
+    leftParentCellCustomColName,
+    leftParentCellCustomRowName,
+    topParentCellCustomColName,
+    topParentCellCustomRowName,
+  };
+}
+
+/**
+ * 计算删除行和列后父格的位置
+ * @param range - 选中的区域,包含起始行、列(`startRow`、`startColumn`)以及结束行、列(`endRow`、`endColumn`)
+ * @param axis - 操作方向,`'row'` 表示插入行,`'col'` 表示插入列
+ * @param offset - 插入的行数或列数,决定偏移量
+ * @param custom - 当前单元格的数据,包含自定义父格信息(如 `topParentCellType` 和 `leftParentCellType`)
+ */
+export function getParentCellPosWhenDelete(range: any, axis: 'col' | 'row', offset: number, custom: any) {
+  const { endColumn = 0, endRow = 0, startColumn = 0, startRow = 0 } = range ?? {};
+
+  let {
+    leftParentCellCustomColName,
+    leftParentCellCustomRowName,
+    leftParentCellType,
+    topParentCellCustomColName,
+    topParentCellCustomRowName,
+    topParentCellType,
+  } = custom ?? {};
+
+  // 上父格自定义
+  if (topParentCellType === 'custom' && !isNullOrUndefined(topParentCellCustomColName) && !isNullOrUndefined(topParentCellCustomRowName)) {
+    // 行操作
+    if (axis === 'row') {
+      const targetTopColIndex = Number(topParentCellCustomColName) - 1;
+
+      if (startRow <= targetTopColIndex && endRow >= targetTopColIndex) {
+        topParentCellCustomColName = null;
+        topParentCellCustomRowName = null;
+      } else if (startRow <= targetTopColIndex) {
+        topParentCellCustomColName = (targetTopColIndex - offset + 1).toString();
+      }
+    }
+
+    // 列操作
+    if (axis === 'col') {
+      const targetTopRowIndex = getIndexFromAlphabetRule(topParentCellCustomRowName);
+      if (targetTopRowIndex !== null) {
+        if (startColumn <= targetTopRowIndex && endColumn >= targetTopRowIndex) {
+          topParentCellCustomColName = null;
+          topParentCellCustomRowName = null;
+        } else if (startColumn <= targetTopRowIndex) {
+          topParentCellCustomRowName = getAlphabetFromIndexRule(targetTopRowIndex - offset);
+        }
+      }
+    }
+  }
+
+  // 左父格自定义
+  if (leftParentCellType === 'custom' && !isNullOrUndefined(leftParentCellCustomColName) && !isNullOrUndefined(leftParentCellCustomRowName)) {
+    // 行操作
+    if (axis === 'row') {
+      const targetLeftColIndex = Number(leftParentCellCustomColName) - 1;
+
+      if (startRow <= targetLeftColIndex && endRow >= targetLeftColIndex) {
+        leftParentCellCustomColName = null;
+        leftParentCellCustomRowName = null;
+      } else if (startRow <= targetLeftColIndex) {
+        leftParentCellCustomColName = (targetLeftColIndex - offset + 1).toString();
+      }
+    }
+
+    // 列操作
+    if (axis === 'col') {
+      const targetLeftRowIndex = getIndexFromAlphabetRule(leftParentCellCustomRowName);
+      if (targetLeftRowIndex !== null) {
+        if (startColumn <= targetLeftRowIndex && endColumn >= targetLeftRowIndex) {
+          leftParentCellCustomColName = null;
+          leftParentCellCustomRowName = null;
+        } else if (startColumn <= targetLeftRowIndex) {
+          leftParentCellCustomRowName = getAlphabetFromIndexRule(targetLeftRowIndex - offset);
+        }
+      }
+    }
+  }
+
+  return {
+    ...custom,
+    leftParentCellCustomColName,
+    leftParentCellCustomRowName,
+    topParentCellCustomColName,
+    topParentCellCustomRowName,
+  };
+}
+
+/**
+ * 计算移动行和列后父格的位置
+ * @param sourceRange - 原选中的区域,包含起始行、列(`startRow`、`startColumn`)以及结束行、列(`endRow`、`endColumn`)
+ * @param targetRange - 后影响的区域,包含起始行、列(`startRow`、`startColumn`)以及结束行、列(`endRow`、`endColumn`)
+ * @param axis - 操作方向,`'row'` 表示插入行,`'col'` 表示插入列
+ * @param offset - 插入的行数或列数,决定偏移量
+ * @param involved - 涉及的行或列的数量
+ * @param custom - 当前单元格的数据,包含自定义父格信息(如 `topParentCellType` 和 `leftParentCellType`)
+ */
+export function getParentCellPosWhenMove(sourceRange: any, targetRange: any, axis: 'col' | 'row', offset: number, involved: number, custom: any) {
+  let {
+    leftParentCellCustomColName,
+    leftParentCellCustomRowName,
+    leftParentCellType,
+    topParentCellCustomColName,
+    topParentCellCustomRowName,
+    topParentCellType,
+  } = custom ?? {};
+
+  // 上父格自定义
+  if (topParentCellType === 'custom' && !isNullOrUndefined(topParentCellCustomColName) && !isNullOrUndefined(topParentCellCustomRowName)) {
+    // 行操作
+    if (axis === 'row') {
+      const targetTopColIndex = Number(topParentCellCustomColName) - 1;
+
+      if (sourceRange.startRow <= targetTopColIndex && targetTopColIndex <= sourceRange.endRow) {
+        topParentCellCustomColName = (targetTopColIndex + offset + 1).toString();
+      } else if (
+        (sourceRange.startRow < targetTopColIndex && targetTopColIndex < targetRange.startRow) ||
+        (sourceRange.startRow > targetTopColIndex && targetTopColIndex >= targetRange.startRow)
+      ) {
+        const moveValue = offset > 0 ? involved : -involved;
+        topParentCellCustomColName = (targetTopColIndex - moveValue + 1).toString();
+      }
+    }
+
+    // 列操作
+    if (axis === 'col') {
+      const targetTopRowIndex = getIndexFromAlphabetRule(topParentCellCustomRowName);
+      if (targetTopRowIndex !== null) {
+        if (sourceRange.startColumn <= targetTopRowIndex && targetTopRowIndex <= sourceRange.endColumn) {
+          topParentCellCustomRowName = getAlphabetFromIndexRule(targetTopRowIndex + offset);
+        } else if (
+          (sourceRange.startColumn < targetTopRowIndex && targetTopRowIndex < targetRange.startColumn) ||
+          (sourceRange.startColumn > targetTopRowIndex && targetTopRowIndex >= targetRange.startColumn)
+        ) {
+          const moveValue = offset > 0 ? involved : -involved;
+          topParentCellCustomRowName = getAlphabetFromIndexRule(targetTopRowIndex - moveValue);
+        }
+      }
+    }
+  }
+
+  // 左父格自定义
+  if (leftParentCellType === 'custom' && !isNullOrUndefined(leftParentCellCustomColName) && !isNullOrUndefined(leftParentCellCustomRowName)) {
+    // 行操作
+    if (axis === 'row') {
+      const targetLeftColIndex = Number(leftParentCellCustomColName) - 1;
+
+      if (sourceRange.startRow <= targetLeftColIndex && targetLeftColIndex <= sourceRange.endRow) {
+        leftParentCellCustomColName = (targetLeftColIndex + offset + 1).toString();
+      } else if (
+        (sourceRange.startRow < targetLeftColIndex && targetLeftColIndex < targetRange.startRow) ||
+        (sourceRange.startRow > targetLeftColIndex && targetLeftColIndex >= targetRange.startRow)
+      ) {
+        const moveValue = offset > 0 ? involved : -involved;
+        leftParentCellCustomColName = (targetLeftColIndex - moveValue + 1).toString();
+      }
+    }
+
+    // 列操作
+    if (axis === 'col') {
+      const targetLeftRowIndex = getIndexFromAlphabetRule(leftParentCellCustomRowName);
+      if (targetLeftRowIndex !== null) {
+        if (sourceRange.startColumn <= targetLeftRowIndex && targetLeftRowIndex <= sourceRange.endColumn) {
+          leftParentCellCustomRowName = getAlphabetFromIndexRule(targetLeftRowIndex + offset);
+        } else if (
+          (sourceRange.startColumn < targetLeftRowIndex && targetLeftRowIndex < targetRange.startColumn) ||
+          (sourceRange.startColumn > targetLeftRowIndex && targetLeftRowIndex >= targetRange.startColumn)
+        ) {
+          const moveValue = offset > 0 ? involved : -involved;
+          leftParentCellCustomRowName = getAlphabetFromIndexRule(targetLeftRowIndex - moveValue);
+        }
+      }
+    }
+  }
+
+  return {
+    ...custom,
+    leftParentCellCustomColName,
+    leftParentCellCustomRowName,
+    topParentCellCustomColName,
+    topParentCellCustomRowName,
+  };
+}
+
+/**
+ * 计算移动单元格后父格的位置
+ * @param {object} fromRange - 被移动的单元格的范围,包含起始和结束行列坐标
+ * @param {object} offset - 行列偏移量,包含纵向(offsetRow)和横向(offsetCol)的偏移
+ * @param {object} custom - 当前单元格的自定义信息,包含上父格和左父格的信息
+ */
+export function getParentCellPosWhenMoveCell(fromRange: any, offset: any, custom: any) {
+  const { offsetCol, offsetRow } = offset ?? {};
+  let {
+    leftParentCellCustomColName,
+    leftParentCellCustomRowName,
+    leftParentCellType,
+    topParentCellCustomColName,
+    topParentCellCustomRowName,
+    topParentCellType,
+  } = custom ?? {};
+
+  // 上父格自定义
+  if (topParentCellType === 'custom' && !isNullOrUndefined(topParentCellCustomColName) && !isNullOrUndefined(topParentCellCustomRowName)) {
+    const targetTopColIndex = Number(topParentCellCustomColName) - 1;
+    const targetTopRowIndex = getIndexFromAlphabetRule(topParentCellCustomRowName);
+
+    if (targetTopRowIndex !== null) {
+      const isLinchpin =
+        fromRange.startRow <= targetTopColIndex &&
+        targetTopColIndex <= fromRange.endRow &&
+        fromRange.startColumn <= targetTopRowIndex &&
+        targetTopRowIndex <= fromRange.endColumn; // 当事单元格
+
+      if (
+        offsetRow !== 0 && // 纵向操作
+        isLinchpin
+      ) {
+        topParentCellCustomColName = (targetTopColIndex + offsetRow + 1).toString();
+      }
+
+      if (
+        offsetCol !== 0 && // 横向操作
+        isLinchpin
+      ) {
+        topParentCellCustomRowName = getAlphabetFromIndexRule(targetTopRowIndex + offsetCol);
+      }
+    }
+  }
+
+  // 左父格自定义
+  if (leftParentCellType === 'custom' && !isNullOrUndefined(leftParentCellCustomColName) && !isNullOrUndefined(leftParentCellCustomRowName)) {
+    const targetLeftColIndex = Number(leftParentCellCustomColName) - 1;
+    const targetLeftRowIndex = getIndexFromAlphabetRule(leftParentCellCustomRowName);
+
+    if (targetLeftRowIndex !== null) {
+      const isLinchpin =
+        fromRange.startRow <= targetLeftColIndex &&
+        targetLeftColIndex <= fromRange.endRow &&
+        fromRange.startColumn <= targetLeftRowIndex &&
+        targetLeftRowIndex <= fromRange.endColumn; // 当事单元格
+
+      if (
+        offsetRow !== 0 && // 纵向操作
+        isLinchpin
+      ) {
+        leftParentCellCustomColName = (targetLeftColIndex + offsetRow + 1).toString();
+      }
+
+      if (
+        offsetCol !== 0 && // 横向操作
+        isLinchpin
+      ) {
+        leftParentCellCustomRowName = getAlphabetFromIndexRule(targetLeftRowIndex + offsetCol);
+      }
+    }
+  }
+
+  return {
+    ...custom,
+    leftParentCellCustomColName,
+    leftParentCellCustomRowName,
+    topParentCellCustomColName,
+    topParentCellCustomRowName,
+  };
+}
+
+/**
+ * 计算清楚单元格后父格的位置
+ * @param ranges - 当前选择的单元格范围列表,每个范围包含起始和结束行列
+ *  @param custom - 父格的自定义信息,包括行列名称和类型
+ */
+export function getParentCellPosWhenClearCell(ranges: any, custom: any) {
+  let {
+    leftParentCellCustomColName,
+    leftParentCellCustomRowName,
+    leftParentCellType,
+    topParentCellCustomColName,
+    topParentCellCustomRowName,
+    topParentCellType,
+  } = custom ?? {};
+
+  // 上父格自定义
+  if (topParentCellType === 'custom' && !isNullOrUndefined(topParentCellCustomColName) && !isNullOrUndefined(topParentCellCustomRowName)) {
+    const targetTopColIndex = Number(topParentCellCustomColName) - 1;
+    const targetTopRowIndex = getIndexFromAlphabetRule(topParentCellCustomRowName);
+
+    if (targetTopRowIndex !== null) {
+      const isLinchpin = ranges.some(({ endColumn, endRow, startColumn, startRow }: any) => {
+        return startRow <= targetTopColIndex && targetTopColIndex <= endRow && startColumn <= targetTopRowIndex && targetTopRowIndex <= endColumn;
+      });
+
+      if (isLinchpin) {
+        topParentCellCustomColName = null;
+        topParentCellCustomRowName = null;
+      }
+    }
+  }
+
+  // 左父格自定义
+  if (leftParentCellType === 'custom' && !isNullOrUndefined(leftParentCellCustomColName) && !isNullOrUndefined(leftParentCellCustomRowName)) {
+    const targetLeftColIndex = Number(leftParentCellCustomColName) - 1;
+    const targetLeftRowIndex = getIndexFromAlphabetRule(leftParentCellCustomRowName);
+
+    if (targetLeftRowIndex !== null) {
+      const isLinchpin = ranges.some(({ endColumn, endRow, startColumn, startRow }: any) => {
+        return startRow <= targetLeftColIndex && targetLeftColIndex <= endRow && startColumn <= targetLeftRowIndex && targetLeftRowIndex <= endColumn;
+      });
+
+      if (isLinchpin) {
+        leftParentCellCustomColName = null;
+        leftParentCellCustomRowName = null;
+      }
+    }
+  }
+
+  return {
+    ...custom,
+    leftParentCellCustomColName,
+    leftParentCellCustomRowName,
+    topParentCellCustomColName,
+    topParentCellCustomRowName,
+  };
+}
+
+/**
+ * 翻译小时
+ * @param hour
+ */
+function to12HourFormat(hour: any) {
+  const h = hour % 12 || 12; // 0 => 12, 13 => 1, 14 => 2 ...
+  return h.toString().padStart(2, '0'); // 补零,例如 2 => '02'
+}
+
+/**
+ * 翻译页眉页脚文本
+ */
+/**
+ * 翻译页眉页脚文本
+ */
+export function translateHeaderFooterText(orientation: string, printConfig: any): string {
+  if (!printConfig?.customHeaderFooterValue?.[orientation]) return '';
+
+  let targetValue = printConfig?.customHeaderFooterValue[orientation];
+
+  // 日期和时间的预计算,减少 slice 调用
+  const dateTime = printConfig?.currentDateTime || '';
+  const dateA = dateTime.slice(0, 10); // YYYY-MM-DD
+  const dateB = dateTime.slice(5, 10); // YYYY-MM
+  const dateC = `${dateTime.slice(5, 7)}/${dateTime.slice(8, 10)}/${dateTime.slice(0, 4)}`; // MM/DD/YYYY
+  const dateD = `${dateTime.slice(5, 7)}/${dateTime.slice(8, 10)}`; // MM/DD
+  const timeA = dateTime.slice(11, 19); // HH:mm:ss
+  const timeB = dateTime.slice(11, 16); // HH:mm
+
+  const hourFormat = to12HourFormat(dateTime.slice(11, 13));
+  const hour = Number(dateTime.slice(11, 13));
+
+  const timeC = `${hour < 13 ? 'AM' : 'PM'} ${hourFormat}:${dateTime.slice(14, 19)}`; // HH:mm:ss
+  const timeD = `${hour < 13 ? 'AM' : 'PM'} ${hourFormat}:${dateTime.slice(14, 16)}`; // HH:mm:ss
+
+  // 依次替换占位符,保持原有业务逻辑
+  targetValue = targetValue
+    .replace('@ReportName', printConfig.workbookTitleText)
+    .replace('@SheetName', printConfig.workSheetTitleText)
+    .replace('@TotalPages', printConfig.bookTotalPage)
+    .replace('@PageNumbers', printConfig.bookPageNumber)
+    .replace('@SheetTotalPages', printConfig.sheetTotalPage)
+    .replace('@SheetPageNumbers', printConfig.sheetPageNumber)
+    .replace('@DateA', dateA)
+    .replace('@DateB', dateB)
+    .replace('@DateC', dateC)
+    .replace('@DateD', dateD)
+    .replace('@TimeA', timeA)
+    .replace('@TimeB', timeB)
+    .replace('@TimeC', timeC)
+    .replace('@TimeD', timeD);
+
+  return targetValue;
+}
+
+/**
+ * 获取打印页面的样式
+ * @param paperType - 纸张类型,对应 `JnpfPrintPaperSizeForType` 中的键值
+ * @param direction - 打印方向,取值为 `JnpfPrintDirectionEnum.portrait`(纵向)或 `JnpfPrintDirectionEnum.landscape`(横向)
+ * @returns 一个包含打印样式的 `<style>` 元素
+ */
+export function getPrintPageStyle(paperType: string, direction: string) {
+  const { h, w } = JnpfPrintPaperSizeForType[paperType as keyof typeof JnpfPrintPaperSizeForType] ?? {};
+  const width = direction === JnpfPrintDirectionEnum?.portrait ? w : h;
+  const height = direction === JnpfPrintDirectionEnum?.portrait ? h : w;
+  const style = `
+@page {
+    size: ${width}px ${height}px;
+}
+@page {
+    margin: 0;
+    visibility: hidden;
+}
+@media print {
+    body > * {
+        display: none!important;
+    }
+    #jnpfReportPrint, #jnpfReportPrint * {
+        display: block!important;
+        height: fit-content;
+        overflow: visible;
+        top: 0;
+        width: fit-content;
+    }
+    #jnpfReportPrint .printContainer {
+        page-break-after: always!important;
+        height: ${height}px;
+        width: ${width}px;
+        position: relative;
+    }
+}`;
+  const $style = document.createElement('style');
+  $style.innerHTML = style;
+  $style.className = 'jnpfPrintCss';
+  return $style;
+}

+ 627 - 0
src/components/Report/Design/univer/utils/univer.ts

@@ -0,0 +1,627 @@
+/* eslint-disable no-prototype-builtins */
+import { IImageIoService, IWorkbookData, LocaleType, merge, Univer, UniverInstanceType } from '@univerjs/core';
+// 数据验证
+import { UniverDataValidationPlugin } from '@univerjs/data-validation';
+import { defaultTheme } from '@univerjs/design';
+import DesignZhCN from '@univerjs/design/locale/zh-CN';
+import { UniverDocsPlugin } from '@univerjs/docs';
+// 图片
+import { UniverDocsDrawingPlugin } from '@univerjs/docs-drawing';
+import { UniverDocsUIPlugin } from '@univerjs/docs-ui';
+import DocsUIZhCN from '@univerjs/docs-ui/locale/zh-CN';
+import { UniverDrawingPlugin } from '@univerjs/drawing';
+import { UniverDrawingUIPlugin } from '@univerjs/drawing-ui';
+import DrawingUIZhCN from '@univerjs/drawing-ui/locale/zh-CN';
+import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula';
+import { UniverRenderEnginePlugin } from '@univerjs/engine-render';
+// 查找 & 替换
+import { UniverFindReplacePlugin } from '@univerjs/find-replace';
+import FindReplaceZhCN from '@univerjs/find-replace/locale/zh-CN';
+import { UniverSheetsPlugin } from '@univerjs/sheets';
+// 条件格式
+import { UniverSheetsConditionalFormattingPlugin } from '@univerjs/sheets-conditional-formatting';
+import { UniverSheetsConditionalFormattingUIPlugin } from '@univerjs/sheets-conditional-formatting-ui';
+import SheetsConditionalFormattingUIZhCN from '@univerjs/sheets-conditional-formatting-ui/locale/zh-CN';
+// 十字高亮
+import { UniverSheetsCrosshairHighlightPlugin } from '@univerjs/sheets-crosshair-highlight';
+import SheetsCrosshairHighlightZhCN from '@univerjs/sheets-crosshair-highlight/locale/zh-CN';
+import { UniverSheetsDataValidationPlugin } from '@univerjs/sheets-data-validation';
+import { UniverSheetsDataValidationUIPlugin } from '@univerjs/sheets-data-validation-ui';
+import SheetsDataValidationZhCN from '@univerjs/sheets-data-validation-ui/locale/zh-CN';
+import { UniverSheetsDrawingPlugin } from '@univerjs/sheets-drawing';
+import { UniverSheetsDrawingUIPlugin } from '@univerjs/sheets-drawing-ui';
+import SheetsDrawingUIZhCN from '@univerjs/sheets-drawing-ui/locale/zh-CN';
+// 筛选
+import { UniverSheetsFilterPlugin } from '@univerjs/sheets-filter';
+import { UniverSheetsFilterUIPlugin } from '@univerjs/sheets-filter-ui';
+import SheetsFilterUIZhCN from '@univerjs/sheets-filter-ui/locale/zh-CN';
+import { UniverSheetsFindReplacePlugin } from '@univerjs/sheets-find-replace';
+import SheetsFindReplaceZhCN from '@univerjs/sheets-find-replace/locale/zh-CN';
+import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula';
+import { UniverSheetsFormulaUIPlugin } from '@univerjs/sheets-formula-ui';
+import SheetsFormulaUIZhCN from '@univerjs/sheets-formula-ui/locale/zh-CN';
+// 超链接
+import { UniverSheetsHyperLinkPlugin } from '@univerjs/sheets-hyper-link';
+import { UniverSheetsHyperLinkUIPlugin } from '@univerjs/sheets-hyper-link-ui';
+import SheetsHyperLinkUIZhCN from '@univerjs/sheets-hyper-link-ui/locale/zh-CN';
+import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt';
+import { UniverSheetsNumfmtUIPlugin } from '@univerjs/sheets-numfmt-ui';
+import SheetsNumfmtUIZhCN from '@univerjs/sheets-numfmt-ui/locale/zh-CN';
+// 排序
+import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort';
+import { UniverSheetsSortUIPlugin } from '@univerjs/sheets-sort-ui';
+import SheetsSortUIZhCN from '@univerjs/sheets-sort-ui/locale/zh-CN';
+import { UniverSheetsThreadCommentPlugin } from '@univerjs/sheets-thread-comment';
+// import { UniverSheetsThreadCommentUIPlugin } from '@univerjs/sheets-thread-comment-ui';
+import SheetsThreadCommentUIZhCN from '@univerjs/sheets-thread-comment-ui/locale/zh-CN';
+import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
+import SheetsUIZhCN from '@univerjs/sheets-ui/locale/zh-CN';
+// 禅编译器
+import { UniverSheetsZenEditorPlugin } from '@univerjs/sheets-zen-editor';
+import SheetsZenEditorZhCN from '@univerjs/sheets-zen-editor/locale/zh-CN';
+import SheetsZhCN from '@univerjs/sheets/locale/zh-CN';
+// 评论/批注
+import { UniverThreadCommentPlugin } from '@univerjs/thread-comment';
+import { UniverThreadCommentUIPlugin } from '@univerjs/thread-comment-ui';
+import ThreadCommentUIZhCN from '@univerjs/thread-comment-ui/locale/zh-CN';
+import { UniverUIPlugin } from '@univerjs/ui';
+import UIZhCN from '@univerjs/ui/locale/zh-CN';
+// uniscript
+// import { UniverUniscriptPlugin } from '@univerjs/uniscript';
+// import UniscriptZhCN from '@univerjs/uniscript/locale/zh-CN';
+// 水印
+import { UniverWatermarkPlugin } from '@univerjs/watermark';
+// 打印
+// import { UniverSheetsPrintPlugin } from '@univerjs-pro/sheets-print';
+
+import { JnpfSheetsCellImagePlugin } from '../plugins/sheet-cell-image.plugin';
+// 自定义
+import { JnpfSheetsCellPlugin } from '../plugins/sheet-cell.plugin';
+import { JnpfSheetsDialogPlugin } from '../plugins/sheet-dialog.plugin';
+import { JnpfSheetsExcelFilePlugin } from '../plugins/sheet-excel-file.plugin';
+import { JnpfSheetsFloatDomPlugin } from '../plugins/sheet-float-dom.plugin';
+import { JnpfSheetsFloatEchartPlugin } from '../plugins/sheet-float-echart.plugin';
+import { JnpfSheetsFloatImagePlugin } from '../plugins/sheet-float-image.plugin';
+import { JnpfSheetsPreviewPlugin } from '../plugins/sheet-preview.plugin';
+import { JnpfSheetsPrintPlugin } from '../plugins/sheet-print.plugin';
+import { JnpfSheetsRangePlugin } from '../plugins/sheet-range.plugin';
+import { DefaultFloatEchartHeight, DefaultFloatEchartWidth, JnpfUniverFloatEchartKey, JnpfUniverFloatImageKey } from './define';
+import { correctSheetCellData, isEmptyObject, isNullOrUndefined, moveArrayValueToEnd } from './index';
+
+// 创建univer实例
+export function createUniverInstance(config: any) {
+  const { container, contextMenu, footer, header, workbookData, darkMode } = config ?? {};
+
+  const univerInstance = new Univer({
+    locale: LocaleType.ZH_CN,
+    locales: {
+      [LocaleType.ZH_CN]: merge(
+        {},
+        DesignZhCN,
+        UIZhCN,
+        DocsUIZhCN,
+        SheetsZhCN,
+        SheetsUIZhCN,
+        SheetsNumfmtUIZhCN,
+        SheetsFormulaUIZhCN,
+        SheetsFilterUIZhCN,
+        SheetsSortUIZhCN,
+        SheetsDataValidationZhCN,
+        SheetsConditionalFormattingUIZhCN,
+        SheetsHyperLinkUIZhCN,
+        DrawingUIZhCN,
+        SheetsDrawingUIZhCN,
+        ThreadCommentUIZhCN,
+        SheetsThreadCommentUIZhCN,
+        FindReplaceZhCN,
+        SheetsFindReplaceZhCN,
+        SheetsCrosshairHighlightZhCN,
+        SheetsZenEditorZhCN,
+        // UniscriptZhCN,
+      ),
+    },
+    theme: defaultTheme,
+    darkMode,
+  });
+
+  univerInstance.registerPlugin(UniverRenderEnginePlugin);
+  univerInstance.registerPlugin(UniverFormulaEnginePlugin);
+  univerInstance.registerPlugin(UniverUIPlugin, {
+    container,
+    contextMenu,
+    footer,
+    header,
+  });
+  univerInstance.registerPlugin(UniverDocsPlugin);
+  univerInstance.registerPlugin(UniverDocsUIPlugin);
+  univerInstance.registerPlugin(UniverSheetsPlugin);
+  univerInstance.registerPlugin(UniverSheetsUIPlugin, {
+    menu: {
+      'sheet.command.delete-range-move-left-confirm': {
+        hidden: true,
+      },
+      'sheet.command.delete-range-move-up-confirm': {
+        hidden: true,
+      },
+      'sheet.command.add-range-protection-from-toolbar': {
+        hidden: true,
+      },
+      'sheet.command.add-range-protection-from-context-menu': {
+        hidden: true,
+      },
+      'sheet.contextMenu.permission': {
+        hidden: true,
+      }
+    },
+  });
+  // univerInstance.registerPlugin(UniverSheetsUIPlugin);
+  univerInstance.registerPlugin(UniverSheetsFormulaPlugin);
+  univerInstance.registerPlugin(UniverSheetsFormulaUIPlugin);
+  univerInstance.registerPlugin(UniverSheetsNumfmtPlugin);
+  univerInstance.registerPlugin(UniverSheetsNumfmtUIPlugin);
+
+  // 这个和注销的冲突掉了
+  univerInstance.registerPlugin(UniverSheetsHyperLinkPlugin);
+  univerInstance.registerPlugin(UniverSheetsHyperLinkUIPlugin);
+
+  univerInstance.registerPlugin(UniverDocsDrawingPlugin);
+  univerInstance.registerPlugin(UniverDrawingPlugin, {
+    override: [[IImageIoService, null]],
+  });
+  univerInstance.registerPlugin(UniverDrawingUIPlugin);
+  univerInstance.registerPlugin(UniverSheetsDrawingPlugin);
+  univerInstance.registerPlugin(UniverSheetsDrawingUIPlugin);
+
+  univerInstance.registerPlugin(UniverFindReplacePlugin);
+  univerInstance.registerPlugin(UniverSheetsFindReplacePlugin);
+  univerInstance.registerPlugin(UniverSheetsFilterPlugin);
+  univerInstance.registerPlugin(UniverSheetsFilterUIPlugin);
+  univerInstance.registerPlugin(UniverSheetsSortPlugin);
+  univerInstance.registerPlugin(UniverSheetsSortUIPlugin);
+  univerInstance.registerPlugin(UniverDataValidationPlugin);
+  univerInstance.registerPlugin(UniverSheetsDataValidationPlugin);
+  univerInstance.registerPlugin(UniverSheetsDataValidationUIPlugin, {
+    showEditOnDropdown: true,
+  });
+  univerInstance.registerPlugin(UniverSheetsConditionalFormattingPlugin);
+  univerInstance.registerPlugin(UniverSheetsConditionalFormattingUIPlugin);
+  univerInstance.registerPlugin(UniverSheetsCrosshairHighlightPlugin);
+  univerInstance.registerPlugin(UniverThreadCommentPlugin);
+  univerInstance.registerPlugin(UniverThreadCommentUIPlugin);
+  univerInstance.registerPlugin(UniverSheetsThreadCommentPlugin);
+  // univerInstance.registerPlugin(UniverSheetsThreadCommentUIPlugin);
+  // univerInstance.registerPlugin(UniverUniscriptPlugin);
+  univerInstance.registerPlugin(UniverSheetsZenEditorPlugin);
+  univerInstance.registerPlugin(UniverWatermarkPlugin);
+
+  // univerInstance.registerPlugin(UniverSheetsPrintPlugin);
+
+  univerInstance.registerPlugin(JnpfSheetsCellPlugin);
+  univerInstance.registerPlugin(JnpfSheetsRangePlugin);
+  univerInstance.registerPlugin(JnpfSheetsDialogPlugin);
+  univerInstance.registerPlugin(JnpfSheetsExcelFilePlugin);
+  univerInstance.registerPlugin(JnpfSheetsFloatDomPlugin);
+  univerInstance.registerPlugin(JnpfSheetsFloatEchartPlugin);
+  univerInstance.registerPlugin(JnpfSheetsCellImagePlugin);
+  univerInstance.registerPlugin(JnpfSheetsFloatImagePlugin);
+  univerInstance.registerPlugin(JnpfSheetsPreviewPlugin);
+  univerInstance.registerPlugin(JnpfSheetsPrintPlugin);
+
+  univerInstance.createUnit(UniverInstanceType.UNIVER_SHEET, workbookData);
+  return univerInstance;
+}
+
+// 解析创建univer实例的快照数据
+export function analysisCreateUnitData(workbookData: IWorkbookData | string, createMode: string, isReadonly: boolean) {
+  try {
+    const parseWorkbookData = typeof workbookData === 'string' ? JSON.parse(workbookData) : workbookData;
+
+    if (createMode === 'design' && !isReadonly) {
+      // 设计模式并且非只读的情况下直接返回快照
+      return parseWorkbookData;
+    }
+
+    const { resources = [] } = parseWorkbookData ?? {};
+
+    // 主要是预览的情况下不让图表可操作
+    const updatedResources = resources?.map((resource: any) => {
+      if (resource?.name === 'SHEET_DRAWING_PLUGIN') {
+        const parseResourceData = JSON.parse(resource?.data ?? '{}');
+
+        const updatedParseResourceData = Object.fromEntries(
+          Object.entries(parseResourceData)?.map(([sheetKey, sheetResourceOption]: any) => {
+            const sheetResourceOptionData = sheetResourceOption?.data || {};
+
+            const updateSheetResourceOption = Object.fromEntries(
+              Object.entries(sheetResourceOptionData).map(([floatDomKey, floatDomValue]: any) => {
+                const { componentKey, ...floatDomValueRest } = floatDomValue || {};
+
+                return componentKey === JnpfUniverFloatEchartKey || componentKey === JnpfUniverFloatImageKey
+                  ? [
+                      floatDomKey,
+                      {
+                        ...floatDomValueRest,
+                        allowTransform: false,
+                        componentKey,
+                      },
+                    ]
+                  : [floatDomKey, floatDomValue];
+              }),
+            );
+
+            return [sheetKey, { ...sheetResourceOption, data: updateSheetResourceOption }];
+          }),
+        );
+
+        return {
+          ...resource,
+          data: JSON.stringify(updatedParseResourceData),
+        };
+      } else {
+        return resource;
+      }
+    });
+
+    return {
+      ...parseWorkbookData,
+      resources: updatedResources,
+    };
+  } catch (error) {
+    console.error(error);
+    return {};
+  }
+}
+
+// 整理设计的数据
+export function arrangeDesignWorkbookData(snapshot: any, floatEchartDataCaches: any, floatImageDataCaches: any) {
+  const updateFloatEcharts: Record<string, any> = {};
+  const updateCellEcharts: Record<string, any> = {};
+  const updateFloatImages: Record<string, any> = {};
+
+  // 给悬浮图表和悬浮图片增加float dom的id 并且剔除已被删除的悬浮图表和悬浮图片数据
+  const { resources = [], sheets = {} } = snapshot ?? {};
+
+  const updatedResources = resources?.map((resource: any) => {
+    if (resource?.name === 'SHEET_DRAWING_PLUGIN') {
+      const parseResourceData = JSON.parse(resource?.data ?? '{}');
+
+      const updatedParseResourceData = Object.fromEntries(
+        Object.entries(parseResourceData)?.map(([sheetKey, sheetResourceOption]: any) => {
+          const sheetResourceOptionData = sheetResourceOption?.data || {};
+
+          const updateSheetResourceOption = Object.fromEntries(
+            Object.entries(sheetResourceOptionData).map(([floatDomKey, floatDomValue]: any) => {
+              const { componentKey, drawingId, data = {}, ...floatDomValueRest } = floatDomValue || {};
+
+              if (componentKey === JnpfUniverFloatEchartKey) {
+                if (floatEchartDataCaches?.hasOwnProperty(drawingId)) {
+                  const { height = DefaultFloatEchartHeight, width = DefaultFloatEchartWidth } = floatDomValue.transform ?? {};
+
+                  updateFloatEcharts[drawingId] = {
+                    ...floatEchartDataCaches[drawingId],
+                    height: Math.floor(height),
+                    width: Math.floor(width),
+                  }; // store中的悬浮图表数据过滤
+                }
+
+                // const domId = floatEchartDataCaches?.[drawingId]?.domId;
+                // const updatedProps = {
+                //   ...props,
+                //   id: domId,
+                // };
+
+                return [
+                  floatDomKey,
+                  {
+                    ...floatDomValueRest,
+                    componentKey,
+                    drawingId,
+                    data,
+                  },
+                ];
+              } else if (componentKey === JnpfUniverFloatImageKey) {
+                if (floatImageDataCaches?.hasOwnProperty(drawingId)) {
+                  updateFloatImages[drawingId] = { ...floatImageDataCaches[drawingId] }; // store中的悬浮图片数据过滤
+                }
+
+                // const domId = floatImageDataCaches?.[drawingId]?.domId;
+                // const updatedProps = {
+                //   ...data,
+                //   id: domId,
+                // };
+
+                return [
+                  floatDomKey,
+                  {
+                    ...floatDomValueRest,
+                    componentKey,
+                    drawingId,
+                    data,
+                  },
+                ];
+              } else {
+                return [floatDomKey, floatDomValue];
+              }
+            }),
+          );
+
+          return [sheetKey, { ...sheetResourceOption, data: updateSheetResourceOption }];
+        }),
+      );
+
+      return {
+        ...resource,
+        data: JSON.stringify(updatedParseResourceData),
+      };
+    } else {
+      return resource;
+    }
+  });
+
+  // 取出所有自定义的custom并整理单元格图表的数据
+  const customs: any[] = [];
+  for (const sheetKey in sheets) {
+    const sheet = sheets[sheetKey];
+    const sheetCellData = sheet?.cellData;
+    if (!sheetCellData) {
+      continue;
+    }
+
+    for (const rowKey in sheetCellData) {
+      const row = sheetCellData[rowKey];
+
+      for (const colKey in row) {
+        const cellData = row[colKey] ?? {};
+        const custom = cellData?.custom;
+
+        if (custom && !isEmptyObject(custom)) {
+          if (custom.type === 'chart') {
+            if (!isNullOrUndefined(custom)) {
+              const { chartType, color, drawingId, height, type, width, ...restOption } = custom ?? {};
+              updateCellEcharts[drawingId] = {
+                col: colKey,
+                drawingId,
+                echartType: chartType,
+                height,
+                option: restOption,
+                row: rowKey,
+                sheet: sheetKey,
+                width,
+              };
+            }
+          } else {
+            if (custom.type !== 'jsbarcode' && custom.type !== 'qrCode') {
+              customs.push({
+                cellData,
+                col: colKey,
+                row: rowKey,
+                sheetId: sheetKey,
+              });
+            }
+          }
+        }
+      }
+    }
+  }
+
+  return {
+    cellEcharts: updateCellEcharts,
+    customs,
+    floatEcharts: updateFloatEcharts,
+    floatImages: updateFloatImages,
+    snapshot: {
+      ...snapshot,
+      resources: updatedResources,
+    },
+  };
+}
+
+// 整理预览的数据
+export function arrangePreviewWorkbookData(designWorkbookData: any, floatEchartToUniverImgDataCaches: any, floatImageToUniverImgDataCaches: any) {
+  const { customs, floatEcharts, floatImages, snapshot } = designWorkbookData ?? {};
+
+  const { resources = [], sheets } = snapshot ?? {};
+  const updateSheets = { ...sheets };
+
+  Object.entries(sheets)?.forEach(([sheetKey, sheetInfo]: any) => {
+    const { cellData, columnCount, rowCount } = sheetInfo ?? {};
+
+    const resource = resources.find((resourceInfo: any) => resourceInfo?.name === 'SHEET_DRAWING_PLUGIN');
+    const parseResourceData = JSON.parse(resource?.data ?? '{}');
+
+    const hasFloatDom = !isEmptyObject(parseResourceData) && parseResourceData[sheetKey]?.order?.length;
+
+    const updateCellData = hasFloatDom ? correctSheetCellData(cellData, rowCount, columnCount, true) : correctSheetCellData(cellData, rowCount, columnCount);
+
+    updateSheets[sheetKey] = {
+      ...sheets[sheetKey],
+      cellData: updateCellData,
+    };
+  });
+
+  const updatedResources = resources?.map((resource: any) => {
+    if (resource?.name === 'SHEET_DRAWING_PLUGIN') {
+      const parseResourceData = JSON.parse(resource?.data ?? '{}');
+
+      const updatedParseResourceData = Object.fromEntries(
+        Object.entries(parseResourceData)?.map(([sheetKey, sheetResourceOption]: any) => {
+          const { data: sheetResourceOptionData = {}, order: originalOrder = [] } = sheetResourceOption || {};
+
+          let sheetOrder = [...originalOrder]; // 使用一个可变的引用来追踪顺序更新
+
+          const updatedSheetData = Object.fromEntries(
+            Object.entries(sheetResourceOptionData).map(([floatDomKey, floatDomValue]: any) => {
+              const { componentKey, drawingId, data, sheetTransform, subUnitId, transform, unitId } = floatDomValue || {};
+
+              if (componentKey === JnpfUniverFloatEchartKey) {
+                const { id: domId } = data ?? {};
+
+                sheetOrder = moveArrayValueToEnd(sheetOrder, drawingId);
+
+                const source = floatEchartToUniverImgDataCaches?.[domId] ?? '';
+
+                return [
+                  floatDomKey,
+                  {
+                    drawingId,
+                    drawingType: 0,
+                    imageSourceType: 'BASE64',
+                    sheetTransform,
+                    source,
+                    subUnitId,
+                    transform,
+                    unitId,
+                    data,
+                  },
+                ];
+              } else if (componentKey === JnpfUniverFloatImageKey) {
+                const { id: domId } = data ?? {};
+
+                sheetOrder = moveArrayValueToEnd(sheetOrder, drawingId);
+
+                const source = floatImageToUniverImgDataCaches?.[domId] ?? '';
+
+                return [
+                  floatDomKey,
+                  {
+                    drawingId,
+                    drawingType: 0,
+                    imageSourceType: source?.startsWith('data:image/') ? 'BASE64' : 'URL',
+                    sheetTransform,
+                    source,
+                    subUnitId,
+                    transform,
+                    unitId,
+                    data,
+                  },
+                ];
+              } else {
+                return [floatDomKey, floatDomValue];
+              }
+            }),
+          );
+
+          return [sheetKey, { ...sheetResourceOption, data: updatedSheetData, order: sheetOrder }];
+        }),
+      );
+
+      return {
+        ...resource,
+        data: JSON.stringify(updatedParseResourceData),
+      };
+    } else {
+      return resource;
+    }
+  });
+
+  return {
+    customs,
+    floatEcharts,
+    floatImages,
+    snapshot: {
+      ...snapshot,
+      resources: updatedResources,
+      sheets: updateSheets,
+    },
+  };
+}
+
+// 判断当前工作本是不是含有自定义的 float dom
+export function judgeSheetHasCustomFloatDom(data: any): boolean {
+  return Object.values(data).some(
+    (floatDomValue: any) => floatDomValue?.componentKey === JnpfUniverFloatEchartKey || floatDomValue?.componentKey === JnpfUniverFloatImageKey,
+  );
+}
+
+// 监听插入悬浮图表
+export function onInsertedFloatEchart(jnpfUniverStore: any, command: any) {
+  const { drawingId } = command?.params ?? {};
+  if (!drawingId) {
+    return;
+  }
+
+  const floatEchartDataCaches = jnpfUniverStore?.floatEchartDataCaches ?? {};
+
+  const updateItems = {
+    ...floatEchartDataCaches,
+    [drawingId]: command?.params,
+  };
+
+  jnpfUniverStore?.setFloatEchartDataCaches(updateItems);
+}
+
+// 监听插入悬浮图片
+export function onInsertedFloatImage(jnpfUniverStore: any, command: any) {
+  const { drawingId } = command?.params ?? {};
+  if (!drawingId) {
+    return;
+  }
+
+  const floatImageDataCaches = jnpfUniverStore?.floatImageDataCaches ?? {};
+
+  const updateItems = {
+    ...floatImageDataCaches,
+    [drawingId]: command?.params,
+  };
+
+  jnpfUniverStore?.setFloatImageDataCaches(updateItems);
+}
+
+// 更新浮动元素的storeId
+export function updateFloatStoreId(snapshot: any, dynamicJnpfUniverStoreId: string) {
+  const { resources, ...rest } = snapshot ?? {};
+  if (!resources) {
+    return '{}';
+  }
+
+  const updatedResources = resources?.map((resource: any) => {
+    if (resource?.name === 'SHEET_DRAWING_PLUGIN') {
+      const parseResourceData = JSON.parse(resource?.data ?? '{}');
+
+      const updatedParseResourceData = Object.fromEntries(
+        Object.entries(parseResourceData)?.map(([sheetKey, sheetResourceOption]: any) => {
+          const { data: sheetResourceOptionData = {}, order: originalOrder = [] } = sheetResourceOption || {};
+
+          const sheetOrder = [...originalOrder]; // 使用一个可变的引用来追踪顺序更新
+
+          const updatedSheetData = Object.fromEntries(
+            Object.entries(sheetResourceOptionData).map(([floatDomKey, floatDomValue]: any) => {
+              const { componentKey, data } = floatDomValue || {};
+
+              if (componentKey === JnpfUniverFloatEchartKey || componentKey === JnpfUniverFloatImageKey) {
+                const newData = {
+                  ...data,
+                  piniaStoreId: dynamicJnpfUniverStoreId,
+                };
+
+                return [
+                  floatDomKey,
+                  {
+                    ...floatDomValue,
+                    data: newData,
+                  },
+                ];
+              } else {
+                return [floatDomKey, floatDomValue];
+              }
+            }),
+          );
+
+          return [sheetKey, { ...sheetResourceOption, data: updatedSheetData, order: sheetOrder }];
+        }),
+      );
+
+      return {
+        ...resource,
+        data: JSON.stringify(updatedParseResourceData),
+      };
+    } else {
+      return resource;
+    }
+  });
+
+  return JSON.stringify({
+    ...rest,
+    resources: updatedResources,
+  });
+}

+ 45 - 0
src/components/Report/Design/univer/utils/uuid.ts

@@ -0,0 +1,45 @@
+const hexList: string[] = [];
+for (let i = 0; i <= 15; i++) {
+  hexList[i] = i.toString(16);
+}
+
+export function buildUUID(): string {
+  let uuid = '';
+  for (let i = 1; i <= 36; i++) {
+    switch (i) {
+      case 9:
+      case 14:
+      case 19:
+      case 24: {
+        uuid += '-';
+
+        break;
+      }
+      case 15: {
+        uuid += 4;
+
+        break;
+      }
+      case 20: {
+        uuid += hexList[(Math.random() * 4) | 8];
+
+        break;
+      }
+      default: {
+        uuid += hexList[(Math.random() * 16) | 0];
+      }
+    }
+  }
+  return uuid.replaceAll('-', '');
+}
+export function buildBitUUID(length = 6): string {
+  return buildUUID().slice(0, Math.max(0, length));
+}
+
+let unique = 0;
+export function buildShortUUID(prefix = ''): string {
+  const time = Date.now();
+  const random = Math.floor(Math.random() * 1_000_000_000);
+  unique++;
+  return `${prefix}_${random}${unique}${String(time)}`;
+}

+ 254 - 0
src/components/Report/Preview/index.vue

@@ -0,0 +1,254 @@
+<script lang="ts" setup>
+  import { nextTick, reactive, ref, toRefs, unref } from 'vue';
+
+  import { Button as AButton, Modal as AModal } from 'ant-design-vue';
+
+  import { CreateUnitProps } from '../Design/index';
+  import JnpfUniverDesign from '../Design/index.vue';
+  import { useJnpfUniverStore } from '../Design/store';
+  import JnpfUniverCellEchart from '../Design/univer/components/Echart/cell.vue';
+  import JnpfUniverFloatEchartVM from '../Design/univer/components/Echart/floatVM.vue';
+
+  const emits = defineEmits(['print']);
+
+  const jnpfUniverDesignPreviewRef = ref();
+  const jnpfUniverFloatEchartVMRef = ref();
+  const jnpfUniverCellEchartRef = ref();
+
+  const state = reactive<{
+    echartsImageCache: any;
+    internalOpen: boolean;
+
+    jnpfUniverAPI: any;
+
+    jnpfUniverStore: any;
+  }>({
+    echartsImageCache: {},
+    internalOpen: false,
+
+    jnpfUniverAPI: null,
+
+    jnpfUniverStore: null,
+  });
+  const { echartsImageCache, internalOpen, jnpfUniverStore } = toRefs(state);
+
+  // 创建预览实例
+  function handleCreatePreviewUnit(options?: CreateUnitProps) {
+    const { cellEcharts: cellEchartsObject, defaultActiveSheetId, floatEcharts, floatImages, snapshot } = options ?? {};
+    if (snapshot === undefined) {
+      console.warn('预览数据错误');
+      return;
+    }
+
+    state.internalOpen = true;
+
+    const updateSheets = JSON.parse(JSON.stringify(snapshot?.sheets ?? {}));
+
+    nextTick(() => {
+      // 单元格图表数据转成成数组
+      const cellEchartsArray = cellEchartsObjectToArray(cellEchartsObject);
+
+      // 单元格图表转单元格图片
+      cellEchartsArray.forEach(cellEchartOption => {
+        const { colKey, drawingId, height: echartHeight, option: echartOption, rowKey, sheetKey, width: echartWidth } = cellEchartOption;
+
+        const echartImg = unref(jnpfUniverCellEchartRef).render(echartOption, echartWidth, echartHeight);
+
+        try {
+          const targetDrawing = updateSheets?.[sheetKey]?.cellData?.[rowKey]?.[colKey]?.p?.drawings?.[drawingId];
+
+          updateSheets[sheetKey].cellData[rowKey][colKey].p.drawings[drawingId] = {
+            ...targetDrawing,
+            source: echartImg,
+          };
+        } catch {
+          console.log('渲染单元格图表失败');
+        }
+      });
+
+      // 将单元格图表的图片更新到快照中
+      snapshot.sheets = JSON.parse(JSON.stringify(updateSheets));
+
+      // 构建univer实例
+      const { jnpfUniverAPI } = unref(jnpfUniverDesignPreviewRef)?.handleCreateDesignUnit({
+        defaultActiveSheetId,
+        floatEcharts,
+        floatImages,
+        loading: true,
+        mode: 'preview',
+        snapshot,
+        uiContextMenu: false,
+        uiHeader: false,
+        workbookReadonly: true,
+      });
+      state.jnpfUniverAPI = jnpfUniverAPI ?? null;
+
+      // 创建悬浮图表的图片
+      createFloatEchartsImage(floatEcharts);
+    });
+  }
+
+  // 打印
+  function handlePrint() {
+    const info = getInstanceInfo();
+
+    emits('print', { ...info });
+  }
+
+  // 导出
+  function handleExport() {
+    const info = getInstanceInfo();
+
+    console.log(info?.snapshot);
+    alert('看控制台的快照');
+  }
+
+  // 关闭弹窗 (建议有个时间缓存周期再关闭弹窗,要销毁设计器)
+  function handleCancel() {
+    unref(jnpfUniverDesignPreviewRef)?.handleDisposeUnit();
+    resetUniverState(); // 重置实例状态
+  }
+
+  // 单元格图表数据转成成数组
+  function cellEchartsObjectToArray(data: any) {
+    const res: any[] = [];
+    for (const sheetKey in data) {
+      const rows = data[sheetKey];
+
+      for (const rowKey in rows) {
+        const cols = rows[rowKey];
+
+        for (const colKey in cols) {
+          const { drawingId, height, option, width } = cols[colKey];
+
+          res.push({
+            colKey,
+            drawingId,
+            height,
+            option,
+            rowKey,
+            sheetKey,
+            width,
+          });
+        }
+      }
+    }
+
+    return res;
+  }
+
+  // 创建悬浮图表的图片信息
+  function createFloatEchartsImage(echarts: any) {
+    const dynamicJnpfUniverStoreId = unref(jnpfUniverDesignPreviewRef)?.getDynamicStoreId();
+    if (!dynamicJnpfUniverStoreId) {
+      return;
+    }
+
+    const jnpfUniverStore = useJnpfUniverStore(dynamicJnpfUniverStoreId);
+    state.jnpfUniverStore = jnpfUniverStore; // 先赋值给 state
+
+    const caches = { ...unref(jnpfUniverStore.floatEchartToUniverImgDataCaches) };
+
+    for (const sheetKey in echarts) {
+      const target = echarts[sheetKey];
+
+      const { domId, height, option, width } = target;
+
+      const res = unref(jnpfUniverFloatEchartVMRef).render(domId, option, width, height);
+      caches[res.domId] = res?.source ?? {};
+
+      Object.assign(state.echartsImageCache, caches); // 直接修改原对象,保持响应性
+    }
+  }
+
+  // 获取实例数据
+  function getInstanceInfo() {
+    jnpfUniverStore.value?.setFloatEchartToUniverImgDataCaches(echartsImageCache.value);
+
+    const { snapshot } = unref(jnpfUniverDesignPreviewRef)?.getPreviewWorkbookData();
+    const defaultActiveSheetId = unref(jnpfUniverDesignPreviewRef)?.getActiveWorksheetId();
+
+    return {
+      defaultActiveSheetId,
+      snapshot,
+    };
+  }
+
+  // 重置实例状态
+  function resetUniverState() {
+    state.jnpfUniverAPI = null;
+    state.jnpfUniverStore = null;
+
+    state.echartsImageCache = {};
+  }
+
+  defineExpose({ handleCreatePreviewUnit });
+</script>
+
+<template>
+  <AModal v-model:open="internalOpen" title="设计预览" width="100%" wrap-class-name="preview-full-modal" @cancel="handleCancel">
+    <!--    测试按钮,随时可以删除掉    -->
+    <div class="buttons-wrap">
+      <AButton type="primary" @click="handlePrint">打印预览</AButton>
+      <AButton style="margin-left: 10px; background: deeppink" type="primary" @click="handleExport">导出快照</AButton>
+    </div>
+
+    <div class="jnpf-univer-preview-module">
+      <JnpfUniverDesign v-if="internalOpen" ref="jnpfUniverDesignPreviewRef" />
+
+      <div id="floatEchartVMContent" class="cell-echarts-vm-content">
+        <JnpfUniverFloatEchartVM ref="jnpfUniverFloatEchartVMRef" univer-create-mode="preview" />
+      </div>
+      <div id="cellEchartsContent" class="cell-echarts-content">
+        <JnpfUniverCellEchart ref="jnpfUniverCellEchartRef" univer-create-mode="preview" />
+      </div>
+    </div>
+  </AModal>
+</template>
+
+<style lang="scss">
+  .preview-full-modal {
+    .ant-modal {
+      top: 0;
+      max-width: 100%;
+      padding-bottom: 0;
+      margin: 0;
+    }
+
+    .ant-modal-content {
+      display: flex;
+      flex-direction: column;
+      height: calc(100vh);
+      padding: 20px 10px 0;
+    }
+
+    .ant-modal-body {
+      display: flex;
+      flex: 1;
+      flex-direction: column;
+
+      .jnpf-univer-preview-module {
+        position: relative;
+        flex: 1;
+      }
+    }
+
+    .ant-modal-footer {
+      display: none;
+    }
+
+    // 隐藏的dom节点
+    .cell-echarts-content {
+      position: absolute;
+      inset: 0;
+      z-index: -1;
+    }
+
+    // 隐藏的dom节点
+    .cell-echarts-vm-content {
+      position: absolute;
+      inset: 0;
+      z-index: -2;
+    }
+  }
+</style>

+ 176 - 0
src/components/Report/Print/Form/index.vue

@@ -0,0 +1,176 @@
+<script lang="ts" setup>
+  import { nextTick, reactive, toRefs, watch } from 'vue';
+
+  import { Form as AForm, FormItem as AFormItem, RadioGroup as ARadioGroup, Select as ASelect, Switch as ASwitch } from 'ant-design-vue';
+  import { debounce } from 'lodash-es';
+
+  import {
+    jnpfPaperPaddingTypeOptions,
+    jnpfPaperTypeOptions,
+    jnpfPrintAreaOptions,
+    jnpfPrintDirectionOptions,
+    jnpfPrintHAlignOptions,
+    jnpfPrintScaleOptions,
+    jnpfPrintVAlignOptions,
+  } from '../../Design/univer/utils/define';
+
+  const props = defineProps(['defaultPrintConfig', 'sheetFreezeStatus']);
+
+  const emits = defineEmits(['change']);
+
+  interface State {
+    formData: Record<string, any>;
+  }
+  const state = reactive<State>({
+    formData: {},
+  });
+  const { formData } = toRefs(state);
+
+  let isInitialized = false; // 标志位,确保 `watch` 仅在初始化后触发
+
+  // radio数据转化下格式
+  function getRadioGroupOptions(data: any[] = []) {
+    return data?.map((item: any) => {
+      return { label: item.fullName, value: item.id };
+    });
+  }
+
+  watch(
+    () => props.sheetFreezeStatus,
+    val => {
+      const updatePrintConfig = {
+        ...props.defaultPrintConfig,
+        xFreeze: val?.colHasFreeze,
+        yFreeze: val?.rowHasFreeze,
+      };
+
+      state.formData = JSON.parse(JSON.stringify(updatePrintConfig));
+
+      nextTick(() => {
+        isInitialized = true;
+      });
+    },
+  );
+
+  // 防抖后的触发方法
+  const emitChangeDebounced = debounce((val: any) => {
+    emits('change', val);
+  }, 300);
+
+  // 监听更改
+  watch(
+    () => state.formData,
+    val => {
+      if (isInitialized) {
+        emitChangeDebounced(val);
+      }
+    },
+    { deep: true, flush: 'post' },
+  );
+</script>
+
+<template>
+  <div class="print-render-form-content">
+    <AForm layout="vertical">
+      <AFormItem label="打印范围">
+        <ASelect v-model:value="formData.printArea" :options="jnpfPrintAreaOptions" :field-names="{ label: 'fullName', value: 'id' }" />
+      </AFormItem>
+    </AForm>
+    <AForm layout="vertical">
+      <AFormItem label="纸张类型">
+        <ASelect v-model:value="formData.paperType" :options="jnpfPaperTypeOptions" :field-names="{ label: 'fullName', value: 'id' }" />
+      </AFormItem>
+    </AForm>
+    <AForm layout="vertical">
+      <AFormItem label="纸张边距">
+        <ASelect v-model:value="formData.padding" :options="jnpfPaperPaddingTypeOptions" :field-names="{ label: 'fullName', value: 'id' }" />
+      </AFormItem>
+    </AForm>
+    <AForm layout="vertical">
+      <AFormItem label="纸张方向">
+        <ARadioGroup v-model:value="formData.direction" :options="getRadioGroupOptions(jnpfPrintDirectionOptions)" option-type="button" button-style="solid" />
+      </AFormItem>
+    </AForm>
+    <AForm layout="vertical">
+      <AFormItem label="页面缩放">
+        <ASelect v-model:value="formData.printScale" :options="jnpfPrintScaleOptions" :field-names="{ label: 'fullName', value: 'id' }" />
+      </AFormItem>
+    </AForm>
+    <AForm layout="vertical">
+      <AFormItem label="左右对齐">
+        <ARadioGroup v-model:value="formData.hAlign" :options="getRadioGroupOptions(jnpfPrintHAlignOptions)" option-type="button" button-style="solid" />
+      </AFormItem>
+    </AForm>
+    <AForm layout="vertical">
+      <AFormItem label="上下对齐">
+        <ARadioGroup v-model:value="formData.vAlign" :options="getRadioGroupOptions(jnpfPrintVAlignOptions)" option-type="button" button-style="solid" />
+      </AFormItem>
+    </AForm>
+    <AForm layout="vertical">
+      <AFormItem label="网格线">
+        <ASwitch v-model:checked="formData.gridlines" />
+      </AFormItem>
+    </AForm>
+    <AForm layout="vertical" style="display: flex">
+      <div style="flex: 1">
+        <AFormItem label="报表名称">
+          <ASwitch v-model:checked="formData.workbookTitle" />
+        </AFormItem>
+      </div>
+      <div style="flex: 1">
+        <AFormItem label="工作本名称">
+          <ASwitch v-model:checked="formData.worksheetTitle" />
+        </AFormItem>
+      </div>
+    </AForm>
+    <AForm layout="vertical" style="display: flex">
+      <div style="flex: 1">
+        <AFormItem label="当前日期">
+          <ASwitch v-model:checked="formData.printDate" />
+        </AFormItem>
+      </div>
+      <div style="flex: 1">
+        <AFormItem label="当前时间">
+          <ASwitch v-model:checked="formData.printTime" />
+        </AFormItem>
+      </div>
+    </AForm>
+    <AForm layout="vertical">
+      <AFormItem label="页码">
+        <ASwitch v-model:checked="formData.pageNumber" />
+      </AFormItem>
+    </AForm>
+    <AForm layout="vertical" style="display: flex">
+      <div style="flex: 1">
+        <AFormItem label="重复冻结行">
+          <ASwitch v-model:checked="formData.yFreeze" :disabled="!sheetFreezeStatus.rowHasFreeze" />
+        </AFormItem>
+      </div>
+      <div style="flex: 1">
+        <AFormItem label="重复冻结列">
+          <ASwitch v-model:checked="formData.xFreeze" :disabled="!sheetFreezeStatus.colHasFreeze" />
+        </AFormItem>
+      </div>
+    </AForm>
+  </div>
+</template>
+
+<style lang="scss">
+  .print-render-form-content {
+    box-sizing: border-box;
+    width: 290px;
+    height: calc(100%);
+    padding: 10px;
+    margin-left: 16px;
+    overflow: hidden auto;
+    background: #fff;
+
+    .ant-form-item-label {
+      padding-bottom: 2px;
+    }
+
+    .ant-form-item {
+      margin-bottom: 9px;
+    }
+  }
+</style>

+ 87 - 0
src/components/Report/Print/Render/Page/index.vue

@@ -0,0 +1,87 @@
+<script lang="ts" setup>
+  import { nextTick, onMounted, onUnmounted, reactive, ref, toRefs, unref } from 'vue';
+
+  import { JnpfSheetsPrintUiService } from '../../../Design/univer/services/sheet-print-ui.service';
+  import { printA4PaperHeight, printA4PaperWidth } from '../../../Design/univer/utils/define';
+
+  const props = defineProps(['jnpfUniverApi', 'pageLayout', 'printConfig', 'watermarkConfig']);
+  const emits = defineEmits(['pageRendered']);
+
+  // ✅ 页面容器引用
+  const pageContainerRef = ref<HTMLElement | null>(null);
+  const defaultContainerStyle = {
+    height: printA4PaperHeight,
+    width: printA4PaperWidth,
+  };
+
+  const state = reactive<{
+    containerStyle: any;
+    renderInstance: any;
+  }>({
+    containerStyle: defaultContainerStyle,
+    renderInstance: null,
+  });
+  const { containerStyle } = toRefs(state);
+
+  onMounted(() => {
+    const { jnpfUniverApi, pageLayout = {}, printConfig = {}, watermarkConfig = {} } = props ?? {};
+
+    const accessor = jnpfUniverApi?.getInjector();
+    if (!accessor) {
+      return;
+    }
+
+    const { paperSize = {} } = pageLayout;
+    const containerScale = countContainerScale(paperSize); // 打印预览容器的伸缩比
+
+    state.renderInstance = new JnpfSheetsPrintUiService(accessor, pageLayout, printConfig, watermarkConfig, containerScale);
+    if (!state.renderInstance) {
+      return;
+    }
+
+    const { h: paperSizeHeight = printA4PaperHeight, w: paperSizeWidth = printA4PaperWidth } = paperSize;
+    state.containerStyle = {
+      height: `${paperSizeHeight * containerScale}px`,
+      width: `${paperSizeWidth * containerScale}px`,
+    };
+
+    state.renderInstance.container.style.cssText = 'width: 100%; height: 100%;';
+
+    nextTick(() => {
+      unref(pageContainerRef)?.append(state.renderInstance.container);
+      state.renderInstance.container.append(state.renderInstance.root);
+      state.renderInstance?.setDirty(true); // 设置脏状态(用于调整视口大小和滚动值的状态)
+      state.renderInstance?.renderPage(); // 渲染页面
+      state.renderInstance?.renderOnReady(); // 主要是在页面变换时确保页面内容的及时更新,并且做了订阅和销毁的管理。
+      emits('pageRendered');
+    });
+  });
+
+  onUnmounted(async () => {
+    await state.renderInstance?.dispose();
+
+    state.renderInstance = null;
+    state.containerStyle = defaultContainerStyle;
+  });
+
+  // 计算打印预览容器的伸缩比
+  function countContainerScale(paperSize: any) {
+    const { w = printA4PaperWidth } = paperSize;
+
+    return Math.round((printA4PaperWidth / w) * 10) / 10;
+  }
+</script>
+
+<template>
+  <div ref="pageContainerRef" :style="containerStyle" class="print-page-container"></div>
+</template>
+
+<style lang="scss">
+  .print-page-container {
+    position: relative;
+    flex: 0 0 auto;
+    margin: 28px auto;
+    background-color: #fff;
+    box-shadow: 0 4px 24px 0 rgb(30 34 43 / 8%);
+  }
+</style>

+ 580 - 0
src/components/Report/Print/Render/index.vue

@@ -0,0 +1,580 @@
+<script lang="ts" setup>
+  import { computed, nextTick, reactive, ref, toRefs } from 'vue';
+
+  import { debounce } from 'lodash-es';
+
+  import { JnpfSheetsPrintUiService } from '../../Design/univer/services/sheet-print-ui.service';
+  import { getPrintPageStyle } from '../../Design/univer/utils';
+  import { printA4PaperHeight, printA4PaperWidth } from '../../Design/univer/utils/define';
+  import JnpfUniverPrintRenderPage from './Page/index.vue';
+
+  defineOptions({ name: 'JnpfUniverPrint' });
+
+  const props = defineProps(['jnpfUniverApi']);
+  const emits = defineEmits(['previewRenderProgress', 'printRenderProgress', 'browserPrintShow']);
+
+  const batchRenderSize = 10; // 分页加载的数量
+  const pagesContainerRef = ref();
+
+  const state = reactive<{
+    completePagesLayout: any[];
+    currentViewPageNumber: number;
+
+    pagingRenderedQuantity: number;
+    pagingTargetQuantity: number;
+    paperSize: any;
+
+    paperViewHeight: number;
+    printConfig: any;
+    printRenderInstances: any[];
+
+    processPagesLayout: any[];
+
+    totalRenderedQuantity: number;
+
+    watermarkConfig: any;
+  }>({
+    completePagesLayout: [], // 完整的页面架构
+    currentViewPageNumber: 1, // 当前视图页码
+
+    pagingRenderedQuantity: 0,
+    pagingTargetQuantity: 0,
+    paperSize: null,
+
+    paperViewHeight: printA4PaperHeight,
+    printConfig: null,
+    printRenderInstances: [], // 打印渲染的实例
+
+    processPagesLayout: [], // 进程中页面架构
+
+    totalRenderedQuantity: 0,
+
+    watermarkConfig: {},
+  });
+  const { completePagesLayout, currentViewPageNumber, printConfig, processPagesLayout, watermarkConfig } = toRefs(state);
+
+  // 禁止向前翻页状态
+  const disabledForward = computed(() => {
+    return state.currentViewPageNumber <= 1;
+  });
+
+  // 禁止向后翻页状态
+  const disabledBackward = computed(() => {
+    return state.currentViewPageNumber >= state.completePagesLayout.length;
+  });
+
+  // 翻页 - 去首页
+  function setFirstViewPageNumber() {
+    state.currentViewPageNumber = 1;
+
+    handleScrollToPosition();
+  }
+
+  // 翻页 - 去尾页
+  function setLastViewPageNumber() {
+    state.currentViewPageNumber = state.completePagesLayout.length;
+
+    handleScrollToPosition();
+  }
+
+  // 翻页 - 前后单页控制
+  function setSingleViewPageNumber(direction: 'next' | 'prev') {
+    const maxPage = state.completePagesLayout.length;
+    const currentPage = state.currentViewPageNumber;
+
+    // 根据方向计算目标页码
+    const newPageNumber =
+      direction === 'prev'
+        ? Math.max(currentPage - 1, 1) // 前一页,不能小于 1
+        : Math.min(currentPage + 1, maxPage); // 下一页,不能大于总页数
+
+    // 如果页码发生变化,更新并滚动
+    if (newPageNumber !== currentPage) {
+      state.currentViewPageNumber = newPageNumber;
+      handleScrollToPosition();
+    }
+  }
+
+  // 翻页 - 直接输入页码
+  function setInputViewPageNumber() {
+    const pageNumber = Number(state.currentViewPageNumber);
+
+    if (Number.isNaN(pageNumber) || pageNumber < 1) {
+      state.currentViewPageNumber = 1;
+    } else if (pageNumber > state.completePagesLayout?.length) {
+      state.currentViewPageNumber = state.completePagesLayout.length;
+    }
+
+    handleScrollToPosition();
+  }
+
+  // 设置滚动的位置
+  function handleScrollToPosition() {
+    const targetScrollTop = state.paperViewHeight * (state.currentViewPageNumber - 1) + 28 * (state.currentViewPageNumber - 1);
+
+    pagesContainerRef.value.scrollTo({
+      behavior: 'smooth',
+      top: targetScrollTop,
+    });
+  }
+
+  // 定义防抖后的函数
+  const debouncedOnPagesContainerScroll = debounce(() => {
+    const scrollTop = pagesContainerRef.value.scrollTop;
+
+    state.currentViewPageNumber = Math.ceil((scrollTop + 28) / (state.paperViewHeight + 28));
+  }, 100); // 100 毫秒的防抖时间,可以根据需要调整
+
+  // 开启监听容器滚动事件
+  function onPagesContainerScroll() {
+    if (pagesContainerRef.value) {
+      pagesContainerRef.value.addEventListener('scroll', debouncedOnPagesContainerScroll);
+    }
+  }
+
+  // 关闭监听容器滚动事件
+  function offPagesContainerScroll() {
+    if (pagesContainerRef.value) {
+      pagesContainerRef.value.removeEventListener('scroll', debouncedOnPagesContainerScroll);
+    }
+  }
+
+  // 暴露 - 处理打印渲染
+  async function handlePrintRender(printConfig: any, watermarkConfig: any) {
+    resetState();
+
+    state.watermarkConfig = watermarkConfig ?? {};
+
+    const { jnpfUniverApi } = props ?? {};
+
+    try {
+      if (!jnpfUniverApi) {
+        return;
+      }
+
+      const res = await getLayoutsAsync(printConfig);
+      const { pagesLayout, paperSize, printConfig: updatePrintConfig } = (res ?? {}) as any;
+
+      if (!pagesLayout.length) {
+        emits('previewRenderProgress', {
+          rendered: 0,
+          total: 0,
+        });
+        return;
+      }
+
+      state.completePagesLayout = Array.isArray(pagesLayout) ? [...pagesLayout] : [];
+      state.printConfig = { ...updatePrintConfig };
+      state.paperSize = { ...paperSize };
+
+      batchRenderPreviewPages();
+    } catch (error) {
+      console.error('Error:', error);
+    }
+  }
+
+  // 获得布局的结果
+  function getLayoutsAsync(config: any) {
+    const { jnpfUniverApi } = props ?? {};
+
+    return new Promise((resolve, reject) => {
+      try {
+        const result = jnpfUniverApi?.getSheetsPrint()?.getLayouts(config);
+        resolve(result);
+      } catch (error) {
+        reject(error);
+      }
+    });
+  }
+
+  // 批量加载预览页面
+  function batchRenderPreviewPages() {
+    const { completePagesLayout, processPagesLayout } = state;
+
+    state.pagingRenderedQuantity = 0;
+
+    // 全部都渲染完了
+    if (processPagesLayout.length >= completePagesLayout.length) {
+      state.pagingTargetQuantity = 0;
+      return;
+    }
+
+    if (completePagesLayout.length <= batchRenderSize) {
+      state.pagingTargetQuantity = completePagesLayout.length;
+      state.processPagesLayout = [...completePagesLayout];
+    } else {
+      const lastPageIndex = processPagesLayout.length;
+      const batchData = completePagesLayout.slice(lastPageIndex, lastPageIndex + batchRenderSize) ?? [];
+
+      state.pagingTargetQuantity = batchData.length;
+      state.processPagesLayout = [...processPagesLayout, ...batchData];
+    }
+  }
+
+  // 预览页面渲染回调
+  function handlePreviewPageRendered() {
+    state.pagingRenderedQuantity += 1;
+    state.totalRenderedQuantity += 1;
+
+    emits('previewRenderProgress', {
+      rendered: state.totalRenderedQuantity,
+      total: state.completePagesLayout.length,
+    });
+
+    if (state.pagingRenderedQuantity === state.pagingTargetQuantity && state.processPagesLayout.length < state.completePagesLayout.length) {
+      setTimeout(batchRenderPreviewPages, 500);
+    } else if (state.pagingRenderedQuantity === state.pagingTargetQuantity && state.processPagesLayout.length >= state.completePagesLayout.length) {
+      nextTick(() => {
+        const firstPageDom = document.getElementsByClassName('print-page-container')?.[0] as HTMLElement | undefined;
+
+        if (firstPageDom) {
+          const styleHeight = firstPageDom?.style?.height;
+          state.paperViewHeight = Number.parseInt(styleHeight);
+        } else {
+          state.paperViewHeight = printA4PaperHeight;
+        }
+
+        // 绑定容器的滚动事件
+        onPagesContainerScroll();
+      });
+    }
+  }
+
+  // 暴露 - 浏览器打印
+  function handleBrowserPrint() {
+    emits('printRenderProgress', {
+      rendered: 0,
+      total: state.completePagesLayout.length,
+    });
+
+    state.printRenderInstances = [];
+
+    const { jnpfUniverApi } = props ?? {};
+
+    const accessor = jnpfUniverApi?.getInjector();
+    if (!accessor) {
+      return;
+    }
+
+    batchRenderPrintPages(accessor);
+  }
+
+  // 批量加载打印页面
+  function batchRenderPrintPages(accessor: any) {
+    const { completePagesLayout, printConfig, printRenderInstances } = state;
+
+    // 已经全部渲染完成,直接返回
+    if (printRenderInstances.length >= completePagesLayout.length) {
+      createPrintEle();
+
+      return;
+    }
+
+    // 获取未渲染的页面数据
+    const lastPageIndex = printRenderInstances.length;
+    const batchData = completePagesLayout.slice(lastPageIndex, lastPageIndex + batchRenderSize) ?? [];
+
+    if (!batchData.length) {
+      return printRenderInstances;
+    }
+
+    // 批量创建渲染实例
+    const renderInstances = batchData.map((pageLayout: any) => {
+      // 这里要加水印配置
+      const renderInstance = new JnpfSheetsPrintUiService(accessor, pageLayout, printConfig, state.watermarkConfig);
+      renderInstance.container.style.cssText = 'width: 100%; height: 100%;';
+      return renderInstance;
+    });
+
+    // 更新数据
+    state.printRenderInstances.push(...renderInstances);
+
+    emits('printRenderProgress', {
+      rendered: state.printRenderInstances.length,
+      total: state.completePagesLayout.length,
+    });
+
+    setTimeout(() => batchRenderPrintPages(accessor), 500);
+  }
+
+  // 创建打印节点
+  function createPrintEle() {
+    const { completePagesLayout, paperSize, printConfig, printRenderInstances } = state;
+    const { h: paperSizeHeight, w: paperSizeWidth = printA4PaperWidth } = paperSize ?? {};
+    const { direction, paperType } = printConfig ?? {};
+    const printStyle = getPrintPageStyle(paperType, direction);
+
+    const containerEle = document.createElement('div');
+    containerEle.id = 'jnpfReportPrint';
+
+    printRenderInstances.forEach((renderInstance: any, index: number) => {
+      const pageLayout = completePagesLayout[index];
+
+      if (pageLayout) {
+        const { browserPreviewScale } = pageLayout ?? {};
+        const scale = Math.round(browserPreviewScale * (printA4PaperWidth / paperSizeWidth) * 10) / 10;
+
+        const pageEle = document.createElement('div');
+        pageEle.className = 'printContainer';
+
+        Object.assign(pageEle.style, {
+          height: `${paperSizeHeight * scale}px`,
+          width: `${paperSizeWidth * scale}px`,
+        });
+
+        pageEle.append(renderInstance.container);
+        containerEle.append(pageEle);
+      } else {
+        containerEle.append(renderInstance.container);
+      }
+    });
+
+    document.head.append(printStyle);
+    document.body.append(containerEle);
+
+    nextTick(() => {
+      setTimeout(() => {
+        emits('browserPrintShow');
+
+        window.addEventListener('beforeprint', () => {
+          printRenderInstances.forEach((renderInstance: any) => {
+            renderInstance?.setDirty(true); // 设置脏状态(用于调整视口大小和滚动值的状态)
+            renderInstance?.renderPage(); // 渲染页面
+            renderInstance?.renderOnReady(); // 主要是在页面变换时确保页面内容的及时更新,并且做了订阅和销毁的管理。
+          });
+        });
+
+        window.addEventListener('afterprint', () => {
+          printRenderInstances.forEach(async (renderInstance: any) => {
+            await renderInstance.dispose();
+            renderInstance = null;
+          });
+          printStyle.remove();
+          containerEle.remove();
+        });
+
+        window.print();
+      }, 100);
+    });
+  }
+
+  // 暴露 - 重置
+  function resetState() {
+    if (pagesContainerRef.value) {
+      pagesContainerRef.value.scrollTop = 0;
+    }
+
+    state.completePagesLayout = [];
+    state.processPagesLayout = [];
+
+    state.printRenderInstances = [];
+
+    state.pagingTargetQuantity = 0;
+    state.pagingRenderedQuantity = 0;
+    state.totalRenderedQuantity = 0;
+
+    state.printConfig = null;
+    state.paperSize = null;
+    state.paperViewHeight = printA4PaperHeight;
+
+    state.currentViewPageNumber = 1;
+
+    state.watermarkConfig = {};
+
+    offPagesContainerScroll();
+  }
+
+  defineExpose({ handleBrowserPrint, handlePrintRender, resetState });
+</script>
+
+<template>
+  <div class="print-render-content">
+    <div class="tools-wrap">
+      <span class="first-page" :class="[{ disabled: disabledForward }]" @click="setFirstViewPageNumber" title="首页">
+        <svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
+          <path
+            d="M507.733333 490.666667L768 230.4 704 170.666667 384 490.666667l320 320 59.733333-59.733334-256-260.266666zM341.333333 170.666667H256v640h85.333333V170.666667z" />
+        </svg>
+        <em>首页</em>
+      </span>
+      <span class="prev-page" :class="[{ disabled: disabledForward }]" @click="setSingleViewPageNumber('prev')" title="上一页" style="margin-right: 12px">
+        <svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
+          <path
+            d="M473.6 490.666667L789.333333 170.666667 853.333333 230.4l-260.266666 260.266667 260.266666 260.266666-64 59.733334-315.733333-320z m-302.933333 0L490.666667 170.666667l59.733333 59.733333-260.266667 260.266667 260.266667 260.266666-59.733333 59.733334L170.666667 490.666667z" />
+        </svg>
+        <em>上一页</em>
+      </span>
+      <input class="target-page" v-model="currentViewPageNumber" type="number" @blur="setInputViewPageNumber" min="1" :max="completePagesLayout.length" />
+      <span class="total-page-number">
+        <em>/</em>
+        <strong>{{ completePagesLayout.length }}</strong>
+      </span>
+      <span class="next-page" :class="[{ disabled: disabledBackward }]" @click="setSingleViewPageNumber('next')" title="下一页" style="margin-left: 12px">
+        <em>下一页</em>
+        <svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
+          <path
+            d="M550.4 490.666667L230.4 170.666667 170.666667 230.4l260.266666 260.266667L170.666667 750.933333 230.4 810.666667l320-320z m298.666667 0L533.333333 170.666667 469.333333 230.4l260.266667 260.266667-260.266667 260.266666 59.733334 59.733334 320-320z" />
+        </svg>
+      </span>
+      <span class="last-page" :class="[{ disabled: disabledBackward }]" @click="setLastViewPageNumber" title="尾页">
+        <em>尾页</em>
+        <svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
+          <path
+            d="M516.266667 490.666667L256 230.4 315.733333 170.666667l320 320L315.733333 810.666667 256 750.933333l260.266667-260.266666zM678.4 170.666667h85.333333v640h-85.333333V170.666667z" />
+        </svg>
+      </span>
+    </div>
+    <div ref="pagesContainerRef" class="print-render-pages-content">
+      <template v-for="pageLayout in processPagesLayout" :key="pageLayout?.layoutId">
+        <JnpfUniverPrintRenderPage
+          :jnpf-univer-api="props.jnpfUniverApi"
+          :page-layout="pageLayout"
+          :print-config="printConfig"
+          :watermark-config="watermarkConfig"
+          @page-rendered="handlePreviewPageRendered" />
+      </template>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+  .print-render-content {
+    display: flex;
+    flex: 1;
+    flex-direction: column;
+    overflow: auto;
+
+    .tools-wrap {
+      display: flex;
+      justify-content: center;
+      min-width: 500px;
+      padding: 10px 0;
+      user-select: none;
+
+      .first-page,
+      .prev-page,
+      .next-page,
+      .last-page {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        //width: 32px;
+        height: 32px;
+        padding: 0 8px;
+        color: rgb(0 0 0 / 88%);
+        cursor: pointer;
+        border-radius: 6px;
+        transition: all 0.2s;
+
+        .icon {
+          fill: rgb(0 0 0 / 88%);
+        }
+
+        &.disabled {
+          color: rgb(0 0 0 / 25%);
+          cursor: not-allowed;
+
+          .icon {
+            fill: rgb(0 0 0 / 25%);
+          }
+        }
+
+        em {
+          margin-left: 2px;
+          font-size: 14px;
+          font-style: normal;
+        }
+
+        &:hover {
+          background: rgb(0 0 0 / 6%);
+        }
+      }
+
+      .target-page {
+        box-sizing: border-box;
+        width: 48px;
+        height: 32px;
+        font-size: 14px;
+        color: rgb(0 0 0 / 88%);
+        text-align: center;
+        appearance: textfield;
+        appearance: none;
+        outline: none;
+        background: #fff;
+        border: 1px solid #d9d9d9;
+        border-radius: 6px;
+        transition: all 0.2s;
+
+        &::-webkit-inner-spin-button,
+        &::-webkit-outer-spin-button {
+          margin: 0;
+          appearance: none;
+        }
+      }
+
+      .total-page-number {
+        display: flex;
+        line-height: 32px;
+        color: rgb(0 0 0 / 88%);
+
+        em {
+          margin: 0 4px;
+          font-style: normal;
+          color: #1677ff;
+        }
+
+        strong {
+          font-weight: normal;
+        }
+      }
+    }
+
+    .print-render-pages-content {
+      position: relative;
+      flex: 1;
+      overflow: auto;
+
+      .print-page-container:first-child {
+        margin-top: 0;
+      }
+    }
+  }
+
+  .dark,
+  .dark[data-theme='custom'],
+  .dark[data-theme='default'] {
+    .print-render-content {
+      color: #fff;
+
+      .tools-wrap {
+        .first-page,
+        .prev-page,
+        .next-page,
+        .last-page,
+        .total-page-number {
+          color: #fff;
+
+          em {
+            color: #fff;
+          }
+
+          &.disabled {
+            color: rgb(255 255 255 / 70%);
+
+            em {
+              color: rgb(255 255 255 / 70%);
+            }
+
+            .icon {
+              fill: rgb(255 255 255 / 70%);
+            }
+          }
+        }
+
+        .icon {
+          fill: #fff;
+        }
+      }
+    }
+  }
+</style>

+ 276 - 0
src/components/Report/Print/index.vue

@@ -0,0 +1,276 @@
+<script lang="ts" setup>
+  import { nextTick, reactive, ref, toRefs, unref } from 'vue';
+
+  import { IWorkbookData } from '@univerjs/core';
+  import { SetWorksheetActiveOperation } from '@univerjs/sheets';
+  import { Button as AButton, Modal as AModal } from 'ant-design-vue';
+
+  import JnpfUniverDesign from '../Design/index.vue';
+  import { DefaultPrintConfig } from '../Design/univer/utils/define';
+  import JnpfUniverPrintForm from './Form/index.vue';
+  import JnpfUniverPrintRender from './Render/index.vue';
+
+  const jnpfUniverDesignPrintRef = ref();
+  const jnpfUniverPrintRenderRef = ref();
+
+  // 默认数据
+  const defaultRenderProgressTip = ' '; // 加载文案
+  const defaultFreezeStatus = {
+    colHasFreeze: false,
+    // 是否存在冻结项
+    rowHasFreeze: false,
+  };
+
+  interface State {
+    internalOpen: boolean;
+
+    jnpfUniverAPI: any;
+    loading: boolean;
+
+    printRenderTip: string;
+
+    sheetFreezeStatus: any;
+  }
+  const state = reactive<State>({
+    internalOpen: false,
+
+    jnpfUniverAPI: null,
+    loading: false,
+
+    printRenderTip: defaultRenderProgressTip,
+
+    sheetFreezeStatus: { ...defaultFreezeStatus },
+  });
+  const { internalOpen, jnpfUniverAPI, loading, printRenderTip, sheetFreezeStatus } = toRefs(state);
+
+  // 创建打印实例
+  function handleCreatePrintUnit(data: { defaultActiveSheetId?: string; snapshot: IWorkbookData }) {
+    state.internalOpen = true;
+
+    state.printRenderTip = defaultRenderProgressTip;
+    state.loading = true;
+
+    const { defaultActiveSheetId, snapshot } = data ?? {};
+
+    nextTick(() => {
+      const { jnpfUniverAPI: jnpfUniverAPICache } = unref(jnpfUniverDesignPrintRef)?.handleCreateDesignUnit({
+        defaultActiveSheetId,
+        loading: false,
+        mode: 'print',
+        snapshot,
+      });
+      state.jnpfUniverAPI = jnpfUniverAPICache ?? null;
+
+      // ??? 这个生命周期待测试,看能不能提前
+      jnpfUniverAPI.value?.getHooks()?.onSteady(() => {
+        // 获取激活的工作表id
+        const subUnitId = defaultActiveSheetId ?? jnpfUniverAPI.value?.getActiveWorkbook()?.getActiveSheet()?.getSheetId();
+
+        // 判断行列的冻结状态
+        const { xSplit = 0, ySplit = 0 } = snapshot?.sheets?.[subUnitId]?.freeze ?? {};
+        const colHasFreeze = xSplit > 0; // 列冻结(冻结左侧的列)
+        const rowHasFreeze = ySplit > 0; // 行冻结(冻结上方的行)
+        state.sheetFreezeStatus = {
+          colHasFreeze, // 列冻结
+          rowHasFreeze, // 行冻结
+        };
+
+        console.log('☕️ The underlying univer has been rendered, and the print preview page is being rendered.');
+        unref(jnpfUniverPrintRenderRef)?.handlePrintRender(JSON.parse(JSON.stringify(DefaultPrintConfig)));
+      });
+    });
+  }
+
+  // 变更打印配置
+  function handleChangePrintConfig(config: any) {
+    state.printRenderTip = defaultRenderProgressTip;
+    state.loading = true;
+
+    console.log('✋ You have switched the print configuration.');
+    unref(jnpfUniverPrintRenderRef)?.handlePrintRender(JSON.parse(JSON.stringify(config)));
+  }
+
+  // 获取打印预览渲染进度
+  function getPreviewRenderProgress(res: any) {
+    const { rendered, total } = res ?? {};
+    state.printRenderTip = `共${total}页,已加载${rendered}页`;
+
+    if (rendered === total) {
+      console.log('🎆 The preview page has been fully rendered.');
+      state.loading = false;
+
+      /**
+       * 下面的内容主要是为了实现打印全部工作表
+       * 必须如此处理,不然打印所有工作表会出现缺失
+       */
+      // 获取工作簿id
+      const activeWorkbook = jnpfUniverAPI.value?.getActiveWorkbook();
+      const unitId = activeWorkbook?.getId();
+
+      // 获取到所有的工作表
+      const sheets = activeWorkbook.save().sheets;
+      const sheetKeys = Object.keys(sheets);
+      const activeSubUnitId = activeWorkbook?.getActiveSheet()?.getSheetId();
+
+      // 自动缓存所有页签
+      function autoRenderAllSheets(index: number) {
+        if (index >= sheetKeys?.length) {
+          // 切换到最初激活的工作表
+          jnpfUniverAPI.value?.executeCommand(SetWorksheetActiveOperation.id, {
+            subUnitId: activeSubUnitId,
+            unitId,
+          });
+          return;
+        }
+
+        const sheetKey = sheetKeys[index];
+
+        jnpfUniverAPI.value?.executeCommand(SetWorksheetActiveOperation.id, {
+          subUnitId: sheetKey,
+          unitId,
+        });
+
+        setTimeout(() => {
+          autoRenderAllSheets(index + 1);
+        }, 100); // 加延迟才能切换命令执行无效
+      }
+
+      autoRenderAllSheets(0);
+    }
+  }
+
+  // 执行浏览器打印
+  function handleBrowserPrint() {
+    state.printRenderTip = defaultRenderProgressTip;
+    state.loading = true;
+
+    unref(jnpfUniverPrintRenderRef)?.handleBrowserPrint();
+  }
+
+  // 获取打印浏览器渲染进度
+  function getPrintRenderProgress(res: any) {
+    const { rendered, total } = res ?? {};
+
+    state.printRenderTip = `共${total}页,已准备${rendered}页`;
+
+    if (rendered === total) {
+      state.printRenderTip = '整理中...';
+    }
+  }
+
+  // 打印准备好了
+  function handleBrowserShow() {
+    state.loading = false;
+  }
+
+  // 关闭弹窗(建议有个时间缓存周期再关闭弹窗,要销毁设计器)
+  function handleCancel() {
+    resetUniverState();
+    unref(jnpfUniverDesignPrintRef)?.handleDisposeUnit();
+  }
+
+  // 重置实例状态
+  function resetUniverState() {
+    state.jnpfUniverAPI = null;
+    state.loading = false;
+    state.printRenderTip = defaultRenderProgressTip;
+    state.sheetFreezeStatus = JSON.parse(JSON.stringify(defaultFreezeStatus));
+
+    unref(jnpfUniverPrintRenderRef)?.resetState();
+  }
+
+  defineExpose({ handleCreatePrintUnit });
+</script>
+
+<template>
+  <AModal v-model:open="internalOpen" title="打印预览" width="100%" wrap-class-name="print-full-modal" @cancel="handleCancel">
+    <div class="content-main" v-loading="loading" :loading-tip="printRenderTip">
+      <!--    测试按钮,随时可以删除掉    -->
+      <div class="buttons-wrap">
+        <AButton type="primary" @click="handleBrowserPrint">浏览器打印</AButton>
+      </div>
+
+      <div class="print-container">
+        <!--        设计器被隐藏-->
+        <div class="print-design-content">
+          <JnpfUniverDesign v-if="internalOpen" ref="jnpfUniverDesignPrintRef" />
+        </div>
+
+        <div class="print-main-container">
+          <!--        视图效果-->
+          <JnpfUniverPrintRender
+            ref="jnpfUniverPrintRenderRef"
+            :jnpf-univer-api="jnpfUniverAPI"
+            @preview-render-progress="getPreviewRenderProgress"
+            @print-render-progress="getPrintRenderProgress"
+            @browser-print-show="handleBrowserShow" />
+
+          <!--          配置表单-->
+          <JnpfUniverPrintForm
+            v-if="internalOpen"
+            :default-print-config="DefaultPrintConfig"
+            :sheet-freeze-status="sheetFreezeStatus"
+            @change="handleChangePrintConfig" />
+        </div>
+      </div>
+    </div>
+  </AModal>
+</template>
+
+<style lang="scss">
+  .print-full-modal {
+    .ant-modal {
+      top: 0;
+      max-width: 100%;
+      padding-bottom: 0;
+      margin: 0;
+    }
+
+    .ant-modal-content {
+      display: flex;
+      flex-direction: column;
+      height: calc(100vh);
+      padding: 20px 10px 0;
+    }
+
+    .ant-modal-body {
+      display: flex;
+      flex: 1;
+
+      .content-main {
+        display: flex;
+        flex: 1;
+        flex-direction: column;
+      }
+
+      .print-container {
+        position: relative;
+        flex: 1;
+
+        .print-design-content {
+          position: absolute;
+          inset: 0;
+          z-index: 2;
+          display: flex;
+
+          .jnpf-univer-design-content {
+            opacity: 0;
+          }
+        }
+
+        .print-main-container {
+          position: absolute;
+          inset: 0;
+          z-index: 99;
+          display: flex;
+          overflow: hidden;
+          background: #f2f3f4;
+        }
+      }
+    }
+
+    .ant-modal-footer {
+      display: none;
+    }
+  }
+</style>

+ 32 - 0
src/components/Report/index.ts

@@ -0,0 +1,32 @@
+import { App, createApp, h } from 'vue';
+
+import { createPinia } from 'pinia';
+
+import { setupGlobDirectives } from '../../directives';
+import JnpfUniver from './Design/index.vue';
+import JnpfUniverCellEchart from './Design/univer/components/Echart/cell.vue';
+import JnpfUniverFloatEchartVM from './Design/univer/components/Echart/floatVM.vue';
+import JnpfUniverPrint from './Print/Render/index.vue';
+
+const pinia = createPinia();
+
+const app = createApp(h('div'));
+
+app.use(pinia);
+setupGlobDirectives(app);
+
+JnpfUniver.install = (app: App) => app.component(JnpfUniver.name as string, JnpfUniver);
+JnpfUniverPrint.install = (app: App) => app.component(JnpfUniverPrint.name as string, JnpfUniverPrint);
+JnpfUniverFloatEchartVM.install = (app: App) => app.component(JnpfUniverFloatEchartVM.name as string, JnpfUniverFloatEchartVM);
+JnpfUniverCellEchart.install = (app: App) => app.component(JnpfUniverCellEchart.name as string, JnpfUniverCellEchart);
+
+export { JnpfUniver, JnpfUniverCellEchart, JnpfUniverFloatEchartVM, JnpfUniverPrint };
+
+export default {
+  install(app: App) {
+    app.component(JnpfUniver.name as string, JnpfUniver);
+    app.component(JnpfUniverPrint.name as string, JnpfUniverPrint);
+    app.component(JnpfUniverFloatEchartVM.name as string, JnpfUniverFloatEchartVM);
+    app.component(JnpfUniverCellEchart.name as string, JnpfUniverCellEchart);
+  },
+};

+ 0 - 0
src/components/Report/style/index.scss


+ 10 - 0
src/directives/index.ts

@@ -0,0 +1,10 @@
+/**
+ * Configure and register global directives
+ */
+import type { App } from 'vue';
+
+import { setupLoadingDirective } from './loading';
+
+export function setupGlobDirectives(app: App) {
+  setupLoadingDirective(app);
+}

+ 40 - 0
src/directives/loading.ts

@@ -0,0 +1,40 @@
+import type { App, Directive } from 'vue';
+
+import { createLoading } from '../components/Loading';
+
+const loadingDirective: Directive = {
+  mounted(el, binding) {
+    const tip = el.getAttribute('loading-tip');
+    const background = el.getAttribute('loading-background');
+    const size = el.getAttribute('loading-size');
+    const fullscreen = !!binding.modifiers.fullscreen;
+    const instance = createLoading(
+      {
+        absolute: !fullscreen,
+        background,
+        loading: !!binding.value,
+        size: size || 'large',
+        tip,
+      },
+      fullscreen ? document.body : el,
+    );
+    el.instance = instance;
+  },
+  unmounted(el) {
+    el?.instance?.close();
+  },
+  updated(el, binding) {
+    const instance = el.instance;
+    if (!instance) return;
+    instance.setTip(el.getAttribute('loading-tip'));
+    if (binding.oldValue !== binding.value) {
+      instance.setLoading?.(binding.value && !instance.loading);
+    }
+  },
+};
+
+export function setupLoadingDirective(app: App) {
+  app.directive('loading', loadingDirective);
+}
+
+export default loadingDirective;

+ 17 - 0
src/main.ts

@@ -0,0 +1,17 @@
+import { createApp } from 'vue';
+
+import { setupGlobDirectives } from '@/directives';
+import { createPinia } from 'pinia';
+
+import App from './App.vue';
+
+import 'ant-design-vue/dist/reset.css';
+
+const pinia = createPinia();
+
+const app = createApp(App);
+
+app.use(pinia);
+setupGlobDirectives(app);
+
+app.mount('#app');

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

@@ -0,0 +1,2 @@
+/// <reference types="vite/client" />
+/// <reference types="@univerjs/vite-plugin/types" />

+ 34 - 0
tsconfig.app.json

@@ -0,0 +1,34 @@
+{
+  "compilerOptions": {
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["src/*"]
+    },
+
+    "experimentalDecorators": true,
+    "emitDecoratorMetadata": true,
+
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+    "target": "ES2020",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "Bundler",
+    "allowImportingTsExtensions": true,
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+    "jsx": "preserve",
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true,
+    "noUncheckedSideEffectImports": true
+  },
+  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
+}

+ 4 - 0
tsconfig.json

@@ -0,0 +1,4 @@
+{
+  "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
+  "files": []
+}

+ 24 - 0
tsconfig.node.json

@@ -0,0 +1,24 @@
+{
+  "compilerOptions": {
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+    "target": "ES2022",
+    "lib": ["ES2023"],
+    "module": "ESNext",
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "Bundler",
+    "allowImportingTsExtensions": true,
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true,
+    "noUncheckedSideEffectImports": true
+  },
+  "include": ["vite.config.ts"]
+}

+ 40 - 0
vite.config.ts

@@ -0,0 +1,40 @@
+import path, { resolve } from 'node:path';
+
+import { univerPlugin } from '@univerjs/vite-plugin';
+import vue from '@vitejs/plugin-vue';
+import { defineConfig } from 'vite';
+
+// https://vite.dev/config/
+export default defineConfig({
+  build: {
+    lib: {
+      entry: path.resolve(__dirname, 'src/components/Report/index.ts'),
+      fileName: `index`,
+      name: 'index',
+    },
+    rollupOptions: {
+      external: ['vue', 'pinia'],
+      output: {
+        exports: 'named',
+        globals: {
+          pinia: 'Pinia',
+          vue: 'Vue',
+        },
+      },
+    },
+  },
+  plugins: [
+    vue(),
+    univerPlugin({
+      css: false,
+    }),
+  ],
+  resolve: {
+    alias: {
+      '@': resolve('src'),
+    },
+  },
+  server: {
+    host: '0.0.0.0',
+  },
+});

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov