hookehuyr

refactor(ui): 提取可复用卡片组件并重构页面

- 新增 MaterialCard 和 ProductCard 组件,减少代码重复
- 重构首页、搜索页、本周热门资料页使用新组件
- 搜索页面集成真实API,支持分页加载和实时查询
- 移除测试数据,全部对接后端接口

影响文件:
- src/components/MaterialCard.vue (新增)
- src/components/ProductCard.vue (新增)
- src/pages/index/index.vue
- src/pages/search/index.vue
- src/pages/week-hot-material/index.vue
- components.d.ts
- docs/CHANGELOG.md

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
...@@ -18,6 +18,7 @@ declare module 'vue' { ...@@ -18,6 +18,7 @@ declare module 'vue' {
18 IndexNav: typeof import('./src/components/indexNav.vue')['default'] 18 IndexNav: typeof import('./src/components/indexNav.vue')['default']
19 LifeInsuranceTemplate: typeof import('./src/components/PlanTemplates/LifeInsuranceTemplate.vue')['default'] 19 LifeInsuranceTemplate: typeof import('./src/components/PlanTemplates/LifeInsuranceTemplate.vue')['default']
20 ListItemActions: typeof import('./src/components/ListItemActions/index.vue')['default'] 20 ListItemActions: typeof import('./src/components/ListItemActions/index.vue')['default']
21 + MaterialCard: typeof import('./src/components/MaterialCard.vue')['default']
21 NavHeader: typeof import('./src/components/NavHeader.vue')['default'] 22 NavHeader: typeof import('./src/components/NavHeader.vue')['default']
22 NutAvatar: typeof import('@nutui/nutui-taro')['Avatar'] 23 NutAvatar: typeof import('@nutui/nutui-taro')['Avatar']
23 NutButton: typeof import('@nutui/nutui-taro')['Button'] 24 NutButton: typeof import('@nutui/nutui-taro')['Button']
...@@ -36,6 +37,7 @@ declare module 'vue' { ...@@ -36,6 +37,7 @@ declare module 'vue' {
36 PlanFormContainer: typeof import('./src/components/PlanFormContainer.vue')['default'] 37 PlanFormContainer: typeof import('./src/components/PlanFormContainer.vue')['default']
37 PlanPopup: typeof import('./src/components/PlanSchemes/PlanPopup.vue')['default'] 38 PlanPopup: typeof import('./src/components/PlanSchemes/PlanPopup.vue')['default']
38 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] 39 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
40 + ProductCard: typeof import('./src/components/ProductCard.vue')['default']
39 QrCode: typeof import('./src/components/qrCode.vue')['default'] 41 QrCode: typeof import('./src/components/qrCode.vue')['default']
40 QrCodeSearch: typeof import('./src/components/qrCodeSearch.vue')['default'] 42 QrCodeSearch: typeof import('./src/components/qrCodeSearch.vue')['default']
41 RadioGroup: typeof import('./src/components/PlanFields/RadioGroup.vue')['default'] 43 RadioGroup: typeof import('./src/components/PlanFields/RadioGroup.vue')['default']
......
...@@ -5,6 +5,30 @@ ...@@ -5,6 +5,30 @@
5 5
6 --- 6 ---
7 7
8 +## [2026-02-06] - 搜索页面联调完成
9 +
10 +### 新增
11 +- 集成搜索API,实现产品和资料的实时搜索功能
12 +- 添加下拉刷新功能,支持刷新当前选中tab的数据
13 +- 实现首次搜索自动选择tab逻辑(根据返回数据判断)
14 +
15 +### 优化
16 +- 移除"全部"tab,只保留"产品"和"资料"两个tab
17 +- 点击tab时实时查询对应类型的数据,不再使用本地缓存
18 +- 优化搜索结果展示,适配后端返回的数据结构
19 +
20 +### 技术改进
21 +- 使用 `searchAPI` 调用后端搜索接口
22 +- 首次搜索不传 `type` 参数,根据返回的 `products.total``files.total` 自动选择有数据的tab
23 +- 切换tab时传递 `type` 参数('product' | 'file')进行精确查询
24 +- 下拉刷新时使用当前选中的tab的type进行查询
25 +
26 +### 修复
27 +- 修复产品卡片显示逻辑,正确显示 `product_name``cover_image``tags`
28 +- 修复资料卡片显示逻辑,正确显示 `name``value``extension`
29 +
30 +---
31 +
8 ## [2026-02-06] - 修复计划书表单重置不稳定问题 32 ## [2026-02-06] - 修复计划书表单重置不稳定问题
9 33
10 ### 修复 34 ### 修复
......
1 +<template>
2 + <view class="flex flex-row bg-white rounded-[24rpx] p-[24rpx] shadow-md border border-gray-50 material-card">
3 + <!-- 左侧图标 -->
4 + <view class="w-[88rpx] h-[88rpx] mr-[24rpx] flex-shrink-0 flex items-center justify-center bg-gradient-to-br from-blue-50 to-blue-100 rounded-[20rpx] shadow-inner self-start">
5 + <image :src="iconUrl" class="w-[48rpx] h-[48rpx]" mode="aspectFit" />
6 + </view>
7 +
8 + <!-- 内容区域 -->
9 + <view class="flex-1 min-w-0">
10 + <!-- 标题 -->
11 + <view class="text-[#1F2937] text-[30rpx] font-bold leading-[1.4] mb-[8rpx] line-clamp-2">
12 + {{ title }}
13 + </view>
14 +
15 + <!-- 学习人数信息 -->
16 + <view v-if="learners" class="flex items-center gap-[12rpx] mb-[16rpx]">
17 + <view class="inline-flex items-center justify-center px-[12rpx] py-[4rpx] bg-orange-50 text-orange-600 text-[20rpx] font-medium rounded-[8rpx]">
18 + <text>{{ learners }}</text>
19 + </view>
20 + <!-- 学习人数比例 -->
21 + <view v-if="readPeoplePercent !== undefined && readPeoplePercent !== null" class="inline-flex items-center justify-center px-[12rpx] py-[4rpx] bg-green-50 text-green-600 text-[20rpx] font-medium rounded-[8rpx]">
22 + <text>{{ readPeoplePercent }}%热度</text>
23 + </view>
24 + </view>
25 +
26 + <!-- 文档类型和文件大小 -->
27 + <view class="flex items-center gap-[12rpx] mb-[16rpx]">
28 + <view class="inline-flex items-center justify-center px-[12rpx] py-[4rpx] bg-gray-100 text-gray-500 text-[20rpx] font-medium rounded-[8rpx]">
29 + <text>{{ docTypeLabel }}</text>
30 + </view>
31 + <view v-if="fileSize" class="text-[#9CA3AF] text-[22rpx]">
32 + {{ fileSize }}
33 + </view>
34 + </view>
35 +
36 + <!-- 分割线 -->
37 + <view class="h-[1rpx] bg-gray-100 my-[20rpx]"></view>
38 +
39 + <!-- 操作按钮 -->
40 + <ListItemActions
41 + :viewable="true"
42 + :collectable="true"
43 + :deletable="false"
44 + :collected="collected"
45 + :item-id="String(id)"
46 + @view="handleView"
47 + @collect="handleCollect"
48 + />
49 + </view>
50 + </view>
51 +</template>
52 +
53 +<script setup>
54 +/**
55 + * 资料卡片组件
56 + *
57 + * @description 热门资料列表项卡片,展示资料图标、标题、学习人数和操作按钮
58 + * @component MaterialCard
59 + */
60 +
61 +import { defineProps, defineEmits } from 'vue';
62 +import Taro from '@tarojs/taro';
63 +import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons';
64 +import ListItemActions from '@/components/ListItemActions/index.vue';
65 +import { useCollectOperation } from '@/composables/useCollectOperation';
66 +import { useListItemClick, ListType } from '@/composables/useListItemClick';
67 +
68 +/**
69 + * 组件属性
70 + */
71 +const props = defineProps({
72 + /** 资料 ID */
73 + id: {
74 + type: [Number, String],
75 + required: true
76 + },
77 + /** 资料标题 */
78 + title: {
79 + type: String,
80 + required: true
81 + },
82 + /** 文件名(用于获取图标和标签) */
83 + fileName: {
84 + type: String,
85 + default: ''
86 + },
87 + /** 文件大小 */
88 + fileSize: {
89 + type: String,
90 + default: ''
91 + },
92 + /** 学习人数文本 */
93 + learners: {
94 + type: String,
95 + default: ''
96 + },
97 + /** 学习人数百分比(热度) */
98 + readPeoplePercent: {
99 + type: Number,
100 + default: null
101 + },
102 + /** 是否已收藏 */
103 + collected: {
104 + type: Boolean,
105 + default: false
106 + },
107 + /** 文件扩展名 */
108 + extension: {
109 + type: String,
110 + default: ''
111 + },
112 + /** 下载URL */
113 + downloadUrl: {
114 + type: String,
115 + default: ''
116 + }
117 +});
118 +
119 +/**
120 + * 组件事件(简化版)
121 + */
122 +const emit = defineEmits({
123 + /** 查看完成 */
124 + viewed: (item) => true,
125 + /** 收藏状态改变 */
126 + collectChanged: (item) => true
127 +});
128 +
129 +/**
130 + * 获取文档图标 URL
131 + *
132 + * @description 根据文件名获取对应的文档类型图标
133 + * @returns {string} 图标 URL
134 + */
135 +const iconUrl = props.fileName ? getDocumentIcon(props.fileName) : '';
136 +
137 +/**
138 + * 获取文档类型标签
139 + *
140 + * @description 根据文件名获取文档类型标签文本
141 + * @returns {string} 文档类型标签
142 + */
143 +const docTypeLabel = props.fileName ? getDocumentLabel(props.fileName) : '';
144 +
145 +/**
146 + * 使用收藏操作 composable
147 + */
148 +const { toggleCollect } = useCollectOperation();
149 +
150 +/**
151 + * 使用文件列表点击处理器(内部实现)
152 + */
153 +const { handleClick } = useListItemClick({
154 + listType: ListType.FILE,
155 + onBeforeClick: async (item) => {
156 + /**
157 + * 检查文件类型并使用对应的预览方式
158 + * - 图片文件:使用 Taro.previewImage 预览
159 + * - 其他文件:继续默认的文件打开流程
160 + */
161 + const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg']
162 + const extension = item.extension?.toLowerCase() || ''
163 +
164 + console.log('[MaterialCard] 文件类型:', extension, '文件名:', item.title)
165 +
166 + if (imageExtensions.includes(extension)) {
167 + // 图片文件:使用 Taro 预览
168 + console.log('[MaterialCard] 检测到图片文件,使用图片预览')
169 +
170 + // 构建图片列表(当前图片)
171 + const urls = [item.downloadUrl]
172 +
173 + try {
174 + // 短暂延迟后打开预览(让用户看到提示)
175 + await new Promise(resolve => setTimeout(resolve, 300))
176 +
177 + await Taro.previewImage({
178 + current: item.downloadUrl,
179 + urls: urls
180 + })
181 +
182 + // 预览成功,阻止默认的文件打开行为
183 + return false
184 + } catch (err) {
185 + console.error('[MaterialCard] 图片预览失败:', err)
186 + Taro.showToast({
187 + title: '图片预览失败',
188 + icon: 'none',
189 + duration: 2000
190 + })
191 + // 预览失败,返回 true 继续默认行为
192 + return true
193 + }
194 + }
195 +
196 + // 非图片文件:继续默认的文件打开流程
197 + console.log('[MaterialCard] 非图片文件,使用默认打开方式')
198 + return true
199 + },
200 + onAfterClick: (item) => {
201 + console.log('[MaterialCard] 用户打开了资料:', item.title)
202 + // 通知父组件查看完成
203 + emit('viewed', item)
204 + }
205 +})
206 +
207 +/**
208 + * 处理查看点击
209 + *
210 + * @description 内部处理查看逻辑,调用 useListItemClick
211 + */
212 +const handleView = () => {
213 + handleClick({
214 + id: props.id,
215 + title: props.title,
216 + fileName: props.fileName,
217 + fileSize: props.fileSize,
218 + learners: props.learners,
219 + readPeoplePercent: props.readPeoplePercent,
220 + collected: props.collected,
221 + extension: props.extension,
222 + downloadUrl: props.downloadUrl
223 + })
224 +};
225 +
226 +/**
227 + * 处理收藏点击
228 + *
229 + * @description 内部处理收藏逻辑,调用 useCollectOperation 并通知父组件
230 + */
231 +const handleCollect = () => {
232 + // 调用收藏操作
233 + toggleCollect({
234 + id: props.id,
235 + title: props.title,
236 + fileName: props.fileName,
237 + fileSize: props.fileSize,
238 + learners: props.learners,
239 + readPeoplePercent: props.readPeoplePercent,
240 + collected: props.collected,
241 + extension: props.extension,
242 + downloadUrl: props.downloadUrl
243 + })
244 +
245 + // 通知父组件收藏状态改变
246 + emit('collectChanged', {
247 + id: props.id,
248 + title: props.title,
249 + fileName: props.fileName,
250 + fileSize: props.fileSize,
251 + learners: props.learners,
252 + readPeoplePercent: props.readPeoplePercent,
253 + collected: !props.collected, // 新状态(取反)
254 + extension: props.extension,
255 + downloadUrl: props.downloadUrl
256 + })
257 +};
258 +</script>
259 +
260 +<style lang="less" scoped>
261 +.material-card {
262 + // 多行文本省略
263 + .line-clamp-2 {
264 + display: -webkit-box;
265 + -webkit-box-orient: vertical;
266 + -webkit-line-clamp: 2;
267 + line-clamp: 2;
268 + overflow: hidden;
269 + word-break: break-all;
270 + }
271 +}
272 +</style>
1 +<template>
2 + <view class="bg-gray-50 rounded-[24rpx] p-[28rpx] product-card">
3 + <!-- 产品名称 -->
4 + <text class="block text-gray-800 text-[28rpx] font-medium mb-[20rpx]">{{ productName }}</text>
5 +
6 + <!-- 产品标签 -->
7 + <view v-if="tags && tags.length" class="flex flex-wrap gap-[12rpx] mb-[24rpx]">
8 + <view
9 + v-for="tag in tags"
10 + :key="tag.id"
11 + class="rounded-[8rpx] px-[16rpx] py-[6rpx]"
12 + :style="{
13 + backgroundColor: tag.bg_color,
14 + color: tag.text_color
15 + }"
16 + >
17 + <text class="text-[22rpx]">{{ tag.name }}</text>
18 + </view>
19 + </view>
20 +
21 + <!-- 操作按钮 -->
22 + <view class="flex justify-between gap-[24rpx]">
23 + <nut-button
24 + plain
25 + color="#2563EB"
26 + class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0 !border-blue-600"
27 + @tap="handleDetail"
28 + >
29 + 产品详情
30 + </nut-button>
31 + <nut-button
32 + color="#2563EB"
33 + class="flex-1 !h-[64rpx] !rounded-[16rpx] !text-[26rpx] !m-0"
34 + @tap="handlePlan"
35 + >
36 + 计划书
37 + </nut-button>
38 + </view>
39 + </view>
40 +</template>
41 +
42 +<script setup>
43 +/**
44 + * 产品卡片组件
45 + *
46 + * @description 热卖产品列表项卡片,展示产品名称、标签和操作按钮
47 + * @component ProductCard
48 + */
49 +
50 +import { defineProps, defineEmits } from 'vue';
51 +
52 +/**
53 + * 组件属性
54 + */
55 +const props = defineProps({
56 + /** 产品 ID */
57 + productId: {
58 + type: Number,
59 + required: true
60 + },
61 + /** 产品名称 */
62 + productName: {
63 + type: String,
64 + required: true
65 + },
66 + /** 产品标签数组 */
67 + tags: {
68 + type: Array,
69 + default: () => []
70 + }
71 +});
72 +
73 +/**
74 + * 组件事件
75 + */
76 +const emit = defineEmits({
77 + /** 点击产品详情按钮 */
78 + detail: (productId) => typeof productId === 'number',
79 + /** 点击计划书按钮 */
80 + plan: (productId) => typeof productId === 'number'
81 +});
82 +
83 +/**
84 + * 处理产品详情点击
85 + *
86 + * @description 触发 detail 事件,传递产品 ID
87 + */
88 +const handleDetail = () => {
89 + emit('detail', props.productId);
90 +};
91 +
92 +/**
93 + * 处理计划书点击
94 + *
95 + * @description 触发 plan 事件,传递产品 ID
96 + */
97 +const handlePlan = () => {
98 + emit('plan', props.productId);
99 +};
100 +</script>
101 +
102 +<style lang="less" scoped>
103 +.product-card {
104 + // 可以添加卡片特定的样式
105 +}
106 +</style>
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
1 <!-- 1 <!--
2 - * @Date: 2026-02-05 2 + * @Date: 2026-02-06
3 - * @Description: 本周热门资料页 - 简化版资料列表(无搜索和Tab) 3 + * @Description: 本周热门资料页 - 使用 MaterialCard 组件
4 --> 4 -->
5 <template> 5 <template>
6 <view class="h-screen bg-[#F9FAFB] flex flex-col py-[32rpx]"> 6 <view class="h-screen bg-[#F9FAFB] flex flex-col py-[32rpx]">
...@@ -19,57 +19,21 @@ ...@@ -19,57 +19,21 @@
19 </view> 19 </view>
20 20
21 <view v-else class="flex flex-col gap-[24rpx]"> 21 <view v-else class="flex flex-col gap-[24rpx]">
22 - <view v-for="(item, index) in currentList" :key="item.meta_id" 22 + <MaterialCard
23 - class="material-item bg-white rounded-[24rpx] p-[24rpx] shadow-md transition-all duration-200 border border-gray-50 flex flex-row" 23 + v-for="(item, index) in currentList"
24 - :style="{ animationDelay: `${index * 50}ms` }"> 24 + :key="item.meta_id"
25 - 25 + :id="item.meta_id"
26 - <view 26 + :title="item.name"
27 - class="w-[88rpx] h-[88rpx] mr-[24rpx] flex-shrink-0 flex items-center justify-center bg-gradient-to-br from-blue-50 to-blue-100 rounded-[20rpx] shadow-inner self-start"> 27 + :file-name="item.name"
28 - <image 28 + :file-size="item.size"
29 - :src="getDocumentIcon(item.extension ? `file.${item.extension}` : item.name)" 29 + :learners="item.read_people_count ? `${item.read_people_count}人学习` : ''"
30 - class="w-[48rpx] h-[48rpx]" 30 + :read-people-percent="item.read_people_percent"
31 - mode="aspectFit" 31 + :collected="item.collected"
32 - /> 32 + :extension="item.extension"
33 - </view> 33 + :download-url="item.downloadUrl"
34 - 34 + :style="{ animationDelay: `${index * 50}ms` }"
35 - <view class="flex-1 min-w-0"> 35 + @collect-changed="handleCollectChanged(item, $event)"
36 - <view class="text-[#1F2937] text-[30rpx] font-bold leading-[1.4] mb-[8rpx] line-clamp-2"> 36 + />
37 - {{ item.name }}
38 - </view>
39 -
40 - <!-- 学习人数信息(本周热门特有) -->
41 - <view v-if="item.read_people_count !== undefined" class="flex items-center gap-[12rpx] mb-[16rpx]">
42 - <view class="inline-flex items-center justify-center px-[12rpx] py-[4rpx] bg-orange-50 text-orange-600 text-[20rpx] font-medium rounded-[8rpx]">
43 - <text>{{ item.read_people_count }}人学习</text>
44 - </view>
45 - <!-- 热度百分比 -->
46 - <view v-if="item.read_people_percent !== undefined" class="inline-flex items-center justify-center px-[12rpx] py-[4rpx] bg-green-50 text-green-600 text-[20rpx] font-medium rounded-[8rpx]">
47 - <text>{{ item.read_people_percent }}%热度</text>
48 - </view>
49 - </view>
50 -
51 - <view class="flex items-center gap-[12rpx] mb-[16rpx]">
52 - <view
53 - class="inline-flex items-center justify-center px-[12rpx] py-[4rpx] bg-gray-100 text-gray-500 text-[20rpx] font-medium rounded-[8rpx]">
54 - {{ getDocumentLabel(item.extension ? `file.${item.extension}` : item.name) }}
55 - </view>
56 - <view class="text-[#9CA3AF] text-[22rpx]">
57 - {{ item.size }}
58 - </view>
59 - </view>
60 -
61 - <view class="h-[1rpx] bg-gray-100 my-[20rpx]"></view>
62 -
63 - <ListItemActions
64 - :viewable="true"
65 - :collectable="true"
66 - :collected="item.collected"
67 - :item-id="String(item.meta_id)"
68 - @view="onView(item)"
69 - @collect="toggleCollect(item)"
70 - />
71 - </view>
72 - </view>
73 37
74 <!-- 空状态 --> 38 <!-- 空状态 -->
75 <view v-if="currentList.length === 0 && !loading && !loadingMore"> 39 <view v-if="currentList.length === 0 && !loading && !loadingMore">
...@@ -93,14 +57,10 @@ ...@@ -93,14 +57,10 @@
93 57
94 <script setup> 58 <script setup>
95 import { ref } from 'vue' 59 import { ref } from 'vue'
96 -import { useLoad, useReachBottom } from '@tarojs/taro' 60 +import Taro, { useLoad, useReachBottom } from '@tarojs/taro'
97 import NavHeader from '@/components/NavHeader.vue' 61 import NavHeader from '@/components/NavHeader.vue'
98 -import ListItemActions from '@/components/ListItemActions/index.vue' 62 +import MaterialCard from '@/components/MaterialCard.vue'
99 -import { useListItemClick, ListType } from '@/composables/useListItemClick'
100 -import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons'
101 import { weekHotAPI } from '@/api/file' 63 import { weekHotAPI } from '@/api/file'
102 -import { useCollectOperation } from '@/composables/useCollectOperation'
103 -import Taro from '@tarojs/taro'
104 64
105 const listVisible = ref(true) 65 const listVisible = ref(true)
106 const listRenderKey = ref(0) 66 const listRenderKey = ref(0)
...@@ -112,26 +72,18 @@ const currentPage = ref(0) // 当前页码(从0开始) ...@@ -112,26 +72,18 @@ const currentPage = ref(0) // 当前页码(从0开始)
112 const pageSize = 20 // 每页数量 72 const pageSize = 20 // 每页数量
113 73
114 /** 74 /**
115 - * 转换文档数据格式 75 + * 处理收藏状态改变
116 - * @description 将 API 返回的文档数据转换为组件需要的格式 76 + *
117 - * @param {Object} doc - API 返回的文档对象 77 + * @description 当用户点击收藏按钮时,更新本地状态
118 - * @returns {Object} 转换后的文档对象 78 + * @param {Object} item - 资料对象
79 + * @param {Object} newStatus - 新的状态
119 */ 80 */
120 -const transformDocItem = (doc) => { 81 +const handleCollectChanged = (item, newStatus) => {
121 - // 处理文件名为空的情况 82 + console.log('[Week Hot] 收藏状态改变:', item.name, newStatus.collected)
122 - const fileName = doc.name || '未命名文件' 83 + // 找到对应的项并更新状态
123 - // 如果没有扩展名,从文件名中提取(如果有) 84 + const material = currentList.value.find(m => m.meta_id === item.meta_id)
124 - const extension = doc.extension || fileName.split('.').pop()?.toLowerCase() || '' 85 + if (material) {
125 - 86 + material.collected = newStatus.collected
126 - return {
127 - meta_id: doc.meta_id,
128 - name: fileName,
129 - size: doc.size || '',
130 - downloadUrl: doc.src,
131 - extension: extension,
132 - collected: doc.is_favorite === '1' || doc.is_favorite === 1 || doc.is_favorite === true,
133 - read_people_count: doc.read_people_count,
134 - read_people_percent: doc.read_people_percent
135 } 87 }
136 } 88 }
137 89
...@@ -161,7 +113,22 @@ const fetchWeekHotList = async (params = {}, isLoadMore = false) => { ...@@ -161,7 +113,22 @@ const fetchWeekHotList = async (params = {}, isLoadMore = false) => {
161 113
162 // 处理列表数据 114 // 处理列表数据
163 if (res.data.list?.length) { 115 if (res.data.list?.length) {
164 - const listData = res.data.list.map(transformDocItem) 116 + // 直接映射为 MaterialCard 需要的格式
117 + const listData = res.data.list.map(item => {
118 + const fileName = item.name || '未命名文件'
119 + const extension = item.extension || fileName.split('.').pop()?.toLowerCase() || ''
120 +
121 + return {
122 + meta_id: item.meta_id,
123 + name: fileName,
124 + size: item.size || '',
125 + downloadUrl: item.src,
126 + extension: extension,
127 + collected: item.is_favorite === '1' || item.is_favorite === 1 || item.is_favorite === true,
128 + read_people_count: item.read_people_count,
129 + read_people_percent: item.read_people_percent
130 + }
131 + })
165 132
166 if (isLoadMore) { 133 if (isLoadMore) {
167 // 加载更多:追加数据 134 // 加载更多:追加数据
...@@ -248,68 +215,6 @@ useReachBottom(() => { ...@@ -248,68 +215,6 @@ useReachBottom(() => {
248 ) 215 )
249 }, 300) 216 }, 300)
250 }) 217 })
251 -
252 -/**
253 - * 使用文件列表点击处理器
254 - * @description 添加图片预览功能,点击图片文件时使用 Taro.previewImage
255 - */
256 -const { handleClick: onView } = useListItemClick({
257 - listType: ListType.FILE,
258 - onBeforeClick: async (item) => {
259 - /**
260 - * 检查文件类型并使用对应的预览方式
261 - * - 图片文件:使用 Taro.previewImage 预览
262 - * - 其他文件:继续默认的文件打开流程
263 - */
264 - const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg']
265 - const extension = item.extension?.toLowerCase() || ''
266 -
267 - console.log('[Week Hot] 文件类型:', extension, '文件名:', item.name)
268 -
269 - if (imageExtensions.includes(extension)) {
270 - // 图片文件:使用 Taro 预览
271 - console.log('[Week Hot] 检测到图片文件,使用图片预览')
272 -
273 - // 构建图片列表(当前图片)
274 - const urls = [item.downloadUrl]
275 -
276 - try {
277 - // 短暂延迟后打开预览(让用户看到提示)
278 - await new Promise(resolve => setTimeout(resolve, 300))
279 -
280 - await Taro.previewImage({
281 - current: item.downloadUrl,
282 - urls: urls
283 - })
284 -
285 - // 预览成功,阻止默认的文件打开行为
286 - return false
287 - } catch (err) {
288 - console.error('[Week Hot] 图片预览失败:', err)
289 - Taro.showToast({
290 - title: '图片预览失败',
291 - icon: 'none',
292 - duration: 2000
293 - })
294 - // 预览失败,返回 true 继续默认行为
295 - return true
296 - }
297 - }
298 -
299 - // 非图片文件:继续默认的文件打开流程
300 - console.log('[Week Hot] 非图片文件,使用默认打开方式')
301 - return true
302 - },
303 - onAfterClick: (item) => {
304 - console.log('用户打开了资料:', item.name)
305 - }
306 -})
307 -
308 -/**
309 - * 切换收藏状态
310 - * @description 使用 useCollectOperation composable 处理收藏操作
311 - */
312 -const { toggleCollect } = useCollectOperation()
313 </script> 218 </script>
314 219
315 <style lang="less"> 220 <style lang="less">
......