hb_zhgd_screen/js/pages/dataAnalysisOctober/projectManagement.js

1310 lines
43 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 项目管理分析页面
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 = `<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>
<span style="color:#fff;margin-left:auto;font-weight:bold;">${p.value}%</span>
</div>`;
});
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 = `<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>
<span style="color:#fff;margin-left:auto;font-weight:bold;">${p.value}%</span>
</div>`;
});
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('<tr><td colspan="5" style="text-align:center;color:rgba(255,255,255,0.5);">暂无预警</td></tr>');
return;
}
renderWarningTable(data);
}
// 渲染分析预警表格
function renderWarningTable(warnings) {
const tbody = $('#warningTableBody');
tbody.empty();
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 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 = `
<tr>
<td>${serialNumber}</td>
<td>${time}</td>
<td>${type}</td>
<td>${content}</td>
<td><span class="warning-action" onclick="formulateStrategy('${warningData}')">${action}</span></td>
</tr>
`;
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('<span style="color: #00FFB8;">已上传文件: ' + fileName + '</span> <span style="color: rgba(255,255,255,0.6); margin-left: 10px;">(点击上传文件可替换)</span>');
} 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('<span style="color: #00FFB8;">已选择文件: ' + file.name + ' (' + formatFileSize(file.size) + ')</span>');
}
});
}
// 格式化文件大小
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);
}
});
}