提交 | 用户 | 时间
820397 1 <template>
H 2   <!-- 第一步,通过流程定义的列表,选择对应的流程 -->
3e359e 3   <template v-if="!selectProcessDefinition">
H 4     <el-input
5       v-model="searchName"
6       class="!w-50% mb-15px"
7       placeholder="请输入流程名称"
8       clearable
9       @input="handleQuery"
10       @clear="handleQuery"
11     >
12       <template #prefix>
13         <Icon icon="ep:search" />
14       </template>
15     </el-input>
16     <ContentWrap
17       :class="{ 'process-definition-container': filteredProcessDefinitionList?.length }"
18       class="position-relative pb-20px h-700px"
19       v-loading="loading"
20     >
21       <el-row v-if="filteredProcessDefinitionList?.length" :gutter="20" class="!flex-nowrap">
22         <el-col :span="5">
23           <div class="flex flex-col">
24             <div
25               v-for="category in availableCategories"
26               :key="category.code"
27               class="flex items-center p-10px cursor-pointer text-14px rounded-md"
28               :class="categoryActive.code === category.code ? 'text-#3e7bff bg-#e8eeff' : ''"
29               @click="handleCategoryClick(category)"
820397 30             >
3e359e 31               {{ category.name }}
H 32             </div>
33           </div>
34         </el-col>
35         <el-col :span="19">
36           <el-scrollbar ref="scrollWrapper" height="700" @scroll="handleScroll">
37             <div
38               class="mb-20px pl-10px"
39               v-for="(definitions, categoryCode) in processDefinitionGroup"
40               :key="categoryCode"
41               :ref="`category-${categoryCode}`"
42             >
43               <h3 class="text-18px font-bold mb-10px mt-5px">
44                 {{ getCategoryName(categoryCode as any) }}
45               </h3>
46               <div class="grid grid-cols-3 gap3">
47                 <el-tooltip
48                   v-for="definition in definitions"
49                   :key="definition.id"
50                   :content="definition.description"
51                   :disabled="!definition.description || definition.description.trim().length === 0"
52                   placement="top"
53                 >
54                   <el-card
55                     shadow="hover"
56                     class="cursor-pointer definition-item-card"
57                     @click="handleSelect(definition)"
58                   >
59                     <template #default>
60                       <div class="flex">
61                         <el-image :src="definition.icon" class="w-32px h-32px" />
62                         <el-text class="!ml-10px" size="large">{{ definition.name }}</el-text>
63                       </div>
64                     </template>
65                   </el-card>
66                 </el-tooltip>
67               </div>
68             </div>
69           </el-scrollbar>
70         </el-col>
71       </el-row>
72       <el-empty class="!py-200px" :image-size="200" description="没有找到搜索结果" v-else />
73     </ContentWrap>
74   </template>
820397 75
H 76   <!-- 第二步,填写表单,进行流程的提交 -->
3e359e 77   <ProcessDefinitionDetail
H 78     v-else
79     ref="processDefinitionDetailRef"
80     :selectProcessDefinition="selectProcessDefinition"
81     @cancel="selectProcessDefinition = undefined"
82   />
820397 83 </template>
3e359e 84
820397 85 <script lang="ts" setup>
H 86 import * as DefinitionApi from '@/api/bpm/definition'
87 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
3e359e 88 import { CategoryApi, CategoryVO } from '@/api/bpm/category'
H 89 import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue'
90 import { groupBy } from 'lodash-es'
820397 91
H 92 defineOptions({ name: 'BpmProcessInstanceCreate' })
93
3e359e 94 const { proxy } = getCurrentInstance() as any
820397 95 const route = useRoute() // 路由
H 96 const message = useMessage() // 消息
97
3e359e 98 const searchName = ref('') // 当前搜索关键字
H 99 const processInstanceId: any = route.query.processInstanceId // 流程实例编号。场景:重新发起时
820397 100 const loading = ref(true) // 加载中
3e359e 101 const categoryList: any = ref([]) // 分类的列表
H 102 const categoryActive: any = ref({}) // 选中的分类
820397 103 const processDefinitionList = ref([]) // 流程定义的列表
H 104
105 /** 查询列表 */
106 const getList = async () => {
107   loading.value = true
108   try {
3e359e 109     // 所有流程分类数据
H 110     await getCategoryList()
111     // 所有流程定义数据
112     await getProcessDefinitionList()
820397 113
H 114     // 如果 processInstanceId 非空,说明是重新发起
115     if (processInstanceId?.length > 0) {
116       const processInstance = await ProcessInstanceApi.getProcessInstance(processInstanceId)
117       if (!processInstance) {
118         message.error('重新发起流程失败,原因:流程实例不存在')
119         return
120       }
121       const processDefinition = processDefinitionList.value.find(
3e359e 122         (item: any) => item.key == processInstance.processDefinition?.key
820397 123       )
H 124       if (!processDefinition) {
125         message.error('重新发起流程失败,原因:流程定义不存在')
126         return
127       }
128       await handleSelect(processDefinition, processInstance.formVariables)
129     }
130   } finally {
131     loading.value = false
132   }
133 }
134
3e359e 135 /** 获取所有流程分类数据 */
H 136 const getCategoryList = async () => {
137   try {
138     // 流程分类
139     categoryList.value = await CategoryApi.getCategorySimpleList()
140   } finally {
141   }
142 }
143
144 /** 获取所有流程定义数据 */
145 const getProcessDefinitionList = async () => {
146   try {
147     // 流程定义
148     processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
149       suspensionState: 1
150     })
151     // 初始化过滤列表为全部流程定义
152     filteredProcessDefinitionList.value = processDefinitionList.value
153
154     // 在获取完所有数据后,设置第一个有效分类为激活状态
155     if (availableCategories.value.length > 0 && !categoryActive.value?.code) {
156       categoryActive.value = availableCategories.value[0]
157     }
158   } finally {
159   }
160 }
161
162 /** 搜索流程 */
163 const filteredProcessDefinitionList = ref([]) // 用于存储搜索过滤后的流程定义
164 const handleQuery = () => {
165   if (searchName.value.trim()) {
166     // 如果有搜索关键字,进行过滤
167     filteredProcessDefinitionList.value = processDefinitionList.value.filter(
168       (definition: any) => definition.name.toLowerCase().includes(searchName.value.toLowerCase()) // 假设搜索依据是流程定义的名称
169     )
170   } else {
171     // 如果没有搜索关键字,恢复所有数据
172     filteredProcessDefinitionList.value = processDefinitionList.value
173   }
174 }
175
176 /** 流程定义的分组 */
177 const processDefinitionGroup: any = computed(() => {
178   if (!processDefinitionList.value?.length) {
179     return {}
180   }
181
182   const grouped = groupBy(filteredProcessDefinitionList.value, 'category')
183   // 按照 categoryList 的顺序重新组织数据
184   const orderedGroup = {}
185   categoryList.value.forEach((category: any) => {
186     if (grouped[category.code]) {
187       orderedGroup[category.code] = grouped[category.code]
188     }
189   })
190   return orderedGroup
820397 191 })
H 192
3e359e 193 /** 左侧分类切换 */
H 194 const handleCategoryClick = (category: any) => {
195   categoryActive.value = category
196   const categoryRef = proxy.$refs[`category-${category.code}`] // 获取点击分类对应的 DOM 元素
197   if (categoryRef?.length) {
198     const scrollWrapper = proxy.$refs.scrollWrapper // 获取右侧滚动容器
199     const categoryOffsetTop = categoryRef[0].offsetTop
820397 200
3e359e 201     // 滚动到对应位置
H 202     scrollWrapper.scrollTo({ top: categoryOffsetTop, behavior: 'smooth' })
203   }
204 }
205
206 /** 通过分类 code 获取对应的名称 */
207 const getCategoryName = (categoryCode: string) => {
208   return categoryList.value?.find((ctg: any) => ctg.code === categoryCode)?.name
209 }
210
211 // ========== 表单相关 ==========
212 const selectProcessDefinition = ref() // 选择的流程定义
213 const processDefinitionDetailRef = ref()
820397 214
H 215 /** 处理选择流程的按钮操作 **/
3e359e 216 const handleSelect = async (row, formVariables?) => {
820397 217   // 设置选择的流程
H 218   selectProcessDefinition.value = row
3e359e 219   // 初始化流程定义详情
H 220   await nextTick()
221   processDefinitionDetailRef.value?.initProcessInfo(row, formVariables)
222 }
820397 223
3e359e 224 /** 处理滚动事件,和左侧分类联动 */
H 225 const handleScroll = (e: any) => {
226   // 直接使用事件对象获取滚动位置
227   const scrollTop = e.scrollTop
820397 228
3e359e 229   // 获取所有分类区域的位置信息
H 230   const categoryPositions = categoryList.value
231     .map((category: CategoryVO) => {
232       const categoryRef = proxy.$refs[`category-${category.code}`]
233       if (categoryRef?.[0]) {
234         return {
235           code: category.code,
236           offsetTop: categoryRef[0].offsetTop,
237           height: categoryRef[0].offsetHeight
820397 238         }
H 239       }
3e359e 240       return null
H 241     })
242     .filter(Boolean)
243
244   // 查找当前滚动位置对应的分类
245   let currentCategory = categoryPositions[0]
246   for (const position of categoryPositions) {
247     // 为了更好的用户体验,可以添加一个缓冲区域(比如 50px)
248     if (scrollTop >= position.offsetTop - 50) {
249       currentCategory = position
250     } else {
251       break
820397 252     }
3e359e 253   }
H 254
255   // 更新当前 active 的分类
256   if (currentCategory && categoryActive.value.code !== currentCategory.code) {
257     categoryActive.value = categoryList.value.find(
258       (c: CategoryVO) => c.code === currentCategory.code
259     )
820397 260   }
H 261 }
262
3e359e 263 /** 过滤出有流程的分类列表。目的:只展示有流程的分类 */
H 264 const availableCategories = computed(() => {
265   if (!categoryList.value?.length || !processDefinitionGroup.value) {
266     return []
820397 267   }
H 268
3e359e 269   // 获取所有有流程的分类代码
H 270   const availableCategoryCodes = Object.keys(processDefinitionGroup.value)
271
272   // 过滤出有流程的分类
273   return categoryList.value.filter((category: CategoryVO) =>
274     availableCategoryCodes.includes(category.code)
275   )
276 })
820397 277
H 278 /** 初始化 */
279 onMounted(() => {
280   getList()
281 })
282 </script>
3e359e 283
H 284 <style lang="scss" scoped>
285 .process-definition-container::before {
286   content: '';
287   border-left: 1px solid #e6e6e6;
288   position: absolute;
289   left: 20.8%;
290   height: 100%;
291 }
292 :deep() {
293   .definition-item-card {
294     .el-card__body {
295       padding: 14px;
296     }
297   }
298 }
299 </style>