feat(bi): 添加BI大屏左侧面板组件和优化界面样式

- 新增NS_BI命名空间定义
- 创建左侧面板组件,包含天气预报、防汛状态、重大危险源等模块
- 实现天气预报卡片组件,集成百度天气API获取实时数据
- 添加防汛状态滚动展示功能
- 实现重大危险源监控和报警处置情况展示
- 集成ECharts图表展示领域整改情况统计
- 优化BI大屏整体布局和样式设计
- 修复气泡组件的描述字段显示问题
- 调整页面组件结构和样式间距
master
fangjiakai 2026-01-05 10:14:37 +08:00
parent a235e64228
commit f6d0cff0a1
6 changed files with 910 additions and 512 deletions

View File

@ -25,7 +25,8 @@ module.exports = {
contextInject: {
// 应用Key
appKey: "",
fileUrl: "https://jpfz.qhdsafety.com/gbsFileTest/",
// fileUrl: "https://jpfz.qhdsafety.com/gbsFileTest/",
fileUrl: "http://192.168.20.240:9787/mnt/",
},
// public/index.html注入全局变量
windowInject: {

View File

@ -6,3 +6,4 @@ import { defineNamespace } from "@cqsjjb/jjb-dva-runtime";
export const NS_GLOBAL = defineNamespace("global");
export const NS_BI = defineNamespace("bi");

View File

@ -1,12 +1,8 @@
import React from 'react';
import { RightOutlined } from '@ant-design/icons';
import closeIcon from '../../../../assets/images/public/bigScreen/close.png';
import { RightOutlined } from "@ant-design/icons";
import closeIcon from "../../../../assets/images/public/bigScreen/close.png";
const Bubble = ({ id, title, description, onClose }) => {
const close = () => {
onClose && onClose();
};
@ -40,4 +36,4 @@ const Bubble = ({ id, title, description, onClose }) => {
);
};
export default Bubble;
export default Bubble;

View File

@ -0,0 +1,632 @@
import React, { useState, useEffect, useRef } from 'react';
import * as echarts from 'echarts';
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_BI } from "~/enumerate/namespace";
import smallTitleImg from '../../../../assets/images/public/bigScreen/smalltitle.png'
import temperature from '../../../../assets/images/public/bigScreen/img10.png'
import windSpeed from '../../../../assets/images/public/bigScreen/img11.png'
import hazardIcon from '../../../../assets/images/public/bigScreen/ico3.png'
import SunIcon from '../../../../assets/images/public/weather/1.png'
import CloudIcon from '../../../../assets/images/public/weather/2.png'
import CloudSunIcon from '../../../../assets/images/public/weather/3.png'
import ThunderstormIcon from '../../../../assets/images/public/weather/4.png'
import MildRainIcon from '../../../../assets/images/public/weather/5.png'
// 中雨 6.png
import ModerateRainIcon from '../../../../assets/images/public/weather/6.png'
// 大雨 7.png
import HeavyRainIcon from '../../../../assets/images/public/weather/7.png'
// 暴雨 8.png
import StormRainIcon from '../../../../assets/images/public/weather/8.png'
// 小雪 9.png
import SnowIcon from '../../../../assets/images/public/weather/9.png'
// 中雪 10.png
import ModerateSnowIcon from '../../../../assets/images/public/weather/10.png'
// 大雪 11.png
import HeavySnowIcon from '../../../../assets/images/public/weather/11.png'
// 雨夹雪 12.png
import SnowRainIcon from '../../../../assets/images/public/weather/12.png'
// 雾 13.png
import FogIcon from '../../../../assets/images/public/weather/13.png'
// 霾 14.png
import HazeIcon from '../../../../assets/images/public/weather/14.png'
// 浮尘 15.png
import DustIcon from '../../../../assets/images/public/weather/15.png'
// 沙尘暴 16.png
import DuststormIcon from '../../../../assets/images/public/weather/16.png'
// 天气类型与图标映射表
const weatherIconMap = {
// 晴天
'晴天': <img src={SunIcon} style={{ width: 30, height: 30 }} />,
'晴': <img src={SunIcon} style={{ width: 30, height: 30 }} />,
// 多云
'多云': <img src={CloudSunIcon} style={{ width: 30, height: 30 }} />,
'阴': <img src={CloudIcon} style={{ width: 30, height: 30 }} />,
// 雷阵雨
'雷阵雨': <img src={ThunderstormIcon} style={{ width: 30, height: 30 }} />,
// 小雨
'小雨': <img src={MildRainIcon} style={{ width: 30, height: 30 }} />,
// 中雨
'中雨': <img src={ModerateRainIcon} style={{ width: 30, height: 30 }} />,
// 大雨
'大雨': <img src={HeavyRainIcon} style={{ width: 30, height: 30 }} />,
// 暴雨
'暴雨': <img src={StormRainIcon} style={{ width: 30, height: 30 }} />,
// 小雪
'小雪': <img src={SnowIcon} style={{ width: 30, height: 30 }} />,
// 中雪
'中雪': <img src={ModerateSnowIcon} style={{ width: 30, height: 30 }} />,
// 大雪
'大雪': <img src={HeavySnowIcon} style={{ width: 30, height: 30 }} />,
// 雨夹雪
'雨夹雪': <img src={SnowRainIcon} style={{ width: 30, height: 30 }} />,
// 雾
'雾': <img src={FogIcon} style={{ width: 30, height: 30 }} />,
// 霾
'霾': <img src={HazeIcon} style={{ width: 30, height: 30 }} />,
// 浮尘
'浮尘': <img src={DustIcon} style={{ width: 30, height: 30 }} />,
// 沙尘暴
'沙尘暴': <img src={DuststormIcon} style={{ width: 30, height: 30 }} />,
// 默认图标
'默认': <img src={SunIcon} style={{ width: 30, height: 30 }} />
};
// 根据天气中文名称获取对应图标
const getWeatherIcon = (weatherName) => {
// 遍历天气映射表,找到匹配的图标
for (const [key, icon] of Object.entries(weatherIconMap)) {
if (weatherName.includes(key)) {
return icon;
}
}
// 如果没有匹配的,返回默认图标
return weatherIconMap['默认'];
};
// 根据预警级别获取颜色
const getAlertColor = (level) => {
switch (level) {
case '蓝色预警':
return '#1E90FF';
case '黄色预警':
return '#FFA500';
case '橙色预警':
return '#FF4500';
case '红色预警':
return '#FF0000';
default:
return '#fff';
}
};
// 天气预报卡片组件
const WeatherCard = (props) => {
const [weatherData, setWeatherData] = useState({
text: '晴天',
temp: '36.5',
wind_class: '8级',
})
const [weatherIcon, setWeatherIcon] = useState()
const [alert, setAlert] = useState([])
const [currentAlertIndex, setCurrentAlertIndex] = useState(0)
useEffect(() => {
const fetchWeatherData = async () => {
try {
const response = await fetch('https://api.map.baidu.com/weather/v1/?district_id=130300&data_type=all&ak=dIqOi34IlTg5FkNck1vqoBpLhPAj36S1');
const data = await response.json();
setWeatherData(data.result.now);
setWeatherIcon(getWeatherIcon(data.result.now.text));
setAlert(Array.isArray(data.result.alerts) ? data.result.alerts : []);
} catch (error) {
console.error('获取天气数据失败:', error);
}
};
fetchWeatherData();
}, []);
// 添加定时器实现预警信息自动切换
useEffect(() => {
if (!alert?.length || alert.length <= 1) return;
const alertInterval = setInterval(() => {
setCurrentAlertIndex((prevIndex) =>
prevIndex === (alert?.length || 0) - 1 ? 0 : prevIndex + 1
);
}, 3000);
return () => clearInterval(alertInterval);
}, [alert?.length]);
return (
<div className="card">
<div className="card-header" style={{ backgroundImage: `url(${smallTitleImg})` }}>
<div className="card-header__text">天气预报情况</div>
</div>
<div className="weather-card__content">
<div className="weather-main">
<div className="weather-icon">
<div className="icon-container">
<div className="icon">{weatherIcon}</div>
</div>
<div className="buttom">{weatherData.text}</div>
</div>
<div className="weather-info">
<div className="weather-info__item">
<img src={temperature} />
<div className="weather-info__text">
<p>温度:</p>
<p>{weatherData.temp}</p>
</div>
</div>
<div className="weather-info__item">
<img src={windSpeed} />
<div className="weather-info__text">
<p>风速:</p>
<p>{weatherData.wind_class}</p>
</div>
</div>
</div>
</div>
<div className="weather-alert" style={{ position: 'relative', height: '30px', overflow: 'hidden' }}>
{alert?.length === 1 ? (
<p style={{ color: getAlertColor(alert[0].level), margin: '0', padding: '5px 0' }}>{alert[0].title}</p>
) : alert?.length > 1 ? (
<div
className="alert-slider"
style={{
position: 'absolute',
top: '0',
left: '0',
width: '100%',
transform: `translateY(${-currentAlertIndex * 30}px)`,
transition: 'transform 0.5s ease-in-out'
}}
>
{alert.map((item, index) => (
<p
key={index}
className="alert-item"
style={{
color: getAlertColor(item.level),
margin: '0',
padding: '5px 0',
height: '30px',
lineHeight: '20px'
}}
>
{item.title}
</p>
))}
</div>
) : null}
</div>
</div>
</div>
);
};
// 防汛状态组件
const FloodControlStatus = () => {
const floodData = [
{ company: '新益公司', status: '未处置', measures: '--' },
{ company: '二公司', status: '已处置', measures: '远离大树、电线杆、简易房等...' },
{ company: '六公司', status: '处置中', measures: '关闭门窗,加固模板、棚架、广告...' },
{ company: '一公司', status: '未处置', measures: '--' },
{ company: '三公司', status: '已处置', measures: '远离大树、电线杆、简易房等...' },
{ company: '五公司', status: '处置中', measures: '关闭门窗,加固模板、棚架、广告...' }
];
const tableBodyRef = useRef(null);
useEffect(() => {
const tableBody = tableBodyRef.current;
if (!tableBody) return;
// 获取实际行高
const firstRow = tableBody.querySelector('.table-row');
const rowHeight = firstRow ? firstRow.offsetHeight : 30;
const originalDataLength = floodData.length;
let scrollTop = 0;
const scrollInterval = setInterval(() => {
scrollTop += 1;
// 当滚动到原始数据末尾时,无缝重置到起始位置
if (scrollTop >= originalDataLength * rowHeight) {
scrollTop = 0;
}
tableBody.scrollTop = scrollTop;
}, 50);
return () => clearInterval(scrollInterval);
}, [floodData]);
return (
<div className="alert-control-card">
<div className="card-body">
<div className="table-header">
<span>公司名称</span>
<span>处置状态</span>
<span>预防措施</span>
</div>
<div className="table-body" ref={tableBodyRef}>
{[...floodData, ...floodData].map((item, index) => (
<div key={index} className="table-row">
<span>{item.company}</span>
<span className={`status-${item.status === '已处置' ? 'done' : item.status === '处置中' ? 'processing' : 'pending'}`}>
{item.status}
</span>
<span>{item.measures}</span>
</div>
))}
</div>
</div>
</div>
);
};
// 重大危险源组件
const MajorHazards = () => {
const [selectedHazard, setSelectedHazard] = useState('北区二号罐液位过低');
const hazards = [
{ id: 1, name: '北区二号罐液位过低', level: '二级', type: '液位低' },
// 可以添加更多危险源
];
const tankStatusList = [
{ name: '东区二号罐', status: '正常' },
{ name: '北区二号罐', status: '液位低' },
{ name: '东区三号罐', status: '正常' },
{ name: '北区二号罐', status: '正常' }
];
const alarmHandlingData = [
{ type: '温度待处置数/预警个数', value: '0/0' },
{ type: '压力待处置数/预警个数', value: '0/0' },
{ type: '液位待处置数/预警个数', value: '1/1' }
];
return (
<div className="card hazards-card">
<div className="card-header" style={{ backgroundImage: `url(${smallTitleImg})` }}>
<div className="card-header__text">重大危险源运行情况</div>
</div>
<div className="card-body">
{/* 告警中的危险源 */}
<div className="selected-hazard">
<div className="hazard-item">
<img src={hazardIcon} />
<span className="hazard-name">{selectedHazard}</span>
</div>
</div>
{/* 危险源统计 */}
<div className="hazard-levels">
<div className="hazard-level">
<div className="hazard-level__title">二级重大危险源 (1)</div>
<div className="hazard-level__content">
<div className="hazard-level__item">储罐数: 3</div>
<div className="hazard-level__item">再用数: 3</div>
<div className="hazard-level__item">停用数: 0</div>
</div>
</div>
<div className="hazard-level">
<div className="hazard-level__title">三级重大危险源 (2)</div>
<div className="hazard-level__content">
<div className="hazard-level__item">储罐数: 10</div>
<div className="hazard-level__item">再用数: 10</div>
<div className="hazard-level__item">停用数: 0</div>
</div>
</div>
</div>
{/* 储罐状态和报警处置情况容器 */}
<div className="tank-alarm-container">
{/* 储罐状态列表 */}
<div className="tank-status-list">
<div className="tank-status-scroller">
<div className="tank-status-scroll-content">
{/* 原始数据 */}
{tankStatusList.map((tank, index) => (
<div key={`original-${index}`} className="tank-status-item">
<span className="tank-name">{tank.name}</span>
<span className={`tank-status ${tank.status === '正常' ? 'normal' : 'warning'}`}>
{tank.status}
</span>
</div>
))}
{/* 复制数据用于无缝滚动 */}
{tankStatusList.map((tank, index) => (
<div key={`duplicate-${index}`} className="tank-status-item">
<span className="tank-name">{tank.name}</span>
<span className={`tank-status ${tank.status === '正常' ? 'normal' : 'warning'}`}>
{tank.status}
</span>
</div>
))}
</div>
</div>
</div>
{/* 报警处置情况 */}
<div className="alarm-handling">
<div className="alarm-handling__title">报警处置情况</div>
<div className="alarm-handling__content">
{alarmHandlingData.map((item, index) => (
<div key={index} className="alarm-handling__item">
<span className="item-label">{item.type}</span>
<span className="item-value">{item.value}</span>
</div>
))}
</div>
</div>
</div>
</div>
</div>
);
};
// 领域整改情况统计组件
const DomainRectification = ({ chartRef }) => {
const initChart = () => {
if (chartRef.current) {
const chart = echarts.init(chartRef.current);
const option = {
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(35, 53, 93, 0.8)',
borderColor: 'rgba(66, 105, 143, 0.8)',
borderWidth: 1,
textStyle: {
color: '#fff'
},
formatter: function (params) {
let result = params[0].name + '<br/>';
params.forEach(item => {
result += `${item.seriesName}: ${item.value}<br/>`;
});
return result;
}
},
legend: {
data: ['整改隐患数', '未整改隐患数', '整改率'],
textStyle: {
color: '#fff'
},
right: 10,
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '10%',
top: '20%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['输电', '电力', '流机', '技术中心', '铁运', '煤炭', '新益公司'],
axisLine: {
lineStyle: {
color: '#42698f'
}
},
axisLabel: {
color: '#fff',
rotate: 45
}
},
yAxis: [
{
type: 'value',
name: '数量',
min: 0,
max: 100,
axisLine: {
lineStyle: {
color: '#42698f'
}
},
axisLabel: {
color: '#fff'
},
splitLine: {
lineStyle: {
color: 'rgba(66, 105, 143, 0.3)'
}
}
},
{
type: 'value',
name: '整改率',
min: 0,
max: 100,
axisLine: {
lineStyle: {
color: '#42698f'
}
},
axisLabel: {
color: '#fff',
formatter: '{value}%'
},
splitLine: {
show: false
}
}
],
series: function() {
const rectified = [35, 40, 90, 50, 60, 75, 45];
const unrectified = [8, 12, 5, 15, 20, 10, 5];
// Calculate rectification rate (rectified / total * 100, rounded to 2 decimals)
const calculateRate = () => {
return rectified.map((r, index) => {
const total = r + unrectified[index];
return total > 0 ? parseFloat(((r / total) * 100).toFixed(2)) : 0;
});
};
return [
{
name: '整改隐患数',
type: 'bar',
stack: 'hidden',
barWidth: 10,
data: rectified,
itemStyle: {
color: '#faad14'
}
},
{
name: '未整改隐患数',
type: 'bar',
stack: 'hidden',
barWidth: 10,
data: unrectified,
itemStyle: {
color: '#ff4d4f'
}
},
{
name: '整改率',
type: 'line',
yAxisIndex: 1,
data: calculateRate(),
itemStyle: {
color: '#52c41a'
},
lineStyle: {
width: 2
},
symbolSize: 8
}
];
}()
};
chart.setOption(option);
// 响应式调整
window.addEventListener('resize', () => {
chart.resize();
});
return chart;
}
return null;
};
useEffect(() => {
const chart = initChart();
return () => {
if (chart) {
chart.dispose();
}
};
}, []);
return (
<div className="card">
<div className="card-header" style={{ backgroundImage: `url(${smallTitleImg})` }}>
<div className="card-header__text">领域整改情况统计</div>
</div>
<div className="card-body">
<div className="chart-container" ref={chartRef} style={{ width: '100%', height: '180px' }}></div>
</div>
</div>
);
};
// 公司作业情况组件
const CompanyOperationStatus = () => {
const operationData = [
{ company: '秦港股份七公司', total: 16630, rectified: 15432, pending: 529, verified: 338 },
{ company: '秦港股份二公司', total: 12771, rectified: 11321, pending: 151, verified: 249 },
{ company: '秦港股份一公司', total: 12451, rectified: 11321, pending: 151, verified: 249 },
{ company: '秦港股份三公司', total: 5533, rectified: 64455, pending: 151, verified: 249 }
];
const tableBodyRef = useRef(null);
useEffect(() => {
const tableBody = tableBodyRef.current;
if (!tableBody) return;
// 获取实际行高
const firstRow = tableBody.querySelector('.table-row');
const rowHeight = firstRow ? firstRow.offsetHeight : 30;
const originalDataLength = operationData.length;
let scrollTop = 0;
const scrollInterval = setInterval(() => {
scrollTop += 1;
// 当滚动到原始数据末尾时,无缝重置到起始位置
if (scrollTop >= originalDataLength * rowHeight) {
scrollTop = 0;
}
tableBody.scrollTop = scrollTop;
}, 50);
return () => clearInterval(scrollInterval);
}, [operationData]);
return (
<div className="card operation-status-card">
<div className="card-header" style={{ backgroundImage: `url(${smallTitleImg})` }}>
<div className="card-header__text">当前各公司作业中情况</div>
</div>
<div className="card-body">
<div className="table-header">
<span>公司名称</span>
<span>发现隐患数</span>
<span>整改隐患数</span>
<span>待整改</span>
<span>待验收</span>
</div>
<div className="table-body" ref={tableBodyRef}>
{[...operationData, ...operationData].map((item, index) => (
<div key={index} className="table-row">
<span>{item.company}</span>
<span>{item.total}</span>
<span>{item.rectified}</span>
<span>{item.pending}</span>
<span>{item.verified}</span>
</div>
))}
</div>
</div>
</div>
);
};
// 主左侧面板组件
const LeftPanel = (props) => {
const chartRef = useRef(null);
return (
<div className="left-panel">
<WeatherCard getWeather={props["getWeather"]} />
<FloodControlStatus />
<MajorHazards />
<DomainRectification chartRef={chartRef} />
<CompanyOperationStatus />
</div>
);
};
export default Connect([NS_BI], true)(LeftPanel);

