Browse Source

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

YunaiV 4 months ago
parent
commit
8807caa3eb

+ 2 - 1
src/pages-bpm/processInstance/create/index.vue

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

+ 21 - 23
src/pages-bpm/processInstance/detail/audit/index.vue

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

+ 38 - 19
src/pages-bpm/processInstance/detail/index.vue

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

+ 53 - 54
src/pages/bpm/components/copy-list.vue

@@ -1,54 +1,55 @@
 <template>
 <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>
+    <!-- 搜索组件 -->
+    <CopySearchForm
+      :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>
-          <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 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>
-        </view>
-        <view class="bpm-card-info">
-          <view class="bpm-user">
-            <view class="bpm-avatar">
-              {{ item.startUser.nickname?.[0] || '?' }}
+          <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>
             </view>
-            <text class="bpm-nickname">{{ item.startUser.nickname }}</text>
+            <text class="bpm-time">{{ formatDateTime(item.createTime) }}</text>
           </view>
           </view>
-          <text class="bpm-time">{{ formatDateTime(item.createTime) }}</text>
         </view>
         </view>
       </view>
       </view>
-    </view>
 
 
-    <view v-if="loadMoreState !== 'loading' && list.length === 0" class="bpm-empty">
-      <wd-status-tip image="content" tip="暂无抄送任务" />
+      <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>
-    <wd-loadmore
-      v-if="list.length > 0"
-      :state="loadMoreState"
-      @reload="loadMore"
-    />
-
-    <!-- 搜索弹窗 -->
-    <CopySearchForm
-      v-model="searchPopupVisible"
-      :search-params="queryParams"
-      @search="handleSearch"
-      @reset="handleReset"
-    />
   </view>
   </view>
 </template>
 </template>
 
 
@@ -64,17 +65,13 @@ import CopySearchForm from './copy-search-form.vue'
 import './index.scss'
 import './index.scss'
 
 
 const props = defineProps<{
 const props = defineProps<{
-  searchVisible?: boolean
-}>()
-
-const emit = defineEmits<{
-  'update:searchVisible': [value: boolean]
+  active?: boolean
 }>()
 }>()
 
 
 const total = ref(0)
 const total = ref(0)
 const list = ref<ProcessInstanceCopy[]>([])
 const list = ref<ProcessInstanceCopy[]>([])
 const loadMoreState = ref<LoadMoreState>('loading')
 const loadMoreState = ref<LoadMoreState>('loading')
