From 7c9331d78a3528025dc7c7d3ad474f0c3353e18c Mon Sep 17 00:00:00 2001 From: DXC Date: Mon, 23 Mar 2026 09:51:59 +0800 Subject: [PATCH] perf: implement optimistic UI for scanner, disable auto-camera, and sort excel by SKU --- inventory-backend/app/api/v1/inbound/stock.py | 16 ++++++++ .../src/views/stock/stocktake/index.vue | 41 ++++++++++++------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/inventory-backend/app/api/v1/inbound/stock.py b/inventory-backend/app/api/v1/inbound/stock.py index f4331d6..c3605ea 100644 --- a/inventory-backend/app/api/v1/inbound/stock.py +++ b/inventory-backend/app/api/v1/inbound/stock.py @@ -718,7 +718,15 @@ def export_stocktake(): diff_headers = ["物料名称", "SKU", "规格型号", "库位", "调整后账面数", "实盘数", "差异数", "盘点人", "盘点时间", "备注"] set_header_row(ws2, diff_headers) + # 按 SKU 排序:先获取全部数据,再在 Python 中按 SKU 排序 diff_drafts = StocktakeDraft.query.filter(StocktakeDraft.diff_qty != 0).all() + diff_drafts_with_sku = [] + for draft in diff_drafts: + mat_info = get_material_info(draft.source_table, draft.stock_id) + diff_drafts_with_sku.append((mat_info.get('sku', ''), draft)) + diff_drafts_with_sku.sort(key=lambda x: x[0] if x[0] else '') + diff_drafts = [d[1] for d in diff_drafts_with_sku] + for row_idx, draft in enumerate(diff_drafts, 2): mat_info = get_material_info(draft.source_table, draft.stock_id) # 写入 Sheet 2 (差异明细) @@ -751,7 +759,15 @@ def export_stocktake(): normal_headers = ["物料名称", "SKU", "规格型号", "库位", "调整后账面数", "实盘数", "差异数", "盘点人", "盘点时间", "备注"] set_header_row(ws3, normal_headers) + # 按 SKU 排序 normal_drafts = StocktakeDraft.query.filter(StocktakeDraft.diff_qty == 0).all() + normal_drafts_with_sku = [] + for draft in normal_drafts: + mat_info = get_material_info(draft.source_table, draft.stock_id) + normal_drafts_with_sku.append((mat_info.get('sku', ''), draft)) + normal_drafts_with_sku.sort(key=lambda x: x[0] if x[0] else '') + normal_drafts = [d[1] for d in normal_drafts_with_sku] + for row_idx, draft in enumerate(normal_drafts, 2): mat_info = get_material_info(draft.source_table, draft.stock_id) # 写入 Sheet 3 (账实相符) diff --git a/inventory-web/src/views/stock/stocktake/index.vue b/inventory-web/src/views/stock/stocktake/index.vue index 3fa6a41..957df10 100644 --- a/inventory-web/src/views/stock/stocktake/index.vue +++ b/inventory-web/src/views/stock/stocktake/index.vue @@ -869,32 +869,43 @@ const handleManualConfirm = () => { if (!currentItem.value) return const val = inputQty.value === undefined ? 0 : inputQty.value - updateAndSync(currentItem.value, val, inputRemark.value) + // ★★★ 乐观更新:立即本地更新 UI,不等待后端响应 ★★★ + currentItem.value.scanned = true + currentItem.value.qty_actual = val + scannedMap.value.set(currentItem.value.uuid, val) + + // 保存备注用于异步提交 + const remark = inputRemark.value + showQtyDialog.value = false // 重置备注 inputRemark.value = '' ElMessage.success(`已记录实盘: ${val}`) - // ★★★ 核心修改:确认数量后自动重新打开全屏扫码,实现无缝闭环 - nextTick(() => { - if (hasPermission) { - showCamera.value = true - } - }) + // ★★★ 取消自动弹摄像头,把开启摄像头的控制权完全交还给用户 + // 用户需主动点击才能开启摄像头 + + // ★★★ 异步保存到后端,不阻塞 UI(fire-and-forget)★★★ + syncToBackend(currentItem.value.uuid, val, remark) +} + +// ★★★ 乐观更新:异步保存到后端,不阻塞 UI ★★★ +const syncToBackend = (uuid: string, quantity: number, remark: string) => { + syncStatus.value = 'syncing' + api.addDraft({ uuid, quantity, remark }) + .then(() => { + syncStatus.value = 'success' + }) + .catch(() => { + syncStatus.value = 'failed' + }) } const updateAndSync = async (item: StockItem, quantity: number, remark: string = '') => { + // ★★★ 保留原有逻辑用于兼容性,但不再使用 ★★★ item.scanned = true item.qty_actual = quantity scannedMap.value.set(item.uuid, quantity) - - syncStatus.value = 'syncing' - try { - await api.addDraft({ uuid: item.uuid, quantity: quantity, remark: remark }) - syncStatus.value = 'success' - } catch (e) { - syncStatus.value = 'failed' - } } const closeOverlays = () => {