Explorar el Código

feat: 信息价总表

vian hace 1 año
padre
commit
6024ef5920

+ 15 - 0
modules/all_models/std_price_info_summary.js

@@ -0,0 +1,15 @@
+const mongoose = require('mongoose');
+
+const Schema = mongoose.Schema;
+const collectionName = 'std_price_info_summary';
+
+const modelSchema = {
+  ID: { type: String, required: true },
+  masterSubCode: String, // 主从对应码
+  classCode: String, // 别名编码
+  expString: String, // 计算式,
+  name: String, // 材料名称
+  specs: String, // 规格型号
+  unit: String, // 单位
+};
+mongoose.model(collectionName, new Schema(modelSchema, { versionKey: false, collection: collectionName }));

+ 47 - 0
modules/price_info_summary/controllers/index.js

@@ -0,0 +1,47 @@
+import BaseController from "../../common/base/base_controller";
+import CompilationModel from '../../users/models/compilation_model';
+const multiparty = require('multiparty');
+const excel = require('node-xlsx');
+const fs = require('fs');
+const facade = require('../facade/index');
+const config = require("../../../config/config.js");
+
+class PriceInfoSummaryController extends BaseController {
+    async main(req, res) {
+        const renderData = {
+            title: '材料信息价总表',
+            userAccount: req.session.managerData.username,
+            userID: req.session.managerData.userID,
+            LicenseKey: config.getLicenseKey(process.env.NODE_ENV),
+        };
+        res.render("maintain/price_info_summary/html/main.html", renderData);
+    }
+
+    // 获取分页数据
+    async getPagingData(req, res) {
+        try {
+            const { page, pageSize, searchStr } = JSON.parse(req.body.data);
+            const data = await facade.getPagingData(page, pageSize, searchStr);
+            res.json({ error: 0, message: 'getData success', data });
+        } catch (err) {
+            console.log(err);
+        }
+    }
+
+    // 编辑总表
+    async editSummaryData(req, res) {
+        try {
+            const { postData } = JSON.parse(req.body.data);
+            await facade.editSummaryData(postData);
+            res.json({ error: 0, message: 'editPrice success' });
+        } catch (err) {
+            console.log(err);
+            res.json({ error: 1, message: err.toString() });
+        }
+    }
+
+}
+
+module.exports = {
+    priceInfoSummaryController: new PriceInfoSummaryController()
+};

+ 61 - 0
modules/price_info_summary/facade/index.js

@@ -0,0 +1,61 @@
+const mongoose = require('mongoose');
+const uuidV1 = require('uuid/v1');
+const _ = require('lodash');
+const scMathUtil = require('../../../public/scMathUtil').getUtil();
+
+const infoPriceSummaryModel = mongoose.model('std_price_info_summary');
+
+// 获取分页数据
+const getPagingData = async (page, pageSize, searchStr) => {
+    let query = {};
+    if (searchStr) {
+        const nameReg = new RegExp(searchStr);
+        query = {
+            $or: [{ classCode: searchStr }, { name: { $regex: nameReg } }]
+        }
+    }
+    const totalCount = await infoPriceSummaryModel.count(query);
+    const items = await infoPriceSummaryModel.find(query).lean().sort({ classCode: 1 }).skip(page * pageSize).limit(pageSize);
+    return { items, totalCount };
+}
+
+const UpdateType = {
+    UPDATE: 'update',
+    DELETE: 'delete',
+    CREATE: 'create',
+};
+
+// 编辑表格
+async function editSummaryData(postData) {
+    const bulks = [];
+    postData.forEach(data => {
+        if (data.type === UpdateType.UPDATE) {
+            bulks.push({
+                updateOne: {
+                    filter: { ID: data.ID },
+                    update: { ...data.data }
+                }
+            });
+        } else if (data.type === UpdateType.DELETE) {
+            bulks.push({
+                deleteOne: {
+                    filter: { ID: data.ID }
+                }
+            });
+        } else {
+            bulks.push({
+                insertOne: {
+                    document: data.data
+                }
+            });
+        }
+    });
+    if (bulks.length) {
+        await infoPriceSummaryModel.bulkWrite(bulks);
+    }
+}
+
+module.exports = {
+    getPagingData,
+    editSummaryData,
+}

+ 17 - 0
modules/price_info_summary/routes/index.js

@@ -0,0 +1,17 @@
+/**
+ * Created by zhang on 2018/9/3.
+ */
+
+const express = require("express");
+const router = express.Router();
+const { priceInfoSummaryController } = require('../controllers/index');
+
+module.exports = function (app) {
+    router.get("/main", priceInfoSummaryController.auth, priceInfoSummaryController.init, priceInfoSummaryController.main);
+    router.post("/getPagingData", priceInfoSummaryController.auth, priceInfoSummaryController.init, priceInfoSummaryController.getPagingData);
+    router.post("/editSummaryData", priceInfoSummaryController.auth, priceInfoSummaryController.init, priceInfoSummaryController.editSummaryData);
+
+    app.use("/priceInfoSummary", router);
+};
+
+

+ 19 - 17
modules/users/controllers/manager_controller.js

@@ -11,7 +11,7 @@ import PermissionModel from "../models/permission_model";
 import PermissionGroupModel from "../models/permission_group_model";
 import Config from "../../../config/config";
 let config = require("../../../config/config.js");
