优化:分类数据优化
This commit is contained in:
parent
af22e1a91c
commit
73cf147348
@ -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` 是对象或其他类型,忽略
|
||||
|
||||
## 使用方式
|
||||
|
||||
|
||||
@ -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
|
||||
})
|
||||
```
|
||||
|
||||
@ -1,27 +1,69 @@
|
||||
# Home.vue
|
||||
|
||||
**路径**:`src/views/Home.vue`
|
||||
**路由**:`/`,name: `home`
|
||||
|
||||
## 功能用途
|
||||
|
||||
首页,展示分类 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<number>()
|
||||
|
||||
// 遍历每一层选中的节点,收集所有 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`
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<PmEventListResponse> {
|
||||
const { page = 1, pageSize = 10, keyword, createdAtRange, tagIds } = params
|
||||
const query: Record<string, string | number | string[] | undefined> = {
|
||||
const query: Record<string, string | number | number[] | string[] | undefined> = {
|
||||
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<PmEventListResponse>('/PmEvent/getPmEventPublic', query)
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ export interface RequestConfig {
|
||||
*/
|
||||
export async function get<T = unknown>(
|
||||
path: string,
|
||||
params?: Record<string, string | number | string[] | undefined>,
|
||||
params?: Record<string, string | number | string[] | number[] | undefined>,
|
||||
config?: RequestConfig,
|
||||
): Promise<T> {
|
||||
const url = new URL(path, BASE_URL || window.location.origin)
|
||||
|
||||
@ -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<number>()
|
||||
|
||||
// 遍历每一层选中的节点,收集所有 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]?] */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user