提交 | 用户 | 时间
820397 1 <template>
H 2   <Dialog v-model="dialogVisible" :title="dialogTitle" width="600">
3     <el-form
4       ref="formRef"
5       v-loading="formLoading"
6       :model="formData"
7       :rules="formRules"
8       label-width="110px"
9     >
10       <el-form-item label="流程标识" prop="key">
3e359e 11         <el-input v-model="formData.key" :disabled="!!formData.id" placeholder="请输入流标标识" />
820397 12         <el-tooltip
H 13           v-if="!formData.id"
14           class="item"
15           content="新建后,流程标识不可修改!"
16           effect="light"
17           placement="top"
18         >
19           <i class="el-icon-question" style="padding-left: 5px"></i>
20         </el-tooltip>
21         <el-tooltip v-else class="item" content="流程标识不可修改!" effect="light" placement="top">
22           <i class="el-icon-question" style="padding-left: 5px"></i>
23         </el-tooltip>
24       </el-form-item>
25       <el-form-item label="流程名称" prop="name">
26         <el-input
27           v-model="formData.name"
28           :disabled="!!formData.id"
29           clearable
30           placeholder="请输入流程名称"
31         />
32       </el-form-item>
3e359e 33       <el-form-item label="流程分类" prop="category">
820397 34         <el-select
H 35           v-model="formData.category"
36           clearable
37           placeholder="请选择流程分类"
38           style="width: 100%"
39         >
40           <el-option
41             v-for="category in categoryList"
42             :key="category.code"
43             :label="category.name"
44             :value="category.code"
45           />
46         </el-select>
47       </el-form-item>
3e359e 48       <el-form-item label="流程图标" prop="icon">
H 49         <UploadImg v-model="formData.icon" :limit="1" height="64px" width="64px" />
820397 50       </el-form-item>
H 51       <el-form-item label="流程描述" prop="description">
52         <el-input v-model="formData.description" clearable type="textarea" />
53       </el-form-item>
3e359e 54       <el-form-item label="流程类型" prop="type">
H 55         <el-radio-group v-model="formData.type">
56           <el-radio
57             v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_TYPE)"
58             :key="dict.value"
59             :label="dict.value"
820397 60           >
3e359e 61             {{ dict.label }}
H 62           </el-radio>
63         </el-radio-group>
64       </el-form-item>
65       <el-form-item label="表单类型" prop="formType">
66         <el-radio-group v-model="formData.formType">
67           <el-radio
68             v-for="dict in getIntDictOptions(DICT_TYPE.BPM_MODEL_FORM_TYPE)"
69             :key="dict.value"
70             :label="dict.value"
820397 71           >
3e359e 72             {{ dict.label }}
H 73           </el-radio>
74         </el-radio-group>
75       </el-form-item>
76       <el-form-item v-if="formData.formType === 10" label="流程表单" prop="formId">
77         <el-select v-model="formData.formId" clearable style="width: 100%">
78           <el-option v-for="form in formList" :key="form.id" :label="form.name" :value="form.id" />
79         </el-select>
80       </el-form-item>
81       <el-form-item
82         v-if="formData.formType === 20"
83         label="表单提交路由"
84         prop="formCustomCreatePath"
85       >
86         <el-input
87           v-model="formData.formCustomCreatePath"
88           placeholder="请输入表单提交路由"
89           style="width: 330px"
90         />
91         <el-tooltip
92           class="item"
93           content="自定义表单的提交路径,使用 Vue 的路由地址,例如说:bpm/oa/leave/create.vue"
94           effect="light"
95           placement="top"
96         >
97           <i class="el-icon-question" style="padding-left: 5px"></i>
98         </el-tooltip>
99       </el-form-item>
100       <el-form-item v-if="formData.formType === 20" label="表单查看地址" prop="formCustomViewPath">
101         <el-input
102           v-model="formData.formCustomViewPath"
103           placeholder="请输入表单查看的组件地址"
104           style="width: 330px"
105         />
106         <el-tooltip
107           class="item"
108           content="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
109           effect="light"
110           placement="top"
111         >
112           <i class="el-icon-question" style="padding-left: 5px"></i>
113         </el-tooltip>
114       </el-form-item>
115       <el-form-item label="是否可见" prop="visible">
116         <el-radio-group v-model="formData.visible">
117           <el-radio
118             v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
119             :key="dict.value as string"
120             :label="dict.value"
121           >
122             {{ dict.label }}
123           </el-radio>
124         </el-radio-group>
125       </el-form-item>
c9a6f7 126       <el-form-item label="谁可以发起" prop="startUserType">
3e359e 127         <el-select
c9a6f7 128           v-model="formData.startUserType"
H 129           placeholder="请选择谁可以发起"
130           @change="handleStartUserTypeChange"
3e359e 131         >
c9a6f7 132           <el-option label="全员" :value="0" />
H 133           <el-option label="指定人员" :value="1" />
134           <el-option label="均不可提交" :value="2" />
3e359e 135         </el-select>
c9a6f7 136         <div v-if="formData.startUserType === 1" class="mt-2 flex flex-wrap gap-2">
H 137           <div
138             v-for="user in selectedStartUsers"
139             :key="user.id"
140             class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
141           >
142             <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
143             <el-avatar class="!m-5px" :size="28" v-else>
144               {{ user.nickname.substring(0, 1) }}
145             </el-avatar>
146             {{ user.nickname }}
147             <Icon
148               icon="ep:close"
149               class="ml-2 cursor-pointer hover:text-red-500"
150               @click="handleRemoveStartUser(user)"
151             />
152           </div>
153           <el-button type="primary" link @click="openStartUserSelect">
154             <Icon icon="ep:plus" />选择人员
155           </el-button>
156         </div>
3e359e 157       </el-form-item>
c9a6f7 158       <el-form-item label="流程管理员" prop="managerUserType">
H 159         <el-select
160           v-model="formData.managerUserType"
161           placeholder="请选择流程管理员"
162           @change="handleManagerUserTypeChange"
163         >
164           <el-option label="全员" :value="0" />
165           <el-option label="指定人员" :value="1" />
166           <el-option label="均不可提交" :value="2" />
3e359e 167         </el-select>
c9a6f7 168         <div v-if="formData.managerUserType === 1" class="mt-2 flex flex-wrap gap-2">
H 169           <div
170             v-for="user in selectedManagerUsers"
171             :key="user.id"
172             class="bg-gray-100 h-35px rounded-3xl flex items-center pr-8px dark:color-gray-600 position-relative"
173           >
174             <el-avatar class="!m-5px" :size="28" v-if="user.avatar" :src="user.avatar" />
175             <el-avatar class="!m-5px" :size="28" v-else>
176               {{ user.nickname.substring(0, 1) }}
177             </el-avatar>
178             {{ user.nickname }}
179             <Icon
180               icon="ep:close"
181               class="ml-2 cursor-pointer hover:text-red-500"
182               @click="handleRemoveManagerUser(user)"
183             />
184           </div>
185           <el-button type="primary" link @click="openManagerUserSelect">
186             <Icon icon="ep:plus" />选择人员
187           </el-button>
188         </div>
3e359e 189       </el-form-item>
820397 190     </el-form>
H 191     <template #footer>
192       <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
193       <el-button @click="dialogVisible = false">取 消</el-button>
194     </template>
195   </Dialog>
c9a6f7 196   <UserSelectForm ref="userSelectFormRef" @confirm="handleUserSelectConfirm" />
820397 197 </template>
H 198 <script lang="ts" setup>
3e359e 199 import { propTypes } from '@/utils/propTypes'
H 200 import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '@/utils/dict'
820397 201 import { ElMessageBox } from 'element-plus'
H 202 import * as ModelApi from '@/api/bpm/model'
203 import * as FormApi from '@/api/bpm/form'
c9a6f7 204 import { CategoryApi, CategoryVO } from '@/api/bpm/category'
3e359e 205 import { BpmModelFormType, BpmModelType } from '@/utils/constants'
H 206 import { UserVO } from '@/api/system/user'
207 import * as UserApi from '@/api/system/user'
208 import { useUserStoreWithOut } from '@/store/modules/user'
c9a6f7 209 import { FormVO } from '@/api/bpm/form'
820397 210
H 211 defineOptions({ name: 'ModelForm' })
212
213 const { t } = useI18n() // 国际化
214 const message = useMessage() // 消息弹窗
3e359e 215 const userStore = useUserStoreWithOut() // 用户信息缓存
H 216 const props = defineProps({
217   categoryId: propTypes.number
218 })
820397 219 const dialogVisible = ref(false) // 弹窗的是否展示
H 220 const dialogTitle = ref('') // 弹窗的标题
221 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
222 const formType = ref('') // 表单的类型:create - 新增;update - 修改
c9a6f7 223 const formData: any = ref({
3e359e 224   id: undefined,
820397 225   name: '',
3e359e 226   key: '',
820397 227   category: undefined,
H 228   icon: undefined,
229   description: '',
3e359e 230   type: BpmModelType.BPMN,
H 231   formType: BpmModelFormType.NORMAL,
820397 232   formId: '',
H 233   formCustomCreatePath: '',
3e359e 234   formCustomViewPath: '',
H 235   visible: true,
c9a6f7 236   startUserType: undefined,
H 237   managerUserType: undefined,
3e359e 238   startUserIds: [],
H 239   managerUserIds: []
820397 240 })
H 241 const formRules = reactive({
3e359e 242   name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
H 243   key: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
244   category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
245   icon: [{ required: true, message: '流程图标不能为空', trigger: 'blur' }],
246   type: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
247   formType: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
248   formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
249   formCustomCreatePath: [{ required: true, message: '表单提交路由不能为空', trigger: 'blur' }],
250   formCustomViewPath: [{ required: true, message: '表单查看地址不能为空', trigger: 'blur' }],
251   visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
252   managerUserIds: [{ required: true, message: '流程管理员不能为空', trigger: 'blur' }]
820397 253 })
H 254 const formRef = ref() // 表单 Ref
c9a6f7 255 const formList = ref<FormVO[]>([]) // 流程表单的下拉框的数据
H 256 const categoryList = ref<CategoryVO[]>([]) // 流程分类列表
3e359e 257 const userList = ref<UserVO[]>([]) // 用户列表
c9a6f7 258 const selectedStartUsers = ref<UserVO[]>([]) // 已选择的发起人列表
H 259 const selectedManagerUsers = ref<UserVO[]>([]) // 已选择的管理员列表
260 const userSelectFormRef = ref() // 用户选择弹窗 ref
261 const currentSelectType = ref<'start' | 'manager'>('start') // 当前选择的是发起人还是管理员
820397 262
H 263 /** 打开弹窗 */
3e359e 264 const open = async (type: string, id?: string) => {
820397 265   dialogVisible.value = true
H 266   dialogTitle.value = t('action.' + type)
267   formType.value = type
268   resetForm()
269   // 修改时,设置数据
270   if (id) {
271     formLoading.value = true
272     try {
273       formData.value = await ModelApi.getModel(id)
274     } finally {
275       formLoading.value = false
c9a6f7 276     }
H 277     // 加载数据时,根据已有的用户ID列表初始化已选用户
278     if (formData.value.startUserIds?.length) {
279       formData.value.startUserType = 1
280       selectedStartUsers.value = userList.value.filter((user) =>
281         formData.value.startUserIds.includes(user.id)
282       )
283     }
284     if (formData.value.managerUserIds?.length) {
285       formData.value.managerUserType = 1
286       selectedManagerUsers.value = userList.value.filter((user) =>
287         formData.value.managerUserIds.includes(user.id)
288       )
820397 289     }
3e359e 290   } else {
H 291     formData.value.managerUserIds.push(userStore.getUser.id)
820397 292   }
H 293   // 获得流程表单的下拉框的数据
294   formList.value = await FormApi.getFormSimpleList()
295   // 查询流程分类列表
296   categoryList.value = await CategoryApi.getCategorySimpleList()
3e359e 297   // 查询用户列表
H 298   userList.value = await UserApi.getSimpleUserList()
299   if (props.categoryId) {
300     formData.value.category = props.categoryId
301   }
820397 302 }
H 303 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
304
305 /** 提交表单 */
306 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
307 const submitForm = async () => {
308   // 校验表单
309   if (!formRef) return
310   const valid = await formRef.value.validate()
311   if (!valid) return
312   // 提交请求
313   formLoading.value = true
314   try {
315     const data = formData.value as unknown as ModelApi.ModelVO
316     if (formType.value === 'create') {
317       await ModelApi.createModel(data)
318       // 提示,引导用户做后续的操作
319       await ElMessageBox.alert(
3e359e 320         '<strong>新建模型成功!</strong>后续需要执行如下 2 个步骤:' +
c9a6f7 321         '<div>1. 点击【设计流程】按钮,绘制流程图</div>' +
H 322         '<div>2. 点击【发布流程】按钮,完成流程的最终发布</div>' +
323         '另外,每次流程修改后,都需要点击【发布流程】按钮,才能正式生效!!!',
820397 324         '重要提示',
H 325         {
326           dangerouslyUseHTMLString: true,
327           type: 'success'
328         }
329       )
330     } else {
331       await ModelApi.updateModel(data)
332       message.success(t('common.updateSuccess'))
333     }
334     dialogVisible.value = false
335     // 发送操作成功的事件
336     emit('success')
337   } finally {
338     formLoading.value = false
339   }
340 }
341
342 /** 重置表单 */
343 const resetForm = () => {
344   formData.value = {
3e359e 345     id: undefined,
820397 346     name: '',
3e359e 347     key: '',
820397 348     category: undefined,
3e359e 349     icon: undefined,
820397 350     description: '',
3e359e 351     type: BpmModelType.BPMN,
H 352     formType: BpmModelFormType.NORMAL,
820397 353     formId: '',
H 354     formCustomCreatePath: '',
3e359e 355     formCustomViewPath: '',
H 356     visible: true,
c9a6f7 357     startUserType: undefined,
H 358     managerUserType: undefined,
3e359e 359     startUserIds: [],
H 360     managerUserIds: []
820397 361   }
H 362   formRef.value?.resetFields()
c9a6f7 363   selectedStartUsers.value = []
H 364   selectedManagerUsers.value = []
365 }
366
367 /** 处理发起人类型变化 */
368 const handleStartUserTypeChange = (value: number) => {
369   if (value !== 1) {
370     selectedStartUsers.value = []
371     formData.value.startUserIds = []
372   }
373 }
374
375 /** 处理管理员类型变化 */
376 const handleManagerUserTypeChange = (value: number) => {
377   if (value !== 1) {
378     selectedManagerUsers.value = []
379     formData.value.managerUserIds = []
380   }
381 }
382
383 /** 打开发起人选择 */
384 const openStartUserSelect = () => {
385   currentSelectType.value = 'start'
386   userSelectFormRef.value.open(0, selectedStartUsers.value)
387 }
388
389 /** 打开管理员选择 */
390 const openManagerUserSelect = () => {
391   currentSelectType.value = 'manager'
392   userSelectFormRef.value.open(0, selectedManagerUsers.value)
393 }
394
395 /** 处理用户选择确认 */
396 const handleUserSelectConfirm = (_, users: UserVO[]) => {
397   if (currentSelectType.value === 'start') {
398     selectedStartUsers.value = users
399     formData.value.startUserIds = users.map((u) => u.id)
400   } else {
401     selectedManagerUsers.value = users
402     formData.value.managerUserIds = users.map((u) => u.id)
403   }
404 }
405
406 /** 移除发起人 */
407 const handleRemoveStartUser = (user: UserVO) => {
408   selectedStartUsers.value = selectedStartUsers.value.filter((u) => u.id !== user.id)
409   formData.value.startUserIds = formData.value.startUserIds.filter((id: number) => id !== user.id)
410 }
411
412 /** 移除管理员 */
413 const handleRemoveManagerUser = (user: UserVO) => {
414   selectedManagerUsers.value = selectedManagerUsers.value.filter((u) => u.id !== user.id)
415   formData.value.managerUserIds = formData.value.managerUserIds.filter(
416     (id: number) => id !== user.id
417   )
820397 418 }
H 419 </script>
c9a6f7 420
H 421 <style lang="scss" scoped>
422 .bg-gray-100 {
423   background-color: #f5f7fa;
424   transition: all 0.3s;
425
426   &:hover {
427     background-color: #e6e8eb;
428   }
429
430   .ep-close {
431     font-size: 14px;
432     color: #909399;
433     transition: color 0.3s;
434
435     &:hover {
436       color: #f56c6c;
437     }
438   }
439 }
440 </style>