# AR02 — 前端架构设计 > **版本:V1.1 | 左导航 + 中间工作区 + 右 AI 侧栏 | Vue3 + Vite + Element Plus** > **参考:pj006-zhilianyuan2 frontend-orgadmin 三栏布局模式** > > **2026-09-16 口径同步说明:** > 本文档记录的是 V1 三栏前端架构基线。 > 当前正式产品导航与对象化工作台口径,请以 `docs/01_System_Overall/SY22_Role_Skill_App_Unified_Task_Architecture.md` 为准: > `新建任务 / 项目 / 专员·技能·APP·连接器 / 长程APP / 知识库 / 后台管理 / 我的` > 另外,当前前端命名已明确拆分为: > `pageRoute` = 普通页面导航,`objectEntryRoute` = 业务对象入口,`ai_route_*` = AI 模型路由。 --- ## 1. 目录结构 ``` frontend/ ├── public/ │ └── static-lib/ # 本地 pdf.js(无 CDN) ├── src/ │ ├── api/ # API 调用层(axios 封装) │ │ ├── auth.js │ │ ├── product.js │ │ ├── course.js │ │ ├── exam.js │ │ ├── media.js │ │ ├── aiChat.js │ │ └── system.js │ ├── components/ # 通用组件 │ │ └── MaterialSuggestUpload.vue # 员工提交素材弹窗 │ ├── layout/ # 全局布局 │ │ ├── MainLayout.vue # 三栏布局(左导航 + 内容 + AI 侧栏) │ │ ├── SideNav.vue # 左边栏导航(含品牌 + 菜单 + 用户信息) │ │ └── PathCoachPanel.vue # 右侧 AI 聊天框 │ ├── router/ # 路由 │ │ ├── index.js # 路由定义 │ │ └── guards.js # 路由守卫(角色/认证) │ ├── store/ # 状态管理(Pinia) │ │ ├── auth.js # 用户认证状态 │ │ └── aiChat.js # AI 聊天会话 │ ├── views/ # 页面视图 │ │ ├── home/ # 首页 │ │ ├── companyTrain/ # 公司介绍培训 │ │ ├── product/ # 产品知识 │ │ ├── salesTrain/ # 产品销售培训 │ │ ├── exam/ # 考试 │ │ ├── knowledge/ # 管理员-知识管理 │ │ └── system/ # 管理员-系统管理 │ ├── App.vue │ └── main.js ├── index.html ├── vite.config.js └── package.json ``` ## 2. 布局结构 ``` ┌──────┬───────────────────────────────────────┬────────────────┐ │ 导航 │ │ │ │ ───── │ 主工作区 │ AI PathCoach │ │ 品牌 │ │ ──────────── │ │ │ │ 消息列表 │ │ 首页 │ │ │ │ 公司 │ │ [输入] [发送] │ │ 产品 │ │ │ │ 销售 │ │ [情景演练] │ │ 考试▼ │ │ [查佣金] │ │ │ │ [产品对比] │ │ ───── │ │ │ │ 知识▼ │ ← admin only │ │ │ 系统▼ │ ← admin only │ │ │ │ │ │ │ ───── │ │ │ │ 用户 │ [退出] │ │ └──────┴───────────────────────────────────────┴────────────────┘ ``` 收起 AI 面板状态:左边导航不变,主内容区占满剩余宽度,右下角浮动 [🤖 展开AI] 按钮。 ## 3. 路由设计 | 路径 | 视图 | 角色 | 说明 | |------|------|------|------| | `/` | Home | all | 首页 | | `/company-train` | CompanyTrain | all | 公司介绍培训 | | `/products` | ProductList | all | 产品列表 | | `/products/:id` | ProductDetail | all | 产品详情 | | `/courses` | CourseList | all | 课程列表 | | `/courses/:id` | CourseDetail | all | 课程详情 | | `/exam/self-test` | ExamSelfTest | all | 自测练习 | | `/exam/formal` | ExamFormal | all | 正式结业考试 | | `/exam/my-records` | ExamMyRecord | all | 我的考试记录 | | `/exam/questions` | ExamQuestionBank | admin | 题库管理 | | `/knowledge/materials` | MaterialManage | admin | 课件素材管理 | | `/knowledge/audit` | MaterialAuditList | admin | 素材审批列表 | | `/system/users` | UserManage | admin | 用户账号管理 | | `/system/exam-records` | ExamRecordManage | admin | 全部考试成绩 | | `/system/config` | SystemConfig | admin | 系统参数配置 | ## 4. 路由守卫 ```javascript // router/guards.js import { useAuthStore } from '@/store/auth' router.beforeEach((to, from, next) => { if (to.path === '/login') { next() return } const authStore = useAuthStore() // 未登录 → 跳转登录页 if (!authStore.isLoggedIn) { return next('/login') } // 管理员路由校验(前端仅作 UX 隐藏,非安全边界) if (to.meta.requiresAdmin && authStore.user?.role !== 'admin') { return next('/') } next() }) ``` ## 5. API 调用模式 ```javascript // api/http.js — axios 封装 import axios from 'axios' import { ElMessage } from 'element-plus' const http = axios.create({ baseURL: '/api' }) http.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) config.headers.Authorization = `Bearer ${token}` return config }) http.interceptors.response.use( res => res.data.data, // 解包 Envelope err => { const resp = err.response if (resp?.status === 401) { localStorage.removeItem('token') window.location.href = '/login' } else if (resp?.status === 501) { // LLM 未配置等服务器配置错误 ElMessage.error(resp.data?.message || '服务未配置,请联系管理员') } return Promise.reject(resp?.data) } ) export default http ``` ## 6. AI 聊天状态管理 ```javascript // store/aiChat.js import { defineStore } from 'pinia' export const useAiChatStore = defineStore('aiChat', { state: () => ({ visible: true, // 是否展开 messages: [], // 对话历史 context: null, // 当前页面上下文(productId/courseId) isStreaming: false, // 是否正在流式响应 }), actions: { toggle() { this.visible = !this.visible }, setContext(ctx) { this.context = ctx }, clearMessages() { this.messages = [] }, } }) ```