fix: Phase 1.3 完善修复 — 管理端对接 + HMS清理 + 编辑器加载
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled

- feat(web): ClassList.tsx 对接 update/deactivate/reset-code API
  - 编辑班级: PUT /diary/classes/:id
  - 停用班级: PATCH /diary/classes/:id/deactivate (Popconfirm 确认)
  - 重置班级码: POST /diary/classes/:id/reset-code (Popconfirm 确认)
  - 数据源改用 listAll() 获取所有班级
- fix(web): JournalList.tsx 班级筛选改用 classApi.listAll()
- fix(app): EditorPage 加载已有日记数据 (journalId 非空时)
  - 从 Isar 恢复笔画/元素/标签/心情/标题
  - _EditorView 改为 StatefulWidget + initState 加载
- chore(web): HMS 遗留代码清理
  - 删除 api/copilot.ts, healthFixtures.ts, healthHandlers.ts
  - AuditLogViewer 资源类型替换为日记模块类型
  - auth.test.ts / renderWithProviders 权限码 health.* → diary.*
- docs: 确认 M6 NotificationService 为误报 (已在 3 处调用)
This commit is contained in:
iven
2026-06-02 22:54:09 +08:00
parent 860844a399
commit 85d6781372
11 changed files with 201 additions and 757 deletions

View File

@@ -1,255 +0,0 @@
// --- 患者 ---
export function createPatientFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'patient-1',
name: '张三',
gender: 'male',
birth_date: '1990-01-15',
blood_type: 'A',
status: 'active',
verification_status: 'verified',
source: 'manual',
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
...overrides,
};
}
// --- 医生 ---
export function createDoctorFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'doctor-1',
user_id: 'user-doc-1',
name: '李医生',
department: '内科',
title: '主治医师',
specialization: '心血管内科',
phone: '13800000001',
email: 'doctor1@test.com',
status: 'online',
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
...overrides,
};
}
// --- 告警 ---
export function createAlertFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'alert-1',
patient_id: 'patient-1',
patient_name: '张三',
alert_type: 'vital_sign',
severity: 'high',
status: 'active',
message: '血压异常偏高',
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
...overrides,
};
}
// --- 预约 ---
export function createAppointmentFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'appt-1',
patient_id: 'patient-1',
patient_name: '张三',
doctor_id: 'doctor-1',
doctor_name: '李医生',
appointment_date: '2026-05-10',
start_time: '09:00',
end_time: '09:30',
status: 'pending',
type: 'follow_up',
notes: '',
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
...overrides,
};
}
// --- 设备 ---
export function createDeviceFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'device-1',
patient_id: 'patient-1',
device_id: 'DEV-20260401-0001',
device_model: 'BP Monitor Pro',
device_type: 'blood_pressure',
status: 'online',
firmware_version: '1.2.3',
manufacturer: 'HealthTech',
connection_type: 'ble',
bound_at: '2026-04-01T10:00:00Z',
last_sync_at: '2026-04-02T08:30:00Z',
version: 1,
...overrides,
};
}
// --- AI 分析 ---
export function createAnalysisFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'analysis-1',
patient_id: 'patient-1',
patient_name: '张三',
analysis_type: 'lab_report_interpretation',
source_ref: 'lab-report-1',
model_used: 'gpt-4',
status: 'completed',
result_content: '## 分析结果\n- 血压正常范围\n- 血糖略高',
result_metadata: null,
error_message: null,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
...overrides,
};
}
// --- 积分商品 ---
export function createPointsProductFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'product-1',
name: '体检套餐兑换券',
product_type: 'service',
points_cost: 100,
stock: 50,
is_active: true,
description: '可兑换基础体检套餐',
image_url: null,
sort_order: 0,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
...overrides,
};
}
// --- 积分规则 ---
export function createPointsRuleFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'rule-1',
event_type: 'checkin',
name: '每日打卡',
description: '每日健康打卡获得积分',
points_value: 10,
daily_cap: 1,
streak_7d_bonus: 20,
streak_14d_bonus: 50,
streak_30d_bonus: 100,
is_active: true,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
...overrides,
};
}
// --- 积分订单 ---
export function createPointsOrderFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'order-00112233-4455',
patient_id: 'patient-1',
product_id: 'product-1',
product_name: '体检套餐兑换券',
points_cost: 100,
status: 'pending',
qr_code: 'QR-ORDER-001',
verified_at: null,
verified_by: null,
expires_at: '2026-06-01T00:00:00Z',
notes: null,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
...overrides,
};
}
// --- 文章 ---
export function createArticleFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'article-1',
title: '健康饮食指南',
summary: '如何通过饮食改善健康状况',
content: '文章内容...',
cover_image: null,
category_id: 'cat-1',
category_name: '营养健康',
tags: [{ id: 'tag-1', name: '饮食' }],
status: 'published',
author: '李医生',
view_count: 128,
published_at: '2026-04-01T10:00:00Z',
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
...overrides,
};
}
// --- 告警规则 ---
export function createAlertRuleFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'rule-1',
name: '血压偏高告警',
description: '收缩压超过 140 时触发告警',
device_type: 'blood_pressure',
condition_type: 'threshold',
condition_params: { direction: 'above', value: 140 },
severity: 'high',
cooldown_minutes: 60,
is_active: true,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
...overrides,
};
}
// --- 排班 ---
export function createScheduleFixture(overrides: Record<string, unknown> = {}) {
return {
id: 'schedule-1',
doctor_id: 'doctor-1',
schedule_date: '2026-05-10',
period_type: 'am',
start_time: '08:00',
end_time: '12:00',
max_appointments: 10,
current_appointments: 3,
status: 'enabled',
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
...overrides,
};
}
// --- 批量生成 ---
export function createFixtureList<T>(
factory: (overrides?: Record<string, unknown>) => T,
count: number,
overridesList: Record<string, unknown>[] = [],
): T[] {
return Array.from({ length: count }, (_, i) => {
const autoId = { id: `${i + 1}` };
return factory({ ...autoId, ...overridesList[i] });
});
}
// --- 分页响应包装 ---
export function wrapPaginated<T>(items: T[], total?: number) {
return {
data: items,
total: total ?? items.length,
page: 1,
page_size: 20,
total_pages: Math.ceil((total ?? items.length) / 20),
};
}

