lrf402788946 4 tahun lalu
induk
melakukan
7e0b437ef3

+ 64 - 0
app/controller/.experience.js

@@ -0,0 +1,64 @@
+module.exports = {
+  create: {
+    requestBody: [
+      "termid",
+      "batchid",
+      "classid",
+      "studentid",
+      "title",
+      "content",
+    ],
+  },
+  destroy: {
+    params: ["!id"],
+    service: "delete",
+  },
+  update: {
+    params: ["!id"],
+    requestBody: [
+      "termid",
+      "batchid",
+      "classid",
+      "studentid",
+      "title",
+      "content",
+    ],
+  },
+  show: {
+    parameters: {
+      params: ["!id"],
+    },
+    service: "fetch",
+  },
+  index: {
+    parameters: {
+      query: {
+        termid: "termid",
+        batchid: "batchid",
+        classid: "classid",
+        studentid: "studentid",
+        content: "content",
+      },
+    },
+    service: "query",
+    options: {
+      query: ["skip", "limit"],
+      sort: ["meta.createdAt"],
+      desc: true,
+      count: true,
+    },
+  },
+  docx: {
+    parameters: {
+      query: {
+        planid:"planid",
+        termid: "termid",
+        batchid: "batchid",
+        classid: "classid",
+        studenti: "studentid",
+        id:"id",
+      },
+    },
+    service: "exportDocx",
+  },
+};

+ 16 - 0
app/controller/experience.js

@@ -0,0 +1,16 @@
+'use strict';
+
+const _ = require('lodash');
+const meta = require('./.experience.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose/lib/controller');
+
+// 职责说明管理
+class ExperienceController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.experience;
+  }
+}
+
+module.exports = CrudController(ExperienceController, meta);

+ 22 - 0
app/model/experience.js

@@ -0,0 +1,22 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
+
+// 职责说明表
+const ExperienceSchema = {
+  termid: { type: String, required: false }, // 班级id
+  batchid: { type: String, required: false }, // 班级id
+  classid: { type: String, ref: 'Class' }, // 班级id
+  studentid: { type: String, ref: 'Student' }, // 学生id
+  title: { type: String }, // 培训心得标题
+  content: { type: String }, // 培训心得内容
+};
+
+const schema = new Schema(ExperienceSchema, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.plugin(metaPlugin);
+
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Experience', schema, 'experience');
+};

+ 4 - 0
app/router.js

@@ -352,6 +352,10 @@ module.exports = app => {
   );
 
 
+  // 职责说明表设置路由
+  router.get('/api/train/experience/docx', controller.experience.docx); // index、create、show、destroy
+  router.resources('experience', '/api/train/experience', controller.experience); // index、create、show、destroy
+  router.post('experience', '/api/train/experience/update/:id', controller.experience.update);
   // 职责说明表设置路由
   router.resources('duty', '/api/train/duty', controller.duty); // index、create、show、destroy
   router.post('duty', '/api/train/duty/update/:id', controller.duty.update);

+ 174 - 0
app/service/experience.js

