Browse Source

word导出

yzx 3 weeks ago
parent
commit
52ac9612db

+ 2 - 1
package.json

@@ -22,7 +22,7 @@
     "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
     "lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
     "lint:style": "stylelint --fix \"./src/**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
-    "lint:lint-staged": "lint-staged -c " 
+    "lint:lint-staged": "lint-staged -c "
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.1.0",
@@ -94,6 +94,7 @@
     "@iconify/json": "^2.2.187",
     "@intlify/unplugin-vue-i18n": "^2.0.0",
     "@purge-icons/generated": "^0.9.0",
+    "@types/file-saver": "^2.0.7",
     "@types/lodash-es": "^4.17.12",
     "@types/node": "^20.11.21",
     "@types/nprogress": "^0.2.3",

+ 8 - 0
pnpm-lock.yaml

@@ -210,6 +210,9 @@ importers:
       '@purge-icons/generated':
         specifier: ^0.9.0
         version: 0.9.0
+      '@types/file-saver':
+        specifier: ^2.0.7
+        version: 2.0.7
       '@types/lodash-es':
         specifier: ^4.17.12
         version: 4.17.12
@@ -1774,6 +1777,9 @@ packages:
   '@types/event-emitter@0.3.5':
     resolution: {integrity: sha512-zx2/Gg0Eg7gwEiOIIh5w9TrhKKTeQh7CPCOPNc0el4pLSwzebA8SmnHwZs2dWlLONvyulykSwGSQxQHLhjGLvQ==}
 
+  '@types/file-saver@2.0.7':
+    resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
+
   '@types/geojson@7946.0.15':
     resolution: {integrity: sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==}
 
@@ -7440,6 +7446,8 @@ snapshots:
 
   '@types/event-emitter@0.3.5': {}
 
+  '@types/file-saver@2.0.7': {}
+
   '@types/geojson@7946.0.15': {}
 
   '@types/json-schema@7.0.15': {}

BIN
public/templates/ceshi.docx


BIN
public/templates/newceshi.docx


+ 0 - 79
src/utils/doc.js

@@ -1,79 +0,0 @@
-import JSZip from 'jszip';
-import { saveAs } from 'file-saver';
-import JSZipUtils from 'jszip-utils';
-import Docxtemplater from 'docxtemplater';
-import ImageModule from 'docxtemplater-image-module-free';
-
-const loadImage = async (tagValue) => {
-  try {
-    const response = await fetch(tagValue);
-    if (!response.ok) {
-      throw new Error('Network response was not ok');
-    }
-    const blob = await response.blob();
-    return URL.createObjectURL(blob);
-  } catch (error) {
-    console.error('There was a problem with the fetch operation:', error);
-    return null; // 返回 null 或者合适的错误处理
-  }
-};
-export const exportDocx = async (tempDocxPath, dataList, zipFileName) => {
-  try {
-    // 加载模板文件
-    const content = await new Promise((resolve, reject) => {
-      JSZipUtils.getBinaryContent(tempDocxPath, (error, content) => {
-        if (error) {
-          reject(error);
-        } else {
-          resolve(content);
-        }
-      });
-    });
-
-    // 初始化 JSZip 实例,用于打包多个文件
-    const zip = new JSZip();
-
-    // 遍历每一条数据,生成单独的 .docx 文件
-    for (let i = 0; i < dataList.length; i++) {
-      const data = dataList[i];
-
-      const imageOptions = {
-        async getImage(tagValue) {
-          const imageUrl = await loadImage(tagValue);
-          console.log(imageUrl, "我在这里");
-          return imageUrl;
-        },
-        getSize() {
-          return [150, 150];
-        },
-      };
-      const doc = new Docxtemplater(zip, {
-        modules: new ImageModule(imageOptions),
-      });
-      doc.render({
-        image: "logo.png",
-      });
-      const zipContent = doc.getZip(); // 获取 zip 对象
-      if (!zipContent) {
-        throw new Error('Docxtemplater failed to load zip content.');
-      }
-
-      // 生成 .docx 文件
-      const out = await zipContent.generateAsync({
-        type: 'blob',
-        mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
-      });
-
-      // 将生成的 .docx 文件添加到 zip 包中
-      zip.file(`document_${i + 1}.docx`, out);
-    }
-
-    // 生成 zip 文件并保存
-    const zipBlob = await zip.generateAsync({ type: 'blob' });
-    saveAs(zipBlob, zipFileName);
-    console.log(`文件已成功生成并打包为 ${zipFileName}`);
-  } catch (error) {
-    console.error('导出文件时出错:', error);
-    throw error;
-  }
-};

