|
|
|
@ -4,60 +4,60 @@ |
|
|
|
<div class="flex justify-between items-center"> |
|
|
|
<div> |
|
|
|
<el-button type="primary" @click="handleAdd"> |
|
|
|
<icon name="add" class="mr-5px" /> |
|
|
|
<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" |
|
|
|
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" |
|
|
|
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-option label="启用" :value="1"/> |
|
|
|
<el-option label="禁用" :value="0"/> |
|
|
|
</el-select> |
|
|
|
<el-button type="primary" @click="handleSearch"> |
|
|
|
<icon name="search" class="mr-5px" /> |
|
|
|
<icon name="search" class="mr-5px"/> |
|
|
|
搜索 |
|
|
|
</el-button> |
|
|
|
<el-button @click="handleReset"> |
|
|
|
<icon name="refresh-right" class="mr-5px" /> |
|
|
|
<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' }" |
|
|
|
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="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)" |
|
|
|
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="创建时间" prop="created_at" width="180"/> |
|
|
|
<el-table-column label="操作" width="180" fixed="right"> |
|
|
|
<template #default="{ row }"> |
|
|
|
<el-button type="primary" link @click="handleEdit(row)"> |
|
|
|
@ -75,68 +75,69 @@ |
|
|
|
|
|
|
|
<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" |
|
|
|
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" |
|
|
|
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" |
|
|
|
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-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="请输入配置描述" |
|
|
|
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" |
|
|
|
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" /> |
|
|
|
<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 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 |
|
|
|
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" /> |
|
|
|
<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)"> |
|
|
|
@ -146,53 +147,53 @@ |
|
|
|
<div class="flex items-center mb-10px"> |
|
|
|
<span class="w-100px">节点名称:</span> |
|
|
|
<el-input |
|
|
|
v-model="element.node_name" |
|
|
|
placeholder="请输入节点名称" |
|
|
|
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="请选择审批人类型" |
|
|
|
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-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="请选择审批人" |
|
|
|
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" |
|
|
|
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" |
|
|
|
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" |
|
|
|
v-for="item in state.departmentOptions" |
|
|
|
:key="item.value" |
|
|
|
:label="item.label" |
|
|
|
:value="item.value" |
|
|
|
/> |
|
|
|
</template> |
|
|
|
</el-select> |
|
|
|
@ -218,10 +219,10 @@ |
|
|
|
|
|
|
|
<!-- 详情弹窗 --> |
|
|
|
<el-dialog |
|
|
|
v-model="state.detailDialog.visible" |
|
|
|
title="审批流配置详情" |
|
|
|
width="800px" |
|
|
|
:destroy-on-close="true" |
|
|
|
v-model="state.detailDialog.visible" |
|
|
|
title="审批流配置详情" |
|
|
|
width="800px" |
|
|
|
:destroy-on-close="true" |
|
|
|
> |
|
|
|
<el-descriptions :column="1" border> |
|
|
|
<el-descriptions-item label="ID"> |
|
|
|
@ -247,10 +248,10 @@ |
|
|
|
<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)" |
|
|
|
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"> |
|
|
|
@ -270,9 +271,16 @@ |
|
|
|
</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' |
|
|
|
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' |
|
|
|
@ -320,17 +328,17 @@ const state = reactive({ |
|
|
|
}, |
|
|
|
// 模拟数据,实际应该从API获取 |
|
|
|
userOptions: [ |
|
|
|
{ label: '用户1', value: '1' }, |
|
|
|
{ label: '用户2', value: '2' }, |
|
|
|
{ label: '用户3', value: '3' } |
|
|
|
{label: '用户1', value: '1'}, |
|
|
|
{label: '用户2', value: '2'}, |
|
|
|
{label: '用户3', value: '3'} |
|
|
|
], |
|
|
|
roleOptions: [ |
|
|
|
{ label: '角色1', value: '1' }, |
|
|
|
{ label: '角色2', value: '2' } |
|
|
|
{label: '角色1', value: '1'}, |
|
|
|
{label: '角色2', value: '2'} |
|
|
|
], |
|
|
|
departmentOptions: [ |
|
|
|
{ label: '部门1', value: '1' }, |
|
|
|
{ label: '部门2', value: '2' } |
|
|
|
{label: '部门1', value: '1'}, |
|
|
|
{label: '部门2', value: '2'} |
|
|
|
], |
|
|
|
dialog: { |
|
|
|
visible: false, |
|
|
|
@ -346,10 +354,10 @@ const state = reactive({ |
|
|
|
}, |
|
|
|
rules: { |
|
|
|
config_name: [ |
|
|
|
{ required: true, message: '请输入配置名称', trigger: 'blur' } |
|
|
|
{required: true, message: '请输入配置名称', trigger: 'blur'} |
|
|
|
], |
|
|
|
nodes: [ |
|
|
|
{ required: true, validator: validateNodes, trigger: 'change' } |
|
|
|
{required: true, validator: validateNodes, trigger: 'change'} |
|
|
|
] |
|
|
|
} |
|
|
|
}, |
|
|
|
@ -446,16 +454,16 @@ async function handleEdit(row: any) { |
|
|
|
state.dialog.title = '编辑审批流配置' |
|
|
|
state.dialog.loading = true |
|
|
|
try { |
|
|
|
const res = await getConfigInfo({ id: row.id }) |
|
|
|
state.dialog.form = { ...res.data } |
|
|
|
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 : []); |
|
|
|
|
|
|
|
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()}`, |
|
|
|
@ -477,7 +485,7 @@ async function handleEdit(row: any) { |
|
|
|
// 查看详情 |
|
|
|
async function handleDetail(row: any) { |
|
|
|
try { |
|
|
|
const res = await getConfigInfo({ id: row.id }) |
|
|
|
const res = await getConfigInfo({id: row.id}) |
|
|
|
state.detailDialog.info = res.data |
|
|
|
state.detailDialog.visible = true |
|
|
|
} catch (error) { |
|
|
|
@ -492,26 +500,27 @@ function handleDelete(row: any) { |
|
|
|
cancelButtonText: '取消', |
|
|
|
type: 'warning' |
|
|
|
}) |
|
|
|
.then(async () => { |
|
|
|
try { |
|
|
|
await deleteConfig({ id: row.id }) |
|
|
|
ElMessage.success('删除成功') |
|
|
|
getList() |
|
|
|
} catch (error) { |
|
|
|
console.error(error) |
|
|
|
} |
|
|
|
}) |
|
|
|
.catch(() => {}) |
|
|
|
.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 }) |
|
|
|
await changeConfigStatus({id: row.id, status: row.status}) |
|
|
|
ElMessage.success( |
|
|
|
row.status |
|
|
|
? '启用成功' |
|
|
|
: '禁用成功' |
|
|
|
row.status |
|
|
|
? '启用成功' |
|
|
|
: '禁用成功' |
|
|
|
) |
|
|
|
} catch (error) { |
|
|
|
console.error(error) |
|
|
|
@ -545,18 +554,18 @@ async function handleSubmit() { |
|
|
|
state.dialog.loading = true |
|
|
|
try { |
|
|
|
// 处理节点数据 |
|
|
|
const formData = { ...state.dialog.form } |
|
|
|
|
|
|
|
const formData = {...state.dialog.form} |
|
|
|
|
|
|
|
// 确保所有节点数据都有有效结构 |
|
|
|
formData.nodes = formData.nodes.map((node: any, index: number) => { |
|
|
|
// 清理无效的key字段,防止API不接受 |
|
|
|
const { key, ...nodeWithoutKey } = node; |
|
|
|
|
|
|
|
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 : ''); |
|
|
|
|
|
|
|
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}`, // 确保节点名称存在 |
|
|
|
@ -613,9 +622,15 @@ onMounted(() => { |
|
|
|
</script> |
|
|
|
|
|
|
|
<style lang="scss" scoped> |
|
|
|
.node-list { |
|
|
|
margin-left: 10px; |
|
|
|
} |
|
|
|
|
|
|
|
.node-item { |
|
|
|
background-color: #f9f9f9; |
|
|
|
transition: all 0.3s; |
|
|
|
padding: 10px; |
|
|
|
margin-top: 10px; |
|
|
|
|
|
|
|
&:hover { |
|
|
|
background-color: #f2f2f2; |
|
|
|
|