Преглед изворни кода

feat: docxToPdf & docxtemplate

ryanemax пре 8 месеци
родитељ
комит
c437745324

+ 1 - 1
docker-office/README.md

@@ -43,7 +43,7 @@ sudo docker build -t fmode-office .
 
 ``` bash
 # 后台启动
-sudo docker run --rm -d -p 3000:3000 gotenberg/gotenberg
+sudo docker run -d -p 3000:3000 gotenberg/gotenberg
 # 直接运行
 sudo docker run --rm -p 3000:3000 gotenberg/gotenberg
 ```

+ 21 - 9
server/cloud/tbook/func-tbook-export.js

@@ -1,4 +1,4 @@
-import { replaceDocx, createZip, uploadFileToOSS, docxToPdf } from "../../lib/docs";
+import { replaceDocx, createZip, uploadFileToOSS, docxToPdf, renderDocx } from "../../lib/docs";
 // const Parse = global.Parse;
 
 const path = require("path")
@@ -98,11 +98,11 @@ module.exports.exportProcessReportDocs = exportProcessReportDocs
 
 function renderReportDocsByTextbook(textbook){
     let json = textbook.toJSON();
-    console.log(json)
+    // console.log(json)
     // 圆圈选中未选 ○ 未选 ● 选中
     let circleCheck = ["○","●"];
     // 方块选中未选 ○ 未选 ● 选中
-    let squareCheck = [``,`☑`];
+    let squareCheck = [``,`☑`];
 
     // 联系电话:默认为作者首个存在的电话;
     let mobile = json?.authorList?.find(item => item.mobile)?.mobile || ""
@@ -257,11 +257,23 @@ function renderReportDocsByTextbook(textbook){
     Object.keys(fixData).forEach(key=>{
         bookData[key] = fixData[key]
     })
-    // console.log(bookData)
+    console.log(bookData)
     // console.log(json)
     let bookid = json.code || json?.objectId;
     let tempFileName = path.join(`${bookid}${json.title}.docx`)
-    return new Promise((resolve)=>{
+    return new Promise(async (resolve)=>{
+        let filePath = renderDocx(TemplateDocxPath,tempFileName,bookData)
+        // 需要API支持
+        let pdfPath = filePath.replaceAll(".docx",".pdf")
+        filePath = await docxToPdf(filePath,pdfPath) || filePath // 成功用pdf,失败继续用docx
+        let url = (await uploadFileToOSS(pdfPath))?.url || null
+        resolve({
+            code:bookid,
+            title:json?.title,
+            filePath,
+            url
+        })
+        return
         replaceDocx(TemplateDocxPath,tempFileName,bookData,{onDocxComplete:async (filePath)=>{
             // 需要API支持
             let pdfPath = filePath.replaceAll(".docx",".pdf")
@@ -270,7 +282,7 @@ function renderReportDocsByTextbook(textbook){
             resolve({
                 code:bookid,
                 title:json?.title,
-                pdfPath,
+                filePath,
                 url
             })
         }})
@@ -287,16 +299,16 @@ function padString(str,width) {
     str = str || ""
     str = String(str)
     width = width || 21 // 目标宽度为21个单位
-    let spaceChar = " " // 占位符
+    let spaceChar = " " // 占位符 render可用空格,但document.xml用 
     // 计算字符串的宽度
     const charWidth = {
         'space': 1, // 空格占用1个单位
-        'zh': 2,    // 汉字占用2个单位
+        'zh': 3,    // 汉字占用3个单位
         'en': 1,    // 英文字母占用1个单位
         'other': 1  // 其他字符(如标点符号)占用1个单位
     };
     let strWidth = 0;
-    console.log(str)
+    // console.log(str)
     // 遍历文本中的每个字符
     for (let char of str) {
         if (/\s/.test(char)) {

BIN
server/cloud/tbook/template/模板-推荐申报表.docx


+ 56 - 10
server/lib/docs/index.js

@@ -6,11 +6,17 @@ const shell = require('shelljs');
 
 const crypto = require('crypto');
 
+// 文档模板生成
+const PizZip = require("pizzip");
+const Docxtemplater = require("docxtemplater");
+
+// 文档转换
 import { Chromiumly } from "chromiumly";
 Chromiumly.configure({ endpoint: "http://8.140.98.43/docs" });
 const { LibreOffice } = require("chromiumly");
-// LibreOffice.configure({ endpoint: "http://8.140.98.43/docs" });
-const tempDir = path.join(__dirname , "temp")
+const { PDFEngines } = require("chromiumly");
+const tempDir = path.join(__dirname , "temp");
+if(!fs.existsSync(tempDir)){fs.mkdirSync(tempDir)};
 
 const OSS = require("ali-oss");
 const ALI_OSS_BUCKET = process.env.ALI_OSS_BUCKET || "hep-textbook"
@@ -106,17 +112,25 @@ module.exports.createZip = createZip
 
         let files = [
             // docxPath
-            { data: docxBuffer, ext: "doc" },
+            { data: docxBuffer, ext: "docx" },
         ];
         console.log(files)
         let pdfBuffer = await LibreOffice.convert({
             files,
-            // properties: {
-            //     // 你可以在这里设置页面属性
-            // },
-            pdfa: false, // PDF/A 选项
-            // pdfUA: false, // PDF/UA 选项
-            // merge: false, // 是否合并PDF
+            properties: {
+                // 设置页面属性,例如纸张大小和方向
+                pageSize: 'A4',
+                orientation: 'portrait',
+                margin: {
+                    top: 10,
+                    right: 10,
+                    bottom: 10,
+                    left: 10
+                }
+            },
+            pdfa: false, // 根据需要设置
+            pdfUA: false, // 根据需要设置
+            merge: false, // 如果只转换一个文件,设置为false
             // metadata: {
             //     // 你可以在这里添加元数据
             // },
@@ -129,7 +143,7 @@ module.exports.createZip = createZip
         // 将 Buffer 写入输出文件
         fs.writeFileSync(outputPath, pdfBuffer);
         console.log(`成功输出 ${outputPath}`);
-        return pdfPath
+        return outputPath
     } catch (error) {
         console.error('转换失败:', error);
         return null
@@ -137,6 +151,38 @@ module.exports.createZip = createZip
 }
 module.exports.docxToPdf = docxToPdf
 
+
+export function renderDocx(inputDocxPath, outputDocxName, options){
+    let outputDocxPath = path.join(tempDir,outputDocxName)
+    // Load the docx file as binary content
+    let content = fs.readFileSync(
+        inputDocxPath,
+        "binary"
+    );
+
+    // Unzip the content of the file
+    let zip = new PizZip(content);
+    let doc = new Docxtemplater(zip, {
+        paragraphLoop: true,
+        linebreaks: true,
+    });
+
+    // Render the document (Replace {first_name} by John, {last_name} by Doe, ...)
+    doc.render(options);
+
+    // Get the zip document and generate it as a nodebuffer
+    let buf = doc.getZip().generate({
+        type: "nodebuffer",
+        // compression: DEFLATE adds a compression step.
+        // For a 50MB output document, expect 500ms additional CPU time
+        compression: "DEFLATE",
+    });
+
+    // buf is a nodejs Buffer, you can either write it to a
+    // file or res.send it with express for example.
+    fs.writeFileSync(outputDocxPath, buf);
+    return outputDocxPath
+}
 /**
  * docx 替换模板字符串内容
  * @example

+ 34 - 0
server/package-lock.json

@@ -15,9 +15,11 @@
         "authing-node-sdk": "^3.1.0",
         "chromiumly": "^3.6.0",
         "compressing": "^1.10.1",
+        "docxtemplater": "^3.49.1",
         "node-schedule": "^2.1.1",
         "parse-dashboard": "^5.4.0",
         "parse-server": "^7.0.0",
+        "pizzip": "^3.1.7",
         "rimraf": "^6.0.1",
         "shelljs": "^0.8.5",
         "yargs": "17.7.2"
@@ -3407,6 +3409,14 @@
       "integrity": "sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==",
       "dev": true
     },
+    "node_modules/@xmldom/xmldom": {
+      "version": "0.8.10",
+      "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
+      "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/@xstate/fsm": {
       "version": "1.4.0",
       "resolved": "https://registry.npmmirror.com/@xstate/fsm/-/fsm-1.4.0.tgz",
@@ -5155,6 +5165,17 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/docxtemplater": {
+      "version": "3.49.1",
+      "resolved": "https://registry.npmmirror.com/docxtemplater/-/docxtemplater-3.49.1.tgz",
+      "integrity": "sha512-GCfIc45xAeJMJ1+KOjAGsvD5Dsu1NAPzwGkrzbfazgaarR/ehdb2Qg5E/rrpZ0eelKJpT+AUDnXzB7/h7bMqWA==",
+      "dependencies": {
+        "@xmldom/xmldom": "^0.8.10"
+      },
+      "engines": {
+        "node": ">=0.10"
+      }
+    },
     "node_modules/dotenv": {
       "version": "16.4.5",
       "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.5.tgz",
@@ -9338,6 +9359,11 @@
       "resolved": "https://registry.npmmirror.com/packet-reader/-/packet-reader-1.0.0.tgz",
       "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
     },
+    "node_modules/pako": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/pako/-/pako-2.1.0.tgz",
+      "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
+    },
     "node_modules/parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
@@ -10019,6 +10045,14 @@
         "url": "https://github.com/sponsors/jonschlinkert"
       }
     },
+    "node_modules/pizzip": {
+      "version": "3.1.7",
+      "resolved": "https://registry.npmmirror.com/pizzip/-/pizzip-3.1.7.tgz",
+      "integrity": "sha512-VemVeAQtdIA74AN1Fsd5OmbMbEeS4YOwwlcudgzvmUrOIOPrk1idYC5Tw5FUFq/I0c26ziNOw9z//iPmGfp1jA==",
+      "dependencies": {
+        "pako": "^2.1.0"
+      }
+    },
     "node_modules/pkg": {
       "version": "5.8.1",
       "resolved": "https://registry.npmmirror.com/pkg/-/pkg-5.8.1.tgz",

+ 2 - 0
server/package.json

@@ -34,9 +34,11 @@
     "authing-node-sdk": "^3.1.0",
     "chromiumly": "^3.6.0",
     "compressing": "^1.10.1",
+    "docxtemplater": "^3.49.1",
     "node-schedule": "^2.1.1",
     "parse-dashboard": "^5.4.0",
     "parse-server": "^7.0.0",
+    "pizzip": "^3.1.7",
     "rimraf": "^6.0.1",
     "shelljs": "^0.8.5",
     "yargs": "17.7.2"