Files
X-Financial/web/src/utils/travelApplicationPlanning.js
caoxiaozhu ee730aa31c feat(web): AI 工作台文件预览/附件关联任务与草稿分支
- 新增 WorkbenchAiFilePreviewDialog 附件预览对话框及 useWorkbenchAiFilePreview,附件支持点击预览
- 新增 attachmentAssociationJobs/linkedReimbursementDraftJobs 前端服务与对应 composable,接入后台任务轮询与状态展示
- 新增 travelReimbursementDraftBranchModel 草稿分支模型,报销关联门控支持跳过/选择草稿
- PersonalWorkbenchAiMode 及各 composable(expense/document/steward/application-preview/attachment-association)重构适配,WorkbenchAiComposer/FileStrip 样式与交互完善
- DocumentsCenter/ReceiptFolder/TravelReimbursementCreate 等视图及 scripts 重构,风险/差旅规划/审批等工具适配
- 新增/更新前端测试:application-result-card、reimbursement-list-preview-fetch、guided-flow、composer-components 等
2026-06-24 10:42:50 +08:00

120 lines
4.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const TRAVEL_PLANNING_ACTION_GENERATE = 'generate_travel_application_plan'
export const TRAVEL_PLANNING_ACTION_SKIP = 'skip_travel_application_plan'
function normalizeText(value) {
return String(value || '').trim()
}
function isTravelApplication(applicationType = '') {
return /差旅|出差/.test(normalizeText(applicationType))
}
function extractDateParts(timeText = '') {
const dates = normalizeText(timeText).match(/20\d{2}[-/.]\d{1,2}[-/.]\d{1,2}/g) || []
return {
startDate: dates[0] || '',
endDate: dates[dates.length - 1] || dates[0] || ''
}
}
function resolveTravelPlanningContext(preview = {}, draftPayload = {}) {
const fields = preview?.fields && typeof preview.fields === 'object' ? preview.fields : {}
const applicationType = normalizeText(fields.applicationType)
if (!isTravelApplication(applicationType)) {
return null
}
const location = normalizeText(fields.location)
const time = normalizeText(fields.time)
if (!location || !time) {
return null
}
const dates = extractDateParts(time)
return {
applicationType,
location,
time,
startDate: dates.startDate,
endDate: dates.endDate,
days: normalizeText(fields.days),
transportMode: normalizeText(fields.transportMode),
reason: normalizeText(fields.reason),
claimNo: normalizeText(draftPayload?.claim_no || draftPayload?.claimNo)
}
}
export function buildTravelPlanningNudgeMessage(preview = {}, draftPayload = {}) {
const context = resolveTravelPlanningContext(preview, draftPayload)
if (!context) {
return ''
}
const timeCopy = context.startDate && context.endDate && context.startDate !== context.endDate
? `${context.startDate}${context.endDate}`
: context.time
const transportCopy = context.transportMode ? `${context.transportMode}时间窗口` : '、交通方式比选'
return [
`本次${context.location}差旅申请已经提交。`,
`如果您愿意,我可以继续按 ${timeCopy} 帮您整理一版行程规划,包括出发/返程${transportCopy}、酒店区域建议,以及还需要确认的事项。`
].join('\n')
}
export function buildTravelPlanningSuggestedActions(preview = {}, draftPayload = {}) {
const context = resolveTravelPlanningContext(preview, draftPayload)
if (!context) {
return []
}
return [
{
label: '生成行程规划',
action_type: TRAVEL_PLANNING_ACTION_GENERATE,
description: '按本次申请的地点和时间给出交通、酒店和待确认事项。',
icon: 'mdi mdi-map-clock-outline',
emphasis: 'primary',
payload: {
context
}
},
{
label: '暂不需要',
action_type: TRAVEL_PLANNING_ACTION_SKIP,
description: '保留申请结果,不继续生成规划。',
icon: 'mdi mdi-check-outline',
payload: {
context
}
}
]
}
export function buildTravelPlanningRecommendation(preview = {}, draftPayload = {}) {
const context = resolveTravelPlanningContext(preview, draftPayload)
if (!context) {
return ''
}
const outboundDate = context.startDate || '出发当天'
const returnDate = context.endDate || '返回当天'
const transport = context.transportMode || '火车/飞机'
const reasonLine = context.reason ? `业务安排:${context.reason}` : '业务安排:以申请事由为准,出发前再确认具体到场时间。'
const hotelArea = `${context.location}核心办公区、客户现场周边或交通枢纽 30 分钟通勤范围内`
const claimLine = context.claimNo ? `关联申请单:${context.claimNo}` : ''
return [
'可以,先给您一版轻量行程规划,后续您可以继续补充偏好。',
'',
claimLine,
`行程时间:${context.time}${context.days ? `${context.days}` : ''}`,
reasonLine,
'',
`交通建议:${outboundDate} 优先看上午到中午抵达 ${context.location}${transport}班次,预留到达后 1.5 小时交通和现场准备时间;${returnDate} 优先看下午或晚间返程,避免压缩最后一天工作安排。`,
`酒店建议:优先选择${hotelArea},同时关注可开发票、可取消、早餐和离现场距离。`,
'需要确认:出发城市、客户现场地址、是否需要同行人、是否有指定住宿协议酒店、是否需要提前准备会议室或网络环境。',
'',
'您也可以继续告诉我出发城市、偏好的交通方式或预算,我再把规划细化成更具体的时间段。'
].filter(Boolean).join('\n')
}