hookehuyr

feat(积分页面): 重构积分页面并添加下拉刷新功能

添加新的图片资源并重构积分页面,实现完整的UI设计和交互功能
- 新增积分列表展示、筛选和搜索功能
- 实现日期选择器和Tab切换功能
- 添加下拉刷新和滚动加载更多功能
- 使用Vant组件优化用户体验
...@@ -78,6 +78,7 @@ declare module 'vue' { ...@@ -78,6 +78,7 @@ declare module 'vue' {
78 VanPickerGroup: typeof import('vant/es')['PickerGroup'] 78 VanPickerGroup: typeof import('vant/es')['PickerGroup']
79 VanPopup: typeof import('vant/es')['Popup'] 79 VanPopup: typeof import('vant/es')['Popup']
80 VanProgress: typeof import('vant/es')['Progress'] 80 VanProgress: typeof import('vant/es')['Progress']
81 + VanPullRefresh: typeof import('vant/es')['PullRefresh']
81 VanRate: typeof import('vant/es')['Rate'] 82 VanRate: typeof import('vant/es')['Rate']
82 VanRow: typeof import('vant/es')['Row'] 83 VanRow: typeof import('vant/es')['Row']
83 VanSearch: typeof import('vant/es')['Search'] 84 VanSearch: typeof import('vant/es')['Search']
......
1 /* 1 /*
2 * @Date: 2025-03-20 20:36:36 2 * @Date: 2025-03-20 20:36:36
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-24 09:36:29 4 + * @LastEditTime: 2025-12-24 10:58:25
5 * @FilePath: /mlaj/src/router/routes.js 5 * @FilePath: /mlaj/src/router/routes.js
6 * @Description: 路由地址映射配置 6 * @Description: 路由地址映射配置
7 */ 7 */
...@@ -97,12 +97,6 @@ export const routes = [ ...@@ -97,12 +97,6 @@ export const routes = [
97 meta: { title: '我的活动' }, 97 meta: { title: '我的活动' },
98 }, 98 },
99 { 99 {
100 - path: '/profile/points',
101 - name: 'Points',
102 - component: () => import('../views/profile/pointsPage.vue'),
103 - meta: { title: '我的积分' },
104 - },
105 - {
106 path: '/recall/login', 100 path: '/recall/login',
107 name: 'RecallLogin', 101 name: 'RecallLogin',
108 component: () => import('../views/recall/login.vue'), 102 component: () => import('../views/recall/login.vue'),
...@@ -242,6 +236,12 @@ export const routes = [ ...@@ -242,6 +236,12 @@ export const routes = [
242 meta: { title: '学习记录' }, 236 meta: { title: '学习记录' },
243 }, 237 },
244 { 238 {
239 + path: '/profile/points',
240 + name: 'Points',
241 + component: () => import('../views/profile/pointsPage.vue'),
242 + meta: { title: '我的积分' },
243 + },
244 + {
245 path: '/test', 245 path: '/test',
246 name: 'test', 246 name: 'test',
247 component: () => import('../views/test.vue'), 247 component: () => import('../views/test.vue'),
......
1 -<!--
2 - * @Date: 2025-12-23 17:53:26
3 - * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-23 17:53:56
5 - * @FilePath: /mlaj/src/views/profile/pointsPage.vue
6 - * @Description: 我的积分页面
7 --->
8 <template> 1 <template>
9 - <div class="points-page"> 2 + <div class="points-page min-h-screen bg-gray-50 flex flex-col">
10 - 我的积分 3 + <!-- 头部区域 -->
4 + <div class="header relative w-full h-48 bg-cover bg-center" :style="{ backgroundImage: `url(${headerBg})` }">
5 + <div class="absolute top-12 left-6">
6 + <div class="text-white text-sm opacity-90 mb-1">当前星球币</div>
7 + <div class="text-[#FFDD01] text-4xl font-bold tracking-wider">15,800</div>
8 + </div>
9 +
10 + <!-- 右侧瓶子 -->
11 + <img :src="bottleImg" class="absolute right-4 top-4 w-32 object-contain animate-float" alt="bottle" />
12 +
13 + <!-- 底部提示条 -->
14 + <div class="absolute bottom-0 left-0 w-full h-8 bg-white/20 flex items-center px-4">
15 + <span class="text-white text-xs scale-90 origin-left">可用于兑换活动优惠券或实物奖励,敬请期待!</span>
16 + </div>
17 + </div>
18 +
19 + <!-- Tab 标签页 -->
20 + <div class="bg-white pt-2 sticky top-0 z-10 shadow-sm">
21 + <div class="grid grid-cols-3 items-center border-gray-100 pb-0 relative">
22 + <div v-for="(tab, index) in tabs" :key="index"
23 + class="relative py-3 flex flex-col items-center cursor-pointer z-10" @click="handleTabChange(index)">
24 + <span class="text-base font-medium transition-colors duration-300"
25 + :class="activeTab === index ? 'text-gray-900 font-bold' : 'text-gray-400'">
26 + {{ tab }}
27 + </span>
28 + </div>
29 + <!-- 移动指示器 -->
30 + <div class="absolute bottom-0 h-1.5 w-1/3 transition-transform duration-300 ease-in-out z-10"
31 + :style="{ transform: `translateX(${activeTab * 100}%)` }">
32 + <div class="w-full h-full flex justify-center">
33 + <img :src="indicatorImg" class="w-8 h-1.5" alt="indicator" />
34 + </div>
35 + </div>
36 + </div>
37 +
38 + <!-- 筛选区域 -->
39 + <div class="p-3 bg-white space-y-3">
40 + <!-- 日期选择 -->
41 + <div class="flex items-center justify-between space-x-2">
42 + <div class="flex-1 bg-white border border-gray-200 rounded-lg h-9 flex items-center px-3 cursor-pointer"
43 + @click="showStartDatePicker = true">
44 + <van-icon name="calendar-o" class="text-gray-400 mr-2" />
45 + <span :class="startDate ? 'text-gray-800' : 'text-gray-400'" class="text-sm truncate">
46 + {{ startDate || '开始日期' }}
47 + </span>
48 + </div>
49 + <span class="text-gray-400 text-sm">至</span>
50 + <div class="flex-1 bg-white border border-gray-200 rounded-lg h-9 flex items-center px-3 cursor-pointer"
51 + @click="showEndDatePicker = true">
52 + <van-icon name="calendar-o" class="text-gray-400 mr-2" />
53 + <span :class="endDate ? 'text-gray-800' : 'text-gray-400'" class="text-sm truncate">
54 + {{ endDate || '结束日期' }}
55 + </span>
56 + </div>
57 + </div>
58 +
59 + <!-- 搜索框 -->
60 + <div class="bg-white border border-gray-200 rounded-lg h-9 flex items-center px-3">
61 + <van-icon name="search" class="text-gray-400 mr-2" size="16" />
62 + <input v-model="searchKeyword" type="text" placeholder="搜索活动或课程名称"
63 + class="flex-1 bg-transparent text-sm text-gray-800 placeholder-gray-400 outline-none"
64 + @keyup.enter="onRefresh" />
65 + </div>
66 + </div>
67 + </div>
68 +
69 + <!-- 列表区域 -->
70 + <div class="flex-1 overflow-y-auto p-3">
71 + <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
72 + <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
73 + <div v-for="item in list" :key="item.id" class="bg-white rounded-xl p-4 mb-3 shadow-sm">
74 + <div class="text-gray-900 text-sm font-medium leading-relaxed mb-3 line-clamp-2">
75 + {{ item.title }}
76 + </div>
77 + <div class="flex justify-between items-center">
78 + <div class="flex flex-col">
79 + <span class="text-gray-400 text-xs mb-1">{{ item.date }}</span>
80 + <span class="text-gray-400 text-xs">{{ item.type }}</span>
81 + </div>
82 + <div class="text-lg font-bold" :class="item.isIncome ? 'text-[#2E85FF]' : 'text-[#FF4D4F]'">
83 + {{ item.isIncome ? '+' : '' }}{{ item.amount }}
84 + </div>
85 + </div>
86 + <!-- 分割线 -->
87 + <div v-if="item.id !== list[list.length - 1].id" class="mt-3 border-t border-gray-50 border-dashed"></div>
88 + </div>
89 + </van-list>
90 + </van-pull-refresh>
91 + </div>
92 +
93 + <!-- 日期选择弹窗 -->
94 + <van-popup v-model:show="showStartDatePicker" position="bottom">
95 + <van-date-picker v-model="currentStartDate" title="选择开始日期" :min-date="minDate" :max-date="maxDate"
96 + @confirm="onConfirmStartDate" @cancel="showStartDatePicker = false" />
97 + </van-popup>
98 +
99 + <van-popup v-model:show="showEndDatePicker" position="bottom">
100 + <van-date-picker v-model="currentEndDate" title="选择结束日期" :min-date="minDate" :max-date="maxDate"
101 + @confirm="onConfirmEndDate" @cancel="showEndDatePicker = false" />
102 + </van-popup>
11 </div> 103 </div>
12 </template> 104 </template>
13 105
14 <script setup> 106 <script setup>
15 -import { ref } from 'vue' 107 +import { ref, computed } from 'vue'
16 import { useRoute, useRouter } from 'vue-router' 108 import { useRoute, useRouter } from 'vue-router'
17 import { useTitle } from '@vueuse/core' 109 import { useTitle } from '@vueuse/core'
110 +import { showToast } from 'vant'
111 +import dayjs from 'dayjs'
112 +
113 +// 导入图片
114 +import headerBg from '@/assets/images/recall/962@2x.png'
115 +import bottleImg from '@/assets/images/recall/pz02@2x.png'
116 +import indicatorImg from '@/assets/images/recall/xian@2x.png'
117 +
118 +useTitle('我的积分')
119 +const route = useRoute()
120 +const router = useRouter()
121 +
122 +// 状态
123 +const tabs = ['全部', '已获得', '已消耗']
124 +const activeTab = ref(0)
125 +const searchKeyword = ref('')
126 +const startDate = ref('')
127 +const endDate = ref('')
128 +const showStartDatePicker = ref(false)
129 +const showEndDatePicker = ref(false)
130 +
131 +const now = new Date()
132 +const minDate = new Date(2020, 0, 1)
133 +const maxDate = new Date(2030, 11, 31)
134 +const currentStartDate = ref([now.getFullYear().toString(), (now.getMonth() + 1).toString().padStart(2, '0'), now.getDate().toString().padStart(2, '0')])
135 +const currentEndDate = ref([now.getFullYear().toString(), (now.getMonth() + 1).toString().padStart(2, '0'), now.getDate().toString().padStart(2, '0')])
18 136
19 -const $route = useRoute(); 137 +// 列表相关
20 -const $router = useRouter(); 138 +const list = ref([])
21 -useTitle($route.meta.title); 139 +const loading = ref(false)
140 +const finished = ref(false)
141 +const refreshing = ref(false)
22 142
143 +// 切换Tab
144 +const handleTabChange = (index) => {
145 + activeTab.value = index
146 + onRefresh()
147 +}
148 +
149 +// 日期确认
150 +const onConfirmStartDate = ({ selectedValues }) => {
151 + const dateStr = selectedValues.join('-')
152 + if (endDate.value && dayjs(dateStr).isAfter(dayjs(endDate.value))) {
153 + showToast('开始日期不能晚于结束日期')
154 + return
155 + }
156 + startDate.value = dateStr
157 + showStartDatePicker.value = false
158 + onRefresh()
159 +}
160 +
161 +const onConfirmEndDate = ({ selectedValues }) => {
162 + const dateStr = selectedValues.join('-')
163 + if (startDate.value && dayjs(dateStr).isBefore(dayjs(startDate.value))) {
164 + showToast('结束日期不能早于开始日期')
165 + return
166 + }
167 + endDate.value = dateStr
168 + showEndDatePicker.value = false
169 + onRefresh()
170 +}
171 +
172 +// 模拟数据
173 +const mockData = [
174 + {
175 + id: 1,
176 + title: '2025.11月3日-10日江苏东台养生营,邀您一起进入童话世界!',
177 + date: '2025-11-03',
178 + type: '活动参与奖励',
179 + amount: 6400,
180 + isIncome: true
181 + },
182 + {
183 + id: 2,
184 + title: '【自然的恩典】青少年成长营-贵阳百花湖3(小学初中专场)',
185 + date: '2025-10-03',
186 + type: '活动参与奖励',
187 + amount: 7998,
188 + isIncome: true
189 + },
190 + {
191 + id: 3,
192 + title: '2024年4月22-25日浙江义乌【中华智慧商业应用论坛】',
193 + date: '2024-04-20',
194 + type: '活动参与奖励',
195 + amount: 3200,
196 + isIncome: true
197 + },
198 + {
199 + id: 4,
200 + title: '2023.7.6-7.11【自然的恩典】“爱我中华”优秀传统文化夏令营-天津场',
201 + date: '2023-07-01',
202 + type: '活动参与奖励',
203 + amount: 3990,
204 + isIncome: true
205 + },
206 + {
207 + id: 5,
208 + title: '兑换活动优惠券',
209 + date: '2023-07-01',
210 + type: '兑换奖励',
211 + amount: 200,
212 + isIncome: false
213 + }
214 +]
215 +
216 +// 加载列表
217 +const onLoad = () => {
218 + setTimeout(() => {
219 + if (refreshing.value) {
220 + list.value = []
221 + refreshing.value = false
222 + }
223 +
224 + // 模拟数据加载
225 + const newData = mockData.map(item => ({ ...item, id: item.id + list.value.length }))
226 + list.value.push(...newData)
227 +
228 + loading.value = false
229 +
230 + // 模拟数据加载完毕
231 + if (list.value.length >= 20) {
232 + finished.value = true
233 + }
234 + }, 1000)
235 +}
236 +
237 +const onRefresh = () => {
238 + finished.value = false
239 + loading.value = true
240 + onLoad()
241 +}
23 </script> 242 </script>
24 243
25 <style lang="less" scoped> 244 <style lang="less" scoped>
245 +.animate-float {
246 + animation: float 3s ease-in-out infinite;
247 +}
248 +
249 +@keyframes float {
250 +
251 + 0%,
252 + 100% {
253 + transform: translateY(0);
254 + }
255 +
256 + 50% {
257 + transform: translateY(-5px);
258 + }
259 +}
26 260
261 +:deep(.van-picker__toolbar) {
262 + border-bottom: 1px solid #f5f5f5;
263 +}
27 </style> 264 </style>
......