diff --git a/web/src/assets/styles/views/travel-reimbursement-create-view.css b/web/src/assets/styles/views/travel-reimbursement-create-view.css index ff558bf..2b79ceb 100644 --- a/web/src/assets/styles/views/travel-reimbursement-create-view.css +++ b/web/src/assets/styles/views/travel-reimbursement-create-view.css @@ -3102,6 +3102,25 @@ flex-wrap: wrap; } +.review-upload-decision-modal { + display: grid; + gap: 18px; +} + +.review-upload-decision-copy { + display: grid; + gap: 10px; +} + +.review-upload-decision-actions { + justify-content: stretch; +} + +.review-upload-decision-actions .primary-dialog-btn, +.review-upload-decision-actions .secondary-dialog-btn { + flex: 1 1 168px; +} + .review-edit-modal { max-height: min(860px, calc(100vh - 48px)); display: grid; @@ -3503,6 +3522,10 @@ justify-content: stretch; } + .review-upload-decision-actions { + width: 100%; + } + .primary-dialog-btn, .secondary-dialog-btn, .danger-dialog-btn { diff --git a/web/src/composables/useAppShell.js b/web/src/composables/useAppShell.js index aca536b..20dc79a 100644 --- a/web/src/composables/useAppShell.js +++ b/web/src/composables/useAppShell.js @@ -3,9 +3,13 @@ import { useRoute, useRouter } from 'vue-router' import { useNavigation, navItems } from './useNavigation.js' import { useRequests } from './useRequests.js' +import { useSystemState } from './useSystemState.js' import { useToast } from './useToast.js' +import { fetchLatestConversation } from '../services/orchestrator.js' import { normalizeRequestForUi } from '../utils/requestViewModel.js' +const SESSION_TYPE_EXPENSE = 'expense' + function isPlaceholderValue(value) { const text = String(value || '').trim() if (!text) { @@ -101,6 +105,7 @@ export function useAppShell() { rejectRequest, reload: reloadRequests } = useRequests() + const { currentUser } = useSystemState() const { toast } = useToast() const customRange = ref({ start: '2024-07-06', end: '2024-07-12' }) @@ -179,7 +184,34 @@ export function useAppShell() { smartEntrySessionId.value += 1 } - function openSmartEntry(payload = {}) { + function resolveCurrentUserId() { + const user = currentUser.value || {} + return String(user.username || user.name || 'anonymous').trim() || 'anonymous' + } + + async function resolveSmartEntryConversation(payload = {}) { + if (payload.conversation) { + return payload.conversation + } + + if (!payload.restoreLatestConversation) { + return null + } + + try { + const latestPayload = await fetchLatestConversation(resolveCurrentUserId(), SESSION_TYPE_EXPENSE, { + preferRecoverable: true + }) + return latestPayload?.found ? latestPayload.conversation || null : null + } catch (error) { + console.warn('Failed to restore latest expense conversation for smart entry:', error) + toast(error?.message || '恢复最近报销会话失败,请稍后重试。') + return null + } + } + + async function openSmartEntry(payload = {}) { + const conversation = await resolveSmartEntryConversation(payload) smartEntryOpen.value = true smartEntryContext.value = { @@ -187,7 +219,7 @@ export function useAppShell() { source: payload.source ?? 'workbench', request: payload.request ?? selectedRequest.value, files: Array.isArray(payload.files) ? payload.files : [], - conversation: payload.conversation ?? null + conversation } smartEntrySessionId.value += 1 } diff --git a/web/src/views/TravelReimbursementCreateView.vue b/web/src/views/TravelReimbursementCreateView.vue index d53055d..9283d6f 100644 --- a/web/src/views/TravelReimbursementCreateView.vue +++ b/web/src/views/TravelReimbursementCreateView.vue @@ -264,16 +264,17 @@ -