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
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:
@@ -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),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user