294 lines
6.6 KiB
Vue
294 lines
6.6 KiB
Vue
<!-- 审批操作弹窗 -->
|
||
<template>
|
||
<el-dialog
|
||
v-model="visible"
|
||
width="640px"
|
||
destroy-on-close
|
||
class="workflow-module-dialog"
|
||
align-center
|
||
append-to-body
|
||
modal-class="protocol-dialog-mask"
|
||
:close-on-click-modal="false"
|
||
:show-close="false"
|
||
style="backdrop-filter: none"
|
||
>
|
||
<div v-loading="bizLoading" 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 { 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, #e8f4ee 0%, #ffffff 55%);
|
||
border-bottom: 1px solid #c5e0d4;
|
||
align-items: center;
|
||
}
|
||
|
||
.view-hero-icon {
|
||
width: 72px;
|
||
height: 72px;
|
||
border-radius: 12px;
|
||
border: 1px solid rgba(4, 120, 87, 0.2);
|
||
background: rgba(4, 120, 87, 0.08);
|
||
color: #065f46;
|
||
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 #c5e0d4;
|
||
background: #f8fafc;
|
||
}
|
||
}
|
||
</style>
|