refactor(mp): 迁移透析列表页 — 使用统一组件库

- PageShell 替代手写 ScrollView + min-height/bg/padding
- SearchSection 替代搜索栏 + SegmentTabs 替代 tabs(预设患者时)
- ContentCard 替代 record-card 手写样式
- StatusTag 替代 status-tag 手写样式
- LoadingCard 替代 Loading 组件
- PaginationBar 替代手写分页
- 精简 SCSS:删除 page/search-bar/card/pagination 通用样式,保留业务特有样式
This commit is contained in:
iven
2026-05-16 00:55:50 +08:00
parent 1579f35ff5
commit 9415807a40
2 changed files with 83 additions and 150 deletions

View File

@@ -1,45 +1,24 @@
@import '../../../styles/variables.scss'; @import '../../../styles/variables.scss';
@import '../../../styles/mixins.scss';
.dialysis-page { // PageShell 已接管min-height, background, padding
min-height: 100vh; // SearchSection 已接管search-bar
background: $bg; // ContentCard 已接管record-card 背景/圆角/阴影/触摸反馈
padding-bottom: 120px; // StatusTag 已接管status-tag 标签样式
} // PaginationBar 已接管pagination 分页样式
.search-bar {
padding: 16px 24px;
background: $card;
}
.search-input {
background: $bg;
border-radius: $r-sm;
padding: 16px 20px;
font-size: var(--tk-font-body-lg);
color: $tx;
}
.record-list {
padding: 16px 24px;
}
.record-count { .record-count {
font-size: var(--tk-font-h2); margin-bottom: 16px;
color: $tx3;
padding: 8px 0 16px; text {
font-size: var(--tk-font-h2);
color: $tx3;
}
} }
.record-card { .record-cards {
background: $card; display: flex;
border-radius: $r; flex-direction: column;
padding: 24px; gap: var(--tk-gap-md);
margin-bottom: 16px;
box-shadow: $shadow-sm;
&:active {
box-shadow: $shadow-md;
}
} }
.record-card__header { .record-card__header {
@@ -69,25 +48,6 @@
} }
} }
.status-tag {
display: inline-block;
padding: 4px 12px;
border-radius: $r-xs;
font-size: var(--tk-font-body);
background: $bd-l;
color: $tx3;
&--completed {
background: $pri-l;
color: $pri;
}
&--reviewed {
background: $acc-l;
color: $acc;
}
}
.record-card__body { .record-card__body {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@@ -106,31 +66,6 @@
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.pagination {
display: flex;
justify-content: center;
align-items: center;
padding: 16px 0;
gap: 24px;
}
.page-btn {
padding: 12px 24px;
background: $card;
border-radius: $r-sm;
font-size: var(--tk-font-h1);
color: $pri;
&--disabled {
opacity: 0.4;
}
}
.page-info {
font-size: var(--tk-font-h2);
color: $tx2;
}
.fab { .fab {
position: fixed; position: fixed;
right: 32px; right: 32px;

View File

@@ -1,16 +1,21 @@
import { useState, useEffect, useCallback, useRef } from 'react'; import { useState, useEffect, useCallback, useRef } from 'react';
import { View, Text, Input, ScrollView } from '@tarojs/components'; import { View, Text } from '@tarojs/components';
import Taro, { useRouter } from '@tarojs/taro'; import Taro, { useRouter } from '@tarojs/taro';
import { usePageData } from '@/hooks/usePageData'; import { usePageData } from '@/hooks/usePageData';
import { listDialysisRecords, type DialysisRecord } from '@/services/doctor/dialysis'; import { listDialysisRecords, type DialysisRecord } from '@/services/doctor/dialysis';
import { listPatients } from '@/services/doctor/patient'; import { listPatients } from '@/services/doctor/patient';
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 SearchSection from '@/components/patterns/SearchSection';
import PaginationBar from '@/components/patterns/PaginationBar';
import SegmentTabs from '@/components/SegmentTabs';
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 { safeNavigateTo } from '@/utils/navigate'; import { safeNavigateTo } from '@/utils/navigate';
import SegmentTabs from '@/components/SegmentTabs'; import './index.scss';
import './index.scss';;
const TABS = [ const TABS = [
{ key: '', label: '全部' }, { key: '', label: '全部' },
@@ -21,6 +26,12 @@ const TABS = [
const TYPE_MAP: Record<string, string> = { HD: 'HD', HDF: 'HDF', HF: 'HF' }; const TYPE_MAP: Record<string, string> = { HD: 'HD', HDF: 'HDF', HF: 'HF' };
const STATUS_LABEL: Record<string, string> = {
draft: '草稿',
completed: '已完成',
reviewed: '已审核',
};
export default function DialysisList() { export default function DialysisList() {
const router = useRouter(); const router = useRouter();
const patientId = router.params.patientId || ''; const patientId = router.params.patientId || '';
@@ -90,82 +101,69 @@ export default function DialysisList() {
setPage(1); setPage(1);
}; };
// 服务端已按 activeTab 过滤,无需客户端二次筛选 if (loading && records.length === 0) return <LoadingCard count={3} />;
if (loading && records.length === 0) return <Loading />;
if (error) return <ErrorState onRetry={() => loadRecords(1)} />; if (error) return <ErrorState onRetry={() => loadRecords(1)} />;
return ( return (
<ScrollView scrollY className={`dialysis-page ${modeClass}`}> <PageShell safeBottom className={modeClass}>
{!patientId && ( {!patientId ? (
<View className='search-bar'> <SearchSection
<Input value={searchPatient}
className='search-input' onChange={setSearchPatient}
placeholder='搜索患者姓名' onSearch={handleSearch}
value={searchPatient} placeholder="搜索患者姓名"
onInput={(e) => setSearchPatient(e.detail.value)} filters={TABS}
confirmType='search' activeFilter={activeTab}
onConfirm={handleSearch} onFilterChange={handleTab}
/> />
</View> ) : (
<SegmentTabs tabs={TABS} activeKey={activeTab} onChange={handleTab} variant="underline" />
)} )}
<SegmentTabs tabs={TABS} activeKey={activeTab} onChange={handleTab} variant="underline" />
{!currentPatientId ? ( {!currentPatientId ? (
<EmptyState text='请搜索并选择患者' /> <EmptyState text="请搜索并选择患者" />
) : records.length === 0 ? ( ) : records.length === 0 ? (
<EmptyState text='暂无透析记录' /> <EmptyState text="暂无透析记录" />
) : ( ) : (
<View className='record-list'> <>
<View className='record-count'><Text> {total} </Text></View> <View className="record-count">
{records.map((r) => ( <Text> {total} </Text>
<View </View>
key={r.id} <View className="record-cards">
className='record-card' {records.map((r) => (
onClick={() => safeNavigateTo(`/pages/pkg-doctor-clinical/dialysis/detail/index?id=${r.id}`)} <ContentCard
> key={r.id}
<View className='record-card__header'> onPress={() => safeNavigateTo(`/pages/pkg-doctor-clinical/dialysis/detail/index?id=${r.id}`)}
<Text className={`type-tag type-tag--${(r.dialysis_type || 'hd').toLowerCase()}`}>
{TYPE_MAP[r.dialysis_type] || r.dialysis_type}
</Text>
<Text className={`status-tag status-tag--${r.status}`}>
{r.status === 'draft' ? '草稿' : r.status === 'completed' ? '已完成' : '已审核'}
</Text>
</View>
<View className='record-card__body'>
<Text className='record-card__date'>{r.dialysis_date}</Text>
{r.dialysis_duration != null && (
<Text className='record-card__meta'> {r.dialysis_duration}</Text>
)}
{r.ultrafiltration_volume != null && (
<Text className='record-card__meta'> {r.ultrafiltration_volume}ml</Text>
)}
</View>
</View>
))}
{total > 20 && (
<View className='pagination'>
<View
className={`page-btn ${page <= 1 ? 'page-btn--disabled' : ''}`}
onClick={() => page > 1 && loadRecords(page - 1)}
> >
<Text></Text> <View className="record-card__header">
</View> <Text className={`type-tag type-tag--${(r.dialysis_type || 'hd').toLowerCase()}`}>
<Text className='page-info'>{page} / {Math.ceil(total / 20)}</Text> {TYPE_MAP[r.dialysis_type] || r.dialysis_type}
<View </Text>
className={`page-btn ${page * 20 >= total ? 'page-btn--disabled' : ''}`} <StatusTag status={r.status} size="sm">{STATUS_LABEL[r.status] || r.status}</StatusTag>
onClick={() => page * 20 < total && loadRecords(page + 1)} </View>
> <View className="record-card__body">
<Text></Text> <Text className="record-card__date">{r.dialysis_date}</Text>
</View> {r.dialysis_duration != null && (
</View> <Text className="record-card__meta"> {r.dialysis_duration}</Text>
)} )}
</View> {r.ultrafiltration_volume != null && (
<Text className="record-card__meta"> {r.ultrafiltration_volume}ml</Text>
)}
</View>
</ContentCard>
))}
</View>
<PaginationBar
current={page}
total={total}
pageSize={20}
onChange={(p) => loadRecords(p)}
/>
</>
)} )}
<View <View
className='fab' className="fab"
onClick={() => { onClick={() => {
if (!currentPatientId) { if (!currentPatientId) {
Taro.showToast({ title: '请先选择患者', icon: 'none' }); Taro.showToast({ title: '请先选择患者', icon: 'none' });
@@ -174,8 +172,8 @@ export default function DialysisList() {
safeNavigateTo(`/pages/pkg-doctor-clinical/dialysis/create/index?patientId=${currentPatientId}`); safeNavigateTo(`/pages/pkg-doctor-clinical/dialysis/create/index?patientId=${currentPatientId}`);
}} }}
> >
<Text className='fab-text'>+</Text> <Text className="fab-text">+</Text>
</View> </View>
</ScrollView> </PageShell>
); );
} }