feat: 报销审批流重构与管家计划全链路贯通
- 重构报销状态注册表、审批流路由与平台风险标记 - 完善管家意图规划器与模型计划构建器全链路 - 新增 OCR Worker 脚本、数据库会话管理与通知状态 - 优化文档中心、日志视图、预算中心与员工管理交互 - 增强工作台摘要、图标资源与全局主题样式 - 补充审批路由、状态注册、OCR 服务与管家规划器测试覆盖
This commit is contained in:
@@ -9,7 +9,9 @@
|
||||
|
||||
<article class="panel assistant-hero" :style="{ '--assistant-bg-image': `url(${workbenchHeroBackground})` }">
|
||||
<div class="assistant-copy">
|
||||
<h1>嗨,{{ displayUserName }},我是您的 <span>小财管家</span></h1>
|
||||
<h1 class="assistant-hero-title">
|
||||
{{ typedTitlePrefix }}<span v-if="titleTypingDone">小财管家</span><span v-if="!titleTypingDone" class="typing-cursor">|</span>
|
||||
</h1>
|
||||
|
||||
<input
|
||||
ref="fileInputRef"
|
||||
@@ -173,11 +175,12 @@
|
||||
|
||||
<div :class="['capability-grid', capabilityGridClass]" aria-label="AI 财务助手能力">
|
||||
<button
|
||||
v-for="item in visibleAssistantCapabilities"
|
||||
v-for="(item, index) in visibleAssistantCapabilities"
|
||||
:key="item.key"
|
||||
type="button"
|
||||
class="capability-card panel"
|
||||
:class="`capability-card--${item.tone}`"
|
||||
:style="{ '--delay': `${index * 80 + 100}ms` }"
|
||||
@click="openCapabilityAssistant(item)"
|
||||
>
|
||||
<WorkbenchListIcon
|
||||
@@ -196,18 +199,19 @@
|
||||
</div>
|
||||
|
||||
<div class="workbench-content-grid">
|
||||
<article class="panel workbench-card progress-panel">
|
||||
<article class="panel workbench-card progress-panel" style="--delay: 200ms;">
|
||||
<div class="section-head">
|
||||
<h2>费用进度</h2>
|
||||
</div>
|
||||
|
||||
<div class="progress-list">
|
||||
<button
|
||||
v-for="item in visibleProgressItems"
|
||||
v-for="(item, index) in visibleProgressItems"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
class="progress-row"
|
||||
:class="{ 'has-long-duration-divider': item.hasLongDurationDivider }"
|
||||
:style="{ '--item-index': index }"
|
||||
@click="openWorkbenchTarget(item)"
|
||||
>
|
||||
<span class="progress-time-wrapper">
|
||||
@@ -256,7 +260,7 @@
|
||||
</article>
|
||||
|
||||
<aside class="side-column">
|
||||
<article class="panel workbench-card side-panel expense-stats-panel">
|
||||
<article class="panel workbench-card side-panel expense-stats-panel" style="--delay: 300ms;">
|
||||
<div class="section-head side-card-head">
|
||||
<h2>费用统计</h2>
|
||||
<button
|
||||
@@ -273,10 +277,11 @@
|
||||
|
||||
<div class="insight-metric-list" aria-label="费用统计">
|
||||
<div
|
||||
v-for="item in visibleExpenseStatItems"
|
||||
v-for="(item, index) in visibleExpenseStatItems"
|
||||
:key="item.key"
|
||||
class="insight-metric-row"
|
||||
:class="`insight-metric-row--${item.tone}`"
|
||||
:style="{ '--item-index': index }"
|
||||
>
|
||||
<span class="insight-metric-icon" aria-hidden="true">
|
||||
<i :class="item.icon"></i>
|
||||
@@ -289,7 +294,7 @@
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel workbench-card side-panel usage-profile-panel">
|
||||
<article class="panel workbench-card side-panel usage-profile-panel" style="--delay: 400ms;">
|
||||
<div class="section-head side-card-head">
|
||||
<h2>用户画像</h2>
|
||||
<button
|
||||
@@ -306,10 +311,11 @@
|
||||
|
||||
<div class="insight-profile-list" aria-label="用户画像">
|
||||
<div
|
||||
v-for="metric in visibleUsageProfileMetrics"
|
||||
v-for="(metric, index) in visibleUsageProfileMetrics"
|
||||
:key="metric.key"
|
||||
class="insight-profile-card"
|
||||
:class="`insight-profile-card--${metric.tone}`"
|
||||
:style="{ '--item-index': index }"
|
||||
>
|
||||
<span class="insight-profile-icon" aria-hidden="true">
|
||||
<i :class="metric.icon"></i>
|
||||
@@ -441,6 +447,35 @@ const displayUserName = computed(() => {
|
||||
const user = currentUser.value || {}
|
||||
return String(user.name || user.username || '同事').trim() || '同事'
|
||||
})
|
||||
|
||||
const heroTitleText = computed(() => `嗨,${displayUserName.value},我是您的 `)
|
||||
const typedTitlePrefix = ref('')
|
||||
const titleTypingDone = ref(false)
|
||||
let typingInterval = null
|
||||
|
||||
const startTypewriter = () => {
|
||||
typedTitlePrefix.value = ''
|
||||
titleTypingDone.value = false
|
||||
clearInterval(typingInterval)
|
||||
let i = 0
|
||||
const text = heroTitleText.value
|
||||
typingInterval = setInterval(() => {
|
||||
if (i < text.length) {
|
||||
typedTitlePrefix.value += text.charAt(i)
|
||||
i++
|
||||
} else {
|
||||
clearInterval(typingInterval)
|
||||
titleTypingDone.value = true
|
||||
}
|
||||
}, 60)
|
||||
}
|
||||
|
||||
watch(displayUserName, (newVal, oldVal) => {
|
||||
if (oldVal !== newVal && titleTypingDone.value) {
|
||||
typedTitlePrefix.value = `嗨,${newVal},我是您的 `
|
||||
}
|
||||
})
|
||||
|
||||
const expenseActionLabel = computed(() => (hasExpenseConversation.value ? '继续报销' : '新建报销'))
|
||||
const isComposerPending = computed(() => Boolean(pendingAction.value))
|
||||
const composerPendingLabel = computed(() => {
|
||||
@@ -861,6 +896,7 @@ async function handleExpenseConversationAction() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
startTypewriter()
|
||||
refreshLocalExpenseSnapshot()
|
||||
refreshLatestExpenseConversation()
|
||||
loadCurrentEmployeeProfile()
|
||||
@@ -869,6 +905,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(typingInterval)
|
||||
clearPendingAction()
|
||||
document.removeEventListener('click', handleWorkbenchDatePickerOutside)
|
||||
window.removeEventListener(ASSISTANT_SESSION_SNAPSHOT_EVENT, handleAssistantSessionSnapshotChange)
|
||||
|
||||
Reference in New Issue
Block a user