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

This commit is contained in:
iven
2026-05-16 00:56:38 +08:00
parent 483342a1d8
commit 40b88c566d
2 changed files with 124 additions and 165 deletions

View File

@@ -1,121 +1,76 @@
@import '../../../styles/variables.scss'; @import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss'; @import '../../../styles/mixins.scss';
.consultation-page { // PageShell 已接管min-height, background, padding
min-height: 100vh; // SearchSection 已接管:标签筛选栏
background: $bg; // ContentCard 已接管session-card 背景/圆角/阴影/触摸反馈
} // StatusTag 已接管:会话状态标签
// PaginationBar 已接管:分页控件
.session-list { .session-list {
padding: 20px 24px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: var(--tk-gap-md);
} }
.session-card { .session-card__top {
background: $card;
border-radius: $r-lg;
padding: 28px;
box-shadow: $shadow-sm;
position: relative;
&:active {
background: $bd-l;
}
&__top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
&__subject {
font-size: var(--tk-font-body-lg);
font-weight: 600;
color: $tx;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 16px;
}
&__status {
@include tag(transparent, $tx2);
flex-shrink: 0;
}
&__status-text {
font-size: var(--tk-font-body);
font-weight: 500;
}
&__info {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 8px;
}
&__type {
@include tag($pri-l, $pri);
}
&__time {
font-size: var(--tk-font-h2);
color: $tx3;
}
&__preview {
font-size: var(--tk-font-h1);
color: $tx2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
}
&__badge {
position: absolute;
top: 20px;
right: 20px;
min-width: 36px;
height: 36px;
background: $dan;
border-radius: $r-pill;
@include flex-center;
padding: 0 8px;
}
&__badge-text {
@include serif-number;
font-size: var(--tk-font-body);
color: $card;
font-weight: 600;
}
}
.pagination {
display: flex; display: flex;
justify-content: center; justify-content: space-between;
align-items: center; align-items: center;
gap: 24px; margin-bottom: 12px;
padding: 24px; }
&__btn { .session-card__subject {
font-size: var(--tk-font-h1); font-size: var(--tk-font-body-lg);
color: $pri; font-weight: 600;
padding: 12px 24px; color: $tx;
flex: 1;
&.disabled { overflow: hidden;
color: $tx3; text-overflow: ellipsis;
} white-space: nowrap;
} margin-right: 16px;
}
&__info {
font-size: var(--tk-font-h2); .session-card__info {
color: $tx2; display: flex;
} align-items: center;
gap: 16px;
margin-bottom: 8px;
}
.session-card__type {
@include tag($pri-l, $pri);
}
.session-card__time {
font-size: var(--tk-font-h2);
color: $tx3;
}
.session-card__preview {
font-size: var(--tk-font-h1);
color: $tx2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
}
.session-card__badge {
position: absolute;
top: 20px;
right: 20px;
min-width: 36px;
height: 36px;
background: $dan;
border-radius: $r-pill;
@include flex-center;
padding: 0 8px;
}
.session-card__badge-text {
@include serif-number;
font-size: var(--tk-font-body);
color: $card;
font-weight: 600;
} }

View File

