import '../../core/utils/dialog_util.dart'; import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/models/page_result.dart'; import '../../core/responsive/responsive.dart'; import '../../core/theme/app_theme.dart'; import '../../models/partner.dart'; import '../../providers/partner_provider.dart'; import '../../widgets/data_table_card.dart'; import '../../widgets/mobile_list_card.dart'; import '../../widgets/page_scaffold.dart'; import '../../core/utils/export_util.dart'; class PartnersScreen extends ConsumerStatefulWidget { const PartnersScreen({super.key}); @override ConsumerState createState() => _PartnersScreenState(); } class _PartnersScreenState extends ConsumerState { final _supplierSearchCtrl = TextEditingController(); final _customerSearchCtrl = TextEditingController(); Timer? _supplierDebounce; Timer? _customerDebounce; @override void dispose() { _supplierSearchCtrl.dispose(); _customerSearchCtrl.dispose(); _supplierDebounce?.cancel(); _customerDebounce?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return PageScaffold( title: '往来单位', tabs: const [ Tab(text: '供应商'), Tab(text: '客户'), ], tabViews: [ _buildSupplierTab(), _buildCustomerTab(), ], ); } Widget _buildSupplierTab() { final asyncPartners = ref.watch(supplierListProvider); return asyncPartners.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.cloud_off, size: 40, color: AppTheme.textSecondary), const SizedBox(height: 12), const Text('暂无数据,网络不可用', style: const TextStyle(color: AppTheme.textSecondary)), const SizedBox(height: 12), ElevatedButton( onPressed: () => ref.read(supplierListProvider.notifier).reload(), child: const Text('重试'), ), ], ), ), data: (result) => _buildPartnerList( result, isSupplier: true, searchCtrl: _supplierSearchCtrl, onSearchChanged: (v) { _supplierDebounce?.cancel(); _supplierDebounce = Timer(const Duration(milliseconds: 300), () { ref.read(supplierListProvider.notifier).setKeyword(v); }); }, onPageChanged: (p) => ref.read(supplierListProvider.notifier).setPage(p), onPageSizeChanged: (s) => ref.read(supplierListProvider.notifier).setPageSize(s), onAdd: () => _showPartnerDialog(context, isSupplier: true), onEdit: (p) => _showPartnerDialog(context, isSupplier: true, partner: p), onDelete: (p) => _confirmDelete(context, p, isSupplier: true), ), ); } Widget _buildCustomerTab() { final asyncPartners = ref.watch(customerListProvider); return asyncPartners.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.cloud_off, size: 40, color: AppTheme.textSecondary), const SizedBox(height: 12), const Text('暂无数据,网络不可用', style: const TextStyle(color: AppTheme.textSecondary)), const SizedBox(height: 12), ElevatedButton( onPressed: () => ref.read(customerListProvider.notifier).reload(), child: const Text('重试'), ), ], ), ), data: (result) => _buildPartnerList( result, isSupplier: false, searchCtrl: _customerSearchCtrl, onSearchChanged: (v) { _customerDebounce?.cancel(); _customerDebounce = Timer(const Duration(milliseconds: 300), () { ref.read(customerListProvider.notifier).setKeyword(v); }); }, onPageChanged: (p) => ref.read(customerListProvider.notifier).setPage(p), onPageSizeChanged: (s) => ref.read(customerListProvider.notifier).setPageSize(s), onAdd: () => _showPartnerDialog(context, isSupplier: false), onEdit: (p) => _showPartnerDialog(context, isSupplier: false, partner: p), onDelete: (p) => _confirmDelete(context, p, isSupplier: false), ), ); } Widget _buildPartnerList( PageResult result, { required bool isSupplier, required TextEditingController searchCtrl, required void Function(String) onSearchChanged, required void Function(int) onPageChanged, required void Function(int) onPageSizeChanged, required VoidCallback onAdd, required void Function(Partner) onEdit, required void Function(Partner) onDelete, }) { final partners = result.data; Widget partnerCard(Partner p) { return MobileListCard( title: Text(p.name), subtitle: p.code != null ? Text(p.code!) : null, trailing: Text( p.status == 'enabled' ? '正常' : '禁用', style: TextStyle( fontSize: 12, color: p.status == 'enabled' ? AppTheme.success : AppTheme.textSecondary, ), ), fields: [ if (p.contact?.isNotEmpty == true) MobileCardField('联系人', p.contact), if (p.phone?.isNotEmpty == true) MobileCardField('电话', p.phone), if (p.address?.isNotEmpty == true) MobileCardField('地址', p.address), ], actions: [ TextButton( key: Key('btn_edit_${p.id}'), onPressed: () => onEdit(p), child: const Text('编辑', style: TextStyle(fontSize: 13)), ), TextButton( key: Key('btn_delete_${p.id}'), onPressed: () => onDelete(p), child: const Text('删除', style: TextStyle(fontSize: 13, color: AppTheme.danger)), ), ], ); } return DataTableCard( totalCount: result.total, page: result.page, pageSize: result.pageSize, onPageChanged: onPageChanged, onPageSizeChanged: onPageSizeChanged, mobileCards: partners.map(partnerCard).toList(), toolbar: Row( children: [ ElevatedButton.icon( onPressed: onAdd, icon: const Icon(Icons.add, size: 16), label: Text(isSupplier ? '新建' : '新建'), ), const SizedBox(width: 8), OutlinedButton.icon( onPressed: () => exportExcel( filename: isSupplier ? '供应商列表' : '客户列表', headers: ['编码', '名称', '联系人', '电话', '地址', '备注'], rows: partners.map((p) => [ p.code ?? '', p.name, p.contact ?? '', p.phone ?? '', p.address ?? '', p.remark ?? '', ]).toList(), ), icon: const Icon(Icons.download, size: 16), label: const Text('导出'), ), const Spacer(), SizedBox( width: 200, child: TextField( controller: searchCtrl, decoration: InputDecoration( hintText: isSupplier ? '搜索供应商名称/编码' : '搜索客户名称/编码', prefixIcon: const Icon(Icons.search, size: 16), hintStyle: const TextStyle(fontSize: 13), ), onChanged: onSearchChanged, ), ), ], ), columns: [ const DataColumn(label: Text('编码')), const DataColumn(label: Text('名称')), const DataColumn(label: Text('联系人')), const DataColumn(label: Text('联系电话')), const DataColumn(label: Text('地址')), const DataColumn(label: Text('状态')), const DataColumn(label: Text('操作')), ], rows: partners.isEmpty ? [ DataRow(cells: [ const DataCell(SizedBox()), DataCell(Text(isSupplier ? '暂无供应商' : '暂无客户', style: const TextStyle(color: AppTheme.textSecondary))), const DataCell(SizedBox()), const DataCell(SizedBox()), const DataCell(SizedBox()), const DataCell(SizedBox()), const DataCell(SizedBox()), ]) ] : partners .map((p) => DataRow( cells: [ DataCell(Text(p.code ?? '-', style: const TextStyle( fontFamily: 'monospace', fontSize: 12, color: AppTheme.textSecondary))), DataCell(SizedBox( width: 180, child: Text(p.name, overflow: TextOverflow.ellipsis, style: const TextStyle( fontWeight: FontWeight.w500)), )), DataCell(Text(p.contact ?? '-')), DataCell(Text(p.phone ?? '-')), DataCell(SizedBox( width: 160, child: Text(p.address ?? '-', overflow: TextOverflow.ellipsis), )), DataCell(Text( p.status == 'enabled' ? '正常' : '禁用', style: TextStyle( fontSize: 13, color: p.status == 'enabled' ? AppTheme.success : AppTheme.textSecondary, ), )), DataCell(Row( mainAxisSize: MainAxisSize.min, children: [ TextButton( key: Key('btn_edit_${p.id}'), onPressed: () => onEdit(p), child: const Text('编辑', style: TextStyle(fontSize: 12)), ), TextButton( key: Key('btn_delete_${p.id}'), onPressed: () => onDelete(p), child: const Text('删除', style: TextStyle( fontSize: 12, color: AppTheme.danger)), ), ], )), ], )) .toList(), ); } void _showPartnerDialog(BuildContext context, {required bool isSupplier, Partner? partner}) { showAppDialog( context: context, builder: (ctx) => _PartnerFormDialog( isSupplier: isSupplier, partner: partner, onSaved: () { if (isSupplier) { ref.read(supplierListProvider.notifier).reload(); } else { ref.read(customerListProvider.notifier).reload(); } }, ), ); } Future _confirmDelete(BuildContext context, Partner partner, {required bool isSupplier}) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('确认删除'), content: Text('确认删除「${partner.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 { if (isSupplier) { await ref .read(supplierListProvider.notifier) .deletePartner(partner.id); } else { await ref .read(customerListProvider.notifier) .deletePartner(partner.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)); } } } } } class _PartnerFormDialog extends ConsumerStatefulWidget { final bool isSupplier; final Partner? partner; final VoidCallback onSaved; const _PartnerFormDialog({ required this.isSupplier, this.partner, required this.onSaved, }); @override ConsumerState<_PartnerFormDialog> createState() => _PartnerFormDialogState(); } class _PartnerFormDialogState extends ConsumerState<_PartnerFormDialog> { final _formKey = GlobalKey(); late final TextEditingController _nameCtrl; late final TextEditingController _codeCtrl; late final TextEditingController _contactCtrl; late final TextEditingController _phoneCtrl; late final TextEditingController _addressCtrl; late final TextEditingController _remarkCtrl; bool _saving = false; @override void initState() { super.initState(); final p = widget.partner; _nameCtrl = TextEditingController(text: p?.name ?? ''); _codeCtrl = TextEditingController(text: p?.code ?? ''); _contactCtrl = TextEditingController(text: p?.contact ?? ''); _phoneCtrl = TextEditingController(text: p?.phone ?? ''); _addressCtrl = TextEditingController(text: p?.address ?? ''); _remarkCtrl = TextEditingController(text: p?.remark ?? ''); } @override void dispose() { _nameCtrl.dispose(); _codeCtrl.dispose(); _contactCtrl.dispose(); _phoneCtrl.dispose(); _addressCtrl.dispose(); _remarkCtrl.dispose(); super.dispose(); } Future _save() async { if (!_formKey.currentState!.validate()) return; setState(() => _saving = true); final data = { 'name': _nameCtrl.text.trim(), 'type': widget.isSupplier ? 'supplier' : 'customer', if (_codeCtrl.text.trim().isNotEmpty) 'code': _codeCtrl.text.trim(), if (_contactCtrl.text.trim().isNotEmpty) 'contact': _contactCtrl.text.trim(), if (_phoneCtrl.text.trim().isNotEmpty) 'phone': _phoneCtrl.text.trim(), if (_addressCtrl.text.trim().isNotEmpty) 'address': _addressCtrl.text.trim(), if (_remarkCtrl.text.trim().isNotEmpty) 'remark': _remarkCtrl.text.trim(), }; try { final notifier = widget.isSupplier ? ref.read(supplierListProvider.notifier) : ref.read(customerListProvider.notifier); if (widget.partner != null) { await notifier.updatePartner(widget.partner!.id, data); } else { await notifier.createPartner(data); } if (mounted) { Navigator.of(context).pop(); widget.onSaved(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(widget.partner != 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) { final isEdit = widget.partner != null; final typeName = widget.isSupplier ? '供应商' : '客户'; return Dialog( child: Container( width: context.dialogWidth(520), padding: const EdgeInsets.all(24), child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(isEdit ? '编辑$typeName' : '新建$typeName', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.w600)), const SizedBox(height: 20), TextFormField( controller: _nameCtrl, decoration: InputDecoration(labelText: '$typeName名称'), validator: (v) => (v == null || v.isEmpty) ? '不能为空' : null, ), const SizedBox(height: 12), Row(children: [ if (isEdit) ...[ Expanded( child: TextFormField( controller: _codeCtrl, decoration: const InputDecoration(labelText: '编码'), ), ), const SizedBox(width: 12), ], Expanded( child: TextFormField( controller: _contactCtrl, decoration: const InputDecoration(labelText: '联系人'), ), ), ]), const SizedBox(height: 12), TextFormField( controller: _phoneCtrl, decoration: const InputDecoration(labelText: '联系电话'), ), const SizedBox(height: 12), TextFormField( controller: _addressCtrl, decoration: const InputDecoration(labelText: '地址'), ), const SizedBox(height: 12), TextFormField( controller: _remarkCtrl, decoration: const InputDecoration(labelText: '备注'), maxLines: 2, ), const SizedBox(height: 24), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('取消'), ), const SizedBox(width: 8), ElevatedButton( onPressed: _saving ? null : _save, child: _saving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white)) : const Text('保存'), ), ], ), ], ), ), ), ); } }