Commit e9b40b82 by licheng

chore: 删除bpm模块

parent 35034c4a
import request from '@/config/axios'
// BPM 流程分类 VO
export interface CategoryVO {
id: number // 分类编号
name: string // 分类名
code: string // 分类标志
status: number // 分类状态
sort: number // 分类排序
}
// BPM 流程分类 API
export const CategoryApi = {
// 查询流程分类分页
getCategoryPage: async (params: any) => {
return await request.get({ url: `/bpm/category/page`, params })
},
// 查询流程分类列表
getCategorySimpleList: async () => {
return await request.get({ url: `/bpm/category/simple-list` })
},
// 查询流程分类详情
getCategory: async (id: number) => {
return await request.get({ url: `/bpm/category/get?id=` + id })
},
// 新增流程分类
createCategory: async (data: CategoryVO) => {
return await request.post({ url: `/bpm/category/create`, data })
},
// 修改流程分类
updateCategory: async (data: CategoryVO) => {
return await request.put({ url: `/bpm/category/update`, data })
},
// 批量修改流程分类的排序
updateCategorySortBatch: async (ids: number[]) => {
return await request.put({
url: `/bpm/category/update-sort-batch`,
params: {
ids: ids.join(',')
}
})
},
// 删除流程分类
deleteCategory: async (id: number) => {
return await request.delete({ url: `/bpm/category/delete?id=` + id })
}
}
import request from '@/config/axios'
export const getProcessDefinition = async (id?: string, key?: string) => {
return await request.get({
url: '/bpm/process-definition/get',
params: { id, key }
})
}
export const getProcessDefinitionPage = async (params) => {
return await request.get({
url: '/bpm/process-definition/page',
params
})
}
export const getProcessDefinitionList = async (params) => {
return await request.get({
url: '/bpm/process-definition/list',
params
})
}
export const getSimpleProcessDefinitionList = async () => {
return await request.get({
url: '/bpm/process-definition/simple-list'
})
}
import request from '@/config/axios'
export type FormVO = {
id: number
name: string
conf: string
fields: string[]
status: number
remark: string
createTime: string
}
// 创建工作流的表单定义
export const createForm = async (data: FormVO) => {
return await request.post({
url: '/bpm/form/create',
data: data
})
}
// 更新工作流的表单定义
export const updateForm = async (data: FormVO) => {
return await request.put({
url: '/bpm/form/update',
data: data
})
}
// 删除工作流的表单定义
export const deleteForm = async (id: number) => {
return await request.delete({
url: '/bpm/form/delete?id=' + id
})
}
// 获得工作流的表单定义
export const getForm = async (id: number) => {
return await request.get({
url: '/bpm/form/get?id=' + id
})
}
// 获得工作流的表单定义分页
export const getFormPage = async (params) => {
return await request.get({
url: '/bpm/form/page',
params
})
}
// 获得动态表单的精简列表
export const getFormSimpleList = async () => {
return await request.get({
url: '/bpm/form/simple-list'
})
}
import request from '@/config/axios'
export type LeaveVO = {
id: number
status: number
type: number
reason: string
processInstanceId: string
startTime: string
endTime: string
createTime: string
}
// 创建请假申请
export const createLeave = async (data: LeaveVO) => {
return await request.post({ url: '/bpm/oa/leave/create', data: data })
}
// 获得请假申请
export const getLeave = async (id: number) => {
return await request.get({ url: '/bpm/oa/leave/get?id=' + id })
}
// 获得请假申请分页
export const getLeavePage = async (params: PageParam) => {
return await request.get({ url: '/bpm/oa/leave/page', params })
}
import request from '@/config/axios'
export type ProcessDefinitionVO = {
id: string
version: number
deploymentTIme: string
suspensionState: number
formType?: number
}
export type ModelVO = {
id: number
formName: string
key: string
name: string
description: string
category: string
formType: number
formId: number
formCustomCreatePath: string
formCustomViewPath: string
processDefinition: ProcessDefinitionVO
status: number
remark: string
createTime: string
bpmnXml: string
}
export const getModelList = async (name: string | undefined) => {
return await request.get({ url: '/bpm/model/list', params: { name } })
}
export const getModel = async (id: string) => {
return await request.get({ url: '/bpm/model/get?id=' + id })
}
export const updateModel = async (data: ModelVO) => {
return await request.put({ url: '/bpm/model/update', data: data })
}
// 批量修改流程分类的排序
export const updateModelSortBatch = async (ids: number[]) => {
return await request.put({
url: `/bpm/model/update-sort-batch`,
params: {
ids: ids.join(',')
}
})
}
export const updateModelBpmn = async (data: ModelVO) => {
return await request.put({ url: '/bpm/model/update-bpmn', data: data })
}
// 任务状态修改
export const updateModelState = async (id: number, state: number) => {
const data = {
id: id,
state: state
}
return await request.put({ url: '/bpm/model/update-state', data: data })
}
export const createModel = async (data: ModelVO) => {
return await request.post({ url: '/bpm/model/create', data: data })
}
export const deleteModel = async (id: number) => {
return await request.delete({ url: '/bpm/model/delete?id=' + id })
}
export const deployModel = async (id: number) => {
return await request.post({ url: '/bpm/model/deploy?id=' + id })
}
export const cleanModel = async (id: number) => {
return await request.delete({ url: '/bpm/model/clean?id=' + id })
}
import request from '@/config/axios'
// BPM 流程表达式 VO
export interface ProcessExpressionVO {
id: number // 编号
name: string // 表达式名字
status: number // 表达式状态
expression: string // 表达式
}
// BPM 流程表达式 API
export const ProcessExpressionApi = {
// 查询BPM 流程表达式分页
getProcessExpressionPage: async (params: any) => {
return await request.get({ url: `/bpm/process-expression/page`, params })
},
// 查询BPM 流程表达式详情
getProcessExpression: async (id: number) => {
return await request.get({ url: `/bpm/process-expression/get?id=` + id })
},
// 新增BPM 流程表达式
createProcessExpression: async (data: ProcessExpressionVO) => {
return await request.post({ url: `/bpm/process-expression/create`, data })
},
// 修改BPM 流程表达式
updateProcessExpression: async (data: ProcessExpressionVO) => {
return await request.put({ url: `/bpm/process-expression/update`, data })
},
// 删除BPM 流程表达式
deleteProcessExpression: async (id: number) => {
return await request.delete({ url: `/bpm/process-expression/delete?id=` + id })
},
// 导出BPM 流程表达式 Excel
exportProcessExpression: async (params) => {
return await request.download({ url: `/bpm/process-expression/export-excel`, params })
}
}
\ No newline at end of file
import request from '@/config/axios'
import { ProcessDefinitionVO } from '@/api/bpm/model'
import { NodeType, CandidateStrategy } from '@/components/SimpleProcessDesignerV2/src/consts'
export type Task = {
id: string
name: string
}
export type ProcessInstanceVO = {
id: number
name: string
processDefinitionId: string
category: string
result: number
tasks: Task[]
fields: string[]
status: number
remark: string
businessKey: string
createTime: string
endTime: string
processDefinition?: ProcessDefinitionVO
}
// 用户信息
export type User = {
id: number
nickname: string
avatar: string
}
// 审批任务信息
export type ApprovalTaskInfo = {
id: number
ownerUser: User
assigneeUser: User
status: number
reason: string
signPicUrl: string
}
// 审批节点信息
export type ApprovalNodeInfo = {
id: number
name: string
nodeType: NodeType
candidateStrategy?: CandidateStrategy
status: number
startTime?: Date
endTime?: Date
candidateUsers?: User[]
tasks: ApprovalTaskInfo[]
}
export const getProcessInstanceMyPage = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/my-page', params })
}
export const getProcessInstanceManagerPage = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/manager-page', params })
}
export const createProcessInstance = async (data) => {
return await request.post({ url: '/bpm/process-instance/create', data: data })
}
export const cancelProcessInstanceByStartUser = async (id: number, reason: string) => {
const data = {
id: id,
reason: reason
}
return await request.delete({ url: '/bpm/process-instance/cancel-by-start-user', data: data })
}
export const cancelProcessInstanceByAdmin = async (id: number, reason: string) => {
const data = {
id: id,
reason: reason
}
return await request.delete({ url: '/bpm/process-instance/cancel-by-admin', data: data })
}
export const getProcessInstance = async (id: string) => {
return await request.get({ url: '/bpm/process-instance/get?id=' + id })
}
export const getProcessInstanceCopyPage = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/copy/page', params })
}
// 获取审批详情
export const getApprovalDetail = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/get-approval-detail', params })
}
// 获取下一个执行的流程节点
export const getNextApprovalNodes = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/get-next-approval-nodes', params })
}
// 获取表单字段权限
export const getFormFieldsPermission = async (params: any) => {
return await request.get({ url: '/bpm/process-instance/get-form-fields-permission', params })
}
// 获取流程实例的 BPMN 模型视图
export const getProcessInstanceBpmnModelView = async (id: string) => {
return await request.get({ url: '/bpm/process-instance/get-bpmn-model-view?id=' + id })
}
import request from '@/config/axios'
// BPM 流程监听器 VO
export interface ProcessListenerVO {
id: number // 编号
name: string // 监听器名字
type: string // 监听器类型
status: number // 监听器状态
event: string // 监听事件
valueType: string // 监听器值类型
value: string // 监听器值
}
// BPM 流程监听器 API
export const ProcessListenerApi = {
// 查询流程监听器分页
getProcessListenerPage: async (params: any) => {
return await request.get({ url: `/bpm/process-listener/page`, params })
},
// 查询流程监听器详情
getProcessListener: async (id: number) => {
return await request.get({ url: `/bpm/process-listener/get?id=` + id })
},
// 新增流程监听器
createProcessListener: async (data: ProcessListenerVO) => {
return await request.post({ url: `/bpm/process-listener/create`, data })
},
// 修改流程监听器
updateProcessListener: async (data: ProcessListenerVO) => {
return await request.put({ url: `/bpm/process-listener/update`, data })
},
// 删除流程监听器
deleteProcessListener: async (id: number) => {
return await request.delete({ url: `/bpm/process-listener/delete?id=` + id })
}
}
import request from '@/config/axios'
export const updateBpmSimpleModel = async (data) => {
return await request.post({
url: '/bpm/model/simple/update',
data: data
})
}
export const getBpmSimpleModel = async (id) => {
return await request.get({
url: '/bpm/model/simple/get?id=' + id
})
}
import request from '@/config/axios'
/**
* 任务状态枚举
*/
export enum TaskStatusEnum {
/**
* 未开始
*/
NOT_START = -1,
/**
* 待审批
*/
WAIT = 0,
/**
* 审批中
*/
RUNNING = 1,
/**
* 审批通过
*/
APPROVE = 2,
/**
* 审批不通过
*/
REJECT = 3,
/**
* 已取消
*/
CANCEL = 4,
/**
* 已退回
*/
RETURN = 5,
/**
* 审批通过中
*/
APPROVING = 7
}
export const getTaskTodoPage = async (params: any) => {
return await request.get({ url: '/bpm/task/todo-page', params })
}
export const getTaskDonePage = async (params: any) => {
return await request.get({ url: '/bpm/task/done-page', params })
}
export const getTaskManagerPage = async (params: any) => {
return await request.get({ url: '/bpm/task/manager-page', params })
}
export const approveTask = async (data: any) => {
return await request.put({ url: '/bpm/task/approve', data })
}
export const rejectTask = async (data: any) => {
return await request.put({ url: '/bpm/task/reject', data })
}
export const getTaskListByProcessInstanceId = async (processInstanceId: string) => {
return await request.get({
url: '/bpm/task/list-by-process-instance-id?processInstanceId=' + processInstanceId
})
}
// 获取所有可退回的节点
export const getTaskListByReturn = async (id: string) => {
return await request.get({ url: '/bpm/task/list-by-return', params: { id } })
}
// 退回
export const returnTask = async (data: any) => {
return await request.put({ url: '/bpm/task/return', data })
}
// 委派
export const delegateTask = async (data: any) => {
return await request.put({ url: '/bpm/task/delegate', data })
}
// 转派
export const transferTask = async (data: any) => {
return await request.put({ url: '/bpm/task/transfer', data })
}
// 加签
export const signCreateTask = async (data: any) => {
return await request.put({ url: '/bpm/task/create-sign', data })
}
// 减签
export const signDeleteTask = async (data: any) => {
return await request.delete({ url: '/bpm/task/delete-sign', data })
}
// 抄送
export const copyTask = async (data: any) => {
return await request.put({ url: '/bpm/task/copy', data })
}
// 获取我的待办任务
export const myTodoTask = async (processInstanceId: string) => {
return await request.get({ url: '/bpm/task/my-todo?processInstanceId=' + processInstanceId })
}
// 获取减签任务列表
export const getChildrenTaskList = async (id: string) => {
return await request.get({ url: '/bpm/task/list-by-parent-task-id?parentTaskId=' + id })
}
import request from '@/config/axios'
export type UserGroupVO = {
id: number
name: string
description: string
userIds: number[]
status: number
remark: string
createTime: string
}
// 创建用户组
export const createUserGroup = async (data: UserGroupVO) => {
return await request.post({
url: '/bpm/user-group/create',
data: data
})
}
// 更新用户组
export const updateUserGroup = async (data: UserGroupVO) => {
return await request.put({
url: '/bpm/user-group/update',
data: data
})
}
// 删除用户组
export const deleteUserGroup = async (id: number) => {
return await request.delete({ url: '/bpm/user-group/delete?id=' + id })
}
// 获得用户组
export const getUserGroup = async (id: number) => {
return await request.get({ url: '/bpm/user-group/get?id=' + id })
}
// 获得用户组分页
export const getUserGroupPage = async (params) => {
return await request.get({ url: '/bpm/user-group/page', params })
}
// 获取用户组精简信息列表
export const getUserGroupSimpleList = async (): Promise<UserGroupVO[]> => {
return await request.get({ url: '/bpm/user-group/simple-list' })
}
...@@ -244,117 +244,6 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -244,117 +244,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
} }
}, },
{ {
path: '/bpm',
component: Layout,
name: 'bpm',
meta: {
hidden: true
},
children: [
{
path: 'manager/form/edit',
component: () => import('@/views/bpm/form/editor/index.vue'),
name: 'BpmFormEditor',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '设计流程表单',
activeMenu: '/bpm/manager/form'
}
},
{
path: 'manager/definition',
component: () => import('@/views/bpm/model/definition/index.vue'),
name: 'BpmProcessDefinition',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '流程定义',
activeMenu: '/bpm/manager/model'
}
},
{
path: 'process-instance/detail',
component: () => import('@/views/bpm/processInstance/detail/index.vue'),
name: 'BpmProcessInstanceDetail',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '流程详情',
activeMenu: '/bpm/task/my'
},
props: (route) => ({
id: route.query.id,
taskId: route.query.taskId,
activityId: route.query.activityId
})
},
{
path: 'process-instance/report',
component: () => import('@/views/bpm/processInstance/report/index.vue'),
name: 'BpmProcessInstanceReport',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '数据报表',
activeMenu: '/bpm/manager/model'
}
},
{
path: 'oa/leave/create',
component: () => import('@/views/bpm/oa/leave/create.vue'),
name: 'OALeaveCreate',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '发起 OA 请假',
activeMenu: '/bpm/oa/leave'
}
},
{
path: 'oa/leave/detail',
component: () => import('@/views/bpm/oa/leave/detail.vue'),
name: 'OALeaveDetail',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '查看 OA 请假',
activeMenu: '/bpm/oa/leave'
}
},
{
path: 'manager/model/create',
component: () => import('@/views/bpm/model/form/index.vue'),
name: 'BpmModelCreate',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '创建流程',
activeMenu: '/bpm/manager/model'
}
},
{
path: 'manager/model/:type/:id',
component: () => import('@/views/bpm/model/form/index.vue'),
name: 'BpmModelUpdate',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '修改流程',
activeMenu: '/bpm/manager/model'
}
}
]
},
{
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
component: () => import('@/views/Error/404.vue'), component: () => import('@/views/Error/404.vue'),
name: '', name: '',
......
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="分类名" prop="name">
<el-input v-model="formData.name" placeholder="请输入分类名" />
</el-form-item>
<el-form-item label="分类标志" prop="code">
<el-input v-model="formData.code" placeholder="请输入分类标志" />
</el-form-item>
<el-form-item label="分类描述" prop="description">
<el-input v-model="formData.description" type="textarea" placeholder="请输入分类描述" />
</el-form-item>
<el-form-item label="分类状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="分类排序" prop="sort">
<el-input-number
v-model="formData.sort"
placeholder="请输入分类排序"
class="!w-1/1"
:precision="0"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import { CommonStatusEnum } from '@/utils/constants'
/** BPM 流程分类 表单 */
defineOptions({ name: 'CategoryForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
id: undefined,
name: undefined,
code: undefined,
description: undefined,
status: CommonStatusEnum.ENABLE,
sort: undefined
})
const formRules = reactive({
name: [{ required: true, message: '分类名不能为空', trigger: 'blur' }],
code: [{ required: true, message: '分类标志不能为空', trigger: 'blur' }],
status: [{ required: true, message: '分类状态不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await CategoryApi.getCategory(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as CategoryVO
if (formType.value === 'create') {
await CategoryApi.createCategory(data)
message.success(t('common.createSuccess'))
} else {
await CategoryApi.updateCategory(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
code: undefined,
description: undefined,
status: CommonStatusEnum.ENABLE,
sort: undefined
}
formRef.value?.resetFields()
}
</script>
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="分类名" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入分类名"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="分类标志" prop="code">
<el-input
v-model="queryParams.code"
placeholder="请输入分类标志"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="分类状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择分类状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['bpm:category:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="分类编号" align="center" prop="id" />
<el-table-column label="分类名" align="center" prop="name" />
<el-table-column label="分类标志" align="center" prop="code" />
<el-table-column label="分类描述" align="center" prop="description" />
<el-table-column label="分类状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="分类排序" align="center" prop="sort" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['bpm:category:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['bpm:category:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<CategoryForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import CategoryForm from './CategoryForm.vue'
/** BPM 流程分类 列表 */
defineOptions({ name: 'BpmCategory' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<CategoryVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
code: undefined,
status: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CategoryApi.getCategoryPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await CategoryApi.deleteCategory(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<template>
<ContentWrap :body-style="{ padding: '0px' }" class="!mb-0">
<!-- 表单设计器 -->
<div
class="h-[calc(100vh-var(--top-tool-height)-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-2px)]"
>
<fc-designer class="my-designer" ref="designer" :config="designerConfig">
<template #handle>
<el-button size="small" type="success" plain @click="handleSave">
<Icon class="mr-5px" icon="ep:plus" />
保存
</el-button>
</template>
</fc-designer>
</div>
</ContentWrap>
<!-- 表单保存的弹窗 -->
<Dialog v-model="dialogVisible" title="保存表单" width="600">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
<el-form-item label="表单名" prop="name">
<el-input v-model="formData.name" placeholder="请输入表单名" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注" type="textarea" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import * as FormApi from '@/api/bpm/form'
import FcDesigner from '@form-create/designer'
import { encodeConf, encodeFields, setConfAndFields } from '@/utils/formCreate'
import { useTagsViewStore } from '@/store/modules/tagsView'
import { useFormCreateDesigner } from '@/components/FormCreate'
import { useRoute } from 'vue-router'
defineOptions({ name: 'BpmFormEditor' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息
const route = useRoute() // 路由
const { push, currentRoute } = useRouter() // 路由
const { query } = useRoute() // 路由信息
const { delView } = useTagsViewStore() // 视图操作
// 表单设计器配置
const designerConfig = ref({
switchType: [], // 是否可以切换组件类型,或者可以相互切换的字段
autoActive: true, // 是否自动选中拖入的组件
useTemplate: false, // 是否生成vue2语法的模板组件
formOptions: {
form: {
labelWidth: '100px' // 设置默认的 label 宽度为 100px
}
}, // 定义表单配置默认值
fieldReadonly: false, // 配置field是否可以编辑
hiddenDragMenu: false, // 隐藏拖拽操作按钮
hiddenDragBtn: false, // 隐藏拖拽按钮
hiddenMenu: [], // 隐藏部分菜单
hiddenItem: [], // 隐藏部分组件
hiddenItemConfig: {}, // 隐藏组件的部分配置项
disabledItemConfig: {}, // 禁用组件的部分配置项
showSaveBtn: false, // 是否显示保存按钮
showConfig: true, // 是否显示右侧的配置界面
showBaseForm: true, // 是否显示组件的基础配置表单
showControl: true, // 是否显示组件联动
showPropsForm: true, // 是否显示组件的属性配置表单
showEventForm: true, // 是否显示组件的事件配置表单
showValidateForm: true, // 是否显示组件的验证配置表单
showFormConfig: true, // 是否显示表单配置
showInputData: true, // 是否显示录入按钮
showDevice: true, // 是否显示多端适配选项
appendConfigData: [] // 定义渲染规则所需的formData
})
const designer = ref() // 表单设计器
useFormCreateDesigner(designer) // 表单设计器增强
const dialogVisible = ref(false) // 弹窗是否展示
const formLoading = ref(false) // 表单的加载中:提交的按钮禁用
const formData = ref({
name: '',
status: CommonStatusEnum.ENABLE,
remark: ''
})
const formRules = reactive({
name: [{ required: true, message: '表单名不能为空', trigger: 'blur' }],
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 处理保存按钮 */
const handleSave = () => {
dialogVisible.value = true
}
/** 提交表单 */
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as FormApi.FormVO
data.conf = encodeConf(designer) // 表单配置
data.fields = encodeFields(designer) // 表单字段
if (!data.id) {
await FormApi.createForm(data)
message.success(t('common.createSuccess'))
} else {
await FormApi.updateForm(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
close()
} finally {
formLoading.value = false
}
}
/** 关闭按钮 */
const close = () => {
delView(unref(currentRoute))
push('/bpm/manager/form')
}
/** 初始化 **/
onMounted(async () => {
// 场景一:新增表单
const id = query.id as unknown as number
if (!id) {
return
}
// 场景二:修改表单
const data = await FormApi.getForm(id)
formData.value = data
setConfAndFields(designer, data.conf, data.fields)
if (route.query.type !== 'copy') {
return
}
// 场景三: 复制表单
const { id: foo, ...copied } = data
formData.value = copied
formData.value.name += '_copy'
})
</script>
<style>
.my-designer {
._fc-l,
._fc-m,
._fc-r {
border-top: none;
}
}
</style>
<template>
<doc-alert title="审批接入(流程表单)" url="https://doc.iocoder.cn/bpm/use-bpm-form/" />
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="68px"
>
<el-form-item label="表单名" prop="name">
<el-input
v-model="queryParams.name"
class="!w-240px"
clearable
placeholder="请输入表单名"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
<el-button v-hasPermi="['bpm:form:create']" plain type="primary" @click="openForm">
<Icon class="mr-5px" icon="ep:plus" />
新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="编号" prop="id" />
<el-table-column align="center" label="表单名" prop="name" />
<el-table-column align="center" label="状态" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column align="center" label="备注" prop="remark" />
<el-table-column
:formatter="dateFormatter"
align="center"
label="创建时间"
prop="createTime"
/>
<el-table-column align="center" label="操作">
<template #default="scope">
<el-button
v-hasPermi="['bpm:form:update']"
link
type="primary"
@click="openForm('copy', scope.row.id)"
>
复制
</el-button>
<el-button
v-hasPermi="['bpm:form:update']"
link
type="primary"
@click="openForm('update', scope.row.id)"
>
编辑
</el-button>
<el-button v-hasPermi="['bpm:form:query']" link @click="openDetail(scope.row.id)">
详情
</el-button>
<el-button
v-hasPermi="['bpm:form:delete']"
link
type="danger"
@click="handleDelete(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单详情的弹窗 -->
<Dialog v-model="detailVisible" title="表单详情" width="800">
<form-create :option="detailData.option" :rule="detailData.rule" />
</Dialog>
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as FormApi from '@/api/bpm/form'
import { setConfAndFields2 } from '@/utils/formCreate'
defineOptions({ name: 'BpmForm' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const { currentRoute, push } = useRouter() // 路由
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null
})
const queryFormRef = ref() // 搜索的表单
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await FormApi.getFormPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const openForm = (type: string, id?: number) => {
const toRouter: { name: string; query: { type: string; id?: number } } = {
name: 'BpmFormEditor',
query: {
type
}
}
console.log(typeof id)
// 表单新建的时候id传的是event需要排除
if (typeof id === 'number' || typeof id === 'string') {
toRouter.query.id = id
}
push(toRouter)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await FormApi.deleteForm(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 详情操作 */
const detailVisible = ref(false)
const detailData = ref({
rule: [],
option: {}
})
const openDetail = async (rowId: number) => {
// 设置表单
const data = await FormApi.getForm(rowId)
setConfAndFields2(detailData, data.conf, data.fields)
// 弹窗打开
detailVisible.value = true
}
/**表单保存返回后重新加载列表 */
watch(
() => currentRoute.value,
() => {
getList()
},
{
immediate: true
}
)
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="100px"
>
<el-form-item label="组名" prop="name">
<el-input v-model="formData.name" placeholder="请输入组名" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="formData.description" placeholder="请输入描述" type="textarea" />
</el-form-item>
<el-form-item label="成员" prop="userIds">
<el-select v-model="formData.userIds" multiple placeholder="请选择成员">
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import * as UserGroupApi from '@/api/bpm/userGroup'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'UserGroupForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
id: undefined,
name: undefined,
description: undefined,
userIds: undefined,
status: CommonStatusEnum.ENABLE
})
const formRules = reactive({
name: [{ required: true, message: '组名不能为空', trigger: 'blur' }],
description: [{ required: true, message: '描述不能为空', trigger: 'blur' }],
userIds: [{ required: true, message: '成员不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const userList = ref<any[]>([]) // 用户列表
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await UserGroupApi.getUserGroup(id)
} finally {
formLoading.value = false
}
}
// 加载用户列表
userList.value = await UserApi.getSimpleUserList()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as UserGroupApi.UserGroupVO
if (formType.value === 'create') {
await UserGroupApi.createUserGroup(data)
message.success(t('common.createSuccess'))
} else {
await UserGroupApi.updateUserGroup(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
description: undefined,
userIds: undefined,
status: CommonStatusEnum.ENABLE
}
formRef.value?.resetFields()
}
</script>
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="组名" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入组名"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['bpm:user-group:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="组名" align="center" prop="name" />
<el-table-column label="描述" align="center" prop="description" />
<el-table-column label="成员" align="center">
<template #default="scope">
<span v-for="userId in scope.row.userIds" :key="userId" class="pr-5px">
{{ userList.find((user) => user.id === userId)?.nickname }}
</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['bpm:user-group:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['bpm:user-group:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<UserGroupForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as UserGroupApi from '@/api/bpm/userGroup'
import * as UserApi from '@/api/system/user'
import UserGroupForm from './UserGroupForm.vue'
import { UserVO } from '@/api/system/user'
defineOptions({ name: 'BpmUserGroup' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
status: null,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const userList = ref<UserVO[]>([]) // 用户列表
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await UserGroupApi.getUserGroupPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await UserGroupApi.deleteUserGroup(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 初始化 **/
onMounted(async () => {
await getList()
// 加载用户列表
userList.value = await UserApi.getSimpleUserList()
})
</script>
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="定义编号" align="center" prop="id" min-width="250" />
<el-table-column label="流程名称" align="center" prop="name" min-width="150" />
<el-table-column label="流程图标" align="center" min-width="50">
<template #default="{ row }">
<el-image v-if="row.icon" :src="row.icon" class="h-24px w-24pxrounded" />
</template>
</el-table-column>
<el-table-column label="可见范围" prop="startUserIds" min-width="100">
<template #default="{ row }">
<el-text v-if="!row.startUsers?.length"> 全部可见 </el-text>
<el-text v-else-if="row.startUsers.length === 1">
{{ row.startUsers[0].nickname }}
</el-text>
<el-text v-else>
<el-tooltip
class="box-item"
effect="dark"
placement="top"
:content="row.startUsers.map((user: any) => user.nickname).join('、')"
>
{{ row.startUsers[0].nickname }}{{ row.startUsers.length }} 人可见
</el-tooltip>
</el-text>
</template>
</el-table-column>
<el-table-column label="流程类型" prop="modelType" min-width="120">
<template #default="{ row }">
<dict-tag :value="row.modelType" :type="DICT_TYPE.BPM_MODEL_TYPE" />
</template>
</el-table-column>
<el-table-column label="表单信息" prop="formType" min-width="150">
<template #default="scope">
<el-button
v-if="scope.row.formType === BpmModelFormType.NORMAL"
type="primary"
link
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formName }}</span>
</el-button>
<el-button
v-else-if="scope.row.formType === BpmModelFormType.CUSTOM"
type="primary"
link
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formCustomCreatePath }}</span>
</el-button>
<label v-else>暂无表单</label>
</template>
</el-table-column>
<el-table-column label="流程版本" align="center" min-width="80">
<template #default="scope">
<el-tag>v{{ scope.row.version }}</el-tag>
</template>
</el-table-column>
<el-table-column
label="部署时间"
align="center"
prop="deploymentTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openModelForm(scope.row.id)"
v-hasPermi="['bpm:model:update']"
>
恢复
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 弹窗:表单详情 -->
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
</Dialog>
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as DefinitionApi from '@/api/bpm/definition'
import { setConfAndFields2 } from '@/utils/formCreate'
import { DICT_TYPE } from '@/utils/dict'
import { BpmModelFormType } from '@/utils/constants'
defineOptions({ name: 'BpmProcessDefinition' })
const { push } = useRouter() // 路由
const { query } = useRoute() // 查询参数
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
key: query.key
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await DefinitionApi.getProcessDefinitionPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 流程表单的详情按钮操作 */
const formDetailVisible = ref(false)
const formDetailPreview = ref({
rule: [],
option: {}
})
const handleFormDetail = async (row: any) => {
if (row.formType == BpmModelFormType.NORMAL) {
// 设置表单
setConfAndFields2(formDetailPreview, row.formConf, row.formFields)
// 弹窗打开
formDetailVisible.value = true
} else {
await push({
path: row.formCustomCreatePath
})
}
}
/** 恢复流程模型弹窗 */
const openModelForm = async (id?: number) => {
await push({
name: 'BpmModelUpdate',
params: { id, type: 'definition' }
})
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<style lang="scss" scoped>
.flow-icon {
display: flex;
width: 38px;
height: 38px;
margin-right: 10px;
background-color: var(--el-color-primary);
border-radius: 0.25rem;
align-items: center;
justify-content: center;
}
</style>
<template>
<el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
<el-form-item label="表单类型" prop="formType" class="mb-20px">
<el-radio-group v-model="modelData.formType">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="modelData.formType === BpmModelFormType.NORMAL" label="流程表单" prop="formId">
<el-select v-model="modelData.formId" clearable style="width: 100%">
<el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
</el-select>
</el-form-item>
<el-form-item v-if="modelData.formType === BpmModelFormType.CUSTOM" label="表单提交路由" prop="formCustomCreatePath">
<el-input
v-model="modelData.formCustomCreatePath"
placeholder="请输入表单提交路由"
style="width: 330px"
/>
<el-tooltip
class="item"
content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create.vue"
effect="light"
placement="top"
>
<Icon icon="ep:question" class="ml-5px" />
</el-tooltip>
</el-form-item>
<el-form-item v-if="modelData.formType === BpmModelFormType.CUSTOM" label="表单查看地址" prop="formCustomViewPath">
<el-input
v-model="modelData.formCustomViewPath"
placeholder="请输入表单查看的组件地址"
style="width: 330px"
/>
<el-tooltip
class="item"
content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
effect="light"
placement="top"
>
<Icon icon="ep:question" class="ml-5px" />
</el-tooltip>
</el-form-item>
<!-- 表单预览 -->
<div
v-if="modelData.formType === BpmModelFormType.NORMAL && modelData.formId && formPreview.rule.length > 0"
class="mt-20px"
>
<div class="flex items-center mb-15px">
<div class="h-15px w-4px bg-[#1890ff] mr-10px"></div>
<span class="text-15px font-bold">表单预览</span>
</div>
<form-create
v-model="formPreview.formData"
:rule="formPreview.rule"
:option="formPreview.option"
/>
</div>
</el-form>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as FormApi from '@/api/bpm/form'
import { setConfAndFields2 } from '@/utils/formCreate'
import { BpmModelFormType } from '@/utils/constants'
const props = defineProps({
formList: {
type: Array,
required: true
}
})
const formRef = ref()
// 创建本地数据副本
const modelData = defineModel<any>()
// 表单预览数据
const formPreview = ref({
formData: {},
rule: [],
option: {
submitBtn: false,
resetBtn: false,
formData: {}
}
})
// 监听表单ID变化,加载表单数据
watch(
() => modelData.value.formId,
async (newFormId) => {
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
const data = await FormApi.getForm(newFormId)
setConfAndFields2(formPreview.value, data.conf, data.fields)
// 设置只读
formPreview.value.rule.forEach((item: any) => {
item.props = { ...item.props, disabled: true }
})
} else {
formPreview.value.rule = []
}
},
{ immediate: true }
)
const rules = {
formType: [{ required: true, message: '表单类型不能为空', trigger: 'blur' }],
formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }]
}
/** 表单校验 */
const validate = async () => {
await formRef.value?.validate()
}
defineExpose({
validate
})
</script>
<template>
<!-- BPMN设计器 -->
<template v-if="modelData.type === BpmModelType.BPMN">
<BpmModelEditor
v-if="showDesigner"
:model-id="modelData.id"
:model-key="modelData.key"
:model-name="modelData.name"
@success="handleDesignSuccess"
/>
</template>
<!-- Simple设计器 -->
<template v-else>
<SimpleModelDesign
v-if="showDesigner"
:model-id="modelData.id"
:model-key="modelData.key"
:model-name="modelData.name"
:start-user-ids="modelData.startUserIds"
:start-dept-ids="modelData.startDeptIds"
@success="handleDesignSuccess"
/>
</template>
</template>
<script lang="ts" setup>
import { BpmModelType } from '@/utils/constants'
import BpmModelEditor from './editor/index.vue'
import SimpleModelDesign from '../../simple/SimpleModelDesign.vue'
// 创建本地数据副本
const modelData = defineModel<any>()
const processData = inject('processData') as Ref
/** 表单校验 */
const validate = async () => {
try {
// 获取最新的流程数据
if (!processData.value) {
throw new Error('请设计流程')
}
return true
} catch (error) {
throw error
}
}
/** 处理设计器保存成功 */
const handleDesignSuccess = async (data?: any) => {
if (data) {
// 创建新的对象以触发响应式更新
const newModelData = {
...modelData.value,
bpmnXml: modelData.value.type === BpmModelType.BPMN ? data : null,
simpleModel: modelData.value.type === BpmModelType.BPMN ? null : data
}
// 使用emit更新父组件的数据
await nextTick()
//更新表单的模型数据部分
modelData.value = newModelData
}
}
/** 是否显示设计器 */
const showDesigner = computed(() => {
return Boolean(modelData.value?.key && modelData.value?.name)
})
defineExpose({
validate
})
</script>
<template>
<ContentWrap>
<!-- 流程设计器,负责绘制流程等 -->
<MyProcessDesigner
key="designer"
v-model="xmlString"
:value="xmlString"
v-bind="controlForm"
keyboard
ref="processDesigner"
@init-finished="initModeler"
:additionalModel="controlForm.additionalModel"
:model="model"
@save="save"
:process-id="modelKey"
:process-name="modelName"
/>
<!-- 流程属性器,负责编辑每个流程节点的属性 -->
<MyProcessPenal
v-if="modeler"
key="penal"
:bpmnModeler="modeler"
:prefix="controlForm.prefix"
class="process-panel"
:model="model"
/>
</ContentWrap>
</template>
<script lang="ts" setup>
import { MyProcessDesigner, MyProcessPenal } from '@/components/bpmnProcessDesigner/package'
// 自定义元素选中时的弹出菜单(修改 默认任务 为 用户任务)
import CustomContentPadProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/content-pad'
// 自定义左侧菜单(修改 默认任务 为 用户任务)
import CustomPaletteProvider from '@/components/bpmnProcessDesigner/package/designer/plugins/palette'
import * as ModelApi from '@/api/bpm/model'
import { BpmModelFormType } from '@/utils/constants'
import * as FormApi from '@/api/bpm/form'
defineOptions({ name: 'BpmModelEditor' })
defineProps<{
modelId?: string
modelKey: string
modelName: string
value?: string
}>()
const emit = defineEmits(['success', 'init-finished'])
const message = useMessage() // 国际化
// 表单信息
const formFields = ref<string[]>([])
// 表单类型,暂仅限流程表单
const formType = ref(BpmModelFormType.NORMAL)
provide('formFields', formFields)
provide('formType', formType)
// 注入流程数据
const xmlString = inject('processData') as Ref
// 注入模型数据
const modelData = inject('modelData') as Ref
const modeler = shallowRef() // BPMN Modeler
const processDesigner = ref()
const controlForm = ref({
simulation: true,
labelEditing: false,
labelVisible: false,
prefix: 'flowable',
headerButtonSize: 'mini',
additionalModel: [CustomContentPadProvider, CustomPaletteProvider]
})
const model = ref<ModelApi.ModelVO>() // 流程模型的信息
/** 初始化 modeler */
const initModeler = async (item: any) => {
// 先初始化模型数据
model.value = modelData.value
modeler.value = item
}
/** 添加/修改模型 */
const save = async (bpmnXml: string) => {
try {
xmlString.value = bpmnXml
emit('success', bpmnXml)
} catch (error) {
console.error('保存失败:', error)
message.error('保存失败')
}
}
/** 监听表单 ID 变化,加载表单数据 */
watch(
() => modelData.value.formId,
async (newFormId) => {
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
const data = await FormApi.getForm(newFormId)
formFields.value = data.fields
} else {
formFields.value = []
}
},
{ immediate: true }
)
// 在组件卸载时清理
onBeforeUnmount(() => {
modeler.value = null
// 清理全局实例
const w = window as any
if (w.bpmnInstances) {
w.bpmnInstances = null
}
})
</script>
<style lang="scss">
.process-panel__container {
position: absolute;
top: 172px;
right: 70px;
}
</style>
<template>
<ContentWrap>
<div class="flex justify-between pl-20px items-center">
<h3 class="font-extrabold">流程模型</h3>
<!-- 搜索工作栏 -->
<el-form
v-if="!isCategorySorting"
class="-mb-15px flex mr-10px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
@submit.prevent
>
<el-form-item prop="name" class="ml-auto">
<el-input
v-model="queryParams.name"
placeholder="搜索流程"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
>
<template #prefix>
<Icon icon="ep:search" class="mx-10px" />
</template>
</el-input>
</el-form-item>
<!-- 右上角:新建模型、更多操作 -->
<el-form-item>
<el-button type="primary" @click="openForm('create')" v-hasPermi="['bpm:model:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新建模型
</el-button>
</el-form-item>
<el-form-item>
<el-dropdown @command="(command) => handleCommand(command)" placement="bottom-end">
<el-button class="w-30px" plain>
<Icon icon="ep:setting" />
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="handleCategoryAdd">
<Icon icon="ep:circle-plus" :size="13" class="mr-5px" />
新建分类
</el-dropdown-item>
<el-dropdown-item command="handleCategorySort">
<Icon icon="fa:sort-amount-desc" :size="13" class="mr-5px" />
分类排序
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-form-item>
</el-form>
<div class="mr-20px" v-else>
<el-button @click="handleCategorySortCancel"> 取 消 </el-button>
<el-button type="primary" @click="handleCategorySortSubmit"> 保存排序 </el-button>
</div>
</div>
<el-divider />
<!-- 按照分类,展示其所属的模型列表 -->
<div class="px-15px">
<draggable
:disabled="!isCategorySorting"
v-model="categoryGroup"
item-key="id"
:animation="400"
>
<template #item="{ element }">
<ContentWrap
class="rounded-lg transition-all duration-300 ease-in-out hover:shadow-xl"
v-loading="loading"
:body-style="{ padding: 0 }"
:key="element.id"
>
<CategoryDraggableModel
:isCategorySorting="isCategorySorting"
:categoryInfo="element"
@success="getList"
/>
</ContentWrap>
</template>
</draggable>
</div>
</ContentWrap>
<!-- 表单弹窗:添加分类 -->
<CategoryForm ref="categoryFormRef" @success="getList" />
<!-- 弹窗:表单详情 -->
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
</Dialog>
</template>
<script lang="ts" setup>
import draggable from 'vuedraggable'
import { CategoryApi } from '@/api/bpm/category'
import * as ModelApi from '@/api/bpm/model'
import CategoryForm from '../category/CategoryForm.vue'
import { cloneDeep } from 'lodash-es'
import CategoryDraggableModel from './CategoryDraggableModel.vue'
defineOptions({ name: 'BpmModel' })
const { push } = useRouter()
const message = useMessage() // 消息弹窗
const loading = ref(true) // 列表的加载中
const isCategorySorting = ref(false) // 是否 category 正处于排序状态
const queryParams = reactive({
name: undefined
})
const queryFormRef = ref() // 搜索的表单
const categoryGroup: any = ref([]) // 按照 category 分组的数据
const originalData: any = ref([]) // 原始数据
/** 搜索按钮操作 */
const handleQuery = () => {
getList()
}
/** 添加/修改操作 */
const openForm = (type: string, id?: number) => {
if (type === 'create') {
push({ name: 'BpmModelCreate' })
} else {
push({
name: 'BpmModelUpdate',
params: { id }
})
}
}
/** 流程表单的详情按钮操作 */
const formDetailVisible = ref(false)
const formDetailPreview = ref({
rule: [],
option: {}
})
/** 右上角设置按钮 */
const handleCommand = (command: string) => {
switch (command) {
case 'handleCategoryAdd':
handleCategoryAdd()
break
case 'handleCategorySort':
handleCategorySort()
break
default:
break
}
}
/** 新建分类 */
const categoryFormRef = ref()
const handleCategoryAdd = () => {
categoryFormRef.value.open('create')
}
/** 分类排序的提交 */
const handleCategorySort = () => {
// 保存初始数据
originalData.value = cloneDeep(categoryGroup.value)
isCategorySorting.value = true
}
/** 分类排序的取消 */
const handleCategorySortCancel = () => {
// 恢复初始数据
categoryGroup.value = cloneDeep(originalData.value)
isCategorySorting.value = false
}
/** 分类排序的保存 */
const handleCategorySortSubmit = async () => {
// 保存排序
const ids = categoryGroup.value.map((item: any) => item.id)
await CategoryApi.updateCategorySortBatch(ids)
// 刷新列表
isCategorySorting.value = false
message.success('排序分类成功')
await getList()
}
/** 加载数据 */
const getList = async () => {
loading.value = true
try {
// 查询模型 + 分裂的列表
const modelList = await ModelApi.getModelList(queryParams.name)
const categoryList = await CategoryApi.getCategorySimpleList()
// 按照 category 聚合
// 注意:必须一次性赋值给 categoryGroup,否则每次操作后,列表会重新渲染,滚动条的位置会偏离!!!
categoryGroup.value = categoryList.map((category: any) => ({
...category,
modelList: modelList.filter((model: any) => model.categoryName == category.name)
}))
} finally {
loading.value = false
}
}
/** 初始化 **/
onActivated(() => {
getList()
})
</script>
<style lang="scss" scoped>
:deep() {
.el-table--fit .el-table__inner-wrapper:before {
height: 0;
}
.el-card {
border-radius: 8px;
}
.el-form--inline .el-form-item {
margin-right: 10px;
}
.el-divider--horizontal {
margin-top: 6px;
}
}
</style>
<template>
<el-row :gutter="20">
<el-col :span="16">
<ContentWrap title="申请信息">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
>
<el-form-item label="请假类型" prop="type">
<el-select v-model="formData.type" clearable placeholder="请选择请假类型">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="开始时间" prop="startTime">
<el-date-picker
v-model="formData.startTime"
clearable
placeholder="请选择开始时间"
type="datetime"
value-format="x"
/>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker
v-model="formData.endTime"
clearable
placeholder="请选择结束时间"
type="datetime"
value-format="x"
/>
</el-form-item>
<el-form-item label="原因" prop="reason">
<el-input v-model="formData.reason" placeholder="请输入请假原因" type="textarea" />
</el-form-item>
<el-form-item>
<el-button :disabled="formLoading" type="primary" @click="submitForm">
确 定
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
</el-col>
<!-- 审批相关:流程信息 -->
<el-col :span="8">
<ContentWrap title="审批流程" :bodyStyle="{ padding: '0 20px 0' }">
<ProcessInstanceTimeline
ref="timelineRef"
:activity-nodes="activityNodes"
:show-status-icon="false"
@select-user-confirm="selectUserConfirm"
/>
</ContentWrap>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as LeaveApi from '@/api/bpm/leave'
import { useTagsViewStore } from '@/store/modules/tagsView'
// 审批相关:import
import * as DefinitionApi from '@/api/bpm/definition'
import ProcessInstanceTimeline from '@/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { CandidateStrategy, NodeId } from '@/components/SimpleProcessDesignerV2/src/consts'
import { ApprovalNodeInfo } from '@/api/bpm/processInstance'
defineOptions({ name: 'BpmOALeaveCreate' })
const message = useMessage() // 消息弹窗
const { delView } = useTagsViewStore() // 视图操作
const { push, currentRoute } = useRouter() // 路由
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formData = ref({
type: undefined,
reason: undefined,
startTime: undefined,
endTime: undefined
})
const formRules = reactive({
type: [{ required: true, message: '请假类型不能为空', trigger: 'blur' }],
reason: [{ required: true, message: '请假原因不能为空', trigger: 'change' }],
startTime: [{ required: true, message: '请假开始时间不能为空', trigger: 'change' }],
endTime: [{ required: true, message: '请假结束时间不能为空', trigger: 'change' }]
})
const formRef = ref() // 表单 Ref
// 审批相关:变量
const processDefineKey = 'oa_leave' // 流程定义 Key
const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
const tempStartUserSelectAssignees = ref({}) // 历史发起人选择审批人的数据,用于每次表单变更时,临时保存
const activityNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([]) // 审批节点信息
const processDefinitionId = ref('')
/** 提交表单 */
const submitForm = async () => {
// 1.1 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 1.2 审批相关:校验指定审批人
if (startUserSelectTasks.value?.length > 0) {
for (const userTask of startUserSelectTasks.value) {
if (
Array.isArray(startUserSelectAssignees.value[userTask.id]) &&
startUserSelectAssignees.value[userTask.id].length === 0
) {
return message.warning(`请选择${userTask.name}的审批人`)
}
}
}
// 2. 提交请求
formLoading.value = true
try {
const data = { ...formData.value } as unknown as LeaveApi.LeaveVO
// 审批相关:设置指定审批人
if (startUserSelectTasks.value?.length > 0) {
data.startUserSelectAssignees = startUserSelectAssignees.value
}
await LeaveApi.createLeave(data)
message.success('发起成功')
// 关闭当前 Tab
delView(unref(currentRoute))
await push({ name: 'BpmOALeave' })
} finally {
formLoading.value = false
}
}
/** 审批相关:获取审批详情 */
const getApprovalDetail = async () => {
try {
const data = await ProcessInstanceApi.getApprovalDetail({
processDefinitionId: processDefinitionId.value,
// TODO 小北:可以支持 processDefinitionKey 查询
activityId: NodeId.START_USER_NODE_ID,
processVariablesStr: JSON.stringify({ day: daysDifference() }) // 解决 GET 无法传递对象的问题,后端 String 再转 JSON
})
if (!data) {
message.error('查询不到审批详情信息!')
return
}
// 获取审批节点,显示 Timeline 的数据
activityNodes.value = data.activityNodes
// 获取发起人自选的任务
startUserSelectTasks.value = data.activityNodes?.filter(
(node: ApprovalNodeInfo) => CandidateStrategy.START_USER_SELECT === node.candidateStrategy
)
// 恢复之前的选择审批人
if (startUserSelectTasks.value?.length > 0) {
for (const node of startUserSelectTasks.value) {
if (
tempStartUserSelectAssignees.value[node.id] &&
tempStartUserSelectAssignees.value[node.id].length > 0
) {
startUserSelectAssignees.value[node.id] = tempStartUserSelectAssignees.value[node.id]
} else {
startUserSelectAssignees.value[node.id] = []
}
}
}
} finally {
}
}
/** 审批相关:选择发起人 */
const selectUserConfirm = (id: string, userList: any[]) => {
startUserSelectAssignees.value[id] = userList?.map((item: any) => item.id)
}
// 计算天数差
// TODO @小北:可以搞到 formatTime 里面去,然后看看 dayjs 里面有没有现成的方法,或者辅助计算的方法。
const daysDifference = () => {
const oneDay = 24 * 60 * 60 * 1000 // 一天的毫秒数
const diffTime = Math.abs(Number(formData.value.endTime) - Number(formData.value.startTime))
return Math.floor(diffTime / oneDay)
}
/** 初始化 */
onMounted(async () => {
// TODO @小北:这里可以简化,统一通过 getApprovalDetail 处理么?
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
undefined,
processDefineKey
)
if (!processDefinitionDetail) {
message.error('OA 请假的流程模型未配置,请检查!')
return
}
processDefinitionId.value = processDefinitionDetail.id
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
// 审批相关:加载最新的审批详情,主要用于节点预测
await getApprovalDetail()
})
/** 审批相关:预测流程节点会因为输入的参数值而产生新的预测结果值,所以需重新预测一次, formData.value可改成实际业务中的特定字段 */
watch(
formData.value,
(newValue, oldValue) => {
if (!oldValue) {
return
}
if (newValue && Object.keys(newValue).length > 0) {
// 记录之前的节点审批人
tempStartUserSelectAssignees.value = startUserSelectAssignees.value
startUserSelectAssignees.value = {}
// 加载最新的审批详情,主要用于节点预测
getApprovalDetail()
}
},
{
immediate: true
}
)
</script>
<template>
<ContentWrap>
<el-descriptions :column="1" border>
<el-descriptions-item label="请假类型">
<dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="detailData.type" />
</el-descriptions-item>
<el-descriptions-item label="开始时间">
{{ formatDate(detailData.startTime, 'YYYY-MM-DD') }}
</el-descriptions-item>
<el-descriptions-item label="结束时间">
{{ formatDate(detailData.endTime, 'YYYY-MM-DD') }}
</el-descriptions-item>
<el-descriptions-item label="原因">
{{ detailData.reason }}
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import { propTypes } from '@/utils/propTypes'
import * as LeaveApi from '@/api/bpm/leave'
defineOptions({ name: 'BpmOALeaveDetail' })
const { query } = useRoute() // 查询参数
const props = defineProps({
id: propTypes.number.def(undefined)
})
const detailLoading = ref(false) // 表单的加载中
const detailData = ref<any>({}) // 详情数据
const queryId = query.id as unknown as number // 从 URL 传递过来的 id 编号
/** 获得数据 */
const getInfo = async () => {
detailLoading.value = true
try {
detailData.value = await LeaveApi.getLeave(props.id || queryId)
} finally {
detailLoading.value = false
}
}
defineExpose({ open: getInfo }) // 提供 open 方法,用于打开弹窗
/** 初始化 **/
onMounted(() => {
getInfo()
})
</script>
<template>
<doc-alert title="审批接入(业务表单)" url="https://doc.iocoder.cn/bpm/use-business-form/" />
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="68px"
>
<el-form-item label="请假类型" prop="type">
<el-select
v-model="queryParams.type"
class="!w-240px"
clearable
placeholder="请选择请假类型"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="申请时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="审批结果" prop="status">
<el-select
v-model="queryParams.status"
class="!w-240px"
clearable
placeholder="请选择审批结果"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="原因" prop="reason">
<el-input
v-model="queryParams.reason"
class="!w-240px"
clearable
placeholder="请输入原因"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
<el-button plain type="primary" @click="handleCreate()">
<Icon class="mr-5px" icon="ep:plus" />
发起请假
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="申请编号" prop="id" />
<el-table-column align="center" label="状态" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"
label="开始时间"
prop="startTime"
width="180"
/>
<el-table-column
:formatter="dateFormatter"
align="center"
label="结束时间"
prop="endTime"
width="180"
/>
<el-table-column align="center" label="请假类型" prop="type">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column align="center" label="原因" prop="reason" />
<el-table-column
:formatter="dateFormatter"
align="center"
label="申请时间"
prop="createTime"
width="180"
/>
<el-table-column align="center" label="操作" width="200">
<template #default="scope">
<el-button
v-hasPermi="['bpm:oa-leave:query']"
link
type="primary"
@click="handleDetail(scope.row)"
>
详情
</el-button>
<el-button
v-hasPermi="['bpm:oa-leave:query']"
link
type="primary"
@click="handleProcessDetail(scope.row)"
>
进度
</el-button>
<el-button
v-if="scope.row.result === 1"
v-hasPermi="['bpm:oa-leave:create']"
link
type="danger"
@click="cancelLeave(scope.row)"
>
取消
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as LeaveApi from '@/api/bpm/leave'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
defineOptions({ name: 'BpmOALeave' })
const message = useMessage() // 消息弹窗
const router = useRouter() // 路由
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
type: undefined,
status: undefined,
reason: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await LeaveApi.getLeavePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加操作 */
const handleCreate = () => {
router.push({ name: 'OALeaveCreate' })
}
/** 详情操作 */
const handleDetail = (row: LeaveApi.LeaveVO) => {
router.push({
name: 'OALeaveDetail',
query: {
id: row.id
}
})
}
/** 取消请假操作 */
const cancelLeave = async (row) => {
// 二次确认
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
inputErrorMessage: '取消原因不能为空'
})
// 发起取消
await ProcessInstanceApi.cancelProcessInstanceByStartUser(row.id, value)
message.success('取消成功')
// 刷新列表
await getList()
}
/** 审批进度 */
const handleProcessDetail = (row) => {
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstanceId
}
})
}
// fix: 列表不刷新的问题。
watch(
() => router.currentRoute.value,
() => {
getList()
}
)
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="名字" prop="name">
<el-input v-model="formData.name" placeholder="请输入名字" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="表达式" prop="expression">
<el-input type="textarea" v-model="formData.expression" placeholder="请输入表达式" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { ProcessExpressionApi, ProcessExpressionVO } from '@/api/bpm/processExpression'
import { CommonStatusEnum } from '@/utils/constants'
/** BPM 流程 表单 */
defineOptions({ name: 'ProcessExpressionForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
id: undefined,
name: undefined,
status: undefined,
expression: undefined
})
const formRules = reactive({
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
expression: [{ required: true, message: '表达式不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await ProcessExpressionApi.getProcessExpression(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as ProcessExpressionVO
if (formType.value === 'create') {
await ProcessExpressionApi.createProcessExpression(data)
message.success(t('common.createSuccess'))
} else {
await ProcessExpressionApi.updateProcessExpression(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
status: CommonStatusEnum.ENABLE,
expression: undefined
}
formRef.value?.resetFields()
}
</script>
<template>
<doc-alert title="流程表达式" url="https://doc.iocoder.cn/bpm/expression/" />
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="名字" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名字"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['bpm:process-expression:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="名字" align="center" prop="name" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="表达式" align="center" prop="expression" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['bpm:process-expression:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['bpm:process-expression:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<ProcessExpressionForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { ProcessExpressionApi, ProcessExpressionVO } from '@/api/bpm/processExpression'
import ProcessExpressionForm from './ProcessExpressionForm.vue'
/** BPM 流程表达式列表 */
defineOptions({ name: 'BpmProcessExpression' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<ProcessExpressionVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
status: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProcessExpressionApi.getProcessExpressionPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ProcessExpressionApi.deleteProcessExpression(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<template>
<el-card v-loading="loading" class="box-card">
<MyProcessViewer key="designer" :xml="view.bpmnXml" :view="view" class="process-viewer" />
</el-card>
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
defineOptions({ name: 'BpmProcessInstanceBpmnViewer' })
const props = defineProps({
loading: propTypes.bool.def(false), // 是否加载中
bpmnXml: propTypes.string, // BPMN XML
modelView: propTypes.object
})
const view = ref({
bpmnXml: ''
}) // BPMN 流程图数据
/** 只有 loading 完成时,才去加载流程列表 */
watch(
() => props.modelView,
async (newModelView) => {
// 加载最新
if (newModelView) {
//@ts-ignore
view.value = newModelView
}
}
)
/** 监听 bpmnXml */
watch(
() => props.bpmnXml,
(value) => {
view.value.bpmnXml = value
}
)
</script>
<style lang="scss" scoped>
.box-card {
height: 100%;
width: 100%;
margin-bottom: 0;
:deep(.el-card__body) {
height: 100%;
padding: 0;
}
:deep(.process-viewer) {
height: 100% !important;
min-height: 100%;
width: 100%;
overflow: auto;
}
}
</style>
<template>
<div v-loading="loading" class="process-viewer-container">
<SimpleProcessViewer
:flow-node="simpleModel"
:tasks="tasks"
:process-instance="processInstance"
/>
</div>
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { TaskStatusEnum } from '@/api/bpm/task'
import { SimpleFlowNode, NodeType } from '@/components/SimpleProcessDesignerV2/src/consts'
import { SimpleProcessViewer } from '@/components/SimpleProcessDesignerV2/src/'
defineOptions({ name: 'BpmProcessInstanceSimpleViewer' })
const props = defineProps({
loading: propTypes.bool.def(false), // 是否加载中
modelView: propTypes.object,
simpleJson: propTypes.string // Simple 模型结构数据 (json 格式)
})
const simpleModel = ref<any>({})
// 用户任务
const tasks = ref([])
// 流程实例
const processInstance = ref()
/** 监控模型视图 包括任务列表、进行中的活动节点编号等 */
watch(
() => props.modelView,
async (newModelView) => {
if (newModelView) {
tasks.value = newModelView.tasks
processInstance.value = newModelView.processInstance
// 已经拒绝的活动节点编号集合,只包括 UserTask
const rejectedTaskActivityIds: string[] = newModelView.rejectedTaskActivityIds
// 进行中的活动节点编号集合, 只包括 UserTask
const unfinishedTaskActivityIds: string[] = newModelView.unfinishedTaskActivityIds
// 已经完成的活动节点编号集合, 包括 UserTask、Gateway 等
const finishedActivityIds: string[] = newModelView.finishedTaskActivityIds
// 已经完成的连线节点编号集合,只包括 SequenceFlow
const finishedSequenceFlowActivityIds: string[] = newModelView.finishedSequenceFlowActivityIds
setSimpleModelNodeTaskStatus(
newModelView.simpleModel,
newModelView.processInstance?.status,
rejectedTaskActivityIds,
unfinishedTaskActivityIds,
finishedActivityIds,
finishedSequenceFlowActivityIds
)
simpleModel.value = newModelView.simpleModel ? newModelView.simpleModel : {}
}
}
)
/** 监控模型结构数据 */
watch(
() => props.simpleJson,
async (value) => {
if (value) {
simpleModel.value = JSON.parse(value)
}
}
)
const setSimpleModelNodeTaskStatus = (
simpleModel: SimpleFlowNode | undefined,
processStatus: number,
rejectedTaskActivityIds: string[],
unfinishedTaskActivityIds: string[],
finishedActivityIds: string[],
finishedSequenceFlowActivityIds: string[]
) => {
if (!simpleModel) {
return
}
// 结束节点
if (simpleModel.type === NodeType.END_EVENT_NODE) {
if (finishedActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = processStatus
} else {
simpleModel.activityStatus = TaskStatusEnum.NOT_START
}
return
}
// 审批节点
if (
simpleModel.type === NodeType.START_USER_NODE ||
simpleModel.type === NodeType.USER_TASK_NODE ||
simpleModel.type === NodeType.TRANSACTOR_NODE ||
simpleModel.type === NodeType.CHILD_PROCESS_NODE
) {
simpleModel.activityStatus = TaskStatusEnum.NOT_START
if (rejectedTaskActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = TaskStatusEnum.REJECT
} else if (unfinishedTaskActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = TaskStatusEnum.RUNNING
} else if (finishedActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = TaskStatusEnum.APPROVE
}
// TODO 是不是还缺一个 cancel 的状态
}
// 抄送节点
if (simpleModel.type === NodeType.COPY_TASK_NODE) {
// 抄送节点,只有通过和未执行状态
if (finishedActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = TaskStatusEnum.APPROVE
} else {
simpleModel.activityStatus = TaskStatusEnum.NOT_START
}
}
// 延迟器节点
if (simpleModel.type === NodeType.DELAY_TIMER_NODE) {
// 延迟器节点,只有通过和未执行状态
if (finishedActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = TaskStatusEnum.APPROVE
} else {
simpleModel.activityStatus = TaskStatusEnum.NOT_START
}
}
// 触发器节点
if (simpleModel.type === NodeType.TRIGGER_NODE) {
// 触发器节点,只有通过和未执行状态
if (finishedActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = TaskStatusEnum.APPROVE
} else {
simpleModel.activityStatus = TaskStatusEnum.NOT_START
}
}
// 条件节点对应 SequenceFlow
if (simpleModel.type === NodeType.CONDITION_NODE) {
// 条件节点,只有通过和未执行状态
if (finishedSequenceFlowActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = TaskStatusEnum.APPROVE
} else {
simpleModel.activityStatus = TaskStatusEnum.NOT_START
}
}
// 网关节点
if (
simpleModel.type === NodeType.CONDITION_BRANCH_NODE ||
simpleModel.type === NodeType.PARALLEL_BRANCH_NODE ||
simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE ||
simpleModel.type === NodeType.ROUTER_BRANCH_NODE
) {
// 网关节点。只有通过和未执行状态
if (finishedActivityIds.includes(simpleModel.id)) {
simpleModel.activityStatus = TaskStatusEnum.APPROVE
} else {
simpleModel.activityStatus = TaskStatusEnum.NOT_START
}
simpleModel.conditionNodes?.forEach((node) => {
setSimpleModelNodeTaskStatus(
node,
processStatus,
rejectedTaskActivityIds,
unfinishedTaskActivityIds,
finishedActivityIds,
finishedSequenceFlowActivityIds
)
})
}
setSimpleModelNodeTaskStatus(
simpleModel.childNode,
processStatus,
rejectedTaskActivityIds,
unfinishedTaskActivityIds,
finishedActivityIds,
finishedSequenceFlowActivityIds
)
}
</script>
<style lang="scss" scoped></style>
<template>
<el-table :data="tasks" border header-cell-class-name="table-header-gray">
<el-table-column label="审批节点" prop="name" min-width="120" align="center" />
<el-table-column label="审批人" min-width="100" align="center">
<template #default="scope">
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
</template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"
label="开始时间"
prop="createTime"
min-width="140"
/>
<el-table-column
:formatter="dateFormatter"
align="center"
label="结束时间"
prop="endTime"
min-width="140"
/>
<el-table-column align="center" label="审批状态" prop="status" min-width="90">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column align="center" label="审批建议" prop="reason" min-width="200">
<template #default="scope">
{{ scope.row.reason }}
<el-button
class="ml-10px"
size="small"
v-if="scope.row.formId > 0"
@click="handleFormDetail(scope.row)"
>
<Icon icon="ep:document" /> 查看表单
</el-button>
</template>
</el-table-column>
<el-table-column align="center" label="耗时" prop="durationInMillis" min-width="100">
<template #default="scope">
{{ formatPast2(scope.row.durationInMillis) }}
</template>
</el-table-column>
</el-table>
<!-- 弹窗:表单 -->
<Dialog title="表单详情" v-model="taskFormVisible" width="600">
<form-create
ref="fApi"
v-model="taskForm.value"
:option="taskForm.option"
:rule="taskForm.rule"
/>
</Dialog>
</template>
<script lang="ts" setup>
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
import { propTypes } from '@/utils/propTypes'
import { DICT_TYPE } from '@/utils/dict'
import type { ApiAttrs } from '@form-create/element-ui/types/config'
import { setConfAndFields2 } from '@/utils/formCreate'
import * as TaskApi from '@/api/bpm/task'
defineOptions({ name: 'BpmProcessInstanceTaskList' })
const props = defineProps({
loading: propTypes.bool.def(false), // 是否加载中
id: propTypes.string // 流程实例的编号
})
const tasks = ref([]) // 流程任务的数组
/** 查看表单 */
const fApi = ref<ApiAttrs>() // form-create 的 API 操作类
const taskForm = ref({
rule: [],
option: {},
value: {}
}) // 流程任务的表单详情
const taskFormVisible = ref(false)
const handleFormDetail = async (row: any) => {
// 设置表单
setConfAndFields2(taskForm, row.formConf, row.formFields, row.formVariables)
// 弹窗打开
taskFormVisible.value = true
// 隐藏提交、重置按钮,设置禁用只读
await nextTick()
fApi.value.fapi.btn.show(false)
fApi.value?.fapi?.resetBtn.show(false)
fApi.value?.fapi?.disabled(true)
}
/** 只有 loading 完成时,才去加载流程列表 */
watch(
() => props.loading,
async (value) => {
if (value) {
tasks.value = await TaskApi.getTaskListByProcessInstanceId(props.id)
}
}
)
</script>
<template>
<el-dialog v-model="signDialogVisible" title="签名" width="935">
<div class="position-relative">
<Vue3Signature class="b b-solid b-gray" ref="signature" w="900px" h="400px" />
<el-button
class="pos-absolute bottom-20px right-10px"
type="primary"
text
size="small"
@click="signature.clear()"
>
<Icon icon="ep:delete" class="mr-5px" />
清除
</el-button>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="signDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submit"> 提交 </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import Vue3Signature from 'vue3-signature'
import * as FileApi from '@/api/infra/file'
import download from '@/utils/download'
const message = useMessage() // 消息弹窗
const signDialogVisible = ref(false)
const signature = ref()
const open = async () => {
signDialogVisible.value = true
}
defineExpose({ open })
const emits = defineEmits(['success'])
const submit = async () => {
message.success('签名上传中请稍等。。。')
const res = await FileApi.updateFile({
file: download.base64ToFile(signature.value.save('image/png'), '签名')
})
emits('success', res.data)
signDialogVisible.value = false
}
</script>
<style scoped></style>
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="发起人" prop="startUserId">
<el-select v-model="queryParams.startUserId" placeholder="请选择发起人" class="!w-240px">
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item label="流程名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入流程名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="所属流程" prop="processDefinitionId">
<el-input
v-model="queryParams.processDefinitionId"
placeholder="请输入流程定义的编号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="流程分类" prop="category">
<el-select
v-model="queryParams.category"
placeholder="请选择流程分类"
clearable
class="!w-240px"
>
<el-option
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
/>
</el-select>
</el-form-item>
<el-form-item label="流程状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择流程状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="发起时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="流程名称" align="center" prop="name" min-width="200px" fixed="left" />
<el-table-column
label="流程分类"
align="center"
prop="categoryName"
min-width="100"
fixed="left"
/>
<el-table-column label="流程发起人" align="center" prop="startUser.nickname" width="120" />
<el-table-column label="发起部门" align="center" prop="startUser.deptName" width="120" />
<el-table-column label="流程状态" prop="status" width="120">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="发起时间"
align="center"
prop="startTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="结束时间"
align="center"
prop="endTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column align="center" label="耗时" prop="durationInMillis" width="169">
<template #default="scope">
{{ scope.row.durationInMillis > 0 ? formatPast2(scope.row.durationInMillis) : '-' }}
</template>
</el-table-column>
<el-table-column label="当前审批任务" align="center" prop="tasks" min-width="120px">
<template #default="scope">
<el-button type="primary" v-for="task in scope.row.tasks" :key="task.id" link>
<span>{{ task.name }}</span>
</el-button>
</template>
</el-table-column>
<el-table-column label="流程编号" align="center" prop="id" min-width="320px" />
<el-table-column label="操作" align="center" fixed="right" width="180">
<template #default="scope">
<el-button
link
type="primary"
v-hasPermi="['bpm:process-instance:cancel']"
@click="handleDetail(scope.row)"
>
详情
</el-button>
<el-button
link
type="primary"
v-if="scope.row.status === 1"
v-hasPermi="['bpm:process-instance:query']"
@click="handleCancel(scope.row)"
>
取消
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
import { ElMessageBox } from 'element-plus'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { CategoryApi } from '@/api/bpm/category'
import * as UserApi from '@/api/system/user'
import { cancelProcessInstanceByAdmin } from '@/api/bpm/processInstance'
// 它和【我的流程】的差异是,该菜单可以看全部的流程实例
defineOptions({ name: 'BpmProcessInstanceManager' })
const router = useRouter() // 路由
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
startUserId: undefined,
name: '',
processDefinitionId: undefined,
category: undefined,
status: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const categoryList = ref([]) // 流程分类列表
const userList = ref<any[]>([]) // 用户列表
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProcessInstanceApi.getProcessInstanceManagerPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 查看详情 */
const handleDetail = (row) => {
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.id
}
})
}
/** 取消按钮操作 */
const handleCancel = async (row) => {
// 二次确认
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
inputErrorMessage: '取消原因不能为空'
})
// 发起取消
await ProcessInstanceApi.cancelProcessInstanceByAdmin(row.id, value)
message.success('取消成功')
// 刷新列表
await getList()
}
/** 激活时 **/
onActivated(() => {
getList()
})
/** 初始化 **/
onMounted(async () => {
await getList()
categoryList.value = await CategoryApi.getCategorySimpleList()
userList.value = await UserApi.getSimpleUserList()
})
</script>
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="发起人" prop="startUserId">
<el-select v-model="queryParams.startUserId" placeholder="请选择发起人" class="!w-240px">
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
<el-form-item label="流程名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入流程名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="流程状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择流程状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="发起时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="结束时间" prop="endTime">
<el-date-picker
v-model="queryParams.endTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item
v-for="(item, index) in formFields"
:key="index"
:label="item.title"
:prop="item.field"
>
<!-- TODO @lesan:目前只支持input类型的字符串搜索 -->
<el-input
:disabled="item.type !== 'input'"
v-model="queryParams.formFieldsParams[item.field]"
:placeholder="`请输入${item.title}`"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" border :data="list">
<el-table-column label="流程名称" align="center" prop="name" fixed="left" width="200" />
<el-table-column label="流程发起人" align="center" prop="startUser.nickname" width="120" />
<el-table-column label="流程状态" prop="status" width="120">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="发起时间"
align="center"
prop="startTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="结束时间"
align="center"
prop="endTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
v-for="(item, index) in formFields"
:key="index"
:label="item.title"
:prop="item.field"
width="120"
>
<!-- TODO @lesan:可以根据formField的type进行展示方式的控制,现在全部以字符串 -->
<template #default="scope">
{{ scope.row.formVariables[item.field] ?? '' }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="180">
<template #default="scope">
<el-button
link
type="primary"
v-hasPermi="['bpm:process-instance:cancel']"
@click="handleDetail(scope.row)"
>
详情
</el-button>
<el-button
link
type="primary"
v-if="scope.row.status === 1"
v-hasPermi="['bpm:process-instance:query']"
@click="handleCancel(scope.row)"
>
取消
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import * as UserApi from '@/api/system/user'
import * as DefinitionApi from '@/api/bpm/definition'
import { parseFormFields } from '@/components/FormCreate/src/utils'
import { ElMessageBox } from 'element-plus'
defineOptions({ name: 'BpmProcessInstanceReport' })
const router = useRouter() // 路由
const { query } = useRoute()
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const formFields = ref()
const processDefinitionId = query.processDefinitionId as string
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
startUserId: undefined,
name: '',
processDefinitionKey: query.processDefinitionKey,
status: undefined,
createTime: [],
endTime: [],
formFieldsParams: {}
})
const queryFormRef = ref() // 搜索的表单
const userList = ref<any[]>([]) // 用户列表
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProcessInstanceApi.getProcessInstanceManagerPage({
...queryParams,
formFieldsParams: JSON.stringify(queryParams.formFieldsParams)
})
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 获取流程定义 */
const getProcessDefinition = async () => {
const processDefinition = await DefinitionApi.getProcessDefinition(processDefinitionId)
formFields.value = parseFormCreateFields(processDefinition.formFields)
}
/** 解析表单字段 */
const parseFormCreateFields = (formFields?: string[]) => {
const result: Array<Record<string, any>> = []
if (formFields) {
formFields.forEach((fieldStr: string) => {
parseFormFields(JSON.parse(fieldStr), result)
})
}
return result
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
queryParams.formFieldsParams = {}
handleQuery()
}
/** 查看详情 */
const handleDetail = (row) => {
router.push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.id
}
})
}
/** 取消按钮操作 */
const handleCancel = async (row) => {
// 二次确认
const { value } = await ElMessageBox.prompt('请输入取消原因', '取消流程', {
confirmButtonText: t('common.ok'),
cancelButtonText: t('common.cancel'),
inputPattern: /^[\s\S]*.*\S[\s\S]*$/, // 判断非空,且非空格
inputErrorMessage: '取消原因不能为空'
})
// 发起取消
await ProcessInstanceApi.cancelProcessInstanceByAdmin(row.id, value)
message.success('取消成功')
// 刷新列表
await getList()
}
/** 初始化 **/
onMounted(async () => {
// 获取流程定义,用于 table column 的展示
await getProcessDefinition()
// 获取流程列表
await getList()
// 获取用户列表
userList.value = await UserApi.getSimpleUserList()
})
</script>
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="110px"
v-loading="formLoading"
>
<el-form-item label="名字" prop="name">
<el-input v-model="formData.name" placeholder="请输入名字" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:value="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select
v-model="formData.type"
placeholder="请选择类型"
@change="formData.event = undefined"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.BPM_PROCESS_LISTENER_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="事件" prop="event">
<el-select v-model="formData.event" placeholder="请选择事件">
<el-option
v-for="event in formData.type == 'execution'
? ['start', 'end']
: ['create', 'assignment', 'complete', 'delete', 'update', 'timeout']"
:label="event"
:value="event"
:key="event"
/>
</el-select>
</el-form-item>
<el-form-item label="值类型" prop="valueType">
<el-select v-model="formData.valueType" placeholder="请选择值类型">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="类路径" prop="value" v-if="formData.type == 'class'">
<el-input v-model="formData.value" placeholder="请输入类路径" />
</el-form-item>
<el-form-item label="表达式" prop="value" v-else>
<el-input v-model="formData.value" placeholder="请输入表达式" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { ProcessListenerApi, ProcessListenerVO } from '@/api/bpm/processListener'
import { CommonStatusEnum } from '@/utils/constants'
/** BPM 流程 表单 */
defineOptions({ name: 'ProcessListenerForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
id: undefined,
name: undefined,
type: undefined,
status: undefined,
event: undefined,
valueType: undefined,
value: undefined
})
const formRules = reactive({
name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],
type: [{ required: true, message: '类型不能为空', trigger: 'change' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
event: [{ required: true, message: '监听事件不能为空', trigger: 'blur' }],
valueType: [{ required: true, message: '值类型不能为空', trigger: 'change' }],
value: [{ required: true, message: '值不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await ProcessListenerApi.getProcessListener(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as ProcessListenerVO
if (formType.value === 'create') {
await ProcessListenerApi.createProcessListener(data)
message.success(t('common.createSuccess'))
} else {
await ProcessListenerApi.updateProcessListener(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
name: undefined,
type: undefined,
status: CommonStatusEnum.ENABLE,
event: undefined,
valueType: undefined,
value: undefined
}
formRef.value?.resetFields()
}
</script>
<template>
<doc-alert title="执行监听器、任务监听器" url="https://doc.iocoder.cn/bpm/listener/" />
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="85px"
>
<el-form-item label="名字" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入名字"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择类型" clearable class="!w-240px">
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.BPM_PROCESS_LISTENER_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['bpm:process-listener:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="名字" align="center" prop="name" />
<el-table-column label="类型" align="center" prop="type">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_LISTENER_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="事件" align="center" prop="event" />
<el-table-column label="值类型" align="center" prop="valueType">
<template #default="scope">
<dict-tag
:type="DICT_TYPE.BPM_PROCESS_LISTENER_VALUE_TYPE"
:value="scope.row.valueType"
/>
</template>
</el-table-column>
<el-table-column label="值" align="center" prop="value" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['bpm:process-listener:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['bpm:process-listener:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<ProcessListenerForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { ProcessListenerApi, ProcessListenerVO } from '@/api/bpm/processListener'
import ProcessListenerForm from './ProcessListenerForm.vue'
/** BPM 流程 列表 */
defineOptions({ name: 'BpmProcessListener' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<ProcessListenerVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
type: undefined,
event: undefined
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProcessListenerApi.getProcessListenerPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await ProcessListenerApi.deleteProcessListener(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<template>
<ContentWrap :bodyStyle="{ padding: '20px 16px' }">
<SimpleProcessDesigner
:model-id="modelId"
:model-key="modelKey"
:model-name="modelName"
@success="handleSuccess"
:start-user-ids="startUserIds"
:start-dept-ids="startDeptIds"
ref="designerRef"
/>
</ContentWrap>
</template>
<script setup lang="ts">
import { SimpleProcessDesigner } from '@/components/SimpleProcessDesignerV2/src/'
defineOptions({
name: 'SimpleModelDesign'
})
defineProps<{
modelId?: string
modelKey?: string
modelName?: string
startUserIds?: number[]
startDeptIds?: number[]
}>()
const emit = defineEmits(['success'])
const designerRef = ref()
// 修改成功回调
const handleSuccess = (data?: any) => {
console.info('handleSuccess', data)
if (data) {
emit('success', data)
}
}
</script>
<style lang="scss" scoped></style>
<!-- 工作流 - 抄送我的流程 -->
<template>
<doc-alert
title="审批转办、委派、抄送"
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
/>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form ref="queryFormRef" :inline="true" class="-mb-15px" label-width="68px">
<el-form-item label="流程名称" prop="name">
<el-input
v-model="queryParams.processInstanceName"
@keyup.enter="handleQuery"
class="!w-240px"
clearable
placeholder="请输入流程名称"
/>
</el-form-item>
<el-form-item label="抄送时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<!-- TODO 芋艿:增加摘要 -->
<el-table-column align="center" label="流程名" prop="processInstanceName" min-width="180" />
<el-table-column label="摘要" prop="summary" min-width="180">
<template #default="scope">
<div class="flex flex-col" v-if="scope.row.summary && scope.row.summary.length > 0">
<div v-for="(item, index) in scope.row.summary" :key="index">
<el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
</div>
</div>
</template>
</el-table-column>
<el-table-column
align="center"
label="流程发起人"
prop="startUser.nickname"
min-width="100"
/>
<el-table-column
:formatter="dateFormatter"
align="center"
label="流程发起时间"
prop="processInstanceStartTime"
width="180"
/>
<el-table-column align="center" label="抄送节点" prop="activityName" min-width="180" />
<el-table-column align="center" label="抄送人" min-width="100">
<template #default="scope"> {{ scope.row.createUser?.nickname || '系统' }} </template>
</el-table-column>
<el-table-column align="center" label="抄送意见" prop="reason" width="150" />
<el-table-column
align="center"
label="抄送时间"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column align="center" label="操作" fixed="right" width="80">
<template #default="scope">
<el-button link type="primary" @click="handleAudit(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
defineOptions({ name: 'BpmProcessInstanceCopy' })
const { push } = useRouter() // 路由
const loading = ref(false) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
processInstanceId: '',
processInstanceName: '',
createTime: []
})
const queryFormRef = ref() // 搜索的表单
/** 查询任务列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProcessInstanceApi.getProcessInstanceCopyPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 处理审批按钮 */
const handleAudit = (row: any) => {
const query = {
id: row.processInstanceId,
activityId: undefined
}
if (row.activityId) {
query.activityId = row.activityId
}
push({
name: 'BpmProcessInstanceDetail',
query: query
})
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<template>
<doc-alert title="审批通过、不通过、驳回" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
<doc-alert
title="审批转办、委派、抄送"
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
/>
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="68px"
>
<el-form-item label="" prop="name">
<el-input
v-model="queryParams.name"
class="!w-240px"
clearable
placeholder="请输入任务名称"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
</el-form-item>
<el-form-item label="" prop="category" :style="{ position: 'absolute', right: '300px' }">
<el-select
v-model="queryParams.category"
placeholder="请选择流程分类"
clearable
class="!w-155px"
@change="handleQuery"
>
<el-option
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
/>
</el-select>
</el-form-item>
<el-form-item label="" prop="status" :style="{ position: 'absolute', right: '130px' }">
<el-select
v-model="queryParams.status"
placeholder="请选择流程状态"
clearable
class="!w-155px"
@change="handleQuery"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<!-- 高级筛选 -->
<el-form-item :style="{ position: 'absolute', right: '0px' }">
<el-popover
:visible="showPopover"
persistent
:width="400"
:show-arrow="false"
placement="bottom-end"
>
<template #reference>
<el-button @click="showPopover = !showPopover">
<Icon icon="ep:plus" class="mr-5px" />高级筛选
</el-button>
</template>
<el-form-item
label="所属流程"
class="font-bold"
label-position="top"
prop="processDefinitionKey"
>
<el-select
v-model="queryParams.processDefinitionKey"
placeholder="请选择流程定义"
clearable
@change="handleQuery"
class="!w-390px"
>
<el-option
v-for="item in processDefinitionList"
:key="item.key"
:label="item.name"
:value="item.key"
/>
</el-select>
</el-form-item>
<el-form-item label="发起时间" class="bold-label" label-position="top" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item class="bold-label" label-position="top">
<el-button @click="handleQuery"> 确认</el-button>
<el-button @click="showPopover = false"> 取消</el-button>
<el-button @click="resetQuery"> 清空</el-button>
</el-form-item>
</el-popover>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
<el-table-column label="摘要" prop="processInstance.summary" width="180">
<template #default="scope">
<div
class="flex flex-col"
v-if="scope.row.processInstance.summary && scope.row.processInstance.summary.length > 0"
>
<div v-for="(item, index) in scope.row.processInstance.summary" :key="index">
<el-text type="info"> {{ item.key }} : {{ item.value }} </el-text>
</div>
</div>
</template>
</el-table-column>
<el-table-column
align="center"
label="发起人"
prop="processInstance.startUser.nickname"
width="100"
/>
<el-table-column
:formatter="dateFormatter"
align="center"
label="发起时间"
prop="createTime"
width="180"
/>
<el-table-column align="center" label="当前任务" prop="name" width="180" />
<el-table-column
:formatter="dateFormatter"
align="center"
label="任务开始时间"
prop="createTime"
width="180"
/>
<el-table-column
:formatter="dateFormatter"
align="center"
label="任务结束时间"
prop="endTime"
width="180"
/>
<el-table-column align="center" label="审批状态" prop="status" width="120">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column align="center" label="审批建议" prop="reason" min-width="180" />
<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
<template #default="scope">
{{ formatPast2(scope.row.durationInMillis) }}
</template>
</el-table-column>
<el-table-column
align="center"
label="流程编号"
prop="processInstanceId"
:show-overflow-tooltip="true"
/>
<el-table-column align="center" label="任务编号" prop="id" :show-overflow-tooltip="true" />
<el-table-column align="center" label="操作" fixed="right" width="80">
<template #default="scope">
<el-button link type="primary" @click="handleAudit(scope.row)">历史</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task'
import { CategoryApi, CategoryVO } from '@/api/bpm/category'
import * as DefinitionApi from '@/api/bpm/definition'
defineOptions({ name: 'BpmDoneTask' })
const { push } = useRouter() // 路由
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const processDefinitionList = ref<any[]>([]) // 流程定义列表
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
category: undefined,
status: undefined,
processDefinitionKey: '',
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
const showPopover = ref(false) // 高级筛选是否展示
/** 查询任务列表 */
const getList = async () => {
loading.value = true
try {
const data = await TaskApi.getTaskDonePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 处理审批按钮 */
const handleAudit = (row: any) => {
push({
name: 'BpmProcessInstanceDetail',
query: {
id: row.processInstance.id,
taskId: row.id
}
})
}
/** 初始化 **/
onMounted(async () => {
await getList()
categoryList.value = await CategoryApi.getCategorySimpleList()
// 获取流程定义列表
processDefinitionList.value = await DefinitionApi.getSimpleProcessDefinitionList()
})
</script>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment