流程待处理、已处理、以及流程详情页操作回显

This commit is contained in:
2024-05-12 19:31:08 +08:00
parent 3b6222e672
commit 7c880dcbe4
23 changed files with 1974 additions and 132 deletions

View File

@@ -0,0 +1,17 @@
import createAxios from '@/utils/request'
import { BPM_BOOT } from '@/utils/constantRequest'
const MAPPING_PATH = BPM_BOOT + '/bpm/activity'
/**
* 生成指定流程实例的高亮流程图
*/
export const getTaskListByReturn = (processInstanceId: string) => {
return createAxios({
url: MAPPING_PATH + '/getActivityList?processInstanceId='+processInstanceId,
method: 'GET'
})
}

View File

@@ -0,0 +1,27 @@
import createAxios from '@/utils/request'
import { BPM_BOOT } from '@/utils/constantRequest'
const MAPPING_PATH = BPM_BOOT + '/bpm/processDefinition'
/**
* 通过任务
*/
export const approveTask = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/approve',
method: 'POST',
data: data
})
}
/**
* 获得流程定义
*/
export const getProcessDefinitionById = (id: string, key: string) => {
return createAxios({
url: MAPPING_PATH + '/get?id=' + id + '&key=' + key,
method: 'GET'
})
}

View File

@@ -0,0 +1,28 @@
import createAxios from '@/utils/request'
import { BPM_BOOT } from '@/utils/constantRequest'
const MAPPING_PATH = BPM_BOOT + '/bpm/processInstance'
/**
* 通过任务
*/
export const approveTask = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/approve',
method: 'POST',
data: data
})
}
/**
* 获得指定流程实例
*/
export const getProcessInstanceById = (id: string) => {
return createAxios({
url: MAPPING_PATH + '/get?id='+id,
method: 'GET'
})
}

127
src/api/bpm-boot/task.ts Normal file
View File

@@ -0,0 +1,127 @@
import createAxios from '@/utils/request'
import { BPM_BOOT } from '@/utils/constantRequest'
const MAPPING_PATH = BPM_BOOT + '/bpm/task'
/**
* 通过任务
*/
export const approveTask = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/approve',
method: 'POST',
data: data
})
}
/**
* 不通过任务
*/
export const rejectTask = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/reject',
method: 'POST',
data: data
})
}
/**
* 委派任务
*/
export const delegateTask = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/delegate',
method: 'POST',
data: data
})
}
/**
* 回退任务
*/
export const returnTask = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/return',
method: 'POST',
data: data
})
}
/**
* 加签
*/
export const createSignTask = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/createSign',
method: 'POST',
data: data
})
}
/**
* 减签
*/
export const deleteSignTask = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/deleteSign',
method: 'POST',
data: data
})
}
/**
* 转派任务
*/
export const transferTask = (data: any) => {
return createAxios({
url: MAPPING_PATH + '/transfer',
method: 'POST',
data: data
})
}
/**
* 加签
*/
export const getTaskListByParentTaskId = (parentTaskId: any) => {
return createAxios({
url: MAPPING_PATH + '/listByParentTaskId?parentTaskId='+parentTaskId,
method: 'GET'
})
}
/**
* 获取所有可回退的节点
*/
export const getTaskListByReturn = (id: string) => {
return createAxios({
url: MAPPING_PATH + '/listByReturn?id='+id,
method: 'GET'
})
}
/**
* 获得指定流程实例的任务列表
*/
export const listByProcessInstanceId = (parentTaskId: string) => {
return createAxios({
url: MAPPING_PATH + '/listByParentTaskId?parentTaskId='+parentTaskId,
method: 'GET'
})
}
/**
* 获得指定流程实例的任务列表
*/
export const getTaskListByProcessInstanceId = (processInstanceId: string) => {
return createAxios({
url: MAPPING_PATH + '/listByProcessInstanceId?processInstanceId='+processInstanceId,
method: 'GET'
})
}

View File

@@ -2,7 +2,7 @@ import createAxios from '@/utils/request'
import { SUPERVISION_BOOT } from '@/utils/constantRequest'
const MAPPING_PATH = SUPERVISION_BOOT + '/workflow/wfForm'
const MAPPING_PATH = SUPERVISION_BOOT + '/userReport'
/**
* 查询流程表单数据
@@ -14,3 +14,14 @@ export const getUserReport = (data: any) => {
data: data
})
}
/**
* 根据id获取用户档案录入的详细数据
*/
export const getUserReportById = (id: any) => {
return createAxios({
url: MAPPING_PATH + '/getById?id='+id,
method: 'GET'
})
}

View File

