Files
X-Financial/web/src/composables/useRequests.js
caoxiaozhu ee730aa31c feat(web): AI 工作台文件预览/附件关联任务与草稿分支
- 新增 WorkbenchAiFilePreviewDialog 附件预览对话框及 useWorkbenchAiFilePreview,附件支持点击预览
- 新增 attachmentAssociationJobs/linkedReimbursementDraftJobs 前端服务与对应 composable,接入后台任务轮询与状态展示
- 新增 travelReimbursementDraftBranchModel 草稿分支模型,报销关联门控支持跳过/选择草稿
- PersonalWorkbenchAiMode 及各 composable(expense/document/steward/application-preview/attachment-association)重构适配,WorkbenchAiComposer/FileStrip 样式与交互完善
- DocumentsCenter/ReceiptFolder/TravelReimbursementCreate 等视图及 scripts 重构,风险/差旅规划/审批等工具适配
- 新增/更新前端测试:application-result-card、reimbursement-list-preview-fetch、guided-flow、composer-components 等
2026-06-24 10:42:50 +08:00

153 lines
3.9 KiB
JavaScript

import { computed, reactive, ref } from 'vue'
import {
REIMBURSEMENT_LIST_PREVIEW_PARAMS,
extractExpenseClaimItems,
fetchExpenseClaims
} from '../services/reimbursements.js'
import { formatDate, toDate } from './requests/requestShared.js'
import { mapExpenseClaimToRequest } from './requests/requestClaimMapper.js'
export { mapExpenseClaimToRequest } from './requests/requestClaimMapper.js'
function getWeekStart(date) {
const nextDate = new Date(date)
const day = nextDate.getDay() || 7
nextDate.setHours(0, 0, 0, 0)
nextDate.setDate(nextDate.getDate() - day + 1)
return nextDate
}
function getRecentDaysStart(date, days) {
const nextDate = new Date(date)
nextDate.setHours(0, 0, 0, 0)
nextDate.setDate(nextDate.getDate() - Math.max(0, Number(days || 1) - 1))
return nextDate
}
function resolveRangeMatch(activeRange, item) {
if (activeRange === 'custom' || activeRange === '本月') {
if (activeRange !== '本月') {
return true
}
}
const targetDate = toDate(item?.submittedAt || item?.createdAt || item?.occurredAt)
if (!targetDate) {
return true
}
const now = new Date()
const targetDay = formatDate(targetDate)
if (activeRange === '今日') {
return targetDay === formatDate(now)
}
if (activeRange === '近10日') {
const recentStart = getRecentDaysStart(now, 10)
return targetDate >= recentStart && targetDate <= now
}
if (activeRange === '本周') {
const weekStart = getWeekStart(now)
const nextWeekStart = new Date(weekStart)
nextWeekStart.setDate(nextWeekStart.getDate() + 7)
return targetDate >= weekStart && targetDate < nextWeekStart
}
if (activeRange === '本月') {
return (
targetDate.getFullYear() === now.getFullYear()
&& targetDate.getMonth() === now.getMonth()
)
}
return true
}
export function useRequests() {
const requests = ref([])
const loading = ref(false)
const loaded = ref(false)
const error = ref('')
const search = ref('')
const filters = reactive({ entity: '全部主体', category: '全部类型', risk: '全部状态' })
const ranges = ['今日', '近10日', '本周', '本月']
const activeRange = ref('近10日')
const filteredRequests = computed(() => {
const key = search.value.trim().toLowerCase()
return requests.value.filter((item) => {
const searchText = [
item.id,
item.person,
item.typeLabel,
item.title,
item.sceneTarget,
item.riskSummary
]
.filter(Boolean)
.join('')
.toLowerCase()
const matchesSearch = !key || searchText.includes(key)
const matchesRange = resolveRangeMatch(activeRange.value, item)
return matchesSearch && matchesRange
})
})
async function reload(options = {}) {
const silent = Boolean(options?.silent)
if (!silent) {
loading.value = true
error.value = ''
}
try {
const payload = await fetchExpenseClaims(REIMBURSEMENT_LIST_PREVIEW_PARAMS)
requests.value = extractExpenseClaimItems(payload).map((item) => mapExpenseClaimToRequest(item))
loaded.value = true
} catch (nextError) {
if (!silent) {
requests.value = []
}
error.value = nextError instanceof Error ? nextError.message : '个人报销列表加载失败。'
} finally {
if (!silent) {
loading.value = false
}
}
}
function approveRequest(request) {
return `${request.id} 未执行本地状态变更,列表当前只展示后端真实数据。`
}
function rejectRequest(request) {
return `${request.id} 未执行本地状态变更,列表当前只展示后端真实数据。`
}
function ensureLoaded() {
return loaded.value ? Promise.resolve() : reload()
}
return {
requests,
loading,
loaded,
error,
search,
filters,
ranges,
activeRange,
filteredRequests,
approveRequest,
rejectRequest,
ensureLoaded,
reload
}
}