// 项目管理分析页面
let layer;
let expenditureTrendChart = null;
let expenditureProportionChart = null;
let projectRiskChart = null;
let riskAnalysisChart = null;
let civilSpecialtyChart = null;
let electricalSpecialtyChart = null;
let bidCode = parent.parent.$('#bidPro').val();
// 获取当天日期
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;
}
// 获取当月第一天和最后一天(保留用于其他功能)
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 };
}
const today = getTodayDate();
let queryParams = {
projectId: bidCode,
startTime: today,
endTime: today,
};
// 调试信息:确认日期范围已设置为当天
console.log('日期范围已设置为当天:', queryParams.startTime, '~', queryParams.endTime);
// 页面初始化
$(document).ready(function () {
// 等待layui加载完成
layui.use(['laydate', 'layer'], function () {
layer = layui.layer;
initDateRange();
// 初始化项目信息模块的图表
initRiskAnalysisChart();
initCivilSpecialtyChart();
initElectricalSpecialtyChart();
// 初始化其他模块的图表
initExpenditureTrendChart();
initExpenditureProportionChart();
initProjectRiskChart();
initWarningTable();
initFileUploadHandler();
// 页面初始化时,按默认当天日期加载一次接口数据
refreshAllModules();
// 窗口大小改变时重新调整图表
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));
});
});
// 防抖函数
function debounce(fn, delay) {
let t = null;
return function () {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, arguments), delay);
};
}
// 获取左上角工序进度
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) {
updateRiskAnalysisChart(result.data);
} 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);
}
// 初始化日期范围选择器(逻辑与工程质量分析保持一致)
function initDateRange() {
const laydate = layui.laydate;
// 初始显示为当天范围
$('#dateRange').val(queryParams.startTime + ' ~ ' + queryParams.endTime);
laydate.render({
elem: '#dateRange',
type: 'date',
range: true,
format: 'yyyy-MM-dd',
theme: 'dark',
// laydate 内部使用 "起始 - 结束" 作为分隔符
value: queryParams.startTime + ' - ' + queryParams.endTime,
done: function (value) {
const resetToToday = function () {
const today = getTodayDate();
queryParams.startTime = today;
queryParams.endTime = today;
$('#dateRange').val(today + ' ~ ' + today);
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 {
resetToToday();
}
} else {
resetToToday();
}
}
});
}
// 格式化日期
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;
}
// 查询数据
function queryData() {
const dateRange = $('#dateRange').val();
if (!dateRange) {
layui.layer.msg('请选择日期范围', { icon: 0 });
return;
}
// 点击查询按钮时,基于当前日期范围刷新接口数据
refreshAllModules();
layui.layer.msg('查询成功', { icon: 1, time: 1000 });
}
// 加载数据
function loadData(dateRange) {
// 模拟数据加载
// 实际应该调用API
updateCharts();
}
// 初始化风险分析饼图
function initRiskAnalysisChart() {
riskAnalysisChart = echarts.init(document.getElementById('riskAnalysisChart'));
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}: {c}个 ({d}%)'
},
legend: {
orient: 'vertical',
right: 10,
top: 'center',
textStyle: { color: '#fff', fontSize: 12 },
itemWidth: 12,
itemHeight: 8,
itemGap: 10,
formatter: function (name) {
// 从图表实例中动态获取最新数据
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';
}
},
series: [
{
name: '风险分析',
type: 'pie',
radius: ['40%', '70%'],
center: ['35%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 5,
borderColor: 'rgba(13, 34, 37, 0.8)',
borderWidth: 2
},
label: {
show: false
},
emphasis: {
label: {
show: true,
fontSize: 14,
fontWeight: 'bold',
color: '#fff'
}
},
data: [
{ value: 0, name: '二级风险', itemStyle: { color: '#4A90E2' } },
{ value: 0, name: '三级风险', itemStyle: { color: '#1CFFA3' } },
{ value: 0, name: '四级风险', itemStyle: { color: '#00FEFC' } },
{ value: 0, name: '五级风险', itemStyle: { color: '#FF9C65' } }
]
}
],
graphic: [
{
type: 'text',
left: '50%',
top: '5%',
style: {
text: '今日风险数量 0个',
textAlign: 'center',
fill: '#fff',
fontSize: 13
}
}
]
};
riskAnalysisChart.setOption(option);
}
// 更新风险分析饼图数据
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);
}
// 更新图表数据
riskAnalysisChart.setOption({
series: [
{
data: [
{ value: twoRisk, name: '二级风险', itemStyle: { color: '#4A90E2' } },
{ value: threeRisk, name: '三级风险', itemStyle: { color: '#1CFFA3' } },
{ value: fourRisk, name: '四级风险', itemStyle: { color: '#00FEFC' } },
{ value: fiveRisk, name: '五级风险', itemStyle: { color: '#FF9C65' } }
]
}
],
graphic: [
{
type: 'text',
left: '60%',
top: '2%',
style: {
text: '今日风险数量 ' + totalRisk + '个',
textAlign: 'center',
fill: '#fff',
fontSize: 13
}
}
]
});
}
// 初始化土建专业双柱状图
function initCivilSpecialtyChart() {
civilSpecialtyChart = echarts.init(document.getElementById('civilSpecialtyChart'));
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
backgroundColor: 'rgba(19, 51, 55, 0.9)',
borderColor: 'rgba(0, 254, 252, 0.5)',
borderWidth: 1,
textStyle: { color: '#fff' },
formatter: function (params) {
if (!params || !params.length) return '';
let html = `
${params[0].name}
`;
params.forEach(p => {
html += `
${p.seriesName}
${p.value}%
`;
});
return html;
}
},
legend: {
data: ['计划进度', '实际进度'],
top: 8,
right: 20,
textStyle: { color: '#fff', fontSize: 12 },
itemWidth: 12,
itemHeight: 8,
itemGap: 15
},
grid: {
top: '20%',
left: '8%',
right: '6%',
bottom: '15%'
},
xAxis: {
type: 'category',
data: [],
axisLabel: { color: '#fff', fontSize: 10, rotate: 0 },
axisLine: { lineStyle: { color: '#5A6E71' } },
axisTick: { show: false }
},
yAxis: {
type: 'value',
min: 0,
max: 100,
axisLabel: { color: '#fff', fontSize: 10, formatter: '{value}%' },
axisLine: { lineStyle: { color: '#5A6E71' } },
splitLine: {
lineStyle: { color: 'rgba(90, 110, 113, 0.3)', type: 'dashed' }
}
},
series: [
{
name: '计划进度',
type: 'bar',
data: [],
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#4A90E2' },
{ offset: 1, color: '#357ABD' }
])
},
barWidth: '30'
},
{
name: '实际进度',
type: 'bar',
data: [],
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#1CFFA3' },
{ offset: 1, color: '#19CC8A' }
])
},
barWidth: '30'
}
]
};
civilSpecialtyChart.setOption(option);
}
// 初始化电气专业双柱状图
function initElectricalSpecialtyChart() {
electricalSpecialtyChart = echarts.init(document.getElementById('electricalSpecialtyChart'));
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
backgroundColor: 'rgba(19, 51, 55, 0.9)',
borderColor: 'rgba(0, 254, 252, 0.5)',
borderWidth: 1,
textStyle: { color: '#fff' },
formatter: function (params) {
if (!params || !params.length) return '';
let html = `${params[0].name}
`;
params.forEach(p => {
html += `
${p.seriesName}
${p.value}%
`;
});
return html;
}
},
legend: {
data: ['计划进度', '实际进度'],
top: 8,
right: 20,
textStyle: { color: '#fff', fontSize: 12 },
itemWidth: 12,
itemHeight: 8,
itemGap: 15
},
grid: {
top: '20%',
left: '8%',
right: '6%',
bottom: '15%'
},
xAxis: {
type: 'category',
data: [],
axisLabel: { color: '#fff', fontSize: 10, rotate: 0 },
axisLine: { lineStyle: { color: '#5A6E71' } },
axisTick: { show: false }
},
yAxis: {
type: 'value',
min: 0,
max: 100,
axisLabel: { color: '#fff', fontSize: 10, formatter: '{value}%' },
axisLine: { lineStyle: { color: '#5A6E71' } },
splitLine: {
lineStyle: { color: 'rgba(90, 110, 113, 0.3)', type: 'dashed' }
}
},
series: [
{
name: '计划进度',
type: 'bar',
data: [],
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#4A90E2' },
{ offset: 1, color: '#357ABD' }
])
},
barWidth: '30'
},
{
name: '实际进度',
type: 'bar',
data: [],
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#1CFFA3' },
{ offset: 1, color: '#19CC8A' }
])
},
barWidth: '30'
}
]
};
electricalSpecialtyChart.setOption(option);
}
// 更新土建和电气专业图表数据
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
}
]
});
}
}
// 初始化项目支出趋势图表
function initExpenditureTrendChart() {
expenditureTrendChart = echarts.init(document.getElementById('expenditureTrendChart'));
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];
const option = {
tooltip: {
trigger: 'axis',
axisPointer: { type: 'line' },
backgroundColor: 'rgba(19, 51, 55, 0.9)',
borderColor: 'rgba(0, 254, 252, 0.5)',
borderWidth: 1,
textStyle: { color: '#fff' }
},
grid: {
top: '15%',
left: '8%',
right: '6%',
bottom: '15%'
},
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' } },
splitLine: {
lineStyle: { color: 'rgba(90, 110, 113, 0.3)', type: 'dashed' }
}
},
series: [
{
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)' }
])
}
}
]
};
expenditureTrendChart.setOption(option);
}
// 初始化项目支出占比图表(环形图)
function initExpenditureProportionChart() {
expenditureProportionChart = echarts.init(document.getElementById('expenditureProportionChart'));
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
},
series: [
{
name: '支出占比',
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '45%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 5,
borderColor: 'rgba(13, 34, 37, 0.8)',
borderWidth: 2
},
label: {
show: true,
formatter: '{b}\n{d}%',
color: '#fff',
fontSize: 12,
fontWeight: 'bold'
},
emphasis: {
label: {
show: true,
fontSize: 14,
fontWeight: 'bold'
}
},
data: [
{ value: 0, name: '支出', itemStyle: { color: '#00FEFC' } },
{ value: 0, name: '预算剩余', itemStyle: { color: '#FFFFFF' } }
]
}
]
};
expenditureProportionChart.setOption(option);
}
// 更新项目支出趋势图表
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 是支出
const budget = parseFloat(data.proCount || 0);
const expenditure = parseFloat(data.allCost || 0);
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' }
}
]
}
]
});
}
// 初始化项目风险图表
function initProjectRiskChart() {
projectRiskChart = echarts.init(document.getElementById('projectRiskChart'));
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: function (params) {
// params.data 包含数据项的所有信息,包括自定义属性
const actualCount = params.data.actualCount || 0;
const percent = params.percent || 0;
return params.name + ': ' + actualCount + '个 (' + percent.toFixed(1) + '%)';
}
},
legend: {
orient: 'vertical',
right: 10,
top: 'center',
textStyle: { color: '#fff', fontSize: 12 },
itemWidth: 12,
itemHeight: 8,
itemGap: 10,
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;
}
},
series: [
{
name: '项目风险',
type: 'pie',
roseType: 'area',
radius: ['10%', '70%'],
center: ['35%', '45%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 5,
borderColor: 'rgba(13, 34, 37, 0.8)',
borderWidth: 2
},
label: {
show: false
},
emphasis: {
label: {
show: true,
fontSize: 14,
fontWeight: 'bold',
color: '#fff'
},
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 254, 252, 0.5)'
}
},
data: [
{ 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' } }
]
}
]
};
projectRiskChart.setOption(option);
}
// 更新项目风险图表
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' }
}
]
},
],
graphic: [
{
type: 'text',
left: '60%',
top: '5%',
style: {
text: '总风险数量 ' + allRisk + '个',
textAlign: 'center',
fill: '#fff',
fontSize: 16
}
}
]
});
// 更新总风险数量显示
$('#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);
}
// 初始化分析预警表格
function initWarningTable() {
// 初始化时调用接口获取数据
getAnalysisWarning(currentWarningFilter);
}
// 更新分析预警表格数据
function updateWarningTable(data) {
if (!data || !Array.isArray(data)) {
$('#warningTableBody').html('| 暂无预警 |
');
return;
}
renderWarningTable(data);
}
// 渲染分析预警表格
function renderWarningTable(warnings) {
const tbody = $('#warningTableBody');
tbody.empty();
if (!warnings || warnings.length === 0) {
tbody.html('| 暂无预警 |
');
return;
}
warnings.forEach((warning, index) => {
// 根据接口返回的字段映射
const serialNumber = (index + 1).toString().padStart(2, '0');
const warningId = warning.id || warning.warnId || '';
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 || '制定策略';
// 将完整的 warning 对象数据编码后传递
const warningData = encodeURIComponent(JSON.stringify({
id: warningId,
measureData: warning.measureData || '',
filePath: warning.filePath || ''
}));
const row = `
| ${serialNumber} |
${time} |
${type} |
${content} |
${action} |
`;
tbody.append(row);
});
}
// 筛选分析预警
let currentWarningFilter = 'all';
function filterWarning(type) {
currentWarningFilter = type;
// 更新标签状态
$('.warning-tab').removeClass('active');
$(`.warning-tab[data-type="${type}"]`).addClass('active');
// 重新调用接口获取筛选后的数据
getAnalysisWarning(type);
}
// 更新图表
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();
}
// 制定策略相关变量
let currentWarningId = null;
let selectedFile = null;
let originalFilePath = null; // 保存原始文件路径
// 打开制定策略弹框
function formulateStrategy(warningDataStr) {
try {
// 解析传递过来的 warning 数据
const warningData = JSON.parse(decodeURIComponent(warningDataStr));
currentWarningId = warningData.id || '';
selectedFile = null;
originalFilePath = warningData.filePath || null;
// 回显应对措施
const measureData = warningData.measureData || '';
$('#measureDataInput').val(measureData);
// 回显文件路径
$('#fileInput').val('');
if (originalFilePath) {
// 从文件路径中提取文件名
const fileName = originalFilePath.split('/').pop() || originalFilePath.split('\\').pop() || '已上传文件';
$('#fileInfo').html('已上传文件: ' + fileName + ' (点击上传文件可替换)');
} else {
$('#fileInfo').text('');
}
// 显示弹框
$('#strategyModal').addClass('show');
} catch (e) {
console.error('解析预警数据失败:', e);
// 如果解析失败,使用旧的方式(兼容性处理)
currentWarningId = warningDataStr;
selectedFile = null;
originalFilePath = null;
$('#measureDataInput').val('');
$('#fileInput').val('');
$('#fileInfo').text('');
$('#strategyModal').addClass('show');
}
}
// 关闭制定策略弹框
function closeStrategyModal() {
$('#strategyModal').removeClass('show');
currentWarningId = null;
selectedFile = null;
originalFilePath = null;
// 重置表单
$('#measureDataInput').val('');
$('#fileInput').val('');
$('#fileInfo').text('');
}
// 初始化文件选择处理(在 layui 初始化后)
function initFileUploadHandler() {
$('#fileInput').on('change', function (e) {
const file = e.target.files[0];
if (file) {
// 检查文件大小(20MB = 20 * 1024 * 1024 字节)
const maxSize = 20 * 1024 * 1024;
if (file.size > maxSize) {
if (layer) {
layer.msg('文件大小不能超过20MB', { icon: 2 });
} else {
alert('文件大小不能超过20MB');
}
$(this).val('');
selectedFile = null;
$('#fileInfo').text('');
return;
}
// 检查文件格式
const allowedExtensions = ['.doc', '.docx', '.pdf', '.jpg', '.png'];
const fileName = file.name.toLowerCase();
const isValidFormat = allowedExtensions.some(ext => fileName.endsWith(ext));
if (!isValidFormat) {
if (layer) {
layer.msg('不支持的文件格式,请上传 .doc .docx .pdf .jpg .png 格式的文件', { icon: 2 });
} else {
alert('不支持的文件格式,请上传 .doc .docx .pdf .jpg .png 格式的文件');
}
$(this).val('');
selectedFile = null;
$('#fileInfo').text('');
return;
}
selectedFile = file;
// 如果选择了新文件,清除原始文件路径标记
originalFilePath = null;
$('#fileInfo').html('已选择文件: ' + file.name + ' (' + formatFileSize(file.size) + ')');
}
});
}
// 格式化文件大小
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}
// 提交制定策略
function submitStrategy() {
const measureData = $('#measureDataInput').val().trim();
// 验证应对措施必填
if (!measureData) {
layer.msg('请输入应对措施', { icon: 2 });
return;
}
// 构建 FormData
const formData = new FormData();
formData.append('measureData', measureData);
// 传递预警ID(必传字段)
formData.append('id', currentWarningId || '');
// 如果有新选择的文件,添加到 FormData
if (selectedFile) {
formData.append('file', selectedFile);
} else if (originalFilePath) {
// 如果没有选择新文件,但有原始文件路径,传递原始文件路径(如果需要保留原文件)
// 注意:根据后端接口需求,可能需要传递 filePath 字段
formData.append('filePath', originalFilePath);
}
// 发送 POST 请求
const url = commonUrl + 'screen/largeScreen/sjNewProManage/uploadFile';
$.ajax({
url: url,
type: 'POST',
headers: {
"authorization": sessionStorage.getItem("zhgd_token"),
"decrypt": 'decrypt'
},
data: formData,
processData: false,
contentType: false,
beforeSend: function () {
layer.load(2);
},
success: function (result) {
layer.closeAll('loading');
// 由于使用了 ajaxSetup 拦截器,result 已经被 modifyResponseData 处理过了
const response = result;
if (response && (response.code === 200 || response.success || response.status === 200)) {
layer.msg('提交成功', { icon: 1 });
closeStrategyModal();
// 刷新预警列表
getAnalysisWarning(currentWarningFilter);
} else {
layer.msg(response.msg || response.message || '提交失败,请重试', { icon: 2 });
}
},
error: function (xhr, status, error) {
layer.closeAll('loading');
layer.msg('提交失败,请检查网络连接', { icon: 2 });
console.error('提交失败:', error);
}
});
}