hookehuyr

refactor(favorites): 优化我的收藏页面布局和图标显示

## 主要更新

### 优化
- 重构"我的收藏"页面 (`src/pages/favorites/index.vue`)
  - 使用真实文档图标系统 (getDocumentIcon),根据文件扩展名自动显示
  - 去掉图标背景容器,图标居中显示并放大到 56rpx
  - 查看和删除按钮参考计划书页面样式重新设计
    - 横向排列(非纵向)
    - 右对齐布局
    - 去掉背景色,使用简洁的图标+文字样式
  - 优化整体布局结构
    - 标题与图标并排显示
    - 信息区域分层更清晰
  - 移除冗余数据字段 (icon/iconColor/iconBgClass)

### 技术细节
- 集成 getDocumentIcon 工具函数
- 统一操作按钮交互体验
- 响应式布局优化

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 +## [2026-01-31] - 优化我的收藏页面
2 +
3 +### 优化
4 +- 重构"我的收藏"页面布局和样式 (`src/pages/favorites/index.vue`)
5 + - 使用真实文档图标系统,根据文件扩展名自动显示对应图标
6 + - 去掉图标背景容器,图标居中显示,适当放大(56rpx)
7 + - 查看和删除按钮布局参考计划书页面样式(横向排列、右对齐)
8 + - 优化页面结构,标题与图标并排,信息更清晰
9 +
10 +---
11 +
12 +**详细信息**
13 +- **影响文件**: src/pages/favorites/index.vue
14 +- **技术栈**: Vue 3, Taro, TailwindCSS
15 +- **测试状态**: 已通过
16 +- **备注**:
17 + - 集成 `getDocumentIcon` 工具函数
18 + - 统一操作按钮样式,与计划书页面保持一致
19 +
20 +---
21 +
1 ## [2026-01-31] - 优化首页热门资料展示 22 ## [2026-01-31] - 优化首页热门资料展示
2 23
3 ### 优化 24 ### 优化
......
1 <template> 1 <template>
2 - <view class="min-h-screen bg-[#F9FAFB] pb-[200rpx] flex flex-col items-center"> 2 + <view class="min-h-screen bg-gray-50 pb-[200rpx]">
3 <!-- Header --> 3 <!-- Header -->
4 <NavHeader title="我的收藏" /> 4 <NavHeader title="我的收藏" />
5 5
6 <!-- Tabs Section --> 6 <!-- Tabs Section -->
7 - <!-- Background image from design --> 7 + <view class="bg-white mt-[2rpx] px-[24rpx] py-[20rpx]">
8 - <view 8 + <div class="flex overflow-x-auto no-scrollbar space-x-[24rpx]">
9 - class="w-[706rpx] h-[144rpx] mt-[40rpx] bg-white flex items-center px-[32rpx] rounded-[24rpx]">
10 - <div class="flex overflow-x-auto no-scrollbar space-x-[24rpx] w-full items-center">
11 <div v-for="(tab, index) in tabs" :key="index" 9 <div v-for="(tab, index) in tabs" :key="index"
12 class="px-[32rpx] py-[16rpx] rounded-full text-[28rpx] whitespace-nowrap transition-colors" 10 class="px-[32rpx] py-[16rpx] rounded-full text-[28rpx] whitespace-nowrap transition-colors"
13 - :class="activeTab === tab.key ? 'bg-[#007AFF] text-white' : 'bg-transparent text-[#6B7280]'" 11 + :class="activeTab === tab.key ? 'bg-[#2563EB] text-white' : 'bg-[#F3F4F6] text-[#6B7280]'"
14 @click="activeTab = tab.key"> 12 @click="activeTab = tab.key">
15 {{ tab.title }} 13 {{ tab.title }}
16 </div> 14 </div>
...@@ -18,39 +16,41 @@ ...@@ -18,39 +16,41 @@
18 </view> 16 </view>
19 17
20 <!-- List Section --> 18 <!-- List Section -->
21 - <view class="w-[706rpx] mt-[32rpx] flex flex-col gap-[24rpx]"> 19 + <view class="px-[24rpx] py-[24rpx]">
22 <view v-for="(item, index) in filteredList" :key="index" 20 <view v-for="(item, index) in filteredList" :key="index"
23 - class="bg-white rounded-[24rpx] p-[32rpx] shadow-sm flex items-center justify-between"> 21 + class="bg-white rounded-[24rpx] p-[24rpx] mb-[24rpx] shadow-sm">
24 - <view class="flex items-center flex-1 mr-[24rpx]">
25 - <!-- Icon (Text Type Icons as requested) -->
26 - <view class="w-[80rpx] h-[80rpx] rounded-[16rpx] flex items-center justify-center mr-[24rpx]"
27 - :class="item.iconBgClass">
28 - <IconFont :name="item.icon" size="24" :color="item.iconColor" />
29 - </view>
30 22
31 - <!-- Text Content --> 23 + <!-- Header with Icon -->
32 - <view class="flex flex-col flex-1 min-w-0"> 24 + <view class="flex gap-[24rpx] mb-[16rpx]">
33 - <view class="text-[32rpx] text-[#1F2937] font-bold mb-[8rpx] line-clamp-2 leading-[1.4]">{{ item.title }}</view> 25 + <!-- Document Icon -->
34 - <view class="flex flex-col items-start gap-[8rpx]"> 26 + <view class="w-[64rpx] h-[64rpx] flex-shrink-0 flex items-center justify-center">
35 - <view class="bg-gray-100 px-[12rpx] py-[4rpx] rounded-[8rpx] text-[24rpx] text-[#9CA3AF] mt-1">{{ item.category 27 + <image :src="getDocumentIcon(item.title)" class="w-[56rpx] h-[56rpx]" mode="aspectFit" />
36 - }}</view>
37 - <view class="text-[24rpx] text-gray-400 mt-2">{{ item.date }}</view>
38 </view> 28 </view>
29 +
30 + <!-- Title -->
31 + <view class="flex-1 min-w-0">
32 + <view class="text-[30rpx] font-bold text-gray-900 leading-normal mb-[8rpx] line-clamp-2">{{ item.title }}</view>
39 </view> 33 </view>
40 </view> 34 </view>
41 35
42 - <!-- Action Buttons --> 36 + <!-- Info -->
43 - <view class="flex flex-col items-end gap-[16rpx] ml-[16rpx] flex-shrink-0"> 37 + <view class="flex justify-between items-center text-gray-500 text-[24rpx] mb-[24rpx]">
44 - <!-- View Button --> 38 + <text>{{ item.category }}</text>
45 - <view class="flex items-center justify-center px-[20rpx] py-[10rpx] bg-blue-50 rounded-full active:bg-blue-100 transition-colors" @tap.stop="viewFile({...item, fileName: item.title})"> 39 + <text>{{ item.date }}</text>
46 - <IconFont name="eye" size="14" color="#2563EB" class="mr-[6rpx]" />
47 - <text class="text-[24rpx] text-blue-600 font-medium leading-none">查看</text>
48 </view> 40 </view>
49 41
50 - <!-- Delete Button --> 42 + <!-- Divider -->
51 - <view class="flex items-center justify-center px-[20rpx] py-[10rpx] bg-red-50 rounded-full active:bg-red-100 transition-colors" @tap.stop="onDelete(item)"> 43 + <view class="h-[1rpx] bg-gray-100 my-[20rpx]"></view>
52 - <IconFont name="del" size="14" color="#EF4444" class="mr-[6rpx]" /> 44 +
53 - <text class="text-[24rpx] text-red-500 font-medium leading-none">删除</text> 45 + <!-- Actions -->
46 + <view class="flex justify-end gap-[24rpx]">
47 + <view class="flex items-center text-blue-600" @tap="viewFile({...item, fileName: item.title})">
48 + <IconFont name="eye" size="14" class="mr-[8rpx]" />
49 + <text class="text-[24rpx]">查看</text>
50 + </view>
51 + <view class="flex items-center text-red-500" @tap="onDelete(item)">
52 + <IconFont name="del" size="14" class="mr-[8rpx]" />
53 + <text class="text-[24rpx]">删除</text>
54 </view> 54 </view>
55 </view> 55 </view>
56 </view> 56 </view>
...@@ -73,6 +73,7 @@ import { ref, computed } from 'vue' ...@@ -73,6 +73,7 @@ import { ref, computed } from 'vue'
73 import Taro from '@tarojs/taro' 73 import Taro from '@tarojs/taro'
74 import { useGo } from '@/hooks/useGo' 74 import { useGo } from '@/hooks/useGo'
75 import { useFileOperation } from '@/composables/useFileOperation' 75 import { useFileOperation } from '@/composables/useFileOperation'
76 +import { getDocumentIcon } from '@/utils/documentIcons'
76 import IconFont from '@/components/IconFont.vue' 77 import IconFont from '@/components/IconFont.vue'
77 import TabBar from '@/components/TabBar.vue' 78 import TabBar from '@/components/TabBar.vue'
78 import NavHeader from '@/components/NavHeader.vue' 79 import NavHeader from '@/components/NavHeader.vue'
...@@ -88,6 +89,11 @@ const tabs = [ ...@@ -88,6 +89,11 @@ const tabs = [
88 { title: '产品知识', key: 'product' } 89 { title: '产品知识', key: 'product' }
89 ] 90 ]
90 91
92 +/**
93 + * Mock 数据:收藏列表
94 + *
95 + * @description 包含不同类型的文档文件
96 + */
91 const list = ref([ 97 const list = ref([
92 { 98 {
93 id: 1, 99 id: 1,
...@@ -95,10 +101,6 @@ const list = ref([ ...@@ -95,10 +101,6 @@ const list = ref([
95 category: '入职培训', 101 category: '入职培训',
96 date: '2024-01-15', 102 date: '2024-01-15',
97 type: 'onboarding', 103 type: 'onboarding',
98 - icon: 'order', // Represents a document
99 - iconColor: '#EF4444', // Red for PDF
100 - iconBgClass: 'bg-red-50',
101 - // 使用真实的可下载 PDF 文件(W3C 测试文件)
102 downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf' 104 downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/1_%E7%BE%8E%E4%B9%90%E7%88%B1%E8%A7%89%E6%95%99%E8%82%B22024%E9%A1%B9%E7%9B%AE%E5%9B%BE%E5%BD%B1%E4%BB%8B%E7%BB%8D_.pdf'
103 }, 105 },
104 { 106 {
...@@ -107,10 +109,6 @@ const list = ref([ ...@@ -107,10 +109,6 @@ const list = ref([
107 category: '签单相关', 109 category: '签单相关',
108 date: '2024-01-14', 110 date: '2024-01-14',
109 type: 'signing', 111 type: 'signing',
110 - icon: 'order', // Represents a document
111 - iconColor: '#2563EB', // Blue for Word
112 - iconBgClass: 'bg-blue-50',
113 - // 使用真实的可下载图片(作为文档示例)
114 downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/%E8%80%81%E6%9D%A5%E8%B5%9B%E9%9A%90%E7%A7%81%E6%94%BF%E7%AD%96.docx' 112 downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/%E8%80%81%E6%9D%A5%E8%B5%9B%E9%9A%90%E7%A7%81%E6%94%BF%E7%AD%96.docx'
115 }, 113 },
116 { 114 {
...@@ -119,10 +117,6 @@ const list = ref([ ...@@ -119,10 +117,6 @@ const list = ref([
119 category: '产品知识', 117 category: '产品知识',
120 date: '2024-01-13', 118 date: '2024-01-13',
121 type: 'product', 119 type: 'product',
122 - icon: 'order', // Represents a document
123 - iconColor: '#F59E0B', // Orange for PPT
124 - iconBgClass: 'bg-orange-50',
125 - // 使用另一个图片作为示例
126 downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/%E8%82%A1%E5%88%A4%E5%90%88%E5%8F%8B%E7%94%A8%E7%9F%A5%E8%AF%86%E8%AF%B4%E6%98%8E20240112110417414.pptx' 120 downloadUrl: 'https://cdn.ipadbiz.cn/manulife/document/%E8%82%A1%E5%88%A4%E5%90%88%E5%8F%8B%E7%94%A8%E7%9F%A5%E8%AF%86%E8%AF%B4%E6%98%8E20240112110417414.pptx'
127 }, 121 },
128 { 122 {
...@@ -131,10 +125,7 @@ const list = ref([ ...@@ -131,10 +125,7 @@ const list = ref([
131 category: '政策解读', 125 category: '政策解读',
132 date: '2024-01-12', 126 date: '2024-01-12',
133 type: 'other', 127 type: 'other',
134 - icon: 'edit', // Represents text 128 + downloadUrl: ''
135 - iconColor: '#10B981', // Green for TXT
136 - iconBgClass: 'bg-green-50',
137 - downloadUrl: '' // 空下载地址,用于测试无地址情况
138 } 129 }
139 ]) 130 ])
140 131
...@@ -143,6 +134,9 @@ const filteredList = computed(() => { ...@@ -143,6 +134,9 @@ const filteredList = computed(() => {
143 return list.value.filter(item => item.type === activeTab.value) 134 return list.value.filter(item => item.type === activeTab.value)
144 }) 135 })
145 136
137 +/**
138 + * 删除收藏
139 + */
146 const onDelete = (item) => { 140 const onDelete = (item) => {
147 Taro.showModal({ 141 Taro.showModal({
148 title: '提示', 142 title: '提示',
......