Pārlūkot izejas kodu

1. 标段选择中间件
2. 中间计量相关
3. 因中间计量需求,重构树结构
4. 更新树表表头绘制,兼容多行表头
5. 树表定义移动至后端常量

MaiXinRong 7 gadi atpakaļ
vecāks
revīzija
ba6d74f4e8

+ 6 - 0
app.js

@@ -9,9 +9,15 @@
  */
 const Uglyfy = require('uglify-es');
 const fs = require('fs');
+const moment = require('moment');
+const uuid = require('node-uuid');
+
 const BaseService = require('./app/base/base_service');
 const BaseController = require('./app/base/base_controller');
+
 module.exports = app => {
+    app.uuid = uuid;
+    app.moment = moment;
     // 数据模型基类
     app.BaseService = BaseService;
 

+ 1 - 0
app/base/base_service.js

@@ -22,6 +22,7 @@ class BaseService extends Service {
         super(ctx);
         this.db = this.app.mysql;
         this.cache = this.app.redis;
+        this.uuid = this.app.uuid;
         this.transaction = null;
         this.sqlBuilder = null;
     }

+ 46 - 0
app/const/code_rule.js

@@ -0,0 +1,46 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/6/22
+ * @version
+ */
+
+const ruleType = {
+    measure: 1,
+};
+const ruleField = [];
+ruleField[ruleType.measure] = 'm_rule';
+const ruleString = [];
+ruleString[ruleType.measure] = 'measure';
+
+// 中间计量编号规则
+const measure = {
+    ruleType: {
+        tenderName: 1,
+        text: 2,
+        inDate: 3,
+        addNo: 4,
+    },
+    ruleString: [],
+};
+measure.ruleString[measure.ruleType.tenderName] = '标段名称';
+measure.ruleString[measure.ruleType.text] = '文本';
+measure.ruleString[measure.ruleType.inDate] = '当前年月';
+measure.ruleString[measure.ruleType.addNo] = '自增编号';
+
+measure.defaultRule = [
+    {ruleType: measure.ruleType.tenderName},
+    {ruleType: measure.ruleType.text, text: 'WJQR1'},
+    {ruleType: measure.ruleType.inDate},
+    {ruleType: measure.ruleType.addNo, format: 3, start: 1},
+];
+
+module.exports = {
+    ruleType,
+    ruleField,
+    ruleString,
+    measure,
+};

+ 34 - 10
app/const/spread.js

@@ -10,16 +10,39 @@
 
 const ledgerSpread = {
     cols: [
-        {title: '项目节编号', field: 'code', width: 150, cellType: 'tree'},
-        {title: '清单编号', field: 'b_code', width: 80},
-        {title: '名称', field: 'name', width: 230},
-        {title: '单位', field: 'unit', width: 50},
-        {title: '单价', field: 'unit_price', width: 60, type: 'Number'},
-        {title: '数量', field: 'quantity', width: 60, type: 'Number'},
-        {title: '金额', field: 'total_price', width: 60, type: 'Number'},
-        {title: '施工图原设计', field: 'design', width: 60},
-        {title: '图(册)号', field: 'drawingCode', width: 80},
-        {title: '备注', field: 'memo', width: 100}
+        {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, cellType: 'tree'},
+        {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80},
+        {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230},
+        {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50},
+        {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number'},
+        {title: '数量', colSpan: '1', rowSpan: '2', field: 'quantity', hAlign: 2, width: 60, type: 'Number'},
+        {title: '金额', colSpan: '1', rowSpan: '2', field: 'total_price', hAlign: 2, width: 60, type: 'Number'},
+        {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawingCode', hAlign: 0, width: 80},
+        {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100}
+    ],
+    emptyRows: 3,
+    headRows: 2,
+    headRowHeight: [40, 40],
+    defaultRowHeight: 21,
+};
+
+const measureSpread = {
+    cols: [
+        {title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 150, readOnly: true, cellType: 'tree'},
+        {title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 80, readOnly: true},
+        {title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 230, readOnly: true},
+        {title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, readOnly: true},
+        {title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+        {title: '0号台账合同|数量', colSpan: '2|1', rowSpan: '1|1', field: 'quantity', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'total_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+        {title: '本次合同计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'deal_quantity', hAlign: 2, width: 60, type: 'Number'},
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'deal_totalprice', hAlign: 2, width: 60, type: 'Number'},
+        {title: '本次数量变更|数量', colSpan: '2|1', rowSpan: '1|1', field: 'qc_quantity', hAlign: 2, width: 60, type: 'Number'},
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'qc_totalprice', hAlign: 2, width: 60, type: 'Number'},
+        {title: '本次完成计量|数量', colSpan: '2|1', rowSpan: '1|1', field: 'gather_quantity', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+        {title: '|金额', colSpan: '|1', rowSpan: '|1', field: 'gather_totalprice', hAlign: 2, width: 60, readOnly: true, type: 'Number'},
+        {title: '图(册)号', colSpan: '1', rowSpan: '2', field: 'drawing_code', hAlign: 0, width: 80, readOnly: true},
+        {title: '备注', colSpan: '1', rowSpan: '2', field: 'memo', hAlign: 0, width: 100, readOnly: true},
     ],
     emptyRows: 3,
     headRows: 2,
@@ -29,4 +52,5 @@ const ledgerSpread = {
 
 module.exports = {
     ledgerSpread,
+    measureSpread,
 }

+ 38 - 59
app/controller/ledger_controller.js

@@ -33,63 +33,40 @@ module.exports = app => {
         }
 
         /**
-         * 台账分解页面
+         * 台账分解页面 (Get)
          *
          * @param {Object} ctx - egg全局变量
          * @return {void}
          */
         async explode(ctx) {
-            if (ctx.request.query.tenderId) {
-                const tenderId = parseInt(ctx.request.query.tenderId);
-                const tender = await ctx.service.tender.getDataById(tenderId);
-                ctx.session.sessionUser.tenderId = tender.id;
-                ctx.session.sessionUser.tenderName = tender.name;
+            const tenderId = ctx.session.sessionUser.tenderId;
+            const tenderList = ctx.tenderList;
+            const tenderData = ctx.tenderData;
 
-                ctx.redirect('/ledger/explode');
-            } else {
-                let tenderId = ctx.session.sessionUser.tenderId;
-                const tenderList = await ctx.service.tender.getList();
-                for (const tender of tenderList) {
-                    tender.url = ctx.menuList.ledger.children.explode.url + '?tenderId=' + tender.id;
-                }
-
-                if (!tenderId) {
-                    // 根据标段列表获取默认tenderId
-                    if (tenderList && tenderList.length > 0) {
-                        tenderId = tenderList[0].id;
-                        ctx.session.sessionUser.tenderId = tenderId;
-                        ctx.session.sessionUser.tenderName = tenderList[0].name;
-                    } else {
-                        throw '项目无标段数据';
-                    }
-                }
-
-                const tenderData = await ctx.service.tender.getDataById(tenderId);
-                if (!tenderData.ledger_status) {
-                    tenderData.ledger_status = auditConst.status.uncheck;
-                }
-                if (!tenderData.ledger_times) {
-                    tenderData.ledger_times = 1;
-                }
-                const curAuditor = await ctx.service.ledgerAudit.getCurAuditor(tenderId, tenderData.ledger_times);
-                const auditors = await ctx.service.ledgerAudit.getAuditors(tenderId, tenderData.ledger_times);
-                const times = tenderData.ledger_status === auditConst.status.checkNo ? tenderData.ledger_times - 1 : tenderData.ledger_times;
-                const content = await ctx.service.ledgerAuditContent.getAllDataByCondition({
-                    where: {tender_id: tenderId, times: times, audit_id: auditors[0].audit_id}
-                });
-                const ledgerData = await ctx.service.ledger.getDataByTenderId(tenderId);
-                const renderData = {
-                    tenderData,
-                    auditConst,
-                    auditors,
-                    curAuditor,
-                    content,
-                    ledger: JSON.stringify(ledgerData),
-                    ledgerSpreadSetting: JSON.stringify(spreadConst.ledgerSpread),
-                    tenderList,
-                };
-                await this.layout('ledger/explode.ejs', renderData, 'ledger/explode_modal.ejs');
+            if (!tenderData.ledger_status) {
+                tenderData.ledger_status = auditConst.status.uncheck;
+            }
+            if (!tenderData.ledger_times) {
+                tenderData.ledger_times = 1;
             }
+            const curAuditor = await ctx.service.ledgerAudit.getCurAuditor(tenderId, tenderData.ledger_times);
+            const auditors = await ctx.service.ledgerAudit.getAuditors(tenderId, tenderData.ledger_times);
+            const times = tenderData.ledger_status === auditConst.status.checkNo ? tenderData.ledger_times - 1 : tenderData.ledger_times;
+            const content = await ctx.service.ledgerAuditContent.getAllDataByCondition({
+                where: {tender_id: tenderId, times: times, audit_id: auditors[0].audit_id}
+            });
+            const ledgerData = await ctx.service.ledger.getDataByTenderId(tenderId);
+            const renderData = {
+                tenderData,
+                auditConst,
+                auditors,
+                curAuditor,
+                content,
+                ledger: JSON.stringify(ledgerData),
+                ledgerSpreadSetting: JSON.stringify(spreadConst.ledgerSpread),
+                tenderList,
+            };
+            await this.layout('ledger/explode.ejs', renderData, 'ledger/explode_modal.ejs');
         }
 
         /**
@@ -103,7 +80,7 @@ module.exports = app => {
         }
 
         /**
-         * 获取子节点
+         * 获取子节点 (Ajax)
          * @param ctx
          * @return {Promise<void>}
          */
@@ -134,7 +111,7 @@ module.exports = app => {
         }
 
         /**
-         * 树结构基本操作(增、删、上下移、升降级)
+         * 树结构基本操作(增、删、上下移、升降级)(Ajax)
          * @param {Object} ctx - egg全局变量
          * @return {Promise<void>}
          */
@@ -190,7 +167,7 @@ module.exports = app => {
         }
 
         /**
-         * 提交更新数据
+         * 提交更新数据 (Ajax)
          * @param ctx
          * @return {Promise<void>}
          */
@@ -249,7 +226,8 @@ module.exports = app => {
         }
 
         /**
-         * 复制粘贴整块
+         * 复制粘贴整块 (Ajax)
+         *
          * @param ctx
          * @return {Promise<void>}
          */
@@ -283,7 +261,7 @@ module.exports = app => {
         }
 
         /**
-         * 从标准项目表添加数据
+         * 从标准项目表添加数据 (Ajax)
          * @param ctx
          * @returns {Promise<void>}
          */
@@ -340,7 +318,7 @@ module.exports = app => {
         }
 
         /**
-         * 批量插入数据
+         * 批量插入数据 (Ajax)
          *
          * data = {id, batchData, batchType}
          * data.batchType = 'batchInsertChild'/'batchInsertNext'
@@ -389,18 +367,19 @@ module.exports = app => {
         }
 
         /**
-         * 台账变更页面
+         * 台账变更页面 (Get)
          *
          * @param {object} ctx - egg全局变量
          * @return {void}
          */
         async change(ctx) {
+
             const renderData = {};
-            await this.layout('ledger/change.ejs', renderData);
+            await this.layout('ledger/change.ejs', renderData, 'ledger/change_modal.ejs');
         }
 
         /**
-         * 计量台账页面
+         * 计量台账页面 (Get)
          *
          * @param {object} ctx - egg全局变量
          * @return {void}

+ 343 - 4
app/controller/measure_controller.js

@@ -8,6 +8,10 @@
  * @version
  */
 
+const spreadConst = require('../const/spread');
+const codeRuleConst = require('../const/code_rule');
+const moment = require('moment');
+
 module.exports = app => {
 
     class MeasureController extends app.BaseController {
@@ -26,18 +30,353 @@ module.exports = app => {
         }
 
         /**
-         * 中间计量--计量编制 页面
+         * 中间计量--计量编制 页面 (Get)
          *
          * @param {Object} ctx - egg全局变量
          * @return {void}
          */
         async work(ctx) {
-            const renderData = {};
-            await this.layout('measure/work.ejs', renderData, 'measure/work_modal.ejs');
+            try {
+                const tenderList = ctx.tenderList;
+                const tenderData = ctx.tenderData;
+
+                const codeRule = tenderData.m_rule ? JSON.parse(tenderData.m_rule) : [];
+                for (const rule of codeRule) {
+                    switch (rule.rule_type) {
+                        case codeRuleConst.measure.ruleType.tenderName:
+                            rule.preview = tenderData.name;
+                            break;
+                        case codeRuleConst.measure.ruleType.inDate:
+                            rule.preview = moment().format('YYYYMM');
+                            break;
+                        case codeRuleConst.measure.ruleType.text:
+                            rule.preview = rule.text;
+                            break;
+                        case codeRuleConst.measure.ruleType.addNo:
+                            const s = '0000000000';
+                            rule.preview = s.substr(s.length - rule.format);
+                            break;
+                    }
+                }
+
+                const measures = await ctx.service.measure.getAllDataByCondition({
+                    where: {tender_id: tenderData.id}
+                });
+                const renderData = {
+                    tenderData,
+                    tenderList,
+                    ruleType: codeRuleConst.ruleType.measure,
+                    ruleConst: codeRuleConst.measure,
+                    codeRule,
+                    measures,
+                    measureSpreadSetting: JSON.stringify(spreadConst.measureSpread),
+                };
+                await this.layout('measure/work.ejs', renderData, 'measure/work_modal.ejs');
+            } catch (err) {
+                console.log(err);
+                this.redirect(ctx.menuList.dashboard.url);
+            }
+        }
+
+        /**
+         * 中间计量 -- 获取新编号 (Ajax)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @returns {Promise<void>}
+         */
+        async newCode(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: '',
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+                const mCodeRule = JSON.parse(tenderData.m_rule);
+
+                const code = [];
+                for (const rule of mCodeRule) {
+                    switch (rule.rule_type) {
+                        case codeRuleConst.measure.ruleType.tenderName:
+                            code.push(tenderData.name);
+                            break;
+                        case codeRuleConst.measure.ruleType.text:
+                            code.push(rule.text);
+                            break;
+                        case codeRuleConst.measure.ruleType.inDate:
+                            code.push(moment().format('YYYYMM'));
+                            break;
+                        case codeRuleConst.measure.ruleType.addNo:
+                            let s = '0000000000';
+                            const count = rule.start + await ctx.service.measure.count();
+                            s = s + count;
+                            code.push(s.substr(s.length - rule.format));
+                            break;
+                    }
+                }
+                responseData.data = code.join('');
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err;
+            }
+
+            ctx.body = responseData;
+        }
+
+        /**
+         * 中间计量 -- 新增中间计量 (Ajax)
+         *
+         * @param {Object} ctx - egg全局变量
+         * @returns {Promise<void>}
+         */
+        async addMeasure(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: '',
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+
+                const data = JSON.parse(ctx.request.body.data);
+                await ctx.service.measure.add(tenderId, data.code, data.date, data.stage);
+
+                responseData.data = await ctx.service.measure.getDataByCondition({
+                    tender_id: tenderId, code: data.code,
+                });
+            } catch (err) {
+                console.log(err);
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+
+            ctx.body = responseData;
+        }
+
+        /**
+         * 获取中间计量详细数据(包括部位和清单) (Ajax)
+         * @param ctx - egg全局变量
+         * @returns {Promise<void>}
+         */
+        async measureDetail(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: '',
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.mid) {
+                    throw '查询数据有误';
+                }
+                const measure = await ctx.service.measure.getDataByCondition({
+                    tender_id: tenderId,
+                    mid: data.mid,
+                });
+                measure.pos = await ctx.service.measurePos.getPosDetail(tenderId, data.mid);
+                measure.bills = await ctx.service.measureBills.getBillsDetail(tenderId, data.mid);
+                measure.billsTree = await ctx.service.measureBills.getBillsDetailWithParent(tenderId, data.mid);
+
+                responseData.data = measure;
+            } catch (err) {
+                console.log(err);
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+
+            ctx.body = responseData;
+        }
+
+        /**
+         * 查询部位或者清单 (Ajax)
+         *
+         * @param ctx - egg全局变量
+         * @returns {Promise<void>}
+         */
+        async search(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: '',
+            };
+
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const tenderData = await ctx.service.tender.getDataById(tenderId);
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.mid || !data.searchType) {
+                    throw '查询数据有误';
+                }
+
+                if (data.searchType === 'pos') {
+                    const key = {
+                        value: app.mysql.escape('%' + data.keyword + '%'),
+                        operate: 'Like',
+                        fields: ['code', 'name'],
+                    };
+                    responseData.data = await ctx.service.ledger.search(tenderId, key);
+                } else if (data.searchType === 'bills') {
+                    const key = {
+                        value: app.mysql.escape('%' + data.keyword + '%'),
+                        operate: 'Like',
+                        fields: ['b_code', 'name'],
+                    };
+
+                    const range = [];
+                    const pos = await ctx.service.measurePos.getPosDetail(tenderId, data.mid);
+                    const posFullPath = [];
+                    for (const p of pos) {
+                        posFullPath.push(app.mysql.escape(p.full_path + '.%'));
+                    }
+
+                    responseData.data = await ctx.service.ledger.searchRange(tenderId, key, [{
+                        value: posFullPath,
+                        operate: 'Like',
+                        fields: ['full_path'],
+                    }, {
+                        value: true,
+                        operate: '=',
+                        fields: ['is_leaf'],
+                    }]);
+                } else {
+                    throw '查询数据有误';
+                }
+            } catch (err) {
+                console.log(err);
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+
+            ctx.body = responseData;
+        }
+
+        /**
+         * 操作部位 -- 增删 (Ajax)
+         *
+         * @param ctx - egg全局变量
+         * @returns {Promise<void>}
+         */
+        async pos(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: {},
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.mid || !data.operate || !data.pid) {
+                    throw '计量范围数据有误';
+                }
+
+                if (data.operate === 'add') {
+                    responseData.data = await ctx.service.measurePos.addPos(tenderId, data.mid, data.pid);
+                } else if (data.operate === 'remove') {
+                    await ctx.service.measurePos.removePos(tenderId, data.mid, data.pid);
+                } else {
+                    throw '未知操作';
+                }
+            } catch(err) {
+                console.log(err);
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+            ctx.body = responseData;
+        }
+
+        /**
+         * 操作清单 -- 增删 (Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async bills(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: {},
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.mid || !data.operate || !data.bid) {
+                    throw '计量清单数据有误';
+                }
+
+                if (data.operate === 'add') {
+                    await ctx.service.measureBills.addBills(tenderId, data.mid, data.bid);
+                    responseData.data = await ctx.service.measureBills.getBillsDataWithParent(tenderId, data.mid, data.bid);
+                } else if (data.operate === 'remove') {
+                    responseData.data = await ctx.service.measureBills.getBillsDataWithParent(tenderId, data.mid, data.bid);
+                    await ctx.service.measureBills.removeBills(tenderId, data.mid, data.bid);
+                } else {
+                    throw '未知操作';
+                }
+            } catch(err) {
+                console.log(err);
+                responseData.err = 1;
+                responseData.msg = err.toString();
+                responseData.data = {};
+            }
+            ctx.body = responseData;
+        }
+
+        /**
+         * 清单 -- 计量 (Ajax)
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async billsUpdate (ctx) {
+            const responseData = { err: 0, msg: '', data: {} };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.mid || !data.bid || !data.update) {
+                    throw '计量清单数据有误';
+                }
+
+                responseData.data = await ctx.service.measureBills.updateBills(tenderId, data.mid, data.bid, data.update);
+            } catch(err) {
+                console.log(err);
+                responseData.err = 1;
+                responseData.msg = err.toString();
+                responseData.data = {};
+            }
+
+            ctx.body = responseData;
         }
 
         /**
-         * 中间计量--计量审批 页面
+         * 中间计量--计量审批 页面 (Get)
          *
          * @param {Object} ctx - egg全局变量
          * @return {void}

+ 35 - 0
app/controller/tender_controller.js

@@ -9,6 +9,7 @@
  */
 
 const tenderConst = require('../const/tender');
+const codeRuleConst = require('../const/code_rule');
 module.exports = app => {
 
     class TenderController extends app.BaseController {
@@ -153,6 +154,40 @@ module.exports = app => {
 
             ctx.redirect(ctx.request.headers.referer);
         }
+
+        async rule(ctx) {
+            const responseData = {
+                err: 0,
+                msg: '',
+                data: {},
+            };
+            try {
+                const tenderId = ctx.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    throw '当前未打开标段';
+                }
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.rule || !codeRuleConst.ruleField[data.rule]) {
+                    throw '参数错误';
+                }
+
+                const updateData = {
+                    id: tenderId,
+                };
+                updateData[codeRuleConst.ruleField[data.rule]] = data.data;
+                console.log(updateData);
+
+                const result = await ctx.service.tender.db.update(ctx.service.tender.tableName, updateData);
+                if (result.affectedRows !== 1) {
+                    throw '更新规则失败';
+                }
+            } catch (err) {
+                responseData.err = 1;
+                responseData.msg = err.toString();
+            }
+
+            ctx.body = responseData;
+        }
     }
 
     return TenderController;

+ 9 - 1
app/middleware/session_auth.js

@@ -30,7 +30,15 @@ module.exports = options => {
             // 同步消息
             yield this.service.notify.syncNotifyData();
         } catch (error) {
-            return this.redirect('/');
+            if (this.helper.isAjax(this.request)) {
+                this.body.response = {
+                    err: 2,
+                    msg: '登录信息异常,请重新登录',
+                    data: '',
+                };
+            } else {
+                return this.redirect('/');
+            }
         }
         yield next;
     };

+ 52 - 0
app/middleware/tender_select.js

@@ -0,0 +1,52 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/7/9
+ * @version
+ */
+
+module.exports = options => {
+    /**
+     * 标段选择中间件
+     *
+     * @param {function} next - 中间件继续执行的方法
+     * @return {void}
+     */
+    return function* tenderSelect(next) {
+        try {
+            if (this.request.query.tenderId) {
+                const tenderId = parseInt(this.request.query.tenderId);
+                const tender = yield this.service.tender.getDataById(tenderId);
+                this.session.sessionUser.tenderId = tender.id;
+                this.session.sessionUser.tenderName = tender.name;
+
+                this.redirect(this.urlInfo.pathname);
+            } else {
+                this.tenderList = yield this.service.tender.getList();
+                for (const tender of this.tenderList) {
+                    tender.url = this.request.url + '?tenderId=' + tender.id;
+                }
+
+                let tenderId = this.session.sessionUser.tenderId;
+                if (!tenderId) {
+                    // 根据标段列表获取默认tenderId
+                    if (this.tenderList && this.tenderList.length > 0) {
+                        tenderId = this.tenderList[0].id;
+                        this.session.sessionUser.tenderId = tenderId;
+                        this.session.sessionUser.tenderName = this.tenderList[0].name;
+                    } else {
+                        throw '项目无标段数据';
+                    }
+                }
+                this.tenderData = yield this.service.tender.getDataById(tenderId);
+
+                yield next;
+            }
+        } catch (err) {
+            this.redirect(this.menuList.dashboard.url);
+        }
+    };
+};

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 1 - 0
app/public/css/datepicker/datepicker.min.css


+ 851 - 0
app/public/css/spreadjs/sheets/gc.spread.sheets.excelsmartcost.css

@@ -0,0 +1,851 @@
+/*-----common css start-----*/
+.gc-theme-version {
+    position: absolute;
+    z-index: 2013;
+}
+.gc-grayArea {
+    background-color: white;
+}
+.gc-corner-hover {
+    background-color: white;
+}
+.gc-corner-selected {
+    background-color: white;
+}
+.gc-corner-normal {
+    background-color: white;
+}
+.gc-corner-triangle-normal {
+    background-color: #dfdfdf;
+    background-image: -webkit-linear-gradient(top, #dfdfdf, #dfdfdf); /* For Chrome and Safari */
+    background-image:    -moz-linear-gradient(top, #dfdfdf, #dfdfdf); /* For old Fx (3.6 to 15) */
+    background-image:     -ms-linear-gradient(top, #dfdfdf, #dfdfdf); /* For pre-releases of IE 10*/
+    background-image:      -o-linear-gradient(top, #dfdfdf, #dfdfdf); /* For old Opera (11.1 to 12.0) */
+    background-image:         linear-gradient(to bottom, #dfdfdf, #dfdfdf); /* Standard syntax; must be last */
+    border-style:solid;
+    border-left-color: #efefef !important;
+    border-right-color: #d5ded5 !important;
+    border-top-color: #efefef !important;
+    border-bottom-color: #d5ded5 !important;
+}
+.gc-corner-triangle-hover {
+    background-color: #9e9e9e;
+    background-image: -webkit-linear-gradient(top, #9e9e9e, #9e9e9e); /* For Chrome and Safari */
+    background-image:    -moz-linear-gradient(top, #9e9e9e, #9e9e9e); /* For old Fx (3.6 to 15) */
+    background-image:     -ms-linear-gradient(top, #9e9e9e, #9e9e9e); /* For pre-releases of IE 10*/
+    background-image:      -o-linear-gradient(top, #9e9e9e, #9e9e9e); /* For old Opera (11.1 to 12.0) */
+    background-image:         linear-gradient(to bottom, #9e9e9e, #9e9e9e); /* Standard syntax; must be last */
+    border-style:solid;
+    border-left-color: #efefef !important;
+    border-right-color: #d5ded5 !important;
+    border-top-color: #efefef !important;
+    border-bottom-color: #d5ded5 !important;
+}
+.gc-corner-triangle-selected {
+    background-color: #73a2e3;
+    background-image: -webkit-linear-gradient(top, #73a2e3, #73a2e3); /* For Chrome and Safari */
+    background-image:    -moz-linear-gradient(top, #73a2e3, #73a2e3); /* For old Fx (3.6 to 15) */
+    background-image:     -ms-linear-gradient(top, #73a2e3, #73a2e3); /* For pre-releases of IE 10*/
+    background-image:      -o-linear-gradient(top, #73a2e3, #73a2e3); /* For old Opera (11.1 to 12.0) */
+    background-image:         linear-gradient(to bottom, #73a2e3, #73a2e3); /* Standard syntax; must be last */
+    border-style:solid;
+    border-left-color: #efefef !important;
+    border-right-color: #d5ded5 !important;
+    border-top-color: #efefef !important;
+    border-bottom-color: #d5ded5 !important;
+}
+.gc-columnHeader-normal {
+    color: #444444;
+    background-image: none;
+    background-color: white;
+    border-style:solid;
+    border-left-color: #dee2e6 !important;
+    border-right-color: #dee2e6 !important;
+    border-bottom-color: #dee2e6 !important;
+}
+.gc-columnHeader-hover {
+    color: #444444;
+    background-image: none;
+    background-color: #f5f5f5;
+    border-style:solid;
+    border-left-color: #efefef !important;
+    border-right-color: #d5ded5 !important;
+    border-bottom-color: #73a2e3 !important;
+}
+.gc-columnHeader-selected {
+    color: #73a2e3;
+    background-image: none;
+    background-color: #f5f5f5;
+    border-style:solid;
+    border-left-color: #efefef !important;
+    border-right-color: #d5ded5 !important;
+    border-bottom-color: #ababab !important;
+}
+.gc-columnHeader-highlight {
+    color: #73a2e3;
+    background-image: none;
+    background-color: #f5f5f5;
+    border-style:solid;
+    border-left-color: #efefef !important;
+    border-right-color: #d5ded5 !important;
+    border-bottom-color: #ababab !important;
+}
+.gc-rowHeader-normal {
+    color: #444444;
+    background-color: white;
+    background-image: none;
+    border-style:solid;
+    border-top-color: #efefef !important;
+    border-bottom-color: #d5ded5 !important;
+    border-right-color: #dee2e6 !important;
+}
+.gc-rowHeader-hover {
+    color: #73a2e3;
+    background-color: #f5f5f5;
+    background-image: none;
+    border-style:solid;
+    border-top-color: #efefef !important;
+    border-bottom-color: #d5ded5 !important;
+    border-right-color: #73a2e3 !important;
+}
+.gc-rowHeader-selected {
+    color: #73a2e3;
+    background-color: #f5f5f5;
+    background-image: none;
+    border-style:solid;
+    border-top-color: #dee2e6 !important;
+    border-bottom-color: #dee2e6 !important;
+    border-right-color: #dee2e6 !important;
+}
+.gc-rowHeader-highlight {
+    color: #73a2e3;
+    background-color: #f5f5f5;
+    background-image: none;
+    border-style:solid;
+    border-top-color: #efefef !important;
+    border-bottom-color: #d5ded5 !important;
+    border-right-color: #ababab !important;
+}
+.gc-horizontal-scrollbar {
+    background-color: #efefef;
+    border-top-color: #efefef;
+}
+.gc-vertical-scrollbar {
+    background-color: #efefef;
+    border-left-color: #efefef;
+}
+.gc-footer-corner {
+    background-color: #fff;
+}
+.gc-selection {
+    background-color: rgba(204, 204, 204, 0.3);
+    border-color:#73a2e3
+}
+.gc-drag-indicator {
+    border-color: #217346;
+}
+.gc-gridlineColor {
+    border-color: #dee2e6;
+}
+.gc-group {
+    background-color: white;
+    color: #ababab;
+}
+.gc-group-box {
+    background-color: white;
+    color: #666666;
+    border-color: #828790;
+}
+
+.gc-tabStripNewTab-highlight {
+    border-color: #777777;
+}
+.gc-tabStripNewTab-hover {
+    border-color: #439467;
+}
+.gc-tabStripBackground {
+    background-image: -webkit-linear-gradient(top, #ffffff, #ffffff); /* For Chrome and Safari */
+    background-image:    -moz-linear-gradient(top, #ffffff, #ffffff); /* For old Fx (3.6 to 15) */
+    background-image:     -ms-linear-gradient(top, #ffffff, #ffffff); /* For pre-releases of IE 10*/
+    background-image:      -o-linear-gradient(top, #ffffff, #ffffff); /* For old Opera (11.1 to 12.0) */
+    background-image:         linear-gradient(to bottom, #ffffff, #ffffff); /* Standard syntax; must be last */
+    background-color: #ffffff;
+    border-color: #ababab;
+}
+.gc-tabStripResizeBarInner {
+    color: #b3b3b3;
+}
+.gc-navMoreButton-highlight {
+    border-color: #0a6332;
+}
+.gc-navMoreButton-hover {
+    border-color: #439467;
+}
+.gc-navButton-hover {
+    border-color: #439467;
+}
+.gc-navButton-highlight {
+    border-color: #0a6332;
+}
+.gc-navButton-normal {
+    border-color: #c6c6c6;
+}
+.gc-tab-normal {
+    color: #444444;
+    background-image: none;
+    background-color: transparent;
+    border-style: solid;
+    border-left-color: #ababab;
+    border-bottom-color: #217346;
+}
+.gc-tab-hover {
+    color: #252627;
+    background-image: none;
+    background-color: transparent;
+    border-style: solid;
+    border-left-color: #ababab;
+    border-bottom-color: #217346;
+}
+.gc-tab-active {
+    color: #217346;
+    background-image: none;
+    background-color: white;
+    border-style: solid;
+    border-left-color: #ababab;
+    border-bottom-color: #217346;
+}
+
+.gc-rowHeaderFill {
+    background-color: #e4ecf7;
+}
+.gc-colHeaderFill {
+    background-image: -webkit-linear-gradient(top, #F6FAFB 12.5%, #D2DBEB); /* For Chrome and Safari */
+    background-image:    -moz-linear-gradient(top, #F6FAFB 12.5%, #D2DBEB); /* For old Fx (3.6 to 15) */
+    background-image:     -ms-linear-gradient(top, #F6FAFB 12.5%, #D2DBEB); /* For pre-releases of IE 10*/
+    background-image:      -o-linear-gradient(top, #F6FAFB 12.5%, #D2DBEB); /* For old Opera (11.1 to 12.0) */
+    background-image:         linear-gradient(to bottom, #F6FAFB 12.5%, #D2DBEB); /* Standard syntax; must be last */
+    background-color: #D2DBEB;
+}
+
+.gc-gradientButton {
+    background-color: #DDDDDD; /* fallback color if gradients are not supported */
+    background-image: -webkit-linear-gradient(top, #F6FAFB, #D2DBEB); /* For Chrome and Safari */
+    background-image:    -moz-linear-gradient(top, #F6FAFB, #D2DBEB); /* For old Fx (3.6 to 15) */
+    background-image:     -ms-linear-gradient(top, #F6FAFB, #D2DBEB); /* For pre-releases of IE 10*/
+    background-image:      -o-linear-gradient(top, #F6FAFB, #D2DBEB); /* For old Opera (11.1 to 12.0) */
+    background-image:         linear-gradient(to bottom, #F6FAFB, #D2DBEB); /* Standard syntax; must be last */
+}
+
+.gc-sheetTabEditor::-ms-clear {
+    display: none;
+}
+
+
+.gc-layout-table {
+    font-size:12px;
+    width:100%;
+    height:100%;
+    font-family:Lucida Grande, Lucida Sans, Arial, sans-serif;
+}
+
+.gc-layout-table-first-column {
+    width:21px;
+    border-right:1px solid #CCC;
+    text-align:right;
+    padding-top:7px;
+}
+
+.gc-layout-table-last-column {
+    width:18px;
+}
+
+.gc-filter-sort-desc-container {
+    border-bottom:1px solid #CCC;
+}
+
+.gc-filter-item-wrapper {
+}
+
+
+.gc-filter-dialog-style {
+    background:#fcfdfd;
+    font-family:Lucida Grande, Lucida Sans, Arial, sans-serif;
+    font-size:12px;
+    border:1px solid #a7abb0;
+}
+
+.gc-search-outer-div {
+    border: none;
+    margin:4px 0px 0px 4px;
+    background-color: white;
+    background-image: none;
+    color:#1e395b;
+    font-weight:normal;
+}
+
+div.gc-search-outer-div input::-ms-clear{
+   display:none;
+}
+
+#gc-filterSearch {
+    width:165px;
+    height:21px;
+    border:1px solid #ababab;
+    margin-left:7px;
+    margin-top:4px;
+    margin-bottom: 0px;
+    padding: 0;
+    font-size: 1em;
+    background-color:white;
+    color:black;
+    float:none;
+}
+#gc-filterSearch:hover,
+#gc-filterSearch:active{
+    background-color:white;
+}
+
+.gc-check-uncheck-all {
+    float:left;
+    width:16px;
+    height:16px;
+    display:inline-block;
+}
+.gc-filter-check-outerDiv .gc-check-image,
+.gc-fill-type-item .gc-check-image {
+    background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAGxJREFUOE+ljsENgDAMAzsaQ3QMxP4/wAhXwTKhEY9TlZOdtK1b/4WVFaysYGUFKxMWdY/hA5T3+x0+BjJYJmOJBoF+87UMYhAwzFBaBnFwYZ1j/kKFltIycHLqMrHyhEvSMrCygpUVrJyntwPdKU02VXQw7gAAAABJRU5ErkJggg==);
+}
+.gc-filter-check-outerDiv .gc-uncheck-image,
+.gc-fill-type-item .gc-uncheck-image {
+    background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAIJJREFUOE+lkssNgDAMQzsCw3UMxEocGKWDQSLVUj5GJeLwhPyI0x7a9qP/gsoKVFagskIUm3ALp3GKZvX63/q0QIcAlqAMXMcFIQ6z7DouTGLptawkMVmeDJi8BFsGQ0jzUcRyvEla4oLAhvVrveu4IOAdxJOwZPkOylBZgcrv9PYAV9tkcyJlS4sAAAAASUVORK5CYII=);
+}
+.gc-check-image,
+.gc-uncheck-image {
+    background-position:center;
+}
+
+.gc-filter-check-outerDiv {
+    height:18px;
+    margin-top:4px;
+}
+
+a.gc-filter-check-style {
+    color:#1e395b;
+    text-shadow:none;
+}
+
+a.gc-filter-check {
+    text-decoration: none;
+}
+
+a.gc-filter-check:hover {
+    text-decoration:underline;
+}
+#gc-sortASC:hover,
+#gc-sortASC:active {
+    border-color: #e3e3e3;
+    outline: none;
+    -webkit-box-shadow: none;
+            box-shadow: none;
+}
+.gc-filter-sort {
+    border:1px solid transparent;
+    font-weight:normal;
+    color:#222222;
+}
+
+.gc-filter-hover {
+    border-radius: 0px;
+    outline:none;
+}
+
+.gc-filter-item {
+    position: relative;
+    cursor: default;
+    font-weight:normal;
+    border-style: solid;
+    border-color: transparent;
+}
+
+.gc-filter-item-container {
+    border:1px solid #a7abb0;
+    border-radius:3px;
+    margin:4px 0px 4px 4px;
+    overflow:hidden;
+}
+
+.gc-filter-item-input {
+    float: left;
+    clear: left;
+}
+
+.gc-filter-item-text {
+    font-family: Lucida Grande,Lucida Sans,Arial,sans-serif;
+    font-size: 12px;
+    margin: 2px;
+    white-space:nowrap;
+    word-wrap:normal;
+    float: left;
+    clear: right;
+}
+
+.gc-filter-button {
+    width:90px;
+    height:27px;
+    margin:2px 1px 5px;
+}
+
+.gc-filter-button-disable {
+    opacity:.35;
+    background-image:none;
+}
+
+#gc-filterOK {
+    margin-left:13px;
+    margin-bottom:5px;
+    float:left;
+}
+#gc-filterCancel {
+    margin-bottom:5px;
+    float:left;
+}
+
+.gc-filter-button-default {
+    border:1px solid #acacac;
+    border-radius: 0;
+    background-image: -webkit-linear-gradient(top, #f0f0f0, #e5e5e5); /* For Chrome and Safari */
+    background-image:    -moz-linear-gradient(top, #f0f0f0, #e5e5e5); /* For old Fx (3.6 to 15) */
+    background-image:     -ms-linear-gradient(top, #f0f0f0, #e5e5e5); /* For pre-releases of IE 10*/
+    background-image:      -o-linear-gradient(top, #f0f0f0, #e5e5e5); /* For old Opera (11.1 to 12.0) */
+    background-image:         linear-gradient(to bottom, #f0f0f0, #e5e5e5); /* Standard syntax; must be last */
+    font-weight:normal;
+    color: black;
+}
+
+.gc-filter-button-hover {
+    border:1px solid #7eb4ea;
+    border-radius: 0;
+    background-color: #d3f0e0;
+    background-image: -webkit-linear-gradient(top, #ecf4fc, #dcecfc); /* For Chrome and Safari */
+    background-image:    -moz-linear-gradient(top, #ecf4fc, #dcecfc); /* For old Fx (3.6 to 15) */
+    background-image:     -ms-linear-gradient(top, #ecf4fc, #dcecfc); /* For pre-releases of IE 10*/
+    background-image:      -o-linear-gradient(top, #ecf4fc, #dcecfc); /* For old Opera (11.1 to 12.0) */
+    background-image:         linear-gradient(to bottom, #ecf4fc, #dcecfc); /* Standard syntax; must be last */
+    color: black;
+    font-weight:normal;
+    text-shadow:none;
+    cursor:pointer;
+}
+
+.gc-filter-button-active {
+    border:1px solid #569de5;
+    border-radius: 0;
+    background-color: #ffe475;
+    background-image: -webkit-linear-gradient(top, #daecfc, #c4e0fc); /* For Chrome and Safari */
+    background-image:    -moz-linear-gradient(top, #daecfc, #c4e0fc); /* For old Fx (3.6 to 15) */
+    background-image:     -ms-linear-gradient(top, #daecfc, #c4e0fc); /* For pre-releases of IE 10*/
+    background-image:      -o-linear-gradient(top, #daecfc, #c4e0fc); /* For old Opera (11.1 to 12.0) */
+    background-image:         linear-gradient(to bottom, #daecfc, #c4e0fc); /* Standard syntax; must be last */
+    font-weight:normal;
+    color: black;
+    -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.gc-filter-item-hover {
+    border:1px solid transparent;
+    background-color: #d3f0e0;
+    background-image: none;
+    color:#1d5987;
+    font-weight:normal;
+    text-shadow:none;
+}
+
+.gc-smartMenu-item-default {
+    border:1px solid transparent;
+    background-color: white;
+    background-image: none;
+    font-weight:normal;
+    color:#1e395b;
+    border-radius:0;
+}
+
+.gc-smartMenu-item-hover {
+    border:1px solid #86bfa0;
+    background-color: #d3f0e0;
+    background-image: none;
+    color:#1d5987;
+    font-weight:normal;
+    text-shadow:none;
+}
+
+.gc-smart-tag-default {
+    border:1px solid #ababab;
+    background: white;
+    color:#1e395b;
+    font-weight:normal;
+    border-radius:0;
+}
+
+.gc-smart-tag-hover {
+    border:1px solid #9fd5b7;
+    background-color: white;
+    background-image: none;
+    color:#1d5987;
+    font-weight:normal;
+    text-shadow:none;
+}
+
+.gc-smart-tag-active {
+    border:1px solid #9fd5b7;
+    background-color:#9fd5b7;
+    background-image: none;
+    font-weight:normal;
+    color:#262626;
+    -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+
+.gc-menu-item-input {
+    width:16px;
+    height:16px;
+    margin:1px;
+    float:left;
+    display:inline-block;
+}
+.gc-menu-item-text {
+    font-size:12px;
+    font-weight:normal;
+    display:inline-block;
+    float:left;
+    padding-top:2px;
+    font-family:Arial;
+}
+.gc-fill-menu-container {
+    box-shadow:rgba(0,0,0,0.4) 1px 2px 5px;
+    cursor:default;
+}
+
+.gc-toolstrip-default {
+    background: white;
+    border:1px solid #c6c6c6;
+}
+.gc-toolstrip-button-style:active,
+.gc-toolstrip-button-style {
+    color: black;
+    background:white;
+    -webkit-box-shadow: none;
+          box-shadow: none;
+}
+
+.gc-tab-tip-span{
+     background:#D6E6F9;
+     color:black;
+     border:1px solid #D6E6F9;
+     font-weight:normal;
+}
+
+.gc-spread-toolTip {
+    border: 1px solid #bebebe;
+    border-radius: 0px;
+    background-color: white;
+    background-image: none;
+    font-weight: normal;
+    color: #217346;
+}
+
+.gc-no-user-select {
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -o-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+}
+/*-----common css end-----*/
+
+/*-----formula textbox start-----*/
+/* function autocomplete */
+.gcsj-func-ac-popup {
+	margin: 0;
+	padding: 0;
+    background: #fff;
+	border: 1px solid rgba(0,0,0,0.2);
+	font-family: arial,sans-serif;
+	font-size: 12px;
+	line-height: 22px;
+	position: absolute;
+	width: 300px;
+	z-index: 2001;
+
+	-webkit-box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+	-moz-box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+	box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+}
+
+.gcsj-func-ac-row {
+    margin: 0;
+    cursor: default;
+    padding: 2px 10px;
+    color: #666666;
+}
+
+.gcsj-func-ac-row-name {
+     color: #222;
+     font-size: 13px;
+     font-family: inconsolata,monospace,arial,sans,sans-serif;
+     margin: -2px 0;
+}
+
+.gcsj-func-ac-row-description {
+    color: #666;
+    display: none;
+    font-size: 11px;
+    margin: -2px 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.gcsj-ac-row-active {
+	background-color: #f5f5f5;
+	color: #000;
+	border-top: 1px solid #ebebeb;
+	border-bottom: 1px solid #ebebeb;
+	padding: 1px 10px
+}
+
+.gcsj-ac-row-active .gcsj-func-ac-row-description {
+    display:block;
+}
+
+/*  function help */
+.gcsj-func-help-popup {
+    background-color:#fff;
+    border: 1px solid rgba(0,0,0,0.2);
+    color: #222;
+    font-size: 11px;
+    word-wrap: break-word;
+    position: absolute;
+    width: 320px;
+    z-index: 2001;
+
+    -webkit-box-shadow:0 2px 4px rgba(0,0,0,0.2);
+	-moz-box-shadow:0 2px 4px rgba(0,0,0,0.2);
+	box-shadow:0 2px 4px rgba(0,0,0,0.2);
+}
+
+.gcsj-func-help-title {
+    background-color: #f5f5f5;
+    color: #222;
+    font-size: 13px;
+    padding: 1px 0 1px 10px;
+}
+
+.gcsj-func-help-body {
+    border-top: 1px solid #ebebeb;
+    font-family: arial, sans-serif;
+    overflow: hidden;
+}
+
+.gcsj-func-help-content {
+    padding-bottom: 2px;
+}
+
+.gcsj-func-help-section {
+    padding: 5px 10px;
+}
+
+.gcsj-func-help-section-title {
+    font-size: 11px;
+    color: #666;
+}
+
+.gcsj-func-help-section-content {
+    font-size: 11px;
+}
+
+.gcsj-func-help-formula {
+    font-family: inconsolata,monospace,arial,sans,sans-serif;
+    padding: 1px 0;
+}
+
+.gcsj-func-help-formula-name {
+}
+
+.gcsj-func-help-paramter {
+    padding-left:1px;
+}
+
+.gcsj-func-help-paramter-paren {
+}
+
+.gcsj-func-help-paramter-active {
+    background-color: #feb;
+}
+
+/* color text */
+.gcsj-func-color-content {
+    white-space: pre-wrap;
+}
+/*-----formula textbox end-----*/
+
+/*-----floatingobject start-----*/
+.gc-floatingobject-selected{
+    border:1px solid #939393;
+}
+
+.gc-floatingobject-unselected{
+    background-color: transparent;
+    border:1px solid transparent;
+}
+
+.gc-floatingobject-container{
+    position: absolute;
+    overflow: hidden;
+    box-sizing:content-box;
+}
+
+.gc-floatingobject-background-cover{
+    -webkit-background-size: cover; /* For WebKit*/
+    -moz-background-size: cover;    /* Mozilla*/
+    -o-background-size: cover;      /* Opera*/
+    background-size: cover;         /* Generic*/
+}
+
+.gc-floatingobject-moving-container{
+    position: absolute;
+    overflow: hidden;
+}
+
+.gc-floatingobject-moving-div{
+    position: absolute;
+    border:1px solid black;
+}
+.gc-floatingobject-resize-indicator {
+    box-sizing:content-box;
+}
+
+.gc-floatingobject-resize-indicator-select{
+    background-color:white;
+    border-radius:2px;
+    -moz-border-radius:1px;
+    border:1px solid #939393;
+    z-index:100;
+}
+
+.gc-floatingobject-resize-indicator-unSelect{
+    display: none;
+}
+
+.gc-floatingobject-absolute{
+    position: absolute;
+}
+
+.gc-floatingobject-content-container {
+    box-sizing:content-box;
+}
+/*-----floatingobject end-----*/
+
+/*-----scrollbar start-----*/
+/*scrollbar*/
+.gc-scroll-container{
+    background-color: #efefef;
+    -moz-box-shadow: none;
+    -webkit-box-shadow: none;
+    box-shadow: none;
+}
+.gc-scroll-corner-all {
+    border-radius: 2px;
+}
+.gc-scroll-arrow{
+    background-color: #efefef;
+    border-style:solid;
+    border-color: #efefef;
+    background-image: none;
+    border-radius: 0;
+}
+
+.gc-scroll-arrow .gc-scroll-arrowUp{
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAClJREFUKFNjGEmgvLz8P5RJHABpgGGoEH6ArIEojdg0wDBUyShAAAYGAHSXJkH1wN/VAAAAAElFTkSuQmCC);
+
+}
+.gc-scroll-arrowUp {
+    background-position: center;
+}
+
+.gc-scroll-arrow .gc-scroll-arrowDown {
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAACRJREFUKFNjGAVYQHl5+X9cGKoEOyBZAwyQrAEGSNYwpAEDAwBvhSZBmzrLGgAAAABJRU5ErkJggg==);
+}
+.gc-scroll-arrowDown {
+    background-position: center;
+}
+
+.gc-scroll-arrow .gc-scroll-arrowLeft{
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAADBJREFUKFNjGMKgvLz8P5RJHABpIEkTTAPRmpA1EK0JBMjSBAJkaQIBsjQNNGBgAABe7iZBxoz5vwAAAABJRU5ErkJggg==);
+}
+.gc-scroll-arrowLeft {
+    background-position: center;
+}
+
+.gc-scroll-arrow .gc-scroll-arrowRight{
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAANCAYAAABy6+R8AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAC5JREFUKFNjGIKgvLz8P5RJPABpIlkjTBNJGpE1Ea2RZA0gQLIGECBZw2ACDAwAhS4mQZAuqGcAAAAASUVORK5CYII=);
+}
+.gc-scroll-arrowRight {
+    background-position: center;
+}
+
+.gc-scroll-bar .gc-scrollbar-vertical {
+    background-image: none;
+    background-repeat: no-repeat;
+}
+.gc-scrollbar-vertical {
+    background-position: center;
+}
+
+.gc-scroll-bar .gc-scrollbar-horizontal {
+    text-indent: 0;
+    background-image: none;
+    background-repeat: no-repeat;
+}
+.gc-scrollbar-horizontal {
+    background-position: center;
+}
+
+.gc-scrollbar-wrapper {
+    background-color: transparent;
+}
+
+.gc-scroll-bar {
+    border-style:solid;
+    border-color:#bfbfbf;
+    background:  #bfbfbf;
+    -moz-border-radius: 0px;
+    -webkit-border-radius: 0px;
+    border-radius: 0px;
+}
+.gc-scroll-arrow-hover {
+    border-style:solid;
+    border-color:#a7a7a7;
+    background: #a7a7a7;
+}
+.gc-scrollbar-stateHover {
+    border-style:solid;
+    border-color:#a7a7a7;
+    background: #a7a7a7;
+}
+
+.gc-scroll-arrow:active,
+.gc-scrollbar-stateActive {
+    border-style:solid;
+    border-color:#a7a7a7;
+    background: #a7a7a7;
+    -webkit-box-shadow: none;
+          box-shadow: none;
+}
+.gc-scroll-bar:active{
+  border-color:#777777;
+  background: #777777;
+}
+/*-----scrollbar end-----*/

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 2 - 0
app/public/js/datepicker/datepicker.min.js


+ 12 - 0
app/public/js/datepicker/datepicker.zh.js

@@ -0,0 +1,12 @@
+;(function (jQuery) { jQuery.fn.datepicker.language['zh'] = {
+    days: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
+    daysShort: ['日', '一', '二', '三', '四', '五', '六'],
+    daysMin: ['日', '一', '二', '三', '四', '五', '六'],
+    months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
+    monthsShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
+    today: '今天',
+    clear: '清除',
+    dateFormat: 'yyyy-mm-dd',
+    timeFormat: 'hh:ii',
+    firstDay: 0
+}; })(jQuery);

+ 2 - 2
app/public/js/global.js

@@ -103,9 +103,9 @@ const postData = function (url, data, successCallback, errorCallBack) {
                     successCallback(result.data);
                 }
             } else {
-                toast('error: ' + result.message, 'error', 'exclamation-circle');
+                toast('error: ' + result.msg, 'error', 'exclamation-circle');
                 if (errorCallBack) {
-                    errorCallBack();
+                    errorCallBack(result.msg);
                 }
             }
         },

+ 1 - 1
app/public/js/ledger.js

@@ -41,7 +41,7 @@ $(document).ready(function() {
     autoFlashHeight();
     const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
     SpreadJsObj.addDeleteBind(ledgerSpread);
-    const ledgerTree = createNewPathTree({
+    const ledgerTree = createNewPathTree('ledger', {
         id: 'ledger_id',
         pid: 'ledger_pid',
         order: 'order',

+ 1 - 1
app/public/js/ledger_audit.js

@@ -42,7 +42,7 @@ function loadContents(data) {
 $(document).ready(() => {
     autoFlashHeight();
     const ledgerSpread = SpreadJsObj.createNewSpread($('#ledger-spread')[0]);
-    const ledgerTree = createNewPathTree({
+    const ledgerTree = createNewPathTree('active', {
         id: 'ledger_id',
         pid: 'ledger_pid',
         order: 'order',

+ 373 - 0
app/public/js/measure_work.js

@@ -0,0 +1,373 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/6/21
+ * @version
+ */
+
+// 向后端请求中间计量号
+function getNewMeasureCode() {
+    postData('/measure/newCode', null, function (code) {
+        if (code !== '') {
+            $('#mj-code').val(code);
+        }
+    });
+}
+// 新增计量范围
+function addMeasurePos(pid) {
+    postData('/measure/pos', {
+        mid: $('#measures').val(),
+        operate: 'add',
+        pid: pid,
+    }, function (data) {
+        const tr = [];
+        tr.push('<tr id="' + data.pid +'">');
+        tr.push('<td>' + data.code + '</td>');
+        tr.push('<td>' + data.name + '</td>');
+        tr.push('<td><a href="javascript: void(0)" class="text-danger" onclick="removeMeasurePos(' + data.pid + ')">移除</a></td>');
+        tr.push('</tr>');
+        $('#measurePosList').append(tr.join(''));
+    })
+}
+// 移除计量范围
+function removeMeasurePos(pid) {
+    postData('/measure/pos', {
+        mid: $('#measures').val(),
+        operate: 'remove',
+        pid: pid,
+    }, function (data) {
+        $('tr[id='+ pid +']', '#measurePosList').remove();
+    })
+}
+
+
+$(document).ready(() => {
+    autoFlashHeight();
+
+    // 新增中间计量 modal显示
+    $('#add-mj').on('show.bs.modal', function() {
+        getNewMeasureCode();
+        if ($('#measures')[0].options.length === 0) {
+            $('#addCancel').hide();
+        } else {
+            $('#addCancel').show();
+        }
+    });
+    // 新增中间计量--刷新编号
+    $('#autoCode').click(getNewMeasureCode);
+    // 添加中间计量 确定
+    $('#addOk').click(function () {
+        const data = {
+            code: $('#mj-code').val(),
+            date: $('#mj-date').val(),
+        };
+        postData('/measure/list/add', data, function (rst) {
+            $('#mj-code').removeClass('is-invalid');
+            $('#mj-add').modal('hide');
+        }, function () {
+            $('#mj-code').addClass('is-invalid');
+            $('#mj-Hint').show();
+        });
+    });
+
+    const billsTree = createNewPathTree('measure', {
+        id: 'ledger_id',
+        pid: 'ledger_pid',
+        order: 'order',
+        level: 'level',
+        rootId: -1,
+        keys: ['id', 'tender_id', 'ledger_id'],
+    });
+    let billsSpread;
+
+    // 获取中间计量详细数据
+    function getMeasureDetail(mid) {
+        postData('/measure/detail', { mid: mid }, function(data) {
+            // 计量范围
+            const posHtml = [];
+            for (const p of data.pos) {
+                const tr = [];
+                tr.push('<tr id="' + p.pid +'">');
+                tr.push('<td>' + p.code + '</td>');
+                tr.push('<td>' + p.name + '</td>');
+                tr.push('<td><a href="javascript: void(0)" class="text-danger" onclick="removeMeasurePos(' + p.pid + ')">移除</a></td>');
+                tr.push('</tr>');
+                posHtml.push(tr.join(''));
+            }
+            $('#measurePosList').html(posHtml.join(''));
+            // 计量清单
+            billsTree.loadDatas(data.billsTree);
+            treeCalc.calculateAll(billsTree, ['total_price', 'deal_totalprice', 'qc_totalprice']);
+            if (!billsSpread) {
+                billsSpread = SpreadJsObj.createNewSpread($('#billsSpread')[0]);
+                const sheet = billsSpread.getActiveSheet();
+                SpreadJsObj.initSheet(sheet, measureSpreadSetting);
+                SpreadJsObj.loadSheetData(sheet, 'tree', billsTree);
+                /**
+                 * 父项不可编辑
+                 * sender - {type: 'EditStarting'}
+                 * args - {sheet, sheetName, row, col, cancel}
+                 */
+                sheet.bind(spreadNS.Events.EditStarting, function (sender, args) {
+                    const sheet = args.sheet;
+                    if (sheet.zh_tree) {
+                        const node = sheet.zh_tree.nodes[args.row];
+                        args.cancel = node ? !node.is_leaf : true;
+                    } else {
+                        args.cancel = true;
+                    }
+                });
+                /**
+                 * 最底层清单编辑后,提交
+                 * sender - {type: 'EditEnding'}
+                 * args - {sheet, sheetName, row, col, editor, editingText cancel}
+                 */
+                sheet.bind(spreadNS.Events.EditEnding, function (sender, args) {
+                    const sheet = args.sheet;
+                    if (sheet.zh_tree || sheet.zh_setting) {
+                        const node = sheet.zh_tree.nodes[args.row];
+                        const col = sheet.zh_setting.cols[args.col];
+                        if (node && node.is_leaf) {
+                            const updateData = {
+                                mid: $('#measures').val(),
+                                bid: node.ledger_id,
+                                update: {},
+                            }
+                            updateData.update[col.field] = col.type === 'Number' ? parseFloat(args.editingText) : args.editingText;
+                            // begin test
+                            // node[col.field] = col.type === 'Number' ? parseFloat(args.editingText) : args.editingText;
+                            // node.deal_totalprice = node.unit_price * node.deal_quantity;
+                            // const nodes = treeCalc.calculateParent(sheet.zh_tree, node, ['deal_totalprice']);
+                            // SpreadJsObj.reLoadNodesData(sheet, nodes);
+                            // end test;
+                            postData('/measure/billsUpdate', updateData, function (data) {
+                                if (sheet.zh_tree.loadLeafData) {
+                                    sheet.zh_tree.loadLeafData(data);
+                                    const nodes = treeCalc.calculateParent(sheet.zh_tree, node, ['deal_totalprice', 'qc_totalprice']);
+                                    SpreadJsObj.reLoadNodesData(sheet, nodes);
+                                }
+                            }, function () {
+                                args.cancel = true;
+                            });
+                        } else {
+                            args.cancel = true;
+                        }
+                    } else {
+                        args.cancel = true;
+                    }
+                });
+            } else {
+                SpreadJsObj.reLoadSheetData(billsSpread.getActiveSheet());
+            }
+        });
+    }
+    // 新增计量清单
+    function addMeasureBills (bid) {
+        const data = {
+            mid: $('#measures').val(),
+            operate: 'add',
+            bid: bid,
+        };
+        postData('/measure/bills', data, function (data) {
+            billsTree.addData(data);
+            SpreadJsObj.reLoadSheetData(billsSpread.getActiveSheet());
+        });
+    }
+    // 移除计量清单
+    function removeMeasureBills (bid) {
+        const data = {
+            mid: $('#measures').val(),
+            operate: 'remove',
+            bid: bid,
+        };
+        postData('/measure/bills', data, function (data) {
+            billsTree.removeData(data);
+            SpreadJsObj.reLoadSheetData(billsSpread.getActiveSheet());
+        });
+    }
+    // 勾选计量清单
+    function checkMeasureBills() {
+        const bid = $(this).parent().parent().attr('id');
+        if (!bid) { return; }
+        if (this.checked) {
+            addMeasureBills(bid);
+        } else {
+            removeMeasureBills(bid);
+        }
+    }
+
+    if (!codeRule || codeRule.length === 0) {
+        codeRule = [];
+        const firstSet = new codeRuleSet($('div.modal-body', '#first'));
+        // 确认规则上传服务器
+        $('#setRule', '#first').bind('click', function () {
+            const data = {
+                rule: ruleType,
+                data: JSON.stringify(codeRule),
+            };
+            postData('/tender/rule', data, function () {
+                $('#first').modal('hide');
+                $('#add-mj').modal('show');
+            });
+        });
+
+        $('#first').modal('show');
+    } else if ($('#measures')[0].options.length === 0) {
+        $('#add-mj').modal('show');
+    } else {
+        // 添加计量清单
+        $('#addMeasureBills').click(function () {
+            if ($('#measurePosList').children().length === 0) {
+                $('#add-pos').modal('show');
+            } else {
+                $('#add-qd').modal('show');
+            }
+        });
+        // 搜索项目节部位
+        $('#search-pos').click(function () {
+            const keyword = $('input', '#add-pos').val();
+            if (keyword !== '') {
+                postData('/measure/search', {
+                    mid: $('#measures').val(),
+                    searchType: 'pos',
+                    keyword: keyword
+                }, function (data) {
+                    const html = [];
+                    for (const d of data) {
+                        const tr = [];
+                        tr.push('<tr id="' + d.ledger_id +'">');
+                        tr.push('<td>' + d.code + '</td>');
+                        tr.push('<td>' + d.name + '</td>');
+                        tr.push('<td><a href="javascript: void(0)" onclick="addMeasurePos(' + d.ledger_id + ')">添加</a></td>');
+                        tr.push('</tr>');
+                        html.push(tr.join(''));
+                    }
+                    $('#searchedPosList').html(html.join(''));
+                });
+            }
+        });
+        // 选择计量清单
+        $('#chooseBills').click(function () {
+            $('#add-pos').modal('hide');
+            $('#add-qd').modal('show');
+        });
+        // 搜索计量清单
+        $('#search-bills').click(function () {
+            const keyword = $('input', '#add-qd').val();
+            if (keyword !== '') {
+                postData('/measure/search', {
+                    mid: $('#measures').val(),
+                    searchType: 'bills',
+                    keyword: keyword
+                }, function (data) {
+                    const html = [];
+                    for (const d of data) {
+                        const tr = [];
+                        const check = billsTree.getItems(d.ledger_id) ? 'checked' : '';
+                        tr.push('<tr id="' + d.ledger_id +'">');
+                        tr.push('<td>' + d.b_code + '</td>');
+                        tr.push('<td>' + d.name + '</td>');
+                        tr.push('<td>' + d.unit + '</td>');
+                        tr.push('<td>' + (d.unit_price ? d.unit_price : '') + '</td>');
+                        tr.push('<td><input type="checkbox" ' + check + '> 已选择</td>');
+                        tr.push('</tr>');
+                        html.push(tr.join(''));
+                    }
+                    $('#searchedBillsList').html(html.join(''));
+                    $('input[type=checkbox]', '#add-qd').click(checkMeasureBills);
+                });
+            }
+        });
+        // 选择计量范围
+        $('#choosePos').click(function () {
+            $('#add-qd').modal('hide');
+            $('#add-pos').modal('show');
+        });
+
+        getMeasureDetail($('#measures').val());
+    }
+    const staticSet = new codeRuleSet($('#rule'), '#static');
+});
+
+class codeRuleSet {
+    constructor (obj) {
+        this.body = obj;
+        // 切换规则组件类型
+        $('select', obj).change(function () {
+            const codeType = this.selectedIndex;
+            if (codeType === ruleConst.ruleType.addNo) {
+                $('#format', obj).show();
+                $('#text', obj).show();
+                $('#text>label', obj).text('起始编号');
+                $('#text>input', obj).val('001');
+                const s = '0000000000' + 1;
+                $('#text>input', obj).val(s.substr(s.length - $('#format>input', obj).val()));
+            } else if (codeType === ruleConst.ruleType.text) {
+                $('#format', obj).hide();
+                $('#text', obj).show();
+                $('#text>label', obj).text('文本');
+                $('#text>input', obj).val('').attr('placeholder', '请在这里输入需要的文本');
+            } else {
+                $('#format', obj).hide();
+                $('#text', obj).hide();
+            }
+        });
+        // 修改编号位数
+        $('#format>input', obj).change(function () {
+            const s = '0000000000' + parseInt($('#text>input', obj).val());
+            $('#text>input', obj).val(s.substr(s.length - $(this).val()));
+        });
+        // 新增规则组件
+        $('#addRule', obj).click(function () {
+            const codeType = $('select', obj)[0].selectedIndex;
+            const rule = {rule_type: codeType}, html = [];
+            let preview;
+            switch (codeType) {
+                case ruleConst.ruleType.tenderName: {
+                    preview = $('#tenderName').text();
+                    break;
+                }
+                case ruleConst.ruleType.text: {
+                    rule.text =  $('#text>input', obj).val();
+                    preview = rule.text;
+                    break;
+                }
+                case ruleConst.ruleType.inDate: {
+                    preview = moment().format('YYYYMM');
+                    break;
+                }
+                case ruleConst.ruleType.addNo: {
+                    rule.format = parseInt($('#format>input', obj).val());
+                    rule.start = parseInt($('#text>input', obj).val());
+                    const s = '0000000000';
+                    preview = s.substr(s.length - rule.format);
+                    break;
+                }
+            }
+            // 更新规则
+            codeRule.push(rule);
+            // 更新规则显示
+            html.push('<span class="badge badge-light" title="' + ruleConst.ruleString[codeType] + '" rule="' + JSON.stringify(rule) + '">');
+            html.push('<span>' + preview + '</span>');
+            html.push('<a href="javascript: void(0);" class="text-danger" title="移除"><i class="fa fa-remove"></i></a>');
+            html.push('</span>');
+            const part = $('#ruleParts', obj).append(html.join(''));
+            // 更新规则预览
+            $('#preview', obj).text($('#preview', obj).text() + preview);
+
+            $('a', part).bind('click', function () {
+                const index = $('a', obj).index(this);
+                codeRule.splice(index, 1);
+                $(this).parent().remove();
+                const rules = $('span>span', obj), ruleText = [];
+                for (const r of rules) {
+                    ruleText.push(r.innerText);
+                }
+                $('#preview', obj).text(ruleText.join(''));
+            });
+        });
+    }
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 7 - 0
app/public/js/moment/moment.min.js


+ 420 - 0
app/public/js/path_tree.bak.js

@@ -0,0 +1,420 @@
+/**
+ * 重构前备份
+ *
+ * 构建pathTree
+ * 可动态加载子节点,要求子节点获取接口按/xxx/get-children定义
+ * @param {Object} setting - 设置
+ * @returns {PathTree}
+ */
+
+const createNewPathTree = function (setting) {
+    const treeSetting = JSON.parse(JSON.stringify(setting));
+    const itemsPre = 'id_';
+
+    const PathTree = function () {
+        // 无索引
+        this.datas = [];
+        // 以key为索引
+        this.items = {};
+        // 以排序为索引
+        this.nodes = [];
+    };
+    const proto = PathTree.prototype;
+
+    /**
+     * 树结构根据显示排序
+     */
+    proto.sortTreeNode = function () {
+        const self = this;
+        const addSortNodes = function (nodes) {
+            for (let i = 0; i < nodes.length; i++) {
+                self.nodes.push(nodes[i]);
+                addSortNodes(self.getChildren(nodes[i]));
+            }
+        };
+        self.nodes = [];
+        addSortNodes(this.getChildren(null));
+    };
+
+    /**
+     * 加载数据(初始化), 并给数据添加部分树结构必须数据
+     * @param datas
+     */
+    proto.loadDatas = function (datas) {
+        // 清空旧数据
+        this.items = {};
+        this.nodes = [];
+        // 加载全部数据
+        for (const data of datas) {
+            const keyName = itemsPre + data[treeSetting.id];
+            this.items[keyName] = JSON.parse(JSON.stringify(data));
+            this.datas.push(this.items[keyName]);
+        }
+        this.sortTreeNode();
+        for (const node of this.nodes) {
+            const children = this.getChildren(node);
+            node.expanded = children.length > 0;
+            node.visible = true;
+        }
+    };
+    /**
+     * 加载数据(动态),只加载不同部分
+     * @param {Array} datas
+     * @return {Array} 加载到树的数据
+     * @privateA
+     */
+    proto._updateData = function (datas) {
+        const loadedData = [];
+        for (const data of datas) {
+            let node = this.getItems(data[treeSetting.id]);
+            if (node) {
+                for (const prop in node) {
+                    if (data[prop] !== undefined) {
+                        node[prop] = data[prop];
+                    }
+                }
+                loadedData.push(node);
+            }
+        }
+        for (const node of loadedData) {
+            const children = this.getChildren(node);
+            node.expanded = children.length > 0 && children[0].visible;
+        }
+        this.sortTreeNode();
+        return loadedData;
+    };
+    /**
+     * 加载数据(动态),只加载不同部分
+     * @param {Array} datas
+     * @return {Array} 加载到树的数据
+     * @privateA
+     */
+    proto._loadData = function (datas) {
+        const loadedData = [];
+        for (const data of datas) {
+            let node = this.getItems(data[treeSetting.id]);
+            if (node) {
+                for (const prop in node) {
+                    if (data[prop] !== undefined) {
+                        node[prop] = data[prop];
+                    }
+                }
+                loadedData.push(node);
+            } else {
+                const keyName = itemsPre + data[treeSetting.id];
+                const node = JSON.parse(JSON.stringify(data));
+                this.items[keyName] = node;
+                this.datas.push(node);
+                node.expanded = false;
+                node.visible = true;
+                loadedData.push(node);
+            }
+        }
+        this.sortTreeNode();
+        for (const node of loadedData) {
+            const children = this.getChildren(node);
+            if (!node.expanded && children.length > 0) {
+                node.expanded = true;
+                this._refreshChildrenVisible(node);
+            }
+        }
+        return loadedData;
+    };
+    /**
+     * 清理数据(动态)
+     * @param datas
+     * @private
+     */
+    proto._freeData = function (datas) {
+        const removeArrayData = function (array, data) {
+            const index = array.indexOf(data);
+            array.splice(index, 1);
+        };
+        for (const data of datas) {
+            const node = this.getItems(data[treeSetting.id]);
+            if (node) {
+                delete this.items[itemsPre + node[treeSetting.id]];
+                removeArrayData(this.datas, node);
+                removeArrayData(this.nodes, node);
+            }
+        }
+    };
+    /**
+     * 加载需展开的数据
+     * @param {Array} datas
+     * @returns {Array}
+     * @private
+     */
+    proto._loadExpandData = function (datas) {
+        const loadedData = [], existData = [], expandData = [];
+        for (const data of datas) {
+            let node = this.getItems(data[treeSetting.id]);
+            if (node) {
+                existData.push(node);
+            } else {
+                const keyName = itemsPre + data[treeSetting.id];
+                const node = JSON.parse(JSON.stringify(data));
+                this.items[keyName] = node;
+                this.datas.push(node);
+                node.expanded = false;
+                node.visible = true;
+                loadedData.push(node);
+            }
+        }
+        this.sortTreeNode();
+        for (const node of loadedData) {
+            const children = this.getChildren(node);
+            if (!node.expanded && children.length > 0) {
+                node.expaned = true;
+                this._refreshChildrenVisible(node);
+            }
+        }
+        for (const node of existData) {
+            const children = this.getChildren(node);
+            if (!node.expanded && children.length > 0) {
+                node.expanded = children.length > 0;
+                this._refreshChildrenVisible(node);
+                expandData.push(node);
+            }
+        }
+        return [loadedData, expandData];
+    };
+
+    /**
+     * 根据id获取树结构节点数据
+     * @param {Number} id
+     * @returns {Object}
+     */
+    proto.getItems = function (id) {
+        return this.items[itemsPre + id];
+    };
+    /**
+     * 查找node的parent
+     * @param {Object} node
+     * @returns {Object}
+     */
+    proto.getParent = function (node) {
+        return this.getItems(node[treeSetting.pid]);
+    };
+    /**
+     * 查询node的已下载子节点
+     * @param {Object} node
+     * @returns {Array}
+     */
+    proto.getChildren = function (node) {
+        const pid = node ? node[treeSetting.id] : treeSetting.rootId;
+        const children = this.datas.filter(function (x) {
+            return x[treeSetting.pid] === pid;
+        });
+        children.sort(function (a, b) {
+            return a.order - b.order;
+        });
+        return children;
+    };
+    /**
+     * 查询node的已下载的全部后代
+     * @param {Object} node
+     * @returns {Array}
+     */
+    proto.getPosterity = function (node) {
+        const reg = new RegExp('^' + node.full_path + '.');
+        return this.datas.filter(function (x) {
+            return reg.test(x.full_path);
+        })
+    };
+    /**
+     * 查询node是否是父节点的最后一个子节点
+     * @param {Object} node
+     * @returns {boolean}
+     */
+    proto.isLastSibling = function (node) {
+        const siblings = this.getChildren(this.getParent(node));
+        return node.order === siblings[siblings.length - 1].order;
+    };
+    /**
+     * 刷新子节点是否可见
+     * @param {Object} node
+     * @private
+     */
+    proto._refreshChildrenVisible = function (node) {
+        const children = this.getChildren(node);
+        for (const child of children) {
+            child.visible = node.expanded && node.visible;
+            this._refreshChildrenVisible(child);
+        }
+    }
+    /**
+     * 设置节点是否展开, 并控制子节点可见
+     * @param {Object} node
+     * @param {Boolean} expanded
+     */
+    proto.setExpanded = function (node, expanded) {
+        node.expanded = expanded;
+        this._refreshChildrenVisible(node);
+    };
+
+    /**
+     * 提取节点key和索引数据
+     * @param {Object} node - 节点
+     * @returns {key}
+     */
+    proto.getNodeKeyData = function (node) {
+        const data = {};
+        for (const key of treeSetting.keys) {
+            data[key] = node[key];
+        }
+        return data;
+    }
+    /**
+     * 得到树结构构成id
+     * @param node
+     * @returns {*}
+     */
+    proto.getNodeKey = function (node) {
+        return node[treeSetting.id];
+    }
+
+    /**
+     * 以下方法需等待响应, 通过callback刷新界面
+     */
+    /**
+     * 加载子节点
+     * @param {Object} node
+     * @param {function} callback
+     */
+    proto.loadChildren = function (node, callback) {
+        const self = this;
+        const url = treeSetting.preUrl ? treeSetting.preUrl + '/get-children' : 'get-children';
+        console.log(url);
+        postData(url, this.getNodeKeyData(node), function (data) {
+            self._loadData(data);
+            callback();
+        });
+    };
+    /**
+     * 树结构基本操作
+     * @param {String} url - 请求地址
+     * @param {Object} node - 操作节点
+     * @param {String} type - 操作类型
+     * @param {function} callback - 界面刷新
+     */
+    proto.baseOperation = function (url, node, type, callback) {
+        const self = this;
+        const data = {
+            id: node[treeSetting.id],
+            postType: type
+        };
+        postData(url, data, function (datas) {
+            const result = {};
+            if (datas.update) {
+                result.update = self._updateData(datas.update);
+            }
+            if (datas.create) {
+                result.create = self._loadData(datas.create);
+            }
+            if (datas.delete) {
+                result.delete = self._freeData(datas.delete);
+            }
+            callback(result);
+        });
+    };
+    /**
+     * 节点数据编辑
+     * @param {String} url - 请求地址
+     * @param {Array|Object} updateData - 需更新的数据
+     * @param {function} callback - 界面刷新
+     */
+    proto.update = function (url, updateData, callback) {
+        const self = this;
+        postData(url, updateData, function (datas) {
+            const result = self._updateData(datas);
+            callback(result);
+        }, function () {
+            if (updateData instanceof Array) {
+                const result = [];
+                for (const data of updateData) {
+                    result.push(self.getItems(data[treeSetting.id]));
+                }
+                callback(result)
+            } else {
+                callback([self.getItems(updateData[treeSetting.id])]);
+            }
+        });
+    };
+    /**
+     * 复制粘贴整块(目前仅可粘贴为后项)
+     * @param {String} url - 请求地址
+     * @param {Object} node - 操作节点
+     * @param {Array} block - 被复制整块的节点列表
+     * @param {function} callback - 界面刷新
+     */
+    proto.pasteBlock = function (url, node, block, callback) {
+        const self = this;
+        const data = {
+            id: node[treeSetting.id],
+            block: block
+        };
+        postData(url, data, function (datas) {
+            const result = {};
+            if (datas.update) {
+                result.update = self._updateData(datas.update);
+            }
+            if (datas.create) {
+                result.create = self._loadData(datas.create);
+            }
+            if (datas.delete) {
+                result.delete = self._freeData(datas.delete);
+            }
+            callback(result);
+        });
+    };
+    /**
+     * 提交数据
+     * @param {String} url - 请求地址
+     * @param {Object} node - 当前选中节点
+     * @param {Object} data - 提交的数据
+     * @param {function} callback - 界面刷新
+     */
+    proto.postData = function (url, node, data, callback) {
+        const self = this;
+        data.id = node[treeSetting.id];
+        postData(url, data, function (datas) {
+            const result = {};
+            if (datas.update) {
+                result.update = self._updateData(datas.update);
+            }
+            if (datas.create) {
+                result.create = self._loadData(datas.create);
+            }
+            if (datas.delete) {
+                result.delete = self._freeData(datas.delete);
+            }
+            if (datas.expand) {
+                const [create, update] = self._loadExpandData(datas.expand);
+                result.create = result.create.concat(create);
+                result.expand = update;
+            }
+            callback(result);
+        });
+    };
+
+    proto.batchInsert = function (url, node, data, callback) {
+        const self = this;
+        data.id = node[treeSetting.id];
+        postData(url, data, function (datas) {
+            const result = {};
+            if (datas.update) {
+                result.update = self._updateData(datas.update);
+            }
+            if (datas.create) {
+                result.create = self._loadData(datas.create);
+            }
+            if (datas.delete) {
+                result.delete = self._freeData(datas.delete);
+            }
+            callback(result);
+        });
+    }
+
+    return new PathTree();
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 586 - 375
app/public/js/path_tree.js


+ 31 - 13
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -156,11 +156,21 @@ const SpreadJsObj = {
 
         this.massOperationSheet(sheet, function () {
             sheet.setColumnCount(sheet.zh_setting.cols.length);
-            for (const i in sheet.zh_setting.cols) {
-                const col = sheet.zh_setting.cols[i];
-                const cell = sheet.getCell(0, i, spreadNS.SheetArea.colHeader);
-                cell.text(col.title);
-                sheet.setColumnWidth(i, col.width);
+            sheet.setRowCount(sheet.zh_setting.headRows, spreadNS.SheetArea.colHeader);
+            for (let iRow = 0; iRow < sheet.zh_setting.headRowHeight.length; iRow ++) {
+                sheet.setRowHeight(iRow, sheet.zh_setting.headRowHeight[iRow], spreadNS.SheetArea.colHeader);
+            }
+            for (let iCol = 0; iCol < sheet.zh_setting.cols.length; iCol++) {
+                const col = sheet.zh_setting.cols[iCol];
+                const title = col.title.split('|'), colSpan = col.colSpan.split('|'), rowSpan = col.rowSpan.split('|');
+                for (let i = 0; i < title.length; i++) {
+                    const cell = sheet.getCell(i, iCol, spreadNS.SheetArea.colHeader);
+                    cell.text(title[i]);
+                    if ((colSpan[i] !== '' && colSpan[i] !== '1') || (rowSpan[i] !== '' && rowSpan[i] !== '1')) {
+                        sheet.addSpan(i, iCol, parseInt(rowSpan[i]), parseInt(colSpan[i]), spreadNS.SheetArea.colHeader);
+                    }
+                }
+                sheet.setColumnWidth(iCol, col.width);
             }
             sheet.rowOutlines.direction(spreadNS.Outlines.OutlineDirection.backward);
             sheet.showRowOutline(false);
@@ -190,6 +200,7 @@ const SpreadJsObj = {
         const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
         this.beginMassOperation(sheet);
         try {
+            sheet.clear(0, 0, sheet.getRowCount(), sheet.getColumnCount(), spreadNS.SheetArea.viewport, spreadNS.StorageType.data)
             // 设置总行数
             const totalRow = sortData.length + sheet.zh_setting.emptyRows;
             sheet.setRowCount(totalRow, spreadNS.SheetArea.viewport);
@@ -201,9 +212,9 @@ const SpreadJsObj = {
                 sheet.zh_setting.cols.forEach(function (col, j) {
                     const cell = sheet.getCell(i, j);
                     if (col.field !== '' && data[col.field]) {
-                        cell.value(data[col.field]).locked(col.readOnly || sheet.zh_setting.readOnly || false);
+                        cell.value(data[col.field]).locked(col.readOnly || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
                     } else {
-                        cell.locked(col.readOnly || sheet.zh_setting.readOnly || false);
+                        cell.locked(col.readOnly || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
                     }
                 });
             });
@@ -248,9 +259,9 @@ const SpreadJsObj = {
                 sheet.zh_setting.cols.forEach(function (col, j) {
                     const cell = sheet.getCell(i, j);
                     if (col.field !== '' && data[col.field]) {
-                        cell.value(data[col.field]).locked(col.readOnly || sheet.zh_setting.readOnly || false);
+                        cell.value(data[col.field]).locked(col.readOnly || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
                     } else {
-                        cell.locked(col.readOnly || sheet.zh_setting.readOnly || false);
+                        cell.locked(col.readOnly || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
                     }
                 });
             };
@@ -295,9 +306,9 @@ const SpreadJsObj = {
                     // 设置值
                     const cell = sheet.getCell(row, j);
                     if (col.field !== '' && data[col.field]) {
-                        cell.value(data[col.field]).locked(col.readOnly || sheet.zh_setting.readOnly || false);
+                        cell.value(data[col.field]).locked(col.readOnly || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
                     } else {
-                        cell.locked(col.readOnly || sheet.zh_setting.readOnly || false);
+                        cell.locked(col.readOnly || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
                     }
                     // 设置单元格格式
                     if (col.cellType) {
@@ -320,6 +331,13 @@ const SpreadJsObj = {
             this.endMassOperation(sheet);
         }
     },
+    reLoadNodesData: function (sheet, nodes) {
+        nodes = nodes instanceof Array ? nodes : [nodes];
+        for (const node of nodes) {
+            const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
+            this.reLoadRowData(sheet, sortData.indexOf(node), 1);
+        }
+    },
     /**
      * 根据data加载sheet数据,合并了一般数据和树结构数据的加载
      * @param {GC.Spread.Sheets.Worksheet} sheet
@@ -595,7 +613,7 @@ const SpreadJsObj = {
 
                 // 点击展开节点时,如果已加载子项,则展开,反之这加载子项,展开
                 if (Math.abs(hitinfo.x - centerX) < halfBoxLength && Math.abs(hitinfo.y - centerY) < halfBoxLength) {
-                    if (!node.expanded && !node.is_leaf && tree.getChildren(node).length === 0) {
+                    if (!node.expanded && !node.is_leaf && tree.getChildren(node).length === 0 && tree.loadChildren) {
                         tree.loadChildren(node, function () {
                             node.expanded = true;
                             const children = tree.getChildren(node);
@@ -613,7 +631,7 @@ const SpreadJsObj = {
                         hitinfo.sheet.repaint();
                     }
                 }
-            }
+            };
 
             return new TreeNodeCellType();
         },

+ 12 - 2
app/router.js

@@ -8,6 +8,8 @@ module.exports = app => {
     const datetimeFill = app.middlewares.datetimeFill();
     // 项目管理员判断中间件
     const projectManagerCheck = app.middlewares.projectManagerCheck();
+    // 标段读取中间件
+    const tenderSelect = app.middlewares.tenderSelect();
 
     // 登入登出相关
     app.get('/login', 'loginController.index');
@@ -32,7 +34,7 @@ module.exports = app => {
     app.post('/project/info', sessionAuth, 'projectController.saveInfo');
 
     // 台账管理相关
-    app.get('/ledger/explode', sessionAuth, 'ledgerController.explode');
+    app.get('/ledger/explode', sessionAuth, tenderSelect, 'ledgerController.explode');
     app.post('/ledger/get-children', sessionAuth, 'ledgerController.getChildren');
     app.post('/ledger/base-operation', sessionAuth, 'ledgerController.baseOperation');
     app.post('/ledger/update', sessionAuth, 'ledgerController.update');
@@ -71,9 +73,17 @@ module.exports = app => {
     app.get('/tender/switch/:tenderId', sessionAuth, 'tenderController.switchTender');
     app.post('/tender/save', sessionAuth, datetimeFill, 'tenderController.save');
     app.post('/tender/delete', sessionAuth, datetimeFill, 'tenderController.delete');
+    app.post('/tender/rule', sessionAuth, 'tenderController.rule');
 
     // 中间计量管理相关
-    app.get('/measure/work', sessionAuth, 'measureController.work');
+    app.get('/measure/work', sessionAuth, tenderSelect, 'measureController.work');
+    app.post('/measure/newCode', sessionAuth, 'measureController.newCode');
+    app.post('/measure/add', sessionAuth, 'measureController.addMeasure');
+    app.post('/measure/search', sessionAuth, 'measureController.search');
+    app.post('/measure/pos', sessionAuth, 'measureController.pos');
+    app.post('/measure/bills', sessionAuth, 'measureController.bills');
+    app.post('/measure/detail', sessionAuth, 'measureController.measureDetail');
+    app.post('/measure/billsUpdate', sessionAuth, 'measureController.billsUpdate');
     app.get('/measure/list', sessionAuth, 'measureController.list');
 
     // 期计量管理相关

+ 70 - 0
app/service/ledger.js

@@ -398,6 +398,76 @@ module.exports = app => {
         };
 
         /**
+         * 获取sql条件语句片段,仅供search、searchRange方法使用
+         * @param {Object} where - 条件参数
+         * @returns {*[]}
+         * e.g.
+         * where = {type: 'And', value: 'A', operate: '=', fields: ['code', 'name']}
+         * return [sql: 'And (?? = \'A\' Or ?? = \'A\')', sqlParam: ['code', name]]
+         * @private
+         */
+        _getWhereString(where) {
+            const sqlParam = [], sqlPart = [];
+            const values = where.value instanceof Array ? where.value : [where.value];
+            const fields = where.fields instanceof Array ? where.fields : [where.fields];
+            for (const field of fields) {
+                for (const v of values) {
+                    sqlPart.push('?? ' + where.operate + ' ' + v);
+                    sqlParam.push(field);
+                }
+            }
+            const sql = ' ' + (where.type ? where.type : 'And') + ' (' + sqlPart.join(' OR ') + ')';
+            return [sql, sqlParam];
+        }
+        /**
+         * 搜索台账
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {Object} key - 查询信息
+         * @returns {Promise<void>}
+         */
+        async search(tenderId, key) {
+            let sql = 'Select * From ?? Where';
+            let sqlParam = [this.tableName];
+
+            sql = sql + ' ?? = ' + tenderId;
+            sqlParam.push('tender_id');
+
+            const [sql1, sqlParam1] = this._getWhereString(key);
+            sql = sql + sql1;
+            sqlParam = sqlParam.concat(sqlParam1);
+
+            return this.db.query(sql, sqlParam);
+        }
+        /**
+         * 范围内搜索台账
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {Object} key - 查询信息
+         * @param {Array} range - 查询范围
+         * @returns {Promise<void>}
+         */
+        async searchRange(tenderId, key, range) {
+            let sql = 'Select * From ?? Where';
+            let sqlParam = [this.tableName];
+
+            sql = sql + ' ?? = ' + tenderId;
+            sqlParam.push('tender_id');
+
+            let [sql1, sqlParam1] = this._getWhereString(key);
+            sql = sql + sql1;
+            sqlParam = sqlParam.concat(sqlParam1);
+
+            for (const r of range) {
+                [sql1, sqlParam1] = this._getWhereString(r);
+                sql = sql + sql1;
+                sqlParam = sqlParam.concat(sqlParam1);
+            }
+
+            return this.db.query(sql, sqlParam);
+        }
+
+        /**
          * 统计子节点total_price
          * @param {Number} tenderId - 标段id
          * @param {Number} pid - 父节点id

+ 16 - 0
app/service/ledger_change.js

@@ -0,0 +1,16 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/6/12
+ * @version
+ */
+
+module.exports = app => {
+    class LedgerChange extends app.BaseService {
+    }
+
+    return LedgerChange;
+};

+ 57 - 0
app/service/measure.js

@@ -0,0 +1,57 @@
+'use strict';
+
+/**
+ * 中间计量数据模型
+ *
+ * @author Mai
+ * @date 2018/6/27
+ * @version
+ */
+
+module.exports = app => {
+
+    class Measure extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'measure';
+        }
+
+        /**
+         * 新增中间计量
+         * @param code - 编号
+         * @param date - 年月
+         * @param stage - 所属期
+         * @returns {Promise<void>}
+         */
+        async add(tenderId, code, date, stage) {
+            const count = await this.ctx.service.measure.count({
+                tender_id: tenderId, code: code,
+            });
+            if (count === 0) {
+                const newData = {
+                    tender_id: tenderId,
+                    mid: this.uuid.v4(),
+                    code: code,
+                    in_time: new Date(),
+                };
+                const result = await this.ctx.service.measure.db.insert(this.tableName, newData);
+                if (result.affectedRow === 0) {
+                    throw '新增中间计量失败';
+                }
+            } else {
+                throw '您输入的编号已存在';
+            }
+
+        }
+    }
+
+    return Measure;
+};
+

+ 178 - 0
app/service/measure_bills.js

@@ -0,0 +1,178 @@
+'use strict';
+
+/**
+ * 中间计量--计量清单 数据模型
+ *
+ * @author Mai
+ * @date 2018/6/27
+ * @version
+ */
+
+module.exports = app => {
+
+    class MeasureBills extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'measure_bills';
+        }
+
+        /**
+         * 查询中间计量下清单信息
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {uuid} mid - 中间计量id
+         * @returns {Promise<*>}
+         */
+        async getBillsDetail(tenderId, mid) {
+            const sql = 'SELECT MB.`tender_id`, MB.`mid`, MB.`bid`, L.`code`, L.`name`, L.`full_path` ' +
+                'FROM ?? AS L, ?? AS MB ' +
+                'WHERE L.`tender_id` = ? and MB.`tender_id` = ? and MB.`mid` = ? ' +
+                '    and MB.`bid` = L.`ledger_id`';
+            const sqlParam = [this.ctx.service.ledger.tableName, this.tableName, tenderId, tenderId, mid];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         *
+         * @param tenderId
+         * @param mid
+         * @param ids
+         * @returns {Promise<*>}
+         * @private
+         */
+        async _getUnionBillsData(tenderId, mid, ids) {
+            const sql = 'SELECT L.*, MB.`deal_quantity`, MB.`deal_totalprice`, MB.`qc_quantity`, MB.`qc_totalprice` ' +
+                'FROM ?? AS L, ?? AS MB ' +
+                'WHERE L.`tender_id` = ? and L.`ledger_id` IN (' + ids.join(',') + ') and MB.`tender_id` = ? and MB.`mid` = ?' +
+                '    and MB.`bid` = L.`ledger_id` ' +
+                'UNION ' +
+                'SELECT L.*, Null, Null, Null, Null ' +
+                'FROM ?? AS L ' +
+                'WHERE L.`tender_id` = ? and L.`ledger_id` IN (' + ids.join(',') + ') and L.`is_leaf` = false';
+            const sqlParam = [this.ctx.service.ledger.tableName, this.tableName, tenderId, tenderId, mid,
+                this.ctx.service.ledger.tableName, tenderId];
+            return await this.db.query(sql, sqlParam);
+        };
+
+        /**
+         * 查询中间计量下清单(含父项)
+         * @param {Number} tenderId - 标段id
+         * @param {uuid} mid - 中间计量id
+         * @returns {Promise<Array>}
+         */
+        async getBillsDetailWithParent(tenderId, mid) {
+            const bills = await this.getBillsDetail(tenderId, mid);
+            if (bills.length > 0) {
+                let ids = [];
+                for (const b of bills) {
+                    ids = ids.concat(b.full_path.split('.'));
+                }
+                return await this._getUnionBillsData(tenderId, mid, ids);
+                //return await this.ctx.service.ledger.getDataByNodeIds(tenderId, ids);
+            } else {
+                return [];
+            }
+        }
+
+        /**
+         * 查询中间计量下,某一条清单信息
+         * @param {Number} tenderId - 标段id
+         * @param {uuid} mid - 计量id
+         * @param {Number} bid - 清单id
+         * @returns {Promise<*>}
+         */
+        async getBillsData(tenderId, mid, bid) {
+            const sql = 'SELECT MB.`tender_id`, MB.`mid`, MB.`bid`, L.`code`, L.`name`, L.`full_path` ' +
+                'FROM ?? AS L, ?? AS MB ' +
+                'WHERE L.`tender_id` = ? and MB.`tender_id` = ? and MB.`mid` = ?  and MB.bid = ?' +
+                '    and MB.`bid` = L.`ledger_id`';
+            const sqlParam = [this.ctx.service.ledger.tableName, this.tableName, tenderId, tenderId, mid, bid];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 查询中间计量下,某一条清单信息(含父项)
+         * @param {Number} tenderId - 标段id
+         * @param {uuid} mid - 计量id
+         * @param {Number} bid - 清单id
+         * @returns {Promise<*>}
+         */
+        async getBillsDataWithParent(tenderId, mid, bid) {
+            const bills = await this.getBillsData(tenderId, mid, bid);
+            const ids = bills.full_path.split('.');
+            return await this._getUnionBillsData(tenderId, mid, ids);
+            //return await this.ctx.service.ledger.getDataByNodeIds(tenderId, bills.full_path.split('.'));
+        }
+
+        /**
+         * 新增计量清单
+         * @param {Number} tenderId - 标段id
+         * @param {uuid} mid - 计量id
+         * @param {Number} bid - 清单id
+         * @returns {Promise<void>}
+         */
+        async addBills(tenderId, mid, bid) {
+            const result = await this.db.insert(this.tableName, {
+                tender_id: tenderId,
+                mid: mid,
+                bid: bid,
+                in_time: new Date(),
+                in_user: this.ctx.session.sessionUser.accountId,
+            });
+        }
+
+        /**
+         * 移除计量清单
+         * @param {Number} tenderId - 标段id
+         * @param {uuid} mid - 计量id
+         * @param {Number} bid - 清单id
+         * @returns {Promise<void>}
+         */
+        async removeBills(tenderId, mid, bid) {
+            await this.db.delete(this.tableName, {
+                tender_id: tenderId,
+                mid: mid,
+                bid: bid,
+            });
+        }
+
+        /**
+         * 更新计量清单并计算
+         * @param {Number} tenderId - 标段id
+         * @param {uuid} mid - 计量id
+         * @param {Number} bid - 清单id
+         * @param {Object} data - 更新数据
+         * @returns {Promise<*>}
+         */
+        async updateBills(tenderId, mid, bid, data) {
+            const bills = await this.ctx.service.ledger.getDataByNodeId(tenderId, bid);
+            if (data.deal_quantity) {
+                data.deal_totalprice = data.deal_quantity * bills.unit_price;
+            } else if (data.qc_quantity) {
+                data.qc_totalprice = data.qc_quantity * bills.unit_price;
+            } else {
+                throw '计量清单数据有误';
+            }
+            const result = await this.update(data, {
+                tender_id: tenderId,
+                mid: mid,
+                bid: bid,
+            });
+            if (result) {
+                data.ledger_id = bid;
+                return data;
+            } else {
+                throw '更新数据失败';
+            }
+        }
+    }
+
+    return MeasureBills;
+};

+ 95 - 0
app/service/measure_pos.js

@@ -0,0 +1,95 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date 2018/6/29
+ * @version
+ */
+
+module.exports = app => {
+
+    class MeasurePos extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'measure_pos';
+        }
+
+        /**
+         * 查询中间计量下部位信息
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {uuid} mid - 计量id
+         * @returns {Promise<void>}
+         */
+        async getPosDetail(tenderId, mid) {
+            const sql = 'SELECT MP.`tender_id`, MP.`mid`, MP.`pid`, L.`code`, L.`name`, L.`full_path` ' +
+                'FROM ?? AS L, ?? AS MP ' +
+                'WHERE L.`tender_id` = ? and MP.`tender_id` = ? and MP.`mid` = ?' +
+                '    and MP.`pid` = L.`ledger_id`';
+            const sqlParam = [this.ctx.service.ledger.tableName, this.tableName, tenderId, tenderId, mid];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 查询中间计量下,某一条部位信息
+         * @param tenderId
+         * @param mid
+         * @param pid
+         * @returns {Promise<*>}
+         */
+        async getPosData(tenderId, mid, pid) {
+            const sql = 'SELECT MP.`tender_id`, MP.`mid`, MP.`pid`, L.`code`, L.`name`, L.`full_path` ' +
+                'FROM ?? AS L, ?? AS MP ' +
+                'WHERE L.`tender_id` = ? and MP.`tender_id` = ? and MP.`mid` = ? and MP.`pid` = ?' +
+                '    and MP.`pid` = L.`ledger_id`';
+            const sqlParam = [this.ctx.service.ledger.tableName, this.tableName, tenderId, tenderId, mid, pid];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         * 新增计量范围
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {uuid} mid - 计量id
+         * @param {Number} pid -- 计量范围id
+         * @returns {Promise<*>}
+         */
+        async addPos(tenderId, mid, pid) {
+            const result = await this.db.insert(this.tableName, {
+                tender_id: tenderId,
+                mid: mid,
+                pid: pid,
+                in_time: new Date(),
+                in_user: this.ctx.session.sessionUser.accountId,
+            });
+            return await this.getPosData(tenderId, mid, pid);
+        }
+
+        /**
+         * 删除计量范围
+         *
+         * @param {Number} tenderId - 标段id
+         * @param {uuid} mid - 计量id
+         * @param {Number} pid -- 计量范围id
+         * @returns {Promise<void>}
+         */
+        async removePos(tenderId, mid, pid) {
+            await this.db.delete(this.tableName, {
+                tender_id: tenderId,
+                mid: mid,
+                pid: pid,
+            });
+        }
+    }
+
+    return MeasurePos;
+};

+ 20 - 0
app/service/tree_sort.js

@@ -0,0 +1,20 @@
+'use strict';
+
+/**
+ * 树结构整理相关
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const Service = require('egg').Service;
+module.exports = app => {
+    class TreeSort extends Service {
+        fxExcelTreeSort(sheet) {
+
+        }
+    }
+
+    return TreeSort;
+}

+ 1 - 1
app/view/layout/layout.ejs

@@ -9,7 +9,7 @@
     <link rel="stylesheet" href="/public/css/bootstrap/bootstrap.min.css">
     <link rel="stylesheet" href="/public/css/main.css">
     <link rel="stylesheet" href="/public/css/font-awesome/font-awesome.min.css">
-    <link rel="stylesheet" href="/public/css/spreadjs/sheets/gc.spread.sheets.excel2013lightGray.10.0.1.css">
+    <link rel="stylesheet" href="/public/css/spreadjs/sheets/gc.spread.sheets.excelsmartcost.css">
     <link rel="stylesheet" href="/public/css/jquery-contextmenu/jquery.contextMenu.min.css">
     <!-- JS. -->
     <script src="/public/js/jquery/jquery-3.2.1.min.js"></script>

+ 1 - 3
app/view/layout/tender_list.ejs

@@ -1,7 +1,5 @@
 <div class="poj-name">
-    <span class="name">
-        <%- ctx.session.sessionUser.tenderName %>
-    </span>
+    <span class="name" id="tenderName"><%- ctx.session.sessionUser.tenderName %></span>
     <div class="btn-group">
         <a class=" btn" data-toggle="dropdown">
             <span class="fa fa-caret-down" data-toggle="tooltip" data-placement="bottom" title="切换标段"></span>

+ 18 - 49
app/view/measure/work.ejs

@@ -6,13 +6,10 @@
                     <a href="#add-mj" data-toggle="modal" data-target="#add-mj" class="btn btn-primary btn-sm">添加中间计量</a>
                 </div>
                 <div class="btn-group">
-                    <select class="form-control form-control-sm mt-0">
-                        <option>WJQR1-201710</option>
-                        <option>WJQR1-201709</option>
-                        <option>WJQR1-201708(第14期)</option>
-                        <option>WJQR1-201707(第14期)</option>
-                        <option>WJQR1-201706(第13期)</option>
-                        <option>WJQR1-201705(第12期)</option>
+                    <select class="form-control form-control-sm mt-0" id="measures">
+                        <% for (const measure of measures) { %>
+                        <option value="<%- measure.mid %>"><%- measure.code %></option>
+                        <% } %>
                     </select>
                 </div>
                 <div class="btn-group">
@@ -33,7 +30,7 @@
                     </div>
                 </div>
                 <div class="btn-group">
-                    <a href="#add-qd" data-toggle="modal" data-target="#add-qd" class="btn btn-success btn-sm">添加计量清单</a>
+                    <a href="javascript: void(0)" class="btn btn-success btn-sm" id="addMeasureBills">添加计量清单</a>
                 </div>
             </div>
             <div>
@@ -48,46 +45,7 @@
         <div class="c-header p-0 col-12"></div>
         <div class="row w-100 sub-content">
             <div class="c-body col-8">
-                <div class="sjs-height-1">
-                    <table class="table table-bordered">
-                        <thead>
-                        <tr>
-                            <th rowspan="2">1</th>
-                            <th rowspan="2">项目节编号</th>
-                            <th rowspan="2">清单编号</th>
-                            <th rowspan="2">名称</th>
-                            <th rowspan="2">单位</th>
-                            <th rowspan="2">单价</th>
-                            <th colspan="2">0号台帐合同</th>
-                            <th colspan="2">本次合同计量</th>
-                            <th colspan="3">本次数量变更计量</th>
-                            <th colspan="2">本次完成计量</th>
-                            <th colspan="2">截止本次合同计量</th>
-                            <th colspan="2">截止本次数量变更</th>
-                            <th colspan="2">截止本次完成计量</th>
-                            <th rowspan="2">图(册)号</th>
-                            <th rowspan="2">累计完成率(%)</th>
-                            <th rowspan="2">备注</th>
-                        </tr>
-                        <tr>
-                            <th>数量</th>
-                            <th>金额</th>
-                            <th>数量</th>
-                            <th>金额</th>
-                            <th>数量</th>
-                            <th>金额</th>
-                            <th>数量</th>
-                            <th>金额</th>
-                            <th>变更令</th>
-                            <th>数量</th>
-                            <th>金额</th>
-                            <th>数量</th>
-                            <th>金额</th>
-                            <th>数量</th>
-                            <th>金额</th>
-                        </tr>
-                        </thead>
-                    </table>
+                <div class="sjs-height-1" id="billsSpread">
                 </div>
             </div>
             <div class="c-body col">
@@ -301,4 +259,15 @@
             </ul>
         </div>
     </div>
-</div>
+</div>
+<script>
+    const measureSpreadSetting = JSON.parse('<%- measureSpreadSetting %>');
+</script>
+<script src="/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js"></script>
+<script>
+    GC.Spread.Sheets.LicenseKey = "559432293813965#A0y3iTOzEDOzkjMyMDN9UTNiojIklkI1pjIEJCLi4TPB9mM5AFNTd4cvZ7SaJUVy3CWKtWYXx4VVhjMpp7dYNGdx2ia9sEVlZGOTh7NRlTUwkWR9wEV4gmbjBDZ4ElR8N7cGdHVvEWVBtCOwIGW0ZmeYVWVr3mI0IyUiwCMzETN8kzNzYTM0IicfJye&Qf35VfiEzRwEkI0IyQiwiIwEjL6ByUKBCZhVmcwNlI0IiTis7W0ICZyBlIsIyNyMzM5ADI5ADNwcTMwIjI0ICdyNkIsIibj9SbvNmL4N7bjRnch56ciojIz5GRiwiI8+Y9sWY9QmZ0Jyp96uL9v6L0wap9biY9qiq95q197Wr9g+89iojIh94Wiqi";
+</script>
+<script src="/public/js/spreadjs_rela/spreadjs_zh<%= min %>.js"></script>
+<script src="/public/js/spreadjs_rela/extend_celltype.js"></script>
+<script src="/public/js/path_tree<%= min %>.js"></script>
+<script src="/public/js/measure_work.js"></script>

+ 126 - 60
app/view/measure/work_modal.ejs

@@ -9,68 +9,94 @@
                 <div class="form-group">
                     <label>中间计量编号<b class="text-danger">*</b></label>
                     <div class="input-group">
-                        <input type="text" class="form-control" placeholder="请输入中间计量编号" value="WJQR1-201711">
+                        <input type="text" class="form-control" placeholder="请输入中间计量编号" value="WJQR1-201711" id="mj-code">
                         <div class="input-group-append">
-                            <button class="btn btn-outline-secondary" type="button" title="自动编号"><i class="fa fa-repeat"></i></button>
+                            <button class="btn btn-outline-secondary" type="button" title="自动编号" id="autoCode"><i class="fa fa-repeat"></i></button>
                         </div>
-                    </div>
-                </div>
-                <div class="form-group">
-                    <label>中间计量编号<b class="text-danger">*</b></label>
-                    <div class="input-group">
-                        <input type="text" class="form-control is-invalid" placeholder="请输入中间计量编号" value="WJQR1-201711">
-                        <div class="input-group-append">
-                            <button class="btn btn-outline-secondary" type="button" title="自动编号"><i class="fa fa-repeat"></i></button>
-                        </div>
-                        <div class="invalid-feedback">您输入的编号已存在</div>
+                        <div class="invalid-feedback" style="display: none" id="mj-Hint">您输入的编号已存在</div>
                     </div>
                 </div>
                 <div class="form-group">
                     <label>计量月份</label>
-                    <input data-view="months" data-min-view="months" class="datepicker-here form-control" placeholder="按时间筛选" type="text" data-date-format="yyyy MM " data-language="zh">
+                    <input data-view="months" data-min-view="months" class="datepicker-here form-control" placeholder="按时间筛选" type="text" data-date-format="yyyy MM " data-language="zh" id="mj-date">
                 </div>
                 <div class="form-group">
                     <label>计量期</label>
                     <select class="form-control">
-                        <option>选填</option>
-                        <option>15</option>
-                        <option>14</option>
+                        <option disabled>选填</option>
                     </select>
                 </div>
             </div>
             <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal" id="addCancel">关闭</button>
+                <button type="button" class="btn btn-primary" id="addOk">添加</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--弹出添加计量清单-->
+<div class="modal fade" id="add-pos" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择计量范围</h5>
+            </div>
+            <div class="modal-body">
+                <div class="input-group mb-3">
+                    <input type="text" class="form-control" placeholder="输入项目节编号/名称 检索">
+                    <div class="input-group-append">
+                        <button class="btn btn-primary" type="button" id="search-pos">检索</button>
+                    </div>
+                </div>
+                <!--检索结果-->
+                <table class="table table-sm table-bordered" style="overflow: auto">
+                    <legend class="h5 ">检索结果</legend>
+                    <tr><th width="200">项目节编号</th><th>项目节名称</th><th width="90">添加</th></tr>
+                    <tbody id="searchedPosList">
+                    </tbody>
+                </table>
+                <table class="table table-sm table-bordered" id="measure-pos">
+                    <legend class="h5 ">已添加项目节</legend>
+                    <tr><th width="200">项目节编号</th><th>项目节名称</th><th width="90">移除</th></tr>
+                    <tbody id="measurePosList">
+                    </tbody>
+                </table>
+            </div>
+            <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary">添加</button>
+                <button type="button" class="btn btn-primary" id="chooseBills">选择计量清单</button>
             </div>
         </div>
     </div>
 </div>
 <!--弹出添加计量清单-->
 <div class="modal fade" id="add-qd" data-backdrop="static">
-    <div class="modal-dialog modal-lgx" role="document">
+    <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
             <div class="modal-header">
-                <h5 class="modal-title">添加计量清单</h5>
+                <h5 class="modal-title">选择计量清单</h5>
             </div>
             <div class="modal-body">
-                <div class="row">
-                    <div class="col-3">
-                        <div class="modal-height-500">
-                            侧栏
-                        </div>
-                    </div>
-                    <div class="col-9">
-                        <div class="modal-height-500">
-                            <table class="table table-bordered">
-                                <tr><th>1</th><th>项目节编号</th><th>清单编号</th><th>名称</th><th>单位</th><th>单价</th><th>操作</th></tr>
-                            </table>
-                        </div>
+                <div class="input-group mb-3">
+                    <input type="text" class="form-control" placeholder="输入清单编号/名称 检索">
+                    <div class="input-group-append">
+                        <button class="btn btn-primary" type="button" id="search-bills">检索</button>
                     </div>
                 </div>
+                <!--检索结果-->
+                <table class="table table-sm table-bordered">
+                    <legend>检索结果</legend>
+                    <tr><th>清单编号</th><th>名称</th><th>单位</th><th>单价</th><th width="90">选择</th></tr>
+                    <tbody id="searchedBillsList">
+                    <!--<tr><td>203-1-1</td><td><a href="#">挖土方</a></td><td>m3</td><td>7.54</td><td><input type="checkbox" checked> 已选择</td></tr>-->
+                    <!--<tr><td colspan="5"><span>└</span> 1-2-5 排水沟工程</td></tr>-->
+                    <!--<tr><td>203-1-1</td><td>挖土方</td><td>m3</td><td>7.54</td><td><input type="checkbox"></td></tr>-->
+                    </tbody>
+                </table>
             </div>
             <div class="modal-footer">
+                <button type="button" class="btn btn-primary" id="choosePos">选择计量范围</button>
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
-                <button type="button" class="btn btn-primary">添加</button>
             </div>
         </div>
     </div>
@@ -115,35 +141,52 @@
                 <h5 class="modal-title">设置中间计量编号规则</h5>
             </div>
             <div class="modal-body">
-                <h5>当前规则:WWUJ2018030001</h5>
-                <h5>
-                    <span class="badge badge-light" title="标段名">WWUJ-1 <a href="#" class="text-danger" title="移除"><i class="fa fa-remove"></i></a></span>
-                    <span class="badge badge-light" title="当前年月">201803 <a href="#" class="text-danger" title="移除"><i class="fa fa-remove"></i></a></span>
-                    <span class="badge badge-light" title="自动编号">0000 <a href="#" class="text-danger" title="移除"><i class="fa fa-remove"></i></a></span>
+                <h5 >
+                    当前规则:
+                    <span id="preview">
+                        <% if (codeRule && codeRule instanceof Array) { %>
+                            <% const preview = []; %>
+                            <% for (const rule of codeRule) { %>
+                                <% preview.push(rule.preview); %>
+                            <% } %>
+                            <%- preview.join(''); %>
+                        <% } %>
+                    </span>
+                </h5>
+                <h5 id="ruleParts">
+                    <% if (codeRule && codeRule instanceof Array) { %>
+                        <% for (const rule of codeRule) { %>
+                        <span class="badge badge-light" title="<%- ruleConst.ruleString[rule.rule_type]%>">
+                            <span>
+                                <%- rule.preview %>
+                            </span>
+                            <a href="javascript: void(0);" class="text-danger" title="移除"><i class="fa fa-remove"></i></a>
+                        </span>
+                        <% } %>
+                    <% } %>
                 </h5>
                 <h5>添加新规则组件</h5>
                 <div class="form-group">
                     <select class="form-control">
-                        <option>请选择组件</option>
-                        <option>标段名</option>
-                        <option>文本</option>
-                        <option>当前年月</option>
-                        <option>自增编号</option>
+                        <option disabled selected>请选择组件</option>
+                        <% for (const index in ruleConst.ruleString) { %>
+                            <option value="<%- index %>"><%- ruleConst.ruleString[index] %></option>
+                        <% } %>
                     </select>
                 </div>
-                <div class="form-group">
+                <div class="form-group" id="format" style="display: none">
                     <label>自动编号位数</label>
                     <input min="3" class="form-control" step="1" max="6" value="3" type="number">
                 </div>
-                <div class="form-group">
+                <div class="form-group" id="text" style="display: none">
                     <label>起始编号</label>
                     <input class="form-control" value="001" type="text">
                 </div>
-                <button class="btn btn-primary">添加组件</button>
+                <button class="btn btn-primary" id="addRule">添加组件</button>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
-                <button type="button" class="btn btn-primary">确定</button>
+                <button class="btn btn-primary" id="setRule">确定</button>
             </div>
         </div>
     </div>
@@ -189,31 +232,46 @@
                         </div>
                     </div>
                     <div class="tab-pane fade" id="rule" role="tabpanel">
-                        <h5>当前规则:WWUJ2018030001</h5>
-                        <h5>
-                            <span class="badge badge-light" title="标段名">WWUJ-1 <a href="#" class="text-danger" title="移除"><i class="fa fa-remove"></i></a></span>
-                            <span class="badge badge-light" title="当前年月">201803 <a href="#" class="text-danger" title="移除"><i class="fa fa-remove"></i></a></span>
-                            <span class="badge badge-light" title="自动编号">0000 <a href="#" class="text-danger" title="移除"><i class="fa fa-remove"></i></a></span>
+                        <h5 id="preview">
+                            当前规则:
+                            <% if (codeRule && codeRule instanceof Array) { %>
+                                <% const preview = []; %>
+                                <% for (const rule of codeRule) { %>
+                                    <% preview.push(rule.preview); %>
+                                <% } %>
+                                <%- preview.join(''); %>
+                            <% } %>
+                        </h5>
+                        <h5 id="ruleParts">
+                            <% if (codeRule && codeRule instanceof Array) { %>
+                                <% for (const rule of codeRule) { %>
+                                    <span class="badge badge-light" title="<%- ruleConst.ruleString[rule.rule_type]%>">
+                                        <span>
+                                            <%- rule.preview %>
+                                        </span>
+                                        <a href="javascript: void(0);" class="text-danger" title="移除"><i class="fa fa-remove"></i></a>
+                                    </span>
+                                <% } %>
+                            <% } %>
                         </h5>
                         <h5>添加新规则组件</h5>
                         <div class="form-group">
                             <select class="form-control">
-                                <option>请选择组件</option>
-                                <option>标段名</option>
-                                <option>文本</option>
-                                <option>当前年月</option>
-                                <option>自增编号</option>
+                                <option disabled selected>请选择组件</option>
+                                <% for (const index in ruleConst.ruleString) { %>
+                                <option value="<%- index %>"><%- ruleConst.ruleString[index] %></option>
+                                <% } %>
                             </select>
                         </div>
-                        <div class="form-group">
+                        <div class="form-group" id="format" style="display: none">
                             <label>自动编号位数</label>
                             <input min="3" class="form-control" step="1" max="6" value="3" type="number">
                         </div>
-                        <div class="form-group">
+                        <div class="form-group" id="text" style="display: none">
                             <label>起始编号</label>
                             <input class="form-control" value="001" type="text">
                         </div>
-                        <button class="btn btn-primary">添加组件</button>
+                        <button class="btn btn-primary" id="addRule">添加组件</button>
                     </div>
                 </div>
             </div>
@@ -333,4 +391,12 @@
             </div>
         </div>
     </div>
-</div>
+</div>
+<script>
+    const ruleType = <%- ruleType %>;
+    const ruleConst = JSON.parse('<%- JSON.stringify(ruleConst) %>');
+    let codeRule = JSON.parse('<%- JSON.stringify(codeRule) %>');
+</script>
+<script src="/public/js/moment/moment.min.js"></script>
+<script src="/public/js/datepicker/datepicker.min.js"></script>
+<script src="/public/js/datepicker/datepicker.zh.js"></script>

+ 1 - 0
package.json

@@ -15,6 +15,7 @@
     "egg-view-ejs": "^1.1.0",
     "gt3-sdk": "^2.0.0",
     "moment": "^2.20.1",
+    "node-uuid": "^1.4.8",
     "node-xlsx": "^0.12.0",
     "stream-to-array": "^2.3.0",
     "ueditor": "^1.2.3",

+ 27 - 0
test/app/service/ledger.test.js

@@ -1129,6 +1129,33 @@ describe('test/app/service/ledger.test.js', () => {
         assert(result3 && result3.toFixed(8) == 60.00007146);
     });
 
+    // 测试搜索类方法
+    it('test search', function* () {
+        const ctx = app.mockContext();
+
+        const result = yield ctx.service.ledger.search(testTenderId, {
+            value: app.mysql.escape('%1-3%'),
+            operate: 'Like',
+            fields: ['code', 'name'],
+        });
+        assert(result.length === 3);
+    });
+    it('test searchRange', function* () {
+        const ctx = app.mockContext();
+        const result = yield ctx.service.ledger.searchRange(testTenderId, {
+            value: app.mysql.escape('%1-3%'),
+            operate: 'Like',
+            fields: ['code', 'name'],
+        }, [{
+            type: 'And',
+            value: app.mysql.escape('1.13.%'),
+            operate: 'Like',
+            fields: ['full_path'],
+        }]);
+        assert(result.length === 1);
+        assert(result[0].code === '1-1-3');
+    });
+
     // it('test ImportExcelData', function* () {
     //     const ctx = app.mockContext();
     //