Files
X-Financial/web/src/components/travel/TravelReimbursementMessageItem.vue
caoxiaozhu 0cde1f8990 feat(web): 工作台 AI 模式与差旅/风险建议交互优化
- 新增 PersonalWorkbenchAiMode 组件、AI 侧边栏与 orb 机器人视觉资源
- 新增 aiApplicationDraftModel / aiExpenseDraftModel / aiWorkbenchConversationStore
  及业务准入 aiSidebarBusinessAccess,支撑 AI 模式下的申请与报销草稿
- 顶栏、侧边栏、工作台样式重构,适配 AI 模式切换与响应式布局
- 同步 steward plan/off_topic、差旅报销引导流、风险建议卡片等测试
2026-06-18 22:12:24 +08:00

674 lines
28 KiB
Vue
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.
<template>
<article
class="message-row"
:class="[message.role, { 'has-steward-plan': message.stewardPlan }]"
>
<span class="message-avatar">
<img
:src="message.role === 'assistant' ? ui.aiAvatar : ui.userAvatar"
:alt="message.role === 'assistant' ? '财务助手头像' : '用户头像'"
/>
</span>
<div class="message-stack">
<details
v-if="message.role === 'assistant' && message.stewardPlan && (message.stewardPlan.streamStatus === 'streaming' || message.stewardPlan.thinkingEvents?.length)"
class="steward-intent-bubble"
:open="message.stewardPlan.streamStatus === 'streaming'"
aria-label="小财管家意图识别智能体"
>
<summary>
<span>
<i class="mdi mdi-brain"></i>
意图识别智能体
</span>
<small>{{ message.stewardPlan.streamStatus === 'streaming' ? '识别中' : `${message.stewardPlan.thinkingEvents?.length || 0}` }}</small>
<i class="mdi mdi-chevron-down"></i>
</summary>
<ol v-if="message.stewardPlan.thinkingEvents?.length" class="steward-intent-event-list">
<li
v-for="event in (message.stewardPlan.thinkingEvents || []).slice(0, message.stewardPlan.visibleThinkingEventCount || message.stewardPlan.thinkingEvents?.length || 0)"
:key="`${message.id}-${event.eventId}`"
>
<strong>{{ event.title }}</strong>
<span :class="{ 'typing': event.status === 'running' }">{{ event.content }}</span>
</li>
</ol>
<p v-else class="steward-intent-empty">正在建立任务上下文...</p>
</details>
<div
v-if="!message.stewardPlan || message.stewardPlan.streamStatus !== 'streaming' || message.text"
class="message-bubble"
:class="ui.buildMessageBubbleClass(message)"
>
<header class="message-meta">
<strong>{{ message.role === 'assistant' ? (message.assistantName || ui.ASSISTANT_DISPLAY_NAME) : '我' }}</strong>
<time>{{ message.time }}</time>
</header>
<div
v-if="message.text && message.role === 'assistant' && message.reviewPayload && ui.buildReviewMainMessageText(message)"
class="review-summary message-answer-content message-answer-markdown"
v-html="ui.renderMarkdown(ui.buildReviewMainMessageText(message))"
@click="ui.handleAssistantMarkdownClick($event, message)"
></div>
<div
v-else-if="message.text && message.role !== 'assistant'"
class="message-answer-content message-answer-markdown message-rich-text"
v-html="ui.renderMarkdown(message.text)"
></div>
<div
v-else-if="message.text && message.role === 'assistant'"
:class="[
'message-answer-content',
'message-answer-markdown',
{
'steward-plan-markdown': message.stewardPlan,
'steward-plan-typing': message.stewardPlan?.streamStatus === 'typing'
}
]"
v-html="ui.renderMarkdown(message.text)"
@click="ui.handleAssistantMarkdownClick($event, message)"
></div>
<BudgetAssistantReport
v-if="message.role === 'assistant' && message.budgetReport"
:report="message.budgetReport"
/>
<div
v-if="message.role === 'assistant' && message.stewardPlan && message.stewardPlan.streamStatus !== 'streaming' && !message.stewardPlan.initialSummaryOnly"
class="steward-plan-block"
role="group"
aria-label="小财管家任务计划"
>
<div v-if="message.stewardPlan.tasks?.length" class="steward-task-list">
<article
v-for="task in message.stewardPlan.tasks"
:key="`${message.id}-${task.taskId}`"
class="steward-task-card"
>
<header class="steward-task-header">
<span class="steward-task-type">{{ task.taskTypeLabel }}</span>
<span class="steward-task-agent">{{ task.assignedAgentLabel }}</span>
</header>
<div class="steward-task-body">
<strong class="steward-task-title">{{ task.title }}</strong>
<p class="steward-task-summary">{{ task.summary }}</p>
</div>
<div class="steward-task-meta">
<span>置信度 {{ Math.round((task.confidence || 0) * 100) }}%</span>
<span v-if="!ui.resolveStewardMissingFieldItems(task).length">信息已基本齐备</span>
</div>
<div
v-if="ui.resolveStewardMissingFieldItems(task).length"
class="steward-task-missing"
>
<span class="steward-task-missing-label">还需要补充</span>
<ul class="steward-task-missing-list">
<li
v-for="field in ui.resolveStewardMissingFieldItems(task)"
:key="`${task.taskId}-missing-${field.key}`"
>
<strong>{{ field.label }}</strong>
<small v-if="field.hint">{{ field.hint }}</small>
</li>
</ul>
</div>
</article>
</div>
<div v-if="message.stewardPlan.attachmentGroups?.length" class="steward-attachment-list">
<article
v-for="group in message.stewardPlan.attachmentGroups"
:key="`${message.id}-${group.groupId}`"
class="steward-attachment-card"
>
<header>
<span>{{ group.sceneLabel }}</span>
<small>{{ Math.round((group.confidence || 0) * 100) }}%</small>
</header>
<p>{{ group.rationale }}</p>
<div class="steward-attachment-chip-row">
<span
v-for="name in group.attachmentNames"
:key="`${group.groupId}-in-${name}`"
class="steward-attachment-chip include"
>{{ name }}</span>
<span
v-for="name in group.excludedAttachmentNames"
:key="`${group.groupId}-out-${name}`"
class="steward-attachment-chip exclude"
>排除{{ name }}</span>
</div>
</article>
</div>
</div>
<Transition name="structured-card-reveal" appear>
<div
v-if="message.role === 'assistant' && message.applicationPreview"
class="application-preview-shell"
aria-label="申请信息核对结果"
>
<div
class="application-preview-table"
role="table"
aria-label="申请信息核对表"
>
<div class="application-preview-row head" role="row">
<span role="columnheader">字段</span>
<span role="columnheader">内容</span>
</div>
<div
v-for="row in ui.resolveApplicationPreviewRows(message)"
:key="`${message.id}-${row.key}`"
class="application-preview-row"
:class="{
missing: row.missing,
editable: row.editable && !ui.submitting && !ui.reviewActionBusy && !ui.sessionSwitchBusy,
highlight: row.highlight
}"
role="row"
:tabindex="row.editable && !ui.submitting && !ui.reviewActionBusy && !ui.sessionSwitchBusy ? 0 : -1"
:aria-label="row.editable ? `编辑${row.label}` : row.label"
@click.stop="row.editable && !ui.isApplicationPreviewEditing(message, row.key) && !ui.submitting && !ui.reviewActionBusy && !ui.sessionSwitchBusy && ui.openApplicationPreviewEditor(message, row.key, row.value)"
@keydown.enter.prevent="row.editable && !ui.isApplicationPreviewEditing(message, row.key) && !ui.submitting && !ui.reviewActionBusy && !ui.sessionSwitchBusy && ui.openApplicationPreviewEditor(message, row.key, row.value)"
@keydown.space.prevent="row.editable && !ui.isApplicationPreviewEditing(message, row.key) && !ui.submitting && !ui.reviewActionBusy && !ui.sessionSwitchBusy && ui.openApplicationPreviewEditor(message, row.key, row.value)"
>
<span class="application-preview-label" role="cell">{{ row.label }}</span>
<span class="application-preview-value" role="cell">
<input
v-if="ui.isApplicationPreviewEditing(message, row.key) && ui.resolveApplicationPreviewEditorControl(row.key) === 'text'"
v-model="ui.applicationPreviewEditor.draftValue"
class="application-preview-input"
type="text"
autofocus
@click.stop
@keydown.stop="ui.handleApplicationPreviewEditorKeydown($event, message)"
@blur="ui.commitApplicationPreviewEditor(message)"
/>
<EnterpriseSelect
v-else-if="ui.isApplicationPreviewEditing(message, row.key) && ui.resolveApplicationPreviewEditorControl(row.key) === 'select'"
v-model="ui.applicationPreviewEditor.draftValue"
class="application-preview-select"
:options="ui.resolveApplicationPreviewEditorOptions(row.key)"
clearable
:teleported="false"
autofocus
@click.stop
@change="ui.commitApplicationPreviewEditor(message)"
@keydown.stop="ui.handleApplicationPreviewEditorKeydown($event, message)"
@blur="ui.commitApplicationPreviewEditor(message)"
/>
<template v-else>
<span
class="application-preview-text"
:class="{ 'application-preview-date-chip': row.key === 'time' && !row.missing }"
>{{ row.value }}</span>
<button
v-if="row.editable"
type="button"
class="application-preview-edit-btn"
title="修改内容"
aria-label="修改内容"
:disabled="ui.submitting || ui.reviewActionBusy || ui.sessionSwitchBusy"
@click.stop="ui.openApplicationPreviewEditor(message, row.key, row.value)"
>
<i class="mdi mdi-pencil-outline"></i>
</button>
</template>
</span>
</div>
</div>
<div
v-if="ui.resolveApplicationPreviewMissingFields(message)?.length"
class="application-preview-footer application-preview-footer-missing"
aria-live="polite"
>
<span class="application-preview-missing-prefix">当前还需要补充</span>
<span class="application-preview-missing-list">
<template
v-for="(field, index) in ui.resolveApplicationPreviewMissingFields(message)"
:key="`${message.id}-missing-${field}`"
>
<span class="application-preview-missing-chip">{{ field }}</span>
<span
v-if="index < ui.resolveApplicationPreviewMissingFields(message).length - 1"
class="application-preview-missing-separator"
></span>
</template>
</span>
<span class="application-preview-missing-suffix">补齐后我再帮您提交申请</span>
</div>
<div
v-else-if="ui.buildApplicationPreviewFooterText(message)"
class="application-preview-footer message-answer-content message-answer-markdown"
v-html="ui.renderMarkdown(ui.buildApplicationPreviewFooterText(message))"
@click="ui.handleAssistantMarkdownClick($event, message)"
></div>
</div>
</Transition>
<div
v-if="message.role === 'assistant' && message.welcomeQuickActions?.length"
class="welcome-quick-actions"
>
<p class="welcome-quick-actions-title">您可以对我进行以下操作</p>
<div class="welcome-quick-action-grid">
<button
v-for="action in message.welcomeQuickActions"
:key="`${message.id}-${action.label}`"
type="button"
class="welcome-quick-action-btn"
:disabled="ui.submitting || ui.reviewActionBusy || ui.sessionSwitchBusy"
@click="ui.runWelcomeQuickAction(action)"
>
<i :class="action.icon"></i>
<span>{{ action.label }}</span>
</button>
</div>
</div>
<Transition name="structured-card-reveal" appear>
<div
v-if="message.role === 'assistant' && !message.reviewPayload && !message.queryPayload && message.suggestedActions?.length"
class="message-suggested-actions"
:class="{ 'compact-guidance-actions': message.assistantVariant === 'compact_guidance' }"
>
<button
v-for="action in message.suggestedActions"
:key="`${message.id}-${action.action_type}-${action.label}`"
type="button"
class="message-suggested-action-btn"
:class="{
selected: ui.isSuggestedActionSelected(message, action),
locked: message.suggestedActionsLocked
}"
:disabled="message.suggestedActionsLocked || ui.submitting || ui.reviewActionBusy || ui.sessionSwitchBusy"
@click="ui.handleSuggestedAction(message, action)"
>
<span class="message-suggested-action-icon" aria-hidden="true">
<i :class="action.icon || 'mdi mdi-shape-outline'"></i>
</span>
<span class="message-suggested-action-copy">
<span class="message-suggested-action-title">{{ action.label }}</span>
<small v-if="action.description">{{ action.description }}</small>
</span>
<i class="message-suggested-action-arrow mdi mdi-arrow-right" aria-hidden="true"></i>
</button>
</div>
</Transition>
<div v-if="message.role === 'assistant' && !message.reviewPayload && message.riskFlags?.length" class="message-detail-block">
<strong>风险标签</strong>
<div class="message-detail-chip-row">
<span v-for="item in message.riskFlags" :key="item" class="message-risk-chip">{{ item }}</span>
</div>
</div>
<details
v-if="message.role === 'assistant' && !message.reviewPayload && !message.queryPayload && message.citations?.length"
class="message-detail-block message-citation-disclosure"
>
<summary>
<strong>引用依据</strong>
<span>{{ message.citations.length }} </span>
<i class="mdi mdi-chevron-down"></i>
</summary>
<div class="message-citation-list">
<article v-for="item in message.citations" :key="`${message.id}-${item.code}`" class="message-citation-card">
<header>
<span>{{ item.title }}</span>
<small>{{ item.version || item.source_type }}</small>
</header>
<p>{{ item.excerpt || item.code }}</p>
</article>
</div>
</details>
<div
v-if="message.role === 'assistant' && !message.reviewPayload && message.queryPayload?.resultType === 'expense_claim_list'"
class="message-detail-block expense-query-block"
>
<strong>
{{ message.queryPayload.title || (message.queryPayload.selectionMode === 'draft_association' ? '选择关联草稿' : '最近 5 条筛选结果') }}
</strong>
<p v-if="ui.buildExpenseQueryWindowLabel(message.queryPayload)" class="expense-query-window-label">
{{ ui.buildExpenseQueryWindowLabel(message.queryPayload) }}
</p>
<div v-if="message.queryPayload.statusGroups?.length" class="expense-query-summary-row">
<span
v-for="item in message.queryPayload.statusGroups"
:key="`${message.id}-${item.key}`"
class="expense-query-summary-chip"
:class="item.key"
>
{{ item.label }} {{ item.count }}
</span>
</div>
<div v-if="message.queryPayload.records?.length" class="expense-query-record-list compact">
<button
v-for="record in ui.getExpenseQueryVisibleRecords(message.queryPayload)"
:key="`${message.id}-${record.claimId}`"
type="button"
class="expense-query-record-card"
:class="{
selectable: message.queryPayload.selectionMode === 'draft_association',
selected: message.selectedQueryRecordId === record.claimId || message.queryPayload.selectedClaimId === record.claimId,
locked: message.querySelectionLocked || message.queryPayload.selectionLocked
}"
:disabled="message.queryPayload.selectionMode === 'draft_association' && (message.querySelectionLocked || message.queryPayload.selectionLocked)"
@click="ui.handleExpenseQueryRecordClick(message, record)"
>
<div class="expense-query-record-main">
<div class="expense-query-record-top">
<strong>{{ record.claimNo }}</strong>
<span class="expense-query-record-status" :class="record.statusGroup || 'other'">
{{ record.statusLabel }}
</span>
</div>
<p>{{ record.summary }}</p>
<div class="expense-query-record-meta">
<span>{{ record.expenseTypeLabel }}</span>
<span>{{ record.dateDisplay }}</span>
<span>{{ record.amountDisplay }}</span>
</div>
<div v-if="record.riskItems?.length" class="expense-query-risk-row">
<button
v-for="risk in record.riskItems"
:key="`${message.id}-${record.claimId}-${risk.key}`"
type="button"
class="expense-query-risk-chip"
:class="risk.level"
@click.stop="ui.appendExpenseQueryRiskToConversation(record, risk)"
>
<span>{{ record.claimNo }}</span>
<strong>{{ risk.levelLabel }}</strong>
<em>{{ risk.title }}</em>
</button>
</div>
</div>
<i class="mdi mdi-chevron-right"></i>
</button>
<div
v-if="ui.getExpenseQueryTotalPages(message.queryPayload) > 1"
class="expense-query-pager"
>
<button
type="button"
class="expense-query-pager-btn"
:disabled="ui.getExpenseQueryActivePage(message.queryPayload) === 1"
aria-label="上一页"
@click="ui.shiftExpenseQueryPage(message, -1)"
>
<i class="mdi mdi-chevron-left"></i>
</button>
<div class="expense-query-pager-dots" aria-label="单据分页">
<button
v-for="page in ui.getExpenseQueryTotalPages(message.queryPayload)"
:key="`${message.id}-query-page-${page}`"
type="button"
class="expense-query-pager-dot"
:class="{ active: ui.getExpenseQueryActivePage(message.queryPayload) === page }"
:aria-label="` ${page} `"
@click="ui.setExpenseQueryPage(message, page)"
></button>
</div>
<button
type="button"
class="expense-query-pager-btn"
:disabled="ui.getExpenseQueryActivePage(message.queryPayload) === ui.getExpenseQueryTotalPages(message.queryPayload)"
aria-label="下一页"
@click="ui.shiftExpenseQueryPage(message, 1)"
>
<i class="mdi mdi-chevron-right"></i>
</button>
</div>
</div>
<div v-else class="expense-query-empty">
<i class="mdi mdi-file-search-outline"></i>
<span>{{ message.queryPayload.emptyText || '当前没有可直接展开的近期待办单据。' }}</span>
</div>
<p
v-if="ui.buildExpenseQueryHint(message.queryPayload)"
class="expense-query-hint message-answer-markdown"
v-html="ui.renderMarkdown(ui.buildExpenseQueryHint(message.queryPayload))"
@click="ui.handleAssistantMarkdownClick($event, message)"
>
</p>
</div>
<div
v-if="message.role === 'assistant' && ui.shouldShowDraftSavedCard(message)"
class="draft-preview application-draft-preview"
:class="{ 'reimbursement-draft-preview': !ui.isApplicationDraftPayload(message.draftPayload) }"
>
<template v-if="ui.isApplicationDraftPayload(message.draftPayload)">
<header class="application-draft-head">
<span class="application-draft-icon" aria-hidden="true">
<i class="mdi mdi-file-document-check-outline"></i>
</span>
<span class="application-draft-title">
<strong>申请单据已生成</strong>
<small>已为本次业务生成申请单请按需查看完整详情</small>
</span>
<span class="application-draft-status">{{ ui.resolveApplicationDraftStatusLabel(message.draftPayload) }}</span>
</header>
<div class="application-draft-brief" role="group" aria-label="申请单据简要信息">
<div
v-for="item in ui.buildApplicationDraftSummaryItems(message.draftPayload)"
:key="`${message.id}-application-draft-${item.label}`"
class="application-draft-brief-item"
:class="{ 'is-primary': item.label === '单号' }"
>
<span>{{ item.label }}</span>
<strong>{{ item.value }}</strong>
</div>
</div>
<footer class="application-draft-footer">
<p>
完整审批链附件和明细可在单据详情中
<button
type="button"
class="application-draft-detail-link"
:disabled="ui.submitting || ui.reviewActionBusy || ui.sessionSwitchBusy"
@click="ui.openApplicationDraftDetail(message)"
>查看</button>
</p>
</footer>
</template>
<template v-else>
<div
class="reimbursement-draft-card"
role="group"
:aria-label="ui.canOpenDraftDetail(message) ? '报销草稿已生成' : '报销草稿待保存'"
>
<span class="reimbursement-draft-icon" aria-hidden="true">
<i class="mdi mdi-file-document-edit-outline"></i>
</span>
<div class="reimbursement-draft-main">
<strong>{{ ui.canOpenDraftDetail(message) ? '报销草稿已生成' : '报销草稿待保存' }}</strong>
<p>
单号<span>{{ ui.resolveReimbursementDraftClaimNo(message.draftPayload) }}</span>
<button
v-if="ui.canOpenDraftDetail(message)"
type="button"
class="reimbursement-draft-link"
:disabled="ui.submitting || ui.reviewActionBusy || ui.sessionSwitchBusy"
@click="ui.openApplicationDraftDetail(message)"
>查看详情</button>
<span v-else class="reimbursement-draft-pending-detail">保存后可查看详情</span>
</p>
</div>
</div>
</template>
</div>
<div v-if="message.role === 'assistant' && message.reviewPayload" class="message-detail-block review-message-block">
<div class="review-plain-followup">
<template
v-for="followup in [ui.buildReviewPlainFollowupForMessage(message)]"
:key="`${message.id}-review-followup`"
>
<h3
class="review-plain-lead"
:class="{ danger: followup.tone === 'danger' }"
>
{{ followup.lead }}
</h3>
<p v-if="followup.summary" class="review-plain-summary">
{{ followup.summary }}
</p>
<ul v-if="followup.items.length" class="review-plain-list">
<li
v-for="item in followup.items"
:key="`${message.id}-${item.key}`"
>
<span class="review-plain-label">{{ item.label }}</span>
<span>{{ item.text }}</span>
</li>
</ul>
<p
v-for="line in followup.notes"
:key="`${message.id}-note-${line}`"
class="review-plain-note"
>
{{ line }}
</p>
<p v-if="ui.canUseInlineSaveDraft(message)" class="review-inline-save-copy">
请核查上面的关键信息您也可以暂时不处理上述的这些内容我可以帮你先保存为
<button
type="button"
class="review-inline-draft-link"
:disabled="ui.submitting || ui.reviewActionBusy || ui.sessionSwitchBusy"
@click="ui.handleInlineSaveDraft(message)"
>
草稿
</button>
</p>
</template>
<div
v-if="ui.buildReviewNextStepRichCopyForMessage(message)"
class="review-next-step-rich-copy message-answer-markdown"
v-html="ui.renderMarkdown(ui.buildReviewNextStepRichCopyForMessage(message))"
@click="ui.handleAssistantMarkdownClick($event, message)"
></div>
<div
v-if="ui.resolveReviewFooterActions(message.reviewPayload).length"
class="review-footer-actions"
>
<div class="review-footer-btn-row">
<button
v-for="action in ui.resolveReviewFooterActions(message.reviewPayload)"
:key="`${message.id}-${action.action_type}`"
type="button"
:class="['review-footer-btn', action.emphasis === 'primary' ? 'primary' : '']"
:disabled="ui.submitting || ui.reviewActionBusy || ui.sessionSwitchBusy"
@click="ui.handleReviewAction(message, action)"
>
{{ action.label || ui.buildReviewPrimaryButtonLabel(message.reviewPayload, message.draftPayload) }}
</button>
</div>
</div>
</div>
</div>
<div v-if="message.attachments?.length" class="message-files">
<span v-for="file in message.attachments" :key="file" class="file-chip">
<i class="mdi mdi-paperclip"></i>
{{ file }}
</span>
</div>
</div>
<div
v-if="ui.shouldShowAssistantMessageActions(message)"
class="message-action-toolbar"
role="toolbar"
aria-label="系统消息操作"
>
<button
type="button"
class="message-action-btn"
title="复制"
aria-label="复制"
@click="ui.copyAssistantMessage(message)"
>
<i class="mdi mdi-content-copy"></i>
</button>
<button
type="button"
class="message-action-btn"
title="语音播报"
aria-label="语音播报"
@click="ui.speakAssistantMessage(message)"
>
<i class="mdi mdi-volume-high"></i>
</button>
<button
type="button"
class="message-action-btn"
:class="{ active: ui.isMessageFeedbackSelected(message, 5) }"
:disabled="Boolean(message.operationFeedback?.submitting)"
title="点赞"
aria-label="点赞"
@click="ui.submitOperationFeedbackForMessage(message, { rating: 5, reason: 'thumbs_up' })"
>
<i :class="ui.isMessageFeedbackSelected(message, 5) ? 'mdi mdi-thumb-up' : 'mdi mdi-thumb-up-outline'"></i>
</button>
<button
type="button"
class="message-action-btn"
:class="{ active: ui.isMessageFeedbackSelected(message, 1) }"
:disabled="Boolean(message.operationFeedback?.submitting)"
title="点踩"
aria-label="点踩"
@click="ui.submitOperationFeedbackForMessage(message, { rating: 1, reason: 'thumbs_down' })"
>
<i :class="ui.isMessageFeedbackSelected(message, 1) ? 'mdi mdi-thumb-down' : 'mdi mdi-thumb-down-outline'"></i>
</button>
</div>
</div>
</article>
</template>
<script>
import BudgetAssistantReport from './BudgetAssistantReport.vue'
import EnterpriseSelect from '../shared/EnterpriseSelect.vue'
export default {
name: 'TravelReimbursementMessageItem',
components: {
BudgetAssistantReport,
EnterpriseSelect
},
props: {
message: {
type: Object,
required: true
},
ui: {
type: Object,
required: true
}
}
}
</script>
<style scoped src="../../assets/styles/components/travel-reimbursement-message-item.css"></style>