diff --git a/inventory-backend/app/api/v1/inbound/product.py b/inventory-backend/app/api/v1/inbound/product.py
index c276606..9ec367b 100644
--- a/inventory-backend/app/api/v1/inbound/product.py
+++ b/inventory-backend/app/api/v1/inbound/product.py
@@ -78,7 +78,40 @@ def get_list():
category = request.args.get('category', '')
material_type = request.args.get('material_type', '')
company = request.args.get('company', '')
- result = ProductInboundService.get_list(page, limit, keyword, statuses, category, material_type, company)
+ order_by_column = request.args.get('orderByColumn', '')
+ is_asc = request.args.get('isAsc', '')
+ advanced_filters_str = request.args.get('advancedFilters', '')
+
+ # 准备额外筛选字典
+ extra_filters = {}
+ if company:
+ extra_filters['company'] = company
+ if category:
+ extra_filters['category'] = category
+ if material_type:
+ extra_filters['material_type'] = material_type
+ if order_by_column:
+ extra_filters['order_by_column'] = order_by_column
+ if is_asc:
+ extra_filters['is_asc'] = is_asc
+ if advanced_filters_str:
+ try:
+ import json
+ advanced_filters = json.loads(advanced_filters_str)
+ extra_filters['advanced_filters'] = advanced_filters
+ except Exception:
+ extra_filters['advanced_filters'] = []
+
+ # 调用服务,传入所有参数
+ result = ProductInboundService.get_list(
+ page, limit, keyword, statuses,
+ category=extra_filters.get('category'),
+ material_type=extra_filters.get('material_type'),
+ company=extra_filters.get('company'),
+ order_by_column=extra_filters.get('order_by_column'),
+ is_asc=extra_filters.get('is_asc'),
+ advanced_filters=extra_filters.get('advanced_filters')
+ )
user_permissions = get_current_user_permissions()
if result.get('items'):
result['items'] = [filter_item_by_permissions(item, user_permissions) for item in result['items']]
@@ -184,4 +217,4 @@ def calculate_bom_cost():
return jsonify({"code": 200, "msg": "success", "data": cost})
except Exception as e:
traceback.print_exc()
- return jsonify({"code": 500, "msg": str(e)}), 500
\ No newline at end of file
+ return jsonify({"code": 500, "msg": str(e)}), 500
diff --git a/inventory-backend/app/services/inbound/product_service.py b/inventory-backend/app/services/inbound/product_service.py
index 7e02dca..d3bb806 100644
--- a/inventory-backend/app/services/inbound/product_service.py
+++ b/inventory-backend/app/services/inbound/product_service.py
@@ -270,7 +270,8 @@ class ProductInboundService:
return []
@staticmethod
- def get_list(page, limit, keyword=None, statuses=None, category=None, material_type=None, company=None):
+ def get_list(page, limit, keyword=None, statuses=None, category=None, material_type=None, company=None,
+ order_by_column=None, is_asc=None, advanced_filters=None):
from app.models.inbound.product import StockProduct
try:
query = db.session.query(StockProduct).outerjoin(MaterialBase, StockProduct.base_id == MaterialBase.id)
@@ -304,8 +305,118 @@ class ProductInboundService:
StockProduct.stock_quantity > 0
)
)
- pagination = query.order_by(StockProduct.production_date.desc()).paginate(page=page, per_page=limit,
- error_out=False)
+
+ # 高级动态筛选
+ if advanced_filters:
+ if isinstance(advanced_filters, str):
+ try:
+ import json
+ advanced_filters = json.loads(advanced_filters)
+ except:
+ advanced_filters = []
+ if isinstance(advanced_filters, list):
+ field_mapping = {
+ 'id': StockProduct.id,
+ 'base_id': StockProduct.base_id,
+ 'company_name': MaterialBase.company_name,
+ 'material_name': MaterialBase.name,
+ 'spec_model': MaterialBase.spec_model,
+ 'category': MaterialBase.category,
+ 'material_type': MaterialBase.material_type,
+ 'unit': MaterialBase.unit,
+ 'sku': StockProduct.sku,
+ 'inbound_date': StockProduct.production_date,
+ 'barcode': StockProduct.barcode,
+ 'serial_number': StockProduct.serial_number,
+ 'batch_number': StockProduct.serial_number,
+ 'status': StockProduct.status,
+ 'quality_status': StockProduct.quality_status,
+ 'in_quantity': StockProduct.in_quantity,
+ 'stock_quantity': StockProduct.stock_quantity,
+ 'available_quantity': StockProduct.available_quantity,
+ 'warehouse_location': StockProduct.warehouse_location,
+ 'bom_code': StockProduct.bom_code,
+ 'bom_version': StockProduct.bom_version,
+ 'work_order_code': StockProduct.work_order_code,
+ 'raw_material_cost': StockProduct.raw_material_cost,
+ 'unit_total_cost': StockProduct.manual_cost,
+ 'order_id': StockProduct.order_id,
+ 'sale_price': StockProduct.sale_price,
+ 'production_manager': StockProduct.production_manager,
+ }
+ for cond in advanced_filters:
+ field = cond.get('field')
+ operator = cond.get('operator')
+ value = cond.get('value')
+ if not field or not operator:
+ continue
+ model_field = field_mapping.get(field)
+ if model_field is None:
+ continue
+ # 防止 SQL 注入,只允许映射的字段
+ if operator == '=':
+ query = query.filter(model_field == value)
+ elif operator == '!=':
+ query = query.filter(model_field != value)
+ elif operator == 'like':
+ query = query.filter(model_field.ilike(f'%{value}%'))
+ elif operator == 'not_like':
+ query = query.filter(~model_field.ilike(f'%{value}%'))
+ elif operator == '>':
+ if value.replace('.', '', 1).isdigit():
+ query = query.filter(model_field > float(value))
+ elif operator == '<':
+ if value.replace('.', '', 1).isdigit():
+ query = query.filter(model_field < float(value))
+ elif operator == '>=':
+ if value.replace('.', '', 1).isdigit():
+ query = query.filter(model_field >= float(value))
+ elif operator == '<=':
+ if value.replace('.', '', 1).isdigit():
+ query = query.filter(model_field <= float(value))
+
+ # 动态排序
+ order_field = None
+ if order_by_column and is_asc is not None:
+ order_mapping = {
+ 'id': StockProduct.id,
+ 'base_id': StockProduct.base_id,
+ 'company_name': MaterialBase.company_name,
+ 'material_name': MaterialBase.name,
+ 'category': MaterialBase.category,
+ 'material_type': MaterialBase.material_type,
+ 'spec_model': MaterialBase.spec_model,
+ 'unit': MaterialBase.unit,
+ 'sku': StockProduct.sku,
+ 'inbound_date': StockProduct.production_date,
+ 'barcode': StockProduct.barcode,
+ 'serial_number': StockProduct.serial_number,
+ 'batch_number': StockProduct.serial_number,
+ 'status': StockProduct.status,
+ 'quality_status': StockProduct.quality_status,
+ 'in_quantity': StockProduct.in_quantity,
+ 'stock_quantity': StockProduct.stock_quantity,
+ 'available_quantity': StockProduct.available_quantity,
+ 'warehouse_location': StockProduct.warehouse_location,
+ 'bom_code': StockProduct.bom_code,
+ 'bom_version': StockProduct.bom_version,
+ 'work_order_code': StockProduct.work_order_code,
+ 'raw_material_cost': StockProduct.raw_material_cost,
+ 'unit_total_cost': StockProduct.manual_cost,
+ 'order_id': StockProduct.order_id,
+ 'sale_price': StockProduct.sale_price,
+ 'production_manager': StockProduct.production_manager,
+ }
+ order_field = order_mapping.get(order_by_column)
+ if order_field is not None:
+ if is_asc == 'true' or is_asc == True:
+ query = query.order_by(order_field.asc())
+ else:
+ query = query.order_by(order_field.desc())
+ if order_field is None:
+ query = query.order_by(StockProduct.production_date.desc())
+
+ pagination = query.paginate(page=page, per_page=limit, error_out=False)
current_items = pagination.items
def parse_img(json_str):
@@ -321,7 +432,7 @@ class ProductInboundService:
item_dict['unit_total_cost'] = float(item.manual_cost or 0)
items.append(item_dict)
return {"total": pagination.total, "items": items}
- except:
+ except Exception as e:
traceback.print_exc()
return {"total": 0, "items": []}
diff --git a/inventory-web/src/views/stock/inbound/product.vue b/inventory-web/src/views/stock/inbound/product.vue
index 4c4759e..aa3b4c0 100644
--- a/inventory-web/src/views/stock/inbound/product.vue
+++ b/inventory-web/src/views/stock/inbound/product.vue
@@ -53,6 +53,36 @@
搜索
重置
+
+
+ 高级筛选
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
@@ -515,10 +548,52 @@ const dialogStatus = ref<'create' | 'update'>('create')
const tableData = ref([])
const total = ref(0)
const formRef = ref()
-const queryParams = reactive({ page: 1, pageSize: 100, keyword: '', category: '', material_type: '', statuses: ['在库', '借库'], company: '' })
+const queryParams = reactive({ page: 1, pageSize: 100, keyword: '', category: '', material_type: '', statuses: ['在库', '借库'], company: '', orderByColumn: '', isAsc: '', advancedFilters: [] })
const categoryOptions = ref([])
const typeOptions = ref([])
const companyOptions = ref([]) // [新增]
+const advancedFilterVisible = ref(false)
+const advancedConditions = ref([{ field: '', operator: '', value: '' }])
+const fieldOptions = ref([
+ { label: 'ID', value: 'id' },
+ { label: 'BaseID', value: 'base_id' },
+ { label: '所属公司', value: 'company_name' },
+ { label: '名称', value: 'material_name' },
+ { label: '规格型号', value: 'spec_model' },
+ { label: '类别', value: 'category' },
+ { label: '类型', value: 'material_type' },
+ { label: '单位', value: 'unit' },
+ { label: 'SKU', value: 'sku' },
+ { label: '入库日期', value: 'inbound_date' },
+ { label: '条码', value: 'barcode' },
+ { label: '序列号', value: 'serial_number' },
+ { label: '批号', value: 'batch_number' },
+ { label: '状态', value: 'status' },
+ { label: '质量状态', value: 'quality_status' },
+ { label: '入库量', value: 'in_quantity' },
+ { label: '库存数', value: 'stock_quantity' },
+ { label: '可用数', value: 'available_quantity' },
+ { label: '库位', value: 'warehouse_location' },
+ { label: 'BOM编号', value: 'bom_code' },
+ { label: 'BOM版本', value: 'bom_version' },
+ { label: '工单号', value: 'work_order_code' },
+ { label: '原料成本', value: 'raw_material_cost' },
+ { label: '单件成本', value: 'unit_total_cost' },
+ { label: '总成本', value: 'total_price' },
+ { label: '生产负责人', value: 'production_manager' },
+ { label: '生产开始', value: 'production_start_time' },
+ { label: '生产结束', value: 'production_end_time' },
+])
+const operatorOptions = ref([
+ { label: '等于', value: '=' },
+ { label: '不等于', value: '!=' },
+ { label: '包含', value: 'like' },
+ { label: '不包含', value: 'not_like' },
+ { label: '大于', value: '>' },
+ { label: '小于', value: '<' },
+ { label: '大于等于', value: '>=' },
+ { label: '小于等于', value: '<=' },
+])
const materialOptions = ref([])
const searchPage = ref(1)
const searchKeyword = ref('')
@@ -551,28 +626,28 @@ const inspection_url = ref('')
// [核心优化] 所有列定义
const allColumns = [
- { prop: 'company_name', label: '所属公司', minWidth: '100' }, // [新增]
- { prop: 'material_name', label: '名称', minWidth: '140' },
- { prop: 'sku', label: 'SKU', minWidth: '110' },
- { prop: 'serial_number', label: '序列号', minWidth: '130' },
- { prop: 'qty_stock', label: '库存', minWidth: '90' },
- { prop: 'status', label: '状态', minWidth: '90' },
- { prop: 'quality_status', label: '质量', minWidth: '90' },
- { prop: 'spec_model', label: '规格', minWidth: '120' },
- { prop: 'unit', label: '单位', minWidth: '80' },
- { prop: 'product_photo', label: '实拍图', minWidth: '100' },
- { prop: 'sale_price', label: '售价', minWidth: '100' },
- { prop: 'order_id', label: '订单号', minWidth: '120' },
- { prop: 'work_order_code', label: '工单号', minWidth: '120' },
- { prop: 'quality_report_link', label: '质量报告', minWidth: '100' },
- { prop: 'inspection_report_link', label: '检测报告', minWidth: '100' },
- { prop: 'bom_code', label: 'BOM', minWidth: '100' },
- { prop: 'production_manager', label: '负责人', minWidth: '100' },
- { prop: 'raw_material_cost', label: '原料成本', minWidth: '100' },
- { prop: 'unit_total_cost', label: '单件成本', minWidth: '100' },
- { prop: 'total_price', label: '总成本', minWidth: '100' },
- { prop: 'inbound_date', label: '生产日期', minWidth: '120' },
- { prop: 'detail_link', label: '详情', minWidth: '100' }
+ { prop: 'company_name', label: '所属公司', minWidth: '100', sortable: true }, // [新增]
+ { prop: 'material_name', label: '名称', minWidth: '140', sortable: true },
+ { prop: 'sku', label: 'SKU', minWidth: '110', sortable: true },
+ { prop: 'serial_number', label: '序列号', minWidth: '130', sortable: true },
+ { prop: 'qty_stock', label: '库存', minWidth: '90', sortable: true },
+ { prop: 'status', label: '状态', minWidth: '90', sortable: true },
+ { prop: 'quality_status', label: '质量', minWidth: '90', sortable: true },
+ { prop: 'spec_model', label: '规格', minWidth: '120', sortable: true },
+ { prop: 'unit', label: '单位', minWidth: '80', sortable: true },
+ { prop: 'product_photo', label: '实拍图', minWidth: '100', sortable: false },
+ { prop: 'sale_price', label: '售价', minWidth: '100', sortable: true },
+ { prop: 'order_id', label: '订单号', minWidth: '120', sortable: true },
+ { prop: 'work_order_code', label: '工单号', minWidth: '120', sortable: true },
+ { prop: 'quality_report_link', label: '质量报告', minWidth: '100', sortable: false },
+ { prop: 'inspection_report_link', label: '检测报告', minWidth: '100', sortable: false },
+ { prop: 'bom_code', label: 'BOM', minWidth: '100', sortable: true },
+ { prop: 'production_manager', label: '负责人', minWidth: '100', sortable: true },
+ { prop: 'raw_material_cost', label: '原料成本', minWidth: '100', sortable: true },
+ { prop: 'unit_total_cost', label: '单件成本', minWidth: '100', sortable: true },
+ { prop: 'total_price', label: '总成本', minWidth: '100', sortable: true },
+ { prop: 'inbound_date', label: '生产日期', minWidth: '120', sortable: true },
+ { prop: 'detail_link', label: '详情', minWidth: '100', sortable: false }
]
// 列与权限Code的映射关系(数据库中的code)
@@ -834,11 +909,17 @@ const handleManagerSelect = (item: any) => {
}
const fetchData = async () => {
- loading.value = true;
+ loading.value = true
try {
- const params = { ...queryParams, statuses: queryParams.statuses.join(',') }
- const res: any = await getProductList(params);
- tableData.value = res.data.items || [];
+ const params = {
+ ...queryParams,
+ statuses: queryParams.statuses.join(','),
+ orderByColumn: queryParams.orderByColumn,
+ isAsc: queryParams.isAsc,
+ advancedFilters: queryParams.advancedFilters.length > 0 ? JSON.stringify(queryParams.advancedFilters) : ''
+ }
+ const res: any = await getProductList(params)
+ tableData.value = res.data.items || []
total.value = res.data.total || 0
} finally { loading.value = false }
}
@@ -862,6 +943,45 @@ const resetQuery = () => {
queryParams.material_type = ''
queryParams.company = ''
queryParams.page = 1
+ queryParams.orderByColumn = ''
+ queryParams.isAsc = ''
+ queryParams.advancedFilters = []
+ fetchData()
+}
+
+const handleSortChange = ({ column, prop, order }: any) => {
+ if (order === 'ascending') {
+ queryParams.orderByColumn = prop
+ queryParams.isAsc = 'true'
+ } else if (order === 'descending') {
+ queryParams.orderByColumn = prop
+ queryParams.isAsc = 'false'
+ } else {
+ queryParams.orderByColumn = ''
+ queryParams.isAsc = ''
+ }
+ queryParams.page = 1
+ fetchData()
+}
+
+const addCondition = () => {
+ advancedConditions.value.push({ field: '', operator: '', value: '' })
+}
+const removeCondition = (index: number) => {
+ advancedConditions.value.splice(index, 1)
+}
+const applyAdvancedFilter = () => {
+ const validConditions = advancedConditions.value.filter(c => c.field && c.operator && c.value !== '')
+ queryParams.advancedFilters = validConditions
+ advancedFilterVisible.value = false
+ queryParams.page = 1
+ fetchData()
+}
+const resetAdvancedFilter = () => {
+ advancedConditions.value = [{ field: '', operator: '', value: '' }]
+ queryParams.advancedFilters = []
+ advancedFilterVisible.value = false
+ queryParams.page = 1
fetchData()
}