- 审批/项目/租户/用户设置等视图新增 asXxx 类型断言辅助函数 - search-fields 由字符串改为数组传参 - backend 依赖新增 psycopg-pool,Dockerfile 依赖校验同步更新
134 lines
4.7 KiB
Vue
134 lines
4.7 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, reactive, ref } from 'vue'
|
|
import { ElMessage } from 'element-plus'
|
|
import DataTablePage from '@/components/DataTablePage.vue'
|
|
import { getApprovalInstances, decideApproval, type ApprovalInstance } from '@/api/modules/approval'
|
|
import { getUsers } from '@/api/modules/system'
|
|
import type { SystemUser } from '@/types'
|
|
|
|
const loading = ref(false)
|
|
const instances = ref<ApprovalInstance[]>([])
|
|
const users = ref<SystemUser[]>([])
|
|
const statusFilter = ref<string | undefined>(undefined)
|
|
const showDecide = ref(false)
|
|
const current = ref<ApprovalInstance | null>(null)
|
|
const decision = ref({ step_index: 0, approver_id: '', approved: true, comment: '' })
|
|
|
|
const statusOptions = [
|
|
{ label: '待审批', value: 'pending' },
|
|
{ label: '已通过', value: 'approved' },
|
|
{ label: '已拒绝', value: 'rejected' },
|
|
]
|
|
|
|
async function load() {
|
|
loading.value = true
|
|
try {
|
|
instances.value = await getApprovalInstances(statusFilter.value)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function loadUsers() {
|
|
try {
|
|
users.value = await getUsers()
|
|
} catch {
|
|
users.value = []
|
|
}
|
|
}
|
|
|
|
function userName(id?: string) {
|
|
if (!id) return '—'
|
|
return users.value.find((u) => u.id === id)?.username || id
|
|
}
|
|
|
|
function openDecide(inst: ApprovalInstance) {
|
|
current.value = inst
|
|
const step = inst.steps.find((s) => s.status === 'pending')
|
|
decision.value = { step_index: step ? step.step_index : 0, approver_id: '', approved: true, comment: '' }
|
|
showDecide.value = true
|
|
}
|
|
|
|
function asApprovalInstance(row: unknown): ApprovalInstance {
|
|
return row as ApprovalInstance
|
|
}
|
|
|
|
async function submitDecision() {
|
|
if (!current.value) return
|
|
if (!decision.value.approver_id) {
|
|
ElMessage.warning('请选择审批人')
|
|
return
|
|
}
|
|
await decideApproval(current.value.id, decision.value.step_index, {
|
|
approver_id: decision.value.approver_id,
|
|
approved: decision.value.approved,
|
|
comment: decision.value.comment,
|
|
})
|
|
ElMessage.success('审批已提交')
|
|
showDecide.value = false
|
|
load()
|
|
}
|
|
|
|
onMounted(() => {
|
|
loadUsers()
|
|
load()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="page">
|
|
<DataTablePage title="审批实例" :data="instances" :loading="loading" searchable :search-fields="['resource_type', 'resource_id']">
|
|
<template #toolbar-extra>
|
|
<el-select v-model="statusFilter" placeholder="状态" clearable style="width: 140px" @change="load">
|
|
<el-option v-for="s in statusOptions" :key="s.value" :label="s.label" :value="s.value" />
|
|
</el-select>
|
|
</template>
|
|
<template #columns>
|
|
<el-table-column prop="resource_type" label="资源类型" min-width="120" />
|
|
<el-table-column prop="resource_id" label="资源 ID" min-width="160" show-overflow-tooltip />
|
|
<el-table-column prop="applicant_id" label="申请人" min-width="120">
|
|
<template #default="{ row }">{{ userName(asApprovalInstance(row).applicant_id) }}</template>
|
|
</el-table-column>
|
|
<el-table-column prop="status" label="状态" min-width="100" />
|
|
<el-table-column prop="current_step" label="当前步骤" min-width="100" />
|
|
<el-table-column prop="create_time" label="创建时间" min-width="180" />
|
|
</template>
|
|
<template #actions="{ row }">
|
|
<el-button v-if="asApprovalInstance(row).status === 'pending'" link type="primary" @click="openDecide(asApprovalInstance(row))">审批</el-button>
|
|
</template>
|
|
</DataTablePage>
|
|
<el-dialog v-model="showDecide" title="审批决策" width="480px">
|
|
<el-form label-width="80px" v-if="current">
|
|
<el-form-item label="实例">
|
|
{{ current.resource_type }} / {{ current.resource_id }}
|
|
</el-form-item>
|
|
<el-form-item label="步骤">
|
|
第 {{ decision.step_index + 1 }} 步
|
|
</el-form-item>
|
|
<el-form-item label="审批人" required>
|
|
<el-select v-model="decision.approver_id" filterable style="width: 100%">
|
|
<el-option v-for="u in users" :key="u.id" :label="u.username" :value="u.id" />
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="结果">
|
|
<el-radio-group v-model="decision.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="decision.comment" type="textarea" :rows="3" />
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="showDecide = false">取消</el-button>
|
|
<el-button type="primary" @click="submitDecision">提交</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped lang="scss">
|
|
.page { padding: 16px; }
|
|
</style>
|