refactor(mp): 迁移告警列表页 — 使用统一组件库 PageShell/ContentCard/StatusTag/LoadingCard/SearchSection/PaginationBar

This commit is contained in:
iven
2026-05-16 00:56:26 +08:00
parent ae23baeece
commit 483342a1d8
2 changed files with 132 additions and 208 deletions

View File

@@ -1,18 +1,16 @@
@import '../../../styles/variables.scss'; @import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
.alert-list-page { // PageShell 已接管min-height, background, padding
min-height: 100vh; // SearchSection 已接管:筛选标签栏
background: $bg; // ContentCard 已接管alert-card 背景/圆角/阴影/触摸反馈
padding: 24px; // StatusTag 已接管:告警严重度和状态标签样式
padding-bottom: 120px; // PaginationBar 已接管:分页控件
}
.alert-list-header { .alert-list-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 24px; margin-bottom: 16px;
} }
.alert-list-title { .alert-list-title {
@@ -29,126 +27,47 @@
.alert-cards { .alert-cards {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: var(--tk-gap-md);
} }
.alert-card { // 告警卡片左侧彩色边框 — 业务特有,不归 ContentCard
background: $card; .alert-card--critical {
border-radius: $r-lg; border-left: 4px solid $dan;
padding: 24px; }
box-shadow: $shadow-sm;
.alert-card--warning {
border-left: 4px solid $wrn; border-left: 4px solid $wrn;
&:active {
background: $bd-l;
}
&--critical {
border-left-color: $dan;
}
&--info {
border-left-color: $tx3;
}
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
&__title {
font-size: var(--tk-font-body-lg);
font-weight: 500;
color: $tx;
margin-bottom: 8px;
}
&__footer {
display: flex;
justify-content: space-between;
align-items: center;
}
&__time {
font-size: var(--tk-font-body);
color: $tx3;
}
} }
.alert-severity { .alert-card--info {
font-size: var(--tk-font-body); border-left: 4px solid $tx3;
font-weight: 600;
padding: 4px 12px;
border-radius: $r-sm;
&--info {
background: $bd-l;
color: $tx2;
}
&--warning {
background: $wrn-l;
color: $wrn;
}
&--critical {
background: $dan-l;
color: $dan;
}
&--urgent {
background: $dan-l;
color: $dan;
}
} }
.alert-status { .alert-card--urgent {
font-size: var(--tk-font-body); border-left: 4px solid $dan;
padding: 4px 12px;
border-radius: $r-sm;
&--pending {
background: $wrn-l;
color: $wrn;
}
&--acknowledged {
background: $pri-l;
color: $pri;
}
&--resolved {
background: $acc-l;
color: $acc;
}
&--dismissed {
background: $bd-l;
color: $tx3;
}
} }
.alert-pagination { .alert-card__header {
display: flex; display: flex;
justify-content: center; justify-content: space-between;
align-items: center; align-items: center;
gap: 24px; margin-bottom: 12px;
margin-top: 32px; }
&__btn { .alert-card__title {
font-size: var(--tk-font-h1); font-size: var(--tk-font-body-lg);
color: $pri; font-weight: 500;
padding: 12px 24px; color: $tx;
margin-bottom: 8px;
&.disabled { }
color: $tx3;
} .alert-card__footer {
} display: flex;
justify-content: space-between;
&__info { align-items: center;
font-size: var(--tk-font-h2); }
color: $tx2;
} .alert-card__time {
font-size: var(--tk-font-body);
color: $tx3;
} }

View File

@@ -1,35 +1,53 @@
import { useState, useEffect, useMemo, useCallback, useRef } from 'react'; import { useState, useEffect, useCallback, useRef } from 'react';
import { View, Text, ScrollView } from '@tarojs/components'; import { View, Text } from '@tarojs/components';
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
import { usePageData } from '@/hooks/usePageData'; import { usePageData } from '@/hooks/usePageData';
import { listAlerts, type Alert } from '@/services/doctor/alerts'; import { listAlerts, type Alert } from '@/services/doctor/alerts';
import Loading from '@/components/Loading'; import PageShell from '@/components/ui/PageShell';
import ContentCard from '@/components/ui/ContentCard';
import StatusTag from '@/components/ui/StatusTag';
import LoadingCard from '@/components/ui/LoadingCard';
import PaginationBar from '@/components/patterns/PaginationBar';
import SearchSection from '@/components/patterns/SearchSection';
import ErrorState from '@/components/ErrorState'; import ErrorState from '@/components/ErrorState';
import EmptyState from '@/components/EmptyState'; import EmptyState from '@/components/EmptyState';
import SegmentTabs from '@/components/SegmentTabs';
import { useElderClass } from '../../../hooks/useElderClass'; import { useElderClass } from '../../../hooks/useElderClass';
import { safeNavigateTo } from '@/utils/navigate'; import { safeNavigateTo } from '@/utils/navigate';
import './index.scss'; import './index.scss';
const SEVERITY_MAP: Record<string, { label: string; className: string }> = { const SEVERITY_COLOR_MAP: Record<string, 'success' | 'warning' | 'error' | 'default'> = {
info: { label: '提示', className: 'alert-severity--info' }, info: 'default',
warning: { label: '警告', className: 'alert-severity--warning' }, warning: 'warning',
critical: { label: '严重', className: 'alert-severity--critical' }, critical: 'error',
urgent: { label: '紧急', className: 'alert-severity--urgent' }, urgent: 'error',
}; };
const STATUS_MAP: Record<string, { label: string; className: string }> = { const SEVERITY_LABEL: Record<string, string> = {
pending: { label: '待处理', className: 'alert-status--pending' }, info: '提示',
acknowledged: { label: '已确认', className: 'alert-status--acknowledged' }, warning: '警告',
resolved: { label: '已恢复', className: 'alert-status--resolved' }, critical: '严重',
dismissed: { label: '已忽略', className: 'alert-status--dismissed' }, urgent: '紧急',
}; };
const STATUS_TABS = [ const STATUS_COLOR_MAP: Record<string, 'success' | 'warning' | 'info' | 'default'> = {
{ value: '', label: '全部' }, pending: 'warning',
{ value: 'pending', label: '待处理' }, acknowledged: 'info',
{ value: 'acknowledged', label: '已确认' }, resolved: 'success',
{ value: 'resolved', label: '已恢复' }, dismissed: 'default',
};
const STATUS_LABEL: Record<string, string> = {
pending: '待处理',
acknowledged: '已确认',
resolved: '已恢复',
dismissed: '已忽略',
};
const STATUS_FILTERS = [
{ key: '', label: '全部' },
{ key: 'pending', label: '待处理' },
{ key: 'acknowledged', label: '已确认' },
{ key: 'resolved', label: '已恢复' },
]; ];
export default function AlertList() { export default function AlertList() {
@@ -42,8 +60,6 @@ export default function AlertList() {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const mountedRef = useRef(false); const mountedRef = useRef(false);
const totalPages = useMemo(() => Math.ceil(total / 20), [total]);
const loadAlerts = useCallback(async () => { const loadAlerts = useCallback(async () => {
setLoading(true); setLoading(true);
setError(false); setError(false);
@@ -65,7 +81,6 @@ export default function AlertList() {
const { trigger } = usePageData(loadAlerts); const { trigger } = usePageData(loadAlerts);
// tab/page 变化时重新加载(跳过首次 mount由 usePageData 的 useDidShow 处理)
useEffect(() => { useEffect(() => {
if (mountedRef.current) { if (mountedRef.current) {
trigger(); trigger();
@@ -94,89 +109,79 @@ export default function AlertList() {
return d.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); return d.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
}; };
if (loading && alerts.length === 0) return <Loading />; if (loading && alerts.length === 0) return <LoadingCard count={3} />;
if (error) { if (error) {
return ( return (
<ScrollView scrollY className={`alert-list-page ${modeClass}`}> <PageShell safeBottom className={modeClass}>
<View className='alert-list-header'> <SearchSection
<Text className='alert-list-title'></Text> value=""
</View> onChange={() => {}}
<View className='alert-tabs'> filters={STATUS_FILTERS}
{STATUS_TABS.map((tab) => ( activeFilter={activeTab}
<Text onFilterChange={handleTabChange}
key={tab.value} />
className={`alert-tab ${activeTab === tab.value ? 'alert-tab--active' : ''}`}
onClick={() => handleTabChange(tab.value)}
>
{tab.label}
</Text>
))}
</View>
<ErrorState onRetry={loadAlerts} /> <ErrorState onRetry={loadAlerts} />
</ScrollView> </PageShell>
); );
} }
return ( return (
<ScrollView scrollY className={`alert-list-page ${modeClass}`}> <PageShell safeBottom className={modeClass}>
<View className='alert-list-header'> <View className="alert-list-header">
<Text className='alert-list-title'></Text> <Text className="alert-list-title"></Text>
<Text className='alert-list-count'> {total} </Text> <Text className="alert-list-count"> {total} </Text>
</View> </View>
<SegmentTabs tabs={STATUS_TABS.map(t => ({ key: t.value, label: t.label }))} activeKey={activeTab} onChange={handleTabChange} variant="pill" /> <SearchSection
value=""
onChange={() => {}}
filters={STATUS_FILTERS}
activeFilter={activeTab}
onFilterChange={handleTabChange}
/>
{alerts.length === 0 ? ( {alerts.length === 0 ? (
<EmptyState description='暂无告警' /> <EmptyState text="暂无告警" />
) : ( ) : (
<View className='alert-cards'> <View className="alert-cards">
{alerts.map((alert) => { {alerts.map((alert) => (
const severity = SEVERITY_MAP[alert.severity] ?? SEVERITY_MAP.info; <ContentCard
const status = STATUS_MAP[alert.status] ?? STATUS_MAP.pending; key={alert.id}
return ( onPress={() => handleAlertClick(alert)}
<View className={`alert-card--${alert.severity || 'info'}`}
key={alert.id} >
className='alert-card' <View className="alert-card__header">
onClick={() => handleAlertClick(alert)} <StatusTag
> status={alert.severity}
<View className='alert-card__header'> colorMap={SEVERITY_COLOR_MAP}
<Text className={`alert-severity ${severity.className}`}> size="sm"
{severity.label} >
</Text> {SEVERITY_LABEL[alert.severity] || '提示'}
<Text className={`alert-status ${status.className}`}> </StatusTag>
{status.label} <StatusTag
</Text> status={alert.status}
</View> colorMap={STATUS_COLOR_MAP}
<Text className='alert-card__title'>{alert.title}</Text> size="sm"
<View className='alert-card__footer'> >
<Text className='alert-card__time'>{formatTime(alert.created_at)}</Text> {STATUS_LABEL[alert.status] || '未知'}
</View> </StatusTag>
</View> </View>
); <Text className="alert-card__title">{alert.title}</Text>
})} <View className="alert-card__footer">
<Text className="alert-card__time">{formatTime(alert.created_at)}</Text>
</View>
</ContentCard>
))}
</View> </View>
)} )}
{total > 20 && ( <PaginationBar
<View className='alert-pagination'> current={page}
<Text total={total}
className={`alert-pagination__btn ${page <= 1 ? 'disabled' : ''}`} pageSize={20}
onClick={() => page > 1 && setPage(page - 1)} onChange={setPage}
> />
</PageShell>
</Text>
<Text className='alert-pagination__info'>
{page} / {totalPages}
</Text>
<Text
className={`alert-pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
onClick={() => page < totalPages && setPage(page + 1)}
>
</Text>
</View>
)}
</ScrollView>
); );
} }