fix: 管理员系统 + 注册流程重构 + UI改版 + 全链路修复

- 新增管理员角色(手机号12345678910),管理员可增删医生、查看患者
- 注册页重构:去掉角色选择+审核码,改为选医生+填姓名
- 医生端点按DoctorId过滤患者,Patient↔Doctor关系建立
- Doctor/DoctorProfile/User实体新增关联字段
- JSON循环引用修复(IgnoreCycles)
- /api/doctors改为公开接口
- 登录闪屏修复+原生Android启动页
- 输入框全局白底+灰框
- 蓝牙重连同步修复
- Web端(doctor_web+health_app/web)全部删除
- 全局UI改版:白底企业风,新配色和组件系统
- 新品牌图标和启动图
This commit is contained in:
MingNian
2026-06-16 15:16:44 +08:00
parent b635e9d25f
commit c70d5d4be6
103 changed files with 12284 additions and 13049 deletions

View File

@@ -8,129 +8,391 @@ import '../../providers/data_providers.dart';
class MedicationEditPage extends ConsumerStatefulWidget {
final String? id;
const MedicationEditPage({super.key, this.id});
@override ConsumerState<MedicationEditPage> createState() => _MedicationEditPageState();
@override
ConsumerState<MedicationEditPage> createState() => _MedicationEditPageState();
}
class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
final _nameCtrl = TextEditingController();
final _dosageCtrl = TextEditingController();
final _notesCtrl = TextEditingController();
int _timesPerDay = 2;
List<TimeOfDay> _times = [const TimeOfDay(hour: 8, minute: 0), const TimeOfDay(hour: 18, minute: 0)];
List<TimeOfDay> _times = [
const TimeOfDay(hour: 8, minute: 0),
const TimeOfDay(hour: 18, minute: 0),
];
DateTime _start = DateTime.now();
DateTime? _end;
bool _loading = false;
@override void initState() { super.initState(); if (widget.id != null) _load(); }
@override void dispose() { _nameCtrl.dispose(); _dosageCtrl.dispose(); _notesCtrl.dispose(); super.dispose(); }
@override
void initState() {
super.initState();
if (widget.id != null) _load();
}
@override
void dispose() {
_nameCtrl.dispose();
_dosageCtrl.dispose();
_notesCtrl.dispose();
super.dispose();
}
Future<void> _load() async {
final srv = ref.read(medicationServiceProvider);
final list = await srv.getList();
final m = list.cast<Map<String, dynamic>?>().firstWhere((x) => x?['id']?.toString() == widget.id, orElse: () => null);
final m = list.cast<Map<String, dynamic>?>().firstWhere(
(x) => x?['id']?.toString() == widget.id,
orElse: () => null,
);
if (m == null || !mounted) return;
_nameCtrl.text = m['name']?.toString() ?? '';
_dosageCtrl.text = m['dosage']?.toString() ?? '';
_notesCtrl.text = m['notes']?.toString() ?? '';
final tod = (m['timeOfDay'] as List?)?.map((t) {
final parts = t.toString().split(':');
return TimeOfDay(hour: int.tryParse(parts[0]) ?? 8, minute: int.tryParse(parts.length > 1 ? parts[1] : '0') ?? 0);
}).toList() ?? [const TimeOfDay(hour: 8, minute: 0), const TimeOfDay(hour: 18, minute: 0)];
final tod =
(m['timeOfDay'] as List?)?.map((t) {
final parts = t.toString().split(':');
return TimeOfDay(
hour: int.tryParse(parts[0]) ?? 8,
minute: int.tryParse(parts.length > 1 ? parts[1] : '0') ?? 0,
);
}).toList() ??
[
const TimeOfDay(hour: 8, minute: 0),
const TimeOfDay(hour: 18, minute: 0),
];
_timesPerDay = tod.length;
_times = tod;
if (m['startDate'] != null) _start = DateTime.tryParse(m['startDate'].toString()) ?? DateTime.now();
if (m['startDate'] != null)
_start = DateTime.tryParse(m['startDate'].toString()) ?? DateTime.now();
if (m['endDate'] != null) _end = DateTime.tryParse(m['endDate'].toString());
setState(() {});
}
Future<void> _save() async {
final name = _nameCtrl.text.trim();
if (name.isEmpty) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('请输入药品名称'))); return; }
if (name.isEmpty) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('请输入药品名称')));
return;
}
setState(() => _loading = true);
final data = {
'name': name, 'dosage': _dosageCtrl.text.trim(), 'frequency': 'Daily',
'name': name,
'dosage': _dosageCtrl.text.trim(),
'frequency': 'Daily',
'notes': _notesCtrl.text.trim(),
'timeOfDay': _times.map((t) => '${t.hour.toString().padLeft(2,'0')}:${t.minute.toString().padLeft(2,'0')}').toList(),
'startDate': '${_start.year}-${_start.month.toString().padLeft(2,'0')}-${_start.day.toString().padLeft(2,'0')}',
if (_end != null) 'endDate': '${_end!.year}-${_end!.month.toString().padLeft(2,'0')}-${_end!.day.toString().padLeft(2,'0')}',
'timeOfDay': _times
.map(
(t) =>
'${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}',
)
.toList(),
'startDate':
'${_start.year}-${_start.month.toString().padLeft(2, '0')}-${_start.day.toString().padLeft(2, '0')}',
if (_end != null)
'endDate':
'${_end!.year}-${_end!.month.toString().padLeft(2, '0')}-${_end!.day.toString().padLeft(2, '0')}',
'source': 'Manual',
};
final srv = ref.read(medicationServiceProvider);
try {
if (widget.id != null) { await srv.update(widget.id!, data); } else { await srv.create(data); }
ref.invalidate(medicationListProvider); ref.invalidate(medicationReminderProvider);
if (mounted) { popRoute(ref); }
} catch (_) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('保存失败'), backgroundColor: AppTheme.error)); }
if (widget.id != null) {
await srv.update(widget.id!, data);
} else {
await srv.create(data);
}
ref.invalidate(medicationListProvider);
ref.invalidate(medicationReminderProvider);
if (mounted) {
popRoute(ref);
}
} catch (_) {
if (mounted)
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('保存失败'),
backgroundColor: AppTheme.error,
),
);
}
if (mounted) setState(() => _loading = false);
}
void _updateTimes(int n) {
setState(() {
_timesPerDay = n;
while (_times.length < n) _times.add(const TimeOfDay(hour: 12, minute: 0));
while (_times.length < n)
_times.add(const TimeOfDay(hour: 12, minute: 0));
while (_times.length > n) _times.removeLast();
});
}
@override Widget build(BuildContext context) {
@override
Widget build(BuildContext context) {
return GradientScaffold(
appBar: AppBar(title: Text(widget.id != null ? '编辑用药' : '添加用药')),
body: ListView(padding: const EdgeInsets.all(16), children: [
// 名称+剂量 一行
Row(children: [
Expanded(flex: 3, child: _field('药品名称', _nameCtrl, hint: '如: 阿司匹林')),
const SizedBox(width: 12),
Expanded(flex: 2, child: _field('剂量', _dosageCtrl, hint: '如: 100mg')),
]),
const SizedBox(height: 16),
// 频率选择
_label('每日服药次数'), const SizedBox(height: 8),
Wrap(spacing: 8, children: List.generate(4, (i) => _chip('${i + 1}', _timesPerDay == i + 1, () => _updateTimes(i + 1)))),
const SizedBox(height: 16),
// 时间选择
_label('服药时间'), const SizedBox(height: 8),
Wrap(spacing: 8, runSpacing: 8, children: List.generate(_timesPerDay, (i) => GestureDetector(
onTap: () async {
final t = await showTimePicker(context: context, initialTime: _times[i]);
if (t != null) setState(() => _times[i] = t);
},
child: Container(padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.border, 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))),
))),
const SizedBox(height: 16),
// 开始+结束 一行
Row(children: [
Expanded(child: _dateField('开始日期', _start, (d) => setState(() => _start = d))),
const SizedBox(width: 12),
Expanded(child: _dateFieldOpt('结束日期(可选)', _end, (d) => setState(() => _end = d))),
]),
const SizedBox(height: 16),
_field('备注', _notesCtrl, hint: '如: 饭后服用、睡前'),
const SizedBox(height: 32),
SizedBox(width: double.infinity, child: ElevatedButton(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)), padding: const EdgeInsets.symmetric(vertical: 14)), child: Text(_loading ? '保存中...' : '保存', style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600)))),
const SizedBox(height: 20),
]),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// 名称+剂量 一行
Row(
children: [
Expanded(
flex: 3,
child: _field('药品名称', _nameCtrl, hint: '如: 阿司匹林'),
),
const SizedBox(width: 12),
Expanded(
flex: 2,
child: _field('剂量', _dosageCtrl, hint: '如: 100mg'),
),
],
),
const SizedBox(height: 16),
// 频率选择
_label('每日服药次数'), const SizedBox(height: 8),
Wrap(
spacing: 8,
children: List.generate(
4,
(i) => _chip(
'${i + 1}',
_timesPerDay == i + 1,
() => _updateTimes(i + 1),
),
),
),
const SizedBox(height: 16),
// 时间选择
_label('服药时间'), const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: List.generate(
_timesPerDay,
(i) => GestureDetector(
onTap: () async {
final t = await showTimePicker(
context: context,
initialTime: _times[i],
);
if (t != null) setState(() => _times[i] = t);
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 10,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.border, 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,
),
),
),
),
),
),
const SizedBox(height: 16),
// 开始+结束 一行
Row(
children: [
Expanded(
child: _dateField(
'开始日期',
_start,
(d) => setState(() => _start = d),
),
),
const SizedBox(width: 12),
Expanded(
child: _dateFieldOpt(
'结束日期(可选)',
_end,
(d) => setState(() => _end = d),
),
),
],
),
const SizedBox(height: 16),
_field('备注', _notesCtrl, hint: '如: 饭后服用、睡前'),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
child: ElevatedButton(
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),
),
padding: const EdgeInsets.symmetric(vertical: 14),
),
child: Text(
_loading ? '保存中...' : '保存',
style: const TextStyle(
fontSize: 19,
fontWeight: FontWeight.w600,
),
),
),
),
const SizedBox(height: 20),
],
),
);
}
Widget _label(String text) => Text(text, style: TextStyle(fontSize: 17, color: AppColors.textSecondary));
Widget _field(String label, TextEditingController ctrl, {String? hint}) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
_label(label), const SizedBox(height: 6),
TextField(controller: ctrl, decoration: InputDecoration(hintText: hint, filled: true, fillColor: AppTheme.surface, border: OutlineInputBorder(borderRadius: BorderRadius.circular(AppTheme.rMd), borderSide: BorderSide.none), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12)), style: const TextStyle(fontSize: 19)),
]);
Widget _chip(String label, bool sel, VoidCallback onTap) => GestureDetector(onTap: onTap, child: Container(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration(color: sel ? Colors.white : AppColors.cardInner, borderRadius: BorderRadius.circular(16), border: sel ? Border.all(color: AppColors.primary, width: 1.5) : null), child: Text(label, style: TextStyle(fontSize: 16, color: sel ? AppColors.primary : AppColors.textSecondary, fontWeight: FontWeight.w500))));
Widget _dateField(String label, DateTime val, ValueChanged<DateTime> cb) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
_label(label), const SizedBox(height: 6),
GestureDetector(onTap: () async { final d = await showDatePicker(context: context, initialDate: val, firstDate: DateTime(2024), lastDate: DateTime(2030)); 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)), child: Text('${val.month}/${val.day}', style: const TextStyle(fontSize: 18)))),
]);
Widget _dateFieldOpt(String label, DateTime? val, ValueChanged<DateTime?> cb) => Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
_label(label), const SizedBox(height: 6),
GestureDetector(onTap: () async { final d = await showDatePicker(context: context, initialDate: val ?? DateTime.now(), firstDate: DateTime(2024), lastDate: DateTime(2030)); 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)), child: Row(children: [
Text(val != null ? '${val.month}/${val.day}' : '不设置', style: TextStyle(fontSize: 18, color: val != null ? null : AppTheme.textHint)),
if (val != null) const Spacer(),
if (val != null) GestureDetector(onTap: () => cb(null), child: const Icon(Icons.close, size: 19, color: AppColors.textHint)),
]))),
]);
Widget _label(String text) => Text(
text,
style: TextStyle(fontSize: 17, color: AppColors.textSecondary),
);
Widget _field(String label, TextEditingController ctrl, {String? hint}) =>
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_label(label),
const SizedBox(height: 6),
TextField(
controller: ctrl,
decoration: InputDecoration(
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),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12,
),
),
style: const TextStyle(fontSize: 19),
),
],
);
Widget _chip(String label, bool sel, VoidCallback onTap) => GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: sel ? Colors.white : AppColors.cardInner,
borderRadius: BorderRadius.circular(16),
border: sel ? Border.all(color: AppColors.primary, width: 1.5) : null,
),
child: Text(
label,
style: TextStyle(
fontSize: 16,
color: sel ? AppColors.primary : AppColors.textSecondary,
fontWeight: FontWeight.w500,
),
),
),
);
Widget _dateField(String label, DateTime val, ValueChanged<DateTime> cb) =>
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_label(label),
const SizedBox(height: 6),
GestureDetector(
onTap: () async {
final d = await showDatePicker(
context: context,
initialDate: val,
firstDate: DateTime(2024),
lastDate: DateTime(2030),
);
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(
'${val.month}/${val.day}',
style: const TextStyle(fontSize: 18),
),
),
),
],
);
Widget _dateFieldOpt(
String label,
DateTime? val,
ValueChanged<DateTime?> cb,
) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_label(label),
const SizedBox(height: 6),
GestureDetector(
onTap: () async {
final d = await showDatePicker(
context: context,
initialDate: val ?? DateTime.now(),
firstDate: DateTime(2024),
lastDate: DateTime(2030),
);
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: Row(
children: [
Text(
val != null ? '${val.month}/${val.day}' : '不设置',
style: TextStyle(
fontSize: 18,
color: val != null ? null : AppTheme.textHint,
),
),
if (val != null) const Spacer(),
if (val != null)
GestureDetector(
onTap: () => cb(null),
child: const Icon(
Icons.close,
size: 19,
color: AppColors.textHint,
),
),
],
),
),
),
],
);
}