@@ -0,0 +1,174 @@
+'use strict';
+
+
+const assert = require('assert');
+const _ = require('lodash');
+const { ObjectId } = require('mongoose').Types;
+const { CrudService } = require('naf-framework-mongoose/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+
+class ExperienceService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'experience');
+    this.model = this.ctx.model.Experience;
+  }
+
+  async query(data, { skip, limit }) {
+    const res = await this.model.find().populate({ path: 'studentid', select: 'name' }).skip(parseInt(skip))
+      .limit(parseInt(limit));
+    return res;
+  }
+
+  async create(data) {
+    const { studentid } = data;
+    let res;
+    const r = await this.model.findOne({ studentid });
+    if (r) {
+      const { title, content } = data;
+      r.title = title;
+      r.content = content;
+      res = await r.save();
+    } else {
+      res = await this.model.create(data);
+    }
+    return res;
+  }
+
+  async exportDocx(data) {
+    const { planid, termid, batchid, classid, id } = data;
+    // 从小到大判断
+    // res是最后的结果,可能是Object也可能是Array
+    // 作者拼接:能直接获取学生姓名,班级名称,需要单独查下期数
+    let res;
+    let fn = '';
+    const trainPlanInfo = async (termid, batchid) => {
+      const trainPlan = await this.ctx.model.Trainplan.findOne({ 'termnum._id': ObjectId(termid) });
+      if (!trainPlan) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到年度计划信息');
+      const { termnum } = trainPlan;
+      if (!termnum) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到年度计划的期信息');
+      const obj = {};
+      if (termid) {
+        const term = termnum.id(termid);
+        if (term) obj.term = term.term;
+        if (batchid) {
+          const { batchnum } = term;
+          const batch = batchnum.id(batchid);
+          if (batch) obj.batch = batch.batch;
+        }
+      }
+      return obj;
+    };
+    if (id) {
+      const r = await this.model.findById(id).populate({ path: 'studentid', select: 'name job' }).populate({ path: 'classid', select: 'name' });
+      if (!r) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定范围的培训心得');
+      const { term } = await trainPlanInfo(r.termid);
+      if (term) { r.term = `第${term}期`; }
+      res = r;
+    } else if (classid) {
+      let r = await this.model.find({ classid }).populate({ path: 'studentid', select: 'name job' }).populate({ path: 'classid', select: 'name' });
+      if (r.length <= 0) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定范围的培训心得');
+      r = JSON.parse(JSON.stringify(r));
+      const h = _.head(r);
+      const { term } = await trainPlanInfo(h.termid);
+      r = r.map(i => {
+        i.term = `第${term}期`;
+        return i;
+      });
+      res = r;
+      fn = `第${term}期${h.classid.name}班培训心得`;
+    } else if (batchid) {
+      let r = await this.model.find({ batchid }).populate({ path: 'studentid', select: 'name job' }).populate({ path: 'classid', select: 'name' });
+      if (r.length <= 0) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定范围的培训心得');
+      r = JSON.parse(JSON.stringify(r));
+      const h = _.head(r);
+      const { term, batch } = await trainPlanInfo(h.termid, h.batchid);
+      r = r.map(i => {
+        i.term = `第${term}期`;
+        i.batch = `第${batch}批`;
+        return i;
+      });
+      res = r;
+      fn = `第${term}期第${batch}批培训心得`;
+    } else if (termid) {
+      let r = await this.model.find({ termid }).populate({ path: 'studentid', select: 'name job' }).populate({ path: 'classid', select: 'name' });
+      if (r.length <= 0) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定范围的培训心得');
+      r = JSON.parse(JSON.stringify(r));
+      const h = _.head(r);
+      const { term } = await trainPlanInfo(h.termid, h.batchid);
+      r = r.map(i => {
+        i.term = `第${term}期`;
+        return i;
+      });
+      res = r;
+      fn = `第${term}期培训心得`;
+    } else if (planid) {
+      const trainPlan = await this.ctx.model.Trainplan.findById(planid);
+      if (!trainPlan) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定年度计划信息');
+      const { termnum } = trainPlan;
+      if (!termnum) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定年度计划下的各期信息');
+      if (!_.isArray(termnum)) throw new BusinessError(ErrorCode.DATA_INVALID, '年度计划下的期信息数据格式错误');
+      const arr = [];
+      for (const t of termnum) {
+        const dup = JSON.parse(JSON.stringify(t));
+        let r = await this.model.find({ termid: dup._id }).populate({ path: 'studentid', select: 'name job' }).populate({ path: 'classid', select: 'name' });
+        if (r.length <= 0) continue;
+        r = JSON.parse(JSON.stringify(r));
+        const h = _.head(r);
+        const { term } = await trainPlanInfo(h.termid);
+        r = r.map(i => {
+          i.term = `第${term}期`;
+          return i;
+        });
+        arr.push(...r);
+      }
+      res = arr;
+      fn = `${trainPlan.title}培训心得`;
+    }
+    const arr = [];
+    if (res && !_.isArray(res)) {
+      const docxObj = this.experienceData(res);
+      arr.push(docxObj);
+      const { author } = docxObj;
+      if (author !== '') { fn = `${author}培训心得`; }
+    } else if (res && _.isArray(res)) {
+      for (const o of res) {
+        const docxObj = this.experienceData(o);
+        arr.push(docxObj);
+      }
+    }
+    if (res.length <= 0) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到任何培训心得');
+    return await this.ctx.service.util.toDocx(arr, fn);
+  }
+
+  experienceData(data) {
+    const { title, content, classid, studentid, term } = data;
+    let docxObj = { title };
+    const cArr = content.split('\n');
+    const ncArr = [];
+    // cArr内容按回车分行,检查每行内容开始是不是有4个空格(首行缩进),没有就加上
+    for (let c of cArr) {
+      if (_.isString(c) && !c.startsWith('    ')) {
+        c = `    ${_.trim(c)}`;
+      }
+      ncArr.push(c);
+    }
+    docxObj = Object.assign(docxObj, { content: ncArr });
+    let author = term;
+    if (_.isObject(classid)) {
+      const { name } = classid;
+      if (name) author = `${author}${name.includes('班') ? name : `${name}班`}`;
+    }
+    if (_.isObject(studentid)) {
+      const { name } = studentid;
+      if (name) author = `${author}${name.includes('班') ? name : `${name}`}`;
+    }
+    if (author !== '') {
+      docxObj = Object.assign(docxObj, { author });
+    }
+
+    return docxObj;
+  }
+
+}
+
+module.exports = ExperienceService;

