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>
Showing
11 changed files
with
1262 additions
and
94 deletions
| ... | @@ -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 数据环境自动切换模式 | ... | ... |
docs/mock-data-complete-summary.md
0 → 100644
| 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 |
docs/mock-data-setup-guide.md
0 → 100644
| 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 |
docs/scroll-load-test-guide.md
0 → 100644
| 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) | ... | ... |
src/utils/mockData.js
0 → 100644
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment