From 3e359e4550d018035406a97c190b98f83839ba7f Mon Sep 17 00:00:00 2001
From: houzhongjian <houzhongyi@126.com>
Date: 星期五, 13 十二月 2024 10:41:05 +0800
Subject: [PATCH] 1、工作流重做更新 2、@import修改为@use以应对sass升级弃用@import 3、进入系统后自动redirect到home页

---
 src/views/bpm/processInstance/create/index.vue |  423 +++++++++++++++++++++++++++++-----------------------
 1 files changed, 233 insertions(+), 190 deletions(-)

diff --git a/src/views/bpm/processInstance/create/index.vue b/src/views/bpm/processInstance/create/index.vue
index 49da30a..284cbdb 100644
--- a/src/views/bpm/processInstance/create/index.vue
+++ b/src/views/bpm/processInstance/create/index.vue
@@ -1,132 +1,115 @@
 <template>
-
   <!-- 第一步,通过流程定义的列表,选择对应的流程 -->
-  <ContentWrap v-if="!selectProcessDefinition" v-loading="loading">
-    <el-tabs tab-position="left" v-model="categoryActive">
-      <el-tab-pane
-        :label="category.name"
-        :name="category.code"
-        :key="category.code"
-        v-for="category in categoryList"
-      >
-        <el-row :gutter="20">
-          <el-col
-            :lg="6"
-            :sm="12"
-            :xs="24"
-            v-for="definition in categoryProcessDefinitionList"
-            :key="definition.id"
-          >
-            <el-card
-              shadow="hover"
-              class="mb-20px cursor-pointer"
-              @click="handleSelect(definition)"
+  <template v-if="!selectProcessDefinition">
+    <el-input
+      v-model="searchName"
+      class="!w-50% mb-15px"
+      placeholder="请输入流程名称"
+      clearable
+      @input="handleQuery"
+      @clear="handleQuery"
+    >
+      <template #prefix>
+        <Icon icon="ep:search" />
+      </template>
+    </el-input>
+    <ContentWrap
+      :class="{ 'process-definition-container': filteredProcessDefinitionList?.length }"
+      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 availableCategories"
+              :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)"
             >
-              <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-col>
-        </el-row>
-      </el-tab-pane>
-    </el-tabs>
-  </ContentWrap>
+              {{ category.name }}
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="19">
+          <el-scrollbar ref="scrollWrapper" height="700" @scroll="handleScroll">
+            <div
+              class="mb-20px pl-10px"
+              v-for="(definitions, categoryCode) in processDefinitionGroup"
+              :key="categoryCode"
+              :ref="`category-${categoryCode}`"
+            >
+              <h3 class="text-18px font-bold mb-10px mt-5px">
+                {{ getCategoryName(categoryCode as any) }}
+              </h3>
+              <div class="grid grid-cols-3 gap3">
+                <el-tooltip
+                  v-for="definition in definitions"
+                  :key="definition.id"
+                  :content="definition.description"
+                  :disabled="!definition.description || definition.description.trim().length === 0"
+                  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>
+          </el-scrollbar>
+        </el-col>
+      </el-row>
+      <el-empty class="!py-200px" :image-size="200" description="没有找到搜索结果" v-else />
+    </ContentWrap>
+  </template>
 
   <!-- 第二步,填写表单,进行流程的提交 -->
-  <ContentWrap v-else>
-    <el-card class="box-card">
-      <div class="clearfix">
-        <span class="el-icon-document">申请信息【{{ selectProcessDefinition.name }}】</span>
-        <el-button style="float: right" type="primary" @click="selectProcessDefinition = undefined">
-          <Icon icon="ep:delete" /> 选择其它流程
-        </el-button>
-      </div>
-      <el-col :span="16" :offset="6" style="margin-top: 20px">
-        <form-create
-          :rule="detailForm.rule"
-          v-model:api="fApi"
-          v-model="detailForm.value"
-          :option="detailForm.option"
-          @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-card>
-    <!-- 流程图预览 -->
-    <ProcessInstanceBpmnViewer :bpmn-xml="bpmnXML as any" />
-  </ContentWrap>
+  <ProcessDefinitionDetail
+    v-else
+    ref="processDefinitionDetailRef"
+    :selectProcessDefinition="selectProcessDefinition"
+    @cancel="selectProcessDefinition = undefined"
+  />
 </template>
