76ab19ea47
- 蓝色顶栏(#1565C0)+ 深蓝侧边栏(#0D47A1),含折叠/展开 - 底部状态栏:门店编号、登录用户、登录时间、实时时钟、版本号 - 登录页:居中卡片,支持密码显示/隐藏 - 入库单列表:筛选/搜索/日期选择/分页,三个 Tab(入库单/查询/审核) - 新建入库单:基本信息 + 商品明细动态增删,实时计算合计金额 - 出库单列表:同入库单风格 - 库存查询:4 张统计卡片 + 颜色高亮缺货/预警行,库存预警 Tab - 往来单位、财务、商品、系统设置(用户/仓库/编号规则/系统参数) - 使用 Riverpod 状态管理 + go_router 路由 + Dio HTTP Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
520 lines
16 KiB
Dart
520 lines
16 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../widgets/page_scaffold.dart';
|
|
import '../../widgets/data_table_card.dart';
|
|
import '../../core/theme/app_theme.dart';
|
|
import '../../widgets/form_dialog.dart';
|
|
|
|
class PartnersScreen extends ConsumerStatefulWidget {
|
|
const PartnersScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<PartnersScreen> createState() => _PartnersScreenState();
|
|
}
|
|
|
|
class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
|
int _suppliersPage = 1;
|
|
int _customersPage = 1;
|
|
final _searchCtrl = TextEditingController();
|
|
|
|
final List<Map<String, dynamic>> _suppliers = [
|
|
{
|
|
'code': 'GYS001',
|
|
'name': '贵州茅台酒股份有限公司',
|
|
'type': '白酒厂商',
|
|
'contact': '王建国',
|
|
'phone': '0851-22886688',
|
|
'address': '贵州省仁怀市茅台镇',
|
|
'balance': '-86000.00',
|
|
'status': '合作中',
|
|
'creditDays': 30,
|
|
},
|
|
{
|
|
'code': 'GYS002',
|
|
'name': '四川五粮液股份有限公司',
|
|
'type': '白酒厂商',
|
|
'contact': '李明华',
|
|
'phone': '0831-12345678',
|
|
'address': '四川省宜宾市翠屏区',
|
|
'balance': '-42500.00',
|
|
'status': '合作中',
|
|
'creditDays': 30,
|
|
},
|
|
{
|
|
'code': 'GYS003',
|
|
'name': '江苏洋河酒厂股份有限公司',
|
|
'type': '白酒厂商',
|
|
'contact': '张秀英',
|
|
'phone': '0527-83999999',
|
|
'address': '江苏省宿迁市宿城区',
|
|
'balance': '0.00',
|
|
'status': '合作中',
|
|
'creditDays': 45,
|
|
},
|
|
{
|
|
'code': 'GYS004',
|
|
'name': '剑南春(集团)有限责任公司',
|
|
'type': '白酒厂商',
|
|
'contact': '陈志强',
|
|
'phone': '0838-88888888',
|
|
'address': '四川省德阳市绵竹市',
|
|
'balance': '-28600.00',
|
|
'status': '合作中',
|
|
'creditDays': 30,
|
|
},
|
|
{
|
|
'code': 'GYS005',
|
|
'name': '泸州老窖股份有限公司',
|
|
'type': '白酒厂商',
|
|
'contact': '赵丽红',
|
|
'phone': '0830-12233456',
|
|
'address': '四川省泸州市江阳区',
|
|
'balance': '-15200.00',
|
|
'status': '合作中',
|
|
'creditDays': 30,
|
|
},
|
|
{
|
|
'code': 'GYS006',
|
|
'name': '法国拉菲集团中国总代理',
|
|
'type': '葡萄酒进口商',
|
|
'contact': 'Pierre Liu',
|
|
'phone': '021-61234567',
|
|
'address': '上海市黄浦区外滩18号',
|
|
'balance': '-124800.00',
|
|
'status': '合作中',
|
|
'creditDays': 60,
|
|
},
|
|
{
|
|
'code': 'GYS007',
|
|
'name': '人头马轩尼诗(中国)有限公司',
|
|
'type': '洋酒进口商',
|
|
'contact': '刘经理',
|
|
'phone': '021-54321678',
|
|
'address': '上海市浦东新区',
|
|
'balance': '-30240.00',
|
|
'status': '合作中',
|
|
'creditDays': 60,
|
|
},
|
|
{
|
|
'code': 'GYS008',
|
|
'name': '山西汾酒集团有限责任公司',
|
|
'type': '白酒厂商',
|
|
'contact': '周建明',
|
|
'phone': '0357-33666888',
|
|
'address': '山西省吕梁市汾阳市',
|
|
'balance': '0.00',
|
|
'status': '暂停合作',
|
|
'creditDays': 30,
|
|
},
|
|
];
|
|
|
|
final List<Map<String, dynamic>> _customers = [
|
|
{
|
|
'code': 'KH001',
|
|
'name': '北京国贸大酒店',
|
|
'type': '五星级酒店',
|
|
'contact': '采购部',
|
|
'phone': '010-65051234',
|
|
'address': '北京市朝阳区建国门外大街1号',
|
|
'balance': '45000.00',
|
|
'status': '合作中',
|
|
},
|
|
{
|
|
'code': 'KH002',
|
|
'name': '上海外滩华尔道夫酒店',
|
|
'type': '五星级酒店',
|
|
'contact': '采购经理',
|
|
'phone': '021-63228888',
|
|
'address': '上海市黄浦区中山东一路2号',
|
|
'balance': '0.00',
|
|
'status': '合作中',
|
|
},
|
|
{
|
|
'code': 'KH003',
|
|
'name': '广州白云国际会议中心',
|
|
'type': '会议中心',
|
|
'contact': '餐饮总监',
|
|
'phone': '020-86001234',
|
|
'address': '广州市白云区云城东路1号',
|
|
'balance': '28600.00',
|
|
'status': '合作中',
|
|
},
|
|
{
|
|
'code': 'KH004',
|
|
'name': '深圳湾万象城购物中心',
|
|
'type': '商超零售',
|
|
'contact': '王采购',
|
|
'phone': '0755-82345678',
|
|
'address': '深圳市南山区望海路购物公园',
|
|
'balance': '12000.00',
|
|
'status': '合作中',
|
|
},
|
|
];
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchCtrl.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return PageScaffold(
|
|
title: '往来单位',
|
|
tabs: const [
|
|
Tab(text: '供应商'),
|
|
Tab(text: '客户'),
|
|
],
|
|
tabViews: [
|
|
_buildSupplierList(),
|
|
_buildCustomerList(),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildSupplierList() {
|
|
final q = _searchCtrl.text.toLowerCase();
|
|
final filtered = _suppliers.where((s) {
|
|
if (q.isEmpty) return true;
|
|
return (s['name'] as String).toLowerCase().contains(q) ||
|
|
(s['code'] as String).toLowerCase().contains(q);
|
|
}).toList();
|
|
|
|
return DataTableCard(
|
|
totalCount: filtered.length,
|
|
page: _suppliersPage,
|
|
onPageChanged: (p) => setState(() => _suppliersPage = p),
|
|
toolbar: Row(
|
|
children: [
|
|
ElevatedButton.icon(
|
|
onPressed: () => _showAddPartnerDialog(context, isSupplier: true),
|
|
icon: const Icon(Icons.add, size: 16),
|
|
label: const Text('新增供应商'),
|
|
),
|
|
const SizedBox(width: 8),
|
|
OutlinedButton.icon(
|
|
onPressed: () {},
|
|
icon: const Icon(Icons.file_download_outlined, size: 16),
|
|
label: const Text('导出'),
|
|
),
|
|
const Spacer(),
|
|
SizedBox(
|
|
width: 200,
|
|
child: TextField(
|
|
controller: _searchCtrl,
|
|
decoration: const InputDecoration(
|
|
hintText: '搜索供应商名称/编码',
|
|
prefixIcon: Icon(Icons.search, size: 16),
|
|
hintStyle: TextStyle(fontSize: 13),
|
|
),
|
|
onChanged: (_) => setState(() {}),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
columns: const [
|
|
DataColumn(label: Text('供应商编码')),
|
|
DataColumn(label: Text('供应商名称')),
|
|
DataColumn(label: Text('类型')),
|
|
DataColumn(label: Text('联系人')),
|
|
DataColumn(label: Text('联系电话')),
|
|
DataColumn(label: Text('账期(天)'), numeric: true),
|
|
DataColumn(label: Text('应付余额'), numeric: true),
|
|
DataColumn(label: Text('状态')),
|
|
DataColumn(label: Text('操作')),
|
|
],
|
|
rows: filtered
|
|
.map((s) => DataRow(cells: [
|
|
DataCell(Text(s['code'] as String,
|
|
style: const TextStyle(
|
|
fontFamily: 'monospace',
|
|
fontSize: 12,
|
|
color: AppTheme.textSecondary))),
|
|
DataCell(SizedBox(
|
|
width: 180,
|
|
child: Text(s['name'] as String,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(fontWeight: FontWeight.w500)),
|
|
)),
|
|
DataCell(Text(s['type'] as String)),
|
|
DataCell(Text(s['contact'] as String)),
|
|
DataCell(Text(s['phone'] as String)),
|
|
DataCell(Text('${s['creditDays']}')),
|
|
DataCell(Text(
|
|
'¥${s['balance']}',
|
|
style: TextStyle(
|
|
color: (s['balance'] as String).startsWith('-')
|
|
? AppTheme.danger
|
|
: AppTheme.textPrimary,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
)),
|
|
DataCell(_StatusChip(s['status'] as String)),
|
|
DataCell(Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextButton(
|
|
onPressed: () {},
|
|
child: const Text('查看',
|
|
style: TextStyle(fontSize: 12))),
|
|
TextButton(
|
|
onPressed: () {},
|
|
child: const Text('编辑',
|
|
style: TextStyle(fontSize: 12))),
|
|
TextButton(
|
|
onPressed: () {},
|
|
child: const Text('对账',
|
|
style: TextStyle(fontSize: 12))),
|
|
],
|
|
)),
|
|
]))
|
|
.toList(),
|
|
);
|
|
}
|
|
|
|
Widget _buildCustomerList() {
|
|
return DataTableCard(
|
|
totalCount: _customers.length,
|
|
page: _customersPage,
|
|
onPageChanged: (p) => setState(() => _customersPage = p),
|
|
toolbar: Row(
|
|
children: [
|
|
ElevatedButton.icon(
|
|
onPressed: () =>
|
|
_showAddPartnerDialog(context, isSupplier: false),
|
|
icon: const Icon(Icons.add, size: 16),
|
|
label: const Text('新增客户'),
|
|
),
|
|
const SizedBox(width: 8),
|
|
OutlinedButton.icon(
|
|
onPressed: () {},
|
|
icon: const Icon(Icons.file_download_outlined, size: 16),
|
|
label: const Text('导出'),
|
|
),
|
|
const Spacer(),
|
|
SizedBox(
|
|
width: 200,
|
|
child: TextField(
|
|
decoration: const InputDecoration(
|
|
hintText: '搜索客户名称/编码',
|
|
prefixIcon: Icon(Icons.search, size: 16),
|
|
hintStyle: TextStyle(fontSize: 13),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
columns: const [
|
|
DataColumn(label: Text('客户编码')),
|
|
DataColumn(label: Text('客户名称')),
|
|
DataColumn(label: Text('类型')),
|
|
DataColumn(label: Text('联系人')),
|
|
DataColumn(label: Text('联系电话')),
|
|
DataColumn(label: Text('应收余额'), numeric: true),
|
|
DataColumn(label: Text('状态')),
|
|
DataColumn(label: Text('操作')),
|
|
],
|
|
rows: _customers
|
|
.map((c) => DataRow(cells: [
|
|
DataCell(Text(c['code'] as String,
|
|
style: const TextStyle(
|
|
fontFamily: 'monospace',
|
|
fontSize: 12,
|
|
color: AppTheme.textSecondary))),
|
|
DataCell(SizedBox(
|
|
width: 180,
|
|
child: Text(c['name'] as String,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: const TextStyle(fontWeight: FontWeight.w500)),
|
|
)),
|
|
DataCell(Text(c['type'] as String)),
|
|
DataCell(Text(c['contact'] as String)),
|
|
DataCell(Text(c['phone'] as String)),
|
|
DataCell(Text(
|
|
'¥${c['balance']}',
|
|
style: const TextStyle(fontWeight: FontWeight.w500),
|
|
)),
|
|
DataCell(_StatusChip(c['status'] as String)),
|
|
DataCell(Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextButton(
|
|
onPressed: () {},
|
|
child: const Text('查看',
|
|
style: TextStyle(fontSize: 12))),
|
|
TextButton(
|
|
onPressed: () {},
|
|
child: const Text('编辑',
|
|
style: TextStyle(fontSize: 12))),
|
|
],
|
|
)),
|
|
]))
|
|
.toList(),
|
|
);
|
|
}
|
|
|
|
void _showAddPartnerDialog(BuildContext context, {required bool isSupplier}) {
|
|
final nameCtrl = TextEditingController();
|
|
final contactCtrl = TextEditingController();
|
|
final phoneCtrl = TextEditingController();
|
|
final addressCtrl = TextEditingController();
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => FormDialog(
|
|
title: isSupplier ? '新增供应商' : '新增客户',
|
|
width: 520,
|
|
onConfirm: () {
|
|
Navigator.of(ctx).pop();
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(isSupplier ? '供应商添加成功' : '客户添加成功'),
|
|
backgroundColor: AppTheme.success,
|
|
),
|
|
);
|
|
},
|
|
content: Column(
|
|
children: [
|
|
_DialogField(
|
|
label: isSupplier ? '供应商名称' : '客户名称',
|
|
required: true,
|
|
child: TextFormField(
|
|
controller: nameCtrl,
|
|
decoration: InputDecoration(
|
|
hintText: isSupplier ? '请输入供应商全称' : '请输入客户名称',
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _DialogField(
|
|
label: '联系人',
|
|
child: TextFormField(
|
|
controller: contactCtrl,
|
|
decoration: const InputDecoration(hintText: '联系人姓名'),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _DialogField(
|
|
label: '联系电话',
|
|
child: TextFormField(
|
|
controller: phoneCtrl,
|
|
decoration: const InputDecoration(hintText: '手机/座机'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
_DialogField(
|
|
label: '地址',
|
|
child: TextFormField(
|
|
controller: addressCtrl,
|
|
decoration: const InputDecoration(hintText: '详细地址'),
|
|
),
|
|
),
|
|
if (isSupplier) ...[
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _DialogField(
|
|
label: '账期(天)',
|
|
child: TextFormField(
|
|
initialValue: '30',
|
|
keyboardType: TextInputType.number,
|
|
decoration: const InputDecoration(hintText: '结款账期'),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _DialogField(
|
|
label: '供应商类型',
|
|
child: DropdownButtonFormField<String>(
|
|
value: '白酒厂商',
|
|
items: ['白酒厂商', '葡萄酒进口商', '洋酒进口商', '啤酒厂商', '其他']
|
|
.map((s) => DropdownMenuItem(
|
|
value: s,
|
|
child: Text(s,
|
|
style: const TextStyle(fontSize: 13))))
|
|
.toList(),
|
|
onChanged: (_) {},
|
|
decoration: const InputDecoration(),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DialogField extends StatelessWidget {
|
|
final String label;
|
|
final Widget child;
|
|
final bool required;
|
|
|
|
const _DialogField({
|
|
required this.label,
|
|
required this.child,
|
|
this.required = false,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
if (required)
|
|
const Text('* ',
|
|
style: TextStyle(color: AppTheme.danger, fontSize: 13)),
|
|
Text(label,
|
|
style: const TextStyle(
|
|
fontSize: 13, color: AppTheme.textSecondary)),
|
|
],
|
|
),
|
|
const SizedBox(height: 6),
|
|
child,
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _StatusChip extends StatelessWidget {
|
|
final String status;
|
|
const _StatusChip(this.status);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final bool isActive = status == '合作中';
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
decoration: BoxDecoration(
|
|
color: isActive
|
|
? const Color(0xFFE8F5E9)
|
|
: const Color(0xFFF5F5F5),
|
|
borderRadius: BorderRadius.circular(3),
|
|
),
|
|
child: Text(
|
|
status,
|
|
style: TextStyle(
|
|
color: isActive ? AppTheme.success : AppTheme.textSecondary,
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|