跳转至

前端架构

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 文件)

组件层分为两个子层,业务组件组合调用原子组件来构建功能:

页面(views/pages)
  └── 业务组件(~155 文件)—— 实现具体业务功能
        └── 原子组件(shadcn-vue · 227 文件)—— 通用 UI 积木,被所有业务组件共享

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:storeIddateRange 变化时自动重新获取

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-monorepoapps/web 目录,通过 GitHub Actions 部署到 Cloudflare Pages。

环境 触发方式 域名
Test push 到 main 时自动部署 studio-test.retaintive.ai
Production workflow_dispatch 手动触发(需输入 prod 确认) studio.retaintive.ai