Explorar o código

Merge branch 'olym'

caiaolin %!s(int64=8) %!d(string=hai) anos
pai
achega
a0b905ed1f

+ 44 - 0
modules/common/base/base_controller.js

@@ -0,0 +1,44 @@
+/**
+ * 控制器基类
+ *
+ * @author CaiAoLin
+ * @date 2017/6/29
+ * @version
+ */
+class BaseController {
+
+    /**
+     * 页面title
+     *
+     * @var string
+     */
+    title = '';
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        if (new.target === BaseController) {
+            throw new Error('BaseController不能实例化,只能继承使用。');
+        }
+    }
+
+    /**
+     * 初始化函数
+     *
+     * @param {object} request
+     * @param {object} response
+     * @param {function} next
+     * @return {void}
+     */
+    init(request, response, next) {
+        // 页面标题
+        response.locals.title = 'test';
+
+        next();
+    }
+}
+
+export default BaseController;

+ 103 - 0
modules/common/base/base_model.js

@@ -0,0 +1,103 @@
+/**
+ * 数据模型基类
+ *
+ * @author CaiAoLin
+ * @date 2017/6/22
+ * @version
+ */
+import MongooseHelper from "../helper/mongoose_helper";
+
+class BaseModel {
+
+    /**
+     * mongoose数据模型
+     *
+     * @var {object}
+     */
+    model = null;
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        if (new.target === BaseModel) {
+            throw new Error('BaseModel不能实例化,只能继承使用。');
+        }
+    }
+
+    /**
+     * 初始化函数
+     *
+     * @return {void}
+     */
+    init() {
+        if (this.model === null) {
+            throw new Error('子类数据有误');
+        }
+
+        this.db = new MongooseHelper();
+        this.db.model = this.model;
+    }
+
+    /**
+     * 根据id查找对应数据
+     *
+     * @param {Object} condition
+     * @param {Object} fields
+     * @param {boolean} singleData
+     * @return {Promise}
+     */
+    async findDataByCondition(condition, fields = null, singleData = true) {
+        if (Object.keys(condition).length <= 0) {
+            return null;
+        }
+
+        let data = await singleData ? this.db.findOne(condition, fields) : this.db.find(condition);
+        return data;
+    }
+
+    /**
+     * 根据条件返回数据数量
+     *
+     * @param {object} condition
+     * @return {Promise}
+     */
+    async count(condition = null) {
+        let total = 0;
+        try {
+            total = await this.db.count(condition);
+        } catch (error) {
+            total = 0;
+        }
+        return total;
+    }
+
+    /**
+     * 根据id删除
+     *
+     * @param {Number} id
+     * @return {Promise}
+     */
+    async deleteById(id) {
+        let result = false;
+        id = parseInt(id);
+        if (isNaN(id) || id <= 0) {
+            return false;
+        }
+
+        try {
+            let deleteResult = await this.db.delete({id: id});
+            result = deleteResult.result.ok === 1;
+        } catch (error) {
+            console.log(error);
+            result = false;
+        }
+
+        return result;
+    }
+
+}
+
+export default BaseModel;

+ 165 - 0
modules/common/helper/mongoose_helper.js

@@ -0,0 +1,165 @@
+/**
+ * mongodb Helper
+ *
+ * @author caiaolin
+ * @date 2017/5/22.
+ */
+
+class MongooseHelper {
+
+    /**
+     * mongoose连接对象
+     *
+     * @var {object}
+     */
+    connect = null;
+
+    /**
+     * mongoose数据模型
+     *
+     * @var {object}
+     */
+    model = null;
+
+    /**
+     * 查找单一数据
+     *
+     * @param {object} conditions
+     * @param {object} fields
+     * @return {Promise}
+     */
+    findOne(conditions, fields = null) {
+        let self = this;
+        return new Promise(function (resolve, reject) {
+            self.model.findOne(conditions, fields, function (error, data) {
+                if (error) {
+                    reject(null);
+                } else {
+                    resolve(data);
+                }
+            });
+        });
+    }
+
+    /**
+     * 查找数据
+     *
+     * @param {object} conditions
+     * @param {object} fields
+     * @return {Promise}
+     */
+    find(conditions, fields = null) {
+        let self = this;
+        return new Promise(function (resolve, reject) {
+            self.model.find(conditions, fields, function (error, data) {
+                if (error) {
+                    reject(error);
+                } else {
+                    resolve(data);
+                }
+            });
+        });
+    }
+
+    /**
+     * 关联查找数据
+     *
+     * @param {object} conditions
+     * @param {object} fields
+     * @param {String|Object} populate
+     * @return {Promise}
+     */
+    findWithPopulate(conditions, fields = null, populate = '') {
+        let self = this;
+        return new Promise(function (resolve, reject) {
+            self.model.find(conditions, fields).populate(populate).exec(function(error, data) {
+                if (error) {
+                    reject(error);
+                } else {
+                    resolve(data);
+                }
+            });
+        });
+    }
+
+    /**
+     * 查找且更新(原子操作)
+     *
+     * @param {object} update
+     * @param {object} condition
+     * @param {object} options
+     * @return {Promise}
+     */
+    findAndModify(condition, update, options) {
+        let self = this;
+        return new Promise(function (resolve, reject) {
+            self.model.findOneAndUpdate(condition, update, options, function(error, data) {
+                    if (error) {
+                        reject(error);
+                    } else {
+                        resolve(data);
+                    }
+                });
+        });
+    }
+
+    /**
+     * 新增操作
+     *
+     * @param {object} data
+     * @return {Promise}
+     */
+    create(data) {
+        let self = this;
+        return new Promise(function (resolve, reject) {
+            self.model.create(data, function(error, data) {
+                if (error) {
+                    reject(error);
+                } else {
+                    resolve(data);
+                }
+            });
+        });
+    }
+
+    /**
+     * 统计数据数量
+     *
+     * @param {Object} condition
+     * @return {Promise}
+     */
+    count(condition) {
+        let self = this;
+        return new Promise(function(resolve, reject) {
+            self.model.count(condition, function(error, data) {
+                if (error) {
+                    reject(error);
+                } else {
+                    resolve(data);
+                }
+            });
+        });
+    }
+
+    /**
+     * 删除数据
+     *
+     * @param {Object} condition
+     * @return {Promise}
+     */
+    delete(condition) {
+        let self = this;
+        return new Promise(function(resolve, reject) {
+            self.model.remove(condition, function(error, data) {
+                if (error) {
+                    reject(error);
+                } else {
+                    resolve(data);
+                }
+            });
+        });
+    }
+
+}
+
+export default MongooseHelper;

+ 139 - 0
modules/glj/controllers/glj_controller.js

