Kaynağa Gözat

中间计量

MaiXinRong 6 yıl önce
ebeveyn
işleme
c77d65fc9e

+ 90 - 1
app/controller/stage_controller.js

@@ -13,6 +13,7 @@ const auditConst = require('../const/audit');
 const spreadConst = require('../const/spread');
 const tenderConst = require('../const/tender');
 const measureType = tenderConst.measureType;
+const path = require('path');
 
 module.exports = app => {
     class StageController extends app.BaseController {
@@ -245,13 +246,14 @@ module.exports = app => {
                 } else if (data.loadType === 'all') {
                     const ledger = await ctx.service.ledger.getDataByTenderId(ctx.tender.id, -1);
                     const curStage = await ctx.service.stageBills.getLastestStageData(ctx.tender.id, ctx.stage.id);
+                    const stageDetail = await ctx.service.stageDetail.getLastestStageData(ctx.tender.id, ctx.stage.id);
                     ctx.body = {
                         err: 0,
                         msg: '',
                         data: {
                             ledger,
                             curStage,
-                            stageDetail: [],
+                            stageDetail,
                         }
                     };
                 }
@@ -287,6 +289,93 @@ module.exports = app => {
         }
 
         /**
+         * 中间计量,编辑中间计量数据
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async saveDetailData(ctx) {
+            try {
+                await this._getStage(ctx);
+                // 检查登录用户,是否可操作
+                if (ctx.session.sessionUser.accountId === ctx.stage.user_id) {
+                    if (ctx.stage.status === auditConst.flow.status.checking || ctx.stage.status === auditConst.flow.status.checked) {
+                        throw '该计量期当前您无权操作';
+                    }
+                } else {
+                    // 检查是否可修改
+                    //const curAuditor = await ctx.service.stageAudit.get
+                }
+
+                const data = JSON.parse(ctx.request.body.data);
+                const responseData = { err: 0, msg: '', data: {}, };
+                responseData.data = await ctx.service.stageDetail.saveDetailData(data);
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = {err: 1, msg: err.toString(), data: null};
+            }
+        }
+
+        /**
+         * 中间计量,编辑中间计量草图
+         * @param ctx
+         * @returns {Promise<void>}
+         */
+        async addCalcImage(ctx) {
+            try {
+                const stream = await ctx.getFileStream();
+                const create_time = Date.parse(new Date()) / 1000;
+                const fileInfo = path.parse(stream.filename);
+                const fileName = path.join('public/upload', this.ctx.tender.id.toString(), 'im', 'calcImg_' + create_time + fileInfo.ext);
+                await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, 'app', fileName));
+                ctx.body = {err: 0, msg: '', data: fileName};
+            } catch(err) {
+                this.log(err);
+                ctx.body = {err: 1, msg: err.toString(), data: null};
+            }
+        }
+
+        async mergeCalcImage(ctx) {
+            try {
+                await this._getStage(ctx);
+                // 检查登录用户,是否可操作
+                if (ctx.session.sessionUser.accountId === ctx.stage.user_id) {
+                    if (ctx.stage.status === auditConst.flow.status.checking || ctx.stage.status === auditConst.flow.status.checked) {
+                        throw '该计量期当前您无权操作';
+                    }
+                } else {
+                    // 检查是否可修改
+                    //const curAuditor = await ctx.service.stageAudit.get
+                }
+                const data = JSON.parse(ctx.request.body.data);
+
+                if (data.updateType === 'update') {
+                    const create_time = Date.parse(new Date()) / 1000;
+                    const fileName = path.join('public/upload', this.ctx.tender.id.toString(), 'im', 'calcImg_' + create_time + '.jpg');
+                    const base64Data = data.img.replace(/^data:image\/\w+;base64,/, "");
+                    const dataBuffer = new Buffer(base64Data, 'base64');
+                    await this.ctx.helper.saveBufferFile(dataBuffer, path.join(this.app.baseDir, 'app', fileName));
+                    data.calc_img = fileName;
+                    data.calc_img_org = JSON.stringify(data.imgInfo);
+                    delete data.updateType;
+                    delete data.img;
+                    delete data.imgInfo;
+                } else if (data.updateType === 'clear') {
+                    data.calc_img = null;
+                    data.calc_img_org = null;
+                    delete data.updateType;
+                }
+                await this.ctx.service.stageDetail.saveDetailData(data);
+                const imData = await ctx.service.stageDetail.getLastestImStageData(this.ctx.tender.id, this.ctx.stage.id, data.lid, data.uuid);
+                const responseData = {err: 0, msg: '', data: imData};
+                ctx.body = responseData;
+            } catch (err) {
+                this.log(err);
+                ctx.body = {err: 1, msg: err.toString(), data: null};
+            }
+        }
+
+        /**
          * 合同支付
          * @param ctx
          * @returns {Promise<void>}

+ 30 - 1
app/extend/helper.js

@@ -9,6 +9,7 @@
  */
 const zeroRange = 0.0000000001;
 const fs = require('fs');