View File

@@ -9,27 +9,41 @@ import '../../widgets/common_widgets.dart';
class MedicationListPage extends ConsumerStatefulWidget {
const MedicationListPage({super.key});
@override ConsumerState<MedicationListPage> createState() => _MedicationListPageState();
@override
ConsumerState<MedicationListPage> createState() => _MedicationListPageState();
}
class _MedicationListPageState extends ConsumerState<MedicationListPage> with SingleTickerProviderStateMixin {
class _MedicationListPageState extends ConsumerState<MedicationListPage>
with SingleTickerProviderStateMixin {
String _filter = '';
Future<List<Map<String, dynamic>>>? _future;
late final _tabCtrl = TabController(length: 3, vsync: this);
@override void initState() {
@override
void initState() {
super.initState();
_tabCtrl.addListener(() {
if (!_tabCtrl.indexIsChanging) {
setState(() { _filter = ['', 'active', 'inactive'][_tabCtrl.index]; });
setState(() {
_filter = ['', 'active', 'inactive'][_tabCtrl.index];
});
_load();
}
});
_load();
}
@override void dispose() { _tabCtrl.dispose(); super.dispose(); }
void _load() { setState(() { _future = ref.read(medicationServiceProvider).getMedications(_filter); }); }
@override
void dispose() {
_tabCtrl.dispose();
super.dispose();
}
void _load() {
setState(() {
_future = ref.read(medicationServiceProvider).getMedications(_filter);
});
}
Future<void> _delete(String id) async {
await ref.read(medicationServiceProvider).deleteMedication(id);
@@ -39,10 +53,14 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage> with Si
static const _tabs = ['全部', '服用中', '已停药'];
@override Widget build(BuildContext context) {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref)),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => popRoute(ref),
),
title: const Text('用药管理'),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(48),
@@ -51,7 +69,10 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage> with Si
indicatorColor: AppColors.primary,
labelColor: AppColors.primary,
unselectedLabelColor: AppColors.textHint,
labelStyle: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
labelStyle: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
),
tabs: _tabs.map((t) => Tab(child: Text(t))).toList(),
),
),
@@ -65,72 +86,124 @@ class _MedicationListPageState extends ConsumerState<MedicationListPage> with Si
body: Container(
decoration: const BoxDecoration(gradient: AppColors.bgGradient),
child: FutureBuilder<List<Map<String, dynamic>>>(
future: _future,
builder: (ctx, snap) {
final list = snap.data ?? [];
if (list.isEmpty) {
return const AppEmptyState(
icon: Icons.medication_outlined,
title: '暂无用药',
subtitle: '点击右下角➕添加药品',
);
}
return ListView.builder(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 80),
itemCount: list.length,
itemBuilder: (ctx, i) {
final m = list[i];
final times = (m['timeOfDay'] as List?)?.map((t) => t.toString().substring(0, 5)).join(' ') ?? '';
final isActive = m['isActive'] == true;
final id = m['id']?.toString() ?? '';
return SwipeDeleteTile(
key: Key(id),
onDelete: () => _delete(id),
onTap: () => pushRoute(ref, 'medCheckIn', params: {'id': id}),
margin: const EdgeInsets.symmetric(horizontal: AppTheme.sLg, vertical: 4),
child: Container(
padding: const EdgeInsets.all(AppTheme.sLg),
decoration: BoxDecoration(
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.rMd),
boxShadow: [AppTheme.shadowLight],
),
child: Row(children: [
Container(
width: 44, height: 44,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(AppTheme.rSm),
),
child: Icon(Icons.medication_outlined, size: 25, color: isActive ? AppColors.primary : AppTheme.textSub),
),
const SizedBox(width: AppTheme.sMd),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Text(m['name']?.toString() ?? '', style: TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: isActive ? AppTheme.text : AppTheme.textSub)),
if (!isActive) ...[
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
decoration: BoxDecoration(
color: AppTheme.bgSoft,
borderRadius: BorderRadius.circular(4),
),
child: Text('已停', style: TextStyle(fontSize: 13, color: AppTheme.textSub)),
),
],
]),
const SizedBox(height: 2),
Text('${m['dosage'] ?? ''} $times', style: TextStyle(fontSize: 16, color: AppTheme.textSub)),
])),
Icon(Icons.chevron_right, size: 21, color: AppTheme.border),
]),
),
future: _future,
builder: (ctx, snap) {
final list = snap.data ?? [];
if (list.isEmpty) {
return const AppEmptyState(
icon: Icons.medication_outlined,
title: '暂无用药',
subtitle: '点击右下角➕添加药品',
);
},
);
},
),
}
return ListView.builder(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 80),
itemCount: list.length,
itemBuilder: (ctx, i) {
final m = list[i];
final times =
(m['timeOfDay'] as List?)
?.map((t) => t.toString().substring(0, 5))
.join(' ') ??
'';
final isActive = m['isActive'] == true;
final id = m['id']?.toString() ?? '';
return SwipeDeleteTile(
key: Key(id),
onDelete: () => _delete(id),
onTap: () => pushRoute(ref, 'medCheckIn', params: {'id': id}),
margin: const EdgeInsets.symmetric(
horizontal: AppTheme.sLg,
vertical: 4,
),
child: Container(
padding: const EdgeInsets.all(AppTheme.sLg),
decoration: BoxDecoration(
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.rMd),
border: Border.all(color: AppColors.border),
boxShadow: AppColors.cardShadowLight,
),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: AppColors.iconBg,
borderRadius: BorderRadius.circular(AppTheme.rSm),
),
child: Icon(
Icons.medication_outlined,
size: 25,
color: isActive
? AppColors.primary
: AppTheme.textSub,
),
),
const SizedBox(width: AppTheme.sMd),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
m['name']?.toString() ?? '',
style: TextStyle(
fontSize: 19,
fontWeight: FontWeight.w600,
color: isActive
? AppTheme.text
: AppTheme.textSub,
),
),
if (!isActive) ...[
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 1,
),
decoration: BoxDecoration(
color: AppTheme.bgSoft,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'已停',
style: TextStyle(
fontSize: 13,
color: AppTheme.textSub,
),
),
),
],
],
),
const SizedBox(height: 2),
Text(
'${m['dosage'] ?? ''} $times',
style: TextStyle(
fontSize: 16,
color: AppTheme.textSub,
),
),
],
),
),
Icon(
Icons.chevron_right,
size: 21,
color: AppTheme.border,
),
],
),
),
);
},
);
},
),
),
);
}