@@ -0,0 +1,139 @@
+/**
+ * 工料机相关控制器
+ *
+ * @author CaiAoLin
+ * @date 2017/6/22
+ * @version
+ */
+import BaseController from "../../common/base/base_controller";
+import GLJListModel from "../models/glj_list_model";
+import UnitPriceModel from "../models/unit_price_model";
+
+class GLJController extends BaseController {
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        let parent = super();
+        parent.title = '工料机';
+    }
+
+    /**
+     * 工料机汇总页
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async index(request, response) {
+        let tenderId = request.query.tender;
+        tenderId = parseInt(tenderId);
+        try {
+            if (isNaN(tenderId) || tenderId <= 0) {
+                throw '标段id有误';
+            }
+            // 先获取对应标段的项目工料机数据
+            let gljListModel = new GLJListModel();
+            // 获取指定标段的单价文件  @todo 后续需要在标段属性中加入该标段绑定的标段id
+            let unitPriceTenderId = 1;
+            let gljList = await gljListModel.getListByTenderId(tenderId, unitPriceTenderId);
+
+            let renderData = {
+                gljList: JSON.stringify(gljList),
+                materialIdList: gljListModel.materialIdList
+            };
+            response.render('glj/html/glj_index', renderData);
+
+        } catch (error) {
+            response.status(404).send('404 Error');
+        }
+
+    }
+
+    /**
+     * 模拟定额插入
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async test(request, response) {
+        // 从定额库获取的数据
+        let data = {
+            glj_repository_id: 1,
+            project_id: 1,
+            tender_id: 1,
+            code: '00010201',
+            name: '土石方综合工日',
+            specs: '',
+            unit: '工日',
+            type: 2,
+            type_of_work: 2,
+            base_price: 38.88,
+            market_price: 38.88
+        };
+        try {
+            let gljListModel = new GLJListModel();
+            let result = await gljListModel.addList(data);
+
+            console.log(result);
+        } catch (error) {
+            console.log(error);
+        }
+
+        response.end('success');
+    }
+
+    /**
+     * 模拟定额修改单价
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async testModify(request, response) {
+        let tenderId = request.query.tender;
+        // 修改数据
+        let updateData = {
+            code: '00010201',
+            market_price: '40',
+            name: '土石方综合工日',
+            tender_id: tenderId,
+            project_id: 1
+        };
+        let unitPriceTenderId = 1;
+        try {
+            let unitPriceModel = new UnitPriceModel();
+            let result = await unitPriceModel.updateUnitPrice(updateData, unitPriceTenderId);
+
+            console.log(result);
+        } catch (error) {
+            console.log(error);
+        }
+        response.end('success');
+    }
+
+    /**
+     * 测试删除
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async delete(request, response) {
+        try {
+            let gljListModel = new GLJListModel();
+            let result = await gljListModel.deleteById(63);
+
+            console.log(result);
+        } catch (error) {
+            console.log(error);
+        }
+        response.end('success');
+    }
+}
+
+export default GLJController;

+ 0 - 8
modules/glj/db/unit_price_file_db.js

@@ -1,8 +0,0 @@
-/**
- * Created by jimiz on 2017/5/12.
- */
-var mongoose = require("mongoose");
-var dbm = require("../../../config/db/db_manager");
-var db = dbm.getCfgConnection("unitPriceFile");
-
-module.exports = db;

+ 0 - 8
modules/glj/db/user_glj_lib_db.js

@@ -1,8 +0,0 @@
-/**
- * Created by jimiz on 2017/5/12.
- */
-var mongoose = require("mongoose");
-var dbm = require("../../../config/db/db_manager");
-var db = dbm.getCfgConnection("userGLJLib");
-
-module.exports = db;

+ 54 - 0
modules/glj/models/counter_model.js

@@ -0,0 +1,54 @@
+/**
+ * ID计数器数据模型
+ *
+ * @author CaiAoLin
+ * @date 2017/6/23
+ * @version
+ */
+import BaseModel from "../../common/base/base_model";
+import CounterSchema from "./schemas/counter";
+
+class CounterModel extends BaseModel {
+
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        let parent = super();
+        parent.model = CounterSchema;
+        parent.init();
+    }
+
+    /**
+     * 获取自增id
+     *
+     * @param {String} model
+     * @return {Number}
+     */
+    async getId(model = '') {
+        if (model === '') {
+            return 0;
+        }
+        let update = {$inc: {sequence_value: 1}};
+        let condition = {_id: model};
+        let options = {new: true};
+
+        // 先查找更新
+        let idData = await this.db.findAndModify(condition, update, options);
+        let idResult = idData === null || idData.sequence_value === undefined ? 0 : idData.sequence_value;
+
+        // 如果没有对应记录则新增
+        if (idResult === 0) {
+            let insertResult = await this.db.create({_id: model, sequence_value: 1});
+            idResult = Object.keys(insertResult).length > 0 ? 1 : 0;
+        }
+
+        return idResult;
+    }
+
+}
+
+export default CounterModel;

+ 0 - 99
modules/glj/models/glj_list.js

@@ -1,99 +0,0 @@
-/**
- * Created by jimiz on 2017/5/10.
- * 单价文件的GLJ列表,注意与项目GLJ区分
- */
-var mongoose = require("mongoose");
-var db = require("../db/unit_price_file_db");
-var Schema = mongoose.Schema;
-var deleteSchema = require('../../../public/models/delete_schema');
-var counter = require("../../../public/counter/counter.js");
-var consts = require('../../main/models/project_consts');
-var projectConsts = consts.projectConst;
-var commonConsts = consts.commonConst;
-
-var GLJListSchema = new Schema({
-    ID: Number,
-    fileID: Number,
-    code: String,
-    name: String,
-    specs: String,
-    unit: String,
-    type: Number,
-    adjustPrice: String,
-    rationPrice: String,
-    price: String, //Decimal
-    deleteInfo: deleteSchema
-});
-
-var GLJList = db.model("GLJList", GLJListSchema, "GLJList");
-
-var GLJListDAO = function(){};
-
-GLJListDAO.prototype.getData = function(fileID, callback){
-    var me = this;
-    GLJList.find({'$or': [{fileID: fileID, deleteInfo: null}, {fileID: fileID, 'deleteInfo.deleted': {$in: [null, false]}}]}, '-_id', function(err, datas){
-        if (!err) {
-            callback(0, projectConsts.GLJLIST, datas);
-        } else {
-            callback(1, '', null);
-        }
-    });
-};
-
-// 单价文件中的工料机不在这里新增,只能由add方法新增,删除需要在前端projectGLJ判断:1、是新工料机;2、没有定额引用,才可以删除
-GLJListDAO.prototype.save = function(fileId, datas, callback){
-    var functions = [];
-    var data;
-
-    // cb中返回doc,以便进行同步
-    function saveOne(doc) {
-        return function (cb) {
-            function updateCallback(err, data){
-                cb(err, doc);
-            }
-            switch (doc.updateType) {
-                case commonConsts.UT_UPDATE:
-                    ration.update({ID: doc.ID}, doc, updateCallback);
-                    break;
-                case commonConsts.UT_DELETE:
-                /* 假删除
-                 var item = new ration(doc);
-                 item.remove(cb);
-                 */
-            }
-        }
-    }
-    for (var i = 0; i < datas.length; i++){
-        data = datas[i];
-        functions.push(saveOne(data));
-    }
-
-    async.parallel(functions, callback);
-};
-
-GLJListDAO.prototype.add = function(fileID, count, callback){
-
-    function newCallback(err, lowID, highID){
-        if (!err) {
-            var datas = [];
-            for (var i = lowID; i <= highID; i++){
-                var GLJ = new GLJList;
-                GLJ.ID = i;
-                GLJ.fileID = fileID;
-                datas.push(GLJ);
-            }
-            callback(err, datas);
-
-        }
-        else{
-            callback(err, null);
-        }
-    }
-
-    counter.counterDAO.getIDAfterCount(COUNTER_MODULE_NAME.unitPriceGLJ, IDStep, function(err, highID){
-        var lowID = highID - IDStep + 1;
-        newCallback(err, lowID, highID);
-    });
-};
-
-module.exports = new GLJListDAO();

+ 172 - 0
modules/glj/models/glj_list_model.js

