Browse Source

修改流程申请审核管理的bug

master
王泽彦 10 months ago
parent
commit
4588ea879b
  1. 10
      admin/src/app/api/school_approval/process.ts
  2. 265
      admin/src/app/views/school_approval/config/index.vue
  3. 178
      admin/src/app/views/school_approval/process/index.vue
  4. 37
      admin/src/utils/request.ts
  5. 10
      niucloud/app/adminapi/controller/school_approval/Process.php
  6. 2
      niucloud/app/service/school_approval/SchoolApprovalProcessService.php

10
admin/src/app/api/school_approval/process.ts

@ -5,7 +5,7 @@ import request from '@/utils/request'
* @param params * @param params
*/ */
export function getProcessList(params?: Record<string, any>) { export function getProcessList(params?: Record<string, any>) {
return request.get({ url: '/school_approval/process/lists', params }) return request.get('/school_approval/process/lists', { params })
} }
/** /**
@ -13,7 +13,7 @@ export function getProcessList(params?: Record<string, any>) {
* @param params * @param params
*/ */
export function getProcessInfo(params: { id: number }) { export function getProcessInfo(params: { id: number }) {
return request.get({ url: '/school_approval/process/info', params }) return request.get('/school_approval/process/info', { params })
} }
/** /**
@ -21,7 +21,7 @@ export function getProcessInfo(params: { id: number }) {
* @param params * @param params
*/ */
export function createProcess(params: any) { export function createProcess(params: any) {
return request.post({ url: '/school_approval/process/create', data: params }) return request.post('/school_approval/process/create', params)
} }
/** /**
@ -29,7 +29,7 @@ export function createProcess(params: any) {
* @param params * @param params
*/ */
export function approveProcess(params: { process_id: number; status: string; remarks?: string }) { export function approveProcess(params: { process_id: number; status: string; remarks?: string }) {
return request.post({ url: '/school_approval/process/approve', data: params }) return request.post('/school_approval/process/approve', params)
} }
/** /**
@ -37,5 +37,5 @@ export function approveProcess(params: { process_id: number; status: string; rem
* @param params * @param params
*/ */
export function cancelProcess(params: { process_id: number }) { export function cancelProcess(params: { process_id: number }) {
return request.post({ url: '/school_approval/process/cancel', data: params }) return request.post('/school_approval/process/cancel', params)
} }

265
admin/src/app/views/school_approval/config/index.vue