+
 <script lang="ts" setup>
 import * as DefinitionApi from '@/api/bpm/definition'
 import * as ProcessInstanceApi from '@/api/bpm/processInstance'
-import { setConfAndFields2 } from '@/utils/formCreate'
-import type { ApiAttrs } from '@form-create/element-ui/types/config'
-import ProcessInstanceBpmnViewer from '../detail/ProcessInstanceBpmnViewer.vue'
-import { CategoryApi } from '@/api/bpm/category'
-import { useTagsViewStore } from '@/store/modules/tagsView'
-import * as UserApi from '@/api/system/user'
+import { CategoryApi, CategoryVO } from '@/api/bpm/category'
+import ProcessDefinitionDetail from './ProcessDefinitionDetail.vue'
+import { groupBy } from 'lodash-es'
 
 defineOptions({ name: 'BpmProcessInstanceCreate' })
 
+const { proxy } = getCurrentInstance() as any
 const route = useRoute() // 路由
-const { push, currentRoute } = useRouter() // 路由
 const message = useMessage() // 消息
-const { delView } = useTagsViewStore() // 视图操作
 
-const processInstanceId = route.query.processInstanceId
+const searchName = ref('') // 当前搜索关键字
+const processInstanceId: any = route.query.processInstanceId // 流程实例编号。场景:重新发起时
 const loading = ref(true) // 加载中
-const categoryList = ref([]) // 分类的列表
-const categoryActive = ref('') // 选中的分类
+const categoryList: any = ref([]) // 分类的列表
+const categoryActive: any = ref({}) // 选中的分类
 const processDefinitionList = ref([]) // 流程定义的列表
 
 /** 查询列表 */
 const getList = async () => {
   loading.value = true
   try {
-    // 流程分类
-    categoryList.value = await CategoryApi.getCategorySimpleList()
-    if (categoryList.value.length > 0) {
-      categoryActive.value = categoryList.value[0].code
-    }
-    // 流程定义
-    processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
-      suspensionState: 1
-    })
+    // 所有流程分类数据
+    await getCategoryList()
+    // 所有流程定义数据
+    await getProcessDefinitionList()
 
     // 如果 processInstanceId 非空,说明是重新发起
     if (processInstanceId?.length > 0) {
@@ -136,7 +119,7 @@
         return
       }
       const processDefinition = processDefinitionList.value.find(
-        (item) => item.key == processInstance.processDefinition?.key
+        (item: any) => item.key == processInstance.processDefinition?.key
       )
       if (!processDefinition) {
         message.error('重新发起流程失败,原因:流程定义不存在')
@@ -149,108 +132,168 @@
   }
 }
 