@@ -0,0 +1,172 @@
+/**
+ * 项目工料机列表数据模型
+ *
+ * @author CaiAoLin
+ * @date 2017/6/22
+ * @version
+ */
+import BaseModel from "../../common/base/base_model";
+import {default as GLJSchemas, collectionName as gljCollectionName} from "./schemas/glj";
+import CounterModel from "./counter_model";
+import UnitPriceModel from "./unit_price_model";
+
+class GLJListModel extends BaseModel {
+
+    /**
+     * 材料类型id
+     *
+     * @var {Array}
+     */
+    materialIdList = [5, 6, 7];
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        let parent = super();
+        parent.model = GLJSchemas;
+        parent.init();
+    }
+
+    /**
+     * 设置场景
+     *
+     * @param {string} scene
+     * @return {void}
+     */
+    setScene(scene = '') {
+        switch (scene) {
+            // 新增数据的验证规则
+            case 'add':
+                this.model.schema.path('glj_repository_id').required(true);
+                this.model.schema.path('project_id').required(true);
+                this.model.schema.path('tender_id').required(true);
+                this.model.schema.path('code').required(true);
+                this.model.schema.path('name').required(true);
+                this.model.schema.path('type_of_work').required(true);
+                break;
+        }
+    }
+
+    /**
+     * 根据标段对应工料机数据
+     *
+     * @param {Number} tenderId
+     * @param {Number} unitPriceTenderId
+     * @return {Promise}
+     */
+    async getListByTenderId(tenderId, unitPriceTenderId) {
+        let gljData = null;
+        try {
+            // 首先获取对应标段下所有的项目工料机数据
+            let condition = {tender_id: tenderId};
+            let fields = {_id: 0};
+            gljData = await this.db.find(condition, fields);
+
+            // 没有数据则直接返回空
+            if (gljData.length <= 0) {
+                throw '无数据';
+            }
+
+            // 获取标段设置的单价文件数据
+            let unitPriceModel = new UnitPriceModel();
+            let unitPriceList = await unitPriceModel.getDataByTenderId(unitPriceTenderId);
+
+            // 组合数据
+            this.combineUnitPrice(gljData, unitPriceList);
+
+        } catch (error) {
+            gljData = [];
+        }
+
+        return gljData;
+    }
+
+    /**
+     * 组合工料机数据和单价文件数据
+     *
+     * @param {object} gljList
+     * @param {object} unitPriceList
+     * @return {void}
+     */
+    combineUnitPrice(gljList, unitPriceList) {
+        // 循环组合数据
+        for(let glj of gljList) {
+            if (glj.code === undefined) {
+                continue;
+            }
+            glj.unit_price = unitPriceList[glj.code + glj.name] !== undefined ? unitPriceList[glj.code + glj.name] : null;
+            glj.adjust_price = glj.adjustment * glj.unit_price.market_price;
+        }
+    }
+
+    /**
+     * 新增项目工料机数据(包括新增单价文件) 定额工料机新增时调用
+     *
+     * @param {object} data
+     * @return {Promise} 返回插入成功的数据id
+     */
+    async addList(data) {
+        let result = null;
+        try {
+            if (Object.keys(data).length <= 0) {
+                throw '新增数据为空';
+            }
+            // 首先查找是否有同编码同名称的工料机数据
+            let projectGljData = await this.findDataByCondition({code: data.code, tender_id: data.tender_id});
+            if (projectGljData && projectGljData.id > 0) {
+                // 如果找到数据则直接返回
+                return projectGljData;
+            }
+
+            // 新增单条记录 (两个操作本来应该是事务操作,然而mongodb事务支持比较弱,就当作是都可以顺利执行)
+            let gljInsertData = await this.add(data);
+            if (!gljInsertData) {
+                throw '新增项目工料机失败!';
+            }
+
+            // 获取标段属性中的单价文件关联标段id @todo
+            let unitPriceTenderId = 1;
+
+            // 新增单价文件
+            let unitPriceModel = new UnitPriceModel();
+            let unitPriceInsertData = await unitPriceModel.updateUnitPrice(data, unitPriceTenderId);
+
+            if (!unitPriceInsertData) {
+                throw '新增单价失败!';
+            }
+
+            gljInsertData.unit_price = unitPriceInsertData;
+            result = gljInsertData;
+        } catch (error) {
+            console.log(error);
+            result = null;
+        }
+
+        return result;
+    }
+
+    /**
+     * 新增单条工料机数据
+     *
+     * @param {object} data
+     * @return {Promise}
+     */
+    async add(data) {
+        if (Object.keys(data).length <= 0) {
+            throw '新增数据为空';
+        }
+
+        let counterModel = new CounterModel();
+        data.id = await counterModel.getId(gljCollectionName);
+
+        this.setScene('add');
+        let result = await this.db.model.create(data);
+        return result;
+    }
+
+}
+
+export default GLJListModel;

+ 70 - 0
modules/glj/models/glj_repository_model.js

@@ -0,0 +1,70 @@
+/**
+ * 工料机数据源模块
+ *
+ * @author CaiAoLin
+ * @date 2017/6/23
+ * @version
+ */
+import BaseModel from "../../common/base/base_model";
+import GLJRepositorySchema from "./schemas/glj_repository";
+
+class GLJRepositoryModel extends BaseModel {
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        let parent = super();
+        parent.model = GLJRepositorySchema;
+        parent.init();
+    }
+
+    /**
+     * 新增数据
+     *
+     * @param {object} data
+     * @return {Promise} boolean
+     */
+    async add(data) {
+        let result = false;
+        try {
+            result = await this.db.create(data);
+        } catch (error) {
+            if (error.name !== null && error.name === 'ValidationError') {
+                // 这里是数据验证失败
+                console.log('数据验证失败!');
+            }
+            result = false;
+        }
+
+        return result;
+    }
+
+    /**
+     * 根据条件获取对应工料机数据
+     *
+     * @param {Object} condition
+     * @return {Promise} Array
+     */
+    async getDataByCondition(condition) {
+        let gljData = [];
+        try {
+            if (Object.keys(condition).length <= 0) {
+                throw '筛选条件有误';
+            }
+
+            gljData = await this.db.find(condition);
+
+        } catch (error) {
+            console.log(error);
+            gljData = [];
+        }
+
+        return gljData;
+    }
+
+}
+
+export default GLJRepositoryModel;

+ 22 - 0
modules/glj/models/schemas/counter.js

@@ -0,0 +1,22 @@
+/**
+ * 计数器数据结构
+ *
+ * @author CaiAoLin
+ * @date 2017/6/29
+ * @version
+ */
+import mongoose from "mongoose";
+
+let Schema = mongoose.Schema;
+let collectionName = 'counter';
+let modelSchema = {
+    // 模块名称
+    _id: String,
+    // 计数器
+    sequence_value: {
+        type: Number,
+        default: 1
+    },
+};
+
+export default mongoose.model(collectionName, new Schema(modelSchema, {versionKey: false}));

+ 69 - 0
modules/glj/models/schemas/glj.js

@@ -0,0 +1,69 @@
+/**
+ * 工料机数据模型
+ *
+ * @author CaiAoLin
+ * @date 2017/6/29
+ * @version
+ */
+import mongoose from "mongoose";
+
+let Schema = mongoose.Schema;
+let collectionName = 'glj_list';
+let modelSchema = {
+    // 自增id
+    id: Number,
+    // 工料机总库ID
+    glj_repository_id: Number,
+    // 项目ID
+    project_id: Number,
+    // 标段ID
+    tender_id: Number,
+    // 编码
+    code: String,
+    // 名称
+    name: String,
+    // 人工工种
+    type_of_work: Number,
+    // 是否暂估 (0为否 1为是)
+    is_evaluate: {
+        type: Number,
+        default: 0
+    },
+    // 供货方式
+    supply: {
+        type: String,
+        default: ''
+    },
+    // 甲供数量
+    supply_quantity: {
+        type: Number,
+        default: 0
+    },
+    // 交货方式
+    delivery: {
+        type: String,
+        default: ''
+    },
+    // 送达地点
+    delivery_address: {
+        type: String,
+        default: ''
+    },
+    // 不调价
+    is_adjust_price: {
+        type: Number,
+        default: 0
+    },
+    // 调整系数
+    adjustment: {
+        type: Number,
+        default: 1
+    },
+    // 显示调整基价
+    adjust_price: String,
+    // 显示关联单价文件的字段
+    unit_price: Schema.Types.Mixed
+};
+let model = mongoose.model(collectionName, new Schema(modelSchema, {versionKey: false}));
+
+export { model as default, collectionName as collectionName} ;

