Explorar el Código

概算控制,权限相关

MaiXinRong hace 3 años
padre
commit
feac827aa9

+ 1 - 0
app/base/base_controller.js

@@ -68,6 +68,7 @@ class BaseController extends Controller {
                 message: postError,
                 message: postError,
             };
             };
         }
         }
+        this.ctx.menuList.budget.display = this.ctx.session.sessionProject.showBudget;
 
 
         try {
         try {
             data.min = this.app.config.min;
             data.min = this.app.config.min;

+ 35 - 4
app/controller/budget_controller.js

@@ -14,6 +14,7 @@ const stdDataAddType = {
 };
 };
 const auditConst = require('../const/audit');
 const auditConst = require('../const/audit');
 const LzString = require('lz-string');
 const LzString = require('lz-string');
+const accountGroup = require('../const/account_group').group;
 module.exports = app => {
 module.exports = app => {
     class BudgetController extends app.BaseController {
     class BudgetController extends app.BaseController {
 
 
@@ -29,12 +30,19 @@ module.exports = app => {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.list),
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.budget.list),
                     auditConst,
                     auditConst,
                 };
                 };
-                renderData.budgetList = await ctx.service.budget.getAllDataByCondition({
-                    where: { pid: ctx.session.sessionProject.id },
-                    orders: [['name', 'asc']],
-                });
+                renderData.budgetList = await ctx.service.budget.getBudget(ctx.session.sessionUser.is_admin)
                 renderData.budgetStd = await ctx.service.budgetStd.getDataByProjectId(ctx.session.sessionProject.id);
                 renderData.budgetStd = await ctx.service.budgetStd.getDataByProjectId(ctx.session.sessionProject.id);
                 renderData.tenderList = await ctx.service.tender.getList4Select('stage');
                 renderData.tenderList = await ctx.service.tender.getList4Select('stage');
+                const accountList = await ctx.service.projectAccount.getAllDataByCondition({
+                    where: { project_id: ctx.session.sessionProject.id, enable: 1 },
+                    columns: ['id', 'name', 'company', 'role', 'enable', 'is_admin', 'account_group', 'mobile'],
+                });
+                renderData.accountList = accountList;
+                renderData.accountGroup = accountGroup.map((item, idx) => {
+                    const groupList = accountList.filter(item => item.account_group === idx);
+                    return { groupName: item, groupList };
+                });
+                renderData.permissionConst = ctx.service.budgetPermission.PermissionConst;
                 await this.layout('budget/list.ejs', renderData, 'budget/list_modal.ejs');
                 await this.layout('budget/list.ejs', renderData, 'budget/list_modal.ejs');
             } catch (err) {
             } catch (err) {
                 ctx.log(err);
                 ctx.log(err);
@@ -84,6 +92,29 @@ module.exports = app => {
             }
             }
         }
         }
 
 
