Files
X-Financial/web/src/views/scripts/RequestsView.js

211 lines
6.8 KiB
JavaScript
Raw Normal View History

import { computed, ref, watch } from 'vue'
import EnterpriseListPage from '../../components/shared/EnterpriseListPage.vue'
import { normalizeRequestForUi } from '../../utils/requestViewModel.js'
function extractRowDate(value) {
const matched = String(value || '').match(/\d{4}-\d{2}-\d{2}/)
return matched ? matched[0] : ''
}
export default {
name: 'RequestsView',
components: {
EnterpriseListPage
},
props: {
filteredRequests: { type: Array, required: true },
hasData: { type: Boolean, default: false },
loading: { type: Boolean, default: false },
error: { type: String, default: '' }
},
emits: ['ask', 'approve', 'reject', 'create-request', 'reload'],
setup(props, { emit }) {
const activeTab = ref('全部')
const tabs = ['全部', '草稿', '待提交', '审批中', '待补充', '待付款', '已完成']
const filters = ['报销状态', '报销类型', '所属主体']
const listKeyword = ref('')
const datePopover = ref(false)
const rangeStart = ref('')
const rangeEnd = ref('')
const appliedStart = ref('')
const appliedEnd = ref('')
const dateRangeLabel = computed(() => {
if (appliedStart.value && appliedEnd.value) {
return `${appliedStart.value} ~ ${appliedEnd.value}`
}
return '选择时间段'
})
function applyDateRange() {
if (!rangeStart.value || !rangeEnd.value) {
return
}
appliedStart.value = rangeStart.value
appliedEnd.value = rangeEnd.value
datePopover.value = false
}
const rows = computed(() =>
props.filteredRequests
.map((item) => normalizeRequestForUi(item))
.filter(Boolean)
)
const currentPage = ref(1)
const pageSize = ref(10)
const pageSizes = [10, 20, 50]
const pageSizeOptions = pageSizes.map((size) => ({ label: `${size} 条/页`, value: size }))
function changePageSize(size) {
pageSize.value = size
currentPage.value = 1
}
const filteredRows = computed(() => {
const keyword = listKeyword.value.trim().toLowerCase()
return rows.value.filter((row) => {
const matchesKeyword =
!keyword
|| [
row.id,
row.documentNo,
row.typeLabel,
row.reason,
row.sceneTarget,
row.relatedCustomer,
row.riskSummary
]
.filter(Boolean)
.join('')
.toLowerCase()
.includes(keyword)
const applyDate = extractRowDate(row.applyTime)
const matchesDateRange =
!appliedStart.value
|| !appliedEnd.value
|| (applyDate && applyDate >= appliedStart.value && applyDate <= appliedEnd.value)
const matchesTab =
activeTab.value === '全部'
|| (activeTab.value === '草稿' && row.approvalKey === 'draft')
|| (activeTab.value === '待提交' && row.approvalKey === 'supplement' && row.status === 'returned')
|| (activeTab.value === '审批中' && row.approvalKey === 'in_progress')
|| (activeTab.value === '待补充' && row.approvalKey === 'supplement' && row.status !== 'returned')
|| (activeTab.value === '待付款' && row.approvalKey === 'pending_payment')
|| (activeTab.value === '已完成' && row.approvalKey === 'completed')
return matchesKeyword && matchesDateRange && matchesTab
})
})
const totalCount = computed(() => filteredRows.value.length)
const totalPages = computed(() => Math.max(1, Math.ceil(totalCount.value / pageSize.value)))
const visibleRows = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return filteredRows.value.slice(start, start + pageSize.value)
})
const showTable = computed(() => !props.loading && !props.error && visibleRows.value.length > 0)
const showEmpty = computed(() => !props.loading && !props.error && visibleRows.value.length === 0)
const hasListFilters = computed(() => {
return Boolean(
activeTab.value !== '全部'
|| listKeyword.value.trim()
|| appliedStart.value
|| appliedEnd.value
)
})
const emptyState = computed(() => {
if (!props.hasData) {
return {
eyebrow: '个人报销',
title: '还没有任何报销单据',
desc: '首张草稿或已提交的报销单会自动出现在这里,后续可以继续补充、提交和跟踪进度。',
icon: 'mdi mdi-receipt-text-plus-outline',
actionLabel: '',
actionIcon: '',
tone: 'theme',
artLabel: 'CLAIM',
tips: ['保存草稿后会自动回到这里', '支持草稿、待提交、审批中和已完成全流程管理']
}
}
return {
eyebrow: hasListFilters.value ? '筛选结果为空' : '状态列表为空',
title: hasListFilters.value ? '当前条件下没有匹配单据' : `${activeTab.value}”里暂时没有单据`,
desc: hasListFilters.value
? '可以清空关键词、时间段或状态筛选后再看看。'
: '当前状态下还没有可展示的报销记录,可以先发起一笔报销或切换到其他状态。',
icon: hasListFilters.value ? 'mdi mdi-magnify-scan' : 'mdi mdi-clipboard-text-clock-outline',
actionLabel: hasListFilters.value ? '清空筛选' : '',
actionIcon: hasListFilters.value ? 'mdi mdi-filter-remove-outline' : '',
tone: hasListFilters.value ? 'sky' : 'slate',
artLabel: hasListFilters.value ? 'FILTER' : 'QUEUE',
tips: hasListFilters.value
? ['关键词、时间段和状态会叠加生效', '可尝试搜索单号、事由或报销类型']
: ['已完成单据会保留在列表中便于追踪', '草稿、待提交、审批中和待补充会按真实状态实时归类']
}
})
function resetFilters() {
activeTab.value = '全部'
listKeyword.value = ''
datePopover.value = false
rangeStart.value = ''
rangeEnd.value = ''
appliedStart.value = ''
appliedEnd.value = ''
currentPage.value = 1
}
function handleEmptyAction() {
if (!props.hasData) {
emit('create-request')
return
}
resetFilters()
}
watch([activeTab, rows, listKeyword, appliedStart, appliedEnd], () => {
currentPage.value = 1
})
return {
emit,
activeTab,
tabs,
filters,
listKeyword,
datePopover,
rangeStart,
rangeEnd,
appliedStart,
appliedEnd,
dateRangeLabel,
applyDateRange,
rows,
currentPage,
pageSize,
pageSizes,
pageSizeOptions,
changePageSize,
filteredRows,
totalCount,
totalPages,
visibleRows,
showTable,
showEmpty,
emptyState,
resetFilters,
handleEmptyAction
}
}
}