+ 29 - 0
modules/glj/models/schemas/glj_repository.js

@@ -0,0 +1,29 @@
+/**
+ * 数据结构
+ *
+ * @author CaiAoLin
+ * @date 2017/6/28
+ * @version
+ */
+import mongoose from "mongoose";
+
+let Schema = mongoose.Schema;
+let collectionName = 'glj_repository';
+let modelSchema = {
+    // 自增id
+    id: Number,
+    // 定额id
+    ration_id: Number,
+    // 名称
+    name: String,
+    // 编号
+    code: String,
+    // 单位
+    unit: String,
+    // 定额单价
+    ration_price: String,
+    // 规格
+    standard: String
+};
+
+export default mongoose.model(collectionName, new Schema(modelSchema, {versionKey: false}));

+ 39 - 0
modules/glj/models/schemas/unit_price.js

@@ -0,0 +1,39 @@
+/**
+ * 单价文件数据结构
+ *
+ * @author CaiAoLin
+ * @date 2017/6/29
+ * @version
+ */
+import mongoose from "mongoose";
+
+let Schema = mongoose.Schema;
+let collectionName = 'unit_price';
+let modelSchema = {
+    // 自增ID
+    id: Number,
+    // 项目id
+    project_id: Number,
+    // 标段id
+    tender_id: Number,
+    // 基价单价
+    base_price: String,
+    // 市场单价
+    market_price: String,
+    // 编码
+    code: String,
+    // 名称
+    name: String,
+    // 规格型号
+    specs: {
+        type: String,
+        default: ''
+    },
+    // 单位
+    unit: String,
+    // 类型
+    type: Number,
+
+};
+let model = mongoose.model(collectionName, new Schema(modelSchema, {versionKey: false, collection: collectionName}));
+export {model as default, collectionName as collectionName};

+ 0 - 77
modules/glj/models/unit_price_file.js

@@ -1,77 +0,0 @@
-/**
- * Created by jimiz on 2017/5/12.
- */
-var mongoose = require("mongoose");
-var db = require("../db/unit_price_file_db");
-var Schema = mongoose.Schema;
-var deleteSchema = require('../../../public/models/delete_schema');
-var consts = require('../../main/models/project_consts');
-var projectConsts = consts.projectConst;
-var commonConsts = consts.commonConst;
-var GLJList = require('./glj_list');
-
-var unitPriceFileSchema = new Schema({
-    ID: Number,
-    name: String,
-    version: Number,
-    projects: Array,
-    deleteInfo: deleteSchema
-});
-
-var unitPriceFile = db.model("unitPriceFile", unitPriceFileSchema, "unitPriceFile");
-
-var unitPriceFileDAO = function(){};
-
-unitPriceFileDAO.prototype.getData = function(fileID, callback){
-    var data;
-    unitPriceFile.find({'$or': [{fileID: fileID, deleteInfo: null}, {fileID: fileID, 'deleteInfo.deleted': {$in: [null, false]}}]}, '-_id', function(err, datas){
-        if (!err) {
-            GLJList.getData(fileID, function(err, gljList){
-                data['properties'] = datas[0];
-                data['glj_list'] = gljList;
-                callback(0, projectConsts.UNITPRICEFILE, data);
-            });
-        } else {
-            callback(1, '', null);
-        }
-    });
-};
-
-unitPriceFileDAO.prototype.save = function(fileId, datas, callback){
-    function changed(err, changedDatas){
-        sync(fileId, changedDatas, callback);
-    }
-
-
-    GLJList.save(fileID, datas, changed);
-};
-
-unitPriceFileDAO.prototype.sync = function(fileID, datas, callback){
-    unitPriceFile.find({'$or': [{ID: fileID, deleteInfo: null}, {ID: fileID, 'deleteInfo.deleted': {$in: [null, false]}}]}, '-_id', function(err, datas){
-        if (!err) {
-            // to do
-
-            callback(0, projectConsts.UNITPRICEFILE, datas);
-        } else {
-            callback(1, '', null);
-        }
-    });
-};
-
-unitPriceFileDAO.prototype.add = function(fileID, count, callback){
-    GLJList.add(fileID, count, callback);
-};
-
-unitPriceFileDAO.prototype.newFile = function(data, callback){
-
-};
-
-unitPriceFileDAO.prototype.useFile = function(fileID, projectID, callback){
-
-};
-
-unitPriceFileDAO.prototype.unuseFile = function(fileID, projectID, callback){
-
-};
-
-module.exports = new unitPriceFileDAO();

+ 210 - 0
modules/glj/models/unit_price_model.js

