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

# Conflicts:
#	src/views/bpm/processInstance/create/ProcessDefinitionDetail.vue
#	src/views/bpm/processInstance/detail/ProcessInstanceTimeline.vue
This commit is contained in:
YunaiV 2024-11-07 22:28:15 +08:00
commit 5814826fa8
4 changed files with 349 additions and 162 deletions

View File

@ -0,0 +1,100 @@
<template>
<Dialog v-model="dialogVisible" title="人员选择" width="900">
<el-row>
<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"
/>
</el-col>
<el-col :span="17" :offset="1">
<el-transfer
v-model="selectedUserIdList"
filterable
filter-placeholder="搜索成员"
:data="userList"
:props="{ label: 'nickname', key: 'id' }"
/>
</el-col>
</el-row>
<template #footer>
<el-button
:disabled="formLoading || !selectedUserIdList?.length"
type="primary"
@click="submitForm"
> </el-button
>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import { defaultProps, handleTree } from '@/utils/tree'
import * as DeptApi from '@/api/system/dept'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'UserSelectForm' })
const emit = defineEmits<{
confirm: [id: any, userList: any[]]
}>()
const { t } = useI18n() //
const deptList = ref<Tree[]>([]) //
const userList: any = ref([]) //
const message = useMessage() //
const selectedUserIdList: any = ref([]) //
const dialogVisible = ref(false) //
const formLoading = ref(false) //
const activityId = ref() // id
/** 打开弹窗 */
const open = async (id, selectedList?) => {
activityId.value = id
resetForm()
deptList.value = handleTree(await DeptApi.getSimpleDeptList())
await getUserList()
selectedUserIdList.value = selectedList?.map((item) => item.id)
//
dialogVisible.value = true
}
/* 获取用户列表 */
const getUserList = async (deptId?) => {
try {
// @ts-ignore
const data = await UserApi.getUserPage({ pageSize: 100, pageNo: 1, deptId })
userList.value = data.list
} finally {
}
}
const submitForm = async () => {
//
formLoading.value = true
try {
message.success(t('common.updateSuccess'))
dialogVisible.value = false
const emitUserList = userList.value.filter((user) => selectedUserIdList.value.includes(user.id))
//
emit('confirm', activityId.value, emitUserList)
} finally {
formLoading.value = false
}
}
const resetForm = () => {
deptList.value = []
userList.value = []
selectedUserIdList.value = []
}
/** 处理部门被点击 */
const handleNodeClick = async (row: { [key: string]: any }) => {
getUserList(row.id)
}
defineExpose({ open }) // open
</script>

View File

