# Conflicts:
#	package.json
This commit is contained in:
YunaiV
2024-03-28 19:30:02 +08:00
112 changed files with 6021 additions and 1847 deletions

View File

@ -188,7 +188,7 @@ const loginData = reactive({
username: 'admin',
password: 'admin123',
captchaVerification: '',
rememberMe: false
rememberMe: true // 默认记录我。如果不需要,可手动修改
}
})
@ -218,14 +218,14 @@ const getTenantId = async () => {
}
}
// 记住我
const getCookie = () => {
const getLoginFormCache = () => {
const loginForm = authUtil.getLoginForm()
if (loginForm) {
loginData.loginForm = {
...loginData.loginForm,
username: loginForm.username ? loginForm.username : loginData.loginForm.username,
password: loginForm.password ? loginForm.password : loginData.loginForm.password,
rememberMe: loginForm.rememberMe ? true : false,
rememberMe: loginForm.rememberMe,
tenantName: loginForm.tenantName ? loginForm.tenantName : loginData.loginForm.tenantName
}
}
@ -326,7 +326,7 @@ watch(
}
)
onMounted(() => {
getCookie()
getLoginFormCache()
getTenantByWebsite()
})
</script>

View File

@ -0,0 +1,124 @@
<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="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="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'
/** 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,
status: undefined,
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,
status: undefined,
sort: undefined
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,200 @@
<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 download from '@/utils/download'
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>

View File

@ -11,11 +11,7 @@
</el-button>
</template>
</el-table-column>
<el-table-column label="定义分类" align="center" prop="category" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
</template>
</el-table-column>
<el-table-column label="定义分类" align="center" prop="categoryName" width="100" />
<el-table-column label="表单信息" align="center" prop="formType" width="200">
<template #default="scope">
<el-button
@ -57,18 +53,6 @@
width="300"
show-overflow-tooltip
/>
<el-table-column label="操作" align="center" width="150" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="handleAssignRule(scope.row)"
v-hasPermi="['bpm:task-assign-rule:query']"
>
分配规则
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
@ -88,8 +72,8 @@
<Dialog title="流程图" v-model="bpmnDetailVisible" width="800">
<MyProcessViewer
key="designer"
v-model="bpmnXML"
:value="bpmnXML as any"
v-model="bpmnXml"
:value="bpmnXml as any"
v-bind="bpmnControlForm"
:prefix="bpmnControlForm.prefix"
/>
@ -97,7 +81,6 @@
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
import * as DefinitionApi from '@/api/bpm/definition'
@ -129,16 +112,6 @@ const getList = async () => {
}
}
/** 点击任务分配按钮 */
const handleAssignRule = (row) => {
push({
name: 'BpmTaskAssignRuleList',
query: {
modelId: row.id
}
})
}
/** 流程表单的详情按钮操作 */
const formDetailVisible = ref(false)
const formDetailPreview = ref({
@ -160,12 +133,12 @@ const handleFormDetail = async (row) => {
/** 流程图的详情按钮操作 */
const bpmnDetailVisible = ref(false)
const bpmnXML = ref(null)
const bpmnXml = ref(null)
const bpmnControlForm = ref({
prefix: 'flowable'
})
const handleBpmnDetail = async (row) => {
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id)
bpmnXml.value = (await DefinitionApi.getProcessDefinition(row.id))?.bpmnXml
bpmnDetailVisible.value = true
}

View File

@ -1,5 +1,5 @@
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<doc-alert title="审批接入(流程表单)" url="https://doc.iocoder.cn/bpm/use-bpm-form/" />
<ContentWrap>
<!-- 搜索工作栏 -->

View File

@ -13,8 +13,8 @@
<el-form-item label="描述">
<el-input v-model="formData.description" placeholder="请输入描述" type="textarea" />
</el-form-item>
<el-form-item label="成员" prop="memberUserIds">
<el-select v-model="formData.memberUserIds" multiple placeholder="请选择成员">
<el-form-item label="成员" prop="userIds">
<el-select v-model="formData.userIds" multiple placeholder="请选择成员">
<el-option
v-for="user in userList"
:key="user.id"
@ -60,13 +60,13 @@ const formData = ref({
id: undefined,
name: undefined,
description: undefined,
memberUserIds: undefined,
userIds: undefined,
status: CommonStatusEnum.ENABLE
})
const formRules = reactive({
name: [{ required: true, message: '组名不能为空', trigger: 'blur' }],
description: [{ required: true, message: '描述不能为空', trigger: 'blur' }],
memberUserIds: [{ required: true, message: '成员不能为空', trigger: 'blur' }],
userIds: [{ required: true, message: '成员不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
@ -124,7 +124,7 @@ const resetForm = () => {
id: undefined,
name: undefined,
description: undefined,
memberUserIds: undefined,
userIds: undefined,
status: CommonStatusEnum.ENABLE
}
formRef.value?.resetFields()

View File

@ -63,7 +63,7 @@
<el-table-column label="描述" align="center" prop="description" />
<el-table-column label="成员" align="center">
<template #default="scope">
<span v-for="userId in scope.row.memberUserIds" :key="userId" class="pr-5px">
<span v-for="userId in scope.row.userIds" :key="userId" class="pr-5px">
{{ userList.find((user) => user.id === userId)?.nickname }}
</span>
</template>

View File

@ -43,13 +43,16 @@
style="width: 100%"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
/>
</el-select>
</el-form-item>
<el-form-item v-if="formData.id" label="流程图标" prop="icon">
<UploadImg v-model="formData.icon" :limit="1" height="128px" width="128px" />
</el-form-item>
<el-form-item label="流程描述" prop="description">
<el-input v-model="formData.description" clearable type="textarea" />
</el-form-item>
@ -126,6 +129,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { ElMessageBox } from 'element-plus'
import * as ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form'
import { CategoryApi } from '@/api/bpm/category'
defineOptions({ name: 'ModelForm' })
@ -140,20 +144,23 @@ const formData = ref({
formType: 10,
name: '',
category: undefined,
icon: undefined,
description: '',
formId: '',
formCustomCreatePath: '',
formCustomViewPath: ''
})
const formRules = reactive({
category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }],
name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],
key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],
category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }],
icon: [{ required: true, message: '参数图标不能为空', trigger: 'blur' }],
value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }],
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const formList = ref([]) // 流程表单的下拉框的数据
const categoryList = ref([]) // 流程分类列表
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
@ -171,7 +178,9 @@ const open = async (type: string, id?: number) => {
}
}
// 获得流程表单的下拉框的数据
formList.value = await FormApi.getSimpleFormList()
formList.value = await FormApi.getFormSimpleList()
// 查询流程分类列表
categoryList.value = await CategoryApi.getCategorySimpleList()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
@ -190,11 +199,10 @@ const submitForm = async () => {
await ModelApi.createModel(data)
// 提示,引导用户做后续的操作
await ElMessageBox.alert(
'<strong>新建模型成功!</strong>后续需要执行如下 4 个步骤:' +
'<strong>新建模型成功!</strong>后续需要执行如下 3 个步骤:' +
'<div>1. 点击【修改流程】按钮,配置流程的分类、表单信息</div>' +
'<div>2. 点击【设计流程】按钮,绘制流程图</div>' +
'<div>3. 点击【分配规则】按钮,设置每个用户任务的审批人</div>' +
'<div>4. 点击【发布流程】按钮,完成流程的最终发布</div>' +
'<div>3. 点击【发布流程】按钮,完成流程的最终发布</div>' +
'另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
'重要提示',
{
@ -220,6 +228,7 @@ const resetForm = () => {
formType: 10,
name: '',
category: undefined,
icon: '',
description: '',
formId: '',
formCustomCreatePath: '',

View File

@ -109,6 +109,7 @@ const submitFormSuccess = async (response: any) => {
}
// 提示成功
message.success('导入流程成功!请点击【设计流程】按钮,进行编辑保存后,才可以进行【发布流程】')
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
}

View File

@ -89,11 +89,21 @@ onMounted(async () => {
}
// 查询模型
const data = await ModelApi.getModel(modelId)
xmlString.value = data.bpmnXml
if (!data.bpmnXml) {
// 首次创建的 Model 模型,它是没有 bpmnXml此时需要给它一个默认的
data.bpmnXml = ` <?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.activiti.org/processdef">
<process id="${data.key}" name="${data.name}" isExecutable="true" />
<bpmndi:BPMNDiagram id="BPMNDiagram">
<bpmndi:BPMNPlane id="${data.key}_di" bpmnElement="${data.key}" />
</bpmndi:BPMNDiagram>
</definitions>`
}
model.value = {
...data,
bpmnXml: undefined // 清空 bpmnXml 属性
}
xmlString.value = data.bpmnXml
})
</script>
<style lang="scss">

View File

@ -1,5 +1,11 @@
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<doc-alert title="流程设计器BPMN" url="https://doc.iocoder.cn/bpm/model-designer-dingding/" />
<doc-alert
title="流程设计器(钉钉、飞书)"
url="https://doc.iocoder.cn/bpm/model-designer-bpmn/"
/>
<doc-alert title="选择审批人、发起人自选" url="https://doc.iocoder.cn/bpm/assignee/" />
<doc-alert title="会签、或签、依次审批" url="https://doc.iocoder.cn/bpm/multi-instance/" />
<ContentWrap>
<!-- 搜索工作栏 -->
@ -36,10 +42,10 @@
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
v-for="category in categoryList"
:key="category.code"
:label="category.name"
:value="category.code"
/>
</el-select>
</el-form-item>
@ -72,11 +78,12 @@
</el-button>
</template>
</el-table-column>
<el-table-column label="流程分类" align="center" prop="category" width="100">
<el-table-column label="流程图标" align="center" prop="icon" width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
<el-image :src="scope.row.icon" class="w-32px h-32px" />
</template>
</el-table-column>
<el-table-column label="流程分类" align="center" prop="categoryName" width="100" />
<el-table-column label="表单信息" align="center" prop="formType" width="200">
<template #default="scope">
<el-button
@ -164,10 +171,10 @@
<el-button
link
type="primary"
@click="handleAssignRule(scope.row)"
v-hasPermi="['bpm:task-assign-rule:query']"
@click="handleSimpleDesign(scope.row.id)"
v-hasPermi="['bpm:model:update']"
>
分配规则
仿钉钉设计流程
</el-button>
<el-button
link
@ -229,7 +236,6 @@
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter, formatDate } from '@/utils/formatTime'
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
import * as ModelApi from '@/api/bpm/model'
@ -237,6 +243,7 @@ import * as FormApi from '@/api/bpm/form'
import ModelForm from './ModelForm.vue'
import ModelImportForm from '@/views/bpm/model/ModelImportForm.vue'
import { setConfAndFields2 } from '@/utils/formCreate'
import { CategoryApi } from '@/api/bpm/category'
defineOptions({ name: 'BpmModel' })
@ -255,6 +262,7 @@ const queryParams = reactive({
category: undefined
})
const queryFormRef = ref() // 搜索的表单
const categoryList = ref([]) // 流程分类列表
/** 查询列表 */
const getList = async () => {
@ -334,6 +342,15 @@ const handleDesign = (row) => {
})
}
const handleSimpleDesign = (row) => {
push({
name: 'SimpleWorkflowDesignEditor',
query: {
modelId: row.id
}
})
}
/** 发布流程 */
const handleDeploy = async (row) => {
try {
@ -347,16 +364,6 @@ const handleDeploy = async (row) => {
} catch {}
}
/** 点击任务分配按钮 */
const handleAssignRule = (row) => {
push({
name: 'BpmTaskAssignRuleList',
query: {
modelId: row.id
}
})
}
/** 跳转到指定流程定义列表 */
const handleDefinitionList = (row) => {
push({
@ -400,7 +407,9 @@ const handleBpmnDetail = async (row) => {
}
/** 初始化 **/
onMounted(() => {
getList()
onMounted(async () => {
await getList()
// 查询流程分类列表
categoryList.value = await CategoryApi.getCategorySimpleList()
})
</script>

View File

@ -37,6 +37,36 @@
<el-form-item label="原因" prop="reason">
<el-input v-model="formData.reason" placeholder="请输请假原因" type="textarea" />
</el-form-item>
<el-col v-if="startUserSelectTasks.length > 0">
<el-card class="mb-10px">
<template #header>指定审批人</template>
<el-form
:model="startUserSelectAssignees"
:rules="startUserSelectAssigneesFormRules"
ref="startUserSelectAssigneesFormRef"
>
<el-form-item
v-for="userTask in startUserSelectTasks"
:key="userTask.id"
:label="`任务【${userTask.name}】`"
:prop="userTask.id"
>
<el-select
v-model="startUserSelectAssignees[userTask.id]"
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>
</el-card>
</el-col>
<el-form-item>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
</el-form-item>
@ -46,10 +76,15 @@
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as LeaveApi from '@/api/bpm/leave'
import { useTagsViewStore } from '@/store/modules/tagsView'
import * as DefinitionApi from '@/api/bpm/definition'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'BpmOALeaveCreate' })
const message = useMessage() // 消息弹窗
const { delView } = useTagsViewStore() // 视图操作
const { push, currentRoute } = useRouter() // 路由
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formData = ref({
type: undefined,
@ -64,18 +99,34 @@ const formRules = reactive({
endTime: [{ required: true, message: '请假结束时间不能为空', trigger: 'change' }]
})
const formRef = ref() // 表单 Ref
const { delView } = useTagsViewStore() // 视图操作
const { push, currentRoute } = useRouter() // 路由
// 指定审批人
const processDefineKey = 'oa_leave' // 流程定义 Key
const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
const userList = ref<any[]>([]) // 用户列表
/** 提交表单 */
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 校验指定审批人
if (startUserSelectTasks.value?.length > 0) {
await startUserSelectAssigneesFormRef.value.validate()
}
// 提交请求
formLoading.value = true
try {
const data = formData.value as unknown as LeaveApi.LeaveVO
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
@ -85,4 +136,29 @@ const submitForm = async () => {
formLoading.value = false
}
}
/** 初始化 */
onMounted(async () => {
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(
undefined,
processDefineKey
)
if (!processDefinitionDetail) {
message.error('OA 请假的流程模型未配置,请检查!')
return
}
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
// 设置指定审批人
if (startUserSelectTasks.value?.length > 0) {
// 设置校验规则
for (const userTask of startUserSelectTasks.value) {
startUserSelectAssignees.value[userTask.id] = []
startUserSelectAssigneesFormRules.value[userTask.id] = [
{ required: true, message: '请选择审批人', trigger: 'blur' }
]
}
// 加载用户列表
userList.value = await UserApi.getSimpleUserList()
}
})
</script>

View File

@ -1,5 +1,5 @@
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<doc-alert title="审批接入(业务表单)" url="https://doc.iocoder.cn/bpm/use-business-form/" />
<ContentWrap>
<!-- 搜索工作栏 -->
@ -36,10 +36,15 @@
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="结果" prop="result">
<el-select v-model="queryParams.result" class="!w-240px" clearable placeholder="请选择结果">
<el-form-item label="审批结果" prop="result">
<el-select
v-model="queryParams.result"
class="!w-240px"
clearable
placeholder="请选择审批结果"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)"
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -78,7 +83,7 @@
<el-table-column align="center" label="申请编号" prop="id" />
<el-table-column align="center" label="状态" prop="result">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="scope.row.result" />
</template>
</el-table-column>
<el-table-column
@ -166,7 +171,7 @@ const queryParams = reactive({
pageNo: 1,
pageSize: 10,
type: undefined,
result: undefined,
status: undefined,
reason: undefined,
createTime: []
})
@ -221,7 +226,7 @@ const cancelLeave = async (row) => {
inputErrorMessage: '取消原因不能为空'
})
// 发起取消
await ProcessInstanceApi.cancelProcessInstance(row.id, value)
await ProcessInstanceApi.cancelProcessInstanceByStartUser(row.id, value)
message.success('取消成功')
// 刷新列表
await getList()

View File

@ -0,0 +1,114 @@
<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"
:label="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>

View File

@ -0,0 +1,182 @@
<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>

View File

@ -1,35 +1,47 @@
<template>
<doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
<!-- 第一步通过流程定义的列表选择对应的流程 -->
<ContentWrap v-if="!selectProcessInstance">
<el-table v-loading="loading" :data="list">
<el-table-column label="流程名称" align="center" prop="name" />
<el-table-column label="流程分类" align="center" prop="category">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
</template>
</el-table-column>
<el-table-column label="流程版本" align="center" prop="version">
<template #default="scope">
<el-tag>v{{ scope.row.version }}</el-tag>
</template>
</el-table-column>
<el-table-column label="流程描述" align="center" prop="description" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button link type="primary" @click="handleSelect(scope.row)">
<Icon icon="ep:plus" /> 选择
</el-button>
</template>
</el-table-column>
</el-table>
<ContentWrap v-if="!selectProcessDefinition" v-loading="loading">
<el-tabs tab-position="left" v-model="categoryActive">
<el-tab-pane
:label="category.name"
:name="category.code"
:key="category.code"
v-for="category in categoryList"
>
<el-row :gutter="20">
<el-col
:lg="6"
:sm="12"
:xs="24"
v-for="definition in categoryProcessDefinitionList"
:key="definition.id"
>
<el-card
shadow="hover"
class="mb-20px cursor-pointer"
@click="handleSelect(definition)"
>
<template #default>
<div class="flex">
<el-image :src="definition.icon" class="w-32px h-32px" />
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
</div>
</template>
</el-card>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</ContentWrap>
<!-- 第二步填写表单进行流程的提交 -->
<ContentWrap v-else>
<el-card class="box-card">
<div class="clearfix">
<span class="el-icon-document">申请信息{{ selectProcessInstance.name }}</span>
<el-button style="float: right" type="primary" @click="selectProcessInstance = undefined">
<span class="el-icon-document">申请信息{{ selectProcessDefinition.name }}</span>
<el-button style="float: right" type="primary" @click="selectProcessDefinition = undefined">
<Icon icon="ep:delete" /> 选择其它流程
</el-button>
</div>
@ -37,9 +49,43 @@
<form-create
:rule="detailForm.rule"
v-model:api="fApi"
v-model="detailForm.value"
:option="detailForm.option"
@submit="submitForm"
/>
>
<template #type-startUserSelect>
<el-col :span="24">
<el-card class="mb-10px">
<template #header>指定审批人</template>
<el-form
:model="startUserSelectAssignees"
:rules="startUserSelectAssigneesFormRules"
ref="startUserSelectAssigneesFormRef"
>
<el-form-item
v-for="userTask in startUserSelectTasks"
:key="userTask.id"
:label="`任务【${userTask.name}】`"
:prop="userTask.id"
>
<el-select
v-model="startUserSelectAssignees[userTask.id]"
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>
</el-card>
</el-col>
</template>
</form-create>
</el-col>
</el-card>
<!-- 流程图预览 -->
@ -47,59 +93,127 @@
</ContentWrap>
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import * as DefinitionApi from '@/api/bpm/definition'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { setConfAndFields2 } from '@/utils/formCreate'
import type { ApiAttrs } from '@form-create/element-ui/types/config'
import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
import { CategoryApi } from '@/api/bpm/category'
import { useTagsViewStore } from '@/store/modules/tagsView'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'BpmProcessInstanceCreate' })
const router = useRouter() // 路由
const route = useRoute() // 路由
const { push, currentRoute } = useRouter() // 路由
const message = useMessage() // 消息
const { delView } = useTagsViewStore() // 视图操作
// ========== 列表相关 ==========
const loading = ref(true) // 列表的加载中
const list = ref([]) // 列表的数据
const queryParams = reactive({
suspensionState: 1
})
const processInstanceId = route.query.processInstanceId
const loading = ref(true) // 加载中
const categoryList = ref([]) // 分类的列表
const categoryActive = ref('') // 选中的分类
const processDefinitionList = ref([]) // 流程定义的列表
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
list.value = await DefinitionApi.getProcessDefinitionList(queryParams)
// 流程分类
categoryList.value = await CategoryApi.getCategorySimpleList()
if (categoryList.value.length > 0) {
categoryActive.value = categoryList.value[0].code
}
// 流程定义
processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
suspensionState: 1
})
// 如果 processInstanceId 非空,说明是重新发起
if (processInstanceId?.length > 0) {
const processInstance = await ProcessInstanceApi.getProcessInstance(processInstanceId)
if (!processInstance) {
message.error('重新发起流程失败,原因:流程实例不存在')
return
}
const processDefinition = processDefinitionList.value.find(
(item) => item.key == processInstance.processDefinition?.key
)
if (!processDefinition) {
message.error('重新发起流程失败,原因:流程定义不存在')
return
}
await handleSelect(processDefinition, processInstance.formVariables)
}
} finally {
loading.value = false
}
}
/** 选中分类对应的流程定义列表 */
const categoryProcessDefinitionList = computed(() => {
return processDefinitionList.value.filter((item) => item.category == categoryActive.value)
})
// ========== 表单相关 ==========
const bpmnXML = ref(null) // BPMN 数据
const fApi = ref<ApiAttrs>()
const detailForm = ref({
// 流程表单详情
rule: [],
option: {}
})
const selectProcessInstance = ref() // 选择的流程实例
option: {},
value: {}
}) // 流程表单详情
const selectProcessDefinition = ref() // 选择的流程定义
// 指定审批人
const bpmnXML = ref(null) // BPMN 数据
const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
const userList = ref<any[]>([]) // 用户列表
/** 处理选择流程的按钮操作 **/
const handleSelect = async (row) => {
const handleSelect = async (row, formVariables) => {
// 设置选择的流程
selectProcessInstance.value = row
selectProcessDefinition.value = row
// 重置指定审批人
startUserSelectTasks.value = []
startUserSelectAssignees.value = {}
startUserSelectAssigneesFormRules.value = {}
// 情况一:流程表单
if (row.formType == 10) {
// 设置表单
setConfAndFields2(detailForm, row.formConf, row.formFields)
setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
// 加载流程图
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(row.id)
const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
if (processDefinitionDetail) {
bpmnXML.value = processDefinitionDetail.bpmnXml
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
// 设置指定审批人
if (startUserSelectTasks.value?.length > 0) {
detailForm.value.rule.push({
type: 'startUserSelect',
props: {
title: '指定审批人'
}
})
// 设置校验规则
for (const userTask of startUserSelectTasks.value) {
startUserSelectAssignees.value[userTask.id] = []
startUserSelectAssigneesFormRules.value[userTask.id] = [
{ required: true, message: '请选择审批人', trigger: 'blur' }
]
}
// 加载用户列表
userList.value = await UserApi.getSimpleUserList()
}
}
// 情况二:业务表单
} else if (row.formCustomCreatePath) {
await router.push({
await push({
path: row.formCustomCreatePath
})
// 这里暂时无需加载流程图,因为跳出到另外个 Tab
@ -108,19 +222,29 @@ const handleSelect = async (row) => {
/** 提交按钮 */
const submitForm = async (formData) => {
if (!fApi.value || !selectProcessInstance.value) {
if (!fApi.value || !selectProcessDefinition.value) {
return
}
// 如果有指定审批人,需要校验
if (startUserSelectTasks.value?.length > 0) {
await startUserSelectAssigneesFormRef.value.validate()
}
// 提交请求
fApi.value.btn.loading(true)
try {
await ProcessInstanceApi.createProcessInstance({
processDefinitionId: selectProcessInstance.value.id,
variables: formData
processDefinitionId: selectProcessDefinition.value.id,
variables: formData,
startUserSelectAssignees: startUserSelectAssignees.value
})
// 提示
message.success('发起流程成功')
router.go(-1)
// 跳转回去
delView(unref(currentRoute))
await push({
name: 'BpmProcessInstanceMy'
})
} finally {
fApi.value.btn.loading(false)
}

View File

@ -33,21 +33,18 @@ const bpmnControlForm = ref({
prefix: 'flowable'
})
const activityList = ref([]) // 任务列表
// const bpmnXML = computed(() => { // TODO 芋艿:不晓得为啊哈不能这么搞
// if (!props.processInstance || !props.processInstance.processDefinition) {
// return
// }
// return DefinitionApi.getProcessDefinitionBpmnXML(props.processInstance.processDefinition.id)
// })
/** 初始化 */
onMounted(async () => {
if (props.id) {
activityList.value = await ActivityApi.getActivityList({
processInstanceId: props.id
})
/** 只有 loading 完成时,才去加载流程列表 */
watch(
() => props.loading,
async (value) => {
if (value && props.id) {
activityList.value = await ActivityApi.getActivityList({
processInstanceId: props.id
})
}
}
})
)
</script>
<style>
.box-card {

View File

@ -1,96 +0,0 @@
<template>
<el-drawer v-model="drawerVisible" title="子任务" size="70%">
<!-- 当前任务 -->
<template #header>
<h4>{{ baseTask.name }} 审批人{{ baseTask.assigneeUser?.nickname }}</h4>
<el-button
style="margin-left: 5px"
v-if="isSubSignButtonVisible(baseTask)"
type="danger"
plain
@click="handleSubSign(baseTask)"
>
<Icon icon="ep:remove" /> 减签
</el-button>
</template>
<!-- 子任务列表 -->
<el-table :data="baseTask.children" style="width: 100%" row-key="id" border>
<el-table-column prop="assigneeUser.nickname" label="审批人" />
<el-table-column prop="assigneeUser.deptName" label="所在部门" />
<el-table-column label="审批状态" prop="result">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
</template>
</el-table-column>
<el-table-column
label="提交时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="结束时间"
align="center"
prop="endTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" prop="operation">
<template #default="scope">
<el-button
v-if="isSubSignButtonVisible(scope.row)"
type="danger"
plain
@click="handleSubSign(scope.row)"
>
<Icon icon="ep:remove" /> 减签
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 减签 -->
<TaskSubSignDialogForm ref="taskSubSignDialogForm" />
</el-drawer>
</template>
<script lang="ts" setup>
import { isEmpty } from '@/utils/is'
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import TaskSubSignDialogForm from './TaskSubSignDialogForm.vue'
defineOptions({ name: 'ProcessInstanceChildrenTaskList' })
const message = useMessage() // 消息弹窗
const drawerVisible = ref(false) // 抽屉的是否展示
const baseTask = ref<object>({})
/** 打开弹窗 */
const open = async (task: any) => {
if (isEmpty(task.children)) {
message.warning('该任务没有子任务')
return
}
baseTask.value = task
// 展开抽屉
drawerVisible.value = true
}
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
/** 发起减签 */
const taskSubSignDialogForm = ref()
const handleSubSign = (item) => {
taskSubSignDialogForm.value.open(item.id)
// TODO @海洋:减签后,需要刷新下界面哈
}
/** 是否显示减签按钮 */
const isSubSignButtonVisible = (task: any) => {
if (task && task.children && !isEmpty(task.children)) {
// 有子任务,且子任务有任意一个是 待处理 和 待前置任务完成 则显示减签按钮
const subTask = task.children.find((item) => item.result === 1 || item.result === 9)
return !isEmpty(subTask)
}
return false
}
</script>

View File

@ -3,25 +3,44 @@
<template #header>
<span class="el-icon-picture-outline">审批记录</span>
</template>
<el-col :offset="4" :span="16">
<el-col :offset="3" :span="17">
<div class="block">
<el-timeline>
<el-timeline-item
v-for="(item, index) in tasks"
:key="index"
:icon="getTimelineItemIcon(item)"
:type="getTimelineItemType(item)"
v-if="processInstance.endTime"
:type="getProcessInstanceTimelineItemType(processInstance)"
>
<p style="font-weight: 700">
任务{{ item.name }}
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="item.result" />
结束流程 {{ formatDate(processInstance?.endTime) }} 结束
<dict-tag
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
:value="processInstance.status"
/>
</p>
</el-timeline-item>
<el-timeline-item
v-for="(item, index) in tasks"
:key="index"
:type="getTaskTimelineItemType(item)"
>
<p style="font-weight: 700">
审批任务{{ item.name }}
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="item.status" />
<el-button
style="margin-left: 5px"
class="ml-10px"
v-if="!isEmpty(item.children)"
@click="openChildrenTask(item)"
size="small"
>
<Icon icon="ep:memo" />
子任务
<Icon icon="ep:memo" /> 子任务
</el-button>
<el-button
class="ml-10px"
size="small"
v-if="item.formId > 0"
@click="handleFormDetail(item)"
>
<Icon icon="ep:document" /> 查看表单
</el-button>
</p>
<el-card :body-style="{ padding: '10px' }">
@ -45,84 +64,112 @@
<label v-if="item.durationInMillis" style="font-weight: normal; color: #8a909c">
{{ formatPast2(item?.durationInMillis) }}
</label>
<p v-if="item.reason">
<el-tag :type="getTimelineItemType(item)">{{ item.reason }}</el-tag>
</p>
<p v-if="item.reason"> 审批建议{{ item.reason }} </p>
</el-card>
</el-timeline-item>
<el-timeline-item type="success">
<p style="font-weight: 700">
发起流程{{ processInstance.startUser?.nickname }}
{{ formatDate(processInstance?.startTime) }} 发起 {{ processInstance.name }} 流程
</p>
</el-timeline-item>
</el-timeline>
</div>
</el-col>
<!-- 子任务 -->
<ProcessInstanceChildrenTaskList ref="processInstanceChildrenTaskList" />
</el-card>
<!-- 弹窗子任务 -->
<TaskSignList ref="taskSignListRef" @success="refresh" />
<!-- 弹窗表单 -->
<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 { formatDate, formatPast2 } from '@/utils/formatTime'
import { propTypes } from '@/utils/propTypes'
import { DICT_TYPE } from '@/utils/dict'
import { isEmpty } from '@/utils/is'
import ProcessInstanceChildrenTaskList from './ProcessInstanceChildrenTaskList.vue'
import TaskSignList from './dialog/TaskSignList.vue'
import type { ApiAttrs } from '@form-create/element-ui/types/config'
import { setConfAndFields2 } from '@/utils/formCreate'
defineOptions({ name: 'BpmProcessInstanceTaskList' })
defineProps({
loading: propTypes.bool, // 是否加载中
processInstance: propTypes.object, // 流程实例
tasks: propTypes.arrayOf(propTypes.object) // 流程任务的数组
})
/** 获得任务对应的 icon */
const getTimelineItemIcon = (item) => {
if (item.result === 1) {
return 'el-icon-time'
/** 获得流程实例对应的颜色 */
const getProcessInstanceTimelineItemType = (item: any) => {
if (item.status === 2) {
return 'success'
}
if (item.result === 2) {
return 'el-icon-check'
if (item.status === 3) {
return 'danger'
}
if (item.result === 3) {
return 'el-icon-close'
}
if (item.result === 4) {
return 'el-icon-remove-outline'
}
if (item.result === 5) {
return 'el-icon-back'
if (item.status === 4) {
return 'warning'
}
return ''
}
/** 获得任务对应的颜色 */
const getTimelineItemType = (item) => {
if (item.result === 1) {
const getTaskTimelineItemType = (item: any) => {
if ([0, 1, 6, 7].includes(item.status)) {
return 'primary'
}
if (item.result === 2) {
if (item.status === 2) {
return 'success'
}
if (item.result === 3) {
if (item.status === 3) {
return 'danger'
}
if (item.result === 4) {
if (item.status === 4) {
return 'info'
}
if (item.result === 5) {
return 'warning'
}
if (item.result === 6) {
return 'default'
}
if (item.result === 7 || item.result === 8) {
if (item.status === 5) {
return 'warning'
}
return ''
}
/**
* 子任务
*/
const processInstanceChildrenTaskList = ref()
/** 子任务 */
const taskSignListRef = ref()
const openChildrenTask = (item: any) => {
taskSignListRef.value.open(item)
}
const openChildrenTask = (item) => {
processInstanceChildrenTaskList.value.open(item)
/** 查看表单 */
const fApi = ref<ApiAttrs>() // form-create 的 API 操作类
const taskForm = ref({
rule: [],
option: {},
value: {}
}) // 流程任务的表单详情
const taskFormVisible = ref(false)
const handleFormDetail = async (row) => {
// 设置表单
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)
}
/** 刷新数据 */
const emit = defineEmits(['refresh']) // 定义 success 事件,用于操作成功后的回调
const refresh = () => {
emit('refresh')
}
</script>

View File

@ -1,242 +0,0 @@
<!-- TODO @kyle需要在讨论下可能直接选人更合适 -->
<template>
<Dialog v-model="dialogVisible" title="修改任务规则" width="600">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
<el-form-item label="任务名称" prop="taskName">
<el-input v-model="formData.taskName" disabled placeholder="请输入任务名称" />
</el-form-item>
<el-form-item label="任务标识" prop="taskKey">
<el-input v-model="formData.taskKey" disabled placeholder="请输入任务标识" />
</el-form-item>
<el-form-item label="流程名称" prop="processInstanceName">
<el-input v-model="formData.processInstanceName" disabled placeholder="请输入流程名称" />
</el-form-item>
<el-form-item label="流程标识" prop="processInstanceKey">
<el-input v-model="formData.processInstanceKey" disabled placeholder="请输入流程标识" />
</el-form-item>
<el-form-item label="规则类型" prop="type">
<el-select v-model="formData.type" clearable style="width: 100%">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="formData.type === 10" label="指定角色" prop="roleIds">
<el-select v-model="formData.roleIds" clearable multiple style="width: 100%">
<el-option
v-for="item in roleOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="formData.type === 20 || formData.type === 21"
label="指定部门"
prop="deptIds"
span="24"
>
<el-tree-select
ref="treeRef"
v-model="formData.deptIds"
:data="deptTreeOptions"
:props="defaultProps"
empty-text="加载中请稍后"
multiple
node-key="id"
show-checkbox
/>
</el-form-item>
<el-form-item v-if="formData.type === 22" label="指定岗位" prop="postIds" span="24">
<el-select v-model="formData.postIds" clearable multiple style="width: 100%">
<el-option
v-for="item in postOptions"
:key="parseInt(item.id)"
:label="item.name"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="formData.type === 30 || formData.type === 31 || formData.type === 32"
label="指定用户"
prop="userIds"
span="24"
>
<el-select v-model="formData.userIds" clearable multiple style="width: 100%">
<el-option
v-for="item in userOptions"
:key="parseInt(item.id)"
:label="item.nickname"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item v-if="formData.type === 40" label="指定用户组" prop="userGroupIds">
<el-select v-model="formData.userGroupIds" clearable multiple style="width: 100%">
<el-option
v-for="item in userGroupOptions"
:key="parseInt(item.id)"
:label="item.name"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item v-if="formData.type === 50" label="指定脚本" prop="scripts">
<el-select v-model="formData.scripts" clearable multiple style="width: 100%">
<el-option
v-for="dict in taskAssignScriptDictDatas"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="抄送原因" prop="reason">
<el-input v-model="formData.reason" 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 { defaultProps, handleTree } from '@/utils/tree'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept'
import * as PostApi from '@/api/system/post'
import * as UserApi from '@/api/system/user'
import * as UserGroupApi from '@/api/bpm/userGroup'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formData = ref({
type: Number(undefined),
taskName: '',
taskKey: '',
processInstanceName: '',
processInstanceKey: '',
startUserId: '',
options: [],
roleIds: [],
deptIds: [],
postIds: [],
userIds: [],
userGroupIds: [],
scripts: [],
reason: ''
})
const formRules = reactive({
type: [{ required: true, message: '规则类型不能为空', trigger: 'change' }],
roleIds: [{ required: true, message: '指定角色不能为空', trigger: 'change' }],
deptIds: [{ required: true, message: '指定部门不能为空', trigger: 'change' }],
postIds: [{ required: true, message: '指定岗位不能为空', trigger: 'change' }],
userIds: [{ required: true, message: '指定用户不能为空', trigger: 'change' }],
userGroupIds: [{ required: true, message: '指定用户组不能为空', trigger: 'change' }],
scripts: [{ required: true, message: '指定脚本不能为空', trigger: 'change' }],
reason: [{ required: true, message: '抄送原因不能为空', trigger: 'change' }]
})
const formRef = ref() // 表单 Ref
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
const deptTreeOptions = ref() // 部门树
const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT)
/** 打开弹窗 */
const open = async (row) => {
// 1. 先重置表单
resetForm()
// 2. 再设置表单
if (row != null) {
formData.value.type = undefined as unknown as number
formData.value.taskName = row.name
formData.value.taskKey = row.id
formData.value.processInstanceName = row.processInstance.name
formData.value.processInstanceKey = row.processInstance.id
formData.value.startUserId = row.processInstance.startUserId
}
// 打开弹窗
dialogVisible.value = true
// 获得角色列表
roleOptions.value = await RoleApi.getSimpleRoleList()
// 获得部门列表
deptOptions.value = await DeptApi.getSimpleDeptList()
deptTreeOptions.value = handleTree(deptOptions.value, 'id')
// 获得岗位列表
postOptions.value = await PostApi.getSimplePostList()
// 获得用户列表
userOptions.value = await UserApi.getSimpleUserList()
// 获得用户组列表
userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 构建表单
const form = {
...formData.value
}
// 将 roleIds 等选项赋值到 options 中
if (form.type === 10) {
form.options = form.roleIds
} else if (form.type === 20 || form.type === 21) {
form.options = form.deptIds
} else if (form.type === 22) {
form.options = form.postIds
} else if (form.type === 30 || form.type === 31 || form.type === 32) {
form.options = form.userIds
} else if (form.type === 40) {
form.options = form.userGroupIds
} else if (form.type === 50) {
form.options = form.scripts
}
form.roleIds = undefined
form.deptIds = undefined
form.postIds = undefined
form.userIds = undefined
form.userGroupIds = undefined
form.scripts = undefined
// 提交请求
formLoading.value = true
try {
const data = form as unknown as ProcessInstanceApi.ProcessInstanceCCVO
await ProcessInstanceApi.createProcessInstanceCC(data)
console.log(data)
message.success(t('common.createSuccess'))
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formRef.value?.resetFields()
}
</script>

View File

@ -37,10 +37,12 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) //
const formData = ref({
id: '',
delegateUserId: undefined
delegateUserId: undefined,
reason: ''
})
const formRules = ref({
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }]
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '委派理由不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
@ -79,7 +81,8 @@ const submitForm = async () => {
const resetForm = () => {
formData.value = {
id: '',
delegateUserId: undefined
delegateUserId: undefined,
reason: ''
}
formRef.value?.resetFields()
}

View File

@ -1,5 +1,5 @@
<template>
<Dialog v-model="dialogVisible" title="回退" width="500">
<Dialog v-model="dialogVisible" title="回退任务" width="500">
<el-form
ref="formRef"
v-loading="formLoading"
@ -7,13 +7,13 @@
:rules="formRules"
label-width="110px"
>
<el-form-item label="退回节点" prop="targetDefinitionKey">
<el-select v-model="formData.targetDefinitionKey" clearable style="width: 100%">
<el-form-item label="退回节点" prop="targetTaskDefinitionKey">
<el-select v-model="formData.targetTaskDefinitionKey" clearable style="width: 100%">
<el-option
v-for="item in returnList"
:key="item.definitionKey"
:key="item.taskDefinitionKey"
:label="item.name"
:value="item.definitionKey"
:value="item.taskDefinitionKey"
/>
</el-select>
</el-form-item>
@ -35,19 +35,19 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) //
const formData = ref({
id: '',
targetDefinitionKey: undefined,
targetTaskDefinitionKey: undefined,
reason: ''
})
const formRules = ref({
targetDefinitionKey: [{ required: true, message: '必须选择回退节点', trigger: 'change' }],
targetTaskDefinitionKey: [{ required: true, message: '必须选择回退节点', trigger: 'change' }],
reason: [{ required: true, message: '回退理由不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const returnList = ref([])
const returnList = ref([] as any)
/** 打开弹窗 */
const open = async (id: string) => {
returnList.value = await TaskApi.getReturnList({ taskId: id })
returnList.value = await TaskApi.getTaskListByReturn(id)
if (returnList.value.length === 0) {
message.warning('当前没有可回退的节点')
return false
@ -82,7 +82,7 @@ const submitForm = async () => {
const resetForm = () => {
formData.value = {
id: '',
targetDefinitionKey: undefined,
targetTaskDefinitionKey: undefined,
reason: ''
}
formRef.value?.resetFields()

View File

@ -7,8 +7,8 @@
:rules="formRules"
label-width="110px"
>
<el-form-item label="加签处理人" prop="userIdList">
<el-select v-model="formData.userIdList" multiple clearable style="width: 100%">
<el-form-item label="加签处理人" prop="userIds">
<el-select v-model="formData.userIds" multiple clearable style="width: 100%">
<el-option
v-for="item in userList"
:key="item.id"
@ -36,18 +36,19 @@
import * as TaskApi from '@/api/bpm/task'
import * as UserApi from '@/api/system/user'
const message = useMessage() //
defineOptions({ name: 'BpmTaskUpdateAssigneeForm' })
defineOptions({ name: 'TaskSignCreateForm' })
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
id: '',
userIdList: [],
type: ''
userIds: [],
type: '',
reason: ''
})
const formRules = ref({
userIdList: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
userIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '加签理由不能为空', trigger: 'change' }]
})
@ -75,7 +76,7 @@ const submitForm = async (type: string) => {
formLoading.value = true
formData.value.type = type
try {
await TaskApi.taskAddSign(formData.value)
await TaskApi.signCreateTask(formData.value)
message.success('加签成功')
dialogVisible.value = false
//
@ -89,8 +90,9 @@ const submitForm = async (type: string) => {
const resetForm = () => {
formData.value = {
id: '',
userIdList: [],
type: ''
userIds: [],
type: '',
reason: ''
}
formRef.value?.resetFields()
}

View File

@ -9,8 +9,10 @@
>
<el-form-item label="减签任务" prop="id">
<el-radio-group v-model="formData.id">
<el-radio-button v-for="item in subTaskList" :key="item.id" :label="item.id">
{{ item.name }}({{ item.assigneeUser.deptName }}{{ item.assigneeUser.nickname }}--审批)
<el-radio-button v-for="item in childrenTaskList" :key="item.id" :label="item.id">
{{ item.name }}
({{ item.assigneeUser?.deptName || item.ownerUser?.deptName }} -
{{ item.assigneeUser?.nickname || item.ownerUser?.nickname }})
</el-radio-button>
</el-radio-group>
</el-form-item>
@ -24,10 +26,12 @@
</template>
</Dialog>
</template>
<script lang="ts" name="TaskRollbackDialogForm" setup>
<script lang="ts" setup>
import * as TaskApi from '@/api/bpm/task'
import { isEmpty } from '@/utils/is'
defineOptions({ name: 'TaskSignDeleteForm' })
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
@ -41,11 +45,11 @@ const formRules = ref({
})
const formRef = ref() // Ref
const subTaskList = ref([])
const childrenTaskList = ref([])
/** 打开弹窗 */
const open = async (id: string) => {
subTaskList.value = await TaskApi.getChildrenTaskList(id)
if (isEmpty(subTaskList.value)) {
childrenTaskList.value = await TaskApi.getChildrenTaskList(id)
if (isEmpty(childrenTaskList.value)) {
message.warning('当前没有可减签的任务')
return false
}
@ -64,7 +68,7 @@ const submitForm = async () => {
//
formLoading.value = true
try {
await TaskApi.taskSubSign(formData.value)
await TaskApi.signDeleteTask(formData.value)
message.success('减签成功')
dialogVisible.value = false
//

View File

@ -0,0 +1,106 @@
<template>
<el-drawer v-model="drawerVisible" title="子任务" size="880px">
<!-- 当前任务 -->
<template #header>
<h4>{{ parentTask.name }} 审批人{{ parentTask?.assigneeUser?.nickname }}</h4>
<el-button
style="margin-left: 5px"
v-if="isSignDeleteButtonVisible(parentTask)"
type="danger"
plain
@click="handleSignDelete(parentTask)"
>
<Icon icon="ep:remove" /> 减签
</el-button>
</template>
<!-- 子任务列表 -->
<el-table :data="parentTask.children" style="width: 100%" row-key="id" border>
<el-table-column prop="assigneeUser.nickname" label="审批人" min-width="100">
<template #default="scope">
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
</template>
</el-table-column>
<el-table-column prop="assigneeUser.deptName" label="所在部门" min-width="100">
<template #default="scope">
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
</template>
</el-table-column>
<el-table-column 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
label="提交时间"
align="center"
prop="createTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column
label="结束时间"
align="center"
prop="endTime"
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" prop="operation" width="90">
<template #default="scope">
<el-button
v-if="isSignDeleteButtonVisible(scope.row)"
type="danger"
plain
size="small"
@click="handleSignDelete(scope.row)"
>
<Icon icon="ep:remove" /> 减签
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 减签 -->
<TaskSignDeleteForm ref="taskSignDeleteFormRef" @success="handleSignDeleteSuccess" />
</el-drawer>
</template>
<script lang="ts" setup>
import { isEmpty } from '@/utils/is'
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import TaskSignDeleteForm from './TaskSignDeleteForm.vue'
defineOptions({ name: 'TaskSignList' })
const message = useMessage() // 消息弹窗
const drawerVisible = ref(false) // 抽屉的是否展示
const parentTask = ref({} as any)
/** 打开弹窗 */
const open = async (task: any) => {
if (isEmpty(task.children)) {
message.warning('该任务没有子任务')
return
}
parentTask.value = task
// 展开抽屉
drawerVisible.value = true
}
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
/** 发起减签 */
const taskSignDeleteFormRef = ref()
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const handleSignDelete = (item: any) => {
taskSignDeleteFormRef.value.open(item.id)
}
const handleSignDeleteSuccess = () => {
emit('success')
// 关闭抽屉
drawerVisible.value = false
}
/** 是否显示减签按钮 */
const isSignDeleteButtonVisible = (task: any) => {
return task && task.children && !isEmpty(task.children)
}
</script>

View File

@ -1,5 +1,5 @@
<template>
<Dialog v-model="dialogVisible" title="转派审批人" width="500">
<Dialog v-model="dialogVisible" title="转派任务" width="500">
<el-form
ref="formRef"
v-loading="formLoading"
@ -17,6 +17,9 @@
/>
</el-select>
</el-form-item>
<el-form-item label="转派理由" prop="reason">
<el-input v-model="formData.reason" clearable placeholder="请输入转派理由" />
</el-form-item>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm"> </el-button>
@ -28,16 +31,18 @@
import * as TaskApi from '@/api/bpm/task'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'BpmTaskUpdateAssigneeForm' })
defineOptions({ name: 'TaskTransferForm' })
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
id: '',
assigneeUserId: undefined
assigneeUserId: undefined,
reason: ''
})
const formRules = ref({
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }]
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '转派理由不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
@ -63,7 +68,7 @@ const submitForm = async () => {
//
formLoading.value = true
try {
await TaskApi.updateTaskAssignee(formData.value)
await TaskApi.transferTask(formData.value)
dialogVisible.value = false
//
emit('success')
@ -76,7 +81,8 @@ const submitForm = async () => {
const resetForm = () => {
formData.value = {
id: '',
assigneeUserId: undefined
assigneeUserId: undefined,
reason: ''
}
formRef.value?.resetFields()
}

View File

@ -21,9 +21,22 @@
{{ processInstance.name }}
</el-form-item>
<el-form-item v-if="processInstance && processInstance.startUser" label="流程发起人">
{{ processInstance.startUser.nickname }}
<el-tag size="small" type="info">{{ processInstance.startUser.deptName }}</el-tag>
{{ processInstance?.startUser.nickname }}
<el-tag size="small" type="info">{{ processInstance?.startUser.deptName }}</el-tag>
</el-form-item>
<el-card class="mb-15px !-mt-10px" v-if="runningTasks[index].formId > 0">
<template #header>
<span class="el-icon-picture-outline">
填写表单{{ runningTasks[index]?.formName }}
</span>
</template>
<form-create
v-model:api="approveFormFApis[index]"
v-model="approveForms[index].value"
:option="approveForms[index].option"
:rule="approveForms[index].rule"
/>
</el-card>
<el-form-item label="审批建议" prop="reason">
<el-input
v-model="auditForms[index].reason"
@ -31,6 +44,16 @@
type="textarea"
/>
</el-form-item>
<el-form-item label="抄送人" prop="copyUserIds">
<el-select v-model="auditForms[index].copyUserIds" multiple placeholder="请选择抄送人">
<el-option
v-for="item in userOptions"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-form>
<div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px">
<el-button type="success" @click="handleAudit(item, true)">
@ -82,25 +105,30 @@
</el-card>
<!-- 审批记录 -->
<ProcessInstanceTaskList :loading="tasksLoad" :tasks="tasks" />
<ProcessInstanceTaskList
:loading="tasksLoad"
:process-instance="processInstance"
:tasks="tasks"
@refresh="getTaskList"
/>
<!-- 高亮流程图 -->
<ProcessInstanceBpmnViewer
:id="`${id}`"
:bpmn-xml="bpmnXML"
:bpmn-xml="bpmnXml"
:loading="processInstanceLoading"
:process-instance="processInstance"
:tasks="tasks"
/>
<!-- 弹窗转派审批人 -->
<TaskUpdateAssigneeForm ref="taskUpdateAssigneeFormRef" @success="getDetail" />
<!-- 弹窗回退节点 -->
<TaskReturnDialog ref="taskReturnDialogRef" @success="getDetail" />
<!-- 委派将任务委派给别人处理处理完成后会重新回到原审批人手中-->
<TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
<!-- 弹窗回退节点 -->
<TaskReturnForm ref="taskReturnFormRef" @success="getDetail" />
<!-- 弹窗委派将任务委派给别人处理处理完成后会重新回到原审批人手中-->
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
<!-- 加签当前任务审批人为A向前加签选了一个C则需要C先审批然后再是A审批向后加签BA审批完需要B再审批完才算完成这个任务节点 -->
<TaskAddSignDialogForm ref="taskAddSignDialogForm" @success="getDetail" />
<!-- 弹窗加签当前任务审批人为A向前加签选了一个C则需要C先审批然后再是A审批向后加签BA审批完需要B再审批完才算完成这个任务节点 -->
<TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
</ContentWrap>
</template>
<script lang="ts" setup>
@ -110,14 +138,15 @@ import type { ApiAttrs } from '@form-create/element-ui/types/config'
import * as DefinitionApi from '@/api/bpm/definition'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import * as TaskApi from '@/api/bpm/task'
import TaskUpdateAssigneeForm from './TaskUpdateAssigneeForm.vue'
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
import TaskReturnDialog from './TaskReturnDialogForm.vue'
import TaskDelegateForm from './TaskDelegateForm.vue'
import TaskAddSignDialogForm from './TaskAddSignDialogForm.vue'
import TaskReturnForm from './dialog/TaskReturnForm.vue'
import TaskDelegateForm from './dialog/TaskDelegateForm.vue'
import TaskTransferForm from './dialog/TaskTransferForm.vue'
import TaskSignCreateForm from './dialog/TaskSignCreateForm.vue'
import { registerComponent } from '@/utils/routerHelper'
import { isEmpty } from '@/utils/is'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'BpmProcessInstanceDetail' })
@ -126,10 +155,10 @@ const message = useMessage() // 消息弹窗
const { proxy } = getCurrentInstance() as any
const userId = useUserStore().getUser.id // 当前登录的编号
const id = query.id as unknown as number // 流程实例的编号
const id = query.id as unknown as string // 流程实例的编号
const processInstanceLoading = ref(false) // 流程实例的加载中
const processInstance = ref<any>({}) // 流程实例
const bpmnXML = ref('') // BPMN XML
const bpmnXml = ref('') // BPMN XML
const tasksLoad = ref(true) // 任务的加载中
const tasks = ref<any[]>([]) // 任务列表
// ========== 审批信息 ==========
@ -138,14 +167,30 @@ const auditForms = ref<any[]>([]) // 审批任务的表单
const auditRule = reactive({
reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
})
const approveForms = ref<any[]>([]) // 审批通过时,额外的补充信息
const approveFormFApis = ref<ApiAttrs[]>([]) // approveForms 的 fAPi
// ========== 申请信息 ==========
const fApi = ref<ApiAttrs>() //
const detailForm = ref({
// 流程表单详情
rule: [],
option: {},
value: {}
})
}) // 流程实例的表单详情
/** 监听 approveFormFApis实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
watch(
() => approveFormFApis.value,
(value) => {
value?.forEach((api) => {
api.btn.show(false)
api.resetBtn.show(false)
})
},
{
deep: true
}
)
/** 处理审批通过和不通过的操作 */
const handleAudit = async (task, pass) => {
@ -161,9 +206,16 @@ const handleAudit = async (task, pass) => {
// 2.1 提交审批
const data = {
id: task.id,
reason: auditForms.value[index].reason
reason: auditForms.value[index].reason,
copyUserIds: auditForms.value[index].copyUserIds
}
if (pass) {
// 审批通过,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
const formCreateApi = approveFormFApis.value[index]
if (formCreateApi) {
await formCreateApi.validate()
data.variables = approveForms.value[index].value
}
await TaskApi.approveTask(data)
message.success('审批通过成功')
} else {
@ -175,28 +227,27 @@ const handleAudit = async (task, pass) => {
}
/** 转派审批人 */
const taskUpdateAssigneeFormRef = ref()
const taskTransferFormRef = ref()
const openTaskUpdateAssigneeForm = (id: string) => {
taskUpdateAssigneeFormRef.value.open(id)
taskTransferFormRef.value.open(id)
}
const taskDelegateForm = ref()
/** 处理审批退回的操作 */
const taskDelegateForm = ref()
const handleDelegate = async (task) => {
taskDelegateForm.value.open(task.id)
}
//回退弹框组件
const taskReturnDialogRef = ref()
/** 处理审批退回的操作 */
const handleBack = async (task) => {
taskReturnDialogRef.value.open(task.id)
const taskReturnFormRef = ref()
const handleBack = async (task: any) => {
taskReturnFormRef.value.open(task.id)
}
const taskAddSignDialogForm = ref()
/** 处理审批加签的操作 */
const handleSign = async (task) => {
taskAddSignDialogForm.value.open(task.id)
const taskSignCreateFormRef = ref()
const handleSign = async (task: any) => {
taskSignCreateFormRef.value.open(task.id)
}
/** 获得详情 */
@ -239,7 +290,9 @@ const getProcessInstance = async () => {
}
// 加载流程图
bpmnXML.value = await DefinitionApi.getProcessDefinitionBpmnXML(processDefinition.id as number)
bpmnXml.value = (
await DefinitionApi.getProcessDefinition(processDefinition.id as number)
)?.bpmnXml
} finally {
processInstanceLoading.value = false
}
@ -247,6 +300,10 @@ const getProcessInstance = async () => {
/** 加载任务列表 */
const getTaskList = async () => {
runningTasks.value = []
auditForms.value = []
approveForms.value = []
approveFormFApis.value = []
try {
// 获得未取消的任务
tasksLoad.value = true
@ -254,7 +311,7 @@ const getTaskList = async () => {
tasks.value = []
// 1.1 移除已取消的审批
data.forEach((task) => {
if (task.result !== 4) {
if (task.status !== 4) {
tasks.value.push(task)
}
})
@ -274,8 +331,6 @@ const getTaskList = async () => {
})
// 获得需要自己审批的任务
runningTasks.value = []
auditForms.value = []
loadRunningTask(tasks.value)
} finally {
tasksLoad.value = false
@ -291,7 +346,7 @@ const loadRunningTask = (tasks) => {
loadRunningTask(task.children)
}
// 2.1 只有待处理才需要
if (task.result !== 1 && task.result !== 6) {
if (task.status !== 1 && task.status !== 6) {
return
}
// 2.2 自己不是处理人
@ -301,13 +356,26 @@ const loadRunningTask = (tasks) => {
// 2.3 添加到处理任务
runningTasks.value.push({ ...task })
auditForms.value.push({
reason: ''
reason: '',
copyUserIds: []
})
// 2.4 处理 approve 表单
if (task.formId && task.formConf) {
const approveForm = {}
setConfAndFields2(approveForm, task.formConf, task.formFields, task.formVariable)
approveForms.value.push(approveForm)
} else {
approveForms.value.push({}) // 占位,避免为空
}
})
}
/** 初始化 */
onMounted(() => {
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
onMounted(async () => {
getDetail()
// 获得用户列表
userOptions.value = await UserApi.getSimpleUserList()
})
</script>

View File

@ -1,5 +1,5 @@
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
<ContentWrap>
<!-- 搜索工作栏 -->
@ -36,15 +36,20 @@
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_CATEGORY)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
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-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"
@ -53,17 +58,7 @@
/>
</el-select>
</el-form-item>
<el-form-item label="结果" prop="result">
<el-select v-model="queryParams.result" placeholder="请选择结果" clearable class="!w-240px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="提交时间" prop="createTime">
<el-form-item label="发起时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
@ -81,7 +76,7 @@
type="primary"
plain
v-hasPermi="['bpm:process-instance:query']"
@click="handleCreate"
@click="handleCreate()"
>
<Icon icon="ep:plus" class="mr-5px" /> 发起流程
</el-button>
@ -92,34 +87,23 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="流程编号" align="center" prop="id" width="300px" />
<el-table-column label="流程名称" align="center" prop="name" />
<el-table-column label="流程分类" align="center" prop="category">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_MODEL_CATEGORY" :value="scope.row.category" />
</template>
</el-table-column>
<el-table-column label="当前审批任务" align="center" prop="tasks">
<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="状态" prop="status">
<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="流程状态" 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="结果" prop="result">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
</template>
</el-table-column>
<el-table-column
label="提交时间"
label="发起时间"
align="center"
prop="createTime"
prop="startTime"
width="180"
:formatter="dateFormatter"
/>
@ -130,7 +114,20 @@
width="180"
:formatter="dateFormatter"
/>
<el-table-column label="操作" align="center">
<el-table-column align="center" label="耗时" prop="durationInMillis" width="160">
<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
@ -143,12 +140,15 @@
<el-button
link
type="primary"
v-if="scope.row.result === 1"
v-if="scope.row.status === 1"
v-hasPermi="['bpm:process-instance:query']"
@click="handleCancel(scope.row)"
>
取消
</el-button>
<el-button link type="primary" v-else @click="handleCreate(scope.row.id)">
重新发起
</el-button>
</template>
</el-table-column>
</el-table>
@ -163,11 +163,12 @@
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
import { ElMessageBox } from 'element-plus'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { CategoryApi } from '@/api/bpm/category'
defineOptions({ name: 'BpmProcessInstance' })
defineOptions({ name: 'BpmProcessInstanceMy' })
const router = useRouter() // 路由
const message = useMessage() // 消息弹窗
@ -183,16 +184,16 @@ const queryParams = reactive({
processDefinitionId: undefined,
category: undefined,
status: undefined,
result: undefined,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const categoryList = ref([]) // 流程分类列表
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProcessInstanceApi.getMyProcessInstancePage(queryParams)
const data = await ProcessInstanceApi.getProcessInstanceMyPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
@ -213,9 +214,10 @@ const resetQuery = () => {
}
/** 发起流程操作 **/
const handleCreate = () => {
const handleCreate = (id) => {
router.push({
name: 'BpmProcessInstanceCreate'
name: 'BpmProcessInstanceCreate',
query: { processInstanceId: id }
})
}
@ -239,14 +241,20 @@ const handleCancel = async (row) => {
inputErrorMessage: '取消原因不能为空'
})
// 发起取消
await ProcessInstanceApi.cancelProcessInstance(row.id, value)
await ProcessInstanceApi.cancelProcessInstanceByStartUser(row.id, value)
message.success('取消成功')
// 刷新列表
await getList()
}
/** 初始化 **/
onMounted(() => {
/** 激活时 **/
onActivated(() => {
getList()
})
/** 初始化 **/
onMounted(async () => {
await getList()
categoryList.value = await CategoryApi.getCategorySimpleList()
})
</script>

View File

@ -0,0 +1,255 @@
<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-220px"
/>
</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>

View File

@ -0,0 +1,162 @@
<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"
:label="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>

View File

@ -0,0 +1,185 @@
<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>

View File

@ -0,0 +1,28 @@
<template>
<div>
<section class="dingflow-design">
<div class="box-scale">
<nodeWrap v-model:nodeConfig="nodeConfig" />
<div class="end-node">
<div class="end-node-circle"></div>
<div class="end-node-text">流程结束</div>
</div>
</div>
</section>
</div>
</template>
<script lang="ts" setup>
import nodeWrap from '@/components/SimpleProcessDesigner/src/nodeWrap.vue'
defineOptions({ name: 'SimpleWorkflowDesignEditor' })
let nodeConfig = ref({
nodeName: '发起人',
type: 0,
id: 'root',
formPerms: {},
nodeUserList: [],
childNode: {}
})
</script>
<style>
@import url('@/components/SimpleProcessDesigner/theme/workflow.css');
</style>

View File

@ -1,5 +1,10 @@
<!-- 工作流 - 抄送我的流程 -->
<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">
@ -11,14 +16,6 @@
placeholder="请输入流程名称"
/>
</el-form-item>
<el-form-item label="所属流程" prop="processDefinitionId">
<el-input
v-model="queryParams.processInstanceId"
placeholder="请输入流程定义的编号"
clearable
class="!w-240px"
/>
</el-form-item>
<el-form-item label="抄送时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
@ -46,12 +43,17 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="所属流程" prop="processInstanceId" width="300px" />
<el-table-column align="center" label="流程名称" prop="processInstanceName" />
<el-table-column align="center" label="任务名称" prop="taskName" />
<el-table-column align="center" label="流程发起人" prop="startUserNickname" />
<el-table-column align="center" label="抄送发起人" prop="creatorNickname" />
<el-table-column align="center" label="抄送原因" prop="reason" />
<el-table-column align="center" label="流程" prop="processInstanceName" min-width="180" />
<el-table-column align="center" label="流程发起人" prop="startUserName" min-width="100" />
<el-table-column
:formatter="dateFormatter"
align="center"
label="流程发起时间"
prop="processInstanceStartTime"
width="180"
/>
<el-table-column align="center" label="抄送任务" prop="taskName" min-width="180" />
<el-table-column align="center" label="抄送人" prop="creatorName" min-width="100" />
<el-table-column
align="center"
label="抄送时间"
@ -59,9 +61,9 @@
width="180"
:formatter="dateFormatter"
/>
<el-table-column align="center" label="操作">
<el-table-column align="center" label="操作" fixed="right" width="80">
<template #default="scope">
<el-button link type="primary" @click="handleAudit(scope.row)">跳转待办</el-button>
<el-button link type="primary" @click="handleAudit(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
@ -78,14 +80,14 @@
import { dateFormatter } from '@/utils/formatTime'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
defineOptions({ name: 'BpmCCProcessInstance' })
defineOptions({ name: 'BpmProcessInstanceCopy' })
const { push } = useRouter() //
const loading = ref(false) //
const total = ref(0) //
const list = ref([]) //
const queryParams = ref({
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
processInstanceId: '',
@ -98,7 +100,7 @@ const queryFormRef = ref() // 搜索的表单
const getList = async () => {
loading.value = true
try {
const data = await ProcessInstanceApi.getProcessInstanceCCPage(queryParams)
const data = await ProcessInstanceApi.getProcessInstanceCopyPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
@ -118,7 +120,7 @@ const handleAudit = (row: any) => {
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNo = 1
queryParams.pageNo = 1
getList()
}

View File

@ -1,51 +0,0 @@
<template>
<Dialog v-model="dialogVisible" :max-height="500" :scroll="true" title="详情">
<el-descriptions :column="1" border>
<el-descriptions-item label="任务编号" min-width="120">
{{ detailData.id }}
</el-descriptions-item>
<el-descriptions-item label="任务名称">
{{ detailData.name }}
</el-descriptions-item>
<el-descriptions-item label="所属流程">
{{ detailData.processInstance.name }}
</el-descriptions-item>
<el-descriptions-item label="流程发起人">
{{ detailData.processInstance.startUserNickname }}
</el-descriptions-item>
<el-descriptions-item label="状态">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="detailData.result" />
</el-descriptions-item>
<el-descriptions-item label="原因">
{{ detailData.reason }}
</el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ formatDate(detailData.createTime) }}
</el-descriptions-item>
</el-descriptions>
</Dialog>
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task'
defineOptions({ name: 'BpmTaskDetail' })
const dialogVisible = ref(false) // 弹窗的是否展示
const detailLoading = ref(false) // 表单的加载中
const detailData = ref() // 详情数据
/** 打开弹窗 */
const open = async (data: TaskApi.TaskVO) => {
dialogVisible.value = true
// 设置数据
detailLoading.value = true
try {
detailData.value = data
} finally {
detailLoading.value = false
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
</script>

View File

@ -1,5 +1,11 @@
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<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>
<!-- 搜索工作栏 -->
@ -46,27 +52,51 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="任务编号" prop="id" width="300px" />
<el-table-column align="center" label="任务名称" prop="name" />
<el-table-column align="center" label="所属流程" prop="processInstance.name" />
<el-table-column align="center" label="流程发起人" prop="processInstance.startUserNickname" />
<el-table-column align="center" label="状态" prop="result">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
</template>
</el-table-column>
<el-table-column align="center" label="原因" prop="reason" />
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
<el-table-column
align="center"
label="发起人"
prop="processInstance.startUser.nickname"
width="100"
/>
<el-table-column
:formatter="dateFormatter"
align="center"
label="创建时间"
label="发起时间"
prop="createTime"
width="180"
/>
<el-table-column align="center" label="操作">
<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">
<el-button link type="primary" @click="openDetail(scope.row)">详情</el-button>
<el-button link type="primary" @click="handleAudit(scope.row)">流程</el-button>
<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="id" :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>
@ -78,15 +108,11 @@
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗详情 -->
<TaskDetail ref="detailRef" @success="getList" />
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task'
import TaskDetail from './TaskDetail.vue'
defineOptions({ name: 'BpmTodoTask' })
@ -107,7 +133,7 @@ const queryFormRef = ref() // 搜索的表单
const getList = async () => {
loading.value = true
try {
const data = await TaskApi.getDoneTaskPage(queryParams)
const data = await TaskApi.getTaskDonePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
@ -127,14 +153,8 @@ const resetQuery = () => {
handleQuery()
}
/** 详情操作 */
const detailRef = ref()
const openDetail = (row: TaskApi.TaskVO) => {
detailRef.value.open(row)
}
/** 处理审批按钮 */
const handleAudit = (row) => {
const handleAudit = (row: any) => {
push({
name: 'BpmProcessInstanceDetail',
query: {

View File

@ -0,0 +1,166 @@
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<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 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">
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
<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="assigneeUser.nickname" width="100" />
<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="id" :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 } from '@/utils/dict'
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task'
// 它和【待办任务】【已办任务】的差异是,该菜单可以看全部的流程任务
defineOptions({ name: 'BpmManagerTask' })
const { push } = useRouter() // 路由
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: '',
createTime: []
})
const queryFormRef = ref() // 搜索的表单
/** 查询任务列表 */
const getList = async () => {
loading.value = true
try {
const data = await TaskApi.getTaskManagerPage(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
}
})
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>

View File

@ -1,5 +1,11 @@
<template>
<doc-alert title="工作流手册" url="https://doc.iocoder.cn/bpm/" />
<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>
<!-- 搜索工作栏 -->
@ -46,27 +52,33 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="任务编号" prop="id" width="300px" />
<el-table-column align="center" label="任务名称" prop="name" />
<el-table-column align="center" label="所属流程" prop="processInstance.name" />
<el-table-column align="center" label="流程发起人" prop="processInstance.startUserNickname" />
<el-table-column align="center" label="流程" prop="processInstance.name" width="180" />
<el-table-column
align="center"
label="发起人"
prop="processInstance.startUser.nickname"
width="100"
/>
<el-table-column
:formatter="dateFormatter"
align="center"
label="创建时间"
label="发起时间"
prop="createTime"
width="180"
/>
<el-table-column label="任务状态" prop="suspensionState">
<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 align="center" label="流程编号" prop="id" :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-tag v-if="scope.row.suspensionState === 1" type="success">激活</el-tag>
<el-tag v-if="scope.row.suspensionState === 2" type="warning">挂起</el-tag>
</template>
</el-table-column>
<el-table-column align="center" label="操作">
<template #default="scope">
<el-button link type="primary" @click="handleAudit(scope.row)">审批进度</el-button>
<el-button link type="primary" @click="handleCC(scope.row)">抄送</el-button>
<el-button link type="primary" @click="handleAudit(scope.row)">办理</el-button>
</template>
</el-table-column>
</el-table>
@ -77,16 +89,14 @@
:total="total"
@pagination="getList"
/>
<TaskCCDialogForm ref="taskCCDialogForm" />
</ContentWrap>
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as TaskApi from '@/api/bpm/task'
import TaskCCDialogForm from '../../processInstance/detail/TaskCCDialogForm.vue'
defineOptions({ name: 'BpmDoneTask' })
defineOptions({ name: 'BpmTodoTask' })
const { push } = useRouter() // 路由
@ -105,7 +115,7 @@ const queryFormRef = ref() // 搜索的表单
const getList = async () => {
loading.value = true
try {
const data = await TaskApi.getTodoTaskPage(queryParams)
const data = await TaskApi.getTaskTodoPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
@ -126,7 +136,7 @@ const resetQuery = () => {
}
/** 处理审批按钮 */
const handleAudit = (row) => {
const handleAudit = (row: any) => {
push({
name: 'BpmProcessInstanceDetail',
query: {
@ -135,12 +145,6 @@ const handleAudit = (row) => {
})
}
const taskCCDialogForm = ref()
/** 处理抄送按钮 */
const handleCC = (row) => {
taskCCDialogForm.value.open(row)
}
/** 初始化 **/
onMounted(() => {
getList()

View File

@ -1,250 +0,0 @@
<template>
<Dialog v-model="dialogVisible" title="修改任务规则" width="600">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="80px">
<el-form-item label="任务名称" prop="taskDefinitionName">
<el-input v-model="formData.taskDefinitionName" disabled placeholder="请输入流标标识" />
</el-form-item>
<el-form-item label="任务标识" prop="taskDefinitionKey">
<el-input v-model="formData.taskDefinitionKey" disabled placeholder="请输入任务标识" />
</el-form-item>
<el-form-item label="规则类型" prop="type">
<el-select v-model="formData.type" clearable style="width: 100%">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="formData.type === 10" label="指定角色" prop="roleIds">
<el-select v-model="formData.roleIds" clearable multiple style="width: 100%">
<el-option
v-for="item in roleOptions"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="formData.type === 20 || formData.type === 21"
label="指定部门"
prop="deptIds"
span="24"
>
<el-tree-select
ref="treeRef"
v-model="formData.deptIds"
:data="deptTreeOptions"
:props="defaultProps"
empty-text="加载中请稍后"
multiple
node-key="id"
show-checkbox
/>
</el-form-item>
<el-form-item v-if="formData.type === 22" label="指定岗位" prop="postIds" span="24">
<el-select v-model="formData.postIds" clearable multiple style="width: 100%">
<el-option
v-for="item in postOptions"
:key="parseInt(item.id)"
:label="item.name"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="formData.type === 30 || formData.type === 31 || formData.type === 32"
label="指定用户"
prop="userIds"
span="24"
>
<el-select v-model="formData.userIds" clearable multiple style="width: 100%">
<el-option
v-for="item in userOptions"
:key="parseInt(item.id)"
:label="item.nickname"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item v-if="formData.type === 40" label="指定用户组" prop="userGroupIds">
<el-select v-model="formData.userGroupIds" clearable multiple style="width: 100%">
<el-option
v-for="item in userGroupOptions"
:key="parseInt(item.id)"
:label="item.name"
:value="parseInt(item.id)"
/>
</el-select>
</el-form-item>
<el-form-item v-if="formData.type === 50" label="指定脚本" prop="scripts">
<el-select v-model="formData.scripts" clearable multiple style="width: 100%">
<el-option
v-for="dict in taskAssignScriptDictDatas"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</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 { defaultProps, handleTree } from '@/utils/tree'
import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept'
import * as PostApi from '@/api/system/post'
import * as UserApi from '@/api/system/user'
import * as UserGroupApi from '@/api/bpm/userGroup'
defineOptions({ name: 'BpmTaskAssignRuleForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中1修改时的数据加载2提交的按钮禁用
const formData = ref({
type: Number(undefined),
modelId: '',
options: [],
roleIds: [],
deptIds: [],
postIds: [],
userIds: [],
userGroupIds: [],
scripts: []
})
const formRules = reactive({
type: [{ required: true, message: '规则类型不能为空', trigger: 'change' }],
roleIds: [{ required: true, message: '指定角色不能为空', trigger: 'change' }],
deptIds: [{ required: true, message: '指定部门不能为空', trigger: 'change' }],
postIds: [{ required: true, message: '指定岗位不能为空', trigger: 'change' }],
userIds: [{ required: true, message: '指定用户不能为空', trigger: 'change' }],
userGroupIds: [{ required: true, message: '指定用户组不能为空', trigger: 'change' }],
scripts: [{ required: true, message: '指定脚本不能为空', trigger: 'change' }]
})
const formRef = ref() // 表单 Ref
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
const deptTreeOptions = ref() // 部门树
const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT)
/** 打开弹窗 */
const open = async (modelId: string, row: TaskAssignRuleApi.TaskAssignVO) => {
// 1. 先重置表单
resetForm()
// 2. 再设置表单
formData.value = {
...row,
modelId: modelId,
options: [],
roleIds: [],
deptIds: [],
postIds: [],
userIds: [],
userGroupIds: [],
scripts: []
}
// 将 options 赋值到对应的 roleIds 等选项
if (row.type === 10) {
formData.value.roleIds.push(...row.options)
} else if (row.type === 20 || row.type === 21) {
formData.value.deptIds.push(...row.options)
} else if (row.type === 22) {
formData.value.postIds.push(...row.options)
} else if (row.type === 30 || row.type === 31 || row.type === 32) {
formData.value.userIds.push(...row.options)
} else if (row.type === 40) {
formData.value.userGroupIds.push(...row.options)
} else if (row.type === 50) {
formData.value.scripts.push(...row.options)
}
// 打开弹窗
dialogVisible.value = true
// 获得角色列表
roleOptions.value = await RoleApi.getSimpleRoleList()
// 获得部门列表
deptOptions.value = await DeptApi.getSimpleDeptList()
deptTreeOptions.value = handleTree(deptOptions.value, 'id')
// 获得岗位列表
postOptions.value = await PostApi.getSimplePostList()
// 获得用户列表
userOptions.value = await UserApi.getSimpleUserList()
// 获得用户组列表
userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 构建表单
const form = {
...formData.value,
taskDefinitionName: undefined
}
// 将 roleIds 等选项赋值到 options 中
if (form.type === 10) {
form.options = form.roleIds
} else if (form.type === 20 || form.type === 21) {
form.options = form.deptIds
} else if (form.type === 22) {
form.options = form.postIds
} else if (form.type === 30 || form.type === 31 || form.type === 32) {
form.options = form.userIds
} else if (form.type === 40) {
form.options = form.userGroupIds
} else if (form.type === 50) {
form.options = form.scripts
}
form.roleIds = undefined
form.deptIds = undefined
form.postIds = undefined
form.userIds = undefined
form.userGroupIds = undefined
form.scripts = undefined
// 提交请求
formLoading.value = true
try {
const data = form as unknown as TaskAssignRuleApi.TaskAssignVO
if (!data.id) {
await TaskAssignRuleApi.createTaskAssignRule(data)
message.success(t('common.createSuccess'))
} else {
await TaskAssignRuleApi.updateTaskAssignRule(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formRef.value?.resetFields()
}
</script>

View File

@ -1,136 +0,0 @@
<template>
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="任务名" align="center" prop="taskDefinitionName" />
<el-table-column label="任务标识" align="center" prop="taskDefinitionKey" />
<el-table-column label="规则类型" align="center" prop="type">
<template #default="scope">
<dict-tag :type="DICT_TYPE.BPM_TASK_ASSIGN_RULE_TYPE" :value="scope.row.type" />
</template>
</el-table-column>
<el-table-column label="规则范围" align="center" prop="options">
<template #default="scope">
<el-tag class="mr-5px" :key="option" v-for="option in scope.row.options">
{{ getAssignRuleOptionName(scope.row.type, option) }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="queryParams.modelId" label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm(scope.row)"
v-hasPermi="['bpm:task-assign-rule:update']"
>
修改
</el-button>
</template>
</el-table-column>
</el-table>
</ContentWrap>
<!-- 添加/修改弹窗 -->
<TaskAssignRuleForm ref="formRef" @success="getList" />
</template>
<script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as TaskAssignRuleApi from '@/api/bpm/taskAssignRule'
import * as RoleApi from '@/api/system/role'
import * as DeptApi from '@/api/system/dept'
import * as PostApi from '@/api/system/post'
import * as UserApi from '@/api/system/user'
import * as UserGroupApi from '@/api/bpm/userGroup'
import TaskAssignRuleForm from './TaskAssignRuleForm.vue'
defineOptions({ name: 'BpmTaskAssignRule' })
const { query } = useRoute() // 查询参数
const loading = ref(true) // 列表的加载中
const list = ref([]) // 列表的数据
const queryParams = reactive({
modelId: query.modelId, // 流程模型的编号。如果 modelId 非空,则用于流程模型的查看与配置
processDefinitionId: query.processDefinitionId // 流程定义的编号。如果 processDefinitionId 非空,则用于流程定义的查看,不支持配置
})
const roleOptions = ref<RoleApi.RoleVO[]>([]) // 角色列表
const deptOptions = ref<DeptApi.DeptVO[]>([]) // 部门列表
const postOptions = ref<PostApi.PostVO[]>([]) // 岗位列表
const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
const userGroupOptions = ref<UserGroupApi.UserGroupVO[]>([]) // 用户组列表
const taskAssignScriptDictDatas = getIntDictOptions(DICT_TYPE.BPM_TASK_ASSIGN_SCRIPT)
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
list.value = await TaskAssignRuleApi.getTaskAssignRuleList(queryParams)
} finally {
loading.value = false
}
}
/** 翻译规则范围 */
// TODO 芋艿:各种 ts 报错
const getAssignRuleOptionName = (type, option) => {
if (type === 10) {
for (const roleOption of roleOptions.value) {
if (roleOption.id === option) {
return roleOption.name
}
}
} else if (type === 20 || type === 21) {
for (const deptOption of deptOptions.value) {
if (deptOption.id === option) {
return deptOption.name
}
}
} else if (type === 22) {
for (const postOption of postOptions.value) {
if (postOption.id === option) {
return postOption.name
}
}
} else if (type === 30 || type === 31 || type === 32) {
for (const userOption of userOptions.value) {
if (userOption.id === option) {
return userOption.nickname
}
}
} else if (type === 40) {
for (const userGroupOption of userGroupOptions.value) {
if (userGroupOption.id === option) {
return userGroupOption.name
}
}
} else if (type === 50) {
option = option + '' // 转换成 string
for (const dictData of taskAssignScriptDictDatas) {
if (dictData.value === option) {
return dictData.label
}
}
}
return '未知(' + option + ')'
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (row: TaskAssignRuleApi.TaskAssignVO) => {
formRef.value.open(queryParams.modelId, row)
}
/** 初始化 */
onMounted(async () => {
await getList()
// 获得角色列表
roleOptions.value = await RoleApi.getSimpleRoleList()
// 获得部门列表
deptOptions.value = await DeptApi.getSimpleDeptList()
// 获得岗位列表
postOptions.value = await PostApi.getSimplePostList()
// 获得用户列表
userOptions.value = await UserApi.getSimpleUserList()
// 获得用户组列表
userGroupOptions.value = await UserGroupApi.getSimpleUserGroupList()
})
</script>

View File

@ -59,11 +59,11 @@ const formData = ref({
remark: ''
})
const formRules = reactive({
name: [{ required: true, message: '岗位标题不能为空', trigger: 'blur' }],
code: [{ required: true, message: '岗位编码不能为空', trigger: 'change' }],
sort: [{ required: true, message: '岗位顺序不能为空', trigger: 'change' }],
status: [{ required: true, message: '岗位状态不能为空', trigger: 'change' }],
remark: [{ required: false, message: '岗位内容不能为空', trigger: 'blur' }]
name: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],
code: [{ required: true, message: '角色标识不能为空', trigger: 'change' }],
sort: [{ required: true, message: '显示顺序不能为空', trigger: 'change' }],
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
remark: [{ required: false, message: '备注不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref