import { computed, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' import { useNavigation, navItems } from './useNavigation.js' import { useRequests } from './useRequests.js' import { useToast } from './useToast.js' import { normalizeRequestForUi } from '../utils/requestViewModel.js' function isPlaceholderValue(value) { const text = String(value || '').trim() if (!text) { return true } return ['待补充', '暂无', '无', '未知', '处理中'].includes(text.replace(/\s+/g, '')) } function hasMissingAttachment(request) { const expenseItems = Array.isArray(request?.expenseItems) ? request.expenseItems : [] if (expenseItems.length) { return expenseItems.some((item) => !String(item?.invoiceId || item?.invoice_id || '').trim()) } const attachmentSummary = String(request?.attachmentSummary || '').trim() const secondaryStatusValue = String(request?.secondaryStatusValue || '').trim() return /待|缺|未/.test(attachmentSummary) || /待|缺|未/.test(secondaryStatusValue) } function hasPendingInfo(request) { if (!request) { return false } if (request.approvalKey === 'draft' || request.approvalKey === 'supplement') { return true } if (!Number.isFinite(Number(request.amountValue)) || Number(request.amountValue) <= 0) { return true } return [ request.profileDepartment, request.profilePosition, request.profileGrade, request.profileManager, request.reason, request.occurredDisplay ].some(isPlaceholderValue) } function resolveDetailAlertTone(request) { if (request?.approvalKey === 'completed') return 'success' if (request?.approvalKey === 'rejected') return 'danger' return 'warning' } function buildDetailAlerts(request) { if (!request) { return [] } const alerts = [] const nodeLabel = String(request.node || request.approval || '').trim() if (nodeLabel) { alerts.push({ label: nodeLabel, tone: resolveDetailAlertTone(request) }) } if (hasMissingAttachment(request)) { alerts.push({ label: '缺少票据', tone: 'warning' }) } if (hasPendingInfo(request)) { alerts.push({ label: '待补信息', tone: 'warning' }) } return alerts.filter((item, index, list) => list.findIndex((entry) => entry.label === item.label) === index).slice(0, 3) } export function useAppShell() { const route = useRoute() const router = useRouter() const smartEntryOpen = ref(false) const smartEntryContext = ref({ prompt: '', source: 'requests', request: null, files: [], conversation: null }) const smartEntrySessionId = ref(0) const { activeView, currentView, setView } = useNavigation() const { requests, loading: requestsLoading, error: requestsError, search, filters, ranges, activeRange, filteredRequests, approveRequest, rejectRequest, reload: reloadRequests } = useRequests() const { toast } = useToast() const customRange = ref({ start: '2024-07-06', end: '2024-07-12' }) const selectedRequest = computed(() => { const requestId = String(route.params.requestId || '') if (!requestId) { return null } const rawRequest = requests.value.find( (item) => String(item.claimId || '').trim() === requestId || String(item.id || '').trim() === requestId ) return normalizeRequestForUi(rawRequest) }) const detailMode = computed(() => route.name === 'app-request-detail') const detailAlerts = computed(() => (detailMode.value ? buildDetailAlerts(selectedRequest.value) : [])) const topBarView = computed(() => { if (detailMode.value) { return { title: '报销单详情', desc: '查看报销明细、票据材料、审批进度与风险提示。' } } return currentView.value }) const requestSummary = computed(() => filteredRequests.value.reduce( (summary, item) => { const request = normalizeRequestForUi(item) if (!request) { return summary } summary.total += 1 if (request.approvalKey === 'draft') { summary.draft += 1 } else if (request.approvalKey === 'in_progress') { summary.inProgress += 1 } else if (request.approvalKey === 'supplement') { summary.supplement += 1 } else if (request.approvalKey === 'completed') { summary.completed += 1 } return summary }, { total: 0, draft: 0, inProgress: 0, supplement: 0, completed: 0 } ) ) function handleApprove(request) { const message = approveRequest(request) toast(message) } function handleReject(request) { const message = rejectRequest(request) toast(message) } function handleNavigate(view) { smartEntryOpen.value = false setView(view) } function openTravelCreate() { smartEntryOpen.value = true smartEntryContext.value = { prompt: '', source: 'topbar', request: null, files: [], conversation: null } smartEntrySessionId.value += 1 } function openSmartEntry(payload = {}) { smartEntryOpen.value = true smartEntryContext.value = { prompt: payload.prompt ?? '', source: payload.source ?? 'workbench', request: payload.request ?? selectedRequest.value, files: Array.isArray(payload.files) ? payload.files : [], conversation: payload.conversation ?? null } smartEntrySessionId.value += 1 } function closeSmartEntry() { smartEntryOpen.value = false } async function handleDraftSaved(payload = {}) { const claimNo = String(payload.claimNo || payload.claim_no || '').trim() const status = String(payload.status || payload.claimStatus || '').trim() const approvalStage = String(payload.approvalStage || payload.approval_stage || '').trim() smartEntryOpen.value = false await reloadRequests() if (status === 'submitted') { toast(`${claimNo || '该'}单据已提交审批${approvalStage ? `,当前节点:${approvalStage}` : ''}。`) } else { toast(`${claimNo || '该'}单据已保存到草稿,请到报销页面查看。`) } router.push({ name: 'app-requests' }) } function openRequestDetail(request) { router.push({ name: 'app-request-detail', params: { requestId: request.claimId || request.id } }) } function closeRequestDetail() { router.push({ name: 'app-requests' }) } async function handleRequestUpdated() { await reloadRequests() } async function handleRequestDeleted() { await reloadRequests() router.push({ name: 'app-requests' }) } return { activeRange, activeView, closeRequestDetail, closeSmartEntry, currentView, customRange, detailMode, filteredRequests, filters, handleApprove, handleDraftSaved, handleNavigate, handleReject, handleRequestDeleted, handleRequestUpdated, navItems, openRequestDetail, openSmartEntry, openTravelCreate, ranges, requestSummary, requestsError, requestsLoading, reloadRequests, requests, search, selectedRequest, setView, smartEntryContext, smartEntryOpen, smartEntrySessionId, detailAlerts, toast, topBarView } }