579 lines
19 KiB
Markdown
579 lines
19 KiB
Markdown
|
|
# Design Document
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
本设计文档描述了在Android直播应用中添加"热门"Tab功能的技术实现方案。该功能将在"发现"页面的分类标签栏(categoryTabs)中添加一个"热门"Tab,位于最左侧("推荐"Tab左边),用于展示后台管理员标记为热门的作品内容。
|
|||
|
|
|
|||
|
|
## Architecture
|
|||
|
|
|
|||
|
|
### 系统架构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ MainActivity │
|
|||
|
|
│ ┌───────────────────────────────────────────────────────┐ │
|
|||
|
|
│ │ topTabs: 关注 | 发现 | 附近 │ │
|
|||
|
|
│ └───────────────────────────────────────────────────────┘ │
|
|||
|
|
│ ┌───────────────────────────────────────────────────────┐ │
|
|||
|
|
│ │ categoryTabs: 热门 | 推荐 | 直播 | 视频 | 音乐 | 游戏│ │
|
|||
|
|
│ └───────────────────────────────────────────────────────┘ │
|
|||
|
|
│ ┌───────────────────────────────────────────────────────┐ │
|
|||
|
|
│ │ RecyclerView (WorksAdapter) │ │
|
|||
|
|
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
|||
|
|
│ │ │ 作品1 │ │ 作品2 │ │ 作品3 │ │ │
|
|||
|
|
│ │ │ 🔥热门 │ │ 🔥热门 │ │ 🔥热门 │ │ │
|
|||
|
|
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
|
|||
|
|
│ └───────────────────────────────────────────────────────┘ │
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
↓
|
|||
|
|
ApiService
|
|||
|
|
↓
|
|||
|
|
GET /api/works/hot?page=1&pageSize=20
|
|||
|
|
↓
|
|||
|
|
后端服务器
|
|||
|
|
↓
|
|||
|
|
返回 is_hot=1 的作品列表
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 数据流
|
|||
|
|
|
|||
|
|
1. **用户操作** → 点击"热门"Tab
|
|||
|
|
2. **MainActivity** → 调用 `loadHotWorks(page)` 方法
|
|||
|
|
3. **ApiService** → 发送 GET 请求到 `/api/works/hot`
|
|||
|
|
4. **后端服务器** → 查询 `is_hot=1` 的作品,按 `hot_time` 降序排序
|
|||
|
|
5. **ApiService** → 接收 `PageResponse<WorksResponse>` 数据
|
|||
|
|
6. **WorksAdapter** → 渲染作品列表
|
|||
|
|
7. **UI** → 显示热门作品,每个作品卡片显示🔥标识
|
|||
|
|
|
|||
|
|
## Components and Interfaces
|
|||
|
|
|
|||
|
|
### 1. MainActivity 修改
|
|||
|
|
|
|||
|
|
#### 新增成员变量
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 热门作品相关
|
|||
|
|
private final List<WorksResponse> hotWorks = new ArrayList<>(); // 热门作品列表
|
|||
|
|
private int currentHotWorksPage = 1; // 当前热门作品页码
|
|||
|
|
private boolean isLoadingHotWorks = false; // 是否正在加载热门作品
|
|||
|
|
private boolean hasMoreHotWorks = true; // 是否还有更多热门作品
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 新增方法
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/**
|
|||
|
|
* 加载热门作品列表
|
|||
|
|
* @param page 页码(从1开始)
|
|||
|
|
*/
|
|||
|
|
private void loadHotWorks(int page) {
|
|||
|
|
if (isLoadingHotWorks) return;
|
|||
|
|
|
|||
|
|
isLoadingHotWorks = true;
|
|||
|
|
|
|||
|
|
// 显示加载状态
|
|||
|
|
if (page == 1) {
|
|||
|
|
if (hotWorks.isEmpty()) {
|
|||
|
|
LoadingStateManager.showSkeleton(binding.roomsRecyclerView, 6);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ApiClient.getService(getApplicationContext())
|
|||
|
|
.getHotWorks(page, 20)
|
|||
|
|
.enqueue(new Callback<ApiResponse<PageResponse<WorksResponse>>>() {
|
|||
|
|
@Override
|
|||
|
|
public void onResponse(Call<ApiResponse<PageResponse<WorksResponse>>> call,
|
|||
|
|
Response<ApiResponse<PageResponse<WorksResponse>>> response) {
|
|||
|
|
isLoadingHotWorks = false;
|
|||
|
|
binding.swipeRefresh.setRefreshing(false);
|
|||
|
|
|
|||
|
|
if (response.isSuccessful() && response.body() != null && response.body().isOk()) {
|
|||
|
|
PageResponse<WorksResponse> pageData = response.body().getData();
|
|||
|
|
if (pageData != null && pageData.getList() != null) {
|
|||
|
|
if (page == 1) {
|
|||
|
|
hotWorks.clear();
|
|||
|
|
}
|
|||
|
|
hotWorks.addAll(pageData.getList());
|
|||
|
|
|
|||
|
|
// 检查是否还有更多数据
|
|||
|
|
hasMoreHotWorks = pageData.getList().size() >= 20;
|
|||
|
|
|
|||
|
|
// 更新UI
|
|||
|
|
updateHotWorksUI();
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
showErrorState("加载失败,请重试");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public void onFailure(Call<ApiResponse<PageResponse<WorksResponse>>> call, Throwable t) {
|
|||
|
|
isLoadingHotWorks = false;
|
|||
|
|
binding.swipeRefresh.setRefreshing(false);
|
|||
|
|
showErrorState("网络错误,请重试");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新热门作品UI
|
|||
|
|
*/
|
|||
|
|
private void updateHotWorksUI() {
|
|||
|
|
if (hotWorks.isEmpty()) {
|
|||
|
|
showEmptyState("暂无热门作品");
|
|||
|
|
} else {
|
|||
|
|
hideEmptyState();
|
|||
|
|
hideErrorState();
|
|||
|
|
|
|||
|
|
// 转换为FeedItem列表
|
|||
|
|
List<FeedItem> feedItems = new ArrayList<>();
|
|||
|
|
for (WorksResponse work : hotWorks) {
|
|||
|
|
feedItems.add(FeedItem.fromWorks(work));
|
|||
|
|
}
|
|||
|
|
adapter.submitList(feedItems);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 刷新热门作品
|
|||
|
|
*/
|
|||
|
|
private void refreshHotWorks() {
|
|||
|
|
currentHotWorksPage = 1;
|
|||
|
|
hasMoreHotWorks = true;
|
|||
|
|
loadHotWorks(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 加载更多热门作品
|
|||
|
|
*/
|
|||
|
|
private void loadMoreHotWorks() {
|
|||
|
|
if (!isLoadingHotWorks && hasMoreHotWorks) {
|
|||
|
|
currentHotWorksPage++;
|
|||
|
|
loadHotWorks(currentHotWorksPage);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 修改 categoryTabs 初始化
|
|||
|
|
|
|||
|
|
在 `activity_main.xml` 中的 `categoryTabs` 添加"热门"Tab:
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<com.google.android.material.tabs.TabLayout
|
|||
|
|
android:id="@+id/categoryTabs"
|
|||
|
|
...>
|
|||
|
|
|
|||
|
|
<!-- 新增:热门Tab -->
|
|||
|
|
<com.google.android.material.tabs.TabItem
|
|||
|
|
android:layout_width="wrap_content"
|
|||
|
|
android:layout_height="wrap_content"
|
|||
|
|
android:text="热门" />
|
|||
|
|
|
|||
|
|
<com.google.android.material.tabs.TabItem
|
|||
|
|
android:layout_width="wrap_content"
|
|||
|
|
android:layout_height="wrap_content"
|
|||
|
|
android:text="推荐" />
|
|||
|
|
|
|||
|
|
<!-- 其他Tab保持不变 -->
|
|||
|
|
...
|
|||
|
|
</com.google.android.material.tabs.TabLayout>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 修改 categoryTabs 监听器
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
binding.categoryTabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
|||
|
|
@Override
|
|||
|
|
public void onTabSelected(TabLayout.Tab tab) {
|
|||
|
|
if (tab == null) return;
|
|||
|
|
CharSequence title = tab.getText();
|
|||
|
|
currentCategory = title != null ? title.toString() : "热门";
|
|||
|
|
|
|||
|
|
// 保存选中的分类
|
|||
|
|
CategoryFilterManager.saveLastCategory(MainActivity.this, currentCategory);
|
|||
|
|
|
|||
|
|
// 根据分类加载数据
|
|||
|
|
if ("热门".equals(currentCategory)) {
|
|||
|
|
// 加载热门作品
|
|||
|
|
refreshHotWorks();
|
|||
|
|
} else {
|
|||
|
|
// 应用其他分类筛选
|
|||
|
|
applyCategoryFilterWithAnimation(currentCategory);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public void onTabUnselected(TabLayout.Tab tab) {}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public void onTabReselected(TabLayout.Tab tab) {
|
|||
|
|
if (tab == null) return;
|
|||
|
|
CharSequence title = tab.getText();
|
|||
|
|
currentCategory = title != null ? title.toString() : "热门";
|
|||
|
|
|
|||
|
|
if ("热门".equals(currentCategory)) {
|
|||
|
|
// 刷新热门作品
|
|||
|
|
refreshHotWorks();
|
|||
|
|
} else {
|
|||
|
|
applyCategoryFilterWithAnimation(currentCategory);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 修改下拉刷新逻辑
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
binding.swipeRefresh.setOnRefreshListener(() -> {
|
|||
|
|
if ("热门".equals(currentCategory)) {
|
|||
|
|
refreshHotWorks();
|
|||
|
|
} else {
|
|||
|
|
fetchDiscoverRooms();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 修改滚动加载更多逻辑
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
binding.roomsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
|||
|
|
@Override
|
|||
|
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
|||
|
|
if (dy <= 0) return;
|
|||
|
|
|
|||
|
|
RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
|
|||
|
|
if (!(lm instanceof GridLayoutManager)) return;
|
|||
|
|
|
|||
|
|
GridLayoutManager glm = (GridLayoutManager) lm;
|
|||
|
|
int total = glm.getItemCount();
|
|||
|
|
int lastVisible = glm.findLastVisibleItemPosition();
|
|||
|
|
|
|||
|
|
if (total <= 0) return;
|
|||
|
|
|
|||
|
|
if (lastVisible >= total - 4) {
|
|||
|
|
if ("热门".equals(currentCategory)) {
|
|||
|
|
loadMoreHotWorks();
|
|||
|
|
} else {
|
|||
|
|
// 其他分类的加载逻辑
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. WorksAdapter 修改
|
|||
|
|
|
|||
|
|
#### 添加热门标识显示
|
|||
|
|
|
|||
|
|
在 `item_works.xml` 布局中添加热门标识:
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<!-- 热门标识 -->
|
|||
|
|
<ImageView
|
|||
|
|
android:id="@+id/hotBadge"
|
|||
|
|
android:layout_width="24dp"
|
|||
|
|
android:layout_height="24dp"
|
|||
|
|
android:layout_margin="8dp"
|
|||
|
|
android:src="@drawable/ic_fire_24"
|
|||
|
|
android:visibility="gone"
|
|||
|
|
app:tint="#FF4500" />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 修改 ViewHolder 的 bind 方法
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
public void bind(WorksResponse works, OnWorksClickListener listener) {
|
|||
|
|
if (works == null) return;
|
|||
|
|
|
|||
|
|
// ... 现有代码 ...
|
|||
|
|
|
|||
|
|
// 显示热门标识
|
|||
|
|
ImageView hotBadge = itemView.findViewById(R.id.hotBadge);
|
|||
|
|
if (hotBadge != null) {
|
|||
|
|
Boolean isHot = works.getIsHot();
|
|||
|
|
if (isHot != null && isHot) {
|
|||
|
|
hotBadge.setVisibility(View.VISIBLE);
|
|||
|
|
} else {
|
|||
|
|
hotBadge.setVisibility(View.GONE);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ... 现有代码 ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. WorksResponse 数据模型
|
|||
|
|
|
|||
|
|
确保 `WorksResponse` 类包含 `isHot` 字段:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
public class WorksResponse {
|
|||
|
|
private Long id;
|
|||
|
|
private String title;
|
|||
|
|
private String coverUrl;
|
|||
|
|
private String videoUrl;
|
|||
|
|
private String type;
|
|||
|
|
private Integer userId;
|
|||
|
|
private String userName;
|
|||
|
|
private String authorName;
|
|||
|
|
private Integer likeCount;
|
|||
|
|
private Boolean isLiked;
|
|||
|
|
private Boolean isHot; // 新增:是否热门
|
|||
|
|
private String hotTime; // 新增:设置热门的时间
|
|||
|
|
|
|||
|
|
// Getters and Setters
|
|||
|
|
public Boolean getIsHot() {
|
|||
|
|
return isHot;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void setIsHot(Boolean isHot) {
|
|||
|
|
this.isHot = isHot;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public String getHotTime() {
|
|||
|
|
return hotTime;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void setHotTime(String hotTime) {
|
|||
|
|
this.hotTime = hotTime;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. ApiService 接口
|
|||
|
|
|
|||
|
|
已存在的接口:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
/**
|
|||
|
|
* 获取热门作品列表
|
|||
|
|
*/
|
|||
|
|
@GET("api/works/hot")
|
|||
|
|
Call<ApiResponse<PageResponse<WorksResponse>>> getHotWorks(
|
|||
|
|
@Query("page") int page,
|
|||
|
|
@Query("pageSize") int pageSize);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Data Models
|
|||
|
|
|
|||
|
|
### WorksResponse
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
public class WorksResponse {
|
|||
|
|
private Long id; // 作品ID
|
|||
|
|
private String title; // 作品标题
|
|||
|
|
private String coverUrl; // 封面图片URL
|
|||
|
|
private String videoUrl; // 视频URL
|
|||
|
|
private String type; // 作品类型(VIDEO/IMAGE)
|
|||
|
|
private Integer userId; // 用户ID
|
|||
|
|
private String userName; // 用户名
|
|||
|
|
private String authorName; // 作者名
|
|||
|
|
private Integer likeCount; // 点赞数
|
|||
|
|
private Boolean isLiked; // 当前用户是否已点赞
|
|||
|
|
private Boolean isHot; // 是否热门
|
|||
|
|
private String hotTime; // 设置热门的时间
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### PageResponse<T>
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
public class PageResponse<T> {
|
|||
|
|
private List<T> list; // 数据列表
|
|||
|
|
private Integer total; // 总数
|
|||
|
|
private Integer page; // 当前页码
|
|||
|
|
private Integer pageSize; // 每页大小
|
|||
|
|
private Integer totalPage; // 总页数
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### ApiResponse<T>
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
public class ApiResponse<T> {
|
|||
|
|
private Integer code; // 响应码(200表示成功)
|
|||
|
|
private String message; // 响应消息
|
|||
|
|
private T data; // 响应数据
|
|||
|
|
|
|||
|
|
public boolean isOk() {
|
|||
|
|
return code != null && code == 200;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Data Models
|
|||
|
|
|
|||
|
|
### 数据库字段(后端)
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- eb_works 表
|
|||
|
|
ALTER TABLE `eb_works`
|
|||
|
|
ADD COLUMN `is_hot` tinyint DEFAULT 0 COMMENT '是否热门:1-是 0-否' AFTER `status`,
|
|||
|
|
ADD COLUMN `hot_time` datetime DEFAULT NULL COMMENT '设置热门的时间' AFTER `is_hot`,
|
|||
|
|
ADD INDEX `idx_is_hot` (`is_hot`);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### API 请求参数
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GET /api/works/hot?page=1&pageSize=20
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### API 响应格式
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 200,
|
|||
|
|
"message": "success",
|
|||
|
|
"data": {
|
|||
|
|
"list": [
|
|||
|
|
{
|
|||
|
|
"id": 1,
|
|||
|
|
"title": "精彩作品",
|
|||
|
|
"coverUrl": "https://example.com/cover.jpg",
|
|||
|
|
"videoUrl": "https://example.com/video.mp4",
|
|||
|
|
"type": "VIDEO",
|
|||
|
|
"userId": 10,
|
|||
|
|
"userName": "用户A",
|
|||
|
|
"authorName": "作者A",
|
|||
|
|
"likeCount": 100,
|
|||
|
|
"isLiked": false,
|
|||
|
|
"isHot": true,
|
|||
|
|
"hotTime": "2026-01-08 10:00:00"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"total": 50,
|
|||
|
|
"page": 1,
|
|||
|
|
"pageSize": 20,
|
|||
|
|
"totalPage": 3
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Error Handling
|
|||
|
|
|
|||
|
|
### 错误场景处理
|
|||
|
|
|
|||
|
|
1. **网络错误**
|
|||
|
|
- 显示错误提示:"网络错误,请重试"
|
|||
|
|
- 提供重试按钮
|
|||
|
|
- 保留已加载的数据
|
|||
|
|
|
|||
|
|
2. **API 返回错误**
|
|||
|
|
- 显示服务器返回的错误消息
|
|||
|
|
- 提供重试按钮
|
|||
|
|
|
|||
|
|
3. **空数据**
|
|||
|
|
- 显示空状态视图:"暂无热门作品"
|
|||
|
|
- 提供刷新按钮
|
|||
|
|
|
|||
|
|
4. **加载超时**
|
|||
|
|
- 设置合理的超时时间(30秒)
|
|||
|
|
- 显示超时提示
|
|||
|
|
- 提供重试选项
|
|||
|
|
|
|||
|
|
### 错误处理代码
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Override
|
|||
|
|
public void onFailure(Call<ApiResponse<PageResponse<WorksResponse>>> call, Throwable t) {
|
|||
|
|
isLoadingHotWorks = false;
|
|||
|
|
binding.swipeRefresh.setRefreshing(false);
|
|||
|
|
|
|||
|
|
String errorMsg = "网络错误";
|
|||
|
|
if (t != null) {
|
|||
|
|
if (t.getMessage() != null && t.getMessage().contains("Unable to resolve host")) {
|
|||
|
|
errorMsg = "无法连接服务器,请检查网络";
|
|||
|
|
} else if (t.getMessage() != null && t.getMessage().contains("timeout")) {
|
|||
|
|
errorMsg = "连接超时,请重试";
|
|||
|
|
} else if (t.getMessage() != null && t.getMessage().contains("Connection refused")) {
|
|||
|
|
errorMsg = "连接被拒绝,请确保服务器已启动";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
showErrorState(errorMsg);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Testing Strategy
|
|||
|
|
|
|||
|
|
### Correctness Properties
|
|||
|
|
|
|||
|
|
*A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
|||
|
|
|
|||
|
|
#### Property 1: 热门Tab仅显示作品内容
|
|||
|
|
*For any* 热门Tab中显示的数据项,该数据项应该是WorksResponse类型,不应包含Room(直播间)类型的数据
|
|||
|
|
**Validates: Requirements 2.3**
|
|||
|
|
|
|||
|
|
#### Property 2: 热门作品显示热门标识
|
|||
|
|
*For any* 在热门Tab中显示的作品,如果该作品的isHot字段为true,则作品卡片上应该显示热门图标(hotBadge可见)
|
|||
|
|
**Validates: Requirements 3.1**
|
|||
|
|
|
|||
|
|
### Unit Tests
|
|||
|
|
|
|||
|
|
1. **MainActivity 单元测试**
|
|||
|
|
- 测试"热门"Tab在categoryTabs的索引为0(最左侧)**Validates: Requirements 1.1, 1.2**
|
|||
|
|
- 测试categoryTabs包含6个Tab,顺序为:热门、推荐、直播、视频、音乐、游戏 **Validates: Requirements 1.4**
|
|||
|
|
- 测试进入"发现"Tab时默认选中"热门"Tab **Validates: Requirements 1.3**
|
|||
|
|
- 测试点击"热门"Tab调用loadHotWorks方法 **Validates: Requirements 2.1**
|
|||
|
|
- 测试loadHotWorks方法调用ApiService.getHotWorks接口 **Validates: Requirements 2.1**
|
|||
|
|
- 测试接收到数据后WorksAdapter被正确更新 **Validates: Requirements 2.2**
|
|||
|
|
- 测试加载中显示骨架屏或加载动画 **Validates: Requirements 2.4**
|
|||
|
|
- 测试加载失败显示错误提示 **Validates: Requirements 2.5**
|
|||
|
|
- 测试空数据显示"暂无热门作品" **Validates: Requirements 2.6**
|
|||
|
|
- 测试下拉刷新触发refreshHotWorks方法 **Validates: Requirements 4.1**
|
|||
|
|
- 测试刷新时SwipeRefreshLayout.isRefreshing为true **Validates: Requirements 4.2**
|
|||
|
|
- 测试刷新完成后isRefreshing为false且列表更新 **Validates: Requirements 4.3**
|
|||
|
|
- 测试刷新失败显示错误提示且保留原数据 **Validates: Requirements 4.4**
|
|||
|
|
- 测试滚动到底部触发loadMoreHotWorks方法 **Validates: Requirements 5.1**
|
|||
|
|
- 测试加载更多时显示底部加载指示器 **Validates: Requirements 5.2**
|
|||
|
|
- 测试hasMoreHotWorks为false时显示"没有更多内容" **Validates: Requirements 5.3**
|
|||
|
|
- 测试加载更多失败显示错误提示 **Validates: Requirements 5.4**
|
|||
|
|
- 测试切换Tab后hotWorks列表数据保留 **Validates: Requirements 6.1**
|
|||
|
|
- 测试返回热门Tab后列表数据和滚动位置恢复 **Validates: Requirements 6.2**
|
|||
|
|
- 测试停留超过5分钟后返回触发自动刷新 **Validates: Requirements 6.3**
|
|||
|
|
|
|||
|
|
2. **WorksAdapter 单元测试**
|
|||
|
|
- 测试isHot=true时hotBadge可见 **Validates: Requirements 3.1**
|
|||
|
|
- 测试isHot=false时hotBadge不可见 **Validates: Requirements 3.1**
|
|||
|
|
- 测试点击作品触发onWorksClick回调 **Validates: Requirements 3.3**
|
|||
|
|
- 测试点击作品启动WorkDetailActivity **Validates: Requirements 3.3**
|
|||
|
|
|
|||
|
|
3. **API 接口测试**
|
|||
|
|
- 测试getHotWorks接口返回PageResponse<WorksResponse>
|
|||
|
|
- 测试page和pageSize参数正确传递
|
|||
|
|
- 测试错误响应正确处理
|
|||
|
|
|
|||
|
|
### Integration Tests
|
|||
|
|
|
|||
|
|
1. **端到端测试**
|
|||
|
|
- 测试从点击"热门"Tab到显示作品列表的完整流程
|
|||
|
|
- 测试下拉刷新功能
|
|||
|
|
- 测试上拉加载更多功能
|
|||
|
|
- 测试点击作品跳转到详情页
|
|||
|
|
|
|||
|
|
2. **UI 测试**
|
|||
|
|
- 测试"热门"Tab在最左侧显示
|
|||
|
|
- 测试热门标识🔥正确显示
|
|||
|
|
- 测试加载动画正确显示
|
|||
|
|
- 测试空状态和错误状态正确显示
|
|||
|
|
|
|||
|
|
### 测试数据准备
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 准备测试数据:设置一些作品为热门
|
|||
|
|
UPDATE eb_works SET is_hot = 1, hot_time = NOW() WHERE id IN (1, 2, 3, 4, 5);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 测试用例
|
|||
|
|
|
|||
|
|
| 测试用例 | 输入 | 预期输出 |
|
|||
|
|
|---------|------|---------|
|
|||
|
|
| 加载热门作品 | page=1, pageSize=20 | 返回20条热门作品 |
|
|||
|
|
| 空数据 | 数据库无热门作品 | 显示"暂无热门作品" |
|
|||
|
|
| 网络错误 | 网络断开 | 显示"网络错误,请重试" |
|
|||
|
|
| 下拉刷新 | 下拉列表 | 重新加载第1页数据 |
|
|||
|
|
| 上拉加载更多 | 滚动到底部 | 加载下一页数据 |
|
|||
|
|
| 点击作品 | 点击作品卡片 | 跳转到作品详情页 |
|
|||
|
|
|