hookehuyr

feat: 实现环境判断的Mock数据自动切换模式

- 修复搜索页面布局:顶部固定(NavHeader+SearchBar+Tabs+ResultCount)+ 列表滚动
- 修复搜索分页hasMore计算时机bug(首次搜索显示没有更多的问题)
- 实现环境判断的Mock数据自动切换
  - 开发环境(pnpm dev:weapp)使用mock数据
  - 生产环境(pnpm build:weapp)使用真实API
  - 使用process.env.NODE_ENV === 'development'自动判断
- 更新5个页面使用环境变量判断
- 新增开发工作流文档到lessons-learned.md

修改文件:
- src/pages/search/index.vue: 修复布局和分页逻辑
- src/pages/week-hot-material/index.vue: 环境判断
- src/pages/message/index.vue: 环境判断
- src/pages/material-list/index.vue: 环境判断
- src/pages/product-center/index.vue: 环境判断
- docs/lessons-learned.md: 新增开发工作流章节Mock数据环境自动切换模式

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
16 - [错误处理](#3-错误处理) 16 - [错误处理](#3-错误处理)
17 - [API 调用错误:使用 fn() 包装](#坑-api-调用了-fn-包装重复-2-次) ⭐ 新增 17 - [API 调用错误:使用 fn() 包装](#坑-api-调用了-fn-包装重复-2-次) ⭐ 新增
18 - [架构设计](#架构设计) 18 - [架构设计](#架构设计)
19 +- [开发工作流](#开发工作流) ⭐ 新增
20 + - [Mock 数据环境自动切换](#mock-数据环境自动切换模式) ⭐ 新增
19 21
20 --- 22 ---
21 23
...@@ -1039,6 +1041,174 @@ src/ ...@@ -1039,6 +1041,174 @@ src/
1039 1041
1040 --- 1042 ---
1041 1043
1044 +## 开发工作流
1045 +
1046 +### ✅ Mock 数据环境自动切换模式
1047 +
1048 +#### 问题描述
1049 +
1050 +在开发过程中,使用 Mock 数据进行前端开发可以提高效率,但手动切换 Mock 数据和真实 API 容易出错:
1051 +
1052 +```javascript
1053 +// ❌ BAD - 硬编码开关,容易忘记切换
1054 +const USE_MOCK_DATA = true // 开发时用 true,部署时忘记改成 false
1055 +
1056 +const res = USE_MOCK_DATA
1057 + ? await mockWeekHotAPI(params)
1058 + : await weekHotAPI(params)
1059 +
1060 +// 风险:部署到生产环境时仍然使用 Mock 数据
1061 +```
1062 +
1063 +**错误表现**:
1064 +- 🔴 开发完成后忘记关闭 Mock 数据开关
1065 +- 🔴 生产环境返回假数据,导致严重问题
1066 +- 🔴 需要手动在每个页面中切换,容易遗漏
1067 +
1068 +#### 解决方案:基于环境变量自动切换
1069 +
1070 +使用 `process.env.NODE_ENV` 判断当前环境,自动选择使用 Mock 数据还是真实 API:
1071 +
1072 +```javascript
1073 +// ✅ GOOD - 环境变量自动判断
1074 +const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
1075 +
1076 +const res = USE_MOCK_DATA
1077 + ? await mockWeekHotAPI(params)
1078 + : await weekHotAPI(params)
1079 +
1080 +console.log('[Week Hot] 使用 Mock 数据:', USE_MOCK_DATA)
1081 +```
1082 +
1083 +#### 环境说明
1084 +
1085 +| 环境 | NODE_ENV | 命令 | Mock 数据 |
1086 +|------|-----------|------|----------|
1087 +| **开发环境** | `'development'` | `pnpm dev:weapp` | ✅ 启用 |
1088 +| **生产环境** | `'production'` | `pnpm build:weapp` | ❌ 禁用 |
1089 +
1090 +#### 实施步骤
1091 +
1092 +1. **修改所有使用 Mock 数据的页面**(共 5 个):
1093 + - `src/pages/search/index.vue`(line 152)
1094 + - `src/pages/week-hot-material/index.vue`(line 76)
1095 + - `src/pages/message/index.vue`(line 57)
1096 + - `src/pages/material-list/index.vue`(line 131)
1097 + - `src/pages/product-center/index.vue`(line 159)
1098 +
1099 +2. **统一修改代码**:
1100 + ```javascript
1101 + // 修改前
1102 + const USE_MOCK_DATA = true // ❌ 硬编码
1103 +
1104 + // 修改后
1105 + const USE_MOCK_DATA = process.env.NODE_ENV === 'development' // ✅ 环境判断
1106 + ```
1107 +
1108 +3. **添加日志**(可选,便于调试):
1109 + ```javascript
1110 + console.log('[PageName] 使用 Mock 数据:', USE_MOCK_DATA)
1111 + ```
1112 +
1113 +#### 收益
1114 +
1115 +✅ **开发环境**:
1116 +- 快速迭代,无需等待后端接口
1117 +- 测试分页、加载更多等前端逻辑
1118 +- 模拟各种数据场景
1119 +
1120 +✅ **生产环境**:
1121 +- 自动切换到真实 API
1122 +- 无需手动修改代码
1123 +- 避免假数据上线风险
1124 +
1125 +✅ **安全性**:
1126 +- 无法在生产环境误用 Mock 数据
1127 +- 一次配置,永久生效
1128 +
1129 +#### 使用示例
1130 +
1131 +```vue
1132 +<script setup>
1133 +import { ref } from 'vue'
1134 +import { weekHotAPI } from '@/api/file'
1135 +import { mockWeekHotAPI } from '@/utils/mockData'
1136 +
1137 +// ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API
1138 +const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
1139 +
1140 +const list = ref([])
1141 +const loading = ref(false)
1142 +
1143 +const fetchList = async (params) => {
1144 + loading.value = true
1145 +
1146 + try {
1147 + console.log('[Week Hot] 使用 Mock 数据:', USE_MOCK_DATA)
1148 +
1149 + // 根据开关选择使用真实 API 或 Mock 数据
1150 + const res = USE_MOCK_DATA
1151 + ? await mockWeekHotAPI(params)
1152 + : await weekHotAPI(params)
1153 +
1154 + if (res.code === 1 && res.data) {
1155 + list.value = res.data.list
1156 + }
1157 + } catch (err) {
1158 + console.error('获取列表失败:', err)
1159 + } finally {
1160 + loading.value = false
1161 + }
1162 +}
1163 +</script>
1164 +```
1165 +
1166 +#### 注意事项
1167 +
1168 +1. **确保 Mock 数据结构一致**:
1169 + - Mock 数据的返回格式必须与真实 API 一致
1170 + - 特别是 `{ code, data, msg }` 结构
1171 + - 确保分页字段名称一致
1172 +
1173 +2. **生产构建前检查**:
1174 + ```bash
1175 + # 开发环境(使用 Mock)
1176 + pnpm dev:weapp
1177 +
1178 + # 生产构建前检查环境变量
1179 + pnpm build:weapp
1180 + # 确认打包后的代码使用真实 API
1181 + ```
1182 +
1183 +3. **代码审查清单**:
1184 + - [ ] 所有 Mock 数据开关都改为 `process.env.NODE_ENV === 'development'`
1185 + - [ ] 添加了调试日志
1186 + - [ ] Mock 数据结构符合真实 API
1187 +
1188 +#### 相关文件
1189 +
1190 +- Mock 数据定义:`src/utils/mockData.js`
1191 +- 使用 Mock 的页面(5 个):
1192 + - `src/pages/search/index.vue`
1193 + - `src/pages/week-hot-material/index.vue`
1194 + - `src/pages/message/index.vue`
1195 + - `src/pages/material-list/index.vue`
1196 + - `src/pages/product-center/index.vue`
1197 +
1198 +#### 最佳实践总结
1199 +
1200 +⚠️ **强制要求**:所有使用 Mock 数据的页面都必须使用环境变量判断,禁止硬编码开关。
1201 +
1202 +```javascript
1203 +// ✅ 推荐写法
1204 +const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
1205 +
1206 +// ❌ 禁止写法
1207 +const USE_MOCK_DATA = true // 容易导致生产环境误用
1208 +```
1209 +
1210 +---
1211 +
1042 ## 总结 1212 ## 总结
1043 1213
1044 ### 🎯 核心经验 1214 ### 🎯 核心经验
...@@ -1051,7 +1221,8 @@ src/ ...@@ -1051,7 +1221,8 @@ src/
1051 6. **代码质量**: 强制 JSDoc 注释,统一命名规范 1221 6. **代码质量**: 强制 JSDoc 注释,统一命名规范
1052 7. **API 调用规范**: ⚠️ **不要使用 `fn()` 包装 API,直接调用并自己处理错误** 1222 7. **API 调用规范**: ⚠️ **不要使用 `fn()` 包装 API,直接调用并自己处理错误**
1053 8. **架构设计**: 分层清晰,职责单一 1223 8. **架构设计**: 分层清晰,职责单一
1054 -9. **⚠️ 写代码前必查**: 先搜索项目中是否有类似实现,保持写法一致 1224 +9. **Mock 数据切换**: ⭐ **使用环境变量 `process.env.NODE_ENV` 自动判断,禁止硬编码**
1225 +10. **⚠️ 写代码前必查**: 先搜索项目中是否有类似实现,保持写法一致
1055 1226
1056 ### 📚 推荐阅读 1227 ### 📚 推荐阅读
1057 1228
...@@ -1067,6 +1238,9 @@ src/ ...@@ -1067,6 +1238,9 @@ src/
1067 1238
1068 --- 1239 ---
1069 1240
1070 -**最后更新**: 2026-02-05 1241 +**最后更新**: 2026-02-08
1071 **维护者**: Claude Code 1242 **维护者**: Claude Code
1072 **项目**: Manulife WeApp 1243 **项目**: Manulife WeApp
1244 +
1245 +**更新记录**:
1246 +- 2026-02-08: 新增 "开发工作流" 章节,记录 Mock 数据环境自动切换模式
......
1 +# Mock 数据配置完成总结
2 +
3 +**日期**: 2026-02-08
4 +**状态**: ✅ 所有页面已配置完成
5 +
6 +---
7 +
8 +## 已添加 Mock 数据的页面
9 +
10 +| # | 页面 | 文件路径 | Mock 函数 | 状态 |
11 +|---|------|---------|----------|------|
12 +| 1 | 周热门资料 | `src/pages/week-hot-material/index.vue` | `mockWeekHotAPI` | ✅ 已完成 |
13 +| 2 | 资料列表 | `src/pages/material-list/index.vue` | `mockFileListAPI` | ✅ 已完成 |
14 +| 3 | 产品中心 | `src/pages/product-center/index.vue` | `mockProductListAPI` | ✅ 已完成 |
15 +| 4 | 搜索页 | `src/pages/search/index.vue` | `mockSearchAPI` | ✅ 已完成 |
16 +| 5 | 消息列表 | `src/pages/message/index.vue` | `mockMessageListAPI` | ✅ 已完成 |
17 +
18 +---
19 +
20 +## 每个页面的修改内容
21 +
22 +### 1. 周热门资料页 ✅
23 +
24 +**文件**: `src/pages/week-hot-material/index.vue`
25 +
26 +**修改**:
27 +- ✅ 导入 `mockWeekHotAPI`
28 +- ✅ 添加 `USE_MOCK_DATA = true` 开关 (第 76 行)
29 +- ✅ 修改 `fetchWeekHotList` 函数 (第 110-112 行)
30 +
31 +**使用**:
32 +```javascript
33 +// 第 76 行
34 +const USE_MOCK_DATA = true // ✅ 已启用
35 +```
36 +
37 +---
38 +
39 +### 2. 资料列表页 ✅
40 +
41 +**文件**: `src/pages/material-list/index.vue`
42 +
43 +**修改**:
44 +- ✅ 导入 `mockFileListAPI`
45 +- ✅ 添加 `USE_MOCK_DATA = true` 开关 (第 128 行)
46 +- ✅ 修改主 API 调用 (第 283-289 行)
47 +- ✅ 修改搜索 API 调用 (第 573-579 行)
48 +
49 +**使用**:
50 +```javascript
51 +// 第 128 行
52 +const USE_MOCK_DATA = true // ✅ 已启用
53 +```
54 +
55 +**特性**:
56 +- ✅ 支持分类过滤 (`cid`)
57 +- ✅ 支持关键词搜索 (`keyword`)
58 +- ✅ 8 页数据,每页 20 条
59 +
60 +---
61 +
62 +### 3. 产品中心页 ✅
63 +
64 +**文件**: `src/pages/product-center/index.vue`
65 +
66 +**修改**:
67 +- ✅ 导入 `mockProductListAPI`
68 +- ✅ 添加 `USE_MOCK_DATA = true` 开关 (第 158 行)
69 +- ✅ 修改 `fetchProducts` 函数 (第 217-222 行)
70 +
71 +**使用**:
72 +```javascript
73 +// 第 158 行
74 +const USE_MOCK_DATA = true // ✅ 已启用
75 +```
76 +
77 +**特性**:
78 +- ✅ 4 种产品分类(人寿保险、健康保险、意外保险、财产保险)
79 +- ✅ 4 种标签(热销、新品、推荐、限时)
80 +- ✅ 支持分类过滤 (`cid`)
81 +- ✅ 支持关键词搜索 (`keyword`)
82 +- ✅ 10 页数据,每页 10 条
83 +
84 +---
85 +
86 +### 4. 搜索页 ✅
87 +
88 +**文件**: `src/pages/search/index.vue`
89 +
90 +**修改**:
91 +- ✅ 导入 `mockSearchAPI`
92 +- ✅ 添加 `USE_MOCK_DATA = true` 开关 (第 151 行)
93 +- ✅ 修改 `performSearch` 函数 (第 229-234 行)
94 +
95 +**使用**:
96 +```javascript
97 +// 第 151 行
98 +const USE_MOCK_DATA = true // ✅ 已启用
99 +```
100 +
101 +**特性**:
102 +- ✅ 同时搜索产品和资料
103 +- ✅ 支持关键词过滤
104 +- ✅ 自动选择有数据的 tab
105 +- ✅ 5 页数据,每页 20 条
106 +
107 +---
108 +
109 +### 5. 消息列表页 ✅
110 +
111 +**文件**: `src/pages/message/index.vue`
112 +
113 +**修改**:
114 +- ✅ 导入 `mockMessageListAPI`
115 +- ✅ 添加 `USE_MOCK_DATA = true` 开关 (第 56 行)
116 +- ✅ 修改 `fetchMessageList` 函数 (第 79-89 行)
117 +
118 +**使用**:
119 +```javascript
120 +// 第 56 行
121 +const USE_MOCK_DATA = true // ✅ 已启用
122 +```
123 +
124 +**特性**:
125 +- ✅ 15 种消息标题模板
126 +- ✅ 随机创建时间(最近30天内)
127 +- ✅ 50%概率已读
128 +- ✅ 两种消息类型(通知、系统)
129 +- ✅ 8 页数据,每页 10 条
130 +
131 +---
132 +
133 +## 全局测试步骤
134 +
135 +### 1. 启动开发服务器
136 +
137 +```bash
138 +pnpm dev:weapp
139 +```
140 +
141 +### 2. 测试每个页面
142 +
143 +#### 周热门资料页
144 +1. 导航到"周热门资料"页
145 +2. 查看首次加载(20 条数据)
146 +3. 向下滚动,触发加载更多
147 +4. 查看 Console: `[Mock] weekHotAPI - 第X页,共Y条`
148 +5. 滚动到底,显示"没有更多了"
149 +
150 +#### 资料列表页
151 +1. 导航到"资料列表"页
152 +2. 查看首次加载
153 +3. 测试切换分类 tab
154 +4. 测试搜索功能
155 +5. 向下滚动加载更多
156 +
157 +#### 产品中心页
158 +1. 导航到"产品中心"页
159 +2. 查看首次加载(10 条数据)
160 +3. 测试切换分类 tab(人寿保险、健康保险等)
161 +4. 测试搜索功能
162 +5. 向下滚动加载更多
163 +
164 +#### 搜索页
165 +1. 导航到"搜索"页
166 +2. 输入关键词(如"保险"、"产品"等)
167 +3. 点击搜索或按回车
168 +4. 查看产品和资料结果
169 +5. 切换产品/资料 tab
170 +6. 向下滚动加载更多
171 +
172 +#### 消息列表页
173 +1. 导航到"我的消息"页
174 +2. 查看首次加载(10 条数据)
175 +3. 向下滚动加载更多
176 +4. 查看消息时间格式(YYYY-MM-DD)
177 +5. 滚动到底,显示"没有更多了"
178 +
179 +### 3. 查看 Console 日志
180 +
181 +正常情况下会看到:
182 +```
183 +[Mock] weekHotAPI - 第0页,共20条
184 +[Mock] fileListAPI - 第0页,共20条
185 +[Mock] listAPI - 第0页,共10条
186 +[Mock] searchAPI - 第0页,产品10条,资料10条
187 +[Mock] myListAPI - 第1页,共10条
188 +```
189 +
190 +---
191 +
192 +## 切换回真实 API
193 +
194 +测试完成后,需要将所有页面的开关改为 `false`:
195 +
196 +### 快速切换脚本
197 +
198 +```bash
199 +# 使用 sed 批量替换
200 +sed -i '' 's/const USE_MOCK_DATA = true/const USE_MOCK_DATA = false/g' \
201 + src/pages/week-hot-material/index.vue \
202 + src/pages/material-list/index.vue \
203 + src/pages/product-center/index.vue \
204 + src/pages/search/index.vue \
205 + src/pages/message/index.vue
206 +```
207 +
208 +### 手动切换
209 +
210 +修改每个页面文件中的 `USE_MOCK_DATA` 常量:
211 +
212 +```javascript
213 +// 修改前
214 +const USE_MOCK_DATA = true
215 +
216 +// 修改后
217 +const USE_MOCK_DATA = false
218 +```
219 +
220 +**需要修改的 5 个文件**:
221 +1. `src/pages/week-hot-material/index.vue`
222 +2. `src/pages/material-list/index.vue`
223 +3. `src/pages/product-center/index.vue`
224 +4. `src/pages/search/index.vue`
225 +5. `src/pages/message/index.vue`
226 +
227 +---
228 +
229 +## Mock 数据字段对照表
230 +
231 +### 周热门资料
232 +
233 +| 字段 | 类型 | 说明 | 示例 |
234 +|------|------|------|------|
235 +| `meta_id` | integer | 文件ID | 1, 2, 3... |
236 +| `name` | string | 文件名称 | "财富管理基础知识指南 PDF文档" |
237 +| `src` | string | 文件URL | "https://cdn.example.com/files/1.pdf" |
238 +| `size` | string | 文件大小 | "2.5MB" |
239 +| `read_people_count` | integer | 学习人数 | 1234 |
240 +| `read_people_percent` | number | 学习百分比 | 75 |
241 +| `is_favorite` | string | 收藏状态 | "1" 或 "0" |
242 +| `extension` | string | 文件扩展名 | "pdf" |
243 +
244 +### 资料列表
245 +
246 +| 字段 | 类型 | 说明 | 示例 |
247 +|------|------|------|------|
248 +| `id` | integer | 资料ID | 1, 2, 3... |
249 +| `name` | string | 资料名称 | "2024年保险行业发展趋势报告" |
250 +| `title` | string | 资料标题 | "2024年保险行业发展趋势报告" |
251 +| `fileName` | string | 文件名 | "报告.pdf" |
252 +| `size` | string | 文件大小 | "2.5MB" |
253 +| `extension` | string | 文件扩展名 | "pdf" |
254 +| `collected` | boolean | 是否已收藏 | true/false |
255 +| `src` | string | 文件URL | "https://cdn.example.com/materials/1.pdf" |
256 +| `downloadUrl` | string | 下载URL | "https://cdn.example.com/materials/1.pdf" |
257 +
258 +### 产品列表
259 +
260 +| 字段 | 类型 | 说明 | 示例 |
261 +|------|------|------|------|
262 +| `id` | integer | 产品ID | 1, 2, 3... |
263 +| `product_name` | string | 产品名称 | "百万年金保险计划" |
264 +| `name` | string | 产品名称(别名) | "百万年金保险计划" |
265 +| `cover_image` | string | 封面图URL | "https://cdn.example.com/products/1.jpg" |
266 +| `recommend` | string | 推荐标识 | "hot" 或 "" |
267 +| `tags` | Array | 标签数组 | [{id: 1, name: "热销", bg_color: "#FEE2E2", text_color: "#DC2626"}] |
268 +| `description` | string | 产品描述 | "这是一款优质的保险产品..." |
269 +| `premium` | number | 保费 | 5000 |
270 +| `category_id` | integer | 分类ID | 1 |
271 +
272 +### 搜索结果
273 +
274 +| 字段 | 类型 | 说明 |
275 +|------|------|------|
276 +| `products` | Array | 产品列表(同产品列表字段) |
277 +| `files` | Array | 资料列表(同资料列表字段) |
278 +
279 +### 消息列表
280 +
281 +| 字段 | 类型 | 说明 | 示例 |
282 +|------|------|------|------|
283 +| `id` | integer | 消息ID | 1, 2, 3... |
284 +| `title` | string | 消息标题 | "关于2024年新产品上线通知" |
285 +| `intro` | string | 消息简介 | "这是一条关于..." |
286 +| `content` | string | 消息内容 | "这是一条关于..." |
287 +| `create_time` | string | 创建时间 | "2024-01-15" |
288 +| `is_read` | integer | 是否已读 | 0 或 1 |
289 +| `type` | string | 消息类型 | "notice" 或 "system" |
290 +
291 +---
292 +
293 +## 常见问题
294 +
295 +### Q: Mock 数据不生效?
296 +
297 +**检查**:
298 +1. 确认对应页面的 `USE_MOCK_DATA = true`
299 +2. 确认已正确导入 mock 函数
300 +3. 查看 console 是否有 `[Mock]` 日志输出
301 +4. 检查网络请求是否调用了 mock 函数
302 +
303 +### Q: 数据格式不对?
304 +
305 +**检查**:
306 +1. 查看 `src/utils/mockData.js` 中的字段定义
307 +2. 对照页面代码中的字段映射逻辑
308 +3. 在 browser console 查看返回的数据结构
309 +
310 +### Q: 想修改数据量?
311 +
312 +**修改**:
313 +- 修改页面的 `pageSize``limit` 变量
314 +- Mock 数据会自动适应请求的数量
315 +
316 +### Q: 想增加总页数?
317 +
318 +**修改** `src/utils/mockData.js`:
319 +```javascript
320 +// 修改 totalPages 值
321 +const totalPages = 10 // 从 5 改为 10
322 +```
323 +
324 +### Q: 搜索功能不工作?
325 +
326 +**检查**:
327 +1. 确认输入了关键词
328 +2. 确认 mock 数据中有包含该关键词的数据
329 +3. 查看 console 日志确认请求参数
330 +
331 +---
332 +
333 +## 验收清单
334 +
335 +测试完成后,请确认:
336 +
337 +- [ ] 周热门资料页滚动加载正常
338 +- [ ] 资料列表页滚动加载正常
339 +- [ ] 资料列表页分类切换正常
340 +- [ ] 资料列表页搜索功能正常
341 +- [ ] 产品中心页滚动加载正常
342 +- [ ] 产品中心页分类切换正常
343 +- [ ] 产品中心页搜索功能正常
344 +- [ ] 搜索页关键词搜索正常
345 +- [ ] 搜索页产品/资料切换正常
346 +- [ ] 搜索页滚动加载正常
347 +- [ ] 消息列表页滚动加载正常
348 +- [ ] 所有页面 Console 日志正确输出
349 +- [ ] 所有页面"没有更多了"正常显示
350 +- [ ] 数据格式正确,无报错
351 +
352 +---
353 +
354 +## 下一步
355 +
356 +1. **测试**: 逐个测试所有页面的滚动加载功能
357 +2. **验证**: 确认数据格式和功能正常
358 +3. **调试**: 如有问题,查看 Console 日志定位
359 +4. **切换**: 测试完成后,切换回真实 API
360 +5. **提交**: 将修改提交到代码仓库
361 +
362 +---
363 +
364 +**最后更新**: 2026-02-08
365 +**维护者**: Claude Code
1 +# Mock 数据测试指南
2 +
3 +## 概述
4 +
5 +已为以下 5 个页面创建完整的 Mock 数据支持:
6 +
7 +| 页面 | API | Mock 函数 | 总页数 | 每页数量 |
8 +|------|-----|----------|--------|---------|
9 +| 周热门资料 | `weekHotAPI` | `mockWeekHotAPI` | 5 页 | 20 条 |
10 +| 资料列表 | `fileListAPI` | `mockFileListAPI` | 8 页 | 20 条 |
11 +| 产品中心 | `listAPI` | `mockProductListAPI` | 10 页 | 10 条 |
12 +| 搜索页 | `searchAPI` | `mockSearchAPI` | 5 页 | 20 条 |
13 +| 消息列表 | `myListAPI` | `mockMessageListAPI` | 8 页 | 10 条 |
14 +
15 +## 快速启用 Mock 数据
16 +
17 +### 方法 1: 修改页面代码开关
18 +
19 +在每个页面的 `<script setup>` 部分添加/修改:
20 +
21 +```javascript
22 +// ⚠️ MOCK 数据开关
23 +const USE_MOCK_DATA = true // ✅ 使用 Mock 数据
24 +// const USE_MOCK_DATA = false // ❌ 使用真实 API
25 +```
26 +
27 +### 方法 2: 在 API 调用处添加条件判断
28 +
29 +```javascript
30 +// 导入 mock 函数
31 +import { mockWeekHotAPI, mockFileListAPI, mockProductListAPI, mockSearchAPI, mockMessageListAPI } from '@/utils/mockData'
32 +
33 +// 在 API 调用处
34 +const res = USE_MOCK_DATA
35 + ? await mockWeekHotAPI(params)
36 + : await weekHotAPI(params)
37 +```
38 +
39 +## 各页面 Mock 数据详情
40 +
41 +### 1. 周热门资料页 (`week-hot-material/index.vue`)
42 +
43 +**文件位置**: `src/pages/week-hot-material/index.vue:76`
44 +
45 +**Mock 数据特性**:
46 +- ✅ 20 种资料名称模板(财富管理、保险、投资等)
47 +- ✅ 10 种文件类型(PDF、Word、Excel、PPT等)
48 +- ✅ 随机学习人数(100-5000人)
49 +- ✅ 随机学习百分比(0-100%)
50 +- ✅ 30%概率已收藏
51 +
52 +**已启用**: ✅ 已添加 `USE_MOCK_DATA` 开关
53 +
54 +---
55 +
56 +### 2. 资料列表页 (`material-list/index.vue`)
57 +
58 +**文件位置**: 需要添加到 `src/pages/material-list/index.vue`
59 +
60 +**Mock 数据特性**:
61 +- ✅ 20 种资料名称模板
62 +- ✅ 支持分类过滤(`cid`)
63 +- ✅ 支持关键词搜索(`keyword`)
64 +- ✅ 包含分类信息(`cate`)
65 +
66 +**需要添加**:
67 +```javascript
68 +// 导入
69 +import { mockFileListAPI } from '@/utils/mockData'
70 +
71 +// 添加开关(在 script setup 顶部)
72 +const USE_MOCK_DATA = true
73 +
74 +// 修改 fetchFileList 函数(约第 176 行)
75 +const res = USE_MOCK_DATA
76 + ? await mockFileListAPI(params)
77 + : await fileListAPI(params)
78 +```
79 +
80 +---
81 +
82 +### 3. 产品中心页 (`product-center/index.vue`)
83 +
84 +**文件位置**: 需要添加到 `src/pages/product-center/index.vue`
85 +
86 +**Mock 数据特性**:
87 +- ✅ 20 种产品名称(人寿险、健康险、意外险等)
88 +- ✅ 4 种产品分类(人寿保险、健康保险、意外保险、财产保险)
89 +- ✅ 4 种标签(热销、新品、推荐、限时)
90 +- ✅ 支持分类过滤(`cid`)
91 +- ✅ 支持关键词搜索(`keyword`)
92 +
93 +**需要添加**:
94 +```javascript
95 +// 导入
96 +import { mockProductListAPI } from '@/utils/mockData'
97 +
98 +// 添加开关(在 script setup 顶部)
99 +const USE_MOCK_DATA = true
100 +
101 +// 修改 fetchProducts 函数(约第 196 行)
102 +const res = USE_MOCK_DATA
103 + ? await mockProductListAPI(params)
104 + : await listAPI(params)
105 +```
106 +
107 +---
108 +
109 +### 4. 搜索页 (`search/index.vue`)
110 +
111 +**文件位置**: 需要添加到 `src/pages/search/index.vue`
112 +
113 +**Mock 数据特性**:
114 +- ✅ 同时支持产品和资料搜索
115 +- ✅ 根据关键词过滤数据
116 +- ✅ 返回匹配的产品和资料列表
117 +
118 +**需要添加**:
119 +```javascript
120 +// 导入
121 +import { mockSearchAPI } from '@/utils/mockData'
122 +
123 +// 添加开关(在 script setup 顶部)
124 +const USE_MOCK_DATA = true
125 +
126 +// 修改 fetchSearchResults 函数
127 +const res = USE_MOCK_DATA
128 + ? await mockSearchAPI(params)
129 + : await searchAPI(params)
130 +```
131 +
132 +---
133 +
134 +### 5. 消息列表页 (`message/index.vue`)
135 +
136 +**文件位置**: 需要添加到 `src/pages/message/index.vue`
137 +
138 +**Mock 数据特性**:
139 +- ✅ 15 种消息标题模板
140 +- ✅ 随机创建时间(最近30天内)
141 +- ✅ 50%概率已读
142 +- ✅ 两种消息类型(通知、系统)
143 +
144 +**需要添加**:
145 +```javascript
146 +// 导入
147 +import { mockMessageListAPI } from '@/utils/mockData'
148 +
149 +// 添加开关(在 script setup 顶部)
150 +const USE_MOCK_DATA = true
151 +
152 +// 修改 fetchMessageList 函数(约第 67 行)
153 +const res = USE_MOCK_DATA
154 + ? await mockMessageListAPI(params)
155 + : await myListAPI(params)
156 +```
157 +
158 +---
159 +
160 +## 统一 Mock API 调用器
161 +
162 +除了单独导入各个 mock 函数,也可以使用统一的调用器:
163 +
164 +```javascript
165 +import { mockAPI } from '@/utils/mockData'
166 +
167 +// 使用示例
168 +const res = await mockAPI('weekHotAPI', params)
169 +const res = await mockAPI('fileListAPI', params)
170 +const res = await mockAPI('listAPI', params)
171 +const res = await mockAPI('searchAPI', params)
172 +const res = await mockAPI('myListAPI', params)
173 +```
174 +
175 +## 测试流程
176 +
177 +### 1. 确认 Mock 数据开关已开启
178 +
179 +检查对应页面的 `USE_MOCK_DATA = true`
180 +
181 +### 2. 启动开发服务器
182 +
183 +```bash
184 +pnpm dev:weapp
185 +```
186 +
187 +### 3. 测试分页加载
188 +
189 +1. **首次加载**: 查看第 1 页数据是否正常显示
190 +2. **滚动加载**: 向下滚动到底部
191 +3. **验证加载**:
192 + - ✅ 显示"加载中..."
193 + - ✅ Console 输出: `[Mock] xxxAPI - 第X页,共Y条`
194 + - ✅ 新数据追加到列表
195 +4. **测试数据耗尽**: 继续滚动直到显示"没有更多了"
196 +
197 +### 4. 查看 Console 日志
198 +
199 +正常的日志输出:
200 +```
201 +[Mock] weekHotAPI - 第0页,共20条
202 +[Mock] weekHotAPI - 第1页,共20条
203 +[Mock] weekHotAPI - 第2页,共20条
204 +...
205 +```
206 +
207 +### 5. 测试搜索/筛选功能(如适用)
208 +
209 +- **资料列表**: 切换分类、输入关键词
210 +- **产品中心**: 切换分类、输入关键词
211 +- **搜索页**: 输入关键词、切换产品/资料 Tab
212 +
213 +## Mock 数据字段说明
214 +
215 +### 周热门资料 (`weekHotAPI`)
216 +
217 +```javascript
218 +{
219 + meta_id: integer, // 文件ID
220 + name: string, // 文件名称
221 + src: string, // 文件URL
222 + size: string, // 文件大小(如 "2.5MB")
223 + read_people_count: integer, // 学习人数
224 + read_people_percent: number, // 学习百分比(0-100)
225 + is_favorite: string // 收藏状态('1' 或 '0')
226 + extension: string // 文件扩展名(如 'pdf')
227 +}
228 +```
229 +
230 +### 资料列表 (`fileListAPI`)
231 +
232 +```javascript
233 +{
234 + id: integer,
235 + meta_id: integer,
236 + name: string, // 资料名称
237 + title: string, // 资料标题
238 + fileName: string, // 文件名
239 + desc: string, // 描述
240 + size: string, // 文件大小
241 + extension: string, // 文件扩展名
242 + collected: boolean, // 是否已收藏
243 + src: string, // 文件URL
244 + downloadUrl: string, // 下载URL
245 + post_date: string // 发布时间
246 +}
247 +```
248 +
249 +### 产品列表 (`listAPI`)
250 +
251 +```javascript
252 +{
253 + id: integer,
254 + product_name: string, // 产品名称
255 + name: string, // 产品名称(别名)
256 + cover_image: string, // 封面图URL
257 + recommend: string, // 推荐标识('hot' 或 '')
258 + tags: Array<{ // 标签数组
259 + id: integer,
260 + name: string, // 标签名称
261 + bg_color: string, // 背景色
262 + text_color: string // 文字颜色
263 + }>,
264 + description: string, // 描述
265 + premium: number, // 保费
266 + category_id: integer // 分类ID
267 +}
268 +```
269 +
270 +### 搜索 (`searchAPI`)
271 +
272 +```javascript
273 +{
274 + code: 1,
275 + msg: 'success',
276 + data: {
277 + products: Array, // 产品列表(同产品列表字段)
278 + files: Array // 资料列表(同资料列表字段)
279 + }
280 +}
281 +```
282 +
283 +### 消息列表 (`myListAPI`)
284 +
285 +```javascript
286 +{
287 + id: integer,
288 + title: string, // 消息标题
289 + intro: string, // 简介
290 + content: string, // 内容
291 + create_time: string, // 创建时间(YYYY-MM-DD)
292 + is_read: integer, // 是否已读(0 或 1)
293 + type: string // 类型('notice' 或 'system')
294 +}
295 +```
296 +
297 +## 切换回真实 API
298 +
299 +测试完成后,记得修改开关:
300 +
301 +```javascript
302 +const USE_MOCK_DATA = false // 使用真实 API
303 +```
304 +
305 +## 常见问题
306 +
307 +### Q1: Mock 数据不生效?
308 +
309 +**检查**:
310 +1. 确认 `USE_MOCK_DATA = true`
311 +2. 确认已正确导入 mock 函数
312 +3. 查看 console 是否有 `[Mock]` 日志
313 +4. 确认 API 调用处使用了条件判断
314 +
315 +### Q2: 数据格式不对?
316 +
317 +**检查**:
318 +1. 查看 mock 数据字段是否与真实 API 一致
319 +2. 在浏览器 console 查看返回的数据结构
320 +3. 对照页面代码中的字段映射逻辑
321 +
322 +### Q3: 想修改每页数量?
323 +
324 +**修改**:
325 +- 在页面代码中修改 `pageSize``limit` 变量
326 +- Mock 数据会自动适应请求的数量
327 +
328 +### Q4: 想增加更多页数?
329 +
330 +**修改** `src/utils/mockData.js`:
331 +```javascript
332 +// 修改 totalPages 值
333 +const totalPages = 10 // 从 5 改为 10
334 +```
335 +
336 +## 完成清单
337 +
338 +使用本指南后,请确认:
339 +
340 +- [ ] 周热门资料页已添加 Mock 数据 ✅
341 +- [ ] 资料列表页已添加 Mock 数据
342 +- [ ] 产品中心页已添加 Mock 数据
343 +- [ ] 搜索页已添加 Mock 数据
344 +- [ ] 消息列表页已添加 Mock 数据
345 +- [ ] 所有页面的滚动加载功能正常
346 +- [ ] Console 日志正确输出
347 +- [ ] 测试完成后已切换回真实 API
348 +
349 +---
350 +
351 +**最后更新**: 2026-02-08
352 +**维护者**: Claude Code
1 +# 周热门资料滚动加载测试指南
2 +
3 +## Mock 数据说明
4 +
5 +已创建 `src/utils/mockData.js` 文件,用于测试滚动加载更多功能。
6 +
7 +### Mock 数据特性
8 +
9 +-**总共 5 页数据**,每页 20 条,共 100 条资料
10 +-**随机生成**的学习人数(100-5000人)
11 +-**随机生成**的学习百分比(0-100%)
12 +-**随机生成**的文件大小(0.5MB-10MB)
13 +-**随机生成**的收藏状态(30%概率已收藏)
14 +-**10种文件类型**: PDF、Word、Excel、PPT、TXT、图片等
15 +-**20种资料名称模板**: 涵盖财富管理、保险、投资等主题
16 +
17 +### 模拟网络延迟
18 +
19 +- 每次请求延迟 **300-800ms**,模拟真实网络环境
20 +
21 +## 如何使用 Mock 数据
22 +
23 +### 方法 1: 修改代码开关(推荐)
24 +
25 +`src/pages/week-hot-material/index.vue` 第 76 行:
26 +
27 +```javascript
28 +// ⚠️ MOCK 数据开关 - 设置为 true 使用 mock 数据,false 使用真实 API
29 +const USE_MOCK_DATA = true // ✅ 使用 Mock 数据
30 +// const USE_MOCK_DATA = false // ❌ 使用真实 API
31 +```
32 +
33 +### 方法 2: 直接调用 Mock API
34 +
35 +```javascript
36 +import { mockWeekHotAPI } from '@/utils/mockData'
37 +
38 +// 调用 Mock API
39 +const res = await mockWeekHotAPI({ page: 0, limit: 20 })
40 +```
41 +
42 +## 测试步骤
43 +
44 +### 1. 启动开发服务器
45 +
46 +```bash
47 +pnpm dev:weapp
48 +```
49 +
50 +### 2. 导航到周热门资料页
51 +
52 +在微信开发者工具中,点击导航到"周热门资料"页面。
53 +
54 +### 3. 测试首次加载
55 +
56 +- ✅ 查看是否加载了第 1 页数据(20条)
57 +- ✅ 查看 console 日志:`[Mock] 生成第0页数据,共20条`
58 +- ✅ 验证列表正常显示
59 +
60 +### 4. 测试滚动加载更多
61 +
62 +- ✅ 向下滚动列表到底部
63 +- ✅ 等待 300-800ms(模拟网络延迟)
64 +- ✅ 查看是否显示"加载中..."状态
65 +- ✅ 查看是否加载了第 2 页数据
66 +- ✅ 查看 console 日志:`[Mock] 生成第1页数据,共20条`
67 +
68 +### 5. 测试多页加载
69 +
70 +继续滚动,依次测试:
71 +- 第 3 页: `[Mock] 生成第2页数据,共20条`
72 +- 第 4 页: `[Mock] 生成第3页数据,共20条`
73 +- 第 5 页: `[Mock] 生成第4页数据,共20条`
74 +
75 +### 6. 测试数据耗尽
76 +
77 +- ✅ 滚动到底部后,应该显示"没有更多了"
78 +- ✅ console 日志:`[Mock] 生成第5页数据,共0条` (空数据)
79 +-`hasMore` 应该变为 `false`
80 +- ✅ 继续滚动不会触发加载
81 +
82 +### 7. 测试收藏功能
83 +
84 +- ✅ 点击任意资料的收藏按钮
85 +- ✅ 验证收藏状态正确更新
86 +- ✅ 查看 console 日志:`[Week Hot] 收藏状态改变: xxx true/false`
87 +
88 +## 验证要点
89 +
90 +### 数据完整性
91 +
92 +- ✅ 每页数据都是 20 条(最后一页除外)
93 +- ✅ 每条数据包含所有必需字段:
94 + - `meta_id`: 文件ID
95 + - `name`: 文件名称
96 + - `src`: 文件URL
97 + - `size`: 文件大小
98 + - `read_people_count`: 学习人数
99 + - `read_people_percent`: 学习百分比
100 + - `is_favorite`: 收藏状态
101 + - `extension`: 文件扩展名
102 +
103 +### 状态管理
104 +
105 +-`loading` 状态: 首次加载时显示
106 +-`loadingMore` 状态: 加载更多时显示
107 +-`hasMore` 状态: 数据耗尽时变为 false
108 +-`currentPage` 状态: 每次加载后正确递增
109 +
110 +### 用户体验
111 +
112 +- ✅ 加载状态友好提示
113 +- ✅ 空状态提示(第1页无数据时)
114 +- ✅ 没有更多数据提示
115 +- ✅ 滚动流畅,无卡顿
116 +
117 +## Console 日志示例
118 +
119 +正常加载流程的日志:
120 +
121 +```
122 +[Week Hot] 页面参数: {}
123 +[Week Hot] 请求参数: {page: 0, limit: 20}
124 +[Week Hot] 使用 Mock 数据: true
125 +[Mock] 生成第0页数据,共20条
126 +[Week Hot] 数据: {list: Array(20)}
127 +[Week Hot] 触底加载更多
128 +[Mock] 生成第1页数据,共20条
129 +[Week Hot] 触底加载更多
130 +[Mock] 生成第2页数据,共20条
131 +...
132 +```
133 +
134 +## 切换回真实 API
135 +
136 +测试完成后,记得切换回真实 API:
137 +
138 +```javascript
139 +// ⚠️ MOCK 数据开关 - 设置为 true 使用 mock 数据,false 使用真实 API
140 +const USE_MOCK_DATA = false // ✅ 使用真实 API
141 +```
142 +
143 +## 常见问题
144 +
145 +### Q1: Mock 数据不生效?
146 +
147 +**检查**:
148 +- 确认 `USE_MOCK_DATA = true`
149 +- 确认已正确导入 `mockWeekHotAPI`
150 +- 查看 console 是否有 `[Week Hot] 使用 Mock 数据: true`
151 +
152 +### Q2: 滚动不触发加载?
153 +
154 +**检查**:
155 +- 确认已滚动到列表最底部
156 +- 查看 `hasMore` 是否为 `true`
157 +- 查看 `loadingMore` 是否为 `false`
158 +- 确认防抖定时器(300ms)已过
159 +
160 +### Q3: 数据重复?
161 +
162 +**检查**:
163 +- 确认 `isLoadMore` 参数正确传递
164 +- 确认代码中使用的是追加逻辑:
165 + ```javascript
166 + if (isLoadMore) {
167 + currentList.value = [...currentList.value, ...listData]
168 + }
169 + ```
170 +
171 +### Q4: 想修改每页数量?
172 +
173 +**修改** `src/pages/week-hot-material/index.vue` 第 72 行:
174 +
175 +```javascript
176 +const pageSize = 20 // 修改为你想要的数量,如 10、30 等
177 +```
178 +
179 +## 扩展 Mock 数据
180 +
181 +如需修改 Mock 数据生成逻辑,编辑 `src/utils/mockData.js`:
182 +
183 +### 修改总页数
184 +
185 +```javascript
186 +const data = generatePageData(page, limit, 10) // 总共10页数据
187 +```
188 +
189 +### 添加更多资料名称模板
190 +
191 +```javascript
192 +const MATERIAL_TEMPLATES = [
193 + // ... 现有模板
194 + '你的新资料名称',
195 + '另一个新资料名称'
196 +]
197 +```
198 +
199 +### 修改文件大小范围
200 +
201 +```javascript
202 +function generateRandomSize() {
203 + const sizeInMB = (Math.random() * 20 + 1).toFixed(1) // 1MB-21MB
204 + return `${sizeInMB}MB`
205 +}
206 +```
207 +
208 +---
209 +
210 +**最后更新**: 2026-02-08
211 +**维护者**: Claude Code
...@@ -123,9 +123,13 @@ import { useListItemClick, ListType } from '@/composables/useListItemClick' ...@@ -123,9 +123,13 @@ import { useListItemClick, ListType } from '@/composables/useListItemClick'
123 import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons' 123 import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons'
124 import { debounce } from '@/utils/debounce' 124 import { debounce } from '@/utils/debounce'
125 import { fileListAPI } from '@/api/file' 125 import { fileListAPI } from '@/api/file'
126 +import { mockFileListAPI } from '@/utils/mockData'
126 import { useCollectOperation } from '@/composables/useCollectOperation' 127 import { useCollectOperation } from '@/composables/useCollectOperation'
127 import Taro from '@tarojs/taro' 128 import Taro from '@tarojs/taro'
128 129
130 +// ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API
131 +const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
132 +
129 const searchValue = ref('') 133 const searchValue = ref('')
130 const activeTabId = ref('all') // 默认选中"全部" 134 const activeTabId = ref('all') // 默认选中"全部"
131 const listVisible = ref(true) 135 const listVisible = ref(true)
...@@ -281,9 +285,12 @@ const fetchMaterialList = async (params = {}, isLoadMore = false) => { ...@@ -281,9 +285,12 @@ const fetchMaterialList = async (params = {}, isLoadMore = false) => {
281 } 285 }
282 286
283 console.log('[Material List] 请求参数:', params) 287 console.log('[Material List] 请求参数:', params)
288 + console.log('[Material List] 使用 Mock 数据:', USE_MOCK_DATA)
284 289
285 - // 调用接口 290 + // 根据开关选择使用真实 API 或 Mock 数据
286 - const res = await fileListAPI(params) 291 + const res = USE_MOCK_DATA
292 + ? await mockFileListAPI(params)
293 + : await fileListAPI(params)
287 294
288 if (res.code === 1 && res.data) { 295 if (res.code === 1 && res.data) {
289 // 如果是初始请求(没有 child_id),保存完整的分类信息 296 // 如果是初始请求(没有 child_id),保存完整的分类信息
...@@ -301,10 +308,18 @@ const fetchMaterialList = async (params = {}, isLoadMore = false) => { ...@@ -301,10 +308,18 @@ const fetchMaterialList = async (params = {}, isLoadMore = false) => {
301 // 加载更多:追加数据 308 // 加载更多:追加数据
302 allList.value = [...allList.value, ...allListData] 309 allList.value = [...allList.value, ...allListData]
303 categoryListCache.value.set('all', allList.value) 310 categoryListCache.value.set('all', allList.value)
311 + // ✅ 同步更新 currentList(如果当前显示的是"全部")
312 + if (activeTabId.value === 'all') {
313 + currentList.value = allList.value
314 + }
304 } else { 315 } else {
305 // 首次加载:替换数据 316 // 首次加载:替换数据
306 allList.value = allListData 317 allList.value = allListData
307 categoryListCache.value.set('all', allListData) 318 categoryListCache.value.set('all', allListData)
319 + // ✅ 同步更新 currentList(如果当前显示的是"全部")
320 + if (activeTabId.value === 'all') {
321 + currentList.value = allListData
322 + }
308 } 323 }
309 324
310 // 判断是否还有更多数据 325 // 判断是否还有更多数据
...@@ -328,7 +343,11 @@ const fetchMaterialList = async (params = {}, isLoadMore = false) => { ...@@ -328,7 +343,11 @@ const fetchMaterialList = async (params = {}, isLoadMore = false) => {
328 const existingData = categoryListCache.value.get(cacheKey) || [] 343 const existingData = categoryListCache.value.get(cacheKey) || []
329 const newData = [...existingData, ...listData] 344 const newData = [...existingData, ...listData]
330 categoryListCache.value.set(cacheKey, newData) 345 categoryListCache.value.set(cacheKey, newData)
331 - currentList.value = newData 346 + // ✅ 同步更新 currentList(如果当前显示的是该缓存)
347 + const currentCacheKey = params.child_id || params.keyword || 'all'
348 + if (activeTabId.value === currentCacheKey) {
349 + currentList.value = newData
350 + }
332 } else { 351 } else {
333 // 首次加载:替换数据 352 // 首次加载:替换数据
334 categoryListCache.value.set(cacheKey, listData) 353 categoryListCache.value.set(cacheKey, listData)
...@@ -572,7 +591,12 @@ const onSearch = async () => { ...@@ -572,7 +591,12 @@ const onSearch = async () => {
572 // 调用接口搜索 591 // 调用接口搜索
573 try { 592 try {
574 loading.value = true 593 loading.value = true
575 - const res = await fileListAPI(params) 594 + console.log('[Material List] 搜索使用 Mock 数据:', USE_MOCK_DATA)
595 +
596 + // 根据开关选择使用真实 API 或 Mock 数据
597 + const res = USE_MOCK_DATA
598 + ? await mockFileListAPI(params)
599 + : await fileListAPI(params)
576 600
577 if (res.code === 1 && res.data) { 601 if (res.code === 1 && res.data) {
578 if (res.data.list?.length) { 602 if (res.data.list?.length) {
......
...@@ -51,6 +51,10 @@ import { useLoad, usePullDownRefresh, useReachBottom, stopPullDownRefresh } from ...@@ -51,6 +51,10 @@ import { useLoad, usePullDownRefresh, useReachBottom, stopPullDownRefresh } from
51 import { useGo } from '@/hooks/useGo' 51 import { useGo } from '@/hooks/useGo'
52 import NavHeader from '@/components/NavHeader.vue' 52 import NavHeader from '@/components/NavHeader.vue'
53 import { myListAPI } from '@/api/news' 53 import { myListAPI } from '@/api/news'
54 +import { mockMessageListAPI } from '@/utils/mockData'
55 +
56 +// ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API
57 +const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
54 58
55 const go = useGo() 59 const go = useGo()
56 60
...@@ -77,10 +81,18 @@ const fetchMessageList = async (refresh = false) => { ...@@ -77,10 +81,18 @@ const fetchMessageList = async (refresh = false) => {
77 loading.value = true 81 loading.value = true
78 82
79 try { 83 try {
80 - const res = await myListAPI({ 84 + console.log('[Message] 使用 Mock 数据:', USE_MOCK_DATA)
81 - page: page.value, 85 +
82 - limit: limit.value 86 + // 根据开关选择使用真实 API 或 Mock 数据
83 - }) 87 + const res = USE_MOCK_DATA
88 + ? await mockMessageListAPI({
89 + page: page.value,
90 + limit: limit.value
91 + })
92 + : await myListAPI({
93 + page: page.value,
94 + limit: limit.value
95 + })
84 96
85 if (res.code === 1) { 97 if (res.code === 1) {
86 const list = res.data?.list || [] 98 const list = res.data?.list || []
......
...@@ -153,6 +153,10 @@ import SearchBar from '@/components/SearchBar.vue' ...@@ -153,6 +153,10 @@ import SearchBar from '@/components/SearchBar.vue'
153 import PlanFormContainer from '@/components/PlanFormContainer.vue' 153 import PlanFormContainer from '@/components/PlanFormContainer.vue'
154 import { useListItemClick, ListType } from '@/composables/useListItemClick' 154 import { useListItemClick, ListType } from '@/composables/useListItemClick'
155 import { listAPI } from '@/api/get_product' 155 import { listAPI } from '@/api/get_product'
156 +import { mockProductListAPI } from '@/utils/mockData'
157 +
158 +// ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API
159 +const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
156 160
157 const activeTabId = ref('') 161 const activeTabId = ref('')
158 162
...@@ -214,7 +218,12 @@ const fetchProducts = async (isLoadMore = false) => { ...@@ -214,7 +218,12 @@ const fetchProducts = async (isLoadMore = false) => {
214 params.keyword = searchValue.value 218 params.keyword = searchValue.value
215 } 219 }
216 220
217 - const res = await listAPI(params) 221 + console.log('[Product Center] 使用 Mock 数据:', USE_MOCK_DATA)
222 +
223 + // 根据开关选择使用真实 API 或 Mock 数据
224 + const res = USE_MOCK_DATA
225 + ? await mockProductListAPI(params)
226 + : await listAPI(params)
218 227
219 if (res.code === 1 && res.data) { 228 if (res.code === 1 && res.data) {
220 // 更新分类列表(首次加载时) 229 // 更新分类列表(首次加载时)
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
3 * @Description: 搜索页面 - 支持产品和资料搜索,实时查询API 3 * @Description: 搜索页面 - 支持产品和资料搜索,实时查询API
4 --> 4 -->
5 <template> 5 <template>
6 - <view class="h-screen bg-[#FFF] flex flex-col"> 6 + <view class="bg-[#FFF]">
7 - <!-- 固定顶部:导航栏 + 搜索栏 --> 7 + <!-- 固定顶部:导航栏 + 搜索栏 + Tabs + 结果计数 -->
8 - <view class="bg-[#FFF] z-10"> 8 + <view class="bg-[#FFF] sticky top-0 z-10">
9 <NavHeader title="搜索" /> 9 <NavHeader title="搜索" />
10 10
11 <!-- Search Input --> 11 <!-- Search Input -->
...@@ -20,10 +20,7 @@ ...@@ -20,10 +20,7 @@
20 @clear="clearSearch" 20 @clear="clearSearch"
21 /> 21 />
22 </view> 22 </view>
23 - </view>
24 23
25 - <!-- Tabs + 列表容器 -->
26 - <view class="flex-1 min-h-0 flex flex-col mt-[32rpx] px-[40rpx]">
27 <!-- Tabs Container --> 24 <!-- Tabs Container -->
28 <nut-tabs v-model="activeTab"> 25 <nut-tabs v-model="activeTab">
29 <!-- 自定义标签栏 --> 26 <!-- 自定义标签栏 -->
...@@ -46,81 +43,79 @@ ...@@ -46,81 +43,79 @@
46 </nut-tabs> 43 </nut-tabs>
47 44
48 <!-- Result Count --> 45 <!-- Result Count -->
49 - <view v-if="currentList.length > 0" class="text-[#6B7280] text-[24rpx] mb-[24rpx]"> 46 + <view v-if="currentList.length > 0" class="px-[60rpx] text-[#6B7280] text-[24rpx] pb-[24rpx]">
50 找到 {{ currentTotal }} 个相关结果 47 找到 {{ currentTotal }} 个相关结果
51 </view> 48 </view>
49 + </view>
50 +
51 + <!-- 列表容器 -->
52 + <view class="px-[40rpx] pb-[calc(160rpx+env(safe-area-inset-bottom))]">
52 53
53 - <!-- 可滚动列表区域(支持触底加载更多) --> 54 + <!-- Search Results -->
54 - <scroll-view 55 + <view
55 - class="flex-1 min-h-0 overflow-y-auto pb-[calc(160rpx+env(safe-area-inset-bottom))] box-border" 56 + v-if="currentList.length > 0"
56 - scroll-y 57 + :key="listRenderKey"
57 > 58 >
58 - <!-- Search Results --> 59 + <!-- Product Results -->
59 - <view 60 + <view v-if="activeTab === 'product'" class="flex flex-col gap-[24rpx] pb-[40rpx]">
60 - v-if="currentList.length > 0" 61 + <ProductCard
61 - :key="listRenderKey" 62 + v-for="(item, index) in currentList"
62 - > 63 + :key="index"
63 - <!-- Product Results --> 64 + :product-id="item.id"
64 - <view v-if="activeTab === 'product'" class="flex flex-col gap-[24rpx] pb-[40rpx]"> 65 + :product-name="item.product_name || item.name"
65 - <ProductCard 66 + :tags="item.tags || []"
66 - v-for="(item, index) in currentList" 67 + class="search-result-item"
67 - :key="index" 68 + :style="{ animationDelay: `${index * 30}ms` }"
68 - :product-id="item.id" 69 + @detail="goToProductDetail"
69 - :product-name="item.product_name || item.name" 70 + @plan="openPlanPopup"
70 - :tags="item.tags || []" 71 + />
71 - class="search-result-item" 72 + </view>
72 - :style="{ animationDelay: `${index * 30}ms` }"
73 - @detail="goToProductDetail"
74 - @plan="openPlanPopup"
75 - />
76 - </view>
77 73
78 - <!-- File Results --> 74 + <!-- File Results -->
79 - <view v-else-if="activeTab === 'file'" class="flex flex-col gap-[24rpx] pb-[40rpx]"> 75 + <view v-else-if="activeTab === 'file'" class="flex flex-col gap-[24rpx] pb-[40rpx]">
80 - <MaterialCard 76 + <MaterialCard
81 - v-for="(item, index) in currentList" 77 + v-for="(item, index) in currentList"
82 - :key="index" 78 + :key="index"
83 - :id="item.id" 79 + :id="item.id"
84 - :title="item.title" 80 + :title="item.title"
85 - :file-name="item.fileName" 81 + :file-name="item.fileName"
86 - :file-size="item.fileSize" 82 + :file-size="item.fileSize"
87 - :learners="item.learners" 83 + :learners="item.learners"
88 - :read-people-percent="item.readPeoplePercent" 84 + :read-people-percent="item.readPeoplePercent"
89 - :collected="item.collected" 85 + :collected="item.collected"
90 - :extension="item.extension" 86 + :extension="item.extension"
91 - :download-url="item.downloadUrl" 87 + :download-url="item.downloadUrl"
92 - class="search-result-item" 88 + class="search-result-item"
93 - :style="{ animationDelay: `${index * 30}ms` }" 89 + :style="{ animationDelay: `${index * 30}ms` }"
94 - @collect-changed="handleCollectChanged(item, $event)" 90 + @collect-changed="handleCollectChanged(item, $event)"
95 - /> 91 + />
96 - </view> 92 + </view>
97 93
98 - <!-- 加载更多提示 --> 94 + <!-- 加载更多提示 -->
99 - <view v-if="currentList.length > 0" class="flex items-center justify-center py-[40rpx]"> 95 + <view v-if="currentList.length > 0" class="flex items-center justify-center py-[40rpx]">
100 - <view v-if="loadingMore" class="flex items-center"> 96 + <view v-if="loadingMore" class="flex items-center">
101 - <view class="loading-spinner-small"></view> 97 + <view class="loading-spinner-small"></view>
102 - <text class="ml-[12rpx] text-[#9CA3AF] text-[24rpx]">加载中...</text> 98 + <text class="ml-[12rpx] text-[#9CA3AF] text-[24rpx]">加载中...</text>
103 - </view> 99 + </view>
104 - <view v-else-if="!hasMore" class="text-[#9CA3AF] text-[24rpx]"> 100 + <view v-else-if="!hasMore" class="text-[#9CA3AF] text-[24rpx]">
105 - 没有更多了 101 + 没有更多了
106 - </view>
107 </view> 102 </view>
108 </view> 103 </view>
104 + </view>
109 105
110 - <!-- Empty State (已搜索但无结果) --> 106 + <!-- Empty State (已搜索但无结果) -->
111 - <view v-else-if="hasSearched && currentList.length === 0" class="flex flex-col items-center justify-center py-[40rpx]"> 107 + <view v-else-if="hasSearched && currentList.length === 0" class="flex flex-col items-center justify-center py-[40rpx]">
112 - <nut-empty description="暂无搜索结果" image="empty"> 108 + <nut-empty description="暂无搜索结果" image="empty">
113 - <view class="text-[#9CA3AF] text-[24rpx] mt-[12rpx]">试试其他关键词吧</view> 109 + <view class="text-[#9CA3AF] text-[24rpx] mt-[12rpx]">试试其他关键词吧</view>
114 - </nut-empty> 110 + </nut-empty>
115 - </view> 111 + </view>
116 112
117 - <!-- Initial State (从未搜索过) --> 113 + <!-- Initial State (从未搜索过) -->
118 - <view v-else class="flex flex-col items-center justify-center py-[120rpx]"> 114 + <view v-else class="flex flex-col items-center justify-center py-[120rpx]">
119 - <IconFont name="search" class="text-gray-300 mb-[24rpx]" size="64" /> 115 + <IconFont name="search" class="text-gray-300 mb-[24rpx]" size="64" />
120 - <view class="text-[#6B7280] text-[28rpx]">搜索产品或资料</view> 116 + <view class="text-[#6B7280] text-[28rpx]">搜索产品或资料</view>
121 - <view class="text-[#9CA3AF] text-[24rpx] mt-[12rpx]">输入关键词开始搜索,自动切换分类</view> 117 + <view class="text-[#9CA3AF] text-[24rpx] mt-[12rpx]">输入关键词开始搜索,自动切换分类</view>
122 - </view> 118 + </view>
123 - </scroll-view>
124 </view> 119 </view>
125 120
126 <!-- Plan Form Container --> 121 <!-- Plan Form Container -->
...@@ -146,6 +141,10 @@ import ProductCard from '@/components/ProductCard.vue' ...@@ -146,6 +141,10 @@ import ProductCard from '@/components/ProductCard.vue'
146 import MaterialCard from '@/components/MaterialCard.vue' 141 import MaterialCard from '@/components/MaterialCard.vue'
147 import PlanFormContainer from '@/components/PlanFormContainer.vue' 142 import PlanFormContainer from '@/components/PlanFormContainer.vue'
148 import { searchAPI } from '@/api/search' 143 import { searchAPI } from '@/api/search'
144 +import { mockSearchAPI } from '@/utils/mockData'
145 +
146 +// ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API
147 +const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
149 148
150 // Navigation 149 // Navigation
151 const go = useGo() 150 const go = useGo()
...@@ -226,7 +225,12 @@ const performSearch = async (keyword, type, page = 0, limit = pageSize, isLoadMo ...@@ -226,7 +225,12 @@ const performSearch = async (keyword, type, page = 0, limit = pageSize, isLoadMo
226 const params = { keyword, page, limit } 225 const params = { keyword, page, limit }
227 if (type) params.type = type 226 if (type) params.type = type
228 227
229 - const res = await searchAPI(params) 228 + console.log('[Search] 使用 Mock 数据:', USE_MOCK_DATA)
229 +
230 + // 根据开关选择使用真实 API 或 Mock 数据
231 + const res = USE_MOCK_DATA
232 + ? await mockSearchAPI(params)
233 + : await searchAPI(params)
230 234
231 if (res.code === 1) { 235 if (res.code === 1) {
232 // 映射产品列表 236 // 映射产品列表
...@@ -266,11 +270,7 @@ const performSearch = async (keyword, type, page = 0, limit = pageSize, isLoadMo ...@@ -266,11 +270,7 @@ const performSearch = async (keyword, type, page = 0, limit = pageSize, isLoadMo
266 productsTotal.value = res.data.products.total || 0 270 productsTotal.value = res.data.products.total || 0
267 filesTotal.value = res.data.files.total || 0 271 filesTotal.value = res.data.files.total || 0
268 272
269 - // 判断是否还有更多数据 273 + // ⚠️ 重要:必须先自动选择 tab,然后再计算 hasMore
270 - // 如果返回的数据量少于请求的量,说明没有更多了
271 - const currentListLength = type === 'product' ? newProducts.length : newFiles.length
272 - hasMore.value = currentListLength >= limit
273 -
274 // 如果不传 type,自动选择有数据的 tab(仅首次搜索时) 274 // 如果不传 type,自动选择有数据的 tab(仅首次搜索时)
275 if (!type && !isLoadMore) { 275 if (!type && !isLoadMore) {
276 if (productsTotal.value > 0) { 276 if (productsTotal.value > 0) {
...@@ -281,6 +281,19 @@ const performSearch = async (keyword, type, page = 0, limit = pageSize, isLoadMo ...@@ -281,6 +281,19 @@ const performSearch = async (keyword, type, page = 0, limit = pageSize, isLoadMo
281 // 如果都为 0,默认 product 281 // 如果都为 0,默认 product
282 } 282 }
283 283
284 + // 判断是否还有更多数据
285 + // 使用当前列表长度与总数比较
286 + // 注意:需要根据实际选择的tab来判断
287 + const actualTab = type || activeTab.value
288 + if (actualTab === 'product') {
289 + hasMore.value = products.value.length < productsTotal.value
290 + } else if (actualTab === 'file') {
291 + hasMore.value = files.value.length < filesTotal.value
292 + } else {
293 + // 如果都没有选中,保守设置为false
294 + hasMore.value = false
295 + }
296 +
284 hasSearched.value = true 297 hasSearched.value = true
285 listRenderKey.value += 1 298 listRenderKey.value += 1
286 299
...@@ -535,7 +548,7 @@ const handleCollectChanged = (item, newStatus) => { ...@@ -535,7 +548,7 @@ const handleCollectChanged = (item, newStatus) => {
535 .filter-tabs-wrapper { 548 .filter-tabs-wrapper {
536 display: flex; 549 display: flex;
537 overflow-x: auto; 550 overflow-x: auto;
538 - padding: 24rpx 24rpx; 551 + padding: 24rpx 60rpx;
539 gap: 24rpx; 552 gap: 24rpx;
540 transition: all 0.3s ease; 553 transition: all 0.3s ease;
541 background-color: #FFF; 554 background-color: #FFF;
......
...@@ -6,5 +6,6 @@ export default { ...@@ -6,5 +6,6 @@ export default {
6 navigationBarTitleText: '本周热门资料', 6 navigationBarTitleText: '本周热门资料',
7 enablePullDownRefresh: true, 7 enablePullDownRefresh: true,
8 backgroundColor: '#F9FAFB', 8 backgroundColor: '#F9FAFB',
9 - navigationStyle: 'custom' 9 + navigationStyle: 'custom',
10 + onReachBottomDistance: 50
10 } 11 }
......
...@@ -3,14 +3,14 @@ ...@@ -3,14 +3,14 @@
3 * @Description: 本周热门资料页 - 使用 MaterialCard 组件 3 * @Description: 本周热门资料页 - 使用 MaterialCard 组件
4 --> 4 -->
5 <template> 5 <template>
6 - <view class="h-screen bg-[#F9FAFB] flex flex-col py-[32rpx]"> 6 + <view class="min-h-screen bg-[#F9FAFB] py-[32rpx]">
7 <NavHeader title="本周热门资料" /> 7 <NavHeader title="本周热门资料" />
8 8
9 <!-- 列表容器 --> 9 <!-- 列表容器 -->
10 <view 10 <view
11 v-if="listVisible" 11 v-if="listVisible"
12 :key="listRenderKey" 12 :key="listRenderKey"
13 - class="flex-1 min-h-0 overflow-y-auto px-[32rpx] pb-[calc(160rpx+env(safe-area-inset-bottom))] box-border" 13 + class="px-[32rpx]"
14 > 14 >
15 <!-- 加载状态 --> 15 <!-- 加载状态 -->
16 <view v-if="loading && currentList.length === 0" class="flex items-center justify-center py-[60rpx]"> 16 <view v-if="loading && currentList.length === 0" class="flex items-center justify-center py-[60rpx]">
...@@ -61,6 +61,7 @@ import Taro, { useLoad, useReachBottom } from '@tarojs/taro' ...@@ -61,6 +61,7 @@ import Taro, { useLoad, useReachBottom } from '@tarojs/taro'
61 import NavHeader from '@/components/NavHeader.vue' 61 import NavHeader from '@/components/NavHeader.vue'
62 import MaterialCard from '@/components/MaterialCard.vue' 62 import MaterialCard from '@/components/MaterialCard.vue'
63 import { weekHotAPI } from '@/api/file' 63 import { weekHotAPI } from '@/api/file'
64 +import { mockWeekHotAPI } from '@/utils/mockData'
64 65
65 const listVisible = ref(true) 66 const listVisible = ref(true)
66 const listRenderKey = ref(0) 67 const listRenderKey = ref(0)
...@@ -71,6 +72,9 @@ const currentList = ref([]) ...@@ -71,6 +72,9 @@ const currentList = ref([])
71 const currentPage = ref(0) // 当前页码(从0开始) 72 const currentPage = ref(0) // 当前页码(从0开始)
72 const pageSize = 20 // 每页数量 73 const pageSize = 20 // 每页数量
73 74
75 +// ⚠️ MOCK 数据开关 - 开发环境使用 mock 数据,生产环境使用真实 API
76 +const USE_MOCK_DATA = process.env.NODE_ENV === 'development'
77 +
74 /** 78 /**
75 * 处理收藏状态改变 79 * 处理收藏状态改变
76 * 80 *
...@@ -104,9 +108,12 @@ const fetchWeekHotList = async (params = {}, isLoadMore = false) => { ...@@ -104,9 +108,12 @@ const fetchWeekHotList = async (params = {}, isLoadMore = false) => {
104 } 108 }
105 109
106 console.log('[Week Hot] 请求参数:', params) 110 console.log('[Week Hot] 请求参数:', params)
111 + console.log('[Week Hot] 使用 Mock 数据:', USE_MOCK_DATA)
107 112
108 - // 调用接口 113 + // 根据开关选择使用真实 API 或 Mock 数据
109 - const res = await weekHotAPI(params) 114 + const res = USE_MOCK_DATA
115 + ? await mockWeekHotAPI(params)
116 + : await weekHotAPI(params)
110 117
111 if (res.code === 1 && res.data) { 118 if (res.code === 1 && res.data) {
112 console.log('[Week Hot] 数据:', res.data) 119 console.log('[Week Hot] 数据:', res.data)
......
This diff is collapsed. Click to expand it.