Bläddra i källkod

Merge branch 'master' of http://smartcost.f3322.net:3000/SmartCost/YangHuCost

TonyKang 6 år sedan
förälder
incheckning
2e598e987a

+ 1 - 0
logs/online_logs.js

@@ -13,6 +13,7 @@ async function saveOnlineTime(req) {
     let interval_time = 10 * 60 *1000;
     let start = req.session.online_start_time;
     let end = + new Date();
+    if(start === undefined) return req.session.online_start_time ==end;
     let online_times =  end - start;
     //1秒内只记一次就好
     if(online_times < 500) return;//如果间隔太短,则忽略

+ 11 - 1
modules/all_models/user.js

@@ -85,6 +85,16 @@ let schema = {
         default: []
     },
     // 是否邮箱已通过验证
-    isUserActive: Number
+    isUserActive: Number,
+    // 是否只允许短信登录
+    isSmsLogin: {
+        type: Number,
+        default: 0
+    },
+    // 登录异常短信通知
+    isLoginValid: {
+        type: Number,
+        default: 0
+    }
 };
 mongoose.model(collectionName, new Schema(schema, {versionKey: false}));

+ 5 - 111
modules/main/controllers/bills_controller.js

@@ -231,24 +231,11 @@ module.exports = {
                     throw 'excel没有对应数据'
                 }
                 let importData = JSON.parse(LZString.decompressFromUTF16(compressData));
-                //匹配的清单库
-                const billsLibId = fields.billsLibId !== undefined && fields.billsLibId.length > 0 && fields.billsLibId[0]? parseInt(fields.billsLibId[0]) : null;
-                let stdBills = [], stdJobs = [], stdCharacters = [];
-                if(billsLibId){
-                    stdBills = await stdBillsModel.find({billsLibId: billsLibId, deleted: false}, '-_id code jobs items engineering billsLibId ruleText');
-                    stdJobs = await stdBillJobsModel.find({billsLibId: billsLibId, deleted: false});
-                    stdCharacters = await stdBillCharacterModel.find({billsLibId: billsLibId, deleted: false});
-                }
-                let stdData = {stdBills: stdBills, stdJobs: stdJobs, stdCharacters: stdCharacters};
                 //导入表
                 let importDateA = +new Date();
-                for(let position in importData){
-                    if(importData[position].length > 0){
-                        let updateFrontData = await importSheet(position, importData[position], req.session.sessionUser.id, projectID, stdData);
-                        if(updateFrontData){
-                            responseData.data.push(updateFrontData);
-                        }
-                    }
+                let updateFrontData = await importSheet(importData, req.session.sessionUser.id, projectID);
+                if(updateFrontData){
+                    responseData.data.push(updateFrontData);
                 }
                 let importDateB = +new Date();
                 console.log(`导入时间: ${importDateB - importDateA}=========================================================================`);
@@ -274,49 +261,15 @@ module.exports = {
     }
 };
 
