Compare commits
4 Commits
80ee0fe88e
...
893be24071
| Author | SHA1 | Date | |
|---|---|---|---|
| 893be24071 | |||
| 2ac64076dd | |||
| c5872aed3c | |||
| 9b794d7f64 |
@ -5,6 +5,7 @@ from app.services.inbound.base_service import MaterialBaseService
|
|||||||
from app.utils.decorators import login_required, permission_required
|
from app.utils.decorators import login_required, permission_required
|
||||||
import traceback
|
import traceback
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
|
|
||||||
inbound_base_bp = Blueprint('stock_base', __name__)
|
inbound_base_bp = Blueprint('stock_base', __name__)
|
||||||
|
|
||||||
@ -105,6 +106,13 @@ def get_list():
|
|||||||
page = request.args.get('pageNum', 1, type=int)
|
page = request.args.get('pageNum', 1, type=int)
|
||||||
limit = request.args.get('pageSize', 10, type=int)
|
limit = request.args.get('pageSize', 10, type=int)
|
||||||
|
|
||||||
|
# 解析高级筛选条件
|
||||||
|
advanced_filters_raw = request.args.get('advancedFilters', '[]')
|
||||||
|
try:
|
||||||
|
advanced_filters_list = json.loads(advanced_filters_raw)
|
||||||
|
except:
|
||||||
|
advanced_filters_list = []
|
||||||
|
|
||||||
# 构造筛选条件
|
# 构造筛选条件
|
||||||
filters = {
|
filters = {
|
||||||
'keyword': request.args.get('keyword', ''),
|
'keyword': request.args.get('keyword', ''),
|
||||||
@ -113,7 +121,8 @@ def get_list():
|
|||||||
'type': request.args.get('type', ''),
|
'type': request.args.get('type', ''),
|
||||||
'isEnabled': request.args.get('isEnabled', None),
|
'isEnabled': request.args.get('isEnabled', None),
|
||||||
'orderByColumn': request.args.get('orderByColumn', ''),
|
'orderByColumn': request.args.get('orderByColumn', ''),
|
||||||
'isAsc': request.args.get('isAsc', None)
|
'isAsc': request.args.get('isAsc', None),
|
||||||
|
'advancedFilters': advanced_filters_list
|
||||||
}
|
}
|
||||||
|
|
||||||
result = MaterialBaseService.get_list(page, limit, filters)
|
result = MaterialBaseService.get_list(page, limit, filters)
|
||||||
|
|||||||
@ -106,7 +106,7 @@ def search_base():
|
|||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# 1. 获取列表 (修改:接收 category 和 material_type)
|
# 1. 获取列表 (修改:接收 category, material_type, orderByColumn, isAsc, advancedFilters)
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@inbound_buy_bp.route('/list', methods=['GET'])
|
@inbound_buy_bp.route('/list', methods=['GET'])
|
||||||
@permission_required('inbound_buy')
|
@permission_required('inbound_buy')
|
||||||
@ -121,11 +121,24 @@ def get_list():
|
|||||||
material_type = request.args.get('material_type', '')
|
material_type = request.args.get('material_type', '')
|
||||||
company = request.args.get('company', '')
|
company = request.args.get('company', '')
|
||||||
|
|
||||||
|
# 排序参数
|
||||||
|
order_by = request.args.get('orderByColumn', '').strip()
|
||||||
|
is_asc = request.args.get('isAsc', '').strip()
|
||||||
|
|
||||||
|
# 高级筛选参数
|
||||||
|
advanced_filters_raw = request.args.get('advancedFilters', '[]')
|
||||||
|
import json
|
||||||
|
try:
|
||||||
|
advanced_filters = json.loads(advanced_filters_raw) if advanced_filters_raw else []
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
advanced_filters = []
|
||||||
|
|
||||||
# 状态参数处理
|
# 状态参数处理
|
||||||
statuses_str = request.args.get('statuses', '')
|
statuses_str = request.args.get('statuses', '')
|
||||||
statuses = statuses_str.split(',') if statuses_str else []
|
statuses = statuses_str.split(',') if statuses_str else []
|
||||||
|
|
||||||
result = BuyInboundService.get_list(page, limit, keyword, statuses, category, material_type, company)
|
result = BuyInboundService.get_list(page, limit, keyword, statuses, category, material_type, company,
|
||||||
|
order_by, is_asc, advanced_filters)
|
||||||
# 字段级脱敏
|
# 字段级脱敏
|
||||||
user_permissions = get_current_user_permissions()
|
user_permissions = get_current_user_permissions()
|
||||||
if result.get('items'):
|
if result.get('items'):
|
||||||
|
|||||||
@ -112,7 +112,7 @@ class MaterialBaseService:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_list(page, limit, filters=None):
|
def get_list(page, limit, filters=None):
|
||||||
"""
|
"""
|
||||||
获取基础信息列表 (带分页和筛选)
|
获取基础信息列表 (带分页、高级筛选和全字段排序)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 构建聚合子查询
|
# 构建聚合子查询
|
||||||
@ -178,19 +178,80 @@ class MaterialBaseService:
|
|||||||
is_active = bool(int(filters['isEnabled']))
|
is_active = bool(int(filters['isEnabled']))
|
||||||
query = query.filter_by(is_enabled=is_active)
|
query = query.filter_by(is_enabled=is_active)
|
||||||
|
|
||||||
# 排序处理
|
# 3. 高级动态筛选
|
||||||
|
advanced_filters = filters.get('advancedFilters', [])
|
||||||
|
if advanced_filters:
|
||||||
|
allowed_fields = {
|
||||||
|
'companyName': 'company_name',
|
||||||
|
'name': 'name',
|
||||||
|
'commonName': 'common_name',
|
||||||
|
'category': 'category',
|
||||||
|
'type': 'material_type',
|
||||||
|
'spec': 'spec_model',
|
||||||
|
'unit': 'unit',
|
||||||
|
'inventoryCount': total_inv,
|
||||||
|
'availableCount': total_avail
|
||||||
|
}
|
||||||
|
filter_conditions = []
|
||||||
|
for condition in advanced_filters:
|
||||||
|
field = condition.get('field')
|
||||||
|
operator = condition.get('operator')
|
||||||
|
value = condition.get('value')
|
||||||
|
if not field or not operator or value is None:
|
||||||
|
continue
|
||||||
|
db_field = allowed_fields.get(field)
|
||||||
|
if not db_field:
|
||||||
|
continue
|
||||||
|
# 对于聚合字段 (inventoryCount, availableCount),需要使用子查询别名
|
||||||
|
if isinstance(db_field, type(total_inv)):
|
||||||
|
column = db_field
|
||||||
|
else:
|
||||||
|
column = getattr(MaterialBase, db_field, None)
|
||||||
|
if column is None:
|
||||||
|
continue
|
||||||
|
if operator == 'eq':
|
||||||
|
filter_conditions.append(column == value)
|
||||||
|
elif operator == 'ne':
|
||||||
|
filter_conditions.append(column != value)
|
||||||
|
elif operator == 'contains':
|
||||||
|
filter_conditions.append(column.ilike(f'%{value}%'))
|
||||||
|
elif operator == 'ge':
|
||||||
|
try:
|
||||||
|
num_val = float(value)
|
||||||
|
filter_conditions.append(column >= num_val)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
elif operator == 'le':
|
||||||
|
try:
|
||||||
|
num_val = float(value)
|
||||||
|
filter_conditions.append(column <= num_val)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
if filter_conditions:
|
||||||
|
query = query.filter(and_(*filter_conditions))
|
||||||
|
|
||||||
|
# 排序处理(支持全字段)
|
||||||
order_by_column = filters.get('orderByColumn', '')
|
order_by_column = filters.get('orderByColumn', '')
|
||||||
is_asc = filters.get('isAsc', None)
|
is_asc = filters.get('isAsc', None)
|
||||||
if order_by_column == 'inventoryCount':
|
if order_by_column:
|
||||||
if is_asc == 'asc':
|
# 字段映射
|
||||||
query = query.order_by(total_inv.asc())
|
sort_field_map = {
|
||||||
else:
|
'companyName': MaterialBase.company_name,
|
||||||
query = query.order_by(total_inv.desc())
|
'name': MaterialBase.name,
|
||||||
elif order_by_column == 'availableCount':
|
'commonName': MaterialBase.common_name,
|
||||||
if is_asc == 'asc':
|
'category': MaterialBase.category,
|
||||||
query = query.order_by(total_avail.asc())
|
'type': MaterialBase.material_type,
|
||||||
else:
|
'spec': MaterialBase.spec_model,
|
||||||
query = query.order_by(total_avail.desc())
|
'unit': MaterialBase.unit,
|
||||||
|
'inventoryCount': total_inv,
|
||||||
|
'availableCount': total_avail
|
||||||
|
}
|
||||||
|
sort_column = sort_field_map.get(order_by_column)
|
||||||
|
if sort_column is not None:
|
||||||
|
if is_asc == 'asc':
|
||||||
|
query = query.order_by(sort_column.asc())
|
||||||
|
elif is_asc == 'desc':
|
||||||
|
query = query.order_by(sort_column.desc())
|
||||||
else:
|
else:
|
||||||
# 默认排序:优先按总库存数降序,当库存相同时,再按规格型号升序
|
# 默认排序:优先按总库存数降序,当库存相同时,再按规格型号升序
|
||||||
query = query.order_by(total_inv.desc(), MaterialBase.spec_model.asc())
|
query = query.order_by(total_inv.desc(), MaterialBase.spec_model.asc())
|
||||||
|
|||||||
@ -237,11 +237,13 @@ class BuyInboundService:
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# 5. 获取列表
|
# 5. 获取列表 (支持排序和高级筛选)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@staticmethod
|
@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='', is_asc='', advanced_filters=None):
|
||||||
try:
|
try:
|
||||||
|
from sqlalchemy import and_, or_
|
||||||
query = db.session.query(StockBuy).outerjoin(MaterialBase, StockBuy.base_id == MaterialBase.id)
|
query = db.session.query(StockBuy).outerjoin(MaterialBase, StockBuy.base_id == MaterialBase.id)
|
||||||
|
|
||||||
# 1. 通用关键词搜索
|
# 1. 通用关键词搜索
|
||||||
@ -279,7 +281,109 @@ class BuyInboundService:
|
|||||||
else:
|
else:
|
||||||
query = query.filter(and_(StockBuy.status.in_(statuses), StockBuy.stock_quantity > 0))
|
query = query.filter(and_(StockBuy.status.in_(statuses), StockBuy.stock_quantity > 0))
|
||||||
|
|
||||||
pagination = query.order_by(StockBuy.in_date.desc()).paginate(page=page, per_page=limit, error_out=False)
|
# 5. 高级动态筛选
|
||||||
|
if advanced_filters:
|
||||||
|
allowed_fields = {
|
||||||
|
'company_name': MaterialBase.company_name,
|
||||||
|
'material_name': MaterialBase.name,
|
||||||
|
'material_type': MaterialBase.material_type,
|
||||||
|
'category': MaterialBase.category,
|
||||||
|
'spec_model': MaterialBase.spec_model,
|
||||||
|
'unit': MaterialBase.unit,
|
||||||
|
'sku': StockBuy.sku,
|
||||||
|
'barcode': StockBuy.barcode,
|
||||||
|
'batch_number': StockBuy.batch_number,
|
||||||
|
'serial_number': StockBuy.serial_number,
|
||||||
|
'warehouse_location': StockBuy.warehouse_location,
|
||||||
|
'status': StockBuy.status,
|
||||||
|
'inspection_status': StockBuy.inspection_status,
|
||||||
|
'qty_inbound': StockBuy.in_quantity,
|
||||||
|
'qty_stock': StockBuy.stock_quantity,
|
||||||
|
'qty_available': StockBuy.available_quantity,
|
||||||
|
'unit_price': StockBuy.pre_tax_unit_price,
|
||||||
|
'total_price': StockBuy.total_price,
|
||||||
|
'tax_rate': StockBuy.tax_rate,
|
||||||
|
'currency': StockBuy.currency,
|
||||||
|
'exchange_rate': StockBuy.exchange_rate,
|
||||||
|
'supplier_name': StockBuy.supplier_name,
|
||||||
|
'purchaser': StockBuy.buyer_name,
|
||||||
|
'purchaser_email': StockBuy.buyer_email,
|
||||||
|
'source_link': StockBuy.original_link,
|
||||||
|
'detail_link': StockBuy.detail_link,
|
||||||
|
}
|
||||||
|
filter_conditions = []
|
||||||
|
for condition in advanced_filters:
|
||||||
|
field = condition.get('field')
|
||||||
|
operator = condition.get('operator')
|
||||||
|
value = condition.get('value')
|
||||||
|
if not field or not operator or value is None:
|
||||||
|
continue
|
||||||
|
column = allowed_fields.get(field)
|
||||||
|
if column is None:
|
||||||
|
continue
|
||||||
|
if operator == 'eq':
|
||||||
|
filter_conditions.append(column == value)
|
||||||
|
elif operator == 'ne':
|
||||||
|
filter_conditions.append(column != value)
|
||||||
|
elif operator == 'contains':
|
||||||
|
filter_conditions.append(column.ilike(f'%{value}%'))
|
||||||
|
elif operator == 'ge':
|
||||||
|
try:
|
||||||
|
num_val = float(value)
|
||||||
|
filter_conditions.append(column >= num_val)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
elif operator == 'le':
|
||||||
|
try:
|
||||||
|
num_val = float(value)
|
||||||
|
filter_conditions.append(column <= num_val)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
if filter_conditions:
|
||||||
|
query = query.filter(and_(*filter_conditions))
|
||||||
|
|
||||||
|
# 6. 排序处理
|
||||||
|
if order_by:
|
||||||
|
sort_field_map = {
|
||||||
|
'company_name': MaterialBase.company_name,
|
||||||
|
'material_name': MaterialBase.name,
|
||||||
|
'material_type': MaterialBase.material_type,
|
||||||
|
'category': MaterialBase.category,
|
||||||
|
'spec_model': MaterialBase.spec_model,
|
||||||
|
'unit': MaterialBase.unit,
|
||||||
|
'sku': StockBuy.sku,
|
||||||
|
'barcode': StockBuy.barcode,
|
||||||
|
'inbound_date': StockBuy.in_date,
|
||||||
|
'serial_number': StockBuy.serial_number,
|
||||||
|
'batch_number': StockBuy.batch_number,
|
||||||
|
'status': StockBuy.status,
|
||||||
|
'inspection_status': StockBuy.inspection_status,
|
||||||
|
'qty_inbound': StockBuy.in_quantity,
|
||||||
|
'qty_stock': StockBuy.stock_quantity,
|
||||||
|
'qty_available': StockBuy.available_quantity,
|
||||||
|
'warehouse_loc': StockBuy.warehouse_location,
|
||||||
|
'unit_price': StockBuy.pre_tax_unit_price,
|
||||||
|
'total_price': StockBuy.total_price,
|
||||||
|
'tax_rate': StockBuy.tax_rate,
|
||||||
|
'currency': StockBuy.currency,
|
||||||
|
'exchange_rate': StockBuy.exchange_rate,
|
||||||
|
'supplier_name': StockBuy.supplier_name,
|
||||||
|
'purchaser': StockBuy.buyer_name,
|
||||||
|
'purchaser_email': StockBuy.buyer_email,
|
||||||
|
'source_link': StockBuy.original_link,
|
||||||
|
'detail_link': StockBuy.detail_link,
|
||||||
|
}
|
||||||
|
column = sort_field_map.get(order_by)
|
||||||
|
if column:
|
||||||
|
if is_asc == 'asc':
|
||||||
|
query = query.order_by(column.asc())
|
||||||
|
elif is_asc == 'desc':
|
||||||
|
query = query.order_by(column.desc())
|
||||||
|
else:
|
||||||
|
# 默认排序
|
||||||
|
query = query.order_by(StockBuy.in_date.desc())
|
||||||
|
|
||||||
|
pagination = query.paginate(page=page, per_page=limit, error_out=False)
|
||||||
items = []
|
items = []
|
||||||
for item in pagination.items:
|
for item in pagination.items:
|
||||||
items.append(item.to_dict()) # 直接使用 model 的 to_dict
|
items.append(item.to_dict()) # 直接使用 model 的 to_dict
|
||||||
|
|||||||
@ -64,6 +64,33 @@
|
|||||||
|
|
||||||
<el-button type="primary" plain @click="handleQuery">搜索</el-button>
|
<el-button type="primary" plain @click="handleQuery">搜索</el-button>
|
||||||
<el-button plain @click="resetQuery">重置</el-button>
|
<el-button plain @click="resetQuery">重置</el-button>
|
||||||
|
<el-popover
|
||||||
|
v-model:visible="advancedFilterVisible"
|
||||||
|
placement="bottom"
|
||||||
|
title="高级筛选"
|
||||||
|
width="600"
|
||||||
|
trigger="manual">
|
||||||
|
<template #reference>
|
||||||
|
<el-button plain @click="advancedFilterVisible = !advancedFilterVisible">高级筛选</el-button>
|
||||||
|
</template>
|
||||||
|
<div class="advanced-filter">
|
||||||
|
<div v-for="(condition, index) in advancedConditions" :key="index" class="condition-row" style="display: flex; align-items: center; margin-bottom: 10px;">
|
||||||
|
<el-select v-model="condition.field" placeholder="字段" style="width: 180px">
|
||||||
|
<el-option v-for="field in fieldOptions" :key="field.value" :label="field.label" :value="field.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="condition.operator" placeholder="操作符" style="width: 120px; margin-left: 8px">
|
||||||
|
<el-option v-for="op in operatorOptions" :key="op.value" :label="op.label" :value="op.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-input v-model="condition.value" placeholder="值" style="width: 180px; margin-left: 8px" />
|
||||||
|
<el-button v-if="advancedConditions.length > 1" type="danger" link @click="removeCondition(index)" style="margin-left: 8px">删除</el-button>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 12px">
|
||||||
|
<el-button type="primary" link @click="addCondition">添加条件</el-button>
|
||||||
|
<el-button @click="applyAdvancedFilter" type="primary">应用筛选</el-button>
|
||||||
|
<el-button @click="resetAdvancedFilter">重置</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right-toolbar">
|
<div class="right-toolbar">
|
||||||
@ -126,29 +153,29 @@
|
|||||||
>
|
>
|
||||||
<el-table-column v-if="columns.id.visible" prop="id" label="ID" min-width="80" align="center" fixed="left" />
|
<el-table-column v-if="columns.id.visible" prop="id" label="ID" min-width="80" align="center" fixed="left" />
|
||||||
|
|
||||||
<el-table-column v-if="columns.companyName.visible" prop="companyName" label="所属公司" min-width="100" align="center" show-overflow-tooltip>
|
<el-table-column v-if="columns.companyName.visible" prop="companyName" label="所属公司" min-width="100" align="center" show-overflow-tooltip sortable="custom">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ scope.row.companyName || '-' }}</span>
|
<span>{{ scope.row.companyName || '-' }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column v-if="columns.name.visible" prop="name" label="名称" min-width="160" show-overflow-tooltip />
|
<el-table-column v-if="columns.name.visible" prop="name" label="名称" min-width="160" show-overflow-tooltip sortable="custom" />
|
||||||
|
|
||||||
<el-table-column v-if="columns.commonName.visible" prop="commonName" label="俗名" min-width="140" show-overflow-tooltip>
|
<el-table-column v-if="columns.commonName.visible" prop="commonName" label="俗名" min-width="140" show-overflow-tooltip sortable="custom">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span v-if="scope.row.commonName">{{ scope.row.commonName }}</span>
|
<span v-if="scope.row.commonName">{{ scope.row.commonName }}</span>
|
||||||
<span v-else style="color: #ccc;">-</span>
|
<span v-else style="color: #ccc;">-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column v-if="columns.category.visible" prop="category" label="类别" min-width="140" show-overflow-tooltip>
|
<el-table-column v-if="columns.category.visible" prop="category" label="类别" min-width="140" show-overflow-tooltip sortable="custom">
|
||||||
<template #default="scope">{{ scope.row.category || '-' }}</template>
|
<template #default="scope">{{ scope.row.category || '-' }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column v-if="columns.type.visible" prop="type" label="类型" min-width="120" align="center" show-overflow-tooltip>
|
<el-table-column v-if="columns.type.visible" prop="type" label="类型" min-width="120" align="center" show-overflow-tooltip sortable="custom">
|
||||||
<template #default="scope">{{ scope.row.type || '-' }}</template>
|
<template #default="scope">{{ scope.row.type || '-' }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column v-if="columns.spec.visible" prop="spec" label="规格型号" min-width="180" show-overflow-tooltip />
|
<el-table-column v-if="columns.spec.visible" prop="spec" label="规格型号" min-width="180" show-overflow-tooltip sortable="custom" />
|
||||||
<el-table-column v-if="columns.unit.visible" prop="unit" label="单位" min-width="80" align="center" />
|
<el-table-column v-if="columns.unit.visible" prop="unit" label="单位" min-width="80" align="center" sortable="custom" />
|
||||||
|
|
||||||
<el-table-column v-if="columns.inventory.visible" prop="inventoryCount" label="库存数" min-width="100" align="center" sortable="custom">
|
<el-table-column v-if="columns.inventory.visible" prop="inventoryCount" label="库存数" min-width="100" align="center" sortable="custom">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@ -466,6 +493,7 @@ interface QueryParams {
|
|||||||
isEnabled?: number;
|
isEnabled?: number;
|
||||||
orderByColumn: string;
|
orderByColumn: string;
|
||||||
isAsc: string | undefined;
|
isAsc: string | undefined;
|
||||||
|
advancedFilters?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CascaderOption {
|
interface CascaderOption {
|
||||||
@ -481,6 +509,26 @@ const total = ref(0);
|
|||||||
const tableData = ref<MaterialBaseVO[]>([]);
|
const tableData = ref<MaterialBaseVO[]>([]);
|
||||||
const submitLoading = ref(false);
|
const submitLoading = ref(false);
|
||||||
const tableSize = ref<'large' | 'default' | 'small'>('large');
|
const tableSize = ref<'large' | 'default' | 'small'>('large');
|
||||||
|
const advancedFilterVisible = ref(false);
|
||||||
|
const advancedConditions = ref([{ field: '', operator: '', value: '' }]);
|
||||||
|
const fieldOptions = ref([
|
||||||
|
{ value: 'companyName', label: '所属公司' },
|
||||||
|
{ value: 'name', label: '名称' },
|
||||||
|
{ value: 'commonName', label: '俗名' },
|
||||||
|
{ value: 'category', label: '类别' },
|
||||||
|
{ value: 'type', label: '类型' },
|
||||||
|
{ value: 'spec', label: '规格型号' },
|
||||||
|
{ value: 'unit', label: '单位' },
|
||||||
|
{ value: 'inventoryCount', label: '库存数' },
|
||||||
|
{ value: 'availableCount', label: '可用数' }
|
||||||
|
]);
|
||||||
|
const operatorOptions = ref([
|
||||||
|
{ value: 'eq', label: '等于' },
|
||||||
|
{ value: 'ne', label: '不等于' },
|
||||||
|
{ value: 'contains', label: '包含' },
|
||||||
|
{ value: 'ge', label: '大于等于' },
|
||||||
|
{ value: 'le', label: '小于等于' }
|
||||||
|
]);
|
||||||
|
|
||||||
// 文件上传相关
|
// 文件上传相关
|
||||||
const fileListImage = ref<any[]>([]);
|
const fileListImage = ref<any[]>([]);
|
||||||
@ -571,7 +619,8 @@ const queryParams = reactive<QueryParams>({
|
|||||||
company: '',
|
company: '',
|
||||||
isEnabled: undefined,
|
isEnabled: undefined,
|
||||||
orderByColumn: '',
|
orderByColumn: '',
|
||||||
isAsc: undefined
|
isAsc: undefined,
|
||||||
|
advancedFilters: []
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 弹窗与表单相关 ---
|
// --- 弹窗与表单相关 ---
|
||||||
@ -760,7 +809,8 @@ const handleInputSearch = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSortChange = ({ column, prop, order }: any) => {
|
const handleSortChange = ({ column, prop, order }: any) => {
|
||||||
if (prop && (prop === 'inventoryCount' || prop === 'availableCount')) {
|
const sortableColumns = ['inventoryCount', 'availableCount', 'companyName', 'name', 'commonName', 'category', 'type', 'spec', 'unit'];
|
||||||
|
if (prop && sortableColumns.includes(prop)) {
|
||||||
queryParams.orderByColumn = prop;
|
queryParams.orderByColumn = prop;
|
||||||
queryParams.isAsc = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : undefined;
|
queryParams.isAsc = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : undefined;
|
||||||
} else {
|
} else {
|
||||||
@ -1056,6 +1106,28 @@ const handleCameraConfirm = async (file: File) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addCondition = () => {
|
||||||
|
advancedConditions.value.push({ field: '', operator: '', value: '' });
|
||||||
|
};
|
||||||
|
const removeCondition = (index: number) => {
|
||||||
|
advancedConditions.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
const applyAdvancedFilter = () => {
|
||||||
|
// Filter out empty conditions
|
||||||
|
const validConditions = advancedConditions.value.filter(c => c.field && c.operator && c.value !== '');
|
||||||
|
queryParams.advancedFilters = validConditions;
|
||||||
|
advancedFilterVisible.value = false;
|
||||||
|
queryParams.pageNum = 1;
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
const resetAdvancedFilter = () => {
|
||||||
|
advancedConditions.value = [{ field: '', operator: '', value: '' }];
|
||||||
|
queryParams.advancedFilters = [];
|
||||||
|
advancedFilterVisible.value = false;
|
||||||
|
queryParams.pageNum = 1;
|
||||||
|
getList();
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 先根据权限初始化列显示状态
|
// 先根据权限初始化列显示状态
|
||||||
initColumnPermissions();
|
initColumnPermissions();
|
||||||
|
|||||||
@ -53,6 +53,33 @@
|
|||||||
|
|
||||||
<el-button type="primary" plain class="search-btn" @click="fetchData">搜索</el-button>
|
<el-button type="primary" plain class="search-btn" @click="fetchData">搜索</el-button>
|
||||||
<el-button class="reset-btn" @click="resetQuery">重置</el-button>
|
<el-button class="reset-btn" @click="resetQuery">重置</el-button>
|
||||||
|
<el-popover
|
||||||
|
v-model:visible="advancedFilterVisible"
|
||||||
|
placement="bottom"
|
||||||
|
title="高级筛选"
|
||||||
|
width="600"
|
||||||
|
trigger="manual">
|
||||||
|
<template #reference>
|
||||||
|
<el-button plain @click="advancedFilterVisible = !advancedFilterVisible">高级筛选</el-button>
|
||||||
|
</template>
|
||||||
|
<div class="advanced-filter">
|
||||||
|
<div v-for="(condition, index) in advancedConditions" :key="index" class="condition-row" style="display: flex; align-items: center; margin-bottom: 10px;">
|
||||||
|
<el-select v-model="condition.field" placeholder="字段" style="width: 180px">
|
||||||
|
<el-option v-for="field in fieldOptions" :key="field.value" :label="field.label" :value="field.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="condition.operator" placeholder="操作符" style="width: 120px; margin-left: 8px">
|
||||||
|
<el-option v-for="op in operatorOptions" :key="op.value" :label="op.label" :value="op.value" />
|
||||||
|
</el-select>
|
||||||
|
<el-input v-model="condition.value" placeholder="值" style="width: 180px; margin-left: 8px" />
|
||||||
|
<el-button v-if="advancedConditions.length > 1" type="danger" link @click="removeCondition(index)" style="margin-left: 8px">删除</el-button>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 12px">
|
||||||
|
<el-button type="primary" link @click="addCondition">添加条件</el-button>
|
||||||
|
<el-button @click="applyAdvancedFilter" type="primary">应用筛选</el-button>
|
||||||
|
<el-button @click="resetAdvancedFilter">重置</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right-actions" style="flex-wrap: wrap;">
|
<div class="right-actions" style="flex-wrap: wrap;">
|
||||||
@ -90,6 +117,7 @@
|
|||||||
class="modern-table"
|
class="modern-table"
|
||||||
highlight-current-row
|
highlight-current-row
|
||||||
header-cell-class-name="table-header-gray"
|
header-cell-class-name="table-header-gray"
|
||||||
|
@sort-change="handleSortChange"
|
||||||
>
|
>
|
||||||
<template v-for="col in allColumns" :key="col.prop">
|
<template v-for="col in allColumns" :key="col.prop">
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@ -98,6 +126,7 @@
|
|||||||
:label="col.label"
|
:label="col.label"
|
||||||
:min-width="col.minWidth || '140'"
|
:min-width="col.minWidth || '140'"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
|
:sortable="isColumnSortable(col.prop) ? 'custom' : false"
|
||||||
>
|
>
|
||||||
<template #default="scope" v-if="col.prop === 'material_name'">
|
<template #default="scope" v-if="col.prop === 'material_name'">
|
||||||
<span class="clickable-text" @click="handleUpdate(scope.row)">
|
<span class="clickable-text" @click="handleUpdate(scope.row)">
|
||||||
@ -723,7 +752,10 @@ const queryParams = reactive({
|
|||||||
category: '',
|
category: '',
|
||||||
material_type: '',
|
material_type: '',
|
||||||
company: '',
|
company: '',
|
||||||
statuses: ['在库', '借库']
|
statuses: ['在库', '借库'],
|
||||||
|
orderByColumn: '',
|
||||||
|
isAsc: undefined as string | undefined,
|
||||||
|
advancedFilters: [] as any[]
|
||||||
})
|
})
|
||||||
|
|
||||||
const materialOptions = ref<any[]>([])
|
const materialOptions = ref<any[]>([])
|
||||||
@ -750,6 +782,44 @@ const cameraRef = ref<InstanceType<typeof WebRtcCamera> | null>(null)
|
|||||||
const currentCameraField = ref<'arrival_photo' | 'inspection_report'>('arrival_photo')
|
const currentCameraField = ref<'arrival_photo' | 'inspection_report'>('arrival_photo')
|
||||||
const inspection_report_url = ref('')
|
const inspection_report_url = ref('')
|
||||||
|
|
||||||
|
const advancedFilterVisible = ref(false)
|
||||||
|
const advancedConditions = ref([{ field: '', operator: '', value: '' }])
|
||||||
|
const fieldOptions = ref([
|
||||||
|
{ value: 'company_name', label: '所属公司' },
|
||||||
|
{ value: 'material_name', label: '名称' },
|
||||||
|
{ value: 'material_type', label: '类型' },
|
||||||
|
{ value: 'category', label: '类别' },
|
||||||
|
{ value: 'spec_model', label: '规格型号' },
|
||||||
|
{ value: 'unit', label: '单位' },
|
||||||
|
{ value: 'sku', label: 'SKU' },
|
||||||
|
{ value: 'barcode', label: '条码' },
|
||||||
|
{ value: 'batch_number', label: '批号' },
|
||||||
|
{ value: 'serial_number', label: '序列号' },
|
||||||
|
{ value: 'warehouse_location', label: '库位' },
|
||||||
|
{ value: 'status', label: '状态' },
|
||||||
|
{ value: 'inspection_status', label: '到检状态' },
|
||||||
|
{ value: 'qty_inbound', label: '入库量' },
|
||||||
|
{ value: 'qty_stock', label: '库存数' },
|
||||||
|
{ value: 'qty_available', label: '可用数' },
|
||||||
|
{ value: 'unit_price', label: '不含税单价' },
|
||||||
|
{ value: 'total_price', label: '不含税总价' },
|
||||||
|
{ value: 'tax_rate', label: '税率' },
|
||||||
|
{ value: 'currency', label: '币种' },
|
||||||
|
{ value: 'exchange_rate', label: '汇率' },
|
||||||
|
{ value: 'supplier_name', label: '供应商' },
|
||||||
|
{ value: 'purchaser', label: '采购人' },
|
||||||
|
{ value: 'purchaser_email', label: '采购邮箱' },
|
||||||
|
{ value: 'source_link', label: '原始链接' },
|
||||||
|
{ value: 'detail_link', label: '详情链接' },
|
||||||
|
])
|
||||||
|
const operatorOptions = ref([
|
||||||
|
{ value: 'eq', label: '等于' },
|
||||||
|
{ value: 'ne', label: '不等于' },
|
||||||
|
{ value: 'contains', label: '包含' },
|
||||||
|
{ value: 'ge', label: '大于等于' },
|
||||||
|
{ value: 'le', label: '小于等于' }
|
||||||
|
])
|
||||||
|
|
||||||
// 基础列
|
// 基础列
|
||||||
const baseColumns = [
|
const baseColumns = [
|
||||||
{prop: 'company_name', label: '所属公司'},
|
{prop: 'company_name', label: '所属公司'},
|
||||||
@ -1123,7 +1193,10 @@ const fetchData = async () => {
|
|||||||
try {
|
try {
|
||||||
const params = {
|
const params = {
|
||||||
...queryParams,
|
...queryParams,
|
||||||
statuses: queryParams.statuses.join(',')
|
statuses: queryParams.statuses.join(','),
|
||||||
|
orderByColumn: queryParams.orderByColumn,
|
||||||
|
isAsc: queryParams.isAsc,
|
||||||
|
advancedFilters: JSON.stringify(queryParams.advancedFilters)
|
||||||
}
|
}
|
||||||
const res: any = await getBuyList(params)
|
const res: any = await getBuyList(params)
|
||||||
tableData.value = res.data.items || []
|
tableData.value = res.data.items || []
|
||||||
@ -1347,6 +1420,42 @@ const handleCameraConfirm = async (file: File) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
const isColumnSortable = (prop: string) => {
|
||||||
|
const sortableColumns = ['company_name', 'material_name', 'material_type', 'category', 'spec_model', 'unit', 'sku', 'barcode', 'inbound_date', 'serial_number', 'batch_number', 'status', 'inspection_status', 'qty_inbound', 'qty_stock', 'qty_available', 'warehouse_loc', 'unit_price', 'total_price', 'tax_rate', 'currency', 'exchange_rate', 'supplier_name', 'purchaser', 'purchaser_email', 'source_link', 'detail_link']
|
||||||
|
return sortableColumns.includes(prop)
|
||||||
|
}
|
||||||
|
const handleSortChange = ({ column, prop, order }: any) => {
|
||||||
|
if (prop && isColumnSortable(prop)) {
|
||||||
|
queryParams.orderByColumn = prop
|
||||||
|
queryParams.isAsc = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : undefined
|
||||||
|
} else {
|
||||||
|
queryParams.orderByColumn = ''
|
||||||
|
queryParams.isAsc = undefined
|
||||||
|
}
|
||||||
|
queryParams.page = 1
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
const handleDelete = async (row: any) => { try { await deleteBuyInbound(row.id); ElMessage.success('删除成功'); fetchData() } catch (e) { ElMessage.error('删除失败') } }
|
const handleDelete = async (row: any) => { try { await deleteBuyInbound(row.id); ElMessage.success('删除成功'); fetchData() } catch (e) { ElMessage.error('删除失败') } }
|
||||||
|
|
||||||
// ------------------------------------
|
// ------------------------------------
|
||||||
@ -1602,4 +1711,4 @@ onMounted(() => {
|
|||||||
.long-dropdown .el-input__suffix {
|
.long-dropdown .el-input__suffix {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user