@@ -0,0 +1,210 @@
+/**
+ * 单价文件业务模型
+ *
+ * @author CaiAoLin
+ * @date 2017/6/30
+ * @version
+ */
+import BaseModel from "../../common/base/base_model"
+import CounterModel from "./counter_model"
+import GLJListModel from "./glj_list_model";
+import {default as UnitPriceSchema, collectionName as collectionName} from "./schemas/unit_price";
+
+class UnitPriceModel extends BaseModel {
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        let parent = super();
+        parent.model = UnitPriceSchema;
+        parent.init();
+    }
+
+    /**
+     * 根据标段获取对应单价数据
+     *
+     * @param {Number} tenderId
+     * @return {Promise}
+     */
+    async getDataByTenderId(tenderId) {
+        tenderId = parseInt(tenderId);
+        if (isNaN(tenderId) || tenderId <= 0) {
+            return null;
+        }
+
+        let unitPriceList = await this.db.model.find({tender_id: tenderId});
+        if (unitPriceList.length <= 0) {
+            return null;
+        }
+
+        // 整理数据
+        let result = {};
+        for(let tmp of unitPriceList) {
+            result[tmp.code + tmp.name] = tmp;
+        }
+
+        return result;
+    }
+
+    /**
+     * 设置场景
+     *
+     * @param {string} scene
+     * @return {void}
+     */
+    setScene(scene = '') {
+        switch (scene) {
+            // 新增数据的验证规则
+            case 'add':
+                this.model.schema.path('project_id').required(true);
+                this.model.schema.path('tender_id').required(true);
+                this.model.schema.path('base_price').required(true);
+                this.model.schema.path('market_price').required(true);
+                this.model.schema.path('name').required(true);
+                this.model.schema.path('code').required(true);
+                this.model.schema.path('unit').required(true);
+                this.model.schema.path('type').required(true);
+        }
+    }
+
+    /**
+     * 新增单价文件
+     *
+     * @param {Object} data
+     * @return {Promise}
+     */
+    async addData(data) {
+        let result = false;
+        try {
+            // 首先查找是否有相同的记录
+            let unitPriceData = await this.db.model.findOne({code: data.code, market_price: data.market_price});
+            if (unitPriceData && unitPriceData.id > 0) {
+                return true;
+            }
+
+            // 没有则新增数据
+            result = this.db.model.create(data);
+        } catch (error) {
+            result = false;
+        }
+
+        return result;
+    }
+
+    /**
+     * 更新单价文件(定额中修改价格时调用)
+     *
+     * @param {Object} data
+     * @param {Number} unitPriceTenderId
+     * @return {Promise}
+     */
+    async updateUnitPrice(data, unitPriceTenderId) {
+        if (data.code === undefined || data.tender_id === undefined || data.name === undefined
+            || data.market_price === undefined) {
+            return null;
+        }
+        let marketPrice = data.market_price !== undefined ? data.market_price : data.base_price;
+
+        // 先查找是否有同code的单价记录 @todo 后续可能会加入单位这个字段进一步确定唯一性
+        let unitPriceData = await this.findDataByCondition({code: data.code, tender_id: unitPriceTenderId}, null, false);
+
+        // 如果有记录,判断是否存在一样的市场单价,有则直接返回数据
+        if (unitPriceData && this.isPriceIncluded(unitPriceData, data.market_price)) {
+            return unitPriceData;
+        }
+
+        // 如果不存在基价单价,则在数据源中获取
+        if (data.base_price === undefined) {
+            let firstUnitPrice = unitPriceData[0] !== undefined ? unitPriceData[0] : [];
+            data.base_price = firstUnitPrice.base_price !== undefined ? firstUnitPrice.base_price : 0;
+            data.type = firstUnitPrice.type !== undefined ? firstUnitPrice.type : 0;
+            data.unit = firstUnitPrice.unit !== undefined ? firstUnitPrice.unit : 0;
+        }
+
+        let insertData = {
+            code: data.code,
+            base_price: data.base_price,
+            market_price: marketPrice,
+            project_id: data.project_id,
+            tender_id: unitPriceTenderId,
+            name: data.name,
+            type: data.type,
+            unit: data.unit
+        };
+
+        // 统计当前同code的数量
+        let sameCount = await this.count({code: data.code});
+
+        if (sameCount > 0) {
+            // 如果存在有别的同code的数据,则新增一条项目工料机,并更改name
+            let regular = /\(\d\)/;
+            let changeString = '(' + sameCount + ')';
+            insertData.name = regular.test(insertData.name) ? insertData.name.replace(regular, changeString) :
+                insertData.name + changeString;
+
+            // 然后再插入一条项目工料机数据
+            let gljListModel = new GLJListModel();
+            // 首先先查找原有数据
+            let originalData = await gljListModel.findDataByCondition({code: data.code, tender_id: data.tender_id}, {_id: 0});
+
+            // 查不到数据直接抛出错误
+            if(!originalData) {
+                throw '没有找到code为:' + data.code + '的数据';
+            }
+
+            // 新增一条新name的项目工料机
+            originalData.name = insertData.name;
+            // 这里由于查出来的数据带有隐藏属性,所以先用json转一下
+            originalData = JSON.stringify(originalData);
+            let addGLJResult = await gljListModel.add(JSON.parse(originalData));
+            if (!addGLJResult) {
+                throw '新增工料机数据失败!';
+            }
+        }
+
+        return this.add(insertData);
+    }
+
+    /**
+     * 新增记录
+     *
+     * @param {object} data
+     * @return {Promise}
+     */
+    async add(data) {
+        let counterModel = new CounterModel();
+        let unitPriceId = await counterModel.getId(collectionName);
+        data.id = unitPriceId;
+
+        this.setScene('add');
+        return this.db.model.create(data);
+    }
+
+    /**
+     * 判断数据中是否包含某个市场价格的记录
+     *
+     * @param {Array} data
+     * @param {Number} price
+     * @return boolean
+     */
+    isPriceIncluded(data, price) {
+        let result = false;
+        if (data.length <= 0) {
+            return result;
+        }
+
+        for(let tmp of data) {
+            if (tmp.market_price === price) {
+                return true;
+            }
+        }
+
+        return result;
+    }
+
+}
+
+export default UnitPriceModel;

+ 0 - 3
modules/glj/models/user_glj_lib.js

@@ -1,3 +0,0 @@
-/**
- * Created by jimiz on 2017/5/12.
- */

+ 20 - 0
modules/glj/routes/glj_router.js

@@ -0,0 +1,20 @@
+/**
+ * 登录相关路由
+ *
+ * @author CaiAoLin
+ * @date 2017/6/22
+ * @version
+ */
+import Express from "express";
+import GLJController from "../controllers/glj_controller";
+
+const router = Express.Router();
+let gljController = new GLJController();
+
+// action定义区域
+router.get('/', gljController.init, gljController.index);
+router.get('/test', gljController.init, gljController.test);
+router.get('/testModify', gljController.init, gljController.testModify);
+router.get('/testDelete', gljController.init, gljController.delete);
+
+module.exports = router;

+ 10 - 9
modules/main/models/project_glj.js

@@ -2,6 +2,7 @@
  * Created by jimiz on 2017/4/1.
  * 工料机汇总
  */
+/*
 var mongoose = require("mongoose");
 var db = require("../db/project_db");
 var Schema = mongoose.Schema;
@@ -9,8 +10,8 @@ var deleteSchema = require('../../../public/models/delete_schema');
 var consts = require('./project_consts');
 var projectConsts = consts.projectConst;
 var commonConsts = consts.commonConst;
-var unitPriceFile = require('../../glj/models/unit_price_file');
-var upGLJList = require('../../glj/models/glj_list');
+var unitPriceFile = require('../../glj_bak/models/unit_price_file');
+var upGLJList = require('../../glj_bak/models/glj_list');
 var projectProperties = require('./project_properties.js')
 
 var projectGLJSchema = new Schema({
@@ -94,16 +95,15 @@ projectGLJDAO.prototype.save = function(projectID, datas, callback){
                 case commonConsts.UT_UPDATE:
                     projectGLJ.update({ID: doc.ID}, doc, cb);
                     break;
-                case commonConsts.UT_DELETE:
-                /* 假删除
-                 var item = new ration(doc);
-                 item.remove(cb);
-                 */
+                // commonConsts.UT_DELETE:
+                 // 假删除
+                 // var item = new ration(doc);
+                 // item.remove(cb);
+
             }
         }
     }
     for (var i = 0; i < datas.length; i++){
-        data = datas[i];
         functions.push(saveOne(data));
     }
 
@@ -121,4 +121,5 @@ projectGLJDAO.prototype.syncData = function(projectID, datas, callback){
 
 };
 
-module.exports = new projectGLJDAO();
+module.exports = new projectGLJDAO();
+ */

+ 4 - 4
modules/users/controllers/boot_controller.js

@@ -5,9 +5,10 @@
  * @date 2017/6/12
  * @version
  */
-let UserModel = require("../models/user_model");
+import UserModel from "../models/user_model";
+import BaseController from "../../common/base/base_controller";
 
