import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../models/sys_role.dart'; import '../../../services/api_client.dart'; import '../../../services/sys_service.dart'; /// 角色管理页。 class RolePage extends ConsumerStatefulWidget { const RolePage({super.key}); @override ConsumerState createState() => _RolePageState(); } class _RolePageState extends ConsumerState { final SystemService _service = SystemService(); final List _items = []; bool _loading = false; int _page = 1; bool _hasMore = true; String? _error; final ScrollController _scrollController = ScrollController(); final int _limit = 20; @override void initState() { super.initState(); _scrollController.addListener(_onScroll); _loadData(); } @override void dispose() { _scrollController.removeListener(_onScroll); _scrollController.dispose(); super.dispose(); } void _onScroll() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { _loadMore(); } } Future _loadData({bool reset = false}) async { if (_loading) return; if (reset) { _page = 1; _hasMore = true; _items.clear(); } setState(() { _loading = true; _error = null; }); try { final list = await _service.getRoleList(page: _page, limit: _limit); _items.addAll(list); _hasMore = list.length >= _limit; } on ApiException catch (e) { _error = e.message; } catch (e) { _error = e.toString(); } finally { if (mounted) setState(() => _loading = false); } } Future _loadMore() async { if (_loading || !_hasMore) return; _page++; await _loadData(); } Future _refresh() async { await _loadData(reset: true); } Future _showForm({SysRole? role}) async { final result = await showModalBottomSheet>( context: context, isScrollControlled: true, builder: (context) => _RoleForm(role: role), ); if (result == null) return; try { await _service.saveRole(result); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('保存成功')), ); _refresh(); } } on ApiException catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.message)), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('保存失败:$e')), ); } } } Future _deleteRole(SysRole role) async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('确认删除'), content: Text('确定要删除角色「${role.roleName}」吗?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('取消'), ), FilledButton( onPressed: () => Navigator.pop(context, true), child: const Text('删除'), ), ], ), ); if (confirmed != true) return; try { await _service.deleteRole([int.tryParse(role.roleId) ?? 0]); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('删除成功')), ); _refresh(); } } on ApiException catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.message)), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('删除失败:$e')), ); } } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(automaticallyImplyLeading: false, title: const Text('角色管理')), body: _buildBody(), floatingActionButton: FloatingActionButton( onPressed: () => _showForm(), child: const Icon(Icons.add), ), ); } Widget _buildBody() { if (_error != null && _items.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(_error!), const SizedBox(height: 8), FilledButton.tonal( onPressed: _refresh, child: const Text('重试'), ), ], ), ); } if (_items.isEmpty && !_loading) { return const Center(child: Text('暂无数据')); } return RefreshIndicator( onRefresh: _refresh, child: ListView.builder( controller: _scrollController, physics: const AlwaysScrollableScrollPhysics(), itemCount: _items.length + 1, itemBuilder: (context, index) { if (index == _items.length) { if (_loading) { return const Padding( padding: EdgeInsets.all(16), child: Center(child: CircularProgressIndicator()), ); } if (!_hasMore) { return const Padding( padding: EdgeInsets.all(16), child: Center(child: Text('没有更多数据')), ); } return const SizedBox.shrink(); } final role = _items[index]; return Card( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), child: ListTile( title: Text(role.roleName), subtitle: Text(role.remark.isEmpty ? '无备注' : role.remark), trailing: PopupMenuButton( onSelected: (value) { if (value == 'edit') { _showForm(role: role); } else if (value == 'delete') { _deleteRole(role); } }, itemBuilder: (context) => [ const PopupMenuItem(value: 'edit', child: Text('编辑')), const PopupMenuItem(value: 'delete', child: Text('删除')), ], ), ), ); }, ), ); } } /// 角色表单。 class _RoleForm extends StatefulWidget { const _RoleForm({this.role}); final SysRole? role; @override State<_RoleForm> createState() => _RoleFormState(); } class _RoleFormState extends State<_RoleForm> { final _formKey = GlobalKey(); late TextEditingController _roleNameCtrl; late TextEditingController _remarkCtrl; @override void initState() { super.initState(); _roleNameCtrl = TextEditingController(text: widget.role?.roleName ?? ''); _remarkCtrl = TextEditingController(text: widget.role?.remark ?? ''); } @override void dispose() { _roleNameCtrl.dispose(); _remarkCtrl.dispose(); super.dispose(); } void _submit() { if (!_formKey.currentState!.validate()) return; final data = { 'roleName': _roleNameCtrl.text.trim(), 'remark': _remarkCtrl.text.trim(), }; if (widget.role != null) { data['roleId'] = widget.role!.roleId; } Navigator.pop(context, data); } @override Widget build(BuildContext context) { final isEdit = widget.role != null; return Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( isEdit ? '编辑角色' : '新增角色', style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 16), TextFormField( controller: _roleNameCtrl, decoration: const InputDecoration( labelText: '角色名称', border: OutlineInputBorder(), ), validator: (v) => (v == null || v.trim().isEmpty) ? '请输入角色名称' : null, ), const SizedBox(height: 12), TextFormField( controller: _remarkCtrl, maxLines: 3, decoration: const InputDecoration( labelText: '备注', border: OutlineInputBorder(), ), ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), const SizedBox(width: 8), FilledButton( onPressed: _submit, child: const Text('保存'), ), ], ), ], ), ), ), ); } }