前端架构¶
1. 架构图¶
源文件:frontend-architecture.drawio(用 draw.io 打开编辑)
1.1 图例¶
| 属性 | 颜色 | 对应组件 |
|---|---|---|
| 外部服务 | 蓝色 | 用户浏览器、AWS Cognito、studio-api、Cloudflare Pages、Sentry |
| 第一层 + 第二层 | 橙色 | 应用入口(main.ts、路由、守卫)、页面层(认证页面、侧边栏所有页面) |
| 业务组件 | 紫色 | 数据可视化、通话分析、活动时间线、数据表、配置管理 |
| 原子组件 | 深紫色 | shadcn-vue UI 积木(表单、弹出层、数据展示、导航交互) |
| 逻辑层 | 绿色 | TanStack Query Hooks、业务 Composables、Pinia Stores |
| API 层 | 红色 | Axios 客户端、OT API 模块、RingCentral API、认证服务 |
| 数据流 | 彩色粗实线 | 用户请求 → 前端资源 → 路由导航 → API 调用 |
| 组件引用 | 灰色虚线 | 页面使用组件、调用逻辑层、状态读写 |
2. 数据流时序¶
用户登录后访问一个页面时,数据如何在五层之间流动:
sequenceDiagram
participant U as 用户浏览器
participant R as 路由守卫
participant P as 页面层<br>Dashboard
participant C as 组件层<br>MetricCard / Chart
participant L as 逻辑层<br>useCalls()
participant A as API 层<br>Axios
participant S as studio-api
U->>R: 访问 /dashboard
R->>R: 检查 authStore.isLogin
alt 未登录
R-->>U: 重定向 /auth/sign-in
end
R->>P: 放行,渲染 Dashboard 页面
P->>C: 挂载业务组件(图表、表格...)
C->>L: 调用 useCalls(storeId, dateRange)
L->>L: TanStack Query 检查缓存
alt 缓存命中
L-->>C: 返回缓存数据
end
L->>A: 缓存未命中,发起请求
A->>A: 拦截器附加 Bearer token
A->>S: GET /calls?storeId=xxx
S-->>A: JSON 响应
A-->>L: 响应数据
L->>L: 写入缓存
L-->>C: 返回数据(Vue 反应式)
C-->>P: 组件重新渲染
P-->>U: 页面显示数据
3. 分层架构¶
前端采用五层架构,数据自上而下流动,每层职责清晰:
3.1 第一层:应用入口¶
main.ts 启动应用
→ 注册插件(Amplify / Pinia / VueQuery / Dayjs)
→ 创建路由(Vue Router + unplugin-vue-router 文件路由)
→ 路由守卫链:dateRedirect → datePreservation → authGuard → analytics
→ 挂载 App.vue(Suspense + RouterView + Toaster)
3.2 第二层:页面层¶
使用 unplugin-vue-router 文件路由,src/pages/ 目录结构自动生成路由:
认证页面(无侧边栏,blank 布局):
| 路由 | 菜单名 | 功能 |
|---|---|---|
/auth/sign-in |
- | Cognito 邮箱密码登录 |
/auth/sign-up |
- | 新用户注册 |
/auth/ringcentral-callback |
- | RingCentral OAuth 回调处理 |
侧边栏 General 分组(default 布局):
| 路由 | 菜单名 | 功能 |
|---|---|---|
/dashboard |
Dashboard | 指标卡、图表、排行榜、转化漏斗 |
/store-activity |
Store Activity | 店铺活动时间线周视图 |
/calls |
Call Table | 通话分页数据表 + 桌面端并排详情面板 |
/call/:telephonySessionId |
(子页面) | UI Manifest 驱动的动态布局、音频播放、文本同步 |
/messages |
Messages | SMS / 语音邮件 / 传真,按对话分组 |
/lead-tracker |
Lead Tracker | 线索列表、结果编辑、通话关联 |
侧边栏 Settings 分组(default 布局):
| 路由 | 菜单名 | 功能 |
|---|---|---|
/stores |
Stores & Groups | RingCentral 连接、电话号码分配 |
/store-schedule |
Store Schedule | 店铺周时间表 |
/access-management |
Access Management | 团队成员角色分配 |
3.3 第三层:组件层(380+ Vue 文件)¶
组件层分为两个子层,业务组件组合调用原子组件来构建功能:
3.3.1 上层:业务组件(~155 文件)¶
业务组件之间有交叉调用关系,不能简单按功能一一对应:
| 目录 | 文件数 | 职责 | 调用的其他组件 |
|---|---|---|---|
| analytics/ + dashboard/ | 44 | 指标卡、图表、漏斗、排行榜、热力图 | ui/Card、ui/Tabs、ui/Skeleton |
| call-analysis/ | 7 | UI Manifest 驱动的通话分析(指标网格、教练建议、告警) | ui/Card、ui/Badge、ui/Tabs |
| call-detail/ | 4 | 音频播放器、转录时间线、说话人标注 | ui/Button、ui/Slider、ui/ScrollArea |
| activity-timeline/ | 7 | 周视图热力图、活动聚类、侧抽屉详情 | ui/Drawer、ui/Card、call-detail/、phone-activity/ |
| phone-activity/ | 9 | 按号码展示通话/SMS/语音邮件/线索历史 | ui/Card、ui/Badge、data-table/ |
| data-table/ | 4 | 可排序列头、分面筛选、分页、列管理 | ui/Button、ui/Popover、ui/DropdownMenu |
| filters/ | 2 | 筛选器面板 | ui/Select、ui/Popover、ui/Checkbox |
| stores/ + store-schedule/ | 9 | 店铺配置向导、排班管理、SLA黑名单 | ui/Dialog、ui/Stepper、ui/Form、ui/Calendar |
| conversation-timeline/ | — | SMS 对话线程展示 | ui/ScrollArea、ui/Avatar |
| app-sidebar/ | 4 | 侧边栏框架、店铺切换器 | ui/Sidebar、ui/Select、ui/Avatar |
3.3.2 下层:原子组件(shadcn-vue · 227 文件)¶
来自 shadcn-vue 的通用 UI 积木,不包含任何业务逻辑,被所有业务组件共享复用:
| 分类 | 组件 | 文件数 |
|---|---|---|
| 表单输入 | Input、Textarea、Checkbox、Switch、Select、Pin-input、Tags-input、Calendar、Range-calendar | ~50 |
| 弹出层 | Dialog、Drawer、Sheet、Popover、Hover-card、Dropdown-menu | ~35 |
| 数据展示 | Table、Card、Badge、Avatar、Skeleton、Progress、Stepper | ~40 |
| 导航交互 | Tabs、Button、Accordion、Breadcrumb、Command、Toggle、Carousel | ~55 |
| 反馈通知 | Alert、Sonner(Toast)、Form(校验) | ~15 |
| 布局容器 | ScrollArea、Separator、Collapsible、Sidebar | ~30 |
3.4 第四层:逻辑层¶
3.4.1 TanStack Query Composables(数据查询)¶
| Composable | 缓存时间 | 用途 |
|---|---|---|
useCalls(storeId, dateRange) |
3 分钟 | 通话列表 |
useCallDetail(sessionId) |
30 天 | 通话详情(不可变) |
useLeads(params) |
5 分钟 | 线索列表 |
useConversations(params) |
3 分钟 | 对话列表 |
usePhoneNumbers() |
5 分钟 | 电话号码列表 |
useStores() |
5 分钟 | 店铺列表 |
useConnections() |
5 分钟 | RC 连接状态 |
useWebhookStatus() |
30 秒 | Webhook 实时状态 |
3.4.2 Pinia Stores(全局状态)¶
| Store | 持久化 | 关键状态 |
|---|---|---|
| auth | localStorage | idToken、accessToken、user、memberships |
| stores | 否 | stores[]、currentStoreId |
| ringcentral | localStorage | isConnected、phoneNumbers |
| date-range | 否 | startDate、endDate |
| theme | localStorage | isDark、accentColor |
3.5 第五层:API 层¶
Axios 实例(src/api/index.ts)
├── 请求拦截器
│ ├── 自动附加 Authorization: Bearer {token}
│ └── Sentry breadcrumb 记录
├── 响应拦截器
│ ├── 401 → 自动刷新令牌(最多重试3次)
│ └── 其他错误 → Sentry 捕获 + Toast 提示
└── OT API 模块(src/api/ot-api/)
├── calls.ts → getCalls / getCallDetail
├── leads.ts → getLeads / updateLeadOutcome
├── messages.ts → getConversations / getMessages
├── stores.ts → getStores / updateStore
└── connections.ts → initiateOAuth / getConnections
4. 认证流程¶
用户访问 → 路由守卫检查 authStore.isLogin
├── 未登录 → 重定向 /auth/sign-in
│ → 输入邮箱密码 → AWS Cognito 验证
│ → 获取 JWT(idToken + accessToken + refreshToken)
│ → 存入 localStorage → 加载用户成员资格
│ → 初始化 RingCentral Store → 重定向 /dashboard
└── 已登录 → 继续访问目标页面
→ API 请求自动携带令牌
→ 401 时自动刷新 → 失败则登出
5. 数据获取模式¶
所有页面数据通过 TanStack Query 管理,保证缓存一致性:
页面组件调用 useCalls({ storeId, dateRange })
↓
TanStack Query 检查缓存
├── 缓存命中且未过期 → 直接返回
└── 缓存不存在或已过期 → 发起请求
↓
Axios → 请求拦截器附加令牌 → studio-api
↓
响应数据写入缓存 → Vue 反应性更新 → 页面重新渲染
缓存失效策略:
- 时间失效:根据数据类型设置不同的 staleTime(30秒 ~ 30天)
- 手动失效:mutation 成功后调用
queryClient.invalidateQueries() - 响应式 Key:
storeId或dateRange变化时自动重新获取
6. 技术栈¶
| 层级 | 技术 |
|---|---|
| 框架 | Vue 3.5 + TypeScript |
| 构建 | Vite 8 (beta) + Rolldown |
| 样式 | Tailwind CSS v4 + shadcn-vue + Reka UI |
| 路由 | Vue Router + unplugin-vue-router(文件路由) |
| 状态 | Pinia + pinia-plugin-persistedstate |
| 数据获取 | TanStack Query + Axios |
| 图表 | ECharts + vue-echarts + Unovis |
| 表单 | Vee-Validate + Zod |
| 认证 | AWS Amplify(Cognito) |
| 图标 | Lucide Vue |
| 动画 | motion-v |
| 工具 | VueUse、Day.js |
| 测试 | Vitest + Vue Test Utils |
| Lint | Oxlint |
| 部署 | Cloudflare Pages(Wrangler) |
| 监控 | Sentry + Google Analytics 4 |
7. 部署环境¶
前端代码位于 studio-website-monorepo 的 apps/web 目录,通过 GitHub Actions 部署到 Cloudflare Pages。
| 环境 | 触发方式 | 域名 |
|---|---|---|
| Test | push 到 main 时自动部署 |
studio-test.retaintive.ai |
| Production | workflow_dispatch 手动触发(需输入 prod 确认) |
studio.retaintive.ai |