View File

@@ -1,376 +0,0 @@
import { http, HttpResponse, delay } from 'msw';
const DEFAULT_PAGE_SIZE = 20;
function paginatedResponse<T>(items: T[], total: number, page: number, pageSize = DEFAULT_PAGE_SIZE) {
return HttpResponse.json({
success: true,
data: {
data: items,
total,
page,
page_size: pageSize,
total_pages: Math.ceil(total / pageSize),
},
});
}
// --- 患者列表 ---
const mockPatients = Array.from({ length: 25 }, (_, i) => ({
id: `patient-${i + 1}`,
name: `测试患者${i + 1}`,
gender: i % 2 === 0 ? 'male' : 'female',
birth_date: '1990-01-15',
blood_type: 'A',
status: 'active',
verification_status: i < 20 ? 'verified' : 'pending',
source: 'manual',
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
}));
export const patientHandlers = [
http.get('/api/v1/health/patients', async ({ request }) => {
await delay(50);
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') || 1);
const pageSize = Number(url.searchParams.get('page_size') || DEFAULT_PAGE_SIZE);
const start = (page - 1) * pageSize;
const items = mockPatients.slice(start, start + pageSize);
return paginatedResponse(items, mockPatients.length, page, pageSize);
}),
http.get('/api/v1/health/patients/:id', async ({ params }) => {
await delay(50);
const patient = mockPatients.find((p) => p.id === params.id);
if (!patient) return HttpResponse.json({ success: false, error: 'Not found' }, { status: 404 });
return HttpResponse.json({ success: true, data: { ...patient, notes: '', allergy_history: '', medical_history_summary: '' } });
}),
];
// --- 告警列表 ---
const mockAlerts = Array.from({ length: 12 }, (_, i) => ({
id: `alert-${i + 1}`,
patient_id: `patient-${i + 1}`,
patient_name: `测试患者${i + 1}`,
alert_type: i % 3 === 0 ? 'vital_sign' : i % 3 === 1 ? 'lab_result' : 'overdue_followup',
severity: (['low', 'medium', 'high'] as const)[i % 3],
status: i < 8 ? 'active' : 'resolved',
message: `告警消息 ${i + 1}`,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
}));
export const alertHandlers = [
http.get('/api/v1/health/alerts', async ({ request }) => {
await delay(50);
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') || 1);
const pageSize = Number(url.searchParams.get('page_size') || DEFAULT_PAGE_SIZE);
return paginatedResponse(mockAlerts.slice(0, pageSize), mockAlerts.length, page, pageSize);
}),
];
// --- 预约列表 ---
const mockAppointments = Array.from({ length: 15 }, (_, i) => ({
id: `appt-${i + 1}`,
patient_id: `patient-${i + 1}`,
patient_name: `测试患者${i + 1}`,
doctor_id: `doctor-${(i % 5) + 1}`,
doctor_name: `测试医生${(i % 5) + 1}`,
appointment_date: '2026-05-10',
start_time: '09:00',
end_time: '09:30',
status: (['pending', 'confirmed', 'completed', 'cancelled'] as const)[i % 4],
type: 'follow_up',
notes: '',
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
}));
export const appointmentHandlers = [
http.get('/api/v1/health/appointments', async ({ request }) => {
await delay(50);
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') || 1);
const pageSize = Number(url.searchParams.get('page_size') || DEFAULT_PAGE_SIZE);
return paginatedResponse(mockAppointments.slice(0, pageSize), mockAppointments.length, page, pageSize);
}),
];
// --- 医生列表 ---
const mockDoctors = Array.from({ length: 8 }, (_, i) => ({
id: `doctor-${i + 1}`,
user_id: `user-doc-${i + 1}`,
name: `测试医生${i + 1}`,
department: '内科',
title: '主治医师',
specialization: '心血管内科',
phone: `1380000${String(i + 1).padStart(4, '0')}`,
email: `doctor${i + 1}@test.com`,
status: 'online',
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
}));
export const doctorHandlers = [
http.get('/api/v1/health/doctors', async ({ request }) => {
await delay(50);
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') || 1);
const pageSize = Number(url.searchParams.get('page_size') || DEFAULT_PAGE_SIZE);
return paginatedResponse(mockDoctors.slice(0, pageSize), mockDoctors.length, page, pageSize);
}),
];
// --- 设备列表 ---
const mockDevices = Array.from({ length: 10 }, (_, i) => ({
id: `device-${i + 1}`,
patient_id: `patient-${(i % 5) + 1}`,
device_id: `DEV-20260401-${String(i + 1).padStart(4, '0')}`,
device_model: i % 2 === 0 ? 'BP Monitor Pro' : 'GlucoSense Lite',
device_type: i % 2 === 0 ? 'blood_pressure' : 'blood_glucose',
status: i < 7 ? 'online' : 'offline',
firmware_version: '1.2.3',
manufacturer: 'HealthTech',
connection_type: i % 3 === 0 ? 'ble' : 'gateway',
bound_at: '2026-04-01T10:00:00Z',
last_sync_at: '2026-04-02T08:30:00Z',
version: 1,
}));
export const deviceHandlers = [
http.get('/api/v1/health/devices', async ({ request }) => {
await delay(50);
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') || 1);
const pageSize = Number(url.searchParams.get('page_size') || DEFAULT_PAGE_SIZE);
return paginatedResponse(mockDevices.slice(0, pageSize), mockDevices.length, page, pageSize);
}),
];
// --- AI 分析历史 ---
const mockAnalyses = Array.from({ length: 6 }, (_, i) => ({
id: `analysis-${i + 1}`,
patient_id: `patient-${(i % 3) + 1}`,
patient_name: `测试患者${(i % 3) + 1}`,
analysis_type: (['lab_report_interpretation', 'health_trend_analysis', 'personalized_checkup_plan', 'report_summary_generation'] as const)[i % 4],
source_ref: `ref-${i + 1}`,
model_used: 'gpt-4',
status: (['completed', 'completed', 'streaming', 'failed', 'pending', 'completed'] as const)[i],
result_content: i % 2 === 0 ? `## 分析结果\n- 指标 ${i + 1} 正常\n- 建议定期复查` : null,
result_metadata: null,
error_message: i === 3 ? '分析超时' : null,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
}));
export const analysisHandlers = [
http.get('/api/v1/ai/analysis/history', async ({ request }) => {
await delay(50);
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') || 1);
const pageSize = Number(url.searchParams.get('page_size') || DEFAULT_PAGE_SIZE);
return paginatedResponse(mockAnalyses.slice(0, pageSize), mockAnalyses.length, page, pageSize);
}),
http.get('/api/v1/ai/analysis/:id', async ({ params }) => {
await delay(50);
const analysis = mockAnalyses.find((a) => a.id === params.id);
if (!analysis) return HttpResponse.json({ success: false, error: 'Not found' }, { status: 404 });
return HttpResponse.json({ success: true, data: analysis });
}),
];
// --- AI 用量统计 ---
export const usageHandlers = [
http.get('/api/v1/ai/usage/overview', async () => {
await delay(50);
return HttpResponse.json({ success: true, data: { total_count: 128 } });
}),
http.get('/api/v1/ai/usage/by-type', async () => {
await delay(50);
return HttpResponse.json({
success: true,
data: [
{ analysis_type: 'lab_report_interpretation', count: 45 },
{ analysis_type: 'health_trend_analysis', count: 38 },
{ analysis_type: 'personalized_checkup_plan', count: 25 },
{ analysis_type: 'report_summary_generation', count: 20 },
],
});
}),
];
// --- 积分商品 ---
const mockProducts = Array.from({ length: 5 }, (_, i) => ({
id: `product-${i + 1}`,
name: ['体检套餐兑换券', '健康手环', 'VIP 问诊权益', '营养咨询券', '运动课程'][i],
product_type: (['service', 'physical', 'privilege', 'service', 'service'] as const)[i],
points_cost: [100, 200, 150, 80, 120][i],
stock: [50, 20, -1, 100, 30][i],
is_active: i < 4,
description: `商品描述 ${i + 1}`,
image_url: null,
sort_order: i,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
}));
export const pointsProductHandlers = [
http.get('/api/v1/health/points/products', async ({ request }) => {
await delay(50);
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') || 1);
const pageSize = Number(url.searchParams.get('page_size') || DEFAULT_PAGE_SIZE);
return paginatedResponse(mockProducts.slice(0, pageSize), mockProducts.length, page, pageSize);
}),
];
// --- 积分规则 ---
const mockRules = Array.from({ length: 4 }, (_, i) => ({
id: `rule-${i + 1}`,
event_type: (['checkin', 'data_report', 'lab_upload', 'event_checkin'] as const)[i],
name: ['每日打卡', '数据上报', '化验上传', '活动签到'][i],
description: `规则描述 ${i + 1}`,
points_value: [10, 5, 20, 15][i],
daily_cap: [1, 3, 1, 1][i],
streak_7d_bonus: [20, 0, 0, 10][i],
streak_14d_bonus: [50, 0, 0, 25][i],
streak_30d_bonus: [100, 0, 0, 50][i],
is_active: i < 3,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
}));
export const pointsRuleHandlers = [
http.get('/api/v1/health/points/rules', async () => {
await delay(50);
return HttpResponse.json({ success: true, data: mockRules });
}),
];
// --- 积分订单 ---
const mockOrders = Array.from({ length: 8 }, (_, i) => ({
id: `order-${String(i + 1).padStart(12, '0')}-abcd`,
patient_id: `patient-${(i % 3) + 1}`,
product_id: `product-${(i % 3) + 1}`,
product_name: ['体检套餐兑换券', '健康手环', 'VIP 问诊权益'][i % 3],
points_cost: [100, 200, 150][i % 3],
status: (['pending', 'verified', 'cancelled', 'expired'] as const)[i % 4],
qr_code: `QR-${i + 1}`,
verified_at: i % 4 === 1 ? '2026-04-02T10:00:00Z' : null,
verified_by: i % 4 === 1 ? 'admin-1' : null,
expires_at: '2026-06-01T00:00:00Z',
notes: null,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
}));
export const pointsOrderHandlers = [
http.get('/api/v1/health/points/orders', async ({ request }) => {
await delay(50);
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') || 1);
const pageSize = Number(url.searchParams.get('page_size') || DEFAULT_PAGE_SIZE);
return paginatedResponse(mockOrders.slice(0, pageSize), mockOrders.length, page, pageSize);
}),
];
// --- 文章管理 ---
const mockArticles = Array.from({ length: 10 }, (_, i) => ({
id: `article-${i + 1}`,
title: ['健康饮食指南', '运动与健康', '睡眠质量提升', '血压管理手册', '糖尿病预防', '心脏健康', '心理健康', '老年人保健', '儿童营养', '体检常识'][i],
summary: `文章摘要 ${i + 1}`,
content: `<p>文章内容 ${i + 1}</p>`,
cover_image: null,
category_id: `cat-${(i % 3) + 1}`,
category_name: ['营养健康', '运动健身', '疾病预防'][i % 3],
tags: [{ id: `tag-${(i % 4) + 1}`, name: ['饮食', '运动', '血压', '睡眠'][i % 4] }],
status: (['draft', 'pending_review', 'published', 'rejected'] as const)[i % 4],
author: `作者${(i % 2) + 1}`,
view_count: [128, 256, 64, 32, 512, 96, 192, 48, 384, 16][i],
published_at: i % 4 === 2 ? '2026-04-01T10:00:00Z' : null,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
}));
export const articleHandlers = [
http.get('/api/v1/health/articles', async ({ request }) => {
await delay(50);
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') || 1);
const pageSize = Number(url.searchParams.get('page_size') || DEFAULT_PAGE_SIZE);
return paginatedResponse(mockArticles.slice(0, pageSize), mockArticles.length, page, pageSize);
}),
http.get('/api/v1/health/article-categories', async () => {
await delay(50);
return HttpResponse.json({
success: true,
data: [
{ id: 'cat-1', name: '营养健康' },
{ id: 'cat-2', name: '运动健身' },
{ id: 'cat-3', name: '疾病预防' },
],
});
}),
];
// --- 告警规则 ---
const mockAlertRules = Array.from({ length: 5 }, (_, i) => ({
id: `alert-rule-${i + 1}`,
name: ['血压偏高告警', '心率异常告警', '血糖偏低告警', '体温异常告警', '血氧偏低告警'][i],
description: `告警规则描述 ${i + 1}`,
device_type: (['blood_pressure', 'heart_rate', 'blood_glucose', 'temperature', 'spo2'] as const)[i],
condition_type: 'threshold',
condition_params: { direction: 'above', value: [140, 100, 3.9, 38.5, 90][i] },
severity: (['low', 'medium', 'high', 'critical', 'medium'] as const)[i],
cooldown_minutes: 60,
is_active: i < 4,
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
}));
export const alertRuleHandlers = [
http.get('/api/v1/health/alert-rules', async ({ request }) => {
await delay(50);
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') || 1);
const pageSize = Number(url.searchParams.get('page_size') || DEFAULT_PAGE_SIZE);
return paginatedResponse(mockAlertRules.slice(0, pageSize), mockAlertRules.length, page, pageSize);
}),
];
// --- 排班 ---
const mockSchedules = Array.from({ length: 8 }, (_, i) => ({
id: `schedule-${i + 1}`,
doctor_id: 'doctor-1',
schedule_date: `2026-05-${String(10 + i).padStart(2, '0')}`,
period_type: i % 2 === 0 ? 'am' : 'pm',
start_time: i % 2 === 0 ? '08:00' : '14:00',
end_time: i % 2 === 0 ? '12:00' : '17:00',
max_appointments: 10,
current_appointments: i % 3,
status: 'enabled',
created_at: '2026-04-01T10:00:00Z',
updated_at: '2026-04-01T10:00:00Z',
version: 1,
}));
export const scheduleHandlers = [
http.get('/api/v1/health/schedules', async ({ request }) => {
await delay(50);
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') || 1);
const pageSize = Number(url.searchParams.get('page_size') || DEFAULT_PAGE_SIZE);
return paginatedResponse(mockSchedules.slice(0, pageSize), mockSchedules.length, page, pageSize);
}),
];