@ -4,60 +4,60 @@
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div> <div>
<el-button type="primary" @click="handleAdd"> <el-button type="primary" @click="handleAdd">
<icon name="add" class="mr-5px" /> <icon name="add" class="mr-5px"/>
添加 添加
</el-button> </el-button>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<el-input <el-input
v-model="state.searchParams.config_name" v-model="state.searchParams.config_name"
class="w-200px mr-15px" class="w-200px mr-15px"
placeholder="请输入配置名称" placeholder="请输入配置名称"
clearable clearable
@keyup.enter="handleSearch" @keyup.enter="handleSearch"
@clear="handleSearch" @clear="handleSearch"
/> />
<el-select <el-select
v-model="state.searchParams.status" v-model="state.searchParams.status"
class="w-150px mr-15px" class="w-150px mr-15px"
placeholder="请选择状态" placeholder="请选择状态"
clearable clearable
@change="handleSearch" @change="handleSearch"
> >
<el-option label="启用" :value="1" /> <el-option label="启用" :value="1"/>
<el-option label="禁用" :value="0" /> <el-option label="禁用" :value="0"/>
</el-select> </el-select>
<el-button type="primary" @click="handleSearch"> <el-button type="primary" @click="handleSearch">
<icon name="search" class="mr-5px" /> <icon name="search" class="mr-5px"/>
搜索 搜索
</el-button> </el-button>
<el-button @click="handleReset"> <el-button @click="handleReset">
<icon name="refresh-right" class="mr-5px" /> <icon name="refresh-right" class="mr-5px"/>
重置 重置
</el-button> </el-button>
</div> </div>
</div> </div>
<el-table <el-table
v-loading="state.loading" v-loading="state.loading"
class="mt-15px" class="mt-15px"
:data="state.configList" :data="state.configList"
:header-cell-style="{ background: '#fafafa', color: '#606266' }" :header-cell-style="{ background: '#fafafa', color: '#606266' }"
> >
<el-table-column label="ID" prop="id" width="80" /> <el-table-column label="ID" prop="id" width="80"/>
<el-table-column label="配置名称" prop="config_name" min-width="180" /> <el-table-column label="配置名称" prop="config_name" min-width="180"/>
<el-table-column label="配置描述" prop="description" min-width="200" show-overflow-tooltip /> <el-table-column label="配置描述" prop="description" min-width="200" show-overflow-tooltip/>
<el-table-column label="状态" prop="status" width="100"> <el-table-column label="状态" prop="status" width="100">
<template #default="{ row }"> <template #default="{ row }">
<el-switch <el-switch
v-model="row.status" v-model="row.status"
:active-value="1" :active-value="1"
:inactive-value="0" :inactive-value="0"
@change="handleStatusChange(row)" @change="handleStatusChange(row)"
/> />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="创建时间" prop="created_at" width="180" /> <el-table-column label="创建时间" prop="created_at" width="180"/>
<el-table-column label="操作" width="180" fixed="right"> <el-table-column label="操作" width="180" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)"> <el-button type="primary" link @click="handleEdit(row)">
@ -75,68 +75,69 @@
<div class="flex justify-end mt-15px"> <div class="flex justify-end mt-15px">
<el-pagination <el-pagination
v-model:current-page="state.searchParams.page" v-model:current-page="state.searchParams.page"
v-model:page-size="state.searchParams.limit" v-model:page-size="state.searchParams.limit"
:page-sizes="[10, 20, 50, 100]" :page-sizes="[10, 20, 50, 100]"
:total="state.total" :total="state.total"
layout="total, sizes, prev, pager, next, jumper" layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
/> />
</div> </div>
</el-card> </el-card>
<!-- 新增/编辑弹窗 --> <!-- 新增/编辑弹窗 -->
<el-dialog <el-dialog
v-model="state.dialog.visible" v-model="state.dialog.visible"
:title="state.dialog.title" :title="state.dialog.title"
width="800px" width="800px"
:close-on-click-modal="false" :close-on-click-modal="false"
:destroy-on-close="true" :destroy-on-close="true"
> >
<el-form <el-form
ref="formRef" ref="formRef"
:model="state.dialog.form" :model="state.dialog.form"
:rules="state.dialog.rules" :rules="state.dialog.rules"
label-width="120px" label-width="120px"
> >
<el-form-item label="配置名称" prop="config_name"> <el-form-item label="配置名称" prop="config_name">
<el-input v-model="state.dialog.form.config_name" placeholder="请输入配置名称" /> <el-input v-model="state.dialog.form.config_name" placeholder="请输入配置名称"/>
</el-form-item> </el-form-item>
<el-form-item label="配置描述" prop="description"> <el-form-item label="配置描述" prop="description">
<el-input <el-input
v-model="state.dialog.form.description" v-model="state.dialog.form.description"
type="textarea" type="textarea"
:rows="3" :rows="3"
placeholder="请输入配置描述" placeholder="请输入配置描述"
/> />
</el-form-item> </el-form-item>
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-switch <el-switch
v-model="state.dialog.form.status" v-model="state.dialog.form.status"
:active-value="1" :active-value="1"
:inactive-value="0" :inactive-value="0"
/> />
</el-form-item> </el-form-item>
<el-form-item label="审批节点" prop="nodes"> <el-form-item label="审批节点" prop="nodes">
<div class="mb-10px"> <div class="mb-10px">
<el-button type="primary" @click="handleAddNode"> <el-button type="primary" @click="handleAddNode">
<icon name="add" class="mr-5px" /> <icon name="add" class="mr-5px"/>
添加节点 添加节点
</el-button> </el-button>
</div> </div>
<div v-if="state.dialog.form.nodes.length === 0" class="text-gray-400 text-center py-20px border border-dashed rounded"> <div v-if="state.dialog.form.nodes.length === 0"
class="text-gray-400 text-center py-20px border border-dashed rounded">
暂无审批节点请点击添加 暂无审批节点请点击添加
</div> </div>
<div v-else class="node-list"> <div v-else class="node-list">
<div <div
v-for="(element, index) in state.dialog.form.nodes" v-for="(element, index) in state.dialog.form.nodes"
:key="index" :key="index"
class="node-item p-15px mb-15px border border-gray-200 rounded" class="node-item p-15px mb-15px border border-gray-200 rounded"
> >
<div class="flex justify-between items-center mb-10px"> <div class="flex justify-between items-center mb-10px">
<div class="flex items-center"> <div class="flex items-center">
<icon name="rank" class="drag-handle mr-5px cursor-move" /> <icon name="rank" class="drag-handle mr-5px cursor-move"/>
<span class="font-bold">节点 {{ index + 1 }}</span> <span class="font-bold">节点 {{ index + 1 }}</span>
</div> </div>
<el-button type="danger" link @click="handleRemoveNode(index)"> <el-button type="danger" link @click="handleRemoveNode(index)">
@ -146,53 +147,53 @@
<div class="flex items-center mb-10px"> <div class="flex items-center mb-10px">
<span class="w-100px">节点名称</span> <span class="w-100px">节点名称</span>
<el-input <el-input
v-model="element.node_name" v-model="element.node_name"
placeholder="请输入节点名称" placeholder="请输入节点名称"
/> />
</div> </div>
<div class="flex items-center mb-10px"> <div class="flex items-center mb-10px">
<span class="w-100px">审批人类型</span> <span class="w-100px">审批人类型</span>
<el-select <el-select
v-model="element.approver_type" v-model="element.approver_type"
class="flex-1" class="flex-1"
placeholder="请选择审批人类型" placeholder="请选择审批人类型"
> >
<el-option label="指定用户" value="user" /> <el-option label="指定用户" value="user"/>
<el-option label="指定角色" value="role" /> <el-option label="指定角色" value="role"/>
<el-option label="指定部门" value="department" /> <el-option label="指定部门" value="department"/>
</el-select> </el-select>
</div> </div>
<div class="flex items-center mb-10px"> <div class="flex items-center mb-10px">
<span class="w-100px">审批人</span> <span class="w-100px">审批人</span>
<el-select <el-select
v-model="element.approver_ids" v-model="element.approver_ids"
class="flex-1" class="flex-1"
multiple multiple
placeholder="请选择审批人" placeholder="请选择审批人"
> >
<!-- 这里根据 approver_type 不同显示不同的选项 --> <!-- 这里根据 approver_type 不同显示不同的选项 -->
<template v-if="element.approver_type === 'user'"> <template v-if="element.approver_type === 'user'">
<el-option <el-option
v-for="item in state.userOptions" v-for="item in state.userOptions"
:key="item.value" :key="item.value"
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
/> />
</template> </template>
<template v-else-if="element.approver_type === 'role'"> <template v-else-if="element.approver_type === 'role'">
<el-option <el-option
v-for="item in state.roleOptions" v-for="item in state.roleOptions"
:key="item.value" :key="item.value"
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
/> />
</template> </template>
<template v-else-if="element.approver_type === 'department'"> <template v-else-if="element.approver_type === 'department'">
<el-option <el-option
v-for="item in state.departmentOptions" v-for="item in state.departmentOptions"
:key="item.value" :key="item.value"
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
/> />
</template> </template>
</el-select> </el-select>
@ -218,10 +219,10 @@
<!-- 详情弹窗 --> <!-- 详情弹窗 -->
<el-dialog <el-dialog
v-model="state.detailDialog.visible" v-model="state.detailDialog.visible"
title="审批流配置详情" title="审批流配置详情"
width="800px" width="800px"
:destroy-on-close="true" :destroy-on-close="true"
> >
<el-descriptions :column="1" border> <el-descriptions :column="1" border>
<el-descriptions-item label="ID"> <el-descriptions-item label="ID">
@ -247,10 +248,10 @@
<div class="font-bold text-16px mb-10px">审批节点列表</div> <div class="font-bold text-16px mb-10px">审批节点列表</div>
<el-timeline> <el-timeline>
<el-timeline-item <el-timeline-item
v-for="(node, index) in state.detailDialog.info?.nodes || []" v-for="(node, index) in state.detailDialog.info?.nodes || []"
:key="index" :key="index"
:type="getNodeType(node.sign_type)" :type="getNodeType(node.sign_type)"
:color="getNodeColor(node.sign_type)" :color="getNodeColor(node.sign_type)"
> >
<div class="font-bold mb-5px">{{ node.node_name }}</div> <div class="font-bold mb-5px">{{ node.node_name }}</div>
<div class="text-gray-500"> <div class="text-gray-500">
@ -270,9 +271,16 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, onMounted } from 'vue' import {reactive, ref, onMounted} from 'vue'
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus' import {ElMessage, ElMessageBox, FormInstance} from 'element-plus'
import { getConfigList, getConfigInfo, addConfig, editConfig, deleteConfig, changeConfigStatus } from '@/app/api/school_approval/config' import {
getConfigList,
getConfigInfo,
addConfig,
editConfig,
deleteConfig,
changeConfigStatus
} from '@/app/api/school_approval/config'
// draggable使 // draggable使
// import draggable from 'vuedraggable' // import draggable from 'vuedraggable'
@ -320,17 +328,17 @@ const state = reactive({
}, },
// API // API
userOptions: [ userOptions: [
{ label: '用户1', value: '1' }, {label: '用户1', value: '1'},
{ label: '用户2', value: '2' }, {label: '用户2', value: '2'},
{ label: '用户3', value: '3' } {label: '用户3', value: '3'}
], ],
roleOptions: [ roleOptions: [
{ label: '角色1', value: '1' }, {label: '角色1', value: '1'},
{ label: '角色2', value: '2' } {label: '角色2', value: '2'}
], ],
departmentOptions: [ departmentOptions: [
{ label: '部门1', value: '1' }, {label: '部门1', value: '1'},
{ label: '部门2', value: '2' } {label: '部门2', value: '2'}
], ],
dialog: { dialog: {
visible: false, visible: false,
@ -346,10 +354,10 @@ const state = reactive({
}, },
rules: { rules: {
config_name: [ config_name: [
{ required: true, message: '请输入配置名称', trigger: 'blur' } {required: true, message: '请输入配置名称', trigger: 'blur'}
], ],
nodes: [ nodes: [
{ required: true, validator: validateNodes, trigger: 'change' } {required: true, validator: validateNodes, trigger: 'change'}
] ]
} }
}, },
@ -446,15 +454,15 @@ async function handleEdit(row: any) {
state.dialog.title = '编辑审批流配置' state.dialog.title = '编辑审批流配置'
state.dialog.loading = true state.dialog.loading = true
try { try {
const res = await getConfigInfo({ id: row.id }) const res = await getConfigInfo({id: row.id})
state.dialog.form = { ...res.data } state.dialog.form = {...res.data}
// key // key
if (state.dialog.form.nodes && Array.isArray(state.dialog.form.nodes)) { if (state.dialog.form.nodes && Array.isArray(state.dialog.form.nodes)) {
state.dialog.form.nodes = state.dialog.form.nodes.map((node: any, index: number) => { state.dialog.form.nodes = state.dialog.form.nodes.map((node: any, index: number) => {
// approver_idssplit // approver_idssplit
const approverIds = typeof node.approver_ids === 'string' const approverIds = typeof node.approver_ids === 'string'
? node.approver_ids.split(',') ? node.approver_ids.split(',')
: (Array.isArray(node.approver_ids) ? node.approver_ids : []); : (Array.isArray(node.approver_ids) ? node.approver_ids : []);
return { return {
...node, ...node,
@ -477,7 +485,7 @@ async function handleEdit(row: any) {
// //
async function handleDetail(row: any) { async function handleDetail(row: any) {
try { try {
const res = await getConfigInfo({ id: row.id }) const res = await getConfigInfo({id: row.id})
state.detailDialog.info = res.data state.detailDialog.info = res.data
state.detailDialog.visible = true state.detailDialog.visible = true
} catch (error) { } catch (error) {
@ -492,26 +500,27 @@ function handleDelete(row: any) {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}) })
.then(async () => { .then(async () => {
try { try {
await deleteConfig({ id: row.id }) await deleteConfig({id: row.id})
ElMessage.success('删除成功') ElMessage.success('删除成功')
getList() getList()
} catch (error) { } catch (error) {
console.error(error) console.error(error)
} }
}) })
.catch(() => {}) .catch(() => {
})
} }
// //
async function handleStatusChange(row: any) { async function handleStatusChange(row: any) {
try { try {
await changeConfigStatus({ id: row.id, status: row.status }) await changeConfigStatus({id: row.id, status: row.status})
ElMessage.success( ElMessage.success(
row.status row.status
? '启用成功' ? '启用成功'
: '禁用成功' : '禁用成功'
) )
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -545,17 +554,17 @@ async function handleSubmit() {
state.dialog.loading = true state.dialog.loading = true
try { try {
// //
const formData = { ...state.dialog.form } const formData = {...state.dialog.form}
// //
formData.nodes = formData.nodes.map((node: any, index: number) => { formData.nodes = formData.nodes.map((node: any, index: number) => {
// keyAPI // keyAPI
const { key, ...nodeWithoutKey } = node; const {key, ...nodeWithoutKey} = node;
// approver_idsjoin // approver_idsjoin
const approverIds = Array.isArray(node.approver_ids) const approverIds = Array.isArray(node.approver_ids)
? node.approver_ids.join(',') ? node.approver_ids.join(',')
: (typeof node.approver_ids === 'string' ? node.approver_ids : ''); : (typeof node.approver_ids === 'string' ? node.approver_ids : '');
return { return {
...nodeWithoutKey, ...nodeWithoutKey,
@ -613,9 +622,15 @@ onMounted(() => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.node-list {
margin-left: 10px;
}
.node-item { .node-item {
background-color: #f9f9f9; background-color: #f9f9f9;
transition: all 0.3s; transition: all 0.3s;
padding: 10px;
margin-top: 10px;
&:hover { &:hover {
background-color: #f2f2f2; background-color: #f2f2f2;

178
admin/src/app/views/school_approval/process/index.vue

@ -5,14 +5,14 @@
<div> <div>
<el-button type="primary" @click="handleCreate"> <el-button type="primary" @click="handleCreate">
<icon name="add" class="mr-5px" /> <icon name="add" class="mr-5px" />
{{ $t('approval.process.create') }} {{ '创建' }}
</el-button> </el-button>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<el-input <el-input
v-model="state.searchParams.process_name" v-model="state.searchParams.process_name"
class="w-200px mr-15px" class="w-200 mr-15"
:placeholder="$t('approval.process.searchPlaceholder')" :placeholder="'搜索'"
clearable clearable
@keyup.enter="handleSearch" @keyup.enter="handleSearch"
@clear="handleSearch" @clear="handleSearch"
@ -20,29 +20,29 @@
<el-select <el-select
v-model="state.searchParams.approval_status" v-model="state.searchParams.approval_status"
class="w-150px mr-15px" class="w-150px mr-15px"
:placeholder="$t('approval.process.statusPlaceholder')" :placeholder="'状态'"
clearable clearable
@change="handleSearch" @change="handleSearch"
> >
<el-option :label="$t('approval.process.pending')" value="pending" /> <el-option :label="'待审批'" value="pending" />
<el-option :label="$t('approval.process.approved')" value="approved" /> <el-option :label="'已审批'" value="approved" />
<el-option :label="$t('approval.process.rejected')" value="rejected" /> <el-option :label="'已拒绝'" value="rejected" />
</el-select> </el-select>
<el-button type="primary" @click="handleSearch"> <el-button type="primary" @click="handleSearch">
<icon name="search" class="mr-5px" /> <icon name="search" class="mr-5px" />
{{ $t('common.search') }} {{ '搜索' }}
</el-button> </el-button>
<el-button @click="handleReset"> <el-button @click="handleReset">
<icon name="refresh-right" class="mr-5px" /> <icon name="refresh-right" class="mr-5px" />
{{ $t('common.reset') }} {{ '重置' }}
</el-button> </el-button>
</div> </div>
</div> </div>
<el-tabs v-model="state.activeTab" class="mt-15px" @tab-click="handleTabChange"> <el-tabs v-model="state.activeTab" class="mt-15px" @tab-click="handleTabChange">
<el-tab-pane :label="$t('approval.process.all')" name="all" /> <el-tab-pane :label="'全部'" name="all" />
<el-tab-pane :label="$t('approval.process.myCreate')" name="myCreate" /> <el-tab-pane :label="'我创建的'" name="myCreate" />
<el-tab-pane :label="$t('approval.process.myApproval')" name="myApproval" /> <el-tab-pane :label="'我审批的'" name="myApproval" />
</el-tabs> </el-tabs>
<el-table <el-table
@ -50,22 +50,22 @@
:data="state.processList" :data="state.processList"
:header-cell-style="{ background: '#fafafa', color: '#606266' }" :header-cell-style="{ background: '#fafafa', color: '#606266' }"
> >
<el-table-column :label="$t('approval.process.id')" prop="id" width="80" /> <el-table-column :label="'ID'" prop="id" width="80" />
<el-table-column :label="$t('approval.process.processName')" prop="process_name" min-width="180" /> <el-table-column :label="'流程名称'" prop="process_name" min-width="180" />
<el-table-column :label="$t('approval.process.applicantId')" prop="applicant_id" width="100" /> <el-table-column :label="'申请人ID'" prop="applicant_id" width="100" />
<el-table-column :label="$t('approval.process.applicationTime')" prop="application_time" width="180" /> <el-table-column :label="'申请时间'" prop="application_time" width="180" />
<el-table-column :label="$t('approval.process.currentApproverId')" prop="current_approver_id" width="100" /> <el-table-column :label="'当前审批人ID'" prop="current_approver_id" width="100" />
<el-table-column :label="$t('approval.process.approvalStatus')" prop="approval_status" width="100"> <el-table-column :label="'审批状态'" prop="approval_status" width="100">
<template #default="{ row }"> <template #default="{ row }">
<el-tag :type="getStatusType(row.approval_status)"> <el-tag :type="getStatusType(row.approval_status)">
{{ getStatusText(row.approval_status) }} {{ getStatusText(row.approval_status) }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('common.action')" width="180" fixed="right"> <el-table-column :label="'操作'" width="180" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link @click="handleDetail(row)"> <el-button type="primary" link @click="handleDetail(row)">
{{ $t('common.detail') }} {{ '详情' }}
</el-button> </el-button>
<el-button <el-button
v-if="row.approval_status === 'pending' && row.applicant_id === state.userInfo.uid" v-if="row.approval_status === 'pending' && row.applicant_id === state.userInfo.uid"
@ -73,7 +73,7 @@
link link
@click="handleCancel(row)" @click="handleCancel(row)"
> >
{{ $t('approval.process.cancel') }} {{ '取消' }}
</el-button> </el-button>
<el-button <el-button
v-if="row.approval_status === 'pending' && row.current_approver_id === state.userInfo.uid" v-if="row.approval_status === 'pending' && row.current_approver_id === state.userInfo.uid"
@ -81,7 +81,7 @@
link link
@click="handleApprove(row)" @click="handleApprove(row)"
> >
{{ $t('approval.process.approve') }} {{ '审批' }}
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
@ -103,7 +103,7 @@
<!-- 创建审批弹窗 --> <!-- 创建审批弹窗 -->
<el-dialog <el-dialog
v-model="state.createDialog.visible" v-model="state.createDialog.visible"
:title="$t('approval.process.create')" :title="'创建审批'"
width="600px" width="600px"
:close-on-click-modal="false" :close-on-click-modal="false"
:destroy-on-close="true" :destroy-on-close="true"
@ -114,17 +114,17 @@
:rules="state.createDialog.rules" :rules="state.createDialog.rules"
label-width="120px" label-width="120px"
> >
<el-form-item :label="$t('approval.process.processName')" prop="process_name"> <el-form-item :label="'流程名称'" prop="process_name">
<el-input <el-input
v-model="state.createDialog.form.process_name" v-model="state.createDialog.form.process_name"
:placeholder="$t('approval.process.processNamePlaceholder')" :placeholder="'请输入流程名称'"
/> />
</el-form-item> </el-form-item>
<el-form-item :label="$t('approval.process.configId')" prop="config_id"> <el-form-item :label="'审批流配置'" prop="config_id">
<el-select <el-select
v-model="state.createDialog.form.config_id" v-model="state.createDialog.form.config_id"
class="w-full" class="w-full"
:placeholder="$t('approval.process.configIdPlaceholder')" :placeholder="'请选择审批流配置'"
> >
<el-option <el-option
v-for="item in state.configOptions" v-for="item in state.configOptions"
@ -134,19 +134,19 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="$t('approval.process.remarks')" prop="remarks"> <el-form-item :label="'备注'" prop="remarks">
<el-input <el-input
v-model="state.createDialog.form.remarks" v-model="state.createDialog.form.remarks"
type="textarea" type="textarea"
:rows="3" :rows="3"
:placeholder="$t('approval.process.remarksPlaceholder')" :placeholder="'请输入备注'"
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="state.createDialog.visible = false">{{ $t('common.cancel') }}</el-button> <el-button @click="state.createDialog.visible = false">{{ '取消' }}</el-button>
<el-button type="primary" :loading="state.createDialog.loading" @click="handleCreateSubmit"> <el-button type="primary" :loading="state.createDialog.loading" @click="handleCreateSubmit">
{{ $t('common.confirm') }} {{ '确认' }}
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
@ -154,7 +154,7 @@
<!-- 审批弹窗 --> <!-- 审批弹窗 -->
<el-dialog <el-dialog
v-model="state.approveDialog.visible" v-model="state.approveDialog.visible"
:title="$t('approval.process.approve')" :title="'审批'"
width="500px" width="500px"
:close-on-click-modal="false" :close-on-click-modal="false"
:destroy-on-close="true" :destroy-on-close="true"
@ -165,25 +165,25 @@
:rules="state.approveDialog.rules" :rules="state.approveDialog.rules"
label-width="80px" label-width="80px"
> >
<el-form-item :label="$t('approval.process.status')" prop="status"> <el-form-item :label="'审批状态'" prop="status">
<el-radio-group v-model="state.approveDialog.form.status"> <el-radio-group v-model="state.approveDialog.form.status">
<el-radio label="approved">{{ $t('approval.process.approved') }}</el-radio> <el-radio label="approved">{{ '已审批' }}</el-radio>
<el-radio label="rejected">{{ $t('approval.process.rejected') }}</el-radio> <el-radio label="rejected">{{ '已拒绝' }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item :label="$t('approval.process.remarks')" prop="remarks"> <el-form-item :label="'备注'" prop="remarks">
<el-input <el-input
v-model="state.approveDialog.form.remarks" v-model="state.approveDialog.form.remarks"
type="textarea" type="textarea"
:rows="3" :rows="3"
:placeholder="$t('approval.process.remarksPlaceholder')" :placeholder="'请输入备注'"
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="state.approveDialog.visible = false">{{ $t('common.cancel') }}</el-button> <el-button @click="state.approveDialog.visible = false">{{ '取消' }}</el-button>
<el-button type="primary" :loading="state.approveDialog.loading" @click="handleApproveSubmit"> <el-button type="primary" :loading="state.approveDialog.loading" @click="handleApproveSubmit">
{{ $t('common.confirm') }} {{ '确认' }}
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
@ -191,41 +191,41 @@
<!-- 详情弹窗 --> <!-- 详情弹窗 -->
<el-dialog <el-dialog
v-model="state.detailDialog.visible" v-model="state.detailDialog.visible"
:title="$t('approval.process.detail')" :title="'审批详情'"
width="800px" width="800px"
:destroy-on-close="true" :destroy-on-close="true"
> >
<el-descriptions :column="1" border> <el-descriptions :column="1" border>
<el-descriptions-item :label="$t('approval.process.id')"> <el-descriptions-item :label="'ID'">
{{ state.detailDialog.info.id }} {{ state.detailDialog.info.id }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.processName')"> <el-descriptions-item :label="'流程名称'">
{{ state.detailDialog.info.process_name }} {{ state.detailDialog.info.process_name }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.applicantId')"> <el-descriptions-item :label="'申请人ID'">
{{ state.detailDialog.info.applicant_id }} {{ state.detailDialog.info.applicant_id }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.applicationTime')"> <el-descriptions-item :label="'申请时间'">
{{ state.detailDialog.info.application_time }} {{ state.detailDialog.info.application_time }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.currentApproverId')"> <el-descriptions-item :label="'当前审批人ID'">
{{ state.detailDialog.info.current_approver_id }} {{ state.detailDialog.info.current_approver_id }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.approvalStatus')"> <el-descriptions-item :label="'审批状态'">
<el-tag :type="getStatusType(state.detailDialog.info.approval_status)"> <el-tag :type="getStatusType(state.detailDialog.info.approval_status)">
{{ getStatusText(state.detailDialog.info.approval_status) }} {{ getStatusText(state.detailDialog.info.approval_status) }}
</el-tag> </el-tag>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.approvalTime')"> <el-descriptions-item :label="'审批时间'">
{{ state.detailDialog.info.approval_time || '-' }} {{ state.detailDialog.info.approval_time || '-' }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :label="$t('approval.process.remarks')"> <el-descriptions-item :label="'备注'">
{{ state.detailDialog.info.remarks || '-' }} {{ state.detailDialog.info.remarks || '-' }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<div class="mt-20px"> <div class="mt-20px">
<div class="font-bold text-16px mb-10px">{{ $t('approval.process.participants') }}</div> <div class="font-bold text-16px mb-10px">{{ '参与者' }}</div>
<el-timeline> <el-timeline>
<el-timeline-item <el-timeline-item
v-for="(node, index) in state.detailDialog.info.participants" v-for="(node, index) in state.detailDialog.info.participants"
@ -234,22 +234,22 @@
:color="getNodeColor(node.status)" :color="getNodeColor(node.status)"
> >
<div class="font-bold mb-5px"> <div class="font-bold mb-5px">
{{ $t('approval.process.participantLabel', { index: index + 1 }) }} {{ '参与者' + (index + 1) }}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
{{ $t('approval.process.participantId') }}{{ node.participant_id }} {{ '参与者ID' }}{{ node.participant_id }}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
{{ $t('approval.process.sequence') }}{{ node.sequence }} {{ '顺序' }}{{ node.sequence }}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
{{ $t('approval.process.status') }}{{ getStatusText(node.status) }} {{ '状态' }}{{ getStatusText(node.status) }}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
{{ $t('approval.process.signType') }}{{ node.sign_type === 'or_sign' ? $t('approval.process.orSign') : $t('approval.process.andSign') }} {{ '签名类型' }}{{ node.sign_type === 'or_sign' ? '或签名' : '和签名' }}
</div> </div>
<div class="text-gray-500"> <div class="text-gray-500">
{{ $t('approval.process.remarks') }}{{ node.remarks || '-' }} {{ '备注' }}{{ node.remarks || '-' }}
</div> </div>
</el-timeline-item> </el-timeline-item>
</el-timeline> </el-timeline>
@ -263,23 +263,39 @@ import { reactive, ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox, FormInstance } from 'element-plus' import { ElMessage, ElMessageBox, FormInstance } from 'element-plus'
import { getProcessList, getProcessInfo, createProcess, approveProcess, cancelProcess } from '@/app/api/school_approval/process' import { getProcessList, getProcessInfo, createProcess, approveProcess, cancelProcess } from '@/app/api/school_approval/process'
import { getConfigList } from '@/app/api/school_approval/config' import { getConfigList } from '@/app/api/school_approval/config'
import { useI18n } from 'vue-i18n' // import { useI18n } from 'vue-i18n'
// import { useUserInfo } from '@/stores/userInfo' // import { useUserInfo } from '@/stores/userInfo'
const { t } = useI18n() // const { t } = useI18n()
// const userInfo = useUserInfo() // const userInfo = useUserInfo()
// //
const createFormRef = ref<FormInstance>() const createFormRef = ref<FormInstance>()
const approveFormRef = 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({ const state = reactive({
loading: false, loading: false,
processList: [], processList: [],
total: 0, total: 0,
activeTab: 'all', activeTab: 'all',
userInfo: null, userInfo: {
uid: 1 // IDstore
},
searchParams: { searchParams: {
page: 1, page: 1,
limit: 10, limit: 10,
@ -288,7 +304,7 @@ const state = reactive({
applicant_id: 0, applicant_id: 0,
approver_id: 0 approver_id: 0
}, },
configOptions: [], // configOptions: [] as ConfigOption[], //
createDialog: { createDialog: {
visible: false, visible: false,
loading: false, loading: false,
@ -299,10 +315,10 @@ const state = reactive({
}, },
rules: { rules: {
process_name: [ process_name: [
{ required: true, message: t('approval.process.processNameRequired'), trigger: 'blur' } { required: true, message: '请输入流程名称', trigger: 'blur' }
], ],
config_id: [ config_id: [
{ required: true, message: t('approval.process.configIdRequired'), trigger: 'change' } { required: true, message: '请选择审批流配置', trigger: 'change' }
] ]
} }
}, },
@ -316,13 +332,23 @@ const state = reactive({
}, },
rules: { rules: {
status: [ status: [
{ required: true, message: t('approval.process.statusRequired'), trigger: 'change' } { required: true, message: '请选择审批状态', trigger: 'change' }
] ]
} }
}, },
detailDialog: { detailDialog: {
visible: false, visible: false,
info: {} info: {
id: 0,
process_name: '',
applicant_id: 0,
application_time: '',
current_approver_id: 0,
approval_status: '',
approval_time: '',
remarks: '',
participants: [] as Participant[] //
}
} }
}) })
@ -394,11 +420,11 @@ function handleTabChange() {
async function getConfigOptions() { async function getConfigOptions() {
try { try {
const res = await getConfigList({ status: 1 }) const res = await getConfigList({ status: 1 })
state.configOptions = res.data.list.map((item: any) => { state.configOptions = (res.data.list || []).map((item: any) => {
return { return {
label: item.config_name, label: item.config_name || '',
value: item.id value: item.id || 0
} } as ConfigOption
}) })
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -425,7 +451,7 @@ async function handleCreateSubmit() {
state.createDialog.loading = true state.createDialog.loading = true
try { try {
await createProcess(state.createDialog.form) await createProcess(state.createDialog.form)
ElMessage.success(t('approval.process.createSuccess')) ElMessage.success('创建成功')
state.createDialog.visible = false state.createDialog.visible = false
getList() getList()
} catch (error) { } catch (error) {
@ -460,7 +486,7 @@ async function handleApproveSubmit() {
status: state.approveDialog.form.status, status: state.approveDialog.form.status,
remarks: state.approveDialog.form.remarks remarks: state.approveDialog.form.remarks
}) })
ElMessage.success(t('approval.process.approveSuccess')) ElMessage.success('审批成功')
state.approveDialog.visible = false state.approveDialog.visible = false
getList() getList()
} catch (error) { } catch (error) {
@ -473,15 +499,15 @@ async function handleApproveSubmit() {
// //
function handleCancel(row: any) { function handleCancel(row: any) {
ElMessageBox.confirm(t('approval.process.confirmCancel'), t('common.warning'), { ElMessageBox.confirm('确认取消审批?', '警告', {
confirmButtonText: t('common.confirm'), confirmButtonText: '确认',
cancelButtonText: t('common.cancel'), cancelButtonText: '取消',
type: 'warning' type: 'warning'
}) })
.then(async () => { .then(async () => {
try { try {
await cancelProcess({ process_id: row.id }) await cancelProcess({ process_id: row.id })
ElMessage.success(t('approval.process.cancelSuccess')) ElMessage.success('取消成功')
getList() getList()
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -514,9 +540,9 @@ function getStatusType(status: string) {
// //
function getStatusText(status: string) { function getStatusText(status: string) {
const map: Record<string, string> = { const map: Record<string, string> = {
pending: t('approval.process.pending'), pending: '待审批',
approved: t('approval.process.approved'), approved: '已审批',
rejected: t('approval.process.rejected') rejected: '已拒绝'
} }
return map[status] || status return map[status] || status
} }

37
admin/src/utils/request.ts

@ -8,7 +8,6 @@ import type {
import { getToken, isUrl } from './common' import { getToken, isUrl } from './common'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import type { MessageParams } from 'element-plus' import type { MessageParams } from 'element-plus'
import { t } from '@/lang'
import useUserStore from '@/stores/modules/user' import useUserStore from '@/stores/modules/user'
import storage from '@/utils/storage' import storage from '@/utils/storage'
@ -172,13 +171,13 @@ class Request {
const errStatus = err.response.status const errStatus = err.response.status
switch (errStatus) { switch (errStatus) {
case 400: case 400:
errMessage = t('axios.400') errMessage = '请求错误'
break break
case 401: case 401:
errMessage = t('axios.401') errMessage = '未授权,请登录'
break break
case 403: case 403:
errMessage = t('axios.403') errMessage = '拒绝访问'
break break
case 404: case 404:
let baseURL = ''; let baseURL = '';
@ -191,38 +190,38 @@ class Request {
} catch (e) { } catch (e) {
baseURL = location.origin; baseURL = location.origin;
} }
errMessage = baseURL + t('axios.baseUrlError') errMessage = baseURL + '请求地址出错'
break break
case 405: case 405:
errMessage = t('axios.405') errMessage = '请求方法未允许'
break break
case 408: case 408:
errMessage = t('axios.408') errMessage = '请求超时'
break break
case 409: case 409:
errMessage = t('axios.409') errMessage = '资源冲突'
break break
case 500: case 500:
errMessage = t('axios.500') errMessage = '服务器内部错误'
break break
case 501: case 501:
errMessage = t('axios.501') errMessage = '服务未实现'
break break
case 502: case 502:
errMessage = t('axios.502') errMessage = '网关错误'
break break
case 503: case 503:
errMessage = t('axios.503') errMessage = '服务不可用'
break break
case 504: case 504:
errMessage = t('axios.504') errMessage = '网关超时'
break break
case 505: case 505:
errMessage = t('axios.505') errMessage = 'HTTP版本不受支持'
break break
} }
} }
err.message.includes('timeout') && (errMessage = t('axios.timeout')) err.message.includes('timeout') && (errMessage = '请求超时')
if (err.code == 'ERR_NETWORK') { if (err.code == 'ERR_NETWORK') {
let baseURL = ''; let baseURL = '';
try { try {
@ -234,7 +233,7 @@ class Request {
} catch (e) { } catch (e) {
baseURL = location.origin; baseURL = location.origin;
} }
errMessage = baseURL + t('axios.baseUrlError') errMessage = baseURL + '请求地址出错'
} }
errMessage && errMessage &&
this.showElMessage({ this.showElMessage({
@ -256,7 +255,11 @@ class Request {
private messageCache = new Map() private messageCache = new Map()
private showElMessage(options: MessageParams) { private showElMessage(options: MessageParams) {
const cacheKey = options.message // 处理类型问题,安全地获取消息
const message = typeof options === 'string' ? options : (options as any).message;
if (!message) return;
const cacheKey = message;
const cachedMessage = this.messageCache.get(cacheKey) const cachedMessage = this.messageCache.get(cacheKey)
if (!cachedMessage || Date.now() - cachedMessage.timestamp > 5000) { if (!cachedMessage || Date.now() - cachedMessage.timestamp > 5000) {

10
niucloud/app/adminapi/controller/school_approval/Process.php

@ -58,7 +58,7 @@ class Process extends BaseAdminController
$where[] = ['approval_status', '=', 'pending']; $where[] = ['approval_status', '=', 'pending'];
} }
$data = $this->service->getList($where, $page, $limit); $data = $this->service->getList($where, (int)$page, (int)$limit);
return success($data); return success($data);
} }
@ -73,7 +73,7 @@ class Process extends BaseAdminController
return fail('参数错误'); return fail('参数错误');
} }
$info = $this->service->getInfo($id); $info = $this->service->getInfo((int)$id);
if (empty($info)) { if (empty($info)) {
return fail('审批流程不存在'); return fail('审批流程不存在');
} }
@ -99,7 +99,7 @@ class Process extends BaseAdminController
} }
// 设置申请人ID // 设置申请人ID
$data['applicant_id'] = $this->user_info['uid']; $data['applicant_id'] = $this->request->uid();
try { try {
$process_id = $this->service->create($data, $config_id); $process_id = $this->service->create($data, $config_id);
@ -127,7 +127,7 @@ class Process extends BaseAdminController
} }
try { try {
$result = $this->service->approve($process_id, $this->user_info['uid'], $status, $remarks); $result = $this->service->approve((int)$process_id, $this->request->uid(), $status, $remarks);
return success($result); return success($result);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail($e->getMessage()); return fail($e->getMessage());
@ -146,7 +146,7 @@ class Process extends BaseAdminController
} }
try { try {
$result = $this->service->cancel($process_id, $this->user_info['uid']); $result = $this->service->cancel($process_id, $this->request->uid());
return success($result); return success($result);
} catch (\Exception $e) { } catch (\Exception $e) {
return fail($e->getMessage()); return fail($e->getMessage());

2
niucloud/app/service/school_approval/SchoolApprovalProcessService.php

@ -81,7 +81,7 @@ class SchoolApprovalProcessService
$process = [ $process = [
'process_name' => $data['process_name'], 'process_name' => $data['process_name'],
'applicant_id' => $data['applicant_id'], 'applicant_id' => $data['applicant_id'],
'application_time' => time(), 'application_time' => date("Y-m-d H:i:s"),
'current_approver_id' => 0, // 初始时为0,后面会更新 'current_approver_id' => 0, // 初始时为0,后面会更新
'approval_status' => SchoolApprovalProcess::STATUS_PENDING, 'approval_status' => SchoolApprovalProcess::STATUS_PENDING,
'remarks' => $data['remarks'] ?? '' 'remarks' => $data['remarks'] ?? ''

Loading…
Cancel
Save