feat(bx): server-side column filters on reimburse apply list.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
1604ffceb1
commit
55cbd1d3d2
2 files changed
+68
-123
No files matched your search
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in new issue
Block a user