-/** 选中分类对应的流程定义列表 */
-const categoryProcessDefinitionList = computed(() => {
-  return processDefinitionList.value.filter((item) => item.category == categoryActive.value)
+/** 获取所有流程分类数据 */
+const getCategoryList = async () => {
+  try {
+    // 流程分类
+    categoryList.value = await CategoryApi.getCategorySimpleList()
+  } finally {
+  }
+}
+
+/** 获取所有流程定义数据 */
+const getProcessDefinitionList = async () => {
+  try {
+    // 流程定义
+    processDefinitionList.value = await DefinitionApi.getProcessDefinitionList({
+      suspensionState: 1
+    })
+    // 初始化过滤列表为全部流程定义
+    filteredProcessDefinitionList.value = processDefinitionList.value
+
+    // 在获取完所有数据后,设置第一个有效分类为激活状态
+    if (availableCategories.value.length > 0 && !categoryActive.value?.code) {
+      categoryActive.value = availableCategories.value[0]
+    }
+  } finally {
+  }
+}
+
+/** 搜索流程 */
+const filteredProcessDefinitionList = ref([]) // 用于存储搜索过滤后的流程定义
+const handleQuery = () => {
+  if (searchName.value.trim()) {
+    // 如果有搜索关键字,进行过滤
+    filteredProcessDefinitionList.value = processDefinitionList.value.filter(
+      (definition: any) => definition.name.toLowerCase().includes(searchName.value.toLowerCase()) // 假设搜索依据是流程定义的名称
+    )
+  } else {
+    // 如果没有搜索关键字,恢复所有数据
+    filteredProcessDefinitionList.value = processDefinitionList.value
+  }
+}
+
+/** 流程定义的分组 */
+const processDefinitionGroup: any = computed(() => {
+  if (!processDefinitionList.value?.length) {
+    return {}
+  }
+
+  const grouped = groupBy(filteredProcessDefinitionList.value, 'category')
+  // 按照 categoryList 的顺序重新组织数据
+  const orderedGroup = {}
+  categoryList.value.forEach((category: any) => {
+    if (grouped[category.code]) {
+      orderedGroup[category.code] = grouped[category.code]
+    }
+  })
+  return orderedGroup
 })
 
-// ========== 表单相关 ==========
-const fApi = ref<ApiAttrs>()
-const detailForm = ref({
-  rule: [],
-  option: {},
-  value: {}
-}) // 流程表单详情
-const selectProcessDefinition = ref() // 选择的流程定义
+/** 左侧分类切换 */
+const handleCategoryClick = (category: any) => {
+  categoryActive.value = category
+  const categoryRef = proxy.$refs[`category-${category.code}`] // 获取点击分类对应的 DOM 元素
+  if (categoryRef?.length) {
+    const scrollWrapper = proxy.$refs.scrollWrapper // 获取右侧滚动容器
+    const categoryOffsetTop = categoryRef[0].offsetTop
 
-// 指定审批人
-const bpmnXML = ref(null) // BPMN 数据
-const startUserSelectTasks = ref([]) // 发起人需要选择审批人的用户任务列表
-const startUserSelectAssignees = ref({}) // 发起人选择审批人的数据
-const startUserSelectAssigneesFormRef = ref() // 发起人选择审批人的表单 Ref
-const startUserSelectAssigneesFormRules = ref({}) // 发起人选择审批人的表单 Rules
-const userList = ref<any[]>([]) // 用户列表
+    // 滚动到对应位置
+    scrollWrapper.scrollTo({ top: categoryOffsetTop, behavior: 'smooth' })
+  }
+}
+
+/** 通过分类 code 获取对应的名称 */
+const getCategoryName = (categoryCode: string) => {
+  return categoryList.value?.find((ctg: any) => ctg.code === categoryCode)?.name
+}
+
+// ========== 表单相关 ==========
+const selectProcessDefinition = ref() // 选择的流程定义
+const processDefinitionDetailRef = ref()
 
 /** 处理选择流程的按钮操作 **/
-const handleSelect = async (row, formVariables) => {
+const handleSelect = async (row, formVariables?) => {
   // 设置选择的流程
   selectProcessDefinition.value = row
+  // 初始化流程定义详情
+  await nextTick()
+  processDefinitionDetailRef.value?.initProcessInfo(row, formVariables)
+}
 
-  // 重置指定审批人
-  startUserSelectTasks.value = []
-  startUserSelectAssignees.value = {}
-  startUserSelectAssigneesFormRules.value = {}
+/** 处理滚动事件,和左侧分类联动 */
+const handleScroll = (e: any) => {
+  // 直接使用事件对象获取滚动位置
+  const scrollTop = e.scrollTop
 
-  // 情况一:流程表单
-  if (row.formType == 10) {
-    // 设置表单
-    setConfAndFields2(detailForm, row.formConf, row.formFields, formVariables)
-    // 加载流程图
-    const processDefinitionDetail = await DefinitionApi.getProcessDefinition(row.id)
-    if (processDefinitionDetail) {
-      bpmnXML.value = processDefinitionDetail.bpmnXml
-      startUserSelectTasks.value = processDefinitionDetail.startUserSelectTasks
-
-      // 设置指定审批人
-      if (startUserSelectTasks.value?.length > 0) {
-        detailForm.value.rule.push({
-          type: 'startUserSelect',
-          props: {
-            title: '指定审批人'
-          }
-        })
-        // 设置校验规则
-        for (const userTask of startUserSelectTasks.value) {
-          startUserSelectAssignees.value[userTask.id] = []
-          startUserSelectAssigneesFormRules.value[userTask.id] = [
-            { required: true, message: '请选择审批人', trigger: 'blur' }
-          ]
+  // 获取所有分类区域的位置信息
+  const categoryPositions = categoryList.value
+    .map((category: CategoryVO) => {
+      const categoryRef = proxy.$refs[`category-${category.code}`]
+      if (categoryRef?.[0]) {
+        return {
+          code: category.code,
+          offsetTop: categoryRef[0].offsetTop,
+          height: categoryRef[0].offsetHeight
         }
-        // 加载用户列表
-        userList.value = await UserApi.getSimpleUserList()
       }
+      return null
+    })
+    .filter(Boolean)
+
+  // 查找当前滚动位置对应的分类
+  let currentCategory = categoryPositions[0]
+  for (const position of categoryPositions) {
+    // 为了更好的用户体验,可以添加一个缓冲区域(比如 50px)
+    if (scrollTop >= position.offsetTop - 50) {
+      currentCategory = position
+    } else {
+      break
     }
-    // 情况二:业务表单
-  } else if (row.formCustomCreatePath) {
-    await push({
-      path: row.formCustomCreatePath
-    })
-    // 这里暂时无需加载流程图,因为跳出到另外个 Tab;
+  }
+
+  // 更新当前 active 的分类
+  if (currentCategory && categoryActive.value.code !== currentCategory.code) {
+    categoryActive.value = categoryList.value.find(
+      (c: CategoryVO) => c.code === currentCategory.code
+    )
   }
 }
 
-/** 提交按钮 */
-const submitForm = async (formData) => {
-  if (!fApi.value || !selectProcessDefinition.value) {
-    return
-  }
-  // 如果有指定审批人,需要校验
-  if (startUserSelectTasks.value?.length > 0) {
-    await startUserSelectAssigneesFormRef.value.validate()
+/** 过滤出有流程的分类列表。目的:只展示有流程的分类 */
+const availableCategories = computed(() => {
+  if (!categoryList.value?.length || !processDefinitionGroup.value) {
+    return []
   }
 
-  // 提交请求
-  fApi.value.btn.loading(true)
-  try {
-    await ProcessInstanceApi.createProcessInstance({
-      processDefinitionId: selectProcessDefinition.value.id,
-      variables: formData,
-      startUserSelectAssignees: startUserSelectAssignees.value
-    })
-    // 提示
-    message.success('发起流程成功')
-    // 跳转回去
-    delView(unref(currentRoute))
-    await push({
-      name: 'BpmProcessInstanceMy'
-    })
-  } finally {
-    fApi.value.btn.loading(false)
-  }
-}
+  // 获取所有有流程的分类代码
+  const availableCategoryCodes = Object.keys(processDefinitionGroup.value)
+
+  // 过滤出有流程的分类
+  return categoryList.value.filter((category: CategoryVO) =>
+    availableCategoryCodes.includes(category.code)
+  )
+})
 
 /** 初始化 */
 onMounted(() => {
   getList()
 })
 </script>
+
+<style lang="scss" scoped>
+.process-definition-container::before {
+  content: '';
+  border-left: 1px solid #e6e6e6;
+  position: absolute;
+  left: 20.8%;
+  height: 100%;
+}
+:deep() {
+  .definition-item-card {
+    .el-card__body {
+      padding: 14px;
+    }
+  }
+}
+</style>

--
Gitblit v1.9.3