-import {default as category, List as categoryList} from "../../common/const/category_const.js";
+import { default as category, List as categoryList } from "../../common/const/category_const.js";
 
 class ManagerController extends BaseController {
 
@@ -30,7 +30,7 @@ class ManagerController extends BaseController {
         try {
             // 查找管理员用户列表
             let managerModel = new ManagerModel();
-            let total = await managerModel.count({super_admin: 0});
+            let total = await managerModel.count({ super_admin: 0 });
 
             // 分页数据
             let page = request.query.page === undefined ? 1 : request.query.page;
@@ -56,7 +56,7 @@ class ManagerController extends BaseController {
                 filter.officeName = officeInfo.name
             }
             if (request.query.permission !== undefined && request.query.permission !== '0') {
-                let permissionGroupInfo = await permissionGroupModel.findDataByCondition({_id: request.query.permission});
+                let permissionGroupInfo = await permissionGroupModel.findDataByCondition({ _id: request.query.permission });
                 filter.permissionGroupName = permissionGroupInfo.name;
             }
 
@@ -70,7 +70,7 @@ class ManagerController extends BaseController {
                         for (let p in groupPermissionList) {
                             if (p === 'top') {
                                 for (let t of groupPermissionList[p]) {
-                                    let topInfo = await permissionModel.findDataByCondition({_id:t});
+                                    let topInfo = await permissionModel.findDataByCondition({ _id: t });
                                     topPermissionList.push(topInfo.name);
                                 }
                                 break;
@@ -89,7 +89,7 @@ class ManagerController extends BaseController {
                     });
                     managerList[tmp].officeName = cate !== undefined ? cate.name : '';
 
-                    let groupInfo = managerList[tmp].permission !== '' ? await permissionGroupModel.findDataByCondition({_id:managerList[tmp].permission}) : '';
+                    let groupInfo = managerList[tmp].permission !== '' ? await permissionGroupModel.findDataByCondition({ _id: managerList[tmp].permission }) : '';
                     managerList[tmp].permissionName = groupInfo !== undefined && groupInfo !== '' ? groupInfo.name : '';
                 }
             }
@@ -106,7 +106,7 @@ class ManagerController extends BaseController {
             permissionGroupList2: permissionGroupList2,
             layout: 'users/views/layout/layout',
             filter: filter,
-            LicenseKey:config.getLicenseKey(process.env.NODE_ENV)
+            LicenseKey: config.getLicenseKey(process.env.NODE_ENV)
         };
         response.render('users/views/manager/index', renderData);
     }
@@ -121,7 +121,7 @@ class ManagerController extends BaseController {
         let id = request.body.manager_id;
         let permission = request.body.permission !== '0' ? request.body.permission : '';
         let managerModel = new ManagerModel();
-        let result = await managerModel.updateById(id, {permission: permission});
+        let result = await managerModel.updateById(id, { permission: permission });
 
         if (!result) {
             throw '修改失败';
@@ -195,7 +195,7 @@ class ManagerController extends BaseController {
         let id = request.params.id;
 
         let managerModel = new ManagerModel();
-        let result = await managerModel.updateById(id, {can_login: canLogin});
+        let result = await managerModel.updateById(id, { can_login: canLogin });
 
         // 修改成功
         if (!result) {
@@ -216,7 +216,7 @@ class ManagerController extends BaseController {
         try {
             // 查找对应超级管理员数据
             let managerModel = new ManagerModel();
-            adminData = await managerModel.findDataByCondition({username: 'admin'});
+            adminData = await managerModel.findDataByCondition({ username: 'admin' });
 
         } catch (error) {
             console.log(error);
@@ -225,7 +225,7 @@ class ManagerController extends BaseController {
         let renderData = {
             adminData: adminData,
             layout: 'users/views/layout/layout',
-            LicenseKey:config.getLicenseKey(process.env.NODE_ENV)
+            LicenseKey: config.getLicenseKey(process.env.NODE_ENV)
         };
         response.render('users/views/manager/save', renderData);
     }
@@ -285,14 +285,16 @@ class ManagerController extends BaseController {
         try {
             // 获取最高级权限列表
             let permissionModel = new PermissionModel();
-            topPermissionList = await permissionModel.getList({pid:0});
+            topPermissionList = await permissionModel.getList({ pid: 0 });
 
             // 获取所有权限列表,按排序
             permissionList = topPermissionList;
             for (let index in permissionList) {
-                let count = await permissionModel.count({pid:permissionList[index].ID});
+                let count = await permissionModel.count({ pid: permissionList[index].ID });
                 if (count > 0) {
-                    permissionList[index].secondPermissionList = await permissionModel.getList({pid: permissionList[index].ID});
+                    permissionList[index].secondPermissionList = await permissionModel.getList({ pid: permissionList[index].ID });
+                    console.log(permissionList[index].ID)
+                    console.log(permissionList[index].secondPermissionList);
                 } else {
                     permissionList[index].secondPermissionList = [];
                 }
@@ -318,14 +320,14 @@ class ManagerController extends BaseController {
             if (groupList.length > 0) {
                 let managerModel = new ManagerModel();
                 for (let tmp in groupList) {
-                    let managerCount = await managerModel.count({permission: groupList[tmp]._id});
+                    let managerCount = await managerModel.count({ permission: groupList[tmp]._id });
                     groupList[tmp].manager_count = managerCount;
                     let groupPermissionList = JSON.parse(groupList[tmp].permission);
                     for (let p in groupPermissionList) {
                         if (p === 'top') {
                             let topPermissionList = [];
                             for (let t of groupPermissionList[p]) {
-                                let topInfo = await permissionModel.findDataByCondition({_id:t});
+                                let topInfo = await permissionModel.findDataByCondition({ _id: t });
                                 topPermissionList.push(topInfo.name);
                             }
                             groupList[tmp].top_name = topPermissionList.join(',');
@@ -342,7 +344,7 @@ class ManagerController extends BaseController {
             permissionList: permissionList,
             pages: pageData,
             layout: 'users/views/layout/layout',
-            LicenseKey:config.getLicenseKey(process.env.NODE_ENV)
+            LicenseKey: config.getLicenseKey(process.env.NODE_ENV)
         };
         response.render('users/views/manager/authority', renderData);
     }
@@ -391,7 +393,7 @@ class ManagerController extends BaseController {
             // 并清空用户所在权限组
             let managerModel = new ManagerModel();
             await managerModel.updateByPermission(id);
-        } catch(err) {
+        } catch (err) {
             throw err;
         }
         response.redirect(request.headers.referer);

+ 1 - 1
modules/users/models/permission_model.js

@@ -65,7 +65,7 @@ class PermissionModel extends BaseModel {
     async getList(condition = null, page = 1, pageSize = 100) {
         page = parseInt(page);
         page = page <= 1 ? 1 : page;
-        let option = {pageSize: pageSize, offset: parseInt((page - 1) * pageSize), sort: {ID:1}};
+        let option = { pageSize: pageSize, offset: parseInt((page - 1) * pageSize), sort: { ID: 1 } };
         let list = await this.db.find(condition, null, option);
         list = list.length > 0 ? list : [];
 

+ 16 - 10
public/web/lock_util.js

@@ -20,11 +20,11 @@ const lockUtil = (() => {
         const $btns = $range.find('.lock-btn-control');
         const toolList = [];
         for (const $btn of $btns) {
-            toolList.push({$ref: $($btn), type: 'button'});
+            toolList.push({ $ref: $($btn), type: 'button' });
         }
         const $texts = $range.find('.lock-text-control');
         for (const $text of $texts) {
-            toolList.push({$ref: $($text), type: 'text'});
+            toolList.push({ $ref: $($text), type: 'text' });
         }
         toolList.forEach(item => {
             switch (item.type) {
@@ -44,7 +44,7 @@ const lockUtil = (() => {
         spreads.forEach(spread => {
             spread.unbind(GC.Spread.Sheets.Events.ButtonClicked);
             const sheetCount = spread.getSheetCount();
-            for(let i = 0; i < sheetCount; i++){
+            for (let i = 0; i < sheetCount; i++) {
                 const sheet = spread.getSheet(i);
                 sheet.unbind(GC.Spread.Sheets.Events.ButtonClicked);
                 sheet.unbind(GC.Spread.Sheets.Events.EditStarting);
@@ -60,8 +60,8 @@ const lockUtil = (() => {
                 sheet.options.isProtected = true;
                 const rowCount = sheet.getRowCount();
                 const colCount = sheet.getColumnCount();
-                for(let row = 0; row < rowCount; row++){
-                    for(let col = 0; col < colCount; col++){
+                for (let row = 0; row < rowCount; row++) {
+                    for (let col = 0; col < colCount; col++) {
                         sheet.getCell(row, col).locked(true);
                     }
                 }
@@ -77,14 +77,19 @@ const lockUtil = (() => {
         const curURL = reg.test(originURL) ? originURL.replace(reg, `locked=${locked}`) : `${originURL}&locked=${locked}`;
         $url.prop('href', curURL);
     }
+
+    function displayLock($lock, locked) {
+        $lock.data('locked', locked);
+        const innerHtml = locked ? '<i class="fa fa-unlock-alt"></i>' : '<i class="fa fa-lock"></i>';
+        $lock.html(innerHtml);
+        const title = locked ? '解锁' : '锁定';
+        $lock.prop('title', title);
+    }
+
     // 库列表页面,锁定按钮点击操作
     function handleLockClick($lock) {
         const curLocked = !$lock.data().locked;
-        $lock.data('locked', curLocked);
-        const innerHtml = curLocked ? '<i class="fa fa-unlock-alt"></i>' : '<i class="fa fa-lock"></i>';
-        $lock.html(innerHtml);
-        const title = curLocked ? '解锁' : '锁定';
-        $lock.prop('title', title);
+        displayLock($lock, curLocked);
         const $url = $lock.parent().parent().children(':first-child').children(':first-child');
         lockURL(curLocked, $url);
         const $range = $lock.parent().parent();
@@ -103,6 +108,7 @@ const lockUtil = (() => {
         lockTools,
         lockSpreads,
         lockURL,
+        displayLock,
         handleLockClick,
         lockSpreadsAndTools
     }

+ 5 - 3
web/maintain/common/html/layout.html

@@ -20,9 +20,11 @@
     </nav>
     <nav class="navbar navbar-toggleable-lg justify-content-between navbar-light p-0">
         <ul class="nav navbar-nav px-1">
-            <li class="nav-item">
-                <a class="nav-link" href="javascript:void(0);" aria-haspopup="true" aria-expanded="false" data-toggle="modal" data-target="#add">新建<%= title%></a>
-            </li>
+            <% if (typeof hideNav === 'undefined') { %>
+                <li class="nav-item">
+                    <a class="nav-link" href="javascript:void(0);" aria-haspopup="true" aria-expanded="false" data-toggle="modal" data-target="#add">新建<%= title%></a>
+                </li>
+            <% } %>
             <% if (typeof listItem !=='undefined') { %>
                 <%- listItem %>
             <% } %>

+ 91 - 0
web/maintain/price_info_summary/css/index.css

@@ -0,0 +1,91 @@
+html {
+    height: 100%;
+}
+
+body {
+    height: 100%;
+    font-size: 0.9rem;
+}
+
+.dropdown-menu {
+    font-size: 0.9rem
+}
+
+/*自定义css*/
+
+.header {
+    position: relative;
+    background: #e1e1e1
+}
+
+.header .header-logo {
+    background: #ff6501;
+    color: #fff;
+    float: left;
+    padding-top: .25rem;
+    padding-bottom: .25rem;
+    margin-right: 1rem;
+    font-size: 1.25rem;
+    line-height: inherit
+}
+
+.top-msg {
+    position: fixed;
+    top: 0;
+    width: 100%;
+    z-index: 999
+}
+
+.in-1 {
+    padding-left: 0rem!important
+}
+
+.in-2 {
+    padding-left: 1rem!important
+}
+
+.in-3 {
+    padding-left: 1.5rem!important
+}
+
+.in-4 {
+    padding-left: 2rem!important
+}
+
+.in-5 {
+    padding-left: 2.5rem!important
+}
+
+.in-6 {
+    padding-left: 3rem!important
+}
+
+.disabled {
+    pointer-events: none;
+    opacity: .65;
+    color: #666;
+}
+
+.wrapper {
+    position: absolute;
+    top: 38px;
+    bottom: 0;
+    width: 100%;
+}
+
+.search {
+    padding: 5px 10px;
+    /* background-color: #f7f7f7; */
+}
+
+.search-input {
+    width: 250px;
+}
+
+.main {
+    height: calc(100% - 78px);
+}
+
+.sheet {
+    height: 100%;
+}

+ 114 - 0
web/maintain/price_info_summary/html/edit.html

@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
+    <title>材料信息价总表</title>
+    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
+    <link rel="stylesheet" href="/web/maintain/price_info_lib/css/index.css">
+    <link rel="stylesheet" href="/lib/font-awesome/font-awesome.min.css">
+    <link rel="stylesheet" href="/lib/spreadjs/sheets/css/gc.spread.sheets.sc.css" type="text/css">
+    <link rel="stylesheet" href="/lib/jquery-contextmenu/jquery.contextMenu.css" type="text/css">
+</head>
+
+<body>
+    <div class="header">
+        <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 ">
+            <span class="header-logo px-2">Smartcost</span>
+            <div class="navbar-text"><a href="/priceInfo/main">信息价库</a><i
+                    class="fa fa-angle-right fa-fw"></i><%= libName  %>
+               <button id="calc-price-index">计算指数</button>     
+            </div>
+
+        </nav>
+    </div>
+    <div class="wrapper" style="overflow: hidden;">
+        <div class="main">
+            <div class="left">
+                <div class="top" id="area-spread"></div>
+                <div class="bottom">
+                    <div class="tab-bar">
+                        <a href="javascript:void(0);" id="tree-insert" class="btn btn-sm lock-btn-control disabled"
+                            data-toggle="tooltip" data-placement="bottom" title="" data-original-title="插入"><i
+                                class="fa fa-plus" aria-hidden="true"></i></a>
+                        <a href="javascript:void(0);" id="tree-remove" class="btn btn-sm lock-btn-control disabled"
+                            data-toggle="tooltip" data-placement="bottom" title="" data-original-title="删除"><i
+                                class="fa fa-remove" aria-hidden="true"></i></a>
+                        <a href="javascript:void(0);" id="tree-up-level" class="btn btn-sm lock-btn-control disabled"
+                            data-toggle="tooltip" data-placement="bottom" title="" data-original-title="升级"><i
+                                class="fa fa-arrow-left" aria-hidden="true"></i></a>
+                        <a href="javascript:void(0);" id="tree-down-level" class="btn btn-sm lock-btn-control disabled"
+                            data-toggle="tooltip" data-placement="bottom" title="" data-original-title="降级"><i
+                                class="fa fa-arrow-right" aria-hidden="true"></i></a>
+                        <a href="javascript:void(0);" id="tree-down-move" class="btn btn-sm lock-btn-control disabled"
+                            data-toggle="tooltip" data-placement="bottom" title="" data-original-title="下移"><i
+                                class="fa fa-arrow-down" aria-hidden="true"></i></a>
+                        <a href="javascript:void(0);" id="tree-up-move" class="btn btn-sm lock-btn-control disabled"
+                            data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i
+                                class="fa fa-arrow-up" aria-hidden="true"></i></a>
+                    </div>
+                    <div class="spread" id="class-spread"></div>
+                </div>
+            </div>
+            <div class="right">
+                <div class="top" id="price-spread"></div>
+                <div class="bottom" id="keyword-spread"></div>
+            </div>
+        </div>
+    </div>
+
+
+    <div class="modal fade in" id="result-info" data-backdrop="static">
+        <div class="modal-dialog" role="document">
+            <div class="modal-content" style=" width: 900px;">
+                <div class="modal-header">
+                    <h5 class="modal-title">结果确认</h5>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">×</span>
+                    </button>
+                </div>
+                <div class="modal-body"  >
+                    <div class="form-group">
+                        <div>以下指数偏差较大,请确认:</div>
+                        <div id="result-info-body">
+                         
+                        </div>
+                    </div>
+                </div>
+                <div class="modal-footer" style="justify-content: center">
+                    <button type="button" class="btn btn-primary" data-dismiss="modal" id="copy-lib-confirm">确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
+    <!-- JS. -->
+    <script src="/lib/jquery/jquery.min.js"></script>
+    <script src="/lib/jquery-contextmenu/jquery.contextMenu.min.js"></script>
+    <script src="/lib/jquery-contextmenu/jquery.ui.position.js"></script>
+    <script src="/lib/tether/tether.min.js"></script>
+    <script src="/lib/bootstrap/bootstrap.min.js"></script>
+    <script src="/public/web/PerfectLoad.js"></script>
+    <script src="/lib/spreadjs/sheets/gc.spread.sheets.all.11.1.2.min.js"></script>
+    <script>GC.Spread.Sheets.LicenseKey = '<%- LicenseKey %>';</script>
+    <script src="/public/web/uuid.js"></script>
+    <script src="/lib/lodash/lodash.js"></script>
+    <script src="/public/web/scMathUtil.js"></script>
+    <script src="/public/web/treeDataHelper.js"></script>
+    <script src="/public/web/common_ajax.js"></script>
+    <script src="/public/web/lock_util.js"></script>
+    <script src="/public/web/id_tree.js"></script>
+    <script src="/public/web/tools_const.js"></script>
+    <script src="/public/web/tree_sheet/tree_sheet_controller.js"></script>
+    <script src="/public/web/tree_sheet/tree_sheet_helper.js"></script>
+    <script src="/public/web/sheet/sheet_common.js"></script>
+    <script>
+        const areaList = JSON.parse('<%- areaList %>');
+        const compilationID = '<%- compilationID %>';
+        const curLibPeriod = '<%- period %>';
+    </script>
+    <script src="/web/maintain/price_info_lib/js/index.js"></script>
+</body>
+
+</html>

+ 53 - 0
web/maintain/price_info_summary/html/main.html

@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
+    <title>材料信息价总表</title>
+    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
+    <link rel="stylesheet" href="/web/maintain/price_info_summary/css/index.css">
+    <link rel="stylesheet" href="/lib/font-awesome/font-awesome.min.css">
+    <link rel="stylesheet" href="/lib/spreadjs/sheets/css/gc.spread.sheets.sc.css" type="text/css">
+    <link rel="stylesheet" href="/lib/jquery-contextmenu/jquery.contextMenu.css" type="text/css">
+</head>
+
+<body>
+    <div class="header">
+        <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 ">
+            <span class="header-logo px-2">材料信息价总表</span>
+            <a class="lock" data-locked="true" href="javascript:void(0);" title="解锁"><i
+                class="fa fa-unlock-alt"></i></a>
+        </nav>
+    </div>
+    <div class="search">
+        <input class="form-control form-control-sm search-input" id="summary-search" type="text" value="" placeholder="输入回车进行搜索">
+    </div>
+
+    <div class="main">
+        <div class="sheet" id="summary-spread"></div>
+    </div>
+    <!-- JS. -->
+    <script src="/lib/jquery/jquery.min.js"></script>
+    <script src="/lib/jquery-contextmenu/jquery.contextMenu.min.js"></script>
+    <script src="/lib/jquery-contextmenu/jquery.ui.position.js"></script>
+    <script src="/lib/tether/tether.min.js"></script>
+    <script src="/lib/bootstrap/bootstrap.min.js"></script>
+    <script src="/public/web/PerfectLoad.js"></script>
+    <script src="/lib/spreadjs/sheets/gc.spread.sheets.all.11.1.2.min.js"></script>
+    <script>GC.Spread.Sheets.LicenseKey = '<%- LicenseKey %>';</script>
+    <script src="/public/web/uuid.js"></script>
+    <script src="/lib/lodash/lodash.js"></script>
+    <script src="/public/web/scMathUtil.js"></script>
+    <script src="/public/web/treeDataHelper.js"></script>
+    <script src="/public/web/common_ajax.js"></script>
+    <script src="/public/web/lock_util.js"></script>
+    <script src="/public/web/id_tree.js"></script>
+    <script src="/public/web/tools_const.js"></script>
+    <script src="/public/web/tree_sheet/tree_sheet_controller.js"></script>
+    <script src="/public/web/tree_sheet/tree_sheet_helper.js"></script>
+    <script src="/public/web/sheet/sheet_common.js"></script>
+    <script src="/web/maintain/price_info_summary/js/summarySheet.js"></script>
+    <script src="/web/maintain/price_info_summary/js/index.js"></script>
+</body>
+</html>

+ 21 - 0
web/maintain/price_info_summary/js/index.js

@@ -0,0 +1,21 @@
+
+
+$(document).ready(() => {
+    //init();
+    // $('[data-toggle="tooltip"]').tooltip();
+    // AREA_BOOK.handleSelectionChanged(0);
+    // 锁定、解锁
+    lockUtil.displayLock($('.lock'), lockUtil.getLocked());
+    $('.lock').click(function () {
+        window.location.search = `?locked=${!lockUtil.getLocked()}`;
+    });
+
+    // 搜索
+    $('#summary-search').bind('keydown', function (event) {
+        if (event.keyCode === 13) {
+            // 回车搜索
+            const searchStr = $(this).val();
+            SUMMARY_BOOK.handleSearch(searchStr);
+        }
+    })
+});

+ 274 - 0
web/maintain/price_info_summary/js/main.js

@@ -0,0 +1,274 @@
+// 节流
+function throttle(fn, time) {
+    let canRun = true;
+    return function () {
+        if (!canRun) {
+            return;
+        }
+        canRun = false;
+        const rst = fn.apply(this, arguments);
+        // 事件返回错误,说明没有发起请求,允许直接继续触发事件,不进行节流处理
+        if (rst === false) {
+            canRun = true;
+            return;
+        }
+        setTimeout(() => canRun = true, time);
+    }
+}
+
+const periodReg = /\d{4}-((0[1-9])|(1[0-2]))$/;
+function createLib() {
+    const name = $('#name').val();
+    if (!name) {
+        $('#name-error').show();
+        return false;
+    }
+    const period = $('#period').val();
+    if (!period || !periodReg.test(period)) {
+        $('#period-error').show();
+        return false;
+    }
+    $('#add-lib-form').submit();
+}
+
+let curLib = {};
+
+//设置当前库
+function setCurLib(libID) {
+    curLib.id = libID;
+    curLib.name = $(`#${libID}`).text();
+}
+
+// 点击编辑按钮
+function handleEditClick(libID) {
+    setCurLib(libID);
+    $('#edit').modal('show');
+}
+
+// 点击确认编辑
+function handleEditConfirm() {
+    const rename = $('#rename-text').val();
+    if (!rename) {
+        $('#rename-error').show();
+        return false;
+    }
+    $('#edit').modal('hide');
+    ajaxPost('/priceInfo/renameLib', { libID: curLib.id, name: rename })
+        .then(() => $(`#${curLib.id} a`).text(rename));
+}
+
+// 删除需要连需点击三次才可删除
+let curDelCount = 0;
+// 点击删除按钮
+function handleDeleteClick(libID) {
+    setCurLib(libID);
+    curDelCount = 0;
+    $('#del').modal('show');
+}
+
+// 删除确认
+function handleDeleteConfirm() {
+    curDelCount++;
+    if (curDelCount === 3) {
+        $.bootstrapLoading.start();
+        curDelCount = -10; // 由于需要连续点击,因此没有对此事件进行节流,为防止多次请求,一旦连续点击到三次,马上清空次数。
+        $('#del').modal('hide');
+        ajaxPost('/priceInfo/deleteLib', { libID: curLib.id })
+            .then(() => $(`#${curLib.id}`).parent().remove())
+            .finally(() => $.bootstrapLoading.end());
+    }
+}
+
+let importType = 'originalData';
+
+// 点击导入按钮
+function handleImportClick(libID, type) {
+    setCurLib(libID);
+    importType = type;
+    $('#import').modal('show');
+}
+
+// 点击导出按钮
+function handleExportClick(libID) {
+    window.location.href = '/priceInfo/export?libID=' + libID;
+}
+
+// 导入确认
+function handleImportConfirm() {
+    $.bootstrapLoading.start();
+    const self = $(this);
+    try {
+        const formData = new FormData();
+        const file = $("input[name='import_data']")[0];
+        if (file.files.length <= 0) {
+            throw '请选择文件!';
+        }
+        formData.append('file', file.files[0]);
+        formData.append('libID', curLib.id);
+        formData.append('importType', importType);
+        $.ajax({
+            url: '/priceInfo/importExcel',
+            type: 'POST',
+            data: formData,
+            cache: false,
+            contentType: false,
+            processData: false,
+            beforeSend: function () {
+                self.attr('disabled', 'disabled');
+                self.text('上传中...');
+            },
+            success: function (response) {
+                self.removeAttr('disabled');
+                self.text('确定导入');
+                if (response.err === 0) {
+                    $.bootstrapLoading.end();
+                    const message = response.msg !== undefined ? response.msg : '';
+                    if (message !== '') {
+                        alert(message);
+                    }
+                    // 成功则关闭窗体
+                    $('#import').modal("hide");
+                } else {
+                    $.bootstrapLoading.end();
+                    const message = response.msg !== undefined ? response.msg : '上传失败!';
+                    alert(message);
+                }
+            },
+            error: function () {
+                $.bootstrapLoading.end();
+                alert("与服务器通信发生错误");
+                self.removeAttr('disabled');
+                self.text('确定导入');
+            }
+        });
+    } catch (error) {
+        alert(error);
+        $.bootstrapLoading.end();
+    }
+}
+
+const { ProcessStatus, CRAWL_LOG_KEY } = window.PRICE_INFO_CONST;
+
+const CHECKING_TIME = 5000;
+// 检测爬取、导入是否完成
+function processChecking(key, cb) {
+    checking();
+
+    function checking() {
+        ajaxPost('/priceInfo/processChecking', { key })
+            .then(handleResolve)
+            .catch(handleReject)
+    }
+
+    let timer;
+    function handleResolve({ key, status, errorMsg }) {
+        if (status === ProcessStatus.START) {
+            if (!$('#progressModal').is(':visible')) {
+                const title = key === CRAWL_LOG_KEY ? '爬取数据' : '导入数据';
+                const text = key === CRAWL_LOG_KEY ? '正在爬取数据,请稍候……' : '正在导入数据,请稍候……';
+                $.bootstrapLoading.progressStart(title, true);
+                $("#progress_modal_body").text(text);
+            }
+            timer = setTimeout(checking, CHECKING_TIME);
+        } else if (status === ProcessStatus.FINISH) {
+            if (timer) {
+                clearTimeout(timer);
+            }
+            $.bootstrapLoading.progressEnd();
+            if (cb) {
+                cb();
+            }
+        } else {
+            if (timer) {
+                clearTimeout(timer);
+            }
+            if (errorMsg) {
+                alert(errorMsg);
+            }
+            if (cb) {
+                cb();
+            }
+            $.bootstrapLoading.progressEnd();
+        }
+    }
+    function handleReject(err) {
+        if (timer) {
+            clearInterval(timer);
+        }
+        alert(err);
+        $.bootstrapLoading.progressEnd();
+    }
+}
+
+const matched = window.location.search.match(/filter=(.+)/);
+const compilationID = matched && matched[1] || '';
+// 爬取数据确认
+function handleCrawlConfirm() {
+    const from = $('#period-start').val();
+    const to = $('#period-end').val();
+    if (!periodReg.test(from) || !periodReg.test(to)) {
+        $('#crawl-error').show();
+        return false;
+    }
+    $('#crawl').modal('hide');
+    ajaxPost('/priceInfo/crawlData', { from, to, compilationID }, 0) // 没有timeout
+        .then(() => {
+            processChecking(CRAWL_LOG_KEY, () => window.location.reload());
+        })
+}
+/* function handleCrawlConfirm() {
+    const from = $('#period-start').val();
+    const to = $('#period-end').val();
+    if (!periodReg.test(from) || !periodReg.test(to)) {
+        $('#crawl-error').show();
+        return false;
+    }
+    $('#crawl').modal('hide');
+    $.bootstrapLoading.progressStart('爬取数据', true);
+    $("#progress_modal_body").text('正在爬取数据,请稍候……');
+    // 不用定时器的话,可能finally处理完后,进度条界面才显示,导致进度条界面没有被隐藏
+    const time = setInterval(() => {
+        if ($('#progressModal').is(':visible')) {
+            clearInterval(time);
+            ajaxPost('/priceInfo/crawlData', { from, to, compilationID }, 0) // 没有timeout
+                .then(() => {
+                    window.location.reload();
+                })
+                .finally(() => $.bootstrapLoading.progressEnd());
+        }
+    }, 500);
+} */
+
+const throttleTime = 1000;
+$(document).ready(function () {
+    processChecking();
+
+    // 锁定、解锁
+    $('.lock').click(function () {
+        lockUtil.handleLockClick($(this));
+    });
+    // 新增
+    $('#add-lib').click(throttle(createLib, throttleTime));
+    // 重命名
+    $('#rename').click(throttle(handleEditConfirm, throttleTime));
+    // 删除
+    $('#delete').click(handleDeleteConfirm);
+    // 爬取数据
+    $('#crawl-confirm').click(throttle(handleCrawlConfirm, throttleTime));
+    // 导入excel
+    $('#import-confirm').click(throttle(handleImportConfirm, throttleTime));
+
+    $('#add').on('hidden.bs.modal', () => {
+        $('#name-error').hide();
+        $('#period-error').hide();
+    });
+    $('#edit').on('hidden.bs.modal', () => $('#rename-error').hide());
+    $('#crawl').on('hidden.bs.modal', () => $('#crawl-error').hide());
+});
+
+$.ajax({
+    url: 'http://api.zjtcn.com/user/dyn_code',
+    type: 'post',
+    data: { service_id: '2020090003' },
+    contentType: 'application/x-www-form-urlencoded',
+})

+ 239 - 0
web/maintain/price_info_summary/js/summarySheet.js

@@ -0,0 +1,239 @@
+
+function setAlign(sheet, headers) {
+  const fuc = () => {
+    headers.forEach(({ hAlign, vAlign }, index) => {
+      sheetCommonObj.setAreaAlign(sheet.getRange(-1, index, -1, 1), hAlign, vAlign)
+    });
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+function setFormatter(sheet, headers) {
+  const fuc = () => {
+    headers.forEach(({ formatter }, index) => {
+      if (formatter) {
+        sheet.setFormatter(-1, index, formatter);
+      }
+    });
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+function initSheet(dom, setting) {
+  const workBook = sheetCommonObj.buildSheet(dom, setting);
+  const sheet = workBook.getSheet(0);
+  setAlign(sheet, setting.header);
+  setFormatter(sheet, setting.header);
+  return workBook;
+}
+
+function showData(sheet, data, headers, emptyRows) {
+  const fuc = () => {
+    sheet.setRowCount(data.length);
+    data.forEach((item, row) => {
+      headers.forEach(({ dataCode }, col) => {
+        sheet.setValue(row, col, item[dataCode] || '');
+      });
+    });
+    if (emptyRows) {
+      sheet.addRows(data.length, emptyRows);
+    }
+  };
+  sheetCommonObj.renderSheetFunc(sheet, fuc);
+}
+
+// 获取当前表中行数据
+function getRowData(sheet, row, headers) {
+  const item = {};
+  headers.forEach(({ dataCode }, index) => {
+    const value = sheet.getValue(row, index) || '';
+    if (value) {
+      item[dataCode] = value;
+    }
+  });
+  return item;
+}
+
+// 获取表数据和缓存数据的不同数据
+function getRowDiffData(curRowData, cacheRowData, headers) {
+  let item = null;
+  headers.forEach(({ dataCode }) => {
+    const curValue = curRowData[dataCode];
+    const cacheValue = cacheRowData[dataCode];
+    if (!cacheValue && !curValue) {
+      return;
+    }
+    if (cacheValue !== curValue) {
+      if (!item) {
+        item = {};
+      }
+      item[dataCode] = curValue || '';
+    }
+  });
+  return item;
+}
+
+const UpdateType = {
+  UPDATE: 'update',
+  DELETE: 'delete',
+  CREATE: 'create',
+};
+
+const TIME_OUT = 10000;
+
+const SUMMARY_BOOK = (() => {
+  const locked = lockUtil.getLocked();
+  const setting = {
+    header: [
+      { headerName: '主从对应码', headerWidth: 200, dataCode: 'masterSubCode', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '别名编码', headerWidth: 100, dataCode: 'classCode', dataType: 'String', hAlign: 'left', vAlign: 'center', formatter: "@" },
+      { headerName: '计算式', headerWidth: 100, dataCode: 'expString', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '材料名称', headerWidth: 350, dataCode: 'name', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '规格型号', headerWidth: 200, dataCode: 'specs', dataType: 'String', hAlign: 'left', vAlign: 'center' },
+      { headerName: '单位', headerWidth: 80, dataCode: 'unit', dataType: 'String', hAlign: 'center', vAlign: 'center' },
+    ],
+  };
+  // 初始化表格
+  const workBook = initSheet($('#summary-spread')[0], setting);
+  workBook.options.allowUserDragDrop = true;
+  workBook.options.allowUserDragFill = true;
+  lockUtil.lockSpreads([workBook], locked);
+  const sheet = workBook.getSheet(0);
+
+  // 当前数据缓存
+  const cache = [];
+  // 清空
+  function clear() {
+    cache.length = 0;
+    sheet.setRowCount(0);
+  }
+
+  let loading = false;
+  // 当前页面数据总量
+  let totalCount = 0;
+  // 当前页数
+  let curPage = 0;
+
+  // 搜索内容
+  let searchStr = '';
+
+  // 加载分页数据
+  const loadPageData = async (page) => {
+    curPage = page;
+    loading = true;
+    const data = await ajaxPost('/priceInfoSummary/getPagingData', { page, searchStr, pageSize: 100 }, TIME_OUT);
+    totalCount = data.totalCount;
+    cache.push(...data.items);
+    showData(sheet, cache, setting.header, 5);
+    loading = false;
+  }
+
+  // 搜索
+  const handleSearch = (val) => {
+    searchStr = val;
+    clear();
+    loadPageData(0);
+  }
+
+
+  // 无限滚动加载
+  const onTopRowChanged = (sender, args) => {
+    const bottomRow = args.sheet.getViewportBottomRow(1);
+    console.log(cache.length, totalCount, loading, cache.length - 1, bottomRow)
+    if (cache.length >= totalCount || loading) {
+      return;
+    }
+    if (cache.length - 1 <= bottomRow) {
+      loadPageData(curPage + 1, searchStr);
+    }
+  }
+  sheet.bind(GC.Spread.Sheets.Events.TopRowChanged, _.debounce(onTopRowChanged, 100));
+
+  // 编辑处理
+  async function handleEdit(changedCells) {
+    $.bootstrapLoading.start();
+    const postData = []; // 请求用
+    // 更新缓存用
+    const updateData = [];
+    const deleteData = [];
+    const insertData = [];
+    try {
+      changedCells.forEach(({ row }) => {
+        if (cache[row]) {
+          const rowData = getRowData(sheet, row, setting.header);
+          if (Object.keys(rowData).length) { // 还有数据,更新
+            const diffData = getRowDiffData(rowData, cache[row], setting.header);
+            if (diffData) {
+              postData.push({ type: UpdateType.UPDATE, ID: cache[row].ID, data: diffData });
+              updateData.push({ row, data: diffData });
+            }
+          } else { // 该行无数据了,删除
+            postData.push({ type: UpdateType.DELETE, ID: cache[row].ID });
+            deleteData.push(cache[row]);
+          }
+        } else { // 新增
+          const rowData = getRowData(sheet, row, setting.header);
+          if (Object.keys(rowData).length) {
+            rowData.ID = uuid.v1();
+            postData.push({ type: UpdateType.CREATE, data: rowData });
+            insertData.push(rowData);
+          }
+        }
+      });
+      if (postData.length) {
+        await ajaxPost('/priceInfoSummary/editSummaryData', { postData }, TIME_OUT);
+        // 更新缓存,先更新然后删除,最后再新增,防止先新增后缓存数据的下标与更新、删除数据的下标对应不上
+        updateData.forEach(item => {
+          Object.assign(cache[item.row], item.data);
+        });
+        deleteData.forEach(item => {
+          const index = cache.indexOf(item);
+          if (index >= 0) {
+            cache.splice(index, 1);
+          }
+        });
+        insertData.forEach(item => cache.push(item));
+        if (deleteData.length || insertData.length) {
+          showData(sheet, cache, setting.header, 5);
+        }
+      }
+    } catch (err) {
+      // 恢复各单元格数据
+      showData(sheet, cache, setting.header, 5);
+    }
+    $.bootstrapLoading.end();
+  }
+  sheet.bind(GC.Spread.Sheets.Events.ValueChanged, function (e, info) {
+    const changedCells = [{ row: info.row }];
+    handleEdit(changedCells);
+  });
+  sheet.bind(GC.Spread.Sheets.Events.RangeChanged, function (e, info) {
+    const changedRows = [];
+    let preRow;
+    info.changedCells.forEach(({ row }) => {
+      if (row !== preRow) {
+        changedRows.push({ row });
+      }
+      preRow = row;
+    });
+    handleEdit(changedRows);
+  });
+
+  const initData = async () => {
+    try {
+      $.bootstrapLoading.start();
+      await loadPageData(0);
+    } catch (error) {
+      console.log(error);
+      alert(error.message);
+    }
+    $.bootstrapLoading.end();
+  }
+
+  initData();
+
+  return {
+    sheet,
+    handleSearch,
+  }
+})()