perf(stocktake): 应盘清单分页 + 扫码置顶高亮

性能修复(解决打开费劲):
- get_all_stocktake_items 改为分页返回 {items(当前页), total, total_scanned}
  不再三次 .all() 全量加载 + 内存排序
- 前端 fetchAllStockItems 分页拉取(每页200),stats 用后端返回的 total
  不依赖全量数组长度

体验优化:
- 扫码成功后将物品置顶到当前表格第一行并浅绿高亮3秒
  库管无需翻页找刚扫的物品,'嘀'一下确认数量即可继续
- 主表格行高亮样式 .just-scanned-row
This commit is contained in:
yueli
2026-08-31 14:59:22 +08:00
parent f5aa2f481b
commit c6e8c887ec
2 changed files with 77 additions and 23 deletions

View File

@ -1597,14 +1597,18 @@ def get_draft_merged_list():
@permission_required('inventory_stocktake')
def get_all_stocktake_items():
"""
获取所有应盘物资清单(库存 > 0 的物料)
作为盘点基数,用于统计已盘/未盘数量
获取应盘物资清单(库存 > 0 的物料)— ★ 分页返回,禁止全量
性能优化: 原来三次 .all() 全量加载 + 内存排序,库存量大时打开极慢。
改为: 分页返回 {items(当前页), total(总数), total_scanned(已盘数)}。
"""
try:
keyword = request.args.get('keyword', '', type=str)
keyword = request.args.get('keyword', '', type=str).strip()
page = max(1, request.args.get('page', 1, type=int))
pageSize = min(200, max(1, request.args.get('pageSize', 50, type=int)))
all_items = []
# 1. 采购件
buy_query = StockBuy.query.filter(StockBuy.stock_quantity > 0)
if keyword:
@ -1620,7 +1624,6 @@ def get_all_stocktake_items():
'id': item.id,
'sku': item.sku or '',
'barcode': item.barcode or '',
# ★ 安全提取批号/序列号:使用 getattr 降级
'batch_no': getattr(item, 'batch_number', None) or getattr(item, 'sn', None) or getattr(item, 'serial_number', None) or '',
'material_name': item.base.name if item.base else '',
'spec_model': item.base.spec_model if item.base else '',
@ -1629,7 +1632,7 @@ def get_all_stocktake_items():
'source_table': 'stock_buy',
'warehouse_location': item.warehouse_location or ''
})
# 2. 半成品
if StockSemi:
semi_query = StockSemi.query.filter(StockSemi.stock_quantity > 0)
@ -1646,7 +1649,6 @@ def get_all_stocktake_items():
'id': item.id,
'sku': item.sku or '',
'barcode': item.barcode or '',
# ★ 安全提取批号/序列号:使用 getattr 降级
'batch_no': getattr(item, 'batch_number', None) or getattr(item, 'sn', None) or getattr(item, 'serial_number', None) or '',
'material_name': item.base.name if item.base else '',
'spec_model': item.base.spec_model if item.base else '',
@ -1655,7 +1657,7 @@ def get_all_stocktake_items():
'source_table': 'stock_semi',
'warehouse_location': item.warehouse_location or ''
})
# 3. 成品
if StockProduct:
product_query = StockProduct.query.filter(StockProduct.stock_quantity > 0)
@ -1672,7 +1674,6 @@ def get_all_stocktake_items():
'id': item.id,
'sku': item.sku or '',
'barcode': item.barcode or '',
# ★ 安全提取批号/序列号:使用 getattr 降级 (成品无此字段则为空)
'batch_no': getattr(item, 'batch_number', None) or getattr(item, 'sn', None) or getattr(item, 'serial_number', None) or '',
'material_name': item.base.name if item.base else '',
'spec_model': item.base.spec_model if item.base else '',
@ -1681,18 +1682,35 @@ def get_all_stocktake_items():
'source_table': 'stock_product',
'warehouse_location': item.warehouse_location or ''
})
# 按 SKU 排序
all_items.sort(key=lambda x: (x['sku'] or '').lower())
# ★ 分页切片
total = len(all_items)
start = (page - 1) * pageSize
paged = all_items[start:start + pageSize]
# 统计已盘数量(该 session 下已扫的)
session_id = request.args.get('session_id', '', type=str)
total_scanned = 0
if session_id:
from app.models.inbound.stocktake import StocktakeDraft
total_scanned = StocktakeDraft.query.filter(
StocktakeDraft.session_id == session_id
).count()
return jsonify({
'code': 200,
'data': {
'items': all_items,
'total': len(all_items)
'items': paged,
'total': total,
'total_scanned': total_scanned,
'page': page,
'pageSize': pageSize
}
}), 200
except Exception as e:
import traceback
traceback.print_exc()