|
|
@@ -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() {
|