智慧教务系统
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

<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>