hb_zhgd_screen/js/pages/newDataAnalysis/resourceRateAnalysis_api.js

810 lines
25 KiB
JavaScript

/**
* 资源利用率分析页面 - API版本
* 使用真实的后端API接口获取数据
*/
let table, layer, form;
let utilizationChart = null, trendsChart = null;
let isAnimating = false;
let lastUpdateTime = '';
// 页面初始化
layui.use(['layer', 'table', 'form'], function () {
layer = layui.layer;
table = layui.table;
form = layui.form;
// 初始化粒子背景
initParticles();
// 加载数据
loadPageData();
});
/**
* 加载页面数据
*/
async function loadPageData() {
try {
// 显示加载动画
showLoading();
// 并行加载所有必要数据
const [overviewData, detailsData, trendsData, distributionData, suggestionsData] = await Promise.all([
resourceApiService.getResourceOverview(),
resourceApiService.getResourceDetails(),
resourceApiService.getResourceTrends(),
resourceApiService.getResourceDistribution(),
resourceApiService.getOptimizationSuggestions()
]);
// 初始化页面数据
initPageData(overviewData, detailsData, suggestionsData);
// 初始化图表
initCharts(distributionData, trendsData);
// 隐藏加载动画
hideLoading();
// 启动数据更新定时器
startDataRefresh();
console.log('页面数据加载完成');
} catch (error) {
console.error('页面数据加载失败:', error);
hideLoading();
// 显示错误提示
layer.msg('数据加载失败,请刷新页面重试', {icon: 2});
}
}
/**
* 显示加载动画
*/
function showLoading() {
$('#loadingOverlay').show();
}
/**
* 隐藏加载动画
*/
function hideLoading() {
$('#loadingOverlay').fadeOut(500);
}
/**
* 初始化页面数据
*/
function initPageData(overviewData, detailsData, suggestionsData) {
// 设置头部统计数据
if (overviewData && overviewData.overallStats) {
const stats = overviewData.overallStats;
$('#totalRate').text(stats.totalRate.toFixed(1) + '%');
$('#personnelRate').text(stats.personnelRate.toFixed(1) + '%');
$('#equipmentRate').text(stats.equipmentRate.toFixed(1) + '%');
$('#materialRate').text(stats.materialRate.toFixed(1) + '%');
}
// 设置资源概览数据
if (overviewData && overviewData.resourceOverview) {
const overview = overviewData.resourceOverview;
$('#personnelCount').text(overview.personnel.count);
$('#equipmentCount').text(overview.equipment.count);
$('#materialCount').text(overview.material.count);
$('#energyCount').text(overview.energy.count + '%');
}
// 更新资源详细表格
if (detailsData && detailsData.list) {
updateResourceTable(detailsData.list);
}
// 更新优化建议
if (suggestionsData) {
updateOptimizationSuggestions(suggestionsData);
}
}
/**
* 更新资源详细表格
*/
function updateResourceTable(resourceList) {
const tableBody = $('#resourceTableBody');
tableBody.empty();
resourceList.forEach((item, index) => {
const assessmentClass = getAssessmentClass(item.assessment);
const statusClass = getStatusClass(item.status);
const row = `
<tr class="table-row-animate" style="animation-delay: ${index * 0.1}s;">
<td>${item.type}</td>
<td>${item.name}</td>
<td><span class="${statusClass}">${translateStatus(item.status)}</span></td>
<td><span class="rate-value">${item.rate}%</span></td>
<td><span class="${assessmentClass}">${item.assessment}</span></td>
</tr>
`;
tableBody.append(row);
});
}
/**
* 更新优化建议
*/
function updateOptimizationSuggestions(suggestions) {
const container = $('#optimizationList');
container.empty();
suggestions.forEach((suggestion, index) => {
const priorityClass = getPriorityClass(suggestion.priority);
const iconClass = getPriorityIcon(suggestion.priority);
const item = `
<div class="optimization-item animate__animated animate__fadeInUp" style="animation-delay: ${index * 0.1}s;">
<div class="optimization-title">
<i class="${iconClass}"></i>
${suggestion.title}
<span class="optimization-priority ${priorityClass}">${suggestion.priority}</span>
</div>
<div class="optimization-desc">${suggestion.description}</div>
</div>
`;
container.append(item);
});
}
/**
* 初始化图表
*/
function initCharts(distributionData, trendsData) {
initUtilizationChart(distributionData);
initTrendsChart(trendsData);
}
/**
* 初始化利用率分布图表
*/
function initUtilizationChart(distributionData) {
const chartDom = document.getElementById('utilizationChart');
utilizationChart = echarts.init(chartDom);
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(22, 186, 170, 0.9)',
borderColor: '#16baaa',
textStyle: {
color: '#fff'
}
},
legend: {
orient: 'vertical',
left: 'left',
top: 'center',
textStyle: {
color: '#16baaa',
fontSize: 12
}
},
series: [
{
name: '资源利用率',
type: 'pie',
radius: ['40%', '70%'],
center: ['65%', '40%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 8,
borderColor: '#0a1e2b',
borderWidth: 2
},
label: {
show: true,
position: 'outside',
color: '#8cc8c1',
fontSize: 11,
formatter: '{b}: {c}%'
},
emphasis: {
label: {
show: true,
fontSize: 12,
fontWeight: 'bold',
color: '#16baaa'
},
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(22, 186, 170, 0.5)'
}
},
data: distributionData || []
}
]
};
utilizationChart.setOption(option);
}
/**
* 初始化趋势图表
*/
function initTrendsChart(trendsData) {
const chartDom = document.getElementById('trendsChart');
trendsChart = echarts.init(chartDom);
if (!trendsData || !trendsData.dates) {
console.warn('趋势数据为空');
return;
}
console.log('趋势数据:', trendsData); // 调试日志
// 处理后端返回的数据格式
let personnelData = [];
let equipmentData = [];
let materialData = [];
let energyData = [];
if (trendsData.trends) {
// 如果数据格式是 { dates: [], trends: { personnel: [], equipment: [], material: [], energy: [] } }
personnelData = trendsData.trends.personnel || [];
equipmentData = trendsData.trends.equipment || [];
materialData = trendsData.trends.material || [];
energyData = trendsData.trends.energy || [];
} else {
// 如果数据格式是 { dates: [], personnel: [], equipment: [], material: [], energy: [] }
personnelData = trendsData.personnel || [];
equipmentData = trendsData.equipment || [];
materialData = trendsData.material || [];
energyData = trendsData.energy || [];
}
// 检查数据是否有效
const hasValidData = personnelData.some(val => val > 0) ||
equipmentData.some(val => val > 0) ||
materialData.some(val => val > 0) ||
energyData.some(val => val > 0);
if (!hasValidData) {
console.warn('所有趋势数据都为0或无效');
// 显示无数据提示
showNoDataMessage();
return;
}
// 动态调整Y轴范围
const allData = [...personnelData, ...equipmentData, ...materialData, ...energyData];
const validData = allData.filter(val => val > 0);
const minValue = validData.length > 0 ? Math.max(0, Math.min(...validData) - 10) : 0;
const maxValue = validData.length > 0 ? Math.min(100, Math.max(...validData) + 10) : 100;
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(22, 186, 170, 0.9)',
borderColor: '#16baaa',
textStyle: {
color: '#fff'
},
axisPointer: {
type: 'cross',
lineStyle: {
color: '#16baaa'
}
},
formatter: function(params) {
let tooltip = `${params[0].axisValueLabel}<br/>`;
params.forEach(param => {
if (param.value > 0) { // 只显示大于0的数据
tooltip += `${param.marker}${param.seriesName}: ${param.value}%<br/>`;
}
});
return tooltip;
}
},
legend: {
data: ['人员', '设备', '材料', '能源'],
textStyle: {
color: '#16baaa'
},
// top: 10
},
/* grid: {
left: '3%',
right: '4%',
bottom: '0%',
top: '5%',
containLabel: true
}, */
xAxis: {
type: 'category',
boundaryGap: false,
data: trendsData.dates.map(date => {
// 处理日期格式,支持不同的日期格式
if (date.length > 10) {
return date.substring(5, 10); // YYYY-MM-DD 格式
} else {
return date.substring(5); // MM-DD 格式
}
}),
axisLine: {
lineStyle: {
color: '#16baaa'
}
},
axisTick: {
lineStyle: {
color: '#16baaa'
}
},
axisLabel: {
color: '#8cc8c1'
}
},
yAxis: {
type: 'value',
min: minValue,
max: maxValue,
axisLine: {
lineStyle: {
color: '#16baaa'
}
},
axisTick: {
lineStyle: {
color: '#16baaa'
}
},
axisLabel: {
color: '#8cc8c1',
formatter: '{value}%'
},
splitLine: {
lineStyle: {
color: 'rgba(22, 186, 170, 0.2)'
}
}
},
series: [
{
name: '人员',
type: 'line',
smooth: true,
data: personnelData,
lineStyle: {
color: '#16baaa',
width: 2
},
itemStyle: {
color: '#16baaa'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(22, 186, 170, 0.3)' },
{ offset: 1, color: 'rgba(22, 186, 170, 0.1)' }
])
},
connectNulls: false // 不连接空值
},
{
name: '设备',
type: 'line',
smooth: true,
data: equipmentData,
lineStyle: {
color: '#20d3c2',
width: 2
},
itemStyle: {
color: '#20d3c2'
},
connectNulls: false
},
{
name: '材料',
type: 'line',
smooth: true,
data: materialData,
lineStyle: {
color: '#25e5d0',
width: 2
},
itemStyle: {
color: '#25e5d0'
},
connectNulls: false
},
{
name: '能源',
type: 'line',
smooth: true,
data: energyData,
lineStyle: {
color: '#2af0dd',
width: 2
},
itemStyle: {
color: '#2af0dd'
},
connectNulls: false
}
]
};
trendsChart.setOption(option);
}
/**
* 显示无数据提示
*/
function showNoDataMessage() {
const chartDom = document.getElementById('trendsChart');
if (trendsChart) {
trendsChart.dispose();
}
trendsChart = echarts.init(chartDom);
const option = {
backgroundColor: 'transparent',
title: {
text: '暂无趋势数据',
left: 'center',
top: 'middle',
textStyle: {
color: '#8cc8c1',
fontSize: 16
}
},
graphic: {
type: 'text',
left: 'center',
top: '60%',
style: {
text: '请检查数据源或联系管理员',
textAlign: 'center',
fill: '#666',
fontSize: 12
}
}
};
trendsChart.setOption(option);
window.addEventListener("resize", function () {
trendsChart.resize();
});
}
/**
* 启动数据刷新定时器
*/
function startDataRefresh() {
setInterval(async () => {
try {
// 获取实时状态更新
const realtimeData = await resourceApiService.getRealtimeStatus(lastUpdateTime);
if (realtimeData && realtimeData.hasUpdate) {
// 更新最后更新时间
lastUpdateTime = realtimeData.updateTime;
// 更新页面数据
if (realtimeData.updates && realtimeData.updates.overallStats) {
updateHeaderStats(realtimeData.updates.overallStats);
}
// 更新图表
updateCharts();
console.log('数据已更新:', new Date().toLocaleTimeString());
}
} catch (error) {
console.error('定时更新失败:', error);
}
}, 60000); // 每60秒更新一次
}
/**
* 更新头部统计数据
*/
function updateHeaderStats(stats) {
if (isAnimating) return;
isAnimating = true;
const currentTotal = parseFloat($('#totalRate').text()) || 0;
const currentPersonnel = parseFloat($('#personnelRate').text()) || 0;
const currentEquipment = parseFloat($('#equipmentRate').text()) || 0;
const currentMaterial = parseFloat($('#materialRate').text()) || 0;
animateValue('totalRate', currentTotal, stats.totalRate, '%');
animateValue('personnelRate', currentPersonnel, stats.personnelRate, '%');
animateValue('equipmentRate', currentEquipment, stats.equipmentRate, '%');
animateValue('materialRate', currentMaterial, stats.materialRate, '%');
setTimeout(() => {
isAnimating = false;
}, 1500);
}
/**
* 更新图表数据
*/
async function updateCharts() {
try {
// 获取最新的分布数据
const distributionData = await resourceApiService.getResourceDistribution();
if (utilizationChart && distributionData) {
utilizationChart.setOption({
series: [{
data: distributionData
}]
});
}
} catch (error) {
console.error('图表更新失败:', error);
}
}
// 数值动画函数
function animateValue(elementId, start, end, suffix = '') {
const element = document.getElementById(elementId);
if (!element) return;
if (isNaN(start) || start < 0) start = 0;
if (isNaN(end)) return;
if (Math.abs(end - start) < 0.1) {
element.textContent = end.toFixed(1) + suffix;
return;
}
const range = end - start;
const duration = 1000;
const startTime = Date.now();
const animate = () => {
const now = Date.now();
const elapsed = now - startTime;
const progress = Math.min(elapsed / duration, 1);
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
const current = start + (range * easeOutQuart);
element.textContent = current.toFixed(1) + suffix;
if (progress < 1) {
requestAnimationFrame(animate);
} else {
element.textContent = end.toFixed(1) + suffix;
}
};
animate();
}
// 工具函数
function getAssessmentClass(assessment) {
switch(assessment) {
case '优秀': return 'assessment-excellent';
case '良好': return 'assessment-good';
case '一般': return 'assessment-normal';
default: return '';
}
}
function getStatusClass(status) {
// 处理英文状态和中英文对照状态
const lowerStatus = status.toLowerCase();
// 正常/良好状态
if (lowerStatus.includes('active') || lowerStatus.includes('normal') || lowerStatus.includes('sufficient') ||
lowerStatus.includes('running') || lowerStatus.includes('working') || lowerStatus.includes('available') ||
lowerStatus.includes('stable') || lowerStatus.includes('good') ||
status.includes('工作中') || status.includes('运行中') || status.includes('充足') ||
status.includes('正常') || status.includes('可用') || status.includes('稳定')) {
return 'status-active';
}
// 警告/异常状态
else if (lowerStatus.includes('rest') || lowerStatus.includes('maintenance') || lowerStatus.includes('low') ||
lowerStatus.includes('high_consumption') || lowerStatus.includes('low_stock') || lowerStatus.includes('warning') ||
lowerStatus.includes('critical') || lowerStatus.includes('offline') || lowerStatus.includes('error') ||
lowerStatus.includes('unavailable') || lowerStatus.includes('unstable') || lowerStatus.includes('poor') ||
status.includes('休息中') || status.includes('维护中') || status.includes('库存低') || status.includes('偏高') ||
status.includes('警告') || status.includes('紧急') || status.includes('离线') || status.includes('故障') ||
status.includes('不可用') || status.includes('不稳定') || status.includes('较差')) {
return 'status-warning';
}
// 默认状态
return 'status-normal';
}
// 状态中英文对照
function translateStatus(status) {
const statusMap = {
'active': '运行中',
'normal': '正常',
'sufficient': '充足',
'rest': '休息中',
'maintenance': '维护中',
'low_stock': '库存不足',
'high_consumption': '消耗偏高',
'offline': '离线',
'error': '故障',
'warning': '警告',
'good': '良好',
'poor': '较差',
'working': '工作中',
'running': '运行中',
'idle': '空闲',
'standby': '待机',
'busy': '繁忙',
'available': '可用',
'unavailable': '不可用',
'critical': '紧急',
'low': '偏低',
'high': '偏高',
'stable': '稳定',
'unstable': '不稳定'
};
// 检查是否为纯英文状态(不包含中文字符)
const isEnglishOnly = /^[a-zA-Z_\-\s]+$/.test(status);
if (isEnglishOnly) {
const lowerStatus = status.toLowerCase().trim();
// 如果是英文状态,返回中文翻译+英文原文
if (statusMap[lowerStatus]) {
return `${statusMap[lowerStatus]}`;
} else {
// 如果没有对应翻译,但是是英文,则添加提示
return `${status} (英文状态)`;
}
}
// 如果已经是中文或混合文本,直接返回
return status;
}
function getPriorityClass(priority) {
switch(priority) {
case '高优先级': return 'priority-high';
case '中优先级': return 'priority-medium';
case '低优先级': return 'priority-low';
default: return '';
}
}
function getPriorityIcon(priority) {
switch(priority) {
case '高优先级': return 'fas fa-exclamation-triangle priority-icon-high';
case '中优先级': return 'fas fa-info-circle priority-icon-medium';
case '低优先级': return 'fas fa-check-circle priority-icon-low';
default: return 'fas fa-circle';
}
}
// 初始化粒子背景
function initParticles() {
if (typeof particlesJS !== 'undefined') {
particlesJS('particles-js', {
"particles": {
"number": {
"value": 80,
"density": {
"enable": true,
"value_area": 800
}
},
"color": {
"value": "#16baaa"
},
"shape": {
"type": "circle"
},
"opacity": {
"value": 0.5,
"random": false
},
"size": {
"value": 3,
"random": true
},
"line_linked": {
"enable": true,
"distance": 150,
"color": "#16baaa",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 2,
"direction": "none",
"random": false,
"straight": false,
"out_mode": "out",
"bounce": false
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": true,
"mode": "repulse"
},
"onclick": {
"enable": true,
"mode": "push"
},
"resize": true
},
"modes": {
"repulse": {
"distance": 200,
"duration": 0.4
},
"push": {
"particles_nb": 4
}
}
},
"retina_detect": true
});
}
}
// 窗口大小改变时重新调整图表
window.addEventListener('resize', function() {
if (utilizationChart) {
utilizationChart.resize();
}
if (trendsChart) {
trendsChart.resize();
}
});
// 添加状态和评估样式
$(document).ready(function() {
$('<style>').prop('type', 'text/css').html(`
.status-active { color: #16baaa; font-weight: bold; }
.status-warning { color: #ffa726; font-weight: bold; }
.status-normal { color: #8cc8c1; }
.assessment-excellent { color: #16baaa; font-weight: bold; }
.assessment-good { color: #20d3c2; font-weight: bold; }
.assessment-normal { color: #ffa726; font-weight: bold; }
.priority-high { color: #ff5722; }
.priority-medium { color: #ffa726; }
.priority-low { color: #4caf50; }
.priority-icon-high { color: #ff5722; margin-right: 5px; animation: blink 1s infinite; }
.priority-icon-medium { color: #ffa726; margin-right: 5px; }
.priority-icon-low { color: #4caf50; margin-right: 5px; }
.rate-value {
color: #20d3c2;
font-weight: bold;
text-shadow: 0 0 5px rgba(32, 211, 194, 0.5);
}
.table-row-animate {
animation: slideInFromLeft 0.5s ease forwards;
opacity: 0;
transform: translateX(-20px);
}
@keyframes slideInFromLeft {
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0.3; }
}
`).appendTo('head');
});