feat(web/miniapp): 高发病天气预警展示(#12)
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
import { get } from './request';
|
||||||
|
|
||||||
|
export interface WeatherAlert {
|
||||||
|
id: string;
|
||||||
|
disease: string;
|
||||||
|
level: string;
|
||||||
|
reason: string;
|
||||||
|
createdAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getWeatherAlerts = (limit = 5) =>
|
||||||
|
get<WeatherAlert[]>('/weather/alerts', { limit });
|
||||||
@@ -6,6 +6,47 @@
|
|||||||
padding-bottom: $spacing-xl;
|
padding-bottom: $spacing-xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.weatherWrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $spacing-sm;
|
||||||
|
margin: $spacing-md $spacing-lg 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weatherCard {
|
||||||
|
background: rgba(255, 125, 0, 0.08);
|
||||||
|
border-radius: $radius-md;
|
||||||
|
padding: $spacing-md;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weatherHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $spacing-sm;
|
||||||
|
margin-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weatherBadge {
|
||||||
|
font-size: $font-size-xs;
|
||||||
|
color: $color-warning;
|
||||||
|
background: rgba(255, 125, 0, 0.15);
|
||||||
|
padding: 4rpx $spacing-sm;
|
||||||
|
border-radius: $radius-round;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weatherDisease {
|
||||||
|
font-size: $font-size-md;
|
||||||
|
font-weight: $font-weight-medium;
|
||||||
|
color: $color-text-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weatherReason {
|
||||||
|
font-size: $font-size-xs;
|
||||||
|
color: $color-text-secondary;
|
||||||
|
line-height: $line-height-normal;
|
||||||
|
}
|
||||||
|
|
||||||
.filterBar {
|
.filterBar {
|
||||||
display: flex;
|
display: flex;
|
||||||
background: $color-bg-card;
|
background: $color-bg-card;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { View, Text } from '@tarojs/components';
|
|||||||
import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
|
import Taro, { useDidShow, usePullDownRefresh } from '@tarojs/taro';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
import { getAlarms, acknowledgeAlarm, getAlarmClip } from '@/api/alarms';
|
import { getAlarms, acknowledgeAlarm, getAlarmClip } from '@/api/alarms';
|
||||||
|
import { getWeatherAlerts, type WeatherAlert } from '@/api/weather';
|
||||||
import {
|
import {
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
formatRelativeTime,
|
formatRelativeTime,
|
||||||
@@ -16,6 +17,7 @@ import type { Alarm } from '@/types';
|
|||||||
|
|
||||||
const AlertsPage: React.FC = () => {
|
const AlertsPage: React.FC = () => {
|
||||||
const [alarms, setAlarms] = useState<Alarm[]>([]);
|
const [alarms, setAlarms] = useState<Alarm[]>([]);
|
||||||
|
const [weatherAlerts, setWeatherAlerts] = useState<WeatherAlert[]>([]);
|
||||||
const [filterOpen, setFilterOpen] = useState(true);
|
const [filterOpen, setFilterOpen] = useState(true);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
@@ -25,6 +27,8 @@ const AlertsPage: React.FC = () => {
|
|||||||
setError('');
|
setError('');
|
||||||
const res = await getAlarms({ openOnly: filterOpen }).catch(() => [] as Alarm[]);
|
const res = await getAlarms({ openOnly: filterOpen }).catch(() => [] as Alarm[]);
|
||||||
setAlarms(res);
|
setAlarms(res);
|
||||||
|
const weatherRes = await getWeatherAlerts(5).catch(() => [] as WeatherAlert[]);
|
||||||
|
setWeatherAlerts(weatherRes);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Alerts] 数据加载失败:', err);
|
console.error('[Alerts] 数据加载失败:', err);
|
||||||
setError(err instanceof Error ? err.message : '数据加载失败');
|
setError(err instanceof Error ? err.message : '数据加载失败');
|
||||||
@@ -147,6 +151,23 @@ const AlertsPage: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* 高发病天气预警 */}
|
||||||
|
{weatherAlerts.length > 0 ? (
|
||||||
|
<View className={styles.weatherWrap}>
|
||||||
|
{weatherAlerts.map((w) => (
|
||||||
|
<View key={w.id} className={styles.weatherCard}>
|
||||||
|
<View className={styles.weatherHeader}>
|
||||||
|
<Text className={styles.weatherBadge}>
|
||||||
|
{w.level === 'orange' ? '高风险' : w.level === 'red' ? '极高风险' : '注意'}
|
||||||
|
</Text>
|
||||||
|
<Text className={styles.weatherDisease}>{w.disease}</Text>
|
||||||
|
</View>
|
||||||
|
<Text className={styles.weatherReason}>{w.reason}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{alarms.length === 0 ? (
|
{alarms.length === 0 ? (
|
||||||
<View className={styles.emptyWrap}>
|
<View className={styles.emptyWrap}>
|
||||||
<Text className={styles.emptyIcon}>{filterOpen ? '✅' : '🔔'}</Text>
|
<Text className={styles.emptyIcon}>{filterOpen ? '✅' : '🔔'}</Text>
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { get } from '../api/http';
|
||||||
|
|
||||||
|
export interface WeatherAlert {
|
||||||
|
id: string;
|
||||||
|
disease: string;
|
||||||
|
level: string;
|
||||||
|
reason: string;
|
||||||
|
createdAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const listWeatherAlerts = (limit = 5) =>
|
||||||
|
get<WeatherAlert[]>('/weather/alerts', { params: { limit } });
|
||||||
+37
-2
@@ -1,9 +1,10 @@
|
|||||||
import { Button, Tag, message } from 'antd';
|
import { Button, Card, Space, Tag, message } from 'antd';
|
||||||
import { CheckOutlined } from '@ant-design/icons';
|
import { CheckOutlined } from '@ant-design/icons';
|
||||||
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
import { ProTable, type ProColumns } from '@ant-design/pro-components';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getAlertClip, listAlerts, markAlertRead, markAllRead, type Alert } from '../dal/alert';
|
import { getAlertClip, listAlerts, markAlertRead, markAllRead, type Alert } from '../dal/alert';
|
||||||
|
import { listWeatherAlerts, type WeatherAlert } from '../dal/weather';
|
||||||
|
|
||||||
const LEVEL_MAP: Record<string, { color: string; text: string }> = {
|
const LEVEL_MAP: Record<string, { color: string; text: string }> = {
|
||||||
info: { color: 'blue', text: '提示' },
|
info: { color: 'blue', text: '提示' },
|
||||||
@@ -19,6 +20,37 @@ const LEVEL_MAP: Record<string, { color: string; text: string }> = {
|
|||||||
const levelOf = (alert: Alert) => alert.level || alert.severity || 'info';
|
const levelOf = (alert: Alert) => alert.level || alert.severity || 'info';
|
||||||
const isRead = (alert: Alert) => alert.read ?? alert.acknowledged ?? false;
|
const isRead = (alert: Alert) => alert.read ?? alert.acknowledged ?? false;
|
||||||
|
|
||||||
|
const WEATHER_LEVEL_COLORS: Record<string, string> = {
|
||||||
|
yellow: 'gold',
|
||||||
|
orange: 'orange',
|
||||||
|
red: 'red',
|
||||||
|
};
|
||||||
|
|
||||||
|
function WeatherAlertsBlock() {
|
||||||
|
const [alerts, setAlerts] = useState<WeatherAlert[]>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
listWeatherAlerts(5)
|
||||||
|
.then(setAlerts)
|
||||||
|
.catch(() => {});
|
||||||
|
}, []);
|
||||||
|
if (alerts.length === 0) return null;
|
||||||
|
return (
|
||||||
|
<Card title="高发病天气预警" size="small" style={{ marginBottom: 16 }}>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
|
{alerts.map((a) => (
|
||||||
|
<div key={a.id}>
|
||||||
|
<Tag color={WEATHER_LEVEL_COLORS[a.level] || 'gold'}>
|
||||||
|
{a.level === 'orange' ? '高风险' : a.level === 'red' ? '极高风险' : '注意'}
|
||||||
|
</Tag>
|
||||||
|
<b>{a.disease}</b>
|
||||||
|
<span style={{ marginLeft: 8, color: '#666' }}>{a.reason}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function AlertPage() {
|
export default function AlertPage() {
|
||||||
const [, setTick] = useState(0);
|
const [, setTick] = useState(0);
|
||||||
|
|
||||||
@@ -89,6 +121,8 @@ export default function AlertPage() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<WeatherAlertsBlock />
|
||||||
<ProTable<Alert>
|
<ProTable<Alert>
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@@ -110,5 +144,6 @@ export default function AlertPage() {
|
|||||||
</Button>,
|
</Button>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user