feat(web): update views
- AppShellRouteView.vue: update app shell route view - TravelReimbursementCreateView.vue: update travel form view - TravelRequestDetailView.vue: update travel request detail view
This commit is contained in:
@@ -38,11 +38,11 @@
|
|||||||
:custom-range="customRange"
|
:custom-range="customRange"
|
||||||
@update:search="search = $event"
|
@update:search="search = $event"
|
||||||
@update:active-range="activeRange = $event"
|
@update:active-range="activeRange = $event"
|
||||||
@update:custom-range="customRange = $event"
|
@update:custom-range="customRange = $event"
|
||||||
@batch-approve="toast('已批量通过 23 条审批任务。')"
|
@batch-approve="toast('已批量通过 23 条审批任务。')"
|
||||||
@open-chat="handleOpenChat"
|
@open-chat="handleOpenChat"
|
||||||
@new-application="openTravelCreate"
|
@new-application="openTravelCreate"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FilterBar
|
<FilterBar
|
||||||
v-if="activeView !== 'chat' && activeView !== 'overview' && activeView !== 'workbench' && activeView !== 'requests' && activeView !== 'approval' && activeView !== 'policies' && activeView !== 'audit' && activeView !== 'employees' && activeView !== 'settings'"
|
v-if="activeView !== 'chat' && activeView !== 'overview' && activeView !== 'workbench' && activeView !== 'requests' && activeView !== 'approval' && activeView !== 'policies' && activeView !== 'audit' && activeView !== 'employees' && activeView !== 'settings'"
|
||||||
@@ -73,10 +73,11 @@
|
|||||||
@reject="handleReject"
|
@reject="handleReject"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PersonalWorkbenchView
|
<PersonalWorkbenchView
|
||||||
v-else-if="activeView === 'workbench'"
|
v-else-if="activeView === 'workbench'"
|
||||||
@open-assistant="openSmartEntry"
|
:assistant-modal-open="smartEntryOpen"
|
||||||
/>
|
@open-assistant="openSmartEntry"
|
||||||
|
/>
|
||||||
|
|
||||||
<ChatView
|
<ChatView
|
||||||
v-else-if="activeView === 'chat'"
|
v-else-if="activeView === 'chat'"
|
||||||
@@ -205,11 +206,11 @@ const {
|
|||||||
sendMessage,
|
sendMessage,
|
||||||
sending,
|
sending,
|
||||||
smartEntryContext,
|
smartEntryContext,
|
||||||
smartEntryOpen,
|
smartEntryOpen,
|
||||||
smartEntrySessionId,
|
smartEntrySessionId,
|
||||||
toast,
|
toast,
|
||||||
topBarView,
|
topBarView,
|
||||||
travelPrompts,
|
travelPrompts,
|
||||||
uploadedFiles
|
uploadedFiles
|
||||||
} = useAppShell()
|
} = useAppShell()
|
||||||
|
|
||||||
|
|||||||
@@ -6,25 +6,47 @@
|
|||||||
<div class="assistant-modal-stage">
|
<div class="assistant-modal-stage">
|
||||||
<header class="assistant-header">
|
<header class="assistant-header">
|
||||||
<div class="assistant-header-main">
|
<div class="assistant-header-main">
|
||||||
<span class="assistant-badge">AI Workspace</span>
|
|
||||||
<div>
|
<div>
|
||||||
<h2>统一对话工作台</h2>
|
<h2>财务AI工作台</h2>
|
||||||
<p>个人工作台、发起报销、智能录入统一走这里,右侧会根据你的意图实时切换状态视图。</p>
|
<p>个人工作台、发起报销、智能录入统一走这里,右侧会根据你的意图实时切换状态视图。</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="assistant-header-actions">
|
<div class="assistant-header-actions">
|
||||||
<span class="source-pill">
|
<button
|
||||||
<i class="mdi mdi-arrow-u-left-top"></i>
|
type="button"
|
||||||
{{ sourceLabel }}
|
class="assistant-toggle-btn"
|
||||||
</span>
|
:class="{ disabled: !hasInsightPanelContent }"
|
||||||
<button class="close-btn" type="button" aria-label="关闭对话工作台" @click="emit('close')">
|
:disabled="!hasInsightPanelContent || sessionSwitchBusy"
|
||||||
|
:title="insightPanelToggleLabel"
|
||||||
|
:aria-label="insightPanelToggleLabel"
|
||||||
|
@click="toggleInsightPanel"
|
||||||
|
>
|
||||||
|
<i :class="showInsightPanel ? 'mdi mdi-arrow-collapse-right' : 'mdi mdi-arrow-expand-right'"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="session-trash-btn"
|
||||||
|
:disabled="!canDeleteCurrentSession || submitting || reviewActionBusy || deleteSessionBusy || sessionSwitchBusy"
|
||||||
|
title="删除当前会话"
|
||||||
|
aria-label="删除当前会话"
|
||||||
|
@click="openDeleteSessionDialog"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-delete-outline"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="close-btn"
|
||||||
|
type="button"
|
||||||
|
title="关闭工作台"
|
||||||
|
aria-label="关闭对话工作台"
|
||||||
|
@click="requestCloseWorkbench"
|
||||||
|
>
|
||||||
<i class="mdi mdi-close"></i>
|
<i class="mdi mdi-close"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="assistant-layout" :class="{ 'has-insight': showInsightPanel }">
|
<div class="assistant-layout" :class="{ 'can-show-insight': hasInsightPanelContent, 'has-insight': showInsightPanel }">
|
||||||
<section class="dialog-panel">
|
<section class="dialog-panel">
|
||||||
<div class="dialog-toolbar">
|
<div class="dialog-toolbar">
|
||||||
<button
|
<button
|
||||||
@@ -32,7 +54,8 @@
|
|||||||
:key="shortcut.label"
|
:key="shortcut.label"
|
||||||
type="button"
|
type="button"
|
||||||
class="shortcut-chip"
|
class="shortcut-chip"
|
||||||
@click="runShortcut(shortcut.prompt)"
|
:disabled="submitting || reviewActionBusy || deleteSessionBusy || sessionSwitchBusy"
|
||||||
|
@click="runShortcut(shortcut)"
|
||||||
>
|
>
|
||||||
<i :class="shortcut.icon"></i>
|
<i :class="shortcut.icon"></i>
|
||||||
<span>{{ shortcut.label }}</span>
|
<span>{{ shortcut.label }}</span>
|
||||||
@@ -58,7 +81,7 @@
|
|||||||
<strong>{{ message.role === 'assistant' ? 'AI 助手' : '我' }}</strong>
|
<strong>{{ message.role === 'assistant' ? 'AI 助手' : '我' }}</strong>
|
||||||
<time>{{ message.time }}</time>
|
<time>{{ message.time }}</time>
|
||||||
</header>
|
</header>
|
||||||
<p v-if="!(message.role === 'assistant' && message.reviewPayload)">{{ message.text }}</p>
|
<p v-if="message.text" :class="{ 'review-summary': message.role === 'assistant' && message.reviewPayload }">{{ message.text }}</p>
|
||||||
|
|
||||||
<div v-if="message.role === 'assistant' && !message.reviewPayload && message.meta?.length" class="message-meta-row">
|
<div v-if="message.role === 'assistant' && !message.reviewPayload && message.meta?.length" class="message-meta-row">
|
||||||
<span v-for="item in message.meta" :key="item" class="message-meta-chip">{{ item }}</span>
|
<span v-for="item in message.meta" :key="item" class="message-meta-chip">{{ item }}</span>
|
||||||
@@ -102,7 +125,7 @@
|
|||||||
<div class="review-card-head">
|
<div class="review-card-head">
|
||||||
<div class="review-card-head-main">
|
<div class="review-card-head-main">
|
||||||
<span class="review-card-icon">
|
<span class="review-card-icon">
|
||||||
<i class="mdi mdi-check-decagram"></i>
|
<i class="mdi mdi-shield-alert-outline"></i>
|
||||||
</span>
|
</span>
|
||||||
<div class="review-card-head-copy">
|
<div class="review-card-head-copy">
|
||||||
<strong>{{ buildReviewHeadline(message.reviewPayload, message.draftPayload) }}</strong>
|
<strong>{{ buildReviewHeadline(message.reviewPayload, message.draftPayload) }}</strong>
|
||||||
@@ -114,40 +137,64 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="review-flow-card">
|
<details class="review-disclosure-card" :open="shouldOpenReviewDisclosure(message.reviewPayload)">
|
||||||
<div v-if="buildReviewAlertChips(message.reviewPayload).length" class="review-alert-chip-row subtle">
|
<summary class="review-disclosure-summary">
|
||||||
<span
|
<div class="review-disclosure-copy">
|
||||||
v-for="item in buildReviewAlertChips(message.reviewPayload)"
|
<strong>{{ buildReviewDisclosureTitle(message.reviewPayload) }}</strong>
|
||||||
:key="`${message.id}-${item.key}`"
|
<p>{{ buildReviewDisclosureHint(message.reviewPayload) }}</p>
|
||||||
class="review-alert-chip"
|
</div>
|
||||||
:class="item.tone"
|
<span class="review-disclosure-toggle">
|
||||||
>
|
<i class="mdi mdi-chevron-down"></i>
|
||||||
<i :class="item.tone === 'danger' ? 'mdi mdi-alert-circle' : item.tone === 'success' ? 'mdi mdi-check-circle' : 'mdi mdi-alert'"></i>
|
|
||||||
{{ item.label }}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</summary>
|
||||||
|
|
||||||
<div class="review-section-head review-flow-head">
|
<div class="review-disclosure-body">
|
||||||
<strong>待补充内容</strong>
|
<div v-if="buildReviewAlertChips(message.reviewPayload).length" class="review-alert-chip-row subtle">
|
||||||
<span>{{ buildReviewTodoItems(message.reviewPayload).length ? `${buildReviewTodoItems(message.reviewPayload).length} 项` : '已齐全' }}</span>
|
<span
|
||||||
</div>
|
v-for="item in buildReviewAlertChips(message.reviewPayload)"
|
||||||
<div class="review-pending-list plain">
|
:key="`${message.id}-${item.key}`"
|
||||||
<article
|
class="review-alert-chip"
|
||||||
v-for="item in buildReviewTodoItems(message.reviewPayload)"
|
:class="item.tone"
|
||||||
:key="`${message.id}-${item.key}`"
|
>
|
||||||
class="review-pending-item"
|
<i :class="item.tone === 'danger' ? 'mdi mdi-alert-circle' : item.tone === 'success' ? 'mdi mdi-check-circle' : 'mdi mdi-alert'"></i>
|
||||||
>
|
{{ item.label }}
|
||||||
<span class="review-pending-icon">
|
|
||||||
<i :class="item.icon"></i>
|
|
||||||
</span>
|
</span>
|
||||||
<div class="review-pending-copy">
|
</div>
|
||||||
|
|
||||||
|
<div v-if="resolveReviewRiskBriefs(message.reviewPayload).length" class="review-risk-brief-list">
|
||||||
|
<article
|
||||||
|
v-for="item in resolveReviewRiskBriefs(message.reviewPayload)"
|
||||||
|
:key="`${message.id}-${item.title}`"
|
||||||
|
class="review-risk-brief"
|
||||||
|
:class="item.level || 'info'"
|
||||||
|
>
|
||||||
<strong>{{ item.title }}</strong>
|
<strong>{{ item.title }}</strong>
|
||||||
<p>{{ item.hint }}</p>
|
<p>{{ item.content }}</p>
|
||||||
</div>
|
</article>
|
||||||
<span class="review-pending-status" :class="item.tone">{{ item.status }}</span>
|
</div>
|
||||||
</article>
|
|
||||||
|
<div class="review-section-head review-flow-head">
|
||||||
|
<strong>{{ buildReviewTodoSectionTitle(message.reviewPayload) }}</strong>
|
||||||
|
<span>{{ buildReviewTodoSectionMeta(message.reviewPayload) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="review-pending-list plain">
|
||||||
|
<article
|
||||||
|
v-for="item in buildReviewTodoItems(message.reviewPayload)"
|
||||||
|
:key="`${message.id}-${item.key}`"
|
||||||
|
class="review-pending-item"
|
||||||
|
>
|
||||||
|
<span class="review-pending-icon">
|
||||||
|
<i :class="item.icon"></i>
|
||||||
|
</span>
|
||||||
|
<div class="review-pending-copy">
|
||||||
|
<strong>{{ item.title }}</strong>
|
||||||
|
<p>{{ item.hint }}</p>
|
||||||
|
</div>
|
||||||
|
<span class="review-pending-status" :class="item.tone">{{ item.status }}</span>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</details>
|
||||||
|
|
||||||
<div v-if="resolveReviewPrimaryAction(message.reviewPayload) || message.draftPayload?.claim_no" class="review-footer-actions">
|
<div v-if="resolveReviewPrimaryAction(message.reviewPayload) || message.draftPayload?.claim_no" class="review-footer-actions">
|
||||||
<div class="review-footer-btn-row">
|
<div class="review-footer-btn-row">
|
||||||
@@ -161,6 +208,16 @@
|
|||||||
{{ buildReviewPrimaryButtonLabel(message.reviewPayload, message.draftPayload) }}
|
{{ buildReviewPrimaryButtonLabel(message.reviewPayload, message.draftPayload) }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="resolveReviewEditAction(message.reviewPayload)"
|
||||||
|
type="button"
|
||||||
|
class="review-footer-btn"
|
||||||
|
:disabled="reviewActionBusy"
|
||||||
|
@click="handleReviewAction(message, resolveReviewEditAction(message.reviewPayload))"
|
||||||
|
>
|
||||||
|
修改识别信息
|
||||||
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="review-footer-btn"
|
class="review-footer-btn"
|
||||||
@@ -177,7 +234,7 @@
|
|||||||
:disabled="submitting || reviewActionBusy"
|
:disabled="submitting || reviewActionBusy"
|
||||||
@click="queryDraftByClaimNo(message.draftPayload.claim_no)"
|
@click="queryDraftByClaimNo(message.draftPayload.claim_no)"
|
||||||
>
|
>
|
||||||
查看草稿 {{ message.draftPayload.claim_no }}
|
查看当前报销信息
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -212,42 +269,121 @@
|
|||||||
@change="handleFilesChange"
|
@change="handleFilesChange"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="composer-shell">
|
<div v-if="!isKnowledgeSession && attachedFiles.length" class="composer-files-panel">
|
||||||
<textarea
|
<div class="composer-files-head">
|
||||||
v-model="composerDraft"
|
<strong>已添加 {{ attachedFiles.length }} 份附件</strong>
|
||||||
rows="3"
|
<div class="composer-files-actions">
|
||||||
:placeholder="composerPlaceholder"
|
<button
|
||||||
:disabled="submitting || reviewActionBusy"
|
v-if="hiddenAttachedFileCount"
|
||||||
@keydown.enter.exact.stop
|
type="button"
|
||||||
@keydown.ctrl.enter.prevent="submitComposer"
|
class="composer-file-link"
|
||||||
/>
|
:disabled="submitting || reviewActionBusy"
|
||||||
|
@click="toggleAttachedFilesExpanded"
|
||||||
|
>
|
||||||
|
<i :class="composerFilesExpanded ? 'mdi mdi-chevron-up' : 'mdi mdi-chevron-down'"></i>
|
||||||
|
{{ composerFilesExpanded ? '收起附件' : `展开其余 ${hiddenAttachedFileCount} 份` }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="composer-file-link danger"
|
||||||
|
:disabled="submitting || reviewActionBusy"
|
||||||
|
@click="clearAttachedFiles"
|
||||||
|
>
|
||||||
|
清空
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="attachedFiles.length" class="composer-files">
|
<div class="composer-file-chip-row">
|
||||||
<span v-for="file in attachedFiles" :key="file.name" class="file-chip active">
|
<span v-for="file in visibleAttachedFiles" :key="file.name" class="file-chip active composer-file-chip">
|
||||||
<i class="mdi mdi-paperclip"></i>
|
<i class="mdi mdi-paperclip"></i>
|
||||||
{{ file.name }}
|
<span class="file-chip-label" :title="file.name">{{ file.name }}</span>
|
||||||
</span>
|
<button
|
||||||
</div>
|
type="button"
|
||||||
|
class="file-chip-remove"
|
||||||
|
:disabled="submitting || reviewActionBusy"
|
||||||
|
:aria-label="`移除 ${file.name}`"
|
||||||
|
@click="removeAttachedFile(file)"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-close"></i>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
|
||||||
<div class="composer-foot">
|
<button
|
||||||
<div class="composer-tools">
|
v-if="hiddenAttachedFileCount"
|
||||||
<button type="button" class="tool-btn" :disabled="submitting || reviewActionBusy" aria-label="上传附件" @click="triggerFileUpload">
|
type="button"
|
||||||
<i class="mdi mdi-paperclip"></i>
|
class="file-chip active summary"
|
||||||
</button>
|
:disabled="submitting || reviewActionBusy"
|
||||||
<span class="composer-tip">Enter 换行,Ctrl + Enter 发送</span>
|
@click="toggleAttachedFilesExpanded"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-file-multiple-outline"></i>
|
||||||
|
另外 {{ hiddenAttachedFileCount }} 份
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="composerFilesExpanded && hiddenAttachedFileCount" class="composer-files-expanded">
|
||||||
|
<article
|
||||||
|
v-for="file in attachedFiles.slice(visibleAttachedFiles.length)"
|
||||||
|
:key="`${file.name}-expanded`"
|
||||||
|
class="composer-expanded-file"
|
||||||
|
>
|
||||||
|
<div class="composer-expanded-file-copy">
|
||||||
|
<i class="mdi mdi-file-document-outline"></i>
|
||||||
|
<span :title="file.name">{{ file.name }}</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="composer-expanded-file-remove"
|
||||||
|
:disabled="submitting || reviewActionBusy"
|
||||||
|
:aria-label="`移除 ${file.name}`"
|
||||||
|
@click="removeAttachedFile(file)"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-close"></i>
|
||||||
|
</button>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="send-btn" type="submit" :disabled="!canSubmit || reviewActionBusy" aria-label="发送">
|
<div class="composer-row" :class="{ 'knowledge-mode': isKnowledgeSession }">
|
||||||
|
<button
|
||||||
|
v-if="!isKnowledgeSession"
|
||||||
|
type="button"
|
||||||
|
class="tool-btn composer-side-btn"
|
||||||
|
:disabled="submitting || reviewActionBusy || sessionSwitchBusy"
|
||||||
|
aria-label="上传附件"
|
||||||
|
@click="triggerFileUpload"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-paperclip"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="composer-shell">
|
||||||
|
<textarea
|
||||||
|
ref="composerTextareaRef"
|
||||||
|
v-model="composerDraft"
|
||||||
|
rows="1"
|
||||||
|
:placeholder="composerPlaceholder"
|
||||||
|
:disabled="submitting || reviewActionBusy || sessionSwitchBusy"
|
||||||
|
@input="handleComposerInput"
|
||||||
|
@keydown.enter.exact.stop
|
||||||
|
@keydown.ctrl.enter.prevent="submitComposer"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="send-btn composer-side-btn" type="submit" :disabled="!canSubmit || reviewActionBusy || sessionSwitchBusy" aria-label="发送">
|
||||||
<i :class="submitting ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-send'"></i>
|
<i :class="submitting ? 'mdi mdi-loading mdi-spin' : 'mdi mdi-send'"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<Transition name="insight-panel">
|
<div
|
||||||
<aside v-if="showInsightPanel" class="insight-panel">
|
v-if="hasInsightPanelContent"
|
||||||
<div class="insight-head" :class="{ 'review-mode': activeReviewPayload }">
|
class="insight-panel-shell"
|
||||||
|
:class="{ collapsed: !showInsightPanel }"
|
||||||
|
:aria-hidden="(!showInsightPanel).toString()"
|
||||||
|
>
|
||||||
|
<aside class="insight-panel">
|
||||||
|
<div v-if="!isKnowledgeSession" class="insight-head" :class="{ 'review-mode': activeReviewPayload }">
|
||||||
<div>
|
<div>
|
||||||
<div v-if="!activeReviewPayload" class="insight-head-eyebrow">
|
<div v-if="!activeReviewPayload" class="insight-head-eyebrow">
|
||||||
<span class="intent-pill" :class="currentInsight.intent">{{ currentIntentLabel }}</span>
|
<span class="intent-pill" :class="currentInsight.intent">{{ currentIntentLabel }}</span>
|
||||||
@@ -267,8 +403,35 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Transition name="insight-switch" mode="out-in">
|
<Transition name="insight-switch" mode="out-in">
|
||||||
<div :key="currentInsight.intent + currentInsight.title" class="insight-body">
|
<div :key="`${activeSessionType}-${currentInsight.intent}-${currentInsight.title}`" class="insight-body">
|
||||||
<template v-if="currentInsight.intent === 'agent' && currentInsight.agent">
|
<template v-if="isKnowledgeSession">
|
||||||
|
<section class="insight-card knowledge-hot-card">
|
||||||
|
<div class="card-head">
|
||||||
|
<h4>热门问题 Top 10</h4>
|
||||||
|
</div>
|
||||||
|
<div class="knowledge-question-list">
|
||||||
|
<button
|
||||||
|
v-for="(item, index) in hotKnowledgeQuestions"
|
||||||
|
:key="item"
|
||||||
|
type="button"
|
||||||
|
class="knowledge-question-btn"
|
||||||
|
:disabled="submitting || reviewActionBusy || deleteSessionBusy || sessionSwitchBusy"
|
||||||
|
@click="askHotKnowledgeQuestion(item)"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="knowledge-question-index"
|
||||||
|
:class="resolveKnowledgeRankTone(index)"
|
||||||
|
>
|
||||||
|
{{ resolveKnowledgeRankLabel(index) }}
|
||||||
|
</span>
|
||||||
|
<span class="knowledge-question-copy">{{ item }}</span>
|
||||||
|
<i class="mdi mdi-arrow-top-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="currentInsight.intent === 'agent' && currentInsight.agent">
|
||||||
<template v-if="activeReviewPayload">
|
<template v-if="activeReviewPayload">
|
||||||
<section class="review-side-card review-side-overview-card">
|
<section class="review-side-card review-side-overview-card">
|
||||||
<div class="review-side-intent-row">
|
<div class="review-side-intent-row">
|
||||||
@@ -422,8 +585,137 @@
|
|||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section v-if="reviewDocumentCount" class="review-side-card review-document-switch-card">
|
||||||
|
<div class="review-side-head review-document-switch-head">
|
||||||
|
<div class="review-side-head-copy">
|
||||||
|
<strong>票据识别结果卡片</strong>
|
||||||
|
<p>逐张查看 OCR 结果,可直接修正后再继续提交。</p>
|
||||||
|
</div>
|
||||||
|
<div class="review-document-nav">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="review-document-nav-btn"
|
||||||
|
:disabled="activeReviewDocumentIndex === 0"
|
||||||
|
aria-label="上一张票据"
|
||||||
|
@click="goReviewDocument(-1)"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
<span>{{ activeReviewDocumentIndex + 1 }} / {{ reviewDocumentCount }}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="review-document-nav-btn"
|
||||||
|
:disabled="activeReviewDocumentIndex >= reviewDocumentCount - 1"
|
||||||
|
aria-label="下一张票据"
|
||||||
|
@click="goReviewDocument(1)"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="activeReviewDocument" class="review-document-stage">
|
||||||
|
<div class="review-document-stage-head">
|
||||||
|
<div class="review-document-stage-copy">
|
||||||
|
<span class="review-document-index-chip">票据 {{ activeReviewDocument.index }}</span>
|
||||||
|
<strong :title="activeReviewDocument.filename">{{ activeReviewDocument.filename }}</strong>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
v-if="canPreviewActiveReviewDocument"
|
||||||
|
type="button"
|
||||||
|
class="review-side-link"
|
||||||
|
:disabled="submitting || reviewActionBusy"
|
||||||
|
@click="openActiveReviewDocumentPreview"
|
||||||
|
>
|
||||||
|
查看原图
|
||||||
|
<i class="mdi mdi-arrow-top-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="review-document-meta-chip-row">
|
||||||
|
<span class="review-document-meta-chip">{{ activeReviewDocument.documentTypeLabel }}</span>
|
||||||
|
<span class="review-document-meta-chip">{{ activeReviewDocument.expenseTypeLabel }}</span>
|
||||||
|
<span class="review-document-meta-chip confidence">{{ activeReviewDocument.confidenceLabel }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="review-document-scroll">
|
||||||
|
<div class="review-document-preview-card" :class="activeReviewDocumentPreview?.kind || 'file'">
|
||||||
|
<img
|
||||||
|
v-if="activeReviewDocumentPreview?.kind === 'image' && activeReviewDocumentPreview?.url"
|
||||||
|
:src="activeReviewDocumentPreview.url"
|
||||||
|
:alt="activeReviewDocument.filename"
|
||||||
|
/>
|
||||||
|
<div v-else-if="activeReviewDocumentPreview?.kind === 'pdf'" class="review-document-preview-placeholder">
|
||||||
|
<i class="mdi mdi-file-pdf-box"></i>
|
||||||
|
<strong>PDF 原件可预览</strong>
|
||||||
|
<p>点击右上角“查看原图”可查看完整 PDF。</p>
|
||||||
|
</div>
|
||||||
|
<div v-else class="review-document-preview-placeholder">
|
||||||
|
<i class="mdi mdi-file-search-outline"></i>
|
||||||
|
<strong>当前无可预览原图</strong>
|
||||||
|
<p>这张票据仍可继续修改识别结果。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="review-document-edit-field summary">
|
||||||
|
<span>票据摘要</span>
|
||||||
|
<textarea
|
||||||
|
v-model="activeReviewDocument.summary"
|
||||||
|
rows="3"
|
||||||
|
:disabled="submitting || reviewActionBusy"
|
||||||
|
placeholder="可根据原图修正 OCR 摘要"
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="review-document-edit-field">
|
||||||
|
<span>票据场景</span>
|
||||||
|
<input
|
||||||
|
v-model="activeReviewDocument.scene_label"
|
||||||
|
type="text"
|
||||||
|
:disabled="submitting || reviewActionBusy"
|
||||||
|
placeholder="例如:业务招待费 / 差旅费"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div v-if="activeReviewDocument.fields.length" class="review-document-edit-grid">
|
||||||
|
<label
|
||||||
|
v-for="field in activeReviewDocument.fields"
|
||||||
|
:key="`${activeReviewDocument.filename}-${field.label}`"
|
||||||
|
class="review-document-edit-field"
|
||||||
|
>
|
||||||
|
<span>{{ field.label }}</span>
|
||||||
|
<input
|
||||||
|
v-model="field.value"
|
||||||
|
type="text"
|
||||||
|
:disabled="submitting || reviewActionBusy"
|
||||||
|
:placeholder="`修正 ${field.label}`"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div v-else class="review-side-empty compact">
|
||||||
|
<span class="review-side-empty-icon">
|
||||||
|
<i class="mdi mdi-text-recognition"></i>
|
||||||
|
</span>
|
||||||
|
<strong>暂无结构化字段</strong>
|
||||||
|
<p>当前只返回了摘要信息,你仍然可以直接修改上面的票据摘要。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="activeReviewDocument.warnings?.length" class="review-document-warning-list">
|
||||||
|
<article
|
||||||
|
v-for="warning in activeReviewDocument.warnings"
|
||||||
|
:key="`${activeReviewDocument.filename}-${warning}`"
|
||||||
|
class="review-document-warning-item"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-alert-circle-outline"></i>
|
||||||
|
<span>{{ warning }}</span>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="reviewInlineDirty"
|
v-if="reviewHasUnsavedChanges"
|
||||||
type="button"
|
type="button"
|
||||||
class="review-side-save-pill"
|
class="review-side-save-pill"
|
||||||
:disabled="reviewActionBusy || submitting"
|
:disabled="reviewActionBusy || submitting"
|
||||||
@@ -473,13 +765,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</aside>
|
</aside>
|
||||||
</Transition>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
:open="deleteSessionDialogOpen"
|
||||||
|
badge="删除会话"
|
||||||
|
badge-tone="danger"
|
||||||
|
title="确认删除当前会话内容?"
|
||||||
|
description="删除后将清空当前类型会话下的全部对话内容,且无法恢复。"
|
||||||
|
cancel-text="取消"
|
||||||
|
confirm-text="确认删除"
|
||||||
|
busy-text="删除中..."
|
||||||
|
confirm-tone="danger"
|
||||||
|
confirm-icon="mdi mdi-delete-outline"
|
||||||
|
:busy="deleteSessionBusy"
|
||||||
|
@close="closeDeleteSessionDialog"
|
||||||
|
@confirm="confirmDeleteCurrentSession"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
:open="leaveKnowledgeDialogOpen"
|
||||||
|
badge="离开问答"
|
||||||
|
badge-tone="warning"
|
||||||
|
title="离开页面会清除当前知识问答会话"
|
||||||
|
description="确认离开后,当前财务知识问答 session 会被清除且不再保留。刷新页面不算退出。"
|
||||||
|
cancel-text="继续停留"
|
||||||
|
confirm-text="确认离开"
|
||||||
|
busy-text="清理中..."
|
||||||
|
confirm-tone="danger"
|
||||||
|
confirm-icon="mdi mdi-exit-to-app"
|
||||||
|
:busy="leaveKnowledgeBusy"
|
||||||
|
@close="closeLeaveKnowledgeDialog"
|
||||||
|
@confirm="confirmLeaveKnowledgeSession"
|
||||||
|
/>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
:open="reviewCancelDialogOpen"
|
:open="reviewCancelDialogOpen"
|
||||||
badge="取消核对"
|
badge="取消核对"
|
||||||
@@ -496,6 +820,42 @@
|
|||||||
@confirm="confirmCancelReview"
|
@confirm="confirmCancelReview"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Transition name="assistant-modal">
|
||||||
|
<div v-if="documentPreviewDialog.open" class="assistant-overlay review-overlay">
|
||||||
|
<section class="review-preview-modal">
|
||||||
|
<header class="review-preview-head">
|
||||||
|
<div>
|
||||||
|
<span class="assistant-badge">票据原图</span>
|
||||||
|
<h3>{{ documentPreviewDialog.filename }}</h3>
|
||||||
|
</div>
|
||||||
|
<button class="close-btn" type="button" aria-label="关闭原图预览" @click="closeDocumentPreview">
|
||||||
|
<i class="mdi mdi-close"></i>
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="review-preview-body" :class="documentPreviewDialog.kind">
|
||||||
|
<img
|
||||||
|
v-if="documentPreviewDialog.kind === 'image'"
|
||||||
|
:src="documentPreviewDialog.url"
|
||||||
|
:alt="documentPreviewDialog.filename"
|
||||||
|
/>
|
||||||
|
<iframe
|
||||||
|
v-else-if="documentPreviewDialog.kind === 'pdf'"
|
||||||
|
:src="documentPreviewDialog.url"
|
||||||
|
title="票据 PDF 原图预览"
|
||||||
|
></iframe>
|
||||||
|
<div v-else class="review-side-empty">
|
||||||
|
<span class="review-side-empty-icon">
|
||||||
|
<i class="mdi mdi-file-outline"></i>
|
||||||
|
</span>
|
||||||
|
<strong>当前文件暂不支持内置预览</strong>
|
||||||
|
<p>请重新上传图片或 PDF 票据,以便在这里查看原图。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
<Transition name="assistant-modal">
|
<Transition name="assistant-modal">
|
||||||
<div v-if="reviewEditDialogOpen" class="assistant-overlay review-overlay">
|
<div v-if="reviewEditDialogOpen" class="assistant-overlay review-overlay">
|
||||||
<section class="review-edit-modal">
|
<section class="review-edit-modal">
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
<div class="applicant-card">
|
<div class="applicant-card">
|
||||||
<div class="portrait">{{ profile.avatar }}</div>
|
<div class="portrait">{{ profile.avatar }}</div>
|
||||||
<div class="applicant-copy">
|
<div class="applicant-copy">
|
||||||
<h2>{{ profile.name }} <span>{{ request.typeLabel }}</span></h2>
|
<h2>{{ profile.name }}</h2>
|
||||||
<p>{{ profile.position }} · {{ profile.identity }}</p>
|
<p>{{ profile.position }}</p>
|
||||||
<div class="applicant-meta">
|
<div class="applicant-meta">
|
||||||
<div v-for="item in profile.facts" :key="item.label" class="applicant-meta-item">
|
<div v-for="item in profile.facts" :key="item.label" class="applicant-meta-item">
|
||||||
<span>{{ item.label }}</span>
|
<span>{{ item.label }}</span>
|
||||||
|
|||||||
Reference in New Issue
Block a user