import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../models/org_node.dart'; import '../../models/store_info.dart'; import '../../services/device_service.dart'; import '../../services/org_service.dart'; /// 设备分组页:门店选择、分组列表、新增/编辑/删除。 class DeviceGroupPage extends ConsumerStatefulWidget { const DeviceGroupPage({super.key}); @override ConsumerState createState() => _DeviceGroupPageState(); } class _DeviceGroupPageState extends ConsumerState { final OrgService _orgService = OrgService(); final DeviceService _deviceService = DeviceService(); final _scrollController = ScrollController(); List _stores = []; String? _selectedStoreId; bool _loadingStores = true; List _items = []; bool _loading = false; bool _hasMore = true; int _page = 1; String? _error; @override void initState() { super.initState(); _scrollController.addListener(_onScroll); _loadStores(); } @override void dispose() { _scrollController.removeListener(_onScroll); _scrollController.dispose(); super.dispose(); } void _onScroll() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { _loadMore(); } } Future _loadStores() async { try { final stores = await _orgService.getMyStores(); if (!mounted) return; setState(() { _stores = stores; _loadingStores = false; if (stores.isNotEmpty) { _selectedStoreId = stores.first.orgId; } }); if (_selectedStoreId != null) { _loadData(); } } catch (e) { if (!mounted) return; setState(() => _loadingStores = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('门店列表加载失败:$e')), ); } } Future _loadData() async { if (_selectedStoreId == null) return; setState(() { _loading = true; _error = null; _page = 1; _hasMore = true; }); try { final items = await _deviceService.getDeviceGroupList( page: 1, limit: 20, storeId: _selectedStoreId!, ); if (!mounted) return; setState(() { _items = items; _hasMore = items.length >= 20; _loading = false; }); } catch (e) { if (!mounted) return; setState(() { _error = '$e'; _loading = false; }); } } Future _loadMore() async { if (_loading || !_hasMore || _selectedStoreId == null) return; setState(() => _loading = true); try { final items = await _deviceService.getDeviceGroupList( page: _page + 1, limit: 20, storeId: _selectedStoreId!, ); if (!mounted) return; setState(() { _page++; _items.addAll(items); _hasMore = items.length >= 20; _loading = false; }); } catch (e) { if (!mounted) return; setState(() => _loading = false); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('加载更多失败:$e')), ); } } Future _deleteGroup(OrgNode group) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('删除分组'), content: Text('确定要删除分组「${group.name}」吗?'), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.pop(ctx, true), style: TextButton.styleFrom(foregroundColor: Colors.red), child: const Text('删除'), ), ], ), ); if (confirmed != true) return; try { await _deviceService.deleteDeviceGroups([group.id]); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('分组已删除')), ); _loadData(); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('删除失败:$e')), ); } } void _showAddEditSheet({OrgNode? group}) { if (_selectedStoreId == null) return; showModalBottomSheet( useSafeArea: true, context: context, isScrollControlled: true, builder: (ctx) => _GroupFormSheet( deviceService: _deviceService, storeId: _selectedStoreId!, group: group, onSaved: () { Navigator.pop(ctx); _loadData(); }, ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, title: const Text('设备分组'), ), body: Column( children: [ _buildStoreSelector(), const Divider(height: 1), Expanded(child: _buildBody()), ], ), floatingActionButton: _selectedStoreId == null ? null : FloatingActionButton( onPressed: () => _showAddEditSheet(), child: const Icon(Icons.add), ), ); } Widget _buildStoreSelector() { if (_loadingStores) { return const Padding( padding: EdgeInsets.all(16), child: Center(child: CircularProgressIndicator()), ); } if (_stores.isEmpty) { return const Padding( padding: EdgeInsets.all(16), child: Text('暂无可选门店'), ); } return Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), child: DropdownButtonFormField( value: _selectedStoreId, decoration: const InputDecoration( labelText: '选择门店', border: OutlineInputBorder(), prefixIcon: Icon(Icons.store), ), items: _stores .map((s) => DropdownMenuItem( value: s.orgId, child: Text(s.name), )) .toList(), onChanged: (v) { setState(() => _selectedStoreId = v); _loadData(); }, ), ); } Widget _buildBody() { if (_selectedStoreId == null) { return const Center(child: Text('请先选择门店')); } if (_loading && _items.isEmpty) { return const Center(child: CircularProgressIndicator()); } if (_error != null && _items.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('加载失败:$_error'), const SizedBox(height: 8), FilledButton.tonal( onPressed: _loadData, child: const Text('重试'), ), ], ), ); } if (_items.isEmpty) { return RefreshIndicator( onRefresh: _loadData, child: ListView( physics: const AlwaysScrollableScrollPhysics(), children: const [ SizedBox(height: 200), Center(child: Text('暂无分组数据')), ], ), ); } return RefreshIndicator( onRefresh: _loadData, child: ListView.builder( controller: _scrollController, physics: const AlwaysScrollableScrollPhysics(), itemCount: _items.length + 1, itemBuilder: (context, index) { if (index == _items.length) { return _loading ? const Padding( padding: EdgeInsets.all(16), child: Center(child: CircularProgressIndicator()), ) : const SizedBox.shrink(); } final group = _items[index]; return _GroupListTile( group: group, onEdit: () => _showAddEditSheet(group: group), onDelete: () => _deleteGroup(group), ); }, ), ); } } /// 分组列表项。 class _GroupListTile extends StatelessWidget { const _GroupListTile({ required this.group, required this.onEdit, required this.onDelete, }); final OrgNode group; final VoidCallback onEdit; final VoidCallback onDelete; @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), child: ListTile( leading: const CircleAvatar( child: Icon(Icons.folder_outlined), ), title: Text(group.name), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), if (group.address.isNotEmpty) _line(Icons.location_on, group.address), if (group.contactPhone.isNotEmpty) _line(Icons.phone, group.contactPhone), _line(Icons.tag, 'ID: ${group.id}'), ], ), trailing: PopupMenuButton( onSelected: (value) { if (value == 'edit') { onEdit(); } else if (value == 'delete') { onDelete(); } }, itemBuilder: (ctx) => [ const PopupMenuItem(value: 'edit', child: Text('编辑')), const PopupMenuItem(value: 'delete', child: Text('删除')), ], ), ), ); } Widget _line(IconData icon, String text) { return Padding( padding: const EdgeInsets.symmetric(vertical: 1), child: Row( children: [ Icon(icon, size: 14, color: Colors.grey), const SizedBox(width: 4), Expanded(child: Text(text, style: const TextStyle(fontSize: 13))), ], ), ); } } /// 新增/编辑分组表单。 class _GroupFormSheet extends StatefulWidget { const _GroupFormSheet({ required this.deviceService, required this.storeId, this.group, required this.onSaved, }); final DeviceService deviceService; final String storeId; final OrgNode? group; final VoidCallback onSaved; @override State<_GroupFormSheet> createState() => _GroupFormSheetState(); } class _GroupFormSheetState extends State<_GroupFormSheet> { final _formKey = GlobalKey(); final _nameController = TextEditingController(); bool _saving = false; @override void initState() { super.initState(); if (widget.group != null) { _nameController.text = widget.group!.name; } } @override void dispose() { _nameController.dispose(); super.dispose(); } Future _save() async { if (!_formKey.currentState!.validate()) return; setState(() => _saving = true); try { final data = { 'name': _nameController.text.trim(), 'storeId': widget.storeId, if (widget.group != null) 'id': widget.group!.id, }; if (widget.group != null) { await widget.deviceService.updateDeviceGroup(data); } else { await widget.deviceService.saveDeviceGroup(data); } if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(widget.group != null ? '分组已更新' : '分组已创建'), ), ); widget.onSaved(); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('保存失败:$e')), ); } finally { if (mounted) setState(() => _saving = false); } } @override Widget build(BuildContext context) { final isEdit = widget.group != null; return SingleChildScrollView( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: Container( padding: const EdgeInsets.all(20), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( isEdit ? '编辑分组' : '新增分组', style: Theme.of(context) .textTheme .titleLarge ?.copyWith(fontWeight: FontWeight.bold), ), const SizedBox(height: 20), TextFormField( controller: _nameController, decoration: const InputDecoration( labelText: '分组名称', border: OutlineInputBorder(), prefixIcon: Icon(Icons.folder), ), validator: (v) => v == null || v.trim().isEmpty ? '请输入分组名称' : null, ), const SizedBox(height: 24), SizedBox( width: double.infinity, child: FilledButton( onPressed: _saving ? null : _save, child: _saving ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : Text(isEdit ? '保存' : '创建'), ), ), ], ), ), ), ); } }