2026-01-21 10:34:06 +08:00
|
|
|
|
// 项目管理分析页面
|
|
|
|
|
|
let expenditureTrendChart = null;
|
|
|
|
|
|
let expenditureProportionChart = null;
|
|
|
|
|
|
let projectRiskChart = null;
|
|
|
|
|
|
let riskAnalysisChart = null;
|
|
|
|
|
|
let civilSpecialtyChart = null;
|
|
|
|
|
|
let electricalSpecialtyChart = null;
|
2026-01-24 17:14:53 +08:00
|
|
|
|
let bidCode = parent.parent.$('#bidPro').val();
|
|
|
|
|
|
|
2026-01-24 18:55:45 +08:00
|
|
|
|
// 获取当天日期
|
|
|
|
|
|
function getTodayDate() {
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
const year = now.getFullYear();
|
|
|
|
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
|
const day = String(now.getDate()).padStart(2, '0');
|
|
|
|
|
|
return year + '-' + month + '-' + day;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当月第一天和最后一天(保留用于其他功能)
|
2026-01-24 17:14:53 +08:00
|
|
|
|
function getCurrentMonthRange() {
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
const year = now.getFullYear();
|
|
|
|
|
|
const month = now.getMonth();
|
|
|
|
|
|
|
|
|
|
|
|
const firstDay = new Date(year, month, 1);
|
|
|
|
|
|
const lastDay = new Date(year, month + 1, 0);
|
|
|
|
|
|
|
|
|
|
|
|
const startDate = formatDate(firstDay);
|
|
|
|
|
|
const endDate = formatDate(lastDay);
|
|
|
|
|
|
|
|
|
|
|
|
return { startDate, endDate };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-24 18:55:45 +08:00
|
|
|
|
const today = getTodayDate();
|
2026-01-24 17:14:53 +08:00
|
|
|
|
let queryParams = {
|
|
|
|
|
|
projectId: bidCode,
|
2026-01-24 18:55:45 +08:00
|
|
|
|
startTime: today,
|
|
|
|
|
|
endTime: today,
|
2026-01-24 17:14:53 +08:00
|
|
|
|
};
|
2026-01-21 10:34:06 +08:00
|
|
|
|
|
2026-01-24 18:55:45 +08:00
|
|
|
|
// 调试信息:确认日期范围已设置为当天
|
|
|
|
|
|
console.log('日期范围已设置为当天:', queryParams.startTime, '~', queryParams.endTime);
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
// 页面初始化
|
|
|
|
|
|
$(document).ready(function () {
|
|
|
|
|
|
// 等待layui加载完成
|
|
|
|
|
|
layui.use(['laydate', 'layer'], function () {
|
|
|
|
|
|
initDateRange();
|
|
|
|
|
|
// 初始化项目信息模块的图表
|
|
|
|
|
|
initRiskAnalysisChart();
|
|
|
|
|
|
initCivilSpecialtyChart();
|
|
|
|
|
|
initElectricalSpecialtyChart();
|
|
|
|
|
|
// 初始化其他模块的图表
|
|
|
|
|
|
initExpenditureTrendChart();
|
|
|
|
|
|
initExpenditureProportionChart();
|
|
|
|
|
|
initProjectRiskChart();
|
|
|
|
|
|
initWarningTable();
|
|
|
|
|
|
|
2026-01-24 18:55:45 +08:00
|
|
|
|
// 页面初始化时,按默认当天日期加载一次接口数据
|
2026-01-24 17:14:53 +08:00
|
|
|
|
refreshAllModules();
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
// 窗口大小改变时重新调整图表
|
|
|
|
|
|
window.addEventListener('resize', debounce(() => {
|
|
|
|
|
|
if (riskAnalysisChart) riskAnalysisChart.resize();
|
|
|
|
|
|
if (civilSpecialtyChart) civilSpecialtyChart.resize();
|
|
|
|
|
|
if (electricalSpecialtyChart) electricalSpecialtyChart.resize();
|
|
|
|
|
|
if (expenditureTrendChart) expenditureTrendChart.resize();
|
|
|
|
|
|
if (expenditureProportionChart) expenditureProportionChart.resize();
|
|
|
|
|
|
if (projectRiskChart) projectRiskChart.resize();
|
|
|
|
|
|
}, 300));
|
2025-10-21 13:38:20 +08:00
|
|
|
|
});
|
2025-10-17 14:57:09 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
// 防抖函数
|
|
|
|
|
|
function debounce(fn, delay) {
|
|
|
|
|
|
let t = null;
|
|
|
|
|
|
return function () {
|
|
|
|
|
|
clearTimeout(t);
|
|
|
|
|
|
t = setTimeout(() => fn.apply(this, arguments), delay);
|
|
|
|
|
|
};
|
2025-10-17 14:57:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-24 17:14:53 +08:00
|
|
|
|
// 获取左上角工序进度
|
|
|
|
|
|
function getProcessProgress() {
|
|
|
|
|
|
const url = commonUrl + 'screen/largeScreen/sjNewProManage/getProProgress'
|
|
|
|
|
|
+ '?bidCode=' + (bidCode || '')
|
|
|
|
|
|
+ '&startTime=' + (queryParams.startTime || '')
|
|
|
|
|
|
+ '&endTime=' + (queryParams.endTime || '');
|
|
|
|
|
|
ajaxRequestGet(url, 'GET', true, function () {
|
|
|
|
|
|
|
|
|
|
|
|
}, function (result) {
|
|
|
|
|
|
|
|
|
|
|
|
if (result && result.data) {
|
|
|
|
|
|
updateProcessProgress(result.data);
|
|
|
|
|
|
} else if (result) {
|
|
|
|
|
|
updateProcessProgress(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, aqEnnable);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取左上角风险分析饼图数据
|
|
|
|
|
|
function getRiskAnalysisChartData() {
|
|
|
|
|
|
const url = commonUrl + 'screen/largeScreen/sjNewProManage/getRiskData'
|
|
|
|
|
|
+ '?bidCode=' + (bidCode || '')
|
|
|
|
|
|
+ '&startTime=' + (queryParams.startTime || '')
|
|
|
|
|
|
+ '&endTime=' + (queryParams.endTime || '');
|
|
|
|
|
|
ajaxRequestGet(url, 'GET', true, function () {
|
|
|
|
|
|
|
|
|
|
|
|
}, function (result) {
|
|
|
|
|
|
|
|
|
|
|
|
if (result) {
|
2026-01-24 18:55:45 +08:00
|
|
|
|
updateRiskAnalysisChart(result.data);
|
2026-01-24 17:14:53 +08:00
|
|
|
|
} else if (result && result.data) {
|
|
|
|
|
|
updateRiskAnalysisChart(result.data);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, aqEnnable);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取右上侧折线图数据
|
|
|
|
|
|
// 获取土建和电气专业图表数据
|
|
|
|
|
|
function getLineChartData() {
|
|
|
|
|
|
const url = commonUrl + 'screen/largeScreen/sjNewProManage/getProgressList'
|
|
|
|
|
|
+ '?bidCode=' + (bidCode || '')
|
|
|
|
|
|
+ '&startTime=' + (queryParams.startTime || '')
|
|
|
|
|
|
+ '&endTime=' + (queryParams.endTime || '');
|
|
|
|
|
|
ajaxRequestGet(url, 'GET', true, function () {
|
|
|
|
|
|
|
|
|
|
|
|
}, function (result) {
|
|
|
|
|
|
|
|
|
|
|
|
if (result && result.data) {
|
|
|
|
|
|
updateSpecialtyCharts(result.data);
|
|
|
|
|
|
} else if (result) {
|
|
|
|
|
|
updateSpecialtyCharts(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, aqEnnable);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取项目支出趋势和项目支出占比
|
|
|
|
|
|
function getExpenditureTrendAndProportion() {
|
|
|
|
|
|
const url = commonUrl + 'screen/largeScreen/sjNewProManage/getProjectCost'
|
|
|
|
|
|
+ '?bidCode=' + (bidCode || '')
|
|
|
|
|
|
+ '&startTime=' + (queryParams.startTime || '')
|
|
|
|
|
|
+ '&endTime=' + (queryParams.endTime || '');
|
|
|
|
|
|
ajaxRequestGet(url, 'GET', true, function () {
|
|
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
function (result) {
|
|
|
|
|
|
if (result && result.data) {
|
|
|
|
|
|
updateExpenditureTrendChart(result.data);
|
|
|
|
|
|
updateExpenditureProportionChart(result.data);
|
|
|
|
|
|
} else if (result) {
|
|
|
|
|
|
updateExpenditureTrendChart(result);
|
|
|
|
|
|
updateExpenditureProportionChart(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
aqEnnable);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取项目风险饼图
|
|
|
|
|
|
function getProjectRiskChartData() {
|
|
|
|
|
|
const url = commonUrl + 'screen/largeScreen/sjNewProManage/getProjectRisk'
|
|
|
|
|
|
+ '?bidCode=' + (bidCode || '')
|
|
|
|
|
|
+ '&startTime=' + (queryParams.startTime || '')
|
|
|
|
|
|
+ '&endTime=' + (queryParams.endTime || '');
|
|
|
|
|
|
ajaxRequestGet(url, 'GET', true, function () {
|
|
|
|
|
|
}, function (result) {
|
|
|
|
|
|
if (result && result.data) {
|
|
|
|
|
|
updateProjectRiskChart(result.data);
|
|
|
|
|
|
} else if (result) {
|
|
|
|
|
|
updateProjectRiskChart(result.data);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
aqEnnable);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取分析预警
|
|
|
|
|
|
function getAnalysisWarning(txType) {
|
|
|
|
|
|
// txType: "全部"传空字符串,其他传对应的中文
|
|
|
|
|
|
const txTypeParam = txType === 'all' ? '' : (txType === 'progress' ? '进度' : txType === 'cost' ? '成本' : txType === 'risk' ? '风险' : '');
|
|
|
|
|
|
const url = commonUrl + 'screen/largeScreen/sjNewProManage/getWarnList'
|
|
|
|
|
|
+ '?bidCode=' + (bidCode || '')
|
|
|
|
|
|
+ '&startTime=' + (queryParams.startTime || '')
|
|
|
|
|
|
+ '&endTime=' + (queryParams.endTime || '')
|
|
|
|
|
|
+ (txTypeParam ? '&txType=' + txTypeParam : '');
|
|
|
|
|
|
ajaxRequestGet(url, 'GET', true, function () {
|
|
|
|
|
|
|
|
|
|
|
|
}, function (result) {
|
|
|
|
|
|
console.log(result, '分析预警');
|
|
|
|
|
|
if (result && Array.isArray(result)) {
|
|
|
|
|
|
updateWarningTable(result);
|
|
|
|
|
|
} else if (result && result.data && Array.isArray(result.data)) {
|
|
|
|
|
|
updateWarningTable(result.data);
|
|
|
|
|
|
} else if (result && result.rows && Array.isArray(result.rows)) {
|
|
|
|
|
|
updateWarningTable(result.rows);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, aqEnnable);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 更新工序进度数据
|
|
|
|
|
|
function updateProcessProgress(data) {
|
|
|
|
|
|
if (!data) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 土建工序进度
|
|
|
|
|
|
// bdGx: 本段工序(土建总工序)
|
|
|
|
|
|
// bdYwcGx: 本段已完成工序(土建已完成)
|
|
|
|
|
|
const civilTotal = data.bdGx || '0';
|
|
|
|
|
|
const civilCompleted = data.bdYwcGx || '0';
|
|
|
|
|
|
$('#civilTotal').text(civilTotal);
|
|
|
|
|
|
$('#civilCompleted').text(civilCompleted);
|
|
|
|
|
|
|
|
|
|
|
|
// 电气工序进度
|
|
|
|
|
|
// dqGx: 当前工序(电气总工序)
|
|
|
|
|
|
// dqYwcGx: 当前已完成工序(电气已完成)
|
|
|
|
|
|
const electricalTotal = data.dqGx || '0';
|
|
|
|
|
|
const electricalCompleted = data.dqYwcGx || '0';
|
|
|
|
|
|
$('#electricalTotal').text(electricalTotal);
|
|
|
|
|
|
$('#electricalCompleted').text(electricalCompleted);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新所有模块数据
|
|
|
|
|
|
function refreshAllModules() {
|
|
|
|
|
|
// 调用工序进度接口
|
|
|
|
|
|
getProcessProgress();
|
|
|
|
|
|
// 调用风险分析饼图数据接口
|
|
|
|
|
|
getRiskAnalysisChartData();
|
|
|
|
|
|
// 调用土建和电气专业图表数据接口
|
|
|
|
|
|
getLineChartData();
|
|
|
|
|
|
// 调用项目支出趋势和项目支出占比接口
|
|
|
|
|
|
getExpenditureTrendAndProportion();
|
|
|
|
|
|
// 调用项目风险饼图接口
|
|
|
|
|
|
getProjectRiskChartData();
|
|
|
|
|
|
// 调用分析预警接口
|
|
|
|
|
|
getAnalysisWarning(currentWarningFilter);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化日期范围选择器(逻辑与工程质量分析保持一致)
|
2026-01-21 10:34:06 +08:00
|
|
|
|
function initDateRange() {
|
|
|
|
|
|
const laydate = layui.laydate;
|
2026-01-24 17:14:53 +08:00
|
|
|
|
|
2026-01-24 18:55:45 +08:00
|
|
|
|
// 初始显示为当天范围
|
2026-01-24 17:14:53 +08:00
|
|
|
|
$('#dateRange').val(queryParams.startTime + ' ~ ' + queryParams.endTime);
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
laydate.render({
|
|
|
|
|
|
elem: '#dateRange',
|
|
|
|
|
|
type: 'date',
|
|
|
|
|
|
range: true,
|
|
|
|
|
|
format: 'yyyy-MM-dd',
|
2026-01-24 17:14:53 +08:00
|
|
|
|
theme: 'dark',
|
|
|
|
|
|
// laydate 内部使用 "起始 - 结束" 作为分隔符
|
|
|
|
|
|
value: queryParams.startTime + ' - ' + queryParams.endTime,
|
2026-01-21 10:34:06 +08:00
|
|
|
|
done: function (value) {
|
2026-01-24 18:55:45 +08:00
|
|
|
|
const resetToToday = function () {
|
|
|
|
|
|
const today = getTodayDate();
|
|
|
|
|
|
queryParams.startTime = today;
|
|
|
|
|
|
queryParams.endTime = today;
|
|
|
|
|
|
$('#dateRange').val(today + ' ~ ' + today);
|
2026-01-24 17:14:53 +08:00
|
|
|
|
refreshAllModules();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (value && value.trim() !== '') {
|
|
|
|
|
|
const dates = value.split(' - ');
|
|
|
|
|
|
if (dates.length === 2) {
|
|
|
|
|
|
const startDate = dates[0].trim();
|
|
|
|
|
|
const endDateStr = dates[1].trim();
|
|
|
|
|
|
|
|
|
|
|
|
// 回显到输入框(使用 ~,与质量分析一致)
|
|
|
|
|
|
$('#dateRange').val(startDate + ' ~ ' + endDateStr);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新查询参数
|
|
|
|
|
|
queryParams.startTime = startDate;
|
|
|
|
|
|
queryParams.endTime = endDateStr;
|
|
|
|
|
|
|
|
|
|
|
|
// 日期变化后,重新拉取接口数据
|
|
|
|
|
|
refreshAllModules();
|
|
|
|
|
|
} else {
|
2026-01-24 18:55:45 +08:00
|
|
|
|
resetToToday();
|
2026-01-24 17:14:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2026-01-24 18:55:45 +08:00
|
|
|
|
resetToToday();
|
2026-01-24 17:14:53 +08:00
|
|
|
|
}
|
2025-10-18 16:58:04 +08:00
|
|
|
|
}
|
2025-10-17 14:57:09 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
// 格式化日期
|
|
|
|
|
|
function formatDate(date) {
|
|
|
|
|
|
const year = date.getFullYear();
|
|
|
|
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
|
|
|
|
return year + '-' + month + '-' + day;
|
2025-10-17 14:57:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
// 查询数据
|
|
|
|
|
|
function queryData() {
|
|
|
|
|
|
const dateRange = $('#dateRange').val();
|
|
|
|
|
|
if (!dateRange) {
|
|
|
|
|
|
layui.layer.msg('请选择日期范围', { icon: 0 });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-01-24 17:14:53 +08:00
|
|
|
|
|
|
|
|
|
|
// 点击查询按钮时,基于当前日期范围刷新接口数据
|
|
|
|
|
|
refreshAllModules();
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
layui.layer.msg('查询成功', { icon: 1, time: 1000 });
|
2025-10-17 14:57:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
// 加载数据
|
|
|
|
|
|
function loadData(dateRange) {
|
|
|
|
|
|
// 模拟数据加载
|
|
|
|
|
|
// 实际应该调用API
|
|
|
|
|
|
updateCharts();
|
2025-10-18 16:58:04 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
// 初始化风险分析饼图
|
|
|
|
|
|
function initRiskAnalysisChart() {
|
|
|
|
|
|
riskAnalysisChart = echarts.init(document.getElementById('riskAnalysisChart'));
|
2026-01-24 17:14:53 +08:00
|
|
|
|
|
2025-10-17 14:57:09 +08:00
|
|
|
|
const option = {
|
|
|
|
|
|
tooltip: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
trigger: 'item',
|
|
|
|
|
|
backgroundColor: 'rgba(19, 51, 55, 0.9)',
|
|
|
|
|
|
borderColor: 'rgba(0, 254, 252, 0.5)',
|
2025-10-17 14:57:09 +08:00
|
|
|
|
borderWidth: 1,
|
2026-01-21 10:34:06 +08:00
|
|
|
|
textStyle: { color: '#fff' },
|
|
|
|
|
|
formatter: '{b}: {c}个 ({d}%)'
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
orient: 'vertical',
|
|
|
|
|
|
right: 10,
|
|
|
|
|
|
top: 'center',
|
|
|
|
|
|
textStyle: { color: '#fff', fontSize: 12 },
|
|
|
|
|
|
itemWidth: 12,
|
2025-10-17 14:57:09 +08:00
|
|
|
|
itemHeight: 8,
|
2026-01-21 10:34:06 +08:00
|
|
|
|
itemGap: 10,
|
2026-01-24 17:14:53 +08:00
|
|
|
|
formatter: function (name) {
|
2026-01-24 18:55:45 +08:00
|
|
|
|
// 从图表实例中动态获取最新数据
|
|
|
|
|
|
if (!riskAnalysisChart) return name;
|
|
|
|
|
|
const option = riskAnalysisChart.getOption();
|
|
|
|
|
|
if (option && option.series && option.series[0] && option.series[0].data) {
|
|
|
|
|
|
const item = option.series[0].data.find(d => d.name === name);
|
|
|
|
|
|
return name + ' ' + (item ? item.value : '0');
|
|
|
|
|
|
}
|
|
|
|
|
|
return name + ' 0';
|
2026-01-21 10:34:06 +08:00
|
|
|
|
}
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
2026-01-21 10:34:06 +08:00
|
|
|
|
name: '风险分析',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['40%', '70%'],
|
|
|
|
|
|
center: ['35%', '50%'],
|
2025-10-17 14:57:09 +08:00
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
borderRadius: 5,
|
2026-01-21 10:34:06 +08:00
|
|
|
|
borderColor: 'rgba(13, 34, 37, 0.8)',
|
|
|
|
|
|
borderWidth: 2
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
label: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
show: false
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
label: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
show: true,
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#fff'
|
|
|
|
|
|
}
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
2026-01-21 10:34:06 +08:00
|
|
|
|
data: [
|
2026-01-24 17:14:53 +08:00
|
|
|
|
{ value: 0, name: '二级风险', itemStyle: { color: '#4A90E2' } },
|
|
|
|
|
|
{ value: 0, name: '三级风险', itemStyle: { color: '#1CFFA3' } },
|
|
|
|
|
|
{ value: 0, name: '四级风险', itemStyle: { color: '#00FEFC' } },
|
|
|
|
|
|
{ value: 0, name: '五级风险', itemStyle: { color: '#FF9C65' } }
|
2026-01-21 10:34:06 +08:00
|
|
|
|
]
|
|
|
|
|
|
}
|
2025-10-17 14:57:09 +08:00
|
|
|
|
],
|
2026-01-21 10:34:06 +08:00
|
|
|
|
graphic: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'text',
|
2026-01-24 18:55:45 +08:00
|
|
|
|
left: '50%',
|
|
|
|
|
|
top: '5%',
|
2026-01-21 10:34:06 +08:00
|
|
|
|
style: {
|
2026-01-24 17:14:53 +08:00
|
|
|
|
text: '今日风险数量 0个',
|
2026-01-21 10:34:06 +08:00
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
fill: '#fff',
|
|
|
|
|
|
fontSize: 13
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2025-10-17 14:57:09 +08:00
|
|
|
|
};
|
2026-01-21 10:34:06 +08:00
|
|
|
|
|
|
|
|
|
|
riskAnalysisChart.setOption(option);
|
2025-10-17 14:57:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-24 17:14:53 +08:00
|
|
|
|
// 更新风险分析饼图数据
|
|
|
|
|
|
function updateRiskAnalysisChart(data) {
|
|
|
|
|
|
if (!riskAnalysisChart || !data) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取各风险等级的数量
|
|
|
|
|
|
const twoRisk = parseInt(data.twoRisk || 0);
|
|
|
|
|
|
const threeRisk = parseInt(data.threeRisk || 0);
|
|
|
|
|
|
const fourRisk = parseInt(data.fourRisk || 0);
|
|
|
|
|
|
const fiveRisk = parseInt(data.fiveRisk || 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算总风险数量
|
|
|
|
|
|
const totalRisk = twoRisk + threeRisk + fourRisk + fiveRisk;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算百分比
|
|
|
|
|
|
let twoRiskPercent = 0;
|
|
|
|
|
|
let threeRiskPercent = 0;
|
|
|
|
|
|
let fourRiskPercent = 0;
|
|
|
|
|
|
let fiveRiskPercent = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (totalRisk > 0) {
|
|
|
|
|
|
twoRiskPercent = (twoRisk / totalRisk * 100).toFixed(1);
|
|
|
|
|
|
threeRiskPercent = (threeRisk / totalRisk * 100).toFixed(1);
|
|
|
|
|
|
fourRiskPercent = (fourRisk / totalRisk * 100).toFixed(1);
|
|
|
|
|
|
fiveRiskPercent = (fiveRisk / totalRisk * 100).toFixed(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-24 18:55:45 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-01-24 17:14:53 +08:00
|
|
|
|
// 更新图表数据
|
|
|
|
|
|
riskAnalysisChart.setOption({
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
data: [
|
2026-01-24 18:55:45 +08:00
|
|
|
|
{ value: twoRisk, name: '二级风险', itemStyle: { color: '#4A90E2' } },
|
|
|
|
|
|
{ value: threeRisk, name: '三级风险', itemStyle: { color: '#1CFFA3' } },
|
|
|
|
|
|
{ value: fourRisk, name: '四级风险', itemStyle: { color: '#00FEFC' } },
|
|
|
|
|
|
{ value: fiveRisk, name: '五级风险', itemStyle: { color: '#FF9C65' } }
|
2026-01-24 17:14:53 +08:00
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
graphic: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'text',
|
2026-01-24 18:55:45 +08:00
|
|
|
|
left: '60%',
|
|
|
|
|
|
top: '2%',
|
2026-01-24 17:14:53 +08:00
|
|
|
|
style: {
|
|
|
|
|
|
text: '今日风险数量 ' + totalRisk + '个',
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
fill: '#fff',
|
|
|
|
|
|
fontSize: 13
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化土建专业双柱状图
|
2026-01-21 10:34:06 +08:00
|
|
|
|
function initCivilSpecialtyChart() {
|
|
|
|
|
|
civilSpecialtyChart = echarts.init(document.getElementById('civilSpecialtyChart'));
|
2025-10-28 09:56:57 +08:00
|
|
|
|
|
2025-10-18 16:58:04 +08:00
|
|
|
|
const option = {
|
2025-10-17 14:57:09 +08:00
|
|
|
|
tooltip: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
trigger: 'axis',
|
2026-01-24 17:14:53 +08:00
|
|
|
|
axisPointer: { type: 'shadow' },
|
2026-01-21 10:34:06 +08:00
|
|
|
|
backgroundColor: 'rgba(19, 51, 55, 0.9)',
|
|
|
|
|
|
borderColor: 'rgba(0, 254, 252, 0.5)',
|
2025-10-17 14:57:09 +08:00
|
|
|
|
borderWidth: 1,
|
2026-01-21 10:34:06 +08:00
|
|
|
|
textStyle: { color: '#fff' },
|
|
|
|
|
|
formatter: function (params) {
|
|
|
|
|
|
if (!params || !params.length) return '';
|
|
|
|
|
|
let html = `<div style="font-weight:bold;margin-bottom:6px;">${params[0].name}</div>`;
|
|
|
|
|
|
params.forEach(p => {
|
|
|
|
|
|
html += `
|
|
|
|
|
|
<div style="display:flex;align-items:center;margin-bottom:2px;">
|
|
|
|
|
|
<span style="display:inline-block;width:10px;height:10px;background:${p.color};margin-right:8px;border-radius:2px;"></span>
|
|
|
|
|
|
<span style="color:#B9D6D9;margin-right:6px;">${p.seriesName}</span>
|
2026-01-24 17:14:53 +08:00
|
|
|
|
<span style="color:#fff;margin-left:auto;font-weight:bold;">${p.value}%</span>
|
2026-01-21 10:34:06 +08:00
|
|
|
|
</div>`;
|
|
|
|
|
|
});
|
|
|
|
|
|
return html;
|
|
|
|
|
|
}
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
2026-01-24 17:14:53 +08:00
|
|
|
|
data: ['计划进度', '实际进度'],
|
2026-01-21 10:34:06 +08:00
|
|
|
|
top: 8,
|
|
|
|
|
|
right: 20,
|
|
|
|
|
|
textStyle: { color: '#fff', fontSize: 12 },
|
|
|
|
|
|
itemWidth: 12,
|
|
|
|
|
|
itemHeight: 8,
|
|
|
|
|
|
itemGap: 15
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
grid: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
top: '20%',
|
|
|
|
|
|
left: '8%',
|
|
|
|
|
|
right: '6%',
|
|
|
|
|
|
bottom: '15%'
|
|
|
|
|
|
},
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
2026-01-24 17:14:53 +08:00
|
|
|
|
data: [],
|
|
|
|
|
|
axisLabel: { color: '#fff', fontSize: 10, rotate: 0 },
|
2026-01-21 10:34:06 +08:00
|
|
|
|
axisLine: { lineStyle: { color: '#5A6E71' } },
|
|
|
|
|
|
axisTick: { show: false }
|
|
|
|
|
|
},
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
min: 0,
|
2026-01-24 17:14:53 +08:00
|
|
|
|
max: 100,
|
|
|
|
|
|
axisLabel: { color: '#fff', fontSize: 10, formatter: '{value}%' },
|
2026-01-21 10:34:06 +08:00
|
|
|
|
axisLine: { lineStyle: { color: '#5A6E71' } },
|
2026-01-24 17:14:53 +08:00
|
|
|
|
splitLine: {
|
|
|
|
|
|
lineStyle: { color: 'rgba(90, 110, 113, 0.3)', type: 'dashed' }
|
2026-01-21 10:34:06 +08:00
|
|
|
|
}
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
2025-10-18 16:58:04 +08:00
|
|
|
|
series: [
|
|
|
|
|
|
{
|
2026-01-24 17:14:53 +08:00
|
|
|
|
name: '计划进度',
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: [],
|
|
|
|
|
|
itemStyle: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
2026-01-24 17:14:53 +08:00
|
|
|
|
{ offset: 0, color: '#4A90E2' },
|
|
|
|
|
|
{ offset: 1, color: '#357ABD' }
|
2026-01-21 10:34:06 +08:00
|
|
|
|
])
|
2026-01-24 17:14:53 +08:00
|
|
|
|
},
|
2026-01-24 18:55:45 +08:00
|
|
|
|
barWidth: '30'
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
2025-10-18 16:58:04 +08:00
|
|
|
|
{
|
2026-01-24 17:14:53 +08:00
|
|
|
|
name: '实际进度',
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: [],
|
|
|
|
|
|
itemStyle: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
2026-01-24 17:14:53 +08:00
|
|
|
|
{ offset: 0, color: '#1CFFA3' },
|
|
|
|
|
|
{ offset: 1, color: '#19CC8A' }
|
2026-01-21 10:34:06 +08:00
|
|
|
|
])
|
2026-01-24 17:14:53 +08:00
|
|
|
|
},
|
2026-01-24 18:55:45 +08:00
|
|
|
|
barWidth: '30'
|
2026-01-21 10:34:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
2025-10-17 14:57:09 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
civilSpecialtyChart.setOption(option);
|
2025-10-17 14:57:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-24 17:14:53 +08:00
|
|
|
|
// 初始化电气专业双柱状图
|
2026-01-21 10:34:06 +08:00
|
|
|
|
function initElectricalSpecialtyChart() {
|
|
|
|
|
|
electricalSpecialtyChart = echarts.init(document.getElementById('electricalSpecialtyChart'));
|
|
|
|
|
|
|
2025-10-18 16:58:04 +08:00
|
|
|
|
const option = {
|
2025-10-17 14:57:09 +08:00
|
|
|
|
tooltip: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
trigger: 'axis',
|
2026-01-24 17:14:53 +08:00
|
|
|
|
axisPointer: { type: 'shadow' },
|
2026-01-21 10:34:06 +08:00
|
|
|
|
backgroundColor: 'rgba(19, 51, 55, 0.9)',
|
|
|
|
|
|
borderColor: 'rgba(0, 254, 252, 0.5)',
|
2025-10-17 14:57:09 +08:00
|
|
|
|
borderWidth: 1,
|
2026-01-21 10:34:06 +08:00
|
|
|
|
textStyle: { color: '#fff' },
|
|
|
|
|
|
formatter: function (params) {
|
|
|
|
|
|
if (!params || !params.length) return '';
|
|
|
|
|
|
let html = `<div style="font-weight:bold;margin-bottom:6px;">${params[0].name}</div>`;
|
|
|
|
|
|
params.forEach(p => {
|
|
|
|
|
|
html += `
|
|
|
|
|
|
<div style="display:flex;align-items:center;margin-bottom:2px;">
|
|
|
|
|
|
<span style="display:inline-block;width:10px;height:10px;background:${p.color};margin-right:8px;border-radius:2px;"></span>
|
|
|
|
|
|
<span style="color:#B9D6D9;margin-right:6px;">${p.seriesName}</span>
|
2026-01-24 17:14:53 +08:00
|
|
|
|
<span style="color:#fff;margin-left:auto;font-weight:bold;">${p.value}%</span>
|
2026-01-21 10:34:06 +08:00
|
|
|
|
</div>`;
|
|
|
|
|
|
});
|
|
|
|
|
|
return html;
|
|
|
|
|
|
}
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
2026-01-24 17:14:53 +08:00
|
|
|
|
data: ['计划进度', '实际进度'],
|
2026-01-21 10:34:06 +08:00
|
|
|
|
top: 8,
|
|
|
|
|
|
right: 20,
|
|
|
|
|
|
textStyle: { color: '#fff', fontSize: 12 },
|
|
|
|
|
|
itemWidth: 12,
|
|
|
|
|
|
itemHeight: 8,
|
|
|
|
|
|
itemGap: 15
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
grid: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
top: '20%',
|
|
|
|
|
|
left: '8%',
|
|
|
|
|
|
right: '6%',
|
|
|
|
|
|
bottom: '15%'
|
|
|
|
|
|
},
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
2026-01-24 17:14:53 +08:00
|
|
|
|
data: [],
|
|
|
|
|
|
axisLabel: { color: '#fff', fontSize: 10, rotate: 0 },
|
2026-01-21 10:34:06 +08:00
|
|
|
|
axisLine: { lineStyle: { color: '#5A6E71' } },
|
|
|
|
|
|
axisTick: { show: false }
|
|
|
|
|
|
},
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
min: 0,
|
2026-01-24 17:14:53 +08:00
|
|
|
|
max: 100,
|
|
|
|
|
|
axisLabel: { color: '#fff', fontSize: 10, formatter: '{value}%' },
|
2026-01-21 10:34:06 +08:00
|
|
|
|
axisLine: { lineStyle: { color: '#5A6E71' } },
|
2026-01-24 17:14:53 +08:00
|
|
|
|
splitLine: {
|
|
|
|
|
|
lineStyle: { color: 'rgba(90, 110, 113, 0.3)', type: 'dashed' }
|
2026-01-21 10:34:06 +08:00
|
|
|
|
}
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
2025-10-18 16:58:04 +08:00
|
|
|
|
series: [
|
|
|
|
|
|
{
|
2026-01-24 17:14:53 +08:00
|
|
|
|
name: '计划进度',
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: [],
|
|
|
|
|
|
itemStyle: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
2026-01-24 17:14:53 +08:00
|
|
|
|
{ offset: 0, color: '#4A90E2' },
|
|
|
|
|
|
{ offset: 1, color: '#357ABD' }
|
2026-01-21 10:34:06 +08:00
|
|
|
|
])
|
2026-01-24 17:14:53 +08:00
|
|
|
|
},
|
2026-01-24 18:55:45 +08:00
|
|
|
|
barWidth: '30'
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
2025-10-18 16:58:04 +08:00
|
|
|
|
{
|
2026-01-24 17:14:53 +08:00
|
|
|
|
name: '实际进度',
|
|
|
|
|
|
type: 'bar',
|
|
|
|
|
|
data: [],
|
|
|
|
|
|
itemStyle: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
2026-01-24 17:14:53 +08:00
|
|
|
|
{ offset: 0, color: '#1CFFA3' },
|
|
|
|
|
|
{ offset: 1, color: '#19CC8A' }
|
2026-01-21 10:34:06 +08:00
|
|
|
|
])
|
2026-01-24 17:14:53 +08:00
|
|
|
|
},
|
2026-01-24 18:55:45 +08:00
|
|
|
|
barWidth: '30'
|
2026-01-21 10:34:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
2025-10-17 14:57:09 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
electricalSpecialtyChart.setOption(option);
|
2025-10-17 14:57:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-24 17:14:53 +08:00
|
|
|
|
// 更新土建和电气专业图表数据
|
|
|
|
|
|
function updateSpecialtyCharts(data) {
|
|
|
|
|
|
if (!data) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新土建专业图表
|
|
|
|
|
|
if (civilSpecialtyChart) {
|
|
|
|
|
|
const bdGx = data.bdGx || [];
|
|
|
|
|
|
const bdPlanProgress = (data.bdPlanProgress || []).map(val => parseFloat(val) || 0);
|
|
|
|
|
|
const bdProgress = (data.bdProgress || []).map(val => parseFloat(val) || 0);
|
|
|
|
|
|
|
|
|
|
|
|
civilSpecialtyChart.setOption({
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
data: bdGx
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '计划进度',
|
|
|
|
|
|
data: bdPlanProgress
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '实际进度',
|
|
|
|
|
|
data: bdProgress
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新电气专业图表
|
|
|
|
|
|
if (electricalSpecialtyChart) {
|
|
|
|
|
|
const dqGx = data.dqGx || [];
|
|
|
|
|
|
const dqPlanProgress = (data.dqPlanProgress || []).map(val => parseFloat(val) || 0);
|
|
|
|
|
|
const dqProgress = (data.dqProgress || []).map(val => parseFloat(val) || 0);
|
|
|
|
|
|
|
|
|
|
|
|
electricalSpecialtyChart.setOption({
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
data: dqGx
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '计划进度',
|
|
|
|
|
|
data: dqPlanProgress
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
name: '实际进度',
|
|
|
|
|
|
data: dqProgress
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
// 初始化项目支出趋势图表
|
|
|
|
|
|
function initExpenditureTrendChart() {
|
|
|
|
|
|
expenditureTrendChart = echarts.init(document.getElementById('expenditureTrendChart'));
|
2026-01-24 17:14:53 +08:00
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
const dates = ['XXXX-XX-01', 'XXXX-XX-02', 'XXXX-XX-03', 'XXXX-XX-04', 'XXXX-XX-05', 'XXXX-XX-06', 'XXXX-XX-07', 'XXXX-XX-08'];
|
|
|
|
|
|
const expenditure = [48, 45, 42, 32, 35, 38, 30, 28];
|
2025-10-17 14:57:09 +08:00
|
|
|
|
|
2025-10-18 16:58:04 +08:00
|
|
|
|
const option = {
|
2025-10-17 14:57:09 +08:00
|
|
|
|
tooltip: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
trigger: 'axis',
|
|
|
|
|
|
axisPointer: { type: 'line' },
|
|
|
|
|
|
backgroundColor: 'rgba(19, 51, 55, 0.9)',
|
|
|
|
|
|
borderColor: 'rgba(0, 254, 252, 0.5)',
|
2025-10-17 14:57:09 +08:00
|
|
|
|
borderWidth: 1,
|
2026-01-21 10:34:06 +08:00
|
|
|
|
textStyle: { color: '#fff' }
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
grid: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
top: '15%',
|
|
|
|
|
|
left: '8%',
|
|
|
|
|
|
right: '6%',
|
|
|
|
|
|
bottom: '15%'
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
2026-01-21 10:34:06 +08:00
|
|
|
|
xAxis: {
|
|
|
|
|
|
type: 'category',
|
|
|
|
|
|
data: dates,
|
|
|
|
|
|
axisLabel: { color: '#fff', fontSize: 11 },
|
|
|
|
|
|
axisLine: { lineStyle: { color: '#5A6E71' } },
|
|
|
|
|
|
axisTick: { show: false }
|
|
|
|
|
|
},
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
type: 'value',
|
|
|
|
|
|
min: 0,
|
|
|
|
|
|
max: 60,
|
|
|
|
|
|
interval: 10,
|
|
|
|
|
|
axisLabel: { color: '#fff', fontSize: 11 },
|
|
|
|
|
|
axisLine: { lineStyle: { color: '#5A6E71' } },
|
2026-01-24 17:14:53 +08:00
|
|
|
|
splitLine: {
|
|
|
|
|
|
lineStyle: { color: 'rgba(90, 110, 113, 0.3)', type: 'dashed' }
|
2026-01-21 10:34:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
2025-10-18 16:58:04 +08:00
|
|
|
|
{
|
2026-01-21 10:34:06 +08:00
|
|
|
|
type: 'line',
|
|
|
|
|
|
data: expenditure,
|
|
|
|
|
|
smooth: true,
|
|
|
|
|
|
lineStyle: { width: 2, color: '#00FEFC' },
|
|
|
|
|
|
itemStyle: { color: '#00FEFC' },
|
|
|
|
|
|
areaStyle: {
|
|
|
|
|
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
|
|
|
{ offset: 0, color: 'rgba(0, 254, 252, 0.4)' },
|
|
|
|
|
|
{ offset: 1, color: 'rgba(0, 254, 252, 0.05)' }
|
|
|
|
|
|
])
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
};
|
2025-10-21 13:38:20 +08:00
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
expenditureTrendChart.setOption(option);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化项目支出占比图表(环形图)
|
|
|
|
|
|
function initExpenditureProportionChart() {
|
|
|
|
|
|
expenditureProportionChart = echarts.init(document.getElementById('expenditureProportionChart'));
|
2026-01-24 17:14:53 +08:00
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
const option = {
|
|
|
|
|
|
tooltip: {
|
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
|
backgroundColor: 'rgba(19, 51, 55, 0.9)',
|
|
|
|
|
|
borderColor: 'rgba(0, 254, 252, 0.5)',
|
|
|
|
|
|
borderWidth: 1,
|
|
|
|
|
|
textStyle: { color: '#fff' },
|
|
|
|
|
|
formatter: '{b}: {d}%'
|
|
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
|
|
|
|
|
bottom: 10,
|
|
|
|
|
|
left: 'center',
|
|
|
|
|
|
textStyle: { color: '#fff', fontSize: 12 },
|
|
|
|
|
|
itemWidth: 12,
|
|
|
|
|
|
itemHeight: 8,
|
|
|
|
|
|
itemGap: 15
|
|
|
|
|
|
},
|
2025-10-18 16:58:04 +08:00
|
|
|
|
series: [
|
|
|
|
|
|
{
|
2026-01-21 10:34:06 +08:00
|
|
|
|
name: '支出占比',
|
|
|
|
|
|
type: 'pie',
|
|
|
|
|
|
radius: ['50%', '70%'],
|
|
|
|
|
|
center: ['50%', '45%'],
|
|
|
|
|
|
avoidLabelOverlap: false,
|
2025-10-18 16:58:04 +08:00
|
|
|
|
itemStyle: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
borderRadius: 5,
|
|
|
|
|
|
borderColor: 'rgba(13, 34, 37, 0.8)',
|
|
|
|
|
|
borderWidth: 2
|
2025-10-18 16:58:04 +08:00
|
|
|
|
},
|
2026-01-21 10:34:06 +08:00
|
|
|
|
label: {
|
2025-10-18 16:58:04 +08:00
|
|
|
|
show: true,
|
2026-01-21 10:34:06 +08:00
|
|
|
|
formatter: '{b}\n{d}%',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
fontWeight: 'bold'
|
2025-10-18 16:58:04 +08:00
|
|
|
|
},
|
2026-01-21 10:34:06 +08:00
|
|
|
|
emphasis: {
|
|
|
|
|
|
label: {
|
|
|
|
|
|
show: true,
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
fontWeight: 'bold'
|
|
|
|
|
|
}
|
2025-10-18 16:58:04 +08:00
|
|
|
|
},
|
2026-01-21 10:34:06 +08:00
|
|
|
|
data: [
|
2026-01-24 17:14:53 +08:00
|
|
|
|
{ value: 0, name: '支出', itemStyle: { color: '#00FEFC' } },
|
|
|
|
|
|
{ value: 0, name: '预算剩余', itemStyle: { color: '#FFFFFF' } }
|
2026-01-21 10:34:06 +08:00
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2025-10-17 14:57:09 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
expenditureProportionChart.setOption(option);
|
2025-10-17 14:57:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-24 17:14:53 +08:00
|
|
|
|
// 更新项目支出趋势图表
|
|
|
|
|
|
function updateExpenditureTrendChart(data) {
|
|
|
|
|
|
if (!expenditureTrendChart || !data) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化时间列表:从 "2026-01-01" 转换为 "01-01"
|
|
|
|
|
|
const timeList = (data.timeList || []).map(dateStr => {
|
|
|
|
|
|
if (!dateStr) return '';
|
|
|
|
|
|
const parts = dateStr.split('-');
|
|
|
|
|
|
if (parts.length >= 2) {
|
|
|
|
|
|
return parts[1] + '-' + parts[2];
|
|
|
|
|
|
}
|
|
|
|
|
|
return dateStr;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 转换数值列表为数字
|
|
|
|
|
|
const valueList = (data.valueList || []).map(val => parseFloat(val) || 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 确保两个数组长度一致,取最小长度
|
|
|
|
|
|
const minLength = Math.min(timeList.length, valueList.length);
|
|
|
|
|
|
const finalTimeList = timeList.slice(0, minLength);
|
|
|
|
|
|
const finalValueList = valueList.slice(0, minLength);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算最大值,用于设置y轴范围
|
|
|
|
|
|
const maxValue = finalValueList.length > 0 ? Math.max(...finalValueList, 0) : 0;
|
|
|
|
|
|
const yAxisMax = maxValue > 0 ? Math.ceil(maxValue * 1.2) : 100;
|
|
|
|
|
|
|
|
|
|
|
|
expenditureTrendChart.setOption({
|
|
|
|
|
|
xAxis: {
|
|
|
|
|
|
data: finalTimeList
|
|
|
|
|
|
},
|
|
|
|
|
|
yAxis: {
|
|
|
|
|
|
max: yAxisMax,
|
|
|
|
|
|
interval: Math.ceil(yAxisMax / 5)
|
|
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
data: finalValueList
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新项目支出占比图表
|
|
|
|
|
|
function updateExpenditureProportionChart(data) {
|
|
|
|
|
|
if (!expenditureProportionChart || !data) return;
|
|
|
|
|
|
|
|
|
|
|
|
// allCost 是预算,proCount 是支出
|
2026-01-24 18:55:45 +08:00
|
|
|
|
const budget = parseFloat(data.proCount || 0);
|
|
|
|
|
|
const expenditure = parseFloat(data.allCost || 0);
|
2026-01-24 17:14:53 +08:00
|
|
|
|
const remaining = budget - expenditure;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算百分比
|
|
|
|
|
|
let expenditurePercent = 0;
|
|
|
|
|
|
let remainingPercent = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (budget > 0) {
|
|
|
|
|
|
expenditurePercent = (expenditure / budget * 100).toFixed(1);
|
|
|
|
|
|
remainingPercent = (remaining / budget * 100).toFixed(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
expenditureProportionChart.setOption({
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
data: [
|
|
|
|
|
|
{
|
|
|
|
|
|
value: parseFloat(expenditurePercent),
|
|
|
|
|
|
name: '支出',
|
|
|
|
|
|
itemStyle: { color: '#00FEFC' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
value: parseFloat(remainingPercent),
|
|
|
|
|
|
name: '预算剩余',
|
|
|
|
|
|
itemStyle: { color: '#FFFFFF' }
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
// 初始化项目风险图表
|
|
|
|
|
|
function initProjectRiskChart() {
|
|
|
|
|
|
projectRiskChart = echarts.init(document.getElementById('projectRiskChart'));
|
2026-01-24 17:14:53 +08:00
|
|
|
|
|
2025-10-17 14:57:09 +08:00
|
|
|
|
const option = {
|
|
|
|
|
|
tooltip: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
trigger: 'item',
|
|
|
|
|
|
backgroundColor: 'rgba(19, 51, 55, 0.9)',
|
|
|
|
|
|
borderColor: 'rgba(0, 254, 252, 0.5)',
|
2025-10-17 14:57:09 +08:00
|
|
|
|
borderWidth: 1,
|
2026-01-21 10:34:06 +08:00
|
|
|
|
textStyle: { color: '#fff' },
|
2026-01-24 17:14:53 +08:00
|
|
|
|
formatter: function (params) {
|
|
|
|
|
|
// params.data 包含数据项的所有信息,包括自定义属性
|
|
|
|
|
|
const actualCount = params.data.actualCount || 0;
|
|
|
|
|
|
const percent = params.percent || 0;
|
|
|
|
|
|
return params.name + ': ' + actualCount + '个 (' + percent.toFixed(1) + '%)';
|
|
|
|
|
|
}
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
legend: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
orient: 'vertical',
|
|
|
|
|
|
right: 10,
|
|
|
|
|
|
top: 'center',
|
|
|
|
|
|
textStyle: { color: '#fff', fontSize: 12 },
|
|
|
|
|
|
itemWidth: 12,
|
2025-10-17 14:57:09 +08:00
|
|
|
|
itemHeight: 8,
|
2026-01-21 10:34:06 +08:00
|
|
|
|
itemGap: 10,
|
2026-01-24 17:14:53 +08:00
|
|
|
|
formatter: function (name) {
|
|
|
|
|
|
// 获取当前图表的数据
|
|
|
|
|
|
const currentOption = projectRiskChart.getOption();
|
|
|
|
|
|
const seriesData = currentOption.series && currentOption.series[0] && currentOption.series[0].data;
|
|
|
|
|
|
if (seriesData) {
|
|
|
|
|
|
const item = seriesData.find(d => d.name === name);
|
|
|
|
|
|
if (item) {
|
|
|
|
|
|
const actualCount = item.actualCount || 0;
|
|
|
|
|
|
const percent = item.value || 0;
|
|
|
|
|
|
return name + ' ' + actualCount + '个 ' + percent.toFixed(1) + '%';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return name;
|
2026-01-21 10:34:06 +08:00
|
|
|
|
}
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
2026-01-21 10:34:06 +08:00
|
|
|
|
name: '项目风险',
|
|
|
|
|
|
type: 'pie',
|
2026-01-24 17:14:53 +08:00
|
|
|
|
roseType: 'area',
|
|
|
|
|
|
radius: ['10%', '70%'],
|
|
|
|
|
|
center: ['35%', '45%'],
|
2025-10-17 14:57:09 +08:00
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
borderRadius: 5,
|
2026-01-21 10:34:06 +08:00
|
|
|
|
borderColor: 'rgba(13, 34, 37, 0.8)',
|
|
|
|
|
|
borderWidth: 2
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
label: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
show: false
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
emphasis: {
|
|
|
|
|
|
label: {
|
2026-01-21 10:34:06 +08:00
|
|
|
|
show: true,
|
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
|
fontWeight: 'bold',
|
|
|
|
|
|
color: '#fff'
|
2026-01-24 17:14:53 +08:00
|
|
|
|
},
|
|
|
|
|
|
itemStyle: {
|
|
|
|
|
|
shadowBlur: 10,
|
|
|
|
|
|
shadowOffsetX: 0,
|
|
|
|
|
|
shadowColor: 'rgba(0, 254, 252, 0.5)'
|
2026-01-21 10:34:06 +08:00
|
|
|
|
}
|
2025-10-17 14:57:09 +08:00
|
|
|
|
},
|
2026-01-21 10:34:06 +08:00
|
|
|
|
data: [
|
2026-01-24 17:14:53 +08:00
|
|
|
|
{ value: 0, actualCount: 0, name: '二级风险', itemStyle: { color: '#4A90E2' } },
|
|
|
|
|
|
{ value: 0, actualCount: 0, name: '三级风险', itemStyle: { color: '#1CFFA3' } },
|
|
|
|
|
|
{ value: 0, actualCount: 0, name: '四级风险', itemStyle: { color: '#00FEFC' } },
|
|
|
|
|
|
{ value: 0, actualCount: 0, name: '五级风险', itemStyle: { color: '#FF9C65' } }
|
2026-01-21 10:34:06 +08:00
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2025-10-17 14:57:09 +08:00
|
|
|
|
};
|
2026-01-21 10:34:06 +08:00
|
|
|
|
|
|
|
|
|
|
projectRiskChart.setOption(option);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-24 17:14:53 +08:00
|
|
|
|
// 更新项目风险图表
|
|
|
|
|
|
function updateProjectRiskChart(data) {
|
|
|
|
|
|
if (!projectRiskChart || !data) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 获取各风险等级的数量
|
|
|
|
|
|
const twoRisk = parseInt(data.twoRisk || 0);
|
|
|
|
|
|
const threeRisk = parseInt(data.threeRisk || 0);
|
|
|
|
|
|
const fourRisk = parseInt(data.fourRisk || 0);
|
|
|
|
|
|
const fiveRisk = parseInt(data.fiveRisk || 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算总风险数量(用于显示)
|
|
|
|
|
|
const allRisk = parseInt(data.allRisk || 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 计算各风险等级的总数(用于计算百分比)
|
|
|
|
|
|
const totalRiskCount = twoRisk + threeRisk + fourRisk + fiveRisk;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算百分比
|
|
|
|
|
|
let twoRiskPercent = 0;
|
|
|
|
|
|
let threeRiskPercent = 0;
|
|
|
|
|
|
let fourRiskPercent = 0;
|
|
|
|
|
|
let fiveRiskPercent = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if (totalRiskCount > 0) {
|
|
|
|
|
|
twoRiskPercent = (twoRisk / totalRiskCount * 100).toFixed(1);
|
|
|
|
|
|
threeRiskPercent = (threeRisk / totalRiskCount * 100).toFixed(1);
|
|
|
|
|
|
fourRiskPercent = (fourRisk / totalRiskCount * 100).toFixed(1);
|
|
|
|
|
|
fiveRiskPercent = (fiveRisk / totalRiskCount * 100).toFixed(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图表数据,同时保存实际数量和百分比
|
|
|
|
|
|
projectRiskChart.setOption({
|
|
|
|
|
|
series: [
|
|
|
|
|
|
{
|
|
|
|
|
|
data: [
|
|
|
|
|
|
{
|
|
|
|
|
|
value: parseFloat(twoRiskPercent),
|
|
|
|
|
|
actualCount: twoRisk,
|
|
|
|
|
|
name: '二级风险',
|
|
|
|
|
|
itemStyle: { color: '#4A90E2' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
value: parseFloat(threeRiskPercent),
|
|
|
|
|
|
actualCount: threeRisk,
|
|
|
|
|
|
name: '三级风险',
|
|
|
|
|
|
itemStyle: { color: '#1CFFA3' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
value: parseFloat(fourRiskPercent),
|
|
|
|
|
|
actualCount: fourRisk,
|
|
|
|
|
|
name: '四级风险',
|
|
|
|
|
|
itemStyle: { color: '#00FEFC' }
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
value: parseFloat(fiveRiskPercent),
|
|
|
|
|
|
actualCount: fiveRisk,
|
|
|
|
|
|
name: '五级风险',
|
|
|
|
|
|
itemStyle: { color: '#FF9C65' }
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
2026-01-24 18:55:45 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
],
|
|
|
|
|
|
graphic: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'text',
|
|
|
|
|
|
left: '60%',
|
|
|
|
|
|
top: '5%',
|
|
|
|
|
|
style: {
|
|
|
|
|
|
text: '总风险数量 ' + allRisk + '个',
|
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
|
fill: '#fff',
|
|
|
|
|
|
fontSize: 16
|
|
|
|
|
|
}
|
2026-01-24 17:14:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 更新总风险数量显示
|
|
|
|
|
|
$('#totalRiskDisplay').text('总风险数量:' + allRisk + '个');
|
|
|
|
|
|
|
|
|
|
|
|
// 更新右侧统计数据
|
|
|
|
|
|
const xhRisk = data.xhRisk || '0';
|
|
|
|
|
|
const lastRiskNum = data.lastRiskNum || '0';
|
|
|
|
|
|
const lastTwoRisk = data.lastTwoRisk || '0';
|
|
|
|
|
|
|
|
|
|
|
|
$('#closedRisk').text(xhRisk);
|
|
|
|
|
|
$('#remainingRisk').text(lastRiskNum);
|
|
|
|
|
|
$('#remainingLevel2Risk').text(lastTwoRisk);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
// 初始化分析预警表格
|
|
|
|
|
|
function initWarningTable() {
|
2026-01-24 17:14:53 +08:00
|
|
|
|
// 初始化时调用接口获取数据
|
|
|
|
|
|
getAnalysisWarning(currentWarningFilter);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新分析预警表格数据
|
|
|
|
|
|
function updateWarningTable(data) {
|
|
|
|
|
|
if (!data || !Array.isArray(data)) {
|
|
|
|
|
|
$('#warningTableBody').html('<tr><td colspan="5" style="text-align:center;color:rgba(255,255,255,0.5);">暂无预警</td></tr>');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
renderWarningTable(data);
|
2026-01-21 10:34:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染分析预警表格
|
|
|
|
|
|
function renderWarningTable(warnings) {
|
|
|
|
|
|
const tbody = $('#warningTableBody');
|
|
|
|
|
|
tbody.empty();
|
2026-01-24 17:14:53 +08:00
|
|
|
|
|
|
|
|
|
|
if (!warnings || warnings.length === 0) {
|
|
|
|
|
|
tbody.html('<tr><td colspan="5" style="text-align:center;color:rgba(255,255,255,0.5);">暂无预警</td></tr>');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
warnings.forEach((warning, index) => {
|
|
|
|
|
|
// 根据接口返回的字段映射
|
|
|
|
|
|
const id = (index + 1).toString().padStart(2, '0');
|
|
|
|
|
|
const time = warning.txTime || warning.warnTime || warning.time || '';
|
|
|
|
|
|
const type = warning.txType || warning.warnType || warning.type || '';
|
|
|
|
|
|
const content = warning.content || warning.warnContent || '';
|
|
|
|
|
|
const action = warning.action || '制定策略';
|
|
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
const row = `
|
|
|
|
|
|
<tr>
|
2026-01-24 17:14:53 +08:00
|
|
|
|
<td>${id}</td>
|
|
|
|
|
|
<td>${time}</td>
|
|
|
|
|
|
<td>${type}</td>
|
|
|
|
|
|
<td>${content}</td>
|
|
|
|
|
|
<td><span class="warning-action">${action}</span></td>
|
2026-01-21 10:34:06 +08:00
|
|
|
|
</tr>
|
|
|
|
|
|
`;
|
|
|
|
|
|
tbody.append(row);
|
2025-10-17 14:57:09 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-01-21 10:34:06 +08:00
|
|
|
|
|
|
|
|
|
|
// 筛选分析预警
|
|
|
|
|
|
let currentWarningFilter = 'all';
|
|
|
|
|
|
function filterWarning(type) {
|
|
|
|
|
|
currentWarningFilter = type;
|
2026-01-24 17:14:53 +08:00
|
|
|
|
|
2026-01-21 10:34:06 +08:00
|
|
|
|
// 更新标签状态
|
|
|
|
|
|
$('.warning-tab').removeClass('active');
|
|
|
|
|
|
$(`.warning-tab[data-type="${type}"]`).addClass('active');
|
2026-01-24 17:14:53 +08:00
|
|
|
|
|
|
|
|
|
|
// 重新调用接口获取筛选后的数据
|
|
|
|
|
|
getAnalysisWarning(type);
|
2026-01-21 10:34:06 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新图表
|
|
|
|
|
|
function updateCharts() {
|
|
|
|
|
|
// 这里可以重新加载图表数据
|
|
|
|
|
|
// 实际应该从API获取数据并更新图表
|
|
|
|
|
|
if (riskAnalysisChart) riskAnalysisChart.resize();
|
|
|
|
|
|
if (civilSpecialtyChart) civilSpecialtyChart.resize();
|
|
|
|
|
|
if (electricalSpecialtyChart) electricalSpecialtyChart.resize();
|
|
|
|
|
|
if (expenditureTrendChart) expenditureTrendChart.resize();
|
|
|
|
|
|
if (expenditureProportionChart) expenditureProportionChart.resize();
|
|
|
|
|
|
if (projectRiskChart) projectRiskChart.resize();
|
|
|
|
|
|
}
|