@ -19,40 +19,7 @@
v-model="detailForm.value" v-model="detailForm.value"
:option="detailForm.option" :option="detailForm.option"
@submit="submitForm" @submit="submitForm"
> />
<template #type-startUserSelect>
<el-col :span="24">
<el-card class="mb-10px">
<template #header>指定审批人</template>
<el-form
:model="startUserSelectAssignees"
:rules="startUserSelectAssigneesFormRules"
ref="startUserSelectAssigneesFormRef"
>
<el-form-item
v-for="userTask in startUserSelectTasks"
:key="userTask.id"
:label="`任务【${userTask.name}】`"
:prop="userTask.id"
>
<el-select
v-model="startUserSelectAssignees[userTask.id]"
multiple
placeholder="请选择审批人"
>
<el-option
v-for="user in userList"
:key="user.id"
:label="user.nickname"
:value="user.id"
/>
</el-select>
</el-form-item>
</el-form>
</el-card>
</el-col>
</template>
</form-create>
</el-col> </el-col>
<el-col :span="6" :offset="1"> <el-col :span="6" :offset="1">
@ -61,7 +28,9 @@
ref="timelineRef" ref="timelineRef"
:activity-nodes="activityNodes" :activity-nodes="activityNodes"
:show-status-icon="false" :show-status-icon="false"
candidateField="candidateUserList" :startUserSelectTasks="startUserSelectTasks"
:startUserSelectAssignees="startUserSelectAssignees"
@select-user-confirm="selectUserConfirm"
/> />
</el-col> </el-col>
</el-row> </el-row>
@ -72,15 +41,15 @@
<el-tab-pane label="流程图" name="diagram"> <el-tab-pane label="流程图" name="diagram">
<div class="form-scroll-area"> <div class="form-scroll-area">
<!-- BPMN 流程图预览 --> <!-- BPMN 流程图预览 -->
<ProcessInstanceBpmnViewer <ProcessInstanceBpmnViewer
:bpmn-xml="bpmnXML" :bpmn-xml="bpmnXML"
v-if="BpmModelType.BPMN === selectProcessDefinition.modelType" v-if="BpmModelType.BPMN === selectProcessDefinition.modelType"
/> />
<!-- Simple 流程图预览 --> <!-- Simple 流程图预览 -->
<ProcessInstanceSimpleViewer <ProcessInstanceSimpleViewer
:simple-json="simpleJson" :simple-json="simpleJson"
v-if="BpmModelType.SIMPLE === selectProcessDefinition.modelType" v-if="BpmModelType.SIMPLE === selectProcessDefinition.modelType"
/> />
</div> </div>
</el-tab-pane> </el-tab-pane>
@ -115,7 +84,6 @@ import type { ApiAttrs } from '@form-create/element-ui/types/config'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import * as ProcessInstanceApi from '@/api/bpm/processInstance' import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import * as DefinitionApi from '@/api/bpm/definition' import * as DefinitionApi from '@/api/bpm/definition'
import * as UserApi from '@/api/system/user'
defineOptions({ name: 'ProcessDefinitionDetail' }) defineOptions({ name: 'ProcessDefinitionDetail' })
const props = defineProps<{ const props = defineProps<{
@ -132,11 +100,8 @@ const detailForm: any = ref({
}) // }) //
const fApi = ref<ApiAttrs>() const fApi = ref<ApiAttrs>()
// //
const startUserSelectAssigneesFormRef = ref() // Ref
const startUserSelectTasks: any = ref([]) // const startUserSelectTasks: any = ref([]) //
const startUserSelectAssignees = ref({}) // const startUserSelectAssignees = ref({}) //
const startUserSelectAssigneesFormRules = ref({}) // Rules
const userList = ref<any[]>([]) //
const bpmnXML: any = ref(null) // BPMN const bpmnXML: any = ref(null) // BPMN
const simpleJson = ref<string|undefined>() // Simple json const simpleJson = ref<string|undefined>() // Simple json
/** 当前的Tab */ /** 当前的Tab */
@ -150,7 +115,6 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
// //
startUserSelectTasks.value = [] startUserSelectTasks.value = []
startUserSelectAssignees.value = {} startUserSelectAssignees.value = {}
startUserSelectAssigneesFormRules.value = {}
// //
if (row.formType == 10) { if (row.formType == 10) {
@ -176,24 +140,12 @@ const initProcessInfo = async (row: any, formVariables?: any) => {
bpmnXML.value = processDefinitionDetail.bpmnXml bpmnXML.value = processDefinitionDetail.bpmnXml
simpleJson.value = processDefinitionDetail.simpleModel simpleJson.value = processDefinitionDetail.simpleModel
startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
// //
if (startUserSelectTasks.value?.length > 0) { if (startUserSelectTasks.value?.length > 0) {
detailForm.value.rule.push({
type: 'startUserSelect',
props: {
title: '指定审批人'
}
})
//
for (const userTask of startUserSelectTasks.value) { for (const userTask of startUserSelectTasks.value) {
//
startUserSelectAssignees.value[userTask.id] = [] startUserSelectAssignees.value[userTask.id] = []
startUserSelectAssigneesFormRules.value[userTask.id] = [
{ required: true, message: '请选择审批人', trigger: 'blur' }
]
} }
//
userList.value = await UserApi.getSimpleUserList()
} }
} }
// //
@ -223,13 +175,19 @@ const getApprovalDetail = async (row: any) => {
} }
/** 提交按钮 */ /** 提交按钮 */
const submitForm = async (formData: any) => { const submitForm = async () => {
if (!fApi.value || !props.selectProcessDefinition) { if (!fApi.value || !props.selectProcessDefinition) {
return return
} }
// //
if (startUserSelectTasks.value?.length > 0) { if (startUserSelectTasks.value?.length > 0) {
await startUserSelectAssigneesFormRef.value.validate() for (const userTask of startUserSelectTasks.value) {
if (
Array.isArray(startUserSelectAssignees.value[userTask.id]) &&
startUserSelectAssignees.value[userTask.id].length === 0
)
return message.warning(`请选择${userTask.name}的审批人`)
}
} }
// //
@ -237,7 +195,7 @@ const submitForm = async (formData: any) => {
try { try {
await ProcessInstanceApi.createProcessInstance({ await ProcessInstanceApi.createProcessInstance({
processDefinitionId: props.selectProcessDefinition.id, processDefinitionId: props.selectProcessDefinition.id,
variables: formData || detailForm.value.value, variables: detailForm.value.value,
startUserSelectAssignees: startUserSelectAssignees.value startUserSelectAssignees: startUserSelectAssignees.value
}) })
// //
@ -256,6 +214,10 @@ const handleCancel = () => {
emit('cancel') emit('cancel')
} }
const selectUserConfirm = (id, userList) => {
startUserSelectAssignees.value[id] = userList?.map((item) => item.id)
}
defineExpose({ initProcessInfo }) defineExpose({ initProcessInfo })
</script> </script>
@ -263,7 +225,7 @@ defineExpose({ initProcessInfo })
$wrap-padding-height: 20px; $wrap-padding-height: 20px;
$wrap-margin-height: 15px; $wrap-margin-height: 15px;
$button-height: 51px; $button-height: 51px;
$process-header-height: 194px; $process-header-height: 105px;
.processInstance-wrap-main { .processInstance-wrap-main {
height: calc( height: calc(

View File

@ -1,46 +1,74 @@
<template> <template>
<!-- 第一步通过流程定义的列表选择对应的流程 --> <!-- 第一步通过流程定义的列表选择对应的流程 -->
<ContentWrap <template v-if="!selectProcessDefinition">
class="process-definition-container position-relative pb-20px" <el-input
v-if="!selectProcessDefinition" v-model="currentSearchKey"
v-loading="loading" class="!w-50% mb-15px"
> placeholder="请输入流程名称"
<el-row :gutter="20" class="!flex-nowrap"> clearable
<el-col :span="5"> @keyup.enter="handleQuery"
<div class="flex flex-col"> @clear="handleClear"
<div >
v-for="category in categoryList" <template #prefix>
:key="category.code" <Icon icon="ep:search" />
class="flex items-center p-10px cursor-pointer text-14px rounded-md" </template>
:class="categoryActive.code === category.code ? 'text-#3e7bff bg-#e8eeff' : ''" </el-input>
@click="handleCategoryClick(category)" <ContentWrap
> :class="{ 'process-definition-container': filteredProcessDefinitionList?.length }"
{{ category.name }} class="position-relative pb-20px h-700px"
v-loading="loading"
>
<el-row v-if="filteredProcessDefinitionList?.length" :gutter="20" class="!flex-nowrap">
<el-col :span="5">
<div class="flex flex-col">
<div
v-for="category in categoryList"
:key="category.code"
class="flex items-center p-10px cursor-pointer text-14px rounded-md"
:class="categoryActive.code === category.code ? 'text-#3e7bff bg-#e8eeff' : ''"
@click="handleCategoryClick(category)"
>
{{ category.name }}
</div>
</div> </div>
</div> </el-col>
</el-col> <el-col :span="19">
<el-col :span="19"> <el-scrollbar ref="scrollWrapper" height="700">
<h3 class="text-16px font-bold mb-10px mt-5px">{{ categoryActive.name }}</h3> <div
<div class="grid grid-cols-3 gap3" v-if="categoryProcessDefinitionList.length"> class="mb-20px pl-10px"
<el-card v-for="(definitions, title) in processDefinitionGroup"
v-for="definition in categoryProcessDefinitionList" :key="title"
:key="definition.id" :ref="`category-${title}`"
shadow="hover" >
class="cursor-pointer definition-item-card" <h3 class="text-18px font-bold mb-10px mt-5px">{{ title }}</h3>
@click="handleSelect(definition)" <div class="grid grid-cols-3 gap3">
> <el-tooltip
<template #default> v-for="definition in definitions"
<div class="flex"> :key="definition.id"
<el-image :src="definition.icon" class="w-32px h-32px" /> :content="definition.description"
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text> placement="top"
>
<el-card
shadow="hover"
class="cursor-pointer definition-item-card"
@click="handleSelect(definition)"
>
<template #default>
<div class="flex">
<el-image :src="definition.icon" class="w-32px h-32px" />
<el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
</div>
</template>
</el-card>
</el-tooltip>
</div> </div>
</template> </div>
</el-card> </el-scrollbar>
</div> </el-col>
<el-empty v-else /> </el-row>
</el-col> <el-empty class="!py-200px" :image-size="200" description="没有找到搜索结果" v-else />
</el-row> </ContentWrap>
</ContentWrap> </template>
<!-- 第二步填写表单进行流程的提交 --> <!-- 第二步填写表单进行流程的提交 -->
<ProcessDefinitionDetail <ProcessDefinitionDetail
@ -56,12 +84,14 @@ import * as DefinitionApi from '@/api/bpm/definition'
import * as ProcessInstanceApi from '@/api/bpm/processInstance' import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import { CategoryApi } from '@/api/bpm/category' import { CategoryApi } from '@/api/bpm/category'
import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue' import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue'
import { groupBy } from 'lodash-es'
defineOptions({ name: 'BpmProcessInstanceCreate' }) defineOptions({ name: 'BpmProcessInstanceCreate' })
const { proxy } = getCurrentInstance() as any
const route = useRoute() // const route = useRoute() //
const message = useMessage() // const message = useMessage() //
const currentSearchKey = ref('') //
const processInstanceId: any = route.query.processInstanceId const processInstanceId: any = route.query.processInstanceId
const loading = ref(true) // const loading = ref(true) //
const categoryList: any = ref([]) // const categoryList: any = ref([]) //
@ -71,15 +101,10 @@ const processDefinitionList = ref([]) // 流程定义的列表
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
// //
categoryList.value = await CategoryApi.getCategorySimpleList() await getCategoryList()
if (categoryList.value.length > 0) { //
categoryActive.value = categoryList.value[0] await getDefinitionList()
}
//
processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
suspensionState: 1
})
// processInstanceId // processInstanceId
if (processInstanceId?.length > 0) { if (processInstanceId?.length > 0) {
@ -102,11 +127,55 @@ const getList = async () => {
} }
} }
/** 选中分类对应的流程定义列表 */ //
const categoryProcessDefinitionList: any = computed(() => { const getCategoryList = async () => {
return processDefinitionList.value.filter( try {
(item: any) => item.category == categoryActive.value.code //
) categoryList.value = await CategoryApi.getCategorySimpleList()
if (categoryList.value.length > 0) {
categoryActive.value = categoryList.value[0]
}
} finally {
}
}
//
const getDefinitionList = async () => {
try {
//
processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
suspensionState: 1
})
//
filteredProcessDefinitionList.value = processDefinitionList.value
} finally {
}
}
const filteredProcessDefinitionList = ref([]) //
//
const handleQuery = () => {
if (currentSearchKey.value.trim()) {
//
filteredProcessDefinitionList.value = processDefinitionList.value.filter(
(definition: any) =>
definition.name.toLowerCase().includes(currentSearchKey.value.toLowerCase()) //
)
} else {
//
filteredProcessDefinitionList.value = processDefinitionList.value
}
}
// input `clearable`
const handleClear = () => {
filteredProcessDefinitionList.value = processDefinitionList.value
}
//
const processDefinitionGroup: any = computed(() => {
if (!processDefinitionList.value?.length) return {}
return groupBy(filteredProcessDefinitionList.value, 'categoryName')
}) })
// ========== ========== // ========== ==========
@ -122,8 +191,16 @@ const handleSelect = async (row, formVariables?) => {
processDefinitionDetailRef.value?.initProcessInfo(row, formVariables) processDefinitionDetailRef.value?.initProcessInfo(row, formVariables)
} }
// //
const handleCategoryClick = (val: number) => { const handleCategoryClick = (category) => {
categoryActive.value = val categoryActive.value = category
const categoryRef = proxy.$refs[`category-${category.name}`] // DOM
if (categoryRef?.length) {
const scrollWrapper = proxy.$refs.scrollWrapper //
const categoryOffsetTop = categoryRef[0].offsetTop
//
scrollWrapper.scrollTo({ top: categoryOffsetTop, behavior: 'smooth' })
}
} }
/** 初始化 */ /** 初始化 */

View File

@ -24,7 +24,7 @@
</div> </div>
</div> </div>
</template> </template>
<div class="flex flex-col items-start"> <div class="flex flex-col items-start gap2" :id="`activity-task-${activity.id}`">
<!-- 第一行节点名称时间 --> <!-- 第一行节点名称时间 -->
<div class="flex w-full"> <div class="flex w-full">
<div class="font-bold"> {{ activity.name }}</div> <div class="font-bold"> {{ activity.name }}</div>
@ -36,53 +36,79 @@
{{ getApprovalNodeTime(activity) }} {{ getApprovalNodeTime(activity) }}
</div> </div>
</div> </div>
<div class="flex items-center flex-wrap mt-1"> <!-- 需要自定义选择审批人 -->
<div
class="flex flex-wrap gap2 items-center"
v-if="
startUserSelectTasks?.length > 0 && Array.isArray(startUserSelectAssignees[activity.id])
"
>
<!-- && activity.nodeType === NodeType.USER_TASK_NODE -->
<el-button
class="!px-8px"
@click="handleSelectUser(activity.id, customApprover[activity.id])"
>
<Icon icon="fa:user-plus" />
</el-button>
<div
v-for="(user, idx1) in customApprover[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"
>
<el-avatar :size="28" v-if="user.avatar" :src="user.avatar" />
<el-avatar :size="28" v-else>
{{ user.nickname.substring(0, 1) }}
</el-avatar>
{{ user.nickname }}
</div>
</div>
<div v-else class="flex items-center flex-wrap mt-1 gap2">
<!-- 情况一遍历每个审批节点下的进行中task 任务 --> <!-- 情况一遍历每个审批节点下的进行中task 任务 -->
<div v-for="(task, idx) in activity.tasks" :key="idx" class="flex items-center"> <div v-for="(task, idx) in activity.tasks" :key="idx" class="flex flex-col pr-2 gap2">
<div class="flex flex-col pr-2 gap2"> <div
class="position-relative flex flex-wrap gap2"
v-if="task.assigneeUser || task.ownerUser"
>
<!-- 信息头像昵称 -->
<div <div
class="position-relative pt-2 flex flex-wrap gap2" class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600 position-relative"
v-if="task.assigneeUser || task.ownerUser"
> >
<!-- 信息头像昵称 --> <template v-if="task.assigneeUser?.avatar || task.assigneeUser?.nickname">
<el-avatar
:size="28"
v-if="task.assigneeUser?.avatar"
:src="task.assigneeUser?.avatar"
/>
<el-avatar :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
:size="28"
v-if="task.ownerUser?.avatar"
:src="task.ownerUser?.avatar"
/>
<el-avatar :size="28" v-else>
{{ task.ownerUser?.nickname.substring(0, 1) }}
</el-avatar>
{{ task.ownerUser?.nickname }}
</template>
<!-- 信息任务 ICON -->
<div <div
class="bg-gray-100 h-35px rounded-3xl flex items-center p-8px gap-2 dark:color-gray-600 position-relative" v-if="onlyStatusIconShow.includes(task.status)"
class="position-absolute top-22px left-26px bg-#fff rounded-full flex items-center p-2px"
> >
<template v-if="task.assigneeUser?.avatar || task.assigneeUser?.nickname"> <Icon
<el-avatar :size="12"
:size="28" :icon="statusIconMap2[task.status]?.icon"
v-if="task.assigneeUser?.avatar" :color="statusIconMap2[task.status]?.color"
:src="task.assigneeUser?.avatar" />
/>
<el-avatar :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
:size="28"
v-if="task.ownerUser?.avatar"
:src="task.ownerUser?.avatar"
/>
<el-avatar :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"
>
<Icon
:size="12"
:icon="statusIconMap2[task.status]?.icon"
:color="statusIconMap2[task.status]?.color"
/>
</div>
</div> </div>
</div> </div>
</div>
<teleport defer :to="`#activity-task-${activity.id}`">
<div <div
v-if=" v-if="
task.reason && task.reason &&
@ -92,7 +118,7 @@
> >
审批意见{{ task.reason }} 审批意见{{ task.reason }}
</div> </div>
</div> </teleport>
</div> </div>
<!-- 情况二遍历每个审批节点下的候选的task 任务例如说1依次审批2未来的审批任务等 --> <!-- 情况二遍历每个审批节点下的候选的task 任务例如说1依次审批2未来的审批任务等 -->
<div <div
@ -121,6 +147,9 @@
</div> </div>
</el-timeline-item> </el-timeline-item>
</el-timeline> </el-timeline>
<!-- 用户选择弹窗 -->
<UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -141,9 +170,13 @@ withDefaults(
defineProps<{ defineProps<{
activityNodes: ProcessInstanceApi.ApprovalNodeInfo[] // activityNodes: ProcessInstanceApi.ApprovalNodeInfo[] //
showStatusIcon?: boolean // showStatusIcon?: boolean //
startUserSelectTasks?: any[] //
startUserSelectAssignees?: any //
}>(), }>(),
{ {
showStatusIcon: true // true showStatusIcon: true, // true
startUserSelectTasks: () => [], //
startUserSelectAssignees: () => {}
} }
) )
@ -241,4 +274,19 @@ const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
return `${formatDate(node.startTime)}` return `${formatDate(node.startTime)}`
} }
} }
//
const userSelectFormRef = ref()
const handleSelectUser = (activityId, selectedList) => {
userSelectFormRef.value.open(activityId, selectedList)
}
const emit = defineEmits<{
selectUserConfirm: [id: any, userList: any[]]
}>()
const customApprover: any = ref({})
//
const handleUserSelectConfirm = (activityId, userList) => {
customApprover.value[activityId] = userList || []
emit('selectUserConfirm', activityId, userList)
}
</script> </script>