feat: 商品详情页、XLS导入修复、分页选择器、导出功能
后端: - 新增 product_images 表,支持每商品最多5张图(服务端压缩至1200px/JPEG85%) - products 表新增 public_id(UUID)、description 字段 - 新增商品详情接口、二维码接口、公开商品接口(无鉴权) - 修复 XLS 导入:OLE2 magic bytes 检测 + 临时文件解析,兼容 extrame/xls - 修复商品/名称/系列/规格三张表导入数据为0(LastCol()=0 bug) - 所有导入接口返回 total/imported/skipped 统计 - config 新增 StorageConfig,支持 STORAGE_* 环境变量覆盖 - 种子数据修复:products 补 public_id、新增 product_images TRUNCATE、schema.sql 表名修正 前端: - 商品详情页:图片上传/删除、描述内联编辑、二维码弹窗、公开链接复制 - 公开商品页:无鉴权路由 /product/:public_id,Flutter Web SPA - 商品详情列表(批次追踪)商品名超链接跳转详情页 - 导航「商品管理」改名「商品详情」 - 所有列表表格新增每页条数选择(10/20/50/100) - 表格列头内嵌筛选(FilterableColumnHeader) - 导出 Excel 功能(入库/出库/库存/财务/批次/往来单位) - 网络恢复自动刷新 + 离线缓存展示 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import '../../models/partner.dart';
|
||||
import '../../providers/partner_provider.dart';
|
||||
import '../../widgets/data_table_card.dart';
|
||||
import '../../widgets/page_scaffold.dart';
|
||||
import '../../core/utils/export_util.dart';
|
||||
|
||||
class PartnersScreen extends ConsumerStatefulWidget {
|
||||
const PartnersScreen({super.key});
|
||||
@@ -78,6 +79,8 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
},
|
||||
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),
|
||||
@@ -119,6 +122,8 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
},
|
||||
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),
|
||||
@@ -133,6 +138,7 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
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,
|
||||
@@ -141,7 +147,9 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
return DataTableCard(
|
||||
totalCount: result.total,
|
||||
page: result.page,
|
||||
pageSize: result.pageSize,
|
||||
onPageChanged: onPageChanged,
|
||||
onPageSizeChanged: onPageSizeChanged,
|
||||
toolbar: Row(
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
@@ -149,6 +157,19 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
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,
|
||||
@@ -170,6 +191,7 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
const DataColumn(label: Text('联系人')),
|
||||
const DataColumn(label: Text('联系电话')),
|
||||
const DataColumn(label: Text('地址')),
|
||||
const DataColumn(label: Text('状态')),
|
||||
const DataColumn(label: Text('操作')),
|
||||
],
|
||||
rows: partners.isEmpty
|
||||
@@ -183,6 +205,7 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
const DataCell(SizedBox()),
|
||||
const DataCell(SizedBox()),
|
||||
const DataCell(SizedBox()),
|
||||
const DataCell(SizedBox()),
|
||||
])
|
||||
]
|
||||
: partners
|
||||
@@ -207,6 +230,15 @@ class _PartnersScreenState extends ConsumerState<PartnersScreen> {
|
||||
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: [
|
||||
|
||||
Reference in New Issue
Block a user