Browse Source

新增部位接入大司空项目清单功能

ellisran 1 year ago
parent
commit
ceb099af64

+ 5 - 0
app/controller/change_controller.js

@@ -1481,10 +1481,15 @@ module.exports = app => {
                     revising = true;
                 }
                 const renderData = await this._getDefaultReviseInfoData(ctx, change, !edit);
+                // 获取原报dsk数据
+                const accountInfo = await ctx.service.projectAccount.getDataById(change.uid);
+                renderData.dskAccountData = accountInfo && accountInfo.dsk_account ? JSON.parse(accountInfo.dsk_account) : null;
+                renderData.dskProjects = accountInfo && accountInfo.dsk_projects ? JSON.parse(accountInfo.dsk_projects) : [];
                 // 台账只读、使用数据
                 renderData.readOnly = !edit;
                 renderData.changing = changing;
                 renderData.revising = revising;
+                renderData.isYb = change.uid === ctx.session.sessionUser.accountId;
                 await this.layout('change/revise.ejs', renderData, 'change/revise_modal.ejs');
             } catch (err) {
                 this.log(err);

+ 131 - 0
app/controller/profile_controller.js

@@ -17,6 +17,7 @@ const sendToWormhole = require('stream-wormhole');
 const loginWay = require('../const/setting').loginWay;
 const wxWork = require('../lib/wx_work');
 const profileConst = require('../const/profile');
+const DSK = require('../lib/dsk');
 
 module.exports = app => {
 
@@ -330,6 +331,7 @@ module.exports = app => {
 
             // 获取账号数据
             const accountData = await ctx.service.projectAccount.getDataByCondition({ id: sessionUser.accountId });
+            accountData.dsk_account = accountData.dsk_account ? JSON.parse(accountData.dsk_account) : null;
 
             const renderData = {
                 accountData,
@@ -360,6 +362,135 @@ module.exports = app => {
             ctx.redirect(ctx.request.header.referer);
         }
 
+        async dskApi(ctx) {
+            const response = {
+                err: 0,
+                msg: '',
+            };
+            try {
+                const sessionUser = ctx.session.sessionUser;
+                const data = JSON.parse(ctx.request.body.data);
+                const dskAccount = await ctx.service.projectAccount.getDataById(sessionUser.accountId);
+                const dsk = new DSK(ctx);
+                switch (data.type) {
+                    case 'sms':
+                        if (!data.mobile) {
+                            throw '参数有误';
+                        }
+                        if (!(/^1[3456789]\d{9}$/.test(data.mobile))) {
+                            throw '手机号码格式有误';
+                        }
+                        await dsk.sendSms(data.mobile);
+                        break;
+                    case 'bind':
+                        if (dskAccount.dsk_account) {
+                            throw '已经绑定过大司空账号';
+                        }
+                        if ((data.method !== 1 && data.method !== 2) || !data.value || !data.mobile) {
+                            throw '参数有误';
+                        }
+                        if (!(/^1[3456789]\d{9}$/.test(data.mobile))) {
+                            throw '手机号码格式有误';
+                        }
+                        const isExist = await ctx.service.projectAccount.isDskExist(ctx.session.sessionProject.id, data.mobile);
+                        if (isExist) {
+                            throw '此大司空账号已被该项目用户绑定,请重新输入';
+                        }
+                        const result = await dsk.accountAuth(data.mobile, data.method, data.value);
+                        response.data = await ctx.service.projectAccount.bindDsk(result, data.mobile, sessionUser.accountId);
+                        break;
+                    case 'unbind':
+                        if (!dskAccount.dsk_account) {
+                            throw '未绑定大司空账号';
+                        }
+                        response.data = await ctx.service.projectAccount.unbindDsk(sessionUser.accountId);
+                        break;
+                    case 'hadbind':
+                        if (!dskAccount.auth_mobile) {
+                            response.data = 1; //  未绑定手机
+                        } else if (!dskAccount.dsk_account) {
+                            response.data = 2; //  未绑定大司空账号
+                        } else {
+                            response.data = JSON.parse(dskAccount.dsk_account);
+                        }
+                        break;
+                    case 'compilation':
+                        if (!dskAccount.dsk_account) {
+                            throw '未绑定大司空账号';
+                        }
+                        const account = JSON.parse(dskAccount.dsk_account);
+                        const compilation = await dsk.getCompilation(data.mobile);
+                        response.data = {};
+                        response.data.compilation = compilation;
+                        if (data.getProject) {
+                            const compilationInfo = data.compilationId ? ctx.helper._.find(compilation, { ID: data.compilationId }) : null;
+                            const compilationId = compilationInfo ? compilationInfo.ID : (compilation.length > 0 ? compilation[0].ID : null);
+                            response.data.select_compilation = compilationId;
+                            const projectInfo = compilationId ? await dsk.getProjectList(account.mobile, compilationId) : [];
+                            response.data.project = projectInfo;
+                        }
+                        break;
+                    case 'project':
+                        if (!dskAccount.dsk_account) {
+                            throw '未绑定大司空账号';
+                        }
+                        const account2 = JSON.parse(dskAccount.dsk_account);
+                        response.data = await dsk.getProjectList(account2.mobile, data.compilationId);
+                        break;
+                    case 'save_projects':
+                        if (!dskAccount.dsk_account) {
+                            throw '未绑定大司空账号';
+                        }
+                        const tender = await ctx.service.tender.getDataById(data.tid);
+                        if (!tender || tender.user_id !== sessionUser.accountId) {
+                            throw '无权限操作';
+                        }
+                        await ctx.service.projectAccount.saveDskProjects(sessionUser.accountId, data.project_list);
+                        const account3 = JSON.parse(dskAccount.dsk_account);
+                        if (data.project_id && ctx.helper._.findIndex(data.project_list, { pid: data.project_id }) !== -1) {
+                            account3.select_project = data.project_id;
+                        } else {
+                            account3.select_project = data.project_list && data.project_list.length > 0 ? data.project_list[0].pid : null;
+                        }
+                        await ctx.service.projectAccount.defaultUpdate({ id: sessionUser.accountId, dsk_account: JSON.stringify(account3) });
+                        response.data = account3.select_project;
+                        break;
+                    case 'project_tree':
+                        response.data = await dsk.getProjectTree(data.compilationId, data.projectId);
+                        const tender2 = data.tid ? await ctx.service.tender.getDataById(data.tid) : null;
+                        if (tender2 && tender2.user_id === sessionUser.accountId && dskAccount.dsk_account) {
+                            const account4 = JSON.parse(dskAccount.dsk_account);
+                            account4.select_project = data.projectId;
+                            let treeId = null;
+                            if (response.data && response.data.length > 0) {
+                                const treeInfo = ctx.helper._.find(response.data, { type: 4 });
+                                if (treeInfo && treeInfo.ID) {
+                                    treeId = treeInfo.ID;
+                                }
+                            }
+                            account4.select_tree = treeId;
+                            await ctx.service.projectAccount.defaultUpdate({ id: sessionUser.accountId, dsk_account: JSON.stringify(account4) });
+                        }
+                        break;
+                    case 'project_bills':
+                        response.data = await dsk.getProjectBills(data.compilationId, data.treeId);
+                        const tender3 = data.tid ? await ctx.service.tender.getDataById(data.tid) : null;
+                        if (tender3 && tender3.user_id === sessionUser.accountId && dskAccount.dsk_account) {
+                            const account5 = JSON.parse(dskAccount.dsk_account);
+                            account5.select_tree = data.treeId;
+                            await ctx.service.projectAccount.defaultUpdate({ id: sessionUser.accountId, dsk_account: JSON.stringify(account5) });
+                        }
+                        break;
+                    default:throw '参数有误';
+                }
+            } catch (error) {
+                response.err = 1;
+                response.msg = error.toString();
+            }
+
+            ctx.body = response;
+        }
+
         /**
          * 电子签名
          *

+ 3 - 3
app/extend/helper.js

@@ -265,7 +265,7 @@ module.exports = {
      * @param {String} dataType - 数据类型 json|text
      * @return {Object} - 请求结果
      */
-    async sendRequest(url, data, type = 'POST', dataType = 'json') {
+    async sendRequest(url, data, type = 'POST', dataType = 'json', showErr = false) {
         // 发起请求
         try {
             const response = await this.ctx.curl(url, {
@@ -273,10 +273,10 @@ module.exports = {
                 data,
                 dataType,
             });
-            if (response.status !== 200) {
+            if (!showErr && response.status !== 200) {
                 throw '请求失败';
             }
-            return response.data;
+            return showErr ? response : response.data;
         } catch (err) {
             throw '请求失败';
         }

+ 111 - 0
app/lib/dsk.js

@@ -0,0 +1,111 @@
+'use strict';
+
+/**
+ * 大司空相关接口
+ *
+ * @author CaiAoLin
+ * @date 2018/1/25
+ * @version
+ */
+const crypto = require('crypto');
+const moment = require('moment');
+class DSK {
+
+    /**
+     * 构造函数
+     *
+     * @param {Object} ctx - egg全局变量
+     * @return {void}
+     */
+    constructor(ctx) {
+        this.ctx = ctx;
+        this.url = 'https://dsk.smartcost.com.cn/';
+        this.tokenKey = 'jH2=lj4j@!$#421?{S54n';
+    }
+
+    getToken() {
+        return crypto.createHash('md5').update(crypto.createHash('md5').update(this.tokenKey).digest('hex') + moment().format('DD-HH')).digest('hex');
+    }
+
+    async sendSms(mobile) {
+        const url = this.url + 'api/sms/external/getSmsCode';
+        const postData = {
+            mobile,
+        };
+        let result = false;
+        try {
+            const response = await this.ctx.helper.sendRequest(url, postData, 'GET', 'json', true);
+            result = true;
+        } catch (error) {
+            console.log(error);
+            result = false;
+        }
+        return result;
+    }
+
+    async accountAuth(mobile, method, value) {
+        const url = this.url + 'api/user/external/auth';
+        const postData = {
+            mobile,
+            loginMode: method === 1 ? 'password' : 'sms',
+        };
+        if (method === 1) {
+            postData.password = value;
+        } else {
+            postData.code = value;
+        }
+        return this.dealWith(await this.ctx.helper.sendRequest(url, postData, 'POST', 'json', true));
+    }
+
+    dealWith(result) {
+        if (result.status !== 200) {
+            throw result.data ? result.data.message : '大司空接口接入失败';
+        }
+        if (!(result.data && result.data.errno === 0 && result.data.data)) {
+            throw result.data ? result.data.message : '大司空接口接入失败';
+        }
+        return result.data.data;
+    }
+
+    async getCompilation() {
+        const url = this.url + 'api/compilation/external/list';
+        const postData = {};
+        const result = this.dealWith(await this.ctx.helper.sendRequest(url, postData, 'GET', 'json', true));
+        return result.filter(item => { return item.type === 'highway'; });
+    }
+
+    async getProjectList(mobile, compilationId) {
+        const url = this.url + 'api/project/external/list';
+        const token = this.getToken();
+        const postData = {
+            compilationID: compilationId,
+            token,
+            mobile,
+        };
+        return this.dealWith(await this.ctx.helper.sendRequest(url, postData, 'POST', 'json', true));
+    }
+
+    async getProjectTree(compilationId, projectId) {
+        const url = this.url + 'api/project/external/tree';
+        const token = this.getToken();
+        const postData = {
+            compilationID: compilationId,
+            token,
+            ID: projectId,
+        };
+        return this.dealWith(await this.ctx.helper.sendRequest(url, postData, 'POST', 'json', true));
+    }
+
+    async getProjectBills(compilationId, treeId) {
+        const url = this.url + 'api/project/external/bills';
+        const token = this.getToken();
+        const postData = {
+            compilationID: compilationId,
+            token,
+            ID: treeId,
+        };
+        return this.dealWith(await this.ctx.helper.sendRequest(url, postData, 'POST', 'json', true));
+    }
+}
+
+module.exports = DSK;

+ 0 - 1
app/public/js/change_information_show.js

@@ -98,7 +98,6 @@ $(document).ready(() => {
                 return ZhCalc.round(ZhCalc.mul(ZhCalc.round(data.unit_price, unitPriceUnit), ZhCalc.round(data.samount, findDecimal(data.unit))), totalPriceUnit);
             },
             changed_amount: function (data) {
-                console.log(data.checked_amount);
                 // return ZhCalc.round(ZhCalc.add(data.oamount, data.spamount), findDecimal(data.unit));
                 return ZhCalc.round(ZhCalc.add(data.oamount, data.checked_amount), findDecimal(data.unit));
             },

+ 812 - 0
app/public/js/change_revise.js

@@ -28,6 +28,7 @@ function transExpr(expr) {
     return $.trim(expr).replace('\t', '').replace('=', '').replace('%', '/100');
 }
 const copyBlockTag = 'zh.calc.copyBlock';
+const dskCompilation = 'zh.calc.dskCompilation';
 const checkOption = {
     sibling: { enable: 1 },
     empty_code: { enable: 1 },
@@ -2432,6 +2433,487 @@ $(document).ready(() => {
         selectedBackColor: '#fffacd',
     });
 
+    class Jlzf {
+        constructor (selector, spreadSetting) {
+            const self = this;
+            this.loaded = false;
+            this.obj = $(selector);
+            const relaSelect = {
+                showLevel: `cb-${spreadSetting.stdType}-sl`,
+                searchText: `cb-${spreadSetting.stdType}-st`,
+                searchResult: `cb-${spreadSetting.stdType}-sr`,
+                searchPre: `cb-${spreadSetting.stdType}-sp`,
+                searchNext: `cb-${spreadSetting.stdType}-sn`,
+                searchClose: `cb-${spreadSetting.stdType}-sc`,
+            };
+            this.obj.html(
+                '<div class="sjs-bar d-flex">\n' +
+                '    <div class="ml-1">\n' +
+        '                <div class="px-2 border-primary border-1 d-flex">\n' +
+        '                    <div class="d-inline-block">\n' +
+        '                        <div class="input-group input-group-sm mr-1">' +
+        `                            <input type="text" class="form-control" placeholder="输入编号/名称查找" id="${relaSelect.searchText}">\n` +
+        `                            <div class="input-group-append" ><span class="input-group-text" id="${relaSelect.searchResult}">结果:0</span></div>\n` +
+        '                            <div class="input-group-append" >\n' +
+        `                                <button class="btn btn-outline-secondary" type="button" title="上一个" id="${relaSelect.searchPre}"><i class="fa fa-angle-double-left"></i></button>\n` +
+        `                                <button class="btn btn-outline-secondary" type="button" title="下一个" id="${relaSelect.searchNext}"><i class="fa fa-angle-double-right"></i></button>\n` +
+        '                            </div>\n' +
+        '                        </div>\n' +
+        '                    </div>\n' +
+        `                    <div class="d-inline-block"><button class="btn btn-light text-danger btn-sm ml-1" type="button" id="${relaSelect.searchClose}"><i class="fa fa-remove"></i></button></div>\n` +
+        '                </div>\n' +
+                '    </div>' +
+                '</div>\n' +
+                `<div id="cb-${spreadSetting.stdType}-spread" class="cb-${spreadSetting.stdType}-sh"></div>\n`
+            );
+            autoFlashHeight();
+            const sh = `.cb-${spreadSetting.stdType}-sh`;
+            function getObjHeight(select) {
+                return select.length > 0 ? select.height() : 0;
+            }
+            const cHeader = getObjHeight($(".c-header"));
+            $(sh).height($(window).height()-cHeader-getObjHeight($('.sjs-bar', sh.parentNode))-getObjHeight($('.sjs-bottom', sh.parentNode))-92+55-30);
+            // this.url = '/tender/' + window.location.pathname.split('/')[2] + '/deal';
+            this.spreadSetting = spreadSetting;
+            this.spread = SpreadJsObj.createNewSpread($(`#cb-${spreadSetting.stdType}-spread`)[0]);
+            const searchSheet = this.spread.getActiveSheet();
+            SpreadJsObj.initSheet(searchSheet, this.spreadSetting);
+            if (!readOnly) {
+                this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
+                    const dealSheet = info.sheet;
+                    const mainSheet = billsSheet;
+
+                    const dealBills = SpreadJsObj.getSelectObject(dealSheet);
+                    if (!dealBills) { return; }
+                    const mainTree = mainSheet.zh_tree;
+                    const mainNode = SpreadJsObj.getSelectObject(mainSheet);
+                    if (!mainNode || !mainTree) { return; }
+
+                    if (mainNode.code && mainNode.code !== '' && !mainTree.isLeafXmj(mainNode)) {
+                        toastr.warning('非最底层项目下,不应添加节点');
+                        return;
+                    }
+                    if (mainNode.settle_status === settleStatus.finish) {
+                        toastr.warning('已结算节点下,不应添加签约清单');
+                        return;
+                    }
+
+                    postData(window.location.pathname + '/update', {
+                        postType: 'add-deal',
+                        postData: {
+                            id: mainNode.ledger_id,
+                            type: mainNode.code ? 'child' : 'next',
+                            dealBills: {
+                                b_code: dealBills.b_code, code: dealBills.code, name: dealBills.name, unit: dealBills.unit,
+                                unit_price: dealBills.unit_price,
+                            }
+                        },
+                    }, function (result) {
+                        const refreshData = mainTree.loadPostData(result);
+                        billsTreeSpreadObj.refreshTree(mainSheet, refreshData);
+                        const sel = mainSheet.getSelections()[0];
+                        if (sel && refreshData.create[0]) {
+                            mainSheet.setSelection(mainTree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
+                            SpreadJsObj.reloadRowsBackColor(mainSheet, [sel.row, mainTree.nodes.indexOf(refreshData.create[0])]);
+                        }
+                        billsTreeSpreadObj.refreshOperationValid(mainSheet);
+                        billsSpread.focus();
+                        posSpreadObj.loadCurPosData();
+                    });
+                });
+            }
+            SpreadJsObj.forbiddenSpreadContextMenu(selector, this.spread);
+            const searchObj = {
+                result: [],
+                cur: 0,
+                searchStdNode: function() {
+                    const keyword = $(`#${relaSelect.searchText}`).val();
+                    const sortData = SpreadJsObj.getSortData(searchSheet);
+                    searchObj.result = keyword ? sortData.filter(x => {
+                        return (x.code && x.code.indexOf(keyword) >= 0) || (x.b_code && x.b_code.indexOf(keyword) >= 0) || (x.name && x.name.indexOf(keyword) >= 0);
+                    }) : [];
+                    $(`#${relaSelect.searchResult}`)[0].innerText = `结果:${searchObj.result.length}`;
+                    searchObj.cur = 0;
+                    if (searchObj.result.length > 0) {
+                        SpreadJsObj.locateData(searchSheet, searchObj.result[searchObj.cur]);
+                    }
+                },
+                searchPre: function () {
+                    if (searchObj.result.length === 0) return;
+                    searchObj.cur = searchObj.cur === 0 ? searchObj.result.length - 1 : this.cur - 1;
+                    SpreadJsObj.locateData(searchSheet, searchObj.result[searchObj.cur]);
+                },
+                searchNext: function () {
+                    if (searchObj.result.length === 0) return;
+                    searchObj.cur = searchObj.cur === searchObj.result.length - 1 ? 0 : searchObj.cur + 1;
+                    SpreadJsObj.locateData(searchSheet, searchObj.result[searchObj.cur]);
+                },
+                clear: function () {
+                    $(`#${relaSelect.searchText}`).val('');
+                    $(`#${relaSelect.searchResult}`)[0].innerText = `结果:${0}`;
+                    searchObj.result = [];
+                    searchObj.cur = 0;
+                }
+            };
+            $(`#${relaSelect.searchText}`).change(searchObj.searchStdNode);
+            $(`#${relaSelect.searchPre}`).click(function (e) {
+                searchObj.searchPre();
+                e.stopPropagation();
+            });
+            $(`#${relaSelect.searchNext}`).click(function (e) {
+                searchObj.searchNext();
+                e.stopPropagation();
+            });
+            $(`#${relaSelect.searchClose}`).click(function (e) {
+                searchObj.clear();
+                e.stopPropagation();
+            });
+        };
+        loadData (callback) {
+            if (this.loaded) {
+                if (callback) callback();
+                return;
+            }
+            const self = this;
+            const data = billsTree.nodes.filter(node => node.ccid !== undefined && node.ccid !== '');
+            SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', data);
+            self.loaded = true;
+            if (callback) callback();
+        }
+    }
+
+    const jlzfBills = new Jlzf('#jlzf-spread', {
+        cols: [
+            {title: '项目节编号', field: 'code', hAlign: 0, width: 85, formatter: '@'},
+            {title: '清单编号', field: 'b_code', hAlign: 0, width: 85, formatter: '@'},
+            {title: '名称', field: 'name', hAlign: 0, width: 150, formatter: '@'},
+            {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@'},
+            {title: '单价', field: 'unit_price', hAlign: 2, width: 50},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [32],
+        headColWidth: [30],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        selectedBackColor: '#fffacd',
+        readOnly: true,
+        stdType: 'jlzf',
+    });
+
+    class Dsk {
+        constructor (selector, spreadSetting) {
+            const self = this;
+            this.loaded = false;
+            this.obj = $(selector);
+            const dskProjectHtml = spreadSetting.libs.map(l => {
+                return `<option value="${l.pid}" ${spreadSetting.select_lib === l.pid ? 'selected' : ''}>${l.name}</option>`;
+            });
+            const relaSelect = {
+                showLevel: `cb-${spreadSetting.stdType}-sl`,
+                searchText: `cb-${spreadSetting.stdType}-st`,
+                searchResult: `cb-${spreadSetting.stdType}-sr`,
+                searchPre: `cb-${spreadSetting.stdType}-sp`,
+                searchNext: `cb-${spreadSetting.stdType}-sn`,
+                searchClose: `cb-${spreadSetting.stdType}-sc`,
+            };
+            this.obj.html(
+                '<div class="sjs-bar d-flex">\n' +
+                `    <div class="input-group input-group-sm pr-1"><select class="form-control form-control-sm col-auto" id="change-dsk-project">${dskProjectHtml.join('')}</select></div>\n` +
+                `    <div class="input-group input-group-sm pr-1"><select class="form-control form-control-sm col-auto" id="change-dsk-project-tree"></select></div>\n` +
+                '    <div class="ml-1">\n' +
+                '        <div class="dropdown">\n' +
+                '            <button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n' +
+                '                <i class="fa fa-search"></i>\n' +
+                '            </button>\n' +
+                '            <div class="dropdown-menu dropdown-menu-right">\n' +
+                '                <div class="px-2 border-primary border-1 d-flex" style="width: 300px">\n' +
+                '                    <div class="d-inline-block">\n' +
+                '                        <div class="input-group input-group-sm mr-1">' +
+                `                            <input type="text" class="form-control" placeholder="输入编号/名称查找" id="${relaSelect.searchText}">\n` +
+                `                            <div class="input-group-append" ><span class="input-group-text" id="${relaSelect.searchResult}">结果:0</span></div>\n` +
+                '                            <div class="input-group-append" >\n' +
+                `                                <button class="btn btn-outline-secondary" type="button" title="上一个" id="${relaSelect.searchPre}"><i class="fa fa-angle-double-left"></i></button>\n` +
+                `                                <button class="btn btn-outline-secondary" type="button" title="下一个" id="${relaSelect.searchNext}"><i class="fa fa-angle-double-right"></i></button>\n` +
+                '                            </div>\n' +
+                '                        </div>\n' +
+                '                    </div>\n' +
+                `                    <div class="d-inline-block"><button class="btn btn-light text-danger btn-sm ml-1" type="button" id="${relaSelect.searchClose}"><i class="fa fa-remove"></i></button></div>\n` +
+                '                </div>\n' +
+                '            </div>\n' +
+                '        </div>\n' +
+                '    </div>' +
+                '</div>\n' +
+                `<div id="cb-${spreadSetting.stdType}-spread" class="cb-${spreadSetting.stdType}-sh"></div>\n`
+            );
+            autoFlashHeight();
+            const sh = `.cb-${spreadSetting.stdType}-sh`;
+            function getObjHeight(select) {
+                return select.length > 0 ? select.height() : 0;
+            }
+            const cHeader = getObjHeight($(".c-header"));
+            $(sh).height($(window).height()-cHeader-getObjHeight($('.sjs-bar', sh.parentNode))-getObjHeight($('.sjs-bottom', sh.parentNode))-92+55-30);
+            // this.url = '/tender/' + window.location.pathname.split('/')[2] + '/deal';
+            this.spreadSetting = spreadSetting;
+            this.spread = SpreadJsObj.createNewSpread($(`#cb-${spreadSetting.stdType}-spread`)[0]);
+            const searchSheet = this.spread.getActiveSheet();
+            SpreadJsObj.initSheet(searchSheet, this.spreadSetting);
+            if (!readOnly) {
+                this.spread.bind(spreadNS.Events.CellDoubleClick, function (e, info) {
+                    const dealSheet = info.sheet;
+                    const mainSheet = billsSheet;
+
+                    const dealBills = SpreadJsObj.getSelectObject(dealSheet);
+                    if (!dealBills) { return; }
+                    const mainTree = mainSheet.zh_tree;
+                    const mainNode = SpreadJsObj.getSelectObject(mainSheet);
+                    if (!mainNode || !mainTree) { return; }
+
+                    if (mainNode.code && mainNode.code !== '' && !mainTree.isLeafXmj(mainNode)) {
+                        toastr.warning('非最底层项目下,不应添加节点');
+                        return;
+                    }
+                    if (mainNode.settle_status === settleStatus.finish) {
+                        toastr.warning('已结算节点下,不应添加签约清单');
+                        return;
+                    }
+                    const is_bill = dealBills.kind === 4;
+                    const oneBills = {
+                        b_code: is_bill ? dealBills.code : null, code: !is_bill ? dealBills.code : null, name: dealBills.name, unit: dealBills.unit,
+                            unit_price: is_bill ? dealBills.unitPrice : null, quantity: is_bill ? dealBills.quantity : null, total_price: is_bill ? dealBills.totalPrice : null,
+                            sgfh_qty: is_bill ? dealBills.quantity : null, sgfh_tp: is_bill ? dealBills.totalPrice : null,
+                    };
+                    postData(window.location.pathname + '/update', {
+                        postType: 'add-deal',
+                        postData: {
+                            id: mainNode.ledger_id,
+                            type: mainNode.code ? 'child' : 'next',
+                            dealBills: oneBills,
+                        },
+                    }, function (result) {
+                        const refreshData = mainTree.loadPostData(result);
+                        billsTreeSpreadObj.refreshTree(mainSheet, refreshData);
+                        const sel = mainSheet.getSelections()[0];
+                        if (sel && refreshData.create[0]) {
+                            mainSheet.setSelection(mainTree.nodes.indexOf(refreshData.create[0]), sel.col, sel.rowCount, sel.colCount);
+                            SpreadJsObj.reloadRowsBackColor(mainSheet, [sel.row, mainTree.nodes.indexOf(refreshData.create[0])]);
+                        }
+                        billsTreeSpreadObj.refreshOperationValid(mainSheet);
+                        billsSpread.focus();
+                        posSpreadObj.loadCurPosData();
+                    });
+                });
+            }
+            SpreadJsObj.forbiddenSpreadContextMenu(selector, this.spread);
+            const searchObj = {
+                result: [],
+                cur: 0,
+                searchStdNode: function() {
+                    const keyword = $(`#${relaSelect.searchText}`).val();
+                    searchObj.result = keyword ? dskProjectBills2Tree.tenderTree.nodes.filter(x => {
+                        return x.code.indexOf(keyword) >= 0 || x.name.indexOf(keyword) >= 0;
+                    }) : [];
+                    // searchObj.result = [];
+                    // for (const x of pathTree.nodes) {
+                    //     if (x.code.indexOf(keyword) >= 0 || x.b_code.indexOf(keyword) >= 0 || x.name.indexOf(keyword) >= 0) {
+                    //         searchObj.result.push(x);
+                    //     }
+                    // }
+                    $(`#${relaSelect.searchResult}`)[0].innerText = `结果:${searchObj.result.length}`;
+                    searchObj.cur = 0;
+                    if (searchObj.result.length > 0) {
+                        SpreadJsObj.locateTreeNode(searchSheet, dskProjectBills2Tree.tenderTree.getNodeKey(searchObj.result[searchObj.cur]));
+                    }
+                },
+                searchPre: function () {
+                    if (searchObj.result.length === 0) return;
+                    searchObj.cur = searchObj.cur === 0 ? searchObj.result.length - 1 : this.cur - 1;
+                    SpreadJsObj.locateTreeNode(searchSheet, dskProjectBills2Tree.tenderTree.getNodeKey(searchObj.result[searchObj.cur]), true);
+                },
+                searchNext: function () {
+                    if (searchObj.result.length === 0) return;
+                    searchObj.cur = searchObj.cur === searchObj.result.length - 1 ? 0 : searchObj.cur + 1;
+                    SpreadJsObj.locateTreeNode(searchSheet, dskProjectBills2Tree.tenderTree.getNodeKey(searchObj.result[searchObj.cur]), true);
+                },
+                clear: function () {
+                    $(`#${relaSelect.searchText}`).val('');
+                    $(`#${relaSelect.searchResult}`)[0].innerText = `结果:${0}`;
+                    searchObj.result = [];
+                    searchObj.cur = 0;
+                }
+            };
+            $(`#${relaSelect.searchText}`).change(searchObj.searchStdNode);
+            $(`#${relaSelect.searchPre}`).click(function (e) {
+                searchObj.searchPre();
+                e.stopPropagation();
+            });
+            $(`#${relaSelect.searchNext}`).click(function (e) {
+                searchObj.searchNext();
+                e.stopPropagation();
+            });
+            $(`#${relaSelect.searchClose}`).click(function (e) {
+                searchObj.clear();
+                e.stopPropagation();
+            });
+        };
+        loadData (callback) {
+            if (this.loaded) {
+                if (callback) callback();
+                return;
+            }
+            const self = this;
+            if (dskAccountData && dskAccountData.select_project) {
+                self.loaded = true;
+                changeDskProject(dskAccountData.select_project);
+            } else {
+                SpreadJsObj.loadSheetData(self.spread.getActiveSheet(), 'data', []);
+                self.loaded = true;
+            }
+            if (callback) callback();
+        }
+    }
+
+    $('body').on('change', '#change-dsk-project', function () {
+        const pid = $(this).val();
+        changeDskProject(pid);
+    });
+
+    $('body').on('change', '#change-dsk-project-tree', function () {
+        const treeId = $(this).val();
+        const projectInfo = _.find(dskProjects, { pid: dskAccountData.select_project });
+        changeDskProjectTree(projectInfo, treeId);
+    });
+
+    function setDskProjectTreeSelect(trees) {
+        let html = '';
+        for (const tree of trees) {
+            html += `<option value="${tree.ID}" ${tree.ID === dskAccountData.select_tree ? 'selected' : ''}>${tree.name}</option>`;
+        }
+        $('#change-dsk-project-tree').html(html);
+    }
+
+    function changeDskProject(pid) {
+        const projectInfo = _.find(dskProjects, { pid });
+        if (!projectInfo) {
+            dskAccountData.select_project = null;
+            dskAccountData.select_tree = null;
+            SpreadJsObj.loadSheetData(dskBills.spread.getActiveSheet(), SpreadJsObj.DataType.Tree, []);
+            return;
+        }
+        postData('/profile/dsk/api', { type: 'project_tree', tid: window.location.pathname.split('/')[2], compilationId: projectInfo.compilationId, projectId: projectInfo.pid }, function (datas) {
+            const trees = _.filter(datas, x => x.type === 4);
+            dskAccountData.select_project = projectInfo.pid;
+            setDskProjectTreeSelect(trees);
+            changeDskProjectTree(projectInfo, trees.length > 0 ? trees[0].ID : null);
+        });
+    }
+
+    function changeDskProjectTree(projectInfo, treeId) {
+        dskAccountData.select_tree = treeId;
+        if (!treeId) {
+            SpreadJsObj.loadSheetData(dskBills.spread.getActiveSheet(), SpreadJsObj.DataType.Tree, []);
+            return;
+        }
+        postData('/profile/dsk/api', { type: 'project_bills', tid: window.location.pathname.split('/')[2], compilationId: projectInfo.compilationId, treeId }, function (result) {
+            const tree = dskProjectBills2Tree.convert(result);
+            console.log(tree);
+            SpreadJsObj.loadSheetData(dskBills.spread.getActiveSheet(), SpreadJsObj.DataType.Tree, tree);
+        });
+    }
+    const dskBills = new Dsk('#dsk-spread', {
+        cols: [
+            {title: '编号', field: 'code', hAlign: 0, width: 150, formatter: '@', cellType: 'tree'},
+            {title: '名称', field: 'name', hAlign: 0, width: 100, formatter: '@'},
+            {title: '单位', field: 'unit', hAlign: 1, width: 50, formatter: '@'},
+            {title: '单价', field: 'unitPrice', hAlign: 2, width: 80, formatter: '@'},
+            {title: '数量', field: 'quantity', hAlign: 2, width: 80, formatter: '@'},
+        ],
+        emptyRows: 0,
+        headRows: 1,
+        headRowHeight: [32],
+        headColWidth: [30],
+        defaultRowHeight: 21,
+        headerFont: '12px 微软雅黑',
+        font: '12px 微软雅黑',
+        selectedBackColor: '#fffacd',
+        readOnly: true,
+        stdType: 'dsk',
+        libs: dskProjects,
+        select_lib: dskAccountData.select_project ? dskAccountData.select_project : null,
+    });
+
+    const dskProjectBills2Tree = (function () {
+        const treeSetting = {
+            id: 'dpb_id',
+            pid: 'dpb_pid',
+            order: 'seq',
+            level: 'level',
+            rootId: '-1',
+            fullPath: 'full_path',
+        };
+        const tenderTree = createNewPathTree('gather', treeSetting);
+
+        function setChildrenNode(node, dxs, files, tenders) {
+            const dxchildren = _.filter(dxs, { parentID: node.bid });
+            const children = _.filter(files, { parentID: node.bid });
+            const tenderChildren = _.filter(tenders, { parentID: node.bid });
+            const checkChildren = _.orderBy([...dxchildren, ...children, ...tenderChildren], ['seq', 'asc']);
+            for (const t of checkChildren) {
+                const child = {
+                    bid: t.ID,
+                    parentID: t.parentID,
+                    name: t.name,
+                    kind: t.kind,
+                    code: t.code,
+                    quantity: t.quantity,
+                    quantity2: t.quantity2,
+                    unit: t.unit,
+                    unitPrice: t.unitPrice,
+                    totalPrice: t.totalPrice,
+                    formulaCode: t.formulaCode,
+                    flag: t.flag,
+                    remark: t.remark,
+                };
+                tenderTree.addNode(child, node);
+                setChildrenNode(child, dxs, files, tenders);
+            }
+        }
+
+        function convert (projects) {
+            tenderTree.clearDatas();
+
+            // 区分文件夹及项目
+            const topLevel = _.orderBy(_.filter(projects, { parentID: '-1' }), ['seq', 'asc']);
+            const dxs = _.filter(projects, { kind: 1 });
+            const files = _.filter(projects, { kind: 4 });
+            const tenders = _.filter(projects, { kind: 8 });
+
+            for (const t of topLevel) {
+                const node = {
+                    bid: t.ID,
+                    parentID: t.parentID,
+                    name: t.name,
+                    kind: t.kind,
+                    code: t.code,
+                    quantity: t.quantity,
+                    quantity2: t.quantity2,
+                    unit: t.unit,
+                    unitPrice: t.unitPrice,
+                    totalPrice: t.totalPrice,
+                    formulaCode: t.formulaCode,
+                    flag: t.flag,
+                    remark: t.remark,
+                };
+                tenderTree.addNode(node, null);
+                setChildrenNode(node, dxs, files, tenders);
+            }
+            tenderTree.sortTreeNode(true);
+            return tenderTree;
+        }
+        return { tenderTree, convert }
+    })();
+
     $.divResizer({
         select: '#revise-right-spr',
         callback: function () {
@@ -2444,6 +2926,8 @@ $(document).ready(() => {
             if (errorList) errorList.spread.refresh();
             if (checkList) checkList.spread.refresh();
             if (sumLoadMiss) sumLoadMiss.spread.refresh();
+            if (jlzfBills) jlzfBills.spread.refresh();
+            if (dskBills) dskBills.spread.refresh();
         }
     });
     $.subMenu({
@@ -2469,6 +2953,8 @@ $(document).ready(() => {
             if (errorList) errorList.spread.refresh();
             if (checkList) checkList.spread.refresh();
             if (sumLoadMiss) sumLoadMiss.spread.refresh();
+            if (jlzfBills) jlzfBills.spread.refresh();
+            if (dskBills) dskBills.spread.refresh();
         }
     });
 
@@ -2677,6 +3163,20 @@ $(document).ready(() => {
                 checkList.spread.refresh();
             } else if (tab.attr('content') === '#sum-load-miss') {
                 sumLoadMiss.spread.refresh();
+            } else if (tab.attr('content') === '#dsk-list') {
+                const dskActiveTab = $('#dsk-tab a[class*="active"]').attr('href');
+                $(dskActiveTab).addClass('active');
+                if (dskActiveTab === '#jlzf-spread') {
+                    $('#get-dsk-btn').hide();
+                    $('#get-dsk-bills-btn').hide();
+                    jlzfBills.loadData();
+                    jlzfBills.spread.refresh();
+                } else if (dskActiveTab === '#dsk-spread') {
+                    $('#get-dsk-btn').show();
+                    $('#get-dsk-bills-btn').show();
+                    dskBills.loadData();
+                    dskBills.spread.refresh();
+                }
             }
         }
         else {// 收起工具栏
@@ -2688,6 +3188,23 @@ $(document).ready(() => {
         if (posSpread) posSpread.refresh();
     });
 
+    // 新增清单切换tab
+    $('#dsk-tab a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
+        e.preventDefault();
+        const dskActiveTab = $(this).attr('href');
+        if (dskActiveTab === '#jlzf-spread') {
+            $('#get-dsk-btn').hide();
+            $('#get-dsk-bills-btn').hide();
+            jlzfBills.loadData();
+            jlzfBills.spread.refresh();
+        } else if (dskActiveTab === '#dsk-spread') {
+            $('#get-dsk-btn').show();
+            $('#get-dsk-bills-btn').show();
+            dskBills.loadData();
+            dskBills.spread.refresh();
+        }
+    });
+
     // 显示层次
     (function (select, sheet) {
         $(select).click(function () {
@@ -2750,5 +3267,300 @@ $(document).ready(() => {
             });
         });
     });
+    if (isYb) {
+        let compilationList = [];
+        $('#get-dsk-btn').click(function () {
+            // 判断是否已绑定dsk用户
+            postData('/profile/dsk/api', { type: 'hadbind' }, function (result) {
+                console.log(result);
+                if (result === 1) {
+                    $('#error-dsk .modal-body').find('h5').eq(0).show();
+                    $('#error-dsk .modal-body').find('h5').eq(1).hide();
+                    $('#error-dsk').modal('show');
+                    $('#error-dsk .modal-footer').find('a').eq(0).text('绑定手机');
+                } else if (result === 2) {
+                    $('#error-dsk .modal-body').find('h5').eq(1).show();
+                    $('#error-dsk .modal-body').find('h5').eq(0).hide();
+                    $('#error-dsk').modal('show');
+                    $('#error-dsk .modal-footer').find('a').eq(0).text('注册账号');
+                } else {
+                    postData('/profile/dsk/api', { type: 'compilation', getProject: 1, compilationId: getLocalCache(dskCompilation) }, function (result) {
+                        let html = '';
+                        for (const data of result.compilation) {
+                            html += `<option value="${data.ID}" ${result.select_compilation === data.ID ? 'selected' : ''}>${data.name}</option>`;
+                        }
+                        compilationList = result.compilation;
+                        console.log(compilationList);
+                        $('#dsk-compilation-list').html(html);
+                        $('#add-dsk').modal('show');
+                        setLocalCache(dskCompilation, result.select_compilation);
+                        makeDskProjectSjsHtml(result.project);
+                    });
+                }
+            })
+        });
+        $('#get-dsk-bills-btn').click(function () {
+            const projectInfo = _.find(dskProjects, { pid: dskAccountData.select_project });
+            changeDskProjectTree(projectInfo, dskAccountData.select_tree);
+        });
+
+        $('body').on('change', '#dsk-compilation-list', function () {
+            const compilationId = $(this).val();
+            setLocalCache(dskCompilation, compilationId);
+            dskProjectSpreadObj.refreshSourceTree();
+        });
+
+        $('.hide-dsk-modal').click(function () {
+            $('#error-dsk').modal('hide');
+        });
+        let dp = false;
+        let gsObj = {
+            setting: null,
+
+            gsSheet: null,
+            grSheet: null,
+
+            tenderSourceTree: null,
+            grArray: dskProjects ? dskProjects : [],
+            orgSelect: null,
+        };
+        function makeDskProjectSjsHtml(datas) {
+            if (!dp) {
+                initDskProjectTree();
+                dp = true;
+            }
+            gsObj.tenderSourceTree = dskProject2Tree.convert(datas);
+            SpreadJsObj.loadSheetData(gsObj.gsSheet, SpreadJsObj.DataType.Tree, gsObj.tenderSourceTree);
+            SpreadJsObj.loadSheetData(gsObj.grSheet, SpreadJsObj.DataType.Data, gsObj.grArray);
+            if (datas.length === 0) {
+                $('#show-project-0').show();
+            } else {
+                $('#show-project-0').hide();
+            }
+        }
+        const dskProjectSpreadObj = {
+            _addTender: function (tender) {
+                const gr = gsObj.grArray.find(function (x) {
+                    return x.pid === tender.pid;
+                });
+                const c_id = getLocalCache(dskCompilation);
+                const t = {pid: tender.pid, name: tender.name, compilationId: c_id, compilationName: _.find(compilationList, { ID: c_id }).name };
+                if (!gr) gsObj.grArray.push(t);
+                return t;
+            },
+            _removeTender: function (tender) {
+                const gri = gsObj.grArray.findIndex(function (x, i, arr) {
+                    return x.pid === tender.pid;
+                });
+                if (gri >= 0) gsObj.grArray.splice(gri, 1);
+            },
+            reloadResultData: function () {
+                SpreadJsObj.reLoadSheetData(gsObj.grSheet);
+            },
+            refreshSourceTree: function () {
+                const c_id = getLocalCache(dskCompilation);
+                if (c_id) {
+                    postData('/profile/dsk/api', { type: 'project', compilationId: c_id }, function (result) {
+                        makeDskProjectSjsHtml(result);
+                    });
+                }
+            },
+            gsButtonClicked: function (e, info) {
+                if (!info.sheet.zh_setting) return;
+
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (col.field !== 'selected') return;
+
+                const node = SpreadJsObj.getSelectObject(info.sheet);
+                node.selected = !node.selected;
+                if (node.children && node.children.length > 0) {
+                    const posterity = gsObj.tenderSourceTree.getPosterity(node);
+                    for (const p of posterity) {
+                        p.selected = node.selected;
+                        if ((!p.children || p.children.length === 0) && p.type === 2){
+                            if (p.selected) {
+                                dskProjectSpreadObj._addTender(p);
+                            } else {
+                                dskProjectSpreadObj._removeTender(p);
+                            }
+                        }
+                    }
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row, posterity.length + 1);
+                } else if (node.type === 2) {
+                    if (node.selected) {
+                        dskProjectSpreadObj._addTender(node);
+                    } else {
+                        dskProjectSpreadObj._removeTender(node);
+                    }
+                    SpreadJsObj.reLoadRowData(info.sheet, info.row, 1);
+                }
+                dskProjectSpreadObj.reloadResultData();
+            },
+            deleteGr: function () {
+                if (gsObj.grArray.length === 0) return;
+                if (gsObj.grSheet.getSelections().length === 0) return;
+                const selections = gsObj.grSheet.getSelections();
+                const sel = selections ? selections[0] : gsObj.grSheet.getSelections()[0];
+                const row = sel && sel.row !== undefined ? sel.row : -1;
+                if (row === -1 || sel.row + sel.rowCount > gsObj.grArray.length) {
+                    return false;
+                }
+                const delList = [];
+                for (let r = 0; r < sel.rowCount; r++) {
+                    const select = gsObj.grArray[row + r];
+                    delList.push(select);
+                }
+                _.pullAll(gsObj.grArray, delList);
+                dskProjectSpreadObj.reloadResultData();
+                dskProjectSpreadObj.refreshSourceTree();
+            },
+        };
+
+        const initDskProjectTree = function () {
+            const gsSpread = SpreadJsObj.createNewSpread($('#dsk-project-source-spread')[0]);
+            gsObj.gsSheet = gsSpread.getActiveSheet();
+            const gsSpreadSetting = {
+                cols: [
+                    {title: '选择', field: 'selected', hAlign: 1, width: 40, formatter: '@', cellType: 'checkbox', readOnly: true,},
+                    {title: '名称', field: 'name', hAlign: 0, width: 350, formatter: '@', readOnly: true, folderCell: true, cellType: 'tree'},
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                headColWidth: [0],
+                selectedBackColor: '#fffacd',
+            };
+            SpreadJsObj.initSheet(gsObj.gsSheet, gsSpreadSetting);
+            gsSpread.bind(spreadNS.Events.ButtonClicked, dskProjectSpreadObj.gsButtonClicked);
+
+            const grSpread = SpreadJsObj.createNewSpread($('#dsk-project-result-spread')[0]);
+            gsObj.grSheet = grSpread.getActiveSheet();
+            const grSpreadSetting = {
+                cols: [
+                    {title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 250, formatter: '@', readOnly: true},
+                    {title: '所属编办', colSpan: '1', rowSpan: '1', field: 'compilationName', hAlign: 0, width: 150, formatter: '@', readOnly: true},
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+                headColWidth: []
+            };
+            SpreadJsObj.initSheet(gsObj.grSheet, grSpreadSetting);
+
+            // 右键删除已选项目
+            const gsContextMenuOptions = {
+                selector: '#dsk-project-result-spread',
+                build: function ($trigger, e) {
+                    const target = SpreadJsObj.safeRightClickSelection($trigger, e, grSpread);
+                    return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
+                },
+                items: {
+                    delete: {
+                        name: '删除',
+                        icon: 'fa-remove',
+                        callback: function (key, opt) {
+                            dskProjectSpreadObj.deleteGr();
+                        },
+                        disabled: function (key, opt) {
+                            const selection = gsObj.grSheet.getSelections();
+                            const sel = selection ? selection[0] : gsObj.grSheet.getSelections()[0];
+                            const row = sel && sel.row !== undefined ? sel.row : -1;
+                            if (row === -1 || sel.row + sel.rowCount > gsObj.grArray.length) {
+                                return true;
+                            }
+                            return false;
+                        }
+                    },
+                }
+            };
+            $.contextMenu(gsContextMenuOptions);
+
+            $('#add-dsk').bind('shown.bs.modal', function () {
+                if (gsSpread) gsSpread.refresh();
+                if (grSpread) grSpread.refresh();
+            });
+        };
+
+        const dskProject2Tree = (function () {
+            const treeSetting = {
+                id: 'tmt_id',
+                pid: 'tmt_pid',
+                order: 'seq',
+                level: 'level',
+                rootId: '-1',
+                fullPath: 'full_path',
+            };
+            const tenderTree = createNewPathTree('gather', treeSetting);
+
+            function setChildrenNode(node, files, tenders) {
+                const children = _.filter(files, { parentID: node.pid });
+                const tenderChildren = _.filter(tenders, { parentID: node.pid });
+                const checkChildren = _.orderBy([...children, ...tenderChildren], ['seq', 'asc']);
+                for (const c of checkChildren) {
+                    const child = {
+                        pid: c.ID,
+                        parentID: c.parentID,
+                        name: c.name,
+                        type: c.type,
+                        selected: _.findIndex(gsObj.grArray, { pid: c.ID }) >= 0
+                    };
+                    tenderTree.addNode(child, node);
+                    if (c.type === 1) setChildrenNode(child, files, tenders);
+                }
+            }
+
+            function convert (projects) {
+                tenderTree.clearDatas();
+
+                // 区分文件夹及项目
+                const topLevel = _.orderBy(_.filter(projects, { parentID: '-1' }), ['seq', 'asc']);
+                const files = _.filter(projects, { type: 1 });
+                const tenders = _.filter(projects, { type: 2 });
+
+                for (const t of topLevel) {
+                    const node = {
+                        pid: t.ID,
+                        parentID: t.parentID,
+                        name: t.name,
+                        type: t.type,
+                        selected: t.type === 2 ? _.findIndex(gsObj.grArray, { pid: t.ID }) >= 0 : false,
+                    };
+                    tenderTree.addNode(node, null);
+                    if (t.type === 1) setChildrenNode(node, files, tenders);
+                }
+                tenderTree.sortTreeNode(true);
+                return tenderTree;
+            }
+            return { tenderTree, convert }
+        })();
+
+        $('#set-dsk-project').click(function () {
+            postData('/profile/dsk/api', { type: 'save_projects', tid: window.location.pathname.split('/')[2], project_list: gsObj.grArray }, function (result) {
+                // dskAccountData.select_project = result;
+                dskProjects = gsObj.grArray;
+                if (dskAccountData.select_project !== result) {
+                    dskAccountData.select_project = result;
+                    changeDskProject(result);
+                    dskBills.spread.refresh();
+                }
+                setDskProjectSelect(gsObj.grArray);
+                $('#add-dsk').modal('hide');
+            });
+        });
+
+        function setDskProjectSelect(projects) {
+            let html = '';
+            for (const project of projects) {
+                html += `<option value="${project.pid}" ${project.pid === dskAccountData.select_project ? 'selected' : ''}>${project.name}</option>`;
+            }
+            $('#change-dsk-project').html(html);
+        }
+    }
 });
 

+ 2 - 0
app/public/js/profile.js

@@ -221,6 +221,7 @@ $(document).ready(function() {
  */
 function codeSuccess(btn) {
     let counter = 60;
+    btn.attr('disabled', true);
     btn.addClass('disabled').text('重新获取 ' + counter + 'S');
     btn.parent().siblings('input').removeAttr('readonly').attr('placeholder', '输入短信中的6位验证码');
     const bindBtn = $("#bind-btn");
@@ -231,6 +232,7 @@ function codeSuccess(btn) {
         // 倒数结束后
         if (countString === '') {
             clearInterval(countDown);
+            btn.removeAttr('disabled');
             btn.removeClass('disabled');
         }
         const text = '重新获取' + countString;

+ 1 - 0
app/router.js

@@ -677,6 +677,7 @@ module.exports = app => {
     app.post('/profile/cert/upload', sessionAuth, 'profileController.certUpload');
     app.get('/profile/sms', sessionAuth, 'profileController.sms');
     app.post('/profile/sms/type', sessionAuth, 'profileController.smsType');
+    app.post('/profile/dsk/api', sessionAuth, 'profileController.dskApi');
     app.get('/profile/sign', sessionAuth, 'profileController.sign');
     app.get('/profile/sign/netca', sessionAuth, 'profileController.netcasign');
     app.post('/profile/sign/save', sessionAuth, 'profileController.signSave');

+ 1 - 1
app/schedule/shenpi_again.js

@@ -50,7 +50,7 @@ class ShenpiAgain extends Subscription {
             const notice_setting = pinfo.notice_setting ? JSON.parse(pinfo.notice_setting) : ctx.helper._.cloneDeep(projectSettingConst.noticeSetting);
             const interval = notice_setting.mode === 'fixed' ? notice_setting.fixed : (i.times === 0 ? notice_setting.activity.first : (i.times === 1 ? notice_setting.activity.second : notice_setting.activity.later));
             const send_time = ctx.helper.calculateNextSendTime(i.last_time, interval, notice_setting.shield_times.start, notice_setting.shield_times.end);
-            console.log(i.id, new Date(), send_time);
+            // console.log(i.id, new Date(), send_time);
             if (new Date() > send_time) {
                 // 判断是否已经sp_type的sp_id已经完成审批或者是否存在,如果已经审批或不存在则需要删除
                 const spInfo = await ctx.service.noticeAgain.getSpResult(i.table_name, i.sp_id);

+ 24 - 0
app/service/project_account.js

@@ -12,6 +12,7 @@
 const crypto = require('crypto');
 const SSO = require('../lib/sso');
 const SMS = require('../lib/sms');
+const DSK = require('../lib/dsk');
 const SmsAliConst = require('../const/sms_alitemplate');
 const loginWay = require('../const/setting').loginWay;
 const smsTypeConst = require('../const/sms_type').type;
@@ -1010,6 +1011,29 @@ module.exports = app => {
             const param = [this.tableName, pid];
             return await this.db.query(sql, param);
         }
+
+        async isDskExist(pid, mobile) {
+            const sql = 'select count(*) as count from ?? where project_id= ? and JSON_CONTAINS(dsk_account, json_object("mobile", ?))';
+            const sqlParam = [this.tableName, pid, mobile];
+            const result = await this.db.queryOne(sql, sqlParam);
+            return result.count > 0;
+        }
+
+        async bindDsk(data, mobile, id) {
+            const dsk_account = {
+                ID: data.ID,
+                mobile,
+            };
+            return await this.db.update(this.tableName, { id, dsk_account: JSON.stringify(dsk_account) });
+        }
+
+        async unbindDsk(id) {
+            return await this.db.update(this.tableName, { id, dsk_account: null });
+        }
+
+        async saveDskProjects(id, projects) {
+            return await this.db.update(this.tableName, { id, dsk_projects: JSON.stringify(projects) });
+        }
     }
 
     return ProjectAccount;

+ 24 - 0
app/view/change/revise.ejs

@@ -112,6 +112,24 @@
                     </div>
                     <div id="sum-load-miss" class="tab-pane tab-select-show">
                     </div>
+                    <div id="dsk-list" class="tab-pane">
+                        <nav class="nav nav-tabs" id="dsk-tab" role="tablist">
+                            <a class="nav-item nav-link active" href="#jlzf-spread" data-toggle="tab" role="tab">云计量</a>
+                            <a class="nav-item nav-link" href="#dsk-spread" data-toggle="tab" role="tab">大司空</a>
+                            <div class="ml-auto">
+                                <% if (isYb) { %>
+                                    <a href="javascript:void(0);" id="get-dsk-bills-btn" class="btn btn-sm btn-light text-primary pull-right mt-1" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="重新获取大司空造价书"><i class="fa fa-refresh" aria-hidden="true"></i></a>
+                                    <a href="javascript:void(0);" id="get-dsk-btn" class="btn btn-sm btn-primary pull-right mt-1 mr-1" style="display: none;">获取计价单价</a>
+                                <% } %>
+                            </div>
+                        </nav>
+                        <div class="tab-content">
+                            <div id="jlzf-spread" class="tab-pane active">
+                            </div>
+                            <div id="dsk-spread" class="tab-pane">
+                            </div>
+                        </div>
+                    </div>
                 </div>
             </div>
         </div>
@@ -137,6 +155,9 @@
                 <li class="nav-item">
                     <a class="nav-link" content="#check-list" id="check-list-tab" href="javascript: void(0);" style="display: none;">数据检查</a>
                 </li>
+                <li class="nav-item">
+                    <a class="nav-link" content="#dsk-list" href="javascript: void(0);">新增清单</a>
+                </li>
             </ul>
         </div>
     </div>
@@ -145,6 +166,7 @@
     const stdChapters = JSON.parse(unescape('<%- escape(JSON.stringify(stdChapters)) %>'));
     const stdBills = JSON.parse(unescape('<%- escape(JSON.stringify(stdBills)) %>'));
     const readOnly = <%- readOnly %>;
+    const isYb = <%- isYb %>;
     const isTz = <%- ctx.tender.data.measure_type === measureType.tz.value %>;
     const billsSpreadSetting = JSON.parse('<%- JSON.stringify(ledgerSpread) %>');
     const posSpreadSetting = JSON.parse('<%- JSON.stringify(posSpread) %>');
@@ -152,4 +174,6 @@
     const decimal = JSON.parse('<%- JSON.stringify(ctx.tender.info.decimal) %>');
     const nodeType = JSON.parse('<%- JSON.stringify(nodeType) %>');
     const settleStatus = JSON.parse('<%- JSON.stringify(settleStatus) %>');
+    const dskAccountData = JSON.parse(unescape('<%- escape(JSON.stringify(dskAccountData)) %>'));
+    let dskProjects = JSON.parse(unescape('<%- escape(JSON.stringify(dskProjects)) %>'));
 </script>

+ 53 - 0
app/view/change/revise_modal.ejs

@@ -72,6 +72,59 @@
     </div>
     <script type="text/javascript">$('#unedit2').modal('show');</script>
 <% } %>
+<% if (isYb) { %>
+<div class="modal fade" id="error-dsk" data-backdrop="static">
+    <div class="modal-dialog " role="document" >
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">授权账号</h5>
+            </div>
+            <div class="modal-body">
+                <h5>当前账号未绑定手机,请先绑定手机。</h5>
+                <h5 class="text-danger">当前账号未绑定大司空账号,可先进行<a href="/profile/sms" target="_blank" class="text-primary hide-dsk-modal">注册账号</a>或者<a href="/profile/sms" target="_blank" class="text-primary hide-dsk-modal">切换账号</a>绑定。</h5>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <a href="/profile/sms" target="_blank" class="btn btn-sm btn-primary hide-dsk-modal">绑定手机</a>
+            </div>
+        </div>
+    </div>
+</div>
+<div class="modal fade" id="add-dsk" data-backdrop="static">
+    <div class="modal-dialog modal-lgx" role="document" >
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">计价项目</h5>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-6">
+                        <div class="row mb-2">
+                            <div class="col-8">
+                                <select class="form-control form-control-sm" id="dsk-compilation-list"></select>
+                            </div>
+                            <div class="col-4 text-danger" style="line-height: 28px;" id="show-project-0">未查询到项目</div>
+                        </div>
+                        <div class="modal-height-500" id="dsk-project-source-spread">
+                        </div>
+                    </div>
+                    <div class="col-6">
+                        <div class="row">
+                            <h5 style="line-height: 28px;">已选项目 </h5>
+                        </div>
+                        <div class="modal-height-500" id="dsk-project-result-spread">
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-sm btn-primary" id="set-dsk-project">确定</button>
+            </div>
+        </div>
+    </div>
+</div>
+<% } %>
 <% include ../shares/delete_hint_modal.ejs %>
 <% include ../shares/check_data_modal.ejs %>
 <% include ../shares/check_modal2.ejs %>

+ 18 - 0
app/view/profile/modal.ejs

@@ -14,3 +14,21 @@
         </div>
     </div>
 </div>
+
+<div class="modal fade" id="remove-dsk-account" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">解绑大司空账号</h5>
+            </div>
+            <div class="modal-body">
+                <h5>确认解绑大司空账号?</h5>
+<!--                <h5>删除后,数据无法恢复,请谨慎操作。</h5>-->
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">取消</button>
+                <button type="submit" class="btn btn-danger btn-sm" id="del-dsk-btn">确定删除</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 222 - 59
app/view/profile/sms.ejs

@@ -8,73 +8,126 @@
     <div class="content-wrap">
         <div class="c-body">
             <div class="sjs-height-0">
-            <div class="row m-0">
-                <div class="col-5 my-3">
-                    <% if (accountData.auth_mobile !== '') { %>
-                    <!--已绑定手机-->
-                    <div class="form-group">
-                        <label>已认证手机</label>
-                        <div class="input-group mb-3">
-                            <input class="form-control form-control-sm" readonly="" value="<%= accountData.auth_mobile %>">
-                            <div class="input-group-append">
-                                <button class="btn btn-outline-secondary btn-sm" id="change-mobile">修改手机</button>
+                <div class="row m-0 mt-3">
+                    <div class="col-6">
+                        <div class="card mb-3 ">
+                            <div class="card-body pt-3">
+                                <h6 class="card-title">认证手机</h6>
+                                <% if (accountData.auth_mobile !== '') { %>
+                                    <!--已绑定手机-->
+                                    <div class="form-group">
+                                        <label>已认证手机</label>
+                                        <div class="input-group mb-3">
+                                            <input class="form-control form-control-sm" readonly="" value="<%= accountData.auth_mobile %>">
+                                            <div class="input-group-append">
+                                                <button class="btn btn-outline-secondary btn-sm" id="change-mobile">修改手机</button>
+                                            </div>
+                                        </div>
+                                    </div>
+                                <% } %>
+                                <!--绑定手机-->
+                                <% if (accountData.auth_mobile === '') { %><div class="alert alert-warning">认证手机用户找回密码操作,请优先设置。</div><% } %>
+                                <form id="mobile-form" <% if (accountData.auth_mobile !== '') { %>style="display: none" <% } %>>
+                                    <div class="form-group">
+                                        <label>认证手机</label>
+                                        <div class="input-group mb-3">
+                                            <input class="form-control form-control-sm" placeholder="输入11位手机号码" value="" name="auth_mobile" maxlength="11"/>
+                                            <div class="input-group-append">
+                                                <button class="btn btn-outline-secondary btn-sm" type="button" id="get-code">获取验证码</button>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="form-group">
+                                        <div class="input-group mb-3">
+                                            <input class="form-control form-control-sm" type="text" readonly="readonly" name="code" placeholder="输入短信中的6位验证码" />
+                                            <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
+                                        </div>
+                                    </div>
+                                    <button type="button" class="btn btn-secondary btn-sm disabled" id="bind-btn">确认绑定</button>
+                                </form>
+                                <% if (accountData.auth_mobile !== '' && false) { %>
+                                    <!--短信通知开关(已有认证手机后显示)-->
+                                    <div class="mt-5">
+                                        <h4>通知类型</h4>
+                                        <p class="text-muted">勾选您需要接收的短信类型。</p>
+                                        <form id="sms-form" method="post" action="/profile/sms/type?csrf=<%- ctx.csrf %>">
+                                            <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
+                                            <% const user_smsType = accountData.sms_type !== '' ? JSON.parse(accountData.sms_type) : null; %>
+                                            <% for (const s in smsType) { %>
+                                                <% if (smsType[s].sms) { %>
+                                                    <div class="form-group row">
+                                                        <label class="col-auto col-form-label"><%= smsType[s].name %>
+                                                            <!--<a href="#sms-view" data-toggle="modal" data-target="#sms-view" class="ml-2"><i class="fa fa-info-circle"></i></a>-->
+                                                        </label>
+                                                        <div class="col-5">
+                                                            <% for (const c of smsType[s].children) { %>
+                                                                <div class="form-check ">
+                                                                    <input class="form-check-input" id="<%= s %>_<%- c.value %>" type="checkbox" name="<%= s %>[]" value="<%= c.value %>" <% if (user_smsType !== null && user_smsType[s] !== undefined && user_smsType[s].indexOf(c.value.toString()) !== -1) { %>checked<% } %>>
+                                                                    <label class="form-check-label" for="<%= s %>_<%- c.value %>"><%= c.title %></label>
+                                                                </div>
+                                                            <% } %>
+                                                        </div>
+                                                    </div>
+                                                <% } %>
+                                            <% } %>
+                                            <input name="type" value="1" type="hidden">
+                                            <button type="submit" class="btn btn-primary btn-sm">确认修改</button>
+                                        </form>
+                                    </div>
+                                <% } %>
                             </div>
                         </div>
-                    </div>
-                    <% } %>
-                    <!--绑定手机-->
-                    <% if (accountData.auth_mobile === '') { %><div class="alert alert-warning">认证手机用户找回密码操作,请优先设置。</div><% } %>
-                    <form id="mobile-form" <% if (accountData.auth_mobile !== '') { %>style="display: none" <% } %>>
-                        <div class="form-group">
-                            <label>认证手机</label>
-                            <div class="input-group mb-3">
-                                <input class="form-control form-control-sm" placeholder="输入11位手机号码" value="" name="auth_mobile" maxlength="11"/>
-                                <div class="input-group-append">
-                                    <button class="btn btn-outline-secondary btn-sm" type="button" id="get-code">获取验证码</button>
+                        <div class="card mb-3 ">
+                            <div class="card-body pt-3">
+                                <h6 class="card-title">大司空账号</h6>
+                                <div class="mb-3">
+                                    <% if (!accountData.auth_mobile) { %>
+                                        <div class="mb-3"><label for="">请先认证手机,再绑定大司空账号。</label></div>
+                                    <% } else { %>
+                                        <div <% if (accountData.dsk_account) { %>style="display: none"<% } %>>
+                                            <div class="form-group">
+                                                <div class="input-group mb-3">
+                                                    <input class="form-control form-control-sm" id="dak-mobile" readonly="" value="<%= accountData.auth_mobile %>">
+                                                    <div class="input-group-append">
+                                                        <button class="btn btn-outline-secondary btn-sm" id="change-dsk-mobile">更改账号</button>
+                                                        <button class="btn btn-outline-secondary btn-sm" style="display: none" id="reset-dsk-mobile">重置</button>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="form-group mb-1">
+                                                <div>
+                                                    <div class="form-check form-check-inline">
+                                                        <input class="form-check-input" type="radio" id="radio1" value="password" name="dskradio" checked>
+                                                        <label class="form-check-label" for="radio1" name="radio33">密码</label>
+                                                    </div>
+                                                    <div class="form-check form-check-inline">
+                                                        <input class="form-check-input" type="radio" id="radio2" value="code" name="dskradio">
+                                                        <label class="form-check-label" for="radio2">手机验证码</label>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div class="form-group mb-3" id="dsk-pwd-div">
+                                                <input class="form-control form-control-sm" name="dsk_pwd" value="" placeholder="请输入密码">
+                                            </div>
+                                            <div class="input-group mb-3" id="dsk-code-div" style="display: none">
+                                                <input class="form-control form-control-sm" placeholder="输入短信中的6位验证码" value="" name="dsk_code" maxlength="6"/>
+                                                <div class="input-group-append">
+                                                    <button class="btn btn-outline-secondary btn-sm" type="button" id="get-dsk-code">获取验证码</button>
+                                                </div>
+                                            </div>
+                                            <button class="btn btn-sm btn-primary" id="dsk-bind-btn">确认绑定</button>
+                                        </div>
+                                        <div class="form-group show-dsk-account" <% if (!accountData.dsk_account) { %>style="display: none"<% } %>>
+                                            <label><%- accountData.dsk_account ? accountData.dsk_account.mobile : '' %></label>
+                                            <a href="#remove-dsk-account" class="btn btn-sm btn-outline-primary" data-toggle="modal" data-target="#remove-dsk-account">解绑</a>
+                                        </div>
                                 </div>
+                                <% } %>
                             </div>
                         </div>
-                        <div class="form-group">
-                            <div class="input-group mb-3">
-                                <input class="form-control form-control-sm" type="text" readonly="readonly" name="code" placeholder="输入短信中的6位验证码" />
-                                <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
-                            </div>
-                        </div>
-                        <button type="button" class="btn btn-secondary btn-sm disabled" id="bind-btn">确认绑定</button>
-                    </form>
-                    <% if (accountData.auth_mobile !== '' && false) { %>
-                    <!--短信通知开关(已有认证手机后显示)-->
-                    <div class="mt-5">
-                        <h4>通知类型</h4>
-                        <p class="text-muted">勾选您需要接收的短信类型。</p>
-                        <form id="sms-form" method="post" action="/profile/sms/type?csrf=<%- ctx.csrf %>">
-                            <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>">
-                            <% const user_smsType = accountData.sms_type !== '' ? JSON.parse(accountData.sms_type) : null; %>
-                            <% for (const s in smsType) { %>
-                            <% if (smsType[s].sms) { %>
-                            <div class="form-group row">
-                                <label class="col-auto col-form-label"><%= smsType[s].name %>
-                                    <!--<a href="#sms-view" data-toggle="modal" data-target="#sms-view" class="ml-2"><i class="fa fa-info-circle"></i></a>-->
-                                </label>
-                                <div class="col-5">
-                                    <% for (const c of smsType[s].children) { %>
-                                    <div class="form-check ">
-                                        <input class="form-check-input" id="<%= s %>_<%- c.value %>" type="checkbox" name="<%= s %>[]" value="<%= c.value %>" <% if (user_smsType !== null && user_smsType[s] !== undefined && user_smsType[s].indexOf(c.value.toString()) !== -1) { %>checked<% } %>>
-                                        <label class="form-check-label" for="<%= s %>_<%- c.value %>"><%= c.title %></label>
-                                    </div>
-                                    <% } %>
-                                </div>
-                            </div>
-                            <% } %>
-                            <% } %>
-                            <input name="type" value="1" type="hidden">
-                            <button type="submit" class="btn btn-primary btn-sm">确认修改</button>
-                        </form>
                     </div>
-                    <% } %>
                 </div>
             </div>
-            </div>
         </div>
     </div>
 </div>
@@ -82,3 +135,113 @@
     const csrf = '<%= ctx.csrf %>';
 </script>
 <script type="text/javascript" src="/public/js/profile.js"></script>
+<script>
+    $(function () {
+        // 更改账号
+        $('#change-dsk-mobile').click(function () {
+            $('#dak-mobile').attr('readonly', false);
+            $('#change-dsk-mobile').hide();
+            $('#reset-dsk-mobile').show();
+        });
+        // 重置账号
+        $('#reset-dsk-mobile').click(function () {
+            $('#dak-mobile').val('<%= accountData.auth_mobile %>');
+        });
+        // 切换密码/验证码
+        $('input[name="dskradio"]').on('change', function () {
+            if ($(this).val() === 'password') {
+                $('#dsk-pwd-div').show();
+                $('#dsk-code-div').hide();
+            } else {
+                $('#dsk-pwd-div').hide();
+                $('#dsk-code-div').show();
+            }
+        });
+        // 获取验证码
+        $("#get-dsk-code").click(function() {
+            // if (isPosting2) {
+            //     return false;
+            // }
+            const mobile = $('#dak-mobile').val();
+            if (!mobile) {
+                toastr.error('请输入手机号');
+                return false;
+            }
+            // 手机号验证
+            if (!/^1[3456789]\d{9}$/.test(mobile)) {
+                toastr.error('请输入正确的手机号');
+                return false;
+            }
+            const btn = $(this);
+
+            postData('/profile/dsk/api', { type: 'sms', mobile: mobile }, function (response) {
+                codeSuccess(btn);
+            });
+
+            // $.ajax({
+            //     url: '/profile/dsk/api?_csrf_j=' + csrf,
+            //     type: 'post',
+            //     data: { type: 'code', mobile: mobile },
+            //     dataTye: 'json',
+            //     error: function() {
+            //         isPosting2 = false;
+            //     },
+            //     beforeSend: function() {
+            //         isPosting2 = true;
+            //     },
+            //     success: function(response) {
+            //         isPosting2 = false;
+            //         if (response.err === 0) {
+            //             codeSuccess(btn);
+            //         } else {
+            //             toastr.error(response.msg);
+            //         }
+            //     }
+            // });
+        });
+
+        $('#dsk-bind-btn').click(function () {
+            const mobile = $('#dak-mobile').val();
+            const pwd = $('#dsk-pwd-div input').val();
+            const code = $('#dsk-code-div input').val();
+            const type = $('input[name="dskradio"]:checked').val();
+            if (!mobile) {
+                toastr.error('请输入手机号');
+                return false;
+            }
+            if (type !== 'password' && type !== 'code') {
+                toastr.error('请选择验证方式');
+                return false;
+            }
+            if (type === 'password' && !pwd) {
+                toastr.error('请输入密码');
+                return false;
+            }
+            if (type === 'code' && !code) {
+                toastr.error('请输入验证码');
+                return false;
+            }
+            const data = {
+                type: 'bind',
+                mobile,
+                method: type === 'password' ? 1 : 2,
+                value: type === 'password' ? pwd : code,
+            }
+            postData('/profile/dsk/api', data, function (response) {
+                toastr.success('绑定大司空账号成功');
+                setTimeout(function () {
+                    location.reload();
+                }, 1000);
+            });
+        });
+
+        $('#del-dsk-btn').click(function () {
+            postData('/profile/dsk/api', { type: 'unbind' }, function (response) {
+                toastr.success('解绑大司空账号成功');
+                setTimeout(function () {
+                    location.reload();
+                }, 1000);
+            });
+        })
+    });
+</script>

+ 4 - 0
sql/update.sql

@@ -50,3 +50,7 @@ ALTER TABLE `zh_change_plan`
 ADD COLUMN `final_auditor_str` varchar(50) NOT NULL DEFAULT '' COMMENT '终审人相关(cache)' AFTER `sp_group`;
 ALTER TABLE `zh_change_project`
 ADD COLUMN `final_auditor_str` varchar(50) NOT NULL DEFAULT '' COMMENT '终审人相关(cache)' AFTER `sp_group`;
+
+ALTER TABLE `zh_project_account`
+ADD COLUMN `dsk_account` json NULL COMMENT '大司空账号信息' AFTER `notice_again`,
+ADD COLUMN `dsk_projects` json NULL COMMENT '大司空项目信息' AFTER `dsk_account`;