797 lines
27 KiB
JavaScript
797 lines
27 KiB
JavaScript
|
|
let table, layer, form;
|
|||
|
|
let utilizationChart = null, trendsChart = null;
|
|||
|
|
let isAnimating = false; // 防止重复动画的标志位
|
|||
|
|
|
|||
|
|
// 模拟数据
|
|||
|
|
const mockData = {
|
|||
|
|
// 总体统计数据
|
|||
|
|
overallStats: {
|
|||
|
|
totalRate: 85.2,
|
|||
|
|
personnelRate: 88.5,
|
|||
|
|
equipmentRate: 82.3,
|
|||
|
|
materialRate: 84.8
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 资源概览数据
|
|||
|
|
resourceOverview: {
|
|||
|
|
personnel: { count: 126, rate: 88.5 },
|
|||
|
|
equipment: { count: 42, rate: 82.3 },
|
|||
|
|
material: { count: 89, rate: 84.8 },
|
|||
|
|
energy: { count: 76, rate: 91.2 }
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 资源详细列表
|
|||
|
|
resourceDetails: [
|
|||
|
|
{ type: '人员', name: '土建班组A', status: 'active', rate: 92.5, assessment: '优秀' },
|
|||
|
|
{ type: '人员', name: '钢筋班组B', status: 'working', rate: 88.2, assessment: '良好' },
|
|||
|
|
{ type: '人员', name: '混凝土班组C', status: 'rest', rate: 85.7, assessment: '良好' },
|
|||
|
|
{ type: '设备', name: '塔吊001', status: 'running', rate: 89.3, assessment: '优秀' },
|
|||
|
|
{ type: '设备', name: '挖掘机002', status: 'normal', rate: 78.6, assessment: '一般' },
|
|||
|
|
{ type: '设备', name: '混凝土泵车', status: 'maintenance', rate: 76.4, assessment: '一般' },
|
|||
|
|
{ type: '设备', name: '起重机003', status: 'offline', rate: 0, assessment: '较差' },
|
|||
|
|
{ type: '材料', name: '钢筋', status: 'sufficient', rate: 91.2, assessment: '优秀' },
|
|||
|
|
{ type: '材料', name: '混凝土', status: 'normal', rate: 83.5, assessment: '良好' },
|
|||
|
|
{ type: '材料', name: '砂石料', status: 'low_stock', rate: 79.8, assessment: '一般' },
|
|||
|
|
{ type: '材料', name: '水泥', status: 'critical', rate: 65.2, assessment: '较差' },
|
|||
|
|
{ type: '能源', name: '电力系统', status: 'stable', rate: 93.1, assessment: '优秀' },
|
|||
|
|
{ type: '能源', name: '柴油储备', status: 'high_consumption', rate: 88.4, assessment: '良好' },
|
|||
|
|
{ type: '能源', name: '天然气', status: 'available', rate: 85.6, assessment: '良好' },
|
|||
|
|
{ type: '能源', name: '备用电源', status: 'standby', rate: 100, assessment: '优秀' }
|
|||
|
|
],
|
|||
|
|
|
|||
|
|
// 利用率分布数据
|
|||
|
|
utilizationData: [
|
|||
|
|
{ name: '人员', value: 88.5, color: '#16baaa' },
|
|||
|
|
{ name: '设备', value: 82.3, color: '#20d3c2' },
|
|||
|
|
{ name: '材料', value: 84.8, color: '#25e5d0' },
|
|||
|
|
{ name: '能源', value: 91.2, color: '#2af0dd' }
|
|||
|
|
],
|
|||
|
|
|
|||
|
|
// 趋势数据(最近7天)
|
|||
|
|
trendsData: {
|
|||
|
|
dates: ['2024-01-15', '2024-01-16', '2024-01-17', '2024-01-18', '2024-01-19', '2024-01-20', '2024-01-21'],
|
|||
|
|
personnel: [85.2, 87.1, 88.5, 89.2, 88.8, 87.9, 88.5],
|
|||
|
|
equipment: [78.9, 80.3, 82.1, 81.7, 82.3, 83.1, 82.3],
|
|||
|
|
material: [82.1, 83.5, 84.2, 84.8, 85.1, 84.6, 84.8],
|
|||
|
|
energy: [89.5, 90.2, 91.2, 90.8, 91.5, 91.0, 91.2]
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 优化建议
|
|||
|
|
optimizationSuggestions: [
|
|||
|
|
{
|
|||
|
|
title: '设备调度优化',
|
|||
|
|
description: '挖掘机002利用率偏低,建议调整作业计划,提高设备运转效率',
|
|||
|
|
priority: '高优先级'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '材料库存管理',
|
|||
|
|
description: '砂石料库存不足,建议提前采购补充,避免影响施工进度',
|
|||
|
|
priority: '中优先级'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '人员配置调整',
|
|||
|
|
description: '土建班组A效率突出,建议增加人员配置或分配更多任务',
|
|||
|
|
priority: '中优先级'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '能源消耗监控',
|
|||
|
|
description: '柴油消耗偏高,建议检查设备运行状态,优化燃油使用',
|
|||
|
|
priority: '低优先级'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
title: '设备维护计划',
|
|||
|
|
description: '混凝土泵车正在维护中,建议制定预防性维护计划减少停机时间',
|
|||
|
|
priority: '高优先级'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 页面初始化
|
|||
|
|
layui.use(['layer', 'table', 'form'], function () {
|
|||
|
|
layer = layui.layer;
|
|||
|
|
table = layui.table;
|
|||
|
|
form = layui.form;
|
|||
|
|
|
|||
|
|
// 初始化粒子背景
|
|||
|
|
initParticles();
|
|||
|
|
|
|||
|
|
// 模拟加载延迟
|
|||
|
|
setTimeout(() => {
|
|||
|
|
// 隐藏加载动画
|
|||
|
|
$('#loadingOverlay').fadeOut(500);
|
|||
|
|
|
|||
|
|
// 初始化页面数据
|
|||
|
|
initPageData();
|
|||
|
|
|
|||
|
|
// 初始化图表
|
|||
|
|
initCharts();
|
|||
|
|
|
|||
|
|
// 启动数据更新定时器
|
|||
|
|
startDataRefresh();
|
|||
|
|
}, 2000);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 初始化粒子背景
|
|||
|
|
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",
|
|||
|
|
"stroke": {
|
|||
|
|
"width": 0,
|
|||
|
|
"color": "#000000"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"opacity": {
|
|||
|
|
"value": 0.5,
|
|||
|
|
"random": false,
|
|||
|
|
"anim": {
|
|||
|
|
"enable": false,
|
|||
|
|
"speed": 1,
|
|||
|
|
"opacity_min": 0.1,
|
|||
|
|
"sync": false
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"size": {
|
|||
|
|
"value": 3,
|
|||
|
|
"random": true,
|
|||
|
|
"anim": {
|
|||
|
|
"enable": false,
|
|||
|
|
"speed": 40,
|
|||
|
|
"size_min": 0.1,
|
|||
|
|
"sync": false
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"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,
|
|||
|
|
"attract": {
|
|||
|
|
"enable": false,
|
|||
|
|
"rotateX": 600,
|
|||
|
|
"rotateY": 1200
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"interactivity": {
|
|||
|
|
"detect_on": "canvas",
|
|||
|
|
"events": {
|
|||
|
|
"onhover": {
|
|||
|
|
"enable": true,
|
|||
|
|
"mode": "repulse"
|
|||
|
|
},
|
|||
|
|
"onclick": {
|
|||
|
|
"enable": true,
|
|||
|
|
"mode": "push"
|
|||
|
|
},
|
|||
|
|
"resize": true
|
|||
|
|
},
|
|||
|
|
"modes": {
|
|||
|
|
"grab": {
|
|||
|
|
"distance": 400,
|
|||
|
|
"line_linked": {
|
|||
|
|
"opacity": 1
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"bubble": {
|
|||
|
|
"distance": 400,
|
|||
|
|
"size": 40,
|
|||
|
|
"duration": 2,
|
|||
|
|
"opacity": 8,
|
|||
|
|
"speed": 3
|
|||
|
|
},
|
|||
|
|
"repulse": {
|
|||
|
|
"distance": 200,
|
|||
|
|
"duration": 0.4
|
|||
|
|
},
|
|||
|
|
"push": {
|
|||
|
|
"particles_nb": 4
|
|||
|
|
},
|
|||
|
|
"remove": {
|
|||
|
|
"particles_nb": 2
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"retina_detect": true
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化页面数据
|
|||
|
|
function initPageData() {
|
|||
|
|
// 直接设置初始值,不使用动画
|
|||
|
|
const stats = mockData.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) + '%');
|
|||
|
|
|
|||
|
|
// 设置资源概览初始值
|
|||
|
|
const overview = mockData.resourceOverview;
|
|||
|
|
$('#personnelCount').text(overview.personnel.count);
|
|||
|
|
$('#equipmentCount').text(overview.equipment.count);
|
|||
|
|
$('#materialCount').text(overview.material.count);
|
|||
|
|
$('#energyCount').text(overview.energy.count + '%');
|
|||
|
|
|
|||
|
|
// 更新资源详细表格
|
|||
|
|
updateResourceTable();
|
|||
|
|
|
|||
|
|
// 更新优化建议
|
|||
|
|
updateOptimizationSuggestions();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新头部统计数据
|
|||
|
|
function updateHeaderStats() {
|
|||
|
|
const stats = mockData.overallStats;
|
|||
|
|
|
|||
|
|
// 获取当前值,如果不存在则从0开始
|
|||
|
|
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, '%');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 数值动画函数
|
|||
|
|
function animateValue(elementId, start, end, suffix = '') {
|
|||
|
|
const element = document.getElementById(elementId);
|
|||
|
|
if (!element) return;
|
|||
|
|
|
|||
|
|
// 如果起始值无效,设为0
|
|||
|
|
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 updateResourceOverview() {
|
|||
|
|
const overview = mockData.resourceOverview;
|
|||
|
|
|
|||
|
|
// 获取当前值,如果不存在则从0开始
|
|||
|
|
const currentPersonnel = parseInt($('#personnelCount').text()) || 0;
|
|||
|
|
const currentEquipment = parseInt($('#equipmentCount').text()) || 0;
|
|||
|
|
const currentMaterial = parseInt($('#materialCount').text()) || 0;
|
|||
|
|
const currentEnergy = parseInt($('#energyCount').text()) || 0;
|
|||
|
|
|
|||
|
|
animateValue('personnelCount', currentPersonnel, overview.personnel.count);
|
|||
|
|
animateValue('equipmentCount', currentEquipment, overview.equipment.count);
|
|||
|
|
animateValue('materialCount', currentMaterial, overview.material.count);
|
|||
|
|
animateValue('energyCount', currentEnergy, overview.energy.count, '%');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新资源详细表格
|
|||
|
|
function updateResourceTable() {
|
|||
|
|
const tableBody = $('#resourceTableBody');
|
|||
|
|
tableBody.empty();
|
|||
|
|
|
|||
|
|
mockData.resourceDetails.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 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]} (${status})`;
|
|||
|
|
} else {
|
|||
|
|
// 如果没有对应翻译,但是是英文,则添加提示
|
|||
|
|
return `${status} (英文状态)`;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果已经是中文或混合文本,直接返回
|
|||
|
|
return status;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新优化建议
|
|||
|
|
function updateOptimizationSuggestions() {
|
|||
|
|
const container = $('#optimizationList');
|
|||
|
|
container.empty();
|
|||
|
|
|
|||
|
|
mockData.optimizationSuggestions.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 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 initCharts() {
|
|||
|
|
initUtilizationChart();
|
|||
|
|
initTrendsChart();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化利用率分布图表
|
|||
|
|
function initUtilizationChart() {
|
|||
|
|
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%', '50%'],
|
|||
|
|
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: mockData.utilizationData.map(item => ({
|
|||
|
|
name: item.name,
|
|||
|
|
value: item.value,
|
|||
|
|
itemStyle: {
|
|||
|
|
color: item.color
|
|||
|
|
}
|
|||
|
|
}))
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
utilizationChart.setOption(option);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 初始化趋势图表
|
|||
|
|
function initTrendsChart() {
|
|||
|
|
const chartDom = document.getElementById('trendsChart');
|
|||
|
|
trendsChart = echarts.init(chartDom);
|
|||
|
|
|
|||
|
|
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'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
legend: {
|
|||
|
|
data: ['人员', '设备', '材料', '能源'],
|
|||
|
|
textStyle: {
|
|||
|
|
color: '#16baaa'
|
|||
|
|
},
|
|||
|
|
top: 10
|
|||
|
|
},
|
|||
|
|
grid: {
|
|||
|
|
left: '3%',
|
|||
|
|
right: '4%',
|
|||
|
|
bottom: '3%',
|
|||
|
|
top: '15%',
|
|||
|
|
containLabel: true
|
|||
|
|
},
|
|||
|
|
xAxis: {
|
|||
|
|
type: 'category',
|
|||
|
|
boundaryGap: false,
|
|||
|
|
data: mockData.trendsData.dates.map(date => date.substring(5)),
|
|||
|
|
axisLine: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: '#16baaa'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
axisTick: {
|
|||
|
|
lineStyle: {
|
|||
|
|
color: '#16baaa'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
axisLabel: {
|
|||
|
|
color: '#8cc8c1'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
yAxis: {
|
|||
|
|
type: 'value',
|
|||
|
|
min: 70,
|
|||
|
|
max: 95,
|
|||
|
|
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: mockData.trendsData.personnel,
|
|||
|
|
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)' }
|
|||
|
|
])
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: '设备',
|
|||
|
|
type: 'line',
|
|||
|
|
smooth: true,
|
|||
|
|
data: mockData.trendsData.equipment,
|
|||
|
|
lineStyle: {
|
|||
|
|
color: '#20d3c2',
|
|||
|
|
width: 2
|
|||
|
|
},
|
|||
|
|
itemStyle: {
|
|||
|
|
color: '#20d3c2'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: '材料',
|
|||
|
|
type: 'line',
|
|||
|
|
smooth: true,
|
|||
|
|
data: mockData.trendsData.material,
|
|||
|
|
lineStyle: {
|
|||
|
|
color: '#25e5d0',
|
|||
|
|
width: 2
|
|||
|
|
},
|
|||
|
|
itemStyle: {
|
|||
|
|
color: '#25e5d0'
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: '能源',
|
|||
|
|
type: 'line',
|
|||
|
|
smooth: true,
|
|||
|
|
data: mockData.trendsData.energy,
|
|||
|
|
lineStyle: {
|
|||
|
|
color: '#2af0dd',
|
|||
|
|
width: 2
|
|||
|
|
},
|
|||
|
|
itemStyle: {
|
|||
|
|
color: '#2af0dd'
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
trendsChart.setOption(option);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 启动数据刷新定时器
|
|||
|
|
function startDataRefresh() {
|
|||
|
|
setInterval(() => {
|
|||
|
|
// 模拟数据更新
|
|||
|
|
updateMockData();
|
|||
|
|
updatePageData();
|
|||
|
|
updateCharts();
|
|||
|
|
}, 60000); // 每60秒更新一次,减少频率
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新模拟数据
|
|||
|
|
function updateMockData() {
|
|||
|
|
// 随机更新一些数据,但变化幅度较小
|
|||
|
|
const variation = 2; // 最大变化幅度
|
|||
|
|
|
|||
|
|
mockData.overallStats.totalRate = Math.round((mockData.overallStats.totalRate + (Math.random() - 0.5) * variation) * 10) / 10;
|
|||
|
|
mockData.overallStats.personnelRate = Math.round((mockData.overallStats.personnelRate + (Math.random() - 0.5) * variation) * 10) / 10;
|
|||
|
|
mockData.overallStats.equipmentRate = Math.round((mockData.overallStats.equipmentRate + (Math.random() - 0.5) * variation) * 10) / 10;
|
|||
|
|
mockData.overallStats.materialRate = Math.round((mockData.overallStats.materialRate + (Math.random() - 0.5) * variation) * 10) / 10;
|
|||
|
|
|
|||
|
|
// 限制数值范围
|
|||
|
|
mockData.overallStats.totalRate = Math.max(75, Math.min(95, mockData.overallStats.totalRate));
|
|||
|
|
mockData.overallStats.personnelRate = Math.max(75, Math.min(95, mockData.overallStats.personnelRate));
|
|||
|
|
mockData.overallStats.equipmentRate = Math.max(70, Math.min(90, mockData.overallStats.equipmentRate));
|
|||
|
|
mockData.overallStats.materialRate = Math.max(75, Math.min(95, mockData.overallStats.materialRate));
|
|||
|
|
|
|||
|
|
// 同步更新资源概览数据
|
|||
|
|
mockData.resourceOverview.personnel.rate = mockData.overallStats.personnelRate;
|
|||
|
|
mockData.resourceOverview.equipment.rate = mockData.overallStats.equipmentRate;
|
|||
|
|
mockData.resourceOverview.material.rate = mockData.overallStats.materialRate;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新页面数据
|
|||
|
|
function updatePageData() {
|
|||
|
|
if (isAnimating) return; // 如果正在动画中,跳过更新
|
|||
|
|
|
|||
|
|
isAnimating = true;
|
|||
|
|
updateHeaderStats();
|
|||
|
|
updateResourceOverview();
|
|||
|
|
|
|||
|
|
// 1.5秒后重置动画标志
|
|||
|
|
setTimeout(() => {
|
|||
|
|
isAnimating = false;
|
|||
|
|
}, 1500);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新图表数据
|
|||
|
|
function updateCharts() {
|
|||
|
|
if (utilizationChart) {
|
|||
|
|
// 更新利用率分布图
|
|||
|
|
mockData.utilizationData[0].value = mockData.overallStats.personnelRate;
|
|||
|
|
mockData.utilizationData[1].value = mockData.overallStats.equipmentRate;
|
|||
|
|
mockData.utilizationData[2].value = mockData.overallStats.materialRate;
|
|||
|
|
|
|||
|
|
utilizationChart.setOption({
|
|||
|
|
series: [{
|
|||
|
|
data: mockData.utilizationData.map(item => ({
|
|||
|
|
name: item.name,
|
|||
|
|
value: parseFloat(item.value.toFixed(1)),
|
|||
|
|
itemStyle: {
|
|||
|
|
color: item.color
|
|||
|
|
}
|
|||
|
|
}))
|
|||
|
|
}]
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 窗口大小改变时重新调整图表
|
|||
|
|
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');
|
|||
|
|
});
|