feat():预警模块开发

qzc-dev
李神峰 2026-02-04 13:53:28 +08:00
parent 98f1781507
commit 33fd04b889
8 changed files with 437 additions and 43 deletions

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

View File

@ -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;

View File

@ -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;
}
}
}
}

View File

@ -1,17 +1,24 @@
import React from 'react';
import CommonCard from '../../UI/CommonCard';
import ThreeDots from '../../UI/ThreeDots';
import WarningSection from './components/WarningSection';
import './index.less';
const WarningToggles = () => {
const WarningToggles = ({ activeType, onToggle }) => {
return (
<div className="warning-toggles">
<div className="toggle-item active">
<div
className={`toggle-item ${activeType === 'monitor' ? 'active' : ''}`}
onClick={() => onToggle('monitor')}
>
<span>监测预警</span>
<span className="badge">4</span>
</div>
<div className="toggle-item">
<div
className={`toggle-item ${activeType === 'forecast' ? 'active' : ''}`}
onClick={() => onToggle('forecast')}
>
<span>预报预警</span>
<span className="badge bg-red">2</span>
</div>
@ -20,6 +27,8 @@ const WarningToggles = () => {
}
const SiYu = () => {
const [warningType, setWarningType] = React.useState('monitor');
return (
<div className="siyu-view">
<div className="side-panel left">
@ -35,9 +44,9 @@ const SiYu = () => {
<CommonCard
title="预警"
className="panel-card card-1"
headerExtra={<WarningToggles />}
headerExtra={<WarningToggles activeType={warningType} onToggle={setWarningType} />}
>
<div className="placeholder-content">内容填充区域</div>
<WarningSection type={warningType} />
</CommonCard>
<CommonCard
title="实时水雨情"

View File

@ -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; }
}