View File

@ -1,18 +1,21 @@
import React, { useState } from 'react';
import { useMount } from 'ahooks';
import autofit from 'autofit.js';
import './index.less';
// import TopTitleArea from './components/topTitleArea';
// import LeftPanel from './components/leftPanel';
import { useMount } from "ahooks";
import autofit from "autofit.js";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { NS_BI } from "~/enumerate/namespace";
import { useState } from "react";
import backgroundimg from "../../../assets/images/public/bigScreen/backgroundimg.jpg";
import ico1 from "../../../assets/images/public/bigScreen/ico1.png";
import ico2 from "../../../assets/images/public/bigScreen/ico2.png";
import icobg1 from "../../../assets/images/public/bigScreen/icobg1.png";
import icobg2 from "../../../assets/images/public/bigScreen/icobg2.png";
// import RightPanel from './components/rightPanel';
// import CenterArea from './components/centerArea';
import Bubble from './bubble';
import ico2 from '../../../assets/images/public/bigScreen/ico2.png';
import icobg2 from '../../../assets/images/public/bigScreen/icobg2.png';
import ico1 from '../../../assets/images/public/bigScreen/ico1.png';
import icobg1 from '../../../assets/images/public/bigScreen/icobg1.png';
import backgroundimg from '../../../assets/images/public/bigScreen/backgroundimg.jpg';
import Bubble from "./bubble";
import LeftPanel from "./components/leftPanel";
import TopTitleArea from "./components/topTitleArea";
import "./index.less";
const mockPoints = [
{
@ -24,7 +27,7 @@ const mockPoints = [
position: { x: 117.91412, y: 38.35902 },
CORP_INFO_ID: "f8da1790b1034058ae2efefd69af3284",
style: { top: "58%", left: "30%" },
description: "公司现共有10个泊位10-20万吨级设计年通过能力6400万吨。堆场面积176万平米堆存能力740万吨大型装卸设备44台套。"
description: "公司现共有10个泊位10-20万吨级设计年通过能力6400万吨。堆场面积176万平米堆存能力740万吨大型装卸设备44台套。",
},
{
id: "00003",
@ -35,7 +38,7 @@ const mockPoints = [
position: { x: 119.61254, y: 39.92572 },
style: { top: "30%", right: "46.8%" },
CORP_INFO_ID: "",
description: "秦皇岛港分为东、西两个港区现有生产性泊位50个年设计通过能力2.26亿吨,经营货类主要包括煤炭、金属矿石、油品及液体化工、集装箱及其他杂货等。"
description: "秦皇岛港分为东、西两个港区现有生产性泊位50个年设计通过能力2.26亿吨,经营货类主要包括煤炭、金属矿石、油品及液体化工、集装箱及其他杂货等。",
},
{
id: "00004",
@ -46,8 +49,8 @@ const mockPoints = [
position: { x: 118.51022, y: 38.93503 },
CORP_INFO_ID: "8854edee3aa94be496cee676b6d4845a",
style: { top: "49%", left: "38.5%" },
description: "公司现共有6个泊位(5-30万吨级)设计年通过能力6550万吨。堆场面积146万平米堆存能力1350万吨大型装卸设备23台套。"
}
description: "公司现共有6个泊位(5-30万吨级)设计年通过能力6550万吨。堆场面积146万平米堆存能力1350万吨大型装卸设备23台套。",
},
];
const BiScreen = () => {
const [bubbleVisibleMeta, setBubbleVisibleMeta] = useState({});
@ -61,7 +64,7 @@ const BiScreen = () => {
const handleBubbleClose = (id) => {
setBubbleVisibleMeta(prev => ({
...prev,
[id]: false
[id]: false,
}));
};
@ -78,7 +81,6 @@ const BiScreen = () => {
};
});
return (
<div id="screenContainerId" className="screen-container" style={{ backgroundImage: `url(${backgroundimg})` }}>
{/* 顶部标题区 */}
@ -87,7 +89,7 @@ const BiScreen = () => {
{/* 左中右面板 */}
<div className="screen-body">
{/* 左侧面板 */}
{/* <LeftPanel/> */}
<LeftPanel />
{/* 中间面板 */}
{/* <CenterArea initPoint={initPoint} /> */}
@ -99,34 +101,36 @@ const BiScreen = () => {
{/* 地图三个点位信息坐标 start */}
{mockPoints.map((item, index) => (
<div key={index} className="center-area__point" style={item.style}>
{item.id === '00003' ? (
<div className="img">
<img
src={ico2}
alt=""
onClick={() => handleBubbleVisible(item.id)}
/>
<img src={icobg2} alt="" />
</div>
) : (
<div className="img">
<img
src={ico1}
alt=""
onClick={() => handleBubbleVisible(item.id)}
/>
<img
src={icobg1}
alt=""
/>
</div>
)}
{item.id === "00003"
? (
<div className="img">
<img
src={ico2}
alt=""
onClick={() => handleBubbleVisible(item.id)}
/>
<img src={icobg2} alt="" />
</div>
)
: (
<div className="img">
<img
src={ico1}
alt=""
onClick={() => handleBubbleVisible(item.id)}
/>
<img
src={icobg1}
alt=""
/>
</div>
)}
<div className="bubble-box">
{bubbleVisibleMeta[item.id] && (
<Bubble
id={item.id}
title={item.name}
description={item.descr || item.description || '暂无描述'}
description={item.descr || item.description || "暂无描述"}
onClose={() => handleBubbleClose(item.id)}
/>
)}
@ -138,4 +142,4 @@ const BiScreen = () => {
);
};
export default BiScreen;
export default Connect([NS_BI], true)(BiScreen);

View File

@ -1,10 +1,8 @@
/* 驾驶舱专用 - 通用样式规则 */
/* 顶部标题区域样式 */
.top-title-area {
box-sizing: border-box;
width: 100%;
height: 80px;
height: 75px;
.top-title-area__back {
width: 65px;
@ -110,7 +108,7 @@
padding-top: 10px;
width: 100%;
display: grid;
grid-template-columns: 450px 1fr 450px;
grid-template-columns: 422px 1fr 450px;
gap: 8px;
}
@ -148,45 +146,11 @@
}
}
// /* 卡片样式 */
// .card {
// width: 100%;
// background: rgba(4, 24, 52, 0.85);
// border-radius: 8px;
// box-shadow: 0 0 12px rgba(20, 143, 255, 0.4);
// overflow: hidden;
// .card-header {
// width: 100%;
// height: 38px;
// background-image: url("../../../assets/images/public/bigScreen/smalltitle.png");
// background-origin: border-box;
// background-position: left;
// background-repeat: no-repeat;
// background-size: contain;
// .card-header__text {
// line-height: 35px;
// padding-left: 25px;
// font-size: 18px;
// font-weight: bold;
// color: #fff;
// }
// }
// .card-body {
// height: calc(100% - 40px);
// padding: 10px;
// box-sizing: border-box;
// }
// }
/* 左侧面板样式 */
.left-panel {
height: 100%;
display: flex;
flex-direction: column;
gap: 15px;
gap: 5px;
z-index: 100;
}
@ -199,7 +163,6 @@
.card-header {
width: 100%;
height: 38px;
background-origin: border-box;
background-position: left;
background-repeat: no-repeat;
@ -208,52 +171,72 @@
.card-header__text {
line-height: 35px;
padding-left: 25px;
font-size: 18px;
padding-left: 30px;
font-size: 14px;
font-weight: bold;
color: #fff;
}
.weather-card__content {
padding: 15px;
padding: 5px 20px;
padding-bottom: 0;
}
.weather-main {
display: flex;
align-items: center;
gap: 20px;
gap: 45px;
margin-bottom: 15px;
}
.weather-icon {
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #42698f;
width: 62px;
height: 53px;
text-align: center;
.buttom {
width: 100%;
height: 20%;
color: #fff;
font-size: 12px;
text-align: center;
}
}
.wind-icon {
width: 60px;
height: 60px;
background: rgba(5, 227, 251, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #05e3fb;
font-size: 18px;
font-weight: bold;
border: 1px solid #05e3fb;
}
.weather-info {
display: flex;
flex-direction: column;
gap: 10px;
justify-content: flex-start;
align-items: center;
gap: 45px;
.weather-info__item {
display: flex;
justify-content: space-around;
align-items: center;
gap: 8px;
&>img {
animation: zooming 1.3s infinite ease-in-out;
}
.weather-info__text {
color: #fff;
&>p:nth-child(1) {
font-size: 12px;
margin: 0;
}
&>p:nth-child(2) {
font-size: 18px;
color: #00e7ff;
margin: 0;
}
}
}
}
.temperature {
@ -291,45 +274,67 @@
}
.weather-alert {
padding: 10px;
background: rgba(255, 87, 34, 0.2);
border: 1px solid #ff5722;
border-radius: 4px;
}
.weather-alert p {
margin: 0;
font-size: 14px;
color: #ff5722;
line-height: 1.4;
color: #FE9E00;
}
/* 防汛状态样式 */
.flood-control-card {
@extend .card;
.alert-control-card {
.card-body {
margin: 0 18px;
padding: 0 10px;
border: 2px solid transparent;
border-image: linear-gradient(to bottom, #24529B, transparent) 1 1;
}
}
.table-header {
display: flex;
justify-content: space-between;
padding: 8px 0;
padding: 4px 0;
border-bottom: 1px solid #42698f;
font-weight: bold;
color: #fff;
font-size: 14px;
font-size: 12px;
}
.table-header span:first-child {
width: 30%;
}
.table-header span:nth-child(2) {
width: 20%;
text-align: center;
}
.table-header span:last-child {
width: 50%;
padding-left: 30px;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.table-body {
margin-top: 10px;
height: 70px;
overflow: hidden;
position: relative;
}
.table-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid rgba(66, 105, 143, 0.3);
font-size: 13px;
font-size: 12px;
color: #fff;
}
@ -344,6 +349,7 @@
.table-row span:last-child {
width: 50%;
padding-left: 30px;
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
@ -352,40 +358,36 @@
.status-done {
color: #52c41a;
background: rgba(82, 196, 26, 0.2);
padding: 2px 8px;
border-radius: 10px;
}
.status-processing {
color: #faad14;
background: rgba(250, 173, 20, 0.2);
padding: 2px 8px;
border-radius: 10px;
}
.status-pending {
color: #ff4d4f;
background: rgba(255, 77, 79, 0.2);
padding: 2px 8px;
border-radius: 10px;
}
/* 重大危险源样式 */
.hazards-card {
@extend .card;
}
.hazard-list {
margin-bottom: 15px;
.card-body {
margin: 5px 18px;
padding: 0 10px;
}
}
.hazard-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background: rgba(255, 77, 79, 0.1);
padding: 5px 10px;
background: rgba(19, 47, 112, 0.1);
border: 1px solid #ff4d4f;
border-radius: 4px;
cursor: pointer;
@ -393,11 +395,6 @@
color: #fff;
}
.hazard-item.selected {
background: rgba(255, 77, 79, 0.3);
border-color: #ff7875;
}
.hazard-icon {
font-size: 20px;
}
@ -406,17 +403,143 @@
font-size: 14px;
}
.hazard-stats {
display: flex;
gap: 20px;
margin-bottom: 15px;
.selected-hazard {
margin-bottom: 5px;
}
.stat-item {
text-align: center;
/* 危险源等级统计 */
.hazard-levels {
display: flex;
gap: 15px;
margin-bottom: 5px;
}
.hazard-level {
flex: 1;
background: rgba(19, 47, 112, 0.1);
border: 1px solid #42698f;
border-radius: 4px;
padding: 5px 10px;
}
.hazard-level__title {
font-size: 14px;
font-weight: bold;
color: #05e3fb;
margin-bottom: 8px;
}
.hazard-level__content {
display: flex;
flex-direction: column;
gap: 4px;
}
.hazard-level__item {
font-size: 12px;
color: #fff;
}
.tank-status-scroller {
height: 114px;
overflow: hidden;
position: relative;
}
.tank-status-scroll-content {
animation: scroll-up 10s linear infinite;
}
@keyframes scroll-up {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-50%);
}
}
.tank-status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
background: rgba(19, 47, 112, 0.1);
border-bottom: 1px solid rgba(66, 105, 143, 0.3);
font-size: 12px;
color: #fff;
// height: 38px;
box-sizing: border-box;
flex-shrink: 0;
}
.tank-status-item:last-child {
border-bottom: none;
}
.tank-status.normal {
color: #52c41a;
padding: 2px 8px;
border-radius: 10px;
font-weight: bold;
}
.tank-status.warning {
color: #faad14;
padding: 2px 8px;
border-radius: 10px;
font-weight: bold;
}
/* 储罐状态和报警处置情况容器 */
.tank-alarm-container {
display: flex;
gap: 15px;
}
.tank-status-list {
flex-basis: 50%;
}
/* 报警处置情况 */
.alarm-handling {
flex-basis: 50%;
background: rgba(19, 47, 112, 0.1);
border: 1px solid #42698f;
border-radius: 4px;
padding: 5px 10px;
}
.alarm-handling__title {
font-size: 14px;
font-weight: bold;
color: #05e3fb;
margin-bottom: 8px;
}
.alarm-handling__content {
display: flex;
flex-direction: column;
gap: 6px;
}
.alarm-handling__item {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #fff;
}
.item-label {
color: #c0c0c0;
}
.item-value {
color: #fff;
font-weight: bold;
}
.stat-label {
font-size: 13px;
margin-bottom: 5px;
@ -519,16 +642,6 @@
font-size: 13px;
}
/* 领域整改情况统计样式 */
.domain-rectification-card {
@extend .card;
}
.chart-container {
width: 100%;
height: 200px;
}
.flow-machine-tooltip {
background: rgba(35, 53, 93, 0.95);
border: 1px solid #42698f;
@ -551,11 +664,6 @@
color: #05e3fb;
}
/* 公司作业情况样式 */
.operation-status-card {
@extend .card;
}
.operation-status-card .table-header span {
width: 20%;
text-align: center;
@ -564,140 +672,7 @@
.operation-status-card .table-row span {
width: 20%;
text-align: center;
font-size: 13px;
}
/* 中间区域样式 */
.center-area {
height: 100%;
display: flex;
flex-direction: column;
gap: 20px;
z-index: 100;
box-sizing: border-box;
width: 100%;
position: relative;
.center-area__navlist {
position: absolute;
top: 73px;
right: 30px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 3px;
.center-area__navlist__item {
cursor: pointer;
width: 157px;
height: 54px;
background-image: url("../../../assets/images/public/bigScreen/bg3.png");
background-origin: border-box;
background-repeat: no-repeat;
background-size: contain;
.center-area__navlist__item__title {
color: #fff;
font-size: 21px;
font-weight: bold;
text-align: center;
line-height: 50px;
margin-right: 17px;
}
}
}
.center-area__content {
width: 100%;
height: 282px;
position: absolute;
bottom: 15px;
left: 50%;
transform: translate(-50%, 0);
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
.node_style__1 {
.card-body {
margin-top: 10px;
.card-body__table {
width: 100%;
background: linear-gradient(180deg, #0e1a4f7a, transparent);
.text-ellipsis {
width: 235px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-body__table_thead {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
&>div {
text-align: start;
color: #fff;
padding: 7px;
font-size: 16px;
}
&>div:nth-child(1) {
width: 258px;
}
}
.card-body__table_tbody {
.card-body__table_tbody_scroll {
width: 100%;
height: 200px;
overflow: hidden;
}
.tbody__item {
color: #fff;
width: 100%;
padding: 12px 5px;
display: flex;
justify-content: space-between;
align-items: center;
&>span:not(:first-child) {
width: 82px;
text-align: center;
}
&>span:nth-child(1) {
display: inline-block;
text-align: start;
}
&>span:nth-child(2) {
display: inline-block;
text-align: center;
}
}
}
}
}
}
.node_style__2 {
.card-body {
margin-top: 10px;
.echarts_node_2 {
width: 100%;
height: 232px;
}
}
}
}
font-size: 12px;
}
/* 右侧面板样式 */
@ -709,217 +684,6 @@
z-index: 100;
}
/* 节点样式 */
.node_style__1,
.node_style__2,
.node_style__3,
.node_style__4 {
height: calc(33.33% - 13.33px);
}
/* 节点1样式 */
.node_style__1 {
.card-body {
display: flex;
justify-content: space-around;
align-items: center;
.card-body__items {
text-align: center;
&>p {
color: #fff;
}
.card-body__items__title {
font-size: 16px;
margin-bottom: 10px;
}
.card-body__items__imgbox {
width: 80px;
height: 40px;
background-image: url("../../../assets/images/public/bigScreen/bg1.png");
background-origin: border-box;
background-repeat: no-repeat;
background-size: cover;
&>img {
width: 40px;
height: 40px;
animation: moveUpDown 1.3s infinite ease-in-out;
}
}
.card-body__items__text {
display: flex;
text-align: left;
margin-top: 10px;
font-size: 14px;
color: #fff;
}
}
}
}
/* 节点2样式 */
.node_style__2 {
.card-body {
margin-top: 8px;
.echarts_node_style_2 {
width: 100%;
height: 200px;
padding: 0 10px;
}
}
}
/* 节点3样式 */
.node_style__3 {
.card-body {
margin-top: 15px;
.card-body__imgbox {
display: flex;
align-items: center;
justify-content: space-between;
gap: 2px;
.card-body__imgbox__items {
width: 242px;
height: 67px;
background-image: url("../../../assets/images/public/bigScreen/bg4.png");
background-origin: border-box;
background-repeat: no-repeat;
background-size: contain;
padding: 12px;
display: flex;
align-items: center;
gap: 12px;
&>div>img {
animation: zooming 1.3s infinite ease-in-out;
}
.items__content {
color: #fff;
&>p:nth-child(1) {
font-size: 15px;
margin-bottom: 5px;
}
&>p:nth-child(2) {
font-size: 18px;
}
}
}
}
.card-body__count {
margin-top: 30px;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 30px;
.card-body__count__items {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 8px;
margin-left: 30px;
.card-body__count_imgbox {
width: 53px;
height: 36px;
background-image: url("../../../assets/images/public/bigScreen/bg2.png");
background-origin: border-box;
background-repeat: no-repeat;
background-size: contain;
&>img {
animation: moveUpDown__2 1.3s infinite ease-in-out;
}
}
.count__content {
color: #fff;
&>p:nth-child(1) {
font-size: 15px;
margin-bottom: 5px;
}
&>p:nth-child(2) {
font-size: 18px;
}
}
}
}
}
}
/* 节点4样式 */
.node_style__4 {
width: 100%;
box-sizing: border-box;
.card-body {
margin-top: 12px;
width: 100%;
color: #fff;
.card-body__tabbar {
display: flex;
justify-content: flex-end;
align-items: center;
.tabBarItem {
cursor: pointer;
padding: 6px 8px;
background-color: #00216d;
&:first-child {
border-radius: 5px 0 0 5px;
}
&:last-child {
border-radius: 0 5px 5px 0;
}
}
.tabBarItemIndex {
background-color: #094ed8;
}
}
.card-body-tabbar__content {
width: 100%;
margin-top: 6px;
.echarts_node_style_4 {
width: 100%;
height: 190px;
}
.echarts_node_style_5 {
width: 100%;
height: 190px;
}
}
}
}
/* 图表容器样式 */
.echarts_node_style_2,
.echarts_node_2,
.echarts_node {
width: 100%;
height: 100%;
}
/* 气泡样式 */
.bubble-wrap {
width: 100%;