diff --git a/inventory-backend/app/api/v1/purchase.py b/inventory-backend/app/api/v1/purchase.py
index aa68fc7..d52ba4e 100644
--- a/inventory-backend/app/api/v1/purchase.py
+++ b/inventory-backend/app/api/v1/purchase.py
@@ -74,6 +74,11 @@ def create_purchase_request():
if field not in data or str(data.get(field, '')).strip() == '':
return jsonify({'code': 400, 'msg': f'缺少必填字段: {field}'}), 400
+ # 图片必填强校验
+ images = data.get('images')
+ if not images or (isinstance(images, list) and len(images) == 0):
+ return jsonify({'code': 400, 'msg': '请上传采购凭证/物品图片'}), 400
+
purchase = PurchaseService.create_purchase_request(data, requester_id=user_id)
return jsonify({
@@ -200,3 +205,29 @@ def auto_fill_purchase():
return jsonify({'code': 200, 'msg': 'ok', 'data': result}), 200
except Exception as e:
return jsonify({'code': 500, 'msg': str(e)}), 500
+
+
+# --------------------------------------------------------
+# 7. 物料基础信息搜索(分页)
+# GET /api/v1/purchase/search-material?keyword=xxx&page=1
+# --------------------------------------------------------
+@purchase_bp.route('/search-material', methods=['GET'])
+@jwt_required()
+def search_material_for_purchase():
+ """物料基础信息搜索接口,支持分页,用于采购申请弹窗"""
+ try:
+ keyword = request.args.get('keyword', '')
+ page = request.args.get('page', 1, type=int)
+ limit = 20
+
+ result = PurchaseService.search_base_material(keyword, page, limit)
+ return jsonify({
+ 'code': 200,
+ 'msg': 'success',
+ 'data': result['items'],
+ 'total': result['total'],
+ 'has_next': result['has_next']
+ }), 200
+ except Exception as e:
+ traceback.print_exc()
+ return jsonify({'code': 500, 'msg': str(e)}), 500
diff --git a/inventory-backend/app/services/purchase_service.py b/inventory-backend/app/services/purchase_service.py
index b9f7a28..41befd1 100644
--- a/inventory-backend/app/services/purchase_service.py
+++ b/inventory-backend/app/services/purchase_service.py
@@ -138,6 +138,49 @@ class PurchaseService:
purchase = db.session.get(PurchaseRequest, purchase_id)
return purchase.to_dict() if purchase else None
+ @staticmethod
+ def search_base_material(keyword: str, page: int = 1, limit: int = 20):
+ """
+ 物料基础信息搜索,支持 name/spec_model/company_name 模糊匹配,返回分页结果
+ 用于采购申请弹窗的物料远程搜索
+ """
+ from sqlalchemy import and_, or_
+
+ query = MaterialBase.query.filter(MaterialBase.is_enabled == True)
+
+ if keyword:
+ k = keyword.strip()
+ k_str = f'%{k}%'
+ query = query.filter(or_(
+ MaterialBase.name.ilike(k_str),
+ MaterialBase.spec_model.ilike(k_str),
+ MaterialBase.company_name.ilike(k_str)
+ ))
+
+ query = query.order_by(MaterialBase.id.desc())
+ pagination = query.paginate(page=page, per_page=limit, error_out=False)
+
+ items = []
+ for item in pagination.items:
+ items.append({
+ 'id': item.id,
+ 'company_name': item.company_name,
+ 'name': item.name,
+ 'spec_model': item.spec_model,
+ 'category': item.category,
+ 'unit': item.unit,
+ 'type': item.material_type,
+ 'pinyin': getattr(item, 'pinyin', ''),
+ 'status': '启用'
+ })
+
+ return {
+ 'items': items,
+ 'total': pagination.total,
+ 'page': page,
+ 'has_next': pagination.has_next
+ }
+
@staticmethod
def _notify_new_request(purchase):
"""发送新申请邮件给审批人"""
diff --git a/inventory-web/src/api/purchase.ts b/inventory-web/src/api/purchase.ts
index 610577f..f16b957 100644
--- a/inventory-web/src/api/purchase.ts
+++ b/inventory-web/src/api/purchase.ts
@@ -89,3 +89,12 @@ export function autoFillPurchase(keyword: string) {
params: { keyword }
})
}
+
+// 物料基础信息搜索(分页),用于采购申请弹窗
+export function searchMaterialPurchase(keyword: string, page: number = 1) {
+ return request({
+ url: '/purchase/search-material',
+ method: 'get',
+ params: { keyword, page }
+ })
+}
diff --git a/inventory-web/src/views/purchase/index.vue b/inventory-web/src/views/purchase/index.vue
index a7b9cec..bb8df41 100644
--- a/inventory-web/src/views/purchase/index.vue
+++ b/inventory-web/src/views/purchase/index.vue
@@ -83,6 +83,8 @@
@change="onMaterialSelected"
default-first-option
popper-class="long-dropdown"
+ v-loadmore="handleLoadMoreMaterials"
+ @visible-change="onMaterialDropdownVisibleChange"
>