stock_out_form_screen.dart)· 扫码枪扫商品 → 明细自动 +1 · 2026-08-25 · 待批准?code={商品编码},而本页 _loadInventory 已把整仓库存(≤1000 个,stockOutPickerPageSize=1000)
聚合进本地 _inventoryPickerItems(每条含 productCode)——扫码→解析 code→本地命中→加行,零网络、零后端改动。
扫码枪 = HID 键盘:扫到内容当作键盘输入敲进当前聚焦的输入框、末尾补回车。本页放一个聚焦的「扫码框」承接即可,零摄像头依赖。
扫到的是 URL https://…/app/product/{public_id}?code={code};我们取其中的 code 与整仓已加载的库存做精确匹配。
数量口径 = A(每扫 +1,已定)。注意:与「从库存选择」默认带出整箱可用量不同,扫码是按扫的次数点件数,故首扫为 1、再扫累加。数量列仍可手动改。
stock_out_form_screen.dart)| 改动点 | 具体 |
|---|---|
| 本地 code 索引 | _loadInventory 里,聚合完 _inventoryPickerItems 后顺手建 _codeIndex = { for it in items : it.productCode : it }(productCode 非空才入)。 |
| 扫码框 | 明细头 DetailHead(actions:[…]) 里,在「从库存选择」左侧加一个紧凑 DsInput(图标 LucideIcons.scanLine,占位「扫码枪扫码…」)+ 独立 FocusNode _scanFocus,桌面端 autofocus。窄屏(context.isMobile)不显示(扫码枪是桌面外设)。 |
| _onScan(raw) | 新增方法:无仓库→toast「请先选择出库仓库」;解析 code;查 _codeIndex;命中则 _addOrBumpByScan(item);未命中红 toast。末尾 _scanCtrl.clear() + _scanFocus.requestFocus()。 |
| _addOrBumpByScan(item) | 在 _items 找 productId==item.productId:有→qtyCtrl.text = (now+1);无→复用 _addItem 的建行逻辑但 qty 固定 1。setState 刷新。超可用量(>availableQty)→黄字 toast 提醒但不拦。 |
| 去重防抖 | 记 _lastScan(code,ts),同 code 300ms 内重复直接忽略(防扳机抖动连发)。 |
| 提示音 | 成功 SystemSound.play(SystemSoundType.click);失败可 HapticFeedback/仅红 toast。轻量,主反馈仍是 toast(操作员眼在货上)。 |
| 清理 | dispose() 里释放 _scanCtrl/_scanFocus。 |
解析函数抽成纯函数 String? parseScanCode(String raw)(放本文件或 util),便于单测:覆盖 /app/product/x?code=1、/product/x?code=1、纯 code、脏串。
不新增依赖:不引 mobile_scanner/camera,pubspec.yaml 不动。
按项目铁律「原型与真实 100% 一致,前端改动必须先改原型」:在 design/prototype/screens/stock-out.html 的明细头,与「从库存选择」并排加一个扫码输入框原件(用登记过的 atoms,占位「扫码枪扫码…」)。评审原型 → 代码随后落地 → 原型与代码同提交。若涉及新图标 scan-line 未登记,先在 index.html + icons.js 登记再用(L1 单源)。
后端 product.go:290 生成 URL 用的是 /app/product/,与 SSR 路由 /product/ 不一致。改成 /product/ 统一(已定)。对本方案非必需(我们只取 ?code=,不依赖路径段),但顺手消歧、利于将来按 public_id 解析。属 server 侧一行改动,随下次 server 发版生效。
本地 code 索引覆盖整仓 ≤1000 个商品,绝大多数门店够用。但两种情况本地会漏:①单仓商品 >1000(超分页);②某商品无 code(QR 只有 public_id,则无 ?code=)。
| 方案 | 范围 | 建议 |
|---|---|---|
| A · 仅前端(本地匹配) | 零后端改动、零部署,扫→加立即生效;漏网情况给红 toast 提示改用「从库存选择」 | 推荐先做,最快落地 |
B · 加后端 GET /inventory/scan | 本地未命中时回退查后端(精确 by code/public_id、shop 隔离、排除 sold),覆盖 >1000 与 public_id-only;需 server 发版 | 作 Phase-2,等确有大仓再加 |
我的建议:先做 A(纯前端) 把这页跑通,B 留作后续。你也可以要求一步到位做 A+B。
…/app/product/xxx?code=P1234)再按回车——效果与扫码枪 100% 等价。看是否新增行、再粘同一条是否 +1、粘不存在的 code 是否红 toast。flutter test 覆盖 parseScanCode 各输入。flutter analyze --no-fatal-infos --no-fatal-warnings 无 error;flutter test 全过。design/prototype/screens/stock-out.html 加扫码框(+ 必要时 index.html/icons.js 登记 scan-line 图标)client/lib/screens/stock_out/stock_out_form_screen.dart —— _codeIndex / 扫码框 / _onScan / _addOrBumpByScan / 防抖 / dispose;抽 parseScanCodeclient/test/…/parse_scan_code_test.dart 新增inventory.go + router 加 GET /inventory/scanproduct.go:290 路径 /app/product/ → /product//release client落地评级:方案 A 为前端单文件 + 原型 + 测试,属中等改动;方案 B 追加后端接口即升为跨模块大改。均遵守 design-first。本文件仅计划评审,你批准后再实现。
/app/product/ → /product/ 这次一并改吗?(已定要改,确认随本次一起)