feat(miniprogram): 医护端小程序页面 — 8 页面覆盖患者/咨询/随访/报告
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

Iteration 2 医护端前端核心页面:

- 新增 doctor.ts service 层(仪表盘/患者/咨询/随访/报告 API)
- 升级医生首页:接入真实仪表盘数据 + 快捷操作入口
- 患者管理:搜索 + 标签筛选 + 详情页(基本信息/过敏史/健康概览)
- 咨询回复:会话列表 + 状态筛选 + 聊天详情 + 发送消息 + 关闭会话
- 随访管理:任务列表 + 状态筛选 + 详情 + 填写随访记录
- 报告解读:化验报告列表 + 异常高亮 + 指标表格 + 医生审核注释
- 修复 login 页面重复解构
- 注册 8 个新页面路由到 app.config.ts
This commit is contained in:
iven
2026-04-26 13:32:08 +08:00
parent a0b72b0f73
commit 3723cd93c0
21 changed files with 2795 additions and 28 deletions

View File

@@ -0,0 +1,118 @@
import { useState, useEffect } from 'react';
import { View, Text, ScrollView } from '@tarojs/components';
import Taro, { useRouter } from '@tarojs/taro';
import * as doctorApi from '@/services/doctor';
import Loading from '@/components/Loading';
import EmptyState from '@/components/EmptyState';
import './index.scss';
const STATUS_MAP: Record<string, { label: string; color: string }> = {
pending: { label: '待处理', color: '#f59e0b' },
in_progress: { label: '进行中', color: '#0891b2' },
completed: { label: '已完成', color: '#10b981' },
overdue: { label: '已逾期', color: '#ef4444' },
cancelled: { label: '已取消', color: '#94a3b8' },
};
const TABS = [
{ key: '', label: '全部' },
{ key: 'pending', label: '待处理' },
{ key: 'in_progress', label: '进行中' },
{ key: 'completed', label: '已完成' },
{ key: 'overdue', label: '已逾期' },
];
export default function FollowUpList() {
const router = useRouter();
const patientId = router.params.patientId || '';
const [tasks, setTasks] = useState<doctorApi.FollowUpTask[]>([]);
const [activeTab, setActiveTab] = useState('');
const [loading, setLoading] = useState(true);
const [total, setTotal] = useState(0);
useEffect(() => {
loadTasks();
}, [activeTab, patientId]);
const loadTasks = async () => {
setLoading(true);
try {
const res = await doctorApi.listFollowUpTasks({
page: 1,
page_size: 50,
status: activeTab || undefined,
patient_id: patientId || undefined,
});
setTasks(res.data || []);
setTotal(res.total || 0);
} catch {
Taro.showToast({ title: '加载失败', icon: 'none' });
} finally {
setLoading(false);
}
};
const formatDate = (dateStr: string) => {
return new Date(dateStr).toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' });
};
const getTypeLabel = (type: string) => {
const map: Record<string, string> = {
phone: '电话随访',
visit: '门诊随访',
online: '线上随访',
home: '家访',
};
return map[type] || type;
};
if (loading && tasks.length === 0) return <Loading />;
return (
<ScrollView scrollY className='followup-page'>
<View className='tabs'>
{TABS.map((t) => (
<View
key={t.key}
className={`tab ${activeTab === t.key ? 'tab--active' : ''}`}
onClick={() => setActiveTab(t.key)}
>
<Text>{t.label}</Text>
</View>
))}
</View>
<View className='task-count'>
<Text> {total} </Text>
</View>
{tasks.length === 0 ? (
<EmptyState text='暂无随访任务' />
) : (
<View className='task-list'>
{tasks.map((task) => {
const st = STATUS_MAP[task.status] || { label: task.status, color: '#94a3b8' };
return (
<View
key={task.id}
className='task-card'
onClick={() => Taro.navigateTo({ url: `/pages/doctor/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={`background: ${st.color}20; color: ${st.color}`}>
<Text>{st.label}</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>
);
})}
</View>
)}
</ScrollView>
);
}