From 28ac8082ce22a769c7c487616007f36a112b72cc Mon Sep 17 00:00:00 2001 From: 祖安之光 <11848914+light-of-zuan@user.noreply.gitee.com> Date: 星期二, 08 七月 2025 10:32:10 +0800 Subject: [PATCH] 修改新增 --- src/main.js | 5 src/views/build/conpanyFunctionConsult/digitalFileDep/manageType/qualityManual/index.vue | 273 ++++++++++++++++++++++------- package.json | 4 src/views/build/conpanyFunctionConsult/digitalFileDep/manageType/qualityManual/components/exportDoc.js | 242 ++++++++++++++++++++++++++ src/views/build/conpanyFunctionConsult/orgStructure/dutyDistributeChart/index.vue | 5 public/qualityFile.docx | 0 6 files changed, 451 insertions(+), 78 deletions(-) diff --git a/package.json b/package.json index a6694ea..a4207a4 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,12 @@ "axios": "0.27.2", "docx-preview": "^0.3.5", "docxtemplater": "^3.63.2", + "docxtemplater-image-module-free": "^1.1.1", "echarts": "5.4.0", "element-plus": "2.2.27", "file-saver": "2.0.5", "fuse.js": "6.6.2", + "html2canvas": "^1.4.1", "js-base64": "^3.7.5", "js-cookie": "3.0.1", "jsencrypt": "3.3.1", @@ -42,11 +44,11 @@ "tinymce": "^5.10.2", "video.js": "^8.12.0", "vue": "3.2.45", - "vue-baidu-map-3x": "^1.0.35", "vue-cropper": "1.0.3", "vue-quill-editor": "^3.0.6", "vue-router": "4.1.4", "vue3-json-excel": "^1.0.10-alpha", + "vue3-tree-org": "^4.2.2", "wangeditor5-for-vue3": "^0.1.0" }, "devDependencies": { diff --git a/public/qualityFile.docx b/public/qualityFile.docx index e530c6e..0052086 100644 --- a/public/qualityFile.docx +++ b/public/qualityFile.docx Binary files differ diff --git a/src/main.js b/src/main.js index 57a30a7..23086c8 100644 --- a/src/main.js +++ b/src/main.js @@ -46,7 +46,8 @@ import preReClick from "@/utils/preReClick"; import vue3JsonExcel from 'vue3-json-excel'; - +import vue3TreeOrg from 'vue3-tree-org'; +import "vue3-tree-org/lib/vue3-tree-org.css"; import { Boot } from '@wangeditor/editor' import attachmentModule from '@wangeditor/plugin-upload-attachment' @@ -75,7 +76,7 @@ app.component('ImagePreview', ImagePreview) app.component('RightToolbar', RightToolbar) app.component('Editor', Editor) - +app.use(vue3TreeOrg) app.use(vue3JsonExcel) app.use(router) app.use(store) 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..cbe4516 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); @@ -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] }); // 设置模板中的占位符数据 @@ -123,6 +352,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({ type: 'blob', diff --git a/src/views/build/conpanyFunctionConsult/digitalFileDep/manageType/qualityManual/index.vue b/src/views/build/conpanyFunctionConsult/digitalFileDep/manageType/qualityManual/index.vue index 0ba9970..2194f8e 100644 --- a/src/views/build/conpanyFunctionConsult/digitalFileDep/manageType/qualityManual/index.vue +++ b/src/views/build/conpanyFunctionConsult/digitalFileDep/manageType/qualityManual/index.vue @@ -42,8 +42,17 @@ </template> </el-table-column> </el-table> -<!-- <org-tree :data="companyInfo.deptList" />--> -<!-- <button @click="exportOrgChart">导出组织架构图</button>--> + + <div class="orgTreeBox" id="org-tree-container"> + <vue3-tree-org + :data="deptList" + :horizontal="false" + :props="treeProps" + :toolBar="tools" + :label-style="labelStyle" + center + /> + </div> <pagination v-show="total > 0" :total="total" @@ -69,6 +78,7 @@ getStandardDetail, getStandardQuality } from "@/api/standardSys/standardSys"; +import {getDepart, getDistribution} from "@/api/orgStructure/depart"; const userStore = useUserStore() const { proxy } = getCurrentInstance(); @@ -82,6 +92,27 @@ }, total: 0, dataList: [], + deptList: { + id: 0, + deptName: "总经理", + children:[ + { + id: '0-1', + deptName: "管理者代表", + children: [] + } + ] + }, + treeProps: { + label: 'deptName' + }, + tools: { + scale: false, restore: false, expand: false, zoom: false, fullscreen: false + }, + labelStyle: { + border: '1px solid #ccc', + background: 'rgba(0,0,0,0)' + }, companyList: [], isAdmin: false, companyInfo: { @@ -99,8 +130,8 @@ {clauseNum: '4.4', content: '质量管理体系及其过程',manage: false,represent: true}, {clauseNum: '5', content: '领导作用'}, {clauseNum: '5.1', content: '领导作用和承诺',manage: true,represent: false}, - {clauseNum: '5.2', content: '方针',manage: true,represent: false}, - {clauseNum: '5.3', content: '组织内的角色、职责和权限',manage: true,represent: false}, + {clauseNum: '5.2', content: '质量方针',manage: true,represent: false}, + {clauseNum: '5.3', content: '组织的岗位、职责和权限',manage: true,represent: false}, {clauseNum: '6', content: ''}, {clauseNum: '6.1', content: '应对风险和机遇的措施',manage: true,represent: false}, {clauseNum: '6.2', content: '质量目标及其实现的策划',manage: false,represent: true}, @@ -112,11 +143,57 @@ {clauseNum: '7.1.3', content: '基础设施',manage: false,represent: true}, {clauseNum: '7.1.4', content: '过程运行环境',manage: false,represent: true}, {clauseNum: '7.1.5', content: '监视和测量资源',manage: false,represent: true}, - {clauseNum: '7.1.6', content: '组织的知识',manage: false,represent: true} + {clauseNum: '7.1.6', content: '组织的知识',manage: false,represent: true}, + {clauseNum: '7.2', content: '能力'}, + {clauseNum: '7.3', content: '意识'}, + {clauseNum: '7.4', content: '沟通'}, + {clauseNum: '7.5', content: '成文信息'}, + {clauseNum: '7.6', content: '质量信息'}, + {clauseNum: '8', content: '运行'}, + {clauseNum: '8.1', content: '运行策划和控制'}, + {clauseNum: '8.2', content: '产品和服务的要求'}, + {clauseNum: '8.2.1', content: '顾客沟通'}, + {clauseNum: '8.2.2', content: '与产品和服务有关的要求的确定'}, + {clauseNum: '8.2.3', content: '与产品和服务有关的要求的评审'}, + {clauseNum: '8.2.4', content: '产品和服务要求的更改'}, + {clauseNum: '8.3', content: '产品和服务的设计和开发'}, + {clauseNum: '8.3.1', content: '总则'}, + {clauseNum: '8.3.2', content: '设计和开发策划'}, + {clauseNum: '8.3.3', content: '设计和开发输入'}, + {clauseNum: '8.3.4', content: '设计和开发控制'}, + {clauseNum: '8.3.5', content: '设计和开发输出'}, + {clauseNum: '8.3.6', content: '设计和开发更改'}, + {clauseNum: '8.3.7', content: '新产品试制'}, + {clauseNum: '8.3.8', content: '设计和开发的试验控制'}, + {clauseNum: '8.4', content: '外部提供过程、产品和服务的控制'}, + {clauseNum: '8.4.1', content: '总则'}, + {clauseNum: '8.4.2', content: '控制类型和程度'}, + {clauseNum: '8.4.3', content: '提供给外部供方的信息'}, + {clauseNum: '8.5', content: '生产和服务提供'}, + {clauseNum: '8.5.1', content: '生产和服务提供的控制'}, + {clauseNum: '8.5.2', content: '标识和可追溯性'}, + {clauseNum: '8.5.3', content: '顾客或外部供方的财产'}, + {clauseNum: '8.5.4', content: '防护'}, + {clauseNum: '8.5.5', content: '交付后的活动'}, + {clauseNum: '8.5.6', content: '更改控制'}, + {clauseNum: '8.5.7', content: '关键过程'}, + {clauseNum: '8.6', content: '产品和服务的放行'}, + {clauseNum: '8.7', content: '不合格输出的控制'}, + {clauseNum: '9', content: '绩效评价'}, + {clauseNum: '9.1', content: '监视、测量、分析和评价'}, + {clauseNum: '9.1.1', content: '总则'}, + {clauseNum: '9.1.2', content: '顾客满意'}, + {clauseNum: '9.1.3', content: '分析和评价'}, + {clauseNum: '9.2', content: '内部审核'}, + {clauseNum: '9.3', content: '管理评审'}, + {clauseNum: '10', content: '持续改进'}, + {clauseNum: '10.1', content: '总则'}, + {clauseNum: '10.2', content: '不合格和纠正措施'}, + {clauseNum: '10.3', content: '持续改进'} ] }); -const { queryParams, total, dataList,companyList, isAdmin, companyInfo } = toRefs(data); +const { queryParams, total, dataList,deptList,treeProps,tools,labelStyle,companyList, isAdmin, companyInfo, caluseList } = toRefs(data); const userInfo = ref() onMounted(async ()=>{ if(userStore.roles.includes('admin')){ @@ -133,26 +210,6 @@ }) -const exportOrgChart=()=> { - const element = document.querySelector('.org-tree'); // 获取组织架构图的容器元素 - html2canvas(element).then(canvas => { - // 创建一个图片元素 - let img = new Image(); - img.src = canvas.toDataURL('image/png'); - - // 创建并触发下载 - img.onload = () => { - let w = img.width; - let h = img.height; - let canvas2 = document.createElement('canvas'); - let ctx = canvas2.getContext('2d'); - canvas2.width = w; - canvas2.height = h; - ctx.drawImage(img, 0, 0, w, h); - saveAs(canvas2.toDataURL('image/png'), '组织架构图.png'); - }; - }); -} const getList = async () => { loading.value = true @@ -166,6 +223,34 @@ loading.value = false } +const getDeptData = async (val) => { + await getDeptList(val.companyId) + const res = await getDistribution({companyId: val.companyId}) + if(res.code == 200){ + for(let item of data.caluseList){ + const sameNum = data.companyInfo.duties.filter(i=>i.clauseNum == item.clauseNum).map(j=> { + return { + deptId: j.deptId, + chooseLab: j.chooseLab + } + } + ) + for(let i of sameNum){ + item[i.deptId] = i.chooseLab? (i.chooseLab==1?'●':'○'):'○' + } + } + }else{ + ElMessage.warning(res.message) + } +} +const getDeptList = async (id) => { + const res = await getDepart({pageNum: 1, pageSize: 999, companyId: id}) + if(res.code == 200){ + data.deptList.children[0].children = res.data + }else{ + ElMessage.warning(res.message) + } +} const addFile = async ()=>{ let params={} if(data.queryParams.companyId){ @@ -200,72 +285,110 @@ } } -const getInfo = async () => { +const getInfo = async (val) => { loading.value = true; - if(!data.queryParams.companyId){ - ElMessage.warning('请选择企业') - } - const res = await getStandardDetail({companyId: data.queryParams.companyId}); + const res = await getStandardDetail({companyId: val.companyId}); if(res.code === 200){ - data.companyInfo.summaries = res.data.companySummaries[0].companySummary - data.companyInfo.policies = res.data.companyQualityPolicies[0].policy - data.companyInfo.deptList = res.data.treeSelects - data.companyInfo.duties = transformDuties(res.data.sysFunctionalDistributions) + if(!res.data || (res.data.companyIndustryTemplates.length == 0 && res.data.companyQualityPolicies.length == 0 && res.data.companySummaries.length == 0 && res.data.sysFunctionalDistributions + .length == 0 && res.data.treeSelects.length == 0)){ + loading.value = false; + return Promise.reject(new Error('该企业暂无质量数据')); + } + data.companyInfo.summaries = res.data.companySummaries ? res.data.companySummaries[0]?.companySummary : [] + data.companyInfo.policies = res.data.companyQualityPolicies ? res.data.companyQualityPolicies[0]?.policy : [] + data.companyInfo.deptList = res.data.treeSelects ? res.data.treeSelects : [] + // data.companyInfo.clauses = data.caluseList + // data.companyInfo.duties = res.data.sysFunctionalDistributions + + const duties = transToTableData(res.data.sysFunctionalDistributions) + data.companyInfo.allDeptNames = duties.allDeptNames + data.companyInfo.clauses = duties.clauses data.companyInfo.temps = res.data.companyIndustryTemplates?.map((item,index)=>{ return { index: index + 1, templateName: item.templateName } - }) + }) || [] + // const imageBase64 = await exportTableToImage() + // data.companyInfo.tableImage = { + // data: imageBase64.split(",")[1], // 去掉 data:image/png;base64, 前缀 + // type: "png", + // } }else{ ElMessage.warning(res.message) } loading.value = false; - } -const transformDuties=(duties)=>{ - let tableData = [] - for(let item of duties){ - const index = tableData.findIndex(i=>i.deptId == item.deptId) - if(index == -1){ - const obj = { - deptName: item.deptName, - deptId: item.deptId - } - obj[item.clauseNum] = item.chooseLab==1?'●':'○' - tableData.push(obj) - }else{ - tableData[index][item.clauseNum] = item.chooseLab==1?'●':'○' - } - } - console.log(tableData,'table1') - tableData = tableData.map(j=>{ - for(let i of data.caluseList){ - if(!j.hasOwnProperty(i.clauseNum)){ - j[i.clauseNum] = '○' - } - } - return j - }) - console.log(tableData,'table2') - return tableData +const transToTableData=(duties)=>{ + // 步骤1:获取所有唯一的部门和条款编号 + const allDeptNames = [...new Set(duties.map(item => item.deptName))]; + const allClauseNums = [...new Set([ + ...data.caluseList.map(c => c.clauseNum), + ...duties.map(d => d.clauseNum) + ])]; + +// 步骤2:为每个条款生成完整的部门数据(缺失数据默认 chooseLab: 0) + const processedClauses = allClauseNums.map(clauseNum => { + const clauseContent = data.caluseList.find(c => c.clauseNum === clauseNum)?.content || ""; + + // 为当前条款生成所有部门的数据(确保每个部门都有值) + const deptValues = allDeptNames.map(deptName => { + const matchedDept = duties.find( + item => item.clauseNum === clauseNum && item.deptName === deptName + ); + return matchedDept ? (matchedDept.chooseLab==1?'●':'○' ): '○'; + + }); + + return { + clauseNum, + content: clauseContent, + deptValues // 数组形式,例如 [0, 1, 0, 0] + }; + }); + +// 最终数据结构 + return { + clauses: processedClauses, + allDeptNames // 用于生成表头 + }; } -const initFile = async (name) => { - await getInfo() - const templatePath = '/qualityFile.docx' +const initFile = async (val) => { try { + await getInfo(val) + await getDeptData(val) + // 2. 等待DOM更新完成 + await nextTick(); + + // 3. 捕获组织结构图图片 + const orgChartImage = await captureElementToImage('org-tree-container'); + data.companyInfo.orgChart = orgChartImage + const templatePath = '/qualityFile.docx' await generateWordDocument( templatePath, data.companyInfo, - name+'质量手册.docx' + val.companyName+'质量手册.docx' ); - ElMessage.success('手册导出成功!'); + ElMessage.success('手册导出成功!') } catch (error) { - console.error('导出失败:', error); - ElMessage.warning('手册导出失败'); + console.error('操作失败:', error); + ElMessage.warning(error.message || '操作失败'); } +} + +const captureElementToImage = async (elementId) => { + const html2canvas = (await import('html2canvas')).default; + const element = document.getElementById(elementId); + if (!element) throw new Error('找不到组织结构图容器'); + + return await html2canvas(element, { + scale: 4, + useCORS: true, + allowTaint: true, + backgroundColor: 'rgba(0,0,0,0)' // 确保背景是白色 + }).then(canvas => canvas.toDataURL('image/png')); } const openDialog = (type, value) => { @@ -303,3 +426,13 @@ } </script> +<style lang="scss"> + .orgTreeBox{ + width: 700px; + height: 700px; + position: absolute; /* 或 fixed */ + left: -9999px; /* 移出可视区域 */ + pointer-events: none; /* 禁止交互 */ + z-index: -1; + } +</style> \ No newline at end of file diff --git a/src/views/build/conpanyFunctionConsult/orgStructure/dutyDistributeChart/index.vue b/src/views/build/conpanyFunctionConsult/orgStructure/dutyDistributeChart/index.vue index 07e0aba..d335188 100644 --- a/src/views/build/conpanyFunctionConsult/orgStructure/dutyDistributeChart/index.vue +++ b/src/views/build/conpanyFunctionConsult/orgStructure/dutyDistributeChart/index.vue @@ -140,7 +140,10 @@ {clauseNum: '9.1.3', content: '分析和评价'}, {clauseNum: '9.2', content: '内部审核'}, {clauseNum: '9.3', content: '管理评审'}, - {clauseNum: '10', content: '持续改进'} + {clauseNum: '10', content: '持续改进'}, + {clauseNum: '10.1', content: '总则'}, + {clauseNum: '10.2', content: '不合格和纠正措施'}, + {clauseNum: '10.3', content: '持续改进'} ], form: { companyId: null, -- Gitblit v1.9.2