|
@ -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 |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
||||
/** 左侧分类切换 */
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 },
|
||||
// 抄送人节点
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|