+ 114 - 15
app/service/util.js

@@ -10,6 +10,8 @@ const { CrudService } = require('naf-framework-mongoose/lib/service');
 const { BusinessError, ErrorCode } = require('naf-core').Error;
 const moment = require('moment');
 const nodemailer = require('nodemailer');
+const { template } = require('lodash');
+const docx = require('docx');
 
 class UtilService extends CrudService {
   async updatedate() {
@@ -70,21 +72,56 @@ class UtilService extends CrudService {
   }
 
   async findmodel({ modelname }) {
-    const _model = _.capitalize(modelname);
-    const data = this.ctx.model[_model].prototype.schema.obj;
-    return data;
-  }
-  async utilMethod(data, body) {
-    console.log(data, body);
-    // const res = await this.ctx.model.Student.updateMany({ ...data, classid: { $exists: true } }, { classid: null });
-    const { code, ids, bedroomid } = body;
-    for (const id of ids) {
-      const r = await this.ctx.model.Student.findById(id);
-      r.bedroom = code;
-      r.bedroomid = bedroomid;
-      await r.save();
-      // await this.ctx.model.Student.findByIdAndUpdate(id, { bedroom: code, bedroomid });
-    }
+    // TODO找到这个班级
+    // const tclass = { name: '1', term: '', batch: '' };
+    // 首先用名字找能不能对上,名字能对上直接把班级信息复制过来
+    // 名字对不上就根据当前班级的期找下本期的所有班,然后找到和他同批次的班级,查下它是第几位,按照这个位置去把模板的同位置班级信息复制过来
+
+    // 获取所有年度的期计划
+
+    // 处理学生入学年份超过4位的情况
+    // const list = await this.ctx.model.Student.find({ $where: 'this.entry_year.length>4' });
+    // const m = /^\w{4}/;
+    // for (const stu of list) {
+    //   const { entry_year } = stu;
+    //   const r = entry_year.match(m);
+    //   if (r) {
+    //     stu.entry_year = r[0];
+    //     stu.save();
+    //   }
+    // }
+    // 处理学生毕业年份超过4位的情况
+    // const list = await this.ctx.model.Student.find({ $where: 'this.finish_year.length>4' });
+    // const m = /^\w{4}/;
+    // for (const stu of list) {
+    //   const { finish_year } = stu;
+    //   const r = finish_year.match(m);
+    //   if (r) {
+    //     stu.finish_year = r[0];
+    //     stu.save();
+    //   }
+    // }
+    // 处理学生专业字段(major)带'专业'二字
+    // const stuList = await this.ctx.model.Student.find({ major: /专业/ });
+    // const m = /(\S+)(专业$)/;
+    // for (const stu of stuList) {
+    //   const { name, major } = stu;
+    //   const r = major.match(m);
+    //   let nm;
+    //   if (r) nm = r[1];
+    //   stu.major = nm;
+    //   stu.save();
+    // }
+    // 处理教师分数错误的初始化
+    // // 所有教师的分数都复制到beforescore上,xsscore需要重新计算
+    // const list = await this.ctx.model.Teacher.find();
+    // console.log(list.length);
+    // for (const tea of list) {
+    //   if (tea.xsscore) {
+    //     tea.beforescore = tea.xsscore;
+    //     await tea.save();
+    //   }
+    // }
   }
 
   async toExcel(dataList, meta, fn = '导出结果') {
@@ -113,6 +150,68 @@ class UtilService extends CrudService {
     await workbook.xlsx.writeFile(filepath);
     return `/files/excel/${filename}`;
   }
+
+  /**
+   * 导出docx
+   * @param {Array} data 数据[{title,content([]),author}]
+   * @param {String} fn 文件名
+   */
+  async toDocx(data, fn = '培训心得') {
+    const { Document, Packer, Paragraph, TextRun, HeadingLevel, AlignmentType } = docx;
+    const doc = new Document();
+    const children = [];
+    // 整理数据
+    for (let i = 0; i < data.length; i++) {
+      const obj = data[i];
+      const { title, content, author } = obj;
+      const c = [];
+      if (title) {
+        const tit = new Paragraph({
+          children: [ new TextRun({ text: title, bold: true }) ],
+          heading: HeadingLevel.TITLE,
+          alignment: AlignmentType.CENTER,
+        });
+        c.push(tit);
+      }
+      if (author) {
+        const auth = new Paragraph({
+          children: [ new TextRun({ color: '#000000', text: author }) ],
+          heading: HeadingLevel.HEADING_2,
+          alignment: AlignmentType.RIGHT,
+        });
+        c.push(auth);
+      }
+      if (content && _.isArray(content) && content.length > 0) {
+        for (const cont of content) {
+          const p = new Paragraph({
+            children: [ new TextRun({ text: cont, bold: true }) ],
+          });
+          c.push(p);
+        }
+      }
+      if (i !== data.length - 1) {
+        // 换页
+        const last = new Paragraph({
+          pageBreakBefore: true,
+        });
+        c.push(last);
+      }
+      if (c.length > 0) children.push(...c);
+    }
+    doc.addSection({
+      properties: {},
+      children,
+    });
+
+    const { app } = this;
+    const rootPath = `${app.config.cdn.repos_root_path}`;
+    const rooturl = `${app.config.cdn.repos_root_url_experience}`;
+    const path = `${rootPath}${rooturl}`;
+    Packer.toBuffer(doc).then(buffer => {
+      fs.writeFileSync(`${path}${fn}.docx`, buffer);
+    });
+    return `/files${rooturl}${fn}.docx`;
+  }
 }
 
 module.exports = UtilService;

+ 1 - 0
config/config.prod.js

@@ -11,6 +11,7 @@ module.exports = () => {
   config.cdn = {
     repos_root_path: '/usr/local/workspace/service-file/upload',
     repos_root_url_excel: '/excel/',
+    repos_root_url_experience: '/experience/',
   };
 
   config.wxapi = {

+ 1 - 0
package.json

@@ -7,6 +7,7 @@
     "framework": "naf-framework-mongoose"
   },
   "dependencies": {
+    "docx": "^5.3.0",
     "egg": "^2.23.0",
     "egg-naf-amqp": "0.0.13",
     "egg-redis": "^2.4.0",