Swee-Mobile PWA
Swee Mobile 是 Swee AI 社交娱乐平台的核心移动端 Web 应用,基于 Next.js 13 + PWA 构建。作为平台最复杂的前端应用,集成了 AI 伴侣聊天、环信即时通讯(Easemob IM)、好感度/亲密度系统、特权等级、直播互动、语音克隆、数字人创作等核心功能,是一款将 AI 生成内容与社交娱乐深度融合的移动优先应用。
一、项目技术架构
1.1 整体架构图
1.2 核心技术栈
| 领域 | 技术选型 | 版本 |
|---|---|---|
| 框架 | Next.js (Pages Router) | 13.4.1 |
| UI 库 | React + Ant Design Mobile | 18.x / 5.37.1 |
| PWA | next-pwa | 5.6.0 |
| 状态管理 | Zustand | 5.0.3 |
| 即时通讯 | Easemob WebSDK (MiniCore) | 4.13.0 |
| 动画 | Framer Motion | 11.3.30 |
| 轮播 | Swiper | 11.0.5 |
| 国际化 | next-i18next + i18next | 15.3.1 / 23.16.5 |
| 二进制协议 | protobufjs | 7.2.3 |
| 样式 | Tailwind CSS + SCSS | 3.4.10 |
| 错误监控 | Sentry | 7.105.0 |
| 日志 | Pino | 8.11.0 |
| 调试 | VConsole | 3.15.1 |
二、PWA 与移动端适配
2.1 PWA 配置
关键配置
- 自动注册:
register: true - 即时更新:
skipWaiting: true,新版本立即激活 - 开发禁用:
disable: process.env.NODE_ENV === 'development' - 深色主题:
theme_color: "#101010"/background_color: "#101010"
2.2 移动端视口适配
页面 viewport 配置
<!-- 固定 375px 宽度(iPhone 标准) -->
<meta name="viewport" content="width=375, user-scalable=no, viewport-fit=cover" />
固定宽度策略
固定 375px 宽度是一种取舍方案:虽然保证了跨设备视觉一致性,但在大屏设备上会出现两侧留白。这种方案适合移动优先且嵌入 WebView 的场景,不建议用于需要适配平板/桌面端的项目。
- 固定宽度 375px:保证所有设备上一致的视觉效果
- 禁止缩放:
user-scalable=no避免双击放大 - 刘海屏适配:
viewport-fit=cover+ SafeArea 组件
三、AI 伴侣聊天系统
3.1 聊天架构
3.2 消息类型支持
| 消息类型 | 说明 | 处理方式 |
|---|---|---|
txt | 文本消息 | 直接渲染文本内容 |
custom | 自定义事件 | 解析 customEvent 类型 |
welcome_event | 欢迎消息 | 特殊 UI 展示 |
3.3 消息转换管线
utils/transformMessages.ts
const transformMessages = ({
messages, // Easemob 原始消息列表
userId, // 当前用户 ID
currentUserInfo, // 当前用户信息
assistantUserInfo, // AI 伴侣信息
}) => {
return messages.map(msg => ({
role: msg.from === userId ? 'user' : 'assistant',
content: msg.type === 'txt' ? msg.msg : parseCustomEvent(msg),
// 统一为 ChatDialog 组件消费的格式
}));
}
四、Easemob IM 即时通讯
4.1 IM 架构设计
4.2 关键实现细节
架构决策:MiniCore 模式
Easemob SDK 提供完整版和 MiniCore 两种模式。项目选择 MiniCore + 按需插件的方式,相比完整版 SDK 显著减小了初始包体积,只加载实际使用的 Contact 和 LocalCache 功能。
初始化流程:
- 动态导入
easemob-websdk/miniCore(减小初始包体积) - 注册 Contact + LocalCache 插件
- 从后端获取 IM Token:
getUserToken()API - 调用
miniCore.open()建立连接 - 注册连接状态事件监听
用户信息缓存策略:
store/easemobStore.ts
// Zustand Store - 避免重复请求
const fetchUserInfoByIdInStore = async (userId: string | string[]) => {
if (Array.isArray(userId)) {
// 批量获取:过滤已缓存的,只请求未缓存的
const unFetchInfoList = userId.filter(uid => !userInfoMap.get(uid));
const res = await miniCore.contact.fetchUserInfoById(unFetchInfoList);
// 缓存到 store
} else {
// 单个获取:先查缓存
const cached = userInfoMap.get(userId);
if (cached) return cached;
}
};
五、好感度/亲密度系统
5.1 亲密度等级架构
5.2 权益系统
等级指南配置:
| 等级 | 指南类型 | 说明 |
|---|---|---|
| 1 | Interaction Guide | 基础互动教程 |
| 2 | Occasional Event | 偶遇事件触发 |
| 3 | Real Connection | 真实情感连接 |
| 4 | Task Guide | 任务系统引导 |
5.3 UI 组件体系
| 组件 | 功能 | 路径 |
|---|---|---|
BenefitBar | 亲密度进度条 | components/BenefitBar/ |
BenefitProgress | 亲密度信息卡 | components/BenefitProgress/ |
BenefitInfo | 权益详情展示 | components/BenefitInfo/ |
IntimacyLevel | 等级图标 | @swee/uikit |
六、直播与音视频
6.1 直播 API 体系
6.2 语音克隆系统
FormData 文件上传:支持 File 对象 + JSON 参数混合提交
七、页面转场动画
7.1 Framer Motion 页面过渡
components/PageTransition.tsx
<AnimatePresence mode="wait">
<motion.div
key={router.route}
initial={{ opacity: 0, x: 300 }} // 从右侧滑入
animate={{ opacity: 1, x: 0 }} // 显示
exit={{ opacity: 0, x: -300 }} // 向左侧滑出
transition={{ duration: 0.3 }} // 300ms
/>
</AnimatePresence>
7.2 自定义动画效果
tailwind.config.ts
// Tailwind 自定义 shake 动画
keyframes: {
shake: {
'0%': { transform: 'scale(0.8) rotate(10deg)' },
'25%': { transform: 'scale(1) rotate(0deg)' },
'50%': { transform: 'scale(0.95) rotate(-10deg)' },
'75%': { transform: 'scale(0.9) rotate(10deg)' },
'100%': { transform: 'scale(0.8) rotate(10deg)' },
},
}
// 使用: animate-[shake_1.1s_ease-in-out_infinite]
八、API 服务层
8.1 API 模块分布
8.2 关键 API 模块
| 模块 | 文件数 | 核心功能 |
|---|---|---|
im/ | 26 | 聊天消息、偶遇剧情、用户 Token、声音管理 |
model/ | 19 | AI 模型管理、数字人、语音克隆、KOL 入驻 |
live/ | 16 | 直播管理、房间查询、关注系统、认证令牌 |
user/ | 17 | 登录认证、个人信息、支付密码、Web3 |
recommend/ | 8 | 直播推荐流、AI 恋人推荐 |
interact/ | 7 | 好感度查询、权益管理 |
8.3 类型定义规模
| 模块 | 类型文件大小 |
|---|---|
| im/typings.d.ts | 33 KB |
| model/typings.d.ts | 33 KB |
| live/typings.d.ts | 22 KB |
| 总计 | ~100 KB |
九、状态管理设计
9.1 混合状态管理方案
9.2 Zustand 设计原则
最小化 Store 原则
Zustand Store 只存储需要跨组件共享且需要缓存的数据(如 IM 用户信息)。API 请求数据交给 useRequest 管理,组件内部状态使用 useState,避免全局状态过度膨胀。
store/easemobStore.ts
// 最小化 Store - 只存储需要跨组件共享且需要缓存的数据
export const useEasemobStore = create<EasemobStore>((set) => ({
userInfoMap: new Map<string, UpdateOwnUserInfoParams>(),
setUserInfoMap: (userId, userInfo) =>
set(({ userInfoMap }) => ({
userInfoMap: new Map(userInfoMap).set(userId, userInfo),
})),
}));
十、部署与环境
10.1 多环境配置
| 环境 | 端口 | APP_ENV | 域名 |
|---|---|---|---|
| 开发 | 3110 | development | localhost |
| 测试 | - | test | test.swee.live |
| 生产 | 3111 / 16666(Docker) | production | swee.live |
10.2 Docker 部署
十一、技术亮点总结
11.1 AI 伴侣聊天系统
- Easemob MiniCore:轻量级 IM SDK,按需加载插件,减小包体积
- 消息转换管线:统一格式转换,支持文本、自定义事件、欢迎消息
- 用户信息缓存:Zustand + Map 结构,批量获取 + 按需请求
11.2 PWA 离线优先
- Service Worker:自动注册、即时更新
- 可安装应用:完整的 manifest 配置,支持 Add to Home Screen
- 深色主题:原生应用级别的视觉体验
11.3 流畅的页面转场
- Framer Motion:AnimatePresence + motion.div 实现页面级动画
- 滑动效果:进入(从右滑入)→ 退出(向左滑出)仿原生体验
- 自定义动画:Tailwind keyframes 实现 shake 等特效
11.4 亲密度情感引擎
- 5 级亲密度系统:从初识到灵魂伴侣的完整情感递进
- 权益解锁机制:每个等级对应特定功能权益
- 等级指南:互动引导用户提升亲密度
11.5 移动端深度适配
- 375px 固定宽度:保证跨设备视觉一致性
- 刘海屏适配:viewport-fit=cover + SafeArea 组件
- 禁止缩放:原生应用级交互体验
11.6 性能优化策略
核心性能优化成果
通过动态导入、ISR、长缓存和精确状态订阅等组合策略,在移动端弱网环境下也能保证流畅的用户体验。
- 动态导入:
next/dynamic懒加载大组件(Inbox 等) - ISR 模式:
fallback: 'blocking'增量静态再生 - 图片长缓存:1 年 immutable 缓存策略
- Zustand 选择器:精确订阅避免不必要重渲染
11.7 语音克隆创作
- 支持朗读内容列表获取
- FormData 混合上传(File + JSON 参数)
- 个人声音库管理
十二、项目数据概览
| 指标 | 数据 |
|---|---|
| 页面路由 | 9 个 |
| 视图组件 | 8 个 |
| 可复用组件 | 11+ |
| API 文件 | 94+ |
| 类型定义 | ~100 KB |
| Zustand Store | 1 个 |
| 支持语言 | 3 种(en-US / zh-TW / id-ID) |
| AI API 模块 | model(19) + im(26) = 45 个 |
| PWA 图标 | 192x192 / 512x512 |
| 页面转场动画 | 300ms 滑动过渡 |