func-tbook-export.js 32 KB

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