+const path = require('path');
 const streamToArray = require('stream-to-array');
 const _ = require('lodash');
 module.exports = {
@@ -349,6 +350,34 @@ module.exports = {
     },
 
     /**
+     * 递归创建文件夹(fs.mkdirSync需要上一层文件夹已存在)
+     * @param pathName
+     * @returns {Promise<void>}
+     */
+    async recursiveMkdirSync(pathName) {
+        const upperPath = path.dirname(pathName);
+        if (!fs.existsSync(upperPath)) {
+            await this.recursiveMkdirSync(upperPath);
+        }
+        await fs.mkdirSync(pathName);
+    },
+
+    /**
+     * 字节 保存至 本地文件
+     * @param buffer - 字节
+     * @param fileName - 文件名
+     * @returns {Promise<void>}
+     */
+    async saveBufferFile(buffer, fileName) {
+        // 检查文件夹是否存在,不存在则直接创建文件夹
+        const pathName = path.dirname(fileName);
+        if (!fs.existsSync(pathName)) {
+            await this.recursiveMkdirSync(pathName);
+        }
+        await fs.writeFileSync(fileName, buffer);
+    },
+
+    /**
      * 将文件流的数据保存至本地文件
      * @param stream
      * @param fileName
@@ -360,7 +389,7 @@ module.exports = {
         // 转化为buffer
         const buffer = Buffer.concat(parts);
         // 写入文件
-        await fs.writeFileSync(fileName, buffer);
+        await this.saveBufferFile(buffer, fileName);
     },
 
     /**

+ 0 - 47
app/lib/stage_im.js

@@ -1,47 +0,0 @@
-'use strict';
-
-/**
- *
- *
- * @author Mai
- * @date
- * @version
- */
-
-const imType = require('../const/tender').imType;
-
-class Stage_IM {
-
-    async initData () {
-        this.ledgerData = await this.ctx.service.ledger.getDataByTenderId(this.ctx.tender.id);
-        this.stageBillsData = await this.ctx.service.stageBills.getAuditorStageData(this.ctx.tender.id, this.ctx.stage.id, this.ctx.stage.times, this.ctx.stage.order);
-        // to do
-        this.orgStageImData = [];
-    }
-
-    async buildTzImData () {
-
-    }
-
-    async buildTzImGatherData () {
-
-    }
-
-    async buildStageImData (ctx) {
-        this.ctx = ctx;
-        if (this.ctx.stage.im_type === imType.tz.value) {
-            if (this.ctx.stage.im_gather) {
-
-            } else {
-
-            }
-        } else if (this.ctx.stage.im_type === imType.zl.value) {
-            if (this.ctx.stage.im_gather) {
-
-            } else {
-
-            }
-        }
-    }
-}
-module.exports = Stage_IM;

BIN
app/public/images/sketch/1.png


BIN
app/public/images/sketch/2.png


+ 235 - 0
app/public/js/html2canvas/canvas2image.js

@@ -0,0 +1,235 @@
+/*
+ * Canvas2Image v0.1
+ * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk
+ * MIT License [http://www.opensource.org/licenses/mit-license.php]
+ */
+
+var Canvas2Image = (function() {
+
+	// check if we have canvas support
+	var bHasCanvas = false;
+	var oCanvas = document.createElement("canvas");
+	if (oCanvas.getContext("2d")) {
+		bHasCanvas = true;
+	}
+
+	// no canvas, bail out.
+	if (!bHasCanvas) {
+		return {
+			saveAsBMP : function(){},
+			saveAsPNG : function(){},
+			saveAsJPEG : function(){}
+		}
+	}
+
+	var bHasImageData = !!(oCanvas.getContext("2d").getImageData);
+	var bHasDataURL = !!(oCanvas.toDataURL);
+	var bHasBase64 = !!(window.btoa);
+
+	var strDownloadMime = "image/octet-stream";
+
+	// ok, we're good
+	var readCanvasData = function(oCanvas) {
+		var iWidth = parseInt(oCanvas.width);
+		var iHeight = parseInt(oCanvas.height);
+		return oCanvas.getContext("2d").getImageData(0,0,iWidth,iHeight);
+	}
+
+	// base64 encodes either a string or an array of charcodes
+	var encodeData = function(data) {
+		var strData = "";
+		if (typeof data == "string") {
+			strData = data;
+		} else {
+			var aData = data;
+			for (var i=0;i<aData.length;i++) {
+				strData += String.fromCharCode(aData[i]);
+			}
+		}
+		return btoa(strData);
+	}
+
+	// creates a base64 encoded string containing BMP data
+	// takes an imagedata object as argument
+	var createBMP = function(oData) {
+		var aHeader = [];
+	
+		var iWidth = oData.width;
+		var iHeight = oData.height;
+
+		aHeader.push(0x42); // magic 1
+		aHeader.push(0x4D); 
+	
+		var iFileSize = iWidth*iHeight*3 + 54; // total header size = 54 bytes
+		aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
+		aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
+		aHeader.push(iFileSize % 256); iFileSize = Math.floor(iFileSize / 256);
+		aHeader.push(iFileSize % 256);
+
+		aHeader.push(0); // reserved
+		aHeader.push(0);
+		aHeader.push(0); // reserved
+		aHeader.push(0);
+
+		aHeader.push(54); // dataoffset
+		aHeader.push(0);
+		aHeader.push(0);
+		aHeader.push(0);
+
+		var aInfoHeader = [];
+		aInfoHeader.push(40); // info header size
+		aInfoHeader.push(0);
+		aInfoHeader.push(0);
+		aInfoHeader.push(0);
+
+		var iImageWidth = iWidth;
+		aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
+		aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
+		aInfoHeader.push(iImageWidth % 256); iImageWidth = Math.floor(iImageWidth / 256);
+		aInfoHeader.push(iImageWidth % 256);
+	
+		var iImageHeight = iHeight;
+		aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
+		aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
+		aInfoHeader.push(iImageHeight % 256); iImageHeight = Math.floor(iImageHeight / 256);
+		aInfoHeader.push(iImageHeight % 256);
+	
+		aInfoHeader.push(1); // num of planes
+		aInfoHeader.push(0);
+	
+		aInfoHeader.push(24); // num of bits per pixel
+		aInfoHeader.push(0);
+	
+		aInfoHeader.push(0); // compression = none
+		aInfoHeader.push(0);
+		aInfoHeader.push(0);
+		aInfoHeader.push(0);
+	
+		var iDataSize = iWidth*iHeight*3; 
+		aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
+		aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
+		aInfoHeader.push(iDataSize % 256); iDataSize = Math.floor(iDataSize / 256);
+		aInfoHeader.push(iDataSize % 256); 
+	
+		for (var i=0;i<16;i++) {
+			aInfoHeader.push(0);	// these bytes not used
+		}
+	
+		var iPadding = (4 - ((iWidth * 3) % 4)) % 4;
+
+		var aImgData = oData.data;
+
+		var strPixelData = "";
+		var y = iHeight;
+		do {
+			var iOffsetY = iWidth*(y-1)*4;
+			var strPixelRow = "";
+			for (var x=0;x<iWidth;x++) {
+				var iOffsetX = 4*x;
+
+				strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX+2]);
+				strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX+1]);
+				strPixelRow += String.fromCharCode(aImgData[iOffsetY+iOffsetX]);
+			}
+			for (var c=0;c<iPadding;c++) {
+				strPixelRow += String.fromCharCode(0);
+			}
+			strPixelData += strPixelRow;
+		} while (--y);
+
+		var strEncoded = encodeData(aHeader.concat(aInfoHeader)) + encodeData(strPixelData);
+
+		return strEncoded;
+	}
+
+
+	// sends the generated file to the client
+	var saveFile = function(strData) {
+		document.location.href = strData;
+	}
+
+	var makeDataURI = function(strData, strMime) {
+		return "data:" + strMime + ";base64," + strData;
+	}
+
+	// generates a <img> object containing the imagedata
+	var makeImageObject = function(strSource) {
+		var oImgElement = document.createElement("img");
+		oImgElement.src = strSource;
+		return oImgElement;
+	}
+
+	var scaleCanvas = function(oCanvas, iWidth, iHeight) {
+		if (iWidth && iHeight) {
+			var oSaveCanvas = document.createElement("canvas");
+			oSaveCanvas.width = iWidth;
+			oSaveCanvas.height = iHeight;
+			oSaveCanvas.style.width = iWidth+"px";
+			oSaveCanvas.style.height = iHeight+"px";
+
+			var oSaveCtx = oSaveCanvas.getContext("2d");
+
+			oSaveCtx.drawImage(oCanvas, 0, 0, oCanvas.width, oCanvas.height, 0, 0, iWidth, iHeight);
+			return oSaveCanvas;
+		}
+		return oCanvas;
+	}
+
+	return {
+
+		saveAsPNG : function(oCanvas, bReturnImg, iWidth, iHeight) {
+			if (!bHasDataURL) {
+				return false;
+			}
+			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);
+			var strData = oScaledCanvas.toDataURL("image/png");
+			if (bReturnImg) {
+				return makeImageObject(strData);
+			} else {
+				saveFile(strData.replace("image/png", strDownloadMime));
+			}
+			return true;
+		},
+
+		saveAsJPEG : function(oCanvas, bReturnImg, iWidth, iHeight) {
+			if (!bHasDataURL) {
+				return false;
+			}
+
+			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);
+			var strMime = "image/jpeg";
+			var strData = oScaledCanvas.toDataURL(strMime);
+	
+			// check if browser actually supports jpeg by looking for the mime type in the data uri.
+			// if not, return false
+			if (strData.indexOf(strMime) != 5) {
+				return false;
+			}
+
+			if (bReturnImg) {
+				return makeImageObject(strData);
+			} else {
+				saveFile(strData.replace(strMime, strDownloadMime));
+			}
+			return true;
+		},
+
+		saveAsBMP : function(oCanvas, bReturnImg, iWidth, iHeight) {
+			if (!(bHasImageData && bHasBase64)) {
+				return false;
+			}
+
+			var oScaledCanvas = scaleCanvas(oCanvas, iWidth, iHeight);
+
+			var oData = readCanvasData(oScaledCanvas);
+			var strImgData = createBMP(oData);
+			if (bReturnImg) {
+				return makeImageObject(makeDataURI(strImgData, "image/bmp"));
+			} else {
+				saveFile(makeDataURI(strImgData, strDownloadMime));
+			}
+			return true;
+		}
+	};
+
+})();

