feat():预警模块开发
parent
98f1781507
commit
33fd04b889
Binary file not shown.
|
After Width: | Height: | Size: 824 B |
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -0,0 +1,164 @@
|
|||
import React, { useState } from 'react';
|
||||
import selectedBg from '@/assets/images/modal/selected.png';
|
||||
import skbg from '@/assets/images/card/skbg.png';
|
||||
import gth from '@/assets/images/card/gth.png';
|
||||
import syl from '@/assets/images/card/syl.png';
|
||||
import wy from '@/assets/images/card/wy.png';
|
||||
import './index.less';
|
||||
|
||||
const WarningSection = ({ type = 'monitor' }) => {
|
||||
const [activeTab, setActiveTab] = useState('water'); // 'water' or 'safety' for monitor; 'flood' or 'weather' for forecast
|
||||
|
||||
// Mock Data
|
||||
const waterWarnings = [
|
||||
{
|
||||
id: 1,
|
||||
name: '双石水库',
|
||||
limitLevel: '108.89',
|
||||
currentLevel: '109.09',
|
||||
exceedance: '0.20',
|
||||
time: '2025-10-28 09:00:00'
|
||||
}
|
||||
];
|
||||
|
||||
const safetyMonitorItems = [
|
||||
{ label: '渗压监测', value: 2, icon: syl },
|
||||
{ label: '渗流监测', value: 1, icon: syl },
|
||||
{ label: '位移监测', value: 0, icon: wy }
|
||||
];
|
||||
|
||||
const floodWarnings = [
|
||||
{
|
||||
id: 1,
|
||||
message: '根据上游流量站实测和产汇流预测结果,未来24小时预计总入库564.2万m³,平均入库流量65.2m³/s,达到橙色预警级别。',
|
||||
time: '2025-10-28 08:10:00'
|
||||
}
|
||||
];
|
||||
|
||||
const weatherWarnings = [
|
||||
{
|
||||
id: 1,
|
||||
title: '赤壁市气象台发布暴雨红色预警[Ⅰ级/特别...',
|
||||
time: '2025-07-28 06:35:30',
|
||||
content: '赤壁市2025年08月10日07时33分08秒发布暴雨红色预警信号:过去3小时最大降水出现在XXXXX为71毫米。受强降雨云团持续影响,预计未来3小时,上述地区及周边乡镇仍将有50-80毫米的降雨,累计雨量可达150毫米以上,局地阵风7-9级。城乡积涝、山区山洪、地质灾害、中小河流洪水风险极高,请特别加强防范。'
|
||||
}
|
||||
];
|
||||
|
||||
const renderContent = () => {
|
||||
if (type === 'monitor') {
|
||||
if (activeTab === 'water') {
|
||||
return (
|
||||
<div className="warning-list">
|
||||
{waterWarnings.map((item) => (
|
||||
<div key={item.id} className="warning-item">
|
||||
<div className="item-header">
|
||||
<img src={gth} alt="warning" className="warning-icon" />
|
||||
<div className="item-title" style={{ backgroundImage: `url(${skbg})` }}>{item.name}</div>
|
||||
</div>
|
||||
<div className="item-content">
|
||||
<p>水库汛限水位 {item.limitLevel} m。</p>
|
||||
<p>实时监测水位 {item.currentLevel} m,超出汛限水位 <span className="highlight"> {item.exceedance} m</span></p>
|
||||
<div className="item-time">{item.time}</div>
|
||||
</div>
|
||||
<div className="divider" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="safety-monitor-view">
|
||||
{safetyMonitorItems.map((item, index) => (
|
||||
<div key={index} className="monitor-item">
|
||||
<div className="monitor-value" style={{ color: item.value > 0 ? '#ff4d4f' : '#fff' }}>{item.value}</div>
|
||||
<img src={item.icon} alt={item.label} className="monitor-icon" />
|
||||
<div className="monitor-label">{item.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Forecast Warning
|
||||
if (activeTab === 'flood') {
|
||||
return (
|
||||
<div className="warning-list">
|
||||
{floodWarnings.map(item => (
|
||||
<div key={item.id} className="warning-item">
|
||||
<div className="item-content full-width">
|
||||
<div className="warning-text-wrapper">
|
||||
<img src={gth} alt="warning" className="warning-icon-inline" />
|
||||
<span className="warning-text">{item.message}</span>
|
||||
</div>
|
||||
<div className="item-time" style={{marginLeft:25,marginTop:0}}>预报时间:{item.time}</div>
|
||||
</div>
|
||||
<div className="divider" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className="warning-list">
|
||||
{weatherWarnings.map(item => (
|
||||
<div key={item.id} className="warning-item">
|
||||
<div className="item-header weather-header">
|
||||
<img src={gth} alt="rainstorm" className="weather-icon" />
|
||||
<div className="item-title" title={item.title} style={{ backgroundImage: `url(${skbg})`,paddingLeft:38 }}>{item.title}</div>
|
||||
</div>
|
||||
<div className="item-time weather-time">{item.time}</div>
|
||||
<div className="item-content weather-content">
|
||||
{item.content}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getTabs = () => {
|
||||
if (type === 'monitor') {
|
||||
return [
|
||||
{ key: 'water', label: `水库水情 (${waterWarnings.length})` },
|
||||
{ key: 'safety', label: `安全监测 (${safetyMonitorItems.reduce((acc, cur) => acc + cur.value, 0)})` } // Sum of values or just count of items? Design shows (3) which matches items count 3. Let's use 3.
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
{ key: 'flood', label: `洪水预报 (${floodWarnings.length})` },
|
||||
{ key: 'weather', label: `气象预报 (${weatherWarnings.length})` }
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
// Reset active tab when type changes
|
||||
React.useEffect(() => {
|
||||
setActiveTab(type === 'monitor' ? 'water' : 'flood');
|
||||
}, [type]);
|
||||
|
||||
const tabs = getTabs();
|
||||
|
||||
return (
|
||||
<div className="warning-section">
|
||||
{/* Tabs */}
|
||||
<div className="tabs-container">
|
||||
{tabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={`tab-item ${activeTab === tab.key ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
style={activeTab === tab.key ? { backgroundImage: `url(${selectedBg})` } : {}}
|
||||
>
|
||||
{tab.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WarningSection;
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
.warning-section {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
|
||||
.tabs-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 10px;
|
||||
padding: 0 10px;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 5px 0;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
cursor: pointer;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
transition: all 0.3s;
|
||||
margin: 0 5px;
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
text-shadow: 0 0 10px #00a0e9;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.warning-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0 10px;
|
||||
max-height: 180px;
|
||||
|
||||
.warning-item {
|
||||
margin-bottom: 15px;
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.warning-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.weather-icon {
|
||||
width: 40px;
|
||||
height: 30px;
|
||||
margin-right: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
width: 180px;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
padding: 2px 20px 2px 20px;
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.weather-header {
|
||||
.item-title {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.item-content {
|
||||
padding-left: 34px; /* Align with title (icon width + margin) */
|
||||
|
||||
&.full-width {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&.weather-content {
|
||||
padding-left: 0;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
line-height: 1.5;
|
||||
text-indent: 2em;
|
||||
}
|
||||
|
||||
.warning-text-wrapper {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.warning-icon-inline {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 5px 0;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
line-height: 1.5;
|
||||
|
||||
.highlight {
|
||||
color: #ff4d4f; /* Red for warning values */
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.item-time {
|
||||
margin-top: 5px;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255);
|
||||
}
|
||||
|
||||
.weather-time {
|
||||
padding-left: 50px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin-top: 10px;
|
||||
height: 1px;
|
||||
background: repeating-linear-gradient(to right, rgba(0, 160, 233, 0.5) 0, rgba(0, 160, 233, 0.5) 5px, transparent 5px, transparent 10px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.safety-monitor-view {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding: 20px 10px;
|
||||
height: 100%;
|
||||
|
||||
.monitor-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.monitor-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.monitor-icon {
|
||||
width: 80px;
|
||||
height: 60px;
|
||||
margin-bottom: 10px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.monitor-label {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +1,65 @@
|
|||
import React from 'react';
|
||||
import CommonCard from '../../UI/CommonCard';
|
||||
import ThreeDots from '../../UI/ThreeDots';
|
||||
import WarningSection from './components/WarningSection';
|
||||
import './index.less';
|
||||
|
||||
|
||||
const WarningToggles = () => {
|
||||
return (
|
||||
<div className="warning-toggles">
|
||||
<div className="toggle-item active">
|
||||
<span>监测预警</span>
|
||||
<span className="badge">4</span>
|
||||
</div>
|
||||
<div className="toggle-item">
|
||||
<span>预报预警</span>
|
||||
<span className="badge bg-red">2</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
const WarningToggles = ({ activeType, onToggle }) => {
|
||||
return (
|
||||
<div className="warning-toggles">
|
||||
<div
|
||||
className={`toggle-item ${activeType === 'monitor' ? 'active' : ''}`}
|
||||
onClick={() => onToggle('monitor')}
|
||||
>
|
||||
<span>监测预警</span>
|
||||
<span className="badge">4</span>
|
||||
</div>
|
||||
<div
|
||||
className={`toggle-item ${activeType === 'forecast' ? 'active' : ''}`}
|
||||
onClick={() => onToggle('forecast')}
|
||||
>
|
||||
<span>预报预警</span>
|
||||
<span className="badge bg-red">2</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const SiYu = () => {
|
||||
const [warningType, setWarningType] = React.useState('monitor');
|
||||
|
||||
return (
|
||||
<div className="siyu-view">
|
||||
<div className="side-panel left">
|
||||
<CommonCard title="预报" className="panel-card card-1">
|
||||
<div className="placeholder-content">内容填充区域</div>
|
||||
</CommonCard>
|
||||
<CommonCard title="预演" className="panel-card card-2">
|
||||
<div className="placeholder-content">内容填充区域</div>
|
||||
</CommonCard>
|
||||
</div>
|
||||
<div className="side-panel left">
|
||||
<CommonCard title="预报" className="panel-card card-1">
|
||||
<div className="placeholder-content">内容填充区域</div>
|
||||
</CommonCard>
|
||||
<CommonCard title="预演" className="panel-card card-2">
|
||||
<div className="placeholder-content">内容填充区域</div>
|
||||
</CommonCard>
|
||||
</div>
|
||||
|
||||
<div className="side-panel right">
|
||||
<CommonCard
|
||||
title="预警"
|
||||
className="panel-card card-1"
|
||||
headerExtra={<WarningToggles />}
|
||||
>
|
||||
<div className="placeholder-content">内容填充区域</div>
|
||||
</CommonCard>
|
||||
<CommonCard
|
||||
title="实时水雨情"
|
||||
className="panel-card card-2"
|
||||
headerExtra={<ThreeDots onClick={() => console.log('实时水雨情 clicked')} />}
|
||||
>
|
||||
<div className="placeholder-content">内容填充区域</div>
|
||||
</CommonCard>
|
||||
<div className="side-panel right">
|
||||
<CommonCard
|
||||
title="预警"
|
||||
className="panel-card card-1"
|
||||
headerExtra={<WarningToggles activeType={warningType} onToggle={setWarningType} />}
|
||||
>
|
||||
<WarningSection type={warningType} />
|
||||
</CommonCard>
|
||||
<CommonCard
|
||||
title="实时水雨情"
|
||||
className="panel-card card-2"
|
||||
headerExtra={<ThreeDots onClick={() => console.log('实时水雨情 clicked')} />}
|
||||
>
|
||||
<div className="placeholder-content">内容填充区域</div>
|
||||
</CommonCard>
|
||||
|
||||
<CommonCard title="预案" className="panel-card card-3">
|
||||
<div className="placeholder-content">内容填充区域</div>
|
||||
</CommonCard>
|
||||
</div>
|
||||
<CommonCard title="预案" className="panel-card card-3">
|
||||
<div className="placeholder-content">内容填充区域</div>
|
||||
</CommonCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,44 @@
|
|||
}
|
||||
|
||||
.right {
|
||||
.card-1 { flex: 1; }
|
||||
.card-1 {
|
||||
flex: 1;
|
||||
|
||||
.warning-toggles {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.toggle-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 15px;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255,0.7);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
text-shadow: 0 0 10px #00a0e9;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
background: #ff4d4f; /* Default red */
|
||||
border-radius: 7px;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
margin-left: 5px;
|
||||
padding: 0 4px;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.card-2 { flex: 1; }
|
||||
.card-3 { flex: 1; }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue