import { replaceDocx, createZip, uploadFileToOSS, docxToPdf, renderDocx, toBarCode } from "../../lib/docs"; // const Parse = global.Parse; const path = require("path") const fs = require("fs") var TemplateDocxDir = path.join(__dirname,"template") if(!fs.existsSync(TemplateDocxDir)){ TemplateDocxDir = path.join(__dirname,"../../template") } const tempDir = path.join(__dirname , "temp"); /** * 常用Word字符 */ // 圆圈选中未选 ○ 未选 ● 选中 const circleCheck = ["○","●"]; // 方块选中未选 ○ 未选 ● 选中 const squareCheck = [`□`,String.fromCharCode(0xFE)]; /** * 定义导出申报合集文档云函数 * @example * Cloud Code test 导出流程 curl -X POST -H "Content-Type: application/json" -H 'X-Parse-Application-Id: edu-textbook' -d '{ "processId": "FR7KZtefyR" }' http://127.0.0.1:61337/parse/functions/tbookExportReport curl -X POST -H "Content-Type: application/json" -H 'X-Parse-Application-Id: edu-textbook' -d '{ "processId": "FR7KZtefyR" }' http://8.140.98.43/parse/functions/tbookExportReport curl -X POST -H "Content-Type: application/json" -H 'X-Parse-Application-Id: edu-textbook' -d '{ "processId": "FR7KZtefyR" }' http://8.140.98.43/parse/api/tbook/export curl -X POST -H "Content-Type: application/json" -H 'X-Parse-Application-Id: edu-textbook' -d '{ "processId": "FR7KZtefyR" }' https://145.tbook.com.cn/parse/api/tbook/export 导出教材列表 curl -X POST -H "Content-Type: application/json" -H 'X-Parse-Application-Id: edu-textbook' -d '{ "bookList": ["9V575dapEM","2YBKitpCJL","xLdiEaHGrX"] }' http://8.140.98.43/parse/functions/tbookExportReport */ export function defineTbookExportReport(app){ // 全新导出,每次都重新导出 app.post("/parse/api/tbook/export",async (request,res)=>{ let processId = request.body.processId; let bookList = request.body.bookList; console.log(request.body) try{ let result if(processId){ result = await exportProcessReportDocs(processId) } if(bookList?.length){ result = await exportProcessReportDocs(null,bookList) } if(result?.docsList?.length==0){ // throw new Parse.Error(404,"合集内无申报教材") res.json({ code:400, err:"合集内无申报教材" }) return } // return result res.json({ code:200, result:result }) return }catch(err){ console.error(err) res.json({ code:400, err:err }) return // throw new Parse.Error(404,"导出申报合集失败") } // throw new Parse.Error(404,"未找到该流程合集") }) // 预览导出:已导出直接返回 // curl -X POST -H "Content-Type: application/json" -H 'X-Parse-Application-Id: edu-textbook' -d '{ "bookid": "j4pt2MMTXM" }' https://145.tbook.com.cn/parse/api/tbook/preview app.post("/parse/api/tbook/preview",async (request,res)=>{ let bookid = request.body.bookid; let isNew = request.body.isNew || false; try{ let result if(bookid){ let book if(!isNew){ let query = new Parse.Query("EduTextbook") query.include("childrens","department") book = await query.get(bookid); } if(book?.get("export")){ res.json({ code:200, data:{ urlPdf:book?.get("export") } }) return } result = await exportProcessReportDocs(null,[bookid]) let urlPdf = result?.docsList?.[0]?.urlPdf; if(urlPdf){ book.set('export',urlPdf); book.save(); res.json({ code:200, data:{ urlPdf:urlPdf } }) return } } console.log(result) if(result?.docsList?.length==0){ // throw new Parse.Error(404,"合集内无申报教材") res.json({ code:400, err:"无该教材" }) return } // return result res.json({ code:200, result:result }) return }catch(err){ console.error(err) res.json({ code:400, err:err }) return // throw new Parse.Error(404,"导出申报合集失败") } // throw new Parse.Error(404,"未找到该流程合集") }) Parse.Cloud.define("tbookExportReport", async (request) => { let processId = request.params.processId; let bookList = request.params.bookList; try{ let result if(processId){ result = await exportProcessReportDocs(processId) } if(bookList?.length){ result = await exportProcessReportDocs(null,bookList) } if(result?.docsList?.length==0){ throw new Parse.Error(404,"合集内无申报教材") } return result }catch(err){ console.error(err) throw new Parse.Error(404,"导出申报合集失败") } throw new Parse.Error(404,"未找到该流程合集") },{ fields : { processId:{ required:false }, } }); } /** * 导出流程教材申报文件 * @returns * docsList * zipUrl */ export async function exportProcessReportDocs(processId,bookList) { if(!processId && !bookList?.length) return {} let textbookList if(processId){ // 流程读取教材列表 let query = new Parse.Query("EduTextbook") query.include("childrens","department") query.equalTo("recommend",true); query.equalTo("eduProcess",processId); textbookList = await query.find(); } console.log(bookList) if(bookList?.length){ // 直接导出教材列表 let query = new Parse.Query("EduTextbook") query.include("childrens","department") query.containedIn("objectId",bookList); textbookList = await query.find(); } let docsList = [] let plist = [] for (let index = 0; index < textbookList.length; index++) { let textbook = textbookList[index]; console.log("textbook",index) // 直接将异步调用的 Promise 添加到 plist plist.push(renderReportDocsByTextbook(textbook)); // 立即执行并返回 Promise } let presults = await Promise.all(plist); presults.forEach(result=>{ if(result?.filePath){ docsList.push(result) } }) let zipPath,zipUrl if(docsList?.length){ let now = new Date(); let fileList = docsList?.map(item=>item?.pdfPath); let zipName = `申报书导出-${now.getFullYear()}${now.getMonth()+1}${now.getDate()}-${now.getHours()}${now.getMinutes()}${now.getSeconds()}.zip` zipPath = await createZip(fileList,zipName,{tempDir:null}) if(zipPath){ zipUrl = (await uploadFileToOSS(zipPath))?.url || null fs.rmSync(zipPath) fileList.forEach(tempFile=>{ console.log("rm",tempFile) fs.rmSync(path.dirname(tempFile),{recursive:true,force:true}); }) } docsList = docsList.map(item=>{return {code:item.code,title:item.title,url:item?.url,urlPdf:item?.urlPdf}}) } let result = { docsList, zipUrl } return result } module.exports.exportProcessReportDocs = exportProcessReportDocs function fixVolumeData(json,volumeData,index,totalJson,isChildren){ let volumeIndex = index; json.index = index + 1; // 教材基本信息 // 教材所属学科专业类:前四位 discipline let disciplineCode4 = volumeData?.discipline?.code?.slice(0,4) || json?.discipline?.code?.slice(0,4) || "" let disciplineName4 = volumeData?.discipline?.name || json?.discipline?.name || "" // 教材应用对象及所属学科专业类:前四位 majorPoniter let majorCode4 = volumeData?.majorPoniter?.code?.slice(0,4) || json?.majorPoniter?.code?.slice(0,4) || "" let majorName4 = volumeData?.majorPoniter?.name || json?.majorPoniter?.name || "" // 专业代码:前六位 major let majorCode6 = volumeData?.major?.code?.slice(0,6) || json?.major?.code?.slice(0,6) || "" let majorName6 = volumeData?.major?.name || json?.major?.name || "" // 初版时间 let firstDate if(volumeData?.editionFirst?.iso){ firstDate = new Date(volumeData?.editionFirst.iso); } let firstYear = firstDate&&firstDate?.getFullYear(); let firstMonth = firstDate&&(firstDate?.getMonth()+1); // 本版时间印次 let currentDate if(volumeData?.editionDate?.iso){ currentDate = new Date(volumeData?.editionDate.iso); } let currentYear = currentDate&¤tDate?.getFullYear(); let currentMonth = currentDate&&(currentDate?.getMonth()+1); // 最新时间印次 let latestDate if(volumeData?.printDate?.iso){ latestDate = new Date(volumeData?.printDate.iso); } let latestYear = latestDate&&latestDate?.getFullYear(); let latestMonth = latestDate&&(latestDate?.getMonth()+1); let latestNum = volumeData?.printNumber || ""; let currentNum = volumeData?.editionNumber || ""; let printSum = volumeData?.printSum?volumeData?.printSum+"万":"" || ""; // 是否重点立项 let importantProject = volumeData?.importantProject?.join(); // 初版至今重点项目 let isBSQT = !((importantProject?.indexOf("建设")>-1) || (importantProject?.indexOf("本科国家")>-1) || (importantProject?.indexOf("省级优秀")>-1) || (importantProject?.indexOf("省级规划")>-1))// 是否其他省级奖项 let bsqtName = (volumeData?.importantProject || [])?.filter(item=>(item?.indexOf("建设")==-1&&item?.indexOf("本科国家")==-1&&item?.indexOf("省级优秀")==-1&&item?.indexOf("省级规划")==-1)); json.importantProject = importantProject json.firstDate = firstDate json.firstYear = firstYear || "" json.firstMonth = firstMonth || "" json.currentDate = currentDate json.currentYear = currentYear || "" json.currentMonth = currentMonth || "" json.latestDate = latestDate json.latestYear = latestYear || "" json.latestMonth = latestMonth || "" json.currentNum = currentNum json.latestMonth = latestMonth json.latestNum = latestNum json.printSum = printSum json.isBSQT = isBSQT json.bsqtName = (importantProject?.indexOf("其他省部级")>-1) && volumeData?.importantProjectOther ? volumeData?.importantProjectOther : bsqtName json.uBS = circleCheck[(volumeData?.unitType?.indexOf("部属")>-1)?1:0]; json.uHJ = circleCheck[(volumeData?.unitType?.indexOf("合建")>-1)?1:0]; json.uGJ = circleCheck[(volumeData?.unitType?.indexOf("共建")>-1)?1:0]; json.uQT = circleCheck[(volumeData?.unitType?.indexOf("其他")>-1)?1:0]; json.dc4 = disciplineCode4; json.dn4 = disciplineName4; json.mc4 = majorCode4; json.mn4 = majorName4; json.majorCodePad = disciplineCode4; json.mc6 = majorCode6; json.mn6 = majorName6; json.lCN = circleCheck[(volumeData?.lang=="中文")?1:0]; json.lEN = circleCheck[(volumeData?.lang=="英文")?1:0]; json.lMW = circleCheck[(volumeData?.lang=="盲文")?1:0]; json.lOT = circleCheck[(volumeData?.lang?.indexOf("其他")>-1)?1:0]; json.lSS = circleCheck[(volumeData?.lang?.indexOf("少数")>-1)?1:0]; json.publisher = volumeData?.editionUnit; json.isDZ = circleCheck[(volumeData?.carrierShape?.indexOf("电子教材")>-1)?1:0]; json.isSZ = circleCheck[(volumeData?.carrierShape?.indexOf("数字教材")>-1 || volumeData?.carrierShape?.indexOf("电子教材")>-1)?1:0]; json.isQT = circleCheck[(volumeData?.carrierShape?.indexOf("其他")>-1)?1:0]; json.isFD = circleCheck[(volumeData?.carrierShape?.indexOf("附带")>-1)?1:0]; json.isZZ = circleCheck[(volumeData?.carrierShape?.indexOf("纸质教材")>-1 && volumeData?.carrierShape?.indexOf("附带")==-1)?1:0]; json.latestY = latestYear; json.latestM = latestMonth; json.currentY = currentYear; json.currentM = currentMonth; console.log(importantProject) json.isGJS = circleCheck[(importantProject?.indexOf("建设")>-1)?1:0]; json.isBGJ = circleCheck[(importantProject?.indexOf("本科国家")>-1)?1:0]; json.isBSYX = circleCheck[(importantProject?.indexOf("省级优秀")>-1)?1:0]; json.isBSGH = circleCheck[(importantProject?.indexOf("省级规划")>-1)?1:0]; // json.isBSQT = circleCheck[isBSQT&&bsqtName?1:0]; json.isBSQT = circleCheck[(importantProject?.indexOf("其他省部级")>-1)?1:0]; json.isFirstNot = circleCheck[volumeData?.importantProject?.length > 0?0:1]; // 作者列表 + 政治审查表 totalJson.examineList = totalJson.examineList || [] totalJson.zhengshenList = totalJson.zhengshenList || [] // console.log(volumeData?.authorList) for (let index = 0; index < 6; index++) { // 补充空值 if(!volumeData?.authorList?.[index]){ volumeData.authorList.push({name:"",unit:"",birth:"",nationality:"",job:"",title:"",mobile:"",email:"",work:""}) } } volumeData.authorList = volumeData?.authorList?.map((item,index)=>{ // 转换格式 item.index = index + 1 item.birth = toYearMonth(item?.birth) if(item?.examine){ if(!(isChildren&&volumeIndex==0)){ // 子册第一册不重复导出 // totalJson.examineList = pushDistinctItem(totalJson.examineList,item) // 名称去重 // console.log(item) totalJson.zhengshenList.push(item?.examine); totalJson.examineList.push(item); // 全量导出 } } return item }) // console.log(totalJson.examineList) json.authorList = volumeData?.authorList; // 其他编者政治审查表 totalJson.otherList = totalJson.otherList || [] if(volumeData?.otherEditor){ if(!(isChildren&&volumeIndex==0)){ // 子册第一册不重复导出 // totalJson.otherList = pushDistinctItem(totalJson.otherList,volumeData?.otherEditor) // 名称去重 totalJson.zhengshenList.push(volumeData?.otherEditor) totalJson.otherList.push(volumeData?.otherEditor); //全量导出 } } // 成果列表 // aclist[index] = [json?.achievementOptions?.[index]?.name||" ",json?.achievementOptions?.[index]?.unit||" ",toYearMonth(json?.achievementOptions?.[index]?.date)||" "] for (let index = 0; index < 5; index++) { // 补充空值 if(!volumeData?.achievementOptions?.[index]){ volumeData.achievementOptions.push({name:"",unit:"",date:""}) } volumeData.achievementOptions = volumeData?.achievementOptions?.map((item,index)=>{ // 转换格式 item.index = index + 1 item.date = toYearMonth(item?.date) return item }) } json.achievementOptions = volumeData?.achievementOptions; // 历程列表 // clist[index] = [num,toYearMonth(json?.courses?.[index]?.date)||" ",json?.courses?.[index]?.wordage||" ",json?.courses?.[index]?.num||" ",json?.courses?.[index]?.sumNum||" ",json?.courses?.[index]?.accolade||" "] for (let index = 0; index < 5; index++) { // 补充空值 if(!volumeData?.courses?.[index]){ volumeData.courses.push({date:"",wordage:"",num:"",sumNum:"",accolade:""}) } } volumeData.courses = volumeData?.courses?.map((item,index)=>{ // 转换格式 item.index = index + 1 item.date = toYearMonth(item?.date) return item }) json.courses = volumeData?.courses; return json } function pushDistinctItem(list,item){ let isDistinct = true if(list?.indexOf(item)>-1){ isDistinct = false; } if(list?.find(old=>old?.name&&old?.name==item?.name)){ isDistinct = false; } if(isDistinct){ list.push(item); } return list } function renderReportDocsByTextbook(textbook){ console.log("renderReportDocsByTextbook") return new Promise(async (resolve)=>{ let json = textbook.toJSON(); json.hasNoSign = false; // 未上传作者签名 json.hasNoUnit = false; // 未上传单位资料 json.hasNoNine = false; // 无需添加滞后九 let bookid = json.code || json?.objectId; // 默认单册为第一分册 let book1Data = json?.childrens?.[0] if(book1Data){ Object.keys(book1Data).forEach(key=>{ json[key] = book1Data[key] || json[key] || "" }) } // 联系电话:默认为作者首个存在的电话; let mobile = json?.authorList?.find(item => item.mobile)?.mobile || "" // 填报时间:默认为创建时间 let createdAt = new Date(textbook?.createdAt); let createdDate = `${createdAt?.getFullYear()}年${createdAt?.getMonth()+1}月${createdAt?.getDate()}日`; // 教材适用 let characteristic = (json?.characteristic?.filter(item=>item?.checked).map(item=>item.label) || [])?.join(",") json = fixVolumeData(json,book1Data,0,json) let volumeList = [] for (let index = 0; index < 12; index++) { if(json?.childrens?.[index]){ let volumeData = json?.childrens?.[index] volumeData = fixVolumeData(volumeData,volumeData,index,json,true) volumeList.push(volumeData) } } // 作者列表 限6人 let alist = [] for (let index = 0; index < 9; index++) { alist[index] = [json?.authorList?.[index]?.name||" ",json?.authorList?.[index]?.unit||" ",toYearMonth(json?.authorList?.[index]?.birth)||" ",json?.authorList?.[index]?.nationality||" ",json?.authorList?.[index]?.job||" ",json?.authorList?.[index]?.title||" ",json?.authorList?.[index]?.mobile||" ",json?.authorList?.[index]?.email||" ",json?.authorList?.[index]?.work||" "] } // 相关成果 限5项 let aclist = [] for (let index = 0; index < 5; index++) { aclist[index] = [json?.achievementOptions?.[index]?.name||" ",json?.achievementOptions?.[index]?.unit||" ",toYearMonth(json?.achievementOptions?.[index]?.date)||" "] } // 申报历程 限4项 let clist = [] for (let index = 0; index < 4; index++) { let num = json?.courses?.[index]?(index+1):" "; clist[index] = [num,toYearMonth(json?.courses?.[index]?.date)||" ",json?.courses?.[index]?.wordage||" ",json?.courses?.[index]?.num||" ",json?.courses?.[index]?.sumNum||" ",json?.courses?.[index]?.accolade||" "] } // 附件信息 let mergeFiles = [] // 教材电子版 // (教材出版单位配合按要求上传各地推荐的本单位出版的纸质教材最新印次的完整PDF电子版;数字教材上传全部教材内容电子版或填写能够查看全部教材内容的链接地址、账号;纸质教材附带数字资源的,上传纸质教材最新印次的完整PDF电子版,以及全部数字资源电子版或能够查看全部数字资源内容的链接地址、账号。) let isNotImpt = (json?.approval?.indexOf("101计划")==-1) && (json?.approval?.indexOf("中央有关部门")==-1) && (json?.approval?.indexOf("四新")==-1) && (json?.approval?.indexOf("首届全国")==-1); // 1.获批截图 if(json?.approvedImgUrl && !isNotImpt){ mergeFiles.push(json?.approvedImgUrl); } // 2.所有作者政治审查意见(必须提供) // (对应作者姓名上传“作者政治审查表”。作者单位党委对作者进行审查,对政治思想表现情况进行评价,确保作者的正确政治方向、价值取向,无违法违纪等记录。教材编写成员涉及多个不同单位时需要各单位分别出具意见,并由所在单位党委盖章,格式要求从申报平台下载。) // console.log("json.examineList"); // console.log("json.examineList"); // console.log("json.examineList"); // console.log("json.examineList"); // console.log(json.examineList); // json.examineList.forEach(author=>{ // if(author?.examine) mergeFiles.push(author?.examine) // }) // console.log(mergeFiles) // let otherMap = {} json?.zhengshenList?.forEach(item=>{ // 合并不重复其他编者政治审查表 // if(otherMap[item]) return // otherMap[item] = true mergeFiles.push(item) }) // json?.otherList?.forEach(item=>{ // 合并不重复其他编者政治审查表 // if(otherMap[item]) return // otherMap[item] = true // mergeFiles.push(item) // }) // 3.图书编校质量自查结果记录表(必须提供) // (教材出版单位对申报教材的编校质量自查后,按要求提供图书编校质量自查结果记录表,并加盖出版社公章。全册教材的不同分册以不同文件分别上传。格式要求从申报平台下载。) if(json?.selfResults?.url){mergeFiles.push(json?.selfResults?.url)} // 4.专家审查意见表(必须提供) // (由第一主编所在单位或出版机构邀请校内外相关学科专业领域专家,对教材进行思想性、学术性审查。专家不少于3名,其中半数以上为校外专家,专家分别实名评价并签字,并注明所在单位及专业身份。评价人不得是本教材的作者。) if(json?.expertOpinion?.url){mergeFiles.push(json?.expertOpinion?.url)} // 5.教材使用情况证明材料(必须提供) // (教材出版单位提供教材主要使用高校名单及使用情况证明材料,并加盖公章。) if(json?.evidence?.url){mergeFiles.push(json?.evidence?.url)} // 6.版权信息及CIP数据(必须提供) // (版权页截图,中国版本图书馆CIP查询截图,如CIP数据中无“教材”字样的,须再上传内容提要或前言或后记中可以证明本书为教材的相关内容截图。) //6.1 版权页截图 if(json?.copyrightImgUrl){mergeFiles.push(json?.copyrightImgUrl)} //6.2 中国版本图书馆 CIP 查询截图 if(json?.CIPImgUrl){mergeFiles.push(json?.CIPImgUrl)} //6.3 CIP 相关证明截图 if(json?.cipProveFile?.length){ json?.cipProveFile.forEach(item=>{ item?.url && mergeFiles.push(item?.url) }) } // 7.其他材料(可选提供) if(json?.moreMaterial?.length){ json?.moreMaterial.forEach(doc=>{ if(doc?.url){ mergeFiles.push(doc?.url) } }) } let bookTempDir = path.join(tempDir,bookid) if(!fs.existsSync(bookTempDir)) fs.mkdirSync(bookTempDir) let codePngBuffer = await toBarCode(json?.code); let codePngPath = path.join(bookTempDir,bookid+"code.png") fs.writeFileSync(codePngPath,codePngBuffer) // let codeBarImg = // console.log(codeBarImg) // (其他佐证材料,限两份以内。) console.log(json?.approval) // let isNotImpt = (json?.approval?.indexOf("101计划")==-1) && (json?.approval?.indexOf("中央有关部门")==-1) && (json?.approval?.indexOf("四新")==-1) && (json?.approval?.indexOf("首届全国")==-1); let fixData = { volumeList:volumeList, // 图片信息 codeBarImg:codePngPath, // 封面信息 titlePad:padString(json?.title,21), ISBNPad:fromatFiled(json?.childrens, 'ISBN') || padString(json?.ISBN,21), ISBN:json?.ISBN, inviteUnit:json?.inviteUnit||json?.department?.name||"", one:squareCheck[(json?.type=="单本"||json?.type=="单册")?1:0], // 单本/单册 方框 full:squareCheck[json?.type=="全册"?1:0], // 全册 oneCircle:circleCheck[(json?.type=="单本"||json?.type=="单册")?1:0], // 单本/单册 圆圈 fullCircle:circleCheck[json?.type=="全册"?1:0], // 全册 tn:json?.typeNumber, authorPad:fromatFiled(json?.childrens, 'author') || padString(json?.author,21), mobile:padString(mobile,21), authorUnit:fromatFiled(json?.childrens, 'unit') || padString(json?.unit,21), publisherPad:fromatFiled(json?.childrens, 'editionUnit') || padString(json?.editionUnit,21), createdDate:padString(createdDate,21), // 基本信息 title:json?.title, isJC: circleCheck[(json?.approval?.indexOf("基础")>-1)?1:0], isZL: circleCheck[(json?.approval?.indexOf("战略")>-1)?1:0], is101: circleCheck[(json?.approval?.indexOf("101计划")>-1)?1:0], // 2024新重点 isZY: circleCheck[(json?.approval?.indexOf("中央有关部门")>-1)?1:0], isSX: circleCheck[(json?.approval?.indexOf("四新")>-1)?1:0], isJS: circleCheck[(json?.approval?.indexOf("首届全国")>-1)?1:0], isNotImpt: circleCheck[isNotImpt?1:0], // 教材适用情况 lessons:json?.lessons||[], period:json?.period||"", // 学时 isBX:squareCheck[(characteristic?.indexOf("必修")>-1)?1:0], isXX:squareCheck[(characteristic?.indexOf("选修")>-1)?1:0], isTS:circleCheck[(characteristic?.indexOf("通识")>-1)?1:0], isGG:circleCheck[(characteristic?.indexOf("公共")>-1)?1:0], isZYK:circleCheck[(characteristic?.indexOf("专业")>-1)?1:0], isSXZZ:squareCheck[(characteristic?.indexOf("思想")>-1)?1:0], isSY:squareCheck[(characteristic?.indexOf("实验")>-1)?1:0], // 作者列表 a11:alist[0][0],a12:alist[0][1],a13:alist[0][2],a14:alist[0][3],a15:alist[0][4],a16:alist[0][5],a17:alist[0][6],a18:alist[0][7],a19:alist[0][8], a21:alist[1][0],a22:alist[1][1],a23:alist[1][2],a24:alist[1][3],a25:alist[1][4],a26:alist[1][5],a27:alist[1][6],a28:alist[1][7],a29:alist[1][8], a31:alist[2][0],a32:alist[2][1],a33:alist[2][2],a34:alist[2][3],a35:alist[2][4],a36:alist[2][5],a37:alist[2][6],a38:alist[2][7],a39:alist[2][8], a41:alist[3][0],a42:alist[3][1],a43:alist[3][2],a44:alist[3][3],a45:alist[3][4],a46:alist[3][5],a47:alist[3][6],a48:alist[3][7],a49:alist[3][8], a51:alist[4][0],a52:alist[4][1],a53:alist[4][2],a54:alist[4][3],a55:alist[4][4],a56:alist[4][5],a57:alist[4][6],a58:alist[4][7],a59:alist[4][8], a61:alist[5][0],a62:alist[5][1],a63:alist[5][2],a64:alist[5][3],a65:alist[5][4],a66:alist[5][5],a67:alist[5][6],a68:alist[5][7],a69:alist[5][8], // 成果列表 ac11:aclist[0][0],ac12:aclist[0][1],ac13:aclist[0][2], ac21:aclist[1][0],ac22:aclist[1][1],ac23:aclist[1][2], ac31:aclist[2][0],ac32:aclist[2][1],ac33:aclist[2][2], ac41:aclist[3][0],ac42:aclist[3][1],ac43:aclist[3][2], ac51:aclist[4][0],ac52:aclist[4][1],ac53:aclist[4][2], // 历程列表 c11:clist[0][0],c12:clist[0][1],c13:clist[0][2],c14:clist[0][3],c15:clist[0][4],c16:clist[0][5], c21:clist[1][0],c22:clist[1][1],c23:clist[1][2],c24:clist[1][3],c25:clist[1][4],c26:clist[1][5], c31:clist[2][0],c32:clist[2][1],c33:clist[2][2],c34:clist[2][3],c35:clist[2][4],c36:clist[2][5], c41:clist[3][0],c42:clist[3][1],c43:clist[3][2],c44:clist[3][3],c45:clist[3][4],c46:clist[3][5], } let bookData = json; Object.keys(fixData).forEach(key=>{ bookData[key] = fixData[key] || bookData[key] || "" if(typeof bookData[key]=="string"){ bookData[key] = bookData[key].replaceAll("\x02"," ") bookData[key] = bookData[key].replaceAll("\u0002"," ") // 字段填写字符中,存在无法转码的乱码字符 if(key=="ac31"){ console.log(bookData[key]) } } }) // console.log(bookData) // console.log(json) let title = bookData.title?.replaceAll("/","") let tempFileName = path.join(`${bookid}_${title}_申报书及附件.docx`) // mergeFiles 处理七八九是否签名或后置问题 let lastPageList = [] let docx7 = await getPageDocx(bookid,"七",bookData); let docx8 = await getPageDocx(bookid,"八",bookData); let docx9 = await getPageDocx(bookid,"九",bookData); lastPageList.push(json?.authorSignPDF?.url||docx7); lastPageList.push(json?.unitMaterial?.url||docx8); lastPageList.push(docx9); let lastPageFileName = path.join(bookTempDir,`${bookid}_${title}_申报书及附件_789.pdf`) console.log(lastPageList) let lastPagePdf = await docxToPdf(null,lastPageFileName,{mergeFiles:lastPageList,tempDir:bookTempDir,mergeAll:true}) // 成功用pdf,失败继续用docx docx7&&fs.rmSync(docx7) docx8&&fs.rmSync(docx8) docx9&&fs.rmSync(docx9) // mergeFiles = [...lastPageList,...mergeFiles] mergeFiles = [lastPagePdf,...mergeFiles] // 开始文件合并导出 let filePath,pdfPath,urlDocx,urlPdf let TemplateDocxPath if(json?.childrens?.length>1){ TemplateDocxPath = path.join(TemplateDocxDir,"模板-本科教材申报书-全册.docx") }else{ TemplateDocxPath = path.join(TemplateDocxDir,"模板-本科教材申报书-单册.docx") } try{ // DOCX模板合成 速度2-3秒 filePath = renderDocx(TemplateDocxPath,tempFileName,bookData,{tempDir:bookTempDir}) urlDocx = (await uploadFileToOSS(filePath))?.url || null console.log("DOCX CREATED:",filePath) // PDF文档拼接 速度10-30s 需要API支持 pdfPath = filePath.replaceAll(".docx",".pdf") let options = { mergeFiles:mergeFiles, tempDir:bookTempDir } pdfPath = await docxToPdf(filePath,pdfPath,options) || filePath // 成功用pdf,失败继续用docx console.log("PDF CREATED:",filePath) if(pdfPath){ urlPdf = (await uploadFileToOSS(pdfPath))?.url || null } }catch(err){ console.error(err) } resolve({ code:bookid, title:json?.title, filePath, pdfPath, urlDocx, urlPdf, }) return }) } async function getPageDocx(bookid,pageName,bookData){ let tplPath = path.join(TemplateDocxDir,`模板-本科教材申报书-${pageName}.docx`); let title = bookData?.title?.replaceAll("/","") let tempFileName = path.join(`${bookid}_${title}_申报书及附件_${pageName}.docx`); let filePath = renderDocx(tplPath,tempFileName,bookData); return filePath; // 转pdf // let pdfPath = filePath.replaceAll(".docx",".pdf") // let options = {} // pdfPath = await docxToPdf(null,pdfPath,options) || filePath // 成功用pdf,失败继续用docx // return pdfPath } function toYearMonth(date){ // console.log("toYearMonth",date) if(typeof date == "string" && (date?.indexOf("年")>-1 || date?.indexOf("-")>-1)) return date if(!date) return "" let datestr = date?.iso||date if(!datestr) return "" date = new Date(datestr); // return `${date.getFullYear()}年${date.getMonth()+1}月` return `${date.getFullYear()}-${get2bitint(date.getMonth()+1)}` } function get2bitint(value){ if(value>=10) return value; if(value>=0&&value<10) return "0" + value; return value } function padString(str,width) { str = str || "" return str str = String(str) width = width || 21 // 目标宽度为21个单位 let spaceChar = " " // 占位符 render可用空格,但document.xml用  // 计算字符串的宽度 const charWidth = { 'space': 1, // 空格占用1个单位 'zh': 3, // 汉字占用3个单位 'en': 1, // 英文字母占用1个单位 'other': 1 // 其他字符(如标点符号)占用1个单位 }; let strWidth = 0; // console.log(str) // 遍历文本中的每个字符 for (let char of str) { if (/\s/.test(char)) { strWidth += charWidth.space; // 空格 } else if (/[\u4e00-\u9fa5]/.test(char)) { strWidth += charWidth.zh; // 汉字 } else if (/[a-zA-Z]/.test(char)) { strWidth += charWidth.en; // 英文字母 } else { strWidth += charWidth.other; // 其他字符 } } // for (let char of str) { // // 判断字符是否为中文 // if (char.match(/[\u4e00-\u9fa5]/)) { // strWidth += 2; // 中文字符占4个单位 // } else { // strWidth += 1; // 英文字符占1个单位 // } // } const totalPadding = width - strWidth; // 如果已经达到或超过目标宽度,直接返回原字符串 if (totalPadding <= 0) { return str; } // 计算左右两侧的空格数 const leftPadding = Math.floor(totalPadding / 2) * 3; const rightPadding = Math.ceil(totalPadding / 2) * 3; // 生成填充空格的字符串 const leftSpaces = spaceChar.repeat(leftPadding); const rightSpaces = spaceChar.repeat(rightPadding); // 返回补充后的字符串 return leftSpaces + str + rightSpaces; } /* 格式化重复字段(首页多册情况展示:书号、第一主编、主编单位、出 版 社) */ function fromatFiled(list, filed){ let arr = []; list.forEach((item) => { arr.push(item[filed]); }); let j = Array.from(new Set(arr)).join('、'); return j || '-'; }