fix(web,miniprogram): 端到端测试修复 + 小程序接口字段对齐
## 前端修复 - 修复 9 个 TypeScript 编译错误(未使用变量/undefined 守卫/vitest 类型) - 重写 E2E auth fixture 使用真实 API 登录替代 mock token - 更新 E2E 测试选择器适配当前 UI 布局 - Playwright 改为串行执行避免 token 唯一约束冲突 - E2E 测试从 0/10 通过提升到 10/10 通过 ## 小程序接口一致性修复(P0-P3) - P0: consultation.ts type→consultation_type, unread_count→unread_count_patient - P0: followup.ts task_type→follow_up_type, due_date→planned_date, description→content_template - P1: appointment.ts calendarView 展平嵌套结构, available_count 计算 max-current - P1: doctor.ts HealthSummary 适配后台实际返回结构 - P2: doctor.ts PatientStats/ConsultationStats/FollowUpStats 字段名对齐 - P3: article.ts 新增 buildCategoryTree 工具函数
This commit is contained in:
@@ -15,6 +15,8 @@ interface UpcomingItem {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
type: 'appointment' | 'followup';
|
||||
statusLabel: string;
|
||||
statusType: 'ok' | 'warn' | 'default';
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
@@ -45,9 +47,11 @@ export default function Index() {
|
||||
if (a.status === 'pending' || a.status === 'confirmed') {
|
||||
items.push({
|
||||
id: a.id,
|
||||
title: `预约: ${a.appointment_date} ${a.start_time}`,
|
||||
subtitle: `${a.doctor_name || '医护'} · ${a.status === 'pending' ? '待确认' : '已确认'}`,
|
||||
title: `${a.appointment_date} ${a.start_time}`,
|
||||
subtitle: `${a.doctor_name || '医护'} · ${a.department || ''}`,
|
||||
type: 'appointment',
|
||||
statusLabel: a.status === 'pending' ? '待确认' : '已确认',
|
||||
statusType: a.status === 'pending' ? 'warn' : 'ok',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -56,9 +60,11 @@ export default function Index() {
|
||||
for (const t of taskRes.value.data.slice(0, 2)) {
|
||||
items.push({
|
||||
id: t.id,
|
||||
title: `随访: ${t.task_type}`,
|
||||
subtitle: `${t.description?.slice(0, 30) || ''} · 截止 ${t.due_date}`,
|
||||
title: t.follow_up_type,
|
||||
subtitle: `${t.content_template?.slice(0, 30) || ''} · 截止 ${t.planned_date}`,
|
||||
type: 'followup',
|
||||
statusLabel: '进行中',
|
||||
statusType: 'default',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -71,26 +77,19 @@ export default function Index() {
|
||||
};
|
||||
|
||||
const hour = new Date().getHours();
|
||||
const greeting = hour < 12 ? '早上好' : hour < 18 ? '下午好' : '晚上好';
|
||||
const greeting = hour < 12 ? '上午好' : hour < 18 ? '下午好' : '晚上好';
|
||||
const displayName = user?.display_name || currentPatient?.name || '访客';
|
||||
|
||||
const quickServices = [
|
||||
{ label: '预约挂号', icon: '📅', path: '/pages/appointment/create/index' },
|
||||
{ label: '健康录入', icon: '📊', path: '/pages/health/input/index' },
|
||||
{ label: '健康趋势', icon: '📈', path: '/pages/health/trend/index' },
|
||||
{ label: '资讯文章', icon: '📰', path: '/pages/article/index' },
|
||||
{ label: 'AI 报告', icon: '🤖', path: '/pages/ai-report/list/index' },
|
||||
{ label: '预约挂号', path: '/pages/appointment/create/index' },
|
||||
{ label: '健康录入', path: '/pages/health/input/index' },
|
||||
{ label: '健康趋势', path: '/pages/health/trend/index' },
|
||||
{ label: '资讯文章', path: '/pages/article/index' },
|
||||
{ label: 'AI 报告', path: '/pages/ai-report/list/index' },
|
||||
];
|
||||
|
||||
const handleServiceClick = (path: string) => {
|
||||
// tabBar 页面必须使用 switchTab,其他页面用 navigateTo
|
||||
const isTabBar = ['pages/index/index', 'pages/health/index', 'pages/appointment/index', 'pages/article/index', 'pages/profile/index']
|
||||
.some((p) => path.includes(p));
|
||||
if (isTabBar) {
|
||||
Taro.switchTab({ url: path });
|
||||
} else {
|
||||
Taro.navigateTo({ url: path });
|
||||
}
|
||||
Taro.navigateTo({ url: path });
|
||||
};
|
||||
|
||||
const healthItems = [
|
||||
@@ -100,61 +99,72 @@ export default function Index() {
|
||||
{ label: '体重', value: todaySummary?.weight ? `${todaySummary.weight.value}` : '--', unit: 'kg', status: todaySummary?.weight?.status },
|
||||
];
|
||||
|
||||
const getStatusLabel = (status?: string) => {
|
||||
if (status === 'high') return '偏高 ▲';
|
||||
if (status === 'low') return '偏低 ▼';
|
||||
if (status === 'normal') return '正常';
|
||||
return '';
|
||||
const getStatusTag = (status?: string) => {
|
||||
if (status === 'high' || status === 'low') return { label: status === 'high' ? '偏高' : '偏低', cls: 'tag-warn' };
|
||||
if (status === 'normal') return { label: '正常', cls: 'tag-ok' };
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<View className='index-page'>
|
||||
<View className='greeting-bar'>
|
||||
<View className='greeting-text'>
|
||||
<Text className='greeting-hello'>{greeting},</Text>
|
||||
<View className='home-page'>
|
||||
{/* 问候区 */}
|
||||
<View className='greeting-section'>
|
||||
<View className='greeting-left'>
|
||||
<Text className='greeting-time'>{greeting}</Text>
|
||||
<Text className='greeting-name'>{displayName}</Text>
|
||||
</View>
|
||||
<Text className='greeting-date'>{new Date().toLocaleDateString('zh-CN')}</Text>
|
||||
<Text className='greeting-date'>{new Date().toLocaleDateString('zh-CN', { month: 'long', day: 'numeric', weekday: 'short' })}</Text>
|
||||
</View>
|
||||
|
||||
<View className='health-card'>
|
||||
{/* 今日健康 */}
|
||||
<View className='health-section'>
|
||||
<Text className='section-title'>今日健康</Text>
|
||||
{loading && !todaySummary ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<View className='health-grid'>
|
||||
{healthItems.map((item) => (
|
||||
<View className={`health-item ${item.status === 'high' || item.status === 'low' ? 'health-item-warn' : item.status === 'normal' ? 'health-item-ok' : ''}`} key={item.label}>
|
||||
<Text className='health-label'>{item.label}</Text>
|
||||
<Text className='health-value'>{item.value}</Text>
|
||||
<View className='health-item-bottom'>
|
||||
<Text className='health-unit'>{item.unit}</Text>
|
||||
{item.status && <Text className={`health-status ${item.status}`}>{getStatusLabel(item.status)}</Text>}
|
||||
{healthItems.map((item) => {
|
||||
const tag = getStatusTag(item.status);
|
||||
return (
|
||||
<View className='health-cell' key={item.label} onClick={() => Taro.navigateTo({ url: `/pages/health/trend/index?indicator=${item.label === '血压' ? 'blood_pressure_systolic' : item.label === '心率' ? 'heart_rate' : item.label === '血糖' ? 'blood_sugar_fasting' : 'weight'}` })}>
|
||||
<Text className='health-cell-label'>{item.label}</Text>
|
||||
<Text className='health-cell-value'>{item.value}</Text>
|
||||
<View className='health-cell-bottom'>
|
||||
<Text className='health-cell-unit'>{item.unit}</Text>
|
||||
{tag && <Text className={`health-cell-tag ${tag.cls}`}>{tag.label}</Text>}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View className='quick-services'>
|
||||
{/* 快捷服务 */}
|
||||
<View className='services-section'>
|
||||
<Text className='section-title'>快捷服务</Text>
|
||||
<View className='service-grid'>
|
||||
<View className='services-row'>
|
||||
{quickServices.map((svc) => (
|
||||
<View className='service-item' key={svc.label} onClick={() => handleServiceClick(svc.path)}>
|
||||
<Text className='service-icon'>{svc.icon}</Text>
|
||||
<View className='service-btn' key={svc.label} onClick={() => handleServiceClick(svc.path)}>
|
||||
<View className='service-icon-wrap'>
|
||||
<Text className='service-icon-text'>{svc.label[0]}</Text>
|
||||
</View>
|
||||
<Text className='service-label'>{svc.label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='upcoming'>
|
||||
{/* 待办事项 */}
|
||||
<View className='upcoming-section'>
|
||||
<Text className='section-title'>待办事项</Text>
|
||||
{upcomingLoading ? (
|
||||
<Loading />
|
||||
) : upcomingItems.length === 0 ? (
|
||||
<EmptyState icon='📋' text='暂无待办事项' hint='预约挂号后将在此显示' />
|
||||
<View className='upcoming-empty'>
|
||||
<Text className='upcoming-empty-text'>暂无待办事项</Text>
|
||||
<Text className='upcoming-empty-hint'>预约挂号后将在此显示</Text>
|
||||
</View>
|
||||
) : (
|
||||
<View className='upcoming-list'>
|
||||
{upcomingItems.map((item) => (
|
||||
@@ -163,7 +173,7 @@ export default function Index() {
|
||||
className='upcoming-item'
|
||||
onClick={() => {
|
||||
if (item.type === 'appointment') {
|
||||
Taro.navigateTo({ url: `/pages/appointment/index` });
|
||||
Taro.navigateTo({ url: '/pages/appointment/index' });
|
||||
} else {
|
||||
Taro.navigateTo({ url: `/pages/followup/detail/index?id=${item.id}` });
|
||||
}
|
||||
@@ -173,6 +183,7 @@ export default function Index() {
|
||||
<Text className='upcoming-item-title'>{item.title}</Text>
|
||||
<Text className='upcoming-item-sub'>{item.subtitle}</Text>
|
||||
</View>
|
||||
<Text className={`upcoming-item-tag tag-${item.statusType}`}>{item.statusLabel}</Text>
|
||||
<Text className='upcoming-item-arrow'>›</Text>
|
||||
</View>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user