hookehuyr

feat(recall): 添加用户信息缓存功能并优化活动历史逻辑

- 在IDQueryPage和CompleteInfoPage中添加用户信息缓存到localStorage
- 修改timeline.vue和ActivityHistoryPage.vue从缓存获取用户信息
- 添加批量活动报名接口并实现收集星球币功能
- 优化活动历史日期显示逻辑
1 /* 1 /*
2 * @Date: 2025-12-24 12:26:27 2 * @Date: 2025-12-24 12:26:27
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-24 12:37:23 4 + * @LastEditTime: 2025-12-24 18:36:29
5 * @FilePath: /mlaj/src/api/points.js 5 * @FilePath: /mlaj/src/api/points.js
6 * @Description: 积分相关接口 6 * @Description: 积分相关接口
7 */ 7 */
...@@ -9,6 +9,7 @@ import { fn, fetch } from './fn'; ...@@ -9,6 +9,7 @@ import { fn, fetch } from './fn';
9 9
10 const Api = { 10 const Api = {
11 POINTS_LIST: '/srv/?a=points&t=list', 11 POINTS_LIST: '/srv/?a=points&t=list',
12 + OLD_ACTIVITY_BATCH_ACTIVITY_REGISTRATION: '/srv/?a=points&t=old_activity_batch_activity_registration',
12 } 13 }
13 14
14 /** 15 /**
...@@ -25,3 +26,21 @@ const Api = { ...@@ -25,3 +26,21 @@ const Api = {
25 * } 26 * }
26 */ 27 */
27 export const getPointsListAPI = (params) => fn(fetch.get(Api.POINTS_LIST, params)); 28 export const getPointsListAPI = (params) => fn(fetch.get(Api.POINTS_LIST, params));
29 +
30 +
31 +/**
32 + * @description 召回老客户-批量活动报名
33 + * @param {*} params {
34 + * name: 姓名
35 + * mobile: 手机号
36 + * idcard: 身份证号
37 + * activity_ids: 活动id列表, 逗号分隔
38 + * }
39 + * @returns data: {
40 + * payment_qty: 已报名活动数量
41 + * volunteer_qty: 已报名志愿者数量
42 + * record_date: 报名记录日期
43 + * last_activity_date: 最后活动日期
44 + * }
45 + */
46 +export const oldActivityBatchActivityRegistrationAPI = (params) => fn(fetch.post(Api.OLD_ACTIVITY_BATCH_ACTIVITY_REGISTRATION, params));
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
4 <div class="w-full relative h-[180px] overflow-hidden"> 4 <div class="w-full relative h-[180px] overflow-hidden">
5 <img :src="historyBg" class="w-full h-full object-cover" alt="History Banner" /> 5 <img :src="historyBg" class="w-full h-full object-cover" alt="History Banner" />
6 <div class="absolute inset-0 flex flex-col justify-center items-center text-center px-4"> 6 <div class="absolute inset-0 flex flex-col justify-center items-center text-center px-4">
7 - <h1 class="text-[#FFDD01] text-3xl font-bold mb-2 tracking-wider drop-shadow-md mt-3">2020-2025</h1> 7 + <h1 class="text-[#FFDD01] text-3xl font-bold mb-2 tracking-wider drop-shadow-md mt-3">{{ recordDate }}</h1>
8 <h2 class="text-white text-2xl font-bold mb-4 tracking-wider drop-shadow-md">您的活动历史</h2> 8 <h2 class="text-white text-2xl font-bold mb-4 tracking-wider drop-shadow-md">您的活动历史</h2>
9 9
10 <div class="w-full max-w-md rounded-lg px-4 py-2" 10 <div class="w-full max-w-md rounded-lg px-4 py-2"
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
63 63
64 <!-- Fixed Bottom Buttons --> 64 <!-- Fixed Bottom Buttons -->
65 <div 65 <div
66 + v-if="!userInfo.has_activity_registration"
66 class="fixed bottom-0 left-0 right-0 bg-white/60 backdrop-blur-md p-4 pb-8 z-30 shadow-[0_-2px_10px_rgba(0,0,0,0.05)]"> 67 class="fixed bottom-0 left-0 right-0 bg-white/60 backdrop-blur-md p-4 pb-8 z-30 shadow-[0_-2px_10px_rgba(0,0,0,0.05)]">
67 <van-button block color="#0052D9" class="!rounded-lg !mb-3 !h-[44px] !text-base !font-bold" 68 <van-button block color="#0052D9" class="!rounded-lg !mb-3 !h-[44px] !text-base !font-bold"
68 @click="handleCollectCoins"> 69 @click="handleCollectCoins">
...@@ -106,6 +107,7 @@ import { useTitle } from '@vueuse/core' ...@@ -106,6 +107,7 @@ import { useTitle } from '@vueuse/core'
106 import { showToast } from 'vant' 107 import { showToast } from 'vant'
107 108
108 import { userInfoAPI, searchOldActivityAPI } from '@/api/recall_users' 109 import { userInfoAPI, searchOldActivityAPI } from '@/api/recall_users'
110 +import { oldActivityBatchActivityRegistrationAPI } from '@/api/points'
109 111
110 const historyBg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/history_bg@2x.png' 112 const historyBg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/history_bg@2x.png'
111 113
...@@ -125,9 +127,30 @@ const handleGeneratePoster = (item) => { ...@@ -125,9 +127,30 @@ const handleGeneratePoster = (item) => {
125 // showToast('生成海报: ' + item.title) 127 // showToast('生成海报: ' + item.title)
126 } 128 }
127 129
128 -const handleCollectCoins = () => { 130 +const handleCollectCoins = async () => {
129 - // showToast('收集星球币成功') 131 + // 从缓存获取用户信息
130 - router.push({ path: '/recall/points' }) 132 + const cachedUserInfo = localStorage.getItem('cached_user_info')
133 + if (cachedUserInfo) {
134 + const userInfo = JSON.parse(cachedUserInfo)
135 + // 调用积分接口
136 + const res = await oldActivityBatchActivityRegistrationAPI({
137 + name: userInfo.name || '',
138 + mobile: userInfo.phone || '',
139 + idcard: userInfo.idCard || '',
140 + old_activity_data: campaign_info.value
141 + })
142 + if (res.code) {
143 + showToast({
144 + message: '收集星球币成功',
145 + icon: 'success'
146 + })
147 + router.push({ path: '/recall/points' })
148 + } else {
149 + showToast({
150 + message: '收集星球币失败',
151 + icon: 'error'
152 + })
153 + }
131 } 154 }
132 155
133 const handleSubmitMissing = () => { 156 const handleSubmitMissing = () => {
...@@ -147,16 +170,47 @@ const handleSubmitMissing = () => { ...@@ -147,16 +170,47 @@ const handleSubmitMissing = () => {
147 missingInfo.value = '' 170 missingInfo.value = ''
148 } 171 }
149 172
173 +const userInfo = ref({});
174 +const campaign_info = ref([]);
175 +
176 +// 记录日期
177 +const recordDate = ref('')
178 +
150 onMounted(async () => { 179 onMounted(async () => {
151 - // 获取用户信息 180 + // 从缓存获取用户信息
152 - const userInfoRes = await userInfoAPI() 181 + const cachedUserInfo = localStorage.getItem('cached_user_info')
153 - if (userInfoRes.code) { 182 + if (cachedUserInfo) {
154 - // 通过用户信息获取活动历史信息 183 + const userInfo = JSON.parse(cachedUserInfo)
155 - const activityRes = await searchOldActivityAPI({ 184 + if (userInfo.name && userInfo.phone && userInfo.idCard) {
156 - name: userInfoRes.data?.user_name || '', 185 + // 通过用户信息获取活动历史信息
157 - mobile: userInfoRes.data?.mobile || '', 186 + const activityRes = await searchOldActivityAPI({
158 - idcard: userInfoRes.data?.idcard || '' 187 + name: userInfo.name,
159 - }) 188 + mobile: userInfo.phone,
189 + idcard: userInfo.idCard
190 + })
191 + if (activityRes.code) {
192 + // 获取历史列表数据
193 + const campaign_info = activityRes.data?.campaign_info || []
194 + if (campaign_info.length) {
195 + campaign_info.value = campaign_info;
196 + activities.value = campaign_info.map(item => ({
197 + id: item.campaign_id || 0,
198 + stu_uid: item.stu_uid || '',
199 + title: item.campaign_name || '',
200 + price: item.fee_stu || 0,
201 + count: item.stu_cnt || 0,
202 + date: item.create_time?.substring(0, 10) || '',
203 + total: item.fee_stu * item.stu_cnt || 0
204 + }))
205 + // 遍历日期把列表date字段从小到大排序, 获取到其中日期段 比如 2020-2025
206 + const sortedDates = activities.value.map(item => item.date).sort((a, b) => new Date(a) - new Date(b))
207 + recordDate.value = sortedDates[0]?.substring(0, 4) + '-' + sortedDates[sortedDates.length - 1]?.substring(0, 4) || ''
208 + }
209 + }
210 + }
211 + } else {
212 + // 如果是收集完成没有缓存字段的情况, 直接查接口
213 + const activityRes = await searchOldActivityAPI()
160 if (activityRes.code) { 214 if (activityRes.code) {
161 // 获取历史列表数据 215 // 获取历史列表数据
162 const campaign_info = activityRes.data?.campaign_info || [] 216 const campaign_info = activityRes.data?.campaign_info || []
...@@ -173,6 +227,12 @@ onMounted(async () => { ...@@ -173,6 +227,12 @@ onMounted(async () => {
173 } 227 }
174 } 228 }
175 } 229 }
230 +
231 + // 获取用户信息
232 + const userInfoRes = await userInfoAPI()
233 + if (userInfoRes.code) {
234 + userInfo.value = userInfoRes.data.user || {};
235 + }
176 }) 236 })
177 </script> 237 </script>
178 238
......
...@@ -135,6 +135,13 @@ const handleConfirm = async () => { ...@@ -135,6 +135,13 @@ const handleConfirm = async () => {
135 // 获取历史列表数据 135 // 获取历史列表数据
136 const campaign_info = activityRes.data?.campaign_info || [] 136 const campaign_info = activityRes.data?.campaign_info || []
137 if (campaign_info.length) { 137 if (campaign_info.length) {
138 + // 有历史数据, 保存用户信息到缓存
139 + localStorage.setItem('cached_user_info', JSON.stringify({
140 + name: form.name,
141 + phone: form.phone,
142 + idCard: form.idCard
143 + }));
144 +
138 // 有历史数据, 跳转到timeline 145 // 有历史数据, 跳转到timeline
139 router.push('/recall/timeline') 146 router.push('/recall/timeline')
140 return 147 return
......
...@@ -160,6 +160,13 @@ const handleConfirm = async () => { ...@@ -160,6 +160,13 @@ const handleConfirm = async () => {
160 // 如果能查到数据, 则跳转到timeline, 否则弹出提示 160 // 如果能查到数据, 则跳转到timeline, 否则弹出提示
161 const flag = campaign_info.length > 0; 161 const flag = campaign_info.length > 0;
162 if (flag) { 162 if (flag) {
163 + // 有历史数据, 保存用户信息到缓存
164 + localStorage.setItem('cached_user_info', JSON.stringify({
165 + name: name.value,
166 + phone: phone.value,
167 + idCard: idCard.value
168 + }));
169 +
163 router.push('/recall/timeline') 170 router.push('/recall/timeline')
164 return 171 return
165 } 172 }
......
...@@ -88,10 +88,11 @@ import { ref, computed, onMounted } from 'vue' ...@@ -88,10 +88,11 @@ import { ref, computed, onMounted } from 'vue'
88 import { useRouter, useRoute } from 'vue-router' 88 import { useRouter, useRoute } from 'vue-router'
89 import { showToast } from 'vant' 89 import { showToast } from 'vant'
90 import { useTitle } from '@vueuse/core' 90 import { useTitle } from '@vueuse/core'
91 +// 导入接口
91 import { smsAPI } from '@/api/common' 92 import { smsAPI } from '@/api/common'
92 import { loginAPI, userInfoAPI } from '@/api/recall_users' 93 import { loginAPI, userInfoAPI } from '@/api/recall_users'
93 import { useTracking } from '@/composables/useTracking' 94 import { useTracking } from '@/composables/useTracking'
94 - 95 +// 导入图片
95 const titleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title01@2x.png' 96 const titleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title01@2x.png'
96 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png' 97 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png'
97 98
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
12 12
13 <!-- Title Section --> 13 <!-- Title Section -->
14 <div class="flex flex-col items-center mb-8 animate-fade-in-down"> 14 <div class="flex flex-col items-center mb-8 animate-fade-in-down">
15 - <h2 class="text-[#FFDD01] text-2xl font-bold mb-4 tracking-wider">@{{ userInfo.value.name }}</h2> 15 + <h2 class="text-[#FFDD01] text-2xl font-bold mb-4 tracking-wider">@{{ userInfo.name }}</h2>
16 <img :src="title03" class="w-64 object-contain" alt="Welcome Back" /> 16 <img :src="title03" class="w-64 object-contain" alt="Welcome Back" />
17 </div> 17 </div>
18 18
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
46 <!-- Title Section --> 46 <!-- Title Section -->
47 <div class="flex flex-col items-center mb-10 animate-fade-in-down"> 47 <div class="flex flex-col items-center mb-10 animate-fade-in-down">
48 <img :src="title04" class="w-64 object-contain mb-4" alt="My Footprints" /> 48 <img :src="title04" class="w-64 object-contain mb-4" alt="My Footprints" />
49 - <h2 class="text-[#FFDD01] text-2xl font-bold tracking-wider">@{{ userInfo.value.name }}</h2> 49 + <h2 class="text-[#FFDD01] text-2xl font-bold tracking-wider">@{{ userInfo.name }}</h2>
50 </div> 50 </div>
51 51
52 <!-- Card Section --> 52 <!-- Card Section -->
...@@ -164,16 +164,17 @@ const handleViewHistory = () => { ...@@ -164,16 +164,17 @@ const handleViewHistory = () => {
164 const userInfo = ref({}) 164 const userInfo = ref({})
165 165
166 onMounted(async () => { 166 onMounted(async () => {
167 - // 获取用户信息 167 + // 从缓存获取用户信息
168 - const res = await userInfoAPI() 168 + const cachedUserInfo = localStorage.getItem('cached_user_info')
169 - if (res.code) { 169 + if (cachedUserInfo) {
170 - userInfo.value = res.data || {}; 170 + userInfo.value = JSON.parse(cachedUserInfo)
171 - if (userInfo.value.name && userInfo.value.mobile && userInfo.value.idcard) { 171 +
172 - // 检查是否有历史数据 172 + // 检查是否有历史数据
173 + if (userInfo.value.name && userInfo.value.phone && userInfo.value.idCard) {
173 const res = await searchOldActivityAPI({ 174 const res = await searchOldActivityAPI({
174 name: userInfo.value.name, 175 name: userInfo.value.name,
175 - mobile: userInfo.value.mobile, 176 + mobile: userInfo.value.phone,
176 - idcard: userInfo.value.idcard 177 + idcard: userInfo.value.idCard
177 }) 178 })
178 if (res.code) { 179 if (res.code) {
179 activityCount.value = res.data?.payment_qty || 0 180 activityCount.value = res.data?.payment_qty || 0
...@@ -182,6 +183,15 @@ onMounted(async () => { ...@@ -182,6 +183,15 @@ onMounted(async () => {
182 lastActivityDate.value = res.data?.last_activity_date || '' 183 lastActivityDate.value = res.data?.last_activity_date || ''
183 } 184 }
184 } 185 }
186 + } else {
187 + // 如果是收集完成没有缓存字段的情况, 直接查接口
188 + const res = await searchOldActivityAPI()
189 + if (res.code) {
190 + activityCount.value = res.data?.payment_qty || 0
191 + volunteerCount.value = res.data?.volunteer_qty || 0
192 + recordDate.value = res.data?.record_date || ''
193 + lastActivityDate.value = res.data?.last_activity_date || ''
194 + }
185 } 195 }
186 }) 196 })
187 197
......