@@ -1,16 +1,19 @@
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 { listSessions, type ConsultationSession } from '@/services/doctor/consultation'; import { listSessions, type ConsultationSession } from '@/services/doctor/consultation';
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 { useElderClass } from '../../../hooks/useElderClass'; import { useElderClass } from '../../../hooks/useElderClass';
import { getStatusInlineStyle, getStatusLabel } from '@/utils/statusTag';
import { formatDateTime } from '@/utils/date'; import { formatDateTime } from '@/utils/date';
import { safeNavigateTo } from '@/utils/navigate'; import { safeNavigateTo } from '@/utils/navigate';
import SegmentTabs from '@/components/SegmentTabs';
import './index.scss'; import './index.scss';
const TABS = [ const TABS = [
@@ -20,6 +23,12 @@ const TABS = [
{ key: 'closed', label: '已关闭' }, { key: 'closed', label: '已关闭' },
]; ];
const STATUS_COLOR_MAP: Record<string, 'success' | 'warning' | 'default' | 'info'> = {
active: 'success',
waiting: 'warning',
closed: 'default',
};
export default function ConsultationList() { export default function ConsultationList() {
const modeClass = useElderClass(); const modeClass = useElderClass();
const [sessions, setSessions] = useState<ConsultationSession[]>([]); const [sessions, setSessions] = useState<ConsultationSession[]>([]);
@@ -30,8 +39,6 @@ export default function ConsultationList() {
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 loadSessions = useCallback(async () => { const loadSessions = useCallback(async () => {
setLoading(true); setLoading(true);
setError(false); setError(false);
@@ -53,7 +60,6 @@ export default function ConsultationList() {
const { trigger } = usePageData(loadSessions); const { trigger } = usePageData(loadSessions);
// tab/page 变化时重新加载(跳过首次 mount由 usePageData 的 useDidShow 处理)
useEffect(() => { useEffect(() => {
if (mountedRef.current) { if (mountedRef.current) {
trigger(); trigger();
@@ -71,63 +77,61 @@ export default function ConsultationList() {
return formatDateTime(dateStr); return formatDateTime(dateStr);
}; };
if (loading && sessions.length === 0) return <Loading />; if (loading && sessions.length === 0) return <LoadingCard count={3} />;
if (error) return <ErrorState onRetry={loadSessions} />; if (error) return <ErrorState onRetry={loadSessions} />;
return ( return (
<ScrollView scrollY className={`consultation-page ${modeClass}`}> <PageShell safeBottom className={modeClass}>
<SegmentTabs tabs={TABS} activeKey={activeTab} onChange={handleTabChange} variant="underline" /> <SearchSection
value=""
onChange={() => {}}
filters={TABS}
activeFilter={activeTab}
onFilterChange={handleTabChange}
/>
{sessions.length === 0 ? ( {sessions.length === 0 ? (
<EmptyState text='暂无咨询会话' /> <EmptyState text="暂无咨询会话" />
) : ( ) : (
<View className='session-list'> <View className="session-list">
{sessions.map((s) => { {sessions.map((s) => (
return ( <ContentCard
<View key={s.id}
key={s.id} onPress={() => safeNavigateTo(`/pages/pkg-doctor-core/consultation/detail/index?id=${s.id}`)}
className='session-card' >
onClick={() => safeNavigateTo(`/pages/pkg-doctor-core/consultation/detail/index?id=${s.id}`)} <View className="session-card__top">
> <Text className="session-card__subject">{s.subject || '在线咨询'}</Text>
<View className='session-card__top'> <StatusTag
<Text className='session-card__subject'>{s.subject || '在线咨询'}</Text> status={s.status}
<View className='session-card__status' style={getStatusInlineStyle(s.status)}> colorMap={STATUS_COLOR_MAP}
<Text className='session-card__status-text'>{getStatusLabel(s.status)}</Text> size="sm"
</View> />
</View>
<View className='session-card__info'>
<Text className='session-card__type'>
{s.consultation_type === 'text' ? '图文' : s.consultation_type === 'video' ? '视频' : '咨询'}
</Text>
<Text className='session-card__time'>{formatTime(s.last_message_at)}</Text>
</View>
{s.last_message && (
<Text className='session-card__preview'>{s.last_message}</Text>
)}
{(s.unread_count_doctor ?? 0) > 0 && (
<View className='session-card__badge'>
<Text className='session-card__badge-text'>{s.unread_count_doctor}</Text>
</View>
)}
</View> </View>
); <View className="session-card__info">
})} <Text className="session-card__type">
{s.consultation_type === 'text' ? '图文' : s.consultation_type === 'video' ? '视频' : '咨询'}
</Text>
<Text className="session-card__time">{formatTime(s.last_message_at)}</Text>
</View>
{s.last_message && (
<Text className="session-card__preview">{s.last_message}</Text>
)}
{(s.unread_count_doctor ?? 0) > 0 && (
<View className="session-card__badge">
<Text className="session-card__badge-text">{s.unread_count_doctor}</Text>
</View>
)}
</ContentCard>
))}
</View> </View>
)} )}
{total > 20 && ( <PaginationBar
<View className='pagination'> current={page}
<Text total={total}
className={`pagination__btn ${page <= 1 ? 'disabled' : ''}`} pageSize={20}
onClick={() => page > 1 && setPage(page - 1)} onChange={setPage}
></Text> />
<Text className='pagination__info'>{page} / {totalPages}</Text> </PageShell>
<Text
className={`pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
onClick={() => page < totalPages && setPage(page + 1)}
></Text>
</View>
)}
</ScrollView>
); );
} }