-const searchPopupVisible = ref(false)
+const isFirstLoad = ref(true)
 const queryParams = reactive({
 const queryParams = reactive({
   pageNo: 1,
   pageNo: 1,
   pageSize: 10,
   pageSize: 10,
@@ -133,16 +130,18 @@ onReachBottom(() => {
   loadMore()
   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(() => {
 onMounted(() => {
   getList()
   getList()
+  isFirstLoad.value = false
 })
 })
 </script>
 </script>

+ 24 - 8
src/pages/bpm/components/copy-search-form.vue

@@ -1,4 +1,13 @@
 <template>
 <template>
+  <!-- 搜索框入口 -->
+  <wd-search
+    :placeholder="searchPlaceholder"
+    :hide-cancel="true"
+    disabled
+    @click="visible = true"
+  />
+
+  <!-- 搜索弹窗 -->
   <wd-popup
   <wd-popup
     v-model="visible"
     v-model="visible"
     position="top"
     position="top"
@@ -93,19 +102,26 @@ export interface CopySearchFormData {
 }
 }
 
 
 const props = defineProps<{
 const props = defineProps<{
-  modelValue: boolean
   searchParams?: Partial<CopySearchFormData>
   searchParams?: Partial<CopySearchFormData>
 }>()
 }>()
 
 
 const emit = defineEmits<{
 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>({
 const formData = reactive<CopySearchFormData>({
@@ -141,7 +157,7 @@ function handleEndCancel() {
 }
 }
 
 
 /** 监听弹窗打开,同步外部参数 */
 /** 监听弹窗打开,同步外部参数 */
-watch(() => props.modelValue, (val) => {
+watch(visible, (val) => {
   if (val && props.searchParams) {
   if (val && props.searchParams) {
     formData.processInstanceName = props.searchParams.processInstanceName
     formData.processInstanceName = props.searchParams.processInstanceName
     formData.createTime = props.searchParams.createTime ?? [undefined, undefined]
     formData.createTime = props.searchParams.createTime ?? [undefined, undefined]

+ 51 - 52
src/pages/bpm/components/done-list.vue

@@ -1,52 +1,53 @@
 <template>
 <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>
+    <!-- 搜索组件 -->
+    <DoneSearchForm
+      :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>
-          <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 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>
-        </view>
-        <view class="bpm-card-info">
-          <view class="bpm-user">
-            <view class="bpm-avatar">
-              {{ item.processInstance?.startUser?.nickname?.[0] || '?' }}
+          <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>
             </view>
-            <text class="bpm-nickname">{{ item.processInstance?.startUser?.nickname }}</text>
+            <text class="bpm-time">{{ formatDateTime(item.createTime) }}</text>
           </view>
           </view>
-          <text class="bpm-time">{{ formatDateTime(item.createTime) }}</text>
         </view>
         </view>
       </view>
       </view>
-    </view>
 
 
-    <view v-if="loadMoreState !== 'loading' && list.length === 0" class="bpm-empty">
-      <wd-status-tip image="content" tip="暂无已办任务" />
+      <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>
-    <wd-loadmore
-      v-if="list.length > 0"
-      :state="loadMoreState"
-      @reload="loadMore"
-    />
-
-    <!-- 搜索弹窗 -->
-    <DoneSearchForm
-      v-model="searchPopupVisible"
-      :search-params="queryParams"
-      @search="handleSearch"
-      @reset="handleReset"
-    />
   </view>
   </view>
 </template>
 </template>
 
 
@@ -63,17 +64,13 @@ import DoneSearchForm from './done-search-form.vue'
 import './index.scss'
 import './index.scss'
 
 
 const props = defineProps<{
 const props = defineProps<{
-  searchVisible?: boolean
-}>()
-
-const emit = defineEmits<{
-  'update:searchVisible': [value: boolean]
+  active?: boolean
 }>()
 }>()
 
 
 const total = ref(0)
 const total = ref(0)
 const list = ref<Task[]>([])
 const list = ref<Task[]>([])
 const loadMoreState = ref<LoadMoreState>('loading')
 const loadMoreState = ref<LoadMoreState>('loading')
-const searchPopupVisible = ref(false)
+const isFirstLoad = ref(true)
 const queryParams = reactive({
 const queryParams = reactive({
   pageNo: 1,
   pageNo: 1,
   pageSize: 10,
   pageSize: 10,
@@ -132,16 +129,18 @@ onReachBottom(() => {
   loadMore()
   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(() => {
 onMounted(() => {
   getList()
   getList()
+  isFirstLoad.value = false
 })
 })
 </script>
 </script>

+ 28 - 9
src/pages/bpm/components/done-search-form.vue

@@ -1,4 +1,13 @@
 <template>
 <template>
+  <!-- 搜索框入口 -->
+  <wd-search
+    :placeholder="searchPlaceholder"
+    :hide-cancel="true"
+    disabled
+    @click="visible = true"
+  />
+
+  <!-- 搜索弹窗 -->
   <wd-popup
   <wd-popup
     v-model="visible"
     v-model="visible"
     position="top"
     position="top"
@@ -126,7 +135,7 @@ import type { ProcessDefinition } from '@/api/bpm/definition'
 import { computed, onMounted, reactive, ref, watch } from 'vue'
 import { computed, onMounted, reactive, ref, watch } from 'vue'
 import { getCategorySimpleList } from '@/api/bpm/category'
 import { getCategorySimpleList } from '@/api/bpm/category'
 import { getProcessDefinitionList } from '@/api/bpm/definition'
 import { getProcessDefinitionList } from '@/api/bpm/definition'
-import { getIntDictOptions } from '@/hooks/useDict'
+import { getDictLabel, getIntDictOptions } from '@/hooks/useDict'
 import { DICT_TYPE } from '@/utils/constants'
 import { DICT_TYPE } from '@/utils/constants'
 import { formatDate } from '@/utils/date'
 import { formatDate } from '@/utils/date'
 
 
@@ -140,19 +149,29 @@ export interface DoneSearchFormData {
 }
 }
 
 
 const props = defineProps<{
 const props = defineProps<{
-  modelValue: boolean
   searchParams?: Partial<DoneSearchFormData>
   searchParams?: Partial<DoneSearchFormData>
 }>()
 }>()
 
 
 const emit = defineEmits<{
 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[]>([])
 const categoryList = ref<Category[]>([])
@@ -211,7 +230,7 @@ async function getProcessDefinitions() {
 }
 }
 
 
 /** 监听弹窗打开,同步外部参数 */
 /** 监听弹窗打开,同步外部参数 */
-watch(() => props.modelValue, (val) => {
+watch(visible, (val) => {
   if (val && props.searchParams) {
   if (val && props.searchParams) {
     formData.name = props.searchParams.name
     formData.name = props.searchParams.name
     formData.processDefinitionKey = props.searchParams.processDefinitionKey
     formData.processDefinitionKey = props.searchParams.processDefinitionKey

+ 62 - 62
src/pages/bpm/components/my-list.vue

@@ -1,61 +1,63 @@
 <template>
 <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>
+    <!-- 搜索组件 -->
+    <MySearchForm
+      :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.name }}
+            </view>
+            <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="item.status" />
           </view>
           </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 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>
-        </view>
-        <view class="bpm-card-info">
-          <view class="bpm-user">
-            <view class="bpm-avatar">
-              {{ userNickname?.[0] }}
+          <view class="bpm-card-info">
+            <view class="bpm-user">
+              <view class="bpm-avatar">
+                {{ userNickname?.[0] }}
+              </view>
+              <text class="bpm-nickname">{{ userNickname }}</text>
             </view>
             </view>
-            <text class="bpm-nickname">{{ userNickname }}</text>
+            <text class="bpm-time">{{ formatDateTime(item.startTime) }}</text>
           </view>
           </view>
-          <text class="bpm-time">{{ formatDateTime(item.startTime) }}</text>
         </view>
         </view>
       </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"
-    />
 
 
-    <!-- 搜索弹窗 -->
-    <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 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>
   </view>
   </view>
 </template>
 </template>
@@ -74,11 +76,7 @@ import MySearchForm from './my-search-form.vue'
 import './index.scss'
 import './index.scss'
 
 
 const props = defineProps<{
 const props = defineProps<{
-  searchVisible?: boolean
-}>()
-
-const emit = defineEmits<{
-  'update:searchVisible': [value: boolean]
+  active?: boolean
 }>()
 }>()
 
 
 const userStore = useUserStore()
 const userStore = useUserStore()
@@ -87,7 +85,7 @@ const userNickname = computed(() => userStore.userInfo?.nickname || '')
 const total = ref(0)
 const total = ref(0)
 const list = ref<ProcessInstance[]>([])
 const list = ref<ProcessInstance[]>([])
 const loadMoreState = ref<LoadMoreState>('loading')
 const loadMoreState = ref<LoadMoreState>('loading')
-const searchPopupVisible = ref(false)
+const isFirstLoad = ref(true)
 
 
 const queryParams = reactive({
 const queryParams = reactive({
   pageNo: 1,
   pageNo: 1,
@@ -157,16 +155,18 @@ onReachBottom(() => {
   loadMore()
   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(() => {
 onMounted(() => {
   getList()
   getList()
+  isFirstLoad.value = false
 })
 })
 </script>
 </script>

+ 28 - 9
src/pages/bpm/components/my-search-form.vue

@@ -1,4 +1,13 @@
 <template>
 <template>
+  <!-- 搜索框入口 -->
+  <wd-search
+    :placeholder="searchPlaceholder"
+    :hide-cancel="true"
+    disabled
+    @click="visible = true"
+  />
+
+  <!-- 搜索弹窗 -->
   <wd-popup
   <wd-popup
     v-model="visible"
     v-model="visible"
     position="top"
     position="top"
@@ -126,7 +135,7 @@ import type { ProcessDefinition } from '@/api/bpm/definition'
 import { computed, onMounted, reactive, ref, watch } from 'vue'
 import { computed, onMounted, reactive, ref, watch } from 'vue'
 import { getCategorySimpleList } from '@/api/bpm/category'
 import { getCategorySimpleList } from '@/api/bpm/category'
 import { getProcessDefinitionList } from '@/api/bpm/definition'
 import { getProcessDefinitionList } from '@/api/bpm/definition'
-import { getIntDictOptions } from '@/hooks/useDict'
+import { getDictLabel, getIntDictOptions } from '@/hooks/useDict'
 import { DICT_TYPE } from '@/utils/constants'
 import { DICT_TYPE } from '@/utils/constants'
 import { formatDate } from '@/utils/date'
 import { formatDate } from '@/utils/date'
 
 
@@ -140,19 +149,29 @@ export interface MySearchFormData {
 }
 }
 
 
 const props = defineProps<{
 const props = defineProps<{
-  modelValue: boolean
   searchParams?: Partial<MySearchFormData>
   searchParams?: Partial<MySearchFormData>
 }>()
 }>()
 
 
 const emit = defineEmits<{
 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[]>([])
 const categoryList = ref<Category[]>([])
@@ -211,7 +230,7 @@ async function getProcessDefinitions() {
 }
 }
 
 
 /** 监听弹窗打开,同步外部参数 */
 /** 监听弹窗打开,同步外部参数 */
-watch(() => props.modelValue, (val) => {
+watch(visible, (val) => {
   if (val && props.searchParams) {
   if (val && props.searchParams) {
     formData.name = props.searchParams.name
     formData.name = props.searchParams.name
     formData.processDefinitionId = props.searchParams.processDefinitionId
     formData.processDefinitionId = props.searchParams.processDefinitionId

+ 61 - 62
src/pages/bpm/components/todo-list.vue

@@ -1,63 +1,64 @@
 <template>
 <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>
+    <!-- 搜索组件 -->
+    <TodoSearchForm
+      :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>
-          <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 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>
-        </view>
-        <view class="bpm-card-info">
-          <view class="bpm-user">
-            <view class="bpm-avatar">
-              {{ item.processInstance?.startUser?.nickname?.[0] || '?' }}
+          <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>
             </view>
-            <text class="bpm-nickname">{{ item.processInstance?.startUser?.nickname }}</text>
+            <text class="bpm-time--warning">{{ formatPast(item.createTime) }}</text>
           </view>
           </view>
-          <text class="bpm-time--warning">{{ formatPast(item.createTime) }}</text>
         </view>
         </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 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>
       </view>
-    </view>
 
 
-    <!-- 加载更多 -->
-    <view v-if="loadMoreState !== 'loading' && list.length === 0" class="bpm-empty">
-      <wd-status-tip image="content" tip="暂无待办任务" />
+      <!-- 加载更多 -->
+      <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>
-    <wd-loadmore
-      v-if="list.length > 0"
-      :state="loadMoreState"
-      @reload="loadMore"
-    />
-
-    <!-- 搜索弹窗 -->
-    <TodoSearchForm
-      v-model="searchPopupVisible"
-      :search-params="queryParams"
-      @search="handleSearch"
-      @reset="handleReset"
-    />
   </view>
   </view>
 </template>
 </template>
 
 
@@ -73,17 +74,13 @@ import TodoSearchForm from './todo-search-form.vue'
 import './index.scss'
 import './index.scss'
 
 
 const props = defineProps<{
 const props = defineProps<{
-  searchVisible?: boolean
-}>()
-
-const emit = defineEmits<{
-  'update:searchVisible': [value: boolean]
+  active?: boolean
 }>()
 }>()
 
 
 const total = ref(0)
 const total = ref(0)
 const list = ref<Task[]>([])
 const list = ref<Task[]>([])
 const loadMoreState = ref<LoadMoreState>('loading')
 const loadMoreState = ref<LoadMoreState>('loading')
-const searchPopupVisible = ref(false)
+const isFirstLoad = ref(true)
 const queryParams = reactive({
 const queryParams = reactive({
   pageNo: 1,
   pageNo: 1,
   pageSize: 10,
   pageSize: 10,
@@ -152,16 +149,18 @@ onReachBottom(() => {
   loadMore()
   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(() => {
 onMounted(() => {
   getList()
   getList()
+  isFirstLoad.value = false
 })
 })
 </script>
 </script>

+ 28 - 9
src/pages/bpm/components/todo-search-form.vue

@@ -1,4 +1,13 @@
 <template>
 <template>
+  <!-- 搜索框入口 -->
+  <wd-search
+    :placeholder="searchPlaceholder"
+    :hide-cancel="true"
+    disabled
+    @click="visible = true"
+  />
+
+  <!-- 搜索弹窗 -->
   <wd-popup
   <wd-popup
     v-model="visible"
     v-model="visible"
     position="top"
     position="top"
@@ -126,7 +135,7 @@ import type { ProcessDefinition } from '@/api/bpm/definition'
 import { computed, onMounted, reactive, ref, watch } from 'vue'
 import { computed, onMounted, reactive, ref, watch } from 'vue'
 import { getCategorySimpleList } from '@/api/bpm/category'
 import { getCategorySimpleList } from '@/api/bpm/category'
 import { getProcessDefinitionList } from '@/api/bpm/definition'
 import { getProcessDefinitionList } from '@/api/bpm/definition'
-import { getIntDictOptions } from '@/hooks/useDict'
+import { getDictLabel, getIntDictOptions } from '@/hooks/useDict'
 import { DICT_TYPE } from '@/utils/constants'
 import { DICT_TYPE } from '@/utils/constants'
 import { formatDate } from '@/utils/date'
 import { formatDate } from '@/utils/date'
 
 
@@ -140,19 +149,29 @@ export interface TodoSearchFormData {
 }
 }
 
 
 const props = defineProps<{
 const props = defineProps<{
-  modelValue: boolean
   searchParams?: Partial<TodoSearchFormData>
   searchParams?: Partial<TodoSearchFormData>
 }>()
 }>()
 
 
 const emit = defineEmits<{
 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[]>([])
 const categoryList = ref<Category[]>([])
@@ -211,7 +230,7 @@ async function getProcessDefinitions() {
 }
 }
 
 
 /** 监听弹窗打开,同步外部参数 */
 /** 监听弹窗打开,同步外部参数 */
-watch(() => props.modelValue, (val) => {
+watch(visible, (val) => {
   if (val && props.searchParams) {
   if (val && props.searchParams) {
     formData.name = props.searchParams.name
     formData.name = props.searchParams.name
     formData.processDefinitionKey = props.searchParams.processDefinitionKey
     formData.processDefinitionKey = props.searchParams.processDefinitionKey

+ 5 - 29
src/pages/bpm/index.vue

@@ -4,13 +4,7 @@
     <wd-navbar
     <wd-navbar
       title="审批"
       title="审批"
       placeholder safe-area-inset-top fixed
       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 区域 -->
     <!-- Tabs 区域 -->
     <view class="bg-white">
     <view class="bg-white">
@@ -22,26 +16,10 @@
       </wd-tabs>
       </wd-tabs>
     </view>
     </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>
   </view>
 </template>
 </template>
 
 
@@ -63,12 +41,10 @@ definePage({
 const tabTypes: string[] = ['todo', 'done', 'my', 'copy']
 const tabTypes: string[] = ['todo', 'done', 'my', 'copy']
 const tabIndex = ref(0)
 const tabIndex = ref(0)
 const tabType = computed<string>(() => tabTypes[tabIndex.value])
 const tabType = computed<string>(() => tabTypes[tabIndex.value])
-const searchVisible = ref(false)
 
 
 /** Tab 切换 */
 /** Tab 切换 */
 function handleTabChange({ index }: { index: number }) {
 function handleTabChange({ index }: { index: number }) {
   tabIndex.value = index
   tabIndex.value = index
-  searchVisible.value = false
 }
 }
 
 
 /** 初始化:根据 tab 参数设置默认 tab */
 /** 初始化:根据 tab 参数设置默认 tab */