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:
wangjia
2026-07-03 09:58:14 +08:00
parent ca7595b113
commit 6238b86dcb
286 changed files with 16831 additions and 11136 deletions
@@ -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