feat: 医生端完整实现 + 双角色登录 + 健康日历 + 饮食记录重做 + 配色系统重构

后端:
- User表加Role字段 + DoctorProfile表 + 废弃旧Doctor数据
- JWT加Role claim + 注册/登录拆分(/api/auth/register + /api/auth/login)
- 医生端点全量加JWT鉴权+Role校验(15个端点)
- 日历端点改造(用药/运动/随访按日期+星期匹配)
- 饮食记录PUT端点 + 健康记录DELETE端点
- 用户Profile端点返回Role

前端:
- 登录页重做(角色选择卡片 用户紫/医生蓝 + 审核码6666 + 注册返登录)
- App路由分流(userRole→患者端/医生端)
- 医生端: 工作台Dashboard + 患者管理(搜索分页) + 问诊/报告/随访CRUD
- 报告审核: AI预分析+指标表格+严重程度4级+模板多选+评语
- 随访编辑: 患者选择器+日期时间+打通患者端
- 健康日历: 可点击日历+当日安排列表+用药/运动/随访颜色标记
- 饮食记录: 热量趋势7/30天+日历(一周)+当日胶囊详情+侧滑编辑删除
- 健康档案: 日期选择器+手术可添加多条+白底渐变保存按钮
- 配色系统: 清理25个未使用色+新增doctorBlue/drawerGradient/pageGrey
- 患者端侧边栏: 新渐变+白底按钮+紫色汉堡图标
- AI对话: 用户气泡白底黑字+药管家改蓝色
- App启动闪屏: 防登录页闪烁

文档: color_design_system.md(20页面完整配色设计+图标底色对照表)
This commit is contained in:
MingNian
2026-06-14 23:28:14 +08:00
parent d102205b18
commit 01cd132476
35 changed files with 3473 additions and 1095 deletions

View File

@@ -3,17 +3,16 @@ import 'package:dio/dio.dart';
import '../core/api_client.dart';
import '../core/local_database.dart';
/// 用户简要信息
class UserInfo {
final String id;
final String phone;
final String role;
final String? name;
final String? avatarUrl;
UserInfo({required this.id, required this.phone, this.name, this.avatarUrl});
UserInfo({required this.id, required this.phone, this.role = 'User', this.name, this.avatarUrl});
}
/// 认证状态
class AuthState {
final UserInfo? user;
final bool isLoggedIn;
@@ -22,7 +21,6 @@ class AuthState {
const AuthState({this.user, this.isLoggedIn = false, this.isLoading = true});
}
/// 认证 Provider
final authProvider = NotifierProvider<AuthNotifier, AuthState>(AuthNotifier.new);
final localDbProvider = Provider<LocalDatabase>((ref) => LocalDatabase.instance);
@@ -53,10 +51,9 @@ class AuthNotifier extends Notifier<AuthState> {
if (data != null) {
await db.write('access_token', data['accessToken']);
await db.write('refresh_token', data['refreshToken']);
state = AuthState(
isLoggedIn: true,
isLoading: false,
user: UserInfo(id: '', phone: '', name: data['user']?['name']),
final u = data['user'] as Map<String, dynamic>?;
state = AuthState(isLoggedIn: true, isLoading: false,
user: UserInfo(id: '', phone: '', role: u?['role'] ?? 'User'),
);
_loadProfile();
} else {
@@ -73,35 +70,53 @@ class AuthNotifier extends Notifier<AuthState> {
final response = await api.get('/api/user/profile');
final user = response.data['data'];
if (user != null) {
state = AuthState(
isLoggedIn: true,
isLoading: false,
state = AuthState(isLoggedIn: true, isLoading: false,
user: UserInfo(
id: user['id'] ?? '',
phone: user['phone'] ?? '',
name: user['name'],
avatarUrl: user['avatarUrl'],
id: user['id'] ?? '', phone: user['phone'] ?? '',
role: user['role'] ?? state.user?.role ?? 'User',
name: user['name'], avatarUrl: user['avatarUrl'],
),
);
}
} catch (e) {
print('[Auth]加载个人信息失败: $e');
print('[Auth] loadProfile: $e');
}
}
/// 发送验证码,返回 (error, devCode)
/// 发送验证码
Future<({String? error, String? devCode})> sendSms(String phone) async {
try {
final api = ref.read(apiClientProvider);
final response = await api.post('/api/auth/send-sms', data: {'phone': phone});
final devCode = response.data['data']?['devCode'] as String?;
return (error: null, devCode: devCode);
return (error: null, devCode: response.data['data']?['devCode'] as String?);
} catch (e) {
return (error: '发送失败: $e', devCode: null);
}
}
/// 验证码登录
/// 注册(新用户,需选身份)
Future<String?> register(String phone, String code, String role, {String? inviteCode}) async {
try {
final api = ref.read(apiClientProvider);
final response = await api.post('/api/auth/register', data: {
'phone': phone, 'smsCode': code, 'role': role,
if (inviteCode != null) 'inviteCode': inviteCode,
});
final data = response.data['data'];
if (data == null) return response.data['message'] ?? '注册失败';
await api.saveTokens(data['accessToken'], data['refreshToken']);
final user = data['user'];
state = AuthState(isLoggedIn: true, isLoading: false,
user: UserInfo(id: user['id'] ?? '', phone: user['phone'] ?? '', role: role),
);
return null;
} catch (e) {
return '注册失败: $e';
}
}
/// 登录(已有账号)
Future<String?> login(String phone, String code) async {
try {
final api = ref.read(apiClientProvider);
@@ -111,13 +126,10 @@ class AuthNotifier extends Notifier<AuthState> {
await api.saveTokens(data['accessToken'], data['refreshToken']);
final user = data['user'];
state = AuthState(
isLoggedIn: true,
isLoading: false,
state = AuthState(isLoggedIn: true, isLoading: false,
user: UserInfo(
id: user['id'] ?? '',
phone: user['phone'] ?? '',
name: user['name'],
id: user['id'] ?? '', phone: user['phone'] ?? '',
role: user['role'] ?? 'User', name: user['name'],
avatarUrl: user['avatarUrl'],
),
);
@@ -133,9 +145,14 @@ class AuthNotifier extends Notifier<AuthState> {
final db = ref.read(localDbProvider);
final refresh = await db.read('refresh_token');
if (refresh != null) {
try { await api.post('/api/auth/logout', data: {'refreshToken': refresh}); } catch (e) { print('[Auth]登出请求失败: $e'); }
try { await api.post('/api/auth/logout', data: {'refreshToken': refresh}); } catch (e) { print('[Auth] logout: $e'); }
}
await api.clearTokens();
state = const AuthState(isLoggedIn: false, isLoading: false);
}
}
/// 便捷:当前用户角色
final userRoleProvider = Provider<String>((ref) {
return ref.watch(authProvider).user?.role ?? 'User';
});