Files
zkxw/web-admin/src/views/system/workflow/components/ApprovalDialog.vue
T
2026-05-21 11:25:43 +08:00

292 lines
6.8 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>
<el-dialog
v-model="visible"
width="640px"
destroy-on-close
class="workflow-module-dialog"
align-center
:close-on-click-modal="false"
:show-close="false"
style="backdrop-filter: none"
>
<div v-if="bizLoading" class="dialog-scroll is-view-loading" style="min-height: 120px">
<DialogDetailSkeleton variant="simple" :section-count="2" :field-count="4" />
</div>
<div v-else class="dialog-scroll">
<!-- Hero header -->
<div class="view-hero">
<div class="view-hero-icon">
<el-icon :size="28"><Checked /></el-icon>
</div>
<div class="view-hero-main">
<h2>{{ instanceData?.processName || '审批操作' }}</h2>
<div class="view-hero-tags">
<el-tag size="small" type="info">{{ instanceData?.businessTypeLabel || instanceData?.businessType || '--' }}</el-tag>
</div>
<p v-if="instanceData" class="view-hero-meta">
发起人:{{ instanceData.starterUserName || '--' }}
<span class="dot">·</span>
业务单号:{{ instanceData.businessId || '--' }}
<span class="dot">·</span>
{{ instanceData.createTime || '--' }}
</p>
</div>
</div>
<!-- 申请信息 -->
<div v-if="formFields.length" class="view-section">
<h3>申请信息</h3>
<dl class="view-kv-grid">
<div v-for="field in formFields" :key="field.label" class="view-kv">
<dt>{{ field.label }}</dt>
<dd>{{ field.value || '--' }}</dd>
</div>
</dl>
</div>
<!-- 审批操作 -->
<div class="view-section">
<h3>审批操作</h3>
<el-form :model="form" label-position="top">
<el-form-item label="审批结果">
<el-radio-group v-model="form.approved">
<el-radio :value="true">通过</el-radio>
<el-radio :value="false">驳回</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审批意见">
<el-input
v-model="form.commentText"
type="textarea"
:rows="3"
placeholder="请输入审批意见"
/>
</el-form-item>
</el-form>
</div>
</div>
<template #footer>
<el-button class="btn-ghost" @click="visible = false"><el-icon><Close /></el-icon>取消</el-button>
<el-button type="primary" class="btn-main" :loading="submitting" @click="handleSubmit">
<el-icon><Check /></el-icon>
确认提交
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import DialogDetailSkeleton from '@/components/business/dialog-detail-skeleton/index.vue'
import { fetchApproveTask, fetchRejectTask, fetchInstanceDetail } from '@/api/workflow'
import { Checked, Close, Check } from '@element-plus/icons-vue'
defineOptions({ name: 'ApprovalDialog' })
const props = defineProps<{
visible: boolean
taskId: string
instanceId?: string
}>()
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void
(e: 'done'): void
}>()
const visible = computed({
get: () => props.visible,
set: (val) => emit('update:visible', val)
})
const submitting = ref(false)
const form = reactive({
approved: true,
commentText: ''
})
const bizLoading = ref(false)
const formFields = ref<Array<{ label: string; value: string }>>([])
const instanceData = ref<Record<string, any> | null>(null)
async function loadFormData() {
if (!props.instanceId) return
bizLoading.value = true
formFields.value = []
instanceData.value = null
try {
const data = await fetchInstanceDetail(props.instanceId)
instanceData.value = data
formFields.value = data.businessFormFields || []
} catch {
// 加载失败不影响审批操作
} finally {
bizLoading.value = false
}
}
watch(
() => props.visible,
(val) => {
if (val) {
form.approved = true
form.commentText = ''
loadFormData()
}
}
)
async function handleSubmit() {
submitting.value = true
try {
const data = { commentText: form.commentText?.trim() || undefined }
if (form.approved) {
await fetchApproveTask(props.taskId, data)
} else {
await fetchRejectTask(props.taskId, data)
}
visible.value = false
emit('done')
} catch {
// 错误已由全局处理
} finally {
submitting.value = false
}
}
</script>
<style scoped lang="scss">
.dialog-scroll {
overflow-y: auto;
max-height: calc(100vh - 120px);
}
.view-hero {
display: flex;
gap: 16px;
padding: 20px 22px;
background: linear-gradient(135deg, #ecfdf5 0%, #ffffff 55%);
border-bottom: 1px solid #d9e8e2;
align-items: center;
}
.view-hero-icon {
width: 72px;
height: 72px;
border-radius: 12px;
border: 1px solid rgba(16, 185, 129, 0.2);
background: rgba(16, 185, 129, 0.08);
color: #059669;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.view-hero-main {
flex: 1;
min-width: 0;
h2 {
margin: 0 0 8px;
font-size: 18px;
font-weight: 700;
color: #1e293b;
letter-spacing: -0.02em;
}
}
.view-hero-tags {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
flex-wrap: wrap;
}
.view-hero-meta {
margin: 0;
font-size: 13px;
color: #64748b;
line-height: 1.5;
.dot {
margin: 0 6px;
opacity: 0.7;
}
}
.view-section {
padding: 18px 22px;
border-bottom: 1px solid #ebeef5;
&:last-child {
border-bottom: none;
}
h3 {
margin: 0 0 14px;
font-size: 13px;
font-weight: 700;
letter-spacing: 0.06em;
color: #64748b;
}
}
.view-kv-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px 28px;
}
.view-kv {
display: grid;
grid-template-columns: 96px 1fr;
gap: 8px 12px;
font-size: 13px;
align-items: start;
dt {
margin: 0;
color: #64748b;
font-weight: 500;
}
dd {
margin: 0;
color: #1e293b;
word-break: break-word;
}
}
@media (max-width: 640px) {
.view-kv-grid {
grid-template-columns: 1fr;
}
.view-kv {
grid-template-columns: 1fr;
gap: 4px;
}
}
</style>
<style lang="scss">
.workflow-module-dialog {
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding: 0;
overflow: hidden;
}
.el-dialog__footer {
padding: 12px 22px;
border-top: 1px solid #d9e8e2;
background: #f8fafc;
}
}
</style>