-class BootController {
+class BootController extends BaseController {
 
     /**
      * 引导页
@@ -28,5 +29,4 @@ class BootController {
     }
 
 }
-
-module.exports = BootController;
+export default BootController;

+ 6 - 3
modules/users/controllers/login_controller.js

@@ -5,8 +5,7 @@
  * @date 2017/6/8
  * @version
  */
-let Request = require('request');
-let UserModel = require("../models/user_model");
+import UserModel from "../models/user_model";
 
 class LoginController {
 
@@ -18,6 +17,10 @@ class LoginController {
      * @return {void}
      */
     index(request, response) {
+        let sessionUser = request.session.sessionUser;
+        if (sessionUser !== undefined && sessionUser.ssoId >= 0) {
+            return response.redirect("/pm");
+        }
         response.render('users/html/login', {});
     }
 
@@ -72,4 +75,4 @@ class LoginController {
 
 }
 
-module.exports = LoginController;
+export default LoginController;

+ 3 - 1
modules/users/controllers/user_controller.js

@@ -6,8 +6,9 @@
  * @version
  */
 import UserModel from "../models/user_model";
+import BaseController from "../../common/base/base_controller";
 
-class UserController {
+class UserController extends BaseController {
 
     /**
      * 用户信息页面
@@ -53,6 +54,7 @@ class UserController {
             // 切换验证场景
             let userModel = new UserModel();
             let condition = {email: sessionUser.email};
+            userModel.setScene('saveInfo');
             let result = await userModel.updateUser(condition, updateData);
 
             if (result.ok !== 1) {

+ 29 - 0
modules/users/models/schema/user.js

@@ -0,0 +1,29 @@
+/**
+ * 用户数据结构
+ *
+ * @author CaiAoLin
+ * @date 2017/6/28
+ * @version
+ */
+let mongoose = require("mongoose");
+let Schema = mongoose.Schema;
+
+// 表名
+let collectionName = 'user';
+
+// 表结构
+let schema = {
+    username: String,
+    email: String,
+    mobile: String,
+    real_name: String,
+    company: String,
+    province: String,
+    area: Number,
+    company_type: Number,
+    company_scale: Number,
+    last_login: Number,
+    create_time: Number
+};
+
+export default mongoose.model(collectionName, new Schema(schema, {versionKey: false}));

+ 41 - 95
modules/users/models/user_model.js

@@ -1,66 +1,15 @@
 /**
- * 用户数据模型
+ * 用户业务模型
  *
  * @author CaiAoLin
  * @date 2017/6/9
  * @version
  */
-let mongoose = require("mongoose");
-let dbm = require("../../../config/db/db_manager");
-let Request = require("request");
+import userSchema from "./schema/user";
+import Request from "request";
+import BaseModel from "../../common/base/base_model"
 
-class UserModel {
-
-    /**
-     * 模型
-     *
-     * @var {object}
-     */
-    model = null;
-
-    /**
-     * 默认结构
-     *
-     * @var {object}
-     */
-    defaultSchema = {
-        username: {
-            type: String,
-        },
-        email: {
-            type: String,
-        },
-        mobile: {
-            type: String,
-        },
-        real_name: {
-            type: String,
-        },
-        company: {
-            type: String,
-        },
-        province: {
-            type: String,
-        },
-        area: {
-            type: Number,
-        },
-        company_type: Number,
-        company_scale: Number,
-        last_login: Number,
-        create_time: Number
-    };
-
-    /**
-     * 构造函数
-     *
-     * @return {void}
-     */
-    constructor() {
-        let umDB = dbm.getCfgConnection('scConstruct');
-        let usersSchema = new mongoose.Schema(this.defaultSchema);
-        this.model = umDB.model('users', usersSchema);
-    }
+class UserModel extends BaseModel {
 
     /**
      * 企业所在地区
@@ -83,6 +32,18 @@ class UserModel {
      */
     companyScale = ['1-20', '20-50', '50-100', '100+'];
 
+
+    /**
+     * 构造函数
+     *
+     * @return {void}
+     */
+    constructor() {
+        let parent = super();
+        parent.model = userSchema;
+        parent.init();
+    }
+
     /**
      * 根据用户名密码调用SSO接口获取信息
      *
@@ -123,6 +84,7 @@ class UserModel {
     async markUser(userData) {
         let userDataFromDb = await this.findDataByName(userData.username);
         let result = false;
+
         // 信息是否补全
         let info = false;
         if (userDataFromDb.length <= 0) {
@@ -143,30 +105,36 @@ class UserModel {
     }
 
     /**
+     * 选择场景
+     *
+     * @param {string} scene
+     */
+    setScene(scene = '') {
+        switch (scene) {
+            case 'saveInfo':
+                this.db.model.schema.path('real_name').required(true);
+                this.db.model.schema.path('company').required(true);
+                this.db.model.schema.path('province').required(true);
+                this.db.model.schema.path('area').required(true);
+                break;
+        }
+    }
+
+    /**
      * 根据用户名查找数据
      *
      * @param {string} username
      * @return {object}
      */
     findDataByName(username) {
-        let self = this;
-        return new Promise(function (resolve, reject) {
-            self.model.find({username: username}, function (error, data) {
-                if (error) {
-                    console.log(error);
-                    reject([]);
-                } else {
-                    resolve(data);
-                }
-            });
-        });
+        return this.db.model.find({username: username});
     }
 
     /**
      * 新增用户
      *
      * @param {object} userData
-     * @return {Promise}
+     * @return {Promise|boolean}
      */
     addUser(userData) {
         let insertData = {
@@ -179,19 +147,10 @@ class UserModel {
             company_type: -1,
             company_scale: -1,
             last_login: 0,
-            create_time: new Date().getTime()
+            create_time: new Date().getTime(),
+            area: 0
         };
-        let userModel = new this.model(insertData);
-        return new Promise(function (resolve, reject) {
-            userModel.save(function (error) {
-                if (error) {
-                    console.log(error);
-                    reject(false);
-                } else {
-                    resolve(true);
-                }
-            });
-        });
+        return this.db.model.create(insertData);
     }
 
     /**
@@ -204,22 +163,9 @@ class UserModel {
         if (Object.keys(condition).length <= 0 || Object.keys(updateData).length <= 0) {
             return null;
         }
-        let userModel = new this.model();
-        return new Promise(function(resolve, reject) {
-            // let validationError = userModel.validateSync();
-            // if (validationError) {
-            //     reject(validationError);
-            // }
-            userModel.update(condition, {$set: updateData}, function(error, result) {
-                if (error) {
-                    reject(error);
-                } else {
-                    resolve(result);
-                }
-            });
-        });
+        return this.db.model.update(condition, updateData);
     }
 
 }
 
-module.exports = UserModel;
+export default UserModel;

+ 2 - 3
modules/users/routes/boot_route.js

@@ -5,11 +5,10 @@
  * @date 2017/6/12
  * @version
  */
+import express from "express";
+import BootController from "../controllers/boot_controller";
 
-let express = require('express');
 let router = express.Router();
-let BootController = require('../controllers/boot_controller');
-
 let bootController = new BootController();
 
 // 引导页面

+ 4 - 2
modules/users/routes/login_route.js

@@ -5,13 +5,15 @@
  * @date 2017/6/1
  * @version
  */
-let express = require('express');
+import express from "express";
+import LoginController from "../controllers/login_controller";
+
 let router = express.Router();
-let LoginController = require('../controllers/login_controller');
 let loginController = new LoginController();
 
 // 登录页面action
 router.get('/login', loginController.index);
+router.get('/', loginController.index);
 
 // 登录操作
 router.post('/login', loginController.login);

+ 5 - 2
server.js

@@ -76,6 +76,9 @@ app.use('/', require("./modules/users/routes/login_route"));
 app.use('/boot', require("./modules/users/routes/boot_route"));
 app.use('/user', require("./modules/users/routes/user_route"));
 
+// 项目工料机相关
+app.use('/glj', require("./modules/glj/routes/glj_router"));
+
 app.use('/feeRates', require('./modules/fee_rates/routes/fee_rates_route'));
 
 app.get('/template/bills', function (req, res) {
@@ -134,7 +137,7 @@ let GLJ_Router = require('./modules/main/routes/GLJ_route');
 app.use('/project', project_Router);
 app.use('/bills', bills_Router);
 app.use('/ration', ration_Router);
-app.use('/glj', GLJ_Router);
+app.use('/glj_bak', GLJ_Router);
 
 //app.use(express.static(_rootDir+"/web"));
 //app.use(express.static(_rootDir+"/lib"));
@@ -174,7 +177,7 @@ app.use("/report_tpl_api", rptTpl_Router);
 //-----------------
 
 app.use(function(req, res, next) {
-	res.status(404).send('404 Error');
+    res.status(404).send('404 Error');
 });
 app.use(function(err, req, res, next) {
     console.error(err.stack);

+ 14 - 0
web/glj/html/footer.html

@@ -0,0 +1,14 @@
+<!-- JS. -->
+<script src="/lib/jquery/jquery.min.js"></script>
+<script src="/lib/tether/tether.min.js"></script>
+<script src="/lib/bootstrap/bootstrap.min.js"></script>
+<script src="/web/building_saas/js/global.js"></script>
+<script src="/web/glj/js/socket.io.slim.js"></script>
+<script>GC.Spread.Sheets.LicenseKey = "559432293813965#A0y3iTOzEDOzkjMyMDN9UTNiojIklkI1pjIEJCLi4TPB9mM5AFNTd4cvZ7SaJUVy3CWKtWYXx4VVhjMpp7dYNGdx2ia9sEVlZGOTh7NRlTUwkWR9wEV4gmbjBDZ4ElR8N7cGdHVvEWVBtCOwIGW0ZmeYVWVr3mI0IyUiwCMzETN8kzNzYTM0IicfJye&Qf35VfiEzRwEkI0IyQiwiIwEjL6ByUKBCZhVmcwNlI0IiTis7W0ICZyBlIsIyNyMzM5ADI5ADNwcTMwIjI0ICdyNkIsIibj9SbvNmL4N7bjRnch56ciojIz5GRiwiI8+Y9sWY9QmZ0Jyp96uL9v6L0wap9biY9qiq95q197Wr9g+89iojIh94Wiqi";</script>
+
+</body>
+<script type="text/javascript">
+    autoFlashHeight();
+</script>
+
+</html>

+ 39 - 0
web/glj/html/glj_index.html

@@ -0,0 +1,39 @@
+<%include header.html %>
+<div class="main">
+    <div class="main-nav">
+        <ul class="nav flex-column">
+            <li><a href="zaojiashu.html">造价书</a></li>
+            <li><a href="gongliaoji.html" class="active">工料机</a></li>
+            <li><a href="baobiao.html">报表</a></li>
+            <li><a href="feilv.html">费率</a></li>
+        </ul>
+    </div>
+    <div class="content">
+        <div class="toolsbar px-1">
+            <div class="tools-btn btn-group align-top">
+                <a href="" class="btn btn-sm"><i class="fa fa-arrow-down" aria-hidden="true"></i>下移</a>
+                <a href="" class="btn btn-sm"><i class="fa fa-arrow-up" aria-hidden="true"></i>上移</a>
+                <a href="" class="btn btn-sm"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
+                <a href="" class="btn btn-sm"><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
+                <a href="" class="btn btn-sm"><i class="fa fa-undo" aria-hidden="true"></i></a>
+            </div>
+        </div>
+        <div class="container-fluid">
+            <div class="row">
+                <div class="main-content col-lg-12 p-0">
+                    <div class="main-data-full" id="test">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script type="text/javascript">
+    let jsonData = '<%- gljList %>';
+    let materialIdList = '<%- materialIdList %>';
+    if(materialIdList !== '') {
+        materialIdList = materialIdList.split(",");
+    }
+</script>
+<%include footer.html %>
+<script type="text/javascript" src="/web/glj/js/glj_index.js"></script>

+ 90 - 0
web/glj/html/header.html

@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title><%= title %>-Smartcost</title>
+    <link rel="stylesheet" href="/lib/bootstrap/css/bootstrap.min.css">
+    <link rel="stylesheet" href="/web/building_saas/css/main.css">
+    <link rel="stylesheet" href="/lib/font-awesome/font-awesome.min.css">
+    <link rel="stylesheet" href="/lib/spreadjs/sheets/css/gc.spread.sheets.excel2013white.10.0.1.css">
+    <script type="text/javascript" src="/public/web/sheet/sheet_common.js"></script>
+    <script type="text/javascript" src="/public/web/sheet/sheet_data_helper.js"></script>
+    <script src="/lib/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js"></script>
+    <style type="text/css">
+        #chat-content {
+            border: 1px solid #000;
+            width: 500px;
+            height: 600px;
+        }
+        #notify {
+            display: none;
+        }
+    </style>
+</head>
+<body>
+<div class="header">
+    <div class="top-msg clearfix" id="notify">
+        <div class="alert alert-warning mb-0 py-0" role="alert">
+            <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+                <span aria-hidden="true">&times;</span>
+            </button>
+            <span id="message"><strong>注意!</strong> 这是一条消息通知 <a href="#">链接</a></span>
+        </div>
+    </div>
+    <nav class="navbar navbar-toggleable-lg navbar-light bg-faded p-0 justify-content-between">
+        <span class="header-logo px-2">Smartcost</span>
+        <div class="navbar-text"><a href="project-management.html">项目管理</a><i class="fa fa-angle-right fa-fw"></i>文件夹<i
+                class="fa fa-angle-right fa-fw"></i>建设项目<i class="fa fa-angle-right fa-fw"></i>单项工程<i
+                class="fa fa-angle-right fa-fw"></i>单位工程
+        </div>
+        <div class="float-lg-right navbar-text pt-0">
+            <div class="dropdown d-inline-block">
+                <button class="btn btn-link btn-sm dropdown-toggle" type="button" data-toggle="dropdown">陈特</button>
+                <div class="dropdown-menu dropdown-menu-right">
+                    <a class="dropdown-item" href="user-info.html" target="_blank">账号资料</a>
+                    <a class="dropdown-item" href="user-buy.html" target="_blank">产品购买</a>
+                    <a class="dropdown-item" href="user-set.html" target="_blank">偏好设置</a>
+                </div>
+            </div>
+            <span class="btn btn-link btn-sm new-msg">
+                  <i class="fa fa-envelope-o" aria-hidden="true"></i>&nbsp;2
+                </span>
+            <button class="btn btn-link btn-sm">注销</button>
+        </div>
+    </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="#" aria-haspopup="true" aria-expanded="false"><i class="fa fa-sliders"></i> 选项</a>
+            </li>
+            <li class="nav-item dropdown">
+                <a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true"
+                   aria-expanded="false"><i class="fa fa-wrench"></i> 工具</a>
+                <div class="dropdown-menu">
+                    <a class="dropdown-item" href="#">定额库编辑器</a>
+                    <a class="dropdown-item" href="#">工料机库编辑器</a>
+                </div>
+            </li>
+            <li class="nav-item dropdown">
+                <a class="nav-link dropdown-toggle" href="#" data-toggle="dropdown" aria-haspopup="true"
+                   aria-expanded="false"><i class="fa fa-question-circle-o"></i> 帮助</a>
+                <div class="dropdown-menu">
+                    <a class="dropdown-item" href="#">帮助</a>
+                    <a class="dropdown-item" href="#">升级说明</a>
+                    <a class="dropdown-item" href="#">重庆市2008定额说明</a>
+                    <a class="dropdown-item" href="#">纵横官网</a>
+                    <a class="dropdown-item" href="#">动画教程</a>
+                    <a class="dropdown-item" href="#">联系客服</a>
+                    <a class="dropdown-item" href="#">关于</a>
+                </div>
+            </li>
+            <li class="nav-item">
+                <a class="nav-link" href="#" aria-haspopup="true" aria-expanded="false"><i class="fa fa-history"></i>
+                    历史记录</a>
+            </li>
+        </ul>
+        <form class="form-inline">
+            <input class="form-control form-control-sm mr-1" type="text" placeholder="告诉我你想做什么">
+        </form>
+    </nav>
+</div>

+ 172 - 0
web/glj/js/glj_index.js

@@ -0,0 +1,172 @@
+/**
+ * 聊天室相关
+ *
+ * @author CaiAoLin
+ * @date 2017/6/15
+ * @version
+ */
+$(document).ready(function () {
+    // excel
+    let header = [
+        {name: '编码', field: 'code', visible: true},
+        {name: '名称', field: 'name', visible: true},
+        {name: '规格型号', field: 'unit_price.specs', visible: true},
+        {name: '单位', field: 'unit_price.unit', visible: true},
+        {name: 'ID', field: 'id', visible: false},
+        {name: '类型', field: 'unit_price.type', visible: false},
+        {name: '人工工种', field: 'type_of_work', visible: false},
+        {name: '总消耗量', field: '', visible: true},
+        {name: '基价单价', field: "unit_price.base_price", visible: true},
+        {name: '调整基价', field: 'adjust_price', visible: true},
+        {name: '市场单价', field: "unit_price.market_price", visible: true},
+        {name: '是否暂估', field: 'is_evaluate', visible: true, cellType: new GC.Spread.Sheets.CellTypes.CheckBox()},
+        {name: '供货方式', field: 'supply', visible: true},
+        {name: '甲供数量', field: 'supply_quantity', visible: true},
+        {name: '交货方式', field: 'delivery', visible: true},
+        {name: '送达地点', field: 'delivery_address', visible: true},
+        {name: '不调价', field: 'is_adjust_price', visible: true, cellType: new GC.Spread.Sheets.CellTypes.CheckBox()},
+    ];
+
+    let setting = {
+        header: []
+    };
+    for(let tmp of header) {
+        setting.header.push({headerName: tmp.name, headerWidth: 120});
+    }
+
+    let spread = sheetCommonObj.buildSheet(document.getElementById("test"), setting, 3);
+    spread.options.scrollbarShowMax = true;
+    spread.options.scrollbarMaxAlign = true;
+    spread.options.showHorizontalScrollbar = true;
+    let sheet = spread.getActiveSheet();
+
+    // 设置表单不可编辑
+    sheet.options.isProtected = true;
+
+    // 居中样式
+    let centerStyleSetting = {hAlign: 1};
+    sheet.setStyle(-1, 11, getStyle(centerStyleSetting), GC.Spread.Sheets.SheetArea.viewport);
+    sheet.setStyle(-1, 16, getStyle(centerStyleSetting), GC.Spread.Sheets.SheetArea.viewport);
+    sheet.setStyle(-1, 3, getStyle(centerStyleSetting), GC.Spread.Sheets.SheetArea.viewport);
+
+    // 向右对齐样式
+    let rightStyleSetting = {hAlign: GC.Spread.Sheets.HorizontalAlign.right};
+    sheet.setStyle(-1, 7, getStyle(rightStyleSetting), GC.Spread.Sheets.SheetArea.viewport);
+    sheet.setStyle(-1, 8, getStyle(rightStyleSetting), GC.Spread.Sheets.SheetArea.viewport);
+    sheet.setStyle(-1, 9, getStyle(rightStyleSetting), GC.Spread.Sheets.SheetArea.viewport);
+    sheet.setStyle(-1, 10, getStyle(rightStyleSetting), GC.Spread.Sheets.SheetArea.viewport);
+
+
+    // 设置可编辑列
+    sheet.getRange(-1, 10, -1, 1).locked(false);
+    sheet.getRange(-1, 11, -1, 1).locked(false);
+    sheet.getRange(-1, 16, -1, 1).locked(false);
+
+    // 设置json数据
+    let sourceData = JSON.parse(jsonData);
+    let rowCounter = 0;
+    for(let data of sourceData) {
+        let columnCounter = 0;
+        for(let columnInfo of header) {
+            // 设置是否显示
+            sheet.setColumnVisible(columnCounter, columnInfo.visible);
+
+            let dataString = columnInfo.field !== '' && columnInfo.field !== undefined ?
+                "data." + columnInfo.field : '';
+            let cellData = eval(dataString);
+
+            // 设置复选框
+            if (columnInfo.cellType !== undefined) {
+                let checkbox = new GC.Spread.Sheets.CellTypes.CheckBox();
+                sheet.setCellType(rowCounter, columnCounter, checkbox, GC.Spread.Sheets.SheetArea.viewport);
+            }
+
+            // 如果不为材料“是否暂估列”根据条件显示
+            if (columnInfo.field === 'is_evaluate' && materialIdList.indexOf(data.type + '') < 0) {
+                let string = new GC.Spread.Sheets.CellTypes.Text();
+                sheet.setCellType(rowCounter, columnCounter, string, GC.Spread.Sheets.SheetArea.viewport);
+                cellData = '';
+                // 并且锁定该单元格
+                sheet.getRange(rowCounter, columnCounter, 1, 1).locked(true);
+            }
+
+            sheet.setValue(rowCounter, columnCounter, cellData, GC.Spread.Sheets.SheetArea.viewport);
+            columnCounter++;
+        }
+        rowCounter++;
+    }
+
+/*
+    let socket = io('http://notify.smartcost.com.cn:3300');
+    socket.on('connect', function () {
+        socket.emit('join', project);
+        console.log('连接成功');
+    });
+
+    // 接受到改变
+    let changeInfo = [];
+    socket.on('dataChange', function(data) {
+        data = JSON.parse(data);
+        if (data.newValue === undefined) {
+            return false;
+        }
+        changeInfo.push(data);
+        $("#message").html('基价单位已被修改,<a href="javascript:void(0);" id="load-data">点击加载</a>');
+        $("#notify").slideDown('fast');
+        initiativeChange = false;
+    });
+
+    // 是否主动更改数据
+    let initiativeChange = false;
+    $("#message").on('click', '#load-data', function() {
+        $("#notify").slideUp('fast');
+        if (changeInfo.length > 0) {
+            for (let index in changeInfo) {
+                let cell = sheet.getCell(changeInfo[index].row, changeInfo[index].col, GC.Spread.Sheets.SheetArea.viewport);
+                cell.value(changeInfo[index].newValue);
+            }
+        }
+        changeInfo = [];
+    });
+
+    // 进入单元格后设置为主动设置数据
+    sheet.bind(GC.Spread.Sheets.Events.EditEnding, function (element, info) {
+        initiativeChange = true;
+    });
+
+    // 绑定事件
+    sheet.bind(GC.Spread.Sheets.Events.CellChanged, function (element, info) {
+        // 如果修改了数据且为主动修改,先存库再广播给其他页面
+        if (info.newValue !== undefined && info.oldValue !== undefined && initiativeChange) {
+            let id = sheet.getCell(info.row, 0).value();
+            $.ajax({
+                url: '/save',
+                type: 'post',
+                data: {priceValue: info.newValue, id: id},
+                error: function() {
+                    // @todo 替换为更好地错误提示
+                    alert('error');
+                },
+                beforeSend: function() {
+
+                },
+                success: function(response) {
+                    if (response.err === 0) {
+                        socket.emit('dataNotify', JSON.stringify(info));
+                    } else {
+                        // @todo 替换为更好地错误提示
+                        alert(response.msg);
+                    }
+                }
+            });
+        }
+    });
+*/
+});
+
+function getStyle(setting) {
+    let style = new GC.Spread.Sheets.Style();
+    style.locked = setting.readOnly === undefined ? true : setting.readOnly;
+    style.hAlign = setting.hAlign === undefined ? GC.Spread.Sheets.HorizontalAlign.center : setting.hAlign;
+    return style;
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 3 - 0
web/glj/js/socket.io.slim.js