+ 146 - 0
src/utils/doc.ts

@@ -0,0 +1,146 @@
+import Docxtemplater from "docxtemplater";
+import PizZip from "pizzip";
+import PizZipUtils from "pizzip/utils/index.js";
+import ImageModule from 'docxtemplater-image-module-free';
+import { ElMessage } from 'element-plus';
+import { saveAs } from "file-saver";
+
+class ExportWord {
+  public url: string;
+  public fileName: string;
+  public data: any;
+  public docContent: Docxtemplater;
+
+  constructor(option) {
+    this.url = option.url;
+    this.fileName = option.filename;
+    this.data = option.obj;
+  }
+
+  // 初始化 Word 文档
+  async initWord(_callback) {
+    try {
+      const _content = await this.loadFile(this.url);
+      await this.setPizZip(_content);
+      _callback(this);
+    } catch (err) {
+      ElMessage.error('下载异常,请重试');
+      console.error('下载异常,请重试', err);
+    }
+  }
+
+  // 读取并获得模板文件的二进制内容
+  loadFile(url: string): Promise<any> {
+    return new Promise((resolve, reject) => {
+      PizZipUtils.getBinaryContent(url, (error, content) => {
+        if (error) {
+          reject(new Error('加载模板文件失败'));
+        } else {
+          resolve(content);
+        }
+      });
+    });
+  }
+
+  // 创建一个JSZip实例,内容为模板的内容
+  async setPizZip(_content) {
+    const imageOpts = {
+      getImage: function (tagValue, tagName) {
+        return new Promise((resolve, reject) => {
+          // 检查 tagValue 是否有效
+          if (!tagValue) {
+            console.error(`图片路径无效:${tagValue}`);
+            reject(new Error(`无效的图片路径: ${tagValue}`));
+            return;
+          }
+
+          // 读取二进制内容
+          PizZipUtils.getBinaryContent(tagValue, (error, content) => {
+            if (error) {
+              console.error(`加载图片失败: ${tagValue}`, error);
+              reject(new Error(`加载图片失败: ${tagValue}`));
+            } else {
+              console.log(`成功加载图片: ${tagValue}`);
+              resolve(content);  // 返回图片的二进制内容
+            }
+          });
+        });
+      },
+      getSize: (img, tagValue, tagName) => {
+        return new Promise((resolve, reject) => {
+          const image = new Image();
+          image.src = tagValue;
+          image.onload = function () {
+            resolve([image.width, image.height]);
+          };
+          image.onerror = function (e) {
+            console.log("img, tagValue, tagName : ", img, tagValue, tagName);
+            alert("图片加载失败: " + tagValue);
+            reject(e);
+          };
+        });
+      }
+    };
+    const zip = new PizZip(_content);
+    this.docContent = new Docxtemplater(zip, {
+      modules: [new ImageModule(imageOpts)],
+      paragraphLoop: true,
+      linebreaks: true
+    });
+    console.log(this.data)
+    await this.setTemplateContent(this.data); // 确保模板内容设置完毕
+  }
+
+  // 设置模板内容
+  async setTemplateContent(_obj) {
+    try {
+      console.log(_obj)
+      await this.docContent.renderAsync(_obj); // 使用异步渲染方法替换模板中的变量
+    } catch (error: any) {
+      this.handleRenderError(error);
+      throw new Error("模板渲染失败");
+    }
+  }
+
+  // 错误处理
+  handleRenderError(error: any) {
+    function replaceErrors(key: any, value: any) {
+      if (value instanceof Error) {
+        return Object.getOwnPropertyNames(value).reduce(function (error: any, key: string) {
+          error[key] = value[key as keyof Error];
+          return error;
+        }, {});
+      }
+      return value;
+    }
+
+    console.log("渲染错误: ", JSON.stringify({ error }, replaceErrors));
+
+    if (error.properties && error.properties.errors instanceof Array) {
+      const errorMessages = error.properties.errors
+          .map((err: any) => err.properties.explanation)
+          .join("\n");
+      console.log("详细错误信息: ", errorMessages);
+      alert(`模板渲染错误: ${errorMessages}`);
+    }
+  }
+
+  // 下载文件
+  downloadFile() {
+    const out = this.docContent.getZip().generate({
+      type: "blob",
+      mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+    });
+    saveAs(out, this.fileName);
+  }
+
+  addDownloadFile() {
+    const out = this.docContent.getZip().generate({
+      type: "blob",
+      mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+    });
+    return out;
+  }
+}
+
+export default ExportWord;

