func-tbook-export.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import { replaceDocx, docsToPdf, createZip, uploadFileToOSS } from "../../lib/docs";
  2. // const Parse = global.Parse;
  3. const path = require("path")
  4. const fs = require("fs")
  5. var TemplateDocxPath = path.join(__dirname,"template/模板-推荐申报表.docx")
  6. if(!fs.existsSync(TemplateDocxPath)){
  7. TemplateDocxPath = path.join(__dirname,"../../template/模板-推荐申报表.docx")
  8. }
  9. /**
  10. * 定义导出申报合集文档云函数
  11. * @example
  12. * Cloud Code test
  13. 导出流程
  14. curl -X POST -H "Content-Type: application/json" -H 'X-Parse-Application-Id: edu-textbook' -d '{ "processId": "Wz34loxdbO" }' http://127.0.0.1:61337/parse/functions/tbookExportReport
  15. curl -X POST -H "Content-Type: application/json" -H 'X-Parse-Application-Id: edu-textbook' -d '{ "processId": "Wz34loxdbO" }' http://8.140.98.43/parse/functions/tbookExportReport
  16. 导出教材列表
  17. curl -X POST -H "Content-Type: application/json" -H 'X-Parse-Application-Id: edu-textbook' -d '{ "bookList": ["2YBKitpCJL","xLdiEaHGrX"] }' http://127.0.0.1:61337/parse/functions/tbookExportReport
  18. */
  19. export function defineTbookExportReport(){
  20. Parse.Cloud.define("tbookExportReport", async (request) => {
  21. let processId = request.params.processId;
  22. let bookList = request.params.bookList;
  23. try{
  24. let result
  25. if(processId){
  26. await exportProcessReportDocs(processId)
  27. }
  28. if(bookList?.length){
  29. await exportProcessReportDocs(null,bookList)
  30. }
  31. if(result?.docsList?.length==0){
  32. throw new Parse.Error(404,"合集内无申报教材")
  33. }
  34. return result
  35. }catch(err){
  36. console.error(err)
  37. throw new Parse.Error(404,"导出申报合集失败")
  38. }
  39. throw new Parse.Error(404,"未找到该流程合集")
  40. },{
  41. fields : {
  42. processId:{
  43. required:false
  44. },
  45. }
  46. });
  47. }
  48. /**
  49. * 导出流程教材申报文件
  50. * @returns
  51. * docsList
  52. * zipUrl
  53. */
  54. export async function exportProcessReportDocs(processId,bookList) {
  55. if(!processId && !bookList?.length) return {}
  56. let textbookList
  57. if(processId){ // 流程读取教材列表
  58. let query = new Parse.Query("EduTextbook")
  59. query.equalTo("eduProcess",processId);
  60. textbookList = await query.find();
  61. }
  62. if(bookList?.length){ // 直接导出教材列表
  63. let query = new Parse.Query("EduTextbook")
  64. query.containedIn("objectId",bookList);
  65. textbookList = await query.find();
  66. }
  67. console.log(textbookList)
  68. let docsList = []
  69. for (let index = 0; index < textbookList.length; index++) {
  70. let textbook = textbookList[index];
  71. let result = await renderReportDocsByTextbook(textbook);
  72. docsList.push(result)
  73. }
  74. let zipPath,zipUrl
  75. if(docsList?.length){
  76. let now = new Date();
  77. let zipName = `申报书导出-${now.getFullYear()}${now.getMonth()+1}${now.getDate()}-${now.getHours()}${now.getMinutes()}${now.getSeconds()}.zip`
  78. zipPath = await createZip(docsList?.map(item=>item?.filePath),zipName)
  79. if(zipPath){
  80. zipUrl = (await uploadFileToOSS(zipPath))?.url || null
  81. }
  82. docsList = docsList.map(item=>{return {code:item.code,title:item.title,url:item?.url}})
  83. }
  84. console.log(textbookList);
  85. console.log(docsList)
  86. console.log(processId)
  87. let result = {
  88. docsList,
  89. zipUrl
  90. }
  91. return result
  92. }
  93. module.exports.exportProcessReportDocs = exportProcessReportDocs
  94. function renderReportDocsByTextbook(textbook){
  95. let json = textbook.toJSON();
  96. console.log(json)
  97. // 圆圈选中未选 ○ 未选 ● 选中
  98. let circleCheck = ["○","●"];
  99. // 方块选中未选 ○ 未选 ● 选中
  100. let squareCheck = [``,`☑`];
  101. // 联系电话:默认为作者首个存在的电话;
  102. let mobile = json?.authorList?.find(item => item.mobile)?.mobile || ""
  103. // 填报时间:默认为创建时间
  104. let createdAt = new Date(textbook?.createdAt);
  105. let createdDate = `${createdAt?.getFullYear()}年${createdAt?.getMonth()+1}月${createdAt?.getDate()}日`;
  106. // 专业代码:前四位
  107. let majorCode = json?.majorId || json?.major?.code
  108. if(majorCode?.length>4){
  109. majorCode = majorCode.slice(0,4)
  110. }
  111. let majorName = json?.majorName || json?.major?.name
  112. // 是否重点立项
  113. let isJC = circleCheck[(json?.approval?.indexOf("基础")>-1)?1:0];
  114. let isZL = circleCheck[(json?.approval?.indexOf("战略")>-1)?1:0];
  115. let isSX = circleCheck[(json?.approval?.indexOf("四新")>-1)?1:0];
  116. let isNotImpt = (json?.approval?.indexOf("基础")==-1) && (json?.approval?.indexOf("战略")==-1) && (json?.approval?.indexOf("四新")==-1)
  117. isNotImpt = circleCheck[isNotImpt?1:0];
  118. // 初版时间
  119. let firstDate = new Date(textbook?.get("editionFirst"));
  120. let firstYear = firstDate?.getFullYear();
  121. let firstMonth = firstDate?.getMonth()+1;
  122. // 本版时间印次
  123. let currentDate = new Date(textbook?.get("editionDate"));
  124. let currentYear = currentDate?.getFullYear();
  125. let currentMonth = currentDate?.getMonth()+1;
  126. // 最新时间印次
  127. let latestDate = new Date(textbook?.get("printDate"));
  128. let latestYear = latestDate?.getFullYear();
  129. let latestMonth = latestDate?.getMonth()+1;
  130. // 初版至今重点项目
  131. let isBSQT = !((json?.importantProject?.indexOf("建设")>-1) || (json?.importantProject?.indexOf("本科国家")>-1) || (json?.importantProject?.indexOf("省级优秀")>-1) || (json?.importantProject?.indexOf("省级规划")>-1))// 是否其他省级奖项
  132. let bookData = {
  133. // 封面信息
  134. titlePad:padString(json?.title,21),
  135. ISBN:padString(json?.ISBN,21),
  136. one:squareCheck[(json?.type=="单本"||json?.type=="单册")?1:0], // 单本/单册 方框
  137. full:squareCheck[json?.type=="全册"?1:0], // 全册
  138. oneCircle:circleCheck[(json?.type=="单本"||json?.type=="单册")?1:0], // 单本/单册 圆圈
  139. fullCircle:circleCheck[json?.type=="全册"?1:0], // 全册
  140. tn:json?.typeNumber,
  141. authorPad:padString(json?.author,21),
  142. mobile:padString(mobile,21),
  143. authorUnit:padString(json?.unit,21),
  144. publisherPad:padString(json?.editionUnit,21),
  145. recommandUnit:padString("",21), // 未找到
  146. majorCodePad:padString((majorCode),14),
  147. createdDate:padString(createdDate,21),
  148. // 基本信息
  149. title:json?.title,
  150. author:json?.author,
  151. unit:json?.unit,
  152. mc:majorCode,
  153. mn:majorName,
  154. lCN:circleCheck[(json?.lang=="中文")?1:0],
  155. lEN:circleCheck[(json?.lang=="英文")?1:0],
  156. lOT:circleCheck[(json?.lang?.indexOf("其他")>-1)?1:0],
  157. lSS:circleCheck[(json?.lang?.indexOf("少数")>-1)?1:0],
  158. authors:json?.authors, // 其他主编
  159. editor:json?.editor, // 其他编者
  160. isJC:isJC,
  161. isZL:isZL,
  162. isSX:isSX,
  163. isNotImpt:isNotImpt,
  164. publisher:json?.editionUnit,
  165. firstYear:firstYear,
  166. firstMonth:firstMonth,
  167. isZZ:circleCheck[(json?.carrierShape?.indexOf("纸质")>-1)?1:0],
  168. isDZ:circleCheck[(json?.carrierShape?.indexOf("电子")>-1)?1:0],
  169. isSZ:circleCheck[(json?.carrierShape?.indexOf("数字")>-1)?1:0],
  170. isQT:circleCheck[(json?.carrierShape?.indexOf("附带")>-1)?1:0],
  171. isFD:circleCheck[(json?.carrierShape?.indexOf("其他")>-1)?1:0],
  172. latestY:latestYear,
  173. latestM:latestMonth,
  174. latestNum:json?.printNumber || "",
  175. currentY:currentYear,
  176. currentM:currentMonth,
  177. currentNum:json?.editionNumber || "",
  178. printSum:json?.printSum || "",
  179. isJS:circleCheck[(json?.importantProject?.indexOf("建设")>-1)?1:0],
  180. isBGJ:circleCheck[(json?.importantProject?.indexOf("本科国家")>-1)?1:0],
  181. isBSYX:circleCheck[(json?.importantProject?.indexOf("省级优秀")>-1)?1:0],
  182. isBSGH:circleCheck[(json?.importantProject?.indexOf("省级规划")>-1)?1:0],
  183. isBSQT:circleCheck[isBSQT?1:0],
  184. bsqtName:isBSQT?json?.importantProject:"",
  185. isFirstNot:circleCheck[json?.importantProject?0:1],
  186. }
  187. console.log(bookData)
  188. let bookid = json.code || json?.objectId;
  189. let tempFileName = path.join(`${bookid}${json.title}.docx`)
  190. return new Promise((resolve)=>{
  191. replaceDocx(TemplateDocxPath,tempFileName,bookData,{onDocxComplete:async (filePath)=>{
  192. // 需要API支持
  193. // docsToPdf(filePath)
  194. let url = (await uploadFileToOSS(filePath))?.url || null
  195. resolve({
  196. code:bookid,
  197. title:json?.title,
  198. filePath,
  199. url
  200. })
  201. }})
  202. })
  203. }
  204. function padString(str,width) {
  205. str = str || ""
  206. str = String(str)
  207. width = width || 21 // 目标宽度为21个单位
  208. let spaceChar = "&#160;" // 占位符
  209. // 计算字符串的宽度
  210. const charWidth = {
  211. 'space': 1, // 空格占用1个单位
  212. 'zh': 2, // 汉字占用2个单位
  213. 'en': 1, // 英文字母占用1个单位
  214. 'other': 1 // 其他字符(如标点符号)占用1个单位
  215. };
  216. let strWidth = 0;
  217. console.log(str)
  218. // 遍历文本中的每个字符
  219. for (let char of str) {
  220. if (/\s/.test(char)) {
  221. strWidth += charWidth.space; // 空格
  222. } else if (/[\u4e00-\u9fa5]/.test(char)) {
  223. strWidth += charWidth.zh; // 汉字
  224. } else if (/[a-zA-Z]/.test(char)) {
  225. strWidth += charWidth.en; // 英文字母
  226. } else {
  227. strWidth += charWidth.other; // 其他字符
  228. }
  229. }
  230. // for (let char of str) {
  231. // // 判断字符是否为中文
  232. // if (char.match(/[\u4e00-\u9fa5]/)) {
  233. // strWidth += 2; // 中文字符占4个单位
  234. // } else {
  235. // strWidth += 1; // 英文字符占1个单位
  236. // }
  237. // }
  238. const totalPadding = width - strWidth;
  239. // 如果已经达到或超过目标宽度,直接返回原字符串
  240. if (totalPadding <= 0) {
  241. return str;
  242. }
  243. // 计算左右两侧的空格数
  244. const leftPadding = Math.floor(totalPadding / 2) * 3;
  245. const rightPadding = Math.ceil(totalPadding / 2) * 3;
  246. // 生成填充空格的字符串
  247. const leftSpaces = spaceChar.repeat(leftPadding);
  248. const rightSpaces = spaceChar.repeat(rightPadding);
  249. // 返回补充后的字符串
  250. return leftSpaces + str + rightSpaces;
  251. }