@@ -1872,7 +1872,7 @@ export const IconJson = {
'umbrella-beach',
'underline',
'undo',
'undo-alt',
'todo-alt',
'union',
'universal-access',
'university',

View File

@@ -2,7 +2,7 @@
<div ref="tableHeader" class="cn-table-header">
<div class="table-header ba-scroll-style">
<el-form
style="flex: 1; height: 32px; overflow: hidden; margin-right: 20px"
style="flex: 1; height: 34px; overflow: hidden; margin-right: 20px"
ref="headerForm"
@submit.prevent=""
@keyup.enter="onComSearch"

View File

@@ -111,6 +111,14 @@ export const adminBaseRoute = {
title: pageTitle('router.modelEdit')
}
},
{
path: 'instanceDetail',
component: () => import('@/views/system/bpm/processInstance/detail/index.vue'),
name: 'BpmProcessInstanceDetail',
meta: {
title: pageTitle('router.instanceDetail')
}
},
// {
// path: 'manager/model/edit',
// component: () => import('@/views/bpm/model/editor/index.vue'),
@@ -135,18 +143,7 @@ export const adminBaseRoute = {
// activeMenu: '/bpm/manager/model'
// }
// },
// {
// path: 'process-instance/detail',
// component: () => import('@/views/bpm/processInstance/detail/index.vue'),
// name: 'BpmProcessInstanceDetail',
// meta: {
// noCache: true,
// hidden: true,
// canTo: true,
// title: '流程详情',
// activeMenu: '/bpm/task/my'
// }
// },
]
},

View File

