feat(web): 工作台 AI 模式与差旅/风险建议交互优化
- 新增 PersonalWorkbenchAiMode 组件、AI 侧边栏与 orb 机器人视觉资源 - 新增 aiApplicationDraftModel / aiExpenseDraftModel / aiWorkbenchConversationStore 及业务准入 aiSidebarBusinessAccess,支撑 AI 模式下的申请与报销草稿 - 顶栏、侧边栏、工作台样式重构,适配 AI 模式切换与响应式布局 - 同步 steward plan/off_topic、差旅报销引导流、风险建议卡片等测试
This commit is contained in:
@@ -38,9 +38,6 @@ const workbenchInsightStyles = readFileSync(
|
||||
fileURLToPath(new URL('../src/assets/styles/components/personal-workbench-insights.css', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const heroBackgroundAsset = fileURLToPath(
|
||||
new URL('../src/assets/images/hero-3d-banner.png', import.meta.url)
|
||||
)
|
||||
const capabilityGlassAsset = fileURLToPath(
|
||||
new URL('../src/assets/personal-workbench-card-glass-capability.webp', import.meta.url)
|
||||
)
|
||||
@@ -48,14 +45,41 @@ const panelGlassAsset = fileURLToPath(
|
||||
new URL('../src/assets/personal-workbench-card-glass-panel.webp', import.meta.url)
|
||||
)
|
||||
|
||||
test('workbench assistant greets the current employee without the old helper tag', () => {
|
||||
test('traditional workbench uses compact real reimbursement trend chart instead of assistant composer', () => {
|
||||
assert.doesNotMatch(workbench, /assistant-tag/)
|
||||
assert.doesNotMatch(workbench, /AI 报销助手/)
|
||||
assert.match(workbench, /\{\{ typedTitlePrefix \}\}<span v-if="titleTypingDone">小财管家<\/span>/)
|
||||
assert.match(workbench, /const heroTitleText = computed\(\(\) => `嗨,\$\{displayUserName\.value\},我是您的 `\)/)
|
||||
assert.match(workbench, /placeholder="一次性描述申请、报销和附件处理事项,小财管家会先拆解再执行\.\.\."/)
|
||||
assert.doesNotMatch(workbench, /class="panel assistant-hero workbench-trend-hero"/)
|
||||
assert.match(workbench, /class="workbench-trend-card"/)
|
||||
assert.match(workbench, /报销趋势/)
|
||||
assert.match(workbench, /import TrendChart from '\.\.\/charts\/TrendChart\.vue'/)
|
||||
assert.match(workbench, /<TrendChart[\s\S]*mode="compareAmount"/)
|
||||
assert.match(workbench, /:labels="reimbursementTrendLabels"/)
|
||||
assert.match(workbench, /:claim-amount="reimbursementTrendAmounts"/)
|
||||
assert.match(workbench, /:comparison-amount="reimbursementTrendPreviousAmounts"/)
|
||||
assert.match(workbench, /compact/)
|
||||
assert.match(workbench, /与分析看板同源/)
|
||||
assert.doesNotMatch(workbench, /class="trend-chart-svg"/)
|
||||
assert.doesNotMatch(workbench, /currentTrendPath/)
|
||||
assert.doesNotMatch(workbench, /comparisonTrendPath/)
|
||||
assert.doesNotMatch(workbench, /reimbursementTrendPoints/)
|
||||
assert.doesNotMatch(workbench, /FALLBACK_REIMBURSEMENT_TREND_ROWS/)
|
||||
assert.doesNotMatch(workbench, /assistant-composer/)
|
||||
assert.doesNotMatch(workbench, /textarea/)
|
||||
assert.doesNotMatch(workbench, /quick-prompts/)
|
||||
assert.doesNotMatch(workbench, /useWorkbenchComposerDate/)
|
||||
assert.match(workbench, /const displayUserName = computed/)
|
||||
assert.match(workbench, /user\.name/)
|
||||
assert.match(workbenchStyles, /--hero-title-size:\s*34px;/)
|
||||
assert.match(workbenchStyles, /--trend-card-min-height:\s*260px;/)
|
||||
assert.match(workbenchStyles, /\.workbench\s*\{[\s\S]*display:\s*flex;[\s\S]*flex-direction:\s*column;/)
|
||||
assert.match(workbenchStyles, /\.workbench-trend-hero\s*\{[\s\S]*height:\s*var\(--trend-card-min-height\);[\s\S]*min-height:\s*0;/)
|
||||
assert.match(workbenchStyles, /\.workbench-trend-card\s*\{[\s\S]*grid-template-columns:\s*minmax\(200px,\s*0\.28fr\) minmax\(0,\s*1fr\);/)
|
||||
assert.match(workbenchStyles, /\.workbench-trend-card\s*\{[\s\S]*height:\s*100%;[\s\S]*min-height:\s*0;/)
|
||||
assert.match(workbenchStyles, /\.workbench-trend-card\s*\{[\s\S]*background:\s*transparent;[\s\S]*box-shadow:\s*none;/)
|
||||
assert.match(workbenchStyles, /\.trend-total\s*\{[\s\S]*font-size:\s*clamp\(38px,\s*3\.3vw,\s*54px\);/)
|
||||
assert.match(workbenchStyles, /\.capability-grid\s*\{[\s\S]*flex:\s*0 0 var\(--capability-row-height\);/)
|
||||
assert.match(workbenchStyles, /\.workbench-content-grid\s*\{[\s\S]*flex:\s*1 1 auto;/)
|
||||
assert.match(workbenchStyles, /\.trend-chart-panel\s*\{[\s\S]*min-height:\s*0;/)
|
||||
})
|
||||
|
||||
test('workbench capability cards open assistant without injecting canned prompts', () => {
|
||||
@@ -91,17 +115,22 @@ test('workbench capability cards keep user-entered context only', () => {
|
||||
assert.equal(payload.files, files)
|
||||
})
|
||||
|
||||
test('workbench hero uses theme-tintable background image', () => {
|
||||
assert.match(workbench, /hero-3d-banner\.png/)
|
||||
test('workbench trend panel is a single compact surface without background art overlay', () => {
|
||||
assert.match(workbench, /class="workbench-trend-card"/)
|
||||
assert.doesNotMatch(workbench, /hero-3d-banner\.png/)
|
||||
assert.doesNotMatch(workbench, /personal-workbench-hero-bg-theme-base\.(webp|png)/)
|
||||
assert.match(workbench, /--assistant-bg-image.*workbenchHeroBackground/)
|
||||
assert.match(workbenchStyles, /--assistant-theme-tint:[\s\S]*--theme-primary-rgb/)
|
||||
assert.match(workbenchStyles, /url\("\.\.\/\.\.\/images\/workbench-hero-right-bg\.png"\) var\(--assistant-bg-position\) \/ var\(--assistant-decor-width\) auto no-repeat/)
|
||||
assert.match(workbenchStyles, /\.assistant-hero::after\s*\{[\s\S]*content:\s*"";/)
|
||||
assert.match(workbenchResponsiveStyles, /--assistant-bg-position:\s*right center;/)
|
||||
assert.doesNotMatch(workbenchStyles, /url\("\.\.\/\.\.\/images\/workbench-hero-right-bg\.png"\)/)
|
||||
assert.doesNotMatch(workbenchStyles, /\.assistant-hero::after/)
|
||||
assert.doesNotMatch(workbenchResponsiveStyles, /--assistant-bg-position/)
|
||||
assert.doesNotMatch(workbenchResponsiveStyles, /\.assistant-hero/)
|
||||
assert.doesNotMatch(workbenchResponsiveStyles, /homepage_backgraound/)
|
||||
assert.ok(statSync(heroBackgroundAsset).size > 1024)
|
||||
assert.ok(statSync(heroBackgroundAsset).size < 600 * 1024)
|
||||
})
|
||||
|
||||
test('workbench hero does not own the global AI mode switch', () => {
|
||||
assert.doesNotMatch(workbench, /workbench-mode-switch/)
|
||||
assert.doesNotMatch(workbench, /ai-mode-toggle/)
|
||||
assert.doesNotMatch(workbenchStyles, /workbench-mode-switch/)
|
||||
assert.doesNotMatch(workbenchResponsiveStyles, /workbench-mode-switch/)
|
||||
})
|
||||
|
||||
test('workbench cards use layered glass material instead of texture-led cards', () => {
|
||||
@@ -141,13 +170,16 @@ test('workbench cards use layered glass material instead of texture-led cards',
|
||||
assert.ok(statSync(panelGlassAsset).size < 24 * 1024)
|
||||
})
|
||||
|
||||
test('workbench submit shows intent recognition feedback before assistant opens', () => {
|
||||
assert.match(workbench, /class="assistant-intent-status"/)
|
||||
assert.match(workbench, /aria-live="polite"/)
|
||||
assert.match(workbench, /正在识别意图,准备进入对应助手/)
|
||||
assert.match(workbench, /startPendingAction\('intent'\)/)
|
||||
assert.match(workbench, /if \(open\) \{\s*clearPendingAction\(\)/)
|
||||
assert.match(workbench, /:readonly="isComposerPending"/)
|
||||
test('traditional workbench no longer keeps composer pending state', () => {
|
||||
assert.doesNotMatch(workbench, /class="assistant-intent-status"/)
|
||||
assert.doesNotMatch(workbench, /正在识别意图,准备进入对应助手/)
|
||||
assert.doesNotMatch(workbench, /startPendingAction\('intent'\)/)
|
||||
assert.doesNotMatch(workbench, /isComposerPending/)
|
||||
assert.doesNotMatch(workbench, /assistantDraft/)
|
||||
assert.doesNotMatch(workbench, /fetchLatestConversation/)
|
||||
assert.doesNotMatch(workbench, /clearUserConversations/)
|
||||
assert.match(workbench, /function openCapabilityAssistant\(item\)/)
|
||||
assert.match(workbench, /buildWorkbenchCapabilityAssistantPayload\(item,\s*buildAssistantPayload\(\)\)/)
|
||||
})
|
||||
|
||||
test('workbench document progress has range filter, document types and empty state', () => {
|
||||
|
||||
Reference in New Issue
Block a user