From 73cf147348681e9e25f0328768383dd5c3aff206 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 28 Feb 2026 00:03:37 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api/category.md | 19 ++++++++++++- docs/api/event.md | 15 ++++++++++ docs/views/Home.md | 68 +++++++++++++++++++++++++++++++++++--------- src/api/category.ts | 11 +++++++ src/api/event.ts | 8 +++--- src/api/request.ts | 2 +- src/views/Home.vue | 26 +++++++++++++---- 7 files changed, 124 insertions(+), 25 deletions(-) diff --git a/docs/api/category.md b/docs/api/category.md index c476868..f121272 100644 --- a/docs/api/category.md +++ b/docs/api/category.md @@ -17,7 +17,24 @@ | 类型 | 说明 | |------|------| | `PmTagMainItem` | 接口返回的 PmTag 结构,含 children | -| `CategoryTreeNode` | 前端使用的树节点,含 id、label、slug、icon、sectionTitle、children | +| `CategoryTreeNode` | 前端使用的树节点,含 id、label、slug、icon、sectionTitle、children、tagIds | + +### CategoryTreeNode.tagIds + +从后端 `PmTagCatalogItem.tagId` 字段提取的标签 ID 列表,用于事件筛选: + +```typescript +interface CategoryTreeNode { + // ... 其他字段 + /** 关联的标签 ID 列表,用于事件筛选 */ + tagIds?: number[] +} +``` + +**提取逻辑**: +- 如果 `tagId` 是数字数组,直接使用(`[1351, 1368]`) +- 如果 `tagId` 是单个数字,包装为数组(`1351` → `[1351]`) +- 如果 `tagId` 是对象或其他类型,忽略 ## 使用方式 diff --git a/docs/api/event.md b/docs/api/event.md index 192a498..36874b5 100644 --- a/docs/api/event.md +++ b/docs/api/event.md @@ -49,3 +49,18 @@ clearEventListCache() 1. **新增筛选参数**:在 `GetPmEventListParams` 中增加字段,并在 `getPmEventPublic` 的 query 中传入 2. **缓存策略**:可改为 sessionStorage 或带 TTL 的缓存 3. **多选项展示**:`mapEventItemToCard` 已支持 multi 类型,可扩展 `EventCardOutcome` 字段 + +## 参数传递方式 + +### tagIds 参数(数组) + +`tagIds` 使用传统数组方式传递,不再是逗号分隔的字符串: + +```typescript +// 正确方式 - 直接传递数组 +const res = await getPmEventPublic({ + page: 1, + pageSize: 10, + tagIds: [1, 2, 3] // 会作为多个同名参数传递:?tagIds=1&tagIds=2&tagIds=3 +}) +``` diff --git a/docs/views/Home.md b/docs/views/Home.md index 7022da5..523e847 100644 --- a/docs/views/Home.md +++ b/docs/views/Home.md @@ -1,27 +1,69 @@ # Home.vue -**路径**:`src/views/Home.vue` -**路由**:`/`,name: `home` +**路径**:`src/views/Home.vue` ## 功能用途 -首页,展示分类 Tab、搜索、事件卡片列表。支持三层分类、下拉刷新、无限滚动、搜索历史,卡片支持单一/多选项展示。 +首页,展示分类导航栏(三层级)、事件卡片列表。支持分类筛选、搜索、下拉刷新、触底加载更多。 ## 核心能力 -- 分类:第一层 Tab、第二层图标、第三层 Tab,从 `getPmTagMain` 获取 -- 搜索:展开浮层、历史记录、`useSearchHistory` -- 列表:`getPmEventPublic` 分页、`mapEventItemToCard` 映射、`MarketCard` 渲染 -- 缓存:`eventListCache` 切换页面时复用,下拉刷新时清空 -- Keep-alive:`Home` 被 include,切换回来时保留状态 +- **分类导航**:三层级分类选择(一级 Tab、二级图标、三级 Tab) +- **事件列表**:卡片式展示,支持下拉刷新、触底加载 +- **搜索**:可按关键词搜索事件 +- **分类筛选**:选中分类后,自动提取所有层级节点的 `tagIds` 进行事件筛选 + +## 数据流 + +``` +PmTagCatalogItem.tagId = [1351] + ↓ mapCatalogToTreeNode +CategoryTreeNode.tagIds = [1351] + ↓ 用户选择分类(如:政治 → 特朗普) +activeTagIds = [1351, 1368] // 合并所有选中层级的 tagIds + ↓ getPmEventPublic +?tagIds=1351&tagIds=1368 +``` + +## 核心计算属性 + +### activeTagIds + +收集所有选中层级节点的 `tagIds`(含父级),用于事件筛选: + +```typescript +const activeTagIds = computed(() => { + const activeIds = layerActiveValues.value + const tagIdSet = new Set() + + // 遍历每一层选中的节点,收集所有 tagIds(含父级) + let currentNodes = filterVisible(categoryTree.value) + for (let i = 0; i < activeIds.length; i++) { + const selectedId = activeIds[i] + if (!selectedId) continue + + const node = currentNodes.find((n) => n.id === selectedId) + if (node?.tagIds && node.tagIds.length > 0) { + node.tagIds.forEach((id) => tagIdSet.add(id)) + } + + currentNodes = filterVisible(node?.children) + } + + return Array.from(tagIdSet) // 去重后的数组 +}) +``` + +**示例**: +- 选中「政治」(tagIds: [1351])→ activeTagIds = [1351] +- 选中「政治 → 特朗普」(tagIds: [1351] + [1368])→ activeTagIds = [1351, 1368] ## 使用方式 -- 访问 `/` 即可进入 -- 分类切换、搜索、下拉刷新、滚动加载均自动工作 +无需手动调用,路由 `/` 自动加载。 ## 扩展方式 -1. **新增筛选**:在搜索浮层旁增加筛选按钮,修改 `getPmEventPublic` 的 params -2. **骨架屏**:在 loading 时展示 `v-skeleton-loader` -3. **空状态**:列表为空时展示空状态插画与文案 +1. **新增分类层级**:修改 `MAX_LAYER` 常量,调整模板渲染逻辑 +2. **自定义筛选逻辑**:修改 `activeTagIds` 计算属性 +3. **列表缓存策略**:调整 `getEventListCache` / `setEventListCache` diff --git a/src/api/category.ts b/src/api/category.ts index 2e3413a..13420ce 100644 --- a/src/api/category.ts +++ b/src/api/category.ts @@ -50,6 +50,8 @@ export interface CategoryTreeNode { updatedAt?: string /** 排序值,有则按从小到大排序 */ sort?: number + /** 关联的标签 ID 列表,用于事件筛选 */ + tagIds?: number[] children?: CategoryTreeNode[] } @@ -204,6 +206,14 @@ function mapCatalogToTreeNode(item: PmTagCatalogItem): CategoryTreeNode { ? item.children.map(mapCatalogToTreeNode) : undefined const icon = item.icon ?? resolveCategoryIcon({ label, slug }) + + // 提取 tagIds:优先使用数组形式的 tagId + const tagIds = Array.isArray(item.tagId) + ? item.tagId.filter((v): v is number => typeof v === 'number') + : typeof item.tagId === 'number' + ? [item.tagId] + : undefined + return { id, label, @@ -211,6 +221,7 @@ function mapCatalogToTreeNode(item: PmTagCatalogItem): CategoryTreeNode { icon, sectionTitle: item.sectionTitle, sort: item.sort, + tagIds, children: children?.length ? children : undefined, } } diff --git a/src/api/event.ts b/src/api/event.ts index 0d4dc1a..665c7cb 100644 --- a/src/api/event.ts +++ b/src/api/event.ts @@ -120,9 +120,9 @@ export interface GetPmEventListParams { page?: number pageSize?: number keyword?: string - /** 创建时间范围,如 ['2025-01-01', '2025-12-31'],collectionFormat: csv */ + /** 创建时间范围,如 ['2025-01-01', '2025-12-31'] */ createdAtRange?: string[] - /** 标签 ID 列表,按分类筛选,collectionFormat: csv */ + /** 标签 ID 列表,按分类筛选,传统数组方式传递 */ tagIds?: number[] } @@ -137,14 +137,14 @@ export async function getPmEventPublic( params: GetPmEventListParams = {}, ): Promise { const { page = 1, pageSize = 10, keyword, createdAtRange, tagIds } = params - const query: Record = { + const query: Record = { page, pageSize, } if (keyword != null && keyword !== '') query.keyword = keyword if (createdAtRange != null && createdAtRange.length) query.createdAtRange = createdAtRange if (tagIds != null && tagIds.length > 0) { - query.tagIds = tagIds.join(',') + query.tagIds = tagIds } return get('/PmEvent/getPmEventPublic', query) } diff --git a/src/api/request.ts b/src/api/request.ts index de4af06..9e60050 100644 --- a/src/api/request.ts +++ b/src/api/request.ts @@ -35,7 +35,7 @@ export interface RequestConfig { */ export async function get( path: string, - params?: Record, + params?: Record, config?: RequestConfig, ): Promise { const url = new URL(path, BASE_URL || window.location.origin) diff --git a/src/views/Home.vue b/src/views/Home.vue index d0283cc..24c16e9 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -415,14 +415,28 @@ function findNodeById(nodes: CategoryTreeNode[], id: string): CategoryTreeNode | return undefined } -/** 当前选中分类的 tagIds:[第一层, 第二层, 第三层],仅包含数字 ID */ +/** 当前选中分类的 tagIds:收集所有选中层级节点的 tagId 数组(含父级),用于事件筛选 */ const activeTagIds = computed(() => { - const ids = layerActiveValues.value - const tagIds: number[] = [] - for (const id of ids) { - if (id && /^\d+$/.test(id)) tagIds.push(parseInt(id, 10)) + const activeIds = layerActiveValues.value + const tagIdSet = new Set() + + // 遍历每一层选中的节点,收集所有 tagIds(含父级) + let currentNodes = filterVisible(categoryTree.value) + for (let i = 0; i < activeIds.length; i++) { + const selectedId = activeIds[i] + if (!selectedId) continue + + const node = currentNodes.find((n) => n.id === selectedId) + if (node?.tagIds && node.tagIds.length > 0) { + // 合并当前选中节点的 tagIds + node.tagIds.forEach((id) => tagIdSet.add(id)) + } + + // 进入下一层 + currentNodes = filterVisible(node?.children) } - return tagIds + + return Array.from(tagIdSet) }) /** 当前展示的层级数据:[[layer0], [layer1]?, [layer2]?] */