feat: 工作台外壳重构 + /home 改为智能助手 + 产品更名
前端 - MainLayout: 移除顶部标题栏,Workspace Tabs 提至最顶层,通知铃随行 - 账号入口由右上角移至左下角侧边栏底部(下拉:我的资料 / 退出登录), 折叠态仅保留头像 - /home 现渲染智能助手并作为其规范 URL;/tools/smart-assistant 保留为重定向别名 - 工作台总览移至 /overview,侧边栏入口同步调整 - workbench store: 新增 tab 归一化,迁移已持久化的旧标签与旧路由 - 智能助手欢迎语改为「我 Bosheng 数字员工,我帮你」 - 最佳实践卡片: 4 张减为 3 张,图片块高度减半(aspect-ratio 4/3 → 8/3) 产品更名 - 后端 system prompt、CLAUDE.md、PROJECT_STATE.md 及设计文档: 博昇内部培训平台 → EAI 数字员工平台 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>博昇内部培训平台</title>
|
||||
<title>数字员工平台 · EAI Agent Platform</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<!-- Second-level navigation for 资料库. The sidebar carries a single 资料库
|
||||
entry; the sections below it switch here instead of in the sidebar. -->
|
||||
<nav v-if="visibleSections.length > 1" class="library-tabs">
|
||||
<button
|
||||
v-for="section in visibleSections"
|
||||
:key="section.route"
|
||||
class="library-tab"
|
||||
:class="{ active: isCurrent(section.route) }"
|
||||
:title="section.caption"
|
||||
@click="router.push(section.route)"
|
||||
>
|
||||
{{ section.label }}
|
||||
</button>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '@/store/auth'
|
||||
import { knowledgeLinks } from '@/config/workbench'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const auth = useAuthStore()
|
||||
|
||||
const visibleSections = computed(() =>
|
||||
knowledgeLinks.filter((section) => !section.adminOnly || auth.isAdmin)
|
||||
)
|
||||
|
||||
function isCurrent(path) {
|
||||
return route.path === path || route.path.startsWith(`${path}/`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.library-tabs {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 0 20px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e8ecf1;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.library-tab {
|
||||
position: relative;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 12px 14px;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.library-tab:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.library-tab.active {
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.library-tab.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
right: 14px;
|
||||
bottom: -1px;
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
background: #409eff;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="chat-layout" :class="{ 'sidebar-collapsed': sidebarCollapsed }">
|
||||
<!-- Left sidebar: conversation history -->
|
||||
<aside class="chat-sidebar" :class="{ collapsed: sidebarCollapsed }">
|
||||
<!-- Left sidebar: conversation history.
|
||||
Off by default: the global shell (MainLayout) owns the WorkBuddy sidebar,
|
||||
so rendering this one too would stack two 220px sidebars. -->
|
||||
<aside v-if="showSidebar" class="chat-sidebar" :class="{ collapsed: sidebarCollapsed }">
|
||||
<div class="sidebar-header">
|
||||
<button class="new-chat-btn" @click="$emit('newChat')">
|
||||
<span class="btn-icon">+</span>
|
||||
@@ -67,8 +69,8 @@
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Sidebar toggle handle -->
|
||||
<div class="sidebar-toggle" @click="sidebarCollapsed = !sidebarCollapsed">
|
||||
<!-- Sidebar toggle handle (only meaningful when the sidebar is shown) -->
|
||||
<div v-if="showSidebar" class="sidebar-toggle" @click="sidebarCollapsed = !sidebarCollapsed">
|
||||
{{ sidebarCollapsed ? '▷' : '◁' }}
|
||||
</div>
|
||||
|
||||
@@ -79,7 +81,7 @@
|
||||
<!-- Welcome state -->
|
||||
<div v-if="!hasMessages" class="welcome-state">
|
||||
<slot name="welcome">
|
||||
<div class="welcome-title">AI Tools, 我帮你</div>
|
||||
<div class="welcome-title">我 Bosheng 数字员工,我帮你</div>
|
||||
<div class="welcome-subtitle">今天帮你做些什么? @ 引用对话文件,/ 调用技能与指令</div>
|
||||
</slot>
|
||||
</div>
|
||||
@@ -122,6 +124,12 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// The global shell provides navigation and the 新建任务 action, so the
|
||||
// page-level sidebar stays hidden unless a caller explicitly opts in.
|
||||
showSidebar: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
@@ -141,11 +149,19 @@ function toggleHistory() {
|
||||
import { onMounted, onBeforeUnmount } from 'vue'
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize)
|
||||
// The shell's 新建任务 button lives outside this component now, so it
|
||||
// asks for a reset through a window event instead of a direct call.
|
||||
window.addEventListener('wb:new-task', handleShellNewTask)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
window.removeEventListener('wb:new-task', handleShellNewTask)
|
||||
})
|
||||
|
||||
function handleShellNewTask() {
|
||||
emit('newChat')
|
||||
}
|
||||
|
||||
function handleResize() {
|
||||
if (window.innerWidth < 768) {
|
||||
sidebarCollapsed.value = true
|
||||
|
||||
@@ -1,10 +1,31 @@
|
||||
// AI 工具(Chat-First 主入口)—— 全部走 ChatLayout 聊天优先界面
|
||||
export const chatTools = [
|
||||
{ key: 'smart-assistant', label: '智能助手', caption: '对话 / 任务拆解 / 文案生成', route: '/home' },
|
||||
{ key: 'document-translate', label: '文档翻译', caption: '多语言翻译与导出', route: '/tools/document-translate' },
|
||||
{ key: 'copy-proofreading', label: '文案校对', caption: '错别字与语病检查', route: '/tools/copy-proofreading' },
|
||||
{ key: 'audio-transcribe', label: '语音转写', caption: '音频转文字', route: '/tools/audio-transcribe' },
|
||||
{ key: 'batch-extract', label: '批量提取', caption: '字段批量抽取', route: '/tools/batch-extract' },
|
||||
{ key: 'contract-review', label: '合同审查', caption: '条款完整性与风险评分', route: '/tools/contract-review' },
|
||||
{ key: 'report-generation', label: '报告生成', caption: '日报 / 周报 / 复盘', route: '/report-gen' },
|
||||
]
|
||||
|
||||
export const topNavItems = [
|
||||
{ key: 'apps', label: '01 专员工作区', title: '专员工作区', route: '/home' },
|
||||
{ key: 'market', label: '02 专员市场', title: '专员市场', route: '/market', adminOnly: true },
|
||||
{ key: 'studio', label: '03 工坊', title: '工坊', route: '/studio', adminOnly: true },
|
||||
{ key: 'knowledge-app', label: '04 知识库应用', title: '知识库应用', route: '/knowledge-hub' },
|
||||
{ key: 'knowledge-admin', label: '05 知识库管理', title: '知识库管理', route: '/knowledge-management', adminOnly: true },
|
||||
{ key: 'console', label: '06 控制台', title: '控制台', route: '/console', adminOnly: true },
|
||||
{ key: 'chat-tools', label: 'AI 工具', title: 'AI 工具', route: '/home' },
|
||||
{ key: 'ziliaoku', label: '字料库', title: '字料库', route: '/knowledge-hub' },
|
||||
{ key: 'apps', label: '专员工作区', title: '专员工作区', route: '/home' },
|
||||
{ key: 'market', label: '专员市场', title: '专员市场', route: '/market', adminOnly: true },
|
||||
{ key: 'studio', label: '工坊', title: '工坊', route: '/studio', adminOnly: true },
|
||||
{ key: 'console', label: '控制台', title: '控制台', route: '/console', adminOnly: true },
|
||||
]
|
||||
|
||||
// 字料库子级 Tab 导航(用于页面内 tab 切换)
|
||||
export const ziliaokuTabs = [
|
||||
{ key: 'chat-search', label: '知识问答', route: '/knowledge-hub' },
|
||||
{ key: 'knowledge-admin', label: '知识管理', route: '/knowledge-management' },
|
||||
{ key: 'product', label: '产品知识', route: '/products' },
|
||||
{ key: 'sales', label: '销售培训', route: '/courses' },
|
||||
{ key: 'company', label: '公司介绍', route: '/company-train' },
|
||||
{ key: 'materials', label: '素材审批', route: '/knowledge/material-approve' },
|
||||
]
|
||||
|
||||
export const businessApps = [
|
||||
@@ -697,6 +718,7 @@ const genericSurfaceContext = {
|
||||
}
|
||||
|
||||
export function getWorkbenchSurface(path) {
|
||||
if (path.startsWith('/tools/') || path === '/report-gen') return 'chat-tools'
|
||||
if (path === '/knowledge-hub' || path.startsWith('/products') || path.startsWith('/courses') || path.startsWith('/company-train')) {
|
||||
return 'knowledge-app'
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,9 +5,9 @@
|
||||
<div class="brand-content">
|
||||
<el-icon class="brand-logo" :size="24"><School /></el-icon>
|
||||
<div class="brand-text">
|
||||
<div class="brand-name">博昇AI研究院</div>
|
||||
<div class="brand-sub">内部培训平台</div>
|
||||
<div class="brand-en">EAI-Training</div>
|
||||
<div class="brand-name">EAI 数字员工平台</div>
|
||||
<div class="brand-sub">AI Agent Platform</div>
|
||||
<div class="brand-en">eai_agentplatform</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,9 @@ const routes = [
|
||||
component: () => import('@/layout/MainLayout.vue'),
|
||||
redirect: '/home',
|
||||
children: [
|
||||
{ path: 'home', name: 'Home', component: () => import('@/views/home/HomePage.vue') },
|
||||
// 智能助手 is the front door; /home is its canonical URL.
|
||||
{ path: 'home', name: 'Home', component: () => import('@/views/tools/SmartAssistantPage.vue') },
|
||||
{ path: 'overview', name: 'Overview', component: () => import('@/views/home/HomePage.vue') },
|
||||
{ path: 'studio', name: 'Studio', component: () => import('@/views/workbench/StudioPage.vue') },
|
||||
{ path: 'console', name: 'Console', component: () => import('@/views/workbench/ConsolePage.vue') },
|
||||
{ path: 'knowledge-hub', name: 'KnowledgeHub', component: () => import('@/views/workbench/KnowledgeHub.vue') },
|
||||
@@ -27,7 +29,8 @@ const routes = [
|
||||
{ path: 'report-gen', name: 'ReportGeneration', component: () => import('@/views/report/ReportGenerationPage.vue') },
|
||||
{ path: 'tools/document-translate', name: 'DocumentTranslate', component: () => import('@/views/tools/DocumentTranslatePage.vue') },
|
||||
{ path: 'tools/copy-proofreading', name: 'CopyProofreading', component: () => import('@/views/tools/CopyProofreadingPage.vue') },
|
||||
{ path: 'tools/smart-assistant', name: 'SmartAssistant', component: () => import('@/views/tools/SmartAssistantPage.vue') },
|
||||
// Kept as an alias so existing links and bookmarks still land correctly.
|
||||
{ path: 'tools/smart-assistant', name: 'SmartAssistant', redirect: '/home' },
|
||||
{ path: 'tools/batch-extract', name: 'BatchExtract', component: () => import('@/views/tools/BatchExtractPage.vue') },
|
||||
{ path: 'tools/audio-transcribe', name: 'AudioTranscribe', component: () => import('@/views/tools/AudioTranscribePage.vue') },
|
||||
{ path: 'apps/wechat-official-account', name: 'WechatOfficialAccountApp', component: () => import('@/views/workbench/OfficialAccountWorkflowPage.vue') },
|
||||
|
||||
@@ -12,10 +12,27 @@ function readPersisted() {
|
||||
}
|
||||
}
|
||||
|
||||
// 智能助手 became the /home surface; the overview moved to /overview.
|
||||
const HOME_TAB = { route: '/home', label: '智能助手', closable: false }
|
||||
|
||||
// Tabs persisted before that move: the old /home tab was 工作台总览, and the
|
||||
// assistant lived at /tools/smart-assistant (now an alias for /home).
|
||||
function normalizeTabs(tabs) {
|
||||
const seen = new Set()
|
||||
const normalized = []
|
||||
for (const tab of tabs || []) {
|
||||
const route = tab.route === '/tools/smart-assistant' ? '/home' : tab.route
|
||||
if (seen.has(route)) continue
|
||||
seen.add(route)
|
||||
normalized.push(route === '/home' ? { ...HOME_TAB } : { ...tab, route })
|
||||
}
|
||||
return normalized.length ? normalized : [{ ...HOME_TAB }]
|
||||
}
|
||||
|
||||
export const useWorkbenchStore = defineStore('workbench', () => {
|
||||
const persisted = readPersisted()
|
||||
|
||||
const openTabs = ref(persisted?.openTabs || [{ route: '/home', label: '工作台总览', closable: false }])
|
||||
const openTabs = ref(normalizeTabs(persisted?.openTabs))
|
||||
const activeBottomTab = ref(persisted?.activeBottomTab || 'timeline')
|
||||
const bottomOpen = ref(persisted?.bottomOpen ?? true)
|
||||
const selectedCases = ref(persisted?.selectedCases || {})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* 博昇内部培训平台 — 全局样式 */
|
||||
/* EAI 数字员工平台 — 全局样式 */
|
||||
/* 参考:docs/08_Design_Rules/DR01_Theme_System.md */
|
||||
|
||||
:root {
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<el-card class="login-card" shadow="always">
|
||||
<template #header>
|
||||
<div class="login-brand">
|
||||
<div class="login-title">博昇AI研究院</div>
|
||||
<div class="login-subtitle">内部培训平台</div>
|
||||
<div class="login-en">EAI-Training</div>
|
||||
<div class="login-title">EAI 数字员工平台</div>
|
||||
<div class="login-subtitle">AI Agent Platform</div>
|
||||
<div class="login-en">eai_agentplatform</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<el-dialog v-model="showCert" title="考试合格证书" width="720px" :append-to-body="true">
|
||||
<div v-if="cert" class="cert-printable">
|
||||
<div class="cert-border">
|
||||
<div class="cert-title">博昇AI研究院内部培训平台</div>
|
||||
<div class="cert-title">EAI 数字员工平台</div>
|
||||
<div class="cert-subtitle">培训合格证书</div>
|
||||
<div class="cert-body">
|
||||
<div class="cert-line">兹证明 <span class="cert-name">{{ cert.user_name }}</span></div>
|
||||
|
||||
@@ -643,7 +643,7 @@
|
||||
|
||||
<!-- Welcome state -->
|
||||
<div class="welcome-state" id="welcomeState">
|
||||
<div class="welcome-title">AI Tools, 我帮你</div>
|
||||
<div class="welcome-title">我 Bosheng 数字员工,我帮你</div>
|
||||
<div class="welcome-subtitle">今天帮你做些什么? @ 引用对话文件,/ 调用技能与指令</div>
|
||||
|
||||
<!-- Category pills -->
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<!-- Welcome state (shown when no messages) -->
|
||||
<template v-if="messages.length <= 1" #welcome>
|
||||
<div class="welcome-content">
|
||||
<div class="welcome-title">AI Tools, 我帮你</div>
|
||||
<div class="welcome-title">我 Bosheng 数字员工,我帮你</div>
|
||||
<div class="welcome-subtitle">今天帮你做些什么? @ 引用对话文件,/ 调用技能与指令</div>
|
||||
|
||||
<!-- Mode pills above input -->
|
||||
@@ -79,11 +79,6 @@
|
||||
<div class="card-title">《思考,快与慢》精读笔记整理</div>
|
||||
<div class="card-meta">合同审查 · 风险评估</div>
|
||||
</div>
|
||||
<div class="practice-card" @click="fillPractice('帮我生成新产品上市的 GTM 发布计划')">
|
||||
<div class="card-image bg-gtm">🚀</div>
|
||||
<div class="card-title">新产品上市 GTM 发布计划</div>
|
||||
<div class="card-meta">文案生成 · 营销策略</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -436,15 +431,15 @@ function handleReference() {
|
||||
|
||||
.practices-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.practice-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e8ecf1;
|
||||
border-radius: 12px;
|
||||
padding: 14px;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, box-shadow 0.2s, transform 0.15s;
|
||||
text-align: left;
|
||||
@@ -458,13 +453,14 @@ function handleReference() {
|
||||
|
||||
.card-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 4/3;
|
||||
border-radius: 8px;
|
||||
/* 8:3 keeps the full card width but halves the block height (was 4:3). */
|
||||
aspect-ratio: 8/3;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 18px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.card-image.bg-translate { background: linear-gradient(135deg, #e8f5e9, #c8e6c9); }
|
||||
|
||||
Reference in New Issue
Block a user