textbook.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. import { Injectable } from '@angular/core';
  2. import Parse from 'parse';
  3. import { HttpClient } from '@angular/common/http';
  4. import { updateDept } from './importDept';
  5. import { NzMessageService } from 'ng-zorro-antd/message';
  6. @Injectable({
  7. providedIn: 'root',
  8. })
  9. export class textbookServer {
  10. company: string = localStorage.getItem('company')!;
  11. theme: boolean = false; //深色主题模式
  12. profile: any = JSON.parse(localStorage.getItem('profile')!);
  13. constructor(private http: HttpClient) {}
  14. authMobile(mobile: string): boolean {
  15. let a = /^1[3456789]\d{9}$/;
  16. if (!String(mobile).match(a)) {
  17. return false;
  18. }
  19. return true;
  20. }
  21. randomPassword(): string {
  22. let sCode =
  23. 'A,B,C,E,F,G,H,J,K,L,M,N,P,Q,R,S,T,W,X,Y,Z,1,2,3,4,5,6,7,8,9,0,q,w,e,r,t,y,u,i,o,p,a,s,d,f,g,h,j,k,l,z,x,c,v,b,n,m';
  24. let aCode = sCode.split(',');
  25. let aLength = aCode.length; //获取到数组的长度
  26. let str = [];
  27. for (let i = 0; i <= 16; i++) {
  28. let j = Math.floor(Math.random() * aLength); //获取到随机的索引值
  29. let txt = aCode[j]; //得到随机的一个内容
  30. str.push(txt);
  31. }
  32. return str.join('');
  33. }
  34. formatTime(fmt: string, date1: Date) {
  35. let ret;
  36. let date = new Date(date1);
  37. const opt: any = {
  38. 'Y+': date.getFullYear().toString(), // 年
  39. 'm+': (date.getMonth() + 1).toString(), // 月
  40. 'd+': date.getDate().toString(), // 日
  41. 'H+': date.getHours().toString(), // 时
  42. 'M+': date.getMinutes().toString(), // 分
  43. 'S+': date.getSeconds().toString(), // 秒
  44. // 有其他格式化字符需求可以继续添加,必须转化成字符串
  45. };
  46. for (let k in opt) {
  47. ret = new RegExp('(' + k + ')').exec(fmt);
  48. if (ret) {
  49. fmt = fmt.replace(
  50. ret[1],
  51. ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0')
  52. );
  53. }
  54. }
  55. return fmt;
  56. }
  57. //格式化链
  58. async formatNode(id: string): Promise<Array<any>> {
  59. if (!id) return [];
  60. let query = new Parse.Query('Department');
  61. query.select('name', 'parent.name', 'branch');
  62. let r = await query.get(id);
  63. let arr = [
  64. {
  65. title: r.get('name'),
  66. key: r.id,
  67. // hasChildren: r.get('hasChildren'), //是否是最下级
  68. // type: r.get('type'),
  69. branch: r?.get('branch'),
  70. parent: r?.get('parent')?.id, //上级
  71. },
  72. ];
  73. if (r?.get('parent')) {
  74. arr.unshift(...(await this.formatNode(r?.get('parent').id)));
  75. }
  76. return arr;
  77. }
  78. //获取下级所有部门
  79. async getChild(id: string): Promise<Array<string>> {
  80. // console.log(id);
  81. let arr: Array<string> = [id];
  82. let query = new Parse.Query('Department');
  83. query.equalTo('parent', id);
  84. query.notEqualTo('isDeleted', true);
  85. query.select('id', 'hasChildren');
  86. query.limit(500);
  87. let r = await query.find();
  88. for (let index = 0; index < r.length; index++) {
  89. if (r[index].get('hasChildren')) {
  90. let child: Array<string> = await this.getChild(r[index].id);
  91. arr.push(...child);
  92. } else {
  93. arr.push(r[index].id);
  94. }
  95. }
  96. return Array.from(new Set([...arr]));
  97. }
  98. userFind(phone: string) {
  99. return new Promise((res) => {
  100. Parse.Cloud.run('userFind', { mobile: phone })
  101. .then((data) => {
  102. if (data) {
  103. res(false);
  104. } else {
  105. res(true);
  106. }
  107. })
  108. .catch(() => res(true));
  109. });
  110. }
  111. async tbookExportReport(options: { processId?: string; bookList?: any[] }) {
  112. let url = Parse.serverURL + '/api/tbook/export';
  113. // console.log(url)
  114. let response = await fetch(url, {
  115. method: 'POST',
  116. headers: {
  117. 'Content-Type': 'application/json',
  118. 'X-Parse-Application-Id': 'edu-textbook',
  119. },
  120. body: JSON.stringify(options),
  121. });
  122. let result = await response.json();
  123. let zipUrl = result?.result?.zipUrl;
  124. if (result?.code == 200) {
  125. zipUrl = zipUrl.replaceAll('http://', 'https://');
  126. const a = document.createElement('a'); // 创建一个&lt;a&gt;元素
  127. a.href = zipUrl; // 设置链接的href属性为要下载的文件的URL
  128. a.download = '报送流程'; // 设置下载文件的名称
  129. document.body.appendChild(a); // 将&lt;a&gt;元素添加到文档中
  130. a.click(); // 模拟点击&lt;a&gt;元素
  131. document.body.removeChild(a); // 下载后移除&lt;a&gt;元素
  132. }
  133. // console.log(result)
  134. return result;
  135. Parse.Cloud.run('tbookExportReport', options).then((data) => {
  136. console.log(data);
  137. let url = data.zipUrl;
  138. url = url.replaceAll('http://', 'https://');
  139. const a = document.createElement('a'); // 创建一个&lt;a&gt;元素
  140. a.href = url; // 设置链接的href属性为要下载的文件的URL
  141. a.download = '报送流程'; // 设置下载文件的名称
  142. document.body.appendChild(a); // 将&lt;a&gt;元素添加到文档中
  143. a.click(); // 模拟点击&lt;a&gt;元素
  144. document.body.removeChild(a); // 下载后移除&lt;a&gt;元素
  145. });
  146. }
  147. async preview(options: { bookid: string }) {
  148. let url = Parse.serverURL + '/api/tbook/preview';
  149. let response = await fetch(url, {
  150. method: 'POST',
  151. headers: {
  152. 'Content-Type': 'application/json',
  153. 'X-Parse-Application-Id': 'edu-textbook',
  154. },
  155. body: JSON.stringify(options),
  156. });
  157. let result = await response.json();
  158. let { urlPdf } = result?.data;
  159. console.log(urlPdf);
  160. window.open(urlPdf);
  161. }
  162. async getEduProcess(id: string): Promise<string | undefined> {
  163. if (!id) return;
  164. let query = new Parse.Query('EduProcess');
  165. query.equalTo('department', id);
  166. query.lessThanOrEqualTo('startDate', new Date());
  167. query.greaterThan('deadline', new Date());
  168. query.notEqualTo('isDeleted', true);
  169. query.containedIn('status', ['200', '300']);
  170. query.select('objectId');
  171. let res = await query.first();
  172. return res?.id;
  173. }
  174. //需要删除是否存在为联系人情况
  175. async getEduProcessProf(filter: Array<string>): Promise<string | undefined> {
  176. let query = new Parse.Query('EduProcess');
  177. query.notEqualTo('isDeleted', true);
  178. query.containedIn('profileSubmitted', filter);
  179. query.select('objectId', 'name');
  180. let res = await query.first();
  181. if (res?.id) {
  182. return res.get('name');
  183. }
  184. return;
  185. }
  186. //更新工作联系人
  187. async updateProfileSubmitted(
  188. pid: string,
  189. type: string,
  190. dpid?: string,
  191. msg?: NzMessageService
  192. ): Promise<boolean | undefined> {
  193. console.log(pid, type, dpid);
  194. let query = new Parse.Query('EduProcess');
  195. if (!dpid) {
  196. query.equalTo('profileSubmitted', pid);
  197. } else {
  198. query.equalTo('department', dpid);
  199. }
  200. query.include('profileSubmitted');
  201. query.select('objectId', 'profileSubmitted');
  202. query.notEqualTo('isDeleted', true);
  203. let res = await query.first();
  204. if (!res?.id) {
  205. msg?.warning('所属单位不存在');
  206. return false;
  207. }
  208. if (type == 'del') {
  209. //删除工作联系人
  210. res.set('profileSubmitted', null);
  211. await res.save();
  212. return true;
  213. } else {
  214. //添加工作联系人
  215. if (res?.get('profileSubmitted')?.get('user')) {
  216. msg?.warning('该单位已有部门联系人,请先移除后再操作');
  217. return false;
  218. }
  219. res?.set('profileSubmitted', {
  220. __type: 'Pointer',
  221. className: 'Profile',
  222. objectId: pid,
  223. });
  224. await res.save();
  225. return true;
  226. }
  227. return false;
  228. }
  229. /* 批量预设(临时) */
  230. async saveProcess() {
  231. let count = 0;
  232. /* 批量关联流程管理员 */
  233. // let queryEdupro = new Parse.Query('EduProcess')
  234. // queryEdupro.select('department','name')
  235. // queryEdupro.equalTo('profileSubmitted',null)
  236. // queryEdupro.limit(1000)
  237. // let res = await queryEdupro.find()
  238. // console.log(res);
  239. // for (let index = 0; index < res.length; index++) {
  240. // const item = res[index];
  241. // let queryParams: any = {
  242. // where: {
  243. // $or: [
  244. // {
  245. // user: {
  246. // $inQuery: {
  247. // where: {
  248. // "department":item?.get('department')?.id,
  249. // "accountState":'已认证',
  250. // },
  251. // className: '_User',
  252. // },
  253. // },
  254. // },
  255. // ],
  256. // },
  257. // };
  258. // let query = Parse.Query.fromJSON('Profile', queryParams);
  259. // query.equalTo('identity','工作联系人')
  260. // query.select('objectId')
  261. // let p = await query.first()
  262. // if(p?.id){
  263. // item.set('profileSubmitted',p.toPointer())
  264. // await item.save()
  265. // console.log('绑定成功'+p.id+':'+item?.get('name'));
  266. // count++
  267. // }
  268. // }
  269. // let arr = [];
  270. // let query = new Parse.Query('EduTextbook');
  271. // query.notEqualTo('isDeleted', true);
  272. // query.notEqualTo('discard', true);
  273. // query.equalTo('render', true);
  274. // query.select(
  275. // 'title',
  276. // 'childrens.ISBN',
  277. // 'childrens.author',
  278. // 'childrens.editionUnit',
  279. // 'inviteUnit'
  280. // );
  281. // query.limit(3000);
  282. // query.containedIn('status', ['400']);
  283. // let eduTextbook = await query.find();
  284. // for (let index = 0; index < eduTextbook.length; index++) {
  285. // const item = eduTextbook[index];
  286. // let ISBN = '',
  287. // author = '',
  288. // editionUnit = '';
  289. // item?.get('childrens').forEach((children: any) => {
  290. // ISBN = children?.get('ISBN') + ' ' + ISBN;
  291. // author = children?.get('author') + ' ' + author;
  292. // editionUnit = children?.get('editionUnit') + ' ' + editionUnit;
  293. // });
  294. // arr.push({
  295. // 教材名称: item?.get('title'),
  296. // ISBN: ISBN,
  297. // 作者: author,
  298. // 出版社: editionUnit,
  299. // 所属院校: item?.get('inviteUnit'),
  300. // });
  301. // }
  302. // console.log(arr);
  303. }
  304. /* 格式化拓展表字段 */
  305. fromatFiled(list: Array<Parse.Object>, filed: string): string {
  306. let arr: Array<string | null> = [];
  307. let isDate = false;
  308. // 监测空值
  309. list?.forEach((item: Parse.Object) => {
  310. if (
  311. isDate ||
  312. Object.prototype.toString.call(item.get(filed)).indexOf('Date') != -1
  313. ) {
  314. arr.push(
  315. this.formatTime('YYYY-mm-dd', item.get(filed)) +
  316. '/' +
  317. item.get('printNumber')
  318. );
  319. isDate = true;
  320. } else {
  321. arr.push(item.get(filed));
  322. }
  323. });
  324. let j = Array.from(arr).join(',');
  325. if (!isDate) {
  326. j = Array.from(new Set(arr)).join(' ');
  327. }
  328. return j || '-';
  329. }
  330. //导出表格
  331. async exportEduTextbook() {
  332. try {
  333. let query = new Parse.Query('EduTextbook');
  334. query.notEqualTo('isDeleted', true);
  335. query.notEqualTo('discard', true);
  336. query.equalTo('render', true);
  337. query.select(
  338. 'title',
  339. 'childrens.ISBN',
  340. 'childrens.author',
  341. 'childrens.printDate',
  342. 'childrens.printNumber',
  343. 'childrens.editionUnit',
  344. 'inviteUnit',
  345. 'user.department',
  346. 'department.branch',
  347. 'code'
  348. // 'eduProcess.profileSubmitted',
  349. // 'eduProcess.profileSubmitted.email',
  350. // 'eduProcess.profileSubmitted.user.name',
  351. // 'eduProcess.profileSubmitted.user.phone'
  352. );
  353. query.containedIn('status', ['102', '103', '200', '201', '400']);
  354. // query.containedIn('status',['400'])
  355. // query.containedIn('objectId',updateDept.list5)
  356. query.ascending('createdAt');
  357. let count = await query.count();
  358. console.log(count);
  359. query.limit(2000);
  360. query.skip(2000);
  361. let data = await query.find();
  362. let table = `<table border="1px" cellspacing="0" cellpadding="0">
  363. <thead>
  364. <tr>
  365. <th>序号</th>
  366. <th>申报教材名称</th>
  367. <th>第一主编/作者</th>
  368. <th>ISBN</th>
  369. <th>出版单位</th>
  370. <th>所属院校</th>
  371. <th>最新印次和时间</th>
  372. </tr>
  373. </thead>
  374. <tbody>
  375. `;
  376. // let table = `<table border="1px" cellspacing="0" cellpadding="0">
  377. // <thead>
  378. // <tr>
  379. // <th>序号</th>
  380. // <th>申报教材名称</th>
  381. // <th>code</th>
  382. // <th>所属单位</th>
  383. // <th>单位联系人</th>
  384. // <th>联系人电话</th>
  385. // <th>联系人邮箱</th>
  386. // </tr>
  387. // </thead>
  388. // <tbody>
  389. // `;
  390. let _body = '';
  391. for (var row = 0; row < data.length; row++) {
  392. // console.log(data[row].get('user')?.get('department'));
  393. let inviteUnit = data[row]?.get('inviteUnit');
  394. if (
  395. data[row]?.get('department')?.get('branch') == '省级教育行政部门' ||
  396. data[row]?.get('department')?.get('branch') ==
  397. '有关部门(单位)教育司(局)'
  398. ) {
  399. let parentMap = await this.formatNode(
  400. data[row].get('user')?.get('department')?.id
  401. );
  402. inviteUnit = parentMap[2]?.title;
  403. }
  404. _body += '<tr>';
  405. _body += '<td>';
  406. _body += `${row + 2001}`;
  407. _body += '</td>';
  408. _body += '<td>';
  409. _body += ` &nbsp;${data[row].get('title') || '-'}`;
  410. _body += '</td>';
  411. // _body += '<td>';
  412. // _body += `&nbsp;${data[row].get('code') || ''}`;
  413. // _body += '</td>';
  414. // _body += '<td>';
  415. // _body += `&nbsp;${data[row].get('inviteUnit') || ''}`;
  416. // _body += '</td>';
  417. // _body += '<td>';
  418. // _body += `&nbsp;${data[row]?.get('eduProcess')?.get('profileSubmitted')?.get('user')?.get('name') || ''}`;
  419. // _body += '</td>';
  420. // _body += '<td>';
  421. // _body += `&nbsp;${data[row]?.get('eduProcess')?.get('profileSubmitted')?.get('user')?.get('phone') || ''}`;
  422. // _body += '</td>';
  423. // _body += '<td>';
  424. // _body += `&nbsp;${data[row]?.get('eduProcess')?.get('profileSubmitted')?.get('email') || ''}`;
  425. // _body += '</td>';
  426. _body += '<td>';
  427. _body += `${this.fromatFiled(data[row]?.get('childrens'), 'author')}`;
  428. _body += '</td>';
  429. _body += '<td>';
  430. _body += `&nbsp;${this.fromatFiled(
  431. data[row]?.get('childrens'),
  432. 'ISBN'
  433. )}`;
  434. _body += '</td>';
  435. _body += '<td>';
  436. _body += `${this.fromatFiled(
  437. data[row]?.get('childrens'),
  438. 'editionUnit'
  439. )}`;
  440. _body += '</td>';
  441. _body += '<td>';
  442. _body += `${inviteUnit}`;
  443. _body += '</td>';
  444. _body += '<td>';
  445. _body += `${this.fromatFiled(
  446. data[row]?.get('childrens'),
  447. 'printDate'
  448. )}`;
  449. _body += '</td>';
  450. _body += '</tr>';
  451. }
  452. table += _body;
  453. table += '</tbody>';
  454. table += '</table>';
  455. let title = '已提交教材';
  456. this.excel(table, `${title}.xls`);
  457. } catch (err) {
  458. console.log(err);
  459. }
  460. }
  461. excel(data: any, filename: string) {
  462. let html =
  463. "<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:x='urn:schemas-microsoft-com:office:excel' xmlns='http://www.w3.org/TR/REC-html40'>";
  464. html +=
  465. '<meta http-equiv="content-type" content="application/vnd.ms-excel; charset=UTF-8">';
  466. html += '<meta http-equiv="content-type" content="application/vnd.ms-excel';
  467. html += '; charset=UTF-8">';
  468. html += '<head>';
  469. html += '</head>';
  470. html += '<body>';
  471. html += data;
  472. html += '</body>';
  473. html += '</html>';
  474. let uri =
  475. 'data:application/vnd.ms-excel;charset=utf-8,' + encodeURIComponent(html);
  476. let link = document.createElement('a');
  477. link.href = uri;
  478. link.download = `${filename}`;
  479. document.body.appendChild(link);
  480. link.click();
  481. document.body.removeChild(link);
  482. }
  483. /* 发送出版社及教师通知短信 */
  484. async sendNoticeMSG() {
  485. let teacherList = new Set<string>();
  486. let unitList = new Set<string>(); //单位出版社
  487. let query = new Parse.Query('EduTextbook');
  488. query.equalTo('status', '400');
  489. query.notEqualTo('isDeleted', true);
  490. query.notEqualTo('discard', true);
  491. query.include('user.phone', 'childrens.editionUnit');
  492. query.select('user.phone', 'childrens.editionUnit')
  493. query.limit(1000);
  494. let r = await query.find();
  495. r.forEach((item) => {
  496. teacherList.add(item?.get('user')?.get('phone'));
  497. item
  498. .get('childrens')
  499. ?.forEach((child: any) => unitList.add(child?.get('editionUnit')));
  500. });
  501. let teacherArr = Array.from(teacherList);
  502. let unitArr = Array.from(unitList);
  503. console.log('教师电话:',teacherArr);
  504. // console.log(unitArr);
  505. // Parse.Cloud.run('aliSmsSend', {
  506. // mobileList: [teacherArr],
  507. // templateCode: 'SMS_474205136',
  508. // params: {},
  509. // signName: '普通高等教育教材网',
  510. // });
  511. let queryParams: any = {
  512. where: {
  513. $or: [
  514. {
  515. department: {
  516. $inQuery: {
  517. where: {
  518. name: { $in: unitArr },
  519. },
  520. className: 'Department',
  521. }
  522. },
  523. },
  524. ],
  525. },
  526. };
  527. let queryEduProcess = Parse.Query.fromJSON('EduProcess', queryParams);
  528. queryEduProcess.include('profileSubmitted.user');
  529. queryEduProcess.select('profileSubmitted.user.phone')
  530. query.limit(1000)
  531. let list = await queryEduProcess.find();
  532. // console.log(list);
  533. let unitPhoneList = new Set<string>(); //出版单位联系人
  534. list.map(item=> {
  535. if(item?.get('profileSubmitted')?.get('user')?.get('phone')){
  536. unitPhoneList.add(item?.get('profileSubmitted')?.get('user')?.get('phone'))
  537. }
  538. })
  539. let unitPhoneArr = Array.from(unitPhoneList)
  540. // Parse.Cloud.run('aliSmsSend', {
  541. // mobileList: [unitPhoneArr],
  542. // templateCode: 'SMS_474290139',
  543. // params: {end_date:'2024-10-20 16:00'},
  544. // signName: '普通高等教育教材网',
  545. // });
  546. console.log('出版社电话:',unitPhoneArr);
  547. }
  548. }