fix(web,miniprogram): 端到端测试修复 + 小程序接口字段对齐
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

## 前端修复
- 修复 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:
iven
2026-04-27 22:09:21 +08:00
parent e1d9f97d79
commit c53f5625bc
21 changed files with 323 additions and 214 deletions

View File

@@ -10,12 +10,12 @@ import WeekCalendar from '../../../components/WeekCalendar';
import './index.scss';
const DEPARTMENTS = [
{ label: '内科', icon: '🫀' },
{ label: '外科', icon: '🔪' },
{ label: '妇科', icon: '👩‍⚕️' },
{ label: '儿科', icon: '👶' },
{ label: '体检中心', icon: '🏥' },
{ label: '中医科', icon: '🌿' },
{ label: '内科', initial: '' },
{ label: '外科', initial: '' },
{ label: '妇科', initial: '' },
{ label: '儿科', initial: '' },
{ label: '体检中心', initial: '' },
{ label: '中医科', initial: '' },
];
interface DoctorItem {
@@ -83,14 +83,13 @@ export default function AppointmentCreate() {
const onSelectDate = useCallback((date: string) => {
setAppointmentDate(date);
setTimeSlot('');
// 从排班数据中提取时段
const daySlots = schedules
.filter((s: any) => (s.date || s.appointment_date) === date)
.map((s: any) => ({
start_time: s.start_time || '',
end_time: s.end_time || '',
label: `${s.start_time || ''}-${s.end_time || ''}`,
available_count: s.available_count ?? (s.max_patients ?? 10),
available_count: s.available_count ?? (s.max_appointments - (s.current_appointments || 0)),
}));
setTimeSlots(daySlots);
}, [schedules]);
@@ -120,7 +119,6 @@ export default function AppointmentCreate() {
});
Taro.showToast({ title: '预约成功', icon: 'success' });
trackEvent('appointment_create', { doctor_id: selectedDoctor.id, date: appointmentDate });
// 订阅消息引导
const tmplId = TEMPLATE_IDS.APPOINTMENT_REMINDER;
if (tmplId) {
try {
@@ -168,7 +166,9 @@ export default function AppointmentCreate() {
key={dept.label}
onClick={() => onSelectDept(dept.label)}
>
<Text className='dept-icon'>{dept.icon}</Text>
<View className='dept-initial-circle'>
<Text className='dept-initial-text'>{dept.initial}</Text>
</View>
<Text className='dept-label'>{dept.label}</Text>
</View>
))}
@@ -181,7 +181,9 @@ export default function AppointmentCreate() {
<View className='step-content'>
<Text className='step-title'>{department} - </Text>
{doctors.length === 0 ? (
<View className='empty-hint'><Text className='empty-text'></Text></View>
<View className='empty-hint'>
<Text className='empty-text'></Text>
</View>
) : (
<View className='doctor-list'>
{doctors.map((doc) => (
@@ -190,13 +192,19 @@ export default function AppointmentCreate() {
key={doc.id}
onClick={() => onSelectDoctor(doc)}
>
<View className='doctor-avatar'><Text className='doctor-avatar-text'>{doc.name.charAt(0)}</Text></View>
<View className='doctor-avatar'>
<Text className='doctor-avatar-text'>{doc.name.charAt(0)}</Text>
</View>
<View className='doctor-detail'>
<Text className='doctor-name'>{doc.name}</Text>
<Text className='doctor-title'>{doc.title || '医生'}</Text>
{doc.specialty && <Text className='doctor-specialty'>{doc.specialty}</Text>}
</View>
{selectedDoctor?.id === doc.id && <Text className='doctor-check'>&#10003;</Text>}
{selectedDoctor?.id === doc.id && (
<View className='doctor-check'>
<Text className='doctor-check-text'>&#10003;</Text>
</View>
)}
</View>
))}
</View>
@@ -208,9 +216,20 @@ export default function AppointmentCreate() {
{currentStep === 2 && (
<View className='step-content'>
<Text className='step-title'></Text>
<View className='form-group'>
<Text className='form-label'></Text>
<View className='form-static'><Text className='form-static-text'>{selectedDoctor?.name} - {department}</Text></View>
<View className='confirm-card'>
<View className='confirm-row'>
<View className='confirm-icon-wrap'>
<Text className='confirm-icon-serif'></Text>
</View>
<View className='confirm-info'>
<Text className='confirm-label'></Text>
<Text className='confirm-value'>{selectedDoctor?.name}</Text>
</View>
<View className='confirm-dept-tag'>
<Text className='confirm-dept-text'>{department}</Text>
</View>
</View>
</View>
<WeekCalendar
@@ -221,7 +240,7 @@ export default function AppointmentCreate() {
{appointmentDate && timeSlots.length > 0 && (
<View className='slot-section'>
<Text className='form-label'></Text>
<Text className='slot-section-title'></Text>
<View className='slot-grid'>
{timeSlots.map((slot) => (
<View
@@ -230,7 +249,9 @@ export default function AppointmentCreate() {
onClick={slot.available_count > 0 ? () => setTimeSlot(slot.label) : undefined}
>
<Text className='slot-time'>{slot.label}</Text>
<Text className='slot-count'>{slot.available_count > 0 ? `剩余 ${slot.available_count}` : '已满'}</Text>
<Text className='slot-count'>
{slot.available_count > 0 ? `剩余 ${slot.available_count}` : '已满'}
</Text>
</View>
))}
</View>
@@ -239,7 +260,12 @@ export default function AppointmentCreate() {
<View className='form-group'>
<Text className='form-label'></Text>
<Input className='form-input' placeholder='请简要描述症状' value={reason} onInput={(e) => setReason(e.detail.value)} />
<Input
className='form-input'
placeholder='请简要描述症状'
value={reason}
onInput={(e) => setReason(e.detail.value)}
/>
</View>
</View>
)}
@@ -256,7 +282,10 @@ export default function AppointmentCreate() {
<Text className='btn-text btn-text-white'></Text>
</View>
) : (
<View className={`btn btn-submit ${loading ? 'btn-disabled' : ''}`} onClick={loading ? undefined : handleSubmit}>
<View
className={`btn btn-submit ${loading ? 'btn-disabled' : ''}`}
onClick={loading ? undefined : handleSubmit}
>
<Text className='btn-text btn-text-white'>{loading ? '提交中...' : '确认预约'}</Text>
</View>
)}