- shops 表新增 logo_url 字段,AutoMigrate 自动建列 - 后端新增 POST /api/v1/shop/logo 接口(管理员),图片裁剪为 256×256 JPEG 存储 - UpdateInfo 支持传入 logo_url - 侧边栏「岩美」替换为门店真实名称 + 自定义 logo - 无 logo 时显示店名首字文字头像(深蓝底白字) - 设置页「酒行信息」展示 logo 预览,编辑弹窗新增「更换 Logo」上传按钮 - 修复库存导入计数逻辑:total/imported/updated/errors 四项分别统计 - 库存导入去重改为双路索引(编号 + 名称|系列|规格),避免 key 不一致误判新增 - settings 页导入结果统一显示「重复 X 条」,兼容 skipped 和 updated 两个字段 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -171,14 +171,17 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
|
||||
stateNotifier.value = _ImportState(
|
||||
stage: _ImportStage.done,
|
||||
uploadPercent: 100,
|
||||
total: data['total'] ?? 0,
|
||||
imported: data['imported'] ?? 0,
|
||||
updated: data['updated'] ?? 0,
|
||||
skipped: data['skipped'] ?? 0,
|
||||
errors: (data['errors'] as List?)?.cast<String>() ?? [],
|
||||
);
|
||||
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
closeDialog();
|
||||
final hasErrors = (data['errors'] as List?)?.isNotEmpty ?? false;
|
||||
if (!hasErrors) {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
closeDialog();
|
||||
}
|
||||
if (context.mounted) ref.read(inventoryListProvider.notifier).reload();
|
||||
} on DioException catch (e) {
|
||||
processingTimer?.cancel();
|
||||
@@ -926,9 +929,9 @@ class _ImportState {
|
||||
final _ImportStage stage;
|
||||
final int uploadPercent;
|
||||
final int processingSeconds;
|
||||
final int total;
|
||||
final int imported;
|
||||
final int updated;
|
||||
final int skipped;
|
||||
final List<String> errors;
|
||||
final String? errorMsg;
|
||||
|
||||
@@ -936,9 +939,9 @@ class _ImportState {
|
||||
required this.stage,
|
||||
required this.uploadPercent,
|
||||
this.processingSeconds = 0,
|
||||
this.total = 0,
|
||||
this.imported = 0,
|
||||
this.updated = 0,
|
||||
this.skipped = 0,
|
||||
this.errors = const [],
|
||||
this.errorMsg,
|
||||
});
|
||||
@@ -1024,25 +1027,39 @@ class _ImportProgressDialog extends StatelessWidget {
|
||||
);
|
||||
|
||||
case _ImportStage.done:
|
||||
final allFailed = state.total == 0 && state.errors.isNotEmpty;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
Icon(Icons.check_circle, color: AppTheme.success, size: 20),
|
||||
Icon(
|
||||
allFailed ? Icons.warning_amber_rounded : Icons.check_circle,
|
||||
color: allFailed ? AppTheme.danger : AppTheme.success,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text('导入成功', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
|
||||
Text(
|
||||
allFailed ? '导入失败' : '导入完成',
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
]),
|
||||
const SizedBox(height: 10),
|
||||
Text('新增:${state.imported} 条', style: const TextStyle(fontSize: 13)),
|
||||
if (state.updated > 0)
|
||||
Text('更新:${state.updated} 条', style: const TextStyle(fontSize: 13)),
|
||||
if (state.skipped > 0)
|
||||
Text('跳过:${state.skipped} 条',
|
||||
style: TextStyle(fontSize: 13, color: Colors.grey[600])),
|
||||
Text('共 ${state.total} 行', style: const TextStyle(fontSize: 13)),
|
||||
Text('成功插入:${state.imported} 行', style: const TextStyle(fontSize: 13)),
|
||||
Text('重复更新:${state.updated} 行',
|
||||
style: TextStyle(fontSize: 13, color: Colors.grey[600])),
|
||||
if (state.errors.isNotEmpty)
|
||||
Text('失败:${state.errors.length} 条',
|
||||
Text('失败:${state.errors.length} 行',
|
||||
style: TextStyle(fontSize: 13, color: AppTheme.danger)),
|
||||
if (state.errors.isNotEmpty) ...[
|
||||
const SizedBox(height: 6),
|
||||
...state.errors.map((e) => Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(e,
|
||||
style: TextStyle(fontSize: 12, color: AppTheme.danger)),
|
||||
)),
|
||||
],
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user