feat(approval): 三端审批页「撤回」入口,明确告知会释放库存

后端已支持撤回时释放预占(见上一提交),前端同步:

一、文案与语义
  按钮 完结 → 撤回,颜色 danger → warning(语义从「危险操作」变为
  「可逆的库存释放」)。确认弹窗明确告知会释放多少项库存:

    确定撤回申请单【APR-OUT-...】吗?
    撤回后该单将被作废,其预占的 5 项物料库存会立即释放,
    可供其它申请使用。此操作不可恢复。

  用户看到的「1 件货」背后其实是一批被锁定的库存,不写清楚会让人
  以为撤回只是「关掉一张单」。

二、报废新增撤回入口
  报废审批页原先只有「执行报废」,没有撤回。补上按钮 + handleWithdraw(),
  并新增 API 封装 withdrawScrapRequest()。
  状态映射同步补充 4: '已撤回'(statusText / statusTagType)。

三、成功提示改为透传后端消息
  后端会返回「申请单已撤回,释放 N 项预占库存」,比前端写死的文案
  更有信息量,故改为 res?.msg 优先、前端文案兜底。

按钮可见性沿用 row.status === 1(已通过但未执行),与后端的执行守卫
(执行成功后 status 置 3)一致,故已执行或已撤回的单不会出现该按钮。
This commit is contained in:
yueli
2026-09-10 15:08:43 +08:00
parent 9925bf2b99
commit d1dd3dd404
4 changed files with 76 additions and 26 deletions

View File

@ -118,12 +118,16 @@
type="danger" size="small" :loading="row._approving" @click="openRejectDialog(row)"
>驳回</el-button>
</template>
<!-- ★ 已通过(待执行):持有 scrap_execute 者可执行按单报废 -->
<!-- ★ 已通过(待执行):执行报废 / 撤回作废 -->
<template v-else-if="row.status === 1">
<el-button
v-if="canExecute"
type="warning" plain size="small" :loading="row._executing" @click="openExecute(row)"
>执行报废</el-button>
<el-button
v-if="canExecute"
type="info" plain size="small" :loading="row._withdrawing" @click="handleWithdraw(row)"
>撤回</el-button>
</template>
<span v-else style="color: #c0c4cc;">-</span>
</template>
@ -167,7 +171,7 @@ import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { Refresh, Warning } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getScrapApprovals, approveScrapRequest } from '@/api/scrap'
import { getScrapApprovals, approveScrapRequest, withdrawScrapRequest } from '@/api/scrap'
import { useUserStore } from '@/stores/user'
const router = useRouter()
@ -199,11 +203,11 @@ const canExecute = computed(() =>
// --- 工具 ---
const statusText = (status: number) => {
const map: Record<number, string> = { 0: '待审批', 1: '已通过', 2: '已驳回', 3: '已执行' }
const map: Record<number, string> = { 0: '待审批', 1: '已通过', 2: '已驳回', 3: '已执行', 4: '已撤回' }
return map[status] ?? '-'
}
const statusTagType = (status: number) => {
const map: Record<number, string> = { 0: 'warning', 1: 'success', 2: 'danger', 3: 'info' }
const map: Record<number, string> = { 0: 'warning', 1: 'success', 2: 'danger', 3: 'info', 4: 'info' }
return map[status] ?? 'info'
}
const sourceLabel = (st: string) => ({ stock_buy: '采购件', stock_semi: '半成品', stock_product: '成品' } as any)[st] || st || '-'
@ -231,6 +235,7 @@ const fetchData = async () => {
if (r.actual_approver_id && r.actual_approver_name) userNameCache.value[r.actual_approver_id] = r.actual_approver_name
r._approving = false
r._executing = false
r._withdrawing = false
})
list.value = records
total.value = res.data?.total || records.length || 0
@ -286,6 +291,32 @@ const openExecute = (row: any) => {
router.push({ path: '/scrap/create', query: { requestId: row.id } })
}
// ★ 撤回已通过但尚未执行的报废申请
// 作废该单;若报废已接入库存预占,后端会一并释放预占量
const handleWithdraw = async (row: any) => {
try {
await ElMessageBox.confirm(
`确定撤回报废申请【${row.request_no}】吗?\n\n` +
`撤回后该单将作废,其占用的物料库存会被释放,可供其它单据使用。\n` +
`此操作不可恢复。`,
'⚠️ 撤回确认',
{ confirmButtonText: '确认撤回', cancelButtonText: '取消', type: 'warning' }
)
} catch (e) {
return
}
row._withdrawing = true
try {
await withdrawScrapRequest(row.id)
ElMessage.success(`报废申请 ${row.request_no} 已撤回`)
await fetchData()
} catch (err: any) {
ElMessage.error(err?.msg || '撤回失败')
} finally {
row._withdrawing = false
}
}
onMounted(() => { fetchData() })
</script>