From 93dc375ba495b2b07a37b786bceafa0a08f0a2d3 Mon Sep 17 00:00:00 2001 From: DXC Date: Mon, 23 Mar 2026 14:52:01 +0800 Subject: [PATCH] perf: replace client-side pagination with server-side pagination in stock selection dialog and fix duplicate variable in semi.vue --- inventory-backend/app/api/v1/inbound/stock.py | 132 ++++++++++++++++-- inventory-web/src/api/inbound/stock.ts | 9 ++ .../src/views/outbound/Selection.vue | 84 +++++++---- 3 files changed, 180 insertions(+), 45 deletions(-) diff --git a/inventory-backend/app/api/v1/inbound/stock.py b/inventory-backend/app/api/v1/inbound/stock.py index 0525fa5..8c2469c 100644 --- a/inventory-backend/app/api/v1/inbound/stock.py +++ b/inventory-backend/app/api/v1/inbound/stock.py @@ -1,6 +1,5 @@ -from flask import Blueprint, jsonify, request, send_file +from flask import Blueprint, jsonify, request, send_file, current_app from app.extensions import db, beijing_time -# ★★★ 修复点:必须引入 datetime,否则下方更新时间时会报错 500 ★★★ from datetime import datetime, timedelta from flask_jwt_extended import jwt_required from app.utils.decorators import permission_required @@ -40,18 +39,6 @@ def _normalize_user_id(user_id): return user_id.strip() -# 尝试导入半成品和成品 -try: - from app.models.inbound.semi import StockSemi -except ImportError: - StockSemi = None - -try: - from app.models.inbound.product import StockProduct -except ImportError: - StockProduct = None - - def get_stock_model(source_table): """根据source_table获取对应的库存模型""" if source_table == 'stock_buy': @@ -151,6 +138,123 @@ def get_all_stock(): return jsonify({"message": f"查询库存失败: {str(e)}"}), 500 +# ============================================================================== +# 分页库存查询接口(服务端分页,出库/盘点/借用模块共用) +# ============================================================================== +@bp.route('/list', methods=['GET']) +@jwt_required() +def get_stock_list(): + """ + 分页获取库存列表(stock_quantity > 0) + 参数: + page - 页码(默认 1) + pageSize - 每页条数(默认 20) + keyword - 搜索关键字(模糊匹配名称/规格/SKU) + """ + try: + page = request.args.get('page', 1, type=int) + pageSize = request.args.get('pageSize', 20, type=int) + keyword = request.args.get('keyword', '', type=str).strip() + + if page < 1: + page = 1 + if pageSize < 1 or pageSize > 200: + pageSize = 20 + + all_items = [] + + # 1. 采购件 + if StockBuy: + q = StockBuy.query.filter(StockBuy.stock_quantity > 0) + if keyword: + q = q.filter( + db.or_( + StockBuy.material_name.ilike(f'%{keyword}%'), + StockBuy.spec_model.ilike(f'%{keyword}%'), + StockBuy.sku.ilike(f'%{keyword}%') + ) + ) + rows = q.all() + for item in rows: + d = item.to_dict() + d['stock_type'] = 'material' + d['type'] = 'material' + d['typeLabel'] = '采购件' + d['name'] = d.get('material_name', d.get('name', '')) + d['standard'] = d.get('spec_model', d.get('standard', '')) + d['available_quantity'] = d.get('qty_available', d.get('available_quantity', 0)) + all_items.append(d) + + # 2. 半成品 + if StockSemi: + try: + q = StockSemi.query.filter(StockSemi.stock_quantity > 0) + if keyword: + q = q.filter( + db.or_( + StockSemi.material_name.ilike(f'%{keyword}%'), + StockSemi.spec_model.ilike(f'%{keyword}%'), + StockSemi.sku.ilike(f'%{keyword}%') + ) + ) + rows = q.all() + for item in rows: + d = item.to_dict() + d['stock_type'] = 'semi' + d['type'] = 'semi' + d['typeLabel'] = '半成品' + d['name'] = d.get('material_name', d.get('name', '')) + d['standard'] = d.get('spec_model', d.get('standard', '')) + d['available_quantity'] = d.get('qty_available', d.get('available_quantity', 0)) + all_items.append(d) + except Exception: + pass + + # 3. 成品 + if StockProduct: + try: + q = StockProduct.query.filter(StockProduct.stock_quantity > 0) + if keyword: + q = q.filter( + db.or_( + StockProduct.product_name.ilike(f'%{keyword}%'), + StockProduct.spec_model.ilike(f'%{keyword}%'), + StockProduct.sku.ilike(f'%{keyword}%') + ) + ) + rows = q.all() + for item in rows: + d = item.to_dict() + d['stock_type'] = 'product' + d['type'] = 'product' + d['typeLabel'] = '成品' + d['name'] = d.get('product_name', d.get('name', '')) + d['standard'] = d.get('spec_model', d.get('standard', '')) + d['available_quantity'] = d.get('qty_available', d.get('available_quantity', 0)) + all_items.append(d) + except Exception: + pass + + total = len(all_items) + start = (page - 1) * pageSize + end = start + pageSize + paged = all_items[start:end] + + return jsonify({ + 'msg': '获取成功', + 'data': { + 'list': paged, + 'total': total, + 'page': page, + 'pageSize': pageSize + } + }), 200 + + except Exception as e: + current_app.logger.error(f"Get Stock List Failed: {str(e)}") + return jsonify({'msg': f'获取库存列表失败: {str(e)}'}), 500 + + # --- 草稿箱接口 --- @bp.route('/draft/list', methods=['GET']) diff --git a/inventory-web/src/api/inbound/stock.ts b/inventory-web/src/api/inbound/stock.ts index bb74985..4e0935d 100644 --- a/inventory-web/src/api/inbound/stock.ts +++ b/inventory-web/src/api/inbound/stock.ts @@ -10,6 +10,15 @@ export function getAllStock() { }) } +// 分页库存列表(服务端分页,支持关键字搜索) +export function getStockList(params: { page?: number; pageSize?: number; keyword?: string }) { + return request({ + url: '/v1/inbound/stock/list', + method: 'get', + params + }) +} + // 打印出库选单 // 修改后: 去掉开头的 /api export function printSelectionList(items: any[]) { diff --git a/inventory-web/src/views/outbound/Selection.vue b/inventory-web/src/views/outbound/Selection.vue index e98de10..50fdb01 100644 --- a/inventory-web/src/views/outbound/Selection.vue +++ b/inventory-web/src/views/outbound/Selection.vue @@ -106,7 +106,8 @@