Forráskód Böngészése

动态投资,项目信息

MaiXinRong 1 éve
szülő
commit
37f3d2248d

+ 7 - 1
app/base/base_controller.js

@@ -35,7 +35,13 @@ class BaseController extends Controller {
                 }
             }
         }
-        if (ctx.controllerName === 'sp') ctx.menu = menuList.file;
+        if (ctx.controllerName === 'sp') {
+            if (ctx.url.indexOf('file') > 0) {
+                ctx.menu = menuList.file;
+            } else {
+                ctx.menu = menuList.budget;
+            }
+        }
         menuList.datacollect.display = ctx.session && ctx.session.sessionProject ? ctx.session.sessionProject.showDataCollect : false;
         menuList.payment.display = ctx.session && ctx.session.sessionProject ? ctx.session.sessionProject.showPayment : false;
         if (ctx.session && ctx.session.sessionProject && ctx.session.sessionProject.page_show && ctx.session.sessionProject.page_show.openManagement) {

+ 37 - 1
app/controller/sub_proj_controller.js

@@ -170,7 +170,43 @@ module.exports = app => {
                 await ctx.service.subProjPermission.savePermission(data.id, data.member);
                 ctx.body = { err: 0, msg: '', data: '' };
             } catch (err) {
-                console.log(err);
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存数据失败');
+            }
+        }
+
+        async info(ctx) {
+            try {
+                const info = await this.ctx.service.subProjInfo.getInfo(ctx.subProject.id);
+                const renderData = {
+                    info,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.subProject.info),
+                };
+                await this.layout('sub_proj/info.ejs', renderData);
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async dataIndex(ctx) {
+            try {
+                const info = await this.ctx.service.subProjInfo.getInfo(ctx.subProject.id);
+                const renderData = {
+                    info,
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.subProject.data),
+                };
+                await this.layout('sub_proj/data_index.ejs', renderData);
+            } catch (err) {
+                ctx.log(err);
+            }
+        }
+
+        async saveInfo(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const result = await ctx.service.subProjInfo.saveInfo(ctx.subProject.id, data);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch(err) {
                 ctx.log(err);
                 ctx.ajaxErrorBody(err, '保存数据失败');
             }

+ 3 - 1
app/public/js/budget_list.js

@@ -62,8 +62,10 @@ $(document).ready(() => {
                     html.push(`<td></td>`);
                 } else {
                     html.push(`<td>`);
+                    if (node.permission.indexOf(2) >= 0)
+                        html.push(`<a href="/sp/${node.id}/info" class="btn btn-outline-primary btn-sm" data-target="#project-info" target="_blank" name="info">项目概况</a>`);
                     if (node.manage_permission.indexOf(1) >= 0)
-                        html.push('<button class="btn btn-outline-primary btn-sm" data-target="#select-rela" name="del" onclick="showModal(this);">关联标段</button>');
+                        html.push('<button class="btn btn-outline-primary btn-sm ml-1" data-target="#select-rela" name="del" onclick="showModal(this);">关联标段</button>');
                     html.push('</td>');
                 }
                 return html.join('');

+ 159 - 0
app/public/js/sp_data.js

@@ -0,0 +1,159 @@
+$(document).ready(() => {
+    autoFlashHeight();
+    class QtyObj {
+        constructor(setting) {
+            this.spread = SpreadJsObj.createNewSpread(setting.obj);
+            this.sheet = this.spread.getActiveSheet();
+            SpreadJsObj.initSheet(this.sheet, setting.spreadSetting);
+            this.data = setting.data;
+            this.type = setting.type;
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Data, this.data);
+
+            const self = this;
+            this.sheet.bind(spreadNS.Events.EditEnded, function(e, info) {
+                if (!info.sheet.zh_setting || !info.sheet.zh_data) return;
+
+                const node = info.sheet.zh_data[info.row];
+                if (!node) return;
+
+                const col = info.sheet.zh_setting.cols[info.col];
+                const data = {};
+
+                data.id = node.id;
+
+                const oldValue = node ? node[col.field] : null;
+                const newValue = trimInvalidChar(info.editingText);
+                if (oldValue == info.editingText || ((!oldValue || oldValue === '') && (newValue === ''))) {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                    return;
+                }
+                data[col.field] = newValue;
+
+                postData('info/save', { type: self.type, updateData: [data]}, function (result) {
+                    self.loadUpdateData(result);
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }, function () {
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                });
+            });
+            this.sheet.bind(spreadNS.Events.ClipboardPasting, function(e, info) {
+                const setting = info.sheet.zh_setting, sortData = info.sheet.zh_data;
+                info.cancel = true;
+
+                if (!setting || !sortData) return;
+                const pasteData = info.pasteData.html
+                    ? SpreadJsObj.analysisPasteHtml(info.pasteData.html)
+                    : (info.pasteData.text === ''
+                        ? SpreadJsObj.Clipboard.getAnalysisPasteText()
+                        : SpreadJsObj.analysisPasteText(info.pasteData.text));
+
+                const uDatas = [];
+                for (let iRow = 0; iRow < info.cellRange.rowCount; iRow++) {
+                    const curRow = info.cellRange.row + iRow;
+                    const node = sortData[curRow];
+
+                    let bPaste = false;
+                    const data = {};
+                    for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
+                        const curCol = info.cellRange.col + iCol;
+                        const colSetting = setting.cols[curCol];
+                        const value = trimInvalidChar(pasteData[iRow][iCol]);
+
+                        if (colSetting.type === 'Number') {
+                            const num = _.toNumber(value);
+                            if (num) {
+                                data[colSetting.field] = num;
+                                bPaste = true;
+                            }
+                        }
+                    }
+                    if (bPaste) {
+                        if (node) {
+                            data.id = node.id;
+                            uDatas.push(data);
+                        }
+                    }
+                }
+                if (uDatas.length > 0) {
+                    postData('info/save', { type: self.type, updateData: uDatas}, function (result) {
+                        self.loadUpdateData(result);
+                        SpreadJsObj.reLoadSheetData(info.sheet);
+                    });
+                } else {
+                    SpreadJsObj.reLoadSheetData(info.sheet);
+                }
+            });
+            SpreadJsObj.addDeleteBind(this.spread, function(sheet) {
+                if (!sheet.zh_setting || !sheet.zh_data) return;
+
+                const sortData = sheet.zh_data;
+                const datas = [];
+                const sels = sheet.getSelections();
+                if (!sels || !sels[0]) return;
+
+                for (let iRow = sels[0].row; iRow < sels[0].row + sels[0].rowCount; iRow++) {
+                    let bDel = false;
+                    const node = sortData[iRow];
+                    if (node) {
+                        const data = { id: node.id };
+                        for (let iCol = sels[0].col; iCol < sels[0].col + sels[0].colCount; iCol++) {
+                            const style = sheet.getStyle(iRow, iCol);
+                            if (!style.locked) {
+                                const colSetting = sheet.zh_setting.cols[iCol];
+                                data[colSetting.field] = 0;
+                                bDel = true;
+                            }
+                        }
+                        if (bDel) {
+                            datas.push(data);
+                        }
+                    }
+                }
+                if (datas.length === 0) return;
+
+                postData('info/save', { type: self.type, updateData: datas}, function (result) {
+                    self.loadUpdateData(result);
+                    SpreadJsObj.reLoadSheetData(sheet);
+                }, function () {
+                    SpreadJsObj.reLoadSheetData(sheet);
+                });
+            });
+        }
+        loadUpdateData(data) {
+            for (const d of data) {
+                const source = this.data.find(x => { return x.id === d.id });
+                if (!source) continue;
+
+                source.dgn_qty = d.dgn_qty;
+                source.final_qty = d.final_qty;
+            }
+        }
+    }
+
+    const spreadSetting = {
+        cols: [
+            {title: '工程名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 300, formatter: '@', readOnly: true},
+            {title: '单位', colSpan: '1', rowSpan: '1', field: 'unit', hAlign: 1, width: 50, formatter: '@', readOnly: true},
+            {title: '设计', colSpan: '1', rowSpan: '1', field: 'dgn_qty', hAlign: 2, width: 100, type: 'Number'},
+            {title: '竣工', colSpan: '1', rowSpan: '1', field: 'final_qty', hAlign: 2, width: 100, type: 'Number'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [32],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+    };
+    const mainQtyObj = new QtyObj({
+        obj: $('#main_qty_spread')[0],
+        spreadSetting,
+        data: mainQty,
+        type: 'main_quantity',
+    });
+    const gclQtyObj = new QtyObj({
+        obj: $('#gcl_qty_spread')[0],
+        spreadSetting,
+        data: gclQty,
+        type: 'gcl_quantity',
+    })
+});

+ 4 - 0
app/router.js

@@ -715,6 +715,10 @@ module.exports = app => {
     app.post('/subproj/rela', sessionAuth, 'subProjController.rela');
     app.post('/subproj/member', sessionAuth, projectManagerCheck, 'subProjController.member');
     app.post('/subproj/memberSave', sessionAuth, projectManagerCheck, 'subProjController.memberSave');
+    // 项目信息
+    app.get('/sp/:id/info', sessionAuth, subProjectCheck, 'subProjController.info');
+    app.get('/sp/:id/data', sessionAuth, subProjectCheck, 'subProjController.dataIndex');
+    app.post('/sp/:id/info/save', sessionAuth, subProjectCheck, 'subProjController.saveInfo');
     // 概算投资
     app.get('/budget', sessionAuth, 'budgetController.list');
     app.get('/budget/:id', sessionAuth, budgetCheck, 'budgetController.budgetInfo');

+ 135 - 0
app/service/sub_proj_info.js

@@ -0,0 +1,135 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+const defaultInfo = {
+    main_quantity: [
+        { id: 1, name: '路基土石方', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 2, name: '特殊路基处理', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 3, name: '路基排水坞工', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 4, name: '路基防护坞工', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 5, name: '路面工程', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 6, name: '大、特大桥', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 7, name: '中、小桥', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 8, name: '涵洞', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 9, name: '隧道', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 10, name: '分离式立交', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 11, name: '通道、天桥', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 12, name: '平面交叉', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 13, name: '互通式立交', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 14, name: '连接线长度、辅导长度', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 15, name: '管理及养护房屋', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+    ],
+    gcl_quantity: [
+        { id: 1, name: '主要人工消耗', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 2, name: '主要材料消耗', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 3, name: '钢材', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 4, name: '沥青', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 5, name: '路面工程', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 6, name: '汽油、柴油', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 7, name: '水泥', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 8, name: '碎石、砂', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 9, name: '电', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+        { id: 10, name: '主要机械消耗', unit: 'm3', dgn_qty: 0, final_qty: 0, },
+    ],
+};
+
+module.exports = app => {
+
+    class SubProjInfo extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'sub_project_info';
+        }
+
+        async addInfo(id, project_id, transaction) {
+            const info = { id, project_id };
+            for (const di in defaultInfo) {
+                info[di] = JSON.stringify(defaultInfo[di]);
+            }
+
+            if (transaction) {
+                await transaction.insert(this.tableName, info);
+            } else {
+                await this.db.insert(this.tableName, info);
+            }
+            return info;
+        }
+
+        /**
+         * 获取标段相关信息
+         * @param tenderId
+         * @return {Promise<void>}
+         */
+        async getInfo(id) {
+            let info = await this.getDataByCondition({ id });
+            // 兼容不存在info的情况
+            if (!info) info = await this.addInfo(id, this.ctx.session.sessionProject.id);
+
+            for (const ai in defaultInfo) {
+                info[ai] = !info[ai] || info[ai] === '' ? defaultInfo[ai] : JSON.parse(info[ai]);
+            }
+            return info;
+        }
+
+        async _saveCommonInfo(id, data) {
+            const updateData = { id };
+            for (const d in data) {
+                if (d === 'id' || d === 'project_id') continue;
+                if (defaultInfo[d]) continue;
+
+                updateData[d] = data[d];
+            }
+
+            await this.db.update(this.tableName, updateData);
+            return updateData;
+        }
+
+        async _saveArrayInfo(id, type, data) {
+            const info = await this.getInfo(id);
+            const source = info[type];
+            const result = [];
+            for (const d of data) {
+                const s = source.find(x => { return x.id === d.id });
+                if (d.dgn_qty !== undefined) s.dgn_qty = d.dgn_qty || 0;
+                if (d.final_qty !== undefined) s.final_qty = d.final_qty || 0;
+                result.push(s);
+            }
+            const updateData = { id };
+            updateData[type] = JSON.stringify(source);
+            await this.db.update(this.tableName, updateData);
+            return result;
+        }
+
+        /**
+         * 保存标段相关信息
+         *
+         * @param data
+         * @return {Promise<void>}
+         */
+        async saveInfo(id, data) {
+            switch (data.type) {
+                case 'main_quantity':
+                case 'gcl_quantity':
+                    return await this._saveArrayInfo(id, data.type, data.updateData);
+                default:
+                    return await this._saveCommonInfo(id, data.updateData);
+            }
+        }
+    }
+
+    return SubProjInfo;
+};

+ 0 - 3
app/view/report/index.ejs

@@ -269,9 +269,6 @@
                             <div class="pageContainer">
                                 <canvas id="rptCanvas" height="820" width="920"></canvas>
                             </div>
-                            <!-- <div style="display: none;">
-                                <canvas id="invisibleCanvas" height="820" width="920"></canvas>
-                            </div> -->
                         </div>
                     </div>
                 </div>

+ 136 - 0
app/view/sub_proj/data_index.ejs

@@ -0,0 +1,136 @@
+<% include ./sp_info_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main">
+            <% include ./sp_info_mini_menu.ejs %>
+            <div class="d-inline-block">
+                项目信息
+            </div>
+        </div>
+        <div class="ml-auto"></div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0" style="height: 570px;">
+                <div class="col-8 px-3">
+                    <form>
+                        <div class="py-2 font-weight-bold">主要建设规模</div>
+                        <div class="row">
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">主线公路里程(km):</label>
+                                    <input type="text" class="form-control form-control-sm" name="mainline_length" value="<%- info.mainline_length %>" org="<%- info.mainline_length %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this);">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">支线里程(km):</label>
+                                    <input type="text" class="form-control form-control-sm" name="branch_length" value="<%- info.branch_length %>" org="<%- info.branch_length %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3"></div>
+                        </div>
+                        <div class="py-2 font-weight-bold">主要技术指标</div>
+                        <div class="row">
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">公路等级:</label>
+                                    <select class="form-control form-control-sm" name="road_level" onchange="changeInfo(this);">
+                                        <option value="">请选择</option>
+                                        <option value="高速公路">高速公路</option>
+                                        <option value="一级公路">一级公路</option>
+                                        <option value="二级公路">二级公路</option>
+                                        <option value="三级公路">三级公路</option>
+                                        <option value="四级公路">四级公路</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">公路设计速度(km/h):</label>
+                                    <input type="text" class="form-control form-control-sm" name="design_speed" value="<%- info.design_speed %>" org="<%- info.design_speed %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">设计荷载:</label>
+                                    <input type="text" class="form-control form-control-sm" name="design_load" value="<%- info.design_load %>" org="<%- info.design_load %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">路基宽度(m):</label>
+                                    <input type="text" class="form-control form-control-sm" name="bed_width" value="<%- info.bed_width %>" org="<%- info.bed_width %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">隧道净宽(m):</label>
+                                    <input type="text" class="form-control form-control-sm" name="tunnel_width" value="<%- info.tunnel_width %>" org="<%- info.tunnel_width %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">地震动峰值系数(m2):</label>
+                                    <input type="text" class="form-control form-control-sm" name="quake_peak_value" value="<%- info.quake_peak_value %>" org="<%- info.quake_peak_value %>" placeholder="请输入" maxlength="20" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+                <div class="col-12 px-3">
+                    <div class="row">
+                        <div class="col-6">
+                            <div class="py-2 font-weight-bold">主要工程数量</div>
+                            <div id="main_qty_spread" style="height: 400px"></div>
+                        </div>
+                        <div class="col-6">
+                            <div class="py-2 font-weight-bold">工料机消耗</div>
+                            <div id="gcl_qty_spread" style="height: 400px"></div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const mainQty = JSON.parse('<%- JSON.stringify(info.main_quantity )%>');
+    const gclQty = JSON.parse('<%- JSON.stringify(info.gcl_quantity )%>');
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+    // 根据Maxlength限制input输入
+    function limitMaxLength (obj) {
+        if (obj.value.length > obj.maxLength) {
+            obj.value = obj.value.substr(0, obj.maxLength);
+        }
+    }
+
+    function changeInfo(obj) {
+        const field = obj.getAttribute('name');
+        if (!field) return;
+        if (obj.getAttribute('org') === obj.value) return;
+
+        const updateData = {};
+        updateData[field] = obj.value;
+        postData('info/save', { updateData }, function (result) {
+            obj.setAttribute('org', obj.value);
+        }, function () {
+            obj.value = obj.getAttribute('org');
+        });
+    };
+</script>

+ 195 - 0
app/view/sub_proj/info.ejs

@@ -0,0 +1,195 @@
+<% include ./sp_info_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main">
+            <% include ./sp_info_mini_menu.ejs %>
+            <div class="d-inline-block">
+                项目信息
+            </div>
+        </div>
+        <div class="ml-auto"></div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0" style="height: 570px;">
+                <div class="col-9 px-3">
+                    <div>
+                        <div class="py-2 font-weight-bold">立项批准(核准)情况:</div>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">部门:</label>
+                                    <input type="text" class="form-control form-control-sm" name="lx_department" value="<%- info.lx_department %>" org="<%- info.lx_department %>" maxlength="100" placeholder="请输入部门名称" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">日期:</label>
+                                    <input type="date" class="form-control form-control-sm" name="lx_date" value="<%- info.lx_date %>" org="<%- info.lx_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">文号:</label>
+                                    <input type="text" class="form-control form-control-sm" name="lx_code" value="<%- info.lx_code %>" org="<%- info.lx_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">初步(修编)设计批准情况:</div>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">部门:</label>
+                                    <input type="text" class="form-control form-control-sm" name="cb_department" value="<%- info.cb_department %>" org="<%- info.cb_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">日期:</label>
+                                    <input type="date" class="form-control form-control-sm" name="cb_date" value="<%- info.cb_date %>" org="<%- info.cb_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">文号:</label>
+                                    <input type="text" class="form-control form-control-sm" name="cb_code" value="<%- info.cb_code %>" org="<%- info.cb_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">施工许可批复情况:</div>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">部门:</label>
+                                    <input type="text" class="form-control form-control-sm" name="sg_department" value="<%- info.sg_department %>" org="<%- info.sg_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">日期:</label>
+                                    <input type="date" class="form-control form-control-sm" name="sg_date" value="<%- info.sg_date %>" org="<%- info.sg_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">文号:</label>
+                                    <input type="text" class="form-control form-control-sm" name="sg_code" value="<%- info.sg_code %>" org="<%- info.sg_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">交工验收情况:</div>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">部门:</label>
+                                    <input type="text" class="form-control form-control-sm" name="jg_department" value="<%- info.jg_department %>" org="<%- info.jg_department %>" placeholder="请输入部门名称" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">日期:</label>
+                                    <input type="date" class="form-control form-control-sm" name="jg_date" value="<%- info.jg_date %>" org="<%- info.jg_date %>" placeholder="请输入" onchange="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">文号:</label>
+                                    <input type="text" class="form-control form-control-sm" name="jg_code" value="<%- info.jg_code %>" org="<%- info.jg_code %>" placeholder="请输入文号" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                        </div>
+                        <div class="row">
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">工程质量评分:</label>
+                                    <input type="text" class="form-control form-control-sm" name="jg_quality_score" value="<%- info.jg_quality_score %>" org="<%- info.jg_quality_score %>" placeholder="请输入" maxlength="50" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-3">
+                                <div class="form-group">
+                                    <label for="">等级:</label>
+                                    <select class="form-control form-control-sm" name="jg_level" value="<%- info.jg_level %>" org="<%- info.jg_level %>" onchange="changeInfo(this)">
+                                        <option>请选择</option>
+                                        <option>优良</option>
+                                        <option>合格</option>
+                                        <option>不合格</option>
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="py-2 font-weight-bold">单位信息:</div>
+                        <div class="row">
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">建设单位:</label>
+                                    <input type="text" class="form-control form-control-sm" name="unit_construction" value="<%- info.unit_construction %>" org="<%- info.unit_construction %>" placeholder="请输入" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">质量监督机构:</label>
+                                    <input type="text" class="form-control form-control-sm" name="unit_qa" value="<%- info.unit_qa %>" org="<%- info.unit_qa %>" placeholder="请输入" maxlength="100" oninput="limitMaxLength(this)" onblur="changeInfo(this)">
+                                </div>
+                            </div>
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">主要设计单位:</label>
+                                    <textarea class="form-control form-control-sm" name="unit_design" org="<%- info.unit_design %>" rows="3" maxlength="500" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.unit_design %></textarea>
+                                </div>
+                            </div>
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">主要监理单位:</label>
+                                    <textarea class="form-control form-control-sm" name="unit_supervision" org="<%- info.unit_supervision %>" rows="3" maxlength="500" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.unit_supervision %></textarea>
+                                </div>
+                            </div>
+                            <div class="col-6">
+                                <div class="form-group">
+                                    <label for="">主要施工单位:</label>
+                                    <textarea class="form-control form-control-sm" name="unit_contract" org="<%- info.unit_contract %>" rows="3" maxlength="500" oninput="limitMaxLength(this)" onblur="changeInfo(this)"><%- info.unit_contract %></textarea>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+    // 根据Maxlength限制input输入
+    function limitMaxLength (obj) {
+        if (obj.value.length > obj.maxLength) {
+            obj.value = obj.value.substr(0, obj.maxLength);
+        }
+    }
+    function changeInfo(obj) {
+        const field = obj.getAttribute('name');
+        if (!field) return;
+        if (obj.getAttribute('org') === obj.value) return;
+
+        const updateData = {};
+        updateData[field] = obj.value;
+        postData('info/save', { updateData }, function (result) {
+            obj.setAttribute('org', obj.value);
+        }, function () {
+            obj.value = obj.getAttribute('org');
+        });
+    };
+</script>

+ 14 - 0
app/view/sub_proj/sp_info_menu.ejs

@@ -0,0 +1,14 @@
+<div class="panel-sidebar" id="sub-menu">
+    <div class="sidebar-title" data-toggle="tooltip" data-placement="right" data-original-title="<%- ctx.subProject.name %>">
+        <%- ctx.subProject.name %>
+    </div>
+    <div class="scrollbar-auto">
+        <% include ./sp_info_menu_list.ejs %>
+        <div class="side-fold"><a href="javascript: void(0)" data-toggle="tooltip" data-placement="top" data-original-title="折叠侧栏" id="to-mini-menu"><i class="fa fa-upload fa-rotate-270"></i></a></div>
+    </div>
+    <script>
+        new Vue({
+            el: '.scrollbar-auto',
+        });
+    </script>
+</div>

+ 3 - 0
app/view/sub_proj/sp_info_menu_list.ejs

@@ -0,0 +1,3 @@
+<nav-menu title="返回" url="/budget" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu>
+<nav-menu title="项目信息" url="/sp/<%- ctx.subProject.id %>/info" ml="3" active="<%= (ctx.url.indexOf('info') > 0 ? 1 : -1) %>"></nav-menu>
+<nav-menu title="数据指标" url="/sp/<%- ctx.subProject.id %>/data" ml="3" active="<%= (ctx.url.indexOf('data') > 0 ? 1 : -1) %>"></nav-menu>

+ 16 - 0
app/view/sub_proj/sp_info_mini_menu.ejs

@@ -0,0 +1,16 @@
+<!--折起的菜单-->
+<div class="min-side" id="sub-mini-menu" style="display: none;">
+    <div id="sub-mini-hint" class="side-switch" data-container="body" data-toggle="popover" data-placement="bottom" data-content="这里打开收起的菜单栏"></div>
+    <div class="side-switch">
+        <i class="fa fa-bars"></i>
+    </div>
+    <div class="side-menu" id="mini-menu-list" style="display: none">
+        <% include ./sp_info_menu_list.ejs %>
+        <div class="side-fold"><a href="javascript: void(0);" data-toggle="tooltip" data-placement="top" data-original-title="展开侧栏" id="to-menu"><i class="fa fa-upload fa-rotate-90"></i></a></div>
+    </div>
+</div>
+<script>
+    new Vue({
+        el: '.side-menu',
+    });
+</script>

+ 21 - 0
config/web.js

@@ -1031,6 +1031,27 @@ const JsFiles = {
                 ],
                 mergeFile: 'sub_project',
             },
+            info: {
+                files: [
+                    '/public/js/component/menu.js',
+                ],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                ],
+                mergeFile: 'sp_info',
+            },
+            data: {
+                files: [
+                    '/public/js/component/menu.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                ],
+                mergeFiles: [
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/sub_menu.js',
+                    '/public/js/sp_data.js',
+                ],
+                mergeFile: 'sp_data',
+            }
         },
         file: {
             index: {