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
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../core/responsive/responsive.dart';
import 'package:go_router/go_router.dart';
import '../../core/theme/app_theme.dart';
import '../../core/auth/auth_state.dart';
@@ -849,8 +850,14 @@ class _FormField extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 窄屏(手机)字段占满整行,便于点选;宽屏沿用固定宽度配合 Wrap 多列。
final effectiveWidth = width == double.infinity
? double.infinity
: (context.isMobile
? MediaQuery.sizeOf(context).width - 64
: width);
return SizedBox(
width: width,
width: effectiveWidth,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -4,11 +4,13 @@ import '../../core/utils/print_util.dart' show safePrint, printStockInOrder, pri
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/responsive/responsive.dart';
import '../../core/theme/app_theme.dart';
import '../../models/stock_in.dart';
import '../../providers/stock_in_provider.dart';
import '../../repositories/stock_in_repository.dart';
import '../../widgets/data_table_card.dart';
import '../../widgets/mobile_list_card.dart';
import '../../widgets/multi_select_dropdown.dart' show ColDef, ColumnToggleButton, FilterableColumnHeader;
import '../../widgets/page_scaffold.dart';
import '../../widgets/status_badge.dart';
@@ -239,88 +241,10 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
return DataCell(SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
TextButton(
onPressed: () => _showDetail(context, o.id),
child: const Text('详情',
style:
TextStyle(fontSize: 12, color: AppTheme.primary)),
),
TextButton(
onPressed: () async {
final order = await ref.read(stockInRepositoryProvider).get(o.id);
if (context.mounted) {
await safePrint(context, () => printStockInOrder(order));
}
},
child: const Text('打印',
style: TextStyle(fontSize: 12, color: AppTheme.primary)),
),
TextButton(
onPressed: () async {
final order = await ref.read(stockInRepositoryProvider).get(o.id);
if (!context.mounted) return;
final shopInfo = ref.read(shopInfoProvider).valueOrNull;
await showDialog(
context: context,
builder: (_) => _LabelPrintDialog(
order: order,
productRepo: ref.read(productRepositoryProvider),
shopName: shopInfo?.name ?? '',
shopAddress: shopInfo?.address ?? '',
shopPhone: shopInfo?.phone ?? '',
),
);
},
child: const Text('打标签',
style: TextStyle(fontSize: 12, color: AppTheme.primary)),
),
if (o.status == 'approved') ...[
TextButton(
onPressed: () => _confirmSettle(context, o.id, 'stock_in'),
child: const Text('结清',
style: TextStyle(fontSize: 12, color: AppTheme.accent)),
),
],
if (o.status == 'draft') ...[
TextButton(
onPressed: () => context.go('/stock-in/edit/${o.id}'),
child: const Text('修改',
style: TextStyle(
fontSize: 12, color: AppTheme.primary)),
),
TextButton(
onPressed: () => _confirmDelete(context, o),
child: const Text('删除',
style: TextStyle(
fontSize: 12, color: AppTheme.danger)),
),
TextButton(
onPressed: () => _confirmSubmit(context, o),
child: const Text('提交',
style: TextStyle(
fontSize: 12, color: AppTheme.primary)),
),
],
if (o.status == 'pending') ...[
TextButton(
key: Key('btn_approve_${o.id}'),
onPressed: () => _confirmApprove(context, o),
child: const Text('通过',
style: TextStyle(
fontSize: 12, color: AppTheme.success)),
),
TextButton(
key: Key('btn_reject_${o.id}'),
onPressed: () => _confirmReject(context, o),
child: const Text('拒绝',
style: TextStyle(
fontSize: 12, color: AppTheme.danger)),
),
],
],
)));
mainAxisSize: MainAxisSize.min,
children: _orderActions(context, o),
),
));
default:
return const DataCell(SizedBox());
}
@@ -414,6 +338,105 @@ class _StockInListScreenState extends ConsumerState<StockInListScreen> {
),
columns: columns,
rows: rows,
mobileCards: orders.map((o) => _orderCard(context, o)).toList(),
);
}
/// 操作按钮列表,表格与移动端卡片共用。
List<Widget> _orderActions(BuildContext context, StockInOrder o) {
return [
TextButton(
onPressed: () => _showDetail(context, o.id),
child: const Text('详情',
style: TextStyle(fontSize: 12, color: AppTheme.primary)),
),
TextButton(
onPressed: () async {
final order = await ref.read(stockInRepositoryProvider).get(o.id);
if (context.mounted) {
await safePrint(context, () => printStockInOrder(order));
}
},
child: const Text('打印',
style: TextStyle(fontSize: 12, color: AppTheme.primary)),
),
TextButton(
onPressed: () async {
final order = await ref.read(stockInRepositoryProvider).get(o.id);
if (!context.mounted) return;
final shopInfo = ref.read(shopInfoProvider).valueOrNull;
await showDialog(
context: context,
builder: (_) => _LabelPrintDialog(
order: order,
productRepo: ref.read(productRepositoryProvider),
shopName: shopInfo?.name ?? '',
shopAddress: shopInfo?.address ?? '',
shopPhone: shopInfo?.phone ?? '',
),
);
},
child: const Text('打标签',
style: TextStyle(fontSize: 12, color: AppTheme.primary)),
),
if (o.status == 'approved')
TextButton(
onPressed: () => _confirmSettle(context, o.id, 'stock_in'),
child: const Text('结清',
style: TextStyle(fontSize: 12, color: AppTheme.accent)),
),
if (o.status == 'draft') ...[
TextButton(
onPressed: () => context.go('/stock-in/edit/${o.id}'),
child: const Text('修改',
style: TextStyle(fontSize: 12, color: AppTheme.primary)),
),
TextButton(
onPressed: () => _confirmDelete(context, o),
child: const Text('删除',
style: TextStyle(fontSize: 12, color: AppTheme.danger)),
),
TextButton(
onPressed: () => _confirmSubmit(context, o),
child: const Text('提交',
style: TextStyle(fontSize: 12, color: AppTheme.primary)),
),
],
if (o.status == 'pending') ...[
TextButton(
key: Key('btn_approve_${o.id}'),
onPressed: () => _confirmApprove(context, o),
child: const Text('通过',
style: TextStyle(fontSize: 12, color: AppTheme.success)),
),
TextButton(
key: Key('btn_reject_${o.id}'),
onPressed: () => _confirmReject(context, o),
child: const Text('拒绝',
style: TextStyle(fontSize: 12, color: AppTheme.danger)),
),
],
];
}
/// 入库单:窄屏卡片
Widget _orderCard(BuildContext context, StockInOrder o) {
return MobileListCard(
onTap: () => _showDetail(context, o.id),
title: Text(o.orderNo,
style: const TextStyle(fontFamily: 'monospace', fontSize: 14)),
trailing: StatusBadge(_apiStatusToEnum(o.status)),
fields: [
MobileCardField('供应商', o.partnerName ?? '-'),
MobileCardField('仓库', o.warehouseName ?? '-'),
MobileCardField(
'金额',
o.totalAmount != null
? '¥${o.totalAmount!.toStringAsFixed(2)}'
: '-'),
MobileCardField('日期', o.orderDate?.substring(0, 10) ?? '-'),
],
actions: _orderActions(context, o),
);
}
@@ -668,7 +691,7 @@ class _StockInDetailDialogState extends ConsumerState<_StockInDetailDialog> {
Widget build(BuildContext context) {
return Dialog(
child: Container(
width: 720,
width: context.dialogWidth(720),
constraints: const BoxConstraints(maxHeight: 600),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -965,7 +988,7 @@ class _LabelPrintDialogState extends State<_LabelPrintDialog> {
final items = widget.order.items;
return Dialog(
child: Container(
width: 520,
width: context.dialogWidth(520),
constraints: const BoxConstraints(maxHeight: 520),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,