From 050ee6d982a8ae40011f1f723198d23fedae40b3 Mon Sep 17 00:00:00 2001 From: 祖安之光 <11848914+light-of-zuan@user.noreply.gitee.com> Date: 星期二, 15 七月 2025 09:39:13 +0800 Subject: [PATCH] 修改新增 --- src/views/build/conpanyFunctionConsult/digitalFileDep/manageType/qualityManual/components/exportDoc.js | 246 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 240 insertions(+), 6 deletions(-) diff --git a/src/views/build/conpanyFunctionConsult/digitalFileDep/manageType/qualityManual/components/exportDoc.js b/src/views/build/conpanyFunctionConsult/digitalFileDep/manageType/qualityManual/components/exportDoc.js index d29dd2f..41a294d 100644 --- a/src/views/build/conpanyFunctionConsult/digitalFileDep/manageType/qualityManual/components/exportDoc.js +++ b/src/views/build/conpanyFunctionConsult/digitalFileDep/manageType/qualityManual/components/exportDoc.js @@ -3,7 +3,7 @@ import Docxtemplater from 'docxtemplater'; import JSZipUtils from 'jszip-utils'; import { saveAs } from 'file-saver'; - +import ImageModule from 'docxtemplater-image-module-free' // 加载 .docx 模板文件 function loadFile(url, callback) { JSZipUtils.getBinaryContent(url, callback); @@ -62,7 +62,7 @@ }); }); - return result.join('\n\n'); // 用两个换行符分隔段落 + return result.join('\n'); // 用两个换行符分隔段落 } function convertTreeToHtml(data) { @@ -70,14 +70,12 @@ function buildList(items) { let listHtml = '<ul style="font-family: 宋体; font-size: 12pt; line-height: 1.5;">'; - items.forEach(item => { - listHtml += `<li style="margin-bottom: 6pt;">${item.label}`; + listHtml += `<li style="margin-bottom: 6pt;">${item.deptName}`; if (item.children && item.children.length > 0) { listHtml += buildList(item.children); } - listHtml += '</li>'; }); @@ -89,8 +87,233 @@ return html; } +function generateTableXML(clauses, deptList) { + const allDeptNames = [...new Set(deptList.map(item => item.deptName))]; + + // 构建数据映射 + const dataMap = {}; + deptList.forEach(item => { + if (!dataMap[item.clauseNum]) dataMap[item.clauseNum] = {}; + dataMap[item.clauseNum][item.deptName] = item.chooseLab; + }); + + return ` + <w:tbl xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"> + <w:tblPr> + <w:tblW w:w="10000" w:type="pct"/> + <!-- 边框设置 --> + <w:tblBorders> + <w:top w:val="single" w:sz="4" w:space="0" w:color="000000"/> + <w:left w:val="single" w:sz="4" w:space="0" w:color="000000"/> + <w:bottom w:val="single" w:sz="4" w:space="0" w:color="000000"/> + <w:right w:val="single" w:sz="4" w:space="0" w:color="000000"/> + <w:insideH w:val="single" w:sz="4" w:space="0" w:color="000000"/> + <w:insideV w:val="single" w:sz="4" w:space="0" w:color="000000"/> + </w:tblBorders> + <w:tblLook w:val="04A0"/> + </w:tblPr> + + <!-- 列宽定义 --> + <w:tblGrid> + <w:gridCol w:w="1500"/> <!-- 条款号列 --> + <w:gridCol w:w="3500"/> <!-- 内容列 --> + ${allDeptNames.map(() => '<w:gridCol w:w="2000"/>').join('')} + </w:tblGrid> + + <!-- 表头 --> + <w:tr> + <w:tc> + <w:tcPr><w:tcW w:w="1500" w:type="dxa"/></w:tcPr> + <w:p><w:r><w:rPr><w:b/></w:rPr><w:t>条款号</w:t></w:r></w:p> + </w:tc> + <w:tc> + <w:tcPr><w:tcW w:w="3500" w:type="dxa"/></w:tcPr> + <w:p><w:r><w:rPr><w:b/></w:rPr><w:t>内容描述</w:t></w:r></w:p> + </w:tc> + ${allDeptNames.map(dept => ` + <w:tc> + <w:tcPr><w:tcW w:w="2000" w:type="dxa"/></w:tcPr> + <w:p><w:r><w:rPr><w:b/></w:rPr><w:t>${dept}</w:t></w:r></w:p> + </w:tc> + `).join('')} + </w:tr> + + <!-- 数据行 --> + ${clauses.map(clause => ` + <w:tr> + <w:tc><w:p><w:r><w:t>${clause.clauseNum}</w:t></w:r></w:p></w:tc> + <w:tc><w:p><w:r><w:t>${clause.content}</w:t></w:r></w:p></w:tc> + ${allDeptNames.map(dept => ` + <w:tc> + <w:p><w:r><w:t>${dataMap[clause.clauseNum]?.[dept] ?? 0}</w:t></w:r></w:p> + </w:tc> + `).join('')} + </w:tr> + `).join('')} + </w:tbl> + `; +} + + +function processTableHtml(html) { + if (!html) return ''; + + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + const table = doc.querySelector('table'); + if (!table) return ''; + + // 提取表格结构 + const rows = table.querySelectorAll('tr'); + const result = []; + + // 处理表头 + const headers = Array.from(rows[0].querySelectorAll('th')) + .map(th => `${th.textContent.trim()}`) + .join('\t'); + result.push(headers); + + // 处理数据行 + for (let i = 1; i < rows.length; i++) { + const cells = rows[i].querySelectorAll('td'); + const rowData = Array.from(cells).map(cell => { + const indent = cell.style.paddingLeft ? ' '.repeat(parseInt(cell.style.paddingLeft)/4) : ''; + const content = cell.textContent.trim(); + return indent + content; + }).join('\t'); + result.push(rowData); + } + + return result.join('\n'); +} + +function generateTableHtml(clauses, deptList) { + const allDeptNames = [...new Set(deptList.map(item => item.deptName))]; + const dataMap = {}; + + // 构建数据映射 + deptList.forEach(item => { + if (!dataMap[item.clauseNum]) dataMap[item.clauseNum] = {}; + dataMap[item.clauseNum][item.deptName] = item.chooseLab? (item.chooseLab==1?'●':'○'):'○' + }); + + return ` + <table style="width: 100%;border-collapse: collapse;font-family: 'Microsoft YaHei', sans-serif;font-size: 10.5pt;margin-bottom: 12pt;border: 1px solid #ccc"> + <thead> + <tr style="background-color: #f5f5f5;width: 100%"> + <th style="padding: 6pt 8pt;border: 1px solid #ccc;text-align: center;font-weight: bold;min-width: 60pt">条款号</th> + <th style="padding: 6pt 8pt;border: 1px solid #cccccc;text-align: left;font-weight: bold">内容描述</th> + ${allDeptNames.map(dept => ` + <th style="padding: 6pt 8pt;border: 1pt solid #cccccc;text-align: center;font-weight: bold;min-width: 50pt">${dept}</th> + `).join('')} + </tr> + </thead> + <tbody> + ${clauses.map(clause => ` + <tr> + <td style="padding: 5pt 8pt; + border: 1pt solid #e0e0e0; + text-align: center; + vertical-align: top">${clause.clauseNum}</td> + <td style=" + padding: 5pt 8pt; + border: 1pt solid #e0e0e0; + text-align: left; + vertical-align: top">${clause.content}</td> + ${allDeptNames.map(dept => ` + <td style=" + padding: 5pt 8pt; + border: 1pt solid #e0e0e0; + text-align: center; + vertical-align: top"> + ${dataMap[clause.clauseNum]?.[dept] ?? '○'} + </td> + `).join('')} + </tr> + `).join('')} + </tbody> + </table> + `; +} +const base64Regex = + /^(?:data:)?image\/(png|jpg|jpeg|svg|svg\+xml);base64,/; + +const validBase64 = + /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; +function base64Parser(tagValue) { + if ( + typeof tagValue !== "string" || + !base64Regex.test(tagValue) + ) { + return false; + } + + const stringBase64 = tagValue.replace(base64Regex, ""); + + if (!validBase64.test(stringBase64)) { + throw new Error( + "Error parsing base64 data, your data contains invalid characters" + ); + } + + // For nodejs, return a Buffer + if (typeof Buffer !== "undefined" && Buffer.from) { + return Buffer.from(stringBase64, "base64"); + } + + // For browsers, return a string (of binary content) : + const binaryString = window.atob(stringBase64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + const ascii = binaryString.charCodeAt(i); + bytes[i] = ascii; + } + return bytes.buffer; +} +const imageOptions = { + getImage(tagValue) { + return base64Parser(tagValue); + }, + getSize(img, tagValue, tagName, context) { + return [600, 600]; + }, +}; + +const base64DataURLToArrayBuffer = (dataURL) => { + // 返回包含 ArrayBuffer 和原始 base64 字符串的对象 + const base64Regex = /^data:image\/(png|jpg|jpeg|svg|svg\+xml);base64,/; + if (!base64Regex.test(dataURL)) { + return { buffer: null, base64: dataURL }; + } + + const stringBase64 = dataURL.replace(base64Regex, ""); + let binaryString = window.atob(stringBase64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + return { + buffer: bytes.buffer, // 图片模块需要的 ArrayBuffer + base64: stringBase64 // 保留原始 base64 字符串(不带前缀) + }; +}; + // 生成并下载 Word 文档 export function generateWordDocument(templatePath, data, name) { + // 处理部门表格数据 + // if (data.clauses && data.duties) { + // const tableHtml = generateTableHtml(data.clauses, data.duties); + // data.departmentsTable = processTableHtml(tableHtml); + // } + + // 生成表格XML + // if (data.clauses && data.duties) { + // data.tableXML = generateTableXML(data.clauses, data.duties); + // } + // 处理富文本字段(如果有) if (data.summaries && typeof data.summaries === 'string') { data.summaries = processRichText(data.summaries); @@ -103,6 +326,10 @@ if (data.deptList && Array.isArray(data.deptList)) { data.departmentsHtml = processRichText(convertTreeToHtml(data.deptList)); } + if (data.orgChart && typeof data.orgChart !== 'string') { + console.warn("orgChart 不是字符串,可能被意外转换:", data.orgChart); + delete data.orgChart; // 避免传递无效数据 + } loadFile(templatePath, function (error, content) { if (error) { @@ -112,9 +339,11 @@ try { // 加载模板文件内容到 PizZip const zip = new PizZip(content); + const imageModule = new ImageModule(imageOptions); const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true, + modules: [imageModule] }); // 设置模板中的占位符数据 @@ -122,6 +351,11 @@ // 渲染文档 doc.render(); + + // 替换占位符 + // let xml = zip.files['word/document.xml'].asText(); + // xml = xml.replace('<!-- TABLE_PLACEHOLDER -->', data.tableXML); + // zip.file('word/document.xml', xml); // 生成最终的文档 Blob const fileWord = doc.getZip().generate({ @@ -135,4 +369,4 @@ throw error; } }); -} \ No newline at end of file +} -- Gitblit v1.9.2