Compare commits
2 Commits
483342a1d8
...
8d41d5a167
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d41d5a167 | ||
|
|
40b88c566d |
@@ -1,121 +1,76 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.consultation-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
}
|
||||
// PageShell 已接管:min-height, background, padding
|
||||
// SearchSection 已接管:标签筛选栏
|
||||
// ContentCard 已接管:session-card 背景/圆角/阴影/触摸反馈
|
||||
// StatusTag 已接管:会话状态标签
|
||||
// PaginationBar 已接管:分页控件
|
||||
|
||||
.session-list {
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: var(--tk-gap-md);
|
||||
}
|
||||
|
||||
.session-card {
|
||||
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 {
|
||||
.session-card__top {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
padding: 24px;
|
||||
|
||||
&__btn {
|
||||
font-size: var(--tk-font-h1);
|
||||
color: $pri;
|
||||
padding: 12px 24px;
|
||||
|
||||
&.disabled {
|
||||
color: $tx3;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
font-size: var(--tk-font-h2);
|
||||
color: $tx2;
|
||||
}
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.session-card__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;
|
||||
}
|
||||
|
||||
.session-card__info {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
||||
import { View, Text, ScrollView } from '@tarojs/components';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro from '@tarojs/taro';
|
||||
import { usePageData } from '@/hooks/usePageData';
|
||||
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 EmptyState from '@/components/EmptyState';
|
||||
import { useElderClass } from '../../../hooks/useElderClass';
|
||||
import { getStatusInlineStyle, getStatusLabel } from '@/utils/statusTag';
|
||||
import { formatDateTime } from '@/utils/date';
|
||||
import { safeNavigateTo } from '@/utils/navigate';
|
||||
import SegmentTabs from '@/components/SegmentTabs';
|
||||
import './index.scss';
|
||||
|
||||
const TABS = [
|
||||
@@ -20,6 +23,12 @@ const TABS = [
|
||||
{ key: 'closed', label: '已关闭' },
|
||||
];
|
||||
|
||||
const STATUS_COLOR_MAP: Record<string, 'success' | 'warning' | 'default' | 'info'> = {
|
||||
active: 'success',
|
||||
waiting: 'warning',
|
||||
closed: 'default',
|
||||
};
|
||||
|
||||
export default function ConsultationList() {
|
||||
const modeClass = useElderClass();
|
||||
const [sessions, setSessions] = useState<ConsultationSession[]>([]);
|
||||
@@ -30,8 +39,6 @@ export default function ConsultationList() {
|
||||
const [page, setPage] = useState(1);
|
||||
const mountedRef = useRef(false);
|
||||
|
||||
const totalPages = useMemo(() => Math.ceil(total / 20), [total]);
|
||||
|
||||
const loadSessions = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
@@ -53,7 +60,6 @@ export default function ConsultationList() {
|
||||
|
||||
const { trigger } = usePageData(loadSessions);
|
||||
|
||||
// tab/page 变化时重新加载(跳过首次 mount,由 usePageData 的 useDidShow 处理)
|
||||
useEffect(() => {
|
||||
if (mountedRef.current) {
|
||||
trigger();
|
||||
@@ -71,63 +77,61 @@ export default function ConsultationList() {
|
||||
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} />;
|
||||
|
||||
return (
|
||||
<ScrollView scrollY className={`consultation-page ${modeClass}`}>
|
||||
<SegmentTabs tabs={TABS} activeKey={activeTab} onChange={handleTabChange} variant="underline" />
|
||||
<PageShell safeBottom className={modeClass}>
|
||||
<SearchSection
|
||||
value=""
|
||||
onChange={() => {}}
|
||||
filters={TABS}
|
||||
activeFilter={activeTab}
|
||||
onFilterChange={handleTabChange}
|
||||
/>
|
||||
|
||||
{sessions.length === 0 ? (
|
||||
<EmptyState text='暂无咨询会话' />
|
||||
<EmptyState text="暂无咨询会话" />
|
||||
) : (
|
||||
<View className='session-list'>
|
||||
{sessions.map((s) => {
|
||||
return (
|
||||
<View
|
||||
key={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__status' style={getStatusInlineStyle(s.status)}>
|
||||
<Text className='session-card__status-text'>{getStatusLabel(s.status)}</Text>
|
||||
</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 className="session-list">
|
||||
{sessions.map((s) => (
|
||||
<ContentCard
|
||||
key={s.id}
|
||||
onPress={() => safeNavigateTo(`/pages/pkg-doctor-core/consultation/detail/index?id=${s.id}`)}
|
||||
>
|
||||
<View className="session-card__top">
|
||||
<Text className="session-card__subject">{s.subject || '在线咨询'}</Text>
|
||||
<StatusTag
|
||||
status={s.status}
|
||||
colorMap={STATUS_COLOR_MAP}
|
||||
size="sm"
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
|
||||
{total > 20 && (
|
||||
<View className='pagination'>
|
||||
<Text
|
||||
className={`pagination__btn ${page <= 1 ? 'disabled' : ''}`}
|
||||
onClick={() => page > 1 && setPage(page - 1)}
|
||||
>上一页</Text>
|
||||
<Text className='pagination__info'>{page} / {totalPages}</Text>
|
||||
<Text
|
||||
className={`pagination__btn ${page >= totalPages ? 'disabled' : ''}`}
|
||||
onClick={() => page < totalPages && setPage(page + 1)}
|
||||
>下一页</Text>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
<PaginationBar
|
||||
current={page}
|
||||
total={total}
|
||||
pageSize={20}
|
||||
onChange={setPage}
|
||||
/>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
@import '../../../styles/mixins.scss';
|
||||
|
||||
.followup-page {
|
||||
min-height: 100vh;
|
||||
background: $bg;
|
||||
}
|
||||
// PageShell 已接管:min-height, background, padding
|
||||
// SearchSection 已接管:标签筛选栏
|
||||
// ContentCard 已接管:task-card 背景/圆角/阴影/触摸反馈
|
||||
// StatusTag 已接管:任务状态标签
|
||||
|
||||
.task-count {
|
||||
padding: 20px 28px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
text {
|
||||
font-size: var(--tk-font-h2);
|
||||
@@ -16,56 +15,37 @@
|
||||
}
|
||||
|
||||
.task-list {
|
||||
padding: 0 24px 120px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
gap: var(--tk-gap-md);
|
||||
}
|
||||
|
||||
.task-card {
|
||||
background: $card;
|
||||
border-radius: $r-lg;
|
||||
padding: 28px;
|
||||
box-shadow: $shadow-sm;
|
||||
|
||||
&:active {
|
||||
background: $bd-l;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__type {
|
||||
font-family: 'Georgia', 'Times New Roman', serif;
|
||||
font-size: var(--tk-font-body-lg);
|
||||
font-weight: 600;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
&__status {
|
||||
@include tag(transparent, $tx2);
|
||||
font-size: var(--tk-font-body);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__patient {
|
||||
font-size: var(--tk-font-h1);
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__date {
|
||||
font-size: var(--tk-font-h2);
|
||||
color: $tx3;
|
||||
}
|
||||
.task-card__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.task-card__type {
|
||||
font-size: var(--tk-font-body-lg);
|
||||
font-weight: 600;
|
||||
color: $tx;
|
||||
}
|
||||
|
||||
.task-card__patient {
|
||||
font-size: var(--tk-font-h1);
|
||||
color: $tx2;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.task-card__footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.task-card__date {
|
||||
font-size: var(--tk-font-h2);
|
||||
color: $tx3;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { View, Text, ScrollView } from '@tarojs/components';
|
||||
import { View, Text } from '@tarojs/components';
|
||||
import Taro, { useRouter } from '@tarojs/taro';
|
||||
import { usePageData } from '@/hooks/usePageData';
|
||||
import { listFollowUpTasks, type FollowUpTask } from '@/services/doctor/followup';
|
||||
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 ErrorState from '@/components/ErrorState';
|
||||
import EmptyState from '@/components/EmptyState';
|
||||
import { useElderClass } from '../../../hooks/useElderClass';
|
||||
import { getStatusInlineStyle, getStatusLabel } from '@/utils/statusTag';
|
||||
import SegmentTabs from '@/components/SegmentTabs';
|
||||
import './index.scss';
|
||||
|
||||
const TABS = [
|
||||
@@ -19,6 +21,13 @@ const TABS = [
|
||||
{ key: 'overdue', label: '已逾期' },
|
||||
];
|
||||
|
||||
const STATUS_COLOR_MAP: Record<string, 'warning' | 'info' | 'success' | 'error'> = {
|
||||
pending: 'warning',
|
||||
in_progress: 'info',
|
||||
completed: 'success',
|
||||
overdue: 'error',
|
||||
};
|
||||
|
||||
export default function FollowUpList() {
|
||||
const router = useRouter();
|
||||
const patientId = router.params.patientId || '';
|
||||
@@ -52,7 +61,6 @@ export default function FollowUpList() {
|
||||
|
||||
const { trigger } = usePageData(loadTasks);
|
||||
|
||||
// tab/patientId 变化时重新加载(跳过首次 mount,由 usePageData 的 useDidShow 处理)
|
||||
useEffect(() => {
|
||||
if (mountedRef.current) {
|
||||
trigger();
|
||||
@@ -74,43 +82,48 @@ export default function FollowUpList() {
|
||||
return map[type] || type;
|
||||
};
|
||||
|
||||
if (loading && tasks.length === 0) return <Loading />;
|
||||
if (loading && tasks.length === 0) return <LoadingCard count={3} />;
|
||||
if (error) return <ErrorState onRetry={loadTasks} />;
|
||||
|
||||
return (
|
||||
<ScrollView scrollY className={`followup-page ${modeClass}`}>
|
||||
<SegmentTabs tabs={TABS} activeKey={activeTab} onChange={(key) => setActiveTab(key)} variant="underline" />
|
||||
<PageShell safeBottom className={modeClass}>
|
||||
<SearchSection
|
||||
value=""
|
||||
onChange={() => {}}
|
||||
filters={TABS}
|
||||
activeFilter={activeTab}
|
||||
onFilterChange={(key) => setActiveTab(key)}
|
||||
/>
|
||||
|
||||
<View className='task-count'>
|
||||
<View className="task-count">
|
||||
<Text>共 {total} 项任务</Text>
|
||||
</View>
|
||||
|
||||
{tasks.length === 0 ? (
|
||||
<EmptyState text='暂无随访任务' />
|
||||
<EmptyState text="暂无随访任务" />
|
||||
) : (
|
||||
<View className='task-list'>
|
||||
{tasks.map((task) => {
|
||||
return (
|
||||
<View
|
||||
key={task.id}
|
||||
className='task-card'
|
||||
onClick={() => Taro.navigateTo({ url: `/pages/pkg-doctor-core/followup/detail/index?id=${task.id}` })}
|
||||
>
|
||||
<View className='task-card__header'>
|
||||
<Text className='task-card__type'>{getTypeLabel(task.follow_up_type)}</Text>
|
||||
<View className='task-card__status' style={getStatusInlineStyle(task.status)}>
|
||||
<Text>{getStatusLabel(task.status)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text className='task-card__patient'>{task.patient_name || '未知患者'}</Text>
|
||||
<View className='task-card__footer'>
|
||||
<Text className='task-card__date'>计划日期: {formatDate(task.planned_date)}</Text>
|
||||
</View>
|
||||
<View className="task-list">
|
||||
{tasks.map((task) => (
|
||||
<ContentCard
|
||||
key={task.id}
|
||||
onPress={() => Taro.navigateTo({ url: `/pages/pkg-doctor-core/followup/detail/index?id=${task.id}` })}
|
||||
>
|
||||
<View className="task-card__header">
|
||||
<Text className="task-card__type">{getTypeLabel(task.follow_up_type)}</Text>
|
||||
<StatusTag
|
||||
status={task.status}
|
||||
colorMap={STATUS_COLOR_MAP}
|
||||
size="sm"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
<Text className="task-card__patient">{task.patient_name || '未知患者'}</Text>
|
||||
<View className="task-card__footer">
|
||||
<Text className="task-card__date">计划日期: {formatDate(task.planned_date)}</Text>
|
||||
</View>
|
||||
</ContentCard>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</PageShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user