Dosya farkı çok büyük olduğundan ihmal edildi
+ 7261 - 0
app/public/js/html2canvas/html2canvas.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 6 - 0
app/public/js/html2canvas/html2canvas.min.js


+ 72 - 20
app/public/js/path_tree.js

@@ -328,6 +328,45 @@ const createNewPathTree = function (type, setting) {
         getNodeKey (node) {
             return node[this.setting.id];
         };
+
+        /**
+         * 递归 设置节点展开状态
+         * @param {Array} nodes - 需要设置状态的节点
+         * @param {Object} parent - nodes的父节点
+         * @param {Function} checkFun - 判断节点展开状态的方法
+         * @private
+         */
+        _recursiveExpand(nodes, parent, checkFun) {
+            for (const node of nodes) {
+                node.expanded = checkFun(node);
+                node.visible = parent ? (parent.expanded && parent.visible) : true;
+                this._recursiveExpand(node.children, node, checkFun);
+            }
+        }
+        /**
+         * 自定义展开规则
+         * @param checkFun
+         */
+        expandByCustom(checkFun) {
+            this._recursiveExpand(this.children, null, checkFun);
+        }
+        /**
+         * 展开到第几层
+         * @param {Number} level - 展开层数
+         */
+        expandByLevel(level) {
+            // function recursiveExpand(nodes, parent) {
+            //     for (const node of nodes) {
+            //         node.expanded = node.level < level;
+            //         node.visible = parent ? (parent.expanded && parent.visible) : true;
+            //         recursiveExpand(node.children, node);
+            //     }
+            // }
+            // recursiveExpand(this.children);
+            this.expandByCustom(function (n) {
+                return n.level < level;
+            });
+        }
     }
 
     class MeasureTree extends BaseTree {
@@ -398,7 +437,38 @@ const createNewPathTree = function (type, setting) {
         };
     }
 
