Merge branch 'feature/bpm' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into feature/bpm

pull/582/head
jason 2024-10-20 21:51:48 +08:00
commit 30052b05f5
19 changed files with 867 additions and 1757 deletions

View File

@ -53,11 +53,14 @@
/>
<el-table-column
label="审批人"
prop="assigneeUser.nickname"
min-width="100"
align="center"
v-if="selectActivityType === 'bpmn:UserTask'"
/>
>
<template #default="scope">
{{ scope.row.assigneeUser?.nickname || scope.row.ownerUser?.nickname }}
</template>
</el-table-column>
<el-table-column
label="发起人"
prop="assigneeUser.nickname"
@ -65,12 +68,11 @@
align="center"
v-else
/>
<el-table-column
label="部门"
prop="assigneeUser.deptName"
min-width="100"
align="center"
/>
<el-table-column label="部门" min-width="100" align="center">
<template #default="scope">
{{ scope.row.assigneeUser?.deptName || scope.row.ownerUser?.deptName }}
</template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"

View File

@ -292,8 +292,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
},
{
path: 'process-instance/detail',
component: () => import('@/views/bpm/processInstance/detail/index_new.vue'),
//component: () => import('@/views/bpm/processInstance/detail/index.vue'),
component: () => import('@/views/bpm/processInstance/detail/index.vue'),
name: 'BpmProcessInstanceDetail',
meta: {
noCache: true,

View File

@ -64,7 +64,11 @@ const designerConfig = ref({
switchType: [], // ,
autoActive: true, //
useTemplate: false, // vue2
formOptions: {}, //
formOptions: {
form: {
labelWidth: '100px' // label 100px
}
}, //
fieldReadonly: false, // field
hiddenDragMenu: false, //
hiddenDragBtn: false, //

View File

@ -1,216 +1,95 @@
<template>
<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>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="流程标识" prop="key">
<el-input
v-model="queryParams.key"
placeholder="请输入流程标识"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</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="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>
<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:model:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新建
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<div class="flex justify-between pl-20px items-center">
<h3 class="font-extrabold">流程模型</h3>
<!-- 搜索工作栏 -->
<el-form
v-if="!isCategorySorting"
class="-mb-15px flex mr-10px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
@submit.prevent
>
<el-form-item align="right" prop="key" class="ml-auto">
<el-input
v-model="queryParams.key"
placeholder="搜索流程"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
>
<template #prefix>
<Icon icon="ep:search" class="mx-10px" />
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="openForm('create')" v-hasPermi="['bpm:model:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新建模型
</el-button>
</el-form-item>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="流程名称" align="center" prop="name" min-width="200" />
<el-table-column label="流程图标" align="center" prop="icon" min-width="100">
<template #default="scope">
<el-image :src="scope.row.icon" class="h-32px w-32px" />
</template>
</el-table-column>
<el-table-column label="可见范围" align="center" prop="startUserIds" min-width="100">
<template #default="scope">
<el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
全部可见
</el-text>
<el-text v-else-if="scope.row.startUsers.length == 1">
{{ scope.row.startUsers[0].nickname }}
</el-text>
<el-text v-else>
<el-tooltip
class="box-item"
effect="dark"
placement="top"
:content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
>
{{ scope.row.startUsers[0].nickname }} {{ scope.row.startUsers.length }} 人可见
</el-tooltip>
</el-text>
</template>
</el-table-column>
<el-table-column label="流程分类" align="center" prop="categoryName" min-width="100" />
<el-table-column label="表单信息" align="center" prop="formType" min-width="200">
<template #default="scope">
<el-button
v-if="scope.row.formType === 10"
type="primary"
link
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formName }}</span>
</el-button>
<el-button
v-else-if="scope.row.formType === 20"
type="primary"
link
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formCustomCreatePath }}</span>
</el-button>
<label v-else></label>
</template>
</el-table-column>
<el-table-column label="最后发布" align="center" prop="deploymentTime" min-width="250">
<template #default="scope">
<span v-if="scope.row.processDefinition">
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
</span>
<el-tag v-if="scope.row.processDefinition" class="ml-10px">
v{{ scope.row.processDefinition.version }}
</el-tag>
<el-tag v-else type="warning">未部署</el-tag>
<el-tag
v-if="scope.row.processDefinition?.suspensionState === 2"
type="warning"
class="ml-10px"
>
已停用
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['bpm:model:update']"
:disabled="!isManagerUser(scope.row)"
>
修改
</el-button>
<el-button
link
class="!ml-5px"
type="primary"
@click="handleDesign(scope.row)"
v-hasPermi="['bpm:model:update']"
:disabled="!isManagerUser(scope.row)"
>
设计
</el-button>
<el-button
link
class="!ml-5px"
type="primary"
@click="handleDeploy(scope.row)"
v-hasPermi="['bpm:model:deploy']"
:disabled="!isManagerUser(scope.row)"
>
发布
</el-button>
<el-dropdown
class="!align-middle ml-5px"
@command="(command) => handleCommand(command, scope.row)"
v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
>
<el-button type="primary" link>更多</el-button>
<el-form-item>
<el-dropdown @command="(command) => handleCommand(command)" placement="bottom-end">
<el-button class="w-30px" plain>
<Icon icon="ep:setting" />
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
command="handleDefinitionList"
v-if="checkPermi(['bpm:process-definition:query'])"
>
历史
<el-dropdown-item command="handleAddCategory">
<Icon icon="ep:circle-plus" :size="13" class="mr-5px" />
新建分类
</el-dropdown-item>
<el-dropdown-item
command="handleChangeState"
v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
:disabled="!isManagerUser(scope.row)"
>
{{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
</el-dropdown-item>
<el-dropdown-item
type="danger"
command="handleDelete"
v-if="checkPermi(['bpm:model:delete'])"
:disabled="!isManagerUser(scope.row)"
>
删除
<el-dropdown-item command="handleSort">
<Icon icon="fa:sort-amount-desc" :size="13" class="mr-5px" />
分类排序
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-form-item>
</el-form>
<div class="mr-20px" v-else>
<el-button @click="cancelSort"> </el-button>
<el-button type="primary" @click="saveSort"> </el-button>
</div>
</div>
<el-divider />
<!-- 分类卡片组 -->
<div class="px-15px">
<draggable
:disabled="!isCategorySorting"
v-model="categoryGroup"
item-key="id"
:animation="400"
>
<template #item="{ element }">
<ContentWrap
class="rounded-lg transition-all duration-300 ease-in-out hover:shadow-xl"
v-loading="loading"
:body-style="{ padding: 0 }"
:key="element.id"
>
<CategoryDraggableModel
ref="categoryDraggableModelRef"
:isCategorySorting="isCategorySorting"
:categoryInfo="element"
@success="getList"
/>
</ContentWrap>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</draggable>
</div>
</ContentWrap>
<!-- 表单弹窗添加/修改流程 -->
<ModelForm ref="formRef" @success="getList" />
<!-- 表单弹窗添加/修改分类 -->
<CategoryForm ref="categoryFormRef" @success="getList" />
<!-- 弹窗表单详情 -->
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
@ -218,26 +97,20 @@
</template>
<script lang="ts" setup>
import { formatDate } from '@/utils/formatTime'
import * as ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form'
import ModelForm from './ModelForm.vue'
import { setConfAndFields2 } from '@/utils/formCreate'
import draggable from 'vuedraggable'
import { CategoryApi } from '@/api/bpm/category'
import { BpmModelType } from '@/utils/constants'
import { checkPermi } from '@/utils/permission'
import { useUserStoreWithOut } from '@/store/modules/user'
import * as ModelApi from '@/api/bpm/model'
import ModelForm from './ModelForm.vue'
import CategoryForm from '../category/CategoryForm.vue'
import { groupBy, cloneDeep } from 'lodash-es'
import CategoryDraggableModel from './CategoryDraggableModel.vue'
defineOptions({ name: 'BpmModel' })
const message = useMessage() //
const { t } = useI18n() //
const { push } = useRouter() //
const userStore = useUserStoreWithOut() //
const categoryDraggableModelRef = ref()
const categoryFormRef = ref()
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const isCategorySorting = ref(false) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@ -246,18 +119,26 @@ const queryParams = reactive({
category: undefined
})
const queryFormRef = ref() //
const categoryList = ref([]) //
const categoryGroup: any = ref([]) // category
const originalData: any = ref([])
//
const getAllCategory = async () => {
// TODO
const data = await CategoryApi.getCategoryPage(queryParams)
categoryGroup.value = data.list.map((item) => ({ ...item, modelList: [] }))
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ModelApi.getModelPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
/** 查询所有流程模型接口 */
const getAllModel = async () => {
// TODO
const data = await ModelApi.getModelPage(queryParams)
const groupedData = groupBy(data.list, 'categoryName')
Object.keys(groupedData).forEach((key) => {
const category = categoryGroup.value.find((item) => item.name === key)
if (category) {
category.modelList = groupedData[key]
}
})
}
/** 搜索按钮操作 */
@ -266,139 +147,82 @@ const handleQuery = () => {
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** '更多'操作按钮 */
const handleCommand = (command: string, row: any) => {
switch (command) {
case 'handleDefinitionList':
handleDefinitionList(row)
break
case 'handleDelete':
handleDelete(row)
break
case 'handleChangeState':
handleChangeState(row)
break
default:
break
}
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (row: any) => {
try {
//
await message.delConfirm()
//
await ModelApi.deleteModel(row.id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 更新状态操作 */
const handleChangeState = async (row: any) => {
const state = row.processDefinition.suspensionState
const newState = state === 1 ? 2 : 1
try {
//
const id = row.id
debugger
const statusState = state === 1 ? '停用' : '启用'
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
await message.confirm(content)
//
await ModelApi.updateModelState(id, newState)
message.success(statusState + '成功')
//
await getList()
} catch {}
}
/** 设计流程 */
const handleDesign = (row: any) => {
if (row.type == BpmModelType.BPMN) {
push({
name: 'BpmModelEditor',
query: {
modelId: row.id
}
})
} else {
push({
name: 'SimpleWorkflowDesignEditor',
query: {
modelId: row.id
}
})
}
}
/** 发布流程 */
const handleDeploy = async (row: any) => {
try {
//
await message.confirm('是否部署该流程!!')
//
await ModelApi.deployModel(row.id)
message.success(t('部署成功'))
//
await getList()
} catch {}
}
/** 跳转到指定流程定义列表 */
const handleDefinitionList = (row) => {
push({
name: 'BpmProcessDefinition',
query: {
key: row.key
}
})
}
/** 流程表单的详情按钮操作 */
const formDetailVisible = ref(false)
const formDetailPreview = ref({
rule: [],
option: {}
})
const handleFormDetail = async (row: any) => {
if (row.formType == 10) {
//
const data = await FormApi.getForm(row.formId)
setConfAndFields2(formDetailPreview, data.conf, data.fields)
//
formDetailVisible.value = true
} else {
await push({
path: row.formCustomCreatePath
})
/** 右上角设置按钮 */
const handleCommand = (command: string) => {
switch (command) {
case 'handleAddCategory':
handleAddCategory()
break
case 'handleSort':
handleSort()
break
default:
break
}
}
/** 判断是否可以操作 */
const isManagerUser = (row: any) => {
const userId = userStore.getUser.id
return row.managerUserIds && row.managerUserIds.includes(userId)
//
const handleAddCategory = () => {
categoryFormRef.value.open('create')
}
//
const handleSort = () => {
//
originalData.value = cloneDeep(categoryGroup.value)
isCategorySorting.value = true
}
//
const cancelSort = () => {
//
categoryGroup.value = cloneDeep(originalData.value)
isCategorySorting.value = false
}
//
const saveSort = () => {
// TODO
}
const getList = async () => {
loading.value = true
try {
await getAllCategory()
await getAllModel()
} finally {
loading.value = false
}
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
categoryList.value = await CategoryApi.getCategorySimpleList()
getList()
})
</script>
<style lang="scss" scoped>
:deep() {
.el-table--fit .el-table__inner-wrapper:before {
height: 0;
}
.el-card {
border-radius: 8px;
}
.el-form--inline .el-form-item {
margin-right: 10px;
}
.el-divider--horizontal {
margin-top: 6px;
}
}
</style>

View File

@ -1,228 +0,0 @@
<template>
<ContentWrap>
<div class="flex justify-between pl-20px items-center">
<h3 class="font-extrabold">流程模型</h3>
<!-- 搜索工作栏 -->
<el-form
v-if="!isCategorySorting"
class="-mb-15px flex mr-10px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
@submit.prevent
>
<el-form-item align="right" prop="key" class="ml-auto">
<el-input
v-model="queryParams.key"
placeholder="搜索流程"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
>
<template #prefix>
<Icon icon="ep:search" class="mx-10px" />
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="openForm('create')" v-hasPermi="['bpm:model:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新建模型
</el-button>
</el-form-item>
<el-form-item>
<el-dropdown @command="(command) => handleCommand(command)" placement="bottom-end">
<el-button class="w-30px" plain>
<Icon icon="ep:setting" />
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="handleAddCategory">
<Icon icon="ep:circle-plus" :size="13" class="mr-5px" />
新建分类
</el-dropdown-item>
<el-dropdown-item command="handleSort">
<Icon icon="fa:sort-amount-desc" :size="13" class="mr-5px" />
分类排序
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-form-item>
</el-form>
<div class="mr-20px" v-else>
<el-button @click="cancelSort"> </el-button>
<el-button type="primary" @click="saveSort"> </el-button>
</div>
</div>
<el-divider />
<!-- 分类卡片组 -->
<div class="px-15px">
<draggable
:disabled="!isCategorySorting"
v-model="categoryGroup"
item-key="id"
:animation="400"
>
<template #item="{ element }">
<ContentWrap
class="rounded-lg transition-all duration-300 ease-in-out hover:shadow-xl"
v-loading="loading"
:body-style="{ padding: 0 }"
:key="element.id"
>
<CategoryDraggableModel
ref="categoryDraggableModelRef"
:isCategorySorting="isCategorySorting"
:categoryInfo="element"
@success="getList"
/>
</ContentWrap>
</template>
</draggable>
</div>
</ContentWrap>
<!-- 表单弹窗添加/修改流程 -->
<ModelForm ref="formRef" @success="getList" />
<!-- 表单弹窗添加/修改分类 -->
<CategoryForm ref="categoryFormRef" @success="getList" />
<!-- 弹窗表单详情 -->
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
</Dialog>
</template>
<script lang="ts" setup>
import draggable from 'vuedraggable'
import { CategoryApi } from '@/api/bpm/category'
import * as ModelApi from '@/api/bpm/model'
import ModelForm from './ModelForm.vue'
import CategoryForm from '../category/CategoryForm.vue'
import { groupBy, cloneDeep } from 'lodash-es'
import CategoryDraggableModel from './CategoryDraggableModel.vue'
defineOptions({ name: 'BpmModel' })
const categoryDraggableModelRef = ref()
const categoryFormRef = ref()
const loading = ref(true) //
const isCategorySorting = ref(false) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
key: undefined,
name: undefined,
category: undefined
})
const queryFormRef = ref() //
const categoryGroup: any = ref([]) // category
const originalData: any = ref([])
//
const getAllCategory = async () => {
// TODO
const data = await CategoryApi.getCategoryPage(queryParams)
categoryGroup.value = data.list.map((item) => ({ ...item, modelList: [] }))
}
/** 查询所有流程模型接口 */
const getAllModel = async () => {
// TODO
const data = await ModelApi.getModelPage(queryParams)
const groupedData = groupBy(data.list, 'categoryName')
Object.keys(groupedData).forEach((key) => {
const category = categoryGroup.value.find((item) => item.name === key)
if (category) {
category.modelList = groupedData[key]
}
})
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 流程表单的详情按钮操作 */
const formDetailVisible = ref(false)
const formDetailPreview = ref({
rule: [],
option: {}
})
/** 右上角设置按钮 */
const handleCommand = (command: string) => {
switch (command) {
case 'handleAddCategory':
handleAddCategory()
break
case 'handleSort':
handleSort()
break
default:
break
}
}
//
const handleAddCategory = () => {
categoryFormRef.value.open('create')
}
//
const handleSort = () => {
//
originalData.value = cloneDeep(categoryGroup.value)
isCategorySorting.value = true
}
//
const cancelSort = () => {
//
categoryGroup.value = cloneDeep(originalData.value)
isCategorySorting.value = false
}
//
const saveSort = () => {
// TODO
}
const getList = async () => {
loading.value = true
try {
await getAllCategory()
await getAllModel()
} finally {
loading.value = false
}
}
/** 初始化 **/
onMounted(async () => {
getList()
})
</script>
<style lang="scss" scoped>
:deep() {
.el-table--fit .el-table__inner-wrapper:before {
height: 0;
}
.el-card {
border-radius: 8px;
}
.el-form--inline .el-form-item {
margin-right: 10px;
}
.el-divider--horizontal {
margin-top: 6px;
}
}
</style>

View File

@ -0,0 +1,404 @@
<template>
<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>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="流程标识" prop="key">
<el-input
v-model="queryParams.key"
placeholder="请输入流程标识"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</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="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>
<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:model:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新建
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column label="流程名称" align="center" prop="name" min-width="200" />
<el-table-column label="流程图标" align="center" prop="icon" min-width="100">
<template #default="scope">
<el-image :src="scope.row.icon" class="h-32px w-32px" />
</template>
</el-table-column>
<el-table-column label="可见范围" align="center" prop="startUserIds" min-width="100">
<template #default="scope">
<el-text v-if="!scope.row.startUsers || scope.row.startUsers.length === 0">
全部可见
</el-text>
<el-text v-else-if="scope.row.startUsers.length == 1">
{{ scope.row.startUsers[0].nickname }}
</el-text>
<el-text v-else>
<el-tooltip
class="box-item"
effect="dark"
placement="top"
:content="scope.row.startUsers.map((user: any) => user.nickname).join('、')"
>
{{ scope.row.startUsers[0].nickname }} {{ scope.row.startUsers.length }} 人可见
</el-tooltip>
</el-text>
</template>
</el-table-column>
<el-table-column label="流程分类" align="center" prop="categoryName" min-width="100" />
<el-table-column label="表单信息" align="center" prop="formType" min-width="200">
<template #default="scope">
<el-button
v-if="scope.row.formType === 10"
type="primary"
link
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formName }}</span>
</el-button>
<el-button
v-else-if="scope.row.formType === 20"
type="primary"
link
@click="handleFormDetail(scope.row)"
>
<span>{{ scope.row.formCustomCreatePath }}</span>
</el-button>
<label v-else></label>
</template>
</el-table-column>
<el-table-column label="最后发布" align="center" prop="deploymentTime" min-width="250">
<template #default="scope">
<span v-if="scope.row.processDefinition">
{{ formatDate(scope.row.processDefinition.deploymentTime) }}
</span>
<el-tag v-if="scope.row.processDefinition" class="ml-10px">
v{{ scope.row.processDefinition.version }}
</el-tag>
<el-tag v-else type="warning">未部署</el-tag>
<el-tag
v-if="scope.row.processDefinition?.suspensionState === 2"
type="warning"
class="ml-10px"
>
已停用
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['bpm:model:update']"
:disabled="!isManagerUser(scope.row)"
>
修改
</el-button>
<el-button
link
class="!ml-5px"
type="primary"
@click="handleDesign(scope.row)"
v-hasPermi="['bpm:model:update']"
:disabled="!isManagerUser(scope.row)"
>
设计
</el-button>
<el-button
link
class="!ml-5px"
type="primary"
@click="handleDeploy(scope.row)"
v-hasPermi="['bpm:model:deploy']"
:disabled="!isManagerUser(scope.row)"
>
发布
</el-button>
<el-dropdown
class="!align-middle ml-5px"
@command="(command) => handleCommand(command, scope.row)"
v-hasPermi="['bpm:process-definition:query', 'bpm:model:update', 'bpm:model:delete']"
>
<el-button type="primary" link>更多</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
command="handleDefinitionList"
v-if="checkPermi(['bpm:process-definition:query'])"
>
历史
</el-dropdown-item>
<el-dropdown-item
command="handleChangeState"
v-if="checkPermi(['bpm:model:update']) && scope.row.processDefinition"
:disabled="!isManagerUser(scope.row)"
>
{{ scope.row.processDefinition.suspensionState === 1 ? '停用' : '启用' }}
</el-dropdown-item>
<el-dropdown-item
type="danger"
command="handleDelete"
v-if="checkPermi(['bpm:model:delete'])"
:disabled="!isManagerUser(scope.row)"
>
删除
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改流程 -->
<ModelForm ref="formRef" @success="getList" />
<!-- 弹窗表单详情 -->
<Dialog title="表单详情" v-model="formDetailVisible" width="800">
<form-create :rule="formDetailPreview.rule" :option="formDetailPreview.option" />
</Dialog>
</template>
<script lang="ts" setup>
import { formatDate } from '@/utils/formatTime'
import * as ModelApi from '@/api/bpm/model'
import * as FormApi from '@/api/bpm/form'
import ModelForm from './ModelForm.vue'
import { setConfAndFields2 } from '@/utils/formCreate'
import { CategoryApi } from '@/api/bpm/category'
import { BpmModelType } from '@/utils/constants'
import { checkPermi } from '@/utils/permission'
import { useUserStoreWithOut } from '@/store/modules/user'
defineOptions({ name: 'BpmModel' })
const message = useMessage() //
const { t } = useI18n() //
const { push } = useRouter() //
const userStore = useUserStoreWithOut() //
const loading = ref(true) //
const total = ref(0) //
const list = ref([]) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
key: undefined,
name: undefined,
category: undefined
})
const queryFormRef = ref() //
const categoryList = ref([]) //
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ModelApi.getModelPage(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 handleCommand = (command: string, row: any) => {
switch (command) {
case 'handleDefinitionList':
handleDefinitionList(row)
break
case 'handleDelete':
handleDelete(row)
break
case 'handleChangeState':
handleChangeState(row)
break
default:
break
}
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (row: any) => {
try {
//
await message.delConfirm()
//
await ModelApi.deleteModel(row.id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 更新状态操作 */
const handleChangeState = async (row: any) => {
const state = row.processDefinition.suspensionState
const newState = state === 1 ? 2 : 1
try {
//
const id = row.id
debugger
const statusState = state === 1 ? '停用' : '启用'
const content = '是否确认' + statusState + '流程名字为"' + row.name + '"的数据项?'
await message.confirm(content)
//
await ModelApi.updateModelState(id, newState)
message.success(statusState + '成功')
//
await getList()
} catch {}
}
/** 设计流程 */
const handleDesign = (row: any) => {
if (row.type == BpmModelType.BPMN) {
push({
name: 'BpmModelEditor',
query: {
modelId: row.id
}
})
} else {
push({
name: 'SimpleWorkflowDesignEditor',
query: {
modelId: row.id
}
})
}
}
/** 发布流程 */
const handleDeploy = async (row: any) => {
try {
//
await message.confirm('是否部署该流程!!')
//
await ModelApi.deployModel(row.id)
message.success(t('部署成功'))
//
await getList()
} catch {}
}
/** 跳转到指定流程定义列表 */
const handleDefinitionList = (row) => {
push({
name: 'BpmProcessDefinition',
query: {
key: row.key
}
})
}
/** 流程表单的详情按钮操作 */
const formDetailVisible = ref(false)
const formDetailPreview = ref({
rule: [],
option: {}
})
const handleFormDetail = async (row: any) => {
if (row.formType == 10) {
//
const data = await FormApi.getForm(row.formId)
setConfAndFields2(formDetailPreview, data.conf, data.fields)
//
formDetailVisible.value = true
} else {
await push({
path: row.formCustomCreatePath
})
}
}
/** 判断是否可以操作 */
const isManagerUser = (row: any) => {
const userId = userStore.getUser.id
return row.managerUserIds && row.managerUserIds.includes(userId)
}
/** 初始化 **/
onMounted(async () => {
await getList()
//
categoryList.value = await CategoryApi.getCategorySimpleList()
})
</script>

View File

@ -1,8 +1,5 @@
<template>
<el-card v-loading="loading" class="box-card">
<template #header v-if="showHeader">
<span class="el-icon-picture-outline">流程图</span>
</template>
<MyProcessViewer key="designer" :xml="view.bpmnXml" :view="view" class="h-700px" />
</el-card>
</template>
@ -16,8 +13,7 @@ defineOptions({ name: 'BpmProcessInstanceBpmnViewer' })
const props = defineProps({
loading: propTypes.bool.def(false), //
id: propTypes.string, //
bpmnXml: propTypes.string, // BPMN XML
showHeader: propTypes.bool.def(true), //
bpmnXml: propTypes.string // BPMN XML
})
const view = ref({

View File

@ -391,7 +391,7 @@
</div>
</el-popover>
<!--按钮 这个对应发起人的取消, 只有发起人可以取消 -->
<!--按钮 这个对应发起人的取消, 只有发起人可以取消 -->
<el-popover
:visible="popOverVisible.cancel"
placement="top-start"
@ -403,7 +403,7 @@
>
<template #reference>
<div @click="openPopover('cancel')" class="hover-bg-gray-100 rounded-xl p-6px">
<Icon :size="14" icon="fa:mail-reply" />&nbsp;
<Icon :size="14" icon="fa:mail-reply" />&nbsp;
</div>
</template>
<div class="flex flex-col flex-1 pt-20px px-20px" v-loading="formLoading">
@ -415,20 +415,20 @@
:rules="genericRule"
label-width="100px"
>
<el-form-item label="消理由" prop="cancelReason">
<span class="text-#878c93 text-12px">&nbsp; 消后该审批流程将自动结束</span>
<el-form-item label="消理由" prop="cancelReason">
<span class="text-#878c93 text-12px">&nbsp; 消后该审批流程将自动结束</span>
<el-input
v-model="genericForm.cancelReason"
clearable
placeholder="请输入消理由"
placeholder="请输入消理由"
type="textarea"
:rows="3"
/>
</el-form-item>
<el-form-item>
<el-button :disabled="formLoading" type="primary" @click="handleCancel()"
>撤消</el-button
>
<el-button :disabled="formLoading" type="primary" @click="handleCancel()">
取消
</el-button>
<el-button @click="popOverVisible.cancel = false"> 取消 </el-button>
</el-form-item>
</el-form>
@ -499,7 +499,7 @@ const formRef = ref()
const genericRule = reactive({
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
returnReason: [{ required: true, message: '退回理由不能为空', trigger: 'blur' }],
cancelReason: [{ required: true, message: '消理由不能为空', trigger: 'blur' }],
cancelReason: [{ required: true, message: '消理由不能为空', trigger: 'blur' }],
copyUserIds: [{ required: true, message: '抄送人不能为空', trigger: 'change' }],
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],

View File

@ -1,9 +1,6 @@
<template>
<el-card v-loading="loading" class="box-card">
<template #header v-if="showHeader">
<span class="el-icon-picture-outline">审批记录</span>
</template>
<el-col :offset="3" :span="17">
<el-col>
<div class="block">
<el-timeline>
<el-timeline-item
@ -26,14 +23,6 @@
<p style="font-weight: 700">
审批任务{{ item.name }}
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="item.status" />
<el-button
class="ml-10px"
v-if="!isEmpty(item.children)"
@click="openChildrenTask(item)"
size="small"
>
<Icon icon="ep:memo" /> 子任务
</el-button>
<el-button
class="ml-10px"
size="small"
@ -78,8 +67,6 @@
</el-col>
</el-card>
<!-- 弹窗子任务 -->
<TaskSignList ref="taskSignListRef" @success="refresh" />
<!-- 弹窗表单 -->
<Dialog title="表单详情" v-model="taskFormVisible" width="600">
<form-create
@ -94,8 +81,6 @@
import { formatDate, formatPast2 } from '@/utils/formatTime'
import { propTypes } from '@/utils/propTypes'
import { DICT_TYPE } from '@/utils/dict'
import { isEmpty } from '@/utils/is'
import TaskSignList from './dialog/TaskSignList.vue'
import type { ApiAttrs } from '@form-create/element-ui/types/config'
import { setConfAndFields2 } from '@/utils/formCreate'
@ -104,8 +89,7 @@ defineOptions({ name: 'BpmProcessInstanceTaskList' })
defineProps({
loading: propTypes.bool, //
processInstance: propTypes.object, //
tasks: propTypes.arrayOf(propTypes.object), //
showHeader: propTypes.bool.def(true), //
tasks: propTypes.arrayOf(propTypes.object) //
})
/** 获得流程实例对应的颜色 */
@ -142,12 +126,6 @@ const getTaskTimelineItemType = (item: any) => {
return ''
}
/** 子任务 */
const taskSignListRef = ref()
const openChildrenTask = (item: any) => {
taskSignListRef.value.open(item)
}
/** 查看表单 */
const fApi = ref<ApiAttrs>() // form-create API
const taskForm = ref({

View File

@ -17,42 +17,21 @@
<div class="flex flex-col pr-2">
<div class="position-relative" v-if="task.assigneeUser || task.ownerUser">
<!-- 信息头像 -->
<el-tooltip
:content="task.reason"
placement="bottom"
<el-avatar
v-if="task.assigneeUser && task.assigneeUser.avatar"
effect="light"
>
<el-avatar :size="36" :src="task.assigneeUser.avatar" />
</el-tooltip>
<el-tooltip
:content="task.reason"
placement="bottom"
v-else-if="task.assigneeUser && task.assigneeUser.nickname"
effect="light"
>
<el-avatar>
{{ task.assigneeUser.nickname.substring(0, 1) }}
</el-avatar>
</el-tooltip>
<el-tooltip
:content="task.reason"
placement="bottom"
:size="36"
:src="task.assigneeUser.avatar"
/>
<el-avatar v-else-if="task.assigneeUser && task.assigneeUser.nickname">
{{ task.assigneeUser.nickname.substring(0, 1) }}
</el-avatar>
<el-avatar
v-else-if="task.ownerUser && task.ownerUser.avatar"
effect="light"
>
<el-avatar :src="task.ownerUser.avatar" />
</el-tooltip>
<el-tooltip
:content="task.reason"
placement="bottom"
v-else-if="task.ownerUser && task.ownerUser.nickname"
effect="light"
>
<el-avatar>
{{ task.ownerUser.nickname.substring(0, 1) }}
</el-avatar>
</el-tooltip>
:src="task.ownerUser.avatar"
/>
<el-avatar v-else-if="task.ownerUser && task.ownerUser.nickname">
{{ task.ownerUser.nickname.substring(0, 1) }}
</el-avatar>
<!-- 信息任务 ICON -->
<div
class="position-absolute top-26px left-26px bg-#fff rounded-full flex items-center p-2px"
@ -78,16 +57,12 @@
>
{{ task.ownerUser.nickname }}
</div>
<!--
<div v-if="task.reason" class="text-#a5a5a5 my-4px text-12px flex items-center w-100%">
<div
:title="task.reason"
class="text-truncate w-200px border-1px border-#a5a5a5 border-dashed rounded py-5px px-15px text-#2d2d2d"
>
{{ task.reason }}
</div>
<div
v-if="task.reason && activity.nodeType === NodeType.USER_TASK_NODE"
class="text-#a5a5a5 text-13px mt-1"
>
审批意见{{ task.reason }}
</div>
-->
</div>
</div>
</div>
@ -131,20 +106,6 @@
>
{{ getApprovalNodeTime(activity) }}
</div>
<!-- TODO @jason审批意见要展示哈 -->
<!-- <div class="color-#a1a6ae text-12px mb-10px"> {{ activity.assigneeUser.nickname }}</div>
<div v-if="activity.opinion" class="text-#a5a5a5 text-12px w-100%">
<div class="mb-5px">审批意见</div>
<div
class="w-100% border-1px border-#a5a5a5 border-dashed rounded py-5px px-15px text-#2d2d2d"
>
{{ activity.opinion }}
</div>
</div>
<div v-if="activity.createTime" class="text-#a5a5a5 text-13px">
{{ formatDate(activity.createTime) }}
</div> -->
</div>
</el-timeline-item>
</el-timeline>
@ -219,8 +180,11 @@ const getApprovalNodeColor = (taskStatus: number) => {
}
const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
if (node.nodeType === NodeType.START_USER_NODE && node.startTime) {
return `发起时间:${formatDate(node.startTime)}`
}
if (node.endTime) {
return `结束时间:${formatDate(node.endTime)}`
return `审批时间:${formatDate(node.endTime)}`
}
if (node.startTime) {
return `创建时间:${formatDate(node.startTime)}`

View File

@ -1,89 +0,0 @@
<template>
<Dialog v-model="dialogVisible" title="委派任务" width="500">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="110px"
>
<el-form-item label="接收人" prop="delegateUserId">
<el-select v-model="formData.delegateUserId" clearable style="width: 100%">
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</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>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as TaskApi from '@/api/bpm/task'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'BpmTaskDelegateForm' })
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
id: '',
delegateUserId: undefined,
reason: ''
})
const formRules = ref({
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '委派理由不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const userList = ref<any[]>([]) //
/** 打开弹窗 */
const open = async (id: string) => {
dialogVisible.value = true
resetForm()
formData.value.id = id
//
userList.value = await UserApi.getSimpleUserList()
}
defineExpose({ open }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
await TaskApi.delegateTask(formData.value)
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: '',
delegateUserId: undefined,
reason: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,90 +0,0 @@
<template>
<Dialog v-model="dialogVisible" title="退回任务" width="500">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="110px"
>
<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.taskDefinitionKey"
:label="item.name"
:value="item.taskDefinitionKey"
/>
</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>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" name="TaskRollbackDialogForm" setup>
import * as TaskApi from '@/api/bpm/task'
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
id: '',
targetTaskDefinitionKey: undefined,
reason: ''
})
const formRules = ref({
targetTaskDefinitionKey: [{ required: true, message: '必须选择退回节点', trigger: 'change' }],
reason: [{ required: true, message: '退回理由不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const returnList = ref([] as any)
/** 打开弹窗 */
const open = async (id: string) => {
returnList.value = await TaskApi.getTaskListByReturn(id)
if (returnList.value.length === 0) {
message.warning('当前没有可退回的节点')
return false
}
dialogVisible.value = true
resetForm()
formData.value.id = id
}
defineExpose({ open }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
await TaskApi.returnTask(formData.value)
message.success('退回成功')
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: '',
targetTaskDefinitionKey: undefined,
reason: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,99 +0,0 @@
<template>
<Dialog v-model="dialogVisible" title="加签" width="500">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="110px"
>
<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"
:label="item.nickname"
:value="item.id"
/>
</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('before')">
向前加签
</el-button>
<el-button :disabled="formLoading" type="primary" @click="submitForm('after')">
向后加签
</el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as TaskApi from '@/api/bpm/task'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'TaskSignCreateForm' })
const message = useMessage() //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
id: '',
userIds: [],
type: '',
reason: ''
})
const formRules = ref({
userIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '加签理由不能为空', trigger: 'change' }]
})
const formRef = ref() // Ref
const userList = ref<any[]>([]) //
/** 打开弹窗 */
const open = async (id: string) => {
dialogVisible.value = true
resetForm()
formData.value.id = id
//
userList.value = await UserApi.getSimpleUserList()
}
defineExpose({ open }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async (type: string) => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
formData.value.type = type
try {
await TaskApi.signCreateTask(formData.value)
message.success('加签成功')
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: '',
userIds: [],
type: '',
reason: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -86,4 +86,5 @@ const resetForm = () => {
}
formRef.value?.resetFields()
}
// TODO @jason
</script>

View File

@ -103,4 +103,5 @@ const handleSignDeleteSuccess = () => {
const isSignDeleteButtonVisible = (task: any) => {
return task && task.children && !isEmpty(task.children)
}
// TODO @jason
</script>

View File

@ -1,89 +0,0 @@
<template>
<Dialog v-model="dialogVisible" title="转派任务" width="500">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="110px"
>
<el-form-item label="新审批人" prop="assigneeUserId">
<el-select v-model="formData.assigneeUserId" clearable style="width: 100%">
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</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>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as TaskApi from '@/api/bpm/task'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'TaskTransferForm' })
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const formData = ref({
id: '',
assigneeUserId: undefined,
reason: ''
})
const formRules = ref({
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '转派理由不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const userList = ref<any[]>([]) //
/** 打开弹窗 */
const open = async (id: string) => {
dialogVisible.value = true
resetForm()
formData.value.id = id
//
userList.value = await UserApi.getSimpleUserList()
}
defineExpose({ open }) // openModal
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
//
formLoading.value = true
try {
await TaskApi.transferTask(formData.value)
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: '',
assigneeUserId: undefined,
reason: ''
}
formRef.value?.resetFields()
}
</script>

View File

@ -1,216 +1,163 @@
<template>
<ContentWrap>
<!-- 审批信息 -->
<el-card
v-for="(item, index) in runningTasks"
:key="index"
v-loading="processInstanceLoading"
class="box-card"
>
<template #header>
<span class="el-icon-picture-outline">审批任务{{ item.name }}</span>
</template>
<el-col :offset="6" :span="16">
<el-form
:ref="'form' + index"
:model="auditForms[index]"
:rules="auditRule"
label-width="100px"
>
<el-form-item v-if="processInstance && processInstance.name" label="流程名">
{{ 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>
</el-form-item>
<el-card v-if="runningTasks[index].formId > 0" class="mb-15px !-mt-10px">
<template #header>
<span class="el-icon-picture-outline">
填写表单{{ runningTasks[index]?.formName }}
</span>
</template>
<form-create
v-model="approveForms[index].value"
v-model:api="approveFormFApis[index]"
:option="approveForms[index].option"
:rule="approveForms[index].rule"
/>
</el-card>
<el-form-item label="审批建议" prop="reason">
<el-input
v-model="auditForms[index].reason"
placeholder="请输入审批建议"
type="textarea"
/>
</el-form-item>
<el-form-item label="抄送人" prop="copyUserIds">
<el-select v-model="auditForms[index].copyUserIds" multiple placeholder="请选择抄送人">
<el-option
v-for="itemx in userOptions"
:key="itemx.id"
:label="itemx.nickname"
:value="itemx.id"
/>
</el-select>
</el-form-item>
</el-form>
<div style="margin-bottom: 20px; margin-left: 10%; font-size: 14px">
<!-- TODO @jason建议搞个 if 来判断替代现有的 !item.buttonsSetting || item.buttonsSetting[OpsButtonType.APPROVE]?.enable -->
<el-button
type="success"
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.APPROVE]?.enable"
@click="handleAudit(item, true)"
>
<Icon icon="ep:select" />
<!-- TODO @jason这个也是类似哈搞个方法来生成名字 -->
{{
item.buttonsSetting?.[OperationButtonType.APPROVE]?.displayName ||
OPERATION_BUTTON_NAME.get(OperationButtonType.APPROVE)
}}
</el-button>
<el-button
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.REJECT]?.enable"
type="danger"
@click="handleAudit(item, false)"
>
<Icon icon="ep:close" />
{{
item.buttonsSetting?.[OperationButtonType.REJECT].displayName ||
OPERATION_BUTTON_NAME.get(OperationButtonType.REJECT)
}}
</el-button>
<el-button
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.TRANSFER]?.enable"
type="primary"
@click="openTaskUpdateAssigneeForm(item.id)"
>
<Icon icon="ep:edit" />
{{
item.buttonsSetting?.[OperationButtonType.TRANSFER]?.displayName ||
OPERATION_BUTTON_NAME.get(OperationButtonType.TRANSFER)
}}
</el-button>
<el-button
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.DELEGATE]?.enable"
type="primary"
@click="handleDelegate(item)"
>
<Icon icon="ep:position" />
{{
item.buttonsSetting?.[OperationButtonType.DELEGATE]?.displayName ||
OPERATION_BUTTON_NAME.get(OperationButtonType.DELEGATE)
}}
</el-button>
<el-button
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.ADD_SIGN]?.enable"
type="primary"
@click="handleSign(item)"
>
<Icon icon="ep:plus" />
{{
item.buttonsSetting?.[OperationButtonType.ADD_SIGN]?.displayName ||
OPERATION_BUTTON_NAME.get(OperationButtonType.ADD_SIGN)
}}
</el-button>
<el-button
v-if="!item.buttonsSetting || item.buttonsSetting[OperationButtonType.RETURN]?.enable"
type="warning"
@click="handleBack(item)"
>
<Icon icon="ep:back" />
{{
item.buttonsSetting?.[OperationButtonType.RETURN]?.displayName ||
OPERATION_BUTTON_NAME.get(OperationButtonType.RETURN)
}}
</el-button>
</div>
</el-col>
</el-card>
<!-- 申请信息 -->
<el-card v-loading="processInstanceLoading" class="box-card">
<template #header>
<span class="el-icon-document">申请信息{{ processInstance.name }}</span>
</template>
<!-- 情况一流程表单 -->
<el-col v-if="processInstance?.processDefinition?.formType === 10" :offset="6" :span="16">
<form-create
v-model="detailForm.value"
v-model:api="fApi"
:option="detailForm.option"
:rule="detailForm.rule"
<ContentWrap :bodyStyle="{ padding: '10px 20px 0' }" class="position-relative">
<div class="processInstance-wrap-main">
<el-scrollbar>
<img
class="position-absolute right-20px"
width="150"
:src="auditIcons[processInstance.status]"
alt=""
/>
</el-col>
<!-- 情况二业务表单 -->
<div v-if="processInstance?.processDefinition?.formType === 20">
<BusinessFormComponent :id="processInstance.businessKey" />
</div>
</el-card>
<div class="text-#878c93 h-15px">编号{{ id }}</div>
<el-divider class="!my-8px" />
<div class="flex items-center gap-5 mb-10px h-40px">
<div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
<dict-tag
v-if="processInstance.status"
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
:value="processInstance.status"
/>
</div>
<!-- 审批记录 -->
<ProcessInstanceTaskList
:loading="tasksLoad"
:process-instance="processInstance"
:tasks="tasks"
@refresh="getTaskList"
/>
<div class="flex items-center gap-5 mb-10px text-13px h-35px">
<div
class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600"
>
<el-avatar
:size="28"
v-if="processInstance?.startUser?.avatar"
:src="processInstance?.startUser?.avatar"
/>
<el-avatar :size="28" v-else-if="processInstance?.startUser?.nickname">
{{ processInstance?.startUser?.nickname.substring(0, 1) }}
</el-avatar>
{{ processInstance?.startUser?.nickname }}
</div>
<div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
</div>
<!-- 高亮流程图 -->
<ProcessInstanceBpmnViewer :id="`${id}`" :loading="processInstanceLoading" />
<el-tabs v-model="activeTab">
<!-- 表单信息 -->
<el-tab-pane label="审批详情" name="form">
<div class="form-scroll-area">
<el-scrollbar>
<el-row>
<el-col :span="17" class="!flex !flex-col formCol">
<!-- 表单信息 -->
<div
v-loading="processInstanceLoading"
class="form-box flex flex-col mb-30px flex-1"
>
<!-- 情况一流程表单 -->
<el-col v-if="processDefinition?.formType === 10">
<form-create
v-model="detailForm.value"
v-model:api="fApi"
:option="detailForm.option"
:rule="detailForm.rule"
/>
</el-col>
<!-- 情况二业务表单 -->
<div v-if="processDefinition?.formType === 20">
<BusinessFormComponent :id="processInstance.businessKey" />
</div>
</div>
</el-col>
<el-col :span="7">
<!-- 审批记录时间线 -->
<ProcessInstanceTimeline ref="timelineRef" :approve-nodes="approveNodes" />
</el-col>
</el-row>
</el-scrollbar>
</div>
</el-tab-pane>
<!-- 弹窗转派审批人 -->
<TaskTransferForm ref="taskTransferFormRef" @success="getDetail" />
<!-- 弹窗退回节点 -->
<TaskReturnForm ref="taskReturnFormRef" @success="getDetail" />
<!-- 弹窗委派将任务委派给别人处理处理完成后会重新回到原审批人手中-->
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
<!-- 弹窗加签当前任务审批人为A向前加签选了一个C则需要C先审批然后再是A审批向后加签BA审批完需要B再审批完才算完成这个任务节点 -->
<TaskSignCreateForm ref="taskSignCreateFormRef" @success="getDetail" />
<!-- 流程图 -->
<el-tab-pane label="流程图" name="diagram">
<div class="form-scroll-area">
<ProcessInstanceBpmnViewer
:id="`${id}`"
:loading="processInstanceLoading"
:show-header="false"
/>
</div>
</el-tab-pane>
<!-- 流转记录 -->
<el-tab-pane label="流转记录" name="record">
<div class="form-scroll-area">
<el-scrollbar>
<ProcessInstanceTaskList
:loading="tasksLoad"
:process-instance="processInstance"
:tasks="tasks"
:show-header="false"
/>
</el-scrollbar>
</div>
</el-tab-pane>
<!-- 流转评论 TODO 待开发 -->
<el-tab-pane label="流转评论" name="comment" v-if="false">
<div class="form-scroll-area">
<el-scrollbar> 流转评论 </el-scrollbar>
</div>
</el-tab-pane>
</el-tabs>
<div class="b-t-solid border-t-1px border-[var(--el-border-color)]">
<!-- 操作栏按钮 -->
<ProcessInstanceOperationButton
ref="operationButtonRef"
:process-instance="processInstance"
:process-definition="processDefinition"
:userOptions="userOptions"
@success="refresh"
/>
</div>
</el-scrollbar>
</div>
</ContentWrap>
</template>
<script lang="ts" setup>
import { useUserStore } from '@/store/modules/user'
import { formatDate } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import { setConfAndFields2 } from '@/utils/formCreate'
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 ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
import ProcessInstanceTaskList from './ProcessInstanceTaskList.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 ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue'
import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
import * as UserApi from '@/api/system/user'
import {
OperationButtonType,
OPERATION_BUTTON_NAME
} from '@/components/SimpleProcessDesignerV2/src/consts'
import { FieldPermissionType } from '@/components/SimpleProcessDesignerV2/src/consts'
import audit1 from '@/assets/svgs/bpm/audit1.svg'
import audit2 from '@/assets/svgs/bpm/audit2.svg'
import audit3 from '@/assets/svgs/bpm/audit3.svg'
import audit4 from '@/assets/svgs/bpm/audit4.svg'
defineOptions({ name: 'BpmProcessInstanceDetail' })
const { query } = useRoute() //
const props = defineProps<{
id: string //
taskId?: string //
activityId?: string //
}>()
const message = useMessage() //
const { proxy } = getCurrentInstance() as any
const userId = useUserStore().getUser.id //
const id = query.id as unknown as string //
const processInstanceLoading = ref(false) //
const processInstance = ref<any>({}) //
const bpmnXml = ref('') // BPMN XML
const processDefinition = ref<any>({}) //
const timelineRef = ref()
// ref
const operationButtonRef = ref()
const tasksLoad = ref(true) //
const tasks = ref<any[]>([]) //
// ========== ==========
const runningTasks = ref<any[]>([]) //
const auditForms = ref<any[]>([]) //
const auditRule = reactive({
reason: [{ required: true, message: '审批建议不能为空', trigger: 'blur' }]
})
const approveForms = ref<any[]>([]) //
const approveFormFApis = ref<ApiAttrs[]>([]) // approveForms fAPi
const auditIcons = {
1: audit1,
2: audit2,
3: audit3,
4: audit4
}
// ========== ==========
const fApi = ref<ApiAttrs>() //
@ -220,163 +167,106 @@ const detailForm = ref({
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) => {
// 1.1
const index = runningTasks.value.indexOf(task)
const auditFormRef = proxy.$refs['form' + index][0]
// 1.2
const elForm = unref(auditFormRef)
if (!elForm) return
let valid = await elForm.validate()
if (!valid) return
//
// TODO @jason if (!fApi.value) return
if (fApi.value) {
valid = await fApi.value.validate()
if (!valid) return
}
// 2.1
const data = {
id: task.id,
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
}
//
if (fApi.value) {
data.variables = getWritableValueOfForm(task.fieldsPermission)
}
await TaskApi.approveTask(data)
message.success('审批通过成功')
} else {
await TaskApi.rejectTask(data)
message.success('审批不通过成功')
}
// 2.2
getDetail()
}
/** 转派审批人 */
const taskTransferFormRef = ref()
const openTaskUpdateAssigneeForm = (id: string) => {
taskTransferFormRef.value.open(id)
}
/** 处理审批退回的操作 */
const taskDelegateForm = ref()
const handleDelegate = async (task) => {
taskDelegateForm.value.open(task.id)
}
/** 处理审批退回的操作 */
const taskReturnFormRef = ref()
const handleBack = async (task: any) => {
taskReturnFormRef.value.open(task.id)
}
/** 处理审批加签的操作 */
const taskSignCreateFormRef = ref()
const handleSign = async (task: any) => {
taskSignCreateFormRef.value.open(task.id)
}
/** 获得详情 */
const getDetail = async () => {
// 1.
await getTaskList()
// 2.
getProcessInstance()
const getDetail = () => {
// 1.
getApprovalDetail()
// 2.
getTaskList()
}
/** 加载流程实例 */
const BusinessFormComponent = ref(null) //
const getProcessInstance = async () => {
const BusinessFormComponent = ref<any>(null) //
/** 获取审批详情 */
const getApprovalDetail = async () => {
processInstanceLoading.value = true
try {
processInstanceLoading.value = true
const data = await ProcessInstanceApi.getProcessInstance(id)
const param = {
processInstanceId: props.id,
activityId: props.activityId,
taskId: props.taskId
}
const data = await ProcessInstanceApi.getApprovalDetail(param)
if (!data) {
message.error('查询不到审批详情信息!')
return
}
if (!data.processDefinition || !data.processInstance) {
message.error('查询不到流程信息!')
return
}
processInstance.value = data
processInstance.value = data.processInstance
processDefinition.value = data.processDefinition
//
const processDefinition = data.processDefinition
if (processDefinition.formType === 10) {
if (processDefinition.value.formType === 10) {
//
const formFieldsPermission = data.formFieldsPermission
if (detailForm.value.rule.length > 0) {
detailForm.value.value = data.formVariables
// form-create
detailForm.value.value = processInstance.value.formVariables
} else {
setConfAndFields2(
detailForm,
processDefinition.formConf,
processDefinition.formFields,
data.formVariables
processDefinition.value.formConf,
processDefinition.value.formFields,
processInstance.value.formVariables
)
}
nextTick().then(() => {
fApi.value?.btn.show(false)
fApi.value?.resetBtn.show(false)
//@ts-ignore
fApi.value?.disabled(true)
//
if (runningTasks.value.length > 0) {
const task = runningTasks.value.at(0)
if (task.fieldsPermission) {
Object.keys(task.fieldsPermission).forEach((item) => {
setFieldPermission(item, task.fieldsPermission[item])
})
}
//
if (formFieldsPermission) {
Object.keys(data.formFieldsPermission).forEach((item) => {
setFieldPermission(item, formFieldsPermission[item])
})
}
})
} else {
// data.processDefinition.formCustomViewPath /crm/contract/detail/index.vue
BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
}
//
bpmnXml.value = (
await DefinitionApi.getProcessDefinition(processDefinition.id as number)
)?.bpmnXml
// Timeline
approveNodes.value = data.approveNodes
//
operationButtonRef.value?.loadTodoTask(data.todoTask)
} finally {
processInstanceLoading.value = false
}
}
//
const approveNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([])
/**
* 设置表单权限
*/
const setFieldPermission = (field: string, permission: string) => {
if (permission === FieldPermissionType.READ) {
//@ts-ignore
fApi.value?.disabled(true, field)
}
if (permission === FieldPermissionType.WRITE) {
//@ts-ignore
fApi.value?.disabled(false, field)
}
if (permission === FieldPermissionType.NONE) {
//@ts-ignore
fApi.value?.hidden(true, field)
}
}
/** 加载任务列表 */
const getTaskList = async () => {
runningTasks.value = []
auditForms.value = []
approveForms.value = []
approveFormFApis.value = []
try {
//
tasksLoad.value = true
const data = await TaskApi.getTaskListByProcessInstanceId(id)
const data = await TaskApi.getTaskListByProcessInstanceId(props.id)
tasks.value = []
// 1.1
data.forEach((task) => {
data.forEach((task: any) => {
if (task.status !== 4) {
tasks.value.push(task)
}
@ -395,77 +285,21 @@ const getTaskList = async () => {
return b.createTime - a.createTime
}
})
//
loadRunningTask(tasks.value)
} finally {
tasksLoad.value = false
}
}
/**
* 设置 runningTasks 中的任务
* 操作成功后刷新
*/
const loadRunningTask = (tasks) => {
tasks.forEach((task) => {
if (!isEmpty(task.children)) {
loadRunningTask(task.children)
}
// 2.1
if (task.status !== 1 && task.status !== 6) {
return
}
// 2.2
if (!task.assigneeUser || task.assigneeUser.id !== userId) {
return
}
// 2.3
runningTasks.value.push({ ...task })
auditForms.value.push({
reason: '',
copyUserIds: []
})
// 2.4 approve
if (task.formId && task.formConf) {
const approveForm = {}
setConfAndFields2(approveForm, task.formConf, task.formFields, task.formVariables)
approveForms.value.push(approveForm)
} else {
approveForms.value.push({}) //
}
})
const refresh = () => {
//
getDetail()
}
/**
* 设置表单权限
*/
const setFieldPermission = (field: string, permission: string) => {
if (permission === '1') {
fApi.value?.disabled(true, field)
}
if (permission === '2') {
fApi.value?.disabled(false, field)
}
if (permission === '3') {
fApi.value?.hidden(true, field)
}
}
/**
* 获取可以编辑字段的值
*/
const getWritableValueOfForm = (fieldsPermission: Object) => {
const fieldsValue = {}
if (fieldsPermission && fApi.value) {
Object.keys(fieldsPermission).forEach((item) => {
if (fieldsPermission[item] === '2') {
fieldsValue[item] = fApi.value.getValue(item)
}
})
}
return fieldsValue
}
/** 当前的Tab */
const activeTab = ref('form')
/** 初始化 */
const userOptions = ref<UserApi.UserVO[]>([]) //
@ -475,3 +309,38 @@ onMounted(async () => {
userOptions.value = await UserApi.getSimpleUserList()
})
</script>
<style lang="scss" scoped>
$wrap-padding-height: 20px;
$wrap-margin-height: 15px;
$button-height: 51px;
$process-header-height: 194px;
.processInstance-wrap-main {
height: calc(
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
);
max-height: calc(
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
);
overflow: auto;
.form-scroll-area {
height: calc(
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
$process-header-height - 40px
);
max-height: calc(
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
$process-header-height - 40px
);
overflow: auto;
}
}
.form-box {
:deep(.el-card) {
border: none;
}
}
</style>

View File

@ -1,341 +0,0 @@
<template>
<ContentWrap :bodyStyle="{ padding: '10px 20px 0' }" class="position-relative">
<div class="processInstance-wrap-main">
<el-scrollbar>
<img
class="position-absolute right-20px"
width="150"
:src="auditIcons[processInstance.status]"
alt=""
/>
<div class="text-#878c93 h-15px">编号{{ id }}</div>
<el-divider class="!my-8px" />
<div class="flex items-center gap-5 mb-10px h-40px">
<div class="text-26px font-bold mb-5px">{{ processInstance.name }}</div>
<dict-tag
v-if="processInstance.status"
:type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS"
:value="processInstance.status"
/>
</div>
<div class="flex items-center gap-5 mb-10px text-13px h-35px">
<div class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600">
<el-avatar
:size="28"
v-if="processInstance?.startUser?.avatar"
:src="processInstance?.startUser?.avatar"
/>
<el-avatar :size="28" v-else-if="processInstance?.startUser?.nickname">
{{ processInstance?.startUser?.nickname.substring(0, 1) }}
</el-avatar>
{{ processInstance?.startUser?.nickname }}
</div>
<div class="text-#878c93"> {{ formatDate(processInstance.startTime) }} 提交 </div>
</div>
<el-tabs v-model="activeTab">
<!-- 表单信息 -->
<el-tab-pane label="审批详情" name="form">
<div class="form-scroll-area">
<el-scrollbar>
<el-row>
<el-col :span="18" class="!flex !flex-col formCol">
<!-- 表单信息 -->
<div
v-loading="processInstanceLoading"
class="form-box flex flex-col mb-30px flex-1"
>
<!-- 情况一流程表单 -->
<el-col v-if="processDefinition?.formType === 10">
<form-create
v-model="detailForm.value"
v-model:api="fApi"
:option="detailForm.option"
:rule="detailForm.rule"
/>
</el-col>
<!-- 情况二业务表单 -->
<div v-if="processDefinition?.formType === 20">
<BusinessFormComponent :id="processInstance.businessKey" />
</div>
</div>
</el-col>
<el-col :span="6">
<!-- 审批记录时间线 -->
<ProcessInstanceTimeline ref="timelineRef" :approve-nodes="approveNodes" />
</el-col>
</el-row>
</el-scrollbar>
</div>
</el-tab-pane>
<!-- 流程图 -->
<el-tab-pane label="流程图" name="diagram">
<div class="form-scroll-area">
<ProcessInstanceBpmnViewer :id="`${id}`" :loading="processInstanceLoading" :show-header="false"/>
</div>
</el-tab-pane>
<!-- 流转记录 -->
<el-tab-pane label="流转记录" name="record">
<div class="form-scroll-area">
<el-scrollbar>
<ProcessInstanceTaskList
:loading="tasksLoad"
:process-instance="processInstance"
:tasks="tasks"
:show-header="false"
/>
</el-scrollbar>
</div>
</el-tab-pane>
<!-- 流转评论 TODO 待开发 -->
<el-tab-pane label="流转评论" name="comment">
<div class="form-scroll-area">
<el-scrollbar> 流转评论 </el-scrollbar>
</div>
</el-tab-pane>
</el-tabs>
<div class="b-t-solid border-t-1px border-[var(--el-border-color)]">
<!-- 操作栏按钮 -->
<ProcessInstanceOperationButton
ref="operationButtonRef"
:process-instance="processInstance"
:process-definition="processDefinition"
:userOptions="userOptions"
@success="refresh"
/>
</div>
</el-scrollbar>
</div>
</ContentWrap>
</template>
<script lang="ts" setup>
import { formatDate } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import { setConfAndFields2 } from '@/utils/formCreate'
import type { ApiAttrs } from '@form-create/element-ui/types/config'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import * as TaskApi from '@/api/bpm/task'
import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
import ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue'
import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
import * as UserApi from '@/api/system/user'
import { FieldPermissionType } from '@/components/SimpleProcessDesignerV2/src/consts'
import audit1 from '@/assets/svgs/bpm/audit1.svg'
import audit2 from '@/assets/svgs/bpm/audit2.svg'
import audit3 from '@/assets/svgs/bpm/audit3.svg'
import audit4 from '@/assets/svgs/bpm/audit4.svg'
defineOptions({ name: 'BpmProcessInstanceDetail' })
const props = defineProps<{
id: string //
taskId?: string //
activityId?: string //
}>()
const message = useMessage() //
const processInstanceLoading = ref(false) //
const processInstance = ref<any>({}) //
const processDefinition = ref<any>({}) //
const timelineRef = ref()
// ref
const operationButtonRef = ref()
const tasksLoad = ref(true) //
const tasks = ref<any[]>([]) //
const auditIcons = {
1: audit1,
2: audit2,
3: audit3,
4: audit4
}
// ========== ==========
const fApi = ref<ApiAttrs>() //
const detailForm = ref({
rule: [],
option: {},
value: {}
}) //
/** 获得详情 */
const getDetail = async () => {
// 1.
getApprovalDetail()
// 2.
getTaskList()
}
/** 加载流程实例 */
const BusinessFormComponent = ref<any>(null) //
/** 获取审批详情 */
const getApprovalDetail = async () => {
processInstanceLoading.value = true
try {
const param = {
processInstanceId: props.id,
activityId: props.activityId,
taskId: props.taskId
}
const data = await ProcessInstanceApi.getApprovalDetail(param);
if (!data) {
message.error('查询不到审批详情信息!')
return
}
if(!data.processDefinition || !data.processInstance) {
message.error('查询不到流程信息!')
return
}
processInstance.value = data.processInstance
processDefinition.value = data.processDefinition
//
if (processDefinition.value.formType === 10) {
//
const formFieldsPermission = data.formFieldsPermission
if (detailForm.value.rule.length > 0) { // form-create
detailForm.value.value = processInstance.value.formVariables
} else {
setConfAndFields2(
detailForm,
processDefinition.value.formConf,
processDefinition.value.formFields,
processInstance.value.formVariables
)
}
nextTick().then(() => {
fApi.value?.btn.show(false)
fApi.value?.resetBtn.show(false)
//@ts-ignore
fApi.value?.disabled(true)
//
if (formFieldsPermission) {
Object.keys(data.formFieldsPermission).forEach((item) => {
setFieldPermission(item, formFieldsPermission[item])
})
}
})
}
// Timeline
approveNodes.value = data.approveNodes
//
operationButtonRef.value?.loadTodoTask(data.todoTask)
} finally {
processInstanceLoading.value = false
}
}
//
const approveNodes = ref<ProcessInstanceApi.ApprovalNodeInfo[]>([])
/**
* 设置表单权限
*/
const setFieldPermission = (field: string, permission: string) => {
if (permission === FieldPermissionType.READ) {
//@ts-ignore
fApi.value?.disabled(true, field)
}
if (permission === FieldPermissionType.WRITE) {
//@ts-ignore
fApi.value?.disabled(false, field)
}
if (permission === FieldPermissionType.NONE) {
//@ts-ignore
fApi.value?.hidden(true, field)
}
}
/** 加载任务列表 */
const getTaskList = async () => {
try {
//
tasksLoad.value = true
const data = await TaskApi.getTaskListByProcessInstanceId(props.id)
tasks.value = []
// 1.1
data.forEach((task: any) => {
if (task.status !== 4) {
tasks.value.push(task)
}
})
// 1.2
tasks.value.sort((a, b) => {
//
if (a.endTime && b.endTime) {
return b.endTime - a.endTime
} else if (a.endTime) {
return 1
} else if (b.endTime) {
return -1
//
} else {
return b.createTime - a.createTime
}
})
} finally {
tasksLoad.value = false
}
}
/**
* 操作成功后刷新
*/
const refresh = () => {
//
getDetail()
}
/** 当前的Tab */
const activeTab = ref('form')
/** 初始化 */
const userOptions = ref<UserApi.UserVO[]>([]) //
onMounted(async () => {
getDetail()
//
userOptions.value = await UserApi.getSimpleUserList()
})
</script>
<style lang="scss" scoped>
$wrap-padding-height: 20px;
$wrap-margin-height: 15px;
$button-height: 51px;
$process-header-height: 194px;
.processInstance-wrap-main {
height: calc(
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
);
max-height: calc(
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px
);
overflow: auto;
.form-scroll-area {
height: calc(
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
$process-header-height - 40px
);
max-height: calc(
100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 35px -
$process-header-height - 40px
);
overflow: auto;
}
}
.form-box {
:deep(.el-card) {
border: none;
}
}
</style>

View File

@ -49,7 +49,11 @@ const designerConfig = ref({
switchType: [], // ,
autoActive: true, //
useTemplate: false, // vue2
formOptions: {}, //
formOptions: {
form: {
labelWidth: '100px' // label 100px
}
}, //
fieldReadonly: false, // field
hiddenDragMenu: false, //
hiddenDragBtn: false, //