chore: release v1.0.18
Deploy / build-linux-web (push) Successful in 53s
Deploy / build-windows (push) Successful in 1m48s
Deploy / build-macos (push) Successful in 1m17s
Deploy / build-android (push) Successful in 4m13s
Deploy / build-ios (push) Successful in 9s
Deploy / release-deploy (push) Successful in 1m37s

移动端响应式适配(抽屉导航/列表卡片/弹窗自适应)、Android 正式签名与 APK 发布、
iOS(TestFlight) 工程与 CI、多平台构建流水线、相关文档同步。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-07 07:55:33 +08:00
parent 94f0fb2095
commit 480ce836bb
81 changed files with 3096 additions and 366 deletions
@@ -5,11 +5,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/config/app_config.dart';
import '../../core/responsive/responsive.dart';
import '../../core/theme/app_theme.dart';
import '../../models/inventory.dart';
import '../../core/auth/auth_state.dart';
import '../../providers/inventory_provider.dart';
import '../../widgets/data_table_card.dart';
import '../../widgets/mobile_list_card.dart';
import '../../widgets/multi_select_dropdown.dart' show FilterableColumnHeader;
import '../../widgets/page_scaffold.dart';
import '../../core/utils/export_util.dart';
@@ -47,7 +49,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
builder: (ctx) => AlertDialog(
title: const Text('修改备注'),
content: SizedBox(
width: 360,
width: context.dialogWidth(360),
child: TextField(
controller: ctrl,
autofocus: true,
@@ -188,6 +190,133 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
}
}
Future<void> _printLabel(BuildContext context, Inventory item) async {
if (item.productId == null) return;
final qrBytes =
await ref.read(productRepositoryProvider).getQRCodeBytes(item.productId!);
final shopInfo = ref.read(shopInfoProvider).valueOrNull;
if (!context.mounted) return;
await safePrint(
context,
() => printProductLabel(
qrBytes: qrBytes,
name: item.productName,
code: item.productCode,
series: item.series.isEmpty ? null : item.series,
spec: item.spec.isEmpty ? null : item.spec,
batchNo: item.batchNo.isEmpty ? null : item.batchNo,
productionDate: item.productionDate,
remark: item.remark.isEmpty ? null : item.remark,
shopName: shopInfo?.name ?? '',
shopAddress: shopInfo?.address ?? '',
shopPhone: shopInfo?.phone ?? '',
),
);
}
/// 库存查询:窄屏卡片
Widget _inventoryCard(Inventory item) {
return MobileListCard(
onTap: item.productId != null
? () => context.push('/products/${item.productId}')
: null,
title: Text(item.productName.isEmpty ? '-' : item.productName),
subtitle: item.productCode.isEmpty ? null : Text(item.productCode),
trailing: _InventoryStatusBadge(item),
fields: [
if (item.spec.isNotEmpty) MobileCardField('规格', item.spec),
if (item.series.isNotEmpty) MobileCardField('系列', item.series),
if (item.batchNo.isNotEmpty) MobileCardField('批次', item.batchNo),
MobileCardField('仓库', item.warehouseName.isEmpty ? '-' : item.warehouseName),
MobileCardField('库存', null,
valueWidget: Text(
'${item.quantity.toStringAsFixed(0)} ${item.unit}'.trim(),
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: item.quantity == 0
? AppTheme.danger
: (item.minStock != null && item.quantity < item.minStock!)
? AppTheme.accent
: AppTheme.textPrimary,
),
)),
if (item.unitPrice != null)
MobileCardField('单价', '¥${item.unitPrice!.toStringAsFixed(2)}'),
if (item.supplierName.isNotEmpty)
MobileCardField('供应商', item.supplierName),
],
actions: [
TextButton(
onPressed: () => _editRemark(context, item),
child: const Text('备注', style: TextStyle(fontSize: 13)),
),
if (item.productId != null)
TextButton(
onPressed: () => _printLabel(context, item),
child: const Text('打标签', style: TextStyle(fontSize: 13)),
),
],
);
}
/// 库存预警:窄屏卡片
Widget _warningCard(Inventory item) {
return MobileListCard(
onTap: item.productId != null
? () => context.push('/products/${item.productId}')
: null,
title: Text(item.productName.isEmpty ? '-' : item.productName),
subtitle: item.productCode.isEmpty ? null : Text(item.productCode),
trailing: _InventoryStatusBadge(item),
fields: [
MobileCardField('仓库', item.warehouseName.isEmpty ? '-' : item.warehouseName),
MobileCardField('当前库存', null,
valueWidget: Text(
item.quantity.toStringAsFixed(0),
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: item.quantity == 0 ? AppTheme.danger : AppTheme.accent),
)),
MobileCardField('安全库存', '${item.minStock}'),
MobileCardField('缺口', null,
valueWidget: Text(
'${item.minStock! - item.quantity.toInt()}',
style: const TextStyle(
fontSize: 13,
color: AppTheme.danger,
fontWeight: FontWeight.w600),
)),
],
);
}
/// 流水记录:窄屏卡片
Widget _logCard(dynamic log) {
return MobileListCard(
title: Text(log.productName ?? '-'),
subtitle: Text(log.createdAt?.substring(0, 19) ?? '-'),
trailing: _DirectionBadge(log.direction),
fields: [
MobileCardField('仓库', log.warehouseName ?? '-'),
MobileCardField('数量', null,
valueWidget: Text(
log.quantity.toStringAsFixed(0),
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: log.direction == 'in'
? AppTheme.success
: AppTheme.danger),
)),
MobileCardField('变前', log.qtyBefore?.toStringAsFixed(0) ?? '-'),
MobileCardField('变后', log.qtyAfter?.toStringAsFixed(0) ?? '-'),
if (log.refType != null) MobileCardField('来源', log.refType),
],
);
}
@override
Widget build(BuildContext context) {
return PageScaffold(
@@ -251,35 +380,45 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
return Column(
children: [
// Summary cards
Container(
color: AppTheme.background,
padding: const EdgeInsets.all(12),
child: Row(
children: [
_SummaryCard(
title: '商品总数',
value: '${result.total}',
unit: '',
icon: Icons.inventory_2,
color: AppTheme.primary),
const SizedBox(width: 12),
_SummaryCard(
title: '库存预警',
value: '$warningCount',
unit: '',
icon: Icons.warning_amber,
color: AppTheme.accent),
const SizedBox(width: 12),
_SummaryCard(
title: '缺货商品',
value: '$emptyCount',
unit: '',
icon: Icons.remove_shopping_cart,
color: AppTheme.danger),
],
),
),
// Summary cards(窄屏横向滚动,宽屏等分)
Builder(builder: (context) {
final w = context.isMobile ? 150.0 : null;
final cards = [
_SummaryCard(
title: '商品总数',
value: '${result.total}',
unit: '',
icon: Icons.inventory_2,
color: AppTheme.primary,
width: w),
const SizedBox(width: 12),
_SummaryCard(
title: '库存预警',
value: '$warningCount',
unit: '',
icon: Icons.warning_amber,
color: AppTheme.accent,
width: w),
const SizedBox(width: 12),
_SummaryCard(
title: '缺货商品',
value: '$emptyCount',
unit: '',
icon: Icons.remove_shopping_cart,
color: AppTheme.danger,
width: w),
];
return Container(
color: AppTheme.background,
padding: const EdgeInsets.all(12),
child: context.isMobile
? SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(children: cards),
)
: Row(children: cards),
);
}),
const Divider(height: 1),
Expanded(
child: DataTableCard(
@@ -346,6 +485,8 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
),
],
),
mobileCards:
filteredItems.map(_inventoryCard).toList(),
columns: [
const DataColumn(label: Text('商品编码')),
const DataColumn(label: Text('商品名称')),
@@ -580,6 +721,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
),
],
),
mobileCards: warnings.map(_warningCard).toList(),
columns: const [
DataColumn(label: Text('商品编码')),
DataColumn(label: Text('商品名称')),
@@ -717,6 +859,7 @@ class _InventoryListScreenState extends ConsumerState<InventoryListScreen> {
),
],
),
mobileCards: logs.map(_logCard).toList(),
columns: const [
DataColumn(label: Text('商品名称')),
DataColumn(label: Text('仓库')),
@@ -788,18 +931,21 @@ class _SummaryCard extends StatelessWidget {
final IconData icon;
final Color color;
/// 固定宽度(窄屏横向滚动用);为 null 时占据等分宽度(Expanded,宽屏用)。
final double? width;
const _SummaryCard({
required this.title,
required this.value,
required this.unit,
required this.icon,
required this.color,
this.width,
});
@override
Widget build(BuildContext context) {
return Expanded(
child: Container(
final card = Container(
height: 72,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
@@ -849,8 +995,10 @@ class _SummaryCard extends StatelessWidget {
),
],
),
),
);
);
return width != null
? SizedBox(width: width, child: card)
: Expanded(child: card);
}
}
@@ -950,7 +1098,7 @@ class _ImportProgressDialog extends StatelessWidget {
content: ValueListenableBuilder<_ImportState>(
valueListenable: stateNotifier,
builder: (_, state, __) => SizedBox(
width: 320,
width: context.dialogWidth(320),
child: _buildContent(context, state),
),
),