feat(client): 登录/注册页照原型重建,ds 真相源组件族统一全部屏
- 登录/注册(login.html/register.html 1:1):两栏卡片+品牌渐变面板+主题小衣服 pill(onSurface 变体)+记住我(记录并预填最近账号)+原型式 toast 校验; 登录 fidelity 1.4–2.1% 三主题全绿;注册暂不入闸(少 门店编号/兑换券 字段, 已记 CONTRACT,screens.mjs 留存根) - ds 原子补齐:DsToast(.toast 单例)/DsCheck(.check/.agree)/DsButton lg 档/ DsSelect 替换全部旧 DropdownButton/DsField label 在上/DsInput 后缀与密码形态 - 全屏统一:对话框按钮全 DsButton、盒式输入主题钉死(visualDensity.standard、 h38、InputDecorationTheme 渗漏修复)、图标全 lucide、JetBrains Mono 三端同源、 BrandMark 真相源 logo、只读模式写操作全量守卫(WriteGuard+DsToast) - 出入库列表:版式对齐原型(卡片对齐+搜索框居中)、KPI 近30天滚动、结清后 失效财务应收应付表;商品编辑抽屉介绍库改搜索下拉、图片双击全屏预览 - 删除旧 UI 死代码:DataTableCard/FormDialog/PageScaffold/SearchChip/ SelectProductDialog/tabStateProvider - golden/fidelity:stock-in/out 补注册(9%)、login 入册(8%)、goldens 全量重打; 修复 pubspec flutter_web_plugins 非法声明(CI pub get 阻断) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@@ -8,6 +9,9 @@ import '../../models/warehouse.dart';
|
||||
import '../../core/config/app_constants.dart';
|
||||
import '../../providers/inventory_provider.dart';
|
||||
import '../../providers/warehouse_provider.dart';
|
||||
import '../../core/theme/app_fonts.dart';
|
||||
import '../../widgets/ds/ds_atoms.dart';
|
||||
import '../../widgets/ds/ds_toast.dart';
|
||||
|
||||
class InventoryCheckScreen extends ConsumerStatefulWidget {
|
||||
const InventoryCheckScreen({super.key});
|
||||
@@ -50,10 +54,8 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
_checkItems.clear();
|
||||
});
|
||||
try {
|
||||
final result = await ref
|
||||
.read(inventoryRepositoryProvider)
|
||||
.listInventory(
|
||||
warehouseId: wh.id, pageSize: AppConstants.inventoryCheckPageSize);
|
||||
final result = await ref.read(inventoryRepositoryProvider).listInventory(
|
||||
warehouseId: wh.id, pageSize: AppConstants.inventoryCheckPageSize);
|
||||
final items = result.data
|
||||
.where((inv) => inv.productId != null)
|
||||
.map((inv) => _CheckItem(inventory: inv))
|
||||
@@ -67,19 +69,14 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() => _loadingItems = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('加载库存失败:$e'),
|
||||
backgroundColor: context.tokens.danger),
|
||||
);
|
||||
showDsToast(context, '加载库存失败:$e', bg: context.tokens.danger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
if (_selectedWarehouse == null) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(const SnackBar(content: Text('请先选择仓库')));
|
||||
showDsToast(context, '请先选择仓库');
|
||||
return;
|
||||
}
|
||||
setState(() => _submitting = true);
|
||||
@@ -106,21 +103,12 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
});
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('盘点单已提交'),
|
||||
backgroundColor: context.tokens.success,
|
||||
),
|
||||
);
|
||||
showDsToast(context, '盘点单已提交', bg: context.tokens.success);
|
||||
context.go('/inventory');
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('提交失败:$e'),
|
||||
backgroundColor: context.tokens.danger),
|
||||
);
|
||||
showDsToast(context, '提交失败:$e', bg: context.tokens.danger);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _submitting = false);
|
||||
@@ -149,13 +137,13 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back, size: 20),
|
||||
icon: const Icon(LucideIcons.arrowLeft, size: 20),
|
||||
onPressed: () => context.go('/inventory'),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text('库存盘点',
|
||||
style: TextStyle(
|
||||
fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
style:
|
||||
TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
const Spacer(),
|
||||
OutlinedButton(
|
||||
onPressed: () => context.go('/inventory'),
|
||||
@@ -163,16 +151,16 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ElevatedButton.icon(
|
||||
onPressed: (_submitting || _checkItems.isEmpty)
|
||||
? null
|
||||
: _submit,
|
||||
onPressed:
|
||||
(_submitting || _checkItems.isEmpty) ? null : _submit,
|
||||
icon: _submitting
|
||||
? const SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2, color: Colors.white))
|
||||
: const Icon(Icons.check_circle_outline, size: 16),
|
||||
strokeWidth: 2,
|
||||
color: Colors.white)) // ds-ignore: 盘点屏待重建批次统一
|
||||
: const Icon(LucideIcons.circleCheck, size: 16),
|
||||
label: const Text('提交盘点'),
|
||||
),
|
||||
],
|
||||
@@ -208,7 +196,9 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
child: Text(
|
||||
_checkNo,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: AppFonts.mono,
|
||||
fontFamilyFallback:
|
||||
AppFonts.monoFallback,
|
||||
fontSize: 13),
|
||||
),
|
||||
),
|
||||
@@ -222,40 +212,30 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
style: TextStyle(
|
||||
color: context.tokens.danger,
|
||||
fontSize: 12)),
|
||||
data: (warehouses) =>
|
||||
DropdownButtonFormField<Warehouse>(
|
||||
data: (warehouses) => DsSelect<Warehouse>(
|
||||
value: _selectedWarehouse,
|
||||
hint: const Text('请选择仓库'),
|
||||
items: warehouses
|
||||
.map((w) => DropdownMenuItem(
|
||||
value: w,
|
||||
child: Text(w.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 13))))
|
||||
.toList(),
|
||||
hint: '请选择仓库',
|
||||
options: [
|
||||
for (final w in warehouses) (w, w.name),
|
||||
],
|
||||
onChanged: (w) {
|
||||
setState(
|
||||
() => _selectedWarehouse = w);
|
||||
if (w != null) _loadInventory(w);
|
||||
setState(() => _selectedWarehouse = w);
|
||||
_loadInventory(w);
|
||||
},
|
||||
decoration: const InputDecoration(),
|
||||
),
|
||||
),
|
||||
),
|
||||
_InfoField(
|
||||
label: '盘点类型',
|
||||
child: DropdownButtonFormField<String>(
|
||||
child: DsSelect<String>(
|
||||
value: _checkType,
|
||||
items: ['全盘', '抽盘', '循环盘点']
|
||||
.map((s) => DropdownMenuItem(
|
||||
value: s,
|
||||
child: Text(s,
|
||||
style: const TextStyle(
|
||||
fontSize: 13))))
|
||||
.toList(),
|
||||
options: const [
|
||||
('全盘', '全盘'),
|
||||
('抽盘', '抽盘'),
|
||||
('循环盘点', '循环盘点'),
|
||||
],
|
||||
onChanged: (v) =>
|
||||
setState(() => _checkType = v!),
|
||||
decoration: const InputDecoration(),
|
||||
setState(() => _checkType = v),
|
||||
),
|
||||
),
|
||||
_InfoField(
|
||||
@@ -266,8 +246,7 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
final now = DateTime.now();
|
||||
return Text(
|
||||
'${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}',
|
||||
style:
|
||||
const TextStyle(fontSize: 13),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
);
|
||||
}),
|
||||
),
|
||||
@@ -301,8 +280,7 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
context.tokens.accent.withOpacity(0.1),
|
||||
borderRadius:
|
||||
BorderRadius.circular(3),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(
|
||||
'差异 ${_checkItems.where((i) => _getDiff(i) != 0).length} 项',
|
||||
@@ -319,8 +297,8 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Text('请先选择盘点仓库',
|
||||
style: TextStyle(
|
||||
color: context.tokens.muted)),
|
||||
style:
|
||||
TextStyle(color: context.tokens.muted)),
|
||||
),
|
||||
)
|
||||
else if (_loadingItems)
|
||||
@@ -335,8 +313,8 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Text('该仓库暂无库存记录',
|
||||
style: TextStyle(
|
||||
color: context.tokens.muted)),
|
||||
style:
|
||||
TextStyle(color: context.tokens.muted)),
|
||||
),
|
||||
)
|
||||
else
|
||||
@@ -353,8 +331,8 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
},
|
||||
children: [
|
||||
TableRow(
|
||||
decoration: BoxDecoration(
|
||||
color: context.tokens.thBg),
|
||||
decoration:
|
||||
BoxDecoration(color: context.tokens.thBg),
|
||||
children: [
|
||||
'序号',
|
||||
'商品编码',
|
||||
@@ -366,21 +344,18 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
'备注',
|
||||
]
|
||||
.map((h) => Padding(
|
||||
padding: const EdgeInsets
|
||||
.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8, vertical: 10),
|
||||
child: Text(h,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight:
|
||||
FontWeight.w600,
|
||||
color: context.tokens.primaryDark)),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: context
|
||||
.tokens.primaryDark)),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
...List.generate(
|
||||
_checkItems.length,
|
||||
...List.generate(_checkItems.length,
|
||||
(i) => _buildCheckRow(i)),
|
||||
],
|
||||
),
|
||||
@@ -448,37 +423,32 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
),
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
child: Text('${index + 1}',
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: context.tokens.muted)),
|
||||
style: TextStyle(fontSize: 13, color: context.tokens.muted)),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
child: Text(inv.productCode.isEmpty ? '-' : inv.productCode,
|
||||
style: TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontFamily: AppFonts.mono,
|
||||
fontFamilyFallback: AppFonts.monoFallback,
|
||||
fontSize: 12,
|
||||
color: context.tokens.muted)),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
child: Text(inv.productName.isEmpty ? '-' : inv.productName,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
overflow: TextOverflow.ellipsis),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
child: Text(inv.unit.isEmpty ? '-' : inv.unit,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
child: Text(inv.quantity.toStringAsFixed(0),
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
),
|
||||
@@ -494,8 +464,7 @@ class _InventoryCheckScreenState extends ConsumerState<InventoryCheckScreen> {
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
|
||||
child: Text(
|
||||
diff == 0 ? '0' : (diff > 0 ? '+$diff' : '$diff'),
|
||||
style: TextStyle(
|
||||
@@ -526,8 +495,8 @@ class _CheckItem {
|
||||
final TextEditingController remarkCtrl;
|
||||
|
||||
_CheckItem({required this.inventory})
|
||||
: actualQtyCtrl = TextEditingController(
|
||||
text: inventory.quantity.toStringAsFixed(0)),
|
||||
: actualQtyCtrl =
|
||||
TextEditingController(text: inventory.quantity.toStringAsFixed(0)),
|
||||
remarkCtrl = TextEditingController();
|
||||
|
||||
void dispose() {
|
||||
@@ -550,8 +519,7 @@ class _InfoField extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(label,
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: context.tokens.muted)),
|
||||
style: TextStyle(fontSize: 13, color: context.tokens.muted)),
|
||||
const SizedBox(height: 6),
|
||||
child,
|
||||
],
|
||||
@@ -573,14 +541,11 @@ class _SummaryItem extends StatelessWidget {
|
||||
return Row(
|
||||
children: [
|
||||
Text(label,
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: context.tokens.muted)),
|
||||
style: TextStyle(fontSize: 13, color: context.tokens.muted)),
|
||||
const SizedBox(width: 4),
|
||||
Text(value,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color)),
|
||||
fontSize: 14, fontWeight: FontWeight.w600, color: color)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user