import { ref } from 'vue' import { APPLICATION_TRANSPORT_MODE_OPTIONS, applyApplicationPolicyEstimateError, applyApplicationPolicyEstimateResult, buildApplicationPreviewRows, buildApplicationPolicyEstimateRequest, buildLocalApplicationPreviewMessage, normalizeApplicationPreview, resolveApplicationDaysFromDateRange, refreshApplicationPreviewTransportEstimate } from '../../utils/expenseApplicationPreview.js' import { buildWorkbenchDateLabel, canApplyWorkbenchDateSelection, getTodayDateValue } from '../../utils/workbenchComposerDate.js' function parseEditorDateValue(value) { const text = String(value || '').trim() const matches = [...text.matchAll(/20\d{2}-\d{1,2}-\d{1,2}/g)].map((item) => item[0]) const startDate = matches[0] || getTodayDateValue() const endDate = matches[1] || startDate return { dateMode: matches.length > 1 && startDate !== endDate ? 'range' : 'single', singleDate: startDate, rangeStartDate: startDate, rangeEndDate: endDate } } function buildEmptyEditor() { return { messageId: '', fieldKey: '', draftValue: '', dateMode: 'single', singleDate: getTodayDateValue(), rangeStartDate: getTodayDateValue(), rangeEndDate: getTodayDateValue(), committing: false } } function shouldRefreshTransportEstimate(fieldKey) { return ['transportMode', 'time', 'location', 'days'].includes(fieldKey) } function resolveEditorCurrentUser(currentUser) { if (currentUser && typeof currentUser === 'object' && 'value' in currentUser) { return currentUser.value || {} } return currentUser || {} } function buildEditedApplicationPreviewFields(fields = {}, editor = {}, nextValue = '') { const nextFields = { ...fields, [editor.fieldKey]: nextValue } if (editor.fieldKey === 'time') { const resolvedDays = resolveApplicationDaysFromDateRange(nextValue) if (resolvedDays) { nextFields.days = resolvedDays } } return nextFields } function buildTransportEstimatePendingPreview(preview = {}) { const fields = preview?.fields || {} return normalizeApplicationPreview({ ...preview, fields: { ...fields, transportPolicy: '正在预估交通费用...', policyEstimate: '正在同步费用测算...', transportEstimatedAmount: '查询中' } }) } export function useApplicationPreviewEditor({ persistSessionState, toast, calculateTravelReimbursement, currentUser } = {}) { const applicationPreviewEditor = ref(buildEmptyEditor()) async function refreshApplicationPreviewEstimate(preview = {}) { const user = resolveEditorCurrentUser(currentUser) const estimateRequest = buildApplicationPolicyEstimateRequest(preview, user) if (estimateRequest.canCalculate && typeof calculateTravelReimbursement === 'function') { try { const result = await calculateTravelReimbursement(estimateRequest.payload) return applyApplicationPolicyEstimateResult(preview, result, user) } catch (error) { console.warn('Application preview estimate refresh failed:', error) return applyApplicationPolicyEstimateError(preview, error, user) } } return refreshApplicationPreviewTransportEstimate(preview) } function resolveApplicationPreviewRows(message) { return buildApplicationPreviewRows(message?.applicationPreview || {}) } function resolveApplicationPreviewEditorControl(fieldKey) { if (fieldKey === 'transportMode') return 'select' if (fieldKey === 'time') return 'date' return 'text' } function resolveApplicationPreviewEditorOptions(fieldKey) { return fieldKey === 'transportMode' ? APPLICATION_TRANSPORT_MODE_OPTIONS : [] } function isApplicationPreviewEditing(message, fieldKey) { return ( String(applicationPreviewEditor.value.messageId || '') === String(message?.id || '') && applicationPreviewEditor.value.fieldKey === fieldKey ) } function openApplicationPreviewEditor(message, fieldKey, value) { if (!message?.applicationPreview || !fieldKey) return const targetRow = buildApplicationPreviewRows(message.applicationPreview) .find((row) => row.key === fieldKey) if (targetRow && targetRow.editable === false) return const normalizedValue = String(value || '').trim() === '待补充' ? '' : String(value || '') const dateState = fieldKey === 'time' ? parseEditorDateValue(normalizedValue) : {} applicationPreviewEditor.value = { messageId: String(message.id || ''), fieldKey, draftValue: fieldKey === 'transportMode' && !APPLICATION_TRANSPORT_MODE_OPTIONS.includes(normalizedValue) ? '' : normalizedValue, committing: false, ...dateState } } function cancelApplicationPreviewEditor() { applicationPreviewEditor.value = buildEmptyEditor() } function isApplicationPreviewDateEditorOpen(message) { return isApplicationPreviewEditing(message, 'time') } function setApplicationPreviewDateMode(mode) { applicationPreviewEditor.value.dateMode = mode === 'range' ? 'range' : 'single' } function canApplyApplicationPreviewDateSelection() { const editor = applicationPreviewEditor.value return canApplyWorkbenchDateSelection({ mode: editor.dateMode, singleDate: editor.singleDate, rangeStartDate: editor.rangeStartDate, rangeEndDate: editor.rangeEndDate }) } function buildApplicationPreviewDateDraftValue() { const editor = applicationPreviewEditor.value return buildWorkbenchDateLabel({ mode: editor.dateMode, singleDate: editor.singleDate, rangeStartDate: editor.rangeStartDate, rangeEndDate: editor.rangeEndDate }) } async function commitApplicationPreviewEditor(message) { const editor = applicationPreviewEditor.value if (editor.committing) { return false } if (!message?.applicationPreview || String(editor.messageId || '') !== String(message.id || '') || !editor.fieldKey) { cancelApplicationPreviewEditor() return false } applicationPreviewEditor.value = { ...editor, committing: true } const nextValue = editor.fieldKey === 'time' ? buildApplicationPreviewDateDraftValue() : String(editor.draftValue || '').trim() if (editor.fieldKey === 'time' && !nextValue) { toast?.('请先选择有效日期。') applicationPreviewEditor.value = { ...applicationPreviewEditor.value, committing: false } return false } const nextPreview = normalizeApplicationPreview({ ...message.applicationPreview, fields: buildEditedApplicationPreviewFields( message.applicationPreview.fields || {}, editor, nextValue ) }) const needRefreshEstimate = shouldRefreshTransportEstimate(editor.fieldKey) message.applicationPreview = needRefreshEstimate ? buildTransportEstimatePendingPreview(nextPreview) : nextPreview message.text = buildLocalApplicationPreviewMessage(message.applicationPreview) cancelApplicationPreviewEditor() persistSessionState?.() if (needRefreshEstimate) { const refreshedPreview = await refreshApplicationPreviewEstimate(nextPreview) message.applicationPreview = refreshedPreview message.text = buildLocalApplicationPreviewMessage(refreshedPreview) persistSessionState?.() toast?.('已更新出行方式和费用测算。') return true } toast?.('已更新核对表内容。') return true } async function commitApplicationPreviewDateEditor(message) { if (!canApplyApplicationPreviewDateSelection()) { toast?.('请确认结束日期不早于开始日期。') return false } return commitApplicationPreviewEditor(message) } function handleApplicationPreviewEditorKeydown(event, message) { if (event.key === 'Enter') { event.preventDefault() commitApplicationPreviewEditor(message) return } if (event.key === 'Escape') { event.preventDefault() cancelApplicationPreviewEditor() } } return { applicationPreviewEditor, resolveApplicationPreviewRows, resolveApplicationPreviewEditorControl, resolveApplicationPreviewEditorOptions, refreshApplicationPreviewEstimate, isApplicationPreviewEditing, isApplicationPreviewDateEditorOpen, openApplicationPreviewEditor, commitApplicationPreviewEditor, commitApplicationPreviewDateEditor, cancelApplicationPreviewEditor, setApplicationPreviewDateMode, canApplyApplicationPreviewDateSelection, handleApplicationPreviewEditorKeydown } }