ellisran преди 1 месец
родител
ревизия
ef43043fc3

+ 1 - 1
app/base/base_controller.js

@@ -46,7 +46,7 @@ class BaseController extends Controller {
                     if (index === 'tender') {
                         im.url = `/sp/${ctx.subProject.id}${ctx.curListUrl}`;
                     } else if (index === 'contract') {
-                        im.url = `/sp/${ctx.subProject.id}/${im.controller}/detail`;
+                        im.url = `/sp/${ctx.subProject.id}/${im.controller}/panel`;
                     } else if (index === 'financial') {
                         im.url = `/sp/${ctx.subProject.id}/${im.controller}/${ctx.subProject.financialToUrl}`;
                     } else {

+ 51 - 8
app/controller/contract_controller.js

@@ -39,10 +39,10 @@ module.exports = app => {
                 renderData.projectList = await ctx.service.subProject.getSubProjectByContract(ctx.session.sessionProject.id, ctx.session.sessionUser.accountId, ctx.session.sessionUser.is_admin);
                 for (const t of renderData.projectList) {
                     if (!t.is_folder) {
-                        const expensessList = await ctx.service.contract.getListByUsers({ spid: t.id, contract_type: contractConst.type.expenses }, ctx.session.sessionUser);
-                        t.expenses_count = expensessList.length;
-                        t.expenses_total_price = expensessList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
-                        t.expenses_yf_price = expensessList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
+                        const expensesList = await ctx.service.contract.getListByUsers({ spid: t.id, contract_type: contractConst.type.expenses }, ctx.session.sessionUser);
+                        t.expenses_count = expensesList.length;
+                        t.expenses_total_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
+                        t.expenses_yf_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
                         const incomeList = await ctx.service.contract.getListByUsers({ spid: t.id, contract_type: contractConst.type.income }, ctx.session.sessionUser);
                         t.income_count = incomeList.length;
                         t.income_total_price = incomeList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
@@ -98,10 +98,10 @@ module.exports = app => {
                     t.tree_level = spInfo.tree_level + 1;
                     t.tree_order = spInfo.child_order + 1;
                     spInfo.child_order++;
-                    const expensessList = await ctx.service.contract.getListByUsers({ tid: t.id, contract_type: contractConst.type.expenses }, ctx.session.sessionUser);
-                    t.expenses_count = expensessList.length;
-                    t.expenses_total_price = expensessList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
-                    t.expenses_yf_price = expensessList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
+                    const expensesList = await ctx.service.contract.getListByUsers({ tid: t.id, contract_type: contractConst.type.expenses }, ctx.session.sessionUser);
+                    t.expenses_count = expensesList.length;
+                    t.expenses_total_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
+                    t.expenses_yf_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
                     const incomeList = await ctx.service.contract.getListByUsers({ tid: t.id, contract_type: contractConst.type.income }, ctx.session.sessionUser);
                     t.income_count = incomeList.length;
                     t.income_total_price = incomeList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
@@ -207,6 +207,49 @@ module.exports = app => {
             }
         }
 
+        async panel(ctx) {
+            try {
+                const whiteList = ctx.app.config.multipart.whitelist;
+                const contractOptions = ctx.helper._.cloneDeep(ctx.contractOptions);
+                contractOptions.contract_type = ctx.contract_type;
+                const contractTreeAudits = await ctx.service.contractTreeAudit.getAllDataByCondition({ where: contractOptions });
+                const expensesOptions = ctx.helper._.cloneDeep(ctx.contractOptions);
+                expensesOptions.contract_type = contractConst.type.expenses;
+                const expensesList = await ctx.service.contract.getListByUsers(expensesOptions, ctx.session.sessionUser);
+                const expenses_total_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
+                const expenses_yf_price = expensesList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
+                const incomeOptions = ctx.helper._.cloneDeep(ctx.contractOptions);
+                incomeOptions.contract_type = contractConst.type.income;
+                const incomeList = await ctx.service.contract.getListByUsers(incomeOptions, ctx.session.sessionUser);
+                const income_total_price = incomeList.reduce((total, item) => ctx.helper.add(total, item.total_price), 0);
+                const income_yf_price = incomeList.reduce((total, item) => ctx.helper.add(total, item.yf_price), 0);
+                const prices = {
+                    expenses_total_price,
+                    expenses_yf_price,
+                    expenses_rate: ctx.helper.div(expenses_yf_price, expenses_total_price) * 100,
+                    income_total_price,
+                    income_yf_price,
+                    income_rate: ctx.helper.div(income_yf_price, income_total_price) * 100,
+                };
+                const renderData = {
+                    contractTreeAudits,
+                    audit_permission: ctx.contract_audit_permission,
+                    preUrl: `/sp/${ctx.subProject.id}` + (ctx.contractOptions.tid ? '/contract/tender' : '/contract'),
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.contract.panel),
+                    contractConst,
+                    prices,
+                    expensesList,
+                    incomeList,
+                    thisUrl: `/sp/${ctx.subProject.id}` + (ctx.contract_tender ? `/contract/tender/${ctx.contract.id}/detail` : '/contract/detail'),
+                };
+                await this.layout('contract/panel.ejs', renderData);
+            } catch (err) {
+                ctx.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(`/sp/${ctx.subProject.id}` + (ctx.contractOptions.spid ? '/contract/detail' : '/contract/tender'));
+            }
+        }
+
         async detail(ctx) {
             try {
                 const whiteList = ctx.app.config.multipart.whitelist;

+ 292 - 0
app/public/js/contract_panel.js

@@ -0,0 +1,292 @@
+$(document).ready(function() {
+    autoFlashHeight();
+
+    const expensesPieValues = [
+        {value: _.filter(expensesList, function (item) {
+            return !item.settle_code && item.yf_price < item.total_price;
+        }).length, name: '履行中'},
+        {value: _.filter(expensesList, function (item) {
+            return !item.settle_code && item.yf_price >= item.total_price;
+        }).length, name: '已完成'},
+        {value: _.filter(expensesList, function (item) {
+            return !!item.settle_code;
+        }).length, name: '已结算'}
+    ];
+    console.log(expensesPieValues);
+
+    const expensesPieChart = echarts.init(document.getElementById('expensesPieChart'));
+    const expensesPieOption = {
+        color: ['#c7b8a1','#9ca8b8','#7b8b6f','#d8caaf'],
+        tooltip: {
+            trigger: 'item',
+            formatter: '{a} <br/>{b}: {c} ({d}%)'
+        },
+        legend: {
+            orient: 'horizontal',
+            left: 0,
+            data: ['履行中', '已完成', '已结算']
+        },
+        series: [
+            {
+                name: '合同',
+                type: 'pie',
+                radius: ['50%', '70%'],
+                avoidLabelOverlap: false,
+                label: {
+                    show: false,
+                    position: 'center'
+                },
+                emphasis: {
+                    label: {
+                        show: true,
+                        fontSize: '20',
+                        fontWeight: 'bold'
+                    }
+                },
+                labelLine: {
+                    show: false
+                },
+                data: expensesPieValues,
+            }
+        ]
+    };
+    expensesPieChart.setOption(expensesPieOption);
+
+    // 按create_time月份整理expensesList数据
+    const expensesBarDatas = [];
+    for (const expenses of expensesList) {
+        const yearMonth = moment(expenses.create_time).format('YYYY年MM月');
+        const ymExpenses = _.find(expensesBarDatas, { yearMonth });
+        if (ymExpenses) {
+            ymExpenses.yf_price = ZhCalc.add(ymExpenses.yf_price, expenses.yf_price);
+        } else {
+            expensesBarDatas.push({ yearMonth, yf_price: expenses.yf_price });
+        }
+    }
+    for (const e of expensesBarDatas) {
+        e.rate = ZhCalc.round(ZhCalc.div(e.yf_price, prices.expenses_total_price) * 100, 2);
+    }
+    console.log(expensesBarDatas);
+
+    const expensesBarChart = echarts.init(document.getElementById('expensesBarChart'));
+    const expensesBarOption = {
+        color: ['#b5c4b1','#965454','#9ca8b8','#d8caaf'],
+        title : {
+            text: '支出合同结算趋势'
+        },
+        tooltip : {
+            trigger: 'axis'
+        },
+        calculable : true,
+        legend: {
+            data:['支付金额','占总金额比例']
+        },
+        dataZoom: [
+            {show: true,start: 0, end: 100}
+        ],
+        xAxis : [
+            {
+                type : 'category',
+                splitLine : {show : true},
+                data : _.map(expensesBarDatas, 'yearMonth'),
+            }
+        ],
+        yAxis : [
+            {
+                type : 'value',
+                name : '金额',
+                position:'left',
+                axisLabel : {
+                    formatter: '{value} 元'
+                },
+                splitArea : {show : true}
+            },
+            {
+                type : 'value',
+                name:'占总金额比例',
+                axisLabel : {
+                    formatter: '{value} %'
+                },
+                position: 'right',
+                splitArea : {show : true}
+            }
+        ],
+        series : [
+            {
+                name:'支付金额',
+                type:'bar',
+                tooltip : {formatter: "{b}<br/>{a}:{c} %"},
+                stack: '结算金额',
+                data: _.map(expensesBarDatas, 'yf_price'),
+            },
+            {
+                name:'占总金额比例',
+                type:'line',
+                tooltip : {trigger: 'axis',formatter: "{b}<br/>{a}:{c} %"},
+                yAxisIndex: 1,
+                data: _.map(expensesBarDatas, 'rate'),
+            },
+        ]
+    };
+    expensesBarChart.setOption(expensesBarOption);
+
+    const incomePieValues = [
+        {value: _.filter(incomeList, function (item) {
+                return !item.settle_code && item.yf_price < item.total_price;
+            }).length, name: '履行中'},
+        {value: _.filter(incomeList, function (item) {
+                return !item.settle_code && item.yf_price >= item.total_price;
+            }).length, name: '已完成'},
+        {value: _.filter(incomeList, function (item) {
+                return !!item.settle_code;
+            }).length, name: '已结算'}
+    ];
+    console.log(incomePieValues);
+
+    const incomePieChart = echarts.init(document.getElementById('incomePieChart'));
+    const incomePieOption = {
+        color: ['#c7b8a1','#9ca8b8','#7b8b6f','#d8caaf'],
+        tooltip: {
+            trigger: 'item',
+            formatter: '{a} <br/>{b}: {c} ({d}%)'
+        },
+        legend: {
+            orient: 'horizontal',
+            left: 0,
+            data: ['履行中', '已完成', '已结算']
+        },
+        series: [
+            {
+                name: '合同',
+                type: 'pie',
+                radius: ['50%', '70%'],
+                avoidLabelOverlap: false,
+                label: {
+                    show: false,
+                    position: 'center'
+                },
+                emphasis: {
+                    label: {
+                        show: true,
+                        fontSize: '20',
+                        fontWeight: 'bold'
+                    }
+                },
+                labelLine: {
+                    show: false
+                },
+                data: incomePieValues,
+            }
+        ]
+    };
+    // 为echarts对象加载数据
+    incomePieChart.setOption(incomePieOption);
+
+    const incomeBarDatas = [];
+    for (const income of incomeList) {
+        const yearMonth = moment(income.create_time).format('YYYY年MM月');
+        const ymIncome = _.find(incomeBarDatas, { yearMonth });
+        if (ymIncome) {
+            ymIncome.yf_price = ZhCalc.add(ymIncome.yf_price, income.yf_price);
+        } else {
+            incomeBarDatas.push({ yearMonth, yf_price: income.yf_price });
+        }
+    }
+    for (const i of incomeBarDatas) {
+        i.rate = ZhCalc.round(ZhCalc.div(i.yf_price, prices.income_total_price) * 100, 2);
+    }
+    console.log(incomeBarDatas);
+
+    const incomeBarChart = echarts.init(document.getElementById('incomeBarChart'));
+    const incomeBarOption = {
+        color: ['#a27e7e','#656565','#b5c4b1','#d8caaf'],
+        title : {
+            text: '收入合同结算趋势'
+        },
+        tooltip : {
+            trigger: 'axis'
+        },
+        calculable : true,
+        legend: {
+            data:['回款金额','占总金额比例']
+        },
+        dataZoom: [
+            {show: true,start: 0, end: 100}
+        ],
+        xAxis : [
+            {
+                type : 'category',
+                splitLine : {show : true},
+                data : _.map(incomeBarDatas, 'yearMonth'),
+            }
+        ],
+        yAxis : [
+            {
+                type : 'value',
+                name : '金额',
+                position:'left',
+                axisLabel : {
+                    formatter: '{value} 元'
+                },
+                splitArea : {show : true}
+            },
+            {
+                type : 'value',
+                name:'占总金额比例',
+                axisLabel : {
+                    formatter: '{value} %'
+                },
+                position: 'right',
+                splitArea : {show : true}
+            }
+        ],
+        series : [
+            {
+                name:'回款金额',
+                type:'bar',
+                tooltip : {formatter: "{b}<br/>{a}:{c} %"},
+                stack: '回款金额',
+                data: _.map(incomeBarDatas, 'yf_price'),
+            },
+            {
+                name:'占总金额比例',
+                type:'line',
+                tooltip : {trigger: 'axis',formatter: "{b}<br/>{a}:{c} %"},
+                yAxisIndex: 1,
+                data: _.map(incomeBarDatas, 'rate'),
+            },
+        ]
+    };
+    incomeBarChart.setOption(incomeBarOption);
+
+    let resizeTimer = null;
+    $(window).bind('resize', function () {
+        if (resizeTimer) clearTimeout(resizeTimer);
+        resizeTimer = setTimeout(function () {
+            echartsReset();
+        }, 500);
+    });
+    function echartsReset() {
+        expensesPieChart.resize();
+        incomePieChart.resize();
+        expensesBarChart.resize();
+        incomeBarChart.resize();
+    }
+
+    $.subMenu({
+        menu: '#sub-menu', miniMenu: '#sub-mini-menu', miniMenuList: '#mini-menu-list',
+        toMenu: '#to-menu', toMiniMenu: '#to-mini-menu',
+        key: 'menu.1.0.0',
+        miniHint: '#sub-mini-hint', hintKey: 'menu.hint.1.0.1',
+        callback: function (info) {
+            if (info.mini) {
+                $('.panel-title').addClass('fluid');
+                $('#sub-menu').removeClass('panel-sidebar');
+            } else {
+                $('.panel-title').removeClass('fluid');
+                $('#sub-menu').addClass('panel-sidebar');
+            }
+            autoFlashHeight();
+        }
+    });
+});

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

@@ -34,7 +34,7 @@ const tenderListSpec = (function(){
             html.push(arr.indexOf(node) === arr.length - 1 ? '└' : '├');
             html.push('</span>');
             //html.push('<a href="/tender/' + node.id + '">', node[c.field], '</a>');
-            html.push('<a href="/sp/' + spid + '/contract/tender/'+ node.id +'/detail" name="name" style="min-width: 200px;word-break:break-all;" id="' + node.id + '">', node.name, '</a>');
+            html.push('<a href="/sp/' + spid + '/contract/tender/'+ node.id +'/panel" name="name" style="min-width: 200px;word-break:break-all;" id="' + node.id + '">', node.name, '</a>');
         }
         html.push('</td>');
 

+ 2 - 0
app/router.js

@@ -296,6 +296,7 @@ module.exports = app => {
     app.post('/sp/:id/contract/tender/:tid/audit/save', sessionAuth, subProjectCheck, contractCheck, 'contractController.auditSave');
     app.get('/sp/:id/contract/tender/:tid/detail', sessionAuth, subProjectCheck, contractCheck, 'contractController.detail');
     app.get('/sp/:id/contract/tender/:tid/detail/:type', sessionAuth, subProjectCheck, contractCheck, 'contractController.detail');
+    app.get('/sp/:id/contract/tender/:tid/panel', sessionAuth, subProjectCheck, contractCheck, 'contractController.panel');
     app.post('/sp/:id/contract/tender/:tid/detail/load', sessionAuth, subProjectCheck, contractCheck, 'contractController.loadDetail');
     app.post('/sp/:id/contract/tender/:tid/detail/:type/load', sessionAuth, subProjectCheck, contractCheck, 'contractController.loadDetail');
     app.post('/sp/:id/contract/tender/:tid/detail/update', sessionAuth, subProjectCheck, contractCheck, 'contractController.updateBills');
@@ -311,6 +312,7 @@ module.exports = app => {
     app.post('/sp/:id/contract/audit/save', sessionAuth, subProjectCheck, contractCheck, 'contractController.auditSave');
     app.get('/sp/:id/contract/detail', sessionAuth, subProjectCheck, contractCheck, 'contractController.detail');
     app.get('/sp/:id/contract/detail/:type', sessionAuth, subProjectCheck, contractCheck, 'contractController.detail');
+    app.get('/sp/:id/contract/panel', sessionAuth, subProjectCheck, contractCheck, 'contractController.panel');
     app.post('/sp/:id/contract/load', sessionAuth, subProjectCheck, contractCheck, 'contractController.loadDetail');
     app.post('/sp/:id/contract/detail/load', sessionAuth, subProjectCheck, contractCheck, 'contractController.loadDetail');
     app.post('/sp/:id/contract/detail/:type/load', sessionAuth, subProjectCheck, contractCheck, 'contractController.loadDetail');

+ 118 - 0
app/view/contract/panel.ejs

@@ -0,0 +1,118 @@
+<% include ./sub_menu.ejs %>
+<div class="panel-content">
+    <div class="panel-title">
+        <div class="title-main  d-flex">
+            <% include ./sub_mini_menu.ejs %>
+            <h2>合同概况</h2>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0">
+                <div class="m-3">
+                    <!--概况-->
+                    <div class="row mx-0 mb-3">
+
+                        <div class="col-6 pl-0 pr-5">
+                            <div class="card text-center mb-3">
+                                <div class="card-body">
+                                    <h5 class="card-title">支出合同结算进度</h5>
+                                    <div class="mb-0">
+                                        <div class="progress">
+                                            <div class="progress-bar bg-danger" style="width: <%- ctx.helper.round(prices.expenses_rate, 0) %>%;" data-placement="bottom" data-toggle="tooltip" data-original-title="已支付:¥<%- ctx.helper.formatMoney(prices.expenses_yf_price) %>"><%- ctx.helper.round(prices.expenses_rate, 0) %>%</div>
+                                            <div class="progress-bar bg-gray" style="width: <%- ctx.helper.sub(100, ctx.helper.round(prices.expenses_rate, 0)) %>%;" data-placement="bottom" data-toggle="tooltip" data-original-title="未支付:¥<%- ctx.helper.formatMoney(ctx.helper.sub(prices.expenses_total_price, prices.expenses_yf_price)) %>"><%- ctx.helper.sub(100, ctx.helper.round(prices.expenses_rate, 0)) %>%</div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="row">
+                                <div class="col-6">
+                                    <div class="card">
+                                        <div class="mt-3" id="expensesPieChart" style="height: 300px; width: 100%;"></div>
+                                    </div>
+                                </div>
+                                <div class="col-6">
+                                    <div class="card text-center">
+                                        <div class="card-body">
+                                            <h5 class="card-title"><%- ctx.helper.formatMoney(prices.expenses_total_price) %></h5>
+                                            <p class="card-text text-muted">支出合同总金额</p>
+                                        </div>
+                                    </div>
+                                    <div class="card text-center my-3">
+                                        <div class="card-body">
+                                            <h5 class="card-title"><%- ctx.helper.formatMoney(ctx.helper.sub(prices.expenses_total_price, prices.expenses_yf_price)) %></h5>
+                                            <p class="card-text text-muted">未支付</p>
+                                        </div>
+                                    </div>
+                                    <div class="card text-center">
+                                        <div class="card-body">
+                                            <h5 class="card-title"><%- ctx.helper.formatMoney(prices.expenses_yf_price) %></h5>
+                                            <p class="card-text text-muted">已支付</p>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="col-12 px-0 mt-3">
+                                <div class="card">
+                                    <div class="mt-3" id="expensesBarChart" style="height: 400px; width: 100%;"></div>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="col-6 pl-5 pr-0">
+                            <!--收入合同进度-->
+                            <div class="card text-center mb-3">
+                                <div class="card-body">
+                                    <h5 class="card-title">收入合同结算进度</h5>
+                                    <div class="mb-0">
+                                        <div class="progress">
+                                            <div class="progress-bar bg-danger" style="width: <%- ctx.helper.round(prices.income_rate, 0) %>%;" data-placement="bottom" data-toggle="tooltip" data-original-title="已支付:¥<%- ctx.helper.formatMoney(prices.income_yf_price) %>"><%- ctx.helper.round(prices.income_rate, 0) %>%</div>
+                                            <div class="progress-bar bg-gray" style="width: <%- ctx.helper.sub(100, ctx.helper.round(prices.income_rate, 0)) %>%;" data-placement="bottom" data-toggle="tooltip" data-original-title="未支付:¥<%- ctx.helper.formatMoney(ctx.helper.sub(prices.income_total_price, prices.income_yf_price)) %>"><%- ctx.helper.sub(100, ctx.helper.round(prices.income_rate, 0)) %>%</div>                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="row">
+                                <div class="col-6">
+                                    <div class="card">
+                                        <div class="mt-3" id="incomePieChart" style="height: 300px; width: 100%;"></div>
+                                    </div>
+                                </div>
+                                <div class="col-6">
+                                    <div class="card text-center">
+                                        <div class="card-body">
+                                            <h5 class="card-title"><%- ctx.helper.formatMoney(prices.income_total_price) %></h5>
+                                            <p class="card-text text-muted">收入合同总金额</p>
+                                        </div>
+                                    </div>
+                                    <div class="card text-center my-3">
+                                        <div class="card-body">
+                                            <h5 class="card-title"><%- ctx.helper.formatMoney(ctx.helper.sub(prices.income_total_price, prices.income_yf_price)) %></h5>
+                                            <p class="card-text text-muted">未回款</p>
+                                        </div>
+                                    </div>
+                                    <div class="card text-center">
+                                        <div class="card-body">
+                                            <h5 class="card-title"><%- ctx.helper.formatMoney(prices.income_yf_price) %></h5>
+                                            <p class="card-text text-muted">已回款</p>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="col-12 px-0 mt-3">
+                                <div class="card">
+                                    <div class="mt-3" id="incomeBarChart" style="height: 400px; width: 100%;"></div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const thisUrl = JSON.parse(unescape('<%- escape(JSON.stringify(thisUrl)) %>'));
+    const prices = JSON.parse(unescape('<%- escape(JSON.stringify(prices)) %>'));
+    const expensesList = JSON.parse(unescape('<%- escape(JSON.stringify(expensesList)) %>'));
+    const incomeList = JSON.parse(unescape('<%- escape(JSON.stringify(incomeList)) %>'));
+    let contractPays = [];
+</script>

+ 7 - 0
app/view/contract/sub_menu_list.ejs

@@ -1,6 +1,13 @@
 <% if (ctx.contract_tender) { %><nav-menu title="返回" url="<%- preUrl %>" tclass="text-primary" ml="1" icon="fa-chevron-left"></nav-menu><% } %>
 <div class="nav-box">
     <ul class="nav-list list-unstyled">
+        <li class="<% if (ctx.url === `/sp/${ctx.subProject.id}` + (ctx.contract_tender ? `/contract/tender/${ctx.contract.id}/panel` : '/contract/panel')) { %>active<% } %>">
+            <a href="<%- `/sp/${ctx.subProject.id}` + (ctx.contract_tender ? `/contract/tender/${ctx.contract.id}/panel` : '/contract/panel') %>"><span class="ml-3">合同概况</span></a>
+        </li>
+    </ul>
+</div>
+<div class="nav-box">
+    <ul class="nav-list list-unstyled">
         <li class="<% if (ctx.url === thisUrl) { %>active<% } %>">
             <a href="<%- thisUrl %>"><span class="ml-3">支出合同</span></a>
         </li>

+ 11 - 0
config/web.js

@@ -1767,6 +1767,17 @@ const JsFiles = {
                 ],
                 mergeFile: 'contract_tender',
             },
+            panel: {
+                files: ['/public/js/echarts/echarts.min.js', '/public/js/decimal.min.js', '/public/js/moment/moment.min.js', '/public/js/component/menu.js'],
+                mergeFiles: [
+                    '/public/js/sub_menu.js',
+                    '/public/js/zh_calc.js',
+                    // '/public/js/datepicker/datepicker.min.js',
+                    // '/public/js/datepicker/datepicker.zh.js',
+                    '/public/js/contract_panel.js',
+                ],
+                mergeFile: 'contract_panel',
+            },
             detail: {
                 files: [
                     '/public/js/js-xlsx/xlsx.full.min.js',