liriming
3 天以前 1220f5ca98b10b735a47c37a81fbfc554b01e2fe
提交 | 用户 | 时间
c9a6f7 1 <template>
H 2   <ContentWrap>
3     <div class="mx-auto">
4       <!-- 头部导航栏 -->
5       <div
6         class="absolute top-0 left-0 right-0 h-50px bg-white border-bottom z-10 flex items-center px-20px"
7       >
8         <!-- 左侧标题 -->
9         <div class="w-200px flex items-center overflow-hidden">
10           <Icon icon="ep:arrow-left" class="cursor-pointer flex-shrink-0" @click="handleBack" />
11           <span class="ml-10px text-16px truncate" :title="formData.name || '创建流程'">
12             {{ formData.name || '创建流程' }}
13           </span>
14         </div>
15
16         <!-- 步骤条 -->
17         <div class="flex-1 flex items-center justify-center h-full">
18           <div class="w-400px flex items-center justify-between h-full">
19             <div
20               v-for="(step, index) in steps"
21               :key="index"
22               class="flex items-center cursor-pointer mx-15px relative h-full"
23               :class="[
24                 currentStep === index
25                   ? 'text-[#3473ff] border-[#3473ff] border-b-2 border-b-solid'
26                   : 'text-gray-500'
27               ]"
28               @click="handleStepClick(index)"
29             >
30               <div
31                 class="w-28px h-28px rounded-full flex items-center justify-center mr-8px border-2 border-solid text-15px"
32                 :class="[
33                   currentStep === index
34                     ? 'bg-[#3473ff] text-white border-[#3473ff]'
35                     : 'border-gray-300 bg-white text-gray-500'
36                 ]"
37               >
38                 {{ index + 1 }}
39               </div>
40               <span class="text-16px font-bold whitespace-nowrap">{{ step.title }}</span>
41             </div>
42           </div>
43         </div>
44
45         <!-- 右侧按钮 -->
46         <div class="w-200px flex items-center justify-end gap-2">
47           <el-button v-if="route.params.id" type="success" @click="handleDeploy">发 布</el-button>
48           <el-button type="primary" @click="handleSave">保 存</el-button>
49         </div>
50       </div>
51
52       <!-- 主体内容 -->
53       <div class="mt-50px">
54         <!-- 第一步:基本信息 -->
55         <div v-if="currentStep === 0" class="mx-auto w-560px">
56           <BasicInfo
57             v-model="formData"
58             :categoryList="categoryList"
59             :userList="userList"
60             ref="basicInfoRef"
61           />
62         </div>
63
64         <!-- 第二步:表单设计 -->
65         <div v-if="currentStep === 1" class="mx-auto w-560px">
66           <FormDesign v-model="formData" :formList="formList" ref="formDesignRef" />
67         </div>
68
69         <!-- 第三步:流程设计 -->
70         <ProcessDesign
71           v-if="currentStep === 2"
72           v-model="formData"
73           ref="processDesignRef"
74           @success="handleDesignSuccess"
75         />
76       </div>
77     </div>
78   </ContentWrap>
79 </template>
80
81 <script lang="ts" setup>
82 import { useRoute, useRouter } from 'vue-router'
83 import { useMessage } from '@/hooks/web/useMessage'
84 import * as ModelApi from '@/api/bpm/model'
85 import * as FormApi from '@/api/bpm/form'
86 import { CategoryApi } from '@/api/bpm/category'
87 import * as UserApi from '@/api/system/user'
88 import { useUserStoreWithOut } from '@/store/modules/user'
89 import { BpmModelFormType, BpmModelType } from '@/utils/constants'
90 import BasicInfo from './BasicInfo.vue'
91 import FormDesign from './FormDesign.vue'
92 import ProcessDesign from './ProcessDesign.vue'
93 import { useTagsViewStore } from '@/store/modules/tagsView'
94
95 const router = useRouter()
96 const { delView } = useTagsViewStore() // 视图操作
97 const route = useRoute()
98 const message = useMessage()
99 const userStore = useUserStoreWithOut()
100
101 // 组件引用
102 const basicInfoRef = ref()
103 const formDesignRef = ref()
104 const processDesignRef = ref()
105
106 /** 步骤校验函数 */
107 const validateBasic = async () => {
108   await basicInfoRef.value?.validate()
109 }
110
111 /** 表单设计校验 */
112 const validateForm = async () => {
113   await formDesignRef.value?.validate()
114 }
115
116 /** 流程设计校验 */
117 const validateProcess = async () => {
118   await processDesignRef.value?.validate()
119 }
120
121 const currentStep = ref(0) // 步骤控制
122 const steps = [
123   { title: '基本信息', validator: validateBasic },
124   { title: '表单设计', validator: validateForm },
125   { title: '流程设计', validator: validateProcess }
126 ]
127
128 // 表单数据
129 const formData: any = ref({
130   id: undefined,
131   name: '',
132   key: '',
133   category: undefined,
134   icon: undefined,
135   description: '',
136   type: BpmModelType.BPMN,
137   formType: BpmModelFormType.NORMAL,
138   formId: '',
139   formCustomCreatePath: '',
140   formCustomViewPath: '',
141   visible: true,
142   startUserType: undefined,
143   managerUserType: undefined,
144   startUserIds: [],
145   managerUserIds: []
146 })
147
148 // 数据列表
149 const formList = ref([])
150 const categoryList = ref([])
151 const userList = ref<UserApi.UserVO[]>([])
152
153 /** 初始化数据 */
154 const initData = async () => {
155   const modelId = route.params.id as string
156   if (modelId) {
157     // 修改场景
158     formData.value = await ModelApi.getModel(modelId)
159   } else {
160     // 新增场景
161     formData.value.managerUserIds.push(userStore.getUser.id)
162   }
163
164   // 获取表单列表
165   formList.value = await FormApi.getFormSimpleList()
166   // 获取分类列表
167   categoryList.value = await CategoryApi.getCategorySimpleList()
168   // 获取用户列表
169   userList.value = await UserApi.getSimpleUserList()
170 }
171
172 /** 校验所有步骤数据是否完整 */
173 const validateAllSteps = async () => {
174   try {
175     // 基本信息校验
176     await basicInfoRef.value?.validate()
177     if (!formData.value.key || !formData.value.name || !formData.value.category) {
178       currentStep.value = 0
179       throw new Error('请完善基本信息')
180     }
181
182     // 表单设计校验
183     await formDesignRef.value?.validate()
184     if (formData.value.formType === 10 && !formData.value.formId) {
185       currentStep.value = 1
186       throw new Error('请选择流程表单')
187     }
188     if (
189       formData.value.formType === 20 &&
190       (!formData.value.formCustomCreatePath || !formData.value.formCustomViewPath)
191     ) {
192       currentStep.value = 1
193       throw new Error('请完善自定义表单信息')
194     }
195
196     // 流程设计校验
197     // 如果已经有流程数据,则不需要重新校验
198     if (!formData.value.bpmnXml && !formData.value.simpleModel) {
199       // 如果当前不在第三步,需要先保存当前步骤数据
200       if (currentStep.value !== 2) {
201         await steps[currentStep.value].validator()
202         // 切换到第三步
203         currentStep.value = 2
204         // 等待组件渲染完成
205         await nextTick()
206       }
207
208       // 校验流程设计
209       await processDesignRef.value?.validate()
210       const processData = await processDesignRef.value?.getProcessData()
211       if (!processData) {
212         throw new Error('请设计流程')
213       }
214
215       // 保存流程数据
216       if (formData.value.type === BpmModelType.BPMN) {
217         formData.value.bpmnXml = processData
218         formData.value.simpleModel = null
219       } else {
220         formData.value.bpmnXml = null
221         formData.value.simpleModel = processData
222       }
223     }
224
225     return true
226   } catch (error) {
227     throw error
228   }
229 }
230
231 /** 保存操作 */
232 const handleSave = async () => {
233   try {
234     // 保存前校验所有步骤的数据
235     await validateAllSteps()
236
237     // 更新表单数据
238     const modelData = {
239       ...formData.value
240     }
241
242     // 如果当前在第三步,获取最新的流程设计数据
243     if (currentStep.value === 2) {
244       const processData = await processDesignRef.value?.getProcessData()
245       if (processData) {
246         if (formData.value.type === BpmModelType.BPMN) {
247           modelData.bpmnXml = processData
248           modelData.simpleModel = null
249         } else {
250           modelData.bpmnXml = null
251           modelData.simpleModel = processData
252         }
253       }
254     }
255
256     if (formData.value.id) {
257       // 修改场景
258       await ModelApi.updateModel(modelData)
259       // 询问是否发布流程
260       try {
261         await message.confirm('修改流程成功,是否发布流程?')
262         // 用户点击确认,执行发布
263         await handleDeploy()
264       } catch {
265         // 用户点击取消,停留在当前页面
266       }
267     } else {
268       // 新增场景
269       formData.value.id = await ModelApi.createModel(modelData)
270       message.success('新增成功')
271       try {
272         await message.confirm('创建流程成功,是否继续编辑?')
273         // 用户点击继续编辑,跳转到编辑页面
274         await nextTick()
275         // 先删除当前页签
276         delView(unref(router.currentRoute))
277         // 跳转到编辑页面
278         await router.push({
279           name: 'BpmModelUpdate',
280           params: { id: formData.value.id }
281         })
282       } catch {
283         // 先删除当前页签
284         delView(unref(router.currentRoute))
285         // 用户点击返回列表
286         await router.push({ name: 'BpmModel' })
287       }
288     }
289   } catch (error: any) {
290     console.error('保存失败:', error)
291     message.warning(error.message || '请完善所有步骤的必填信息')
292   }
293 }
294
295 /** 发布操作 */
296 const handleDeploy = async () => {
297   try {
298     // 修改场景下直接发布,新增场景下需要先确认
299     if (!formData.value.id) {
300       await message.confirm('是否确认发布该流程?')
301     }
302
303     // 校验所有步骤
304     await validateAllSteps()
305
306     // 更新表单数据
307     const modelData = {
308       ...formData.value
309     }
310
311     // 如果当前在第三步,获取最新的流程设计数据
312     if (currentStep.value === 2) {
313       const processData = await processDesignRef.value?.getProcessData()
314       if (processData) {
315         if (formData.value.type === BpmModelType.BPMN) {
316           modelData.bpmnXml = processData
317           modelData.simpleModel = null
318         } else {
319           modelData.bpmnXml = null
320           modelData.simpleModel = processData
321         }
322       }
323     }
324
325     // 先保存所有数据
326     if (formData.value.id) {
327       await ModelApi.updateModel(modelData)
328     } else {
329       const result = await ModelApi.createModel(modelData)
330       formData.value.id = result.id
331     }
332
333     // 发布
334     await ModelApi.deployModel(formData.value.id)
335     message.success('发布成功')
336     // 返回列表页
337     await router.push({ name: 'BpmModel' })
338   } catch (error: any) {
339     console.error('发布失败:', error)
340     message.warning(error.message || '发布失败')
341   }
342 }
343
344 /** 步骤切换处理 */
345 const handleStepClick = async (index: number) => {
346   try {
347     // 如果是切换到第三步(流程设计),需要校验key和name
348     if (index === 2) {
349       if (!formData.value.key || !formData.value.name) {
350         message.warning('请先填写流程标识和流程名称')
351         return
352       }
353     }
354
355     // 保存当前步骤的数据
356     if (currentStep.value === 2) {
357       const processData = await processDesignRef.value?.getProcessData()
358       if (processData) {
359         if (formData.value.type === BpmModelType.BPMN) {
360           formData.value.bpmnXml = processData
361           formData.value.simpleModel = null
362         } else {
363           formData.value.bpmnXml = null
364           formData.value.simpleModel = processData
365         }
366       }
367     } else {
368       // 只有在向后切换时才进行校验
369       if (index > currentStep.value) {
370         if (typeof steps[currentStep.value].validator === 'function') {
371           await steps[currentStep.value].validator()
372         }
373       }
374     }
375
376     // 切换步骤
377     currentStep.value = index
378
379     // 如果切换到流程设计步骤,等待组件渲染完成后刷新设计器
380     if (index === 2) {
381       await nextTick()
382       // 等待更长时间确保组件完全初始化
383       await new Promise(resolve => setTimeout(resolve, 200))
384       if (processDesignRef.value?.refresh) {
385         await processDesignRef.value.refresh()
386       }
387     }
388   } catch (error) {
389     console.error('步骤切换失败:', error)
390     message.warning('请先完善当前步骤必填信息')
391   }
392 }
393
394 /** 处理设计器保存成功 */
395 const handleDesignSuccess = (bpmnXml?: string) => {
396   if (bpmnXml) {
397     formData.value.bpmnXml = bpmnXml
398   }
399 }
400
401 /** 返回列表页 */
402 const handleBack = () => {
403   // 先删除当前页签
404   delView(unref(router.currentRoute))
405   // 跳转到列表页
406   router.push({ name: 'BpmModel' })
407 }
408
409 /** 初始化 */
410 onMounted(async () => {
411   await initData()
412 })
413
414 // 添加组件卸载前的清理代码
415 onBeforeUnmount(() => {
416   // 清理所有的引用
417   basicInfoRef.value = null
418   formDesignRef.value = null
419   processDesignRef.value = null
420 })
421 </script>
422
423 <style lang="scss" scoped>
424 .border-bottom {
425   border-bottom: 1px solid #dcdfe6;
426 }
427
428 .text-primary {
429   color: #3473ff;
430 }
431
432 .bg-primary {
433   background-color: #3473ff;
434 }
435
436 .border-primary {
437   border-color: #3473ff;
438 }
439 </style>