This commit is contained in:
Developer
2026-03-10 17:25:18 +08:00
parent c9c62d3e48
commit 18eebfb0d2
6 changed files with 174 additions and 2 deletions

119
CLAUDE.md Normal file
View File

@@ -0,0 +1,119 @@
# reactBilibiliApp — 项目架构与技术要点
## 项目概述
仿 B 站 React Native 客户端,使用 Expo SDK 55 + expo-router调用 Bilibili 官方 Web API 获取热门视频、视频详情、评论及扫码登录。
## 技术栈
| 层 | 技术 |
|---|---|
| 框架 | React Native 0.83 + Expo SDK 55 |
| 路由 | expo-router v4文件系统路由 |
| 状态管理 | Zustand |
| 网络请求 | Axios |
| 本地存储 | @react-native-async-storage/async-storage |
| 视频播放 | react-native-webview内嵌 HTML5 video |
| 图标 | @expo/vector-iconsIonicons |
## 目录结构
```
app/
_layout.tsx # 根布局Tab 导航 + 启动时恢复登录态
index.tsx # 首页:热门视频瀑布流(双列 FlatList
video/
_layout.tsx # Stack 导航(无头部)
[bvid].tsx # 视频详情页(播放 + 简介 + 评论)
components/
VideoCard.tsx # 视频卡片封面、标题、UP主、播放量
VideoPlayer.tsx # 视频播放器入口web 用 <video>native 用 NativeVideoPlayer
NativeVideoPlayer.tsx# WebView 内嵌 HTML5 video 播放,用 JS 注入 src 避免 URL 特殊字符问题
CommentItem.tsx # 评论条目
LoginModal.tsx # 扫码登录 Modal底部弹出
hooks/
useVideoList.ts # 热门视频分页加载(支持下拉刷新、上拉加载更多)
useVideoDetail.ts # 视频详情 + 获取播放流 URL
useComments.ts # 评论分页加载
services/
bilibili.ts # 所有 API 请求axios 实例 + Cookie 拦截器)
types.ts # 数据类型定义
store/
authStore.ts # Zustand 登录状态sessdata/uid/username + 持久化)
utils/
format.ts # formatCount / formatDuration / formatTime
```
## 路由结构
- `/` → 首页Tab
- `/video/[bvid]` → 视频详情Stack从 Tab 内 push
- `video` Tab 在导航栏中隐藏(`href: null`
## Bilibili API 关键点
### axios 实例services/bilibili.ts
- 统一携带 `Referer: https://www.bilibili.com``User-Agent`(模拟 Chrome
- 请求拦截器自动注入 Cookie`buvid3`(随机生成并持久化)+ `SESSDATA`(登录后)
### 主要接口
| 函数 | 接口 | 说明 |
|---|---|---|
| `getPopularVideos(pn)` | `/x/web-interface/popular` | 热门视频,每页 20 条 |
| `getVideoDetail(bvid)` | `/x/web-interface/view` | 视频详情 |
| `getPlayUrl(bvid, cid)` | `/x/player/playurl` | 获取播放流 |
| `getComments(aid, pn)` | `/x/v2/reply` | 评论列表sort=2热评 |
| `generateQRCode()` | passport 域名 | 生成登录二维码 |
| `pollQRCode(key)` | passport 域名 | 轮询扫码状态2s 间隔) |
### 播放流参数(重要)
```ts
params: { bvid, cid, qn: 64, fnval: 0, platform: 'html5' }
```
- `fnval: 0` + `platform: 'html5'` → 返回 **MP4** 格式(`durl[0].url`
- `fnval: 1` → FLV**不可用**HTML5 video 不支持WebView 无法播放)
- `fnval: 16` → DASH结构不同`dash` 字段,非 `durl`),需单独实现
- `qn: 64` = 720P未登录时 B 站可能降级到较低清晰度
## 视频播放架构
```
VideoPlayer
├── web (Platform.OS === 'web') → 原生 <video> 标签
└── native → NativeVideoPlayer (WebView)
└── HTML 模板 + JS 注入 src
document.getElementById('v').src = JSON.stringify(uri)
```
**为什么用 WebView 而非 expo-av**
- `expo-av` 需要 development buildnative 编译),在 Expo Go 中报 `Cannot find native module 'ExponentAV'`
- `react-native-webview` 在 Expo Go 中开箱即用
-`JSON.stringify(uri)` 注入 src防止 URL 中 `&``=` 等特殊字符破坏 HTML 属性
## 登录流程
1. 首页右上角点击头像图标 → 弹出 `LoginModal`
2. 调用 `generateQRCode()` 获取 `qrcode_key` + 二维码 URL
3.`https://api.qrserver.com` 渲染二维码图片(第三方服务)
4. 每 2s 轮询 `pollQRCode``code === 0` 时从响应 Header `set-cookie` 提取 `SESSDATA`
5. `useAuthStore.login()``SESSDATA` 写入 AsyncStorage 并更新 Zustand 状态
6. 下次启动时 `_layout.tsx` 调用 `restore()` 恢复登录态
## 主题色
- 主色:`#00AEEC`B 站蓝)
- 文字主色:`#212121`
- 次要文字:`#999`
- 背景:`#f4f4f4`(列表)/ `#fff`(卡片)
## 开发注意事项
### 运行方式
- Expo Go`expo start` 扫码,视频播放用 WebView 方案
- Dev Build`expo run:android`,可使用更多 native 模块
### 常见问题
- **视频无法播放**:检查 `fnval` 是否为 `0`,确认返回的是 MP4 而非 FLV
- **API 403**`buvid3` Cookie 或 `SESSDATA` 失效,清除 AsyncStorage 重试
- **评论为空**B 站部分视频评论区关闭,`replies` 字段为 null 属正常
- **二维码过期**`pollQRCode` 返回 `code === 86038`,关闭 Modal 重新打开即可
### 扩展方向
- 搜索功能:`/x/web-interface/search/all/v2`
- 动态流:需更高权限 Cookie`bili_jct` CSRF Token
- DASH 播放:需解析 MPD配合支持 DASH 的播放器(如 `react-native-video` + dev build
- 弹幕:`/x/v1/dm/list.so?oid={cid}` 返回 XML 格式

View File

@@ -36,8 +36,7 @@
"favicon": "./assets/favicon.png"
},
"plugins": [
"expo-router",
"expo-av"
"expo-router"
],
"experiments": {
"typedRoutes": true

22
build-apk.ps1 Normal file
View File

@@ -0,0 +1,22 @@
$env:JAVA_HOME = "C:\Program Files\Android\Android Studio\jbr"
$env:ANDROID_HOME = "C:\Users\Administrator\AppData\Local\Android\Sdk"
$env:PATH = "$env:JAVA_HOME\bin;C:\nvm4w\nodejs;$env:ANDROID_HOME\platform-tools;$env:PATH"
# Create short path alias to avoid 260-char limit
subst Z: "C:\Users\Administrator\Desktop\claude code studly\reactBilibiliApp" 2>$null
Set-Location "Z:\android"
.\gradlew.bat assembleDebug --no-daemon
Write-Host ""
if ($LASTEXITCODE -eq 0) {
$apk = Get-ChildItem "Z:\android\app\build\outputs\apk\debug\*.apk" | Select-Object -First 1
Write-Host "SUCCESS! APK: $($apk.FullName)" -ForegroundColor Green
# Copy to Desktop
Copy-Item $apk.FullName "C:\Users\Administrator\Desktop\BilibiliApp-debug.apk"
Write-Host "Copied to Desktop: BilibiliApp-debug.apk" -ForegroundColor Cyan
} else {
Write-Host "Build FAILED" -ForegroundColor Red
}
subst Z: /D 2>$null

21
package-lock.json generated
View File

@@ -17,6 +17,7 @@
"expo-linear-gradient": "~55.0.8",
"expo-router": "~55.0.4",
"expo-status-bar": "~55.0.4",
"expo-system-ui": "~55.0.9",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-native": "0.83.2",
@@ -4574,6 +4575,26 @@
"react-native": "*"
}
},
"node_modules/expo-system-ui": {
"version": "55.0.9",
"resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-55.0.9.tgz",
"integrity": "sha512-8ygP1B0uFAFI8s7eHY2IcGnE83GhFeZYwHBr/fQ4dSXnc7iVT9zp2PvyTyiDiibQ69dBG+fauMQ4KlPcOO51kQ==",
"license": "MIT",
"dependencies": {
"@react-native/normalize-colors": "0.83.2",
"debug": "^4.3.2"
},
"peerDependencies": {
"expo": "*",
"react-native": "*",
"react-native-web": "*"
},
"peerDependenciesMeta": {
"react-native-web": {
"optional": true
}
}
},
"node_modules/expo-updates-interface": {
"version": "55.1.3",
"resolved": "https://registry.npmjs.org/expo-updates-interface/-/expo-updates-interface-55.1.3.tgz",

View File

@@ -18,6 +18,7 @@
"expo-linear-gradient": "~55.0.8",
"expo-router": "~55.0.4",
"expo-status-bar": "~55.0.4",
"expo-system-ui": "~55.0.9",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-native": "0.83.2",

10
test-cmd.ps1 Normal file
View File

@@ -0,0 +1,10 @@
$env:PATH = "C:\nvm4w\nodejs;" + $env:PATH
subst Z: "C:\Users\Administrator\Desktop\claude code studly\reactBilibiliApp" 2>$null
Start-Sleep -Milliseconds 500
Write-Host "=== Testing autolinking from Z:\android ===" -ForegroundColor Yellow
$result = & cmd /c "cd /d Z:\android && node --no-warnings --eval `"require('expo/bin/autolinking')`" expo-modules-autolinking resolve --platform android --json 2>&1"
Write-Host "Exit code: $LASTEXITCODE"
$result | Select-Object -First 10 | Write-Host
subst Z: /D 2>$null