fix: QA 第二轮修复 — PatientDetail 重构/测试覆盖/id_number 列宽/小程序 URL 规范化
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

- refactor(web): PatientDetail.tsx 拆分为 4 个子组件(737→334行)
- refactor(web): 提取 usePaginatedData hook 消除重复分页状态
- feat(db): patient.id_number varchar(20)→varchar(255) 容纳加密值
- test(health): 添加预约模块集成测试(创建/列表/租户隔离)
- test(plugin): 添加 6 个 SQL 注入 sanitize 测试
- fix(miniprogram): 7 个 service 文件 URL 构建规范化(params 对象)
- fix(miniprogram): 跨平台字段名对齐(birth_date/start_time/end_time)
This commit is contained in:
iven
2026-04-25 10:22:44 +08:00
parent 55a3fd32d0
commit 0bf1822fa9
34 changed files with 1110 additions and 641 deletions

View File

@@ -29,7 +29,10 @@ export interface DoctorSchedule {
}
export async function listAppointments(page = 1) {
return api.get<{ data: Appointment[]; total: number }>(`/health/appointments?page=${page}&page_size=20`);
return api.get<{ data: Appointment[]; total: number }>('/health/appointments', {
page,
page_size: 20,
});
}
export async function getAppointment(id: string) {
@@ -56,17 +59,25 @@ export async function cancelAppointment(id: string, version: number) {
}
export async function getDoctorSchedules(doctorId: string, startDate: string, endDate: string) {
return api.get<{ data: DoctorSchedule[]; total: number }>(
`/health/doctor-schedules?doctor_id=${doctorId}&start_date=${startDate}&end_date=${endDate}&page_size=50`
);
return api.get<{ data: DoctorSchedule[]; total: number }>('/health/doctor-schedules', {
doctor_id: doctorId,
start_date: startDate,
end_date: endDate,
page_size: 50,
});
}
export async function listDoctors(department?: string) {
const deptParam = department ? `&department=${department}` : '';
return api.get<{ data: Doctor[]; total: number }>(`/health/doctors?page_size=100${deptParam}`);
return api.get<{ data: Doctor[]; total: number }>('/health/doctors', {
page_size: 100,
...(department && { department }),
});
}
export async function calendarView(startDate: string, endDate: string, doctorId?: string) {
const docParam = doctorId ? `&doctor_id=${doctorId}` : '';
return api.get<DoctorSchedule[]>(`/health/doctor-schedules/calendar?start_date=${startDate}&end_date=${endDate}${docParam}`);
return api.get<DoctorSchedule[]>('/health/doctor-schedules/calendar', {
start_date: startDate,
end_date: endDate,
...(doctorId && { doctor_id: doctorId }),
});
}

View File

@@ -12,7 +12,10 @@ export interface Article {
}
export async function listArticles(page = 1) {
return api.get<{ data: Article[]; total: number }>(`/health/articles?page=${page}&page_size=20`);
return api.get<{ data: Article[]; total: number }>('/health/articles', {
page,
page_size: 20,
});
}
export async function getArticleDetail(id: string) {

View File

@@ -23,10 +23,11 @@ export interface FollowUpRecord {
}
export async function listTasks(status?: string) {
const statusParam = status ? `&status=${status}` : '';
return api.get<{ data: FollowUpTask[]; total: number }>(
`/health/follow-up-tasks?page=1&page_size=50${statusParam}`
);
return api.get<{ data: FollowUpTask[]; total: number }>('/health/follow-up-tasks', {
page: 1,
page_size: 50,
...(status && { status }),
});
}
export async function getTaskDetail(id: string) {
@@ -38,8 +39,9 @@ export async function submitRecord(data: { task_id: string; content: FollowUpCon
}
export async function listRecords(taskId?: string) {
const taskParam = taskId ? `&task_id=${taskId}` : '';
return api.get<{ data: FollowUpRecord[]; total: number }>(
`/health/follow-up-records?page=1&page_size=50${taskParam}`
);
return api.get<{ data: FollowUpRecord[]; total: number }>('/health/follow-up-records', {
page: 1,
page_size: 50,
...(taskId && { task_id: taskId }),
});
}

View File

@@ -25,6 +25,7 @@ export async function inputVitalSign(patientId: string, data: VitalSignInput) {
export async function getTrend(indicator: string, range: string) {
return api.get<{ indicator: string; data_points: { date: string; value: number }[] }>(
`/health/vital-signs/trend?indicator=${indicator}&range=${range}`
'/health/vital-signs/trend',
{ indicator, range },
);
}

View File

@@ -12,7 +12,10 @@ export interface Patient {
}
export async function listPatients() {
return api.get<{ data: Patient[]; total: number }>('/health/patients?page=1&page_size=100');
return api.get<{ data: Patient[]; total: number }>('/health/patients', {
page: 1,
page_size: 100,
});
}
export async function createPatient(data: {

View File

@@ -20,7 +20,8 @@ export interface LabReport {
export async function listReports(patientId: string, page = 1) {
return api.get<{ data: LabReport[]; total: number }>(
`/health/patients/${patientId}/lab-reports?page=${page}&page_size=20`
`/health/patients/${patientId}/lab-reports`,
{ page, page_size: 20 },
);
}

View File

@@ -2,6 +2,7 @@ import Taro from '@tarojs/taro';
import { secureGet, secureSet, secureRemove } from '@/utils/secure-storage';
const BASE_URL = process.env.TARO_APP_API_URL || 'http://localhost:3000/api/v1';
const IS_DEV = process.env.NODE_ENV !== 'production';
interface ApiResponse<T> {
success: boolean;
@@ -44,12 +45,23 @@ async function tryRefreshToken(): Promise<boolean> {
export async function request<T>(method: string, path: string, data?: unknown): Promise<T> {
const headers = await getHeaders();
const res = await Taro.request({ url: `${BASE_URL}${path}`, method, data, header: headers });
const url = `${BASE_URL}${path}`;
if (IS_DEV) {
console.log(`[API] ${method} ${path}`, data ?? '');
}
const res = await Taro.request({ url, method: method as any, data, header: headers, timeout: 30000 });
if (IS_DEV) {
console.log(`[API] ${method} ${path}${res.statusCode}`);
}
if (res.statusCode === 401) {
const refreshed = await tryRefreshToken();
if (refreshed) return request<T>(method, path, data);
Taro.redirectTo({ url: '/pages/login/index' });
const pages = Taro.getCurrentPages();
const currentPath = pages[pages.length - 1]?.path || '';
if (!currentPath.includes('pages/login')) {
Taro.redirectTo({ url: '/pages/login/index' });
}
throw new Error('登录已过期');
}
@@ -58,8 +70,20 @@ export async function request<T>(method: string, path: string, data?: unknown):
return body.data as T;
}
function buildQuery(params?: Record<string, string | number | undefined>): string {
if (!params) return '';
const entries = Object.entries(params).filter(([, v]) => v !== undefined && v !== '');
return entries.length > 0
? '?' +
entries
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
.join('&')
: '';
}
export const api = {
get: <T>(path: string) => request<T>('GET', path),
get: <T>(path: string, params?: Record<string, string | number | undefined>) =>
request<T>('GET', `${path}${buildQuery(params)}`),
post: <T>(path: string, data?: unknown) => request<T>('POST', path, data),
put: <T>(path: string, data?: unknown) => request<T>('PUT', path, data),
delete: <T>(path: string) => request<T>('DELETE', path),