This commit is contained in:
zlwu committed 2026-08-01 17:29:37 +08:00
1 parent 06956b742b
commit e59b2aa6cb
104 files changed
+653227 -7621

No files matched your search

+30 -5
View File
@@ -684,15 +684,40 @@ async function handleToolbarSubmit() {
ElMessage.warning('请先勾选要提交的申请')
return
}
const validTargets = selectedRows.value.filter(isDraftEditable)
const skippedCount = selectedRows.value.length - validTargets.length
if (!validTargets.length) {
ElMessage.warning('所选申请均不是草稿或已驳回状态,无法提交审批')
return
}
if (skippedCount > 0) {
ElMessage.warning(
`已跳过 ${skippedCount} 条不可提交的申请(仅草稿或已驳回可提交),将继续提交其余 ${validTargets.length} 条`
)
}
loadingMap.submit = true
try {
for (const row of selectedRows.value) {
await advanceApi.submit(row.id)
const results = await Promise.allSettled(validTargets.map((row) => advanceApi.submit(row.id)))
const successCount = results.filter((item) => item.status === 'fulfilled').length
const failedCount = results.length - successCount
const skipText = skippedCount > 0 ? `,跳过 ${skippedCount} 条` : ''
if (successCount === 0) {
ElMessage.error(`提交失败${skipText}`)
return
}
if (failedCount > 0) {
ElMessage.warning(`成功提交 ${successCount} 条,失败 ${failedCount} 条${skipText}`)
} else if (successCount === 1 && skippedCount === 0) {
ElMessage.success('提交成功')
} else {
ElMessage.success(`成功提交 ${successCount} 条${skipText}`)
}
ElMessage.success('提交成功')
await refreshList()
} catch (e: any) {
ElMessage.error(e?.message || '提交失败')
} finally {
loadingMap.submit = false
}
@@ -420,6 +420,22 @@
destroy-on-close
:close-on-click-modal="false"
>
<el-alert
v-if="approveRows.length > 1"
type="success"
:closable="false"
show-icon
class="batch-action-alert batch-action-alert--success"
:title="`将${approveAction === 'approve' ? '审批通过' : '审批驳回'} ${approveRows.length} 条申请(使用同一审批意见)`"
/>
<el-alert
v-if="approveSkippedCount > 0"
type="error"
:closable="false"
show-icon
class="batch-action-alert batch-action-alert--danger"
:title="`已跳过 ${approveSkippedCount} 条不可审批的申请`"
/>
<el-form label-position="top" class="bx-dialog-form">
<div class="form-grid-2">
<el-form-item label="审批时间">
@@ -570,8 +586,32 @@ function formatNow() {
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
}
const approveSkippedCount = ref(0)
function isApprovableRow(row?: any) {
if (!row) return false
return !row.status || row.status === 'pending'
}
function openApproveDialog(rows: any[], action: 'approve' | 'reject') {
approveRows.value = rows
const validRows = (rows || []).filter(isApprovableRow)
const skippedCount = (rows || []).length - validRows.length
if (!validRows.length) {
ElMessage.warning(
action === 'approve' ? '所选申请均不可审批通过' : '所选申请均不可驳回'
)
return
}
if (skippedCount > 0) {
ElMessage.warning(
`已跳过 ${skippedCount} 条不可审批的申请,将继续处理其余 ${validRows.length} 条`
)
}
approveRows.value = validRows
approveSkippedCount.value = skippedCount
approveAction.value = action
approvalOpinion.value = action === 'approve' ? '审批通过' : '审批驳回'
approveTime.value = formatNow()
@@ -742,22 +782,61 @@ const pendingSummaryMethod = amountSummary
const approvedSummaryMethod = amountSummary
async function handleConfirmApprove() {
if (!approveRows.value.length) {
ElMessage.warning('没有可审批的申请')
return
}
const key = approveAction.value === 'approve' ? 'approvePass' : 'approveReject'
loadingMap[key] = true
const apiFn = approveAction.value === 'approve' ? advanceApi.approve : advanceApi.reject
const msg = approveAction.value === 'approve' ? '审批通过成功' : '驳回成功'
const actionLabel = approveAction.value === 'approve' ? '审批通过' : '审批驳回'
try {
for (const row of approveRows.value) {
await apiFn(row.id, { approvalOpinion: approvalOpinion.value })
const results = await Promise.allSettled(
approveRows.value.map((row) => apiFn(row.id, { approvalOpinion: approvalOpinion.value }))
)
const successCount = results.filter((item) => item.status === 'fulfilled').length
const failedCount = results.length - successCount
const skipText = approveSkippedCount.value > 0 ? `,跳过 ${approveSkippedCount.value} 条` : ''
if (successCount === 0) {
ElMessage.error(`${actionLabel}失败${skipText}`)
return
}
ElMessage.success(approveRows.value.length > 1 ? `批量${msg}` : msg)
if (failedCount > 0) {
ElMessage.warning(`成功${actionLabel} ${successCount} 条,失败 ${failedCount} 条${skipText}`)
} else if (successCount === 1 && approveSkippedCount.value === 0) {
ElMessage.success(`${actionLabel}成功`)
} else {
ElMessage.success(`成功${actionLabel} ${successCount} 条${skipText}`)
}
dialogs.approve = false
await refreshPending()
await refreshMenuBadges(menuStore.menuList)
} catch (e: any) {
ElMessage.error(e?.message || '操作失败')
} finally {
loadingMap[key] = false
}
}
</script>
<style scoped lang="scss">
.batch-action-alert {
margin-bottom: 12px;
&--success {
:deep(.el-alert__title),
:deep(.el-alert__icon) {
color: var(--primary, #10b981);
}
}
&--danger {
:deep(.el-alert__title),
:deep(.el-alert__icon) {
color: #ef4444;
}
}
}
</style>
@@ -1107,13 +1107,22 @@ async function handleToolbarSubmit() {
ElMessage.warning('请先勾选要提交的报销单')
return
}
if (!selectedRows.value.every(isDraftEditable)) {
ElMessage.warning('仅草稿或已驳回的报销单可提交')
const validTargets = selectedRows.value.filter(isDraftEditable)
const skippedCount = selectedRows.value.length - validTargets.length
if (!validTargets.length) {
ElMessage.warning('所选报销单均不是草稿或已驳回状态,无法提交审批')
return
}
if (skippedCount > 0) {
ElMessage.warning(`已跳过 ${skippedCount} 条不可提交的报销单(仅草稿或已驳回可提交),将继续提交其余 ${validTargets.length} 条`)
}
// 按申请人分别校验发票余额(代申请时用申请人额度)
const needByApplicant = new Map<string, number>()
for (const row of selectedRows.value) {
for (const row of validTargets) {
const aid = String(row.applicantId || currentUserId())
needByApplicant.set(aid, (needByApplicant.get(aid) || 0) + Number(row.amount || 0))
}
@@ -1126,15 +1135,26 @@ async function handleToolbarSubmit() {
return
}
}
loadingMap.submit = true
try {
for (const row of selectedRows.value) {
await reimburseApi.submit(row.id)
const results = await Promise.allSettled(validTargets.map((row) => reimburseApi.submit(row.id)))
const successCount = results.filter((item) => item.status === 'fulfilled').length
const failedCount = results.length - successCount
const skipText = skippedCount > 0 ? `,跳过 ${skippedCount} 条` : ''
if (successCount === 0) {
ElMessage.error(`提交失败${skipText}`)
return
}
if (failedCount > 0) {
ElMessage.warning(`成功提交 ${successCount} 条,失败 ${failedCount} 条${skipText}`)
} else if (successCount === 1 && skippedCount === 0) {
ElMessage.success('提交成功')
} else {
ElMessage.success(`成功提交 ${successCount} 条${skipText}`)
}
ElMessage.success('提交成功')
await refreshList()
} catch (e: any) {
ElMessage.error(e?.message || '提交失败')
} finally {
loadingMap.submit = false
}
@@ -551,6 +551,22 @@
destroy-on-close
:close-on-click-modal="false"
>
<el-alert
v-if="approveRows.length > 1"
type="success"
:closable="false"
show-icon
class="batch-action-alert batch-action-alert--success"
:title="`将${approveAction === 'approve' ? '审批通过' : '审批驳回'} ${approveRows.length} 条报销单(使用同一审批意见)`"
/>
<el-alert
v-if="approveSkippedCount > 0"
type="error"
:closable="false"
show-icon
class="batch-action-alert batch-action-alert--danger"
:title="`已跳过 ${approveSkippedCount} 条不可审批的报销单`"
/>
<el-form label-position="top" class="bx-dialog-form">
<div class="form-grid-2">
<el-form-item label="审批时间">
@@ -741,8 +757,35 @@ function formatNow() {
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
}
const approveSkippedCount = ref(0)
function isApprovableRow(row?: any) {
if (!row) return false
// 待审批列表中的单据;若状态已变则不可再审
return !row.status || row.status === 'pending'
}
function openApproveDialog(rows: any[], action: 'approve' | 'reject') {
approveRows.value = rows
const validRows = (rows || []).filter(isApprovableRow)
const skippedCount = (rows || []).length - validRows.length
if (!validRows.length) {
ElMessage.warning(
action === 'approve'
? '所选报销单均不可审批通过'
: '所选报销单均不可驳回'
)
return
}
if (skippedCount > 0) {
ElMessage.warning(
`已跳过 ${skippedCount} 条不可审批的报销单,将继续处理其余 ${validRows.length} 条`
)
}
approveRows.value = validRows
approveSkippedCount.value = skippedCount
approveAction.value = action
approvalOpinion.value = action === 'approve' ? '审批通过' : '审批驳回'
approveTime.value = formatNow()
@@ -943,20 +986,39 @@ async function openView(row: any) {
}
async function handleConfirmApprove() {
if (!approveRows.value.length) {
ElMessage.warning('没有可审批的报销单')
return
}
const key = approveAction.value === 'approve' ? 'approvePass' : 'approveReject'
loadingMap[key] = true
const apiFn = approveAction.value === 'approve' ? reimburseApi.approve : reimburseApi.reject
const msg = approveAction.value === 'approve' ? '审批通过成功' : '驳回成功'
const actionLabel = approveAction.value === 'approve' ? '审批通过' : '审批驳回'
try {
for (const row of approveRows.value) {
await apiFn(row.id, { approvalOpinion: approvalOpinion.value })
const results = await Promise.allSettled(
approveRows.value.map((row) => apiFn(row.id, { approvalOpinion: approvalOpinion.value }))
)
const successCount = results.filter((item) => item.status === 'fulfilled').length
const failedCount = results.length - successCount
const skipText = approveSkippedCount.value > 0 ? `,跳过 ${approveSkippedCount.value} 条` : ''
if (successCount === 0) {
ElMessage.error(`${actionLabel}失败${skipText}`)
return
}
ElMessage.success(approveRows.value.length > 1 ? `批量${msg}` : msg)
if (failedCount > 0) {
ElMessage.warning(`成功${actionLabel} ${successCount} 条,失败 ${failedCount} 条${skipText}`)
} else if (successCount === 1 && approveSkippedCount.value === 0) {
ElMessage.success(`${actionLabel}成功`)
} else {
ElMessage.success(`成功${actionLabel} ${successCount} 条${skipText}`)
}
dialogs.approve = false
await refreshPending()
await refreshMenuBadges(menuStore.menuList)
} catch (e: any) {
ElMessage.error(e?.message || '操作失败')
} finally {
loadingMap[key] = false
}
@@ -987,3 +1049,23 @@ function viewDetailSummary({ columns, data }: { columns: any[]; data: any[] }) {
return sums
}
</script>
<style scoped lang="scss">
.batch-action-alert {
margin-bottom: 12px;
&--success {
:deep(.el-alert__title),
:deep(.el-alert__icon) {
color: var(--primary, #10b981);
}
}
&--danger {
:deep(.el-alert__title),
:deep(.el-alert__icon) {
color: #ef4444;
}
}
}
</style>
+8 -6
View File
@@ -496,7 +496,7 @@ async function handleApprove(row: any) {
loadingMap[key] = true
try {
const api = row.bizType === '备用金' ? advanceApi : reimburseApi
await api.approve(row.id, { approvalOpinion: '' })
await api.approve(row.id, { approvalOpinion: '审批通过' })
ElMessage.success('审批通过')
await loadData()
await refreshSidebarBadges()
@@ -509,7 +509,7 @@ async function handleApprove(row: any) {
function openRejectDialog(row: any) {
currentRow.value = row
approveOpinion.value = ''
approveOpinion.value = '审批驳回'
dialogs.approve = true
}
@@ -518,7 +518,8 @@ async function handleDialogApprove() {
loadingMap.approvePass = true
try {
const api = currentRow.value.bizType === '备用金' ? advanceApi : reimburseApi
await api.approve(currentRow.value.id, { approvalOpinion: approveOpinion.value })
const opinion = approveOpinion.value.trim() || '审批通过'
await api.approve(currentRow.value.id, { approvalOpinion: opinion })
ElMessage.success('审批通过')
dialogs.approve = false
await loadData()
@@ -535,7 +536,8 @@ async function handleDialogReject() {
loadingMap.approveReject = true
try {
const api = currentRow.value.bizType === '备用金' ? advanceApi : reimburseApi
await api.reject(currentRow.value.id, { approvalOpinion: approveOpinion.value })
const opinion = approveOpinion.value.trim() || '审批驳回'
await api.reject(currentRow.value.id, { approvalOpinion: opinion })
ElMessage.success('已驳回')
dialogs.approve = false
await loadData()
@@ -562,7 +564,7 @@ async function handleBatchPass() {
try {
for (const row of selectedRows.value) {
const api = row.bizType === '备用金' ? advanceApi : reimburseApi
await api.approve(row.id, { approvalOpinion: '' })
await api.approve(row.id, { approvalOpinion: '审批通过' })
}
ElMessage.success('批量审批通过成功')
await loadData()
@@ -583,7 +585,7 @@ async function handleBatchReject() {
try {
for (const row of selectedRows.value) {
const api = row.bizType === '备用金' ? advanceApi : reimburseApi
await api.reject(row.id, { approvalOpinion: '' })
await api.reject(row.id, { approvalOpinion: '审批驳回' })
}
ElMessage.success('批量驳回成功')
await loadData()
@@ -271,7 +271,7 @@
<script setup lang="ts" name="ContractApproval">
import ListToolbarEndActions from '@/components/business/list-toolbar-end-actions/index.vue'
import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
import { ref, reactive, computed, onMounted } from 'vue'
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { watchDebounced } from '@vueuse/core'
import { useRouter } from 'vue-router'
import {
@@ -479,11 +479,29 @@
})
}
const DEFAULT_OPINIONS = ['审批通过', '审批驳回'] as const
const defaultOpinion = (result: string) => (result === 'rejected' ? '审批驳回' : '审批通过')
watch(
() => approvalForm.value.approvalResult,
(result) => {
if (
!approvalForm.value.approvalOpinion.trim() ||
DEFAULT_OPINIONS.includes(
approvalForm.value.approvalOpinion as (typeof DEFAULT_OPINIONS)[number]
)
) {
approvalForm.value.approvalOpinion = defaultOpinion(result)
}
}
)
const handleApprove = (row: ApprovalVO) => {
approvalForm.value = {
id: row.id,
approvalResult: 'approved',
approvalOpinion: ''
approvalOpinion: defaultOpinion('approved')
}
approvalDialog.visible = true
}
@@ -10,6 +10,22 @@
modal-class="order-module-dialog-mask"
:close-on-click-modal="false"
>
<el-alert
v-if="isBatchMode && batchCount > 0"
type="success"
:closable="false"
show-icon
class="batch-action-alert batch-action-alert--success"
:title="batchInfoTitle"
/>
<el-alert
v-if="skippedCount > 0"
type="error"
:closable="false"
show-icon
class="batch-action-alert batch-action-alert--danger"
:title="`已跳过 ${skippedCount} 条不可处理的合同`"
/>
<el-form
ref="formRef"
:model="form"
@@ -35,14 +51,6 @@
<template #label>
选择审批人<span class="form-label-tip">支持搜索用户姓名</span>
</template>
<el-alert
v-if="submitPrefillTip"
type="success"
:closable="false"
show-icon
title="已按「合同审批人设置」预填审批人,可修改后提交"
style="margin-bottom: 12px"
/>
<el-select
v-model="form.approverIds"
multiple
@@ -158,6 +166,7 @@ const dialogVisible = ref(false)
const actionType = ref<'submit' | 'approve' | 'reject' | 'withdraw'>('submit')
const contractId = ref<string | number>()
const contractIds = ref<(string | number)[]>()
const skippedCount = ref(0)
const form = ref({
approvalType: 'sequential',
@@ -170,9 +179,11 @@ const form = ref({
const approverList = ref<ApproverUser[]>([])
const submitLoading = ref(false)
const userLoading = ref(false)
const submitPrefillTip = ref(false)
const formRef = ref<ElFormInstance>()
const isBatchMode = computed(() => !!(contractIds.value && contractIds.value.length > 0))
const batchCount = computed(() => contractIds.value?.length || 0)
const rules = computed(() => {
const baseRules: any = {}
if (actionType.value === 'submit') {
@@ -195,12 +206,25 @@ const dialogTitle = computed(() => {
withdraw: '撤回审批'
}
let title = titleMap[actionType.value]
if (contractIds.value && contractIds.value.length > 0) {
title += `(${contractIds.value.length}条)`
if (isBatchMode.value) {
title += `(${batchCount.value}条)`
}
return title
})
const batchInfoTitle = computed(() => {
if (actionType.value === 'submit') {
return `将提交 ${batchCount.value} 条草稿合同(使用同一组审批人与说明)`
}
if (actionType.value === 'approve') {
return `将审批通过 ${batchCount.value} 条合同(使用同一审批意见)`
}
if (actionType.value === 'reject') {
return `将审批驳回 ${batchCount.value} 条合同(使用同一审批意见)`
}
return `将处理 ${batchCount.value} 条合同`
})
const confirmButtonText = computed(() => {
const textMap = {
submit: '提交',
@@ -214,6 +238,7 @@ const confirmButtonText = computed(() => {
const open = async (id: string | number, type: 'submit' | 'approve' | 'reject' | 'withdraw' = 'submit') => {
contractId.value = id
contractIds.value = undefined
skippedCount.value = 0
actionType.value = type
dialogVisible.value = true
resetForm()
@@ -225,10 +250,12 @@ const open = async (id: string | number, type: 'submit' | 'approve' | 'reject' |
const openBatch = async (
idList: (string | number)[],
type: 'submit' | 'approve' | 'reject' | 'withdraw' = 'submit'
type: 'submit' | 'approve' | 'reject' | 'withdraw' = 'submit',
options?: { skippedCount?: number }
) => {
contractIds.value = idList
contractId.value = undefined
skippedCount.value = options?.skippedCount || 0
actionType.value = type
dialogVisible.value = true
resetForm()
@@ -238,6 +265,33 @@ const openBatch = async (
}
}
const showBatchResultMessage = (options: {
actionLabel: string
successCount: number
failedCount: number
skippedCount?: number
}) => {
const { actionLabel, successCount, failedCount, skippedCount: skip = 0 } = options
const skipText = skip > 0 ? `,跳过 ${skip} 条` : ''
if (successCount === 0 && failedCount === 0) {
$modal.msgWarning(`没有可${actionLabel}的合同${skipText}`)
return
}
if (successCount === 0) {
$modal.msgError(`${actionLabel}失败${skipText}`)
return
}
if (failedCount > 0) {
$modal.msgWarning(`成功${actionLabel} ${successCount} 条,失败 ${failedCount} 条${skipText}`)
return
}
if (successCount === 1 && skip === 0) {
$modal.msgSuccess(`${actionLabel}成功`)
return
}
$modal.msgSuccess(`成功${actionLabel} ${successCount} 条${skipText}`)
}
const loadApproverList = async (nickName?: string) => {
userLoading.value = true
try {
@@ -278,9 +332,7 @@ async function resolvePrefillApproverIds(userList: ApproverUser[]) {
}
async function applyPrefillApprovers() {
const prefillIds = await resolvePrefillApproverIds(approverList.value)
form.value.approverIds = prefillIds
submitPrefillTip.value = prefillIds.length > 0
form.value.approverIds = await resolvePrefillApproverIds(approverList.value)
}
const handleSearchUser = (query: string) => {
@@ -298,34 +350,73 @@ const getApproverName = (approverId: string | number): string => {
const handleConfirm = async () => {
await formRef.value?.validate()
const isBatch = !!(contractIds.value && contractIds.value.length > 0)
const isBatch = isBatchMode.value
if (!isBatch && (contractId.value == null || contractId.value === '')) {
$modal.msgError('合同 ID 缺失,请关闭后重新打开提交审批')
$modal.msgError('合同 ID 缺失,请关闭后重新打开')
return
}
submitLoading.value = true
try {
let shouldClose = true
if (actionType.value === 'submit') {
if (isBatch) {
const count = await batchSubmitContract(contractIds.value!, form.value.approverIds, form.value.remark)
$modal.msgSuccess(`提交成功,成功 ${count ?? contractIds.value!.length} 条`)
showBatchResultMessage({
actionLabel: '提交',
successCount: count ?? contractIds.value!.length,
failedCount: 0,
skippedCount: skippedCount.value
})
} else {
await submitContract(contractId.value!, form.value.approverIds, form.value.remark)
$modal.msgSuccess('提交成功')
}
} else if (actionType.value === 'approve') {
await approveContract(contractId.value!, form.value.comment)
$modal.msgSuccess('审批通过')
if (isBatch) {
const results = await Promise.allSettled(
contractIds.value!.map((id) => approveContract(id, form.value.comment))
)
const successCount = results.filter((item) => item.status === 'fulfilled').length
const failedCount = results.length - successCount
showBatchResultMessage({
actionLabel: '审批通过',
successCount,
failedCount,
skippedCount: skippedCount.value
})
shouldClose = successCount > 0
} else {
await approveContract(contractId.value!, form.value.comment)
$modal.msgSuccess('审批通过')
}
} else if (actionType.value === 'reject') {
await rejectContract(contractId.value!, form.value.comment)
$modal.msgSuccess('已驳回')
if (isBatch) {
const results = await Promise.allSettled(
contractIds.value!.map((id) => rejectContract(id, form.value.comment))
)
const successCount = results.filter((item) => item.status === 'fulfilled').length
const failedCount = results.length - successCount
showBatchResultMessage({
actionLabel: '审批驳回',
successCount,
failedCount,
skippedCount: skippedCount.value
})
shouldClose = successCount > 0
} else {
await rejectContract(contractId.value!, form.value.comment)
$modal.msgSuccess('已驳回')
}
} else if (actionType.value === 'withdraw') {
await withdrawContract(contractId.value!, form.value.reason)
$modal.msgSuccess('已撤回')
}
dialogVisible.value = false
emit('success')
if (shouldClose) {
dialogVisible.value = false
emit('success')
}
} finally {
submitLoading.value = false
}
@@ -350,7 +441,6 @@ const resetForm = () => {
remark: '',
reason: ''
}
submitPrefillTip.value = false
formRef.value?.clearValidate()
}
@@ -361,6 +451,24 @@ defineExpose({
</script>
<style scoped lang="scss">
.batch-action-alert {
margin-bottom: 12px;
&--success {
:deep(.el-alert__title),
:deep(.el-alert__icon) {
color: var(--primary, #10b981);
}
}
&--danger {
:deep(.el-alert__title),
:deep(.el-alert__icon) {
color: #ef4444;
}
}
}
.form-tip-block {
display: block;
margin-top: 6px;
+50 -26
View File
@@ -141,16 +141,16 @@
<el-button
v-if="activeView === 'submitted'"
class="btn-ghost"
:disabled="single"
@click="handleApprove(contractList[contractList.findIndex((item) => item.id === ids[0])])"
:disabled="!canApproveBatch"
@click="handleApprove()"
>
<el-icon><Select /></el-icon>审批通过
</el-button>
<el-button
v-if="activeView === 'submitted'"
class="btn-ghost"
:disabled="single"
@click="handleReject(contractList[contractList.findIndex((item) => item.id === ids[0])])"
:disabled="!canApproveBatch"
@click="handleReject()"
>
<el-icon><Close /></el-icon>审批驳回
</el-button>
@@ -1111,12 +1111,13 @@ const handleSubmitBatch = () => {
return;
}
if (draftContracts.length < selectedContracts.length) {
$modal.msgWarning(`选中了 ${selectedContracts.length} 条合同,其中 ${draftContracts.length} 条为草稿状态,只有草稿状态的合同可以提交审批`);
const skippedCount = selectedContracts.length - draftContracts.length;
if (skippedCount > 0) {
$modal.msgWarning(`已跳过 ${skippedCount} 条非草稿合同,仅处理草稿状态单据`);
}
const draftIds = draftContracts.map((item) => item.id);
approvalDialogRef.value?.openBatch(draftIds, 'submit');
approvalDialogRef.value?.openBatch(draftIds, 'submit', { skippedCount });
};
/** 判断是否可以提交审批(至少有一条草稿状态的合同) */
@@ -1126,32 +1127,55 @@ const canSubmit = computed(() => {
return selectedContracts.some((item) => item.status === 'draft');
});
/** 审批通过 */
const handleApprove = (row?: ContractVO) => {
if (!row && ids.value.length !== 1) {
$modal.msgWarning('请选择一条数据进行审批');
/** 判断是否可以审批(至少有一条当前用户可审批的合同) */
const canApproveBatch = computed(() => {
if (!selectedRows.value.length) return false;
return selectedRows.value.some((item) => item.canApprove);
});
/** 筛选可审批合同并打开审批弹窗(支持单条与批量) */
const openApprovalForTargets = (
row: ContractVO | undefined,
type: 'approve' | 'reject'
) => {
const targets = row ? [row] : selectedRows.value;
if (!targets.length) {
$modal.msgWarning('请选择要审批的合同');
return;
}
const targetRow = row || contractList.value[contractList.value.findIndex((item) => item.id === ids.value[0])];
if (!targetRow) {
$modal.msgWarning('未找到选中的数据');
const validTargets = targets.filter((item) => item.canApprove);
const skippedCount = targets.length - validTargets.length;
if (!validTargets.length) {
$modal.msgWarning(
targets.length === 1
? '您没有该合同的审批权限,或已审批过该合同'
: '所选合同均不可审批(无权、已审或不在待审批状态)'
);
return;
}
approvalDialogRef.value?.open(targetRow.id, 'approve');
if (skippedCount > 0) {
$modal.msgWarning(`已跳过 ${skippedCount} 条不可审批的合同,仅处理可审批单据`);
}
const validIds = validTargets.map((item) => item.id);
if (validIds.length === 1 && skippedCount === 0) {
approvalDialogRef.value?.open(validIds[0], type);
} else {
approvalDialogRef.value?.openBatch(validIds, type, { skippedCount });
}
};
/** 审批驳回 */
/** 审批通过(支持单条与批量) */
const handleApprove = (row?: ContractVO) => {
openApprovalForTargets(row, 'approve');
};
/** 审批驳回(支持单条与批量) */
const handleReject = (row?: ContractVO) => {
if (!row && ids.value.length !== 1) {
$modal.msgWarning('请选择一条数据进行审批');
return;
}
const targetRow = row || contractList.value[contractList.value.findIndex((item) => item.id === ids.value[0])];
if (!targetRow) {
$modal.msgWarning('未找到选中的数据');
return;
}
approvalDialogRef.value?.open(targetRow.id, 'reject');
openApprovalForTargets(row, 'reject');
};
/** 上传合同扫描件 */
@@ -81,11 +81,13 @@
try {
const res = await fetchUserOptions()
const options = (res as any)?.data ?? res ?? []
userList.value = (Array.isArray(options) ? options : []).map((item: any) => ({
userId: item.value ?? item.userId ?? item.id,
nickName: item.nickName ?? item.label ?? item.userName ?? '',
userName: item.userNo ?? item.user_no ?? item.userName ?? ''
}))
userList.value = (Array.isArray(options) ? options : [])
.map((item: any) => ({
userId: String(item.value ?? item.userId ?? item.id ?? ''),
nickName: item.nickName ?? item.label ?? item.userName ?? '',
userName: item.userNo ?? item.user_no ?? item.userName ?? ''
}))
.filter((u) => u.userId)
} catch (error) {
console.error('加载用户列表失败:', error)
userList.value = []
@@ -123,13 +123,29 @@ watch(dialogVisible, (val) => {
emit('update:visible', val)
})
const DEFAULT_OPINIONS = ['审批通过', '审批驳回'] as const
const form = ref({
quoteId: undefined as number | undefined,
result: 'approved',
opinion: '',
opinion: '审批通过',
nextApproverUserId: undefined as string | number | undefined
})
const defaultOpinion = (result: string) => (result === 'rejected' ? '审批驳回' : '审批通过')
watch(
() => form.value.result,
(result) => {
if (
!form.value.opinion.trim() ||
DEFAULT_OPINIONS.includes(form.value.opinion as (typeof DEFAULT_OPINIONS)[number])
) {
form.value.opinion = defaultOpinion(result)
}
}
)
const rules = computed<FormRules>(() => {
if (props.approvalType === 'approve') {
return {
@@ -165,7 +181,7 @@ const resetForm = () => {
form.value = {
quoteId: props.quoteInfo.id as number | undefined,
result: 'approved',
opinion: '',
opinion: defaultOpinion('approved'),
nextApproverUserId: undefined
}
formRef.value?.clearValidate()
@@ -140,12 +140,14 @@ const loadUsers = async (nickName?: string) => {
try {
const res = await fetchUserOptions()
const options = (res as any)?.data ?? res ?? []
const mapped = (Array.isArray(options) ? options : []).map((item: any) => ({
userId: item.value ?? item.userId ?? item.id,
nickName: item.nickName ?? item.label ?? item.userName ?? '',
userName: item.userNo ?? item.user_no ?? item.userName ?? '',
deptName: item.deptName ?? ''
}))
const mapped = (Array.isArray(options) ? options : [])
.map((item: any) => ({
userId: String(item.value ?? item.userId ?? item.id ?? ''),
nickName: item.nickName ?? item.label ?? item.userName ?? '',
userName: item.userNo ?? item.user_no ?? item.userName ?? '',
deptName: item.deptName ?? ''
}))
.filter((user) => user.userId)
userList.value = nickName
? mapped.filter((user) => user.nickName.includes(nickName) || user.userName.includes(nickName))
: mapped
@@ -463,14 +463,6 @@
scroll-to-error
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
>
<el-alert
v-if="submitPrefillTip"
type="success"
:closable="false"
show-icon
title="已按「报价单审批人设置」预填审批人,可修改后提交"
style="margin-bottom: 12px"
/>
<div class="form-grid-2">
<el-form-item prop="approverIds" class="form-col-span-2">
<template #label>
@@ -697,7 +689,6 @@ const submitForm = ref({
const submitRules = ref({
approverIds: [{ required: true, message: '请选择审批人', trigger: 'change' }]
});
const submitPrefillTip = ref(false);
const submitFormRef = ref<FormInstance>();
// 用户列表(负责人 + 审批人候选)
@@ -1097,11 +1088,11 @@ const loadUserList = async () => {
const res = await fetchUserOptions();
const options = (res as any)?.data ?? res ?? [];
userList.value = (Array.isArray(options) ? options : []).map((item: any) => ({
userId: item.value ?? item.userId ?? item.id,
userId: String(item.value ?? item.userId ?? item.id ?? ''),
nickName: item.nickName ?? item.label ?? item.userName ?? '',
userName: item.userNo ?? item.user_no ?? item.userName ?? '',
deptName: item.deptName ?? ''
}));
})).filter((u) => u.userId);
} catch (error) {
console.error('加载用户列表失败:', error);
userList.value = [];
@@ -1719,7 +1710,6 @@ const handleSubmit = async () => {
approverIds: prefillIds,
remark: ''
};
submitPrefillTip.value = prefillIds.length > 0;
// 打开审批对话框
submitDialog.value.visible = true;
+294 -97
View File
@@ -115,7 +115,7 @@
<el-button
v-if="activeView !== 'submitted' && activeView !== 'accepted'"
class="btn-ghost"
:disabled="single"
:disabled="multiple"
@click="handleSubmit()"
>
<el-icon><Document /></el-icon>提交审批
@@ -123,7 +123,7 @@
<el-button
v-if="activeView === 'submitted'"
class="btn-ghost"
:disabled="single"
:disabled="multiple"
@click="handleApproval()"
>
<el-icon><DocumentChecked /></el-icon>审批
@@ -406,6 +406,22 @@
modal-class="order-module-dialog-mask"
:close-on-click-modal="false"
>
<el-alert
v-if="submitTargetIds.length > 1"
type="success"
:closable="false"
show-icon
class="batch-action-alert batch-action-alert--success"
:title="`将提交 ${submitTargetIds.length} 条草稿报价单(使用同一组审批人与备注)`"
/>
<el-alert
v-if="submitSkippedCount > 0"
type="error"
:closable="false"
show-icon
class="batch-action-alert batch-action-alert--danger"
:title="`已跳过 ${submitSkippedCount} 条非草稿报价单`"
/>
<el-form
ref="submitFormRef"
:model="submitForm"
@@ -415,14 +431,6 @@
scroll-to-error
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
>
<el-alert
v-if="submitPrefillTip"
type="success"
:closable="false"
show-icon
title="已按「报价单审批人设置」预填审批人,可修改后提交"
style="margin-bottom: 12px"
/>
<div class="form-grid-2">
<el-form-item prop="approverIds" class="form-col-span-2">
<template #label>
@@ -567,7 +575,40 @@
modal-class="order-module-dialog-mask"
:close-on-click-modal="false"
>
<section v-if="currentApprovalQuote" class="approval-dialog-section">
<el-alert
v-if="batchApprovalTargets.length > 1"
type="info"
:closable="false"
show-icon
class="batch-action-alert"
:title="`将审批 ${batchApprovalTargets.length} 条报价单(使用同一审批结果与意见)`"
/>
<el-alert
v-if="approvalSkippedCount > 0"
type="warning"
:closable="false"
show-icon
class="batch-action-alert"
:title="`已跳过 ${approvalSkippedCount} 条无权或不在待审批状态的报价单`"
/>
<section
v-if="batchApprovalTargets.length > 1"
class="approval-dialog-section"
>
<h3 class="approval-section-title">待审批报价单</h3>
<div class="batch-approval-quote-list">
<el-tag
v-for="item in batchApprovalTargets"
:key="String(item.quote.id)"
size="small"
class="batch-approval-quote-tag"
>
{{ item.quote.quoteNo || item.quote.id }}
</el-tag>
</div>
</section>
<section v-else-if="currentApprovalQuote" class="approval-dialog-section">
<h3 class="approval-section-title">报价单信息</h3>
<el-descriptions :column="2" class="approval-kv-descriptions">
<el-descriptions-item label="报价单号">{{ currentApprovalQuote.quoteNo }}</el-descriptions-item>
@@ -588,7 +629,7 @@
</el-descriptions>
</section>
<section v-if="approvalList.length > 0" class="approval-dialog-section">
<section v-if="batchApprovalTargets.length <= 1 && approvalList.length > 0" class="approval-dialog-section">
<div class="approval-section-head">
<h3 class="approval-section-title">审批流程</h3>
<el-tag size="small" type="info">
@@ -637,8 +678,7 @@
ref="approvalFormRef"
:model="approvalForm"
:rules="approvalRules"
label-position="right"
label-width="100px"
label-position="top"
class="crm-dialog-form"
scroll-to-error
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
@@ -730,7 +770,7 @@ import DictTag from '@/components/DictTag/index.vue'
import { $modal } from '@/utils/modal'
import { formatMoneyDisplay } from '@/utils/money-format'
import { formatBrandForPrint } from '@/utils/crm-product-brand'
import { ref, reactive, onMounted, nextTick, getCurrentInstance, toRefs, computed, type ComponentInternalInstance } from 'vue';
import { ref, reactive, onMounted, nextTick, getCurrentInstance, toRefs, computed, watch, type ComponentInternalInstance } from 'vue';
import { useRouter } from 'vue-router';
import { useClipboard } from '@vueuse/core';
import type { FormInstance } from 'element-plus';
@@ -1035,16 +1075,15 @@ const submitDialog = reactive({
visible: false,
loading: false
});
const submitTargetIds = ref<(string | number)[]>([]);
const submitSkippedCount = ref(0);
const submitForm = ref({
id: undefined as string | number | undefined,
approverIds: [] as (string | number)[],
remark: ''
});
const submitRules = ref({
approverIds: [{ required: true, message: '请选择审批人', trigger: 'change' }]
});
const submitPrefillTip = ref(false);
// 用户列表(审批人候选)
const userList = ref<UserVO[]>([]);
@@ -1095,7 +1134,7 @@ const approvalDialog = reactive({
const approvalForm = ref<ApprovalForm>({
id: undefined as string | number | undefined,
approvalResult: 'approved',
approvalOpinion: '',
approvalOpinion: '审批通过',
remark: ''
});
const approvalRules = ref({
@@ -1113,8 +1152,53 @@ const approvalRules = ref({
}
]
});
const QUOTE_DEFAULT_OPINIONS = ['审批通过', '审批驳回'] as const
watch(
() => approvalForm.value.approvalResult,
(result) => {
if (
!approvalForm.value.approvalOpinion.trim() ||
QUOTE_DEFAULT_OPINIONS.includes(
approvalForm.value.approvalOpinion as (typeof QUOTE_DEFAULT_OPINIONS)[number]
)
) {
approvalForm.value.approvalOpinion = result === 'rejected' ? '审批驳回' : '审批通过'
}
}
)
const approvalList = ref<QuoteApprovalVO[]>([]);
const currentApprovalQuote = ref<QuoteVO | null>(null);
type BatchApprovalTarget = { quote: QuoteVO; approvalId: string | number };
const batchApprovalTargets = ref<BatchApprovalTarget[]>([]);
const approvalSkippedCount = ref(0);
/** 批量操作结果提示 */
const showBatchResultMessage = (options: {
actionLabel: string
successCount: number
failedCount: number
skippedCount?: number
}) => {
const { actionLabel, successCount, failedCount, skippedCount = 0 } = options
const skipText = skippedCount > 0 ? `,跳过 ${skippedCount} 条` : ''
if (successCount === 0 && failedCount === 0) {
$modal.msgWarning(`没有可${actionLabel}的报价单${skipText}`)
return
}
if (successCount === 0) {
$modal.msgError(`${actionLabel}失败${skipText}`)
return
}
if (failedCount > 0) {
$modal.msgWarning(`成功${actionLabel} ${successCount} 条,失败 ${failedCount} 条${skipText}`)
return
}
if (successCount === 1 && skippedCount === 0) {
$modal.msgSuccess(`${actionLabel}成功`)
return
}
$modal.msgSuccess(`成功${actionLabel} ${successCount} 条${skipText}`)
}
const queryFormRef = ref<FormInstance>();
const submitFormRef = ref<FormInstance>();
@@ -1301,12 +1385,12 @@ const handleView = (row?: QuoteVO) => {
const handleQuoteDetailSubmit = async (quote: QuoteVO) => {
await loadUserList();
const prefillIds = await resolvePrefillApproverIds(userList.value);
submitTargetIds.value = [quote.id];
submitSkippedCount.value = 0;
submitForm.value = {
id: quote.id,
approverIds: prefillIds,
remark: ''
};
submitPrefillTip.value = prefillIds.length > 0;
submitDialog.visible = true;
};
@@ -1386,11 +1470,12 @@ const loadUserList = async () => {
const res = await fetchUserOptions();
const options = (res as any)?.data ?? res ?? [];
userList.value = (Array.isArray(options) ? options : []).map((item: any) => ({
userId: item.value ?? item.userId ?? item.id,
// 与设置/预填统一为字符串,避免 el-select 因 number/string 类型不一致无法回显
userId: String(item.value ?? item.userId ?? item.id ?? ''),
nickName: item.nickName ?? item.label ?? item.userName ?? '',
userName: item.userNo ?? item.user_no ?? item.userName ?? '',
deptName: item.deptName ?? ''
}));
})).filter((u) => u.userId);
} catch (error) {
console.error('加载用户列表失败:', error);
userList.value = [];
@@ -1408,46 +1493,71 @@ async function resolvePrefillApproverIds(userList: { userId: string | number }[]
}
}
/** 提交审批 */
/** 提交审批(支持单条与批量) */
const handleSubmit = async (row?: QuoteVO) => {
if (!row && ids.value.length !== 1) {
$modal.msgWarning('请选择一条数据进行提交审批');
const targets = row ? [row] : selectedRows.value;
if (!targets.length) {
$modal.msgWarning('请选择要提交审批的数据');
return;
}
const id = row?.id || ids.value[0];
const quote = row || quoteList.value[quoteList.value.findIndex((item) => item.id === id)];
if (quote && quote.status !== 'draft') {
$modal.msgWarning('只能提交草稿状态的报价单');
const validTargets = targets.filter((item) => item.status === 'draft');
const skippedCount = targets.length - validTargets.length;
if (!validTargets.length) {
$modal.msgWarning('所选报价单均不是草稿状态,无法提交审批');
return;
}
if (skippedCount > 0) {
$modal.msgWarning(`已跳过 ${skippedCount} 条非草稿报价单,仅处理草稿状态单据`);
}
await loadUserList();
const prefillIds = await resolvePrefillApproverIds(userList.value);
submitTargetIds.value = validTargets.map((item) => item.id);
submitSkippedCount.value = skippedCount;
submitForm.value = {
id: id,
approverIds: prefillIds,
remark: ''
};
submitPrefillTip.value = prefillIds.length > 0;
submitDialog.visible = true;
};
/** 确认提交审批 */
const confirmSubmit = async () => {
await submitFormRef.value?.validate();
if (!submitTargetIds.value.length) {
$modal.msgWarning('没有可提交审批的报价单');
return;
}
submitDialog.loading = true;
try {
// 直接使用原始ID,不转换类型(避免大整数精度丢失)
// 后端会正确处理字符串或数字类型的ID
await submitQuote({
id: submitForm.value.id!,
approverIds: submitForm.value.approverIds,
remark: submitForm.value.remark
const results = await Promise.allSettled(
submitTargetIds.value.map((id) =>
submitQuote({
id,
approverIds: submitForm.value.approverIds,
remark: submitForm.value.remark
})
)
);
const successCount = results.filter((item) => item.status === 'fulfilled').length;
const failedCount = results.length - successCount;
showBatchResultMessage({
actionLabel: '提交',
successCount,
failedCount,
skippedCount: submitSkippedCount.value
});
$modal.msgSuccess('提交成功');
submitDialog.visible = false;
await getList();
await quoteDetailRef.value?.refresh();
if (successCount > 0) {
submitDialog.visible = false;
await getList();
await quoteDetailRef.value?.refresh();
}
} finally {
submitDialog.loading = false;
}
@@ -1551,73 +1661,104 @@ const handleToContract = async (row: QuoteVO) => {
await getList();
};
/** 审批报价单 */
/** 审批报价单(支持单条与批量) */
const handleApproval = async (row?: QuoteVO) => {
if (!row && ids.value.length !== 1) {
$modal.msgWarning('请选择一条数据进行审批');
const targets = row ? [row] : selectedRows.value;
if (!targets.length) {
$modal.msgWarning('请选择要审批的数据');
return;
}
const id = row?.id || ids.value[0];
const quote = row || quoteList.value[quoteList.value.findIndex((item) => item.id === id)];
if (!quote.canApprove || activeView.value !== 'submitted') {
$modal.msgWarning('只能审批待审批的报价单');
if (activeView.value !== 'submitted') {
$modal.msgWarning('请在「待审批」视图中进行审批');
return;
}
// 保存当前审批的报价单
currentApprovalQuote.value = quote;
// 重置表单
const currentUserId = resolveCurrentUserId();
if (!currentUserId) {
$modal.msgError('无法获取当前用户信息,请重新登录');
return;
}
// 重置表单(默认审批意见,可直接确认)
approvalForm.value = {
id: undefined,
id: undefined as unknown as string | number,
approvalResult: 'approved',
approvalOpinion: '',
approvalOpinion: '审批通过',
remark: ''
};
// 清空审批记录列表
approvalList.value = [];
batchApprovalTargets.value = [];
currentApprovalQuote.value = null;
const candidateQuotes = targets.filter((item) => item.canApprove);
let skippedCount = targets.length - candidateQuotes.length;
const validTargets: BatchApprovalTarget[] = [];
let singleApprovalList: QuoteApprovalVO[] = [];
// 加载审批记录
try {
const res = await listApproval(id);
approvalList.value = res.data || [];
// 获取当前用户ID
const currentUserId = resolveCurrentUserId();
if (!currentUserId) {
$modal.msgError('无法获取当前用户信息,请重新登录');
return;
}
// 找到当前用户的待审批记录
const pendingApproval = approvalList.value.find(
(item) => String(item.approverId) === String(currentUserId) && item.approvalResult === 'pending'
const loadResults = await Promise.allSettled(
candidateQuotes.map(async (quote) => {
const res = await listApproval(quote.id);
return { quote, list: res.data || [] };
})
);
if (!pendingApproval) {
// 检查用户是否已经审批过
const approvedRecord = approvalList.value.find(
(item) => String(item.approverId) === String(currentUserId)
for (const result of loadResults) {
if (result.status !== 'fulfilled') {
skippedCount += 1;
continue;
}
const { quote, list } = result.value;
const pendingApproval = list.find(
(item) =>
String(item.approverId) === String(currentUserId) && item.approvalResult === 'pending'
);
if (approvedRecord) {
const resultText = approvedRecord.approvalResult === 'approved' ? '已通过' : '已拒绝';
$modal.msgWarning(`您已经审批过该报价单(${resultText}),不能重复审批`);
} else {
$modal.msgWarning('您没有该报价单的审批权限');
if (!pendingApproval) {
skippedCount += 1;
continue;
}
validTargets.push({ quote, approvalId: pendingApproval.id });
if (candidateQuotes.length === 1) {
singleApprovalList = list;
}
return;
}
// 设置审批记录ID
approvalForm.value.id = pendingApproval.id;
// 打开审批对话框
approvalDialog.visible = true;
} catch (error: any) {
console.error('加载审批记录失败:', error);
$modal.msgError(error?.msg || '加载审批记录失败,请稍后重试');
approvalList.value = [];
return;
}
if (!validTargets.length) {
if (targets.length === 1) {
$modal.msgWarning('您没有该报价单的审批权限,或已审批过该报价单');
} else {
$modal.msgWarning('所选报价单均不可审批(无权、已审或不在待审批状态)');
}
return;
}
if (skippedCount > 0) {
$modal.msgWarning(`已跳过 ${skippedCount} 条不可审批的报价单,仅处理可审批单据`);
}
batchApprovalTargets.value = validTargets;
approvalSkippedCount.value = skippedCount;
if (validTargets.length === 1) {
const only = validTargets[0];
currentApprovalQuote.value = only.quote;
approvalForm.value.id = only.approvalId;
if (singleApprovalList.length) {
approvalList.value = singleApprovalList;
} else {
try {
const res = await listApproval(only.quote.id);
approvalList.value = res.data || [];
} catch {
approvalList.value = [];
}
}
}
approvalDialog.visible = true;
};
/** 确认审批 */
@@ -1630,12 +1771,22 @@ const confirmApproval = async () => {
return;
}
if (!batchApprovalTargets.value.length) {
$modal.msgWarning('没有可审批的报价单');
return;
}
// 二次确认
const resultText = approvalForm.value.approvalResult === 'approved' ? '通过' : '拒绝';
const targetCount = batchApprovalTargets.value.length;
const confirmMsg =
approvalForm.value.approvalResult === 'rejected'
? `确认要拒绝该报价单吗?拒绝后报价单将无法再次提交审批。\n拒绝理由:${approvalForm.value.approvalOpinion}`
: `确认要通过该报价单吗?`;
? targetCount === 1
? `确认要拒绝该报价单吗?拒绝后报价单将无法再次提交审批。\n拒绝理由:${approvalForm.value.approvalOpinion}`
: `确认要拒绝选中的 ${targetCount} 条报价单吗?拒绝后将无法再次提交审批。\n拒绝理由:${approvalForm.value.approvalOpinion}`
: targetCount === 1
? '确认要通过该报价单吗?'
: `确认要通过选中的 ${targetCount} 条报价单吗?`;
try {
await $modal.confirm(confirmMsg);
@@ -1645,17 +1796,35 @@ const confirmApproval = async () => {
approvalDialog.loading = true;
try {
await approveQuote(approvalForm.value);
const results = await Promise.allSettled(
batchApprovalTargets.value.map((item) =>
approveQuote({
id: item.approvalId,
approvalResult: approvalForm.value.approvalResult,
approvalOpinion: approvalForm.value.approvalOpinion,
remark: approvalForm.value.remark
})
)
);
const successCount = results.filter((item) => item.status === 'fulfilled').length;
const failedCount = results.length - successCount;
// 显示成功消息
const quoteNo = currentApprovalQuote.value?.quoteNo || '';
$modal.msgSuccess(`报价单【${quoteNo}】审批${resultText}成功`);
if (targetCount === 1 && successCount === 1 && approvalSkippedCount.value === 0) {
const quoteNo = currentApprovalQuote.value?.quoteNo || batchApprovalTargets.value[0]?.quote.quoteNo || '';
$modal.msgSuccess(`报价单【${quoteNo}】审批${resultText}成功`);
} else {
showBatchResultMessage({
actionLabel: `审批${resultText}`,
successCount,
failedCount,
skippedCount: approvalSkippedCount.value
});
}
// 关闭对话框
approvalDialog.visible = false;
// 刷新列表
await getList();
if (successCount > 0) {
approvalDialog.visible = false;
await getList();
}
} catch (error: any) {
console.error('审批失败:', error);
$modal.msgError(error?.msg || `审批${resultText}失败,请稍后重试`);
@@ -2739,6 +2908,34 @@ onMounted(() => {
}
}
.batch-action-alert {
margin-bottom: 12px;
&--success {
:deep(.el-alert__title),
:deep(.el-alert__icon) {
color: var(--primary, #10b981);
}
}
&--danger {
:deep(.el-alert__title),
:deep(.el-alert__icon) {
color: #ef4444;
}
}
}
.batch-approval-quote-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.batch-approval-quote-tag {
margin: 0;
}
.approval-kv-descriptions {
:deep(.el-descriptions__label) {
font-weight: 500;
-4
View File
@@ -6,10 +6,6 @@
</div>
<div id="app-portal-body">
<aside id="app-sidebar">
<ArtSidebarMenu />
</aside>
<main id="app-workspace">
<div id="app-content">
<ArtPageContent />
+9 -44
View File
@@ -2,7 +2,10 @@
display: flex;
flex-direction: column;
width: 100%;
min-height: 100vh;
/* 锁死视口高度,避免「顶栏 + calc(100vh-56)」叠出外层纵向滚动条 */
height: 100vh;
max-height: 100vh;
overflow: hidden;
background: var(--mint-bg-page);
#app-portal-header {
@@ -14,25 +17,17 @@
#app-portal-body {
display: flex;
flex: 1;
flex-direction: row;
flex-direction: column;
/* 由 flex 吃掉顶栏剩余高度,勿再写 calc(100vh - 56px) */
min-height: 0;
height: calc(100vh - 56px);
overflow: hidden;
}
#app-sidebar {
flex-shrink: 0;
height: 100%;
/* 边框画在 aside 上:内部 .menu-left 与侧栏同宽时会盖住 .layout-sidebar 的 border */
border-right: 1px solid var(--mint-border, #d9e8e2);
background: var(--mint-surface, #fff);
box-sizing: border-box;
}
#app-workspace {
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
min-width: 0;
height: 100%;
overflow: hidden;
@@ -68,48 +63,18 @@
}
}
/* 页面主标题已移至门户顶栏居中展示 */
:deep(
.page-container .panel-top:not(.panel-top-with-actions):not(.user-dept-aside__head):not(
.storage-aside__head
):not(.storage-content-header),
.console-container .panel-top
) {
display: none;
}
:deep(.panel-top.panel-top-with-actions .title-bar-heading) {
display: none;
}
:deep(.panel-top.panel-top-with-actions:not(:has(.panel-top-main))) {
justify-content: flex-end;
}
}
}
@media only screen and (width <= 1180px) {
.app-layout-portal {
#app-portal-body {
height: calc(100dvh - 56px);
}
height: 100dvh;
max-height: 100dvh;
}
}
@media only screen and (width <= 800px) {
.app-layout-portal {
#app-portal-body {
position: relative;
}
#app-sidebar {
position: fixed;
top: 56px;
left: 0;
z-index: 300;
height: calc(100vh - 56px);
}
#app-workspace {
width: 100%;
}
@@ -102,13 +102,19 @@
const submitting = ref(false)
const form = reactive({
approved: true,
commentText: ''
commentText: '审批通过'
})
const DEFAULT_OPINIONS = ['审批通过', '审批驳回'] as const
const bizLoading = ref(false)
const formFields = ref<Array<{ label: string; value: string }>>([])
const instanceData = ref<Record<string, any> | null>(null)
function defaultOpinion(approved: boolean) {
return approved ? '审批通过' : '审批驳回'
}
async function loadFormData() {
if (!props.instanceId) return
bizLoading.value = true
@@ -130,12 +136,21 @@
(val) => {
if (val) {
form.approved = true
form.commentText = ''
form.commentText = defaultOpinion(true)
loadFormData()
}
}
)
watch(
() => form.approved,
(approved) => {
if (!form.commentText.trim() || DEFAULT_OPINIONS.includes(form.commentText as (typeof DEFAULT_OPINIONS)[number])) {
form.commentText = defaultOpinion(approved)
}
}
)
async function handleSubmit() {
submitting.value = true
try {