feat(web): 工作台 AI 模式与差旅/风险建议交互优化
- 新增 PersonalWorkbenchAiMode 组件、AI 侧边栏与 orb 机器人视觉资源 - 新增 aiApplicationDraftModel / aiExpenseDraftModel / aiWorkbenchConversationStore 及业务准入 aiSidebarBusinessAccess,支撑 AI 模式下的申请与报销草稿 - 顶栏、侧边栏、工作台样式重构,适配 AI 模式切换与响应式布局 - 同步 steward plan/off_topic、差旅报销引导流、风险建议卡片等测试
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<header class="topbar" :class="{ 'chat-mode': isChat, 'detail-mode': isRequestDetail }">
|
||||
<div class="title-group">
|
||||
<div v-if="!isWorkbenchAiHome" class="title-group">
|
||||
<div class="eyebrow">{{ eyebrowLabel }}</div>
|
||||
<h1>{{ currentView.title }}</h1>
|
||||
<p>{{ currentView.desc }}</p>
|
||||
</div>
|
||||
<div v-else class="title-group" aria-hidden="true"></div>
|
||||
|
||||
<div class="top-actions">
|
||||
<template v-if="isChat">
|
||||
@@ -278,12 +279,23 @@
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<button class="company-switcher" type="button" aria-label="切换公司">
|
||||
<span>{{ displayCompanyName }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<button class="company-switcher" type="button" aria-label="切换公司">
|
||||
<span>{{ displayCompanyName }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="topbar-ai-mode-toggle"
|
||||
:class="{ active: isTopbarAiMode }"
|
||||
:aria-pressed="isTopbarAiMode"
|
||||
:aria-label="topbarWorkbenchModeTitle"
|
||||
:title="topbarWorkbenchModeTitle"
|
||||
@click="toggleTopbarWorkbenchMode"
|
||||
>
|
||||
<span class="topbar-ai-mode-toggle__glyph">AI</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isDocuments">
|
||||
<div class="kpi-chips">
|
||||
@@ -345,18 +357,36 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isEmployees">
|
||||
<div class="kpi-chips">
|
||||
<div v-for="kpi in employeeKpis" :key="kpi.label" class="kpi-chip" :style="{ '--chip-color': kpi.color }">
|
||||
<span class="chip-value">{{ kpi.value }}<small>{{ kpi.unit }}</small></span>
|
||||
<span class="chip-label">{{ kpi.label }}</span>
|
||||
<span class="chip-delta" :class="kpi.trend">{{ kpi.meta }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
<template v-else-if="isEmployees">
|
||||
<div class="kpi-chips">
|
||||
<div v-for="kpi in employeeKpis" :key="kpi.label" class="kpi-chip" :style="{ '--chip-color': kpi.color }">
|
||||
<span class="chip-value">{{ kpi.value }}<small>{{ kpi.unit }}</small></span>
|
||||
<span class="chip-label">{{ kpi.label }}</span>
|
||||
<span class="chip-delta" :class="kpi.trend">{{ kpi.meta }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="showAiModeUtilityActions" class="topbar-utility-actions" aria-label="AI模式快捷操作">
|
||||
<button class="company-switcher" type="button" aria-label="切换公司">
|
||||
<span>{{ displayCompanyName }}</span>
|
||||
<i class="mdi mdi-chevron-down"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="topbar-ai-mode-toggle"
|
||||
:class="{ active: isTopbarAiMode }"
|
||||
:aria-pressed="isTopbarAiMode"
|
||||
:aria-label="topbarWorkbenchModeTitle"
|
||||
:title="topbarWorkbenchModeTitle"
|
||||
@click="toggleTopbarWorkbenchMode"
|
||||
>
|
||||
<span class="topbar-ai-mode-toggle__glyph">AI</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
@@ -394,14 +424,18 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
workbenchSummary: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
companyName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
workbenchSummary: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
workbenchMode: {
|
||||
type: String,
|
||||
default: 'traditional'
|
||||
},
|
||||
companyName: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
detailMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -431,10 +465,11 @@ const emit = defineEmits([
|
||||
'update:overviewDashboard',
|
||||
'batchApprove',
|
||||
'openChat',
|
||||
'newApplication',
|
||||
'openDocument',
|
||||
'navigate'
|
||||
])
|
||||
'newApplication',
|
||||
'openDocument',
|
||||
'navigate',
|
||||
'toggleWorkbenchMode'
|
||||
])
|
||||
const isChat = computed(() => props.activeView === 'chat')
|
||||
const isOverview = computed(() => props.activeView === 'overview')
|
||||
const isWorkbench = computed(() => props.activeView === 'workbench')
|
||||
@@ -444,12 +479,16 @@ const isRequests = computed(() => props.activeView === 'requests')
|
||||
const isDigitalEmployees = computed(() => props.activeView === 'digitalEmployees')
|
||||
const isApproval = computed(() => props.activeView === 'approval')
|
||||
const isPolicies = computed(() => props.activeView === 'policies')
|
||||
const isEmployees = computed(() => props.activeView === 'employees')
|
||||
const isEmployees = computed(() => props.activeView === 'employees')
|
||||
const eyebrowLabel = computed(() => (
|
||||
String(props.currentView?.eyebrow || '').trim()
|
||||
|| (isChat.value ? 'Smart Finance Q&A' : 'Smart Expense Operations')
|
||||
))
|
||||
const displayCompanyName = computed(() => String(props.companyName || '远光软件股份有限公司').trim() || '远光软件股份有限公司')
|
||||
const isTopbarAiMode = computed(() => props.workbenchMode === 'ai')
|
||||
const topbarWorkbenchModeTitle = computed(() => (isTopbarAiMode.value ? 'AI 模式,点击切换传统模式' : '传统模式,点击切换 AI 模式'))
|
||||
const isWorkbenchAiHome = computed(() => isWorkbench.value && isTopbarAiMode.value)
|
||||
const showAiModeUtilityActions = computed(() => isTopbarAiMode.value && !isWorkbench.value)
|
||||
const MAX_NOTIFICATION_ITEMS = 30
|
||||
const {
|
||||
markDocumentInboxRowRead,
|
||||
@@ -576,12 +615,16 @@ const readNotifications = computed(() => notificationItems.value.filter((item) =
|
||||
const activeNotifications = computed(() => (
|
||||
notificationTab.value === 'unread' ? unreadNotifications.value : readNotifications.value
|
||||
))
|
||||
const topbarNotificationCount = computed(() => {
|
||||
const count = unreadNotifications.value.length
|
||||
return count > 0 ? Math.min(count, 99) : 0
|
||||
})
|
||||
|
||||
function clearDocumentInboxInitialRefreshTimer() {
|
||||
const topbarNotificationCount = computed(() => {
|
||||
const count = unreadNotifications.value.length
|
||||
return count > 0 ? Math.min(count, 99) : 0
|
||||
})
|
||||
|
||||
function toggleTopbarWorkbenchMode() {
|
||||
emit('toggleWorkbenchMode')
|
||||
}
|
||||
|
||||
function clearDocumentInboxInitialRefreshTimer() {
|
||||
if (documentInboxInitialRefreshTimer && typeof window !== 'undefined') {
|
||||
window.clearTimeout(documentInboxInitialRefreshTimer)
|
||||
documentInboxInitialRefreshTimer = null
|
||||
|
||||
Reference in New Issue
Block a user