feat(index): 首页网格导航动态化,支持后台配置
## 主要变更 ### 优化 - 移除硬编码的 CATEGORY_IDS 和 loopNav 配置 - 新增 fetchHomeIcons 函数,从 API 动态获取导航数据 - 实现智能参数解析,自动提取 link 字段中的路由和查询参数 - 支持任意数量的查询参数(如 cid, category_id 等) - API 失败时自动降级到默认配置,保证可用性 ### 导航跳转逻辑优化 - 重构 handleGridNav 函数,使用通用的参数提取逻辑 - 自动添加 title 参数到目标页面 - 移除硬编码的路由判断,提升可维护性 ### 代码质量提升 - 性能优化:使用 shallowRef 存储导航数据 - 错误处理:完善的 try-catch 和降级方案 - 代码注释:添加完整的 JSDoc 注释 ## 影响文件 - src/pages/index/index.vue - docs/CHANGELOG.md ## 技术细节 - link 字段格式:/pages/category-list/index?cid=3129684 - 自动解析路由和查询参数 - 参数值自动 URL 解码 - 灵活支持后台配置,无需发版 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Showing
2 changed files
with
98 additions
and
24 deletions
| ... | @@ -5,6 +5,33 @@ | ... | @@ -5,6 +5,33 @@ |
| 5 | 5 | ||
| 6 | --- | 6 | --- |
| 7 | 7 | ||
| 8 | +## [2026-02-05] - 首页网格导航动态化 | ||
| 9 | + | ||
| 10 | +### 优化 | ||
| 11 | +- **首页网格导航** (`src/pages/index/index.vue`) | ||
| 12 | + - 移除硬编码的 `CATEGORY_IDS` 配置和 `loopNav` 数组 | ||
| 13 | + - 新增 `fetchHomeIcons` 函数,从 API 动态获取导航数据 | ||
| 14 | + - 实现智能参数解析,自动提取 `link` 字段中的路由和查询参数 | ||
| 15 | + - 支持任意数量的查询参数 (如 `cid`, `category_id` 等) | ||
| 16 | + - API 失败时自动降级到默认配置,保证可用性 | ||
| 17 | +- **导航跳转逻辑优化** | ||
| 18 | + - 重构 `handleGridNav` 函数,使用通用的参数提取逻辑 | ||
| 19 | + - 自动添加 `title` 参数到目标页面 | ||
| 20 | + - 移除硬编码的路由判断,提升可维护性 | ||
| 21 | + | ||
| 22 | +### 代码质量提升 | ||
| 23 | +- **性能优化**: 使用 `shallowRef` 存储导航数据,避免深度响应式 | ||
| 24 | +- **错误处理**: 完善的 try-catch 和降级方案 | ||
| 25 | +- **代码注释**: 添加完整的 JSDoc 注释 | ||
| 26 | +- **可维护性**: 支持后台配置,无需修改代码即可调整导航 | ||
| 27 | + | ||
| 28 | +### 影响 | ||
| 29 | +- **灵活性**: 可通过后台接口动态配置首页导航,无需发版 | ||
| 30 | +- **可扩展性**: 支持任意数量的查询参数 | ||
| 31 | +- **稳定性**: API 失败时有降级方案,不影响核心功能 | ||
| 32 | + | ||
| 33 | +--- | ||
| 34 | + | ||
| 8 | ## [2026-02-05] - 代码重构:收藏操作逻辑抽取 | 35 | ## [2026-02-05] - 代码重构:收藏操作逻辑抽取 |
| 9 | 36 | ||
| 10 | ### 重构 | 37 | ### 重构 | ... | ... |
| ... | @@ -191,6 +191,7 @@ import ListItemActions from '@/components/ListItemActions/index.vue'; | ... | @@ -191,6 +191,7 @@ import ListItemActions from '@/components/ListItemActions/index.vue'; |
| 191 | import { listAPI } from '@/api/get_product'; | 191 | import { listAPI } from '@/api/get_product'; |
| 192 | import { weekHotAPI } from '@/api/file'; | 192 | import { weekHotAPI } from '@/api/file'; |
| 193 | import { useCollectOperation } from '@/composables/useCollectOperation'; | 193 | import { useCollectOperation } from '@/composables/useCollectOperation'; |
| 194 | +import { homeIconAPI } from '@/api/home'; | ||
| 194 | 195 | ||
| 195 | // User Store | 196 | // User Store |
| 196 | const userStore = useUserStore(); | 197 | const userStore = useUserStore(); |
| ... | @@ -223,25 +224,55 @@ const handlePlanSubmit = (formData) => { | ... | @@ -223,25 +224,55 @@ const handlePlanSubmit = (formData) => { |
| 223 | }; | 224 | }; |
| 224 | 225 | ||
| 225 | /** | 226 | /** |
| 226 | - * 分类 ID 配置 | 227 | + * 首页网格导航数据 |
| 227 | - * @description 各业务模块对应的分类 ID,需要根据后端实际返回的 ID 配置 | 228 | + * |
| 228 | - * TODO: 将这些 CID 替换为实际的分类 ID | 229 | + * @description 从 API 动态获取,包含图标、名称、路由等信息 |
| 229 | */ | 230 | */ |
| 230 | -const CATEGORY_IDS = { | 231 | +const loopNav = shallowRef([]); |
| 231 | - onboarding: '3129684', // 入职相关分类 ID | ||
| 232 | - signing: '', // 签单相关分类 ID | ||
| 233 | - familyOffice: '', // 家办相关分类 ID | ||
| 234 | - customerService: '' // 客户服务分类 ID | ||
| 235 | -} | ||
| 236 | 232 | ||
| 237 | -const loopNav = shallowRef([ | 233 | +/** |
| 238 | - { id: 'plan', icon: 'order', name: '计划书', route: '/pages/plan/index' }, | 234 | + * 获取首页图标列表 |
| 239 | - { id: 'onboarding', icon: 'my', name: '入职相关', route: '/pages/category-list/index', cid: CATEGORY_IDS.onboarding }, | 235 | + * |
| 240 | - { id: 'signing', icon: 'cart', name: '签单相关', route: '/pages/category-list/index', cid: CATEGORY_IDS.signing }, | 236 | + * @description 从 API 获取首页网格导航数据,并解析 link 字段 |
| 241 | - { id: 'family-office', icon: 'home', name: '家办相关', route: '/pages/category-list/index', cid: CATEGORY_IDS.familyOffice }, | 237 | + */ |
| 242 | - { id: 'knowledge-base', icon: 'category', name: '产品知识库', route: '/pages/knowledge-base/index' }, | 238 | +const fetchHomeIcons = async () => { |
| 243 | - { id: 'customer-service', icon: 'star', name: '客户服务', route: '/pages/category-list/index', cid: CATEGORY_IDS.customerService }, | 239 | + try { |
| 244 | -]); | 240 | + const res = await homeIconAPI(); |
| 241 | + | ||
| 242 | + if (res.code === 1 && res.data) { | ||
| 243 | + // 将 API 数据映射为 loopNav 格式 | ||
| 244 | + loopNav.value = res.data.map(item => { | ||
| 245 | + // 解析 link 字段,格式: "/pages/category-list/index?cid=3129684" | ||
| 246 | + const [route, queryStr] = item.link.split('?'); | ||
| 247 | + const params = {}; | ||
| 248 | + | ||
| 249 | + // 如果有查询参数,解析为对象 | ||
| 250 | + if (queryStr) { | ||
| 251 | + queryStr.split('&').forEach(param => { | ||
| 252 | + const [key, value] = param.split('='); | ||
| 253 | + params[key] = decodeURIComponent(value); | ||
| 254 | + }); | ||
| 255 | + } | ||
| 256 | + | ||
| 257 | + // 返回导航项对象 | ||
| 258 | + return { | ||
| 259 | + id: String(item.id), | ||
| 260 | + icon: item.icon, | ||
| 261 | + name: item.name, | ||
| 262 | + route, | ||
| 263 | + ...params // 展开参数(如 cid) | ||
| 264 | + }; | ||
| 265 | + }); | ||
| 266 | + } | ||
| 267 | + } catch (err) { | ||
| 268 | + console.error('获取首页图标失败:', err); | ||
| 269 | + // 如果 API 调用失败,使用默认配置 | ||
| 270 | + loopNav.value = [ | ||
| 271 | + { id: 'plan', icon: 'order', name: '计划书', route: '/pages/plan/index' }, | ||
| 272 | + { id: 'knowledge-base', icon: 'category', name: '产品知识库', route: '/pages/knowledge-base/index' } | ||
| 273 | + ]; | ||
| 274 | + } | ||
| 275 | +}; | ||
| 245 | 276 | ||
| 246 | /** | 277 | /** |
| 247 | * 热卖产品数据 | 278 | * 热卖产品数据 |
| ... | @@ -337,7 +368,15 @@ const { handleClick: onViewMaterial } = useListItemClick({ | ... | @@ -337,7 +368,15 @@ const { handleClick: onViewMaterial } = useListItemClick({ |
| 337 | // 使用收藏操作 composable | 368 | // 使用收藏操作 composable |
| 338 | const { toggleCollect: toggleMaterialCollect } = useCollectOperation(); | 369 | const { toggleCollect: toggleMaterialCollect } = useCollectOperation(); |
| 339 | 370 | ||
| 340 | -// Handle grid navigation click | 371 | +/** |
| 372 | + * 处理网格导航点击 | ||
| 373 | + * | ||
| 374 | + * @description 根据导航项的路由和参数进行跳转 | ||
| 375 | + * @param {Object} item - 导航项对象 | ||
| 376 | + * @param {string} item.route - 目标路由 | ||
| 377 | + * @param {string} item.name - 导航名称 | ||
| 378 | + * @param {Object} [item.cid] - 可选的分类 ID 参数 | ||
| 379 | + */ | ||
| 341 | const handleGridNav = (item) => { | 380 | const handleGridNav = (item) => { |
| 342 | if (!item.route) { | 381 | if (!item.route) { |
| 343 | Taro.showToast({ | 382 | Taro.showToast({ |
| ... | @@ -348,14 +387,21 @@ const handleGridNav = (item) => { | ... | @@ -348,14 +387,21 @@ const handleGridNav = (item) => { |
| 348 | return; | 387 | return; |
| 349 | } | 388 | } |
| 350 | 389 | ||
| 351 | - // 如果是分类列表页面,需要带参数跳转 | 390 | + // 提取除 route 以外的所有参数(如 cid) |
| 352 | - if (item.route === '/pages/category-list/index') { | 391 | + const params = { ...item }; |
| 392 | + delete params.id; | ||
| 393 | + delete params.icon; | ||
| 394 | + delete params.name; | ||
| 395 | + delete params.route; | ||
| 396 | + | ||
| 397 | + // 如果有参数(如 cid),则带参数跳转 | ||
| 398 | + if (Object.keys(params).length > 0) { | ||
| 353 | go(item.route, { | 399 | go(item.route, { |
| 354 | - cid: item.cid, // 分类 ID | 400 | + ...params, |
| 355 | - title: item.name // 页面标题 | 401 | + title: item.name // 将导航名称作为页面标题 |
| 356 | }); | 402 | }); |
| 357 | } else { | 403 | } else { |
| 358 | - // 其他页面直接跳转 | 404 | + // 无参数,直接跳转 |
| 359 | go(item.route); | 405 | go(item.route); |
| 360 | } | 406 | } |
| 361 | }; | 407 | }; |
| ... | @@ -374,8 +420,9 @@ const openWebView = (url) => { | ... | @@ -374,8 +420,9 @@ const openWebView = (url) => { |
| 374 | }); | 420 | }); |
| 375 | }; | 421 | }; |
| 376 | 422 | ||
| 377 | -// 页面加载时获取热卖产品和热门资料 | 423 | +// 页面加载时获取首页图标、热卖产品和热门资料 |
| 378 | useLoad(() => { | 424 | useLoad(() => { |
| 425 | + fetchHomeIcons(); | ||
| 379 | fetchHotProducts(); | 426 | fetchHotProducts(); |
| 380 | fetchHotMaterials(); | 427 | fetchHotMaterials(); |
| 381 | }); | 428 | }); | ... | ... |
-
Please register or login to post a comment