feat(purchase): 采购列表按批次分组展示 + 批次级一键审批
- 列表按批次分组(树形展开):一个批次一行,显示物品数/批次总价/申请人/状态汇总 - 展开批次行显示批内明细(每条记录状态/价格/操作) - 批次行提供「审批本批」「驳回本批」一键操作(按批次前缀批量处理待审批记录) - 保留逐条展开明细内的通过/驳回,以及顶部多选批量审批 - fetchData 按批次前缀聚合当前页数据
This commit is contained in:
@ -17,62 +17,89 @@
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- ★ 批量审批操作栏(仅待审批页 + 有审批权限) -->
|
||||
<div v-if="filterStatus === 0 && canApprove && selectedRows.length > 0" class="batch-bar">
|
||||
<span>已选 <b style="color:#409EFF;">{{ selectedRows.length }}</b> 条待审批申请</span>
|
||||
<el-button type="success" size="small" :loading="batchLoading" @click="handleBatchApprove">批量通过</el-button>
|
||||
<el-button type="danger" size="small" :loading="batchLoading" @click="openBatchRejectDialog">批量驳回</el-button>
|
||||
<el-button size="small" @click="clearSelection">取消选择</el-button>
|
||||
</div>
|
||||
<!-- ★ 按批次分组的列表 -->
|
||||
<el-table v-loading="loading" :data="list" border stripe style="margin-top: 16px;" row-key="batchKey">
|
||||
<!-- ★ 展开/收起 + 批次信息 -->
|
||||
<el-table-column type="expand" width="50" align="center">
|
||||
<template #default="{ row }">
|
||||
<div style="padding: 12px 24px; background: #f5f7fa;">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">批次明细({{ row.itemCount }} 项)</div>
|
||||
<el-table :data="row.items" border size="small" style="width: 100%;">
|
||||
<el-table-column prop="request_no" label="申请单号" width="200" />
|
||||
<el-table-column prop="name" label="采购物品" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="spec_model" label="规格型号" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="quantity" label="数量" width="70" align="center" />
|
||||
<el-table-column prop="purchase_date" label="采购日期" width="110" />
|
||||
<el-table-column v-if="userStore.hasPermission('inbound_purchase:unit_price')" label="单价" width="90" align="right">
|
||||
<template #default="{ row: it }">
|
||||
{{ it.unit_price ? '¥' + Number(it.unit_price).toFixed(2) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="userStore.hasPermission('inbound_purchase:total_price')" label="总价" width="100" align="right">
|
||||
<template #default="{ row: it }">
|
||||
{{ it.total_price ? '¥' + Number(it.total_price).toFixed(2) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="90" align="center">
|
||||
<template #default="{ row: it }">
|
||||
<el-tag :type="statusTagType(it.status)" size="small">{{ it.status_text }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="110" align="center">
|
||||
<template #default="{ row: it }">
|
||||
<el-button type="primary" link size="small" @click="openDetailDialog(it)">详情</el-button>
|
||||
<template v-if="it.status === 0 && canApprove">
|
||||
<el-button type="success" link size="small" @click="handleApprove(it)">通过</el-button>
|
||||
<el-button type="danger" link size="small" @click="openRejectDialog(it)">驳回</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table v-loading="loading" :data="list" border stripe style="margin-top: 16px;" row-key="id" @selection-change="handleSelectionChange">
|
||||
<!-- ★ 多选列:仅待审批状态可勾选 -->
|
||||
<el-table-column v-if="filterStatus === 0 && canApprove" type="selection" width="50" align="center"
|
||||
:selectable="(row: any) => row.status === 0" />
|
||||
<el-table-column prop="request_no" label="申请单号" width="180" />
|
||||
<el-table-column prop="name" label="采购物品" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="spec_model" label="规格型号" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="quantity" label="数量" width="80" align="center" />
|
||||
<el-table-column prop="purchase_date" label="采购日期" width="110" />
|
||||
<el-table-column v-if="userStore.hasPermission('inbound_purchase:unit_price')" label="含税单价" width="110" align="right">
|
||||
<!-- 批次主行 -->
|
||||
<el-table-column label="批次号" width="220">
|
||||
<template #default="{ row }">
|
||||
{{ row.unit_price ? '¥' + Number(row.unit_price).toFixed(2) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="userStore.hasPermission('inbound_purchase:total_price')" label="含税总价" width="120" align="right">
|
||||
<template #default="{ row }">
|
||||
{{ row.total_price ? '¥' + Number(row.total_price).toFixed(2) : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="userStore.hasPermission('inbound_purchase:tax_rate')" prop="tax_rate" label="税率" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.tax_rate != null ? row.tax_rate + '%' : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="requester_name" label="申请人" width="100" />
|
||||
<el-table-column prop="approver_name" label="审批人" width="100" />
|
||||
<!-- ★ 批次号列:同一批提交的记录共享(request_no 去掉末尾流水号),可点击查看整批 -->
|
||||
<el-table-column label="批次号" width="170">
|
||||
<template #default="{ row }">
|
||||
<el-link v-if="getBatchKey(row.request_no)" type="primary" :underline="false" @click="openBatchDialog(row.request_no)">
|
||||
{{ getBatchKey(row.request_no) }}
|
||||
<el-link type="primary" :underline="false" @click="openBatchDialog(row.batchKey)">
|
||||
{{ row.batchKey }}
|
||||
</el-link>
|
||||
<span v-else style="color:#ccc">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<el-table-column label="物品" width="70" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" size="small">{{ row.status_text }}</el-tag>
|
||||
<el-tag size="small" type="info">{{ row.itemCount }} 项</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="240" fixed="right" align="center">
|
||||
<el-table-column v-if="userStore.hasPermission('inbound_purchase:total_price')" label="批次总价" width="120" align="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link size="small" @click="openDetailDialog(row)">详情</el-button>
|
||||
<el-button v-if="getBatchKey(row.request_no)" type="info" link size="small" @click="openBatchDialog(row.request_no)">整批</el-button>
|
||||
<template v-if="row.status === 0 && canApprove">
|
||||
<el-button type="success" link size="small" @click="handleApprove(row)">通过</el-button>
|
||||
<el-button type="danger" link size="small" @click="openRejectDialog(row)">驳回</el-button>
|
||||
<span style="font-weight: bold; color: #303133;">¥{{ Number(row.totalAmount).toFixed(2) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="requesterName" label="申请人" width="100" />
|
||||
<el-table-column prop="purchaseDate" label="采购日期" width="110" />
|
||||
<el-table-column label="状态" width="140" align="center">
|
||||
<template #default="{ row }">
|
||||
<template v-for="(text, i) in row.statusTexts" :key="i">
|
||||
<el-tag v-if="row.statusTexts.length === 1"
|
||||
:type="statusTagType(row.summaryStatus)" size="small" style="margin-right:2px;">
|
||||
{{ text }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<el-tag v-if="row.statusTexts.length > 1" type="warning" size="small">混合</el-tag>
|
||||
<div v-if="row.pendingCount > 0" style="font-size: 11px; color: #F56C6C;">
|
||||
待审批 {{ row.pendingCount }} 项
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="info" link size="small" @click="openBatchDialog(row.batchKey)">查看整批</el-button>
|
||||
<!-- ★ 一键审批本批 -->
|
||||
<template v-if="row.pendingCount > 0 && canApprove">
|
||||
<el-button type="success" link size="small" :loading="batchLoading" @click="handleApproveBatch(row.batchKey)">审批本批</el-button>
|
||||
<el-button type="danger" link size="small" :loading="batchLoading" @click="openRejectBatchDialog(row.batchKey)">驳回本批</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@ -349,6 +376,22 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ========== 驳回本批弹窗 ========== -->
|
||||
<el-dialog v-model="rejectBatchDialogVisible" title="驳回本批" width="480px" destroy-on-close :close-on-click-modal="false" :close-on-press-escape="false">
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="批次号">
|
||||
<el-tag>{{ rejectBatchKey }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="驳回原因" required>
|
||||
<el-input v-model="rejectBatchReason" type="textarea" :rows="4" placeholder="请填写统一驳回原因(必填)" maxlength="200" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="rejectBatchDialogVisible = false">取消</el-button>
|
||||
<el-button type="danger" :loading="batchLoading" @click="confirmRejectBatch">确认驳回本批</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -400,6 +443,60 @@ const clearSelection = () => {
|
||||
selectedRows.value = []
|
||||
}
|
||||
|
||||
// ★ 审批本批:一键通过批次内所有待审批记录
|
||||
const handleApproveBatch = async (batchKey: string) => {
|
||||
const batch = list.value.find(b => b.batchKey === batchKey)
|
||||
if (!batch || batch.pendingCount === 0) return ElMessage.warning('本批没有待审批记录')
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定通过批次【${batchKey}】的 ${batch.pendingCount} 条待审批记录吗?`,
|
||||
'审批本批',
|
||||
{ confirmButtonText: '全部通过', cancelButtonText: '取消', type: 'info' }
|
||||
)
|
||||
} catch { return }
|
||||
|
||||
batchLoading.value = true
|
||||
try {
|
||||
const res: any = await batchApprovePurchase({ batch_key: batchKey, action: 'approve' })
|
||||
ElMessage.success(res?.msg || '本批已全部通过')
|
||||
await fetchData()
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err?.msg || '审批失败')
|
||||
} finally {
|
||||
batchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ★ 驳回本批:批次内所有待审批记录统一驳回
|
||||
const rejectBatchKey = ref('')
|
||||
const rejectBatchDialogVisible = ref(false)
|
||||
const rejectBatchReason = ref('')
|
||||
|
||||
const openRejectBatchDialog = (batchKey: string) => {
|
||||
rejectBatchKey.value = batchKey
|
||||
rejectBatchReason.value = ''
|
||||
rejectBatchDialogVisible.value = true
|
||||
}
|
||||
|
||||
const confirmRejectBatch = async () => {
|
||||
if (!rejectBatchReason.value.trim()) { ElMessage.warning('请填写驳回原因'); return }
|
||||
batchLoading.value = true
|
||||
try {
|
||||
const res: any = await batchApprovePurchase({
|
||||
batch_key: rejectBatchKey.value,
|
||||
action: 'reject',
|
||||
reject_reason: rejectBatchReason.value
|
||||
})
|
||||
ElMessage.success(res?.msg || '本批已驳回')
|
||||
rejectBatchDialogVisible.value = false
|
||||
await fetchData()
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err?.msg || '驳回失败')
|
||||
} finally {
|
||||
batchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 批量通过
|
||||
const handleBatchApprove = async () => {
|
||||
if (selectedRows.value.length === 0) return
|
||||
@ -617,8 +714,36 @@ const fetchData = async () => {
|
||||
if (filterStatus.value !== '') params.status = filterStatus.value
|
||||
|
||||
const res: any = await getPurchaseList(params)
|
||||
list.value = res.data?.items || []
|
||||
const items = res.data?.items || []
|
||||
total.value = res.data?.total || 0
|
||||
|
||||
// ★ 按批次分组:同批次(单号去末尾-批内序号)聚合为一行
|
||||
const groupMap = new Map<string, any[]>()
|
||||
items.forEach(item => {
|
||||
const key = getBatchKey(item.request_no) || '独立'
|
||||
if (!groupMap.has(key)) groupMap.set(key, [])
|
||||
groupMap.get(key)!.push(item)
|
||||
})
|
||||
|
||||
list.value = Array.from(groupMap.entries()).map(([batchKey, batchItems]) => {
|
||||
const totalAmount = batchItems.reduce((sum, it) => sum + (Number(it.total_price) || 0), 0)
|
||||
const pendingCount = batchItems.filter(it => it.status === 0).length
|
||||
// 状态汇总:全通过=1,全驳回=2,混合/待审=0
|
||||
const statuses = new Set(batchItems.map(it => it.status))
|
||||
let summaryStatus = batchItems[0]?.status ?? 0
|
||||
if (statuses.size > 1) summaryStatus = 0 // 混合状态按待审处理
|
||||
return {
|
||||
batchKey,
|
||||
items: batchItems,
|
||||
itemCount: batchItems.length,
|
||||
totalAmount,
|
||||
pendingCount,
|
||||
summaryStatus,
|
||||
requesterName: batchItems[0]?.requester_name || '',
|
||||
purchaseDate: batchItems[0]?.purchase_date || '',
|
||||
statusTexts: batchItems.map(it => it.status_text).filter((v, i, a) => a.indexOf(v) === i)
|
||||
}
|
||||
})
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err?.msg || '加载失败')
|
||||
} finally {
|
||||
@ -626,6 +751,7 @@ const fetchData = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const fetchApprovers = async () => {
|
||||
try {
|
||||
const res: any = await getPurchaseApprovers()
|
||||
|
||||
Reference in New Issue
Block a user