跳转至

前端架构

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.configure({...})(pre-bootstrap,不是 plugin)
→ setupPlugins(app):Dayjs / NProgress / ECharts / AutoAnimate / TanStack Query / i18n / Pinia(共 7 个 plugin)
→ 创建路由(Vue Router + unplugin-vue-router 文件路由)
→ 路由守卫链:dateRedirect → datePreservation → commonGuard(nprogress)→ authGuard → analytics(5 步)
→ 挂载 App.vue(Suspense + RouterView + Toaster)

3.2 第二层:页面层

使用 unplugin-vue-router 文件路由,src/pages/ 目录结构自动生成路由。下表基于 2026-04-23 find src/pages -name "*.vue" 实查(共 117 文件 / 30+ 顶层目录)。

认证页面(无侧边栏,blank 布局):

路由 菜单名 功能
/auth/sign-in · /auth/sign-in-2 - Cognito 邮箱密码登录(2 个 variant 待 cleanup)
/auth/sign-up - 新用户注册
/auth/otp · /auth/forgot-password - OTP + 忘记密码流程
/auth/ringcentral-callback · /auth/callback · /auth/auth-callback - RingCentral / Cognito OAuth 回调

侧边栏 Operational 分组(default 布局):

路由 菜单名 功能
/dashboard Dashboard 指标卡、图表、排行榜、转化漏斗
/activity-timeline Activity Timeline 店铺活动时间线周视图(原 doc 说 /store-activity,实际路径是 /activity-timeline
/staff-timeline Staff Timeline 员工维度通话详情 + 周排班视图
/calls Call Table 通话分页数据表 + 桌面端并排详情面板
/call/:telephonySessionId (子页面) UI Manifest 驱动的动态布局、音频播放、文本同步
/messages Messages SMS / 语音邮件 / 传真,按对话分组
/tasks Tasks Lead pipeline、任务 CRUD、import、通信日志(16 sub-components)
/calendar Calendar 店铺排班日历(9 sub-components,逐步替代 /store-schedule
/lead-tracker Lead Tracker 线索列表、结果编辑、通话关联
/phone-activity 按号码展示通话/SMS/语音邮件/线索历史

侧边栏 Settings 分组(default 布局):

路由 菜单名 功能
/stores · /stores-management Stores & Groups RingCentral 连接、电话号码分配(2 个路径待统一)
/store-schedule Store Schedule 店铺周时间表(被 /calendar 替代中)
/access-management Access Management 团队成员角色分配
/settings · /settings/connections · /settings/change-password Settings 账号设置多页面
/connections Connections 顶层 connections 页(区别于 /settings/connections
/billing · /billing-history · /billing-plan · /transactions Billing 计费、历史、交易
/apps Apps 应用卡片 / 集成

系统页面:

路由 功能
/errors/401 · /403 · /404 · /500 · /503 错误页
/legal Terms / Privacy 占位
/dev/call-detail-test 开发测试 fixture

Disabled / WIP(仅 .disabled stub,未启用):

路径 说明
src/pages/users/index.vue.disabled Users / invite / roles 页 — 开发中,与 /access-management 可能重叠
src/pages/help-center.vue.disabled Help center 页 — stub

Cleanup 候选: activity-timeline-old/lead-tracker-old/ 目录仍存在,未启用。

3.3 第三层:组件层(~450 Vue/TS 文件)

组件层分为两个子层,业务组件组合调用原子组件来构建功能。数字基于 2026-04-23 find src/components -type f \( -name "*.vue" -o -name "*.ts" \) | wc -l 实查。

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

未列出的 doc-silent 目录:access-management/auth/call-display/command-menu-panel/command-palette/custom-theme/dialogs/dynamic-layout/empty-state/global-layout/inspira-ui/legal/marketing/marketing-layout/pagination/settings/snapshot/sort-select/CallAnalysisManifest.vueErrorBoundary.vue 等。

3.3.1 上层:业务组件(~170 文件)

业务组件之间有交叉调用关系,不能简单按功能一一对应:

目录 文件数 职责 调用的其他组件
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(数据查询)

位于 src/composables/queries/ — 共 20 个 hook 文件(2026-04-23 ls 实查)。另有 ~35 个非 query composable 分散在 src/composables/ 根,处理 UI 状态(如 useCalendaruseDashboardMetricsuseStaffPerformance),不走 TanStack 缓存。

Composable 缓存时间 用途
useCalls(storeId, dateRange) 3 分钟 通话列表
useCallDetail(sessionId) 30 天 通话详情(不可变)
useRecentCalls() 最近通话
useLinkedCall() 关联通话
useAudioUrl() 录音预签 URL
useLeads(params) 5 分钟 线索列表
useConversations(params) 10 分钟 对话列表(匹配旧 conversationsCache TTL)
useConversationStatus() 2 分钟 对话状态(同文件)
useMessagesStore() 消息 store 查询
usePhoneNumbers() 5 分钟 电话号码列表
usePhoneActivity() 按号码历史
useStores() 5 分钟 店铺列表
useTypedStores() 类型化店铺查询
useOrganizations() 组织列表(多租户)
useConnections() 5 分钟 RC 连接状态
useOAuth() OAuth 状态
useWebhook()(即旧 useWebhookStatus) 30 秒 Webhook 实时状态
useBlackouts() 禁呼时段
useOperatingHours() 营业时间
useTasks() 任务列表
useDashboardAnalytics() Dashboard 分析数据
useStaffTimelineAnalytics() 员工时间线分析

("—" = 该 hook 未显式设置 staleTime,使用 TanStack Query 默认值 0。)

3.4.2 Pinia Stores(全局状态)

pinia-plugin-persistedstate 默认 storage 在 src/plugins/pinia/index.ts:9 设为 sessionStorage(tab 生命周期内持久)。只有 auth store 直接用 localStorage.* API 手动持久,绕开了 plugin。

Store 持久化 关键状态
auth localStorage(手动 localStorage.setItem idToken、accessToken、user、memberships
stores stores[]、currentStoreId
organization sessionStorage(persist: true 当前 org、多租户切换(核心多租户开关
ringcentral sessionStorage(手动 sessionStorage.setItem isConnected、phoneNumbers、OAuth state
date-range startDate、endDate(URL 已是持久载体)
theme (system-config) sessionStorage(persist: true radius: number + theme: Theme(8 种色名枚举 zinc / red / rose / orange / green / blue / yellow / violet
access-management sessionStorage Access management 页状态
analytics-dashboard sessionStorage Dashboard 筛选、tab 状态
calls-table sessionStorage Call table 列配置、排序
lead-tracker-table sessionStorage Lead tracker 列配置、排序

3.5 第五层:API 层

Axios 实例(src/api/index.ts)
├── 请求拦截器:自动附加 Authorization: Bearer {token} + Sentry breadcrumb
├── 响应拦截器:401 → 自动刷新令牌(最多重试 3 次);其他错误 → Sentry 捕获 + Toast
├── OT API 模块(src/api/ot-api/,共 15 个文件)
│   ├── auth.ts / base.ts / index.ts(底座)
│   ├── calls.ts · conversations.ts · messages.ts · messages-store.ts · webhook.ts(通话 + 消息)
│   ├── leads.ts · tasks.ts · phone-activity.ts · phone-numbers.ts(线索 + 活动)
│   ├── stores.ts · connections.ts · blackouts.ts · operating-hours.ts(店铺 + 配置)
└── 并行 Typed API 层(Hono RPC)
    ├── src/api/hono-client.ts     → Hono 类型化 HTTP client
    ├── src/api/typed-api.ts       → 基于 schema 的类型化查询
    ├── src/api/ot-api-schemas.ts  → Zod schema 定义
    ├── src/api/analytics-14day-api.ts / calls-client.ts / tasks-client.ts
    ├── src/api/ringcentral-api.ts   → 直调 RC API(OAuth flow)
    └── src/api/sub-accounts-api.ts  → Sub-account 操作

为什么有两套? 大部分域走 Axios + src/api/ot-api/ 惯用模式。新域(analytics、tasks、typed routes)试点 Hono RPC 获得编译期类型安全。迁移策略尚未 finalize。

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-ready;当前仍是 Rollup,未 swap 到 rolldown-vite)
样式 Tailwind CSS v4 + shadcn-vue + Reka UI
路由 Vue Router + unplugin-vue-router(文件路由)+ vite-plugin-vue-layouts(blank / default / analysis 布局)
状态 Pinia + pinia-plugin-persistedstate(默认 sessionStorage
数据获取 TanStack Query + Axios + Hono RPC(typed API 试点)
图表 ECharts + vue-echarts + Unovis
表单 Vee-Validate + Zod
国际化 vue-i18n 11.x
认证 AWS Amplify(Cognito)
图标 Lucide Vue
动画 motion-v + AutoAnimate
工具 VueUse、Day.js
共享类型 @retaintive/common ^0.3.0(⚠️ 后端 infra 当前 ^0.18.0,前端落后 ~15 个 minor;使用方需确认是否需要 bump)
测试 Vitest + Vue Test Utils + Puppeteer + Lighthouse(perf budget)
代码健康 Oxlint + Knip(未使用依赖扫描)
部署 Cloudflare Pages(Wrangler),通过 @sentry/vite-plugin 在 prod build 上传 sourcemap
监控 Sentry + Google Analytics 4

7. 部署环境

前端代码位于 studio-website-monorepoapps/web 目录,通过 GitHub Actions 部署到 Cloudflare Pages。共 3 个环境(对应 3 个 workflow 文件:deploy-test.yml / deploy-pre.yml / deploy-prod.yml)。

环境 触发方式 域名
Test push 到 main 时自动部署 studio-test.retaintive.ai
Pre push 到 release/pre 分支 或 workflow_dispatch studio-pre Cloudflare project(自托管 runner)
Production workflow_dispatch 手动触发(需输入 prod 确认) studio.retaintive.ai

Legacy retaintive/studio-web repo

github.com/retaintive/studio-web 是 pre-monorepo 版本的前端仓库,已不再活跃(最后 commit 2026-03-04)。所有新工作走 studio-website-monorepo/apps/web。如果误落到 studio-web,请与团队确认是否待 archive。