@@ -0,0 +1,50 @@
<template>
<ContentWrap>
<el-descriptions :column="1" border>
<el-descriptions-item label="请假类型">
</el-descriptions-item>
<el-descriptions-item label="开始时间">
{{ formatDate(detailData.startTime, 'YYYY-MM-DD') }}
</el-descriptions-item>
<el-descriptions-item label="结束时间">
{{ formatDate(detailData.endTime, 'YYYY-MM-DD') }}
</el-descriptions-item>
<el-descriptions-item label="原因">
{{ detailData.reason }}
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
</template>
<script lang="ts" setup>
import { onMounted, provide, ref, getCurrentInstance, reactive, watch, unref, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { formatDate } from '@/utils/formatTime'
import { propTypes } from '@/utils/propTypes'
defineOptions({ name: 'BpmOALeaveDetail' })
const { query } = useRoute() // 查询参数
const props = defineProps({
id: propTypes.number.def(undefined)
})
const detailLoading = ref(false) // 表单的加载中
const detailData = ref<any>({}) // 详情数据
const queryId = query.id as unknown as number // 从 URL 传递过来的 id 编号
/** 获得数据 */
const getInfo = async () => {
detailLoading.value = true
try {
// detailData.value = await LeaveApi.getLeave(props.id || queryId)
} finally {
detailLoading.value = false
}
}
defineExpose({ open: getInfo }) // 提供 open 方法,用于打开弹窗
/** 初始化 **/
onMounted(() => {
getInfo()
})
</script>

View File

@@ -0,0 +1,107 @@
<template>
<div class='default-main'>
<el-descriptions :column='2' border>
<el-descriptions-item label='填报人'>
{{ detailData.reporter }}
</el-descriptions-item>
<el-descriptions-item label='填报日期'>
{{ formatDate(detailData.reportDate, 'YYYY-MM-DD') }}
</el-descriptions-item>
<el-descriptions-item label='填报部门'>
{{ detailData.orgName }}
</el-descriptions-item>
<el-descriptions-item label='工程预期投产日期'>
{{ formatDate(detailData.expectedProductionDate, 'YYYY-MM-DD') }}
</el-descriptions-item>
<el-descriptions-item :span='2' label='用户性质'>
{{ detailData.userType }}
</el-descriptions-item>
<el-descriptions-item label='所属地址'>
{{ detailData.city }}
</el-descriptions-item>
<el-descriptions-item label='归口管理部门'>
{{ detailData.responsibleDepartment }}
</el-descriptions-item>
<el-descriptions-item label='用户状态'>
{{ detailData.userStatus }}
</el-descriptions-item>
<el-descriptions-item label='变电站'>
{{ detailData.substation }}
</el-descriptions-item>
<el-descriptions-item :span='2' label='工程名'>
{{ detailData.projectName }}
</el-descriptions-item>
<el-descriptions-item label='用户协议容量'>
{{ detailData.reason }}
</el-descriptions-item>
<el-descriptions-item label='电压等级'>
{{ detailData.voltageLevel }}
</el-descriptions-item>
<el-descriptions-item label='非线性设备类型'>
{{ detailData.reason }}
</el-descriptions-item>
<el-descriptions-item label='预测评估单位'>
{{ detailData.reason }}
</el-descriptions-item>
<el-descriptions-item :span='2' label='预测评估结论'>
{{ detailData.reason }}
</el-descriptions-item>
<el-descriptions-item label='是否需要治理'>
{{ detailData.reason }}
</el-descriptions-item>
<el-descriptions-item label='是否开展背景测试'>
{{ detailData.reason }}
</el-descriptions-item>
<el-descriptions-item :span='2' label='可研报告'>
{{ detailData.reason }}
</el-descriptions-item>
<el-descriptions-item :span='2' label='项目初步设计说明书'>
{{ detailData.reason }}
</el-descriptions-item>
<el-descriptions-item :span='2' label='预测评估评审意见报告'>
{{ detailData.reason }}
</el-descriptions-item>
<el-descriptions-item :span='2' label='其他附件'>
{{ detailData.reason }}
</el-descriptions-item>
</el-descriptions>
</div>
</template>
<script lang='ts' setup>
import { onMounted, provide, ref, getCurrentInstance, reactive, watch, unref, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { formatDate } from '@/utils/formatTime'
import { propTypes } from '@/utils/propTypes'
import { getUserReportById } from '@/api/supervision-boot/userReport/form'
defineOptions({ name: 'BpmOALeaveDetail' })
const { query } = useRoute() // 查询参数
const props = defineProps({
id: propTypes.number.def(undefined)
})
const detailLoading = ref(false) // 表单的加载中
const detailData = ref<any>({}) // 详情数据
const queryId = query.id as unknown as number // 从 URL 传递过来的 id 编号
/** 获得数据 */
const getInfo = async () => {
detailLoading.value = true
try {
await getUserReportById(props.id || queryId).then(res => {
detailData.value = res.data
})
} finally {
detailLoading.value = false
}
}
defineExpose({ open: getInfo }) // 提供 open 方法,用于打开弹窗
/** 初始化 **/
onMounted(() => {
getInfo()
})
</script>

View File

@@ -1,49 +1,49 @@
<template>
<div>
<content-wrap>
<!-- 表单设计器 -->
<FcDesigner ref='designer' height='780px'>
<template #handle>
<el-button round size='small' type='primary' @click='handleSave' class='ml10' :icon='Plus'>保存
</el-button>
<el-button round size='small' type='primary' @click='go(-1)' class='ml10' :icon='Back'>取消
</el-button>
</template>
</FcDesigner>
</content-wrap>
<div class='default-main'>
<content-wrap>
<!-- 表单设计器 -->
<FcDesigner ref='designer' height='780px'>
<template #handle>
<el-button round size='small' type='primary' @click='handleSave' class='ml10' :icon='Plus'>保存
</el-button>
<el-button round size='small' @click='go(-1)' class='ml10' :icon='Back'>取消
</el-button>
</template>
</FcDesigner>
</content-wrap>
<!-- 表单保存的弹窗 -->
<el-dialog v-model='dialogVisible' title='保存表单' class='cn-operate-dialog' style='width: 415px;height: 350px'
top='30vh'>
<el-scrollbar>
<el-form ref='formRef' :model='formData' :rules='formRules' label-width='80px'>
<el-form-item label='表单名' prop='name'>
<el-input v-model='formData.name' placeholder='请输入表单名' />
</el-form-item>
<el-form-item label='状态' prop='status'>
<el-radio-group v-model='formData.status'>
<el-radio
v-for='dict in options'
:key='dict.value'
:label='dict.value'
>
{{ dict.name }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label='备注' prop='remark'>
<el-input v-model='formData.remark' placeholder='请输入备注' type='textarea' />
</el-form-item>
</el-form>
</el-scrollbar>
<template #footer>
<!-- 表单保存的弹窗 -->
<el-dialog v-model='dialogVisible' title='保存表单' class='cn-operate-dialog' style='width: 415px;height: 350px'
top='30vh'>
<el-scrollbar>
<el-form ref='formRef' :model='formData' :rules='formRules' label-width='80px'>
<el-form-item label='表单名' prop='name'>
<el-input v-model='formData.name' placeholder='请输入表单名' />
</el-form-item>
<el-form-item label='状态' prop='status'>
<el-radio-group v-model='formData.status'>
<el-radio
v-for='dict in options'
:key='dict.value'
:label='dict.value'
>
{{ dict.name }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label='备注' prop='remark'>
<el-input v-model='formData.remark' placeholder='请输入备注' type='textarea' />
</el-form-item>
</el-form>
</el-scrollbar>
<template #footer>
<span class='dialog-footer'>
<el-button :disabled='formLoading' type='primary' @click='submitForm'> </el-button>
<el-button @click='dialogVisible = false'> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang='ts' setup>
import { onMounted, ref, reactive } from 'vue'
@@ -57,14 +57,14 @@ import { Plus, Back } from '@element-plus/icons-vue'
import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue'
const options = ref([
{
value: 1,
name: '开启'
},
{
value: 0,
name: '关闭'
}
{
value: 1,
name: '开启'
},
{
value: 0,
name: '关闭'
}
])
defineOptions({ name: 'BpmFormEditor' })
@@ -81,74 +81,74 @@ useFormCreateDesigner(designer) // 表单设计器增强
const dialogVisible = ref(false) // 弹窗是否展示
const formLoading = ref(false) // 表单的加载中:提交的按钮禁用
const formData = ref({
name: '',
status: 1,
remark: ''
name: '',
status: 1,
remark: ''
})
const formRules = reactive({
name: [{ required: true, message: '表单名不能为空', trigger: 'blur' }],
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
name: [{ required: true, message: '表单名不能为空', trigger: 'blur' }],
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 处理保存按钮 */
const handleSave = () => {
dialogVisible.value = true
dialogVisible.value = true
}
type FormVO = {
id: number
name: string
conf: string
fields: string[]
status: number
remark: string
createTime: string
id: number
name: string
conf: string
fields: string[]
status: number
remark: string
createTime: string
}
/** 提交表单 */
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as FormVO
data.conf = encodeConf(designer) // 表单配置
data.fields = encodeFields(designer) // 表单字段
if (!data.id) {
await addForm(data)
ElMessage.success('保存成功')
} else {
await updateForm(data)
ElMessage.success('更新成功')
}
dialogVisible.value = false
close()
} finally {
formLoading.value = false
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as FormVO
data.conf = encodeConf(designer) // 表单配置
data.fields = encodeFields(designer) // 表单字段
if (!data.id) {
await addForm(data)
ElMessage.success('保存成功')
} else {
await updateForm(data)
ElMessage.success('更新成功')
}
dialogVisible.value = false
close()
} finally {
formLoading.value = false
}
}
/** 关闭按钮 */
const close = () => {
delView(unref(currentRoute))
go(-1)
delView(unref(currentRoute))
go(-1)
}
/** 初始化 **/
onMounted(async () => {
// 场景一:新增表单
const id = query.id as unknown as string
if (!id) {
return
}
// 场景二:修改表单
await getById(id).then(res => {
formData.value = res.data
setConfAndFields(designer, res.data.conf, res.data.fields)
})
// 场景一:新增表单
const id = query.id as unknown as string
if (!id) {
return
}
// 场景二:修改表单
await getById(id).then(res => {
formData.value = res.data
setConfAndFields(designer, res.data.conf, res.data.fields)
})
})
</script>

View File

@@ -1,27 +1,29 @@
<template>
<ContentWrap>
<!-- 流程设计器负责绘制流程等 -->
<MyProcessDesigner
key='designer'
v-if='xmlString !== undefined'
v-model='xmlString'
:value='xmlString'
v-bind='controlForm'
keyboard
ref='processDesigner'
@init-finished='initModeler'
:additionalModel='controlForm.additionalModel'
@save='save'
/>
<!-- 流程属性器负责编辑每个流程节点的属性 -->
<MyProcessPenal
key='penal'
:bpmnModeler='modeler as any'
:prefix='controlForm.prefix'
class='process-panel'
:model='model'
/>
</ContentWrap>
<div class='default-main'>
<ContentWrap>
<!-- 流程设计器负责绘制流程等 -->
<MyProcessDesigner
key='designer'
v-if='xmlString !== undefined'
v-model='xmlString'
:value='xmlString'
v-bind='controlForm'
keyboard
ref='processDesigner'
@init-finished='initModeler'
:additionalModel='controlForm.additionalModel'
@save='save'
/>
<!-- 流程属性器负责编辑每个流程节点的属性 -->
<MyProcessPenal
key='penal'
:bpmnModeler='modeler as any'
:prefix='controlForm.prefix'
class='process-panel'
:model='model'
/>
</ContentWrap>
</div>
</template>
<script lang='ts' setup>

View File

@@ -0,0 +1,56 @@
<template>
<el-card v-loading="loading" class="box-card">
<template #header>
<span class="el-icon-picture-outline">流程图</span>
</template>
<MyProcessViewer
key="designer"
:activityData="activityList"
:prefix="bpmnControlForm.prefix"
:processInstanceData="processInstance"
:taskData="tasks"
:value="bpmnXml"
v-bind="bpmnControlForm"
/>
</el-card>
</template>
<script lang="ts" setup>
import { onMounted, provide, ref, getCurrentInstance, reactive, watch, unref,nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { propTypes } from '@/utils/propTypes'
import { MyProcessViewer } from '@/components/bpmnProcessDesigner/package'
import { getTaskListByReturn } from '@/api/bpm-boot/activity'
defineOptions({ name: 'BpmProcessInstanceBpmnViewer' })
const props = defineProps({
loading: propTypes.bool, // 是否加载中
id: propTypes.string, // 流程实例的编号
processInstance: propTypes.any, // 流程实例的信息
tasks: propTypes.array, // 流程任务的数组
bpmnXml: propTypes.string // BPMN XML
})
const bpmnControlForm = ref({
prefix: 'flowable'
})
const activityList = ref([]) // 任务列表
/** 只有 loading 完成时,才去加载流程列表 */
watch(
() => props.loading,
async (value) => {
if (value && props.id) {
await getTaskListByReturn(props.id).then(res =>{
activityList.value = res.data
})
}
}
)
</script>
<style>
.box-card {
width: 100%;
margin-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,194 @@
<template>
<el-card v-loading="loading" class="box-card">
<template #header>
<span class="el-icon-picture-outline">审批记录</span>
</template>
<el-col :offset="3" :span="17">
<div class="block">
<el-timeline>
<el-timeline-item
v-if="processInstance.endTime"
:type="getProcessInstanceTimelineItemType(processInstance)"
>
<p style="font-weight: 700">
结束流程 {{ formatDate(processInstance?.endTime) }} 结束
<el-tag :type='getTaskTimelineItemType(processInstance.status)'>
{{getTaskNameItemType(processInstance.status)}}
</el-tag>
</p>
</el-timeline-item>
<el-timeline-item
v-for="(item, index) in tasks"
:key="index"
:type="getTaskTimelineItemType(item)"
>
<p style="font-weight: 700;margin: 8px">
审批任务{{ item.name }}
<el-tag :type='getTaskTimelineItemType(item.status)'>
{{getTaskNameItemType(item.status)}}
</el-tag>
<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"
v-if="item.formId > 0"
@click="handleFormDetail(item)"
>
<Icon icon="ep:document" /> 查看表单
</el-button>
</p>
<el-card :body-style="{ padding: '10px' }">
<label v-if="item.assigneeUser" style="margin-right: 30px; font-weight: normal">
审批人{{ item.assigneeUser.name }}
<el-tag size="small" type="info">{{ item.assigneeUser.deptName }}</el-tag>
</label>
<label v-if="item.createTime" style="font-weight: normal">创建时间</label>
<label style="font-weight: normal; color: #8a909c">
{{ formatDate(item?.createTime) }}
</label>
<label v-if="item.endTime" style="margin-left: 30px; font-weight: normal">
审批时间
</label>
<label v-if="item.endTime" style="font-weight: normal; color: #8a909c">
{{ formatDate(item?.endTime) }}
</label>
<label v-if="item.durationInMillis" style="margin-left: 30px; font-weight: normal">
耗时
</label>
<label v-if="item.durationInMillis" style="font-weight: normal; color: #8a909c">
{{ formatPast2(item?.durationInMillis) }}
</label>
<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?.name }}
{{ formatDate(processInstance?.startTime) }} 发起 {{ processInstance.name }} 流程
</p>
</el-timeline-item>
</el-timeline>
</div>
</el-col>
</el-card>
<!-- 弹窗子任务 -->
<TaskSignList ref="taskSignListRef" @success="refresh" />
<!-- 弹窗表单 -->
<el-dialog title="表单详情" v-model="taskFormVisible" width="600">
<form-create
ref="fApi"
v-model="taskForm.value"
:option="taskForm.option"
:rule="taskForm.rule"
/>
</el-dialog>
</template>
<script lang="ts" setup>
import { onMounted, provide, ref, getCurrentInstance, reactive, watch, unref,nextTick } from 'vue'
import { ElMessage } from 'element-plus'
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'
defineOptions({ name: 'BpmProcessInstanceTaskList' })
defineProps({
loading: propTypes.bool, // 是否加载中
processInstance: propTypes.object, // 流程实例
tasks: propTypes.arrayOf(propTypes.object) // 流程任务的数组
})
/** 获得流程实例对应的颜色 */
const getProcessInstanceTimelineItemType = (item: any) => {
if (item.status === 2) {
return 'success'
}
if (item.status === 3) {
return 'danger'
}
if (item.status === 4) {
return 'warning'
}
return ''
}
/** 获得任务对应的颜色 */
const getTaskTimelineItemType = (itemStatus: any) => {
if ([0, 1, 6, 7].includes(itemStatus)) {
return 'primary'
}
if (itemStatus === 2) {
return 'success'
}
if (itemStatus === 3) {
return 'danger'
}
if (itemStatus === 4) {
return 'info'
}
if (itemStatus === 5) {
return 'warning'
}
return 'primary'
}
const getTaskNameItemType = (itemStatus: any) => {
if (itemStatus === 1) {
return '审批中'
}
if (itemStatus === 2) {
return '审批通过'
}
if (itemStatus === 3) {
return '审批不通过'
}
if (itemStatus === 4) {
return '已取消'
}
return '审批中'
}
/** 子任务 */
const taskSignListRef = ref()
const openChildrenTask = (item: any) => {
taskSignListRef.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

@@ -0,0 +1,93 @@
<template>
<el-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>
</el-dialog>
</template>
<script lang="ts" setup>
import { onMounted, provide, ref, getCurrentInstance, reactive, watch, unref,nextTick } from 'vue'
import { getUserSimpleList } from '@/api/user-boot/user'
import { delegateTask } from '@/api/bpm-boot/task'
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
// 获得用户列表
await getUserSimpleList().then(res => {
userList.value = res.data
})
}
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 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

@@ -0,0 +1,93 @@
<template>
<el-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>
</el-dialog>
</template>
<script lang='ts' name='TaskRollbackDialogForm' setup>
import { onMounted, provide, ref, getCurrentInstance, reactive, watch, unref, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { getTaskListByReturn, returnTask } from '@/api/bpm-boot/task'
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) => {
await getTaskListByReturn(id).then(res => {
returnList.value = res.data
})
if (returnList.value.length === 0) {
ElMessage.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 returnTask(formData.value)
ElMessage.success('回退成功')
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: '',
targetTaskDefinitionKey: undefined,
reason: ''
}
formRef.value?.resetFields()
}
</script>

View File

@@ -0,0 +1,102 @@
<template>
<el-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>
</el-dialog>
</template>
<script lang="ts" setup>
import { onMounted, provide, ref, getCurrentInstance, reactive, watch, unref,nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { getUserSimpleList } from '@/api/user-boot/user'
import { createSignTask } from '@/api/bpm-boot/task'
defineOptions({ name: 'TaskSignCreateForm' })
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
// 获得用户列表
// 获得用户列表
await getUserSimpleList().then(res => {
userList.value = res.data
})
}
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 createSignTask(formData.value)
ElMessage.success('加签成功')
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: '',
userIds: [],
type: '',
reason: ''
}
formRef.value?.resetFields()
}
</script>

View File

@@ -0,0 +1,94 @@
<template>
<el-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="id">
<el-radio-group v-model="formData.id">
<!-- <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>
<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>
</el-dialog>
</template>
<script lang="ts" setup>
import { onMounted, provide, ref, getCurrentInstance, reactive, watch, unref,nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { isEmpty } from '@/utils/is'
import { deleteSignTask, getTaskListByParentTaskId } from '@/api/bpm-boot/task'
defineOptions({ name: 'TaskSignDeleteForm' })
const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中
const formData = ref({
id: '',
reason: ''
})
const formRules = ref({
id: [{ required: true, message: '必须选择减签任务', trigger: 'change' }],
reason: [{ required: true, message: '减签理由不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const childrenTaskList = ref([])
/** 打开弹窗 */
const open = async (id: string) => {
await getTaskListByParentTaskId(id).then(res => {
childrenTaskList.value = res.data
})
if (isEmpty(childrenTaskList.value)) {
ElMessage.warning('当前没有可减签的任务')
return false
}
dialogVisible.value = true
resetForm()
}
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 deleteSignTask(formData.value)
ElMessage.success('减签成功')
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: '',
reason: ''
}
formRef.value?.resetFields()
}
</script>

View File

@@ -0,0 +1,107 @@
<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 { onMounted, provide, ref, getCurrentInstance, reactive, watch, unref,nextTick } from 'vue'
import { ElMessage } from 'element-plus'
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 drawerVisible = ref(false) // 抽屉的是否展示
const parentTask = ref({} as any)
/** 打开弹窗 */
const open = async (task: any) => {
if (isEmpty(task.children)) {
ElMessage.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

@@ -0,0 +1,93 @@
<template>
<el-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>
</el-dialog>
</template>
<script lang="ts" setup>
import { onMounted, provide, ref, getCurrentInstance, reactive, watch, unref,nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { getUserSimpleList } from '@/api/user-boot/user'
import { transferTask } from '@/api/bpm-boot/task'
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
// 获得用户列表
await getUserSimpleList().then(res => {
userList.value = res.data
})
}
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 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

@@ -0,0 +1,429 @@
<template>
<div class='default-main'>
<!--返回按钮-->
<back-component style='margin: 8px;position: absolute;z-index: 10;top: -3px;right: 2px' />
<el-tabs type='border-card' v-model='tab'>
<el-tab-pane label='流程审核' name='流程审核' v-if='runningTasks.length>0'>
<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.name }}
</el-form-item>
<el-form-item v-if='processInstance && processInstance.startUser' label='发起部门'>
{{ processInstance?.startUser.deptName }}
</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='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'
:icon='Select'
@click='handleAudit(item, true)'
>通过
</el-button>
<el-button
type='danger'
:icon='Close'
@click='handleAudit(item, false)'
>不通过
</el-button>
<!-- <el-button type='primary' @click='openTaskUpdateAssigneeForm(item.id)'>-->
<!-- <Icon icon='ep:edit' />-->
<!-- 转办-->
<!-- </el-button>-->
<!-- <el-button type='primary' @click='handleDelegate(item)'>-->
<!-- <Icon icon='ep:position' />-->
<!-- 委派-->
<!-- </el-button>-->
<!-- <el-button type='primary' @click='handleSign(item)'>-->
<!-- <Icon icon='ep:plus' />-->
<!-- 加签-->
<!-- </el-button>-->
<el-button
type='warning'
:icon='Back'
@click='handleBack(item)'
>回退
</el-button>
</div>
</el-col>
</el-card>
</el-tab-pane>
<el-tab-pane label='申请信息' name='申请信息'>
<!-- 申请信息 -->
<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'
/>
</el-col>
<!-- 情况二:业务表单 -->
<div v-if='processInstance?.processDefinition?.formType === 20'>
<BusinessFormComponent :id='processInstance.businessKey' />
</div>
</el-card>
</el-tab-pane>
<el-tab-pane label='审批记录'>
<!-- 审批记录 -->
<ProcessInstanceTaskList
:loading='tasksLoad'
:process-instance='processInstance'
:tasks='tasks'
@refresh='getTaskList'
/>
</el-tab-pane>
<el-tab-pane label='流程图'>
<!-- 高亮流程图 -->
<!-- <ProcessInstanceBpmnViewer-->
<!-- :id="`${id}`"-->
<!-- :bpmn-xml="bpmnXml"-->
<!-- :loading="processInstanceLoading"-->
<!-- :process-instance="processInstance"-->
<!-- :tasks="tasks"-->
<!-- />--></el-tab-pane>
</el-tabs>
<ContentWrap>
<!-- 弹窗:转派审批人 -->
<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' />
</ContentWrap>
</div>
</template>
<script lang='ts' setup>
import { onMounted, provide, ref, getCurrentInstance, reactive, watch, unref, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/stores/modules/user'
import ContentWrap from '@/components/ContentWrap/src/ContentWrap.vue'
import { setConfAndFields2 } from '@/utils/formCreate'
import type { ApiAttrs } from '@form-create/element-ui/types/config'
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 { useRouter, useRoute } from 'vue-router'
import { approveTask, getTaskListByProcessInstanceId, listByProcessInstanceId, rejectTask } from '@/api/bpm-boot/task'
import { getProcessInstanceById } from '@/api/bpm-boot/instance'
import { getProcessDefinitionById } from '@/api/bpm-boot/definition'
import { getUserSimpleList } from '@/api/user-boot/user'
import { Close, Back, Select } from '@element-plus/icons-vue'
import BackComponent from '@/components/icon/back/index.vue'
defineOptions({ name: 'BpmProcessInstanceDetail' })
const { query } = useRoute() // 查询参数
const { proxy } = getCurrentInstance() as any
const userId = JSON.parse(window.localStorage.getItem('adminInfo')).id// 当前登录的编号
const id = query.id as unknown as string // 流程实例的编号
const todo = query.todo// 是否存在待办事项
const processInstanceLoading = ref(false) // 流程实例的加载中
const processInstance = ref<any>({}) // 流程实例
const bpmnXml = ref('') // BPMN XML
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 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) => {
// 1.1 获得对应表单
const index = runningTasks.value.indexOf(task)
const auditFormRef = proxy.$refs['form' + index][0]
// 1.2 校验表单
const elForm = unref(auditFormRef)
if (!elForm) return
const valid = await elForm.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
}
await approveTask(data)
ElMessage.success('审批通过成功')
} else {
await rejectTask(data)
ElMessage.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 = () => {
// 1. 获得流程实例相关
getProcessInstance()
// 2. 获得流程任务列表(审批记录)
getTaskList()
}
/** 加载流程实例 */
const BusinessFormComponent = ref(null) // 异步组件
const getProcessInstance = async () => {
try {
processInstanceLoading.value = true
let data
await getProcessInstanceById(id).then(res => {
data = res.data
})
if (!data) {
ElMessage.error('查询不到流程信息')
return
}
processInstance.value = data
// 设置表单信息
const processDefinition = data.processDefinition
if (processDefinition.formType === 10) {
setConfAndFields2(
detailForm,
processDefinition.formConf,
processDefinition.formFields,
data.formVariables
)
nextTick().then(() => {
fApi.value?.btn.show(false)
fApi.value?.resetBtn.show(false)
fApi.value?.disabled(true)
})
} else {
// 注意data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
// BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
BusinessFormComponent.value = registerComponent('/pqs/supervise/interfere/components/undocumented/detail')
}
// 加载流程图
bpmnXml.value = (
await getProcessDefinitionById(processDefinition.id)
)?.bpmnXml
} finally {
processInstanceLoading.value = false
}
}
/** 加载任务列表 */
const getTaskList = async () => {
runningTasks.value = []
auditForms.value = []
approveForms.value = []
approveFormFApis.value = []
try {
// 获得未取消的任务
tasksLoad.value = true
let data
await getTaskListByProcessInstanceId(id).then(res => {
data = res.data
})
tasks.value = []
// 1.1 移除已取消的审批
data.forEach((task) => {
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
}
})
// 获得需要自己审批的任务
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.formVariable)
approveForms.value.push(approveForm)
} else {
approveForms.value.push({}) // 占位,避免为空
}
})
}
const tab = ref('')
/** 初始化 */
const userOptions = ref([]) // 用户列表
onMounted(async () => {
getDetail()
// 获得用户列表
await getUserSimpleList().then(res => {
userOptions.value = res.data
})
if (todo) {
tab.value = '流程审核'
} else {
tab.value = '申请信息'
}
})
</script>

View File

@@ -0,0 +1,119 @@
<!--待办事项列表-->
<template>
<div class='default-main'>
<TableHeader date-picker>
<template v-slot:select>
<el-form-item label='任务名称'>
<el-input
v-model='tableStore.table.params.searchValue'
clearable
placeholder='请输入任务名称'
/>
</el-form-item>
</template>
</TableHeader>
<!--表格-->
<Table ref='tableRef'></Table>
</div>
</template>
<script setup lang='ts'>
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import { onMounted, provide, ref } from 'vue'
import { useRouter } from 'vue-router'
import { dateFormatter, formatPast2 } from '@/utils/formatTime'
defineOptions({
name: 'businessUser'
})
const { push } = useRouter()
const tableStore = new TableStore({
url: '/bpm-boot/bpm/task/doneList',
method: 'POST',
column: [
{ title: '序号', type: 'seq', width: 80 },
{ title: '流程名称', field: 'processInstance.name', minWidth: 130 },
{ title: '发起人', field: 'processInstance.startUser.name', minWidth: 130 },
{ title: '发起部门', field: 'processInstance.startUser.deptName', minWidth: 130 },
{ title: '当前任务', field: 'name', minWidth: 130 },
{ title: '发起时间', field: 'createTime', minWidth: 170 },
{ title: '结束时间', field: 'endTime', minWidth: 170 },
{
title: '审批状态', field: 'status', minWidth: 130,
render: 'tag',
custom: {
1: 'primary',
2: 'success',
3: 'danger',
4: 'warning'
},
replaceValue: {
1: '审批中',
2: '审批通过',
3: '审批不通过',
4: '已取消'
}
},
{ title: '审批建议', field: 'reason', minWidth: 150 },
{ title: '耗时s', field: 'durationInMillis', minWidth: 150,
formatter: (obj: any) => {
return formatPast2(obj.row.durationInMillis)
} },
{
title: '操作',
align: 'center',
minWidth: '150',
fixed: 'right',
render: 'buttons',
buttons: [
{
name: 'productSetting',
title: '流程历史',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton',
click: row => {
handleAudit(row.processInstance.id)
}
}
]
}
],
beforeSearchFun: () => {
if (tableStore.table.params.addr) {
tableStore.table.params.addrStrOption = tableStore.table.params.addr.map(tempArray => tempArray.join('/'))
}
for (let key in tableStore.table.params) {
if (tableStore.table.params[key] === '') {
delete tableStore.table.params[key]
}
}
}
})
onMounted(() => {
// 加载数据
tableStore.index()
})
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
/** 处理审批按钮 */
const handleAudit = (instanceId: any) => {
push({
name: 'BpmProcessInstanceDetail',
query: {
id: instanceId
}
})
}
</script>

View File

@@ -0,0 +1,96 @@
<!--待办事项列表-->
<template>
<div class='default-main'>
<TableHeader date-picker>
<template v-slot:select>
<el-form-item label='任务名称'>
<el-input
v-model='tableStore.table.params.searchValue'
clearable
placeholder='请输入任务名称'
/>
</el-form-item>
</template>
</TableHeader>
<!--表格-->
<Table ref='tableRef'></Table>
</div>
</template>
<script setup lang='ts'>
import TableStore from '@/utils/tableStore'
import Table from '@/components/table/index.vue'
import TableHeader from '@/components/table/header/index.vue'
import { onMounted, provide, ref } from 'vue'
import { useRouter } from 'vue-router'
defineOptions({
name: 'businessUser'
})
const { push } = useRouter()
const tableStore = new TableStore({
url: '/bpm-boot/bpm/task/todoList',
method: 'POST',
column: [
{ title: '序号', type: 'seq', width: 80 },
{ title: '流程名称', field: 'processInstance.name', minWidth: 130 },
{ title: '发起人', field: 'processInstance.startUser.name', minWidth: 130 },
{ title: '发起部门', field: 'processInstance.startUser.deptName', minWidth: 130 },
{ title: '发起时间', field: 'createTime', minWidth: 170 },
{ title: '当前任务', field: 'name', minWidth: 130 },
{
title: '操作',
align: 'center',
width: '230',
render: 'buttons',
buttons: [
{
name: 'productSetting',
title: '办理',
type: 'primary',
icon: 'el-icon-EditPen',
render: 'basicButton',
click: row => {
handleAudit(row.processInstance.id)
}
}
]
}
],
beforeSearchFun: () => {
if (tableStore.table.params.addr) {
tableStore.table.params.addrStrOption = tableStore.table.params.addr.map(tempArray => tempArray.join('/'))
}
for (let key in tableStore.table.params) {
if (tableStore.table.params[key] === '') {
delete tableStore.table.params[key]
}
}
}
})
onMounted(() => {
// 加载数据
tableStore.index()
})
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
/** 处理审批按钮 */
const handleAudit = (instanceId: any) => {
push({
name: 'BpmProcessInstanceDetail',
query: {
id: instanceId,
todo: 'todo'
}
})
}
</script>