feat(web): 申请单预览编辑器增强与报销流程细节适配
- useApplicationPreviewEditor 扩展字段编辑与校验,useTravelReimbursementApplicationPreviewDateEditor 微调日期处理 - travelReimbursementExpenseQueryModel/reimbursements 服务/expenseApplicationPreview 适配工号/邮箱字段与关联动作 - useWorkbenchAiApplicationPreviewFlow/usePersonalWorkbenchAiMode 接入关联门控后的预览流转 - TravelReimbursementCreateView 调整入口,TravelReimbursementMessageItem 适配 - 新增 expense-application-fast-preview 测试,更新 attachment-association-confirmation、review-drawer-switch 测试
This commit is contained in:
@@ -17,9 +17,15 @@ import {
|
||||
getTodayDateValue
|
||||
} from '../../utils/workbenchComposerDate.js'
|
||||
|
||||
function parseEditorDateValue(value) {
|
||||
function parseEditorDateMatches(value) {
|
||||
const text = String(value || '').trim()
|
||||
const matches = [...text.matchAll(/20\d{2}-\d{1,2}-\d{1,2}/g)].map((item) => item[0])
|
||||
return [...text.matchAll(/20\d{2}[-/.]\d{1,2}[-/.]\d{1,2}/g)]
|
||||
.map((item) => normalizeEditorIsoDate(item[0]))
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function parseEditorDateValue(value) {
|
||||
const matches = parseEditorDateMatches(value)
|
||||
const startDate = matches[0] || getTodayDateValue()
|
||||
const endDate = matches[1] || startDate
|
||||
return {
|
||||
@@ -30,6 +36,113 @@ function parseEditorDateValue(value) {
|
||||
}
|
||||
}
|
||||
|
||||
function isApplicationPreviewDateField(fieldKey = '') {
|
||||
return ['time', 'time_return'].includes(String(fieldKey || '').trim())
|
||||
}
|
||||
|
||||
function formatApplicationPreviewDateRange(startDate = '', endDate = '') {
|
||||
const start = String(startDate || '').trim()
|
||||
const end = String(endDate || '').trim()
|
||||
if (!start && !end) return ''
|
||||
if (!start) return end
|
||||
if (!end || end === start) return start
|
||||
return `${start} 至 ${end}`
|
||||
}
|
||||
|
||||
function normalizeEditorIsoDate(value = '') {
|
||||
const match = String(value || '').trim().match(/^(20\d{2})[-/.](\d{1,2})[-/.](\d{1,2})$/)
|
||||
if (!match) return ''
|
||||
return buildEditorIsoDate(Number(match[1]), Number(match[2]), Number(match[3]))
|
||||
}
|
||||
|
||||
function buildEditorIsoDate(year, month, day) {
|
||||
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) return ''
|
||||
const date = new Date(Date.UTC(year, month - 1, day))
|
||||
if (
|
||||
Number.isNaN(date.getTime()) ||
|
||||
date.getUTCFullYear() !== year ||
|
||||
date.getUTCMonth() + 1 !== month ||
|
||||
date.getUTCDate() !== day
|
||||
) {
|
||||
return ''
|
||||
}
|
||||
return [
|
||||
String(year).padStart(4, '0'),
|
||||
String(month).padStart(2, '0'),
|
||||
String(day).padStart(2, '0')
|
||||
].join('-')
|
||||
}
|
||||
|
||||
function parseEditorDateParts(value = '') {
|
||||
const normalized = normalizeEditorIsoDate(value)
|
||||
const match = normalized.match(/^(20\d{2})-(\d{2})-(\d{2})$/)
|
||||
if (!match) return null
|
||||
return {
|
||||
year: Number(match[1]),
|
||||
month: Number(match[2]),
|
||||
day: Number(match[3]),
|
||||
value: normalized
|
||||
}
|
||||
}
|
||||
|
||||
function addDaysToEditorDate(value = '', offset = 0) {
|
||||
const parts = parseEditorDateParts(value)
|
||||
if (!parts) return ''
|
||||
const date = new Date(Date.UTC(parts.year, parts.month - 1, parts.day))
|
||||
date.setUTCDate(date.getUTCDate() + offset)
|
||||
return buildEditorIsoDate(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate())
|
||||
}
|
||||
|
||||
function buildEditorDateFromMonthDay(referenceDate = '', month = 0, day = 0) {
|
||||
const reference = parseEditorDateParts(referenceDate) || parseEditorDateParts(getTodayDateValue())
|
||||
if (!reference) return ''
|
||||
const year = month < reference.month ? reference.year + 1 : reference.year
|
||||
return buildEditorIsoDate(year, month, day)
|
||||
}
|
||||
|
||||
function buildEditorDateFromDay(referenceDate = '', day = 0) {
|
||||
const reference = parseEditorDateParts(referenceDate) || parseEditorDateParts(getTodayDateValue())
|
||||
if (!reference) return ''
|
||||
let year = reference.year
|
||||
let month = reference.month
|
||||
let candidate = buildEditorIsoDate(year, month, day)
|
||||
if (candidate && candidate < reference.value) {
|
||||
month += 1
|
||||
if (month > 12) {
|
||||
month = 1
|
||||
year += 1
|
||||
}
|
||||
candidate = buildEditorIsoDate(year, month, day)
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
|
||||
function normalizeEditorDateInput(value = '', referenceDate = '') {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) return ''
|
||||
|
||||
const fullDate = text.match(/20\d{2}[-/.]\d{1,2}[-/.]\d{1,2}/)
|
||||
if (fullDate) {
|
||||
return normalizeEditorIsoDate(fullDate[0])
|
||||
}
|
||||
|
||||
const monthDay = text.match(/(\d{1,2})\s*月\s*(\d{1,2})\s*日?/) || text.match(/(\d{1,2})[/-](\d{1,2})/)
|
||||
if (monthDay) {
|
||||
return buildEditorDateFromMonthDay(referenceDate, Number(monthDay[1]), Number(monthDay[2]))
|
||||
}
|
||||
|
||||
const dayOnly = text.match(/^(\d{1,2})\s*日?$/)
|
||||
if (dayOnly) {
|
||||
return buildEditorDateFromDay(referenceDate, Number(dayOnly[1]))
|
||||
}
|
||||
|
||||
if (/大后天/.test(text)) return addDaysToEditorDate(referenceDate || getTodayDateValue(), 3)
|
||||
if (/后天/.test(text)) return addDaysToEditorDate(referenceDate || getTodayDateValue(), 2)
|
||||
if (/明天/.test(text)) return addDaysToEditorDate(referenceDate || getTodayDateValue(), 1)
|
||||
if (/今天/.test(text)) return normalizeEditorIsoDate(referenceDate || getTodayDateValue())
|
||||
return ''
|
||||
}
|
||||
|
||||
function buildEmptyEditor() {
|
||||
return {
|
||||
messageId: '',
|
||||
@@ -44,7 +157,7 @@ function buildEmptyEditor() {
|
||||
}
|
||||
|
||||
function shouldRefreshTransportEstimate(fieldKey) {
|
||||
return ['transportMode', 'time', 'location', 'days'].includes(fieldKey)
|
||||
return ['transportMode', 'time', 'time_return', 'location', 'days'].includes(fieldKey)
|
||||
}
|
||||
|
||||
function resolveEditorCurrentUser(currentUser) {
|
||||
@@ -55,11 +168,12 @@ function resolveEditorCurrentUser(currentUser) {
|
||||
}
|
||||
|
||||
function buildEditedApplicationPreviewFields(fields = {}, editor = {}, nextValue = '') {
|
||||
const isDateField = isApplicationPreviewDateField(editor.fieldKey)
|
||||
const nextFields = {
|
||||
...fields,
|
||||
[editor.fieldKey]: nextValue
|
||||
[isDateField ? 'time' : editor.fieldKey]: nextValue
|
||||
}
|
||||
if (editor.fieldKey === 'time') {
|
||||
if (isDateField) {
|
||||
const resolvedDays = resolveApplicationDaysFromDateRange(nextValue)
|
||||
if (resolvedDays) {
|
||||
nextFields.days = resolvedDays
|
||||
@@ -68,6 +182,45 @@ function buildEditedApplicationPreviewFields(fields = {}, editor = {}, nextValue
|
||||
return nextFields
|
||||
}
|
||||
|
||||
function buildApplicationPreviewDateSelectionValue(editor = {}) {
|
||||
return buildWorkbenchDateLabel({
|
||||
mode: editor.dateMode,
|
||||
singleDate: editor.singleDate,
|
||||
rangeStartDate: editor.rangeStartDate,
|
||||
rangeEndDate: editor.rangeEndDate
|
||||
})
|
||||
}
|
||||
|
||||
function buildApplicationPreviewTextDateValue(fields = {}, editor = {}) {
|
||||
const currentDateState = parseEditorDateValue(fields.time)
|
||||
const referenceDate = currentDateState.rangeStartDate || currentDateState.singleDate || getTodayDateValue()
|
||||
const typedDates = parseEditorDateMatches(editor.draftValue)
|
||||
if (typedDates.length >= 2) {
|
||||
return formatApplicationPreviewDateRange(typedDates[0], typedDates[typedDates.length - 1])
|
||||
}
|
||||
|
||||
const typedDate = typedDates[0] || normalizeEditorDateInput(editor.draftValue, referenceDate) || String(editor.draftValue || '').trim()
|
||||
if (!typedDate) {
|
||||
return ''
|
||||
}
|
||||
|
||||
if (editor.fieldKey === 'time_return') {
|
||||
return formatApplicationPreviewDateRange(currentDateState.rangeStartDate, typedDate)
|
||||
}
|
||||
|
||||
const currentEndDate = currentDateState.rangeEndDate || typedDate
|
||||
return formatApplicationPreviewDateRange(typedDate, currentEndDate)
|
||||
}
|
||||
|
||||
function buildApplicationPreviewEditorValue(fields = {}, editor = {}, options = {}) {
|
||||
if (!isApplicationPreviewDateField(editor.fieldKey)) {
|
||||
return String(editor.draftValue || '').trim()
|
||||
}
|
||||
return options.useDateSelection
|
||||
? buildApplicationPreviewDateSelectionValue(editor)
|
||||
: buildApplicationPreviewTextDateValue(fields, editor)
|
||||
}
|
||||
|
||||
function buildTransportEstimatePendingPreview(preview = {}) {
|
||||
const fields = preview?.fields || {}
|
||||
return normalizeApplicationPreview({
|
||||
@@ -110,7 +263,7 @@ export function useApplicationPreviewEditor({
|
||||
|
||||
function resolveApplicationPreviewEditorControl(fieldKey) {
|
||||
if (fieldKey === 'transportMode') return 'select'
|
||||
if (fieldKey === 'time') return 'date'
|
||||
if (isApplicationPreviewDateField(fieldKey)) return 'date'
|
||||
return 'text'
|
||||
}
|
||||
|
||||
@@ -131,7 +284,10 @@ export function useApplicationPreviewEditor({
|
||||
.find((row) => row.key === fieldKey)
|
||||
if (targetRow && targetRow.editable === false) return
|
||||
const normalizedValue = String(value || '').trim() === '待补充' ? '' : String(value || '')
|
||||
const dateState = fieldKey === 'time' ? parseEditorDateValue(normalizedValue) : {}
|
||||
const fields = message.applicationPreview.fields || {}
|
||||
const dateState = isApplicationPreviewDateField(fieldKey)
|
||||
? parseEditorDateValue(fields.time || normalizedValue)
|
||||
: {}
|
||||
applicationPreviewEditor.value = {
|
||||
messageId: String(message.id || ''),
|
||||
fieldKey,
|
||||
@@ -148,7 +304,10 @@ export function useApplicationPreviewEditor({
|
||||
}
|
||||
|
||||
function isApplicationPreviewDateEditorOpen(message) {
|
||||
return isApplicationPreviewEditing(message, 'time')
|
||||
return (
|
||||
isApplicationPreviewEditing(message, 'time') ||
|
||||
isApplicationPreviewEditing(message, 'time_return')
|
||||
)
|
||||
}
|
||||
|
||||
function setApplicationPreviewDateMode(mode) {
|
||||
@@ -165,17 +324,7 @@ export function useApplicationPreviewEditor({
|
||||
})
|
||||
}
|
||||
|
||||
function buildApplicationPreviewDateDraftValue() {
|
||||
const editor = applicationPreviewEditor.value
|
||||
return buildWorkbenchDateLabel({
|
||||
mode: editor.dateMode,
|
||||
singleDate: editor.singleDate,
|
||||
rangeStartDate: editor.rangeStartDate,
|
||||
rangeEndDate: editor.rangeEndDate
|
||||
})
|
||||
}
|
||||
|
||||
async function commitApplicationPreviewEditor(message) {
|
||||
async function commitApplicationPreviewEditor(message, options = {}) {
|
||||
const editor = applicationPreviewEditor.value
|
||||
if (editor.committing) {
|
||||
return false
|
||||
@@ -189,10 +338,12 @@ export function useApplicationPreviewEditor({
|
||||
committing: true
|
||||
}
|
||||
|
||||
const nextValue = editor.fieldKey === 'time'
|
||||
? buildApplicationPreviewDateDraftValue()
|
||||
: String(editor.draftValue || '').trim()
|
||||
if (editor.fieldKey === 'time' && !nextValue) {
|
||||
const nextValue = buildApplicationPreviewEditorValue(
|
||||
message.applicationPreview.fields || {},
|
||||
editor,
|
||||
options
|
||||
)
|
||||
if (isApplicationPreviewDateField(editor.fieldKey) && !nextValue) {
|
||||
toast?.('请先选择有效日期。')
|
||||
applicationPreviewEditor.value = {
|
||||
...applicationPreviewEditor.value,
|
||||
@@ -232,7 +383,7 @@ export function useApplicationPreviewEditor({
|
||||
toast?.('请确认结束日期不早于开始日期。')
|
||||
return false
|
||||
}
|
||||
return commitApplicationPreviewEditor(message)
|
||||
return commitApplicationPreviewEditor(message, { useDateSelection: true })
|
||||
}
|
||||
|
||||
function handleApplicationPreviewEditorKeydown(event, message) {
|
||||
|
||||
Reference in New Issue
Block a user