大屏数据展示

This commit is contained in:
Rue Ji
2026-02-04 23:46:37 +08:00
parent 79a8fc11cf
commit 14c9bd8ce0
14 changed files with 2566 additions and 909 deletions

View File

@ -4,6 +4,8 @@
<head>
<meta charset="UTF-8">
<title>排污户大屏展示</title>
<script type="text/javascript" src="<%=request.getContextPath()%>/node_modules/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="<%=request.getContextPath()%>/JS/echarts3.0.js"></script>
<style>
body, html {
margin: 0;
@ -11,6 +13,8 @@
width: 100%;
height: 100%;
overflow: auto;
background-color: #030829;
font-family: "Microsoft YaHei", sans-serif;
}
.screen-container {
width: 6500px;
@ -20,10 +24,794 @@
background-repeat: no-repeat;
position: relative;
}
/* Area 1: Left Stats + Ranking */
.rank-module-container {
position: absolute;
top: 275px;
left: 66px;
width: 1926px;
height: 1500px;
display: flex;
flex-direction: column;
/* padding: 20px; */
box-sizing: border-box;
z-index: 10;
}
.stats-header {
display: flex;
justify-content: space-between;
margin-bottom: 30px;
background: rgba(0, 0, 0, 0.3);
padding: 20px;
border-radius: 10px;
border: 1px solid rgba(0, 234, 255, 0.2);
}
.stat-item {
text-align: center;
}
.stat-label {
font-size: 24px;
color: #ccc;
margin-bottom: 10px;
}
.stat-value {
font-size: 48px;
font-weight: bold;
color: #00eaff;
font-family: 'DIN Alternate', sans-serif;
}
.stat-unit {
font-size: 20px;
margin-left: 5px;
color: #aaa;
}
.ranking-title {
font-size: 28px;
font-weight: bold;
padding-left: 10px;
border-left: 4px solid;
margin-bottom: 15px;
letter-spacing: 1px;
}
/* Area 3: Right Details */
.detail-module-container {
position: absolute;
top: 275px;
right: 66px;
width: 1926px;
height: 1500px;
border: 1px solid rgba(0, 234, 255, 0.3);
backdrop-filter: blur(10px);
border-radius: 12px;
/* padding: 30px; */
box-sizing: border-box;
display: flex;
flex-direction: column;
color: #fff;
/* background: rgba(0, 20, 50, 0.6) url('<%=request.getContextPath()%>/IMG/bim/渐变背景.png') no-repeat center center; */
background-size: cover;
z-index: 10;
}
/* Area 2: Center Map */
.map-module-container {
position: absolute;
top: 180px;
left: 2000px; /* Positioned between Left (1900+66) and Right */
width: 2400px; /* Approximate remaining space: 6500 - 1900 - 1926 - margins */
height: 1500px;
/* background: rgba(0, 255, 0, 0.1); Debug */
position: relative;
z-index: 5;
}
.map-point {
position: absolute;
width: 30px;
height: 30px;
background: rgba(0, 234, 255, 0.8);
border: 2px solid #fff;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 0 15px #00eaff;
transition: all 0.3s ease;
transform: translate(-50%, -50%);
}
.map-point.active {
background: #ffaa00;
box-shadow: 0 0 25px #ffaa00;
transform: translate(-50%, -50%) scale(1.5);
z-index: 20;
}
.map-point:hover {
transform: translate(-50%, -50%) scale(1.2);
}
.map-tooltip {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 5px 10px;
border-radius: 4px;
white-space: nowrap;
font-size: 20px;
pointer-events: none;
display: none;
border: 1px solid #00eaff;
}
.map-point:hover .map-tooltip {
display: block;
}
.enterprise-detail-header {
font-size: 56px;
font-weight: bold;
color: #00eaff;
text-align: center;
margin-bottom: 40px;
text-shadow: 0 0 20px rgba(0, 234, 255, 0.8);
border-bottom: 2px solid rgba(0, 234, 255, 0.2);
padding-bottom: 20px;
letter-spacing: 2px;
}
.detail-content {
display: flex;
height: 450px;
margin-bottom: 40px;
gap: 30px;
}
.info-section {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
font-size: 36px;
line-height: 2.2;
padding: 20px;
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
}
.info-item {
border-bottom: 1px dashed rgba(255, 255, 255, 0.1);
}
.info-item span {
color: #ffaa00;
font-weight: bold;
margin-left: 15px;
font-family: 'DIN Alternate', sans-serif;
}
.photo-section {
flex: 1;
border: 2px solid rgba(0, 234, 255, 0.3);
border-radius: 10px;
overflow: hidden;
background: #000;
position: relative;
}
.photo-label {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background: rgba(0, 0, 0, 0.6);
color: #fff;
text-align: center;
padding: 10px 0;
font-size: 24px;
}
.detail-charts {
flex: 1;
display: flex;
flex-direction: column;
gap: 30px;
}
.detail-chart-box {
flex: 1;
background: rgba(0, 0, 0, 0.2);
border: 1px solid rgba(0, 234, 255, 0.1);
border-radius: 12px;
padding: 20px;
display: flex;
flex-direction: column;
}
.chart-subtitle {
font-size: 32px;
color: #ffffff;
margin-bottom: 15px;
padding-left: 15px;
border-left: 6px solid #00eaff;
font-weight: bold;
}
.chart-content {
flex: 1;
width: 100%;
min-height: 0; /* Important for flex child scroll/resize */
}
.new-bar-chart-container {
position: absolute;
top: 400px;
left: 66px;
width: 1920px;
height: 1400px;
z-index: 100;
display: none;
}
</style>
</head>
<body>
<div class="screen-container">
<div class="rank-module-container">
<!-- Statistics -->
<div class="stats-header">
<div class="stat-item">
<div class="stat-label">辖区排污企业总数</div>
<div class="stat-value" id="totalCount">0<span class="stat-unit">家</span></div>
</div>
<div class="stat-item">
<div class="stat-label">已接入监控</div>
<div class="stat-value" style="color: #00ff00;" id="connectedCount">0<span class="stat-unit">家</span></div>
</div>
<div class="stat-item">
<div class="stat-label">正在接入中</div>
<div class="stat-value" style="color: #ffaa00;" id="connectingCount">0<span class="stat-unit">家</span></div>
</div>
</div>
<!-- Ranking List -->
<div class="ranking-list-wrapper" style="flex: 1; display: flex; flex-direction: column; overflow: hidden;">
<div id="chartRanking" style="flex: 1; width: 100%;"></div>
</div>
</div>
<!-- Left Side Detail Module -->
<div class="detail-module-container">
<div class="enterprise-detail-header" id="detailTitle">企业名称加载中...</div>
<div class="detail-content">
<div class="info-section">
<div class="info-item">污水特性: <span id="sewageType">--</span></div>
<div class="info-item">当前排污量: <span id="dischargeVolume">--</span></div>
<div class="info-item">接入状态: <span id="connectionStatus">--</span></div>
</div>
<div class="photo-section">
<img id="sitePhoto" src="<%=request.getContextPath()%>/IMG/login/company.png" alt="现场照片" style="width:100%; height:100%; object-fit: cover;">
<div class="photo-label">现场监控画面</div>
</div>
</div>
<div class="detail-charts">
<div class="detail-chart-box">
<div class="chart-subtitle">瞬时排污量历史曲线 (近3天)</div>
<div id="chartInstant" class="chart-content"></div>
</div>
<div class="detail-chart-box">
<div class="chart-subtitle">累计流量历史曲线 (日差值 - 近14天)</div>
<div id="chartCumulative" class="chart-content"></div>
</div>
</div>
</div>
<!-- New Bar Chart Area -->
<div class="new-bar-chart-container">
<div id="newBarChart" style="width: 100%; height: 100%;"></div>
</div>
</div>
<script>
var myChartInstant = null;
var myChartCumulative = null;
var myChartRanking = null;
var myNewBarChart = null;
var currentMaxData = []; // Top 10 Max
var currentMinData = []; // Top 10 Min
var allEnterprises = []; // All data for map
var currentFocusIndex = 0;
var rotationTimer = null;
$(document).ready(function() {
launchIntoFullscreen(document.documentElement);
initData();
// Double click to toggle fullscreen
$('body').on('dblclick', function() {
launchIntoFullscreen(document.documentElement);
});
});
function launchIntoFullscreen(element) {
if(element.requestFullscreen) {
element.requestFullscreen();
} else if(element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if(element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if(element.msRequestFullscreen) {
element.msRequestFullscreen();
}
}
function exitFullscreen() {
if(document.exitFullscreen) {
document.exitFullscreen();
} else if(document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if(document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
}
function initData() {
// Stats
var total = 158;
var connected = 124;
var connecting = 34;
$('#totalCount').html(total + '<span class="stat-unit">家</span>');
$('#connectedCount').html(connected + '<span class="stat-unit">家</span>');
$('#connectingCount').html(connecting + '<span class="stat-unit">家</span>');
// Generate Enterprises with Details
var enterprises = [];
var sewageTypes = ['化学需氧量', '氨氮', '总磷', '重金属', '酸碱废水'];
var connectionStatuses = ['已接入', '接入中'];
for (var i = 1; i <= 50; i++) {
var val = Math.floor(Math.random() * 10000) + 500;
enterprises.push({
id: i,
name: '企业-' + i,
value: val,
// Details
sewageType: sewageTypes[Math.floor(Math.random() * sewageTypes.length)],
connectionStatus: connectionStatuses[Math.floor(Math.random() * connectionStatuses.length)],
indicator: val > 8000 ? '超级' : '普通',
photo: '<%=request.getContextPath()%>/IMG/login/company.png', // Placeholder
// Map Coordinates (Relative % in map container)
x: Math.random() * 90 + 5, // 5% to 95%
y: Math.random() * 80 + 10, // 10% to 90%
// History Data
instantHistory: generateHistoryData(72), // 3 days * 24 hours
cumulativeHistory: generateHistoryData(14) // 14 days
});
}
allEnterprises = enterprises;
// Init Charts
myChartInstant = echarts.init(document.getElementById('chartInstant'));
myChartCumulative = echarts.init(document.getElementById('chartCumulative'));
myChartRanking = echarts.init(document.getElementById('chartRanking'));
// myNewBarChart = echarts.init(document.getElementById('newBarChart'));
// renderNewBarChart();
// Initial Sort
updateSortedData(enterprises);
// Render Lists & Map
renderRankingChart();
renderMapPoints();
updateDetailView();
// Start Loops
startDataSimulation(enterprises);
startRotation();
}
function generateHistoryData(count) {
var data = [];
for(var k=0; k<count; k++) {
data.push(Math.floor(Math.random() * 500) + 50);
}
return data;
}
function updateSortedData(enterprises) {
// Sort Descending for Max
currentMaxData = [...enterprises].sort((a, b) => b.value - a.value).slice(0, 10);
// Sort Ascending for Min
currentMinData = [...enterprises].sort((a, b) => a.value - b.value).slice(0, 10);
}
function renderRankingChart() {
var maxNames = [];
var maxValues = [];
var minNames = [];
var minValues = [];
// Process Max Data (Top 10 High)
currentMaxData.forEach((item, index) => {
maxNames.push(item.name);
var isHighlighted = (index === currentFocusIndex);
var colorStart = isHighlighted ? '#ffffff' : '#ffaa00';
var colorEnd = isHighlighted ? '#cccccc' : '#ff5500';
maxValues.push({
value: item.value,
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{
offset: 0, color: colorStart
}, {
offset: 1, color: colorEnd
}])
}
}
});
});
// Process Min Data (Top 10 Low)
currentMinData.forEach((item) => {
minNames.push(item.name);
minValues.push({
value: item.value,
itemStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{
offset: 0, color: '#00eaff'
}, {
offset: 1, color: '#0055ff'
}])
}
}
});
});
var option = {
title: [{
text: '近30天排污量最大 Top 10',
left: 'center',
top: '2%',
textStyle: { color: '#ffaa00', fontSize: 24, fontWeight: 'bold' }
}, {
text: '近30天排污量最小 Top 10',
left: 'center',
top: '52%',
textStyle: { color: '#00eaff', fontSize: 24, fontWeight: 'bold' }
}],
grid: [{
top: '10%',
left: '5%',
right: '10%',
bottom: '53%',
containLabel: true
}, {
top: '60%',
left: '5%',
right: '10%',
bottom: '5%',
containLabel: true
}],
xAxis: [{
type: 'value',
show: false,
gridIndex: 0
}, {
type: 'value',
show: false,
gridIndex: 1
}],
yAxis: [{
type: 'category',
data: maxNames,
inverse: true,
axisLabel: { color: '#fff', fontSize: 18 },
axisLine: { show: false },
axisTick: { show: false },
gridIndex: 0
}, {
type: 'category',
data: minNames,
inverse: true,
axisLabel: { color: '#fff', fontSize: 18 },
axisLine: { show: false },
axisTick: { show: false },
gridIndex: 1
}],
series: [{
name: 'Max',
type: 'bar',
xAxisIndex: 0,
yAxisIndex: 0,
data: maxValues,
barWidth: 30,
label: {
normal: {
show: true,
position: 'right',
textStyle: { color: '#ffaa00', fontSize: 18, fontWeight: 'bold' },
formatter: '{c} m³'
}
},
itemStyle: { normal: { barBorderRadius: [0, 15, 15, 0] } },
animationDuration: 1000,
animationDurationUpdate: 1000
}, {
name: 'Min',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
data: minValues,
barWidth: 30,
label: {
normal: {
show: true,
position: 'right',
textStyle: { color: '#00eaff', fontSize: 18, fontWeight: 'bold' },
formatter: '{c} m³'
}
},
itemStyle: { normal: { barBorderRadius: [0, 15, 15, 0] } },
animationDuration: 1000,
animationDurationUpdate: 1000
}]
};
myChartRanking.setOption(option);
}
function renderMapPoints() {
var html = '';
allEnterprises.forEach((item) => {
html += '<div class="map-point ' + (item.value > 8000 ? 'super' : '') + '" id="point-' + item.id + '" style="left:' + item.x + '%; top:' + item.y + '%;" onclick="handleMapClick(' + item.id + ')">';
html += '<div class="map-tooltip" onclick="event.stopPropagation()">'; // Stop prop to prevent point click
html += '<div class="map-tooltip-row" style="font-weight:bold; color:#00eaff; font-size:22px; margin-bottom:10px; cursor:pointer;" onclick="handleMapClick(' + item.id + ')">' + item.name + '</div>';
html += '<div class="map-tooltip-row"><span class="map-tooltip-label">瞬时流量:</span><span class="clickable-val" onclick="showHistoryModal(\'instant\', ' + item.id + ')">' + item.value + ' m³/h</span></div>';
html += '<div class="map-tooltip-row"><span class="map-tooltip-label">累计流量:</span><span class="clickable-val" onclick="showHistoryModal(\'cumulative\', ' + item.id + ')">' + (item.value * 24) + ' m³</span></div>';
html += '</div>';
html += '</div>';
});
$('#mapContainer').html(html);
}
function closeModal() {
$('#historyModal').fadeOut();
}
var modalChartInstance = null;
function showHistoryModal(type, id) {
var ent = allEnterprises.find(e => e.id === id);
if(!ent) return;
$('#historyModal').css('display', 'flex').hide().fadeIn();
if(modalChartInstance) {
modalChartInstance.dispose();
}
modalChartInstance = echarts.init(document.getElementById('modalChart'));
var title = (type === 'instant' ? '瞬时排污量' : '累计流量') + '历史曲线 - ' + ent.name;
$('#modalTitle').text(title);
var xData, yData, color;
if(type === 'instant') {
xData = [];
for(var i=0; i<72; i++) xData.push(i + 'h');
yData = ent.instantHistory;
color = '#ffaa00';
} else {
xData = [];
for(var j=0; j<14; j++) xData.push('D' + (j+1));
yData = ent.cumulativeHistory;
color = '#00eaff';
}
var option = {
tooltip: { trigger: 'axis', backgroundColor: 'rgba(0,0,0,0.8)', textStyle: {color: '#fff'} },
grid: { left: '3%', right: '4%', bottom: '3%', top: '10%', containLabel: true },
xAxis: {
type: 'category',
boundaryGap: false,
data: xData,
axisLabel: { color: '#fff', fontSize: 16 },
axisLine: { lineStyle: { color: '#00eaff' } }
},
yAxis: {
type: 'value',
axisLabel: { color: '#fff', fontSize: 16 },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
},
series: [{
name: type === 'instant' ? '瞬时流量' : '累计流量',
type: 'line',
smooth: true,
data: yData,
lineStyle: { width: 3, color: color },
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0, color: color
}, {
offset: 1, color: 'rgba(0,0,0,0)'
}])
},
itemStyle: { color: color }
}]
};
modalChartInstance.setOption(option);
}
function handleMapClick(id) {
// Find if this enterprise is in currentMaxData
var index = currentMaxData.findIndex(e => e.id === id);
if (index !== -1) {
currentFocusIndex = index;
updateDetailView();
// Reset rotation timer to pause briefly or restart?
// For simplicity, just let it continue or reset interval
clearInterval(rotationTimer);
startRotation();
} else {
// If clicked item is not in Top 10, we can still show it in Detail View
// But rotation logic might overwrite it next tick.
// Let's just show it temporarily.
var enterprise = allEnterprises.find(e => e.id === id);
if(enterprise) {
renderDetailContent(enterprise);
highlightMapPoint(enterprise.id);
// Clear ranking highlight since it's not in the list (visually)
$('.ranking-row').removeClass('active');
}
}
}
function startDataSimulation(enterprises) {
setInterval(function() {
// Randomize values
enterprises.forEach(item => {
item.value += Math.floor(Math.random() * 200) - 100;
if(item.value < 0) item.value = 0;
item.indicator = item.value > 8000 ? '超级' : '普通';
// Update Map Point Style
var el = $('#point-' + item.id);
if(item.value > 8000) {
el.addClass('super');
} else {
el.removeClass('super');
}
el.find('.map-tooltip').html(item.name + '<br>排污量: ' + item.value);
});
updateSortedData(enterprises);
renderRankingChart();
// Update map tooltips if needed? Or just let them be static until hover re-render?
// Re-rendering map points entirely is heavy if DOM is large, but 50 is fine.
// However, re-rendering loses hover state. Better to update text.
// For demo simplicity, we skip real-time map value update or do it simply:
/*
enterprises.forEach(item => {
$('#point-' + item.id + ' .map-tooltip').html(item.name + '<br>排污量: ' + item.value);
});
*/
}, 10000);
}
function startRotation() {
rotationTimer = setInterval(function() {
currentFocusIndex = (currentFocusIndex + 1) % 10;
updateDetailView();
}, 10000); // Rotate every 5 seconds
}
function updateDetailView() {
if(!currentMaxData || currentMaxData.length === 0) return;
if(currentFocusIndex >= currentMaxData.length) currentFocusIndex = 0;
var enterprise = currentMaxData[currentFocusIndex];
renderDetailContent(enterprise);
// Highlight Ranking Chart
renderRankingChart();
// Highlight Map Point
highlightMapPoint(enterprise.id);
}
function highlightMapPoint(id) {
$('.map-point').removeClass('active');
$('#point-' + id).addClass('active');
}
function renderDetailContent(enterprise) {
// Update DOM
$('#detailTitle').text(enterprise.name);
$('#sewageType').text(enterprise.sewageType);
$('#dischargeVolume').text(enterprise.value + ' m³');
$('#connectionStatus').text(enterprise.connectionStatus);
renderDetailCharts(enterprise);
}
function renderDetailCharts(enterprise) {
// Generate X labels
var hours = [];
for(var i=0; i<72; i++) hours.push(i+'h');
var days = [];
for(var j=0; j<14; j++) days.push('D'+(j+1));
// Instant Chart (Line)
var optionInstant = {
tooltip: { trigger: 'axis' },
grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true },
xAxis: {
type: 'category',
boundaryGap: false,
data: hours,
axisLabel: { color: '#ccc' },
axisLine: { lineStyle: { color: '#00eaff' } }
},
yAxis: {
type: 'value',
axisLabel: { color: '#ccc' },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
},
series: [{
name: '瞬时量',
type: 'line',
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: { color: '#00eaff' },
areaStyle: { normal: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: 'rgba(0, 234, 255, 0.5)' }, { offset: 1, color: 'rgba(0, 234, 255, 0)' }]) } },
data: enterprise.instantHistory
}]
};
// Cumulative Chart (Bar)
var optionCumulative = {
tooltip: { trigger: 'axis' },
grid: { left: '3%', right: '4%', bottom: '3%', top: '15%', containLabel: true },
xAxis: {
type: 'category',
data: days,
axisLabel: { color: '#ccc' },
axisLine: { lineStyle: { color: '#ffaa00' } }
},
yAxis: {
type: 'value',
axisLabel: { color: '#ccc' },
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.1)' } }
},
series: [{
name: '累计流量',
type: 'bar',
itemStyle: { normal: { color: '#ffaa00', barBorderRadius: [5, 5, 0, 0] } },
data: enterprise.cumulativeHistory
}]
};
myChartInstant.setOption(optionInstant);
myChartCumulative.setOption(optionCumulative);
}
</script>
</body>
</html>