You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
578 lines
16 KiB
578 lines
16 KiB
<template>
|
|
<div class="approval-process-container">
|
|
<el-card class="box-card !border-none" shadow="never">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<el-button type="primary" @click="handleCreate">
|
|
<icon name="add" class="mr-5px" />
|
|
{{ '创建(测试暂留)' }}
|
|
</el-button>
|
|
</div>
|
|
<div class="flex items-center">
|
|
<el-input
|
|
v-model="state.searchParams.process_name"
|
|
class="w-200 mr-15"
|
|
:placeholder="'搜索'"
|
|
clearable
|
|
@keyup.enter="handleSearch"
|
|
@clear="handleSearch"
|
|
/>
|
|
<el-select
|
|
v-model="state.searchParams.approval_status"
|
|
class="w-150px mr-15px"
|
|
:placeholder="'状态'"
|
|
clearable
|
|
@change="handleSearch"
|
|
>
|
|
<el-option :label="'待审批'" value="pending" />
|
|
<el-option :label="'已审批'" value="approved" />
|
|
<el-option :label="'已拒绝'" value="rejected" />
|
|
</el-select>
|
|
<el-button type="primary" @click="handleSearch">
|
|
<icon name="search" class="mr-5px" />
|
|
{{ '搜索' }}
|
|
</el-button>
|
|
<el-button @click="handleReset">
|
|
<icon name="refresh-right" class="mr-5px" />
|
|
{{ '重置' }}
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<el-tabs v-model="state.activeTab" class="mt-15px" @tab-click="handleTabChange">
|
|
<el-tab-pane :label="'全部'" name="all" />
|
|
<el-tab-pane :label="'我创建的'" name="myCreate" />
|
|
<el-tab-pane :label="'我审批的'" name="myApproval" />
|
|
</el-tabs>
|
|
|
|
<el-table
|
|
v-loading="state.loading"
|
|
:data="state.processList"
|
|
:header-cell-style="{ background: '#fafafa', color: '#606266' }"
|
|
>
|
|
<el-table-column :label="'ID'" prop="id" width="80" />
|
|
<el-table-column :label="'流程名称'" prop="process_name" min-width="180" />
|
|
<el-table-column :label="'申请人'" prop="applicant_name" width="100" />
|
|
<el-table-column :label="'申请时间'" prop="application_time" width="180" />
|
|
<el-table-column :label="'当前审批人'" prop="current_approver_name" width="100" />
|
|
<el-table-column :label="'审批状态'" prop="approval_status" width="100">
|
|
<template #default="{ row }">
|
|
<el-tag :type="getStatusType(row.approval_status)">
|
|
{{ getStatusText(row.approval_status) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="'操作'" width="180" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button type="primary" link @click="handleDetail(row)">
|
|
{{ '详情' }}
|
|
</el-button>
|
|
<el-button
|
|
v-if="row.approval_status === 'pending' && row.applicant_id === state.userInfo.uid"
|
|
type="danger"
|
|
link
|
|
@click="handleCancel(row)"
|
|
>
|
|
{{ '取消' }}
|
|
</el-button>
|
|
<el-button
|
|
v-if="row.approval_status === 'pending' && row.current_approver_id === state.userInfo.uid"
|
|
type="success"
|
|
link
|
|
@click="handleApprove(row)"
|
|
>
|
|
{{ '审批' }}
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<div class="flex justify-end mt-15px">
|
|
<el-pagination
|
|
v-model:current-page="state.searchParams.page"
|
|
v-model:page-size="state.searchParams.limit"
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
:total="state.total"
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
@size-change="handleSizeChange"
|
|
@current-change="handleCurrentChange"
|
|
/>
|
|
</div>
|
|
</el-card>
|
|
|
|
<!-- 创建审批弹窗 -->
|
|
<el-dialog
|
|
v-model="state.createDialog.visible"
|
|
:title="'创建审批'"
|
|
width="600px"
|
|
:close-on-click-modal="false"
|
|
:destroy-on-close="true"
|
|
>
|
|
<el-form
|
|
ref="createFormRef"
|
|
:model="state.createDialog.form"
|
|
:rules="state.createDialog.rules"
|
|
label-width="120px"
|
|
>
|
|
<el-form-item :label="'流程名称'" prop="process_name">
|
|
<el-input
|
|
v-model="state.createDialog.form.process_name"
|
|
:placeholder="'请输入流程名称'"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item :label="'审批流配置'" prop="config_id">
|
|
<el-select
|
|
v-model="state.createDialog.form.config_id"
|
|
class="w-full"
|
|
:placeholder="'请选择审批流配置'"
|
|
>
|
|
<el-option
|
|
v-for="item in state.configOptions"
|
|
:key="item.value"
|
|
:label="item.label"
|
|
:value="item.value"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item :label="'备注'" prop="remarks">
|
|
<el-input
|
|
v-model="state.createDialog.form.remarks"
|
|
type="textarea"
|
|
:rows="3"
|
|
:placeholder="'请输入备注'"
|
|
/>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="state.createDialog.visible = false">{{ '取消' }}</el-button>
|
|
<el-button type="primary" :loading="state.createDialog.loading" @click="handleCreateSubmit">
|
|
{{ '确认' }}
|
|
</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 审批弹窗 -->
|
|
<el-dialog
|
|
v-model="state.approveDialog.visible"
|
|
:title="'审批'"
|
|
width="500px"
|
|
:close-on-click-modal="false"
|
|
:destroy-on-close="true"
|
|
>
|
|
<el-form
|
|
ref="approveFormRef"
|
|
:model="state.approveDialog.form"
|
|
:rules="state.approveDialog.rules"
|
|
label-width="80px"
|
|
>
|
|
<el-form-item :label="'审批状态'" prop="status">
|
|
<el-radio-group v-model="state.approveDialog.form.status">
|
|
<el-radio label="approved">{{ '已审批' }}</el-radio>
|
|
<el-radio label="rejected">{{ '已拒绝' }}</el-radio>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
<el-form-item :label="'备注'" prop="remarks">
|
|
<el-input
|
|
v-model="state.approveDialog.form.remarks"
|
|
type="textarea"
|
|
:rows="3"
|
|
:placeholder="'请输入备注'"
|
|
/>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="state.approveDialog.visible = false">{{ '取消' }}</el-button>
|
|
<el-button type="primary" :loading="state.approveDialog.loading" @click="handleApproveSubmit">
|
|
{{ '确认' }}
|
|
</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 详情弹窗 -->
|
|
<el-dialog
|
|
v-model="state.detailDialog.visible"
|
|
:title="'审批详情'"
|
|
width="800px"
|
|
:destroy-on-close="true"
|
|
>
|
|
<el-descriptions :column="1" border>
|
|
<el-descriptions-item :label="'ID'">
|
|
{{ state.detailDialog.info.id }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item :label="'流程名称'">
|
|
{{ state.detailDialog.info.process_name }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item :label="'申请人'">
|
|
{{ state.detailDialog.info.applicant_name }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item :label="'申请时间'">
|
|
{{ state.detailDialog.info.application_time }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item :label="'当前审批人'">
|
|
{{ state.detailDialog.info.current_approver_name }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item :label="'审批状态'">
|
|
<el-tag :type="getStatusType(state.detailDialog.info.approval_status)">
|
|
{{ getStatusText(state.detailDialog.info.approval_status) }}
|
|
</el-tag>
|
|
</el-descriptions-item>
|
|
<el-descriptions-item :label="'审批时间'">
|
|
{{ state.detailDialog.info.approval_time || '-' }}
|
|
</el-descriptions-item>
|
|
<el-descriptions-item :label="'备注'">
|
|
{{ state.detailDialog.info.remarks || '-' }}
|
|
</el-descriptions-item>
|
|
</el-descriptions>
|
|
|
|
<div class="mt-20px">
|
|
<div class="font-bold text-16px mb-10px">{{ '参与者' }}</div>
|
|
<el-timeline>
|
|
<el-timeline-item
|
|
v-for="(node, index) in state.detailDialog.info.participants"
|
|
:key="index"
|
|
:type="getNodeType(node.status)"
|
|
:color="getNodeColor(node.status)"
|
|
>
|
|
<div class="font-bold mb-5px">
|
|
{{ '参与者' + (index + 1) }}
|
|
</div>
|
|
<div class="text-gray-500">
|
|
{{ '参与者' }}:{{ node.name }}
|
|
</div>
|
|
<div class="text-gray-500">
|
|
{{ '顺序' }}:{{ node.sequence }}
|
|
</div>
|
|
<div class="text-gray-500">
|
|
{{ '状态' }}:{{ getStatusText(node.status) }}
|
|
</div>
|
|
<div class="text-gray-500">
|
|
{{ '签名类型' }}:{{ node.sign_type === 'or_sign' ? '或签名' : '和签名' }}
|
|
</div>
|
|
<div class="text-gray-500">
|
|
{{ '备注' }}:{{ node.remarks || '-' }}
|
|
</div>
|
|
</el-timeline-item>
|
|
</el-timeline>
|
|
</div>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { reactive, ref, onMounted } from 'vue'
|
|
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
|
|
import { getProcessList, getProcessInfo, createProcess, approveProcess, cancelProcess } from '@/app/api/school_approval/process'
|
|
import { getConfigList } from '@/app/api/school_approval/config'
|
|
// import { useI18n } from 'vue-i18n'
|
|
// import { useUserInfo } from '@/stores/userInfo'
|
|
|
|
// const { t } = useI18n()
|
|
// const userInfo = useUserInfo()
|
|
|
|
// 表单引用
|
|
const createFormRef = ref<FormInstance>()
|
|
const approveFormRef = ref<FormInstance>()
|
|
|
|
// 在script区域顶部添加接口定义
|
|
interface ConfigOption {
|
|
label: string;
|
|
value: number;
|
|
}
|
|
|
|
interface Participant {
|
|
participant_id: number;
|
|
sequence: number;
|
|
status: string;
|
|
sign_type: string;
|
|
remarks?: string;
|
|
}
|
|
|
|
// 状态
|
|
const state = reactive({
|
|
loading: false,
|
|
processList: [],
|
|
total: 0,
|
|
activeTab: 'all',
|
|
userInfo: {
|
|
uid: 1 // 提供一个默认的用户ID,实际项目中应该从store获取
|
|
},
|
|
searchParams: {
|
|
page: 1,
|
|
limit: 10,
|
|
process_name: '',
|
|
approval_status: '',
|
|
applicant_id: 0,
|
|
approver_id: 0
|
|
},
|
|
configOptions: [] as ConfigOption[], // 添加类型标注
|
|
createDialog: {
|
|
visible: false,
|
|
loading: false,
|
|
form: {
|
|
process_name: '',
|
|
config_id: '',
|
|
remarks: ''
|
|
},
|
|
rules: {
|
|
process_name: [
|
|
{ required: true, message: '请输入流程名称', trigger: 'blur' }
|
|
],
|
|
config_id: [
|
|
{ required: true, message: '请选择审批流配置', trigger: 'change' }
|
|
]
|
|
}
|
|
},
|
|
approveDialog: {
|
|
visible: false,
|
|
loading: false,
|
|
processId: 0,
|
|
form: {
|
|
status: 'approved',
|
|
remarks: ''
|
|
},
|
|
rules: {
|
|
status: [
|
|
{ required: true, message: '请选择审批状态', trigger: 'change' }
|
|
]
|
|
}
|
|
},
|
|
detailDialog: {
|
|
visible: false,
|
|
info: {
|
|
id: 0,
|
|
process_name: '',
|
|
applicant_id: 0,
|
|
application_time: '',
|
|
current_approver_id: 0,
|
|
approval_status: '',
|
|
approval_time: '',
|
|
remarks: '',
|
|
participants: [] as Participant[] // 添加类型标注
|
|
}
|
|
}
|
|
})
|
|
|
|
// 获取列表数据
|
|
async function getList() {
|
|
state.loading = true
|
|
try {
|
|
const res = await getProcessList(state.searchParams)
|
|
state.processList = res.data.list
|
|
state.total = res.data.count
|
|
} catch (error) {
|
|
console.error(error)
|
|
} finally {
|
|
state.loading = false
|
|
}
|
|
}
|
|
|
|
// 搜索
|
|
function handleSearch() {
|
|
state.searchParams.page = 1
|
|
getList()
|
|
}
|
|
|
|
// 重置
|
|
function handleReset() {
|
|
state.searchParams = {
|
|
page: 1,
|
|
limit: 10,
|
|
process_name: '',
|
|
approval_status: '',
|
|
applicant_id: 0,
|
|
approver_id: 0
|
|
}
|
|
state.activeTab = 'all'
|
|
getList()
|
|
}
|
|
|
|
// 页码变化
|
|
function handleCurrentChange(page: number) {
|
|
state.searchParams.page = page
|
|
getList()
|
|
}
|
|
|
|
// 每页条数变化
|
|
function handleSizeChange(size: number) {
|
|
state.searchParams.limit = size
|
|
state.searchParams.page = 1
|
|
getList()
|
|
}
|
|
|
|
// 标签页切换
|
|
function handleTabChange() {
|
|
// 重置搜索条件
|
|
state.searchParams.applicant_id = 0
|
|
state.searchParams.approver_id = 0
|
|
|
|
// 根据标签设置搜索条件
|
|
if (state.activeTab === 'myCreate') {
|
|
state.searchParams.applicant_id = state.userInfo.uid
|
|
} else if (state.activeTab === 'myApproval') {
|
|
state.searchParams.approver_id = state.userInfo.uid
|
|
}
|
|
|
|
state.searchParams.page = 1
|
|
getList()
|
|
}
|
|
|
|
// 获取配置选项
|
|
async function getConfigOptions() {
|
|
try {
|
|
const res = await getConfigList({ status: 1 })
|
|
state.configOptions = (res.data.list || []).map((item: any) => {
|
|
return {
|
|
label: item.config_name || '',
|
|
value: item.id || 0
|
|
} as ConfigOption
|
|
})
|
|
} catch (error) {
|
|
console.error(error)
|
|
}
|
|
}
|
|
|
|
// 创建审批
|
|
function handleCreate() {
|
|
state.createDialog.form = {
|
|
process_name: '',
|
|
config_id: '',
|
|
remarks: ''
|
|
}
|
|
state.createDialog.visible = true
|
|
}
|
|
|
|
// 提交创建
|
|
async function handleCreateSubmit() {
|
|
if (!createFormRef.value) return
|
|
|
|
await createFormRef.value.validate(async (valid) => {
|
|
if (!valid) return
|
|
|
|
state.createDialog.loading = true
|
|
try {
|
|
await createProcess(state.createDialog.form)
|
|
ElMessage.success('创建成功')
|
|
state.createDialog.visible = false
|
|
getList()
|
|
} catch (error) {
|
|
console.error(error)
|
|
} finally {
|
|
state.createDialog.loading = false
|
|
}
|
|
})
|
|
}
|
|
|
|
// 审批
|
|
function handleApprove(row: any) {
|
|
state.approveDialog.processId = row.id
|
|
state.approveDialog.form = {
|
|
status: 'approved',
|
|
remarks: ''
|
|
}
|
|
state.approveDialog.visible = true
|
|
}
|
|
|
|
// 提交审批
|
|
async function handleApproveSubmit() {
|
|
if (!approveFormRef.value) return
|
|
|
|
await approveFormRef.value.validate(async (valid) => {
|
|
if (!valid) return
|
|
|
|
state.approveDialog.loading = true
|
|
try {
|
|
await approveProcess({
|
|
process_id: state.approveDialog.processId,
|
|
status: state.approveDialog.form.status,
|
|
remarks: state.approveDialog.form.remarks
|
|
})
|
|
ElMessage.success('审批成功')
|
|
state.approveDialog.visible = false
|
|
getList()
|
|
} catch (error) {
|
|
console.error(error)
|
|
} finally {
|
|
state.approveDialog.loading = false
|
|
}
|
|
})
|
|
}
|
|
|
|
// 撤销
|
|
function handleCancel(row: any) {
|
|
ElMessageBox.confirm('确认取消审批?', '警告', {
|
|
confirmButtonText: '确认',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
})
|
|
.then(async () => {
|
|
try {
|
|
await cancelProcess({ process_id: row.id })
|
|
ElMessage.success('取消成功')
|
|
getList()
|
|
} catch (error) {
|
|
console.error(error)
|
|
}
|
|
})
|
|
.catch(() => {})
|
|
}
|
|
|
|
// 查看详情
|
|
async function handleDetail(row: any) {
|
|
try {
|
|
const res = await getProcessInfo({ id: row.id })
|
|
state.detailDialog.info = res.data
|
|
state.detailDialog.visible = true
|
|
} catch (error) {
|
|
console.error(error)
|
|
}
|
|
}
|
|
|
|
// 获取状态类型
|
|
function getStatusType(status: string) {
|
|
const map: Record<string, string> = {
|
|
pending: 'warning',
|
|
approved: 'success',
|
|
rejected: 'danger'
|
|
}
|
|
return map[status] || 'info'
|
|
}
|
|
|
|
// 获取状态文本
|
|
function getStatusText(status: string) {
|
|
const map: Record<string, string> = {
|
|
pending: '待审批',
|
|
approved: '已审批',
|
|
rejected: '已拒绝'
|
|
}
|
|
return map[status] || status
|
|
}
|
|
|
|
// 获取节点类型
|
|
function getNodeType(status: string) {
|
|
const map: Record<string, string> = {
|
|
pending: 'warning',
|
|
approved: 'success',
|
|
rejected: 'danger'
|
|
}
|
|
return map[status] || 'info'
|
|
}
|
|
|
|
// 获取节点颜色
|
|
function getNodeColor(status: string) {
|
|
const map: Record<string, string> = {
|
|
pending: '#E6A23C',
|
|
approved: '#67C23A',
|
|
rejected: '#F56C6C'
|
|
}
|
|
return map[status] || '#909399'
|
|
}
|
|
|
|
onMounted(() => {
|
|
getList()
|
|
getConfigOptions()
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
// 自定义样式
|
|
</style>
|