index.vue
7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
<template>
<div class="min-h-screen bg-[#f9fafb] pb-[calc(160rpx+env(safe-area-inset-bottom))]">
<!-- Navigation Header -->
<NavHeader :title="pageTitle" />
<!-- Loading State -->
<div v-if="loading" class="px-[40rpx] mt-[80rpx] flex items-center justify-center">
<text class="text-[#9CA3AF] text-[28rpx]">加载中...</text>
</div>
<!-- Content List -->
<div v-else-if="filteredChildren.length > 0" class="px-[40rpx] mt-[40rpx] relative z-10">
<template v-for="item in filteredChildren" :key="item.id">
<!-- 有子分类:显示 SectionCard -->
<SectionCard
v-if="item.max_depth > 1"
:title="item.category_name"
:items="convertToItems(item.children)"
@item-click="handleItemClick"
/>
<!-- 无子分类且有文章:直接显示 ArticleCard 列表 -->
<template v-else-if="item.max_depth === 1 && item.list?.length">
<view class="bg-white rounded-[32rpx] mb-[32rpx] overflow-hidden shadow-sm">
<!-- 标题区域 - 与 SectionCard 一致 -->
<view class="px-[40rpx] py-[32rpx]" style="background: linear-gradient(90deg, #EFF6FF 0%, #DBEAFE 100%)">
<text class="text-[#1f2937] text-[32rpx] font-normal">{{ item.category_name }}</text>
</view>
<!-- 文章列表 -->
<view class="px-[32rpx] pt-[24rpx] pb-[32rpx]">
<view v-for="(article, index) in item.list" :key="article.id">
<ArticleCard
:id="article.id"
:title="article.post_title"
:excerpt="article.post_excerpt"
:date="article.formatted_post_date || article.post_date"
:collected="article.is_favorite === 1"
:show-cover="false"
@collect-changed="handleCollectChanged"
/>
<!-- 卡片间距(最后一项不显示) -->
<view v-if="index < item.list.length - 1" class="h-[16rpx]"></view>
</view>
</view>
</view>
</template>
</template>
</div>
<!-- Empty State -->
<div v-else class="px-[40rpx] mt-[80rpx] flex items-center justify-center">
<view class="flex flex-col items-center">
<view class="text-[#9CA3AF] text-[120rpx] mb-[24rpx]">📂</view>
<text class="text-gray-600 text-[28rpx]">暂无分类</text>
</view>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useLoad } from '@tarojs/taro'
import NavHeader from '@/components/navigation/NavHeader.vue'
import SectionCard from '@/components/list/SectionCard.vue'
import ArticleCard from '@/components/cards/ArticleCard.vue'
import { listAPI } from '@/api/article'
import { mockArticleListAPI } from '@/utils/mockData'
import { useGo } from '@/hooks/useGo'
import Taro from '@tarojs/taro'
import { USE_MOCK_DATA } from '@/config/app'
const go = useGo()
/**
* 页面状态
*/
const loading = ref(false)
const data = ref({})
/**
* 页面标题
*/
const pageTitle = ref('分类列表')
/**
* 过滤后的子分类列表
* @description 过滤掉 max_depth === 1 且 list 为空的项
*/
const filteredChildren = computed(() => {
const children = data.value?.children || []
return children.filter(item => {
// 保留有子分类的项
if (item.max_depth > 1) return true
// 保留有文章列表的项
if (item.max_depth === 1 && item.list?.length > 0) return true
// 过滤掉空项
return false
})
})
/**
* 获取文章分类列表
* @param {Object} options - 页面参数
* @param {string} options.cid - 分类ID(首次进入)
* @param {string} options.id - 子分类ID(后续层级)
* @param {string} options.title - 页面标题
*/
const fetchCategoryList = async (options) => {
try {
loading.value = true
// 构建请求参数
const params = {}
if (options.cid) {
params.cid = options.cid // 首次进入使用 cid
} else if (options.id) {
params.cid = options.id // 后续层级使用 id
}
console.log('[Category List] 请求参数:', params)
console.log('[Category List] 使用 Mock 数据:', USE_MOCK_DATA)
// 调用文章列表接口(支持分类结构)
const res = USE_MOCK_DATA
? await mockArticleListAPI(params)
: await listAPI(params)
if (res.code === 1 && res.data) {
data.value = res.data
console.log('[Category List] 分类数据:', res.data)
} else {
Taro.showToast({
title: res.msg || '获取分类列表失败',
icon: 'none',
duration: 2000
})
}
} catch (error) {
console.error('[Category List] 获取分类列表失败:', error)
throw error
} finally {
loading.value = false
}
}
/**
* 处理分类点击事件
* @description 根据该分类的层级和是否有子分类决定跳转
*
* 数据结构说明:
* - level=1: 第一层(大标题),如"入职前"、"入职中"、"入职后"
* - level=2: 第二层(小标题),如"考试报名"、"资格考试报名入口"
* - max_level: 总的最大层级数
* - max_depth: 当前分支的最大深度
*
* 跳转规则:
* - 如果当前是第二层(level=2),直接跳转到 material-list(最终层)
* - category-list 只显示两层结构
*
* @param {Object} item - 被点击的项目数据(第二层分类)
*/
const handleItemClickWithNav = (item, go) => {
console.log('[Category List] 点击分类:', item)
console.log('[Category List] 分类层级:', item.level)
console.log('[Category List] 最大深度:', item.maxDepth)
// 当前点击的是第二层(level=2),直接跳转到文章列表
console.log('[Category List] 跳转到文章列表')
go('/pages/material-list/index', {
id: item.id,
title: item.title
})
}
// 导出 handleItemClick 供 SectionCard 使用
const handleItemClick = (item) => handleItemClickWithNav(item, go)
/**
* 将子分类数组转换为 SectionCard items 格式
* @param {Array} children - 子分类数组
* @returns {Array} SectionCard items 格式
*/
const convertToItems = (children) => {
if (!children || children.length === 0) return []
return children.map(category => ({
id: category.id,
title: category.category_name,
subtitle: category.list?.length ? `${category.list.length} 篇文章` : '',
icon: category.icon || '',
level: category.level,
maxDepth: category.max_depth,
_raw: category
}))
}
/**
* 处理收藏状态变化
* @param {Object} payload - 收藏事件数据
*/
const handleCollectChanged = (payload) => {
console.log('[Category List] 收藏状态变化:', payload)
// TODO: 更新本地状态或刷新列表
}
/**
* 页面加载时接收参数并初始化
*/
useLoad((options) => {
console.log('[Category List] 页面参数:', options)
// 设置页面标题
if (options.title) {
pageTitle.value = options.title
}
// 获取分类列表
fetchCategoryList(options)
})
</script>
<script>
export default {
name: 'CategoryListIndex'
}
</script>