Просмотр исходного кода

feat(bx): server-side column filters on reimburse apply list.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 1 месяц назад
Родитель
Сommit
55cbd1d3d2
2 измененных файлов с 68 добавлено и 123 удалено
  1. 17 1
      web-admin/src/api/bx/index.ts
  2. 51 122
      web-admin/src/views/bx/reimburse/apply/index.vue

+ 17 - 1
web-admin/src/api/bx/index.ts

@@ -162,7 +162,23 @@ export const reimburseApi = {
   exportApprovedByMe(data: Record<string, any>) {
     return bxRequest.post('/reimburses/approved-by-me/export', data, { responseType: 'blob' })
   },
-  my(params: { page: number; pageSize: number; status?: string; keyword?: string } & BxApplyDateFilter) {
+  my(
+    params: {
+      page: number
+      pageSize: number
+      status?: string
+      statusIn?: string
+      keyword?: string
+      applyCode?: string
+      title?: string
+      expenseName?: string
+      currentNode?: string
+      amountOp?: string
+      amount?: string
+      amountMin?: string
+      amountMax?: string
+    } & BxApplyDateFilter
+  ) {
     return bxRequest.get('/reimburses/my', { params })
   },
   myStats() {

+ 51 - 122
web-admin/src/views/bx/reimburse/apply/index.vue

@@ -3,49 +3,6 @@
 <template>
   <div class="page-container">
     <section class="panel">
-      <div class="filter-block">
-        <el-form :model="filterForm" class="filter-form" @submit.prevent>
-          <div class="filter-grid">
-            <el-form-item label="报销单号">
-              <el-input v-model="filterForm.keyword" placeholder="请输入报销单号" clearable />
-            </el-form-item>
-            <el-form-item label="费用项目">
-              <el-input v-model="filterForm.expenseName" placeholder="请输入费用项目" clearable />
-            </el-form-item>
-            <el-form-item label="状态">
-              <el-select v-model="filterForm.status" placeholder="全部" clearable>
-                <el-option label="待提交" value="draft" />
-                <el-option label="审批中" value="pending" />
-                <el-option label="审批通过" value="approved" />
-                <el-option label="审批驳回" value="rejected" />
-              </el-select>
-            </el-form-item>
-            <el-form-item label="申请时间">
-              <el-date-picker
-                v-model="filterForm.applyDateRange"
-                type="daterange"
-                range-separator="-"
-                start-placeholder="开始日期"
-                end-placeholder="结束日期"
-                value-format="YYYY-MM-DD"
-                clearable
-                style="width: 100%"
-              />
-            </el-form-item>
-          </div>
-          <div class="filter-actions">
-            <div class="filter-actions-end">
-              <el-button class="btn-ghost" :loading="loading" @click="handleReset">
-                <el-icon><RefreshLeft /></el-icon>重置
-              </el-button>
-              <el-button class="btn-main" type="primary" :loading="loading" @click="handleSearch">
-                <el-icon><Search /></el-icon>搜索
-              </el-button>
-            </div>
-          </div>
-        </el-form>
-      </div>
-
       <div class="list-toolbar">
         <div class="list-toolbar-row">
           <ListQuickSearchInput
@@ -111,8 +68,8 @@
             v-model:visible-columns="visibleColumns"
             v-model:check-all="checkAll"
             :is-indeterminate="isIndeterminate"
-            @refresh="handleSearch"
-            @reset-columns="resetToDefault"
+            @refresh="runListSearch"
+            @reset-columns="handleResetColumns"
           />
 
         </div>
@@ -125,7 +82,7 @@
             table-key="bx-reimburse-apply"
             class="list-table"
             :height="tableHeight"
-            :data="filteredRows"
+            :data="rows"
             v-loading="loading"
             stripe
             @selection-change="onSelectionChange"
@@ -178,7 +135,6 @@
                     type="text"
                     :is-active="isFilterActive('applyCode')"
                     :model-value="filters.applyCode || null"
-                    :options="collectUniqueOptions('applyCode')"
                     @update:model-value="(v) => setFilterState('applyCode', v)"
                   />
                 </div>
@@ -205,7 +161,6 @@
                     type="text"
                     :is-active="isFilterActive('title')"
                     :model-value="filters.title || null"
-                    :options="collectUniqueOptions('title')"
                     @update:model-value="(v) => setFilterState('title', v)"
                   />
                 </div>
@@ -229,7 +184,6 @@
                     type="text"
                     :is-active="isFilterActive('expenseName')"
                     :model-value="filters.expenseName || null"
-                    :options="collectUniqueOptions('expenseName')"
                     @update:model-value="(v) => setFilterState('expenseName', v)"
                   />
                 </div>
@@ -287,7 +241,7 @@
                     type="enum"
                     :is-active="isFilterActive('status')"
                     :model-value="filters.status || null"
-                    :options="collectUniqueOptions('status')"
+                    :options="enumOptionsFor('status')"
                     @update:model-value="(v) => setFilterState('status', v)"
                   />
                 </div>
@@ -315,7 +269,6 @@
                     type="text"
                     :is-active="isFilterActive('currentNode')"
                     :model-value="filters.currentNode || null"
-                    :options="collectUniqueOptions('currentNode')"
                     @update:model-value="(v) => setFilterState('currentNode', v)"
                   />
                 </div>
@@ -338,7 +291,7 @@
             ref="reimburseApplyStatsRowRef"
             storage-key="bx-reimburse-apply-column-stats"
             :columns="reimburseApplyStatsRowColumns"
-            :data="filteredRows"
+            :data="rows"
             :table-wrap-el="reimburseApplyTableWrapRef"
             :get-cell-value="getReimburseApplyStatCellValue"
           />
@@ -817,13 +770,10 @@ import ListToolbarEndActions from '@/components/business/list-toolbar-end-action
 import ListExportDropdown from '@/components/business/list-export-dropdown/index.vue'
 import ListColumnStatsRow from '@/components/business/list-column-stats-row/index.vue'
 import ColumnHeaderFilter from '@/components/business/column-header-filter/index.vue'
-import { useDbInstantSearch } from '@/composables/useDbInstantSearch'
 import { usePersistedListColumns } from '@/composables/usePersistedListColumns'
 import { useListTableViewportHeight } from '@/composables/useListTableViewportHeight'
-import {
-  useClientColumnFilters,
-  type ColumnFilterDef
-} from '@/composables/useClientColumnFilters'
+import { type ColumnFilterDef } from '@/composables/useClientColumnFilters'
+import { useServerColumnFilters } from '@/composables/useServerColumnFilters'
 import ListQuickSearchInput from '@/components/business/list-quick-search-input/index.vue'
 import { reactive, ref, computed, onMounted, nextTick } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
@@ -834,8 +784,6 @@ import {
   View,
   Close,
   Delete,
-  Search,
-  RefreshLeft,
   FolderOpened,
   FolderChecked,
   Promotion,
@@ -849,7 +797,6 @@ import {
 } from '@element-plus/icons-vue'
 import type { UploadFile } from 'element-plus'
 import { reimburseApi, invoiceWalletApi, attachmentApi } from '@/api/bx'
-import { buildApplyDateRangeQuery, type ApplyDateRange } from '@/utils/bx/apply-date-filter'
 import { canDeleteByCreator, allRowsDeletableByCreator, CREATOR_DELETE_TIP } from '@/utils/creator-delete'
 import { useExpenseItemTree, type ExpenseItem } from '@/hooks/bx/useExpenseItemTree'
 import { useApplicantOptions } from '@/hooks/bx/useApplicantOptions'
@@ -908,19 +855,13 @@ const { rows, total, currentPage, pageSize, rangeText, loading, refresh, search,
   }
 )
 
+const quickSearchKeyword = ref('')
+
 const columnFilterDefs: ColumnFilterDef[] = [
   { prop: 'applyTime', label: '申请时间', type: 'datetime' },
   { prop: 'applyCode', label: '报销单号', type: 'text' },
   { prop: 'title', label: '报销标题', type: 'text' },
-  {
-    prop: 'expenseName',
-    label: '费用项目',
-    type: 'text',
-    getValue: (row) => {
-      const lines = getReimburseExpenseItemLines(row.details, row)
-      return lines?.length ? lines.join('\n') : row.expenseName || ''
-    }
-  },
+  { prop: 'expenseName', label: '费用项目', type: 'text' },
   { prop: 'amount', label: '报销金额(元)', type: 'number' },
   {
     prop: 'status',
@@ -933,23 +874,27 @@ const columnFilterDefs: ColumnFilterDef[] = [
       { value: 'rejected', label: '审批驳回' }
     ]
   },
-  {
-    prop: 'currentNode',
-    label: '当前节点',
-    type: 'text',
-    getValue: (row) => workflowNodeLabel(row.currentNode, row.status) || ''
-  }
+  { prop: 'currentNode', label: '当前节点', type: 'text' }
 ]
 
 const {
   filters,
-  filteredRows,
   hasActiveFilters,
   setFilterState,
   clearAllFilters,
   isFilterActive,
-  collectUniqueOptions
-} = useClientColumnFilters(rows, columnFilterDefs)
+  toQueryParams,
+  resetToInitial,
+  watchVisibleColumns
+} = useServerColumnFilters(columnFilterDefs, {
+  initialFilters: {},
+  onChange: () => runListSearch()
+})
+
+function enumOptionsFor(prop: string) {
+  const def = columnFilterDefs.find((d) => d.prop === prop)
+  return (def?.enumOptions || []).map((o) => ({ ...o, count: 0 }))
+}
 
 const { expenseTree } = useExpenseItemTree()
 const { applicantOptions, loadApplicantOptions } = useApplicantOptions()
@@ -969,8 +914,6 @@ function markDisabledExpenseNodes(tree: ExpenseItem[]): ExpenseItem[] {
 
 const expenseTreeForSelect = computed(() => markDisabledExpenseNodes(expenseTree.value))
 
-const quickSearchKeyword = ref('')
-
 const listColumnDefs = [
   { prop: 'applyTime', label: '申请时间' },
   { prop: 'title', label: '报销标题' },
@@ -988,6 +931,20 @@ const {
   resetToDefault
 } = usePersistedListColumns('bx-reimburse-apply-list-columns', listColumnDefs)
 
+const visibleFilterProps = computed(() => {
+  const props: string[] = ['applyCode']
+  for (const def of listColumnDefs) {
+    if (isColumnVisible(def.prop)) props.push(def.prop)
+  }
+  return props
+})
+watchVisibleColumns(visibleFilterProps)
+
+function handleResetColumns() {
+  resetToDefault()
+  resetToInitial()
+}
+
 const reimburseApplyTableWrapRef = ref<HTMLElement | null>(null)
 const reimburseApplyBelowRef = ref<HTMLElement | null>(null)
 const reimburseApplyTableRef = ref<any>(null)
@@ -999,6 +956,18 @@ const { tableHeight, recalc: recalcTableHeight } = useListTableViewportHeight({
   tableRef: reimburseApplyTableRef
 })
 
+function buildSearchQuery() {
+  const q: Record<string, string> = { ...toQueryParams() }
+  const kw = quickSearchKeyword.value.trim()
+  if (kw) q.keyword = kw
+  return q
+}
+
+function runListSearch() {
+  search(buildSearchQuery())
+  nextTick(() => recalcTableHeight())
+}
+
 const reimburseApplyStatsRowColumns = computed((): StatsRowColumn[] => {
   const cols: StatsRowColumn[] = [
     { key: '__selection', interactive: false },
@@ -1042,13 +1011,6 @@ function onReimburseApplyHeaderDragEnd() {
   reimburseApplyStatsRowRef.value?.resync?.()
 }
 
-const filterForm = reactive({
-  keyword: '',
-  status: '',
-  expenseName: '',
-  applyDateRange: null as ApplyDateRange
-})
-
 function todayYmd() {
   const d = new Date()
   const y = d.getFullYear()
@@ -1560,46 +1522,13 @@ onMounted(() => {
   refreshList()
 })
 
-function buildSearchQuery() {
-  const q: Record<string, string> = {}
-  const kw = (quickSearchKeyword.value.trim() || filterForm.keyword.trim())
-  if (kw) q.keyword = kw
-  if (filterForm.status) q.status = filterForm.status
-  if (filterForm.expenseName) q.expenseName = filterForm.expenseName
-  buildApplyDateRangeQuery(filterForm.applyDateRange, q)
-  return q
-}
-
-function handleSearch() {
-  quickSearchKeyword.value = ''
-  search(buildSearchQuery())
-  nextTick(() => recalcTableHeight())
-}
-
-useDbInstantSearch(
-  () => ({
-    keyword: filterForm.keyword,
-    status: filterForm.status,
-    expenseName: filterForm.expenseName,
-    applyDateRange: filterForm.applyDateRange
-  }),
-  handleSearch
-)
-
-function handleReset() {
-  quickSearchKeyword.value = ''
-  Object.assign(filterForm, { keyword: '', status: '', expenseName: '', applyDateRange: null })
-  search()
-  nextTick(() => recalcTableHeight())
-}
-
 function handleQuickSearch() {
-  search(buildSearchQuery())
+  runListSearch()
 }
 
 function handleQuickSearchClear() {
   quickSearchKeyword.value = ''
-  search(buildSearchQuery())
+  runListSearch()
 }
 
 async function handleToolbarSubmit() {