feat(scrap): 报废记录按单号分组 + 可展开明细 + 高级搜索

一、后端按「订单」分组(原为平铺明细列表)
  有 scrap_request_no → 按单号分组;
  无单号(历史直接报废)→ 按 操作时间(精确到分钟) + 操作人 虚拟分组,
  生成 LEGACY-<yyyyMMddHHmm>-<操作人> 形式的虚拟单号,避免历史数据成为无主记录。
  每单返回 items 明细数组、损失合计、报废总数、申请人(取自 ScrapApproval)。

二、物料解析改为批量预加载
  原实现逐条记录 N+1 查询,且 5 条来源路径(stock_buy/semi/product、
  trans_borrow、trans_repair)各自 query.get()。改为按 (source_table, stock_id)
  收集 ID → 批量 joinedload 查询 → 内存拼装。

三、损失金额改为按权限可见
  原 /records 无条件剥离 total_loss,导致持有 scrap_list:loss_amount 的角色
  也看不到金额,与权限元素的存在相矛盾。改为对齐 outbound 的
  filter_item_by_permissions:无权限置 None,前端 v-if 隐藏整列。

四、新增 keyword / search_type 高级搜索参数
  (单号/SKU 在 SQL 层过滤,操作人/申请人/物料名在分组后过滤)

五、修复申请人显示
  ScrapApproval._user_name() 返回 '杜邢宸/duxingchen' 全名,统一经
  _display_name() 去掉 '/账号' 后缀。

前端 scrap/index.vue 重写为 el-table type=expand 嵌套表格,对齐出库记录版式,
搜索区改用统一 el-form inline 版式(含重置按钮)。

实测:3 单(1 张申请单 2 明细 + 1 组 legacy 合并 2 条),申请人/损失合计/
虚拟单号均正确。
This commit is contained in:
yueli
2026-09-10 12:06:04 +08:00
parent 3cadd42338
commit 23a7fc65f1
2 changed files with 423 additions and 147 deletions

View File

