Files
aiot-uniapp/src/pages-bpm/processInstance/create/index.vue

233 lines
6.6 KiB
Vue

<template>
<view class="page-container">
<!-- 顶部导航栏 -->
<wd-navbar
title="发起申请"
left-arrow placeholder safe-area-inset-top fixed
@click-left="handleBack"
/>
<!-- 搜索框 -->
<view class="bg-white p-24rpx">
<wd-search
v-model="searchName"
placeholder="请输入流程名称"
placeholder-left
hide-cancel
@search="handleSearch"
@clear="handleSearch"
/>
</view>
<!-- 分类标签 -->
<view class="flex overflow-x-auto bg-white px-16rpx">
<view
v-for="(item, index) in categoryList"
:key="item.id"
class="relative whitespace-nowrap px-24rpx py-20rpx text-28rpx"
:class="activeIndex === index ? 'font-bold text-[#1890ff]' : 'text-[#666]'"
@click="switchCategory(index)"
>
{{ item.name }}
<view
v-if="activeIndex === index"
class="absolute bottom-0 left-24rpx right-24rpx h-4rpx bg-[#1890ff]"
/>
</view>
</view>
<!-- 流程定义列表 -->
<scroll-view
scroll-y
class="h-[calc(100vh-280rpx)]"
:scroll-into-view="scrollIntoView"
scroll-with-animation
>
<view
v-for="(definitions, category) in groupedDefinitions"
:id="`category-${category}`"
:key="category"
class="mx-24rpx mt-24rpx"
>
<!-- 分类标题 -->
<view class="mb-16rpx flex items-center justify-between">
<text class="text-28rpx text-[#333] font-bold">{{ getCategoryName(category as string) }}</text>
<wd-icon
:name="expandedCategories[category as string] ? 'arrow-up' : 'arrow-down'"
size="32rpx"
@click="toggleCategory(category as string)"
/>
</view>
<!-- 流程列表 -->
<view v-if="expandedCategories[category as string]" class="overflow-hidden rounded-16rpx bg-white">
<view
v-for="(item, index) in definitions"
:key="item.id"
class="flex items-center border-b border-[#f5f5f5] p-24rpx last:border-b-0"
@click="handleSelect(item)"
>
<view
class="mr-16rpx h-64rpx w-64rpx flex items-center justify-center rounded-12rpx"
:style="{ backgroundColor: getIconColor(index) }"
>
<wd-icon :name="getIconName(index)" size="40rpx" color="#fff" />
</view>
<text class="text-28rpx text-[#333]">{{ item.name }}</text>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="Object.keys(groupedDefinitions).length === 0" class="py-100rpx">
<wd-status-tip image="content" tip="暂无可发起的流程" />
</view>
</scroll-view>
</view>
</template>
<script lang="ts" setup>
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 { useToast } from 'wot-design-uni'
import { getCategorySimpleList } from '@/api/bpm/category'
import { getProcessDefinitionList } from '@/api/bpm/definition'
import { navigateBackPlus } from '@/utils'
definePage({
style: {
navigationBarTitleText: '',
navigationStyle: 'custom',
},
})
const toast = useToast()
const searchName = ref('')
const activeIndex = ref(0)
const scrollIntoView = ref('')
const categoryList = ref<Category[]>([])
const definitionList = ref<ProcessDefinition[]>([])
const expandedCategories = ref<Record<string, boolean>>({})
/** 图标配置 */
// TODO @芋艿:这个研发,是否需要弄?!
const iconConfig = [
{ icon: 'warning', color: '#D98469' },
{ icon: 'heart', color: '#7BC67C' },
{ icon: 'cart', color: '#4A7FEB' },
{ icon: 'home', color: '#4A7FEB' },
{ icon: 'location', color: '#4A9DEB' },
]
/** 过滤后的流程定义 */
const filteredDefinitions = computed(() => {
if (!searchName.value.trim()) {
return definitionList.value
}
return definitionList.value.filter(item =>
item.name.toLowerCase().includes(searchName.value.toLowerCase()),
)
})
/** 按分类分组的流程定义 */
const groupedDefinitions = computed<Record<string, ProcessDefinition[]>>(() => {
const grouped: Record<string, ProcessDefinition[]> = {}
filteredDefinitions.value.forEach((item) => {
if (!item.category)
return
if (!grouped[item.category])
grouped[item.category] = []
grouped[item.category].push(item)
})
// 按 categoryList 顺序排序
const ordered: Record<string, ProcessDefinition[]> = {}
categoryList.value.forEach((cat) => {
if (grouped[cat.code])
ordered[cat.code] = grouped[cat.code]
})
return ordered
})
/** 返回上一页 */
function handleBack() {
navigateBackPlus('/pages/bpm/index')
}
/** 搜索 */
function handleSearch() {
// 搜索时展开所有分类
categoryList.value.forEach((cat) => {
expandedCategories.value[cat.code] = true
})
}
/** 切换分类 */
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)
}
}
/** 切换分类展开/收起 */
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 handleSelect(item: ProcessDefinition) {
// TODO @芋艿:跳转到流程表单页面
toast.show(`选择了: ${item.name}`)
}
/** 加载分类列表 */
async function loadCategoryList() {
try {
categoryList.value = await getCategorySimpleList()
// 默认展开所有分类
categoryList.value.forEach((cat) => {
expandedCategories.value[cat.code] = true
})
} catch (error) {
console.error('[create] 加载分类失败:', error)
}
}
/** 加载流程定义列表 */
async function loadDefinitionList() {
try {
definitionList.value = await getProcessDefinitionList({ suspensionState: 1 })
} catch (error) {
console.error('[create] 加载流程定义失败:', error)
}
}
/** 初始化 */
onLoad(async () => {
await Promise.all([loadCategoryList(), loadDefinitionList()])
})
</script>