2026-05-06 22:23:42 +08:00
|
|
|
import { computed, ref, watch } from 'vue'
|
|
|
|
|
|
2026-05-13 06:52:30 +00:00
|
|
|
import TableEmptyState from '../../components/shared/TableEmptyState.vue'
|
2026-05-06 22:23:42 +08:00
|
|
|
import { normalizeRequestForUi } from '../../utils/requestViewModel.js'
|
2026-05-06 11:00:38 +08:00
|
|
|
|
2026-05-13 03:35:44 +00:00
|
|
|
function extractRowDate(value) {
|
|
|
|
|
const matched = String(value || '').match(/\d{4}-\d{2}-\d{2}/)
|
|
|
|
|
return matched ? matched[0] : ''
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 11:00:38 +08:00
|
|
|
export default {
|
|
|
|
|
name: 'RequestsView',
|
2026-05-13 06:52:30 +00:00
|
|
|
components: {
|
|
|
|
|
TableEmptyState
|
|
|
|
|
},
|
2026-05-06 11:00:38 +08:00
|
|
|
props: {
|
2026-05-13 03:35:44 +00:00
|
|
|
filteredRequests: { type: Array, required: true },
|
|
|
|
|
hasData: { type: Boolean, default: false },
|
|
|
|
|
loading: { type: Boolean, default: false },
|
|
|
|
|
error: { type: String, default: '' }
|
2026-05-06 22:23:42 +08:00
|
|
|
},
|
2026-05-13 03:35:44 +00:00
|
|
|
emits: ['ask', 'approve', 'reject', 'create-request', 'reload'],
|
2026-05-06 11:00:38 +08:00
|
|
|
setup(props, { emit }) {
|
|
|
|
|
const activeTab = ref('全部')
|
2026-05-13 03:35:44 +00:00
|
|
|
const tabs = ['全部', '草稿', '审批中', '待补充', '已完成']
|
|
|
|
|
const filters = ['报销状态', '报销类型', '所属主体']
|
|
|
|
|
const listKeyword = ref('')
|
2026-05-06 11:00:38 +08:00
|
|
|
|
|
|
|
|
const datePopover = ref(false)
|
|
|
|
|
const rangeStart = ref('')
|
|
|
|
|
const rangeEnd = ref('')
|
|
|
|
|
const appliedStart = ref('')
|
|
|
|
|
const appliedEnd = ref('')
|
|
|
|
|
|
|
|
|
|
const dateRangeLabel = computed(() => {
|
2026-05-06 22:23:42 +08:00
|
|
|
if (appliedStart.value && appliedEnd.value) {
|
|
|
|
|
return `${appliedStart.value} ~ ${appliedEnd.value}`
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 11:00:38 +08:00
|
|
|
return '选择时间段'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function applyDateRange() {
|
2026-05-06 22:23:42 +08:00
|
|
|
if (!rangeStart.value || !rangeEnd.value) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 11:00:38 +08:00
|
|
|
appliedStart.value = rangeStart.value
|
|
|
|
|
appliedEnd.value = rangeEnd.value
|
|
|
|
|
datePopover.value = false
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 22:23:42 +08:00
|
|
|
const rows = computed(() =>
|
|
|
|
|
props.filteredRequests
|
|
|
|
|
.map((item) => normalizeRequestForUi(item))
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
)
|
2026-05-06 11:00:38 +08:00
|
|
|
|
|
|
|
|
const currentPage = ref(1)
|
|
|
|
|
const pageSize = ref(10)
|
|
|
|
|
const pageSizes = [10, 20, 50]
|
|
|
|
|
const pageSizeOpen = ref(false)
|
|
|
|
|
|
|
|
|
|
function changePageSize(size) {
|
|
|
|
|
pageSize.value = size
|
|
|
|
|
pageSizeOpen.value = false
|
|
|
|
|
currentPage.value = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const filteredRows = computed(() => {
|
2026-05-13 03:35:44 +00:00
|
|
|
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 === 'in_progress')
|
|
|
|
|
|| (activeTab.value === '待补充' && row.approvalKey === 'supplement')
|
|
|
|
|
|| (activeTab.value === '已完成' && row.approvalKey === 'completed')
|
|
|
|
|
|
|
|
|
|
return matchesKeyword && matchesDateRange && matchesTab
|
|
|
|
|
})
|
2026-05-06 11:00:38 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
})
|
2026-05-13 03:35:44 +00:00
|
|
|
const showTable = computed(() => !props.loading && !props.error && visibleRows.value.length > 0)
|
|
|
|
|
const showEmpty = computed(() => !props.loading && !props.error && visibleRows.value.length === 0)
|
2026-05-13 06:52:30 +00:00
|
|
|
const hasListFilters = computed(() => {
|
|
|
|
|
return Boolean(
|
|
|
|
|
activeTab.value !== '全部'
|
|
|
|
|
|| listKeyword.value.trim()
|
|
|
|
|
|| appliedStart.value
|
|
|
|
|
|| appliedEnd.value
|
|
|
|
|
)
|
|
|
|
|
})
|
2026-05-13 03:35:44 +00:00
|
|
|
const emptyState = computed(() => {
|
|
|
|
|
if (!props.hasData) {
|
|
|
|
|
return {
|
2026-05-13 06:52:30 +00:00
|
|
|
eyebrow: '个人报销',
|
|
|
|
|
title: '还没有任何报销单据',
|
|
|
|
|
desc: '首张草稿或已提交的报销单会自动出现在这里,后续可以继续补充、提交和跟踪进度。',
|
|
|
|
|
icon: 'mdi mdi-receipt-text-plus-outline',
|
|
|
|
|
actionLabel: '发起报销',
|
|
|
|
|
actionIcon: 'mdi mdi-plus-circle-outline',
|
|
|
|
|
tone: 'emerald',
|
|
|
|
|
artLabel: 'CLAIM',
|
|
|
|
|
tips: ['保存草稿后会自动回到这里', '支持草稿、待提交、审批中和已完成全流程管理']
|
2026-05-13 03:35:44 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
2026-05-13 06:52:30 +00:00
|
|
|
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
|
|
|
|
|
? ['关键词、时间段和状态会叠加生效', '可尝试搜索单号、事由或报销类型']
|
|
|
|
|
: ['已完成单据会保留在列表中便于追踪', '草稿、审批中和待补充会按真实状态实时归类']
|
2026-05-13 03:35:44 +00:00
|
|
|
}
|
|
|
|
|
})
|
2026-05-06 11:00:38 +08:00
|
|
|
|
2026-05-13 06:52:30 +00:00
|
|
|
function resetFilters() {
|
|
|
|
|
activeTab.value = '全部'
|
|
|
|
|
listKeyword.value = ''
|
|
|
|
|
datePopover.value = false
|
|
|
|
|
rangeStart.value = ''
|
|
|
|
|
rangeEnd.value = ''
|
|
|
|
|
appliedStart.value = ''
|
|
|
|
|
appliedEnd.value = ''
|
|
|
|
|
pageSizeOpen.value = false
|
|
|
|
|
currentPage.value = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleEmptyAction() {
|
|
|
|
|
if (!props.hasData) {
|
|
|
|
|
emit('create-request')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resetFilters()
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 03:35:44 +00:00
|
|
|
watch([activeTab, rows, listKeyword, appliedStart, appliedEnd], () => {
|
2026-05-06 22:23:42 +08:00
|
|
|
currentPage.value = 1
|
|
|
|
|
})
|
2026-05-06 11:00:38 +08:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
emit,
|
|
|
|
|
activeTab,
|
|
|
|
|
tabs,
|
|
|
|
|
filters,
|
2026-05-13 03:35:44 +00:00
|
|
|
listKeyword,
|
2026-05-06 11:00:38 +08:00
|
|
|
datePopover,
|
|
|
|
|
rangeStart,
|
|
|
|
|
rangeEnd,
|
|
|
|
|
appliedStart,
|
|
|
|
|
appliedEnd,
|
|
|
|
|
dateRangeLabel,
|
|
|
|
|
applyDateRange,
|
|
|
|
|
rows,
|
|
|
|
|
currentPage,
|
|
|
|
|
pageSize,
|
|
|
|
|
pageSizes,
|
|
|
|
|
pageSizeOpen,
|
|
|
|
|
changePageSize,
|
|
|
|
|
filteredRows,
|
|
|
|
|
totalCount,
|
|
|
|
|
totalPages,
|
2026-05-13 03:35:44 +00:00
|
|
|
visibleRows,
|
|
|
|
|
showTable,
|
|
|
|
|
showEmpty,
|
2026-05-13 06:52:30 +00:00
|
|
|
emptyState,
|
|
|
|
|
resetFilters,
|
|
|
|
|
handleEmptyAction
|
2026-05-06 11:00:38 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|