import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/theme/app_theme.dart'; import '../../models/user.dart'; import '../../models/warehouse.dart'; import '../../providers/user_provider.dart'; import '../../providers/warehouse_provider.dart'; class SettingsScreen extends ConsumerStatefulWidget { const SettingsScreen({super.key}); @override ConsumerState createState() => _SettingsScreenState(); } class _SettingsScreenState extends ConsumerState { @override Widget build(BuildContext context) { return DefaultTabController( length: 4, child: Column( children: [ Container( color: AppTheme.surface, child: const TabBar( isScrollable: true, labelColor: AppTheme.primary, unselectedLabelColor: AppTheme.textSecondary, indicatorColor: AppTheme.primary, indicatorWeight: 2, labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), tabs: [ Tab(text: '用户管理'), Tab(text: '仓库管理'), Tab(text: '编号规则'), Tab(text: '系统参数'), ], ), ), const Divider(height: 1), Expanded( child: TabBarView( children: [ _buildUsersTab(), _buildWarehousesTab(), _buildNumberRulesTab(), _buildSystemParamsTab(), ], ), ), ], ), ); } Widget _buildUsersTab() { final asyncUsers = ref.watch(userListProvider); return Column( children: [ Container( height: 52, color: AppTheme.surface, padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ ElevatedButton.icon( onPressed: () => _showAddUserDialog(context), icon: const Icon(Icons.person_add, size: 16), label: const Text('新增用户'), ), ], ), ), const Divider(height: 1), Expanded( child: asyncUsers.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('加载失败:$e', style: const TextStyle(color: AppTheme.danger)), const SizedBox(height: 12), ElevatedButton( onPressed: () => ref.read(userListProvider.notifier).reload(), child: const Text('重试'), ), ], ), ), data: (users) => SingleChildScrollView( child: DataTable( headingRowColor: WidgetStateProperty.all(const Color(0xFFF0F4FF)), columns: const [ DataColumn(label: Text('姓名')), DataColumn(label: Text('用户名')), DataColumn(label: Text('角色')), DataColumn(label: Text('状态')), DataColumn(label: Text('操作')), ], rows: users .map((u) => DataRow(cells: [ DataCell(Row( mainAxisSize: MainAxisSize.min, children: [ CircleAvatar( radius: 14, backgroundColor: AppTheme.primary.withOpacity(0.15), child: Text( (u.realName ?? u.username) .substring(0, 1), style: const TextStyle( fontSize: 12, color: AppTheme.primary, fontWeight: FontWeight.w600), ), ), const SizedBox(width: 8), Text(u.realName ?? u.username), ], )), DataCell(Text(u.username, style: const TextStyle( fontFamily: 'monospace', fontSize: 12))), DataCell(_RoleBadge(u.roleLabel)), DataCell(Switch( value: u.isActive, onChanged: (v) => ref .read(userListProvider.notifier) .updateUser(u.id, {'is_active': v}), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, )), DataCell(Row( mainAxisSize: MainAxisSize.min, children: [ TextButton( onPressed: () => _showEditUserDialog(context, u), child: const Text('编辑', style: TextStyle(fontSize: 12)), ), TextButton( onPressed: () => _showResetPasswordDialog(context, u), child: const Text('重置密码', style: TextStyle(fontSize: 12)), ), ], )), ])) .toList(), ), ), ), ), ], ); } Widget _buildWarehousesTab() { final asyncWarehouses = ref.watch(warehouseListProvider); return Column( children: [ Container( height: 52, color: AppTheme.surface, padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ ElevatedButton.icon( onPressed: () => _showWarehouseDialog(context), icon: const Icon(Icons.add, size: 16), label: const Text('新建'), ), ], ), ), const Divider(height: 1), Expanded( child: asyncWarehouses.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('加载失败:$e', style: const TextStyle(color: AppTheme.danger)), const SizedBox(height: 12), ElevatedButton( onPressed: () => ref.read(warehouseListProvider.notifier).reload(), child: const Text('重试'), ), ], ), ), data: (warehouses) { if (warehouses.isEmpty) { return const Center( child: Text('暂无仓库', style: TextStyle(color: AppTheme.textSecondary))); } return SingleChildScrollView( child: DataTable( headingRowColor: WidgetStateProperty.all(const Color(0xFFF0F4FF)), columns: const [ DataColumn(label: Text('仓库名称')), DataColumn(label: Text('位置')), DataColumn(label: Text('默认仓库')), DataColumn(label: Text('操作')), ], rows: warehouses .map((w) => DataRow(cells: [ DataCell(Text(w.name, style: const TextStyle( fontWeight: FontWeight.w500))), DataCell(Text(w.location ?? '-')), DataCell(w.isDefault ? const Icon(Icons.check_circle, color: AppTheme.success, size: 18) : const SizedBox()), DataCell(Row( mainAxisSize: MainAxisSize.min, children: [ TextButton( key: Key('btn_edit_${w.id}'), onPressed: () => _showWarehouseDialog(context, warehouse: w), child: const Text('编辑', style: TextStyle(fontSize: 12)), ), TextButton( key: Key('btn_delete_${w.id}'), onPressed: () => _confirmDeleteWarehouse(context, w), child: const Text('删除', style: TextStyle( fontSize: 12, color: AppTheme.danger)), ), ], )), ])) .toList(), ), ); }, ), ), ], ); } Future _confirmDeleteWarehouse( BuildContext context, Warehouse w) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('确认删除'), content: Text('确认删除仓库「${w.name}」?'), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: const Text('取消')), ElevatedButton( onPressed: () => Navigator.of(ctx).pop(true), style: ElevatedButton.styleFrom( backgroundColor: AppTheme.danger, foregroundColor: Colors.white), child: const Text('删除'), ), ], ), ); if (confirmed == true && mounted) { try { await ref .read(warehouseListProvider.notifier) .deleteWarehouse(w.id); if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('删除成功'), backgroundColor: AppTheme.success)); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('删除失败:$e'), backgroundColor: AppTheme.danger)); } } } } void _showWarehouseDialog(BuildContext context, {Warehouse? warehouse}) { showDialog( context: context, builder: (ctx) => _WarehouseFormDialog( warehouse: warehouse, onSaved: () => ref.read(warehouseListProvider.notifier).reload(), ), ); } Widget _buildNumberRulesTab() { final rules = [ {'type': '入库单', 'prefix': 'RK', 'format': 'RK{年}{月}{日}{序号4}', 'example': 'RK20260404001', 'currentNo': 4}, {'type': '出库单', 'prefix': 'CK', 'format': 'CK{年}{月}{日}{序号4}', 'example': 'CK20260404001', 'currentNo': 2}, {'type': '盘点单', 'prefix': 'PD', 'format': 'PD{年}{月}{日}{序号4}', 'example': 'PD20260404001', 'currentNo': 1}, {'type': '商品编码', 'prefix': 'SP', 'format': 'SP{序号3}', 'example': 'SP001', 'currentNo': 12}, ]; return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('编号规则配置', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), const SizedBox(height: 4), const Text('配置各类单据的自动编号规则,修改后对新建单据生效', style: TextStyle(fontSize: 13, color: AppTheme.textSecondary)), const SizedBox(height: 16), Card( child: DataTable( headingRowColor: WidgetStateProperty.all(const Color(0xFFF0F4FF)), columns: const [ DataColumn(label: Text('单据类型')), DataColumn(label: Text('前缀')), DataColumn(label: Text('格式')), DataColumn(label: Text('示例')), DataColumn(label: Text('当前序号'), numeric: true), DataColumn(label: Text('操作')), ], rows: rules .map((r) => DataRow(cells: [ DataCell(Text(r['type'] as String, style: const TextStyle(fontWeight: FontWeight.w500))), DataCell(Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2), decoration: BoxDecoration( color: AppTheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(3), ), child: Text(r['prefix'] as String, style: const TextStyle( color: AppTheme.primary, fontFamily: 'monospace', fontSize: 13, fontWeight: FontWeight.w600)), )), DataCell(Text(r['format'] as String, style: const TextStyle( fontSize: 12, color: AppTheme.textSecondary))), DataCell(Text(r['example'] as String, style: const TextStyle( fontFamily: 'monospace', fontSize: 12))), DataCell(Text('${r['currentNo']}')), DataCell(TextButton( onPressed: () {}, child: const Text('编辑', style: TextStyle(fontSize: 12)))), ])) .toList(), ), ), ], ), ); } Widget _buildSystemParamsTab() { return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('系统参数', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), const SizedBox(height: 16), Card( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('基本设置', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppTheme.primaryDark)), const Divider(height: 24), _ParamRow(label: '系统名称', value: '酒库管理系统'), _ParamRow(label: '货币单位', value: '人民币(CNY)'), _ParamRow(label: '日期格式', value: 'YYYY-MM-DD'), _ParamRow(label: '时区', value: 'Asia/Shanghai (UTC+8)'), const SizedBox(height: 16), const Text('审核设置', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppTheme.primaryDark)), const Divider(height: 24), _ParamRow(label: '入库单需要审核', value: '是', isSwitch: true), _ParamRow(label: '出库单需要审核', value: '是', isSwitch: true), _ParamRow(label: '允许超量出库', value: '否', isSwitch: false), ], ), ), ), const SizedBox(height: 12), Row( children: [ ElevatedButton( onPressed: () {}, child: const Text('保存设置'), ), const SizedBox(width: 8), OutlinedButton( onPressed: () {}, child: const Text('重置默认'), ), ], ), ], ), ); } void _showAddUserDialog(BuildContext context) { showDialog( context: context, builder: (ctx) => _UserFormDialog( onSaved: () => ref.read(userListProvider.notifier).reload(), ), ); } void _showEditUserDialog(BuildContext context, AppUser user) { showDialog( context: context, builder: (ctx) => _UserFormDialog( user: user, onSaved: () => ref.read(userListProvider.notifier).reload(), ), ); } void _showResetPasswordDialog(BuildContext context, AppUser user) { showDialog( context: context, builder: (ctx) => _ResetPasswordDialog(user: user), ); } } class _WarehouseFormDialog extends ConsumerStatefulWidget { final Warehouse? warehouse; final VoidCallback onSaved; const _WarehouseFormDialog({this.warehouse, required this.onSaved}); @override ConsumerState<_WarehouseFormDialog> createState() => _WarehouseFormDialogState(); } class _WarehouseFormDialogState extends ConsumerState<_WarehouseFormDialog> { final _formKey = GlobalKey(); late final TextEditingController _nameCtrl; late final TextEditingController _locationCtrl; late bool _isDefault; bool _saving = false; @override void initState() { super.initState(); _nameCtrl = TextEditingController(text: widget.warehouse?.name ?? ''); _locationCtrl = TextEditingController(text: widget.warehouse?.location ?? ''); _isDefault = widget.warehouse?.isDefault ?? false; } @override void dispose() { _nameCtrl.dispose(); _locationCtrl.dispose(); super.dispose(); } Future _save() async { if (!_formKey.currentState!.validate()) return; setState(() => _saving = true); final data = { 'name': _nameCtrl.text.trim(), if (_locationCtrl.text.trim().isNotEmpty) 'location': _locationCtrl.text.trim(), 'is_default': _isDefault, }; try { final notifier = ref.read(warehouseListProvider.notifier); if (widget.warehouse != null) { await notifier.updateWarehouse(widget.warehouse!.id, data); } else { await notifier.createWarehouse(data); } if (mounted) { Navigator.of(context).pop(); widget.onSaved(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( widget.warehouse != null ? '仓库更新成功' : '仓库创建成功'), backgroundColor: AppTheme.success, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('保存失败:$e'), backgroundColor: AppTheme.danger), ); } } finally { if (mounted) setState(() => _saving = false); } } @override Widget build(BuildContext context) { return AlertDialog( title: Text(widget.warehouse != null ? '编辑仓库' : '新建仓库'), content: SizedBox( width: 400, child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ TextFormField( controller: _nameCtrl, decoration: const InputDecoration(labelText: '仓库名称'), validator: (v) => (v == null || v.isEmpty) ? '不能为空' : null, ), const SizedBox(height: 12), TextFormField( controller: _locationCtrl, decoration: const InputDecoration(labelText: '位置'), ), const SizedBox(height: 12), CheckboxListTile( title: const Text('设为默认仓库'), value: _isDefault, onChanged: (v) => setState(() => _isDefault = v ?? false), contentPadding: EdgeInsets.zero, ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('取消'), ), ElevatedButton( onPressed: _saving ? null : _save, child: _saving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white)) : const Text('保存'), ), ], ); } } // ── 新增/编辑用户弹窗 ───────────────────────────────────── class _UserFormDialog extends ConsumerStatefulWidget { final AppUser? user; final VoidCallback onSaved; const _UserFormDialog({this.user, required this.onSaved}); @override ConsumerState<_UserFormDialog> createState() => _UserFormDialogState(); } class _UserFormDialogState extends ConsumerState<_UserFormDialog> { final _formKey = GlobalKey(); late final TextEditingController _usernameCtrl; late final TextEditingController _realNameCtrl; late final TextEditingController _phoneCtrl; late final TextEditingController _passwordCtrl; late String _role; bool _saving = false; bool get _isEdit => widget.user != null; @override void initState() { super.initState(); _usernameCtrl = TextEditingController(text: widget.user?.username ?? ''); _realNameCtrl = TextEditingController(text: widget.user?.realName ?? ''); _phoneCtrl = TextEditingController(text: widget.user?.phone ?? ''); _passwordCtrl = TextEditingController(); _role = widget.user?.role ?? 'operator'; } @override void dispose() { _usernameCtrl.dispose(); _realNameCtrl.dispose(); _phoneCtrl.dispose(); _passwordCtrl.dispose(); super.dispose(); } Future _save() async { if (!_formKey.currentState!.validate()) return; setState(() => _saving = true); try { final notifier = ref.read(userListProvider.notifier); if (_isEdit) { await notifier.updateUser(widget.user!.id, { 'real_name': _realNameCtrl.text.trim(), 'phone': _phoneCtrl.text.trim(), 'role': _role, }); } else { await notifier.createUser({ 'username': _usernameCtrl.text.trim(), 'password': _passwordCtrl.text, 'real_name': _realNameCtrl.text.trim(), 'phone': _phoneCtrl.text.trim(), 'role': _role, }); } if (mounted) { Navigator.of(context).pop(); widget.onSaved(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(_isEdit ? '用户更新成功' : '用户创建成功'), backgroundColor: AppTheme.success, )); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('保存失败:$e'), backgroundColor: AppTheme.danger, )); } } finally { if (mounted) setState(() => _saving = false); } } @override Widget build(BuildContext context) { return AlertDialog( title: Text(_isEdit ? '编辑用户' : '新增用户'), content: SizedBox( width: 400, child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ if (!_isEdit) TextFormField( controller: _usernameCtrl, decoration: const InputDecoration(labelText: '用户名'), validator: (v) => (v == null || v.isEmpty) ? '不能为空' : null, ), if (!_isEdit) const SizedBox(height: 12), TextFormField( controller: _realNameCtrl, decoration: const InputDecoration(labelText: '姓名'), ), const SizedBox(height: 12), TextFormField( controller: _phoneCtrl, decoration: const InputDecoration(labelText: '手机号'), ), const SizedBox(height: 12), if (!_isEdit) ...[ TextFormField( controller: _passwordCtrl, obscureText: true, decoration: const InputDecoration(labelText: '初始密码'), validator: (v) => (v == null || v.isEmpty) ? '不能为空' : null, ), const SizedBox(height: 12), ], DropdownButtonFormField( value: _role, items: const [ DropdownMenuItem(value: 'admin', child: Text('管理员')), DropdownMenuItem(value: 'operator', child: Text('操作员')), DropdownMenuItem(value: 'readonly', child: Text('只读')), ], onChanged: (v) => setState(() => _role = v ?? 'operator'), decoration: const InputDecoration(labelText: '角色'), ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('取消'), ), ElevatedButton( onPressed: _saving ? null : _save, child: _saving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Text('保存'), ), ], ); } } // ── 重置密码弹窗 ────────────────────────────────────────── class _ResetPasswordDialog extends ConsumerStatefulWidget { final AppUser user; const _ResetPasswordDialog({required this.user}); @override ConsumerState<_ResetPasswordDialog> createState() => _ResetPasswordDialogState(); } class _ResetPasswordDialogState extends ConsumerState<_ResetPasswordDialog> { final _ctrl = TextEditingController(); bool _saving = false; @override void dispose() { _ctrl.dispose(); super.dispose(); } Future _save() async { if (_ctrl.text.isEmpty) return; setState(() => _saving = true); try { await ref.read(userListProvider.notifier).resetPassword(widget.user.id, _ctrl.text); if (mounted) { Navigator.of(context).pop(); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('密码已重置'), backgroundColor: AppTheme.success, )); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('重置失败:$e'), backgroundColor: AppTheme.danger, )); } } finally { if (mounted) setState(() => _saving = false); } } @override Widget build(BuildContext context) { return AlertDialog( title: Text('重置密码 — ${widget.user.username}'), content: SizedBox( width: 360, child: TextField( controller: _ctrl, obscureText: true, decoration: const InputDecoration(labelText: '新密码'), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('取消'), ), ElevatedButton( onPressed: _saving ? null : _save, child: _saving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Text('确认重置'), ), ], ); } } class _RoleBadge extends StatelessWidget { final String role; const _RoleBadge(this.role); @override Widget build(BuildContext context) { final Color bg; final Color fg; switch (role) { case '管理员': bg = const Color(0xFFE3F2FD); fg = AppTheme.primary; break; case '操作员': bg = const Color(0xFFE8F5E9); fg = AppTheme.success; break; default: bg = const Color(0xFFF5F5F5); fg = AppTheme.textSecondary; } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(3)), child: Text(role, style: TextStyle(color: fg, fontSize: 12, fontWeight: FontWeight.w500)), ); } } class _ParamRow extends StatelessWidget { final String label; final String value; final bool? isSwitch; const _ParamRow({required this.label, required this.value, this.isSwitch}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ SizedBox( width: 180, child: Text(label, style: const TextStyle(fontSize: 14, color: AppTheme.textSecondary)), ), if (isSwitch != null) Switch( value: isSwitch!, onChanged: (_) {}, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ) else Text(value, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), const Spacer(), TextButton(onPressed: () {}, child: const Text('修改', style: TextStyle(fontSize: 12))), ], ), ); } }