Merge pull request #102 from GoldenZqqq/feature/bpm

工作流发起与审核页面具体细节优化
pull/586/MERGE
芋道源码 2024-11-19 12:41:32 +08:00 committed by GitHub
commit 3783582b33
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 161 additions and 84 deletions

View File

@ -0,0 +1 @@
<svg t="1731390087280" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4297" width="200" height="200"><path d="M639.9 541.7c76.4-44.2 127.9-126.8 127.9-221.5C767.7 179 653.2 64.5 512 64.5S256.3 179 256.3 320.2c0 89.6 46.1 168.4 115.8 214.1C193.5 593 64.5 761.2 64.5 959.5h63.9c0-211.5 172.1-383.6 383.6-383.6 44.9 0 87.8 8.1 127.9 22.4v-56.6zM320.2 320.2c0-105.8 86-191.8 191.8-191.8s191.8 86 191.8 191.8S617.7 512 512 512s-191.8-86-191.8-191.8zM831.6 767.7V639.9h-63.9v127.8H639.9v63.9h127.8v127.9h63.9V831.6h127.9v-63.9z" fill="#5f6266" p-id="4298"></path></svg>

After

Width:  |  Height:  |  Size: 608 B

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -1,25 +1,27 @@
<template>
<Dialog v-model="dialogVisible" title="人员选择" width="900">
<el-row>
<Dialog v-model="dialogVisible" title="人员选择" width="800">
<el-row class="gap2" v-loading="formLoading">
<el-col :span="6">
<el-tree
ref="treeRef"
:data="deptList"
:expand-on-click-node="false"
:props="defaultProps"
default-expand-all
highlight-current
node-key="id"
@node-click="handleNodeClick"
/>
<ContentWrap class="h-1/1">
<el-tree
ref="treeRef"
:data="deptList"
:expand-on-click-node="false"
:props="defaultProps"
default-expand-all
highlight-current
node-key="id"
@node-click="handleNodeClick"
/>
</ContentWrap>
</el-col>
<el-col :span="17" :offset="1">
<el-col :span="17">
<el-transfer
v-model="selectedUserIdList"
:titles="['未选', '已选']"
filterable
filter-placeholder="搜索成员"
:data="userList"
:data="transferUserList"
:props="{ label: 'nickname', key: 'id' }"
/>
</el-col>
@ -47,62 +49,87 @@ const emit = defineEmits<{
}>()
const { t } = useI18n() //
const message = useMessage() //
const deptList = ref<Tree[]>([]) //
const userList: any = ref([]) //
const allUserList = ref<UserApi.UserVO[]>([]) //
const filteredUserList = ref<UserApi.UserVO[]>([]) //
const selectedUserIdList: any = ref([]) //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const activityId = ref() // TODO @goldenzqqq activityId 使 @submitForm="xxx()"
const activityId = ref()
//
const transferUserList = computed(() => {
//
const selectedUsers = allUserList.value.filter((user: any) =>
selectedUserIdList.value.includes(user.id)
)
//
const filteredUnselectedUsers = filteredUserList.value.filter(
(user: any) => !selectedUserIdList.value.includes(user.id)
)
//
return [...selectedUsers, ...filteredUnselectedUsers]
})
/** 打开弹窗 */
const open = async (id: number, selectedList?: any[]) => {
activityId.value = id
//
resetForm()
//
deptList.value = handleTree(await DeptApi.getSimpleDeptList())
await getUserList()
//
selectedUserIdList.value = selectedList?.map((item: any) => item.id)
//
//
await getAllUserList()
//
filteredUserList.value = [...allUserList.value]
selectedUserIdList.value = selectedList?.map((item: any) => item.id) || []
dialogVisible.value = true
}
/** 获取用户列表 */
const getUserList = async (deptId?: number) => {
/** 获取所有用户列表 */
const getAllUserList = async () => {
try {
// @ts-ignore
// TODO @ simple List
const data = await UserApi.getUserPage({ pageSize: 100, pageNo: 1, deptId })
userList.value = data.list
const data = await UserApi.getSimpleUserList()
allUserList.value = data
} finally {
}
}
/** 获取部门过滤后的用户列表 */
const getUserList = async (deptId?: number) => {
formLoading.value = true
try {
// @ts-ignore
// TODO @ simple List deptId
const data = await UserApi.getUserPage({ pageSize: 100, pageNo: 1, deptId })
//
filteredUserList.value = data.list
} finally {
formLoading.value = false
}
}
/** 提交选择 */
const submitForm = async () => {
//
formLoading.value = true
try {
message.success(t('common.updateSuccess'))
dialogVisible.value = false
const emitUserList = userList.value.filter((user: any) =>
//
const emitUserList = allUserList.value.filter((user: any) =>
selectedUserIdList.value.includes(user.id)
)
//
emit('confirm', activityId.value, emitUserList)
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
deptList.value = []
userList.value = []
allUserList.value = []
filteredUserList.value = []
selectedUserIdList.value = []
}
@ -113,3 +140,20 @@ const handleNodeClick = (row: { [key: string]: any }) => {
defineExpose({ open }) // open
</script>
<style lang="scss" scoped>
:deep() {
.el-transfer {
display: flex;
}
.el-transfer__buttons {
display: flex !important;
flex-direction: column-reverse;
justify-content: center;
gap: 20px;
.el-transfer__button:nth-child(2) {
margin: 0;
}
}
}
</style>

View File

@ -41,7 +41,7 @@
:ref="`category-${categoryCode}`"
>
<h3 class="text-18px font-bold mb-10px mt-5px">
{{ getCategoryName(categoryCode) }}
{{ getCategoryName(categoryCode as any) }}
</h3>
<div class="grid grid-cols-3 gap3">
<el-tooltip
@ -175,7 +175,17 @@ const handleQuery = () => {
/** 流程定义的分组 */
const processDefinitionGroup: any = computed(() => {
if (!processDefinitionList.value?.length) return {}
return groupBy(filteredProcessDefinitionList.value, 'category')
const grouped = groupBy(filteredProcessDefinitionList.value, 'category')
const orderedGroup = {}
// categoryList
categoryList.value.forEach((category: any) => {
if (grouped[category.code]) {
orderedGroup[category.code] = grouped[category.code]
}
})
return orderedGroup
})
/** 左侧分类切换 */

View File

@ -1,6 +1,6 @@
<template>
<el-card v-loading="loading" class="box-card">
<MyProcessViewer key="designer" :xml="view.bpmnXml" :view="view" class="h-700px" />
<MyProcessViewer key="designer" :xml="view.bpmnXml" :view="view" class="process-viewer" />
</el-card>
</template>
<script lang="ts" setup>
@ -45,4 +45,9 @@ watch(
width: 100%;
margin-bottom: 20px;
}
:deep(.process-viewer) {
height: 100% !important;
min-height: 500px;
}
</style>

View File

@ -4,6 +4,7 @@
:flow-node="simpleModel"
:tasks="tasks"
:process-instance="processInstance"
class="process-viewer"
/>
</div>
</template>
@ -151,3 +152,10 @@ const setSimpleModelNodeTaskStatus = (
)
}
</script>
<style lang="scss" scoped>
:deep(.process-viewer) {
height: 100% !important;
min-height: 500px;
}
</style>

View File

@ -16,9 +16,10 @@
<img class="w-full h-full" :src="getApprovalNodeImg(activity.nodeType)" alt="" />
<div
v-if="showStatusIcon"
class="position-absolute top-17px left-17px bg-#fff rounded-full flex items-center p-2px"
class="position-absolute top-17px left-17px rounded-full flex items-center p-2px"
:style="{ backgroundColor: getApprovalNodeColor(activity.status) }"
>
<el-icon :size="12" :color="getApprovalNodeColor(activity.status)">
<el-icon :size="12" color="#fff">
<component :is="getApprovalNodeIcon(activity.status, activity.nodeType)" />
</el-icon>
</div>
@ -46,19 +47,22 @@
"
>
<!-- && activity.nodeType === NodeType.USER_TASK_NODE -->
<el-button
class="!px-8px"
@click="handleSelectUser(activity.id, customApproveUsers[activity.id])"
>
<Icon icon="fa:user-plus" />
</el-button>
<el-tooltip content="添加用户" placement="left">
<el-button
class="!px-6px"
@click="handleSelectUser(activity.id, customApproveUsers[activity.id])"
>
<img class="w-18px text-#ccc" src="@/assets/svgs/bpm/add-user.svg" alt="" />
</el-button>
</el-tooltip>
<div
v-for="(user, idx1) in customApproveUsers[activity.id]"
:key="idx1"
class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600 position-relative"
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
>
<el-avatar :size="28" v-if="user.avatar" :src="user.avatar" />
<el-avatar :size="28" v-else>
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
<el-avatar class="!m-5px" :size="28" v-else>
{{ user.nickname.substring(0, 1) }}
</el-avatar>
{{ user.nickname }}
@ -73,40 +77,39 @@
>
<!-- 信息头像昵称 -->
<div
class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600 position-relative"
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
>
<template v-if="task.assigneeUser?.avatar || task.assigneeUser?.nickname">
<el-avatar
class="!m-5px"
:size="28"
v-if="task.assigneeUser?.avatar"
:src="task.assigneeUser?.avatar"
/>
<el-avatar :size="28" v-else>
<el-avatar class="!m-5px" :size="28" v-else>
{{ task.assigneeUser?.nickname.substring(0, 1) }}
</el-avatar>
{{ task.assigneeUser?.nickname }}
</template>
<template v-else-if="task.ownerUser?.avatar || task.ownerUser?.nickname">
<el-avatar
class="!m-5px"
:size="28"
v-if="task.ownerUser?.avatar"
:src="task.ownerUser?.avatar"
/>
<el-avatar :size="28" v-else>
<el-avatar class="!m-5px" :size="28" v-else>
{{ task.ownerUser?.nickname.substring(0, 1) }}
</el-avatar>
{{ task.ownerUser?.nickname }}
</template>
<!-- 信息任务 ICON -->
<div
v-if="onlyStatusIconShow.includes(task.status)"
class="position-absolute top-22px left-26px bg-#fff rounded-full flex items-center p-2px"
v-if="showStatusIcon && onlyStatusIconShow.includes(task.status)"
class="position-absolute top-19px left-23px rounded-full flex items-center p-2px"
:style="{ backgroundColor: statusIconMap2[task.status]?.color }"
>
<Icon
:size="12"
:icon="statusIconMap2[task.status]?.icon"
:color="statusIconMap2[task.status]?.color"
/>
<Icon :size="12" :icon="statusIconMap2[task.status]?.icon" color="#FFFFFF" />
</div>
</div>
</div>
@ -126,23 +129,21 @@
<div
v-for="(user, idx1) in activity.candidateUsers"
:key="idx1"
class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600 position-relative"
class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
>
<el-avatar :size="28" v-if="user.avatar" :src="user.avatar" />
<el-avatar :size="28" v-else>
<el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
<el-avatar class="!m-5px" :size="28" v-else>
{{ user.nickname.substring(0, 1) }}
</el-avatar>
{{ user.nickname }}
<!-- 信息任务 ICON -->
<div
class="position-absolute top-22px left-26px bg-#fff rounded-full flex items-center p-2px"
v-if="showStatusIcon"
class="position-absolute top-19px left-23px rounded-full flex items-center p-2px"
:style="{ backgroundColor: statusIconMap2['-1']?.color }"
>
<Icon
:size="12"
:icon="statusIconMap2['-1']?.icon"
:color="statusIconMap2['-1']?.color"
/>
<Icon :size="12" :icon="statusIconMap2['-1']?.icon" color="#FFFFFF" />
</div>
</div>
</div>
@ -184,7 +185,7 @@ const statusIconMap2 = {
//
'-1': { color: '#909398', icon: 'ep-clock' },
//
'0': { color: '#e5e7ec', icon: 'ep:loading' },
'0': { color: '#00b32a', icon: 'ep:loading' },
//
'1': { color: '#448ef7', icon: 'ep:loading' },
//
@ -204,7 +205,7 @@ const statusIconMap2 = {
const statusIconMap = {
//
'-1': { color: '#909398', icon: Clock },
'0': { color: '#e5e7ec', icon: Clock },
'0': { color: '#00b32a', icon: Clock },
//
'1': { color: '#448ef7', icon: Loading },
//
@ -223,9 +224,9 @@ const statusIconMap = {
const nodeTypeSvgMap = {
//
[NodeType.END_EVENT_NODE]: { color: '#ffffff', svg: finishSvg },
[NodeType.END_EVENT_NODE]: { color: '#909398', svg: finishSvg },
//
[NodeType.START_USER_NODE]: { color: '#ffffff', svg: starterSvg },
[NodeType.START_USER_NODE]: { color: '#909398', svg: starterSvg },
//
[NodeType.USER_TASK_NODE]: { color: '#ff943e', svg: auditorSvg },
//

View File

@ -5,7 +5,7 @@
<img
class="position-absolute right-20px"
width="150"
:src="auditIcons[processInstance.status]"
:src="auditIconsMap[processInstance.status]"
alt=""
/>
<div class="text-#878c93 h-15px">编号{{ id }}</div>
@ -137,11 +137,11 @@ import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
import ProcessInstanceOperationButton from './ProcessInstanceOperationButton.vue'
import ProcessInstanceTimeline from './ProcessInstanceTimeline.vue'
import { FieldPermissionType } from '@/components/SimpleProcessDesignerV2/src/consts'
// TODO icon
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'
import { TaskStatusEnum } from '@/api/bpm/task'
import runningSvg from '@/assets/svgs/bpm/running.svg'
import approveSvg from '@/assets/svgs/bpm/approve.svg'
import rejectSvg from '@/assets/svgs/bpm/reject.svg'
import cancelSvg from '@/assets/svgs/bpm/cancel.svg'
defineOptions({ name: 'BpmProcessInstanceDetail' })
const props = defineProps<{
@ -155,11 +155,11 @@ const processInstance = ref<any>({}) // 流程实例
const processDefinition = ref<any>({}) //
const processModelView = ref<any>({}) //
const operationButtonRef = ref() // ref
const auditIcons = {
1: audit1,
2: audit2,
3: audit3,
4: audit4
const auditIconsMap = {
[TaskStatusEnum.RUNNING]: runningSvg,
[TaskStatusEnum.APPROVE]: approveSvg,
[TaskStatusEnum.REJECT]: rejectSvg,
[TaskStatusEnum.CANCEL]: cancelSvg
}
// ========== ==========
@ -242,7 +242,6 @@ const getApprovalDetail = async () => {
/** 获取流程模型视图*/
const getProcessModelView = async () => {
if (BpmModelType.BPMN === processDefinition.value?.modelType) {
// BPMN
processModelView.value = {
@ -320,6 +319,15 @@ $process-header-height: 194px;
$process-header-height - 40px
);
overflow: auto;
:deep(.box-card) {
height: 100%;
.el-card__body {
height: 100%;
padding: 0;
}
}
}
}