refactor(mp): 试点迁移患者列表页 — 使用统一组件库
替换手写 UI 为: - PageShell 替代手动 min-height/bg/padding - SearchSection 替代手写搜索栏 + 标签筛选 - ContentCard 替代手写卡片样式(背景/圆角/阴影/触摸反馈) - StatusTag 替代 @include tag() mixin - LoadingCard 替代初始加载的 Loading 组件 SCSS 从 151 行精简到 65 行,保留页面特有业务样式。 数据加载逻辑(无限滚动 + usePageData)保持不变。
This commit is contained in:
@@ -1,47 +1,9 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.patient-list-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
padding: 24px;
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.search-input {
|
||||
background: $card;
|
||||
border-radius: $r;
|
||||
padding: 20px 24px;
|
||||
font-size: var(--tk-font-body-lg);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-filter {
|
||||
white-space: nowrap;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tag-chip {
|
||||
display: inline-block;
|
||||
padding: 10px 24px;
|
||||
border-radius: $r-pill;
|
||||
background: $bd-l;
|
||||
font-size: var(--tk-font-h2);
|
||||
color: $tx2;
|
||||
margin-right: 16px;
|
||||
|
||||
&.active {
|
||||
background: $pri;
|
||||
color: $card;
|
||||
}
|
||||
}
|
||||
// PageShell 已接管:min-height, background, padding
|
||||
// SearchSection 已接管:search-bar
|
||||
// ContentCard 已接管:patient-card 背景/圆角/阴影/触摸反馈
|
||||
// StatusTag 已接管:patient-card__status 标签样式
|
||||
|
||||
.patient-count {
|
||||
margin-bottom: 16px;
|
||||
@@ -55,90 +17,44 @@
|
||||
.patient-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--tk-gap-md);
|
||||
}
|
||||
|
||||
.patient-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.patient-card {
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 28px;
|
||||
box-shadow: $shadow-sm;
|
||||
.patient-card__name {
|
||||
font-size: var(--tk-font-num);
|
||||
font-weight: 600;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: $bd-l;
|
||||
}
|
||||
.patient-card__meta {
|
||||
font-size: var(--tk-font-h2);
|
||||
color: $tx2;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
font-size: var(--tk-font-num);
|
||||
font-weight: 600;
|
||||
color: $tx;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
font-size: var(--tk-font-h2);
|
||||
color: $tx2;
|
||||
}
|
||||
|
||||
&__tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__status {
|
||||
@include tag($bg, $tx2);
|
||||
|
||||
&--active {
|
||||
@include tag($acc-l, $acc);
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
@include tag($bd-l, $tx3);
|
||||
}
|
||||
}
|
||||
.patient-card__tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.patient-tag {
|
||||
padding: 4px 14px;
|
||||
border-radius: $r;
|
||||
background: $pri-l;
|
||||
background: rgba($pri, 0.1);
|
||||
|
||||
&__text {
|
||||
font-size: var(--tk-font-body);
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
margin-top: 32px;
|
||||
|
||||
&__btn {
|
||||
font-size: var(--tk-font-h1);
|
||||
color: $pri;
|
||||
padding: 12px 24px;
|
||||
|
||||
&.disabled {
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
font-size: var(--tk-font-h2);
|
||||
color: $tx2;
|
||||
}
|
||||
}
|
||||
|
||||
.load-more-hint-wrap {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { View, Text, Input, ScrollView } from '@tarojs/components';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useReachBottom } from '@tarojs/taro';
|
||||
import { usePageData } from '@/hooks/usePageData';
|
||||
import { listPatients, listPatientTags, type PatientItem, type PatientTag } 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 EmptyState from '@/components/EmptyState';
|
||||
import Loading from '@/components/Loading';
|
||||
import { useElderClass } from '../../../hooks/useElderClass';
|
||||
import './index.scss';
|
||||
|
||||
@@ -19,9 +24,7 @@ export default function PatientList() {
|
||||
const [page, setPage] = useState(1);
|
||||
const mountedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
loadTags();
|
||||
}, []);
|
||||
useEffect(() => { loadTags(); }, []);
|
||||
|
||||
const loadTags = async () => {
|
||||
try {
|
||||
@@ -40,11 +43,7 @@ export default function PatientList() {
|
||||
tag_id: activeTag || undefined,
|
||||
});
|
||||
const list = res.data || [];
|
||||
if (isRefresh) {
|
||||
setPatients(list);
|
||||
} else {
|
||||
setPatients((prev) => [...prev, ...list]);
|
||||
}
|
||||
setPatients(prev => isRefresh ? list : [...prev, ...list]);
|
||||
setTotal(res.total || 0);
|
||||
setPage(pageNum);
|
||||
} catch {
|
||||
@@ -59,24 +58,15 @@ export default function PatientList() {
|
||||
{ enablePullDown: true },
|
||||
);
|
||||
|
||||
// tag 变化时重新加载(跳过首次 mount,由 usePageData 的 useDidShow 处理)
|
||||
useEffect(() => {
|
||||
if (mountedRef.current) {
|
||||
loadPatients(1, true);
|
||||
}
|
||||
if (mountedRef.current) { loadPatients(1, true); }
|
||||
mountedRef.current = true;
|
||||
}, [activeTag, loadPatients]);
|
||||
|
||||
useReachBottom(() => {
|
||||
if (!loading && patients.length < total) {
|
||||
loadPatients(page + 1);
|
||||
}
|
||||
if (!loading && patients.length < total) { loadPatients(page + 1); }
|
||||
});
|
||||
|
||||
const handleSearch = () => {
|
||||
loadPatients(1, true);
|
||||
};
|
||||
|
||||
const handleTagFilter = (tagId: string) => {
|
||||
setActiveTag(tagId === activeTag ? '' : tagId);
|
||||
};
|
||||
@@ -98,91 +88,69 @@ export default function PatientList() {
|
||||
return `${age}岁`;
|
||||
};
|
||||
|
||||
if (loading && patients.length === 0) return <Loading />;
|
||||
const filters = [
|
||||
{ key: '', label: '全部' },
|
||||
...tags.map(t => ({ key: t.id, label: t.name })),
|
||||
];
|
||||
|
||||
if (loading && patients.length === 0) return <LoadingCard count={3} />;
|
||||
|
||||
return (
|
||||
<ScrollView scrollY className={`patient-list-page ${modeClass}`}>
|
||||
<View className='search-bar'>
|
||||
<Input
|
||||
className='search-input'
|
||||
placeholder='搜索患者姓名/手机号'
|
||||
value={search}
|
||||
onInput={(e) => setSearch(e.detail.value)}
|
||||
confirmType='search'
|
||||
onConfirm={handleSearch}
|
||||
/>
|
||||
</View>
|
||||
<PageShell safeBottom className={modeClass}>
|
||||
<SearchSection
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
onSearch={() => loadPatients(1, true)}
|
||||
placeholder="搜索患者姓名/手机号"
|
||||
filters={filters}
|
||||
activeFilter={activeTag}
|
||||
onFilterChange={handleTagFilter}
|
||||
/>
|
||||
|
||||
{tags.length > 0 && (
|
||||
<ScrollView scrollX className='tag-filter'>
|
||||
<View
|
||||
className={`tag-chip ${!activeTag ? 'active' : ''}`}
|
||||
onClick={() => handleTagFilter('')}
|
||||
>
|
||||
<Text>全部</Text>
|
||||
</View>
|
||||
{tags.map((tag) => (
|
||||
<View
|
||||
key={tag.id}
|
||||
className={`tag-chip ${activeTag === tag.id ? 'active' : ''}`}
|
||||
style={activeTag === tag.id && tag.color ? `background: ${tag.color}; color: white` : ''}
|
||||
onClick={() => handleTagFilter(tag.id)}
|
||||
>
|
||||
<Text>{tag.name}</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
<View className='patient-count'>
|
||||
<View className="patient-count">
|
||||
<Text>共 {total} 位患者</Text>
|
||||
</View>
|
||||
|
||||
{patients.length === 0 ? (
|
||||
<EmptyState text='暂无患者数据' />
|
||||
<EmptyState text="暂无患者数据" />
|
||||
) : (
|
||||
<View className='patient-cards'>
|
||||
<View className="patient-cards">
|
||||
{patients.map((p) => (
|
||||
<View
|
||||
<ContentCard
|
||||
key={p.id}
|
||||
className='patient-card'
|
||||
onClick={() => Taro.navigateTo({ url: `/pages/pkg-doctor-core/patients/detail/index?id=${p.id}` })}
|
||||
onPress={() => Taro.navigateTo({ url: `/pages/pkg-doctor-core/patients/detail/index?id=${p.id}` })}
|
||||
>
|
||||
<View className='patient-card__header'>
|
||||
<Text className='patient-card__name'>{p.name}</Text>
|
||||
<Text className='patient-card__meta'>
|
||||
<View className="patient-card__header">
|
||||
<Text className="patient-card__name">{p.name}</Text>
|
||||
<Text className="patient-card__meta">
|
||||
{getGenderLabel(p.gender)} {calcAge(p.birth_date)}
|
||||
</Text>
|
||||
{p.status && <StatusTag status={p.status} size="sm" />}
|
||||
</View>
|
||||
{p.tags && p.tags.length > 0 && (
|
||||
<View className='patient-card__tags'>
|
||||
<View className="patient-card__tags">
|
||||
{p.tags.map((t) => (
|
||||
<View
|
||||
key={t.id}
|
||||
className='patient-tag'
|
||||
className="patient-tag"
|
||||
style={t.color ? `background: ${t.color}20; color: ${t.color}` : ''}
|
||||
>
|
||||
<Text className='patient-tag__text'>{t.name}</Text>
|
||||
<Text className="patient-tag__text">{t.name}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
{p.status && (
|
||||
<Text className={`patient-card__status patient-card__status--${p.status}`}>
|
||||
{p.status === 'active' ? '活跃' : p.status === 'inactive' ? '非活跃' : p.status}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</ContentCard>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{!loading && patients.length >= total && total > 0 && (
|
||||
<View className='load-more-hint-wrap'>
|
||||
<Text className='load-more-hint'>没有更多了</Text>
|
||||
<View className="load-more-hint-wrap">
|
||||
<Text className="load-more-hint">没有更多了</Text>
|
||||
</View>
|
||||
)}
|
||||
{loading && patients.length > 0 && <Loading />}
|
||||
</ScrollView>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user