feat: UI 系统中心化 + 趋势图重构 + 法律文档 H5 上线
- UI 系统: 新增 design_tokens / module_visuals / app_buttons / app_status_badge / app_toast / ai_content 六个共享模块; 28 个页面接入, SnackBar 全部替换为 AppToast - 趋势图: 直线折线 + 渐变填充 + 阴影; 去网格线; X 轴日+月份分隔; Y 轴整数刻度最多 4 个; 点击数据点/录入记录显示上方浮层, 选中点变实心 - 法律文档 H5: 后端 wwwroot 托管隐私政策/服务协议/关于/个人信息收集清单/第三方 SDK 清单 + 索引页; Program.cs 启用 UseDefaultFiles + UseStaticFiles - 其他: auth_provider 登录流程调整; api_client IP 适配; 主页智能体栏间距优化
This commit is contained in:
@@ -8,6 +8,7 @@ import '../../providers/auth_provider.dart';
|
||||
import '../../providers/data_providers.dart';
|
||||
import '../../widgets/app_empty_state.dart';
|
||||
import '../../widgets/app_error_state.dart';
|
||||
import '../../widgets/app_toast.dart';
|
||||
|
||||
class MedicationCheckInPage extends ConsumerStatefulWidget {
|
||||
final String? medId;
|
||||
@@ -66,11 +67,10 @@ class _MedicationCheckInPageState extends ConsumerState<MedicationCheckInPage> {
|
||||
} catch (e) {
|
||||
debugPrint('[MedCheckIn] 打卡失败: $e');
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('打卡失败,请稍后重试'),
|
||||
backgroundColor: AppTheme.error,
|
||||
),
|
||||
AppToast.show(
|
||||
context,
|
||||
'打卡失败,请稍后重试',
|
||||
type: AppToastType.error,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../core/app_theme.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../core/app_design_tokens.dart';
|
||||
import '../../core/app_module_visuals.dart';
|
||||
import '../../core/app_theme.dart';
|
||||
import '../../core/navigation_provider.dart';
|
||||
import '../../providers/data_providers.dart';
|
||||
import '../../widgets/app_toast.dart';
|
||||
|
||||
class MedicationEditPage extends ConsumerStatefulWidget {
|
||||
final String? id;
|
||||
|
||||
const MedicationEditPage({super.key, this.id});
|
||||
|
||||
@override
|
||||
ConsumerState<MedicationEditPage> createState() => _MedicationEditPageState();
|
||||
}
|
||||
|
||||
class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
static const _visual = AppModuleVisuals.medication;
|
||||
|
||||
final _nameCtrl = TextEditingController();
|
||||
final _dosageCtrl = TextEditingController();
|
||||
final _notesCtrl = TextEditingController();
|
||||
@@ -74,9 +82,7 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
Future<void> _save() async {
|
||||
final name = _nameCtrl.text.trim();
|
||||
if (name.isEmpty) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('请输入药品名称')));
|
||||
AppToast.show(context, '请输入药品名称', type: AppToastType.warning);
|
||||
return;
|
||||
}
|
||||
setState(() => _loading = true);
|
||||
@@ -107,17 +113,10 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
}
|
||||
ref.invalidate(medicationListProvider);
|
||||
ref.invalidate(medicationReminderProvider);
|
||||
if (mounted) {
|
||||
popRoute(ref);
|
||||
}
|
||||
if (mounted) popRoute(ref);
|
||||
} catch (_) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('保存失败'),
|
||||
backgroundColor: AppTheme.error,
|
||||
),
|
||||
);
|
||||
AppToast.show(context, '保存失败', type: AppToastType.error);
|
||||
}
|
||||
}
|
||||
if (mounted) setState(() => _loading = false);
|
||||
@@ -148,23 +147,22 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
// 名称+剂量 一行
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: _field('药品名称', _nameCtrl, hint: '如: 阿司匹林'),
|
||||
child: _field('药品名称', _nameCtrl, hint: '如:阿司匹林'),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: _field('剂量', _dosageCtrl, hint: '如: 100mg'),
|
||||
child: _field('剂量', _dosageCtrl, hint: '如:100mg'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 频率选择
|
||||
_label('每日服药次数'), const SizedBox(height: 8),
|
||||
_label('每日服药次数'),
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final n = await showAppCountPicker(
|
||||
@@ -172,27 +170,17 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
initialValue: _timesPerDay,
|
||||
min: 1,
|
||||
max: 4,
|
||||
label: ' 次',
|
||||
label: '次',
|
||||
);
|
||||
if (n != null) _updateTimes(n);
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surface,
|
||||
borderRadius: BorderRadius.circular(AppTheme.rMd),
|
||||
border: Border.all(color: AppColors.border),
|
||||
),
|
||||
child: Text(
|
||||
'$_timesPerDay 次/天',
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
child: _PickerBox(
|
||||
child: Text('$_timesPerDay 次/天', style: AppTextStyles.formValue),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 时间选择
|
||||
_label('服药时间'), const SizedBox(height: 8),
|
||||
_label('服药时间'),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
@@ -213,23 +201,18 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: AppColors.border, width: 1),
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
border: Border.all(color: _visual.borderColor, width: 1),
|
||||
),
|
||||
child: Text(
|
||||
'${_times[i].hour.toString().padLeft(2, '0')}:${_times[i].minute.toString().padLeft(2, '0')}',
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
color: AppColors.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: AppTextStyles.formValue,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// 开始+结束 一行
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -242,7 +225,7 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _dateFieldOpt(
|
||||
'结束日期(可选)',
|
||||
'结束日期(可选)',
|
||||
_end,
|
||||
(d) => setState(() => _end = d),
|
||||
),
|
||||
@@ -250,7 +233,7 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_field('备注', _notesCtrl, hint: '如: 饭后服用、睡前'),
|
||||
_field('备注', _notesCtrl, hint: '如:饭后服用、睡前'),
|
||||
const SizedBox(height: 32),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
@@ -258,20 +241,12 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
onPressed: _loading ? null : _save,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: AppColors.primary,
|
||||
side: const BorderSide(color: AppColors.primary, width: 1.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
foregroundColor: _visual.color,
|
||||
side: BorderSide(color: _visual.color, width: 1.5),
|
||||
shape: RoundedRectangleBorder(borderRadius: AppRadius.lgBorder),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
child: Text(
|
||||
_loading ? '保存中...' : '保存',
|
||||
style: const TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
child: Text(_loading ? '保存中...' : '保存', style: AppTextStyles.button),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
@@ -280,10 +255,8 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _label(String text) => Text(
|
||||
text,
|
||||
style: TextStyle(fontSize: 17, color: AppColors.textSecondary),
|
||||
);
|
||||
Widget _label(String text) => Text(text, style: AppTextStyles.formLabel);
|
||||
|
||||
Widget _field(String label, TextEditingController ctrl, {String? hint}) =>
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -296,27 +269,19 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
hintText: hint,
|
||||
filled: true,
|
||||
fillColor: AppTheme.surface,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppTheme.rMd),
|
||||
borderSide: const BorderSide(color: AppColors.border),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppTheme.rMd),
|
||||
borderSide: const BorderSide(color: AppColors.border),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppTheme.rMd),
|
||||
borderSide: const BorderSide(color: AppColors.primary),
|
||||
),
|
||||
border: _inputBorder(AppColors.border),
|
||||
enabledBorder: _inputBorder(AppColors.border),
|
||||
focusedBorder: _inputBorder(_visual.color),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
style: const TextStyle(fontSize: 19),
|
||||
style: AppTextStyles.formValue,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _dateField(String label, DateTime val, ValueChanged<DateTime> cb) =>
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -333,22 +298,13 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
);
|
||||
if (d != null) cb(d);
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surface,
|
||||
borderRadius: BorderRadius.circular(AppTheme.rMd),
|
||||
border: Border.all(color: AppColors.border),
|
||||
),
|
||||
child: Text(
|
||||
_displayDate(val),
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
child: _PickerBox(
|
||||
child: Text(_displayDate(val), style: AppTextStyles.formValue),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Widget _dateFieldOpt(
|
||||
String label,
|
||||
DateTime? val,
|
||||
@@ -368,24 +324,17 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
);
|
||||
if (d != null) cb(d);
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surface,
|
||||
borderRadius: BorderRadius.circular(AppTheme.rMd),
|
||||
border: Border.all(color: AppColors.border),
|
||||
),
|
||||
child: _PickerBox(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
val != null ? _displayDate(val) : '不设置',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
color: val != null ? null : AppTheme.textHint,
|
||||
Expanded(
|
||||
child: Text(
|
||||
val != null ? _displayDate(val) : '不设置',
|
||||
style: AppTextStyles.formValue.copyWith(
|
||||
color: val != null ? null : AppTheme.textHint,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (val != null) const Spacer(),
|
||||
if (val != null)
|
||||
GestureDetector(
|
||||
onTap: () => cb(null),
|
||||
@@ -401,6 +350,31 @@ class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
OutlineInputBorder _inputBorder(Color color) => OutlineInputBorder(
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
borderSide: BorderSide(color: color),
|
||||
);
|
||||
}
|
||||
|
||||
class _PickerBox extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const _PickerBox({required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppTheme.surface,
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
border: Border.all(color: AppColors.border),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _displayDate(DateTime date) {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../core/app_colors.dart';
|
||||
import '../../core/app_design_tokens.dart';
|
||||
import '../../core/app_module_visuals.dart';
|
||||
import '../../core/app_theme.dart';
|
||||
import '../../core/navigation_provider.dart';
|
||||
import '../../providers/data_providers.dart';
|
||||
import '../../widgets/app_empty_state.dart';
|
||||
import '../../widgets/app_future_view.dart';
|
||||
import '../../widgets/app_status_badge.dart';
|
||||
import '../../widgets/common_widgets.dart';
|
||||
import '../../widgets/enterprise_widgets.dart';
|
||||
|
||||
@@ -16,8 +19,7 @@ class MedicationListPage extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class _MedicationListPageState extends ConsumerState<MedicationListPage> {
|
||||
static const _medBlue = Color(0xFF60A5FA);
|
||||
static const _medCyan = Color(0xFF8B5CF6);
|
||||
static const _medVisual = AppModuleVisuals.medication;
|
||||
Future<List<Map<String, dynamic>>>? _future;
|
||||
|
||||
@override
|
||||
@@ -50,7 +52,7 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage> {
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => pushRoute(ref, 'medicationEdit'),
|
||||
backgroundColor: _medBlue,
|
||||
backgroundColor: _medVisual.color,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: const Icon(Icons.add, size: 28, color: Colors.white),
|
||||
),
|
||||
@@ -60,8 +62,8 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage> {
|
||||
errorTitle: '用药信息加载失败',
|
||||
onData: (ctx, list) {
|
||||
if (list.isEmpty) {
|
||||
return const AppEmptyState(
|
||||
icon: Icons.medication_outlined,
|
||||
return AppEmptyState(
|
||||
icon: _medVisual.icon,
|
||||
title: '暂无用药',
|
||||
subtitle: '点击右下角➕添加药品',
|
||||
);
|
||||
@@ -77,9 +79,9 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage> {
|
||||
EnterpriseHeader(
|
||||
title: '今日用药概览',
|
||||
subtitle: '集中管理服药计划、剂量和每日打卡状态',
|
||||
icon: Icons.medication_outlined,
|
||||
color: _medBlue,
|
||||
accent: _medCyan,
|
||||
icon: _medVisual.icon,
|
||||
color: _medVisual.color,
|
||||
accent: AppColors.primary,
|
||||
showIcon: false,
|
||||
stats: [
|
||||
EnterpriseStat(
|
||||
@@ -129,17 +131,13 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage> {
|
||||
height: 44,
|
||||
decoration: BoxDecoration(
|
||||
gradient: isActive
|
||||
? const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [_medBlue, _medCyan],
|
||||
)
|
||||
? _medVisual.gradient
|
||||
: AppColors.surfaceGradient,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderRadius: AppRadius.mdBorder,
|
||||
border: Border.all(color: AppColors.borderLight),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.medication_outlined,
|
||||
_medVisual.icon,
|
||||
size: 25,
|
||||
color: isActive ? Colors.white : AppTheme.textSub,
|
||||
),
|
||||
@@ -153,9 +151,7 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage> {
|
||||
children: [
|
||||
Text(
|
||||
m['name']?.toString() ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 19,
|
||||
fontWeight: FontWeight.w600,
|
||||
style: AppTextStyles.listTitle.copyWith(
|
||||
color: isActive
|
||||
? AppTheme.text
|
||||
: AppTheme.textSub,
|
||||
@@ -163,24 +159,9 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage> {
|
||||
),
|
||||
if (!isActive) ...[
|
||||
const SizedBox(width: 6),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 6,
|
||||
vertical: 1,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _medBlue.withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(
|
||||
999,
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'已停',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: _medBlue,
|
||||
),
|
||||
),
|
||||
AppStatusBadge(
|
||||
label: '已停',
|
||||
color: _medVisual.color,
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -188,8 +169,7 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage> {
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
'${m['dosage'] ?? ''} $times',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
style: AppTextStyles.listSubtitle.copyWith(
|
||||
color: AppTheme.textSub,
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user