-    class LedgerTree extends BaseTree {
+    class FxTree extends BaseTree {
+
+        /**
+         * 检查节点是否是最底层项目节
+         * @param node
+         * @returns {boolean}
+         */
+        isLeafXmj(node) {
+            for (const child of node.children) {
+                if (!child.b_code || child.b_code === '') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * 展开至最底层项目节
+         */
+        expandToLeafXmj() {
+            const self = this;
+            this.expandByCustom(function (node) {
+                if (node.b_code && node.b_code !== '') {
+                    return false;
+                } else {
+                    return !self.isLeafXmj(node);
+                }
+            })
+        }
+    }
+
+    class LedgerTree extends FxTree {
 
         /**
          * 加载数据(动态),只加载不同部分
@@ -584,15 +654,6 @@ const createNewPathTree = function (type, setting) {
             }
         }
 
-        isLeafXmj(node) {
-            for (const child of node.children) {
-                if (child.code !== '') {
-                    return false;
-                }
-            }
-            return true;
-        }
-
         /**
          * 因为提交其他数据,引起的树结构数据更新,调用该方法
          *
@@ -728,7 +789,7 @@ const createNewPathTree = function (type, setting) {
         };
     }
 
-    class StageTree extends BaseTree {
+    class StageTree extends FxTree {
         /**
          * 构造函数
          */
@@ -824,15 +885,6 @@ const createNewPathTree = function (type, setting) {
             }
         }
 
-        isLeafXmj(node) {
-            for (const child of node.children) {
-                if (child.code !== '') {
-                    return false;
-                }
-            }
-            return true;
-        }
-
         /**
          * 提交数据至后端,返回的前端树结构应刷新的部分
          * StageTree仅有更新CurStage部分,不需要增删

+ 36 - 0
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -227,6 +227,7 @@ const SpreadJsObj = {
                         cell.locked(col.readOnly || sheet.zh_setting.readOnly || false).vAlign(1).hAlign(col.hAlign);
                     }
                 });
+                sheet.setRowVisible(i, data.visible);
             });
             // 设置列单元格格式
             sheet.zh_setting.cols.forEach(function (col, j) {
@@ -341,6 +342,41 @@ const SpreadJsObj = {
             this.endMassOperation(sheet);
         }
     },
+    /**
+     * 重新加载部分列数据
+     * @param {GC.Spread.Sheets.Worksheet} sheet
+     * @param {Array} cols
+     */
+    reLoadColsData: function (sheet, cols) {
+        const self = this;
+        const sortData = sheet.zh_dataType === 'tree' ? sheet.zh_tree.nodes : sheet.zh_data;
+
+        this.beginMassOperation(sheet);
+        try {
+            for (const iCol of cols) {
+                // 清空原单元格数据
+                sheet.clear(-1, iCol, -1, 1, spreadNS.SheetArea.viewport, spreadNS.StorageType.data);
+                const col = sheet.zh_setting.cols[iCol];
+
+                sortData.forEach(function (data, i) {
+                    // 设置值
+                    const cell = sheet.getCell(i, iCol);
+                    if (col.field !== '' && data[col.field]) {
+                        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).vAlign(1).hAlign(col.hAlign);
+                    }
+                    // 设置单元格格式
+                    if (col.formatter) {
+                        cell.formatter(col.formatter);
+                    }
+                });
+            }
+            this.endMassOperation(sheet);
+        } catch (err) {
+            this.endMassOperation(sheet);
+        }
+    },
     reLoadNodesData: function (sheet, nodes) {
         this.beginMassOperation(sheet);
         nodes = nodes instanceof Array ? nodes : [nodes];

+ 255 - 27
app/public/js/stage_detail.js

@@ -12,6 +12,31 @@ $(document).ready(() => {
     let gsSpread, gsTree;
     stageIm.init(stage, imType);
 
+    function getSelectDetailData() {
+        const rowIndex = parseInt($('#im-list').attr('rowIndex'));
+        const data = stageIm.getImData()[rowIndex];
+        return data;
+    }
+
+    function reLoadDetailData() {
+        const data = getSelectDetailData();
+        $('#edit-detail').show();
+        $('#save-detail').hide();
+        $('#cancel-detail').hide();
+        $('#bgl-code').val(data.bgl_code ? data.bgl_code : '');
+        $('#bw-name').val(data.bw ? data.bw : '');
+        $('#start-peg').val(data.start_peg ? data.start_peg : '');
+        $('#end-peg').val(data.end_peg ? data.end_peg : '');
+        $('#unit-name').val(data.jldy ? data.jldy : '');
+        $('#drawing-code').val(data.drawing_code ? data.drawing_code : '');
+        $('#calc-memo').val(data.calc_memo ? data.calc_memo : '');
+        if (data.calc_img) {
+            $('#calc-img').html('<img src="/' + data.calc_img + '" class="d-100" width="100%">');
+        } else {
+            $('#calc-img').html('');
+        }
+    }
+
     function reBuildImData() {
         const imData = stageIm.buildImData();
         const html = [];
@@ -35,6 +60,15 @@ $(document).ready(() => {
             html.push('</tr>');
         }
         $('#im-list').html(html.join(''));
+        $('tr:first', '#im-list').addClass('table-warning');
+        $('#im-list').attr('rowIndex', 0);
+        reLoadDetailData();
+        $('tr', '#im-list').click(function () {
+            $('tr.table-warning').removeClass('table-warning');
+            $(this).addClass('table-warning');
+            $('#im-list').attr('rowIndex', this.rowIndex - 1);
+            reLoadDetailData();
+        });
     }
 
     postData(window.location.pathname + '/load', { loadType: 'all' }, function (data) {
@@ -42,23 +76,23 @@ $(document).ready(() => {
         reBuildImData();
     });
 
-    let gatherComfirmPopover = {
+    let gatherConfirmPopover = {
         reBind: function (obj, eventName, fun) {
             obj.unbind(eventName);
             obj.bind(eventName, fun);
         },
         check: function (pos, hint, okCallback) {
-            const comfirmObj = $('#gather-confirm'), hintObj = $('#gather-confirm-hint');
+            const confirmObj = $('#gather-confirm'), hintObj = $('#gather-confirm-hint');
             const okObj = $('#gather-confirm-ok'), cancelObj = $('#gather-confirm-cancel');
             this.reBind(cancelObj, 'click', function () {
-                comfirmObj.hide();
+                confirmObj.hide();
             });
             this.reBind(okObj, 'click', function () {
                 okCallback();
-                comfirmObj.hide();
+                confirmObj.hide();
             });
             hintObj.text(hint);
-            comfirmObj.css("top", pos.y).css("left", pos.x).show();
+            confirmObj.css("top", pos.y).css("left", pos.x).show();
         }
     };
 
@@ -159,7 +193,7 @@ $(document).ready(() => {
                     if (!node.check) {
                         if (checkParent(node)) {
                             const rect = info.sheet.getCellRect(info.row, info.col);
-                            gatherComfirmPopover.check({
+                            gatherConfirmPopover.check({
                                 x: rect.x + rect.width / 2 + 25,
                                 y: rect.y + rect.height / 2 + 3,
                             }, '父项已勾选,继续将取消父项勾选。', function () {
@@ -176,7 +210,7 @@ $(document).ready(() => {
                             });
                         } else if (checkChildren(node)) {
                             const rect = info.sheet.getCellRect(info.row, info.col);
-                            gatherComfirmPopover.check({
+                            gatherConfirmPopover.check({
                                 x: rect.x + rect.width / 2 + 25,
                                 y: rect.y + rect.height / 2 + 3,
                             }, '子项已勾选,继续将取消子项勾选。', function () {
@@ -201,31 +235,21 @@ $(document).ready(() => {
                     }
                 }
             });
-        }
-        if (!gsTree) {
-            postData(window.location.pathname + '/load', { loadType: 'ledger' }, function (data) {
-                gsTree = createNewPathTree('base', {
-                    id: 'ledger_id',
-                    pid: 'ledger_pid',
-                    order: 'order',
-                    level: 'level',
-                    rootId: -1,
-                    keys: ['id', 'tender_id', 'ledger_id'],
-                });
-                const gatherNodes = stage.im_gather_node ? _.map(stage.im_gather_node.split(','), _.toNumber) : [];
-                for (const node of data) {
-                    node.check = gatherNodes.indexOf(node.id) !== -1;
-                }
-                gsTree.loadDatas(data);
-                SpreadJsObj.loadSheetData(gsSpread.getActiveSheet(), 'tree', gsTree);
-                SpreadJsObj.resetFieldReadOnly(gsSpread.getActiveSheet, 'check', !$('#im-gather-check')[0].checked);
-            });
+            const gsTree = stageIm.getGsTree();
+            const gatherNodes = stage.im_gather_node ? _.map(stage.im_gather_node.split(','), _.toNumber) : [];
+            for (const node of gsTree.datas) {
+                node.check = gatherNodes.indexOf(node.id) !== -1;
+            }
+            gsTree.expandByLevel(4);
+            SpreadJsObj.loadSheetData(gsSpread.getActiveSheet(), 'tree', gsTree);
+            SpreadJsObj.resetFieldReadOnly(gsSpread.getActiveSheet, 'check', !$('#im-gather-check')[0].checked);
         } else {
+            const gsTree = stageIm.getGsTree();
             const gatherNodes = stage.im_gather_node ? _.map(stage.im_gather_node.split(','), _.toNumber) : [];
             for (const node of gsTree.datas) {
                 node.check = gatherNodes.indexOf(node.id) !== -1;
             }
-            SpreadJsObj.resetFieldReadOnly(gsSpread.getActiveSheet(), 'check', !$('#im-gather-check')[0].checked);
+            SpreadJsObj.reLoadColsData(gsSpread.getActiveSheet(), [0]);
         }
     });
     // 提交 高级设置
@@ -247,4 +271,208 @@ $(document).ready(() => {
             $('#choose2').modal('hide');
         });
     });
+    // 编辑
+    $('#edit-detail').click(function () {
+        $(this).hide();
+        $('#save-detail').show();
+        $('#cancel-detail').show();
+        $('#bgl-code').removeAttr('readonly');
+        $('#bw-name').removeAttr('readonly');
+        $('#start-peg').removeAttr('readonly');
+        $('#end-peg').removeAttr('readonly');
+        $('#unit-name').removeAttr('readonly');
+        $('#drawing-code').removeAttr('readonly');
+        $('#calc-memo').removeAttr('readonly');
+    });
+    // 保存
+    $('#save-detail').click(() => {
+        function check(field, obj, org, update) {
+            const newValue = obj.val();
+            if (!org[field]) {
+                if (newValue !== '') {
+                    update[field] = newValue;
+                }
+            } else if (newValue !== org[field]){
+                update[field] = newValue;
+            }
+        }
+
+        const data = getSelectDetailData();
+        const updateData = {lid: data.lid};
+        if (data.uuid) {
+            updateData.uuid = data.uuid;
+        } else {
+            updateData.code = data.code;
+            updateData.name = data.name;
+            updateData.unit = data.unit;
+            updateData.unit_price = data.unit_price;
+        }
+        check('bgl_code', $('#bgl-code'), data, updateData);
+        check('bw', $('#bw-name'), data, updateData);
+        check('start_peg', $('#start-peg'), data, updateData);
+        check('end_peg', $('#end-peg'), data, updateData);
+        check('jldy', $('#unit-name'), data, updateData);
+        check('drawing_code', $('#drawing-code'), data, updateData);
+        check('calc_memo', $('#calc-memo'), data, updateData);
+        postData(window.location.pathname + '/save', updateData, function (result) {
+            _.assign(data, result);
+            $('#edit-detail').show();
+            $('#save-detail').hide();
+            $('#cancel-detail').hide();
+            $('#bgl-code').attr('readonly', '');
+            $('#bw-name').attr('readonly', '');
+            $('#start-peg').attr('readonly', '');
+            $('#end-peg').attr('readonly', '');
+            $('#unit-name').attr('readonly', '');
+            $('#drawing-code').attr('readonly', '');
+            $('#calc-memo').attr('readonly', '');
+        });
+    });
+    // 取消
+    $('#cancel-detail').click(() => {
+        $('#edit-detail').show();
+        $(this).hide();
+        $('#save-detail').hide();
+        $('#bgl-code').attr('readonly', '');
+        $('#bw-name').attr('readonly', '');
+        $('#start-peg').attr('readonly', '');
+        $('#end-peg').attr('readonly', '');
+        $('#unit-name').attr('readonly', '');
+        $('#drawing-code').attr('readonly', '');
+        $('#calc-memo').attr('readonly', '');
+        reLoadDetailData();
+    });
+
+
+    // 草图相关
+    // 移动图片
+    const moveImageItem = function (ev) {
+        const item = this;
+        const view = $('.img-view')[0];
+        let oEvent = ev;
+        // 浏览器有一些图片的默认事件,这里要阻止
+        oEvent.preventDefault();
+        let disX = oEvent.clientX - item.offsetLeft;
+        let disY = oEvent.clientY - item.offsetTop;
+        view.onmousemove = function (ev) {
+            oEvent = ev;
+            oEvent.preventDefault();
+            let x = oEvent.clientX -disX;
+            let y = oEvent.clientY -disY;
+
+            // 图形移动的边界判断
+            x = x <= 0 ? 0 : x;
+            x = x >= view.offsetWidth - item.offsetWidth ? view.offsetWidth - item.offsetWidth : x;
+            y = y <= 0 ? 0 : y;
+            y = y >= view.offsetHeight - item.offsetHeight ? view.offsetHeight - item.offsetHeight : y;
+            item.style.left = x + 'px';
+            item.style.top = y + 'px';
+        };
+        // 图形移出父盒子取消移动事件,防止移动过快触发鼠标移出事件,导致鼠标弹起事件失效
+        view.onmouseleave = function () {
+            view.onmousemove = null;
+            view.onmouseup = null;
+        };
+        // 鼠标弹起后停止移动
+        view.onmouseup=function() {
+            view.onmousemove = null;
+            view.onmouseup = null;
+        };
+    };
+    const removeImageItem = function () {
+        $(this).parent().remove();
+    };
+    // 加载草图组成
+    $('#edit-img').on('show.bs.modal', function () {
+        const data = getSelectDetailData();
+        const items = JSON.parse(data.calc_img_org) || [];
+        const html = [];
+        for (const item of items) {
+            const itemStyle = 'top:' + item.top + ';' + 'left:' + item.left + ';';
+            html.push('<div class="img-item" style="' + itemStyle + '">');
+            html.push('<div class="img-bar">');
+            html.push('<a href="javascript: void(0);" class="text-danger" title="删除"><i class="fa fa-remove"></i></a>');
+            html.push('</div>');
+            html.push('<img src="', item.src, '" id="draggable">');
+            html.push('</div>');
+        }
+        $('.img-view').html(html.join(''));
+        $('.img-item').mousedown(moveImageItem);
+        $('.img-bar').click(removeImageItem);
+    });
+    // 上传图片
+    $('#upload-img').click(function () {
+        $('#upload-img-file').trigger('click');
+    });
+    $('#upload-img-file').change(function () {
+        if ($(this).val()) {
+            const formData = new FormData();
+            formData.append('file', this.files[0]);
+            postDataWithFile(window.location.pathname + '/add-img', formData, function (result) {
+                const html = [];
+                html.push('<div class="img-item">');
+                html.push('<div class="img-bar">');
+                html.push('<a href="" class="text-danger" title="删除"><i class="fa fa-remove"></i></a>');
+                html.push('</div>');
+                html.push('<img src="', '/' + result, '" id="draggable">');
+                html.push('</div>');
+                $('.img-view').append(html.join(''));
+                $('.img-item').mousedown(moveImageItem);
+                $('.img-bar').click(removeImageItem);
+            });
+        }
+    });
+    // 保存草图修改结果
+    $('#edit-img-ok').click(function () {
+        // 记录上传的图片的信息
+        const items = $('.img-item');
+        const data = getSelectDetailData();
+        if (items.length > 0) {
+            const itemInfo = [];
+            for (const item of items) {
+                const itemData = {
+                    src: $('img', item).attr('src'),
+                    left: item.style.left,
+                    top: item.style.top,
+                };
+                itemInfo.push(itemData);
+            }
+            // 获取合并好的图片数据
+            const canvas = document.createElement('canvas');
+            const view = $('.img-view')[0];
+            canvas.height = view.clientHeight;
+            canvas.width = view.clientWidth;
+            const ctx = canvas.getContext('2d');
+            ctx.fillStyle = '#ffffff';
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+            for (const b of $('.img-item')) {
+                const pos = $(b).position();
+                const img = $('img', b)[0];
+                ctx.drawImage(img, pos.left, pos.top, img.width, img.height);
+            }
+            // 生成上传数据
+            const updateData = {updateType: 'update', lid: data.lid};
+            if (data.uuid) {
+                updateData.uuid = data.uuid;
+            } else {
+                updateData.code = data.code;
+                updateData.name = data.name;
+                updateData.unit = data.unit;
+                updateData.unit_price = data.unit_price;
+            }
+            updateData.img = canvas.toDataURL('image/jpeg');
+            updateData.imgInfo = itemInfo;
+            postData(window.location.pathname + '/merge-img', updateData, function (result) {
+                _.assign(data, result);
+                $('#calc-img').html('<img src="/' + data.calc_img + '" class="d-100" width="100%">');
+                $('#edit-img').modal('hide');
+            });
+        } else if (data.calc_img) {
+            postData(window.location.pathname + '/merge-img', {updateType: 'clear', lid: data.lid, uuid: data.uuid}, function (result) {
+                _.assign(data, result);
+                $('#calc-img').html('');
+                $('#edit-img').modal('hide');
+            });
+        }
+    });
 });

+ 109 - 155
app/public/js/stage_im.js

@@ -8,145 +8,6 @@
  * @version
  */
 
-// class Stage_IM {
-//
-//     /**
-//      *
-//      * @param stage
-//      * @param imType
-//      */
-//     constructor (stage, imType) {
-//         this.stage = stage;
-//         this.imType = imType;
-//         this.gsTreeSetting = {
-//             id: 'ledger_id',
-//             pid: 'ledger_pid',
-//             order: 'order',
-//             level: 'level',
-//             rootId: -1,
-//             keys: ['id', 'tender_id', 'ledger_id'],
-//         };
-//         this.gsTreeSetting.updateFields = ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp'];
-//         this.gsTreeSetting.calcFields = ['deal_tp', 'total_price', 'contract_tp', 'qc_tp', 'gather_tp', 'end_contract_tp', 'end_qc_tp', 'end_gather_tp'];
-//         this.gsTreeSetting.calcFun = function (node) {
-//             if (node.children && node.children.length === 0) {
-//                 node.end_contract_qty = _.toNumber(node.pre_contract_qty) + _.toNumber(node.contract_qty);
-//                 node.end_qc_qty = _.toNumber(node.pre_qc_qty) + _.toNumber(node.qc_qty);
-//                 node.end_gather_qty = _.toNumber(node.pre_gather_qty) + _.toNumber(node.gather_qty);
-//             }
-//             node.gather_tp = _.toNumber(node.contract_tp) + _.toNumber(node.qc_tp);
-//             node.end_contract_tp = _.toNumber(node.pre_contract_tp) + _.toNumber(node.contract_tp);
-//             node.end_qc_tp = _.toNumber(node.pre_qc_tp) + _.toNumber(node.qc_tp);
-//             node.end_gather_tp = _.toNumber(node.pre_gather_tp) + _.toNumber(node.gather_tp);
-//             if (checkZero(node.dgn_qty1)) {
-//                 node.dgn_price = _.round(node.total_price/node.dgn_qty1, 2);
-//             } else {
-//                 node.dgn_price = null;
-//             }
-//         };
-//         this.splitChar = '-';
-//     }
-//
-//     initCheck() {
-//         const gatherNodes = this.stage.im_gather_node ? _.map(this.stage.im_gather_node.split(','), _.toNumber) : [];
-//         for (const node of this.gsTree.datas) {
-//             node.check = gatherNodes.indexOf(node.id) !== -1;
-//         }
-//     }
-//
-//     initData (ledger, curStage, stageDetail) {
-//         this.gsTree = createNewPathTree('stage', this.gsTreeSetting);
-//         this.gsTree.loadDatas(ledger);
-//
-//         this.gsTree.loadCurStageData(curStage);
-//         // 根据设置 计算 台账树结构
-//         treeCalc.calculateAll(this.gsTree);
-//
-//         this.initCheck();
-//         this.stageDetail = stageDetail;
-//     }
-//
-//     getNumberFormat(num, length) {
-//         let s = '0000000000';
-//         s = s + count;
-//         return s.substr(s.length - length);
-//     }
-//
-//     getNewImCode () {
-//         return this.pre + this.getNumberFormat(this.stage.order, 2) + this.splitChar + this.getNumberFormat(this.ImData.length + 1, 3);
-//     }
-//
-//     getFbfx (node) {
-//         // to do
-//         return node.name;
-//     }
-//
-//     getJldy (node) {
-//         return '';
-//     }
-//
-//     getPeg (node) {
-//         return '';
-//     }
-//
-//     buildTzImData (nodes) {
-//         if (!nodes || nodes.length === 0) { return; }
-//
-//         for (const node of nodes) {
-//             if (!node.code || node.code === '') { continue; }
-//
-//             if (this.gsTree.isLeafXmj(node)) {
-//                 if (node.gather_tp) {
-//                     const im = {
-//                         code: node.code,
-//                         tp: node.gather_tp,
-//                         im_code: this.getNewImCode(),
-//                         fbfx: this.getFbfx(node),
-//                         jldy: this.getJldy(node),
-//                         peg: this.getPeg(node),
-//                         drawing_code: this.getDrawingCode(node),
-//                     };
-//                     this.ImData.push(im);
-//                 }
-//             } else {
-//                 this.buildTzImData(node.children);
-//             }
-//         }
-//     }
-//
-//     buildTzImGatherData (nodes) {
-//
-//     }
-//
-//     buildZlImData (nodes) {
-//
-//     };
-//
-//     buildZlImGatherData (nodes) {
-//
-//     };
-//
-//     buildImData () {
-//         this.ImData = [];
-//         this.pre = (this.stage.im_pre && this.stage.im_pre !== '') ? this.stage.im_pre + this.splitChar : '';
-//         if (this.stage.im_type === imType.tz.value) {
-//             if (this.ctx.stage.im_gather) {
-//                 this.initCheck();
-//                 this.buildTzImGatherData(this.gsTree.children);
-//             } else {
-//                 this.buildTzImData(this.gsTree.children);
-//             }
-//         } else if (this.stage.im_type === imType.zl.value) {
-//             if (this.stage.im_gather) {
-//                 this.initCheck();
-//                 this.buildZlImGatherData(this.gsTree.children);
-//             } else {
-//                 this.buildZlImData(this.gsTree.children);
-//             }
-//         }
-//     }
-// }
-
 const stageIm = (function () {
     const splitChar = '-';
     let stage, imType, details, ImData, pre;
@@ -204,49 +65,125 @@ const stageIm = (function () {
         details = stageDetail;
     }
 
+    /**
+     * 整数前补零
+     * @param {Number} num - 数字
+     * @param {Number} length - 总位数
+     * @returns {*}
+     */
     function getNumberFormat(num, length) {
         let s = '0000000000';
         s = s + num;
         return s.substr(s.length - length);
     }
 
+    /**
+     * 获取新 计量编号
+     * @returns {string}
+     */
     function getNewImCode () {
         return pre + getNumberFormat(stage.order, 2) + splitChar + getNumberFormat(ImData.length + 1, 3);
     }
 
-    function getFbfx (node) {
-        // to do
+    function getNodeByLevel(node, level) {
+        let cur = node;
+        while (cur && cur.level > level) {
+            cur = gsTree.getParent(cur);
+        }
+        return cur;
+    }
+
+    function getFbfx (node, pegNode) {
+        if (pegNode) {
+            const subPegNode = getNodeByLevel(node, pegNode.level + 1);
+            return subPegNode.id === pegNode.id ? pegNode.name : pegNode.name + '-' + subPegNode.name;
+        } else {
+            if (node.level < 3) {
+                return node.name;
+            } else {
+                const l3Node = this.getNodeByLevel(node, 3);
+                const l4Node = this.getNodeByLevel(node, 4);
+                return l3Node.id === l4Node.id ? l3Node.name : l3Node.name + '-' + l4Node.name;
+            }
+        }
         return node.name;
     }
 
-    function getJldy (node) {
-        return '';
+    function CheckPeg(text) {
+        const pegReg = /[kK][0-9][++][0-9]{3}/;
+        return pegReg.test(text);
     }
 
-    function getPeg (node) {
-        return '';
+    function getPegNode (node) {
+        if (node) {
+            if (CheckPeg(node.name)) {
+                return node;
+            } else {
+                const parent = gsTree.getParent(node);
+                return parent ? getPegNode(parent) : null;
+            }
+        }
     }
 
     function getDrawingCode(node) {
-        return '';
+        if (!node) {
+            return '';
+        } else if (node.drawing_code) {
+            return node.drawing_code;
+        } else {
+            const parent = gsTree.getParent(node);
+            return parent ? getDrawingCode(parent) : '';
+        }
+    }
+
+    function checkCustomDetail(im) {
+        const fields = ['uuid', 'doc_code', 'bgl_code', 'start_peg', 'end_peg', 'bw', 'jldy', 'drawing_code', 'calc_memo', 'calc_img'];
+        const cd = _.find(details, function (d) {
+            return im.lid === d.lid &&
+                (!im.code || im.code === d.code) &&
+                (!im.name || im.name === d.name) &&
+                (!im.unit || im.unit === d.unit);
+        });
+        if (cd) {
+            _.assignInWith(im, cd, function (oV, sV, key) {
+                return fields.indexOf(key) > -1 && sV ? sV : oV;
+            });
+            console.log(im);
+        }
     }
 
+    /**
+     * 生成 0号台账 中间计量数据
+     * @param {Object} node - 生成中间计量表的节点
+     */
     function generateTzImData (node) {
         if (node.gather_tp) {
+            const peg = getPegNode(node);
             const im = {
                 lid: node.id,
                 code: node.code,
                 jl: node.gather_tp,
                 im_code: getNewImCode(),
-                fbfx: getFbfx(node),
-                jldy: getJldy(node),
-                peg: getPeg(node),
+                fbfx: getFbfx(node, peg),
+                peg: peg ? peg.name : '',
                 drawing_code: getDrawingCode(node),
             };
+            if (stage.im_gather && node.check) {
+                im.bw = node.name;
+            } else {
+                im.jldy = node.name;
+                // to do
+                im.bw = node.name
+            }
+            checkCustomDetail(im);
             ImData.push(im);
         }
     }
 
+    /**
+     * 生成 总量控制 中间计量数据
+     * @param {Object} node - 生成中间计量表的节点
+     */
     function generateZlImData (node) {
         const nodeImData = [], posterity = gsTree.getPosterity(node);
         for (const p of posterity) {
@@ -257,6 +194,7 @@ const stageIm = (function () {
                 return d.code === p.b_code && p.name === d.name && p.unit === d.unit && checkZero(p.unit_price - d.unit_price);
             });
             if (!im) {
+                const peg = getPegNode(node);
                 im = {
                     lid: node.lid,
                     code: p.b_code,
@@ -265,18 +203,28 @@ const stageIm = (function () {
                     unit_price: p.unit_price,
                     jl: 0,
                     im_code: getNewImCode(),
-                    fbfx: getFbfx(node),
-                    jldy: getJldy(node),
-                    peg: getPeg(node),
+                    fbfx: getFbfx(node, peg),
+                    peg: peg ? peg.name : '',
                     drawing_code: getDrawingCode(node),
+                    bw: node.name,
                 };
+                if (stage.im_gather && node.check) {
+
+                } else {
+                    im.jldy = p.name;
+                }
                 nodeImData.push(im);
+                checkCustomDetail(im);
                 ImData.push(im);
             }
             im.jl = _.add(im.jl, p.gather_qty);
         }
     }
 
+    /**
+     * 递归 生成中间计量表
+     * @param {Array} nodes
+     */
     function recursiveBuildImData (nodes) {
         if (!nodes || nodes.length === 0) { return; }
         for (const node of nodes) {
@@ -292,24 +240,30 @@ const stageIm = (function () {
         }
     }
 
+    /**
+     * 生成 中间计量表 全部数据
+     * @returns {Array}
+     */
     function buildImData () {
+        // 初始化
         ImData = [];
         pre = (stage.im_pre && stage.im_pre !== '') ? stage.im_pre + splitChar : '';
         if (stage.im_gather) {
             initCheck();
         }
+        // 生成数据
         recursiveBuildImData(gsTree.children);
         return ImData;
     }
 
     return {
-        getGsTree: function () {
-            return gsTree;
-        },
         init,
         initCheck,
         loadData,
         buildImData,
+        getGsTree: function () {
+            return gsTree;
+        },
         getImData: function () {
             return ImData;
         },

+ 3 - 0
app/router.js

@@ -118,6 +118,9 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/detail/build', sessionAuth, tenderCheck, 'stageController.buildDetailData');
     app.post('/tender/:id/measure/stage/:order/detail/adv', sessionAuth, tenderCheck, 'stageController.setAdvancedConfig');
     app.post('/tender/:id/measure/stage/:order/detail/load', sessionAuth, tenderCheck, 'stageController.loadDetailRelaData');
+    app.post('/tender/:id/measure/stage/:order/detail/save', sessionAuth, tenderCheck, 'stageController.saveDetailData');
+    app.post('/tender/:id/measure/stage/:order/detail/add-img', sessionAuth, tenderCheck, 'stageController.addCalcImage');
+    app.post('/tender/:id/measure/stage/:order/detail/merge-img', sessionAuth, tenderCheck, 'stageController.mergeCalcImage');
     // 合同支付
     app.get('/tender/:id/measure/stage/:order/pay', sessionAuth, tenderCheck, 'stageController.pay');
     // 变更令

+ 105 - 0
app/service/stage_detail.js

@@ -0,0 +1,105 @@
+'use strict';
+
+/**
+ * 期计量 中间计量 详细数据
+ *
+ * @author Mai
+ * @date 2019/2/13
+ * @version
+ */
+
+module.exports = app => {
+    class StageDetail extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_detail';
+        }
+
+        /**
+         * 查询最后审核人数据
+         * @param {Number} tid - 标段id
+         * @param {Number} sid - 期id
+         * @param {Number|Array} lid - 台账节点id(可以为空)
+         * @returns {Promise<*>}
+         */
+        async getLastestStageData(tid, sid, lid) {
+            const lidSql = lid ? ' And Bills.lid in (?)' : '';
+            const sql = 'SELECT * FROM ' + this.tableName + ' As Bills ' +
+                '  INNER JOIN ( ' +
+                '    SELECT MAX(`times`) As `times`, MAX(`order`) As `order`, `lid`, `uuid` From ' + this.tableName +
+                '      GROUP BY `lid`, `uuid`' +
+                '  ) As MaxFilter ' +
+                '  ON Bills.times = MaxFilter.times And Bills.order = MaxFilter.order And Bills.lid = MaxFilter.lid And Bills.uuid = MaxFilter.uuid' +
+                '  WHERE Bills.tid = ? And Bills.sid = ?' + lidSql;
+            const sqlParam = [tid, sid];
+            if (!lid) {
+                return await this.db.query(sql, sqlParam);
+            } else if (lid instanceof Array) {
+                sqlParam.push(lid.join(', '));
+                return await this.db.query(sql, sqlParam);
+            } else {
+                sqlParam.push(lid);
+                return await this.db.queryOne(sql, sqlParam);
+            }
+        }
+
+        /**
+         * 获取 中间计量 用户最新输入数据
+         * @param {Number} tid - 标段id
+         * @param {Number} sid - 期id
+         * @param {Number} lid - 关联的台账节点id
+         * @param {String} uuid - 中间计量单 唯一id
+         * @returns {Promise<*>}
+         */
+        async getLastestImStageData(tid, sid, lid, uuid) {
+            const sql = 'SELECT * FROM ' + this.tableName + ' As Bills ' +
+                '  INNER JOIN ( ' +
+                '    SELECT MAX(`times`) As `times`, MAX(`order`) As `order`, `lid`, `uuid` From ' + this.tableName +
+                '      WHERE lid = ? And uuid = ?' +
+                '      GROUP BY `lid`, `uuid`' +
+                '  ) As MaxFilter ' +
+                '  ON Bills.times = MaxFilter.times And Bills.order = MaxFilter.order And Bills.lid = MaxFilter.lid And Bills.uuid = MaxFilter.uuid' +
+                '  WHERE Bills.tid = ? And Bills.sid = ?';
+            const sqlParam = [lid, uuid, tid, sid];
+            return await this.db.queryOne(sql, sqlParam);
+        }
+
+        /**
+         *
+         * @param data
+         * @returns {Promise<void>}
+         */
+        async saveDetailData(data) {
+            if (data.uuid) {
+                const org = await this.getLastestImStageData(this.ctx.tender.id, this.ctx.stage.id, data.lid, data.uuid);
+                const order = this.ctx.stage.curAuditor ? this.ctx.stage.curAuditor.order : 0;
+                if (org.times === this.ctx.stage.times && org.order === order) {
+                    const newData = this._.assign(org, data);
+                    await this.db.update(this.tableName, newData);
+                } else {
+                    data.uuid = org.uuid;
+                    data.tid = this.ctx.tender.id;
+                    data.sid = this.ctx.stage.id;
+                    data.times = this.ctx.stage.times;
+                    data.order = order;
+                    await this.db.insert(this.tableName, data);
+                }
+            } else {
+                data.uuid = this.uuid.v4();
+                data.tid = this.ctx.tender.id;
+                data.sid = this.ctx.stage.id;
+                data.times = this.ctx.stage.times;
+                data.order = this.ctx.stage.curAuditor ? this.ctx.stage.curAuditor.order : 0;
+                await this.db.insert(this.tableName, data);
+            }
+        }
+    }
+
+    return StageDetail;
+};

+ 12 - 12
app/view/stage/detail.ejs

@@ -56,44 +56,44 @@
                     <div class="tab-pane active" id="zhongjian">
                         <div class="sjs-sh-1" style="overflow: auto;">
                             <div class="d-flex justify-content-end mt-1 mr-1">
-                                <a href="#" class="btn btn-sm btn-outline-primary">编辑</a>
-                                <a href="#" class="btn btn-sm btn-outline-success mr-1">保存</a>
-                                <a href="#" class="btn btn-sm btn-outline-secondary">取消</a>
+                                <a href="#" class="btn btn-sm btn-outline-primary" id="edit-detail">编辑</a>
+                                <a href="#" class="btn btn-sm btn-outline-success mr-1" id="save-detail">保存</a>
+                                <a href="#" class="btn btn-sm btn-outline-secondary" id="cancel-detail">取消</a>
                             </div>
                             <div class="form-group">
                                 <label>变更令号:</label>
-                                <input class="form-control form-control-sm" type="text" readonly="">
+                                <input class="form-control form-control-sm" type="text" readonly="" id="bgl-code">
                             </div>
                             <div class="form-group">
                                 <label>部位:</label>
-                                <input class="form-control form-control-sm" type="text" readonly="" value="左3#中横梁">
+                                <input class="form-control form-control-sm" type="text" readonly="" value="" id="bw-name">
                             </div>
                             <div class="form-group">
                                 <label>起始桩号:</label>
-                                <input class="form-control form-control-sm" type="text" readonly="" value="K170+170.0">
+                                <input class="form-control form-control-sm" type="text" readonly="" value="" id="start-peg">
                             </div>
                             <div class="form-group">
                                 <label>终止桩号:</label>
-                                <input class="form-control form-control-sm" type="text" readonly="" value="K170+170.0">
+                                <input class="form-control form-control-sm" type="text" readonly="" value="" id="end-peg">
                             </div>
                             <div class="form-group">
                                 <label>计量单元:</label>
-                                <input class="form-control form-control-sm" type="text" readonly="" value="光圆钢筋(HPB235、HPB300)">
+                                <input class="form-control form-control-sm" type="text" readonly="" value="" id="unit-name">
                             </div>
                             <div class="form-group">
                                 <label>图号:</label>
-                                <input class="form-control form-control-sm" type="text" readonly="" value="XL-1-13(通用图1)">
+                                <input class="form-control form-control-sm" type="text" readonly="" value="" id="drawing-code">
                             </div>
                             <div class="form-group">
                                 <label>计算式说明:</label>
-                                <textarea class="form-control" readonly=""></textarea>
+                                <textarea class="form-control" readonly="" id="calc-memo"></textarea>
                             </div>
                             <div class="form-group">
                                 <div class="d-flex justify-content-between my-3">
                                     <label>计算草图:</label>
-                                    <a href="#edit-img" data-toggle="modal" data-target="#edit-img">添加草图</a>
+                                    <a href="#edit-img" data-toggle="modal" data-target="#edit-img" id="modify-img">添加草图</a>
                                 </div>
-                                <p><img src="img/sketch/2.png" class="d-100"></p>
+                                <p id="calc-img"><img src="" class="d-100" width="100%"></p>
                             </div>
                         </div>
                     </div>

+ 5 - 14
app/view/stage/detail_modal.ejs

@@ -79,25 +79,16 @@
                 <h5 class="modal-title">添加草图</h5>
             </div>
             <div class="modal-body">
-                <p><a href="" class="btn btn-outline-primary btn-sm">上传图片</a></p>
+                <p>
+                    <input type="file" id="upload-img-file" style="display: none;">
+                    <a href="javascript: void(0);" class="btn btn-outline-primary btn-sm" id="upload-img">上传图片</a>
+                </p>
                 <div class="img-view">
-                    <div class="img-item">
-                        <div class="img-bar">
-                            <a href="" class="text-danger" title="删除"><i class="fa fa-remove"></i></a>
-                        </div>
-                        <img src="img/sketch/1.png" id="draggable">
-                    </div>
-                    <div class="img-item" style="left:300px;">
-                        <div class="img-bar">
-                            <a href="" class="text-danger" title="删除"><i class="fa fa-remove"></i></a>
-                        </div>
-                        <img src="img/sketch/2.png" id="draggable">
-                    </div>
                 </div>
             </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="edit-img-ok">确认</button>
             </div>
         </div>
     </div>

+ 2 - 0
config/web.js

@@ -98,6 +98,8 @@ const JsFiles = {
             detail: {
                 files: [
                     "/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js",
+                    "/public/js/html2canvas/html2canvas.min.js",
+                    "/public/js/html2canvas/canvas2image.js",
                 ],
                 mergeFiles: [
                     "/public/js/spreadjs_rela/spreadjs_zh.js",