+        async member(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                const member = await ctx.service.budgetPermission.getBudgetPermission(data.id);
+                ctx.body = { err: 0, msg: '', data: member };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '查询项目成员失败');
+            }
+        }
+
+        async memberSave(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.id) throw '参数有误';
+                await ctx.service.budgetPermission.saveBudgetPermission(data.id, data.member);
+                ctx.body = { err: 0, msg: '', data: '' };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '保存数据失败');
+            }
+        }
+
         async compare(ctx) {
         async compare(ctx) {
             try {
             try {
                 const renderData = {
                 const renderData = {

+ 3 - 0
app/middleware/budget_check.js

@@ -24,6 +24,9 @@ module.exports = options => {
             if (!id) throw '参数错误';
             if (!id) throw '参数错误';
             this.budget = yield this.service.budget.getDataById(id);
             this.budget = yield this.service.budget.getDataById(id);
             if (!this.budget) throw '项目不存在';
             if (!this.budget) throw '项目不存在';
+            const bp = yield this.service.budgetPermission.getBudgetUserPermission(id);
+            if (!bp) throw '您无权查看该项目';
+            this.budget.readOnly = bp.permission.indexOf(this.service.budgetPermission.PermissionConst.edit.value) < 0;
             yield next;
             yield next;
         } catch (err) {
         } catch (err) {
             this.log(err);
             this.log(err);

+ 5 - 0
app/middleware/session_auth.js

@@ -55,6 +55,11 @@ module.exports = options => {
                 }
                 }
             }
             }
             this.session.sessionProject.showDataCollect = showDataCollect;
             this.session.sessionProject.showDataCollect = showDataCollect;
+            if (sessionUser.is_admin) {
+                this.session.sessionProject.showBudget = true;
+            } else {
+                this.session.sessionProject.showBudget = yield this.service.budgetPermission.showBudget(sessionUser.accountId);
+            }
 
 
             // 同步消息
             // 同步消息
             yield this.service.notify.syncNotifyData();
             yield this.service.notify.syncNotifyData();

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

@@ -19,6 +19,7 @@ $(document).ready(() => {
     const budgetSpread = SpreadJsObj.createNewSpread($('#budget-spread')[0]);
     const budgetSpread = SpreadJsObj.createNewSpread($('#budget-spread')[0]);
     const budgetSheet = budgetSpread.getActiveSheet();
     const budgetSheet = budgetSpread.getActiveSheet();
     sjsSettingObj.setFxTreeStyle(spreadSetting, sjsSettingObj.FxTreeStyle.jz);
     sjsSettingObj.setFxTreeStyle(spreadSetting, sjsSettingObj.FxTreeStyle.jz);
+    spreadSetting.readOnly = readOnly;
     // if (thousandth) sjsSettingObj.setTpThousandthFormat(spreadSetting);
     // if (thousandth) sjsSettingObj.setTpThousandthFormat(spreadSetting);
     SpreadJsObj.initSheet(budgetSheet, spreadSetting);
     SpreadJsObj.initSheet(budgetSheet, spreadSetting);
 
 

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

@@ -61,6 +61,7 @@ const relaTender = function () {
     });
     });
 };
 };
 
 
+
 $(document).ready(() => {
 $(document).ready(() => {
     autoFlashHeight();
     autoFlashHeight();
     $('#del-budget').on('show.bs.modal', () => {
     $('#del-budget').on('show.bs.modal', () => {
@@ -69,9 +70,134 @@ $(document).ready(() => {
     $('#select-rela').on('show.bs.modal', () => {
     $('#select-rela').on('show.bs.modal', () => {
         $('[name=select-rela-check]').removeAttr('checked');
         $('[name=select-rela-check]').removeAttr('checked');
         const rela = curBudget.rela_tender ? curBudget.rela_tender.split(',') : [];
         const rela = curBudget.rela_tender ? curBudget.rela_tender.split(',') : [];
-        console.log(rela);
         for (const r of rela) {
         for (const r of rela) {
             $(`[tid=${r}]`).attr("checked", "checked");
             $(`[tid=${r}]`).attr("checked", "checked");
         }
         }
     });
     });
+
+    let timer = null;
+    let oldSearchVal = null;
+    $('#member-search').bind('input propertychange', function(e) {
+        oldSearchVal = e.target.value;
+        timer && clearTimeout(timer);
+        timer = setTimeout(() => {
+            const newVal = $('#member-search').val();
+            let html = '';
+            if (newVal && newVal === oldSearchVal) {
+                accountList
+                    .filter(item => item && (item.name.indexOf(newVal) !== -1 || (item.mobile && item.mobile.indexOf(newVal) !== -1)))
+                    .forEach(item => {
+                        html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                        <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                class="ml-auto">${item.mobile || ''}</span></p>
+                        <span class="text-muted">${item.role || ''}</span></dd>`
+                    });
+                $('.book-list').empty();
+                $('.book-list').append(html);
+            } else {
+                if (!$('.acc-btn').length) {
+                    accountGroup.forEach((group, idx) => {
+                        if (!group) return;
+                        html += `<dt><a href="javascript: void(0);" class="acc-btn" data-groupid="${idx}" data-type="hide"><i class="fa fa-plus-square"></i>
+                        </a> ${group.groupName}</dt>
+                        <div class="dd-content" data-toggleid="${idx}">`;
+                        group.groupList.forEach(item => {
+                            html += `<dd class="border-bottom p-2 mb-0 " data-id="${item.id}" >
+                                    <p class="mb-0 d-flex"><span class="text-primary">${item.name}</span><span
+                                            class="ml-auto">${item.mobile || ''}</span></p>
+                                    <span class="text-muted">${item.role || ''}</span>
+                                </dd>`
+                        });
+                        html += '</div>';
+                    });
+                    $('.book-list').empty();
+                    $('.book-list').append(html);
+                }
+            }
+        }, 400);
+    });
+    $('.book-list').on('click', 'dt', function () {
+        const idx = $(this).find('.acc-btn').attr('data-groupid')
+        const type = $(this).find('.acc-btn').attr('data-type')
+        if (type === 'hide') {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).show(() => {
+                $(this).children().find('i').removeClass('fa-plus-square').addClass('fa-minus-square-o')
+                $(this).find('.acc-btn').attr('data-type', 'show')
+
+            })
+        } else {
+            $(this).parent().find(`div[data-toggleid="${idx}"]`).hide(() => {
+                $(this).children().find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square')
+                $(this).find('.acc-btn').attr('data-type', 'hide')
+            })
+        }
+        return false
+    });
+    let orgMembers, newMembers;
+    const generateMemberHtml = function () {
+        const html = [];
+        for (const mem of newMembers) {
+            html.push('<tr>');
+            html.push(`<td>${mem.name}</td>`);
+            html.push(`<td>${mem.role}</td>`);
+            // const view = mem.permission.indexOf(permissionConst.view.value) >= 0;
+            // html.push(`<td><div class="custom-control custom-checkbox mb-2">
+            //             <input type="checkbox" id="view${mem.uid}" ptype="view" uid="${mem.uid}" class="custom-control-input" ${(view ? 'checked' : '')}>
+            //             <label class="custom-control-label" for="view${mem.uid}"></label></div></td>`);
+            const edit = mem.permission.indexOf(permissionConst.edit.value) >= 0;
+            html.push(`<td><div class="custom-control custom-checkbox mb-2">
+                        <input type="checkbox" id="edit${mem.uid}" ptype="edit" uid="${mem.uid}" class="custom-control-input" ${(edit ? 'checked' : '')}>
+                        <label class="custom-control-label" for="edit${mem.uid}"></label></div></td>`);
+            html.push(`<td><a href="javascript: void(0);" class="btn btn-outline-danger btn-sm ml-1" name="del-member" uid="${mem.uid}">移除</a></td>`);
+            html.push('</tr>');
+        }
+        $('#member-list').html(html.join(''));
+    };
+    $('[data-target="#member"]').click(function(){
+        const tr = this.parentNode.parentNode;
+        curBudget.id = tr.getAttribute('bid');
+        curBudget.name = tr.getAttribute('bname');
+        curBudget.rela_tender = tr.getAttribute('rela-tender');
+        postData(window.location.pathname + '/member', curBudget, function (result) {
+            orgMembers = result;
+            newMembers = result;
+            generateMemberHtml();
+            $('#member').modal('show');
+        });
+    });
+    $('dl').on('click', 'dd', function () {
+        const auditorId = parseInt($(this).data('id'));
+        const user = accountList.find(x => { return x.id === auditorId; });
+        const check = $(`tr[uid=${auditorId}]`, '#member-list');
+        if (check.length > 0) {
+            toastr.error('请勿重复添加成员');
+            return;
+        }
+        newMembers.push({
+            uid: user.id,
+            name: user.name,
+            role: user.role,
+            permission: [1],
+        });
+        generateMemberHtml();
+    });
+    $('#member').on('click', 'a[name="del-member"]', function () {
+        const id = parseInt(this.getAttribute('uid'));
+        newMembers.splice(newMembers.findIndex(x => { return x.id === id}), 1);
+        generateMemberHtml();
+    });
+    $('#member-ok').click(() => {
+        postData(window.location.pathname + '/member-save', {id: curBudget.id, member: newMembers}, function () {
+            $('#member').modal('hide');
+        })
+    });
+    $('#member-list').on('click', 'input', function () {
+        const id = parseInt(this.getAttribute('uid'));
+        const mem = newMembers.find(x => { return x.uid === id});
+        if (this.checked) {
+            mem.permission.push(permissionConst[this.getAttribute('ptype')].value);
+        } else {
+            mem.permission.splice(permissionConst[this.getAttribute('ptype')].value, 1);
+        }
+    });
 });
 });

+ 1 - 1
app/public/js/std_lib.js

@@ -25,7 +25,7 @@ class stdLib {
         this.spread = SpreadJsObj.createNewSpread(this.obj);
         this.spread = SpreadJsObj.createNewSpread(this.obj);
         SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
         SpreadJsObj.initSheet(this.spread.getActiveSheet(), this.spreadSetting);
         SpreadJsObj.forbiddenSpreadContextMenu(setting.selector, this.spread);
         SpreadJsObj.forbiddenSpreadContextMenu(setting.selector, this.spread);
-        this.spread.getActiveSheet().bind(spreadNS.Events.CellDoubleClick, setting.cellDoubleClick);
+        if (setting.cellDoubleClick) this.spread.getActiveSheet().bind(spreadNS.Events.CellDoubleClick, setting.cellDoubleClick);
         this.spread.getActiveSheet().bind(spreadNS.Events.SelectionChanged, function (e, info) {
         this.spread.getActiveSheet().bind(spreadNS.Events.SelectionChanged, function (e, info) {
             if (!info.oldSelections[0] || info.newSelections[0].row !== info.oldSelections[0].row) {
             if (!info.oldSelections[0] || info.newSelections[0].row !== info.oldSelections[0].row) {
                 SpreadJsObj.saveTopAndSelect(info.sheet, self.cacheKey.node);
                 SpreadJsObj.saveTopAndSelect(info.sheet, self.cacheKey.node);

+ 4 - 2
app/router.js

@@ -577,9 +577,11 @@ module.exports = app => {
 
 
     // 概算投资
     // 概算投资
     app.get('/budget', sessionAuth, 'budgetController.list');
     app.get('/budget', sessionAuth, 'budgetController.list');
-    app.post('/budget/add', sessionAuth, 'budgetController.add');
-    app.post('/budget/del', sessionAuth, 'budgetController.del');
+    app.post('/budget/add', sessionAuth, projectManagerCheck, 'budgetController.add');
+    app.post('/budget/del', sessionAuth, projectManagerCheck, 'budgetController.del');
     app.post('/budget/save', sessionAuth, 'budgetController.save');
     app.post('/budget/save', sessionAuth, 'budgetController.save');
+    app.post('/budget/member', sessionAuth, projectManagerCheck, 'budgetController.member');
+    app.post('/budget/member-save', sessionAuth, projectManagerCheck, 'budgetController.memberSave');
     app.get('/budget/:id/compare', sessionAuth, budgetCheck, 'budgetController.compare');
     app.get('/budget/:id/compare', sessionAuth, budgetCheck, 'budgetController.compare');
     app.post('/budget/:id/compare/load', sessionAuth, budgetCheck, 'budgetController.compareLoad');
     app.post('/budget/:id/compare/load', sessionAuth, budgetCheck, 'budgetController.compareLoad');
     app.post('/budget/:id/compare/final', sessionAuth, budgetCheck, 'budgetController.compareFinal');
     app.post('/budget/:id/compare/final', sessionAuth, budgetCheck, 'budgetController.compareFinal');

+ 18 - 0
app/service/budget.js

@@ -50,6 +50,24 @@ module.exports = app => {
             return rule;
             return rule;
         }
         }
 
 
+        async getBudget(admin) {
+            let result = await this.getAllDataByCondition({
+                where: { pid: this.ctx.session.sessionProject.id },
+                orders: [['name', 'asc']],
+            });
+            if (admin) return result;
+
+            const permissionConst = this.ctx.service.budgetPermission.PermissionConst;
+            const permissionBudget = await this.ctx.service.budgetPermission.getUserPermission();
+            result = result.filter(x => {
+                const pb = permissionBudget.find(y => { return x.id === y.bid});
+                if (pb) {
+                    x.canEdit = pb.permission.indexOf(permissionConst.edit.value) >= 0;
+                }
+                return !!pb;
+            });
+            return result;
+        }
 
 
         /**
         /**
          * 新增标段
          * 新增标段

+ 100 - 0
app/service/budget_permission.js

@@ -0,0 +1,100 @@
+'use strict';
+
+/**
+ *
+ *
+ * @author Mai
+ * @date
+ * @version
+ */
+
+module.exports = app => {
+    class BudgetPermission extends app.BaseService {
+
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @param {String} tableName - 表名
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'budget_permission';
+            this.PermissionConst = {
+                view: { title: '查看', value: 1 },
+                edit: { title: '编辑', value: 2 },
+            };
+        }
+
+        async showBudget(uid) {
+            const count = await this.count({ pid: this.ctx.session.sessionProject.id, uid });
+            return count > 0;
+        }
+
+        async getBudgetPermission(bid) {
+            const _ = this.ctx.helper._;
+            const result = await this.db.query(`SELECT bp.*, p.name, p.role 
+                FROM ${this.tableName} bp LEFT JOIN ${this.ctx.service.projectAccount.tableName} p
+                On bp.uid = p.id WHERE bid = ?`, [bid]);
+            result.forEach(x => {
+                x.permission = x.permission ? _.map(x.permission.split(','), _.toInteger) : []
+            });
+            return result;
+        }
+
+        async getUserPermission() {
+            const _ = this.ctx.helper._;
+            const result = await this.getAllDataByCondition({
+                where: { uid: this.ctx.session.sessionUser.accountId, pid: this.ctx.session.sessionProject.id }
+            });
+            result.forEach(x => {
+                x.permission = x.permission ? _.map(x.permission.split(','), _.toInteger) : []
+            });
+            return result;
+        }
+
+        async getBudgetUserPermission(bid) {
+            const _ = this.ctx.helper._;
+            const result = await this.getDataByCondition({uid: this.ctx.session.sessionUser.accountId, bid});
+            if (result) result.permission = result.permission ? _.map(result.permission.split(','), _.toInteger) : [];
+            return result;
+        }
+
+        async saveBudgetPermission(bid, member) {
+            const orgMember = await this.getAllDataByCondition({ where: { bid } });
+            const dm = [], um = [], im = [], cur = new Date();
+            for (const om of orgMember) {
+                const nm = member.find(x => { return om.uid === x.uid });
+                if (!nm) {
+                    dm.push(om.id);
+                } else {
+                    um.push({
+                        id: om.id,
+                        permission: nm.permission.join(','),
+                        modify_time: cur,
+                    });
+                    member.splice(member.indexOf(nm), 1);
+                }
+            }
+            for (const m of member) {
+                im.push({
+                    pid: this.ctx.session.sessionProject.id, bid, uid: m.uid,
+                    permission: m.permission.join(','), in_time: cur, modify_time: cur,
+                })
+            }
+            const conn = await this.db.beginTransaction();
+            try {
+                if (dm.length > 0) await conn.delete(this.tableName, { id: dm });
+                if (um.length > 0) await conn.updateRows(this.tableName, um);
+                if (im.length > 0) await conn.insert(this.tableName, im);
+                await conn.commit();
+            } catch (err) {
+                await conn.rollback();
+                throw err;
+            }
+        }
+    }
+
+    return BudgetPermission;
+};

+ 3 - 1
app/view/budget/detail.ejs

@@ -28,7 +28,9 @@
                     <a href="javascript: void(0);" name="base-opr" type="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
                     <a href="javascript: void(0);" name="base-opr" type="up-move" class="btn btn-sm btn-light text-primary" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="上移"><i class="fa fa-arrow-up" aria-hidden="true"></i></a>
                 </div>
                 </div>
                 <div class="d-inline-block ml-3">
                 <div class="d-inline-block ml-3">
+                    <% if (!ctx.budget.readOnly) { %>
                     <a class="btn btn-sm btn-primary" href="javascript: void(0);" id="budget-import">导入</a>
                     <a class="btn btn-sm btn-primary" href="javascript: void(0);" id="budget-import">导入</a>
+                    <% } %>
                 </div>
                 </div>
             </div>
             </div>
         </div>
         </div>
@@ -113,7 +115,7 @@
     </div>
     </div>
 </div>
 </div>
 <script>
 <script>
-    const readOnly = false;
+    const readOnly = <%- ctx.budget.readOnly %>;
     const needGcl = <%- needGcl %>;
     const needGcl = <%- needGcl %>;
     const spreadSetting = JSON.parse('<%- JSON.stringify(spreadSetting) %>');
     const spreadSetting = JSON.parse('<%- JSON.stringify(spreadSetting) %>');
 </script>
 </script>

+ 7 - 0
app/view/budget/list.ejs

@@ -2,9 +2,11 @@
     <div class="panel-title fluid">
     <div class="panel-title fluid">
         <div class="title-main  d-flex justify-content-between">
         <div class="title-main  d-flex justify-content-between">
             <div>概算投资</div>
             <div>概算投资</div>
+            <% if (ctx.session.sessionUser.is_admin) { %>
             <div class="ml-auto">
             <div class="ml-auto">
                 <a href="#add-budget" name="add" data-toggle="modal" data-target="#add-budget" class="btn btn-sm btn-primary pull-right">新建项目</a>
                 <a href="#add-budget" name="add" data-toggle="modal" data-target="#add-budget" class="btn btn-sm btn-primary pull-right">新建项目</a>
             </div>
             </div>
+            <% } %>
         </div>
         </div>
     </div>
     </div>
     <div class="content-wrap">
     <div class="content-wrap">
@@ -23,9 +25,14 @@
                         <td><%- bl.username %></td>
                         <td><%- bl.username %></td>
                         <td><%- ctx.moment(bl.in_time).format('YYYY-MM-DD HH:mm:ss') %></td>
                         <td><%- ctx.moment(bl.in_time).format('YYYY-MM-DD HH:mm:ss') %></td>
                         <td>
                         <td>
+                            <% if (ctx.session.sessionUser.is_admin || bl.canEdit) { %>
                             <a href="javascript: void(0);" data-target="#modify-budget" class="btn btn-outline-primary btn-sm" onclick="showModal(this);">编辑</a>
                             <a href="javascript: void(0);" data-target="#modify-budget" class="btn btn-outline-primary btn-sm" onclick="showModal(this);">编辑</a>
                             <a href="javascript: void(0);" data-target="#select-rela" class="btn btn-outline-primary btn-sm" onclick="showModal(this);">关联标段</a>
                             <a href="javascript: void(0);" data-target="#select-rela" class="btn btn-outline-primary btn-sm" onclick="showModal(this);">关联标段</a>
+                            <% } %>
+                            <% if (ctx.session.sessionUser.is_admin) { %>
+                            <a href="javascript: void(0);" data-target="#member" class="btn btn-outline-primary btn-sm ml-1">成员管理</a>
                             <a href="javascript: void(0);" data-target="#del-budget" class="btn btn-outline-danger btn-sm ml-1" onclick="showModal(this);">删除</a>
                             <a href="javascript: void(0);" data-target="#del-budget" class="btn btn-outline-danger btn-sm ml-1" onclick="showModal(this);">删除</a>
+                            <% } %>
                         </td>
                         </td>
                     </tr>
                     </tr>
                     <% } %>
                     <% } %>

+ 149 - 1
app/view/budget/list_modal.ejs

@@ -94,4 +94,152 @@
             </div>
             </div>
         </div>
         </div>
     </div>
     </div>
-</div>
+</div>
+
+<div class="modal fade" id="add-xm" 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">
+                <div class="form-group">
+                    <label>标段名称<b class="text-danger">*</b></label>
+                    <input class="form-control"  placeholder="输入标段名称" type="text">
+                </div>
+                <div class="form-group">
+                    <label>项目名称<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm"  placeholder="输入项目名称" type="text">
+                </div>
+                <div class="form-group">
+                    <label>项目名称<b class="text-danger">*</b></label>
+                    <input class="form-control form-control-sm is-invalid"  placeholder="输入标段名称" type="text" value="名称超过100个字">
+                    <div class="invalid-feedback">
+                        名称超过100个字,请缩减名称。
+                    </div>
+                </div>
+
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
+                <button type="button" class="btn btn-primary">确定修改</button>
+            </div>
+        </div>
+    </div>
+</div>
+<!--删除标段-->
+<div class="modal fade" id="del-bd" 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">
+                <h6>确认删除「XXX高速公路」?</h6>
+                <h6>删除后,数据无法恢复,请谨慎操作。</h6>
+            </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-danger">确定删除</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!--弹出关联标段-->
+<div class="modal fade" id="add-selectbd" 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>
+                <table class="table table-sm table-bordered">
+                    <thead>
+                    <tr class="text-center">
+                        <th>选择</th>
+                        <th>标段名称</th>
+                        <th>期数</th>
+                        <th>状态</th>
+                    </tr>
+                    </thead>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第一合同段</td><td>第15期</td><td>审批完成</td>
+                    </tr>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第二合同段</td><td>第16期</td><td>审批完成</td>
+                    </tr>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第三合同段</td><td>第15期</td><td>审批完成</td>
+                    </tr>
+                    <tr>
+                        <td class="text-center"><input type="checkbox"></td><td>第四合同段</td><td>第14期</td><td>审批完成</td>
+                    </tr>
+                </table>
+            </div>
+            <div class="modal-footer d-flex justify-content-between">
+                <div>
+                    <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                    <button type="button" class="btn btn-sm btn-primary" >确定</button>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<!--成员管理-->
+<div class="modal fade" id="member" 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">
+                <div class="dropdown">
+                    <button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+                        添加用户
+                    </button>
+                    <div class="dropdown-menu" aria-labelledby="dropdownMenuButton" style="width:220px">
+                        <div class="mb-2 p-2"><input class="form-control form-control-sm" placeholder="姓名/手机 检索" id="member-search" autocomplete="off"></div>
+                        <dl class="list-unstyled book-list">
+                            <% accountGroup.forEach((group, idx) => { %>
+                            <dt><a href="javascript: void(0);" class="acc-btn" data-groupid="<%- idx %>" data-type="hide"><i class="fa fa-plus-square"></i></a> <%- group.groupName %></dt>
+                            <div class="dd-content" data-toggleid="<%- idx %>">
+                                <% group.groupList.forEach(item => { %>
+                                <dd class="border-bottom p-2 mb-0 " data-id="<%- item.id %>" >
+                                    <p class="mb-0 d-flex"><span class="text-primary"><%- item.name %></span><span
+                                                class="ml-auto"><%- item.mobile %></span></p>
+                                    <span class="text-muted"><%- item.role %></span>
+                                </dd>
+                                <% });%>
+                            </div>
+                            <% }) %>
+                        </dl>
+                    </div>
+                </div>
+                <div class="mt-1">
+                    <table class="table table-bordered">
+                        <thead>
+                        <th>成员名称</th>
+                        <th>角色/职位</th>
+                        <th>编辑</th>
+                        <th>移除</th>
+                        </thead>
+                        <tbody id="member-list">
+
+                        </tbody>
+                    </table>
+                </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="member-ok">确认修改</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const accountList = JSON.parse('<%- JSON.stringify(accountList) %>');
+    const accountGroup = JSON.parse('<%- JSON.stringify(accountGroup) %>');
+    const permissionConst = JSON.parse('<%- JSON.stringify(permissionConst) %>');
+</script>