diff --git a/src/api/bpm/definition/index.ts b/src/api/bpm/definition/index.ts index 61352a5..6858e51 100644 --- a/src/api/bpm/definition/index.ts +++ b/src/api/bpm/definition/index.ts @@ -6,6 +6,7 @@ export interface ProcessDefinition { key: string name: string description?: string + icon?: string category: string formType?: number formId?: number diff --git a/src/pages-bpm/processInstance/create/index.vue b/src/pages-bpm/processInstance/create/index.vue index 54ea74c..a64ad6d 100644 --- a/src/pages-bpm/processInstance/create/index.vue +++ b/src/pages-bpm/processInstance/create/index.vue @@ -9,78 +9,75 @@ /> - - - + - - - - {{ item.name }} - - - + + + - - {{ getCategoryName(category as string) }} - + + {{ item.name }} - + + - + {{ getIconText(definition.name) }} - {{ item.name }} + {{ definition.name }} + + 该分类下暂无流程 + - + @@ -91,7 +88,7 @@ import type { Category } from '@/api/bpm/category' import type { ProcessDefinition } from '@/api/bpm/definition' import { onLoad } from '@dcloudio/uni-app' -import { computed, ref } from 'vue' +import { computed, nextTick, ref } from 'vue' import { useToast } from 'wot-design-uni' import { getCategorySimpleList } from '@/api/bpm/category' import { getProcessDefinitionList } from '@/api/bpm/definition' @@ -121,23 +118,29 @@ definePage({ const toast = useToast() const searchName = ref('') -const activeIndex = ref(0) -const scrollIntoView = ref('') +const activeCategory = ref('') const categoryList = ref([]) -const definitionList = ref([]) -const expandedCategories = ref>({}) +const categoryPositions = ref<{ code: string, top: number }[]>([]) // 分类区域位置信息(用于滚动时自动切换 tab) +const scrollIntoView = ref('') +const isTabClicking = ref(false) // 是否正在通过点击 tab 触发滚动(避免滚动事件反向更新 tab) -/** 图标配置 */ -// TODO @芋艿:【流程定义图标】支持显示流程定义的自定义图标 definition.icon -// 对应 vben 第 175-189 行:优先显示 definition.icon,无图标时显示流程名称前两个字 -// TODO @AI:优化下,图标使用 vben 对应的逻辑; -const iconConfig = [ - { icon: 'warning', color: '#D98469' }, - { icon: 'heart', color: '#7BC67C' }, - { icon: 'cart', color: '#4A7FEB' }, - { icon: 'home', color: '#4A7FEB' }, - { icon: 'location', color: '#4A9DEB' }, -] +const definitionList = ref([]) + +/** 根据流程名称获取图标背景色 */ +function getIconColor(name: string): string { + const iconColors = ['#D98469', '#7BC67C', '#4A7FEB', '#9B7FEB', '#4A9DEB'] + // 根据名称 hashcode 取模选择颜色 + let hash = 0 + for (let i = 0; i < name.length; i++) { + hash = (hash * 31 + name.charCodeAt(i)) | 0 + } + return iconColors[Math.abs(hash) % iconColors.length] +} + +/** 获取流程名称的前两个字符作为图标文字 */ +function getIconText(name: string): string { + return name?.slice(0, 2) || '' +} /** 过滤后的流程定义 */ const filteredDefinitions = computed(() => { @@ -159,13 +162,7 @@ const groupedDefinitions = computed>(() => { grouped[item.category] = [] grouped[item.category].push(item) }) - // 按 categoryList 顺序排序 - const ordered: Record = {} - categoryList.value.forEach((cat) => { - if (grouped[cat.code]) - ordered[cat.code] = grouped[cat.code] - }) - return ordered + return grouped }) /** 返回上一页 */ @@ -174,46 +171,63 @@ function handleBack() { } /** 搜索 */ -function handleSearch() { - // 搜索时展开所有分类 - categoryList.value.forEach((cat) => { - expandedCategories.value[cat.code] = true +async function handleSearch() { + // 搜索后重新计算分类位置 + await nextTick() + updateCategoryPositions() +} + +/** Tab 点击 */ +function handleTabClick({ name }: { index: number, name: string }) { + isTabClicking.value = true + // 滚动到对应分类 + scrollIntoView.value = '' + nextTick(() => { + scrollIntoView.value = `category-${name}` + // 300ms 后恢复滚动监听 + setTimeout(() => { + isTabClicking.value = false + }, 300) }) } -/** 切换分类 */ -// TODO @AI:目前有个 bug;滚动到为止后,选中的 category 不会变; -function switchCategory(index: number) { - activeIndex.value = index - const category = categoryList.value[index] - if (category) { - expandedCategories.value[category.code] = true - // 滚动到对应分类 - scrollIntoView.value = '' - setTimeout(() => { - scrollIntoView.value = `category-${category.code}` - }, 50) +/** 滚动事件 - 自动切换 tab */ +function handleScroll(e: { detail: { scrollTop: number } }) { + if (isTabClicking.value || categoryPositions.value.length === 0) { + return + } + // 找到当前滚动位置对应的分类 + const scrollTop = e.detail.scrollTop + for (let i = categoryPositions.value.length - 1; i >= 0; i--) { + if (scrollTop >= categoryPositions.value[i].top - 20) { + if (activeCategory.value !== categoryPositions.value[i].code) { + activeCategory.value = categoryPositions.value[i].code + } + break + } } } -/** 切换分类展开/收起 */ -function toggleCategory(code: string) { - expandedCategories.value[code] = !expandedCategories.value[code] -} - -/** 获取分类名称 */ -function getCategoryName(code: string) { - return categoryList.value.find(item => item.code === code)?.name || code -} - -/** 获取图标名称 */ -function getIconName(index: number) { - return iconConfig[index % iconConfig.length].icon -} - -/** 获取图标颜色 */ -function getIconColor(index: number) { - return iconConfig[index % iconConfig.length].color +/** 更新分类区域位置信息 */ +function updateCategoryPositions() { + const query = uni.createSelectorQuery() + query.selectAll('.category-section').boundingClientRect() + query.exec((res) => { + if (res && res[0]) { + const positions: { code: string, top: number }[] = [] + const firstTop = res[0][0]?.top || 0 + res[0].forEach((item: { top: number, dataset?: { category?: string } }, index: number) => { + const cat = categoryList.value[index] + if (cat) { + positions.push({ + code: cat.code, + top: item.top - firstTop, + }) + } + }) + categoryPositions.value = positions + } + }) } /** 选择流程定义 */ @@ -231,10 +245,6 @@ function handleSelect(item: ProcessDefinition) { /** 加载分类列表 */ async function loadCategoryList() { categoryList.value = await getCategorySimpleList() - // 默认展开所有分类 - categoryList.value.forEach((cat) => { - expandedCategories.value[cat.code] = true - }) } /** 加载流程定义列表 */ @@ -245,5 +255,17 @@ async function loadDefinitionList() { /** 初始化 */ onLoad(async () => { await Promise.all([loadCategoryList(), loadDefinitionList()]) + // 默认选中第一个分类 + if (categoryList.value.length > 0) { + activeCategory.value = categoryList.value[0].code + } + // 等待 DOM 渲染后计算分类位置 + await nextTick() + setTimeout(() => { + updateCategoryPositions() + }, 100) }) + +