feat: 自动更新、系统设置、安全修复
后端: - 新增 GET /version 版本检查端点(version.go + version.yaml) - 新增 GET /license/info 接口,返回门店授权信息 - 修复 GenerateOrderNo 并发重复单号:事务内加 FOR UPDATE 行锁 - 修复 ApproveStockOut 超卖竞态:预检和库存更新均加 FOR UPDATE - 修复 Product Create 并发 code 冲突:加重试逻辑,schema 加 UNIQUE KEY - 修复 Product Update 全字段覆盖:改用 selective Updates() - 挂载 ReadOnly 中间件(全局)+ AdminOnly(用户管理路由) - version.go 配置缺失时返回 500 而非静默降级 前端: - 新增自动更新检测(update_provider.dart)+ shell 更新 banner/弹窗 - 新增系统设置"关于"标签页:版本、授权、开发信息、意见反馈 - 新增离线缓存:所有 AsyncNotifierProvider 支持断网浏览历史数据 - 新增门店信息弹窗(点击左上角 logo 或右上角门店号触发) - 提取 AppConfig 统一管理 BASE_URL,支持 --dart-define 注入 - update_provider.dart 加 kIsWeb 保护,修复 Web 平台崩溃 - dev.sh 新增 stop 命令,修复 stop 误杀前端进程问题 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,9 +9,11 @@ import '../../providers/partner_provider.dart';
|
||||
import '../../providers/product_provider.dart';
|
||||
import '../../providers/stock_out_provider.dart';
|
||||
import '../../providers/warehouse_provider.dart';
|
||||
import '../../repositories/stock_out_repository.dart';
|
||||
|
||||
class StockOutFormScreen extends ConsumerStatefulWidget {
|
||||
const StockOutFormScreen({super.key});
|
||||
final int? editOrderId;
|
||||
const StockOutFormScreen({super.key, this.editOrderId});
|
||||
|
||||
@override
|
||||
ConsumerState<StockOutFormScreen> createState() => _StockOutFormScreenState();
|
||||
@@ -24,15 +26,57 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
|
||||
int? _partnerId;
|
||||
DateTime _orderDate = DateTime.now();
|
||||
bool _submitting = false;
|
||||
bool _loadingEdit = false;
|
||||
// productId → available quantity in selected warehouse
|
||||
Map<int, double> _inventoryMap = {};
|
||||
|
||||
final List<_ItemRow> _items = [];
|
||||
|
||||
bool get _isEdit => widget.editOrderId != null;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_items.add(_ItemRow());
|
||||
if (_isEdit) {
|
||||
_loadEditOrder();
|
||||
} else {
|
||||
_items.add(_ItemRow());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadEditOrder() async {
|
||||
setState(() => _loadingEdit = true);
|
||||
try {
|
||||
final order = await ref
|
||||
.read(stockOutRepositoryProvider)
|
||||
.get(widget.editOrderId!);
|
||||
setState(() {
|
||||
_warehouseId = order.warehouseId;
|
||||
_partnerId = order.partnerId;
|
||||
if (order.orderDate != null) {
|
||||
_orderDate = DateTime.tryParse(order.orderDate!) ?? DateTime.now();
|
||||
}
|
||||
_remarkCtrl.text = order.remark ?? '';
|
||||
_items.clear();
|
||||
for (final item in order.items ?? []) {
|
||||
final row = _ItemRow();
|
||||
row.productId = item.productId;
|
||||
row.qtyCtrl.text = item.quantity.toStringAsFixed(0);
|
||||
row.priceCtrl.text = item.unitPrice.toStringAsFixed(2);
|
||||
_items.add(row);
|
||||
}
|
||||
if (_items.isEmpty) _items.add(_ItemRow());
|
||||
});
|
||||
if (_warehouseId != null) await _loadInventory(_warehouseId!);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('加载失败:$e'), backgroundColor: AppTheme.danger),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _loadingEdit = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -117,7 +161,14 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
|
||||
};
|
||||
|
||||
try {
|
||||
await ref.read(stockOutListProvider.notifier).createOrder(data);
|
||||
if (_isEdit) {
|
||||
await ref
|
||||
.read(stockOutRepositoryProvider)
|
||||
.update(widget.editOrderId!, data);
|
||||
ref.read(stockOutListProvider.notifier).reload();
|
||||
} else {
|
||||
await ref.read(stockOutListProvider.notifier).createOrder(data);
|
||||
}
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -160,8 +211,8 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
|
||||
tooltip: '返回',
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text('新建出库单',
|
||||
style: TextStyle(
|
||||
Text(_isEdit ? '修改出库单' : '新建出库单',
|
||||
style: const TextStyle(
|
||||
fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
const Spacer(),
|
||||
OutlinedButton(
|
||||
@@ -190,6 +241,9 @@ class _StockOutFormScreenState extends ConsumerState<StockOutFormScreen> {
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
if (_loadingEdit)
|
||||
const Expanded(child: Center(child: CircularProgressIndicator())),
|
||||
if (!_loadingEdit)
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
|
||||
@@ -6,6 +6,7 @@ import '../../models/stock_out.dart';
|
||||
import '../../providers/stock_out_provider.dart';
|
||||
import '../../repositories/stock_out_repository.dart';
|
||||
import '../../widgets/data_table_card.dart';
|
||||
import '../../widgets/multi_select_dropdown.dart';
|
||||
import '../../widgets/page_scaffold.dart';
|
||||
import '../../widgets/status_badge.dart';
|
||||
|
||||
@@ -20,6 +21,19 @@ class StockOutListScreen extends ConsumerStatefulWidget {
|
||||
class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
|
||||
String _statusFilter = '';
|
||||
DateTimeRange? _dateRange;
|
||||
Set<String> _filterWarehouse = {};
|
||||
Set<String> _filterCustomer = {};
|
||||
Set<String> _hiddenCols = {};
|
||||
|
||||
static const _colDefs = [
|
||||
ColDef('order_no', '出库单号', required: true),
|
||||
ColDef('customer', '客户', minWidth: 900),
|
||||
ColDef('warehouse', '仓库'),
|
||||
ColDef('amount', '金额', minWidth: 800),
|
||||
ColDef('status', '状态'),
|
||||
ColDef('date', '日期', minWidth: 900),
|
||||
ColDef('actions', '操作', required: true),
|
||||
];
|
||||
|
||||
String? get _startDate => _dateRange != null
|
||||
? '${_dateRange!.start.year}-${_dateRange!.start.month.toString().padLeft(2, '0')}-${_dateRange!.start.day.toString().padLeft(2, '0')}'
|
||||
@@ -65,8 +79,10 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('加载失败:$e',
|
||||
style: const TextStyle(color: AppTheme.danger)),
|
||||
const Icon(Icons.cloud_off, size: 40, color: AppTheme.textSecondary),
|
||||
const SizedBox(height: 12),
|
||||
const Text('暂无数据,网络不可用',
|
||||
style: const TextStyle(color: AppTheme.textSecondary)),
|
||||
const SizedBox(height: 12),
|
||||
ElevatedButton(
|
||||
onPressed: () =>
|
||||
@@ -77,20 +93,55 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
|
||||
),
|
||||
),
|
||||
data: (result) {
|
||||
final List<StockOutOrder> orders;
|
||||
final allOrders = result.data;
|
||||
final List<StockOutOrder> statusFiltered;
|
||||
if (filterStatus == 'pending') {
|
||||
orders = result.data.where((o) => o.status == 'pending').toList();
|
||||
statusFiltered = allOrders
|
||||
.where((o) => o.status == 'draft' || o.status == 'pending')
|
||||
.toList();
|
||||
} else if (filterStatus == 'exclude_pending') {
|
||||
orders = result.data.where((o) => o.status != 'pending').toList();
|
||||
statusFiltered = allOrders
|
||||
.where((o) => o.status != 'draft' && o.status != 'pending')
|
||||
.toList();
|
||||
} else {
|
||||
orders = result.data;
|
||||
statusFiltered = allOrders;
|
||||
}
|
||||
|
||||
// Derive filter options from all loaded orders
|
||||
final warehouseOptions = allOrders
|
||||
.map((o) => o.warehouseName ?? '')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toSet()
|
||||
.toList()
|
||||
..sort();
|
||||
final customerOptions = allOrders
|
||||
.map((o) => o.partnerName ?? '')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.toSet()
|
||||
.toList()
|
||||
..sort();
|
||||
|
||||
// Apply multi-select filters
|
||||
var orders = statusFiltered;
|
||||
if (_filterWarehouse.isNotEmpty) {
|
||||
orders = orders
|
||||
.where((o) => _filterWarehouse.contains(o.warehouseName ?? ''))
|
||||
.toList();
|
||||
}
|
||||
if (_filterCustomer.isNotEmpty) {
|
||||
orders = orders
|
||||
.where((o) => _filterCustomer.contains(o.partnerName ?? ''))
|
||||
.toList();
|
||||
}
|
||||
|
||||
return _buildOrderTable(
|
||||
orders: orders,
|
||||
totalCount: orders.length,
|
||||
page: result.page,
|
||||
showStatusFilter: filterStatus == 'exclude_pending',
|
||||
showNewButton: showNewButton,
|
||||
warehouseOptions: warehouseOptions,
|
||||
customerOptions: customerOptions,
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -102,139 +153,205 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
|
||||
required int page,
|
||||
required bool showStatusFilter,
|
||||
required bool showNewButton,
|
||||
required List<String> warehouseOptions,
|
||||
required List<String> customerOptions,
|
||||
}) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final visibleCols = _colDefs
|
||||
.where((c) =>
|
||||
!_hiddenCols.contains(c.key) &&
|
||||
(c.minWidth == null || screenWidth >= c.minWidth!))
|
||||
.toList();
|
||||
|
||||
final columns = visibleCols
|
||||
.map((c) => DataColumn(
|
||||
label: Text(c.label),
|
||||
numeric: c.key == 'amount',
|
||||
))
|
||||
.toList();
|
||||
|
||||
DataCell buildOrderCell(String key, StockOutOrder o) {
|
||||
switch (key) {
|
||||
case 'order_no':
|
||||
return DataCell(GestureDetector(
|
||||
onTap: () => _showDetail(context, o.id),
|
||||
child: Text(o.orderNo,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.primary,
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12,
|
||||
decoration: TextDecoration.underline)),
|
||||
));
|
||||
case 'customer':
|
||||
return DataCell(Text(o.partnerName ?? '-'));
|
||||
case 'warehouse':
|
||||
return DataCell(Text(o.warehouseName ?? '-'));
|
||||
case 'amount':
|
||||
return DataCell(Text(o.totalAmount != null
|
||||
? '¥${o.totalAmount!.toStringAsFixed(2)}'
|
||||
: '-'));
|
||||
case 'status':
|
||||
return DataCell(StatusBadge(_apiStatusToEnum(o.status)));
|
||||
case 'date':
|
||||
return DataCell(Text(o.orderDate?.substring(0, 10) ?? '-'));
|
||||
case 'actions':
|
||||
return DataCell(Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => _showDetail(context, o.id),
|
||||
child: const Text('详情',
|
||||
style:
|
||||
TextStyle(fontSize: 12, color: AppTheme.primary)),
|
||||
),
|
||||
if (o.status == 'draft') ...[
|
||||
TextButton(
|
||||
onPressed: () => context.go('/stock-out/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)),
|
||||
),
|
||||
],
|
||||
],
|
||||
));
|
||||
default:
|
||||
return const DataCell(SizedBox());
|
||||
}
|
||||
}
|
||||
|
||||
final rows = orders.isEmpty
|
||||
? [
|
||||
DataRow(
|
||||
cells: List.generate(
|
||||
visibleCols.length,
|
||||
(i) => i == 0
|
||||
? const DataCell(Text('暂无出库单',
|
||||
style: TextStyle(color: AppTheme.textSecondary)))
|
||||
: const DataCell(SizedBox()),
|
||||
),
|
||||
),
|
||||
]
|
||||
: orders
|
||||
.map((o) => DataRow(
|
||||
cells: visibleCols
|
||||
.map((c) => buildOrderCell(c.key, o))
|
||||
.toList(),
|
||||
))
|
||||
.toList();
|
||||
|
||||
return DataTableCard(
|
||||
totalCount: totalCount,
|
||||
page: page,
|
||||
onPageChanged: (p) =>
|
||||
ref.read(stockOutListProvider.notifier).setPage(p),
|
||||
toolbar: Row(
|
||||
toolbar: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (showNewButton)
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => context.go('/stock-out/new'),
|
||||
icon: const Icon(Icons.add, size: 16),
|
||||
label: const Text('新建出库审核单'),
|
||||
),
|
||||
const Spacer(),
|
||||
if (showStatusFilter) ...[
|
||||
_StatusFilterDropdown(
|
||||
value: _statusFilter,
|
||||
onChanged: (v) {
|
||||
setState(() => _statusFilter = v ?? '');
|
||||
ref
|
||||
.read(stockOutListProvider.notifier)
|
||||
.setStatus(v ?? '');
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
OutlinedButton.icon(
|
||||
onPressed: _pickDateRange,
|
||||
icon: const Icon(Icons.date_range, size: 16),
|
||||
label: Text(
|
||||
_dateRange == null
|
||||
? '选择日期'
|
||||
: '$_startDate ~ $_endDate',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
// Row 1: new button + status filter + date picker
|
||||
Row(
|
||||
children: [
|
||||
if (showNewButton)
|
||||
ElevatedButton.icon(
|
||||
onPressed: () => context.go('/stock-out/new'),
|
||||
icon: const Icon(Icons.add, size: 16),
|
||||
label: const Text('新建出库审核单'),
|
||||
),
|
||||
const Spacer(),
|
||||
if (showStatusFilter) ...[
|
||||
_StatusFilterDropdown(
|
||||
value: _statusFilter,
|
||||
onChanged: (v) {
|
||||
setState(() => _statusFilter = v ?? '');
|
||||
ref
|
||||
.read(stockOutListProvider.notifier)
|
||||
.setStatus(v ?? '');
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
OutlinedButton.icon(
|
||||
onPressed: _pickDateRange,
|
||||
icon: const Icon(Icons.date_range, size: 16),
|
||||
label: Text(
|
||||
_dateRange == null
|
||||
? '选择日期'
|
||||
: '$_startDate ~ $_endDate',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
if (_dateRange != null) ...[
|
||||
const SizedBox(width: 4),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear, size: 16),
|
||||
onPressed: () {
|
||||
setState(() => _dateRange = null);
|
||||
ref
|
||||
.read(stockOutListProvider.notifier)
|
||||
.setDateRange(null, null);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
// Row 2: multi-select filters + column toggle
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
if (customerOptions.length > 1)
|
||||
MultiSelectDropdown(
|
||||
label: '客户',
|
||||
options: customerOptions,
|
||||
selected: _filterCustomer,
|
||||
onChanged: (v) => setState(() => _filterCustomer = v),
|
||||
),
|
||||
if (customerOptions.length > 1) const SizedBox(width: 8),
|
||||
if (warehouseOptions.length > 1)
|
||||
MultiSelectDropdown(
|
||||
label: '仓库',
|
||||
options: warehouseOptions,
|
||||
selected: _filterWarehouse,
|
||||
onChanged: (v) => setState(() => _filterWarehouse = v),
|
||||
),
|
||||
const Spacer(),
|
||||
ColumnToggleButton(
|
||||
columns: _colDefs,
|
||||
hidden: _hiddenCols,
|
||||
onChanged: (v) => setState(() => _hiddenCols = v),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_dateRange != null) ...[
|
||||
const SizedBox(width: 4),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear, size: 16),
|
||||
onPressed: () {
|
||||
setState(() => _dateRange = null);
|
||||
ref
|
||||
.read(stockOutListProvider.notifier)
|
||||
.setDateRange(null, null);
|
||||
},
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
columns: const [
|
||||
DataColumn(label: Text('出库单号')),
|
||||
DataColumn(label: Text('客户/往来单位')),
|
||||
DataColumn(label: Text('仓库')),
|
||||
DataColumn(label: Text('金额'), numeric: true),
|
||||
DataColumn(label: Text('状态')),
|
||||
DataColumn(label: Text('日期')),
|
||||
DataColumn(label: Text('操作')),
|
||||
],
|
||||
rows: orders.isEmpty
|
||||
? [
|
||||
const DataRow(cells: [
|
||||
DataCell(SizedBox()),
|
||||
DataCell(Text('暂无出库单',
|
||||
style: TextStyle(color: AppTheme.textSecondary))),
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
DataCell(SizedBox()),
|
||||
])
|
||||
]
|
||||
: orders
|
||||
.map((o) => DataRow(
|
||||
cells: [
|
||||
DataCell(Text(o.orderNo,
|
||||
style: const TextStyle(
|
||||
color: AppTheme.primary,
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 12))),
|
||||
DataCell(Text(o.partnerName ?? '-')),
|
||||
DataCell(Text(o.warehouseName ?? '-')),
|
||||
DataCell(Text(o.totalAmount != null
|
||||
? '¥${o.totalAmount!.toStringAsFixed(2)}'
|
||||
: '-')),
|
||||
DataCell(StatusBadge(
|
||||
_apiStatusToEnum(o.status))),
|
||||
DataCell(Text(o.orderDate?.substring(0, 10) ?? '-')),
|
||||
DataCell(Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => _showDetail(context, o.id),
|
||||
child: const Text('详情',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppTheme.primary)),
|
||||
),
|
||||
if (o.status == 'draft')
|
||||
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)),
|
||||
),
|
||||
],
|
||||
],
|
||||
)),
|
||||
],
|
||||
))
|
||||
.toList(),
|
||||
columns: columns,
|
||||
rows: rows,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -263,6 +380,43 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _confirmDelete(BuildContext context, StockOutOrder o) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('删除确认'),
|
||||
content: Text('确认删除出库单「${o.orderNo}」?此操作不可恢复。'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(false),
|
||||
child: const Text('取消')),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.of(ctx).pop(true),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppTheme.danger,
|
||||
foregroundColor: Colors.white),
|
||||
child: const Text('删除'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (confirmed == true && mounted) {
|
||||
try {
|
||||
await ref.read(stockOutListProvider.notifier).deleteOrder(o.id);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content: Text('已删除'), backgroundColor: AppTheme.success));
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text('删除失败:$e'),
|
||||
backgroundColor: AppTheme.danger));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _confirmSubmit(
|
||||
BuildContext context, StockOutOrder o) async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
@@ -364,9 +518,7 @@ class _StockOutListScreenState extends ConsumerState<StockOutListScreen> {
|
||||
);
|
||||
if (confirmed == true && mounted) {
|
||||
try {
|
||||
await ref
|
||||
.read(stockOutListProvider.notifier)
|
||||
.rejectOrder(o.id);
|
||||
await ref.read(stockOutListProvider.notifier).rejectOrder(o.id);
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content: Text('已拒绝'), backgroundColor: AppTheme.accent));
|
||||
@@ -446,9 +598,17 @@ class _StockOutDetailDialogState extends State<_StockOutDetailDialog> {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (snap.hasError) {
|
||||
return Center(
|
||||
child: Text('加载失败:${snap.error}',
|
||||
style: const TextStyle(color: AppTheme.danger)));
|
||||
return const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.cloud_off, size: 40, color: AppTheme.textSecondary),
|
||||
SizedBox(height: 12),
|
||||
Text('暂无数据,网络不可用',
|
||||
style: TextStyle(color: AppTheme.textSecondary)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return _buildContent(snap.data!);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user