Files
KCGL/inventory-web/src/views/my-requests/index.vue
yueli 851c3a9c1f feat(my-requests): 主列表展示数量,移除类型列
问题
----
主列表只有「物料数 N 种」(种类数),具体数量藏在展开行的明细表里,
不展开就看不到。

改动
----
· 移除「类型」列(type_label);
· 新增「数量」列,紧跟申请单号,橙色加粗;
· 新增 totalQty() 汇总单据总数量。

totalQty 只认 quantity 字段,无需按 type 分支 —— 因为聚合端点已做字段
归一化(报废的 scrap_qty → quantity)。这正是当初做归一化的价值。
数值经 Number(sum.toFixed(4)) 处理,去掉 1.0000 这类无意义的小数尾巴。

顺带移除已无引用的 typeTagType()。
2026-09-10 15:57:15 +08:00

254 lines
9.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="app-container">
<el-card shadow="never">
<template #header>
<div class="card-header">
<span class="title">我的申请单</span>
<span class="subtitle">这里汇总你提交过的全部申请出库 / 借库 / 报废</span>
</div>
</template>
<!-- 筛选 -->
<div class="filter-container">
<span class="filter-label">类型:</span>
<el-radio-group v-model="filterType" @change="handleFilterChange">
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="outbound">出库</el-radio-button>
<el-radio-button label="borrow">借库</el-radio-button>
<el-radio-button label="scrap">报废</el-radio-button>
</el-radio-group>
<span class="filter-label" style="margin-left:16px;">状态:</span>
<el-radio-group v-model="filterStatus" @change="handleFilterChange">
<el-radio-button label="">全部</el-radio-button>
<el-radio-button :label="0">待审批</el-radio-button>
<el-radio-button :label="1">已通过</el-radio-button>
<el-radio-button :label="2">已驳回</el-radio-button>
<el-radio-button :label="3">已完成</el-radio-button>
<el-radio-button :label="4">已撤回</el-radio-button>
</el-radio-group>
<el-button type="primary" :icon="Refresh" :loading="loading" @click="fetchData">刷新</el-button>
</div>
<el-table :data="list" v-loading="loading" border stripe style="width:100%; margin-top:16px">
<el-table-column type="expand">
<template #default="{ row }">
<div class="expand-box">
<p class="expand-title">物料明细 {{ row.items?.length || 0 }} </p>
<el-table :data="row.items || []" border size="small" style="width:100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="name" label="名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="spec_model" label="规格型号" min-width="130" show-overflow-tooltip />
<el-table-column prop="sku" label="SKU" width="130" show-overflow-tooltip />
<el-table-column prop="warehouse_location" label="库位" width="120" show-overflow-tooltip />
<!-- 数量字段已由后端归一化报废的 scrap_qty quantity -->
<el-table-column prop="quantity" label="数量" width="90" align="right" />
</el-table>
</div>
</template>
</el-table-column>
<el-table-column prop="request_no" label="申请单号" min-width="190" show-overflow-tooltip />
<!-- 数量前置并直接展示原先只显示N 种类数具体数量
藏在展开行的明细表里主列表看不到 -->
<el-table-column label="数量" width="90" align="center">
<template #default="{ row }">
<span style="color: #E6A23C; font-weight: bold; font-size: 15px;">
{{ totalQty(row) }}
</span>
</template>
</el-table-column>
<el-table-column label="物料数" width="80" align="center">
<template #default="{ row }">{{ row.items?.length || 0 }} </template>
</el-table-column>
<el-table-column prop="remark" label="申请原因" min-width="150" show-overflow-tooltip>
<template #default="{ row }">{{ row.remark || '-' }}</template>
</el-table-column>
<el-table-column label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag size="small" :type="statusTagType(row.status)">{{ statusText(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="驳回原因" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.status === 2" style="color:#F56C6C;">{{ row.reject_reason || '-' }}</span>
<span v-else style="color:#c0c4cc;">-</span>
</template>
</el-table-column>
<el-table-column label="提交时间" width="160" align="center">
<template #default="{ row }">
<span>{{ (row.created_at || '').substring(0, 16) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="{ row }">
<!-- 待审批(0)已通过(1)可撤回 两者都已预占库存但尚未执行 -->
<el-button
v-if="row.status === 0 || row.status === 1"
type="warning" link size="small"
:loading="row._withdrawing"
@click="handleWithdraw(row)"
>撤回</el-button>
<span v-else style="color:#c0c4cc;">-</span>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!loading && list.length === 0" description="暂无申请单" :image-size="70" />
<el-pagination
v-if="total > 0"
background
style="margin-top:16px; justify-content:flex-end; display:flex;"
layout="total, prev, pager, next"
:total="total"
:page-size="pageSize"
:current-page="page"
@current-change="handlePageChange"
/>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Refresh } from '@element-plus/icons-vue'
import { getMyRequests } from '@/api/outbound'
import request from '@/utils/request'
// ============================================================================
// 我的申请单(跨模块聚合)
//
// 数据来自专用聚合端点 /my-requests服务端硬编码 applicant_id = 当前用户,
// 因此本页对任何登录用户可用,且从设计上不可能看到他人单据。
//
// 撤回是**写操作**,保留在各模块自己的端点(三者释放逻辑不同),
// 后端在每条记录上回传 withdraw_endpoint前端按此分发 —— 无需在此
// 硬编码三个模块的 URL 映射。
// ============================================================================
const list = ref<any[]>([])
const loading = ref(false)
const total = ref(0)
const page = ref(1)
const pageSize = ref(20)
const filterType = ref<'all' | 'outbound' | 'borrow' | 'scrap'>('all')
const filterStatus = ref<number | ''>('')
const statusText = (s: number) => {
const map: Record<number, string> = {
0: '待审批', 1: '已通过', 2: '已驳回', 3: '已完成', 4: '已撤回',
}
return map[s] ?? '-'
}
// 状态配色:
// 0 待审批 → 蓝primary待处理
// 1 已通过 → 绿success正向结果
// 2 已驳回 → 红danger
// 3 已完成 → 灰info终态、不再需要关注
// 4 已撤回 → 黄warning需留意但非错误
const statusTagType = (s: number) => {
const map: Record<number, string> = {
0: 'primary', 1: 'success', 2: 'danger', 3: 'info', 4: 'warning',
}
return map[s] ?? 'info'
}
// 单据的总数量 = 各明细数量之和
//
// 数量字段已由后端归一化(报废的 scrap_qty → quantity此处只认 quantity
// 无需按 type 分支处理 —— 这正是聚合端点做字段归一化的意义。
const totalQty = (row: any): string => {
const items = row?.items || []
if (!items.length) return '-'
const sum = items.reduce((acc: number, it: any) => acc + (Number(it.quantity) || 0), 0)
// 去掉无意义的小数尾巴1.0000 → 1
return String(Number(sum.toFixed(4)))
}
const fetchData = async () => {
loading.value = true
try {
const res: any = await getMyRequests({
page: page.value,
limit: pageSize.value,
type: filterType.value,
status: filterStatus.value,
})
const items = res?.data?.items || []
list.value = items.map((r: any) => ({ ...r, _withdrawing: false }))
total.value = res?.data?.total || 0
} catch (e: any) {
ElMessage.error(e?.msg || '加载我的申请单失败')
} finally {
loading.value = false
}
}
const handleFilterChange = () => {
page.value = 1
fetchData()
}
const handlePageChange = (p: number) => {
page.value = p
fetchData()
}
const handleWithdraw = async (row: any) => {
if (!row.withdraw_endpoint) {
return ElMessage.error('该类型暂不支持撤回')
}
const itemCount = row.items?.length || 0
try {
await ElMessageBox.confirm(
`确定撤回【${row.type_label}${row.request_no} 吗?\n\n` +
`撤回后该单将作废,其预占的 ${itemCount} 项物料库存会立即释放,` +
`可供其它申请使用。\n此操作不可恢复。`,
'⚠️ 撤回确认',
{ confirmButtonText: '确认撤回', cancelButtonText: '取消', type: 'warning' }
)
} catch (e) {
return
}
row._withdrawing = true
try {
// ★ 按后端回传的端点分发,前端不硬编码三模块 URL 映射
const res: any = await request({ url: row.withdraw_endpoint, method: 'post' })
ElMessage.success(res?.msg || `申请单 ${row.request_no} 已撤回`)
await fetchData()
} catch (err: any) {
ElMessage.error(err?.msg || err?.response?.data?.msg || '撤回失败')
} finally {
row._withdrawing = false
}
}
onMounted(fetchData)
</script>
<style scoped>
.card-header { display: flex; align-items: baseline; gap: 10px; }
.title { font-size: 17px; font-weight: bold; }
.subtitle { font-size: 12px; color: #909399; }
.filter-container {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.filter-label { font-size: 14px; color: #606266; font-weight: 500; }
.expand-box { padding: 12px 24px; background: #f5f7fa; }
.expand-title { margin: 0 0 10px 0; font-weight: bold; font-size: 13px; color: #606266; }
</style>