22 changed files with 6077 additions and 3339 deletions
@ -0,0 +1,103 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
/** |
|||
* 获取审批流配置列表 |
|||
* @param params |
|||
*/ |
|||
export function getConfigList(params?: Record<string, any>) { |
|||
try { |
|||
return request.get('/school_approval/config/lists', { params }) |
|||
} catch (error) { |
|||
console.error('Error in getConfigList:', error) |
|||
return Promise.reject(error) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取审批流配置详情 |
|||
* @param params |
|||
*/ |
|||
export function getConfigInfo(params: { id: number }) { |
|||
if (!params || typeof params.id !== 'number') { |
|||
console.error('Invalid params for getConfigInfo', params) |
|||
return Promise.reject(new Error('Invalid params')) |
|||
} |
|||
try { |
|||
return request.get('/school_approval/config/info', { params }) |
|||
} catch (error) { |
|||
console.error('Error in getConfigInfo:', error) |
|||
return Promise.reject(error) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 添加审批流配置 |
|||
* @param params |
|||
*/ |
|||
export function addConfig(params: any) { |
|||
if (!params) { |
|||
console.error('Invalid params for addConfig', params) |
|||
return Promise.reject(new Error('Invalid params')) |
|||
} |
|||
try { |
|||
// 清理params中的不必要属性,防止序列化问题
|
|||
const sanitizedParams = JSON.parse(JSON.stringify(params)) |
|||
return request.post('/school_approval/config/add', sanitizedParams) |
|||
} catch (error) { |
|||
console.error('Error in addConfig:', error) |
|||
return Promise.reject(error) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 编辑审批流配置 |
|||
* @param params |
|||
*/ |
|||
export function editConfig(params: any) { |
|||
if (!params || !params.id) { |
|||
console.error('Invalid params for editConfig', params) |
|||
return Promise.reject(new Error('Invalid params')) |
|||
} |
|||
try { |
|||
// 清理params中的不必要属性,防止序列化问题
|
|||
const sanitizedParams = JSON.parse(JSON.stringify(params)) |
|||
return request.post('/school_approval/config/edit', sanitizedParams) |
|||
} catch (error) { |
|||
console.error('Error in editConfig:', error) |
|||
return Promise.reject(error) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 删除审批流配置 |
|||
* @param params |
|||
*/ |
|||
export function deleteConfig(params: { id: number }) { |
|||
if (!params || typeof params.id !== 'number') { |
|||
console.error('Invalid params for deleteConfig', params) |
|||
return Promise.reject(new Error('Invalid params')) |
|||
} |
|||
try { |
|||
return request.post('/school_approval/config/delete', params) |
|||
} catch (error) { |
|||
console.error('Error in deleteConfig:', error) |
|||
return Promise.reject(error) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 修改状态 |
|||
* @param params |
|||
*/ |
|||
export function changeConfigStatus(params: { id: number; status: number }) { |
|||
if (!params || typeof params.id !== 'number' || typeof params.status !== 'number') { |
|||
console.error('Invalid params for changeConfigStatus', params) |
|||
return Promise.reject(new Error('Invalid params')) |
|||
} |
|||
try { |
|||
return request.post('/school_approval/config/changeStatus', params) |
|||
} catch (error) { |
|||
console.error('Error in changeConfigStatus:', error) |
|||
return Promise.reject(error) |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
import request from '@/utils/request' |
|||
|
|||
/** |
|||
* 获取审批流程列表 |
|||
* @param params |
|||
*/ |
|||
export function getProcessList(params?: Record<string, any>) { |
|||
return request.get({ url: '/school_approval/process/lists', params }) |
|||
} |
|||
|
|||
/** |
|||
* 获取审批流程详情 |
|||
* @param params |
|||
*/ |
|||
export function getProcessInfo(params: { id: number }) { |
|||
return request.get({ url: '/school_approval/process/info', params }) |
|||
} |
|||
|
|||
/** |
|||
* 创建审批流程 |
|||
* @param params |
|||
*/ |
|||
export function createProcess(params: any) { |
|||
return request.post({ url: '/school_approval/process/create', data: params }) |
|||
} |
|||
|
|||
/** |
|||
* 审批 |
|||
* @param params |
|||
*/ |
|||
export function approveProcess(params: { process_id: number; status: string; remarks?: string }) { |
|||
return request.post({ url: '/school_approval/process/approve', data: params }) |
|||
} |
|||
|
|||
/** |
|||
* 撤销审批流程 |
|||
* @param params |
|||
*/ |
|||
export function cancelProcess(params: { process_id: number }) { |
|||
return request.post({ url: '/school_approval/process/cancel', data: params }) |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
export default { |
|||
menu: { |
|||
title: '审批管理', |
|||
config: '审批流配置', |
|||
process: '审批流程' |
|||
}, |
|||
config: { |
|||
id: 'ID', |
|||
configName: '配置名称', |
|||
configNamePlaceholder: '请输入配置名称', |
|||
configNameRequired: '请输入配置名称', |
|||
description: '配置描述', |
|||
descriptionPlaceholder: '请输入配置描述', |
|||
status: '状态', |
|||
statusPlaceholder: '请选择状态', |
|||
enabled: '启用', |
|||
disabled: '禁用', |
|||
createdAt: '创建时间', |
|||
nodes: '审批节点', |
|||
addNode: '添加节点', |
|||
noNodes: '暂无审批节点,请点击添加', |
|||
nodeLabel: '节点 {index}', |
|||
nodeName: '节点名称', |
|||
nodeNamePlaceholder: '请输入节点名称', |
|||
nodeNameRequired: '请输入节点名称', |
|||
approverType: '审批人类型', |
|||
approverTypePlaceholder: '请选择审批人类型', |
|||
approverTypeRequired: '请选择审批人类型', |
|||
approverIds: '审批人', |
|||
approverIdsPlaceholder: '请选择审批人', |
|||
approverIdsRequired: '请选择审批人', |
|||
signType: '审批类型', |
|||
orSign: '或签(一人通过即可)', |
|||
andSign: '会签(需全部通过)', |
|||
searchPlaceholder: '请输入配置名称', |
|||
nodesRequired: '请至少添加一个审批节点', |
|||
add: '添加审批流配置', |
|||
edit: '编辑审批流配置', |
|||
detail: '审批流配置详情', |
|||
nodeList: '审批节点列表', |
|||
enableSuccess: '启用成功', |
|||
disableSuccess: '禁用成功' |
|||
}, |
|||
process: { |
|||
id: 'ID', |
|||
processName: '流程名称', |
|||
processNamePlaceholder: '请输入流程名称', |
|||
processNameRequired: '请输入流程名称', |
|||
applicantId: '申请人', |
|||
applicationTime: '申请时间', |
|||
currentApproverId: '当前审批人', |
|||
approvalStatus: '审批状态', |
|||
approvalTime: '审批时间', |
|||
remarks: '备注', |
|||
remarksPlaceholder: '请输入备注', |
|||
searchPlaceholder: '请输入流程名称', |
|||
statusPlaceholder: '请选择审批状态', |
|||
pending: '待审批', |
|||
approved: '已批准', |
|||
rejected: '已拒绝', |
|||
all: '全部', |
|||
myCreate: '我的申请', |
|||
myApproval: '待我审批', |
|||
create: '发起审批', |
|||
createSuccess: '发起审批成功', |
|||
configId: '审批配置', |
|||
configIdPlaceholder: '请选择审批配置', |
|||
configIdRequired: '请选择审批配置', |
|||
approve: '审批', |
|||
approveSuccess: '审批成功', |
|||
status: '审批结果', |
|||
statusRequired: '请选择审批结果', |
|||
cancel: '撤销', |
|||
cancelSuccess: '撤销成功', |
|||
confirmCancel: '确定要撤销该审批流程吗?', |
|||
detail: '审批流程详情', |
|||
participants: '审批参与人', |
|||
participantLabel: '参与人 {index}', |
|||
participantId: '参与人ID', |
|||
sequence: '审批顺序', |
|||
orSign: '或签', |
|||
andSign: '会签' |
|||
} |
|||
} |
|||
@ -0,0 +1,638 @@ |
|||
<template> |
|||
<div class="approval-config-container"> |
|||
<el-card class="box-card !border-none" shadow="never"> |
|||
<div class="flex justify-between items-center"> |
|||
<div> |
|||
<el-button type="primary" @click="handleAdd"> |
|||
<icon name="add" class="mr-5px" /> |
|||
添加 |
|||
</el-button> |
|||
</div> |
|||
<div class="flex items-center"> |
|||
<el-input |
|||
v-model="state.searchParams.config_name" |
|||
class="w-200px mr-15px" |
|||
placeholder="请输入配置名称" |
|||
clearable |
|||
@keyup.enter="handleSearch" |
|||
@clear="handleSearch" |
|||
/> |
|||
<el-select |
|||
v-model="state.searchParams.status" |
|||
class="w-150px mr-15px" |
|||
placeholder="请选择状态" |
|||
clearable |
|||
@change="handleSearch" |
|||
> |
|||
<el-option label="启用" :value="1" /> |
|||
<el-option label="禁用" :value="0" /> |
|||
</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-table |
|||
v-loading="state.loading" |
|||
class="mt-15px" |
|||
:data="state.configList" |
|||
:header-cell-style="{ background: '#fafafa', color: '#606266' }" |
|||
> |
|||
<el-table-column label="ID" prop="id" width="80" /> |
|||
<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="status" width="100"> |
|||
<template #default="{ row }"> |
|||
<el-switch |
|||
v-model="row.status" |
|||
:active-value="1" |
|||
:inactive-value="0" |
|||
@change="handleStatusChange(row)" |
|||
/> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="创建时间" prop="created_at" width="180" /> |
|||
<el-table-column label="操作" width="180" fixed="right"> |
|||
<template #default="{ row }"> |
|||
<el-button type="primary" link @click="handleEdit(row)"> |
|||
编辑 |
|||
</el-button> |
|||
<el-button type="primary" link @click="handleDetail(row)"> |
|||
详情 |
|||
</el-button> |
|||
<el-button type="danger" link @click="handleDelete(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.dialog.visible" |
|||
:title="state.dialog.title" |
|||
width="800px" |
|||
:close-on-click-modal="false" |
|||
:destroy-on-close="true" |
|||
> |
|||
<el-form |
|||
ref="formRef" |
|||
:model="state.dialog.form" |
|||
:rules="state.dialog.rules" |
|||
label-width="120px" |
|||
> |
|||
<el-form-item label="配置名称" prop="config_name"> |
|||
<el-input v-model="state.dialog.form.config_name" placeholder="请输入配置名称" /> |
|||
</el-form-item> |
|||
<el-form-item label="配置描述" prop="description"> |
|||
<el-input |
|||
v-model="state.dialog.form.description" |
|||
type="textarea" |
|||
:rows="3" |
|||
placeholder="请输入配置描述" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="状态" prop="status"> |
|||
<el-switch |
|||
v-model="state.dialog.form.status" |
|||
:active-value="1" |
|||
:inactive-value="0" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item label="审批节点" prop="nodes"> |
|||
<div class="mb-10px"> |
|||
<el-button type="primary" @click="handleAddNode"> |
|||
<icon name="add" class="mr-5px" /> |
|||
添加节点 |
|||
</el-button> |
|||
</div> |
|||
<div v-if="state.dialog.form.nodes.length === 0" class="text-gray-400 text-center py-20px border border-dashed rounded"> |
|||
暂无审批节点,请点击添加 |
|||
</div> |
|||
<div v-else class="node-list"> |
|||
<div |
|||
v-for="(element, index) in state.dialog.form.nodes" |
|||
:key="index" |
|||
class="node-item p-15px mb-15px border border-gray-200 rounded" |
|||
> |
|||
<div class="flex justify-between items-center mb-10px"> |
|||
<div class="flex items-center"> |
|||
<icon name="rank" class="drag-handle mr-5px cursor-move" /> |
|||
<span class="font-bold">节点 {{ index + 1 }}</span> |
|||
</div> |
|||
<el-button type="danger" link @click="handleRemoveNode(index)"> |
|||
删除 |
|||
</el-button> |
|||
</div> |
|||
<div class="flex items-center mb-10px"> |
|||
<span class="w-100px">节点名称:</span> |
|||
<el-input |
|||
v-model="element.node_name" |
|||
placeholder="请输入节点名称" |
|||
/> |
|||
</div> |
|||
<div class="flex items-center mb-10px"> |
|||
<span class="w-100px">审批人类型:</span> |
|||
<el-select |
|||
v-model="element.approver_type" |
|||
class="flex-1" |
|||
placeholder="请选择审批人类型" |
|||
> |
|||
<el-option label="指定用户" value="user" /> |
|||
<el-option label="指定角色" value="role" /> |
|||
<el-option label="指定部门" value="department" /> |
|||
</el-select> |
|||
</div> |
|||
<div class="flex items-center mb-10px"> |
|||
<span class="w-100px">审批人:</span> |
|||
<el-select |
|||
v-model="element.approver_ids" |
|||
class="flex-1" |
|||
multiple |
|||
placeholder="请选择审批人" |
|||
> |
|||
<!-- 这里根据 approver_type 不同显示不同的选项 --> |
|||
<template v-if="element.approver_type === 'user'"> |
|||
<el-option |
|||
v-for="item in state.userOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</template> |
|||
<template v-else-if="element.approver_type === 'role'"> |
|||
<el-option |
|||
v-for="item in state.roleOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</template> |
|||
<template v-else-if="element.approver_type === 'department'"> |
|||
<el-option |
|||
v-for="item in state.departmentOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</template> |
|||
</el-select> |
|||
</div> |
|||
<div class="flex items-center"> |
|||
<span class="w-100px">审批类型:</span> |
|||
<el-radio-group v-model="element.sign_type"> |
|||
<el-radio label="or_sign">或签(一人通过即可)</el-radio> |
|||
<el-radio label="and_sign">会签(需全部通过)</el-radio> |
|||
</el-radio-group> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="state.dialog.visible = false">取消</el-button> |
|||
<el-button type="primary" :loading="state.dialog.loading" @click="handleSubmit"> |
|||
确定 |
|||
</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?.config_name || '-' }} |
|||
</el-descriptions-item> |
|||
<el-descriptions-item label="配置描述"> |
|||
{{ state.detailDialog.info?.description || '-' }} |
|||
</el-descriptions-item> |
|||
<el-descriptions-item label="状态"> |
|||
<el-tag :type="state.detailDialog.info?.status ? 'success' : 'danger'"> |
|||
{{ state.detailDialog.info?.status ? '启用' : '禁用' }} |
|||
</el-tag> |
|||
</el-descriptions-item> |
|||
<el-descriptions-item label="创建时间"> |
|||
{{ state.detailDialog.info?.created_at || '-' }} |
|||
</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?.nodes || []" |
|||
:key="index" |
|||
:type="getNodeType(node.sign_type)" |
|||
:color="getNodeColor(node.sign_type)" |
|||
> |
|||
<div class="font-bold mb-5px">{{ node.node_name }}</div> |
|||
<div class="text-gray-500"> |
|||
审批人类型:{{ getApproverTypeText(node.approver_type) }} |
|||
</div> |
|||
<div class="text-gray-500"> |
|||
审批人:{{ node.approver_ids }} |
|||
</div> |
|||
<div class="text-gray-500"> |
|||
审批类型:{{ node.sign_type === 'or_sign' ? '或签' : '会签' }} |
|||
</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 { getConfigList, getConfigInfo, addConfig, editConfig, deleteConfig, changeConfigStatus } from '@/app/api/school_approval/config' |
|||
|
|||
// 移除draggable导入,暂时不使用拖拽功能 |
|||
// import draggable from 'vuedraggable' |
|||
// 移除i18n导入 |
|||
// import { useI18n } from 'vue-i18n' |
|||
// const { t } = useI18n() |
|||
|
|||
// 表单引用 |
|||
const formRef = ref<FormInstance>() |
|||
|
|||
// 定义详情对话框信息的类型 |
|||
interface DetailInfo { |
|||
id?: number; |
|||
config_name?: string; |
|||
description?: string; |
|||
status?: number; |
|||
created_at?: string; |
|||
nodes?: Array<{ |
|||
node_name: string; |
|||
approver_type: string; |
|||
approver_ids: string; |
|||
sign_type: string; |
|||
}>; |
|||
} |
|||
|
|||
// 定义节点类型 |
|||
interface ApprovalNode { |
|||
key: string; |
|||
node_name: string; |
|||
approver_type: string; |
|||
approver_ids: string[]; |
|||
sign_type: string; |
|||
} |
|||
|
|||
// 状态 |
|||
const state = reactive({ |
|||
loading: false, |
|||
configList: [], |
|||
total: 0, |
|||
searchParams: { |
|||
page: 1, |
|||
limit: 10, |
|||
config_name: '', |
|||
status: '' |
|||
}, |
|||
// 模拟数据,实际应该从API获取 |
|||
userOptions: [ |
|||
{ label: '用户1', value: '1' }, |
|||
{ label: '用户2', value: '2' }, |
|||
{ label: '用户3', value: '3' } |
|||
], |
|||
roleOptions: [ |
|||
{ label: '角色1', value: '1' }, |
|||
{ label: '角色2', value: '2' } |
|||
], |
|||
departmentOptions: [ |
|||
{ label: '部门1', value: '1' }, |
|||
{ label: '部门2', value: '2' } |
|||
], |
|||
dialog: { |
|||
visible: false, |
|||
title: '', |
|||
loading: false, |
|||
type: 'add', // add or edit |
|||
form: { |
|||
id: 0, |
|||
config_name: '', |
|||
description: '', |
|||
status: 1, |
|||
nodes: [] as ApprovalNode[] |
|||
}, |
|||
rules: { |
|||
config_name: [ |
|||
{ required: true, message: '请输入配置名称', trigger: 'blur' } |
|||
], |
|||
nodes: [ |
|||
{ required: true, validator: validateNodes, trigger: 'change' } |
|||
] |
|||
} |
|||
}, |
|||
detailDialog: { |
|||
visible: false, |
|||
info: {} as DetailInfo |
|||
} |
|||
}) |
|||
|
|||
// 校验节点 |
|||
function validateNodes(rule: any, value: any, callback: any) { |
|||
if (!value || value.length === 0) { |
|||
callback(new Error('请至少添加一个审批节点')) |
|||
} else { |
|||
for (const node of value) { |
|||
if (!node.node_name) { |
|||
callback(new Error('请输入节点名称')) |
|||
return |
|||
} |
|||
if (!node.approver_type) { |
|||
callback(new Error('请选择审批人类型')) |
|||
return |
|||
} |
|||
if (!node.approver_ids || node.approver_ids.length === 0) { |
|||
callback(new Error('请选择审批人')) |
|||
return |
|||
} |
|||
} |
|||
callback() |
|||
} |
|||
} |
|||
|
|||
// 获取列表数据 |
|||
async function getList() { |
|||
state.loading = true |
|||
try { |
|||
const res = await getConfigList(state.searchParams) |
|||
state.configList = 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, |
|||
config_name: '', |
|||
status: '' |
|||
} |
|||
getList() |
|||
} |
|||
|
|||
// 页码变化 |
|||
function handleCurrentChange(page: number) { |
|||
state.searchParams.page = page |
|||
getList() |
|||
} |
|||
|
|||
// 每页条数变化 |
|||
function handleSizeChange(size: number) { |
|||
state.searchParams.limit = size |
|||
state.searchParams.page = 1 |
|||
getList() |
|||
} |
|||
|
|||
// 添加 |
|||
function handleAdd() { |
|||
state.dialog.type = 'add' |
|||
state.dialog.title = '添加审批流配置' |
|||
state.dialog.form = { |
|||
id: 0, |
|||
config_name: '', |
|||
description: '', |
|||
status: 1, |
|||
nodes: [] |
|||
} |
|||
state.dialog.visible = true |
|||
} |
|||
|
|||
// 编辑 |
|||
async function handleEdit(row: any) { |
|||
state.dialog.type = 'edit' |
|||
state.dialog.title = '编辑审批流配置' |
|||
state.dialog.loading = true |
|||
try { |
|||
const res = await getConfigInfo({ id: row.id }) |
|||
state.dialog.form = { ...res.data } |
|||
// 处理节点数据,确保每个节点都有唯一的key |
|||
if (state.dialog.form.nodes && Array.isArray(state.dialog.form.nodes)) { |
|||
state.dialog.form.nodes = state.dialog.form.nodes.map((node: any, index: number) => { |
|||
// 确保approver_ids是字符串才进行split操作 |
|||
const approverIds = typeof node.approver_ids === 'string' |
|||
? node.approver_ids.split(',') |
|||
: (Array.isArray(node.approver_ids) ? node.approver_ids : []); |
|||
|
|||
return { |
|||
...node, |
|||
key: `node_${index}_${Date.now()}`, |
|||
approver_ids: approverIds |
|||
} |
|||
}) |
|||
} else { |
|||
state.dialog.form.nodes = [] |
|||
} |
|||
state.dialog.visible = true |
|||
} catch (error) { |
|||
console.error(error) |
|||
ElMessage.error('获取数据失败') |
|||
} finally { |
|||
state.dialog.loading = false |
|||
} |
|||
} |
|||
|
|||
// 查看详情 |
|||
async function handleDetail(row: any) { |
|||
try { |
|||
const res = await getConfigInfo({ id: row.id }) |
|||
state.detailDialog.info = res.data |
|||
state.detailDialog.visible = true |
|||
} catch (error) { |
|||
console.error(error) |
|||
} |
|||
} |
|||
|
|||
// 删除 |
|||
function handleDelete(row: any) { |
|||
ElMessageBox.confirm('确定要删除此项吗?', '警告', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消', |
|||
type: 'warning' |
|||
}) |
|||
.then(async () => { |
|||
try { |
|||
await deleteConfig({ id: row.id }) |
|||
ElMessage.success('删除成功') |
|||
getList() |
|||
} catch (error) { |
|||
console.error(error) |
|||
} |
|||
}) |
|||
.catch(() => {}) |
|||
} |
|||
|
|||
// 修改状态 |
|||
async function handleStatusChange(row: any) { |
|||
try { |
|||
await changeConfigStatus({ id: row.id, status: row.status }) |
|||
ElMessage.success( |
|||
row.status |
|||
? '启用成功' |
|||
: '禁用成功' |
|||
) |
|||
} catch (error) { |
|||
console.error(error) |
|||
row.status = row.status ? 0 : 1 // 失败时恢复原状态 |
|||
} |
|||
} |
|||
|
|||
// 添加节点 |
|||
function handleAddNode() { |
|||
state.dialog.form.nodes.push({ |
|||
key: `node_${state.dialog.form.nodes.length}_${Date.now()}`, |
|||
node_name: '', |
|||
approver_type: 'user', |
|||
approver_ids: [], |
|||
sign_type: 'or_sign' |
|||
}) |
|||
} |
|||
|
|||
// 移除节点 |
|||
function handleRemoveNode(index: number) { |
|||
state.dialog.form.nodes.splice(index, 1) |
|||
} |
|||
|
|||
// 提交表单 |
|||
async function handleSubmit() { |
|||
if (!formRef.value) return |
|||
|
|||
await formRef.value.validate(async (valid) => { |
|||
if (!valid) return |
|||
|
|||
state.dialog.loading = true |
|||
try { |
|||
// 处理节点数据 |
|||
const formData = { ...state.dialog.form } |
|||
|
|||
// 确保所有节点数据都有有效结构 |
|||
formData.nodes = formData.nodes.map((node: any, index: number) => { |
|||
// 清理无效的key字段,防止API不接受 |
|||
const { key, ...nodeWithoutKey } = node; |
|||
|
|||
// 确保approver_ids是数组才进行join操作 |
|||
const approverIds = Array.isArray(node.approver_ids) |
|||
? node.approver_ids.join(',') |
|||
: (typeof node.approver_ids === 'string' ? node.approver_ids : ''); |
|||
|
|||
return { |
|||
...nodeWithoutKey, |
|||
node_name: node.node_name || `节点${index + 1}`, // 确保节点名称存在 |
|||
approver_ids: approverIds, |
|||
sign_type: node.sign_type || 'or_sign' // 确保审批类型存在 |
|||
} |
|||
}); |
|||
|
|||
// 打印提交的数据,帮助调试 |
|||
console.log('提交的表单数据:', JSON.stringify(formData)); |
|||
|
|||
if (state.dialog.type === 'add') { |
|||
await addConfig(formData) |
|||
ElMessage.success('添加成功') |
|||
} else { |
|||
await editConfig(formData) |
|||
ElMessage.success('编辑成功') |
|||
} |
|||
|
|||
state.dialog.visible = false |
|||
getList() |
|||
} catch (error) { |
|||
console.error('提交表单时出错:', error) |
|||
ElMessage.error('提交失败,请检查表单数据或网络连接') |
|||
} finally { |
|||
state.dialog.loading = false |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// 获取节点类型 |
|||
function getNodeType(signType: string) { |
|||
return signType === 'or_sign' ? 'primary' : 'success' |
|||
} |
|||
|
|||
// 获取节点颜色 |
|||
function getNodeColor(signType: string) { |
|||
return signType === 'or_sign' ? '#409EFF' : '#67C23A' |
|||
} |
|||
|
|||
// 获取审批人类型文本 |
|||
function getApproverTypeText(type: string) { |
|||
const map: Record<string, string> = { |
|||
user: '指定用户', |
|||
role: '指定角色', |
|||
department: '指定部门' |
|||
} |
|||
return map[type] || type |
|||
} |
|||
|
|||
onMounted(() => { |
|||
getList() |
|||
}) |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.node-item { |
|||
background-color: #f9f9f9; |
|||
transition: all 0.3s; |
|||
|
|||
&:hover { |
|||
background-color: #f2f2f2; |
|||
} |
|||
} |
|||
|
|||
.ghost { |
|||
opacity: 0.5; |
|||
background: #c8ebfb; |
|||
} |
|||
|
|||
.drag-handle { |
|||
cursor: move; |
|||
color: #909399; |
|||
|
|||
&:hover { |
|||
color: #409eff; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,552 @@ |
|||
<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" /> |
|||
{{ $t('approval.process.create') }} |
|||
</el-button> |
|||
</div> |
|||
<div class="flex items-center"> |
|||
<el-input |
|||
v-model="state.searchParams.process_name" |
|||
class="w-200px mr-15px" |
|||
:placeholder="$t('approval.process.searchPlaceholder')" |
|||
clearable |
|||
@keyup.enter="handleSearch" |
|||
@clear="handleSearch" |
|||
/> |
|||
<el-select |
|||
v-model="state.searchParams.approval_status" |
|||
class="w-150px mr-15px" |
|||
:placeholder="$t('approval.process.statusPlaceholder')" |
|||
clearable |
|||
@change="handleSearch" |
|||
> |
|||
<el-option :label="$t('approval.process.pending')" value="pending" /> |
|||
<el-option :label="$t('approval.process.approved')" value="approved" /> |
|||
<el-option :label="$t('approval.process.rejected')" value="rejected" /> |
|||
</el-select> |
|||
<el-button type="primary" @click="handleSearch"> |
|||
<icon name="search" class="mr-5px" /> |
|||
{{ $t('common.search') }} |
|||
</el-button> |
|||
<el-button @click="handleReset"> |
|||
<icon name="refresh-right" class="mr-5px" /> |
|||
{{ $t('common.reset') }} |
|||
</el-button> |
|||
</div> |
|||
</div> |
|||
|
|||
<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="$t('approval.process.myCreate')" name="myCreate" /> |
|||
<el-tab-pane :label="$t('approval.process.myApproval')" name="myApproval" /> |
|||
</el-tabs> |
|||
|
|||
<el-table |
|||
v-loading="state.loading" |
|||
:data="state.processList" |
|||
:header-cell-style="{ background: '#fafafa', color: '#606266' }" |
|||
> |
|||
<el-table-column :label="$t('approval.process.id')" prop="id" width="80" /> |
|||
<el-table-column :label="$t('approval.process.processName')" prop="process_name" min-width="180" /> |
|||
<el-table-column :label="$t('approval.process.applicantId')" prop="applicant_id" width="100" /> |
|||
<el-table-column :label="$t('approval.process.applicationTime')" prop="application_time" width="180" /> |
|||
<el-table-column :label="$t('approval.process.currentApproverId')" prop="current_approver_id" width="100" /> |
|||
<el-table-column :label="$t('approval.process.approvalStatus')" 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="$t('common.action')" width="180" fixed="right"> |
|||
<template #default="{ row }"> |
|||
<el-button type="primary" link @click="handleDetail(row)"> |
|||
{{ $t('common.detail') }} |
|||
</el-button> |
|||
<el-button |
|||
v-if="row.approval_status === 'pending' && row.applicant_id === state.userInfo.uid" |
|||
type="danger" |
|||
link |
|||
@click="handleCancel(row)" |
|||
> |
|||
{{ $t('approval.process.cancel') }} |
|||
</el-button> |
|||
<el-button |
|||
v-if="row.approval_status === 'pending' && row.current_approver_id === state.userInfo.uid" |
|||
type="success" |
|||
link |
|||
@click="handleApprove(row)" |
|||
> |
|||
{{ $t('approval.process.approve') }} |
|||
</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="$t('approval.process.create')" |
|||
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="$t('approval.process.processName')" prop="process_name"> |
|||
<el-input |
|||
v-model="state.createDialog.form.process_name" |
|||
:placeholder="$t('approval.process.processNamePlaceholder')" |
|||
/> |
|||
</el-form-item> |
|||
<el-form-item :label="$t('approval.process.configId')" prop="config_id"> |
|||
<el-select |
|||
v-model="state.createDialog.form.config_id" |
|||
class="w-full" |
|||
:placeholder="$t('approval.process.configIdPlaceholder')" |
|||
> |
|||
<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="$t('approval.process.remarks')" prop="remarks"> |
|||
<el-input |
|||
v-model="state.createDialog.form.remarks" |
|||
type="textarea" |
|||
:rows="3" |
|||
:placeholder="$t('approval.process.remarksPlaceholder')" |
|||
/> |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="state.createDialog.visible = false">{{ $t('common.cancel') }}</el-button> |
|||
<el-button type="primary" :loading="state.createDialog.loading" @click="handleCreateSubmit"> |
|||
{{ $t('common.confirm') }} |
|||
</el-button> |
|||
</template> |
|||
</el-dialog> |
|||
|
|||
<!-- 审批弹窗 --> |
|||
<el-dialog |
|||
v-model="state.approveDialog.visible" |
|||
:title="$t('approval.process.approve')" |
|||
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="$t('approval.process.status')" prop="status"> |
|||
<el-radio-group v-model="state.approveDialog.form.status"> |
|||
<el-radio label="approved">{{ $t('approval.process.approved') }}</el-radio> |
|||
<el-radio label="rejected">{{ $t('approval.process.rejected') }}</el-radio> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
<el-form-item :label="$t('approval.process.remarks')" prop="remarks"> |
|||
<el-input |
|||
v-model="state.approveDialog.form.remarks" |
|||
type="textarea" |
|||
:rows="3" |
|||
:placeholder="$t('approval.process.remarksPlaceholder')" |
|||
/> |
|||
</el-form-item> |
|||
</el-form> |
|||
<template #footer> |
|||
<el-button @click="state.approveDialog.visible = false">{{ $t('common.cancel') }}</el-button> |
|||
<el-button type="primary" :loading="state.approveDialog.loading" @click="handleApproveSubmit"> |
|||
{{ $t('common.confirm') }} |
|||
</el-button> |
|||
</template> |
|||
</el-dialog> |
|||
|
|||
<!-- 详情弹窗 --> |
|||
<el-dialog |
|||
v-model="state.detailDialog.visible" |
|||
:title="$t('approval.process.detail')" |
|||
width="800px" |
|||
:destroy-on-close="true" |
|||
> |
|||
<el-descriptions :column="1" border> |
|||
<el-descriptions-item :label="$t('approval.process.id')"> |
|||
{{ state.detailDialog.info.id }} |
|||
</el-descriptions-item> |
|||
<el-descriptions-item :label="$t('approval.process.processName')"> |
|||
{{ state.detailDialog.info.process_name }} |
|||
</el-descriptions-item> |
|||
<el-descriptions-item :label="$t('approval.process.applicantId')"> |
|||
{{ state.detailDialog.info.applicant_id }} |
|||
</el-descriptions-item> |
|||
<el-descriptions-item :label="$t('approval.process.applicationTime')"> |
|||
{{ state.detailDialog.info.application_time }} |
|||
</el-descriptions-item> |
|||
<el-descriptions-item :label="$t('approval.process.currentApproverId')"> |
|||
{{ state.detailDialog.info.current_approver_id }} |
|||
</el-descriptions-item> |
|||
<el-descriptions-item :label="$t('approval.process.approvalStatus')"> |
|||
<el-tag :type="getStatusType(state.detailDialog.info.approval_status)"> |
|||
{{ getStatusText(state.detailDialog.info.approval_status) }} |
|||
</el-tag> |
|||
</el-descriptions-item> |
|||
<el-descriptions-item :label="$t('approval.process.approvalTime')"> |
|||
{{ state.detailDialog.info.approval_time || '-' }} |
|||
</el-descriptions-item> |
|||
<el-descriptions-item :label="$t('approval.process.remarks')"> |
|||
{{ state.detailDialog.info.remarks || '-' }} |
|||
</el-descriptions-item> |
|||
</el-descriptions> |
|||
|
|||
<div class="mt-20px"> |
|||
<div class="font-bold text-16px mb-10px">{{ $t('approval.process.participants') }}</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"> |
|||
{{ $t('approval.process.participantLabel', { index: index + 1 }) }} |
|||
</div> |
|||
<div class="text-gray-500"> |
|||
{{ $t('approval.process.participantId') }}:{{ node.participant_id }} |
|||
</div> |
|||
<div class="text-gray-500"> |
|||
{{ $t('approval.process.sequence') }}:{{ node.sequence }} |
|||
</div> |
|||
<div class="text-gray-500"> |
|||
{{ $t('approval.process.status') }}:{{ getStatusText(node.status) }} |
|||
</div> |
|||
<div class="text-gray-500"> |
|||
{{ $t('approval.process.signType') }}:{{ node.sign_type === 'or_sign' ? $t('approval.process.orSign') : $t('approval.process.andSign') }} |
|||
</div> |
|||
<div class="text-gray-500"> |
|||
{{ $t('approval.process.remarks') }}:{{ 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>() |
|||
|
|||
// 状态 |
|||
const state = reactive({ |
|||
loading: false, |
|||
processList: [], |
|||
total: 0, |
|||
activeTab: 'all', |
|||
userInfo: userInfo, |
|||
searchParams: { |
|||
page: 1, |
|||
limit: 10, |
|||
process_name: '', |
|||
approval_status: '', |
|||
applicant_id: 0, |
|||
approver_id: 0 |
|||
}, |
|||
configOptions: [], // 审批流配置选项 |
|||
createDialog: { |
|||
visible: false, |
|||
loading: false, |
|||
form: { |
|||
process_name: '', |
|||
config_id: '', |
|||
remarks: '' |
|||
}, |
|||
rules: { |
|||
process_name: [ |
|||
{ required: true, message: t('approval.process.processNameRequired'), trigger: 'blur' } |
|||
], |
|||
config_id: [ |
|||
{ required: true, message: t('approval.process.configIdRequired'), trigger: 'change' } |
|||
] |
|||
} |
|||
}, |
|||
approveDialog: { |
|||
visible: false, |
|||
loading: false, |
|||
processId: 0, |
|||
form: { |
|||
status: 'approved', |
|||
remarks: '' |
|||
}, |
|||
rules: { |
|||
status: [ |
|||
{ required: true, message: t('approval.process.statusRequired'), trigger: 'change' } |
|||
] |
|||
} |
|||
}, |
|||
detailDialog: { |
|||
visible: false, |
|||
info: {} |
|||
} |
|||
}) |
|||
|
|||
// 获取列表数据 |
|||
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 |
|||
} |
|||
}) |
|||
} 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(t('approval.process.createSuccess')) |
|||
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(t('approval.process.approveSuccess')) |
|||
state.approveDialog.visible = false |
|||
getList() |
|||
} catch (error) { |
|||
console.error(error) |
|||
} finally { |
|||
state.approveDialog.loading = false |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// 撤销 |
|||
function handleCancel(row: any) { |
|||
ElMessageBox.confirm(t('approval.process.confirmCancel'), t('common.warning'), { |
|||
confirmButtonText: t('common.confirm'), |
|||
cancelButtonText: t('common.cancel'), |
|||
type: 'warning' |
|||
}) |
|||
.then(async () => { |
|||
try { |
|||
await cancelProcess({ process_id: row.id }) |
|||
ElMessage.success(t('approval.process.cancelSuccess')) |
|||
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: t('approval.process.pending'), |
|||
approved: t('approval.process.approved'), |
|||
rejected: t('approval.process.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> |
|||
@ -0,0 +1,45 @@ |
|||
import { RouteRecordRaw } from 'vue-router' |
|||
import Default from '@/layout/index.vue' |
|||
|
|||
/** |
|||
* @param name 路由名称, 必须设置,且不能重名 |
|||
* @param meta 路由元信息(路由附带扩展信息) |
|||
* @param redirect 重定向地址, 访问这个路由时,自动进行重定向 |
|||
* @param meta.title 菜单名称 |
|||
* @param meta.icon 菜单图标 |
|||
* @param meta.keepAlive 缓存该路由 |
|||
* @param meta.sort 排序越小越排前 |
|||
* |
|||
* */ |
|||
const routes: Array<RouteRecordRaw> = [ |
|||
{ |
|||
path: '/admin/school_approval', |
|||
name: 'SchoolApproval', |
|||
component: Default, |
|||
redirect: '/admin/school_approval/config', |
|||
meta: { |
|||
title: 'approval.menu.title', |
|||
sort: 100 |
|||
}, |
|||
children: [ |
|||
{ |
|||
path: 'config', |
|||
name: 'SchoolApprovalConfig', |
|||
component: () => import('@/app/views/school_approval/config/index.vue'), |
|||
meta: { |
|||
title: 'approval.menu.config' |
|||
} |
|||
}, |
|||
{ |
|||
path: 'process', |
|||
name: 'SchoolApprovalProcess', |
|||
component: () => import('@/app/views/school_approval/process/index.vue'), |
|||
meta: { |
|||
title: 'approval.menu.process' |
|||
} |
|||
} |
|||
] |
|||
} |
|||
] |
|||
|
|||
export default routes |
|||
File diff suppressed because it is too large
@ -0,0 +1,192 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
|
|||
namespace app\adminapi\controller\school_approval; |
|||
|
|||
use app\adminapi\controller\BaseAdminApi; |
|||
use app\service\school_approval\SchoolApprovalConfigService; |
|||
use think\facade\Request; |
|||
|
|||
/** |
|||
* 审批流配置控制器 |
|||
* Class Config |
|||
* @package app\adminapi\controller\school_approval |
|||
*/ |
|||
class Config extends BaseAdminApi |
|||
{ |
|||
/** |
|||
* @var SchoolApprovalConfigService |
|||
*/ |
|||
protected $service; |
|||
|
|||
public function initialize() |
|||
{ |
|||
parent::initialize(); |
|||
$this->service = new SchoolApprovalConfigService(); |
|||
} |
|||
|
|||
/** |
|||
* 获取审批流配置列表 |
|||
*/ |
|||
public function lists() |
|||
{ |
|||
$page = input('page', 1); |
|||
$limit = input('limit', 10); |
|||
$status = input('status', ''); |
|||
|
|||
$where = []; |
|||
if ($status !== '') { |
|||
$where[] = ['status', '=', intval($status)]; |
|||
} |
|||
|
|||
$config_name = input('config_name', ''); |
|||
if (!empty($config_name)) { |
|||
$where[] = ['config_name', 'like', "%{$config_name}%"]; |
|||
} |
|||
|
|||
$data = $this->service->getList($where, $page, $limit); |
|||
|
|||
return success($data); |
|||
} |
|||
|
|||
/** |
|||
* 获取审批流配置详情 |
|||
*/ |
|||
public function info() |
|||
{ |
|||
$id = input('id', 0); |
|||
if (empty($id)) { |
|||
return error('参数错误'); |
|||
} |
|||
|
|||
$info = $this->service->getInfo($id); |
|||
if (empty($info)) { |
|||
return error('审批流配置不存在'); |
|||
} |
|||
|
|||
return success($info); |
|||
} |
|||
|
|||
/** |
|||
* 添加审批流配置 |
|||
*/ |
|||
public function add() |
|||
{ |
|||
$data = Request::only(['config_name', 'description', 'status', 'nodes']); |
|||
|
|||
// 验证参数 |
|||
if (empty($data['config_name'])) { |
|||
return error('配置名称不能为空'); |
|||
} |
|||
|
|||
if (empty($data['nodes']) || !is_array($data['nodes'])) { |
|||
return error('至少需要添加一个审批节点'); |
|||
} |
|||
|
|||
// 验证节点数据 |
|||
foreach ($data['nodes'] as $node) { |
|||
if (empty($node['node_name'])) { |
|||
return error('节点名称不能为空'); |
|||
} |
|||
|
|||
if (empty($node['approver_type'])) { |
|||
return error('审批人类型不能为空'); |
|||
} |
|||
|
|||
if (empty($node['approver_ids'])) { |
|||
return error('审批人不能为空'); |
|||
} |
|||
} |
|||
|
|||
// 设置创建人ID |
|||
$data['creator_id'] = $this->user_info['uid']; |
|||
|
|||
try { |
|||
$config_id = $this->service->add($data); |
|||
return success(['id' => $config_id]); |
|||
} catch (\Exception $e) { |
|||
return error($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 编辑审批流配置 |
|||
*/ |
|||
public function edit() |
|||
{ |
|||
$data = Request::only(['id', 'config_name', 'description', 'status', 'nodes']); |
|||
|
|||
// 验证参数 |
|||
if (empty($data['id'])) { |
|||
return error('参数错误'); |
|||
} |
|||
|
|||
if (empty($data['config_name'])) { |
|||
return error('配置名称不能为空'); |
|||
} |
|||
|
|||
if (empty($data['nodes']) || !is_array($data['nodes'])) { |
|||
return error('至少需要添加一个审批节点'); |
|||
} |
|||
|
|||
// 验证节点数据 |
|||
foreach ($data['nodes'] as $node) { |
|||
if (empty($node['node_name'])) { |
|||
return error('节点名称不能为空'); |
|||
} |
|||
|
|||
if (empty($node['approver_type'])) { |
|||
return error('审批人类型不能为空'); |
|||
} |
|||
|
|||
if (empty($node['approver_ids'])) { |
|||
return error('审批人不能为空'); |
|||
} |
|||
} |
|||
|
|||
try { |
|||
$result = $this->service->edit($data); |
|||
return success($result); |
|||
} catch (\Exception $e) { |
|||
return error($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 删除审批流配置 |
|||
*/ |
|||
public function delete() |
|||
{ |
|||
$id = input('id', 0); |
|||
if (empty($id)) { |
|||
return error('参数错误'); |
|||
} |
|||
|
|||
try { |
|||
$result = $this->service->delete($id); |
|||
return success($result); |
|||
} catch (\Exception $e) { |
|||
return error($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 修改状态 |
|||
*/ |
|||
public function changeStatus() |
|||
{ |
|||
$id = input('id', 0); |
|||
$status = input('status', 0); |
|||
|
|||
if (empty($id)) { |
|||
return error('参数错误'); |
|||
} |
|||
|
|||
try { |
|||
$result = $this->service->changeStatus($id, $status); |
|||
return success($result); |
|||
} catch (\Exception $e) { |
|||
return error($e->getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,154 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
|
|||
namespace app\adminapi\controller\school_approval; |
|||
|
|||
use app\adminapi\controller\BaseAdminApi; |
|||
use app\service\school_approval\SchoolApprovalProcessService; |
|||
use think\facade\Request; |
|||
|
|||
/** |
|||
* 审批流程控制器 |
|||
* Class Process |
|||
* @package app\adminapi\controller\school_approval |
|||
*/ |
|||
class Process extends BaseAdminApi |
|||
{ |
|||
/** |
|||
* @var SchoolApprovalProcessService |
|||
*/ |
|||
protected $service; |
|||
|
|||
public function initialize() |
|||
{ |
|||
parent::initialize(); |
|||
$this->service = new SchoolApprovalProcessService(); |
|||
} |
|||
|
|||
/** |
|||
* 获取审批流程列表 |
|||
*/ |
|||
public function lists() |
|||
{ |
|||
$page = input('page', 1); |
|||
$limit = input('limit', 10); |
|||
$status = input('approval_status', ''); |
|||
|
|||
$where = []; |
|||
if ($status !== '') { |
|||
$where[] = ['approval_status', '=', $status]; |
|||
} |
|||
|
|||
$process_name = input('process_name', ''); |
|||
if (!empty($process_name)) { |
|||
$where[] = ['process_name', 'like', "%{$process_name}%"]; |
|||
} |
|||
|
|||
// 我发起的审批 |
|||
$applicant_id = input('applicant_id', 0); |
|||
if (!empty($applicant_id)) { |
|||
$where[] = ['applicant_id', '=', $applicant_id]; |
|||
} |
|||
|
|||
// 待我审批的 |
|||
$approver_id = input('approver_id', 0); |
|||
if (!empty($approver_id)) { |
|||
$where[] = ['current_approver_id', '=', $approver_id]; |
|||
$where[] = ['approval_status', '=', 'pending']; |
|||
} |
|||
|
|||
$data = $this->service->getList($where, $page, $limit); |
|||
|
|||
return success($data); |
|||
} |
|||
|
|||
/** |
|||
* 获取审批流程详情 |
|||
*/ |
|||
public function info() |
|||
{ |
|||
$id = input('id', 0); |
|||
if (empty($id)) { |
|||
return error('参数错误'); |
|||
} |
|||
|
|||
$info = $this->service->getInfo($id); |
|||
if (empty($info)) { |
|||
return error('审批流程不存在'); |
|||
} |
|||
|
|||
return success($info); |
|||
} |
|||
|
|||
/** |
|||
* 创建审批流程 |
|||
*/ |
|||
public function create() |
|||
{ |
|||
$data = Request::only(['process_name', 'remarks']); |
|||
$config_id = input('config_id', 0); |
|||
|
|||
// 验证参数 |
|||
if (empty($data['process_name'])) { |
|||
return error('流程名称不能为空'); |
|||
} |
|||
|
|||
if (empty($config_id)) { |
|||
return error('请选择审批流配置'); |
|||
} |
|||
|
|||
// 设置申请人ID |
|||
$data['applicant_id'] = $this->user_info['uid']; |
|||
|
|||
try { |
|||
$process_id = $this->service->create($data, $config_id); |
|||
return success(['id' => $process_id]); |
|||
} catch (\Exception $e) { |
|||
return error($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 审批 |
|||
*/ |
|||
public function approve() |
|||
{ |
|||
$process_id = input('process_id', 0); |
|||
$status = input('status', ''); |
|||
$remarks = input('remarks', ''); |
|||
|
|||
if (empty($process_id)) { |
|||
return error('参数错误'); |
|||
} |
|||
|
|||
if (empty($status) || !in_array($status, ['approved', 'rejected'])) { |
|||
return error('请选择审批结果'); |
|||
} |
|||
|
|||
try { |
|||
$result = $this->service->approve($process_id, $this->user_info['uid'], $status, $remarks); |
|||
return success($result); |
|||
} catch (\Exception $e) { |
|||
return error($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 撤销审批流程 |
|||
*/ |
|||
public function cancel() |
|||
{ |
|||
$process_id = input('process_id', 0); |
|||
|
|||
if (empty($process_id)) { |
|||
return error('参数错误'); |
|||
} |
|||
|
|||
try { |
|||
$result = $this->service->cancel($process_id, $this->user_info['uid']); |
|||
return success($result); |
|||
} catch (\Exception $e) { |
|||
return error($e->getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | 审批流路由 |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
use think\facade\Route; |
|||
|
|||
// 审批流配置 |
|||
Route::group('school_approval/config', function () { |
|||
// 审批流配置列表 |
|||
Route::get('lists', 'school_approval.Config/lists'); |
|||
// 审批流配置详情 |
|||
Route::get('info', 'school_approval.Config/info'); |
|||
// 添加审批流配置 |
|||
Route::post('add', 'school_approval.Config/add'); |
|||
// 编辑审批流配置 |
|||
Route::post('edit', 'school_approval.Config/edit'); |
|||
// 删除审批流配置 |
|||
Route::post('delete', 'school_approval.Config/delete'); |
|||
// 修改状态 |
|||
Route::post('changeStatus', 'school_approval.Config/changeStatus'); |
|||
})->middleware( |
|||
[ |
|||
app\adminapi\middleware\AdminCheckToken::class, |
|||
app\adminapi\middleware\AdminCheckRole::class, |
|||
app\adminapi\middleware\AdminLog::class |
|||
] |
|||
); |
|||
|
|||
// 审批流程 |
|||
Route::group('school_approval/process', function () { |
|||
// 审批流程列表 |
|||
Route::get('lists', 'school_approval.Process/lists'); |
|||
// 审批流程详情 |
|||
Route::get('info', 'school_approval.Process/info'); |
|||
// 创建审批流程 |
|||
Route::post('create', 'school_approval.Process/create'); |
|||
// 审批 |
|||
Route::post('approve', 'school_approval.Process/approve'); |
|||
// 撤销审批流程 |
|||
Route::post('cancel', 'school_approval.Process/cancel'); |
|||
})->middleware( |
|||
[ |
|||
app\adminapi\middleware\AdminCheckToken::class, |
|||
app\adminapi\middleware\AdminCheckRole::class, |
|||
app\adminapi\middleware\AdminLog::class |
|||
] |
|||
); |
|||
@ -0,0 +1,53 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
|
|||
namespace app\model\school_approval; |
|||
|
|||
use think\Model; |
|||
|
|||
/** |
|||
* 审批流配置模型 |
|||
* Class SchoolApprovalConfig |
|||
* @package app\model\school_approval |
|||
*/ |
|||
class SchoolApprovalConfig extends Model |
|||
{ |
|||
/** |
|||
* 数据表主键 |
|||
* @var string |
|||
*/ |
|||
protected $pk = 'id'; |
|||
|
|||
/** |
|||
* 模型名称 |
|||
* @var string |
|||
*/ |
|||
protected $name = 'school_approval_config'; |
|||
|
|||
/** |
|||
* 自动写入时间戳 |
|||
* @var bool |
|||
*/ |
|||
protected $autoWriteTimestamp = true; |
|||
|
|||
/** |
|||
* 创建时间字段 |
|||
* @var string |
|||
*/ |
|||
protected $createTime = 'created_at'; |
|||
|
|||
/** |
|||
* 更新时间字段 |
|||
* @var string |
|||
*/ |
|||
protected $updateTime = 'updated_at'; |
|||
|
|||
/** |
|||
* 关联审批流配置节点 |
|||
* @return \think\model\relation\HasMany |
|||
*/ |
|||
public function nodes() |
|||
{ |
|||
return $this->hasMany(SchoolApprovalConfigNode::class, 'config_id', 'id')->order('sequence', 'asc'); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
|
|||
namespace app\model\school_approval; |
|||
|
|||
use think\Model; |
|||
|
|||
/** |
|||
* 审批流配置节点模型 |
|||
* Class SchoolApprovalConfigNode |
|||
* @package app\model\school_approval |
|||
*/ |
|||
class SchoolApprovalConfigNode extends Model |
|||
{ |
|||
/** |
|||
* 数据表主键 |
|||
* @var string |
|||
*/ |
|||
protected $pk = 'id'; |
|||
|
|||
/** |
|||
* 模型名称 |
|||
* @var string |
|||
*/ |
|||
protected $name = 'school_approval_config_node'; |
|||
|
|||
/** |
|||
* 自动写入时间戳 |
|||
* @var bool |
|||
*/ |
|||
protected $autoWriteTimestamp = true; |
|||
|
|||
/** |
|||
* 创建时间字段 |
|||
* @var string |
|||
*/ |
|||
protected $createTime = 'created_at'; |
|||
|
|||
/** |
|||
* 更新时间字段 |
|||
* @var string |
|||
*/ |
|||
protected $updateTime = 'updated_at'; |
|||
|
|||
/** |
|||
* 审批人类型 |
|||
*/ |
|||
const APPROVER_TYPE_USER = 'user'; // 指定用户 |
|||
const APPROVER_TYPE_ROLE = 'role'; // 指定角色 |
|||
const APPROVER_TYPE_DEPARTMENT = 'department'; // 指定部门 |
|||
|
|||
/** |
|||
* 签署类型 |
|||
*/ |
|||
const SIGN_TYPE_OR = 'or_sign'; // 或签(一人通过即可) |
|||
const SIGN_TYPE_AND = 'and_sign'; // 会签(需全部通过) |
|||
|
|||
/** |
|||
* 关联审批流配置 |
|||
* @return \think\model\relation\BelongsTo |
|||
*/ |
|||
public function config() |
|||
{ |
|||
return $this->belongsTo(SchoolApprovalConfig::class, 'config_id', 'id'); |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
|
|||
namespace app\model\school_approval; |
|||
|
|||
use think\Model; |
|||
|
|||
/** |
|||
* 审批参与人模型 |
|||
* Class SchoolApprovalParticipants |
|||
* @package app\model\school_approval |
|||
*/ |
|||
class SchoolApprovalParticipants extends Model |
|||
{ |
|||
/** |
|||
* 数据表主键 |
|||
* @var string |
|||
*/ |
|||
protected $pk = 'id'; |
|||
|
|||
/** |
|||
* 模型名称 |
|||
* @var string |
|||
*/ |
|||
protected $name = 'school_approval_participants'; |
|||
|
|||
/** |
|||
* 自动写入时间戳 |
|||
* @var bool |
|||
*/ |
|||
protected $autoWriteTimestamp = true; |
|||
|
|||
/** |
|||
* 创建时间字段 |
|||
* @var string |
|||
*/ |
|||
protected $createTime = 'created_at'; |
|||
|
|||
/** |
|||
* 更新时间字段 |
|||
* @var string |
|||
*/ |
|||
protected $updateTime = 'updated_at'; |
|||
|
|||
/** |
|||
* 审批状态 |
|||
*/ |
|||
const STATUS_PENDING = 'pending'; // 待审批 |
|||
const STATUS_APPROVED = 'approved'; // 已批准 |
|||
const STATUS_REJECTED = 'rejected'; // 已拒绝 |
|||
|
|||
/** |
|||
* 签署类型 |
|||
*/ |
|||
const SIGN_TYPE_OR = 'or_sign'; // 或签(一人通过即可) |
|||
const SIGN_TYPE_AND = 'and_sign'; // 会签(需全部通过) |
|||
|
|||
/** |
|||
* 关联审批流程 |
|||
* @return \think\model\relation\BelongsTo |
|||
*/ |
|||
public function process() |
|||
{ |
|||
return $this->belongsTo(SchoolApprovalProcess::class, 'process_id', 'id'); |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
|
|||
namespace app\model\school_approval; |
|||
|
|||
use think\Model; |
|||
|
|||
/** |
|||
* 审批流程模型 |
|||
* Class SchoolApprovalProcess |
|||
* @package app\model\school_approval |
|||
*/ |
|||
class SchoolApprovalProcess extends Model |
|||
{ |
|||
/** |
|||
* 数据表主键 |
|||
* @var string |
|||
*/ |
|||
protected $pk = 'id'; |
|||
|
|||
/** |
|||
* 模型名称 |
|||
* @var string |
|||
*/ |
|||
protected $name = 'school_approval_process'; |
|||
|
|||
/** |
|||
* 自动写入时间戳 |
|||
* @var bool |
|||
*/ |
|||
protected $autoWriteTimestamp = true; |
|||
|
|||
/** |
|||
* 创建时间字段 |
|||
* @var string |
|||
*/ |
|||
protected $createTime = 'created_at'; |
|||
|
|||
/** |
|||
* 更新时间字段 |
|||
* @var string |
|||
*/ |
|||
protected $updateTime = 'updated_at'; |
|||
|
|||
/** |
|||
* 审批状态 |
|||
*/ |
|||
const STATUS_PENDING = 'pending'; // 待审批 |
|||
const STATUS_APPROVED = 'approved'; // 已批准 |
|||
const STATUS_REJECTED = 'rejected'; // 已拒绝 |
|||
|
|||
/** |
|||
* 关联审批参与人 |
|||
* @return \think\model\relation\HasMany |
|||
*/ |
|||
public function participants() |
|||
{ |
|||
return $this->hasMany(SchoolApprovalParticipants::class, 'process_id', 'id')->order('sequence', 'asc'); |
|||
} |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
-- ======================== |
|||
-- 数据库结构增强脚本(MySQL) |
|||
-- ======================== |
|||
|
|||
-- 1. 会计期间有效性校验 |
|||
ALTER TABLE financial_accounting_periods |
|||
ADD COLUMN status ENUM('active', 'closed', 'locked') DEFAULT 'active' COMMENT '期间状态', |
|||
ADD COLUMN validation_date DATE COMMENT '最后验证日期'; |
|||
|
|||
-- 2. 预算表(支持多版本) |
|||
CREATE TABLE financial_budgets ( |
|||
budget_id INT AUTO_INCREMENT PRIMARY KEY COMMENT '预算ID', |
|||
company_id INT NOT NULL COMMENT '公司ID', |
|||
period_id INT NOT NULL COMMENT '会计期间ID', |
|||
account_id INT NOT NULL COMMENT '科目ID', |
|||
budget_version INT DEFAULT 1 COMMENT '版本号', |
|||
planned_amount DECIMAL(18,2) COMMENT '计划金额', |
|||
approved_date DATE COMMENT '审批日期', |
|||
FOREIGN KEY (company_id) REFERENCES financial_companies(company_id), |
|||
FOREIGN KEY (period_id) REFERENCES financial_accounting_periods(period_id), |
|||
FOREIGN KEY (account_id) REFERENCES financial_chart_of_accounts(account_id) |
|||
) COMMENT='财务预算表'; |
|||
|
|||
-- 3. 科目层级管理增强 |
|||
ALTER TABLE financial_chart_of_accounts |
|||
ADD COLUMN path VARCHAR(255) COMMENT '层级路径(如0/1001/)', |
|||
ADD COLUMN level INT COMMENT '层级深度'; |
|||
|
|||
-- 4. 数据血缘追踪表 |
|||
CREATE TABLE financial_data_trace ( |
|||
trace_id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '追踪记录ID', |
|||
source_table VARCHAR(50) NOT NULL COMMENT '源表名', |
|||
source_id BIGINT NOT NULL COMMENT '源记录ID', |
|||
target_table VARCHAR(50) NOT NULL COMMENT '目标表名', |
|||
target_id BIGINT NOT NULL COMMENT '目标记录ID', |
|||
operation_type ENUM('insert','update','delete') NOT NULL COMMENT '操作类型', |
|||
operator VARCHAR(50) COMMENT '操作人', |
|||
operation_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间' |
|||
) COMMENT='数据操作追踪表'; |
|||
|
|||
-- 5. 会计期间重叠检查触发器 |
|||
DELIMITER // |
|||
CREATE TRIGGER trg_check_period_overlap |
|||
BEFORE INSERT ON financial_accounting_periods |
|||
FOR EACH ROW |
|||
BEGIN |
|||
IF EXISTS ( |
|||
SELECT 1 FROM financial_accounting_periods |
|||
WHERE company_id = NEW.company_id |
|||
AND period_id != NEW.period_id |
|||
AND ( |
|||
(NEW.start_date BETWEEN start_date AND end_date) |
|||
OR (NEW.end_date BETWEEN start_date AND end_date) |
|||
OR (start_date BETWEEN NEW.start_date AND NEW.end_date) |
|||
) |
|||
) THEN |
|||
SIGNAL SQLSTATE '45000' |
|||
SET MESSAGE_TEXT = '会计期间时间重叠'; |
|||
END IF; |
|||
END// |
|||
DELIMITER ; |
|||
|
|||
-- 6. 初始化会计期间状态 |
|||
UPDATE financial_accounting_periods |
|||
SET status = CASE |
|||
WHEN end_date >= CURDATE() THEN 'active' |
|||
WHEN end_date < CURDATE() AND start_date > CURDATE() THEN 'locked' |
|||
ELSE 'closed' |
|||
END; |
|||
|
|||
-- 7. 创建科目层级维护存储过程 |
|||
DELIMITER // |
|||
CREATE PROCEDURE sp_refresh_account_hierarchy() |
|||
BEGIN |
|||
UPDATE financial_chart_of_accounts c |
|||
LEFT JOIN ( |
|||
SELECT |
|||
a.account_id, |
|||
CONCAT( |
|||
IFNULL((SELECT CONCAT(path, parent_id, '/') FROM financial_chart_of_accounts |
|||
WHERE account_id = a.parent_id), '0/'), |
|||
a.account_id, '/' |
|||
) AS new_path, |
|||
(LENGTH(IFNULL((SELECT path FROM financial_chart_of_accounts |
|||
WHERE account_id = a.parent_id), '0/')) - LENGTH(REPLACE(IFNULL( |
|||
(SELECT path FROM financial_chart_of_accounts |
|||
WHERE account_id = a.parent_id), '0/'), '/', ''))) + 1 |
|||
AS new_level |
|||
FROM financial_chart_of_accounts a |
|||
) AS sub ON c.account_id = sub.account_id |
|||
SET |
|||
c.path = sub.new_path, |
|||
c.level = sub.new_level |
|||
WHERE c.is_active = TRUE; |
|||
END// |
|||
DELIMITER ; |
|||
@ -0,0 +1,185 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
|
|||
namespace app\service\school_approval; |
|||
|
|||
use app\model\school_approval\SchoolApprovalConfig; |
|||
use app\model\school_approval\SchoolApprovalConfigNode; |
|||
use think\Exception; |
|||
use think\facade\Db; |
|||
|
|||
/** |
|||
* 审批流配置服务 |
|||
* Class SchoolApprovalConfigService |
|||
* @package app\service\school_approval |
|||
*/ |
|||
class SchoolApprovalConfigService |
|||
{ |
|||
/** |
|||
* 获取审批流配置列表 |
|||
* @param array $where |
|||
* @param int $page |
|||
* @param int $limit |
|||
* @return array |
|||
*/ |
|||
public function getList(array $where = [], int $page = 1, int $limit = 10): array |
|||
{ |
|||
$field = 'id, config_name, description, status, creator_id, created_at, updated_at'; |
|||
$order = 'id desc'; |
|||
|
|||
$list = (new SchoolApprovalConfig()) |
|||
->where($where) |
|||
->field($field) |
|||
->order($order) |
|||
->page($page, $limit) |
|||
->select() |
|||
->toArray(); |
|||
|
|||
$count = (new SchoolApprovalConfig())->where($where)->count(); |
|||
|
|||
return [ |
|||
'list' => $list, |
|||
'count' => $count |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* 获取审批流配置详情 |
|||
* @param int $id |
|||
* @return array |
|||
*/ |
|||
public function getInfo(int $id): array |
|||
{ |
|||
$info = (new SchoolApprovalConfig())->with(['nodes'])->where(['id' => $id])->find(); |
|||
if (empty($info)) { |
|||
return []; |
|||
} |
|||
|
|||
return $info->toArray(); |
|||
} |
|||
|
|||
/** |
|||
* 添加审批流配置 |
|||
* @param array $data |
|||
* @return int |
|||
* @throws \Exception |
|||
*/ |
|||
public function add(array $data): int |
|||
{ |
|||
Db::startTrans(); |
|||
try { |
|||
$config = [ |
|||
'config_name' => $data['config_name'], |
|||
'description' => $data['description'] ?? '', |
|||
'status' => $data['status'] ?? 1, |
|||
'creator_id' => $data['creator_id'] |
|||
]; |
|||
|
|||
$config_id = (new SchoolApprovalConfig())->insertGetId($config); |
|||
|
|||
// 添加节点 |
|||
if (!empty($data['nodes'])) { |
|||
$nodes = []; |
|||
foreach ($data['nodes'] as $sequence => $node) { |
|||
$nodes[] = [ |
|||
'config_id' => $config_id, |
|||
'node_name' => $node['node_name'], |
|||
'approver_type' => $node['approver_type'], |
|||
'approver_ids' => is_array($node['approver_ids']) ? implode(',', $node['approver_ids']) : $node['approver_ids'], |
|||
'sign_type' => $node['sign_type'] ?? SchoolApprovalConfigNode::SIGN_TYPE_OR, |
|||
'sequence' => $sequence + 1 |
|||
]; |
|||
} |
|||
|
|||
(new SchoolApprovalConfigNode())->insertAll($nodes); |
|||
} |
|||
|
|||
Db::commit(); |
|||
return $config_id; |
|||
} catch (\Exception $e) { |
|||
Db::rollback(); |
|||
throw new Exception($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 编辑审批流配置 |
|||
* @param array $data |
|||
* @return bool |
|||
* @throws \Exception |
|||
*/ |
|||
public function edit(array $data): bool |
|||
{ |
|||
Db::startTrans(); |
|||
try { |
|||
$config = [ |
|||
'config_name' => $data['config_name'], |
|||
'description' => $data['description'] ?? '', |
|||
'status' => $data['status'] ?? 1 |
|||
]; |
|||
|
|||
(new SchoolApprovalConfig())->where(['id' => $data['id']])->update($config); |
|||
|
|||
// 先删除原有节点 |
|||
(new SchoolApprovalConfigNode())->where(['config_id' => $data['id']])->delete(); |
|||
|
|||
// 添加新节点 |
|||
if (!empty($data['nodes'])) { |
|||
$nodes = []; |
|||
foreach ($data['nodes'] as $sequence => $node) { |
|||
$nodes[] = [ |
|||
'config_id' => $data['id'], |
|||
'node_name' => $node['node_name'], |
|||
'approver_type' => $node['approver_type'], |
|||
'approver_ids' => is_array($node['approver_ids']) ? implode(',', $node['approver_ids']) : $node['approver_ids'], |
|||
'sign_type' => $node['sign_type'] ?? SchoolApprovalConfigNode::SIGN_TYPE_OR, |
|||
'sequence' => $sequence + 1 |
|||
]; |
|||
} |
|||
|
|||
(new SchoolApprovalConfigNode())->insertAll($nodes); |
|||
} |
|||
|
|||
Db::commit(); |
|||
return true; |
|||
} catch (\Exception $e) { |
|||
Db::rollback(); |
|||
throw new Exception($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 删除审批流配置 |
|||
* @param int $id |
|||
* @return bool |
|||
* @throws \Exception |
|||
*/ |
|||
public function delete(int $id): bool |
|||
{ |
|||
Db::startTrans(); |
|||
try { |
|||
// 删除配置 |
|||
(new SchoolApprovalConfig())->where(['id' => $id])->delete(); |
|||
|
|||
// 删除节点 |
|||
(new SchoolApprovalConfigNode())->where(['config_id' => $id])->delete(); |
|||
|
|||
Db::commit(); |
|||
return true; |
|||
} catch (\Exception $e) { |
|||
Db::rollback(); |
|||
throw new Exception($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 修改状态 |
|||
* @param int $id |
|||
* @param int $status |
|||
* @return bool |
|||
*/ |
|||
public function changeStatus(int $id, int $status): bool |
|||
{ |
|||
return (new SchoolApprovalConfig())->where(['id' => $id])->update(['status' => $status]) !== false; |
|||
} |
|||
} |
|||
@ -0,0 +1,289 @@ |
|||
<?php |
|||
declare(strict_types=1); |
|||
|
|||
namespace app\service\school_approval; |
|||
|
|||
use app\model\school_approval\SchoolApprovalConfig; |
|||
use app\model\school_approval\SchoolApprovalConfigNode; |
|||
use app\model\school_approval\SchoolApprovalParticipants; |
|||
use app\model\school_approval\SchoolApprovalProcess; |
|||
use think\Exception; |
|||
use think\facade\Db; |
|||
|
|||
/** |
|||
* 审批流程服务 |
|||
* Class SchoolApprovalProcessService |
|||
* @package app\service\school_approval |
|||
*/ |
|||
class SchoolApprovalProcessService |
|||
{ |
|||
/** |
|||
* 获取审批流程列表 |
|||
* @param array $where |
|||
* @param int $page |
|||
* @param int $limit |
|||
* @return array |
|||
*/ |
|||
public function getList(array $where = [], int $page = 1, int $limit = 10): array |
|||
{ |
|||
$field = 'id, process_name, applicant_id, application_time, current_approver_id, approval_status, approval_time, remarks, created_at, updated_at'; |
|||
$order = 'id desc'; |
|||
|
|||
$list = (new SchoolApprovalProcess()) |
|||
->where($where) |
|||
->field($field) |
|||
->order($order) |
|||
->page($page, $limit) |
|||
->select() |
|||
->toArray(); |
|||
|
|||
$count = (new SchoolApprovalProcess())->where($where)->count(); |
|||
|
|||
return [ |
|||
'list' => $list, |
|||
'count' => $count |
|||
]; |
|||
} |
|||
|
|||
/** |
|||
* 获取审批流程详情 |
|||
* @param int $id |
|||
* @return array |
|||
*/ |
|||
public function getInfo(int $id): array |
|||
{ |
|||
$info = (new SchoolApprovalProcess())->with(['participants'])->where(['id' => $id])->find(); |
|||
if (empty($info)) { |
|||
return []; |
|||
} |
|||
|
|||
return $info->toArray(); |
|||
} |
|||
|
|||
/** |
|||
* 创建审批流程 |
|||
* @param array $data |
|||
* @param int $config_id 审批配置ID |
|||
* @return int |
|||
* @throws \Exception |
|||
*/ |
|||
public function create(array $data, int $config_id): int |
|||
{ |
|||
Db::startTrans(); |
|||
try { |
|||
// 获取审批配置详情 |
|||
$config_info = (new SchoolApprovalConfigService())->getInfo($config_id); |
|||
if (empty($config_info)) { |
|||
throw new Exception('审批配置不存在'); |
|||
} |
|||
|
|||
// 创建审批流程 |
|||
$process = [ |
|||
'process_name' => $data['process_name'], |
|||
'applicant_id' => $data['applicant_id'], |
|||
'application_time' => time(), |
|||
'current_approver_id' => 0, // 初始时为0,后面会更新 |
|||
'approval_status' => SchoolApprovalProcess::STATUS_PENDING, |
|||
'remarks' => $data['remarks'] ?? '' |
|||
]; |
|||
|
|||
$process_id = (new SchoolApprovalProcess())->insertGetId($process); |
|||
|
|||
// 创建审批参与人 |
|||
$participants = []; |
|||
foreach ($config_info['nodes'] as $sequence => $node) { |
|||
$approver_ids = explode(',', $node['approver_ids']); |
|||
foreach ($approver_ids as $approver_id) { |
|||
$participants[] = [ |
|||
'process_id' => $process_id, |
|||
'participant_id' => $approver_id, |
|||
'sequence' => $node['sequence'], |
|||
'status' => SchoolApprovalParticipants::STATUS_PENDING, |
|||
'sign_type' => $node['sign_type'] |
|||
]; |
|||
} |
|||
} |
|||
|
|||
if (!empty($participants)) { |
|||
(new SchoolApprovalParticipants())->insertAll($participants); |
|||
|
|||
// 更新当前审批人为第一个审批人 |
|||
$first_participant = (new SchoolApprovalParticipants()) |
|||
->where(['process_id' => $process_id]) |
|||
->order('sequence', 'asc') |
|||
->find(); |
|||
|
|||
if (!empty($first_participant)) { |
|||
(new SchoolApprovalProcess())->where(['id' => $process_id]) |
|||
->update(['current_approver_id' => $first_participant['participant_id']]); |
|||
} |
|||
} |
|||
|
|||
Db::commit(); |
|||
return $process_id; |
|||
} catch (\Exception $e) { |
|||
Db::rollback(); |
|||
throw new Exception($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 审批 |
|||
* @param int $process_id 流程ID |
|||
* @param int $approver_id 审批人ID |
|||
* @param string $status 审批状态 |
|||
* @param string $remarks 备注 |
|||
* @return bool |
|||
* @throws \Exception |
|||
*/ |
|||
public function approve(int $process_id, int $approver_id, string $status, string $remarks = ''): bool |
|||
{ |
|||
Db::startTrans(); |
|||
try { |
|||
// 获取审批流程 |
|||
$process_info = (new SchoolApprovalProcess())->where(['id' => $process_id])->find(); |
|||
if (empty($process_info)) { |
|||
throw new Exception('审批流程不存在'); |
|||
} |
|||
|
|||
// 检查是否当前审批人 |
|||
if ($process_info['current_approver_id'] != $approver_id) { |
|||
throw new Exception('您不是当前审批人'); |
|||
} |
|||
|
|||
// 检查流程状态 |
|||
if ($process_info['approval_status'] != SchoolApprovalProcess::STATUS_PENDING) { |
|||
throw new Exception('该审批流程已完成'); |
|||
} |
|||
|
|||
// 获取当前审批节点 |
|||
$current_participant = (new SchoolApprovalParticipants()) |
|||
->where([ |
|||
'process_id' => $process_id, |
|||
'participant_id' => $approver_id, |
|||
'status' => SchoolApprovalParticipants::STATUS_PENDING |
|||
]) |
|||
->find(); |
|||
|
|||
if (empty($current_participant)) { |
|||
throw new Exception('审批节点信息错误'); |
|||
} |
|||
|
|||
// 更新当前审批人状态 |
|||
(new SchoolApprovalParticipants())->where(['id' => $current_participant['id']]) |
|||
->update([ |
|||
'status' => $status, |
|||
'remarks' => $remarks |
|||
]); |
|||
|
|||
// 如果拒绝,直接更新整个流程状态为拒绝 |
|||
if ($status == SchoolApprovalParticipants::STATUS_REJECTED) { |
|||
(new SchoolApprovalProcess())->where(['id' => $process_id]) |
|||
->update([ |
|||
'approval_status' => SchoolApprovalProcess::STATUS_REJECTED, |
|||
'approval_time' => time(), |
|||
'remarks' => $remarks |
|||
]); |
|||
|
|||
Db::commit(); |
|||
return true; |
|||
} |
|||
|
|||
// 检查当前节点是否需要会签 |
|||
$same_sequence_participants = (new SchoolApprovalParticipants()) |
|||
->where([ |
|||
'process_id' => $process_id, |
|||
'sequence' => $current_participant['sequence'], |
|||
'status' => SchoolApprovalParticipants::STATUS_PENDING |
|||
]) |
|||
->select(); |
|||
|
|||
// 如果是会签且还有其他人未审批,则等待 |
|||
if ($current_participant['sign_type'] == SchoolApprovalParticipants::SIGN_TYPE_AND && !$same_sequence_participants->isEmpty()) { |
|||
// 不做任何处理,等待其他人审批 |
|||
Db::commit(); |
|||
return true; |
|||
} |
|||
|
|||
// 获取下一个审批节点 |
|||
$next_participant = (new SchoolApprovalParticipants()) |
|||
->where([ |
|||
'process_id' => $process_id, |
|||
'status' => SchoolApprovalParticipants::STATUS_PENDING |
|||
]) |
|||
->order('sequence', 'asc') |
|||
->find(); |
|||
|
|||
if (empty($next_participant)) { |
|||
// 没有下一个审批人,流程结束,标记为已通过 |
|||
(new SchoolApprovalProcess())->where(['id' => $process_id]) |
|||
->update([ |
|||
'approval_status' => SchoolApprovalProcess::STATUS_APPROVED, |
|||
'approval_time' => time() |
|||
]); |
|||
} else { |
|||
// 更新当前审批人为下一个审批人 |
|||
(new SchoolApprovalProcess())->where(['id' => $process_id]) |
|||
->update(['current_approver_id' => $next_participant['participant_id']]); |
|||
} |
|||
|
|||
Db::commit(); |
|||
return true; |
|||
} catch (\Exception $e) { |
|||
Db::rollback(); |
|||
throw new Exception($e->getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 撤销审批流程 |
|||
* @param int $process_id 流程ID |
|||
* @param int $applicant_id 申请人ID |
|||
* @return bool |
|||
* @throws \Exception |
|||
*/ |
|||
public function cancel(int $process_id, int $applicant_id): bool |
|||
{ |
|||
Db::startTrans(); |
|||
try { |
|||
// 获取审批流程 |
|||
$process_info = (new SchoolApprovalProcess())->where(['id' => $process_id])->find(); |
|||
if (empty($process_info)) { |
|||
throw new Exception('审批流程不存在'); |
|||
} |
|||
|
|||
// 检查是否申请人 |
|||
if ($process_info['applicant_id'] != $applicant_id) { |
|||
throw new Exception('您不是该流程的申请人'); |
|||
} |
|||
|
|||
// 检查流程状态 |
|||
if ($process_info['approval_status'] != SchoolApprovalProcess::STATUS_PENDING) { |
|||
throw new Exception('该审批流程已完成,无法撤销'); |
|||
} |
|||
|
|||
// 更新流程状态为已拒绝(撤销) |
|||
(new SchoolApprovalProcess())->where(['id' => $process_id]) |
|||
->update([ |
|||
'approval_status' => SchoolApprovalProcess::STATUS_REJECTED, |
|||
'approval_time' => time(), |
|||
'remarks' => '申请人撤销' |
|||
]); |
|||
|
|||
// 更新所有待审批节点为已拒绝 |
|||
(new SchoolApprovalParticipants())->where([ |
|||
'process_id' => $process_id, |
|||
'status' => SchoolApprovalParticipants::STATUS_PENDING |
|||
])->update([ |
|||
'status' => SchoolApprovalParticipants::STATUS_REJECTED, |
|||
'remarks' => '申请人撤销' |
|||
]); |
|||
|
|||
Db::commit(); |
|||
return true; |
|||
} catch (\Exception $e) { |
|||
Db::rollback(); |
|||
throw new Exception($e->getMessage()); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue