状态管理.md 17 KB

状态管理

本文引用的文件

  • main.ts
  • audio.ts
  • user.ts
  • index.ts
  • storage.ts
  • request.ts
  • config.ts
  • debug.ts
  • index.vue(播放器)
  • index.vue(登录)
  • index.vue(我的)

目录

  1. 简介
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能考量
  8. 故障排查指南
  9. 结论
  10. 附录

简介

本文件面向“AI有声书生成平台”的状态管理,围绕 Pinia 在 uni-app/Vue3 中的应用进行系统化梳理。重点覆盖以下方面:

  • Store 设计模式与模块化组织
  • 状态定义与 Action 实现
  • 音频播放状态管理(播放/暂停、进度、列表、模式)
  • 用户信息与登录态管理(Token、用户信息、会员状态)
  • 全局状态同步与跨页面共享
  • 状态持久化策略与同步方案
  • 类型安全与最佳实践
  • 性能优化与调试方法

项目结构

前端采用 uni-app + Vue3 + Pinia 架构,状态集中在 src/store 目录,类型定义在 src/types,网络层在 src/utils。

graph TB
subgraph "应用入口"
M["main.ts<br/>创建 Pinia 并初始化用户状态"]
end
subgraph "状态模块"
U["store/user.ts<br/>用户状态与登录态"]
A["store/audio.ts<br/>音频播放与播放列表"]
end
subgraph "页面与组件"
P["pages/player/index.vue<br/>播放器页面"]
L["pages/login/index.vue<br/>登录页"]
MI["pages/mine/index.vue<br/>我的页面"]
end
subgraph "工具与类型"
R["utils/request.ts<br/>统一请求封装"]
S["utils/storage.ts<br/>本地存储工具"]
C["utils/config.ts<br/>环境与 API 地址配置"]
D["utils/debug.ts<br/>全局调试工具"]
T["types/index.ts<br/>类型定义"]
end
M --> U
M --> A
P --> A
L --> U
MI --> U
U --> R
A --> R
U --> S
R --> C
R --> D
U --> T
A --> T

图表来源

  • main.ts:10-31
  • user.ts:7-107
  • audio.ts:6-297
  • index.vue(播放器):192-660
  • index.vue(登录):57-143
  • index.vue(我的):147-334
  • request.ts:34-207
  • storage.ts:1-63
  • config.ts:44-72
  • debug.ts:194-305
  • index.ts:9-89

章节来源

  • main.ts:10-31
  • audio.ts:6-297
  • user.ts:7-107
  • index.ts:9-89
  • storage.ts:1-63
  • request.ts:34-207
  • config.ts:44-72
  • debug.ts:194-305
  • index.vue(播放器):192-660
  • index.vue(登录):57-143
  • index.vue(我的):147-334

核心组件

  • 用户状态模块(user.ts)
    • 状态:token、userInfo、memberStatus
    • 计算属性:isLoggedIn、isMember
    • 行为:initUser、login、sendCode、fetchUserInfo、fetchMemberStatus、logout、updateUserInfo
  • 音频状态模块(audio.ts)
    • 状态:voices、currentAudio、playlist、currentIndex、isPlaying、currentTime、duration、playRate、playMode
    • 计算属性:hasPlaylist、hasNext、hasPrev
    • 行为:fetchVoices、generateAudio、play、pause、resume、togglePlay、playPrev、playNext、handlePlayMode、togglePlayMode、seek、setPlayRate、setPlaylist、destroy
  • 应用入口(main.ts)
    • 创建 Pinia 并注入应用
    • 初始化用户状态(调用 userStore.initUser)

章节来源

  • user.ts:7-107
  • audio.ts:6-297
  • main.ts:10-31

架构总览

Pinia Store 作为单一事实来源,页面通过组合式 API 使用 Store 实例,实现跨页面状态共享与同步。网络层统一走 request.ts,按环境动态选择 API 基地址;本地持久化通过 storage.ts 封装 token 与用户信息;类型定义集中于 types/index.ts,确保类型安全。

sequenceDiagram
participant App as "应用入口(main.ts)"
participant Pinia as "Pinia实例"
participant User as "用户Store(user.ts)"
participant Audio as "音频Store(audio.ts)"
participant PageP as "播放器页面(player/index.vue)"
participant PageL as "登录页(login/index.vue)"
participant PageM as "我的页面(mine/index.vue)"
participant Req as "请求封装(request.ts)"
App->>Pinia : 创建并挂载
App->>User : initUser()
PageP->>Audio : 读取播放状态/列表
PageP->>Req : 播放/生成/列表请求
PageL->>User : login()/sendCode()
PageL->>Req : 登录/验证码请求
PageM->>User : fetchMemberStatus()
PageM->>Req : 历史/统计请求

图表来源

  • main.ts:10-31
  • user.ts:18-107
  • audio.ts:29-297
  • index.vue(播放器):192-660
  • index.vue(登录):57-143
  • index.vue(我的):147-334
  • request.ts:34-207

详细组件分析

用户状态模块(user.ts)

  • 状态设计
    • token:认证令牌,来自 storage.ts 的本地持久化
    • userInfo:用户信息,来自 storage.ts 的本地持久化
    • memberStatus:会员状态,来自后端接口
  • 计算属性
    • isLoggedIn:基于 token 与 userInfo 的布尔值
    • isMember:基于 memberStatus 的会员判定
  • 初始化流程
    • initUser:读取本地 token 与 userInfo,若有效则拉取会员状态
  • 登录流程
    • login:调用 /auth/login,成功后写入 token 与 userInfo,并拉取会员状态
    • sendCode:调用 /auth/send-code
    • fetchUserInfo/fetchMemberStatus:分别拉取用户信息与会员状态
    • logout:清空 token 与 userInfo
    • updateUserInfo:更新本地 userInfo 并持久化
  • 类型与持久化

    • 类型:UserInfo、MemberStatus、ApiResponse、PaginationResult
    • 存储:storage.ts 提供 getToken/setToken/removeToken 与 getUserInfo/setUserInfo/removeUserInfo

      flowchart TD
      Start(["initUser 入口"]) --> CheckSaved["检查本地 token 与 userInfo"]
      CheckSaved --> HasSaved{"存在有效登录态?"}
      HasSaved --> |是| LoadState["设置 token 与 userInfo"]
      LoadState --> FetchMember["拉取会员状态"]
      HasSaved --> |否| WaitLogin["等待用户主动登录"]
      FetchMember --> End(["完成"])
      WaitLogin --> End
      

图表来源

  • user.ts:18-30
  • user.ts:62-71
  • storage.ts:9-63

章节来源

  • user.ts:7-107
  • storage.ts:1-63
  • index.ts:9-89

音频状态模块(audio.ts)

  • 状态设计
    • voices:可用音色列表
    • currentAudio:当前播放音频项
    • playlist:播放列表
    • currentIndex:当前播放索引
    • isPlaying:播放中状态
    • currentTime/duration:播放进度与时长
    • playRate:播放速度
    • playMode:播放模式(顺序/循环/单曲/随机)
  • 计算属性
    • hasPlaylist、hasNext、hasPrev:基于列表与索引的便捷判断
  • 核心行为
    • fetchVoices:拉取音色列表
    • generateAudio:调用 /tts/generate 生成音频
    • play/pause/resume/togglePlay:播放控制
    • playPrev/playNext:上一首/下一首
    • handlePlayMode/togglePlayMode:处理与切换播放模式
    • seek:跳转进度
    • setPlayRate:设置播放速度
    • setPlaylist:设置播放列表并可自动播放
    • destroy:销毁音频上下文
  • 音频上下文生命周期

    • initAudioContext:创建 InnerAudioContext 并绑定事件(播放、暂停、结束、进度、错误、可播放)
    • onEnded:根据 hasNext 或 handlePlayMode 决定自动播放下一首或回到模式逻辑
    • onTimeUpdate:更新 currentTime,并在合理范围内回填 duration(避免元数据异常)
    • onError:记录错误并尝试打印上下文信息
    • onCanplay:日志提示

      sequenceDiagram
      participant Page as "播放器页面"
      participant Store as "音频Store"
      participant Ctx as "InnerAudioContext"
      Page->>Store : setPlaylist(list, index, autoPlay)
      Store->>Ctx : initAudioContext()
      Store->>Store : play(list[index])
      Store->>Ctx : 设置 src 与 play()
      Ctx-->>Store : onPlay -> isPlaying=true
      Ctx-->>Store : onTimeUpdate -> 更新 currentTime/duration
      Ctx-->>Store : onEnded -> isPlaying=false
      Store->>Store : hasNext? -> playNext() 或 handlePlayMode()
      Store->>Store : onError -> 记录错误
      

图表来源

  • audio.ts:29-79
  • audio.ts:112-141
  • audio.ts:174-212
  • index.vue(播放器):309-348

章节来源

  • audio.ts:6-297
  • index.vue(播放器):192-660

页面与状态交互(播放器、登录、我的)

  • 播放器页面(player/index.vue)
    • 从 store 读取 isPlaying、currentTime、duration、playRate、hasPrev、hasNext、playlist
    • 通过 store.action 控制播放、切歌、模式切换、进度跳转、速度设置
    • 加载专辑章节列表并设置播放列表,支持歌词时间轴渲染与滚动同步
  • 登录页(login/index.vue)
    • 使用 userStore.sendCode 与 userStore.login
    • 成功后返回上一页或跳转首页
  • 我的页面(mine/index.vue)

    • 展示用户信息、会员等级与额度
    • 拉取今日创作统计与最近创作
    • 未登录时跳转登录页

      sequenceDiagram
      participant Login as "登录页"
      participant User as "用户Store"
      participant Req as "请求封装"
      participant Mine as "我的页面"
      Login->>User : sendCode(phone)
      User->>Req : POST /auth/send-code
      Login->>User : login(phone, code)
      User->>Req : POST /auth/login
      Req-->>User : 返回 token 与 user
      User-->>Login : 完成登录
      Mine->>User : fetchMemberStatus()
      User->>Req : GET /member/status
      

