func-tbook-export.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. import { replaceDocx, createZip, uploadFileToOSS, docxToPdf, renderDocx, toBarCode } from "../../lib/docs";
  2. // const Parse = global.Parse;
  3. const path = require("path")
  4. const fs = require("fs")
  5. var TemplateDocxDir = path.join(__dirname,"template")
  6. if(!fs.existsSync(TemplateDocxDir)){
  7. TemplateDocxDir = path.join(__dirname,"../../template")
  8. }
  9. const tempDir = path.join(__dirname , "temp");
  10. /**
  11. * 常用Word字符
  12. */
  13. // 圆圈选中未选 ○ 未选 ● 选中
  14. const circleCheck = ["○","●"];
  15. // 方块选中未选 ○ 未选 ● 选中
  16. const squareCheck = [`□`,String.fromCharCode(0xFE)];
  17. /**
  18. * 定义导出申报合集文档云函数
  19. * @example
  20. * Cloud Code test
  21. 导出流程
  22. 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
  23. 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
  24. 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
  25. 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
  26. 导出教材列表
  27. 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
  28. */
  29. export function defineTbookExportReport(app){
  30. app.post("/parse/api/tbook/export",async (request,res)=>{
  31. let processId = request.body.processId;
  32. let bookList = request.body.bookList;
  33. console.log(request.body)
  34. try{
  35. let result
  36. if(processId){
  37. result = await exportProcessReportDocs(processId)
  38. }
  39. if(bookList?.length){
  40. result = await exportProcessReportDocs(null,bookList)
  41. }
  42. if(result?.docsList?.length==0){
  43. // throw new Parse.Error(404,"合集内无申报教材")
  44. res.json({
  45. code:400,
  46. err:"合集内无申报教材"
  47. })
  48. }
  49. // return result
  50. res.json({
  51. code:200,
  52. result:result
  53. })
  54. }catch(err){
  55. console.error(err)
  56. res.json({
  57. code:400,
  58. err:err
  59. })
  60. // throw new Parse.Error(404,"导出申报合集失败")
  61. }
  62. // throw new Parse.Error(404,"未找到该流程合集")
  63. })
  64. Parse.Cloud.define("tbookExportReport", async (request) => {
  65. let processId = request.params.processId;
  66. let bookList = request.params.bookList;
  67. try{
  68. let result
  69. if(processId){
  70. result = await exportProcessReportDocs(processId)
  71. }
  72. if(bookList?.length){
  73. result = await exportProcessReportDocs(null,bookList)
  74. }
  75. if(result?.docsList?.length==0){
  76. throw new Parse.Error(404,"合集内无申报教材")
  77. }
  78. return result
  79. }catch(err){
  80. console.error(err)
  81. throw new Parse.Error(404,"导出申报合集失败")
  82. }
  83. throw new Parse.Error(404,"未找到该流程合集")
  84. },{
  85. fields : {
  86. processId:{
  87. required:false
  88. },
  89. }
  90. });
  91. }
  92. /**
  93. * 导出流程教材申报文件
  94. * @returns
  95. * docsList
  96. * zipUrl
  97. */
  98. export async function exportProcessReportDocs(processId,bookList) {
  99. if(!processId && !bookList?.length) return {}
  100. let textbookList
  101. if(processId){ // 流程读取教材列表
  102. let query = new Parse.Query("EduTextbook")
  103. query.include("childrens")
  104. query.equalTo("recommend",true);
  105. query.equalTo("eduProcess",processId);
  106. textbookList = await query.find();
  107. }
  108. if(bookList?.length){ // 直接导出教材列表
  109. let query = new Parse.Query("EduTextbook")
  110. query.include("childrens")
  111. query.containedIn("objectId",bookList);
  112. textbookList = await query.find();
  113. }
  114. let docsList = []
  115. let plist = []
  116. for (let index = 0; index < textbookList.length; index++) {
  117. let textbook = textbookList[index];
  118. console.log("textbook",index)
  119. // 直接将异步调用的 Promise 添加到 plist
  120. plist.push(renderReportDocsByTextbook(textbook)); // 立即执行并返回 Promise
  121. }
  122. let presults = await Promise.all(plist);
  123. presults.forEach(result=>{
  124. if(result?.filePath){
  125. docsList.push(result)
  126. }
  127. })
  128. let zipPath,zipUrl
  129. if(docsList?.length){
  130. let now = new Date();
  131. let fileList = docsList?.map(item=>item?.pdfPath);
  132. let zipName = `申报书导出-${now.getFullYear()}${now.getMonth()+1}${now.getDate()}-${now.getHours()}${now.getMinutes()}${now.getSeconds()}.zip`
  133. zipPath = await createZip(fileList,zipName)
  134. if(zipPath){
  135. zipUrl = (await uploadFileToOSS(zipPath))?.url || null
  136. }
  137. docsList = docsList.map(item=>{return {code:item.code,title:item.title,url:item?.url}})
  138. }
  139. let result = {
  140. docsList,
  141. zipUrl
  142. }
  143. return result
  144. }
  145. module.exports.exportProcessReportDocs = exportProcessReportDocs
  146. function fixVolumeData(json,volumeData,index){
  147. json.index = index + 1;
  148. // 教材基本信息
  149. // 专业代码:前四位 majorPoniter
  150. let majorCode4 = volumeData?.majorPoniter?.code?.slice(0,4) || json?.majorPoniter?.code?.slice(0,4) || ""
  151. let majorName4 = volumeData?.majorPoniter?.name || json?.majorPoniter?.name || ""
  152. // 专业代码:前六位 major
  153. let majorCode6 = volumeData?.major?.code?.slice(0,6) || json?.major?.code?.slice(0,6) || ""
  154. let majorName6 = volumeData?.major?.name || json?.major?.name || ""
  155. // 是否重点立项
  156. let importantProject = volumeData?.importantProject?.join();
  157. let isJC = circleCheck[(volumeData?.approval?.indexOf("基础")>-1)?1:0];
  158. let isZL = circleCheck[(volumeData?.approval?.indexOf("战略")>-1)?1:0];
  159. let is101 = circleCheck[(volumeData?.approval?.indexOf("101计划")>-1)?1:0]; // 2024新重点
  160. let isZY = circleCheck[(volumeData?.approval?.indexOf("中央")>-1)?1:0];
  161. let isSX = circleCheck[(volumeData?.approval?.indexOf("四新")>-1)?1:0];
  162. let isJS = circleCheck[(volumeData?.approval?.indexOf("建设")>-1)?1:0];
  163. let isNotImpt = (volumeData?.approval?.indexOf("101计划")==-1) && (volumeData?.approval?.indexOf("中央")==-1) && (volumeData?.approval?.indexOf("四新")==-1) && (volumeData?.approval?.indexOf("建设")==-1)
  164. isNotImpt = circleCheck[isNotImpt?1:0];
  165. // 初版时间
  166. let firstDate = new Date(volumeData?.editionFirst.iso);
  167. let firstYear = firstDate?.getFullYear();
  168. let firstMonth = firstDate?.getMonth()+1;
  169. // 本版时间印次
  170. let currentDate = new Date(volumeData?.editionDate.iso);
  171. let currentYear = currentDate?.getFullYear();
  172. let currentMonth = currentDate?.getMonth()+1;
  173. // 最新时间印次
  174. let latestDate = new Date(volumeData?.printDate.iso);
  175. let latestYear = latestDate?.getFullYear();
  176. let latestMonth = latestDate?.getMonth()+1;
  177. let latestNum = volumeData?.printNumber || "";
  178. let currentNum = volumeData?.editionNumber || "";
  179. let printSum = volumeData?.printSum?volumeData?.printSum+"万":"" || "";
  180. // 初版至今重点项目
  181. let isBSQT = !((importantProject?.indexOf("建设")>-1) || (importantProject?.indexOf("本科国家")>-1) || (importantProject?.indexOf("省级优秀")>-1) || (importantProject?.indexOf("省级规划")>-1))// 是否其他省级奖项
  182. let bsqtName = (volumeData?.importantProject || [])?.filter(item=>(item?.indexOf("建设")==-1&&item?.indexOf("本科国家")==-1&&item?.indexOf("省级优秀")==-1&&item?.indexOf("省级规划")==-1));
  183. json.importantProject = importantProject
  184. json.isJC = isJC
  185. json.isZL = isZL
  186. json.is101 = is101
  187. json.isZY = isZY
  188. json.isSX = isSX
  189. json.isJS = isJS
  190. json.isNotImpt = isNotImpt
  191. json.firstDate = firstDate
  192. json.firstYear = firstYear
  193. json.firstMonth = firstMonth
  194. json.currentDate = currentDate
  195. json.currentYear = currentYear
  196. json.currentMonth = currentMonth
  197. json.latestDate = latestDate
  198. json.latestYear = latestYear
  199. json.latestMonth = latestMonth
  200. json.currentNum = currentNum
  201. json.latestMonth = latestMonth
  202. json.latestNum = latestNum
  203. json.printSum = printSum
  204. json.isBSQT = isBSQT
  205. json.bsqtName = bsqtName
  206. json.uBS = circleCheck[(volumeData?.unitType?.indexOf("部属")>-1)?1:0];
  207. json.uHJ = circleCheck[(volumeData?.unitType?.indexOf("合建")>-1)?1:0];
  208. json.uGJ = circleCheck[(volumeData?.unitType?.indexOf("共建")>-1)?1:0];
  209. json.uQT = circleCheck[(volumeData?.unitType?.indexOf("其他")>-1)?1:0];
  210. json.mc4 = majorCode4;
  211. json.mn4 = majorName4;
  212. json.mc6 = majorCode6;
  213. json.mn6 = majorName6;
  214. json.lCN = circleCheck[(volumeData?.lang=="中文")?1:0];
  215. json.lEN = circleCheck[(volumeData?.lang=="英文")?1:0];
  216. json.lMW = circleCheck[(volumeData?.lang=="盲文")?1:0];
  217. json.lOT = circleCheck[(volumeData?.lang?.indexOf("其他")>-1)?1:0];
  218. json.lSS = circleCheck[(volumeData?.lang?.indexOf("少数")>-1)?1:0];
  219. json.publisher = volumeData?.editionUnit;
  220. json.isZZ = circleCheck[(volumeData?.carrierShape?.indexOf("纸质")>-1)?1:0];
  221. json.isDZ = circleCheck[(volumeData?.carrierShape?.indexOf("电子")>-1)?1:0];
  222. json.isSZ = circleCheck[(volumeData?.carrierShape?.indexOf("数字")>-1 || volumeData?.carrierShape?.indexOf("电子")>-1)?1:0];
  223. json.isQT = circleCheck[(volumeData?.carrierShape?.indexOf("附带")>-1)?1:0];
  224. json.isFD = circleCheck[(volumeData?.carrierShape?.indexOf("其他")>-1)?1:0];
  225. json.latestY = latestYear;
  226. json.latestM = latestMonth;
  227. json.currentY = currentYear;
  228. json.currentM = currentMonth;
  229. json.isBGJ = circleCheck[(importantProject?.indexOf("本科国家")>-1)?1:0];
  230. json.isBSYX = circleCheck[(importantProject?.indexOf("省级优秀")>-1)?1:0];
  231. json.isBSGH = circleCheck[(importantProject?.indexOf("省级规划")>-1)?1:0];
  232. json.isBSQT = circleCheck[isBSQT?1:0];
  233. json.isFirstNot = circleCheck[volumeData?.importantProject?0:1];
  234. // 作者列表
  235. for (let index = 0; index < 6; index++) { // 补充空值
  236. if(!volumeData?.authorList?.[index]){
  237. volumeData.authorList.push({name:"",unit:"",birth:"",nationality:"",job:"",title:"",mobile:"",email:"",work:""})
  238. }
  239. }
  240. volumeData.authorList = volumeData?.authorList?.map((item,index)=>{ // 转换格式
  241. item.index = index + 1
  242. item.birth = toYearMonth(item?.birth)
  243. return item
  244. })
  245. json.authorList = volumeData?.authorList;
  246. // 成果列表
  247. // aclist[index] = [json?.achievementOptions?.[index]?.name||" ",json?.achievementOptions?.[index]?.unit||" ",toYearMonth(json?.achievementOptions?.[index]?.date)||" "]
  248. for (let index = 0; index < 5; index++) { // 补充空值
  249. if(!volumeData?.achievementOptions?.[index]){
  250. volumeData.achievementOptions.push({name:"",unit:"",date:""})
  251. }
  252. volumeData.achievementOptions = volumeData?.achievementOptions?.map((item,index)=>{ // 转换格式
  253. item.index = index + 1
  254. item.date = toYearMonth(item?.date)
  255. return item
  256. })
  257. }
  258. json.achievementOptions = volumeData?.achievementOptions;
  259. // 历程列表
  260. // clist[index] = [num,toYearMonth(json?.courses?.[index]?.date)||" ",json?.courses?.[index]?.wordage||" ",json?.courses?.[index]?.num||" ",json?.courses?.[index]?.sumNum||" ",json?.courses?.[index]?.accolade||" "]
  261. for (let index = 0; index < 4; index++) { // 补充空值
  262. if(!volumeData?.courses?.[index]){
  263. volumeData.courses.push({date:"",wordage:"",num:"",sumNum:"",accolade:""})
  264. }
  265. volumeData.courses = volumeData?.courses?.map((item,index)=>{ // 转换格式
  266. item.index = index + 1
  267. item.date = toYearMonth(item?.date)
  268. return item
  269. })
  270. }
  271. json.courses = volumeData?.courses;
  272. return json
  273. }
  274. function renderReportDocsByTextbook(textbook){
  275. console.log("renderReportDocsByTextbook")
  276. return new Promise(async (resolve)=>{
  277. let json = textbook.toJSON();
  278. let bookid = json.code || json?.objectId;
  279. // 默认单册为第一分册
  280. let book1Data = json?.childrens?.[0]
  281. if(book1Data){
  282. Object.keys(book1Data).forEach(key=>{
  283. json[key] = book1Data[key] || json[key] || ""
  284. })
  285. }
  286. // 联系电话:默认为作者首个存在的电话;
  287. let mobile = json?.authorList?.find(item => item.mobile)?.mobile || ""
  288. // 填报时间:默认为创建时间
  289. let createdAt = new Date(textbook?.createdAt);
  290. let createdDate = `${createdAt?.getFullYear()}年${createdAt?.getMonth()+1}月${createdAt?.getDate()}日`;
  291. // 教材适用
  292. let characteristic = (json?.characteristic?.filter(item=>item?.checked).map(item=>item.label) || [])?.join(",")
  293. json = fixVolumeData(json,book1Data,0)
  294. let volumeList = []
  295. for (let index = 0; index < 12; index++) {
  296. if(json?.childrens?.[index]){
  297. let volumeData = json?.childrens?.[index]
  298. volumeData = fixVolumeData(volumeData,volumeData,index)
  299. volumeList.push(volumeData)
  300. }
  301. }
  302. json.volumeList = volumeList
  303. // 作者列表 限6人
  304. let alist = []
  305. for (let index = 0; index < 9; index++) {
  306. 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||" "]
  307. }
  308. // 相关成果 限5项
  309. let aclist = []
  310. for (let index = 0; index < 5; index++) {
  311. aclist[index] = [json?.achievementOptions?.[index]?.name||" ",json?.achievementOptions?.[index]?.unit||" ",toYearMonth(json?.achievementOptions?.[index]?.date)||" "]
  312. }
  313. // 申报历程 限4项
  314. let clist = []
  315. for (let index = 0; index < 4; index++) {
  316. let num = json?.courses?.[index]?(index+1):" ";
  317. clist[index] = [num,toYearMonth(json?.courses?.[index]?.date)||" ",json?.courses?.[index]?.wordage||" ",json?.courses?.[index]?.num||" ",json?.courses?.[index]?.sumNum||" ",json?.courses?.[index]?.accolade||" "]
  318. }
  319. // 附件信息
  320. let mergeFiles = []
  321. // 1.教材电子版(必须提供)
  322. // (教材出版单位配合按要求上传各地推荐的本单位出版的纸质教材最新印次的完整PDF电子版;数字教材上传全部教材内容电子版或填写能够查看全部教材内容的链接地址、账号;纸质教材附带数字资源的,上传纸质教材最新印次的完整PDF电子版,以及全部数字资源电子版或能够查看全部数字资源内容的链接地址、账号。)
  323. // 2.所有作者政治审查意见(必须提供)
  324. // (对应作者姓名上传“作者政治审查表”。作者单位党委对作者进行审查,对政治思想表现情况进行评价,确保作者的正确政治方向、价值取向,无违法违纪等记录。教材编写成员涉及多个不同单位时需要各单位分别出具意见,并由所在单位党委盖章,格式要求从申报平台下载。)
  325. // 3.图书编校质量自查结果记录表(必须提供)
  326. // (教材出版单位对申报教材的编校质量自查后,按要求提供图书编校质量自查结果记录表,并加盖出版社公章。全册教材的不同分册以不同文件分别上传。格式要求从申报平台下载。)
  327. if(json?.selfResults?.url){mergeFiles.push(json?.selfResults?.url)}
  328. // 4.专家审查意见表(必须提供)
  329. // (由第一主编所在单位和出版机构邀请校内外相关学科专业领域专家,对教材进行思想性、学术性审查。专家不少于3名,其中半数以上为校外专家,专家分别实名评价并签字,并注明所在单位及专业身份。评价人不得是本教材的作者。)
  330. if(json?.expertOpinion?.url){mergeFiles.push(json?.expertOpinion?.url)}
  331. // 5.教材使用情况证明材料(必须提供)
  332. // (教材出版单位提供教材主要使用高校名单及使用情况证明材料,并加盖公章。)
  333. if(json?.evidence?.url){mergeFiles.push(json?.evidence?.url)}
  334. // 6.版权信息及CIP数据(必须提供)
  335. // (版权页截图,中国版本图书馆CIP查询截图,如CIP数据中无“教材”字样的,须再上传内容提要或前言或后记中可以证明本书为教材的相关内容截图。)
  336. if(json?.CIPImgUrl){mergeFiles.push(json?.CIPImgUrl)}
  337. // 7.其他材料(可选提供)
  338. if(json?.moreMaterial?.length){
  339. json?.moreMaterial.forEach(doc=>{
  340. if(doc?.url){
  341. mergeFiles.push(doc?.url)
  342. }
  343. })
  344. }
  345. let codePngBuffer = await toBarCode(json?.code);
  346. let codePngPath = path.join(tempDir,bookid+"code.png")
  347. fs.writeFileSync(codePngPath,codePngBuffer)
  348. // let codeBarImg =
  349. // console.log(codeBarImg)
  350. // (其他佐证材料,限两份以内。)
  351. console.log(codePngPath)
  352. let fixData = {
  353. volumeList:volumeList,
  354. // 图片信息
  355. codeBarImg:codePngPath,
  356. // 封面信息
  357. titlePad:padString(json?.title,21),
  358. ISBNPad:padString(json?.ISBN,21),
  359. ISBN:json?.ISBN,
  360. inviteUnit:json?.inviteUnit||"",
  361. one:squareCheck[(json?.type=="单本"||json?.type=="单册")?1:0], // 单本/单册 方框
  362. full:squareCheck[json?.type=="全册"?1:0], // 全册
  363. oneCircle:circleCheck[(json?.type=="单本"||json?.type=="单册")?1:0], // 单本/单册 圆圈
  364. fullCircle:circleCheck[json?.type=="全册"?1:0], // 全册
  365. tn:json?.typeNumber,
  366. authorPad:padString(json?.author,21),
  367. mobile:padString(mobile,21),
  368. authorUnit:padString(json?.unit,21),
  369. publisherPad:padString(json?.editionUnit,21),
  370. createdDate:padString(createdDate,21),
  371. // 基本信息
  372. title:json?.title,
  373. // 教材适用情况
  374. lessons:json?.lessons||[],
  375. period:json?.period||"", // 学时
  376. isBX:squareCheck[(characteristic?.indexOf("必修")>-1)?1:0],
  377. isXX:squareCheck[(characteristic?.indexOf("选修")>-1)?1:0],
  378. isTS:circleCheck[(characteristic?.indexOf("通识")>-1)?1:0],
  379. isGG:circleCheck[(characteristic?.indexOf("公共")>-1)?1:0],
  380. isZY:circleCheck[(characteristic?.indexOf("专业")>-1)?1:0],
  381. isSXZZ:squareCheck[(characteristic?.indexOf("思想")>-1)?1:0],
  382. isSY:squareCheck[(characteristic?.indexOf("实验")>-1)?1:0],
  383. // 作者列表
  384. 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],
  385. 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],
  386. 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],
  387. 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],
  388. 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],
  389. 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],
  390. // 成果列表
  391. ac11:aclist[0][0],ac12:aclist[0][1],ac13:aclist[0][2],
  392. ac21:aclist[1][0],ac22:aclist[1][1],ac23:aclist[1][2],
  393. ac31:aclist[2][0],ac32:aclist[2][1],ac33:aclist[2][2],
  394. ac41:aclist[3][0],ac42:aclist[3][1],ac43:aclist[3][2],
  395. ac51:aclist[4][0],ac52:aclist[4][1],ac53:aclist[4][2],
  396. // 历程列表
  397. c11:clist[0][0],c12:clist[0][1],c13:clist[0][2],c14:clist[0][3],c15:clist[0][4],c16:clist[0][5],
  398. c21:clist[1][0],c22:clist[1][1],c23:clist[1][2],c24:clist[1][3],c25:clist[1][4],c26:clist[1][5],
  399. c31:clist[2][0],c32:clist[2][1],c33:clist[2][2],c34:clist[2][3],c35:clist[2][4],c36:clist[2][5],
  400. c41:clist[3][0],c42:clist[3][1],c43:clist[3][2],c44:clist[3][3],c45:clist[3][4],c46:clist[3][5],
  401. }
  402. let bookData = json;
  403. Object.keys(fixData).forEach(key=>{
  404. bookData[key] = fixData[key] || bookData[key] || ""
  405. })
  406. // console.log(bookData)
  407. // console.log(json)
  408. let tempFileName = path.join(`${bookid}${json.title}.docx`)
  409. // 开始文件合并导出
  410. let filePath,pdfPath,urlDocx,urlPdf
  411. let TemplateDocxPath
  412. if(json?.childrens?.length>1){
  413. TemplateDocxPath = path.join(TemplateDocxDir,"模板-本科教材申报书-全册.docx")
  414. }else{
  415. }
  416. TemplateDocxPath = path.join(TemplateDocxDir,"模板-本科教材申报书-单册.docx")
  417. try{
  418. // DOCX模板合成 速度2-3秒
  419. filePath = renderDocx(TemplateDocxPath,tempFileName,bookData)
  420. urlDocx = (await uploadFileToOSS(filePath))?.url || null
  421. console.log("DOCX CREATED:",filePath)
  422. // PDF文档拼接 速度10-30s 需要API支持
  423. pdfPath = filePath.replaceAll(".docx",".pdf")
  424. let options = {
  425. mergeFiles:mergeFiles
  426. }
  427. pdfPath = await docxToPdf(filePath,pdfPath,options) || filePath // 成功用pdf,失败继续用docx
  428. console.log("PDF CREATED:",filePath)
  429. if(pdfPath){
  430. urlPdf = (await uploadFileToOSS(pdfPath))?.url || null
  431. }
  432. }catch(err){
  433. console.error(err)
  434. }
  435. resolve({
  436. code:bookid,
  437. title:json?.title,
  438. filePath,
  439. pdfPath,
  440. urlDocx,
  441. urlPdf,
  442. })
  443. return
  444. replaceDocx(TemplateDocxPath,tempFileName,bookData,{onDocxComplete:async (filePath)=>{
  445. // 需要API支持
  446. let pdfPath = filePath.replaceAll(".docx",".pdf")
  447. filePath = await docxToPdf(filePath,pdfPath) || filePath // 成功用pdf,失败继续用docx
  448. let url = (await uploadFileToOSS(pdfPath))?.url || null
  449. resolve({
  450. code:bookid,
  451. title:json?.title,
  452. filePath,
  453. url
  454. })
  455. }})
  456. })
  457. }
  458. function toYearMonth(date){
  459. // console.log("toYearMonth",date)
  460. if(typeof date == "string" && date?.indexOf("年")>-1) return date
  461. if(!date) return ""
  462. let datestr = date?.iso||date
  463. if(!datestr) return ""
  464. date = new Date(datestr);
  465. return `${date.getFullYear()}年${date.getMonth()+1}月`
  466. }
  467. function padString(str,width) {
  468. str = str || ""
  469. return str
  470. str = String(str)
  471. width = width || 21 // 目标宽度为21个单位
  472. let spaceChar = " " // 占位符 render可用空格,但document.xml用&#160;
  473. // 计算字符串的宽度
  474. const charWidth = {
  475. 'space': 1, // 空格占用1个单位
  476. 'zh': 3, // 汉字占用3个单位
  477. 'en': 1, // 英文字母占用1个单位
  478. 'other': 1 // 其他字符(如标点符号)占用1个单位
  479. };
  480. let strWidth = 0;
  481. // console.log(str)
  482. // 遍历文本中的每个字符
  483. for (let char of str) {
  484. if (/\s/.test(char)) {
  485. strWidth += charWidth.space; // 空格
  486. } else if (/[\u4e00-\u9fa5]/.test(char)) {
  487. strWidth += charWidth.zh; // 汉字
  488. } else if (/[a-zA-Z]/.test(char)) {
  489. strWidth += charWidth.en; // 英文字母
  490. } else {
  491. strWidth += charWidth.other; // 其他字符
  492. }
  493. }
  494. // for (let char of str) {
  495. // // 判断字符是否为中文
  496. // if (char.match(/[\u4e00-\u9fa5]/)) {
  497. // strWidth += 2; // 中文字符占4个单位
  498. // } else {
  499. // strWidth += 1; // 英文字符占1个单位
  500. // }
  501. // }
  502. const totalPadding = width - strWidth;
  503. // 如果已经达到或超过目标宽度,直接返回原字符串
  504. if (totalPadding <= 0) {
  505. return str;
  506. }
  507. // 计算左右两侧的空格数
  508. const leftPadding = Math.floor(totalPadding / 2) * 3;
  509. const rightPadding = Math.ceil(totalPadding / 2) * 3;
  510. // 生成填充空格的字符串
  511. const leftSpaces = spaceChar.repeat(leftPadding);
  512. const rightSpaces = spaceChar.repeat(rightPadding);
  513. // 返回补充后的字符串
  514. return leftSpaces + str + rightSpaces;
  515. }