feat(client): 移动端布局适配 + 信息架构整理
- 入库/出库详情页:窄屏商品明细切换为卡片竖排,顶部操作收入溢出菜单 - 基础数据:新增「仓库」tab,使用 DataTableCard + mobileCards 适配窄屏 - 系统设置:移除仓库 tab(已迁入基础数据),新增「授权」tab,数据导入改名「数据管理」 - 关于我们:移除授权信息,补充帮助与文档/扫码防伪/法律信息/系统信息等模块 - AppInfo 新增 phone/wechat/termsUrl/privacyUrl/docsUrl 字段(可选,空则不渲染) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import '../../providers/inventory_provider.dart';
|
||||
import '../../providers/stock_in_provider.dart';
|
||||
import '../../providers/warehouse_provider.dart';
|
||||
import '../../widgets/searchable_option_field.dart';
|
||||
import '../../widgets/mobile_list_card.dart';
|
||||
|
||||
class StockInFormScreen extends ConsumerStatefulWidget {
|
||||
final int? editOrderId;
|
||||
@@ -296,6 +297,7 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
|
||||
final asyncWarehouses = ref.watch(warehouseListProvider);
|
||||
final asyncSuppliers = ref.watch(supplierListProvider);
|
||||
final currentUser = ref.watch(authStateProvider).user;
|
||||
final isMobile = context.isMobile;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: AppTheme.background,
|
||||
@@ -316,35 +318,71 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
|
||||
Text(_isEdit ? '修改入库单' : '新建入库单',
|
||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
const Spacer(),
|
||||
if (_isEdit && _loadedOrder != null) ...[
|
||||
OutlinedButton.icon(
|
||||
onPressed: _printOrder,
|
||||
icon: const Icon(Icons.print_outlined, size: 16),
|
||||
label: const Text('打印'),
|
||||
// 窄屏:仅保留主操作「提交」,其余收进溢出菜单,避免按钮挤爆顶栏
|
||||
if (isMobile) ...[
|
||||
ElevatedButton(
|
||||
onPressed: _submitting ? null : () => _submit(false),
|
||||
child: _submitting
|
||||
? const SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2, color: Colors.white))
|
||||
: const Text('提交'),
|
||||
),
|
||||
PopupMenuButton<String>(
|
||||
onSelected: (v) {
|
||||
switch (v) {
|
||||
case 'draft':
|
||||
_submit(true);
|
||||
break;
|
||||
case 'print':
|
||||
_printOrder();
|
||||
break;
|
||||
case 'cancel':
|
||||
context.go('/stock-in');
|
||||
break;
|
||||
}
|
||||
},
|
||||
itemBuilder: (_) => [
|
||||
const PopupMenuItem(value: 'draft', child: Text('保存草稿')),
|
||||
if (_isEdit && _loadedOrder != null)
|
||||
const PopupMenuItem(value: 'print', child: Text('打印')),
|
||||
const PopupMenuItem(value: 'cancel', child: Text('取消')),
|
||||
],
|
||||
),
|
||||
] else ...[
|
||||
if (_isEdit && _loadedOrder != null) ...[
|
||||
OutlinedButton.icon(
|
||||
onPressed: _printOrder,
|
||||
icon: const Icon(Icons.print_outlined, size: 16),
|
||||
label: const Text('打印'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
OutlinedButton(
|
||||
onPressed: _submitting ? null : () => _submit(true),
|
||||
child: const Text('保存草稿'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _submitting ? null : () => _submit(false),
|
||||
icon: _submitting
|
||||
? const SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2, color: Colors.white))
|
||||
: const Icon(Icons.send, size: 16),
|
||||
label: const Text('提交审核'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => context.go('/stock-in'),
|
||||
icon: const Icon(Icons.cancel_outlined, size: 16),
|
||||
label: const Text('取消'),
|
||||
),
|
||||
],
|
||||
OutlinedButton(
|
||||
onPressed: _submitting ? null : () => _submit(true),
|
||||
child: const Text('保存草稿'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: _submitting ? null : () => _submit(false),
|
||||
icon: _submitting
|
||||
? const SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
|
||||
: const Icon(Icons.send, size: 16),
|
||||
label: const Text('提交审核'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => context.go('/stock-in'),
|
||||
icon: const Icon(Icons.cancel_outlined, size: 16),
|
||||
label: const Text('取消'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -498,41 +536,53 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Table(
|
||||
columnWidths: const {
|
||||
0: FixedColumnWidth(36), // 序号
|
||||
1: FlexColumnWidth(1.2), // 商品编码
|
||||
2: FlexColumnWidth(2.0), // 名称
|
||||
3: FlexColumnWidth(1.3), // 系列
|
||||
4: FlexColumnWidth(1.3), // 规格
|
||||
5: FlexColumnWidth(0.9), // 单品数量
|
||||
6: FlexColumnWidth(1.0), // 数量
|
||||
7: FlexColumnWidth(1.0), // 单价
|
||||
8: FlexColumnWidth(1.0), // 金额
|
||||
9: FlexColumnWidth(1.2), // 批次号
|
||||
10: FlexColumnWidth(1.2), // 生产日期
|
||||
11: FixedColumnWidth(60), // 操作
|
||||
},
|
||||
children: [
|
||||
TableRow(
|
||||
decoration: const BoxDecoration(color: Color(0xFFF0F4FF)),
|
||||
children: [
|
||||
'序号', '商品编码', '名称', '系列', '规格', '单品数量', '数量', '单价', '金额', '批次号', '生产日期', '操作',
|
||||
]
|
||||
.map((h) => Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 10),
|
||||
child: Text(h,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppTheme.primaryDark)),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
...List.generate(_items.length, (i) => _buildItemRow(i)),
|
||||
],
|
||||
),
|
||||
// 窄屏:逐项卡片竖排,避免 12 列表格横向溢出;宽屏保持表格
|
||||
if (isMobile)
|
||||
Column(
|
||||
children: List.generate(
|
||||
_items.length,
|
||||
(i) => Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(bottom: 10),
|
||||
child: _buildItemCard(i),
|
||||
)),
|
||||
)
|
||||
else
|
||||
Table(
|
||||
columnWidths: const {
|
||||
0: FixedColumnWidth(36), // 序号
|
||||
1: FlexColumnWidth(1.2), // 商品编码
|
||||
2: FlexColumnWidth(2.0), // 名称
|
||||
3: FlexColumnWidth(1.3), // 系列
|
||||
4: FlexColumnWidth(1.3), // 规格
|
||||
5: FlexColumnWidth(0.9), // 单品数量
|
||||
6: FlexColumnWidth(1.0), // 数量
|
||||
7: FlexColumnWidth(1.0), // 单价
|
||||
8: FlexColumnWidth(1.0), // 金额
|
||||
9: FlexColumnWidth(1.2), // 批次号
|
||||
10: FlexColumnWidth(1.2), // 生产日期
|
||||
11: FixedColumnWidth(60), // 操作
|
||||
},
|
||||
children: [
|
||||
TableRow(
|
||||
decoration: const BoxDecoration(color: Color(0xFFF0F4FF)),
|
||||
children: [
|
||||
'序号', '商品编码', '名称', '系列', '规格', '单品数量', '数量', '单价', '金额', '批次号', '生产日期', '操作',
|
||||
]
|
||||
.map((h) => Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 10),
|
||||
child: Text(h,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppTheme.primaryDark)),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
...List.generate(_items.length, (i) => _buildItemRow(i)),
|
||||
],
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
@@ -566,104 +616,191 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
// ── 明细字段构件(表格行与移动卡片共用,保持两种布局一致)──────────
|
||||
|
||||
Widget _nameField(_ItemRow item) {
|
||||
final asyncNames = ref.watch(productNameListProvider);
|
||||
return asyncNames.when(
|
||||
loading: () => const LinearProgressIndicator(),
|
||||
error: (_, __) => const Text('加载失败'),
|
||||
data: (names) => SearchableOptionField(
|
||||
options: names
|
||||
.map((o) => OptionItem(id: o.id, name: o.name, code: o.code))
|
||||
.toList(),
|
||||
selectedId: item.selectedNameId,
|
||||
hint: '选择名称',
|
||||
dialogTitle: '选择商品名称',
|
||||
isRequired: true,
|
||||
onChanged: (v) => setState(() {
|
||||
item.selectedNameId = v;
|
||||
item.productId = null;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _seriesField(_ItemRow item) {
|
||||
final asyncSeries = ref.watch(productSeriesListProvider);
|
||||
return asyncSeries.when(
|
||||
loading: () => const LinearProgressIndicator(),
|
||||
error: (_, __) => const Text('加载失败'),
|
||||
data: (series) => SearchableOptionField(
|
||||
options: series
|
||||
.map((o) => OptionItem(id: o.id, name: o.name, code: o.code))
|
||||
.toList(),
|
||||
selectedId: item.selectedSeriesId,
|
||||
hint: '选择系列',
|
||||
dialogTitle: '选择系列',
|
||||
isRequired: true,
|
||||
onChanged: (v) => setState(() {
|
||||
item.selectedSeriesId = v;
|
||||
item.productId = null;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _specField(_ItemRow item) {
|
||||
final asyncSpecs = ref.watch(productSpecListProvider);
|
||||
return asyncSpecs.when(
|
||||
loading: () => const LinearProgressIndicator(),
|
||||
error: (_, __) => const Text('加载失败'),
|
||||
data: (specs) => SearchableOptionField(
|
||||
options: specs
|
||||
.map((o) => OptionItem(id: o.id, name: o.name, code: o.code))
|
||||
.toList(),
|
||||
selectedId: item.selectedSpecId,
|
||||
hint: '选择规格',
|
||||
dialogTitle: '选择规格',
|
||||
isRequired: true,
|
||||
onChanged: (v) => setState(() {
|
||||
item.selectedSpecId = v;
|
||||
item.productId = null;
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _qtyField(_ItemRow item) {
|
||||
return TextFormField(
|
||||
controller: item.qtyCtrl,
|
||||
decoration: const InputDecoration(hintText: '0', isDense: true),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}'))
|
||||
],
|
||||
onChanged: (_) => setState(() {}),
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return '不能为空';
|
||||
if ((double.tryParse(v) ?? 0) <= 0) return '>0';
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _priceField(_ItemRow item) {
|
||||
return TextFormField(
|
||||
controller: item.priceCtrl,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '0.00', prefixText: '¥', isDense: true),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}'))
|
||||
],
|
||||
onChanged: (_) => setState(() {}),
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return '不能为空';
|
||||
if ((double.tryParse(v) ?? 0) <= 0) return '>0';
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _batchField(_ItemRow item) {
|
||||
return TextFormField(
|
||||
controller: item.batchNoCtrl,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '选填',
|
||||
labelText: '批次号',
|
||||
isDense: true,
|
||||
),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
onChanged: (_) => setState(() {}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _dateField(_ItemRow item) {
|
||||
return TextFormField(
|
||||
controller: item.productionDateCtrl,
|
||||
readOnly: true,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '请选择',
|
||||
labelText: '* 生产日期',
|
||||
isDense: true,
|
||||
suffixIcon: Icon(Icons.calendar_today, size: 14),
|
||||
),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
onTap: () async {
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: item.productionDate ?? DateTime.now(),
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2100),
|
||||
locale: const Locale('zh', 'CN'),
|
||||
);
|
||||
if (date != null) {
|
||||
setState(() {
|
||||
item.productionDate = date;
|
||||
item.productionDateCtrl.text =
|
||||
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
int _specQtyOf(_ItemRow item) =>
|
||||
ref.read(productSpecListProvider).valueOrNull
|
||||
?.where((o) => o.id == item.selectedSpecId)
|
||||
.firstOrNull
|
||||
?.quantity ??
|
||||
0;
|
||||
|
||||
String _productCodeOf(_ItemRow item) =>
|
||||
ref.read(productNameListProvider).valueOrNull
|
||||
?.where((o) => o.id == item.selectedNameId)
|
||||
.firstOrNull
|
||||
?.code ??
|
||||
'';
|
||||
|
||||
TableRow _buildItemRow(int index) {
|
||||
final item = _items[index];
|
||||
final asyncNames = ref.watch(productNameListProvider);
|
||||
final asyncSeries = ref.watch(productSeriesListProvider);
|
||||
final asyncSpecs = ref.watch(productSpecListProvider);
|
||||
final qty = double.tryParse(item.qtyCtrl.text) ?? 0;
|
||||
final price = double.tryParse(item.priceCtrl.text) ?? 0;
|
||||
final amount = qty * price;
|
||||
final specQty = asyncSpecs.valueOrNull
|
||||
?.where((o) => o.id == item.selectedSpecId)
|
||||
.firstOrNull
|
||||
?.quantity ?? 0;
|
||||
final productCode = asyncNames.valueOrNull
|
||||
?.where((o) => o.id == item.selectedNameId)
|
||||
.firstOrNull
|
||||
?.code ?? '';
|
||||
final specQty = _specQtyOf(item);
|
||||
final productCode = _productCodeOf(item);
|
||||
|
||||
return TableRow(
|
||||
decoration: BoxDecoration(
|
||||
color: index.isEven ? Colors.white : const Color(0xFFFAFAFA),
|
||||
),
|
||||
children: [
|
||||
// 序号
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
|
||||
child: Text('${index + 1}',
|
||||
style: const TextStyle(fontSize: 13, color: AppTheme.textSecondary)),
|
||||
),
|
||||
// 商品编码
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
|
||||
child: Text(productCode,
|
||||
style: const TextStyle(fontSize: 12, color: AppTheme.textSecondary)),
|
||||
),
|
||||
// 名称
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: asyncNames.when(
|
||||
loading: () => const LinearProgressIndicator(),
|
||||
error: (_, __) => const Text('加载失败'),
|
||||
data: (names) => SearchableOptionField(
|
||||
options: names
|
||||
.map((o) => OptionItem(id: o.id, name: o.name, code: o.code))
|
||||
.toList(),
|
||||
selectedId: item.selectedNameId,
|
||||
hint: '选择名称',
|
||||
dialogTitle: '选择商品名称',
|
||||
isRequired: true,
|
||||
onChanged: (v) => setState(() {
|
||||
item.selectedNameId = v;
|
||||
item.productId = null;
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 系列
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: asyncSeries.when(
|
||||
loading: () => const LinearProgressIndicator(),
|
||||
error: (_, __) => const Text('加载失败'),
|
||||
data: (series) => SearchableOptionField(
|
||||
options: series
|
||||
.map((o) => OptionItem(id: o.id, name: o.name, code: o.code))
|
||||
.toList(),
|
||||
selectedId: item.selectedSeriesId,
|
||||
hint: '选择系列',
|
||||
dialogTitle: '选择系列',
|
||||
isRequired: true,
|
||||
onChanged: (v) => setState(() {
|
||||
item.selectedSeriesId = v;
|
||||
item.productId = null;
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 规格
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: asyncSpecs.when(
|
||||
loading: () => const LinearProgressIndicator(),
|
||||
error: (_, __) => const Text('加载失败'),
|
||||
data: (specs) => SearchableOptionField(
|
||||
options: specs
|
||||
.map((o) => OptionItem(id: o.id, name: o.name, code: o.code))
|
||||
.toList(),
|
||||
selectedId: item.selectedSpecId,
|
||||
hint: '选择规格',
|
||||
dialogTitle: '选择规格',
|
||||
isRequired: true,
|
||||
onChanged: (v) => setState(() {
|
||||
item.selectedSpecId = v;
|
||||
item.productId = null;
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 单品数量
|
||||
Padding(padding: const EdgeInsets.all(4), child: _nameField(item)),
|
||||
Padding(padding: const EdgeInsets.all(4), child: _seriesField(item)),
|
||||
Padding(padding: const EdgeInsets.all(4), child: _specField(item)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
|
||||
child: Text(
|
||||
@@ -674,46 +811,8 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
// 数量
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: TextFormField(
|
||||
controller: item.qtyCtrl,
|
||||
decoration: const InputDecoration(hintText: '0', isDense: true),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}'))
|
||||
],
|
||||
onChanged: (_) => setState(() {}),
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return '不能为空';
|
||||
if ((double.tryParse(v) ?? 0) <= 0) return '>0';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
// 单价
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: TextFormField(
|
||||
controller: item.priceCtrl,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '0.00', prefixText: '¥', isDense: true),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}'))
|
||||
],
|
||||
onChanged: (_) => setState(() {}),
|
||||
validator: (v) {
|
||||
if (v == null || v.isEmpty) return '不能为空';
|
||||
if ((double.tryParse(v) ?? 0) <= 0) return '>0';
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
// 金额
|
||||
Padding(padding: const EdgeInsets.all(4), child: _qtyField(item)),
|
||||
Padding(padding: const EdgeInsets.all(4), child: _priceField(item)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
|
||||
child: Text(
|
||||
@@ -721,52 +820,8 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
// 批次号
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: TextFormField(
|
||||
controller: item.batchNoCtrl,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '选填',
|
||||
labelText: '批次号',
|
||||
isDense: true,
|
||||
),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
),
|
||||
// 生产日期
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: TextFormField(
|
||||
controller: item.productionDateCtrl,
|
||||
readOnly: true,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '请选择',
|
||||
labelText: '* 生产日期',
|
||||
isDense: true,
|
||||
suffixIcon: Icon(Icons.calendar_today, size: 14),
|
||||
),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
onTap: () async {
|
||||
final date = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: item.productionDate ?? DateTime.now(),
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2100),
|
||||
locale: const Locale('zh', 'CN'),
|
||||
);
|
||||
if (date != null) {
|
||||
setState(() {
|
||||
item.productionDate = date;
|
||||
item.productionDateCtrl.text =
|
||||
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
// 操作
|
||||
Padding(padding: const EdgeInsets.all(4), child: _batchField(item)),
|
||||
Padding(padding: const EdgeInsets.all(4), child: _dateField(item)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
|
||||
child: IconButton(
|
||||
@@ -781,6 +836,38 @@ class _StockInFormScreenState extends ConsumerState<StockInFormScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
/// 窄屏(手机):每个明细行渲染为一张卡片,字段竖排,避免表格横向溢出。
|
||||
Widget _buildItemCard(int index) {
|
||||
final item = _items[index];
|
||||
final qty = double.tryParse(item.qtyCtrl.text) ?? 0;
|
||||
final price = double.tryParse(item.priceCtrl.text) ?? 0;
|
||||
final amount = qty * price;
|
||||
final specQty = _specQtyOf(item);
|
||||
final productCode = _productCodeOf(item);
|
||||
|
||||
return MobileListCard(
|
||||
title: Text('商品 ${index + 1}'),
|
||||
subtitle: productCode.isNotEmpty ? Text('编码 $productCode') : null,
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete_outline, size: 20, color: AppTheme.danger),
|
||||
onPressed: _items.length > 1 ? () => _removeItem(index) : null,
|
||||
tooltip: '删除',
|
||||
visualDensity: VisualDensity.compact,
|
||||
),
|
||||
fields: [
|
||||
MobileCardField('名称', null, valueWidget: _nameField(item)),
|
||||
MobileCardField('系列', null, valueWidget: _seriesField(item)),
|
||||
MobileCardField('规格', null, valueWidget: _specField(item)),
|
||||
MobileCardField('单品数量', specQty > 0 ? '$specQty' : '-'),
|
||||
MobileCardField('数量', null, valueWidget: _qtyField(item)),
|
||||
MobileCardField('单价', null, valueWidget: _priceField(item)),
|
||||
MobileCardField('金额', '¥${amount.toStringAsFixed(2)}'),
|
||||
MobileCardField('批次号', null, valueWidget: _batchField(item)),
|
||||
MobileCardField('生产日期', null, valueWidget: _dateField(item)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInventoryCell(int? productId) {
|
||||
if (productId == null) {
|
||||
return const Padding(
|
||||
|
||||
Reference in New Issue
Block a user