feat:优化 bpm 相关代码,重点处理 back 逻辑,search 表单

This commit is contained in:
YunaiV
2025-12-15 20:40:37 +08:00
parent b15f2327db
commit 8807caa3eb
12 changed files with 412 additions and 348 deletions

View File

@@ -92,6 +92,7 @@ import { onLoad } from '@dcloudio/uni-app'
import { computed, ref } from 'vue'
import { getCategorySimpleList } from '@/api/bpm/category'
import { getProcessDefinitionList } from '@/api/bpm/definition'
import { navigateBackPlus } from '@/utils';
definePage({
style: {
@@ -148,7 +149,7 @@ const groupedDefinitions = computed<Record<string, ProcessDefinition[]>>(() => {
/** 返回上一页 */
function handleBack() {
uni.navigateBack()
navigateBackPlus('/pages/bpm/index')
}
/** 搜索 */

View File

@@ -43,9 +43,14 @@
</template>
<script lang="ts" setup>
import { onLoad } from '@dcloudio/uni-app'
import { computed, reactive, ref } from 'vue'
import { approveTask, rejectTask } from '@/api/bpm/task'
import { navigateBackPlus } from '@/utils'
const props = defineProps<{
id?: string | any
pass?: string | any
}>()
definePage({
style: {
@@ -54,19 +59,27 @@ definePage({
},
})
const taskId = ref('')
const pass = ref(true) // true: 同意, false: 拒绝
const taskId = computed(() => props.id || '')
const isPass = computed(() => props.pass !== 'false') // true: 同意, false: 拒绝
const submitting = ref(false)
const formData = reactive({
reason: '',
})
/** 是否为同意操作 */
const isApprove = computed(() => pass.value)
const isApprove = computed(() => isPass.value)
/** 返回上一页 */
function handleBack() {
uni.navigateBack()
navigateBackPlus(`/pages-bpm/processInstance/detail/index?id=${taskId.value}`)
}
/** 初始化校验 */
if (!props.id) {
uni.showToast({
title: '参数错误',
icon: 'none',
})
}
/** 校验表单 */
@@ -92,7 +105,7 @@ async function handleSubmit() {
try {
const api = isApprove.value ? approveTask : rejectTask
const result = await api({
id: taskId.value,
id: taskId.value as string,
reason: formData.reason,
})
if (result) {
@@ -104,25 +117,10 @@ async function handleSubmit() {
uni.navigateBack()
}, 1500)
}
}
catch (error) {
} catch (error) {
console.error('[audit] 审批失败:', error)
}
finally {
} finally {
submitting.value = false
}
}
/** 初始化 */
onLoad((options) => {
if (!options?.id) {
uni.showToast({
title: '参数错误',
icon: 'none',
})
return
}
taskId.value = options.id
pass.value = options.pass !== 'false' // 默认为同意
})
</script>

View File

@@ -117,6 +117,7 @@ import { getProcessInstance } from '@/api/bpm/processInstance'
import { getTaskListByProcessInstanceId } from '@/api/bpm/task'
import { useUserStore } from '@/store'
import { formatDateTime, formatPast } from '@/utils/date'
import { navigateBackPlus } from '@/utils';
definePage({
style: {
@@ -135,8 +136,9 @@ const orderAsc = ref(true)
const runningTask = computed(() => {
return tasks.value.find((task) => {
// 待处理状态
if (task.status !== 1 && task.status !== 6)
if (task.status !== 1 && task.status !== 6) {
return false
}
// 当前用户是处理人
return task.assigneeUser?.id === userStore.userInfo?.id
})
@@ -146,12 +148,15 @@ const runningTask = computed(() => {
const sortedTasks = computed(() => {
const list = [...tasks.value].filter(t => t.status !== 4) // 过滤已取消
list.sort((a, b) => {
if (a.endTime && b.endTime)
if (a.endTime && b.endTime) {
return orderAsc.value ? a.endTime - b.endTime : b.endTime - a.endTime
if (a.endTime)
}
if (a.endTime) {
return orderAsc.value ? -1 : 1
if (b.endTime)
}
if (b.endTime) {
return orderAsc.value ? 1 : -1
}
return orderAsc.value ? a.createTime - b.createTime : b.createTime - a.createTime
})
return list
@@ -159,7 +164,7 @@ const sortedTasks = computed(() => {
/** 返回上一页 */
function handleBack() {
uni.navigateBack()
navigateBackPlus('/pages/bpm/index')
}
/** 切换排序 */
@@ -184,54 +189,68 @@ function getStatusText(status?: number) {
/** 获取状态标签类型 */
function getStatusType(status?: number): 'default' | 'primary' | 'success' | 'warning' | 'danger' {
if ([1, 6, 7].includes(status ?? 0))
if ([1, 6, 7].includes(status ?? 0)) {
return 'primary'
if (status === 2)
}
if (status === 2) {
return 'success'
if (status === 3)
}
if (status === 3) {
return 'danger'
if (status === 4 || status === 5)
}
if (status === 4 || status === 5) {
return 'warning'
}
return 'default'
}
/** 获取任务圆点样式 */
function getTaskDotClass(task: Task) {
if ([1, 6, 7].includes(task.status))
if ([1, 6, 7].includes(task.status)) {
return 'bg-[#1890ff]'
if (task.status === 2)
}
if (task.status === 2) {
return 'bg-[#52c41a]'
if (task.status === 3)
}
if (task.status === 3) {
return 'bg-[#ff4d4f]'
if (task.status === 5)
}
if (task.status === 5) {
return 'bg-[#faad14]'
}
return 'bg-[#d9d9d9]'
}
/** 获取状态文本样式 */
function getStatusTextClass(status: number) {
if ([1, 6, 7].includes(status))
if ([1, 6, 7].includes(status)) {
return 'text-[#1890ff]'
if (status === 2)
}
if (status === 2) {
return 'text-[#52c41a]'
if (status === 3)
}
if (status === 3) {
return 'text-[#ff4d4f]'
if (status === 5)
}
if (status === 5) {
return 'text-[#faad14]'
}
return 'text-[#999]'
}
/** 同意 */
function handleApprove() {
if (!runningTask.value)
if (!runningTask.value) {
return
}
uni.navigateTo({ url: `/pages-bpm/processInstance/detail/audit/index?id=${runningTask.value.id}&pass=true` })
}
/** 拒绝 */
function handleReject() {
if (!runningTask.value)
if (!runningTask.value) {
return
}
uni.navigateTo({ url: `/pages-bpm/processInstance/detail/audit/index?id=${runningTask.value.id}&pass=false` })
}

View File

@@ -1,54 +1,55 @@
<template>
<view class="bpm-list">
<view
v-for="item in list"
:key="item.id"
class="bpm-card"
@click="handleDetail(item)"
>
<view class="bpm-card-content">
<view class="bpm-card-header">
<view class="bpm-card-title">
{{ item.processInstanceName }}
</view>
<wd-tag type="primary" plain>
查看详情
</wd-tag>
</view>
<view v-if="item.summary?.length" class="bpm-summary">
<view v-for="(s, idx) in item.summary" :key="idx" class="bpm-summary-item">
<text class="text-[#999]">{{ s.key }}</text>
<text>{{ s.value }}</text>
</view>
</view>
<view class="bpm-card-info">
<view class="bpm-user">
<view class="bpm-avatar">
{{ item.startUser.nickname?.[0] || '?' }}
</view>
<text class="bpm-nickname">{{ item.startUser.nickname }}</text>
</view>
<text class="bpm-time">{{ formatDateTime(item.createTime) }}</text>
</view>
</view>
</view>
<view v-if="loadMoreState !== 'loading' && list.length === 0" class="bpm-empty">
<wd-status-tip image="content" tip="暂无抄送任务" />
</view>
<wd-loadmore
v-if="list.length > 0"
:state="loadMoreState"
@reload="loadMore"
/>
<!-- 搜索弹窗 -->
<view>
<!-- 搜索组件 -->
<CopySearchForm
v-model="searchPopupVisible"
:search-params="queryParams"
@search="handleSearch"
@reset="handleReset"
/>
<view class="bpm-list">
<view
v-for="item in list"
:key="item.id"
class="bpm-card"
@click="handleDetail(item)"
>
<view class="bpm-card-content">
<view class="bpm-card-header">
<view class="bpm-card-title">
{{ item.processInstanceName }}
</view>
<wd-tag type="primary" plain>
查看详情
</wd-tag>
</view>
<view v-if="item.summary?.length" class="bpm-summary">
<view v-for="(s, idx) in item.summary" :key="idx" class="bpm-summary-item">
<text class="text-[#999]">{{ s.key }}</text>
<text>{{ s.value }}</text>
</view>
</view>
<view class="bpm-card-info">
<view class="bpm-user">
<view class="bpm-avatar">
{{ item.startUser.nickname?.[0] || '?' }}
</view>
<text class="bpm-nickname">{{ item.startUser.nickname }}</text>
</view>
<text class="bpm-time">{{ formatDateTime(item.createTime) }}</text>
</view>
</view>
</view>
<view v-if="loadMoreState !== 'loading' && list.length === 0" class="bpm-empty">
<wd-status-tip image="content" tip="暂无抄送任务" />
</view>
<wd-loadmore
v-if="list.length > 0"
:state="loadMoreState"
@reload="loadMore"
/>
</view>
</view>
</template>
@@ -64,17 +65,13 @@ import CopySearchForm from './copy-search-form.vue'
import './index.scss'
const props = defineProps<{
searchVisible?: boolean
}>()
const emit = defineEmits<{
'update:searchVisible': [value: boolean]
active?: boolean
}>()
const total = ref(0)
const list = ref<ProcessInstanceCopy[]>([])
const loadMoreState = ref<LoadMoreState>('loading')
const searchPopupVisible = ref(false)
const isFirstLoad = ref(true)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@@ -133,16 +130,18 @@ onReachBottom(() => {
loadMore()
})
watch(() => props.searchVisible, (val) => {
searchPopupVisible.value = val ?? false
})
watch(searchPopupVisible, (val) => {
emit('update:searchVisible', val)
/** 监听激活状态,刷新数据 */
watch(() => props.active, (val) => {
if (val && !isFirstLoad.value) {
queryParams.pageNo = 1
list.value = []
getList()
}
})
/** 初始化 */
onMounted(() => {
getList()
isFirstLoad.value = false
})
</script>

View File

@@ -1,4 +1,13 @@
<template>
<!-- 搜索框入口 -->
<wd-search
:placeholder="searchPlaceholder"
:hide-cancel="true"
disabled
@click="visible = true"
/>
<!-- 搜索弹窗 -->
<wd-popup
v-model="visible"
position="top"
@@ -93,19 +102,26 @@ export interface CopySearchFormData {
}
const props = defineProps<{
modelValue: boolean
searchParams?: Partial<CopySearchFormData>
}>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
'search': [data: CopySearchFormData]
'reset': []
search: [data: CopySearchFormData]
reset: []
}>()
const visible = computed({
get: () => props.modelValue,
set: (val: boolean) => emit('update:modelValue', val),
const visible = ref(false)
/** 搜索条件 placeholder 拼接 */
const searchPlaceholder = computed(() => {
const conditions: string[] = []
if (props.searchParams?.processInstanceName) {
conditions.push(`名称:${props.searchParams.processInstanceName}`)
}
if (props.searchParams?.createTime?.[0] && props.searchParams?.createTime?.[1]) {
conditions.push(`时间:${formatDate(props.searchParams.createTime[0])}~${formatDate(props.searchParams.createTime[1])}`)
}
return conditions.length > 0 ? conditions.join(' | ') : '搜索抄送任务'
})
const formData = reactive<CopySearchFormData>({
@@ -141,7 +157,7 @@ function handleEndCancel() {
}
/** 监听弹窗打开,同步外部参数 */
watch(() => props.modelValue, (val) => {
watch(visible, (val) => {
if (val && props.searchParams) {
formData.processInstanceName = props.searchParams.processInstanceName
formData.createTime = props.searchParams.createTime ?? [undefined, undefined]

View File

@@ -1,52 +1,53 @@
<template>
<view class="bpm-list">
<view
v-for="item in list"
:key="item.id"
class="bpm-card"
@click="handleDetail(item)"
>
<view class="bpm-card-content">
<view class="bpm-card-header">
<view class="bpm-card-title">
{{ item.processInstance?.name }}
</view>
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="item.status" />
</view>
<view v-if="item.processInstance?.summary?.length" class="bpm-summary">
<view v-for="(s, idx) in item.processInstance.summary" :key="idx" class="bpm-summary-item">
<text class="text-[#999]">{{ s.key }}</text>
<text>{{ s.value }}</text>
</view>
</view>
<view class="bpm-card-info">
<view class="bpm-user">
<view class="bpm-avatar">
{{ item.processInstance?.startUser?.nickname?.[0] || '?' }}
</view>
<text class="bpm-nickname">{{ item.processInstance?.startUser?.nickname }}</text>
</view>
<text class="bpm-time">{{ formatDateTime(item.createTime) }}</text>
</view>
</view>
</view>
<view v-if="loadMoreState !== 'loading' && list.length === 0" class="bpm-empty">
<wd-status-tip image="content" tip="暂无已办任务" />
</view>
<wd-loadmore
v-if="list.length > 0"
:state="loadMoreState"
@reload="loadMore"
/>
<!-- 搜索弹窗 -->
<view>
<!-- 搜索组件 -->
<DoneSearchForm
v-model="searchPopupVisible"
:search-params="queryParams"
@search="handleSearch"
@reset="handleReset"
/>
<view class="bpm-list">
<view
v-for="item in list"
:key="item.id"
class="bpm-card"
@click="handleDetail(item)"
>
<view class="bpm-card-content">
<view class="bpm-card-header">
<view class="bpm-card-title">
{{ item.processInstance?.name }}
</view>
<dict-tag :type="DICT_TYPE.BPM_TASK_STATUS" :value="item.status" />
</view>
<view v-if="item.processInstance?.summary?.length" class="bpm-summary">
<view v-for="(s, idx) in item.processInstance.summary" :key="idx" class="bpm-summary-item">
<text class="text-[#999]">{{ s.key }}</text>
<text>{{ s.value }}</text>
</view>
</view>
<view class="bpm-card-info">
<view class="bpm-user">
<view class="bpm-avatar">
{{ item.processInstance?.startUser?.nickname?.[0] || '?' }}
</view>
<text class="bpm-nickname">{{ item.processInstance?.startUser?.nickname }}</text>
</view>
<text class="bpm-time">{{ formatDateTime(item.createTime) }}</text>
</view>
</view>
</view>
<view v-if="loadMoreState !== 'loading' && list.length === 0" class="bpm-empty">
<wd-status-tip image="content" tip="暂无已办任务" />
</view>
<wd-loadmore
v-if="list.length > 0"
:state="loadMoreState"
@reload="loadMore"
/>
</view>
</view>
</template>
@@ -63,17 +64,13 @@ import DoneSearchForm from './done-search-form.vue'
import './index.scss'
const props = defineProps<{
searchVisible?: boolean
}>()
const emit = defineEmits<{
'update:searchVisible': [value: boolean]
active?: boolean
}>()
const total = ref(0)
const list = ref<Task[]>([])
const loadMoreState = ref<LoadMoreState>('loading')
const searchPopupVisible = ref(false)
const isFirstLoad = ref(true)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@@ -132,16 +129,18 @@ onReachBottom(() => {
loadMore()
})
watch(() => props.searchVisible, (val) => {
searchPopupVisible.value = val ?? false
})
watch(searchPopupVisible, (val) => {
emit('update:searchVisible', val)
/** 监听激活状态,刷新数据 */
watch(() => props.active, (val) => {
if (val && !isFirstLoad.value) {
queryParams.pageNo = 1
list.value = []
getList()
}
})
/** 初始化 */
onMounted(() => {
getList()
isFirstLoad.value = false
})
</script>

View File

@@ -1,4 +1,13 @@
<template>
<!-- 搜索框入口 -->
<wd-search
:placeholder="searchPlaceholder"
:hide-cancel="true"
disabled
@click="visible = true"
/>
<!-- 搜索弹窗 -->
<wd-popup
v-model="visible"
position="top"
@@ -126,7 +135,7 @@ import type { ProcessDefinition } from '@/api/bpm/definition'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { getCategorySimpleList } from '@/api/bpm/category'
import { getProcessDefinitionList } from '@/api/bpm/definition'
import { getIntDictOptions } from '@/hooks/useDict'
import { getDictLabel, getIntDictOptions } from '@/hooks/useDict'
import { DICT_TYPE } from '@/utils/constants'
import { formatDate } from '@/utils/date'
@@ -140,19 +149,29 @@ export interface DoneSearchFormData {
}
const props = defineProps<{
modelValue: boolean
searchParams?: Partial<DoneSearchFormData>
}>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
'search': [data: DoneSearchFormData]
'reset': []
search: [data: DoneSearchFormData]
reset: []
}>()
const visible = computed({
get: () => props.modelValue,
set: (val: boolean) => emit('update:modelValue', val),
const visible = ref(false)
/** 搜索条件 placeholder 拼接 */
const searchPlaceholder = computed(() => {
const conditions: string[] = []
if (props.searchParams?.name) {
conditions.push(`名称:${props.searchParams.name}`)
}
if (props.searchParams?.status !== undefined && props.searchParams.status !== -1) {
conditions.push(`状态:${getDictLabel(DICT_TYPE.BPM_TASK_STATUS, props.searchParams.status)}`)
}
if (props.searchParams?.createTime?.[0] && props.searchParams?.createTime?.[1]) {
conditions.push(`时间:${formatDate(props.searchParams.createTime[0])}~${formatDate(props.searchParams.createTime[1])}`)
}
return conditions.length > 0 ? conditions.join(' | ') : '搜索已办任务'
})
const categoryList = ref<Category[]>([])
@@ -211,7 +230,7 @@ async function getProcessDefinitions() {
}
/** 监听弹窗打开,同步外部参数 */
watch(() => props.modelValue, (val) => {
watch(visible, (val) => {
if (val && props.searchParams) {
formData.name = props.searchParams.name
formData.processDefinitionKey = props.searchParams.processDefinitionKey

View File

@@ -1,61 +1,63 @@
<template>
<view class="bpm-list">
<view
v-for="item in list"
:key="item.id"
class="bpm-card"
@click="handleDetail(item)"
>
<view class="bpm-card-content">
<view class="bpm-card-header">
<view class="bpm-card-title">
{{ item.name }}
</view>
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="item.status" />
</view>
<view v-if="item.summary?.length" class="bpm-summary">
<view v-for="(s, idx) in item.summary" :key="idx" class="bpm-summary-item">
<text class="text-[#999]">{{ s.key }}</text>
<text>{{ s.value }}</text>
</view>
</view>
<view class="bpm-card-info">
<view class="bpm-user">
<view class="bpm-avatar">
{{ userNickname?.[0] }}
</view>
<text class="bpm-nickname">{{ userNickname }}</text>
</view>
<text class="bpm-time">{{ formatDateTime(item.startTime) }}</text>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="loadMoreState !== 'loading' && list.length === 0" class="bpm-empty">
<wd-status-tip image="content" tip="暂无发起的流程" />
</view>
<!-- 加载更多 -->
<wd-loadmore
v-if="list.length > 0"
:state="loadMoreState"
@reload="loadMore"
/>
<!-- 搜索弹窗 -->
<view>
<!-- 搜索组件 -->
<MySearchForm
v-model="searchPopupVisible"
:search-params="queryParams"
@search="handleSearch"
@reset="handleReset"
/>
<!-- 新增按钮 -->
<view
class="fixed bottom-100rpx right-32rpx z-10 h-100rpx w-100rpx flex items-center justify-center rounded-full bg-[#1890ff] shadow-lg"
@click="handleCreate"
>
<wd-icon name="add" size="24px" color="#fff" />
<view class="bpm-list">
<view
v-for="item in list"
:key="item.id"
class="bpm-card"
@click="handleDetail(item)"
>
<view class="bpm-card-content">
<view class="bpm-card-header">
<view class="bpm-card-title">
{{ item.name }}
</view>
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="item.status" />
</view>
<view v-if="item.summary?.length" class="bpm-summary">
<view v-for="(s, idx) in item.summary" :key="idx" class="bpm-summary-item">
<text class="text-[#999]">{{ s.key }}</text>
<text>{{ s.value }}</text>
</view>
</view>
<view class="bpm-card-info">
<view class="bpm-user">
<view class="bpm-avatar">
{{ userNickname?.[0] }}
</view>
<text class="bpm-nickname">{{ userNickname }}</text>
</view>
<text class="bpm-time">{{ formatDateTime(item.startTime) }}</text>
</view>
</view>
</view>
<!-- 空状态 -->
<view v-if="loadMoreState !== 'loading' && list.length === 0" class="bpm-empty">
<wd-status-tip image="content" tip="暂无发起的流程" />
</view>
<!-- 加载更多 -->
<wd-loadmore
v-if="list.length > 0"
:state="loadMoreState"
@reload="loadMore"
/>
<!-- 新增按钮 -->
<!-- TODO @AI换成 wd-fat要注意可能高度不对晚点在改 -->
<view
class="fixed bottom-100rpx right-32rpx z-10 h-100rpx w-100rpx flex items-center justify-center rounded-full bg-[#1890ff] shadow-lg"
@click="handleCreate"
>
<wd-icon name="add" size="24px" color="#fff" />
</view>
</view>
</view>
</template>
@@ -74,11 +76,7 @@ import MySearchForm from './my-search-form.vue'
import './index.scss'
const props = defineProps<{
searchVisible?: boolean
}>()
const emit = defineEmits<{
'update:searchVisible': [value: boolean]
active?: boolean
}>()
const userStore = useUserStore()
@@ -87,7 +85,7 @@ const userNickname = computed(() => userStore.userInfo?.nickname || '')
const total = ref(0)
const list = ref<ProcessInstance[]>([])
const loadMoreState = ref<LoadMoreState>('loading')
const searchPopupVisible = ref(false)
const isFirstLoad = ref(true)
const queryParams = reactive({
pageNo: 1,
@@ -157,16 +155,18 @@ onReachBottom(() => {
loadMore()
})
watch(() => props.searchVisible, (val) => {
searchPopupVisible.value = val ?? false
})
watch(searchPopupVisible, (val) => {
emit('update:searchVisible', val)
/** 监听激活状态,刷新数据 */
watch(() => props.active, (val) => {
if (val && !isFirstLoad.value) {
queryParams.pageNo = 1
list.value = []
getList()
}
})
/** 初始化 */
onMounted(() => {
getList()
isFirstLoad.value = false
})
</script>

View File

@@ -1,4 +1,13 @@
<template>
<!-- 搜索框入口 -->
<wd-search
:placeholder="searchPlaceholder"
:hide-cancel="true"
disabled
@click="visible = true"
/>
<!-- 搜索弹窗 -->
<wd-popup
v-model="visible"
position="top"
@@ -126,7 +135,7 @@ import type { ProcessDefinition } from '@/api/bpm/definition'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { getCategorySimpleList } from '@/api/bpm/category'
import { getProcessDefinitionList } from '@/api/bpm/definition'
import { getIntDictOptions } from '@/hooks/useDict'
import { getDictLabel, getIntDictOptions } from '@/hooks/useDict'
import { DICT_TYPE } from '@/utils/constants'
import { formatDate } from '@/utils/date'
@@ -140,19 +149,29 @@ export interface MySearchFormData {
}
const props = defineProps<{
modelValue: boolean
searchParams?: Partial<MySearchFormData>
}>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
'search': [data: MySearchFormData]
'reset': []
search: [data: MySearchFormData]
reset: []
}>()
const visible = computed({
get: () => props.modelValue,
set: (val: boolean) => emit('update:modelValue', val),
const visible = ref(false)
/** 搜索条件 placeholder 拼接 */
const searchPlaceholder = computed(() => {
const conditions: string[] = []
if (props.searchParams?.name) {
conditions.push(`名称:${props.searchParams.name}`)
}
if (props.searchParams?.status !== undefined && props.searchParams.status !== -1) {
conditions.push(`状态:${getDictLabel(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS, props.searchParams.status)}`)
}
if (props.searchParams?.createTime?.[0] && props.searchParams?.createTime?.[1]) {
conditions.push(`时间:${formatDate(props.searchParams.createTime[0])}~${formatDate(props.searchParams.createTime[1])}`)
}
return conditions.length > 0 ? conditions.join(' | ') : '搜索我的流程'
})
const categoryList = ref<Category[]>([])
@@ -211,7 +230,7 @@ async function getProcessDefinitions() {
}
/** 监听弹窗打开,同步外部参数 */
watch(() => props.modelValue, (val) => {
watch(visible, (val) => {
if (val && props.searchParams) {
formData.name = props.searchParams.name
formData.processDefinitionId = props.searchParams.processDefinitionId

View File

@@ -1,63 +1,64 @@
<template>
<view class="bpm-list">
<view
v-for="item in list"
:key="item.id"
class="bpm-card"
@click="handleDetail(item)"
>
<view class="bpm-card-content">
<view class="bpm-card-header">
<view class="bpm-card-title">
{{ item.processInstance?.name }}
</view>
<wd-tag type="primary" plain>
待审批
</wd-tag>
</view>
<view v-if="item.processInstance?.summary?.length" class="bpm-summary">
<view v-for="(s, idx) in item.processInstance.summary" :key="idx" class="bpm-summary-item">
<text class="text-[#999]">{{ s.key }}</text>
<text>{{ s.value }}</text>
</view>
</view>
<view class="bpm-card-info">
<view class="bpm-user">
<view class="bpm-avatar">
{{ item.processInstance?.startUser?.nickname?.[0] || '?' }}
</view>
<text class="bpm-nickname">{{ item.processInstance?.startUser?.nickname }}</text>
</view>
<text class="bpm-time--warning">{{ formatPast(item.createTime) }}</text>
</view>
</view>
<view class="bpm-actions">
<view class="bpm-action-btn" @click.stop="handleReject(item)">
<text>拒绝</text>
</view>
<view class="bpm-action-btn" @click.stop="handleApprove(item)">
<text>同意</text>
</view>
</view>
</view>
<!-- 加载更多 -->
<view v-if="loadMoreState !== 'loading' && list.length === 0" class="bpm-empty">
<wd-status-tip image="content" tip="暂无待办任务" />
</view>
<wd-loadmore
v-if="list.length > 0"
:state="loadMoreState"
@reload="loadMore"
/>
<!-- 搜索弹窗 -->
<view>
<!-- 搜索组件 -->
<TodoSearchForm
v-model="searchPopupVisible"
:search-params="queryParams"
@search="handleSearch"
@reset="handleReset"
/>
<view class="bpm-list">
<view
v-for="item in list"
:key="item.id"
class="bpm-card"
@click="handleDetail(item)"
>
<view class="bpm-card-content">
<view class="bpm-card-header">
<view class="bpm-card-title">
{{ item.processInstance?.name }}
</view>
<wd-tag type="primary" plain>
待审批
</wd-tag>
</view>
<view v-if="item.processInstance?.summary?.length" class="bpm-summary">
<view v-for="(s, idx) in item.processInstance.summary" :key="idx" class="bpm-summary-item">
<text class="text-[#999]">{{ s.key }}</text>
<text>{{ s.value }}</text>
</view>
</view>
<view class="bpm-card-info">
<view class="bpm-user">
<view class="bpm-avatar">
{{ item.processInstance?.startUser?.nickname?.[0] || '?' }}
</view>
<text class="bpm-nickname">{{ item.processInstance?.startUser?.nickname }}</text>
</view>
<text class="bpm-time--warning">{{ formatPast(item.createTime) }}</text>
</view>
</view>
<view class="bpm-actions">
<view class="bpm-action-btn" @click.stop="handleReject(item)">
<text>拒绝</text>
</view>
<view class="bpm-action-btn" @click.stop="handleApprove(item)">
<text>同意</text>
</view>
</view>
</view>
<!-- 加载更多 -->
<view v-if="loadMoreState !== 'loading' && list.length === 0" class="bpm-empty">
<wd-status-tip image="content" tip="暂无待办任务" />
</view>
<wd-loadmore
v-if="list.length > 0"
:state="loadMoreState"
@reload="loadMore"
/>
</view>
</view>
</template>
@@ -73,17 +74,13 @@ import TodoSearchForm from './todo-search-form.vue'
import './index.scss'
const props = defineProps<{
searchVisible?: boolean
}>()
const emit = defineEmits<{
'update:searchVisible': [value: boolean]
active?: boolean
}>()
const total = ref(0)
const list = ref<Task[]>([])
const loadMoreState = ref<LoadMoreState>('loading')
const searchPopupVisible = ref(false)
const isFirstLoad = ref(true)
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
@@ -152,16 +149,18 @@ onReachBottom(() => {
loadMore()
})
watch(() => props.searchVisible, (val) => {
searchPopupVisible.value = val ?? false
})
watch(searchPopupVisible, (val) => {
emit('update:searchVisible', val)
/** 监听激活状态,刷新数据 */
watch(() => props.active, (val) => {
if (val && !isFirstLoad.value) {
queryParams.pageNo = 1
list.value = []
getList()
}
})
/** 初始化 */
onMounted(() => {
getList()
isFirstLoad.value = false
})
</script>

View File

@@ -1,4 +1,13 @@
<template>
<!-- 搜索框入口 -->
<wd-search
:placeholder="searchPlaceholder"
:hide-cancel="true"
disabled
@click="visible = true"
/>
<!-- 搜索弹窗 -->
<wd-popup
v-model="visible"
position="top"
@@ -126,7 +135,7 @@ import type { ProcessDefinition } from '@/api/bpm/definition'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { getCategorySimpleList } from '@/api/bpm/category'
import { getProcessDefinitionList } from '@/api/bpm/definition'
import { getIntDictOptions } from '@/hooks/useDict'
import { getDictLabel, getIntDictOptions } from '@/hooks/useDict'
import { DICT_TYPE } from '@/utils/constants'
import { formatDate } from '@/utils/date'
@@ -140,19 +149,29 @@ export interface TodoSearchFormData {
}
const props = defineProps<{
modelValue: boolean
searchParams?: Partial<TodoSearchFormData>
}>()
const emit = defineEmits<{
'update:modelValue': [value: boolean]
'search': [data: TodoSearchFormData]
'reset': []
search: [data: TodoSearchFormData]
reset: []
}>()
const visible = computed({
get: () => props.modelValue,
set: (val: boolean) => emit('update:modelValue', val),
const visible = ref(false)
/** 搜索条件 placeholder 拼接 */
const searchPlaceholder = computed(() => {
const conditions: string[] = []
if (props.searchParams?.name) {
conditions.push(`名称:${props.searchParams.name}`)
}
if (props.searchParams?.status !== undefined && props.searchParams.status !== -1) {
conditions.push(`状态:${getDictLabel(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS, props.searchParams.status)}`)
}
if (props.searchParams?.createTime?.[0] && props.searchParams?.createTime?.[1]) {
conditions.push(`时间:${formatDate(props.searchParams.createTime[0])}~${formatDate(props.searchParams.createTime[1])}`)
}
return conditions.length > 0 ? conditions.join(' | ') : '搜索待办任务'
})
const categoryList = ref<Category[]>([])
@@ -211,7 +230,7 @@ async function getProcessDefinitions() {
}
/** 监听弹窗打开,同步外部参数 */
watch(() => props.modelValue, (val) => {
watch(visible, (val) => {
if (val && props.searchParams) {
formData.name = props.searchParams.name
formData.processDefinitionKey = props.searchParams.processDefinitionKey

View File

@@ -4,13 +4,7 @@
<wd-navbar
title="审批"
placeholder safe-area-inset-top fixed
>
<template #right>
<view class="flex items-center" @click="searchVisible = !searchVisible">
<wd-icon name="search" size="20px" />
</view>
</template>
</wd-navbar>
/>
<!-- Tabs 区域 -->
<view class="bg-white">
@@ -22,26 +16,10 @@
</wd-tabs>
</view>
<!-- 列表内容 -->
<TodoList
v-show="tabType === 'todo'"
:search-visible="searchVisible && tabType === 'todo'"
@update:search-visible="searchVisible = $event"
/>
<DoneList
v-show="tabType === 'done'"
:search-visible="searchVisible && tabType === 'done'"
@update:search-visible="searchVisible = $event"
/>
<MyList
v-show="tabType === 'my'"
:search-visible="searchVisible && tabType === 'my'"
@update:search-visible="searchVisible = $event"
/>
<CopyList
v-show="tabType === 'copy'"
:search-visible="searchVisible && tabType === 'copy'"
@update:search-visible="searchVisible = $event"
/>
<TodoList v-show="tabType === 'todo'" :active="tabType === 'todo'" />
<DoneList v-show="tabType === 'done'" :active="tabType === 'done'" />
<MyList v-show="tabType === 'my'" :active="tabType === 'my'" />
<CopyList v-show="tabType === 'copy'" :active="tabType === 'copy'" />
</view>
</template>
@@ -63,12 +41,10 @@ definePage({
const tabTypes: string[] = ['todo', 'done', 'my', 'copy']
const tabIndex = ref(0)
const tabType = computed<string>(() => tabTypes[tabIndex.value])
const searchVisible = ref(false)
/** Tab 切换 */
function handleTabChange({ index }: { index: number }) {
tabIndex.value = index
searchVisible.value = false
}
/** 初始化:根据 tab 参数设置默认 tab */