-async function importSheet(position, excelBills, userID, projectID, stdData){
+async function importSheet(excelBills, userID, projectID,){
     //导入位置的有固定行
-    let flag = getImportFlag(position);
-    if(!flag){
-        throw 'excel数据错误';
-    }
+    let flag = fixedFlag.ONE_SEVEN_BILLS;   //第100章至700章清单
     let fixedBill = await billsData.model.findOne({projectID: projectID, 'flags.flag': flag, deleteInfo: null});
-    let insertFixedBill = null;
-    //导入xx措施项目,若不存在此固定清单,则先插入相关固定清单
-    if(!fixedBill){
-        //分部分项工程(不可删除)应存在
-        if(flag === fixedFlag.SUB_ENGINERRING){
-            throw '项目不存在分部分项工程'
-        }
-        //措施项目是否存在
-        let csxm = await billsData.model.findOne({projectID: projectID, 'flags.flag': fixedFlag.MEASURE, deleteInfo: null});
-        if(!csxm){
-            throw '项目不存在措施项目'
-        }
-        //插入清单固定行(施工技术措施项目、施工组织措施项目可删除)
-        insertFixedBill = {projectID: projectID, name: flag === fixedFlag.CONSTRUCTION_TECH ? '施工技术措施项目' : '施工组织措施项目', code: '1',
-            ID: uuidV1(), NextSiblingID: -1, ParentID: csxm.ID, flags: [{fieldName: 'fixed', flag: flag}], type: billType.BILL};
-        //更新前节点
-        let preDatas = await billsData.model.find({projectID: projectID, ParentID: csxm.ID, deleteInfo: null});
-        for(let preData of preDatas){
-            if(preData.NextSiblingID == -1){
-                await billsData.model.update({ID: preData.ID}, {$set: {NextSiblingID: insertFixedBill.ID}});
-                break;
-            }
-        }
-        await billsData.model.create(insertFixedBill);
-        fixedBill = insertFixedBill;
-    }
-    //将excel清单数据转换成完整清单数据(设置ParentID、匹配标准清单库)
-    parseToCompleteBills(excelBills, fixedBill, stdData);
     //删除相关数据
     let deleteDatas = await billsData.deepDeleteBill([fixedBill], userID);
     //新增清单数据
     await billsData.importBills(excelBills);
     //返回数据以更新前端
-    if(insertFixedBill){
-        excelBills.push(insertFixedBill);
-    }
     return {fixedBill: fixedBill, insert: {bill: excelBills, ration: []}, remove: {bill: deleteDatas.bill, ration: deleteDatas.ration}};
 }
 
@@ -328,65 +281,6 @@ function getImportFlag(position){
 function isDef(data){
     return typeof data !== 'undefined' && data !== null && data !== '';
 }
-//将前端解析生成的清单节点数据完善(ParentID、匹配标准清单)
-function parseToCompleteBills(excelBills, fixedBills, stdData){
-    //设置清单ParentID
-    let rootID = fixedBills ? fixedBills.ID: -1;
-    for(let bills of excelBills){
-        if(bills.nodeType === 'root'){
-            rootID = bills.ID;
-            bills.ParentID = fixedBills.ID;
-        }
-        else {
-            bills.ParentID = rootID;
-        }
-        delete bills.nodeType;
-        matchStdBill(bills, stdData);
-    }
-
-    //excel数据与标准库数据匹配,根据清单前九位编码匹配,匹配成功则获取标准清单对应的工程专业、特征及内容
-    function matchStdBill(excelBill, stdData){
-        let isMatch = false;
-        let regExp = /^\d{12}$/g;
-        if(excelBill.code.length >8){
-            let nineCode = excelBill.code.substr(0, 9);
-            for(let stdBill of stdData.stdBills){
-                //set programID
-                if(nineCode == stdBill.code){
-                    isMatch = true;
-                    excelBill.programID = stdBill.engineering ? stdBill.engineering : null;
-                    excelBill.billsLibId = stdBill.billsLibId ? stdBill.billsLibId : null;
-                    excelBill.ruleText = stdBill.ruleText ? stdBill.ruleText : '';
-                    //set jobContent and itemCharacter
-                    let tempJob = [], tempCharacter = [];
-                    for(let billJob of stdBill.jobs){
-                        for(let stdJob of stdData.stdJobs) {
-                            if (billJob.id == stdJob.id) {
-                                tempJob.push({isChecked: false, serialNo: billJob.serialNo, content: stdJob.content});
-                            }
-                        }
-                    }
-                    for(let billCharacter of stdBill.items){
-                        for(let stdCharacter of stdData.stdCharacters){
-                            if(billCharacter.id == stdCharacter.id){
-                                let eigenvalue = [];
-                                for(let eValue of stdCharacter.itemValue){
-                                    eigenvalue.push({isSelected: false, value: eValue.value});
-                                }
-                                tempCharacter.push({isChecked: false, serialNo: billCharacter.serialNo, character: stdCharacter.content, eigenvalue: eigenvalue});
-                            }
-                        }
-                    }
-                    excelBill.jobContent = tempJob;
-                    excelBill.itemCharacter = tempCharacter;
-                }
-            }
-        }
-        if(!isMatch && excelBill.type === billType.FX){//分项不为空,同时在标准清单中不匹配,则识别为补项
-            excelBill.type = billType.BX;
-        }
-    }
-}
 
 async function doBillsOrRationsDelete(data) {
     let billTask = [];

+ 0 - 2
modules/main/models/bills.js

@@ -194,8 +194,6 @@ class billsModel extends baseModel {
             await rationGljModel.deleteMany({rationID: {$in: ration_ids}});
             //删除ration-coe
             await rationCoeModel.deleteMany({rationID: {$in: ration_ids}});
-            //删除ration-installation
-            await rationInstModel.deleteMany({rationID: {$in: ration_ids}});
             //删除ration-quantity_detail
             await quantityDelModel.deleteMany({rationID: {$in: ration_ids}});
         }

+ 82 - 5
modules/users/controllers/login_controller.js

@@ -8,7 +8,10 @@
 import UserModel from "../models/user_model";
 import SettingModel from "../models/setting_model";
 import CompilationModel from "../models/compilation_model";
-// import Captcha from "../models/captcha";
+import LogModel from "../models/log_model";
+import LogType from "../../common/const/log_type_const";
+const SMS = require('../models/sms');
+const moment = require('moment');
 // 验证码
 const Captcha = require("../models/captcha");
 
@@ -48,6 +51,17 @@ class LoginController {
                     throw '接口返回数据错误';
                 }
                 let userData = responseData[0];
+
+                // 判断用户是否开启了只使用短信登录
+                const userInfo = await userModel.findDataByAccount(userData.mobile);
+                if (userInfo !== undefined && userInfo !== null && userInfo.isSmsLogin === 1) {
+                    let renderData = {
+                        mobile: userData.mobile,
+                    };
+                    response.render('users/html/login-sms', renderData);
+                    return;
+                }
+
                 let sessionUser = {
                     ssoId: userData.id,
                     username: userData.username,
@@ -116,14 +130,43 @@ class LoginController {
      * @return {string}
      */
     async login(request, response) {
-        let account = request.body.account;
-        let password = request.body.pw;
         let preferenceSetting = {};
         let compilationList = [];
         try {
-            // 调用接口验证登录信息
             let userModel = new UserModel();
-            let responseData = await userModel.getInfoFromSSO(account, password);
+            let responseData = '';
+            if (request.body.account === undefined) {
+                let mobile = request.body.mobile;
+                let codeMsg = request.session.code;
+                if (codeMsg !== undefined && request.body.code !== '') {
+                    console.log(codeMsg);
+                    const validMobile = codeMsg.split('_')[0];
+                    const code = codeMsg.split('_')[1];
+                    const time = codeMsg.split('_')[2];
+                    if (validMobile !== mobile) {
+                        throw '短信验证码错误';
+                    }
+                    if (Date.parse(new Date())/1000 > time+60*5 || request.body.code !== code) {
+                        throw '短信验证码错误或已过期';
+                    } else {
+                        delete request.session.code;
+                    }
+                } else {
+                    throw '短信验证码错误或已过期。';
+                }
+                responseData = await userModel.getInfoFromSSOMobile(mobile);
+            } else {
+                let account = request.body.account;
+                let password = request.body.pw;
+
+                // 调用接口验证登录信息
+                responseData = await userModel.getInfoFromSSO(account, password);
+            }
+
+            // 先判断返回值是否为未激活状态
+            if ( responseData === '-3') {
+                throw '因邮箱未完成认证,账号未激活;去<a href="https://sso.smartcost.com.cn" target="_blank">激活</a>。';
+            }
             responseData = JSON.parse(responseData);
             if (typeof responseData !== 'object') {
                 throw '邮箱/手机 或 密码错误';
@@ -177,6 +220,12 @@ class LoginController {
                 throw '极验验证码错误';
             }
 
+            // 判断用户是否开启了只使用短信登录
+            const userInfo = await userModel.findDataByAccount(userData.mobile);
+            if (request.body.mobile === undefined && request.body.code === undefined && userInfo !== undefined && userInfo !== null && userInfo.isSmsLogin === 1) {
+                return response.json({error: 3, msg: '只能手机短信登录。', data: userData.mobile});
+            }
+
             let sessionUser = {
                 ssoId: userData.id,
                 username: userData.username,
@@ -216,6 +265,34 @@ class LoginController {
                 if(request.session.sessionUser.latest_used !== preferenceSetting.select_version) await userModel.updateLatestUsed(request.session.sessionUser.id,preferenceSetting.select_version);
             }
 
+            // 登录异常短信提醒功能
+            const userinfo2 = await userModel.findDataByAccount(userData.mobile);
+            if (userinfo2.isLoginValid === 1) {
+                // 获取本次访问ip
+                let ip = request.connection.remoteAddress;
+                ip = ip.split(':');
+                ip = ip[3] === undefined ? '' : ip[3];
+                let logModel = new LogModel();
+                let logCount = await logModel.count();
+                logCount = logCount > 30 ? 30 : logCount;
+                let page = 1;
+                const loginList = await logModel.getLog(request.session.sessionUser.id, LogType.LOGIN_LOG, page, logCount);
+                let messageFlag = true;
+                for (const [index,log] of loginList.entries()) {
+                    if (log.message.ip === ip && index !== 0) {
+                        messageFlag = false;
+                        break;
+                    }
+                }
+                messageFlag = true;
+                if (messageFlag) {
+                    // 发送短信
+                    const Sms = new SMS();
+                    const logInfo = loginList[0];
+                    await Sms.sendLoginMsg(userData.mobile, request.session.sessionUser.real_name, moment(logInfo.create_time).format('YYYY-MM-DD'), moment(logInfo.create_time).format('HH:mm:ss'), logInfo.message.ip_info, logInfo.message.ip);
+                }
+            }
+
         } catch (error) {
             console.log(error);
             return response.json({error: 1, msg: error});

+ 35 - 1
modules/users/controllers/sms_controller.js

@@ -5,6 +5,7 @@
  * @date 2018/4/17
  * @version
  */
+import UserModel from "../models/user_model";
 import SmsModel from "../models/sms_model";
 const SMS = require('../models/sms');
 
@@ -34,7 +35,7 @@ class SmsController {
                 // if (parseInt(JSON.stringify(returnStatus).statusCode) !== 200) {
                 //     throw '短信发送失败!';
                 // }
-                request.session.code = code + '_' + Date.parse(new Date())/1000;
+                request.session.code = mobile + '_' + code + '_' + Date.parse(new Date())/1000;
             } else {
                 let returnStatus = await smsModel.sendSmsFromSSO(mobile, type);
                 if (returnStatus === null) {
@@ -87,6 +88,39 @@ class SmsController {
 
         response.json(responseData);
     }
+
+    /**
+     * 检测通行账号是否已存在该手机号码注册
+     *
+     * @param {object} request
+     * @param {object} response
+     * @return {void}
+     */
+    async checkMobile (request, response) {
+        let responseData = {
+            err: 0,
+            msg: ''
+        };
+        let mobile = request.body.mobile;
+        try {
+            let userModel = new UserModel();
+            let responseData = await userModel.getInfoFromSSOMobile(mobile, 0);
+            if ( responseData === '-2') {
+                throw '参数有误或手机号码不正确';
+            }
+            responseData = JSON.parse(responseData);
+            if (typeof responseData !== 'object') {
+                throw '该手机号未注册通行账号';
+            }
+            responseData.msg = 'success';
+        } catch (error) {
+            console.log(error);
+            responseData.err = 1;
+            responseData.msg = error;
+        }
+
+        response.json(responseData);
+    }
 }
 
 export default SmsController;

+ 40 - 0
modules/users/controllers/user_controller.js

@@ -342,6 +342,46 @@ class UserController extends BaseController {
         }
     }
 
+    /*
+    * 更改用户账号登录方式
+    * */
+    async changeIsSmsLogin(request, response) {
+        try{
+            let status = request.body.status;
+            let userModel = new UserModel();
+            let userId = request.session.sessionUser.id;
+            let result = await userModel.updateUser({ _id: userId }, { isSmsLogin: status });
+            if (result) {
+                response.json({error: 0, msg: 'success', data: null});
+            } else {
+                throw '更新失败';
+            }
+        }
+        catch(error){
+            response.json({error: 1, msg: error, data: null});
+        }
+    }
+
+    /*
+    * 更改异常登录通知
+    * */
+    async changeIsLoginValid(request, response) {
+        try{
+            let status = request.body.status;
+            let userModel = new UserModel();
+            let userId = request.session.sessionUser.id;
+            let result = await userModel.updateUser({ _id: userId }, { isLoginValid: status });
+            if (result) {
+                response.json({error: 0, msg: 'success', data: null});
+            } else {
+                throw '更新失败';
+            }
+        }
+        catch(error){
+            response.json({error: 1, msg: error, data: null});
+        }
+    }
+
 }
 
 export default UserController;

+ 41 - 2
modules/users/models/sms.js

@@ -23,7 +23,6 @@ class SMS {
         this.url = 'http://www.sendcloud.net/smsapi/send';
         this.smsUser = 'smartcost';
         this.smskey = 'kuGmqTt10n6vBXivhxXsAuG8aoCsQ1x6';
-        this.templateId = 25595;
     }
 
     /**
@@ -37,7 +36,7 @@ class SMS {
         try {
             const formData = {
                 smsUser: this.smsUser,
-                templateId: this.templateId,
+                templateId: 25595,
                 msgType: 0,
                 phone: mobile,
                 vars: '{"%code%":'+ code +'}',
@@ -72,6 +71,46 @@ class SMS {
         }
     }
 
+    async sendLoginMsg(mobile, name, date, time, local, ip) {
+        console.log(mobile, name, time, local, ip);
+        try {
+            const formData = {
+                smsUser: this.smsUser,
+                templateId: 27561,
+                msgType: 0,
+                phone: mobile,
+                vars: '{"%name%": "' + name + '", "%date%": "' + date + '", "%time%": "' + time + '", "%local%": "' + local + '", "%IP%": "' + ip + '"}',
+            };
+            const signature = await this.getSignature(this.sortDict(formData), this.smskey);
+            formData.signature = signature;
+
+            let postData = {
+                url: this.url,
+                form: formData,
+                encoding: 'utf8'
+            };
+
+            return new Promise(function (resolve, reject) {
+                try {
+                    // 请求接口
+                    Request.post(postData, function (err, postResponse, body) {
+                        if (err) {
+                            throw '请求错误';
+                        }
+                        if (postResponse.statusCode !== 200) {
+                            throw '短信发送失败!';
+                        }
+                        resolve(body);
+                    });
+                } catch (error) {
+                    reject([]);
+                }
+            });
+        } catch (error) {
+            console.log(error);
+        }
+    }
+
     md5(data) {
         var str = data;
         return crypto.createHash("md5").update(str).digest("hex");

+ 36 - 0
modules/users/models/user_model.js

@@ -81,6 +81,42 @@ class UserModel extends BaseModel {
     }
 
     /**
+     * 根据用户手机号码调用SSO接口获取信息
+     *
+     * @param {string} mobile
+     * @param {string} login 1为登录,不存在则是查询
+     * @return {object}
+     */
+    async getInfoFromSSOMobile(mobile, login = '1') {
+        const fromData = {account: mobile};
+        if (login === '1') {
+            fromData.login = 1;
+        }
+        let postData = {
+            url: 'http://sso.smartcost.com.cn/building/api/mobile/login',
+            form: fromData,
+            encoding: 'utf8'
+        };
+        return new Promise(function (resolve, reject) {
+            try {
+                // 请求接口
+                Request.post(postData, function (err, postResponse, body) {
+                    if (err) {
+                        console.log('111');
+                        throw '请求错误';
+                    }
+                    if (postResponse.statusCode !== 200) {
+                        throw '通行证验证失败!';
+                    }
+                    resolve(body);
+                });
+            } catch (error) {
+                reject([]);
+            }
+        });
+    }
+
+    /**
      * 根据用户id和token调用SSO接口获取信息
      *
      * @param {string} username

+ 2 - 1
modules/users/routes/sms_route.js

@@ -16,6 +16,7 @@ module.exports = function (app) {
 // action定义区域
     router.post('/code', smsController.code);
     router.post('/mobile', smsController.setMobile);
+    router.post('/check/mobile', smsController.checkMobile);
 
     app.use('/sms',router);
-};
+};

+ 3 - 1
modules/users/routes/user_route.js

@@ -24,5 +24,7 @@ module.exports = function (app) {
     router.post('/getUsers', userController.init, userController.getUsers);
 
     router.post('/getVersionInfo', userController.init, userController.getVersionInfo);
+    router.post('/change/isSmsLogin', userController.init, userController.changeIsSmsLogin);
+    router.post('/change/isLoginValid', userController.init, userController.changeIsLoginValid);
     app.use('/user',router);
-};
+};

BIN
public/static/uploadExample.xlsx


+ 22 - 5
public/web/slideResize.js

@@ -15,6 +15,14 @@
 * */
 
 const SlideResize = (function() {
+    //函数防抖
+    let timer = null;
+    function deBounce(fn, wait) {
+        if (timer) {
+            clearTimeout(timer);
+        }
+        timer = setTimeout(fn, wait);
+    }
     //设置水平拖动条的宽度
     //@param {Object dom}resize滚动条
     function setResizeWidth (resize,otherIndex) {
@@ -38,8 +46,6 @@ const SlideResize = (function() {
             otherPercentWidth = otherDecimalWidth * 100 + '%';
         resize.css('width', resizePercentWidth);
         other.css('width', otherPercentWidth);
-        console.log(other)
-        console.log(otherPercentWidth)
     }
 
     let mouseMoveCount = 0;
@@ -81,7 +87,12 @@ const SlideResize = (function() {
                 eleObj.left.css('width', leftPercentWidth);
                 eleObj.right.css('width', rightPercentWidth);
                 setResizeWidth(eleObj.resize,otherIndex);
-                callback();
+                deBounce(function () {
+                    if (callback) {
+                        callback();
+                        mouseMoveCount = 0;
+                    }
+                }, 20);
                /* mouseMoveCount += Math.abs(moveSize);
                 if (mouseMoveCount > triggerCBSize && callback) {
                     callback();
@@ -169,10 +180,16 @@ const SlideResize = (function() {
                     eleObj.bottomSpread.height(bottomChange - notBottomHeight);
                 }
                 mouseMoveCount += Math.abs(moveSize);
-                if (mouseMoveCount > triggerCBSize && callback) {
+                deBounce(function () {
+                    if (callback) {
+                        callback();
+                        mouseMoveCount = 0;
+                    }
+                }, 20);
+                /*if (mouseMoveCount > triggerCBSize && callback) {
                     callback();
                     mouseMoveCount = 0;
-                }
+                }*/
             }
         });
         $('body').mouseup(function (e) {

+ 6 - 6
web/building_saas/main/html/main.html

@@ -87,13 +87,13 @@
                       <span id="openProjSet" class="btn btn-light btn-sm" data-toggle="tooltip" data-original-title="项目属性" data-placement="bottom">
                         <a href="javascript:void(0);"><i class="fa fa-cog"></i></a>
                     </span>
-                    <!--<span class="btn btn-light btn-sm" id="importSpan" data-toggle="tooltip" data-original-title="导入" data-placement="bottom">
-                        <a id="importDropDown" class="dropdown-toggle" href="#"><i class="fa fa-cloud-upload"></i></a>
-                        <div class="dropdown-menu">
-                            <a id="uploadLj" class="dropdown-item" href="#import" data-toggle="modal" data-target="#import">导入报表Excel清单</a>
+                    <span class="btn btn-light btn-sm" id="importSpan" data-toggle="tooltip" data-original-title="导入Excel清单" data-placement="bottom" style="display: none;">
+                        <a id="importDropDown" href="javascript:void(0);"><i class="fa fa-cloud-upload"></i></a>
+                        <!--<div class="dropdown-menu">
+                            <a id="uploadLj" class="dropdown-item" href="#import" data-toggle="modal" data-target="#import">导入工程量清单</a>
                             <a id="uploadGld" class="dropdown-item" href="#import" data-toggle="modal" data-target="#import">导入广联达算量Excel清单</a>
-                        </div>
-                    </span>-->
+                        </div>-->
+                    </span>
                     <a href="javascript:void(0)" class="btn btn-light btn-sm" id="insertRation" data-toggle="tooltip" data-placement="bottom" data-original-title="插入定额"><i class="fa fa-sign-in" aria-hidden="true"></i></a>
                     <!--2018-11-15 隐藏删除按钮   <a href="javascript:void(0)" class="btn btn-light btn-sm" id="delete" data-toggle="tooltip" data-placement="bottom" data-original-title="删除"><i class="fa fa-remove" aria-hidden="true"></i></a>-->
                     <a href="javascript:void(0)" class="btn btn-light btn-sm" id="upLevel" data-toggle="tooltip" data-placement="bottom" data-original-title="升级"><i class="fa fa-arrow-left" aria-hidden="true"></i></a>

+ 176 - 391
web/building_saas/main/js/views/importBills.js

@@ -26,430 +26,215 @@ const importBills = (function(){
     function _deNR(data) {
         return _isDef(data) ? data.toString().replace(/\r\r/g, '\r') : data;
     }
-
-    //列名对应中文字符
-    const colText = {
-        serialNo: ['序号'],
-        code: ['编码', '项目编码'],
-        name: ['名称', '项目名称'],
-        itemCharacterText: ['特征', '项目特征', '项目特征描述'],
-        unit: ['单位', '计量单位'],
-        quantity: ['工程量', '项目工程量'],
-        money: ['金额'],
-        quantityDetail: ['工程量明细'],
-        feeDetail: ['费用明细'],
-        summation: ['合计', '本页小计'],
-    };
-
-    //导入位置对应清单固定标记
-    const positionFlag = {
-        fbfx: fixedFlag.SUB_ENGINERRING,
-        jscsxm: fixedFlag.CONSTRUCTION_TECH,
-        zzcsxm: fixedFlag.CONSTRUCTION_ORGANIZATION,
-    };
-
-    //上传类型
-    const uploadType = {
-        lj: 'lj',
-        gld: 'gld',
-    };
-
-    //设置导入表内容(选择导入位置)
-    //@param {Object}workBook
-    function setImportSheetsInfo(sheets){
-        let sheetNames = [];
-        let indexMapping = {};
-        for(let sheetName in sheets){
-            indexMapping[sheets[sheetName]['index']] = sheetName;
-        }
-        let sheetsCount = Object.keys(sheets).length;
-        for(let i = 0; i < sheetsCount; i++){
-            sheetNames.push(indexMapping[i]);
-        }
-        let sheetArea = $('#uploadSheets'),
-            sheetHeader = $('#uploadSheetsHead');
-        $('#uploadSheets').height('');
-        sheetArea.empty();
-        for(let sheetName of sheetNames){
-            let sheetDiv = $(`<div style="margin-left: 5px;margin-top: 5px;" title="${sheetName}" class="input-group form-check"><label class="form-check-label" style="width:270px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis">
-                           <input class="form-check-input" type="checkbox">${sheetName}</label></div>`);
-            sheetDiv.find('input[type="checkbox"]').click(function () {
-                if($('#uploadAlert').is(':visible')){
-                    $('#uploadAlert').hide();
-                }
-            });
-            let sel = $(`<select style="margin-left: 5px; border-radius: .20rem;"><option value="fbfx">分部分项工程</option><option value="zzcsxm">施工组织措施项目</option><option value="jscsxm">施工技术措施项目</option></select>`);
-            if(sheetName.includes('分部分项工程项目清单计价表')){
-                sheetDiv.find('input[type="checkbox"]').prop('checked', true);
-                sel.find('option:eq(0)').prop('selected', true);
-            }
-            else if(sheetName.includes('施工组织措施项目清单计价表')){
-                sheetDiv.find('input[type="checkbox"]').prop('checked', true);
-                sel.find('option:eq(1)').prop('selected', true);
-            }
-            else if(sheetName.includes('施工技术措施项目清单计价表')){
-                sheetDiv.find('input[type="checkbox"]').prop('checked', true);
-                sel.find('option:eq(2)').prop('selected', true);
-            }
-            sheetDiv.append(sel);
-            sheetArea.append(sheetDiv);
-
-        }
-        if(sheetNames.length > 0){
-            sheetArea.show();
-            sheetHeader.show();
-        }
-        if($('#uploadSheets').height() > 250){
-            sheetArea.css('overflow', 'auto');
-            sheetArea.height(250);
-        }
-        else {
-            sheetArea.css('overflow', 'hidden');
+    //find 返回最后匹配
+    function findLast(datas, func) {
+        let filter = datas.filter(func);
+        if (filter.length > 0) {
+            return filter[filter.length - 1];
         }
+        return null;
     }
-
-    //获得选择导入的表信息(表索引及导入位置)
-    //@return {Object}
-    function getImportSheetsInfo(){
-        let rst = [];
-        let sheetArea = $('#uploadSheets');
-        let checkedInputs = sheetArea.find('input:checked');
-        for(let checked of checkedInputs){
-            rst.push({index: $(checked).parent().parent().index(), position: $(checked).parent().next().select().val()})
+    const fileType = {
+        gcl: 0, //工程量清单
+        qdsl: 1 //清单示例
+    };
+    //获取列字段对应
+    function getColMapping(type){
+        if (type === 0) {   //工程量清单
+            return {code: 0, name: 1, unit: 2, quantity: 4};
+        } else {    //清单示例表
+            return {code: 0, name: 1, unit: 2, quantity: 3};
         }
-        return rst;
     }
-
-    function getSheetByIndex(sheets, index){
-        for(let sheetName in sheets){
-            let sheet = sheets[sheetName];
-            if(sheet.index === index){
-                return sheet;
+    function isGCLHead(dataRow) {
+        let cell = dataRow[0];
+        return cell.value === '工程量清单';
+    }
+    //分析文件,1、工程量清单 2、清单示例表
+    function getFileType(sheetData) {
+        let dataTable = sheetData.data.dataTable,
+            rowCount = sheetData.rowCount;
+        for (let row = 0; row < rowCount; row++) {
+            if (isGCLHead(dataTable[row])) {
+                return fileType.gcl;
             }
         }
-        return null;
+        return fileType.qdsl;
     }
-
-    //提取excel表头列名与列下标映射
-    //@
-    function getColMapping(dataTable){
-        //获取表头
-        function getHeadRow(dataTable){
-            for(let rowIdx in dataTable ){
-                for(let colIdx in dataTable[rowIdx]){
-                    let cellData = dataTable[rowIdx][colIdx]['value'];
-                    if(cellData && _deESC(cellData) === colText.serialNo[0]){
-                        return dataTable[rowIdx];
+    //提取工程量清单数据
+    //层级由depth确定,表格里最顶层depth为0(表头里一清单),表格内容里数据的depth为空格数+1
+    function extractGCLDatas(sheetData, colMapping) {
+        //let colMapping = {code: 0, name: 1, unit: 2, quantity: 4};
+        let dataTable = sheetData.data.dataTable,
+            rowCount = sheetData.rowCount;
+        let rst = [];
+        for (let row = 0; row < rowCount; row++) {
+            //表格中顶层节点
+            if (isGCLHead(dataTable[row])) {
+                let rootRow = dataTable[row + 2];
+                let name = rootRow[0].value;
+                let existsRoot = findLast(rst, x => x.name === name && x.depth === 0);
+                if (!existsRoot) {
+                    let root = {ID: uuid.v1(), NextSiblingID: -1, ParentID: -1, name: name, depth: 0, parent: null};
+                    let preData = findLast(rst, x => x.depth === root.depth);
+                    if (preData) {
+                        preData.NextSiblingID = root.ID;
                     }
+                    rst.push(root);
                 }
-            }
-            return {};
-        }
-        //获取需要的表头列与列号对应关系
-        let colMapping = {};
-        let headRow = getHeadRow(dataTable);
-        for(let colIdx in headRow){
-            let cellData = headRow[colIdx]['value'];
-            if(!_isDef(cellData)){
+                row += 3;
                 continue;
             }
-            //序号
-            if(colMapping.serialNo === undefined && _deESC(cellData) === colText.serialNo[0]){
-                colMapping.serialNo = colIdx;
-            }
-            //编码
-            else if(colMapping.code === undefined && (_deESC(cellData) === colText.code[0] || _deESC(cellData) === colText.code[1])){
-                colMapping.code = colIdx;
-            }
-            //名称
-            else if(colMapping.name === undefined && (_deESC(cellData) === colText.name[0] || _deESC(cellData) === colText.name[1])){
-                colMapping.name = colIdx;
-            }
-            //项目特征
-            else if(colMapping.itemCharacterText === undefined && (_deESC(cellData) === colText.itemCharacterText[0] || _deESC(cellData) === colText.itemCharacterText[1]
-                ||  _deESC(cellData) === colText.itemCharacterText[2])){
-                colMapping.itemCharacterText = colIdx;
-            }
-            //单位
-            else if(colMapping.unit === undefined && (_deESC(cellData) === colText.unit[0] || _deESC(cellData) === colText.unit[1])){
-                colMapping.unit = colIdx;
-            }
-            //工程量
-            else if(colMapping.quantity === undefined && (_deESC(cellData) === colText.quantity[0] || _deESC(cellData) === colText.quantity[1])){
-                colMapping.quantity = colIdx;
-            }
-            //金额
-            else if(colMapping.money === undefined && _deESC(cellData).includes(colText.money[0])){
-                colMapping.money = colIdx;
-            }
-            //工程量明细
-            else if(colMapping.quantityDetail === undefined && _deESC(cellData) === colText.quantityDetail[0]){
-                colMapping.quantityDetail = colIdx;
-            }
-            //费用明细
-            else if(colMapping.feeDetail === undefined && _deESC(cellData) === colText.feeDetail[0]){
-                colMapping.feeDetail = colIdx;
-            }
-        }
-        return colMapping;
-    }
-
-    //是否是有效的表头列格式,只要含有各表需要的列就行,不严格控制多少列
-    function isValidSheet(colMapping, fileType){
-        function hasField(field, all){
-            for(let i of all){
-                if(field === i){
-                    return true;
-                }
-            }
-            return false;
-        }
-        let needFields;
-        if(fileType === uploadType.lj){
-            //09表:序号、项目编码、项目名称、项目特征、计量单位、工程量、金额
-            needFields = ['serialNo', 'code', 'name', 'money'];
-        }
-        else {
-            //广联达表:序号、项目编码、项目名称、项目特征、计量单位、工程量、工程量明细、费用明细
-            needFields = ['serialNo', 'code', 'name', 'itemCharacterText', 'unit', 'quantity', 'quantityDetail', 'feeDetail'];
-        }
-        let hasFieldCount = 0;
-        for(let attr in colMapping){
-            if(hasField(attr, needFields)){
-                hasFieldCount++;
-            }
-        }
-        return hasFieldCount === needFields.length;
-    }
-
-    //获取要无效和有效导入表
-    //@param {Array}importSheetInfo 勾选要导入的表
-    function getImportSheets(sheets, sheetsInfo, fileType){
-        let rst = {invalidSheets: [], validSheets: {fbfx: [], jscsxm: [], zzcsxm: []}};
-
-        for(let sheetInfo of sheetsInfo){
-            let sheet = getSheetByIndex(sheets, sheetInfo.index);
-            if(!sheet){
-                continue;
-            }
-            //没有数据
-            if(!sheet.data.dataTable){
-                rst.invalidSheets.push(sheet.name);
+            let code = dataTable[row][colMapping.code] ? dataTable[row][colMapping.code].value : null,
+                name = dataTable[row][colMapping.name] ? dataTable[row][colMapping.name].value : null,
+                unit = dataTable[row][colMapping.unit] ? dataTable[row][colMapping.unit].value : null,
+                quantity = dataTable[row][colMapping.quantity] ? dataTable[row][colMapping.quantity].value : null;
+            if (!code && !name || /合计/.test(code)) {   //过滤掉同时没有编号和名称的、过滤合计行
                 continue;
             }
-            //获取表的列设置确定导入的格式是否合法(09、广联达)
-            let colMapping = getColMapping(sheet.data.dataTable);
-            if(!isValidSheet(colMapping, fileType)){
-                rst.invalidSheets.push(sheet.name);
-                continue;
-            }
-            //合法的表
-            sheet.colMapping = colMapping;
-            //将合法的表按导入位置分类当做一个表来处理
-            if(rst.validSheets[sheetInfo.position] !== undefined){
-                rst.validSheets[sheetInfo.position].push(sheet)
+            //表格内的数据
+            let depth = getDepth(code);
+            let data = {ID: uuid.v1(), NextSiblingID: -1, ParentID: -1, code: code, name: name, unit: unit, quantity: quantity, depth: depth};
+            let lastData = rst[rst.length - 1];
+            //获取data的父节点链,成为兄弟节点,只能在父链里找前兄弟(不能跨父链)
+            let parents = getParents(lastData);
+            let preData = findLast(parents, x => x.depth === depth);
+            if (preData) {
+                preData.NextSiblingID = data.ID;
+                data.ParentID = preData.ParentID;
+                data.parent = preData.parent;
+            } else {
+                data.ParentID = lastData.ID;
+                data.parent = lastData;
+            }
+            rst.push(data);
+
+        }
+        console.log(rst);
+        return rst;
+        function getDepth(code) {
+            if (!code) {
+                return 1;
             }
+            let match = code.match(/\s/g);
+            return match ? match.length + 1 : 1;
         }
-        return rst;
     }
 
-    //行存在数据
-    function rowExistData(rowData){
-        for(let colIdx in rowData){
-            let colData = rowData[colIdx]['value'];
-            if(_isDef(colData)){
-                return true;
-            }
+    function getParents(data) {
+        let rst = [];
+        let parent = data.parent;
+        while (parent) {
+            rst.push(parent);
+            parent = parent.parent;
         }
-        return false;
+        rst.push(data);
+        return rst;
     }
 
-    //提取excel表数据中的有效数据(去表头表尾,提取其中的excel数据)(根据fixedBill获取栏头占行数)
-    function getValidImportData(colMapping, sheetData){
-        let withingD = false;
-        let validData = [];
-        function isHead(rData){
-            return rData[colMapping.serialNo] && _deESC(rData[colMapping.serialNo]['value']) === colText.serialNo[0];
-        }
-        function isTail(rowData){
-            for(let colIdx in rowData){
-                let colData = rowData[colIdx]['value'];
-                if(colData){
-                    let trimColData= _deESC(colData);
-                    if(trimColData === colText.summation[0] || trimColData === colText.summation[1]){
-                        return true;
-                    }
-                }
-            }
-            return false;
-        }
-        for(let rowIdx in sheetData){
-            let rowData = sheetData[rowIdx];
-            if(isHead(rowData)){
-                withingD = true;
-                continue;
-            }
-            else if(isTail(rowData)){
-                withingD = false;
-            }
-            if(withingD && rowExistData(rowData)){
-                validData.push(rowData);
-            }
+    //获取编号前缀: 101-1 => 101   101-1-1 => 101-1
+    function getPrefix(v) {
+        if (!v) {
+            return null;
         }
-        return validData;
+        let reg = /(.*)-/;
+        let match = reg.exec(v);
+        return match ? match[1] : null;
     }
 
-    //excel数据转换成清单数据
-    function parseToBillData(validData, colMapping, flag, projectID){
+    //提取清单示例数据
+    function extractSLDatas(sheetData) {
+        let colMapping = {code: 0, name: 1, unit: 2, quantity: 3};
+        let dataTable = sheetData.data.dataTable,
+            rowCount = sheetData.rowCount;
         let rst = [];
-        let billIdx = {};
-        let preRootID = -1,
-            preLeafID = -1,
-            preID = -1;
-        //父节点:1.无序号 2有编码
-        function isRoot(rData){
-            //序号和编码去除转义字符(有的表格单元格看起来是没数据,实际含有\r,\n等数据)
-            let serialNo = rData[colMapping.serialNo] ? _deESC(rData[colMapping.serialNo]['value']) : '';
-            let code = rData[colMapping.code] ? _deESC(rData[colMapping.code]['value']) : '';
-            return !_isDef(serialNo) && _isDef(code);
-        }
-        //子节点:有序号
-        function isLeaf(rData){
-            let serialNo = rData[colMapping.serialNo] ? _deESC(rData[colMapping.serialNo]['value']) : '';
-            return _isDef(serialNo);
-        }
-        //续数据:1. 前数据有效 2.无序号 3.无编码 4.有名称或特征
-        function isExtend(preData, rData){
-            let serialNo = rData[colMapping.serialNo] ? _deESC(rData[colMapping.serialNo]['value']) : '';
-            let code = rData[colMapping.code] ? _deESC(rData[colMapping.code]['value']) : '';
-            let name = rData[colMapping.name] ? _deESC(rData[colMapping.name]['value']) : '';
-            let itemCharacterText = rData[colMapping.itemCharacterText] ? _deESC(rData[colMapping.itemCharacterText]['value']) : '';
-            return _isDef(preData) && (isRoot(preData) || isLeaf(preData)) && !_isDef(serialNo) && !_isDef(code) && (_isDef(name) || _isDef(itemCharacterText));
-        }
-        function getBillType(rData, flag){
-            if(flag === fixedFlag.CONSTRUCTION_TECH || flag === fixedFlag.CONSTRUCTION_ORGANIZATION){
-                return billType.BILL;
-            }
-            else if(flag === fixedFlag.SUB_ENGINERRING){
-                return isLeaf(rData) ? billType.FX : billType.FB;
-            }
-            return null;
-        }
-        let preData = null;
-        for(let r = 0; r < validData.length; r++){
-            let rData = validData[r];
-            if(flag == fixedFlag.CONSTRUCTION_TECH && rData[colMapping.name] && rData[colMapping.name]['value'] === '施工技术措施项目'
-                || flag == fixedFlag.CONSTRUCTION_ORGANIZATION && rData[colMapping.name] && rData[colMapping.name]['value'] === '施工组织措施项目'){
-                continue;
-            }
-            //过滤无效数据
-            if(!isRoot(rData) && !isLeaf(rData) && !isExtend(preData, rData)){
-                continue;
-            }
-            if(isExtend(preData, rData)){
-                let preBill = billIdx[preID];
-                if(preBill){
-                    //合并续数据
-                    preBill.code += rData[colMapping.code] && rData[colMapping.code]['value'] && _isDef(_deESC(rData[colMapping.code]['value'])) ? rData[colMapping.code]['value'] : '';
-                    preBill.name += rData[colMapping.name] && rData[colMapping.name]['value'] && _isDef(_deESC(rData[colMapping.name]['value'])) ? rData[colMapping.name]['value'] : '';
-                    preBill.itemCharacterText += rData[colMapping.itemCharacterText] && rData[colMapping.itemCharacterText]['value'] && _isDef(_deESC(rData[colMapping.itemCharacterText]['value']))
-                        ? '\n' + _deNR(rData[colMapping.itemCharacterText]['value']) : '';
-                    preBill.unit += rData[colMapping.unit] && rData[colMapping.unit]['value'] && _isDef(_deESC(rData[colMapping.unit]['value'])) ? rData[colMapping.unit]['value'] : '';
-                    preBill.quantity += rData[colMapping.quantity] && rData[colMapping.quantity]['value'] && _isDef(_deESC(rData[colMapping.quantity]['value'])) ? rData[colMapping.quantity]['value'] : '';
+        let curRoot = null;
+        for (let row = 0; row < rowCount; row++) {
+            let code = dataTable[row][colMapping.code] ? dataTable[row][colMapping.code].value : null,
+                name = dataTable[row][colMapping.name] ? dataTable[row][colMapping.name].value : null,
+                unit = dataTable[row][colMapping.unit] ? dataTable[row][colMapping.unit].value : null,
+                quantity = dataTable[row][colMapping.quantity] ? dataTable[row][colMapping.quantity].value : null;
+            if (!code) {    //没有编号的数据,名称必须为:清单 第xx章,认为新的表根节点
+                if (name && /清单 第\d+章/.test(name)) {
+                    curRoot = {code: null, name: name, ID: uuid.v1(), ParentID: -1, NextSiblingID: -1, parent: null};
+                    rst.push(curRoot);
+                } else {
+                    curRoot = null;
                 }
-            }
-            else {
-                let newID = uuid.v1();
-                let pID = -1;
-                let preBill = null;
-                let preRoot = null,
-                    preLeaf = null;
-                let nodeType = 'root';//后端以此标记来设置ParentID
-                let preSerialBill = billIdx[preID];
-                if(isRoot(rData)){
-                    //pID = 'fixedBillID';
-                    preBill = billIdx[preRootID];
-                    preRoot = billIdx[preRootID];
-                }
-                else if(isLeaf(rData)){
-                    nodeType = 'leaf';
-                    //pID = preRootID !== -1 ? preRootID : fixedBill.ID;
-                    preBill = billIdx[preLeafID];
-                    preLeaf = billIdx[preLeafID];
-                }
-                //set bill data
-                billIdx[newID] = {
-                    nodeType: nodeType,
-                    ID: newID, ParentID: pID, NextSiblingID: -1,
-                    code: rData[colMapping.code] && rData[colMapping.code]['value'] ? _deESC(rData[colMapping.code]['value']) : '',
-                    name: rData[colMapping.name] && rData[colMapping.name]['value'] ? _deESC(rData[colMapping.name]['value']) : '',
-                    itemCharacterText: rData[colMapping.itemCharacterText] && rData[colMapping.itemCharacterText]['value'] ? _deNR(rData[colMapping.itemCharacterText]['value']) : '',
-                    itemCharacter: [],
-                    jobContentText: '',
-                    jobContent: [],
-                    programID: null,
-                    unit: rData[colMapping.unit] && rData[colMapping.unit]['value'] ? _deESC(rData[colMapping.unit]['value']) : '',
-                    quantity: rData[colMapping.quantity] && rData[colMapping.quantity]['value'] ? _deESC(rData[colMapping.quantity]['value']) : '',
-                    quantityEXP: rData[colMapping.quantity] && rData[colMapping.quantity]['value'] ? _deESC(rData[colMapping.quantity]['value']) : '',
-                    //安全文明
-                    flags: flag === fixedFlag.CONSTRUCTION_ORGANIZATION && (rData[colMapping.name] && (rData[colMapping.name]['value'] === '安全文明施工专项费用' || rData[colMapping.name]['value'] === '安全文明施工费')) ?
-                        [{fieldName: 'fixed', flag: fixedFlag.SAFETY_CONSTRUCTION}] : [],
-                    fees: [],
-                    projectID: projectID,
-                    type: getBillType(rData, flag)};
-                //update preBill NextSibling
-                if(nodeType === 'root' && preRoot){
-                    preRoot.NextSiblingID = newID;
+            } else if (!curRoot) {  //根节点为无效根节点,其下子数据全部过滤掉
+                continue;
+            } else {
+                //有code且有有效表根节点
+                let prefix = getPrefix(code);
+                let data = {
+                    code: code,
+                    name: name,
+                    unit: unit,
+                    quantity: quantity,
+                    ID: uuid.v1(),
+                    NextSiblingID: -1,
+                };
+                let lastData = rst[rst.length - 1];
+                let parents = getParents(lastData);
+                //某数据编号为此数据的前缀,则某数据为此数据的父节点
+                let parentData = findLast(parents, x => prefix === x.code);
+                if (!parentData && prefix === '') { // -x的数据,在父链上找不到编号与prefix相同的数据时,父链上-x的数据,则这两数据为兄弟节点,没有则上一行数据为其父节点
+                    let samePrefixData = findLast(parents, x => getPrefix(x.code) === prefix);
+                    parentData = samePrefixData ? samePrefixData.parent : lastData;
+                } else if (!parentData && prefix !== '') {  //不是-x的数据,在父链上找不到编号与prefix相同的数据时,表根节点为其父节点
+                    parentData = curRoot;
                 }
-                else if(nodeType === 'leaf' && preLeaf && preSerialBill && preSerialBill.nodeType === preLeaf.nodeType){
-                    preLeaf.NextSiblingID = newID;
+                data.ParentID = parentData.ID;
+                data.parent = parentData;
+                let preData = findLast(parents, x => x.ParentID === data.ParentID);
+                if (preData) {
+                    preData.NextSiblingID = data.ID;
                 }
-               /* if(preBill){
-                    preBill.NextSiblingID = newID;
-                }*/
-                //set new preID
-                preID = newID;
-                preRootID = isRoot(rData) ? newID : preRootID;
-                preLeafID = isLeaf(rData) ? newID : preLeafID;
+                rst.push(data);
             }
-            preData = rData;
-        }
-        for(let i in billIdx){
-            rst.push(billIdx[i]);
         }
+        console.log(rst);
         return rst;
     }
-
-    function getImportData(validSheets, projectID){
-        let rst = {fbfx: [], jscsxm: [], zzcsxm: []};
-        let validSheetsDatas = [];
-        for(let uploadPosition in validSheets){
-            let validExcelData = [];
-            for(let uSheet of validSheets[uploadPosition]){
-                validExcelData = validExcelData.concat(getValidImportData(uSheet.colMapping, uSheet.data.dataTable));
-            }
-            if(validSheets[uploadPosition].length > 0){
-                validSheetsDatas.push({position: uploadPosition, colMapping: validSheets[uploadPosition][0].colMapping, validExcelData: validExcelData});
-            }
-        }
-        for(let validSheetData of validSheetsDatas){
-            if(validSheetData.validExcelData.length > 0){
-                rst[validSheetData.position] = parseToBillData(validSheetData.validExcelData, validSheetData.colMapping, positionFlag[validSheetData.position], projectID);
-            }
+    
+    function extactDatas(sheets) {
+        let rst = [];
+        for (let sheetName in sheets) {
+            let sheetData = sheets[sheetName];
+            let sheetType = getFileType(sheetData);
+            let colMapping = getColMapping(sheetType);
+            let datas = sheetType === fileType.gcl ? extractGCLDatas(sheetData, colMapping) : extractSLDatas(sheetData, colMapping);
+            rst = rst.concat(datas);
+        }
+        //编号去除空格 清除多余数据 设置数据
+        for (let data of rst) {
+            if (data.code && typeof data.code === 'string') {
+                data.code = data.code.replace(/\s/g, '');
+            }
+            if (data.unit === '㎡') {
+                data.unit = 'm2';
+            } else if (data.unit === 'm³') {
+                data.unit = 'm3';
+            }
+            data.projectID = projectObj.project.ID();
+            data.type = billType.BILL;
+            delete data.parent;
+            delete data.depth;
+        }
+        //将表根节点的ParentID设置成第100章至700章清单的ID
+        let fixedBill = projectObj.project.Bills.tree.roots.find(node => node.data &&
+            node.data.flagsIndex && node.data.flagsIndex.fixed && node.data.flagsIndex.fixed.flag === fixedFlag.ONE_SEVEN_BILLS);
+        let rootDatas = rst.filter(data => data.ParentID === -1);
+        for (let root of rootDatas) {
+            root.ParentID = fixedBill.data.ID;
+        }
+        //清单 第100章 总则清单需要加上固定ID
+        let oneHundredBills = rootDatas.find(data => data.name && /第100章/.test(data.name));
+        if (oneHundredBills) {
+            oneHundredBills.flags = [{flag: fixedFlag.ONE_HUNDRED_BILLS, fieldName: 'fixed'}];
         }
         return rst;
     }
 
-    function excelHasValidBills(importBillsData){
-        for(let i in importBillsData){
-            if(importBillsData[i].length > 0){
-                return true;
-            }
-        }
-        return false;
-    }
-
-
-
-    return {setImportSheetsInfo, getImportSheetsInfo, getImportSheets, getImportData, excelHasValidBills}
+    return {extactDatas}
 })();

+ 4 - 0
web/building_saas/main/js/views/project_info.js

@@ -28,6 +28,10 @@ var projectInfoObj = {
     },
     showProjectInfo: function (data) {
         if (data) {
+            //如果是工程量清单项目,则显示导入功能
+            if (data.property.valuationType === 'ration') {
+                $('#importSpan').show();
+            }
             if(!data.engineeringInfo.billsGuidance_lib || data.engineeringInfo.billsGuidance_lib.length === 0){
                 $('#stdBillsGuidanceTab').addClass('disabled');
             } else {

+ 5 - 41
web/building_saas/main/js/views/project_view.js

@@ -2116,14 +2116,7 @@ let displayLevel = function(nodes, depth, type){
 //导入下拉
 $('#importSpan').click(function () {
     $('[data-toggle="tooltip"]').tooltip('hide');
-    let toggle = $('#importDropDown').attr('data-toggle');
-    if (toggle) {
-        $('#importDropDown').removeAttr('data-toggle');
-    }
-    $('#importDropDown').dropdown('toggle');
-    setTimeout(function () {
-        $('#importDropDown').attr('data-toggle', 'dropdown');
-    }, 100);
+    $('#import').modal('show');
 });
 $('#importDropDown').click(function () {
     $('[data-toggle="tooltip"]').tooltip('hide');
@@ -2679,17 +2672,6 @@ function canInsertRationNode(selected) {//判断是否能插入定额、量价
     }
 }
 
-//导入类型(09表、广联达)
-const uploadType = {lj: 'lj', gld: 'gld'};
-let fileType = uploadType.lj;
-
-$('#uploadLj').click(function () {
-    fileType = uploadType.lj;
-});
-$('#uploadGld').click(function () {
-   fileType = uploadType.gld;
-});
-
 let importJson = null;
 //选择导入的excel文件
 $('#customFile').change(function () {
@@ -2716,8 +2698,6 @@ $('#customFile').change(function () {
        excelIo.open(excelFile, function (json) {
            importJson = json;
            console.log(json);
-           //读取各个表及表名
-           importBills.setImportSheetsInfo(importJson.sheets);
            console.log(`解析Excel文件时间:${+new Date() - sDate}`);
            $.bootstrapLoading.end();
        }, function (e) {
@@ -2746,21 +2726,8 @@ $('#importConfirm').click(function () {
         }
         let sDate = +new Date();
         formData.append('projectID', projectID);
-        //要去匹配的清单库(第一个)
-        let matchBillLibId = projectObj.project.projectInfo.engineeringInfo.bill_lib.length > 0 ? projectObj.project.projectInfo.engineeringInfo.bill_lib[0].id : null;
-        formData.append('billsLibId', matchBillLibId);
-        //选择的表及导入位置
-        let importSheetsInfo = importBills.getImportSheetsInfo();
-        if(importSheetsInfo.length === 0){
-            throw '请勾选要导入的表';
-        }
-        let importSheets = importBills.getImportSheets(importJson.sheets, importSheetsInfo, fileType);
-        console.log(`importSheets`);
-        console.log(importSheets);
-        let invalidSheetsInfo = importSheets.invalidSheets.join('、');
-        //获取同类表的有效数据
-        let importBillsData = importBills.getImportData(importSheets.validSheets, projectID);
-        if(!importBills.excelHasValidBills(importBillsData)){
+        let importBillsData = importBills.extactDatas(importJson.sheets);
+        if(importBillsData.length === 0){
             throw 'excel无有效数据'
         }
         let compressData = LZString.compressToUTF16(JSON.stringify(importBillsData));
@@ -2776,9 +2743,6 @@ $('#importConfirm').click(function () {
             processData: false,
             success: function(response){
                 if (response.err === 0) {
-                    if(invalidSheetsInfo !== ''){
-                        alert(`${invalidSheetsInfo},导入失败。`);
-                    }
                     // 成功则关闭窗体
                     $('#import').modal("hide");
                     //更新前端
@@ -2895,8 +2859,8 @@ function doAfterImportPosition(positionData){
 //导入后更新操作
 function doAfterImport(resData){
     if(resData && resData.length > 0){
-        for(let positionData of resData){
-            doAfterImportPosition(positionData);
+        for(let data of resData){
+            doAfterImportPosition(data);
         }
         //如果清单未锁定,导入后锁定清单
         /*if(!projectObj.project.projectInfo.property.lockBills){

+ 1 - 1
web/building_saas/main/js/views/sub_view.js

@@ -277,7 +277,7 @@ function refreshSubSpread(){
     if(MaterialController.spread) MaterialController.spread.refresh();
     BillsElf.refreshWorkBook();
     //if($('#linkZMHS').hasClass('active')) zmhs_obj.refresh();
-    if($('#rnc-zm').is(':visible')|| $('#rnc-fz').is(':visible')) zmhs_obj.refresh();
+    if($('#rnc-zm').is(':visible')|| $('#rnc-fz').is(':visible') || $('#rnc-cus')) zmhs_obj.refresh();
     if($('#linkMBZM').hasClass('active')) mbzm_obj.refresh();
 }
 

+ 1 - 0
web/building_saas/main/js/views/zmhs_view.js

@@ -261,6 +261,7 @@ let zmhs_obj = {
     refresh:function () {
         $('#coeSpread').is(':visible')&&this.coeSpread?this.coeSpread.refresh():'';
         $('#coeSpread').is(':visible')&&this.coeSpread?this.showDatas():'';//这里combobox下拉框要重新加载一下
+        $('#coeSpread').is(':visible')&&this.coeSpread?this.coeSpread.refresh():'';
         $('#cusSpread').is(':visible')&&this.cusSpread?this.cusSpread.refresh():'';
         $('#assSpread').is(':visible')&&this.assSpread?this.assSpread.refresh():'';
     },

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 90 - 0
web/users/html/login-sms.html


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 37 - 5
web/users/html/login.html


+ 4 - 4
web/users/html/user-safe.html

@@ -72,8 +72,8 @@
                             <label class="form-control-label">异常登录提醒</label>
                             <div class="form-control-static">
                                 <div class="custom-control custom-checkbox" >
-                                    <input type="checkbox" class="custom-control-input" id="customCheck1">
-                                    <label class="custom-control-label" for="customCheck1" >开启</label>
+                                    <input type="checkbox" class="custom-control-input" id="isLoginValid" <% if (userData.isLoginValid === 1) { %>checked<% } %>>
+                                    <label class="custom-control-label" for="isLoginValid" >开启</label>
                                 </div>
                                 <p class="text-muted">
                                     账号出现异常登录时将给你的手机号码发送通知。
@@ -84,8 +84,8 @@
                             <label class="form-control-label">关闭账号登录</label>
                             <div class="form-control-static">
                                 <div class="custom-control custom-checkbox" >
-                                    <input type="checkbox" class="custom-control-input" id="customCheck2">
-                                    <label class="custom-control-label" for="customCheck2" >关闭</label>
+                                    <input type="checkbox" class="custom-control-input" id="isSmsLogin" <% if (userData.isSmsLogin === 1) { %>checked<% } %>>
+                                    <label class="custom-control-label" for="isSmsLogin" >关闭</label>
                                 </div>
                                 <p class="text-muted">
                                     关闭账号登录后,只能通过短信验证码方式登录。

+ 179 - 82
web/users/js/login.js

@@ -50,39 +50,54 @@ $(document).ready(function () {
             if (!valid()) {
                 return false;
             }
-            let account = $("#inputEmail").val();
+            if ($('#changeLogin').attr('data-status') === 'user') {
+                let account = $("#inputEmail").val();
+                if(/^1[3456789]\d{9}$/.test(account) || /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(account)) {
+                    login(captchaObj);
+                } else {
+                    $('#emailHelp').text('您输入的 邮箱/手机 格式不对');
+                }
+            } else {
+                let account = $("#mobileLogin").val();
+                if(/^1[3456789]\d{9}$/.test(account)) {
+                    login(captchaObj);
+                } else {
+                    $('#phoneHelp').text('您输入的 手机 格式不对');
+                }
+            }
+            // let account = $("#inputEmail").val();
             // let pw = $("#inputPassword").val();
 
             // 判断输入的邮箱/手机是否格式正确
-            if(/^1[3456789]\d{9}$/.test(account) || /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(account)) {
-                // 先判断是否是专业版用户,是的话弹出短信验证
-                // $.ajax({
-                //     url: '/accountIsPro',
-                //     type: 'post',
-                //     async: true,
-                //     data: {"account": account, "pw": pw},
-                //     success: function (response) {
-                //         if (response.error === 0) {
-                //             const ispro = response.result;
-                //             if (!ispro) {
-                                login(captchaObj);
-                //             } else {
-                //                 $('#phonepass').modal('show');
-                //                 $('#proMobile').val(response.data);
-                //                 $('#pro_mobile').text(response.data.substr(0, 3) + '****' + response.data.substr(7, 11));
-                //             }
-                //         } else if(response.error === 2) {
-                //             $('#check_ssoId').val(response.ssoId);
-                //             $('#phone').modal('show');
-                //         } else {
-                //             let msg = response.msg !== undefined ? response.msg : '未知错误';
-                //             showError(msg, $("input"));
-                //         }
-                //     }
-                // });
-            } else {
-                $('#emailHelp').text('您输入的 邮箱/手机 格式不对');
-            }
+            // if(/^1[3456789]\d{9}$/.test(account) || /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(account)) {
+            // // 先判断是否是专业版用户,是的话弹出短信验证
+            // $.ajax({
+            //     url: '/accountIsPro',
+            //     type: 'post',
+            //     async: true,
+            //     data: {"account": account, "pw": pw},
+            //     success: function (response) {
+            //         if (response.error === 0) {
+            //             const ispro = response.result;
+            //             if (!ispro) {
+            //                 login(captchaObj);
+            //             } else {
+            //                 $('#phonepass').modal('show');
+            //                 $('#proMobile').val(response.data);
+            //                 $('#pro_mobile').text(response.data.substr(0, 3) + '****' + response.data.substr(7, 11));
+            //             }
+            //         } else if(response.error === 2) {
+            //             $('#check_ssoId').val(response.ssoId);
+            //             $('#phone').modal('show');
+            //         } else {
+            //             let msg = response.msg !== undefined ? response.msg : '未知错误';
+            //             showError(msg, $("input"));
+            //         }
+            //     }
+            //     // });
+            // } else {
+            //     $('#emailHelp').text('您输入的 邮箱/手机 格式不对');
+            // }
         });
 
         // $('#loginPro').click(function () {
@@ -172,53 +187,111 @@ $(document).ready(function () {
         }
     });
 
-    // $("#get-code2").click(function() {
-    //     const mobile = $("#proMobile").val();
-    //     if(!validMobile(mobile)){
-    //         return false;
-    //     }
-    //     const btn = $(this);
-    //     if(!btn.hasClass('disabled')){
-    //         $.ajax({
-    //             url: '/sms/code',
-    //             type: 'post',
-    //             data: { mobile: mobile, type: 3},
-    //             error: function() {
-    //                 showValidError('短信接口出错!',$('#smsCode'));
-    //             },
-    //             beforeSend: function() {
-    //             },
-    //             success: function(response) {
-    //                 if (response.err === 0) {
-    //                     codeSuccess(btn);
-    //                 } else {
-    //                     showValidError(response.msg,$('#smsCode'));
-    //                 }
-    //             }
-    //         });
-    //     }
-    // });
+    // 切换登录方式
+    $('#changeLogin').click(function () {
+        if ($(this).attr('data-status') === 'user') {
+            $(this).attr('data-status', 'sms');
+            $('.change-login-p').text('短信登录');
+            $(this).text('账号登录');
+            $('.sms-login-modal').show();
+            $('.user-login-modal').hide();
+            $('.sms-login-modal input').attr('disabled', false);
+            $('.user-login-modal input').attr('disabled', true);
+        } else {
+            $(this).attr('data-status', 'user');
+            $('.change-login-p').text('账号登录');
+            $(this).text('短信登录');
+            $('.sms-login-modal').hide();
+            $('.user-login-modal').show();
+            $('.sms-login-modal input').attr('disabled', true);
+            $('.user-login-modal input').attr('disabled', false);
+        }
+    });
+
+    // 切换到短信登录
+    $('#changeSmsLogin').click(function () {
+        $('#changeLogin').attr('data-status', 'sms');
+        $('.change-login-p').text('短信登录');
+        $('#changeLogin').text('账号登录');
+        $('.sms-login-modal').show();
+        $('.user-login-modal').hide();
+        $('.sms-login-modal input').attr('disabled', false);
+        $('.user-login-modal input').attr('disabled', true);
+        $('#phonepass').modal('hide');
+    });
+
+    $("#get-code2").click(function() {
+        const mobile = $("#mobileLogin").val();
+        if(!validMobile(mobile, 0)){
+            return false;
+        }
+        const btn = $(this);
+        if(!btn.hasClass('disabled')){
+            // 判断该手机号是否已注册通行账号
+            $.ajax({
+                url: '/sms/check/mobile',
+                type: 'post',
+                data: {mobile: mobile},
+                error: function() {
+                    $('#phoneHelp').text('号码查询接口出错!');
+                },
+                beforeSend: function() {
+                },
+                success: function(response) {
+                    if (response.err === 0) {
+                        $.ajax({
+                            url: '/sms/code',
+                            type: 'post',
+                            data: { mobile: mobile, type: 3},
+                            error: function() {
+                                $('#phoneHelp').text('短信接口出错!');
+                            },
+                            beforeSend: function() {
+                            },
+                            success: function(response) {
+                                if (response.err === 0) {
+                                    codeSuccess(btn);
+                                } else {
+                                    $('#phoneHelp').text(response.msg);
+                                }
+                            }
+                        });
+                    } else {
+                        $('#phoneHelp').text(response.msg);
+                    }
+                }
+            })
+        }
+    });
 });
 
 function login(captchaObj) {
-    let account = $("#inputEmail").val();
-    let pw = $("#inputPassword").val();
+
     let geetest_challenge = $('input[name="geetest_challenge"]').val();
     let geetest_validate = $('input[name="geetest_validate"]').val();
     let geetest_seccode = $('input[name="geetest_seccode"]').val();
-    // let code = $("#smsCode").val();
+
+    const postData = {
+        geetest_challenge: geetest_challenge,
+        geetest_validate: geetest_validate,
+        geetest_seccode: geetest_seccode,
+    };
+    if ($('#changeLogin').attr('data-status') === 'user') {
+        let account = $("#inputEmail").val();
+        let pw = $("#inputPassword").val();
+        postData.account = account;
+        postData.pw = pw;
+    } else {
+        let mobile = $('#mobileLogin').val();
+        let code = $("#codeLogin").val();
+        postData.mobile = mobile;
+        postData.code = code;
+    }
 
     $.ajax({
         url: '/login',
         type: 'post',
-        data: {
-            "account": account,
-            "pw": pw,
-            "geetest_challenge": geetest_challenge,
-            "geetest_validate": geetest_validate,
-            "geetest_seccode": geetest_seccode,
-            // "code": code,
-        },
+        data: postData,
         success: function (response) {
             if (response.error === 0) {
                 // $('#phonepass').modal('hide');
@@ -242,8 +315,10 @@ function login(captchaObj) {
                 captchaObj.reset();
                 $('#check_ssoId').val(response.ssoId);
                 $('#phone').modal('show');
-            // } else if(response.error === 3) {
-            //     showValidError(response.msg,$('#smsCode'));
+            } else if(response.error === 3) {
+                captchaObj.reset();
+                $('#phonepass').modal('show');
+                $('#mobileLogin').val(response.data);
             } else {
                 // $('#phonepass').modal('hide');
                 let msg = response.msg !== undefined ? response.msg : '未知错误';
@@ -286,15 +361,23 @@ function codeSuccess(btn) {
  *
  * @return {boolean}
  */
-function validMobile(mobile) {
+function validMobile(mobile, status = 1) {
     let result = true;
     if($.trim(mobile) === ''){
-        showValidError('手机号不能为空!',$('#mobile'));
+        if (status === 1) {
+            showValidError('手机号不能为空!',$('#mobile'));
+        } else {
+            $('#phoneHelp').text('手机号不能为空!');
+        }
         return false;
     }
-    let mobileValid =  /^1[3456789]\d{9}$/;
+    let mobileValid = /^1[3456789]\d{9}$/;
     if(!mobileValid.test(mobile)){
-        showValidError('手机号码格式有误!',$('#mobile'));
+        if (status === 1) {
+            showValidError('手机号码格式有误!',$('#mobile'));
+        } else {
+            $('#phoneHelp').text('手机号码格式有误!');
+        }
         return false;
     }
     return result;
@@ -331,18 +414,32 @@ function cleanValidError(element) {
  */
 function valid() {
     let result = true;
-    let account = $("#inputEmail").val();
-    if (account === undefined || account === '') {
-        showError('用户名不能为空!', $("#inputEmail"));
-        return false;
-    }
+    if ($('#changeLogin').attr('data-status') === 'user') {
+        let account = $("#inputEmail").val();
+        if (account === undefined || account === '') {
+            showError('用户名不能为空!', $("#inputEmail"));
+            return false;
+        }
 
-    let password = $("#inputPassword").val();
-    if (password === undefined || password === '') {
-        showError('密码不能为空!', $("#inputPassword"));
-        return false;
+        let password = $("#inputPassword").val();
+        if (password === undefined || password === '') {
+            showError('密码不能为空!', $("#inputPassword"));
+            return false;
+        }
+    } else {
+        let mobile = $('#mobileLogin').val();
+        if (mobile === undefined || mobile === '') {
+            showError('手机号码不能为空!', $("#mobileLogin"));
+            return false;
+        }
+        let code = $('#codeLogin').val();
+        if (code === undefined || code === '') {
+            showError('验证码不能为空!', $("#codeLogin"));
+            return false;
+        }
     }
 
+
     return result;
 }
 

+ 31 - 1
web/users/js/user.js

@@ -27,6 +27,36 @@ $(document).ready(function() {
             $('#upgrade-title').text('联系销售代表激活');
         }
         CommonHeader.getCategoryList(category);
+    });
+
+    // 关闭和开启账号登录
+    $('#isSmsLogin').click(function () {
+        let status = $(this).is(':checked') ? 1 : 0;
+        $.ajax({
+            type: 'post',
+            url: '/user/change/isSmsLogin',
+            data: {status : status},
+            success: function (response) {
+                if (response.error !== 0) {
+                    alert(response.msg);
+                }
+            }
+        })
+    });
+
+    // 关闭和开启异常登录提醒
+    $('#isLoginValid').click(function () {
+        let status = $(this).is(':checked') ? 1 : 0;
+        $.ajax({
+            type: 'post',
+            url: '/user/change/isLoginValid',
+            data: {status : status},
+            success: function (response) {
+                if (response.error !== 0) {
+                    alert(response.msg);
+                }
+            }
+        })
     })
 });
 
@@ -87,4 +117,4 @@ function cleanError() {
     $("input").removeClass('orm-control-danger');
     $("input").parent().removeClass('has-danger');
     $(".form-control-feedback").remove();
-}
+}