View File

@@ -16,45 +16,22 @@ interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
}>;
}
const ALL_HEALTH_PERMISSIONS = [
'health.patient.manage',
'health.patient.list',
'health.doctor.manage',
'health.doctor.list',
'health.appointment.manage',
'health.appointment.list',
'health.alerts.manage',
'health.alerts.list',
'health.follow-up.manage',
'health.follow-up.list',
'health.follow-up-templates.manage',
'health.follow-up-templates.list',
'health.consultation.manage',
'health.consultation.list',
'health.dialysis.manage',
'health.dialysis.list',
'health.articles.manage',
'health.articles.list',
'health.articles.review',
'health.points.manage',
'health.points.list',
'health.health-data.manage',
'health.health-data.list',
'health.devices.manage',
'health.devices.list',
'health.dashboard.manage',
'health.oauth.manage',
'ai.analysis.manage',
'ai.analysis.list',
'ai.usage.list',
'ai.prompt.manage',
'ai.prompt.list',
const ALL_DIARY_PERMISSIONS = [
'diary.journal.create',
'diary.journal.read',
'diary.journal.update',
'diary.journal.delete',
'diary.class.manage',
'diary.topic.assign',
'diary.comment.write',
'diary.comment.delete',
'diary.parent.bind',
];
const DEFAULT_AUTH = {
user: { id: 'test-user-id', username: 'admin', tenant_id: 'test-tenant-id' },
isAuthenticated: true,
permissions: ALL_HEALTH_PERMISSIONS,
permissions: ALL_DIARY_PERMISSIONS,
};
// 简单 JWT payload — 不会过期