图表来源

  • index.vue(登录):76-134
  • user.ts:33-71
  • index.vue(我的):264-274

章节来源

  • index.vue(播放器):192-660
  • index.vue(登录):57-143
  • index.vue(我的):147-334

依赖关系分析

  • 模块耦合
    • main.ts 仅负责创建 Pinia 与初始化用户状态,低耦合
    • 页面通过组合式 API 使用 Store,解耦于具体实现
    • Store 之间无直接依赖,通过 request.ts 间接依赖网络层
  • 外部依赖
    • uni.createInnerAudioContext:音频播放上下文
    • uni.request:HTTP 请求
    • localStorage/uni.setStorageSync:本地存储(H5 与 App 端)
  • 类型依赖

    • types/index.ts 为所有 Store 与页面提供强类型支撑

      graph LR
      Main["main.ts"] --> User["store/user.ts"]
      Main --> Audio["store/audio.ts"]
      User --> Types["types/index.ts"]
      Audio --> Types
      User --> Storage["utils/storage.ts"]
      Audio --> Request["utils/request.ts"]
      User --> Request
      Request --> Config["utils/config.ts"]
      Request --> Debug["utils/debug.ts"]
      PageP["pages/player/index.vue"] --> Audio
      PageL["pages/login/index.vue"] --> User
      PageM["pages/mine/index.vue"] --> User
      

图表来源

  • main.ts:10-31
  • user.ts:7-107
  • audio.ts:6-297
  • index.ts:9-89
  • storage.ts:1-63
  • request.ts:34-207
  • config.ts:44-72
  • debug.ts:194-305
  • index.vue(播放器):192-660
  • index.vue(登录):57-143
  • index.vue(我的):147-334

章节来源

  • main.ts:10-31
  • request.ts:34-207
  • storage.ts:1-63
  • config.ts:44-72
  • debug.ts:194-305

性能考量

  • 请求缓存
    • request.ts 对 GET 请求提供内存缓存(Map),支持 TTL 与按需清理,减少重复请求
  • 重试与超时
    • request.ts 支持 retry/retryDelay/timeout,提升弱网稳定性
  • 播放时长校正
    • audio.ts 在 onTimeUpdate 中对异常时长进行保护性回填,避免元数据错误导致 UI 异常
  • 进度与歌词同步
    • 播放器页面根据 currentTime 自动滚动歌词并高亮当前句,避免频繁重排
  • 环境适配
    • config.ts 根据平台与环境动态选择 API 基地址,减少代理与跨域问题

章节来源

  • request.ts:20-99
  • audio.ts:53-62
  • config.ts:26-72
  • index.vue(播放器):289-297

故障排查指南

  • 登录态失效
    • request.ts 在收到 401 时清除本地 token 与 userInfo,并跳转登录页
  • 请求过于频繁
    • request.ts 对 429 做友好提示,包含 retryAfter
  • 服务器错误
    • request.ts 对 5xx 统一提示并拒绝
  • 音频播放异常
    • audio.ts 在 onError 中打印错误码、错误信息与上下文状态,便于定位
  • 调试工具
    • debug.ts 提供全局错误捕获、API 请求/响应日志、页面切换拦截等能力

章节来源

  • request.ts:135-159
  • audio.ts:64-74
  • debug.ts:97-191

结论

本项目采用 Pinia 实现清晰的模块化状态管理,结合类型定义与统一请求封装,实现了用户登录态、音频播放状态与跨页面共享的稳定方案。通过本地持久化与环境适配,兼顾了多端一致性与性能。建议持续完善:

  • 对 Store 增加必要的快照/回滚能力(如播放历史)
  • 在高频更新场景引入节流/防抖(如进度更新)
  • 对复杂页面(如播放器)拆分更细的子模块或使用模块化 Store

附录

  • 状态树设计原则
    • 单一职责:每个 Store 负责一个领域(用户/音频)
    • 明确边界:Store 之间不互相依赖,通过请求层交互
    • 可观测:配合 debug.ts 与 request.ts 日志,便于问题定位
  • 模块化管理策略
    • store 目录按领域划分,页面按功能划分
    • 类型集中定义,避免分散维护
  • 类型安全保证
    • 使用 TypeScript 接口约束状态与 API 响应
    • 在请求封装中统一处理响应格式差异