feat: 优化差旅报销预审流程与个人工作台 UI 体系

- 完善 user_agent_application 申请差旅报销预审槽位与消息组装
- 增强预算助理报告与风险建议卡片交互
- 重构登录页视觉样式与移动端响应式适配
- 优化个人工作台、文档中心、政策中心、员工管理等页面布局
- 拆分 travelRequestDetailPreReviewModel 为 advice/submit 模型
- 补充报销草稿、风险复核、Item Sync 与模板执行器测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-02 14:01:51 +08:00
parent 92444e7eae
commit ca691f3ee0
107 changed files with 5663 additions and 1542 deletions

View File

@@ -110,6 +110,20 @@ function normalizeValues(values) {
}, {})
}
function hasLinkedApplication(values) {
return Boolean(normalizeText(values?.application_claim_id) || normalizeText(values?.application_claim_no))
}
function buildApplicationSummaryParts(values) {
return [
normalizeText(values?.application_claim_no),
normalizeText(values?.application_reason),
normalizeText(values?.application_business_time),
normalizeText(values?.application_location),
normalizeText(values?.application_amount_label || values?.application_amount)
].filter(Boolean)
}
function normalizeApplicationCandidates(applications) {
if (!Array.isArray(applications)) {
return []
@@ -125,6 +139,7 @@ function normalizeApplicationCandidates(applications) {
location: normalizeText(item.location || item.application_location),
amount: normalizeText(item.amount || item.application_amount),
amount_label: normalizeText(item.amount_label || item.application_amount_label),
business_time: normalizeText(item.business_time || item.application_business_time),
status: normalizeText(item.status || item.application_status),
status_label: normalizeText(item.status_label || item.application_status_label),
application_date: normalizeText(item.application_date)
@@ -238,7 +253,6 @@ export function waitForGuidedApplicationSelection(state, expenseType, applicatio
export function selectGuidedRequiredApplication(state, application = {}) {
const current = normalizeGuidedFlowState(state)
const steps = getGuidedReimbursementSteps(current.expenseType)
return {
...current,
values: normalizeValues({
@@ -249,9 +263,11 @@ export function selectGuidedRequiredApplication(state, application = {}) {
application_location: application.application_location || application.location || '',
application_amount: application.application_amount || application.amount || '',
application_amount_label: application.application_amount_label || application.amount_label || '',
application_status_label: application.application_status_label || application.status_label || ''
application_business_time: application.application_business_time || application.business_time || '',
application_status_label: application.application_status_label || application.status_label || '',
application_date: application.application_date || ''
}),
stepKey: steps[0]?.key || 'summary',
stepKey: 'summary',
pendingInterruptionText: '',
applicationCandidates: []
}
@@ -346,40 +362,41 @@ export function buildGuidedReimbursementSummaryText(state) {
const current = normalizeGuidedFlowState(state)
const typeLabel = getGuidedExpenseTypeLabel(current.expenseType) || '报销'
const steps = getGuidedReimbursementSteps(current.expenseType)
const linkedApplication = hasLinkedApplication(current.values)
const lines = [
`已完成“${typeLabel}”的引导填写。`,
'',
'请核查下面的关键信息:'
]
if (current.values.application_claim_no) {
const applicationParts = [
current.values.application_claim_no,
current.values.application_reason,
current.values.application_location,
current.values.application_amount_label
].filter(Boolean)
if (linkedApplication) {
const applicationParts = buildApplicationSummaryParts(current.values)
lines.push(`- 关联申请单:${applicationParts.join(' / ')}`)
lines.push('- 报销票据:可先生成草稿,随后在草稿详情中上传对应票据。')
} else {
steps.forEach((step) => {
const value = step.key === 'attachments'
? (current.values.attachment_names?.length
? current.values.attachment_names.join('、')
: current.values.attachments || '稍后上传')
: current.values[step.key]
lines.push(`- ${step.summaryLabel}${value || '待补充'}`)
})
}
steps.forEach((step) => {
const value = step.key === 'attachments'
? (current.values.attachment_names?.length
? current.values.attachment_names.join('、')
: current.values.attachments || '稍后上传')
: current.values[step.key]
lines.push(`- ${step.summaryLabel}${value || '待补充'}`)
})
lines.push('')
lines.push('如果这些信息无误,我可以继续生成右侧报销核对信息;生成核对信息后,再由你决定保存草稿或继续下一步。')
lines.push(
linkedApplication
? '如果关联信息无误,我可以直接生成报销草稿;后续由你在草稿详情中上传和归集票据。'
: '如果这些信息无误,我可以继续生成报销草稿;草稿生成后可继续上传票据或补充信息。'
)
return lines.join('\n')
}
export function buildGuidedReviewConfirmationActions() {
return [{
label: '生成报销核对信息',
description: '进入现有报销核对流程,不会直接保存草稿',
label: '生成报销草稿',
description: '使用当前信息生成草稿,票据可在草稿详情继续上传',
icon: 'mdi mdi-clipboard-check-outline',
action_type: GUIDED_ACTION_CONFIRM_REIMBURSEMENT_REVIEW
}]
@@ -390,14 +407,23 @@ export function buildGuidedReviewSubmitOptions(state, files = []) {
const type = getGuidedExpenseType(current.expenseType)
const values = current.values || {}
const typeLabel = type?.label || '其他费用'
const fieldLines = getGuidedReimbursementSteps(current.expenseType).map((step) => {
const value = step.key === 'attachments'
? (values.attachment_names?.length ? values.attachment_names.join('、') : values.attachments || '稍后上传')
: values[step.key]
return `${step.summaryLabel}${value || '待补充'}`
})
if (values.application_claim_no) {
fieldLines.unshift(`关联申请单:${values.application_claim_no}`)
const linkedApplication = hasLinkedApplication(values)
const applicationReason = values.application_reason || ''
const applicationLocation = values.application_location || ''
const applicationAmount = values.application_amount || values.application_amount_label || ''
const applicationBusinessTime = values.application_business_time || ''
const fieldLines = []
if (linkedApplication) {
const applicationParts = buildApplicationSummaryParts(values)
fieldLines.push(`关联申请单:${applicationParts.join(' / ')}`)
fieldLines.push('报销票据:草稿生成后在详情中上传')
} else {
getGuidedReimbursementSteps(current.expenseType).forEach((step) => {
const value = step.key === 'attachments'
? (values.attachment_names?.length ? values.attachment_names.join('、') : values.attachments || '稍后上传')
: values[step.key]
fieldLines.push(`${step.summaryLabel}${value || '待补充'}`)
})
}
const rawText = [
`报销类型:${typeLabel}`,
@@ -406,31 +432,35 @@ export function buildGuidedReviewSubmitOptions(state, files = []) {
const reviewFormValues = {
expense_type: typeLabel,
reimbursement_type: typeLabel,
reason: values.reason || values.customer_name || '',
reason_value: values.reason || '',
reason: values.reason || applicationReason || values.customer_name || '',
reason_value: values.reason || applicationReason || '',
customer_name: values.customer_name || '',
participants: values.participants || '',
location: values.location || '',
business_location: values.location || '',
time_range: values.time_range || '',
business_time: values.time_range || '',
amount: values.amount || '',
location: values.location || applicationLocation || '',
business_location: values.location || applicationLocation || '',
time_range: values.time_range || applicationBusinessTime || '',
business_time: values.time_range || applicationBusinessTime || '',
amount: linkedApplication ? (values.amount || '') : (values.amount || applicationAmount || ''),
attachment_names: Array.isArray(values.attachment_names) ? values.attachment_names : [],
application_claim_id: values.application_claim_id || '',
application_claim_no: values.application_claim_no || '',
application_reason: values.application_reason || '',
application_location: values.application_location || '',
application_amount: values.application_amount || ''
application_amount: values.application_amount || '',
application_amount_label: values.application_amount_label || '',
application_business_time: values.application_business_time || '',
application_date: values.application_date || ''
}
return {
rawText,
userText: '生成报销核对信息',
pendingText: '正在生成右侧报销核对信息...',
userText: '生成报销草稿',
pendingText: '正在生成报销草稿...',
systemGenerated: true,
files,
extraContext: {
draft_claim_id: '',
review_action: 'save_draft',
user_input_text: rawText,
expense_scene_selection: {
expense_type: type?.key || current.expenseType || 'other',