@ -16,6 +16,29 @@ import math
scrap_bp = Blueprint('scrap', __name__, url_prefix='/scrap')
# ==============================================================================
# 报废记录字段级权限过滤(对齐 outbound 的 filter_item_by_permissions
#
# 原先 /records 无条件剥离 cost_at_scrap / total_loss导致即使角色持有
# scrap_list:loss_amount 也看不到金额——与「损失金额」权限元素的存在相矛盾。
# 改为按权限决定可见性,与出库记录保持一致。
# ==============================================================================
def filter_scrap_by_permissions(item, user_permissions):
field_to_perm = {
'total_loss': 'scrap_list:loss_amount',
'loss_amount': 'scrap_list:loss_amount',
}
if 'scrap_list:*' in user_permissions:
return item
for field, perm_code in field_to_perm.items():
if field in item and perm_code not in user_permissions:
item[field] = None
for sub in (item.get('items') or []):
if isinstance(sub, dict):
filter_scrap_by_permissions(sub, user_permissions)
return item
# ==============================================================================
# 辅助函数:获取当前用户的完整权限列表(基于角色查询)
# ==============================================================================
@ -135,6 +158,8 @@ def get_scrap_records():
sku = request.args.get('sku', '')
start_date = request.args.get('start_date', '')
end_date = request.args.get('end_date', '')
keyword = request.args.get('keyword', '')
search_type = request.args.get('search_type', 'all')
try:
result = ScrapService.query_records(
@ -142,12 +167,16 @@ def get_scrap_records():
page_size=page_size,
sku=sku,
start_date=start_date,
end_date=end_date
end_date=end_date,
keyword=keyword,
search_type=search_type,
)
# ★ Fail-Closed: 报废记录剥离成本字段
for item in (result.get('list') or []):
for k in ('cost_at_scrap', 'total_loss'):
item.pop(k, None)
# 损失金额按 scrap_list:loss_amount 权限决定可见性(原为无条件剥离,
# 会让持有该权限的角色也看不到金额,与权限元素的存在相矛盾)
perms = get_current_user_permissions()
if 'scrap_list:*' not in perms:
for item in (result.get('list') or []):
filter_scrap_by_permissions(item, perms)
return jsonify({'code': 200, 'msg': 'success', 'data': result})
except Exception as e:
traceback.print_exc()
@ -333,9 +362,97 @@ class ScrapService:
db.session.commit()
return {'count': len(created_records)}
# ------------------------------------------------------------------
# 辅助:用户名解析(库里存 '张三/zhangsan' 格式时取斜杠前的姓名)
# ------------------------------------------------------------------
@staticmethod
def query_records(page=1, page_size=50, sku='', start_date='', end_date=''):
"""分页查询报废记录"""
def _display_name(raw):
if raw is None or raw == '':
return ''
s = str(raw)
if s.isdigit():
u = SysUser.query.get(int(s))
if u and u.username:
s = u.username
return s.split('/')[0] if '/' in s else s
# ------------------------------------------------------------------
# 辅助:批量解析报废行的物料名称/规格/库位/批号(多态 5 条来源路径)
# ------------------------------------------------------------------
@staticmethod
def _resolve_materials(rows):
from app.models.transaction import TransBorrow
from sqlalchemy.orm import joinedload
model_map = {
'stock_buy': StockBuy,
'stock_semi': StockSemi,
'stock_product': StockProduct,
}
resolved = {}
def _from_stock(stock_row):
if not stock_row:
return None
base = getattr(stock_row, 'base', None)
return {
'material_name': (base.name if base else '') or '',
'spec_model': (base.spec_model if base else '') or '',
'warehouse_location': getattr(stock_row, 'warehouse_location', '') or '',
'batch_number': (getattr(stock_row, 'batch_number', '')
or getattr(stock_row, 'serial_number', '') or ''),
}
# 1) 常规库存三表:批量拉取并 joinedload 物料主表
for table, model in model_map.items():
ids = {r.stock_id for r in rows if r.source_table == table and r.stock_id}
if not ids:
continue
for obj in model.query.options(joinedload(model.base)).filter(model.id.in_(ids)).all():
resolved[(table, obj.id)] = _from_stock(obj)
# 2) 借库转报废stock_id 指向 trans_borrow.id再经其 source_table 找库存
borrow_ids = {r.stock_id for r in rows if r.source_table == 'trans_borrow' and r.stock_id}
if borrow_ids:
borrows = TransBorrow.query.filter(TransBorrow.id.in_(borrow_ids)).all()
inner_ids = {}
for b in borrows:
if b.source_table in model_map and b.stock_id:
inner_ids.setdefault(b.source_table, set()).add(b.stock_id)
inner_map = {}
for table, ids in inner_ids.items():
model = model_map[table]
for obj in model.query.options(joinedload(model.base)).filter(model.id.in_(ids)).all():
inner_map[(table, obj.id)] = _from_stock(obj)
for b in borrows:
info = inner_map.get((b.source_table, b.stock_id))
if info:
resolved[('trans_borrow', b.id)] = info
# 3) 维修单来源:物料名在 TransRepair 上,无规格
repair_ids = {r.stock_id for r in rows if r.source_table == 'trans_repair' and r.stock_id}
if repair_ids:
for rp in TransRepair.query.filter(TransRepair.id.in_(repair_ids)).all():
resolved[('trans_repair', rp.id)] = {
'material_name': rp.material_name or '',
'spec_model': '',
'warehouse_location': '',
'batch_number': getattr(rp, 'serial_number', '') or '',
}
return resolved
@staticmethod
def query_records(page=1, page_size=50, sku='', start_date='', end_date='',
keyword='', search_type='all'):
"""
分页查询报废记录 —— ★ 按报废申请单号分组,返回「订单级」结果。
· 有 scrap_request_no按单号分组
· 无单号(历史直接报废):按 操作时间(精确到分钟) + 操作人 虚拟分组,
并生成可读的虚拟单号,避免历史数据成为无主记录。
每单返回 items 明细数组、损失合计、申请人(取自 ScrapApproval
"""
query = TransScrap.query
if sku:
@ -346,6 +463,12 @@ class ScrapService:
if end_date:
query = query.filter(TransScrap.operation_time <= end_date + ' 23:59:59')
# 单号 / SKU 可在 SQL 层直接过滤
if keyword and search_type == 'no':
query = query.filter(TransScrap.scrap_request_no.ilike(f'%{keyword}%'))
elif keyword and search_type == 'sku':
query = query.filter(TransScrap.sku.ilike(f'%{keyword}%'))
# 【行级数据隔离】基于 JWT 多租户公司过滤
# 通过 stock 表或 trans_repair 关联到 MaterialBase
company_limit = get_current_company_filter()
@ -403,83 +526,117 @@ class ScrapService:
# 按时间倒序
query = query.order_by(TransScrap.operation_time.desc())
total = query.count()
records = query.offset((page - 1) * page_size).limit(page_size).all()
# ==================================================================
# ★ 分组:先取全量(报废量级小),在内存里按「订单」聚合后再分页
# 有单号 → 按 scrap_request_no
# 无单号 → 按 操作时间(分钟) + 操作人 虚拟分组
# ==================================================================
rows = query.all()
mat_info = ScrapService._resolve_materials(rows)
# 遍历结果,补充操作人姓名、物料名称、规格
result_list = []
for r in records:
item = r.to_dict()
# 1. 解析操作人姓名
if r.operator_name:
# operator_name 可能是用户ID或用户名尝试解析为真实姓名
try:
# 尝试将 operator_name 当作用户ID查询
user_id = int(r.operator_name)
user = SysUser.query.get(user_id)
if user:
# 解析存储格式: "张三/zhangsan"
raw_name = user.username
if '/' in raw_name:
item['operator_name'] = raw_name.split('/')[0]
except (ValueError, TypeError):
# 如果不是数字ID保持原值
pass
# 2. 多态解析物料名称与规格
material_name = ''
spec_model = ''
if r.source_table == 'trans_borrow':
# 借库转报废stock_id 指向 trans_borrow.id通过源库存表取物料
from app.models.transaction import TransBorrow
borrow = TransBorrow.query.get(r.stock_id)
if borrow:
bstock_model = None
if borrow.source_table == 'stock_buy':
bstock_model = StockBuy.query.get(borrow.stock_id)
elif borrow.source_table == 'stock_semi':
bstock_model = StockSemi.query.get(borrow.stock_id)
elif borrow.source_table == 'stock_product':
bstock_model = StockProduct.query.get(borrow.stock_id)
if bstock_model and hasattr(bstock_model, 'base_id') and bstock_model.base_id:
base = MaterialBase.query.get(bstock_model.base_id)
if base:
material_name = base.name or ''
spec_model = base.spec_model or ''
elif r.source_table == 'trans_repair':
# 维修单
repair = TransRepair.query.get(r.stock_id)
if repair:
material_name = repair.material_name or ''
spec_model = ''
elif r.source_table in ['stock_buy', 'stock_semi', 'stock_product']:
# 常规库存表
stock_model = None
if r.source_table == 'stock_buy':
stock_model = StockBuy.query.get(r.stock_id)
elif r.source_table == 'stock_semi':
stock_model = StockSemi.query.get(r.stock_id)
elif r.source_table == 'stock_product':
stock_model = StockProduct.query.get(r.stock_id)
if stock_model and hasattr(stock_model, 'base_id') and stock_model.base_id:
base = MaterialBase.query.get(stock_model.base_id)
if base:
material_name = base.name or ''
spec_model = base.spec_model or ''
elif stock_model and hasattr(stock_model, 'base') and stock_model.base:
material_name = stock_model.base.name or ''
spec_model = stock_model.base.spec_model or ''
item['material_name'] = material_name
item['spec_model'] = spec_model
result_list.append(item)
groups = {}
for r in rows:
if r.scrap_request_no:
gkey = ('req', r.scrap_request_no)
else:
ts = r.operation_time.strftime('%Y-%m-%d %H:%M') if r.operation_time else '未知时间'
gkey = ('legacy', ts, r.operator_name or '')
g = groups.get(gkey)
if g is None:
op_name = ScrapService._display_name(r.operator_name)
if gkey[0] == 'req':
req_no = r.scrap_request_no
else:
# 虚拟单号:无单号的历史直接报废,仍给出可读标识便于追溯
ts_raw = r.operation_time.strftime('%Y%m%d%H%M') if r.operation_time else '000000000000'
req_no = f"LEGACY-{ts_raw}-{op_name or '未知'}"
g = {
'scrap_request_no': req_no,
'is_legacy': gkey[0] == 'legacy',
'operator_name': op_name,
'applicant_id': None,
'applicant_name': '',
'scrap_time': r.operation_time.strftime('%Y-%m-%d %H:%M:%S') if r.operation_time else '',
'_sort_time': r.operation_time,
'approval_status': r.approval_status or '',
'total_loss': 0.0,
'total_quantity': 0.0,
'reason': r.reason or '',
'items': [],
}
groups[gkey] = g
info = mat_info.get((r.source_table, r.stock_id), {})
qty = float(r.quantity or 0)
loss = float(r.total_loss or 0)
g['total_quantity'] += qty
g['total_loss'] += loss
g['items'].append({
'id': r.id,
'sku': r.sku or '',
'material_name': info.get('material_name', ''),
'spec_model': info.get('spec_model', ''),
'warehouse_location': info.get('warehouse_location', ''),
'batch_number': info.get('batch_number', ''),
'quantity': qty,
'reason': r.reason or '',
'source_table': r.source_table or '',
'loss_amount': round(loss, 2),
})
orders = list(groups.values())
# ★ 补申请人:有单号的取 ScrapApproval无单号的以操作人兜底
req_nos = [o['scrap_request_no'] for o in orders if not o['is_legacy']]
if req_nos:
from app.models.scrap_approval import ScrapApproval
approver_cache = {}
for ap in ScrapApproval.query.filter(
ScrapApproval.request_no.in_(req_nos)
).all():
if ap.applicant_id not in approver_cache:
# 统一走 _display_name去掉 username 里的 '/账号' 后缀
approver_cache[ap.applicant_id] = ScrapService._display_name(
ScrapApproval._user_name(ap.applicant_id)
)
o = groups.get(('req', ap.request_no))
if o:
o['applicant_id'] = ap.applicant_id
o['applicant_name'] = approver_cache.get(ap.applicant_id, '')
for o in orders:
if not o['applicant_name']:
o['applicant_name'] = o['operator_name']
o['total_loss'] = round(o['total_loss'], 2)
o['items'].sort(key=lambda x: x['sku'] or '')
# 关键词过滤:单号/SKU 已在 SQL 层处理,这里处理操作人/申请人/物料名
if keyword and search_type in ('all', 'name', 'material_name'):
kw = keyword.lower()
def _hit(o):
if search_type == 'name':
return kw in (o['operator_name'] or '').lower() or kw in (o['applicant_name'] or '').lower()
if search_type == 'material_name':
return any(kw in (it['material_name'] or '').lower() for it in o['items'])
return (kw in (o['scrap_request_no'] or '').lower()
or kw in (o['operator_name'] or '').lower()
or kw in (o['applicant_name'] or '').lower()
or any(kw in (it['sku'] or '').lower()
or kw in (it['material_name'] or '').lower()
or kw in (it['spec_model'] or '').lower() for it in o['items']))
orders = [o for o in orders if _hit(o)]
orders.sort(key=lambda o: (o['_sort_time'] is None, o['_sort_time']), reverse=True)
total = len(orders)
start = (page - 1) * page_size
paged = orders[start:start + page_size]
for o in paged:
o.pop('_sort_time', None)
return {
'list': result_list,
'list': paged,
'total': total,
'page': page,
'pageSize': page_size

View File

@ -1,48 +1,125 @@
<template>
<div v-if="userStore.hasPermission('scrap_list')" class="app-container">
<div class="filter-container">
<el-input v-model="sku" placeholder="SKU" style="width: 200px" @keyup.enter="fetchData" />
<el-date-picker
v-model="dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="margin-left: 10px; width: 240px"
/>
<el-button type="primary" @click="fetchData">查询</el-button>
<el-button @click="resetFilter">重置</el-button>
</div>
<!-- 高级搜索与出库记录 / 借还记录统一版式 -->
<el-form :inline="true" class="filter-form" @submit.prevent>
<el-form-item label="搜索">
<el-input
v-model="listQuery.keyword"
placeholder="输入关键词"
style="width: 240px"
clearable
@input="debouncedSearch"
@clear="handleClearSearch"
>
<template #prepend>
<el-select v-model="listQuery.search_type" placeholder="搜索类型" style="width: 110px">
<el-option label="全部" value="all" />
<el-option label="单号" value="no" />
<el-option label="操作人" value="name" />
<el-option label="SKU" value="sku" />
<el-option label="物料名称" value="material_name" />
</el-select>
</template>
</el-input>
</el-form-item>
<el-form-item label="报废日期">
<el-date-picker
v-model="listQuery.dateRange"
type="daterange"
range-separator=""
start-placeholder="开始日期"
end-placeholder="结束日期"
value-format="YYYY-MM-DD"
style="width: 260px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="fetchData">查询</el-button>
<el-button @click="resetFilter">重置</el-button>
</el-form-item>
</el-form>
<el-table
:data="list"
border
stripe
style="margin-top: 20px"
v-loading="loading"
:data="list"
border
v-loading="loading"
style="width: 100%; margin-top: 20px;"
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
>
<el-table-column prop="sku" label="SKU" width="140" show-overflow-tooltip />
<el-table-column label="物料名称" min-width="160">
<template #default="{ row }">
{{ row.material_name || '-' }}
<!-- 展开行报废明细 -->
<el-table-column type="expand">
<template #default="props">
<div style="padding: 10px 40px; background: #fafafa;">
<h4 style="margin: 0 0 10px 0; color: #F56C6C;">报废明细 ({{ props.row.items?.length || 0 }} )</h4>
<el-table :data="props.row.items" border size="small">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="sku" label="SKU" width="150" show-overflow-tooltip />
<el-table-column prop="material_name" label="物料名称" min-width="160" show-overflow-tooltip>
<template #default="{ row }">{{ row.material_name || '-' }}</template>
</el-table-column>
<el-table-column prop="spec_model" label="规格型号" min-width="150" show-overflow-tooltip>
<template #default="{ row }">{{ row.spec_model || '-' }}</template>
</el-table-column>
<el-table-column label="库位" width="120" show-overflow-tooltip>
<template #default="{ row }">{{ row.warehouse_location || '-' }}</template>
</el-table-column>
<el-table-column label="批次/序列号" width="130" show-overflow-tooltip>
<template #default="{ row }">{{ row.batch_number || '-' }}</template>
</el-table-column>
<el-table-column prop="quantity" label="报废数量" width="100" align="right" />
<el-table-column v-if="canViewLoss" label="损失金额" width="110" align="right">
<template #default="{ row }">
<span v-if="row.loss_amount !== null && row.loss_amount !== undefined" style="color:#F56C6C;">
¥{{ row.loss_amount.toFixed(2) }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="reason" label="报废原因" min-width="160" show-overflow-tooltip>
<template #default="{ row }">{{ row.reason || '-' }}</template>
</el-table-column>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column label="规格型号" min-width="140">
<el-table-column label="报废单号" min-width="220" show-overflow-tooltip>
<template #default="{ row }">
{{ row.spec_model || '-' }}
<span>{{ row.scrap_request_no }}</span>
<el-tag v-if="row.is_legacy" size="small" type="info" style="margin-left:6px;">历史</el-tag>
</template>
</el-table-column>
<el-table-column prop="quantity" label="报废数量" width="100" align="right" />
<el-table-column prop="reason" label="报废原因" min-width="160" show-overflow-tooltip />
<el-table-column prop="operator_name" label="操作人" width="100" />
<el-table-column prop="operation_time" label="报废时间" width="160" />
<el-table-column label="损失金额" width="100" align="right">
<el-table-column prop="applicant_name" label="申请人" width="110" show-overflow-tooltip>
<template #default="{ row }">{{ row.applicant_name || '-' }}</template>
</el-table-column>
<el-table-column prop="operator_name" label="操作人" width="110" show-overflow-tooltip>
<template #default="{ row }">{{ row.operator_name || '-' }}</template>
</el-table-column>
<el-table-column label="报废时间" width="170" align="center">
<template #default="{ row }">
{{ row.total_loss ? `¥${row.total_loss}` : '-' }}
<span>{{ row.scrap_time ? row.scrap_time.substring(0, 16) : '-' }}</span>
</template>
</el-table-column>
<el-table-column prop="approval_status" label="审批状态" width="100" align="center">
<el-table-column label="报废总数" width="100" align="right">
<template #default="{ row }">{{ row.total_quantity }}</template>
</el-table-column>
<el-table-column v-if="canViewLoss" label="总损失金额" width="130" align="right">
<template #default="{ row }">
<span v-if="row.total_loss !== null && row.total_loss !== undefined" style="color:#F56C6C; font-weight:bold; font-size:15px;">
¥{{ Number(row.total_loss).toFixed(2) }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="审批状态" width="110" align="center">
<template #default="{ row }">
<el-tag :type="getStatusType(row.approval_status)">
{{ getStatusText(row.approval_status) }}
@ -51,20 +128,21 @@
</el-table-column>
</el-table>
<el-pagination
background
layout="prev, pager, next, total"
:total="total"
:page-size="pageSize"
:current-page="page"
@current-change="handlePage"
style="margin-top: 10px; text-align: right"
/>
<div style="margin-top: 20px; text-align: right;">
<el-pagination
background
layout="prev, pager, next, total"
:total="total"
:page-size="listQuery.pageSize"
:current-page="listQuery.page"
@current-change="handlePage"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue'
import { getScrapRecords } from '@/api/scrap'
import { useUserStore } from '@/stores/user'
@ -72,70 +150,111 @@ const userStore = useUserStore()
const list = ref<any[]>([])
const total = ref(0)
const page = ref(1)
const pageSize = ref(50)
const sku = ref('')
const dateRange = ref<[string, string] | null>(null)
const loading = ref(false)
let debounceTimer: ReturnType<typeof setTimeout> | null = null
const listQuery = reactive({
page: 1,
pageSize: 50,
keyword: '',
search_type: 'all',
dateRange: [] as string[],
})
// 损失金额可见性:与后端 scrap_list:loss_amount 权限一致
const canViewLoss = computed(() =>
userStore.role === 'SUPER_ADMIN' || userStore.hasPermission('scrap_list:loss_amount')
)
const fetchData = async () => {
loading.value = true
try {
const params: any = {
page: page.value,
pageSize: pageSize.value,
sku: sku.value
page: listQuery.page,
pageSize: listQuery.pageSize,
keyword: listQuery.keyword,
search_type: listQuery.search_type,
}
if (dateRange.value && dateRange.value.length === 2) {
params.start_date = dateRange.value[0]
params.end_date = dateRange.value[1]
if (listQuery.dateRange && listQuery.dateRange.length === 2) {
params.start_date = listQuery.dateRange[0]
params.end_date = listQuery.dateRange[1]
}
const res = await getScrapRecords(params)
list.value = res.data.list || []
total.value = res.data.total || 0
const res: any = await getScrapRecords(params)
list.value = res.data?.list || []
total.value = res.data?.total || 0
} catch (e) {
console.error('加载报废记录失败', e)
} finally {
loading.value = false
}
}
const debouncedSearch = () => {
if (debounceTimer) clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => {
listQuery.page = 1
fetchData()
}, 500)
}
const handleClearSearch = () => {
if (debounceTimer) clearTimeout(debounceTimer)
listQuery.page = 1
fetchData()
}
const handlePage = (val: number) => {
page.value = val
listQuery.page = val
fetchData()
}
const resetFilter = () => {
sku.value = ''
dateRange.value = null
page.value = 1
listQuery.keyword = ''
listQuery.search_type = 'all'
listQuery.dateRange = []
listQuery.page = 1
fetchData()
}
// 报废台账写在执行/提交时approval_status 的取值为这两个
const getStatusType = (status: string) => {
const map: Record<string, string> = {
executed: 'success',
approved: 'success',
rejected: 'danger',
pending: 'warning'
pending: 'warning',
}
return map[status] || 'info'
}
const getStatusText = (status: string) => {
const map: Record<string, string> = {
approved: '已审批',
executed: '已执行',
approved: '已报废',
rejected: '已拒绝',
pending: '待审批'
pending: '待审批',
}
return map[status] || status
return map[status] || status || '-'
}
onMounted(fetchData)
onBeforeUnmount(() => {
if (debounceTimer) {
clearTimeout(debounceTimer)
debounceTimer = null
}
})
</script>
<style scoped>
.filter-container {
.filter-form {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.filter-form :deep(.el-form-item) {
margin-bottom: 0;
margin-right: 12px;
}
</style>