2026-05-26 09:15:14 +08:00
|
|
|
import { ref } from 'vue'
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
APPLICATION_TRANSPORT_MODE_OPTIONS,
|
|
|
|
|
buildApplicationPreviewRows,
|
|
|
|
|
buildLocalApplicationPreviewMessage,
|
2026-06-01 17:07:14 +08:00
|
|
|
normalizeApplicationPreview,
|
|
|
|
|
refreshApplicationPreviewTransportEstimate
|
2026-05-26 09:15:14 +08:00
|
|
|
} from '../../utils/expenseApplicationPreview.js'
|
2026-06-01 17:07:14 +08:00
|
|
|
import { waitForMockApplicationTransportQuote } from '../../utils/expenseApplicationEstimate.js'
|
2026-05-30 15:46:51 +08:00
|
|
|
import {
|
|
|
|
|
buildWorkbenchDateLabel,
|
|
|
|
|
canApplyWorkbenchDateSelection,
|
|
|
|
|
getTodayDateValue
|
|
|
|
|
} from '../../utils/workbenchComposerDate.js'
|
2026-05-26 09:15:14 +08:00
|
|
|
|
2026-05-30 15:46:51 +08:00
|
|
|
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 {
|
2026-05-26 09:15:14 +08:00
|
|
|
messageId: '',
|
|
|
|
|
fieldKey: '',
|
2026-05-30 15:46:51 +08:00
|
|
|
draftValue: '',
|
|
|
|
|
dateMode: 'single',
|
|
|
|
|
singleDate: getTodayDateValue(),
|
|
|
|
|
rangeStartDate: getTodayDateValue(),
|
2026-06-01 17:07:14 +08:00
|
|
|
rangeEndDate: getTodayDateValue(),
|
|
|
|
|
committing: false
|
2026-05-30 15:46:51 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 17:07:14 +08:00
|
|
|
function shouldRefreshTransportEstimate(fieldKey) {
|
|
|
|
|
return ['transportMode', 'time', 'location', 'days'].includes(fieldKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildTransportEstimatePendingPreview(preview = {}) {
|
|
|
|
|
const fields = preview?.fields || {}
|
|
|
|
|
return normalizeApplicationPreview({
|
|
|
|
|
...preview,
|
|
|
|
|
fields: {
|
|
|
|
|
...fields,
|
|
|
|
|
transportPolicy: '正在查询交通参考票价...',
|
|
|
|
|
policyEstimate: '正在同步费用测算...',
|
|
|
|
|
transportEstimatedAmount: '查询中'
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 15:46:51 +08:00
|
|
|
export function useApplicationPreviewEditor({ persistSessionState, toast } = {}) {
|
|
|
|
|
const applicationPreviewEditor = ref(buildEmptyEditor())
|
2026-05-26 09:15:14 +08:00
|
|
|
|
|
|
|
|
function resolveApplicationPreviewRows(message) {
|
|
|
|
|
return buildApplicationPreviewRows(message?.applicationPreview || {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resolveApplicationPreviewEditorControl(fieldKey) {
|
2026-05-30 15:46:51 +08:00
|
|
|
if (fieldKey === 'transportMode') return 'select'
|
|
|
|
|
if (fieldKey === 'time') return 'date'
|
|
|
|
|
return 'text'
|
2026-05-26 09:15:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 || '')
|
2026-05-30 15:46:51 +08:00
|
|
|
const dateState = fieldKey === 'time' ? parseEditorDateValue(normalizedValue) : {}
|
2026-05-26 09:15:14 +08:00
|
|
|
applicationPreviewEditor.value = {
|
|
|
|
|
messageId: String(message.id || ''),
|
|
|
|
|
fieldKey,
|
|
|
|
|
draftValue: fieldKey === 'transportMode' && !APPLICATION_TRANSPORT_MODE_OPTIONS.includes(normalizedValue)
|
|
|
|
|
? ''
|
2026-05-30 15:46:51 +08:00
|
|
|
: normalizedValue,
|
2026-06-01 17:07:14 +08:00
|
|
|
committing: false,
|
2026-05-30 15:46:51 +08:00
|
|
|
...dateState
|
2026-05-26 09:15:14 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cancelApplicationPreviewEditor() {
|
2026-05-30 15:46:51 +08:00
|
|
|
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
|
|
|
|
|
})
|
2026-05-26 09:15:14 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-01 17:07:14 +08:00
|
|
|
async function commitApplicationPreviewEditor(message) {
|
2026-05-26 09:15:14 +08:00
|
|
|
const editor = applicationPreviewEditor.value
|
2026-06-01 17:07:14 +08:00
|
|
|
if (editor.committing) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2026-05-26 09:15:14 +08:00
|
|
|
if (!message?.applicationPreview || String(editor.messageId || '') !== String(message.id || '') || !editor.fieldKey) {
|
|
|
|
|
cancelApplicationPreviewEditor()
|
|
|
|
|
return false
|
|
|
|
|
}
|
2026-06-01 17:07:14 +08:00
|
|
|
applicationPreviewEditor.value = {
|
|
|
|
|
...editor,
|
|
|
|
|
committing: true
|
|
|
|
|
}
|
2026-05-26 09:15:14 +08:00
|
|
|
|
2026-05-30 15:46:51 +08:00
|
|
|
const nextValue = editor.fieldKey === 'time'
|
|
|
|
|
? buildApplicationPreviewDateDraftValue()
|
|
|
|
|
: String(editor.draftValue || '').trim()
|
|
|
|
|
if (editor.fieldKey === 'time' && !nextValue) {
|
|
|
|
|
toast?.('请先选择有效日期。')
|
2026-06-01 17:07:14 +08:00
|
|
|
applicationPreviewEditor.value = {
|
|
|
|
|
...applicationPreviewEditor.value,
|
|
|
|
|
committing: false
|
|
|
|
|
}
|
2026-05-30 15:46:51 +08:00
|
|
|
return false
|
|
|
|
|
}
|
2026-05-26 09:15:14 +08:00
|
|
|
const nextPreview = normalizeApplicationPreview({
|
|
|
|
|
...message.applicationPreview,
|
|
|
|
|
fields: {
|
|
|
|
|
...(message.applicationPreview.fields || {}),
|
|
|
|
|
[editor.fieldKey]: nextValue
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-06-01 17:07:14 +08:00
|
|
|
const needRefreshTransport = shouldRefreshTransportEstimate(editor.fieldKey) && String(nextPreview.fields?.transportMode || '').trim()
|
|
|
|
|
message.applicationPreview = needRefreshTransport
|
|
|
|
|
? buildTransportEstimatePendingPreview(nextPreview)
|
|
|
|
|
: nextPreview
|
|
|
|
|
message.text = buildLocalApplicationPreviewMessage(message.applicationPreview)
|
2026-05-26 09:15:14 +08:00
|
|
|
cancelApplicationPreviewEditor()
|
|
|
|
|
persistSessionState?.()
|
2026-06-01 17:07:14 +08:00
|
|
|
if (needRefreshTransport) {
|
|
|
|
|
await waitForMockApplicationTransportQuote({
|
|
|
|
|
transportMode: nextPreview.fields.transportMode,
|
|
|
|
|
location: nextPreview.fields.matchedCity || nextPreview.fields.location,
|
|
|
|
|
time: nextPreview.fields.time
|
|
|
|
|
})
|
|
|
|
|
const refreshedPreview = refreshApplicationPreviewTransportEstimate(nextPreview)
|
|
|
|
|
message.applicationPreview = refreshedPreview
|
|
|
|
|
message.text = buildLocalApplicationPreviewMessage(refreshedPreview)
|
|
|
|
|
persistSessionState?.()
|
|
|
|
|
toast?.('已更新出行方式和费用测算。')
|
|
|
|
|
return true
|
|
|
|
|
}
|
2026-05-26 09:15:14 +08:00
|
|
|
toast?.('已更新核对表内容。')
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 17:07:14 +08:00
|
|
|
async function commitApplicationPreviewDateEditor(message) {
|
2026-05-30 15:46:51 +08:00
|
|
|
if (!canApplyApplicationPreviewDateSelection()) {
|
|
|
|
|
toast?.('请确认结束日期不早于开始日期。')
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
return commitApplicationPreviewEditor(message)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-26 09:15:14 +08:00
|
|
|
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,
|
|
|
|
|
isApplicationPreviewEditing,
|
2026-05-30 15:46:51 +08:00
|
|
|
isApplicationPreviewDateEditorOpen,
|
2026-05-26 09:15:14 +08:00
|
|
|
openApplicationPreviewEditor,
|
|
|
|
|
commitApplicationPreviewEditor,
|
2026-05-30 15:46:51 +08:00
|
|
|
commitApplicationPreviewDateEditor,
|
2026-05-26 09:15:14 +08:00
|
|
|
cancelApplicationPreviewEditor,
|
2026-05-30 15:46:51 +08:00
|
|
|
setApplicationPreviewDateMode,
|
|
|
|
|
canApplyApplicationPreviewDateSelection,
|
2026-05-26 09:15:14 +08:00
|
|
|
handleApplicationPreviewEditorKeydown
|
|
|
|
|
}
|
|
|
|
|
}
|