+ 29 - 16
src/views/system/studentSelectSupervisorRecord/record.vue

@@ -199,25 +199,18 @@
 
 <script setup lang="ts">
 import { ref } from 'vue'
-import axios from 'axios';
 import { dateFormatter } from '@/utils/formatTime'
-import download from '@/utils/download'
-import { supervisorSelectionSettingApi, supervisorSelectionSettingVO } from '@/api/system/supervisorSelectionSetting'
 import * as UserApi from '@/api/system/user'
 import { getUserProfile, ProfileVO } from '@/api/system/user/profile'
 import {studentSelectSupervisorRecordApi, studentSelectSupervisorRecordVO} from "@/api/system/studentSelectSupervisorRecord";
 import {DICT_TYPE} from "@/utils/dict";
 import studentSelectSupervisorRecordForm from './studentSelectSupervisorRecordForm.vue'
 import {studentSelectionProjectApi} from "@/api/system/studentSelectionProject"
-import { exportDocx } from '@/utils/doc.js';
+import ExportWord  from '@/utils/doc.ts';
 import { selectionBookApi } from '@/api/system/studentSelectSupervisorRecord/selectionBook';
+import { saveAs } from "file-saver";
 
-// import docxtemplater from "docxtemplater"
-// import PizZip from "pizzip"
-// import JSZipUtils from "jszip-utils"
-// import JSZip from "jszip"
-// import { saveAs } from 'file-saver'
-// import { htmlPdf } from "@/utils/htmlToPDF.js"  
+import JSZip from "jszip"
 
 /** 导师学硕专硕名额设置 列表 */
 defineOptions({ name: 'RecordList' })
@@ -311,21 +304,41 @@ const exportWordTemplate = async () => {
       major: selectionBook.major,
       mobile: selectionBook.studentMobile,
       nickname: selectionBook.supervisor,
-      // title: selectionBook.title,
       title: selectionBook.supervisor,
       studentAchievementRequirement: selectionBook.studentAchievementRequirement,
       studentSignDate: new Date(selectionBook.studentSignDate).toLocaleDateString(),
       supervisorSignDate: new Date(selectionBook.supervisorSignDate).toLocaleDateString(),
-      // studentSignature: selectionBook.studentSignature,
-      supervisorSignature: selectionBook.supervisorSignature,
+      supervisorSignature: "/logo.png",
       studentSignature: "/logo.png"
     };
   }));
   console.log("导出的数据对象:", dataList);
-    
-  const zipFileName = '师生互选表.zip';
+  const zip = new JSZip();
+
+  const newFileName = '我就不信了.docx';
   try {
-    exportDocx("/templates/ceshi.docx", dataList, zipFileName);
+    // 使用 for 循环遍历 dataList 并生成多个 .docx 文件
+    for (let i = 0; i < dataList.length; i++) {
+      const data = dataList[i];
+      const _word = new ExportWord({
+        url: '/templates/newceshi.docx',
+        filename: newFileName,
+        obj: data
+      });
+
+      // 使用 await 确保每个 Word 文件生成并下载前才继续执行
+      await _word.initWord((_this) => {
+        const out = _this.addDownloadFile(); // 获取文件的 Blob
+        console.log(`document_${i + 1}.docx 文件已生成`);
+
+        // 将生成的 .docx 文件添加到 zip 压缩包中
+        zip.file(`document_${i + 1}.docx`, out);
+      });
+    }
+
+    // 生成 zip 文件并保存
+    const zipBlob = await zip.generateAsync({ type: 'blob' });
+    saveAs(zipBlob, "导出文件.zip");
   } catch (error) {
     console.error('导出文档失败:', error);
   }

+ 1 - 1
src/views/system/workroomTeacher/dept/DeptForm.vue

@@ -40,7 +40,7 @@ import { FormRules } from 'element-plus';
 
 const { t } = useI18n();
 const message = useMessage();
-defineProps({
+const props = defineProps({
   visible: {
     type: Boolean, // 明确类型为布尔值
     required: true, // 表示必须由父组件传入