hookehuyr

feat(recall): 实现用户召回功能相关接口和逻辑

- 添加用户信息获取接口和登录接口
- 完善个人信息页面增加身份证验证和提交逻辑
- 活动历史页面对接真实数据接口
- 时间轴页面显示用户真实数据和活动统计
- 海报页面支持上传和编辑背景图片
- 积分页面显示用户实际积分数据
1 /* 1 /*
2 * @Date: 2025-12-19 10:43:09 2 * @Date: 2025-12-19 10:43:09
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-24 16:01:49 4 + * @LastEditTime: 2025-12-24 17:19:47
5 * @FilePath: /mlaj/src/api/recall_users.js 5 * @FilePath: /mlaj/src/api/recall_users.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 USER_LOGIN: '/srv/?a=user_login', 11 USER_LOGIN: '/srv/?a=user_login',
12 + USER_INFO: '/srv/?a=desk_calendar&t=user_info',
12 USER_SEARCH_OLD_ACTIVITY: '/srv/?a=desk_calendar&t=search_old_activity', 13 USER_SEARCH_OLD_ACTIVITY: '/srv/?a=desk_calendar&t=search_old_activity',
13 USER_GET_POSTER: '/srv/?a=desk_calendar&t=get_poster', 14 USER_GET_POSTER: '/srv/?a=desk_calendar&t=get_poster',
14 USER_EDIT_POSTER: '/srv/?a=desk_calendar&t=edit_poster', 15 USER_EDIT_POSTER: '/srv/?a=desk_calendar&t=edit_poster',
...@@ -18,11 +19,16 @@ const Api = { ...@@ -18,11 +19,16 @@ const Api = {
18 * @description: 用户登录 19 * @description: 用户登录
19 * @param: mobile 手机号 20 * @param: mobile 手机号
20 * @param: password 用户密码 21 * @param: password 用户密码
21 - * @return: data: { user_info 用户信息 }
22 */ 22 */
23 export const loginAPI = (params) => fn(fetch.post(Api.USER_LOGIN, params)); 23 export const loginAPI = (params) => fn(fetch.post(Api.USER_LOGIN, params));
24 24
25 /** 25 /**
26 + * @description: 获取用户信息
27 + * @return: data.user: { id 用户ID, name 姓名, mobile 手机号, has_idcard 是否填写了身份证, has_activity_registration 是否收集了星球壁币 }
28 + */
29 +export const userInfoAPI = () => fn(fetch.get(Api.USER_INFO));
30 +
31 +/**
26 * @description: 搜索历史活动 32 * @description: 搜索历史活动
27 * @param: name 姓名 33 * @param: name 姓名
28 * @param: mobile 手机号 34 * @param: mobile 手机号
......
1 /* 1 /*
2 * @Date: 2025-03-23 23:45:53 2 * @Date: 2025-03-23 23:45:53
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-03-26 16:09:46 4 + * @LastEditTime: 2025-12-24 17:10:13
5 * @FilePath: /mlaj/src/api/users.js 5 * @FilePath: /mlaj/src/api/users.js
6 * @Description: 用户相关接口 6 * @Description: 用户相关接口
7 */ 7 */
...@@ -48,6 +48,9 @@ export const getUserInfoAPI = () => fn(fetch.get(Api.USER_INFO)); ...@@ -48,6 +48,9 @@ export const getUserInfoAPI = () => fn(fetch.get(Api.USER_INFO));
48 * @description: 更新用户信息 48 * @description: 更新用户信息
49 * @param: name 用户名称 49 * @param: name 用户名称
50 * @param: avatar 头像 50 * @param: avatar 头像
51 + * @param: mobile 手机号
52 + * @param: sms_code 短信验证码
53 + * @param: idcard 身份证号
51 */ 54 */
52 export const updateUserInfoAPI = (params) => fn(fetch.post(Api.USER_UPDATE, params)); 55 export const updateUserInfoAPI = (params) => fn(fetch.post(Api.USER_UPDATE, params));
53 56
......
...@@ -85,6 +85,11 @@ const handleUsernameChange = async () => { ...@@ -85,6 +85,11 @@ const handleUsernameChange = async () => {
85 }; 85 };
86 // 更新localStorage中的用户信息 86 // 更新localStorage中的用户信息
87 localStorage.setItem('currentUser', JSON.stringify(currentUser.value)); 87 localStorage.setItem('currentUser', JSON.stringify(currentUser.value));
88 + // 更新司总缓存user_info键里面的user_name的值改成新的用户名
89 + localStorage.setItem('user_info', JSON.stringify({
90 + ...JSON.parse(localStorage.getItem('user_info') || '{}'),
91 + user_name: username.value
92 + }));
88 showToast('用户名修改成功'); 93 showToast('用户名修改成功');
89 } 94 }
90 } catch (error) { 95 } catch (error) {
......
...@@ -100,61 +100,29 @@ ...@@ -100,61 +100,29 @@
100 </template> 100 </template>
101 101
102 <script setup> 102 <script setup>
103 -import { ref } from 'vue' 103 +import { ref, onMounted } from 'vue'
104 import { useRouter } from 'vue-router' 104 import { useRouter } from 'vue-router'
105 import { useTitle } from '@vueuse/core' 105 import { useTitle } from '@vueuse/core'
106 import { showToast } from 'vant' 106 import { showToast } from 'vant'
107 107
108 +import { userInfoAPI, searchOldActivityAPI } from '@/api/recall_users'
109 +
108 const historyBg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/history_bg@2x.png' 110 const historyBg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/history_bg@2x.png'
109 111
110 const router = useRouter() 112 const router = useRouter()
111 useTitle('活动历史') 113 useTitle('活动历史')
112 114
113 -// Mock Data 115 +
114 -const activities = ref([ 116 +const activities = ref([])
115 - {
116 - id: 1,
117 - title: '2025.11月3日-10日江苏东台养生营,邀您一起进入童话世界!',
118 - price: 3200.00,
119 - count: 2,
120 - date: '2025-11-03',
121 - total: 6400
122 - },
123 - {
124 - id: 2,
125 - title: '【自然的恩典】青少年成长营-贵阳百花湖3(小学初中专场)',
126 - price: 3999.00,
127 - count: 2,
128 - date: '2025-10-03',
129 - total: 7998
130 - },
131 - {
132 - id: 3,
133 - title: '2024年4月22-25日浙江义乌【中华智慧商业应用论坛】',
134 - price: 3200.00,
135 - count: 1,
136 - date: '2024-04-03',
137 - total: 3200
138 - },
139 - {
140 - id: 4,
141 - title: '2023.7.6-7.11【自然的恩典】“爱我中华”优秀传统文化夏令营-天津场',
142 - price: 3990.00,
143 - count: 1,
144 - date: '2023-07-01',
145 - total: 3990
146 - }
147 -])
148 117
149 // State 118 // State
150 const showMissingPopup = ref(false) 119 const showMissingPopup = ref(false)
151 const missingInfo = ref('') 120 const missingInfo = ref('')
152 121
153 -// Actions 122 +// 处理生成海报
154 const handleGeneratePoster = (item) => { 123 const handleGeneratePoster = (item) => {
155 - // 跳转到/recall/poster?id=item.id 124 + router.push({ path: '/recall/poster', query: { id: item.id, stu_uid: item.stu_uid, campaign_id: item.campaign_id } })
156 - router.push({ path: '/recall/poster', query: { id: item.id } }) 125 + // showToast('生成海报: ' + item.title)
157 - showToast('生成海报: ' + item.title)
158 } 126 }
159 127
160 const handleCollectCoins = () => { 128 const handleCollectCoins = () => {
...@@ -179,6 +147,33 @@ const handleSubmitMissing = () => { ...@@ -179,6 +147,33 @@ const handleSubmitMissing = () => {
179 missingInfo.value = '' 147 missingInfo.value = ''
180 } 148 }
181 149
150 +onMounted(async () => {
151 + // 获取用户信息
152 + const userInfoRes = await userInfoAPI()
153 + if (userInfoRes.code) {
154 + // 通过用户信息获取活动历史信息
155 + const activityRes = await searchOldActivityAPI({
156 + name: userInfoRes.data?.user_name || '',
157 + mobile: userInfoRes.data?.mobile || '',
158 + idcard: userInfoRes.data?.idcard || ''
159 + })
160 + if (activityRes.code) {
161 + // 获取历史列表数据
162 + const campaign_info = activityRes.data?.campaign_info || []
163 + if (campaign_info.length) {
164 + activities.value = campaign_info.map(item => ({
165 + id: item.campaign_id || 0,
166 + stu_uid: item.stu_uid || '',
167 + title: item.campaign_name || '',
168 + price: item.fee_stu || 0,
169 + count: item.stu_cnt || 0,
170 + date: item.create_time?.substring(0, 10) || '',
171 + total: item.fee_stu * item.stu_cnt || 0
172 + }))
173 + }
174 + }
175 + }
176 +})
182 </script> 177 </script>
183 178
184 <style lang="less" scoped> 179 <style lang="less" scoped>
......
...@@ -75,6 +75,8 @@ import { ref, reactive } from 'vue' ...@@ -75,6 +75,8 @@ import { ref, reactive } from 'vue'
75 import { useRoute, useRouter } from 'vue-router' 75 import { useRoute, useRouter } from 'vue-router'
76 import { useTitle } from '@vueuse/core' 76 import { useTitle } from '@vueuse/core'
77 import { showToast } from 'vant' 77 import { showToast } from 'vant'
78 +import { updateUserInfoAPI } from '@/api/users'
79 +import { searchOldActivityAPI } from '@/api/recall_users'
78 80
79 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png' 81 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png'
80 const starImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/xing@2x.png' 82 const starImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/xing@2x.png'
...@@ -92,7 +94,7 @@ const form = reactive({ ...@@ -92,7 +94,7 @@ const form = reactive({
92 }) 94 })
93 95
94 // Actions 96 // Actions
95 -const handleConfirm = () => { 97 +const handleConfirm = async () => {
96 if (!form.name) { 98 if (!form.name) {
97 showToast('请输入姓名') 99 showToast('请输入姓名')
98 return 100 return
...@@ -113,16 +115,36 @@ const handleConfirm = () => { ...@@ -113,16 +115,36 @@ const handleConfirm = () => {
113 return 115 return
114 } 116 }
115 117
116 - // Validation passed 118 + const res = await updateUserInfoAPI({
117 - // showToast('提交成功') 119 + name: form.name,
118 - // TODO: 完善信息之后, 如果能查看历史数据, 则需要跳转到timeline 120 + mobile: form.phone,
119 - const flag = false; 121 + idcard: form.idCard
120 - if (flag) { 122 + })
121 - router.push('/recall/timeline') 123 + if (res.code) {
122 - return 124 + const activityRes = await searchOldActivityAPI({
125 + name: form.name,
126 + mobile: form.phone,
127 + idcard: form.idCard
128 + })
129 + if (activityRes.code) {
130 + // 更新司总缓存user_info键里面的user_name的值改成新的用户名
131 + localStorage.setItem('user_info', JSON.stringify({
132 + ...JSON.parse(localStorage.getItem('user_info') || '{}'),
133 + user_name: form.name
134 + }));
135 + // 获取历史列表数据
136 + const campaign_info = activityRes.data?.campaign_info || []
137 + if (campaign_info.length) {
138 + // 有历史数据, 跳转到timeline
139 + router.push('/recall/timeline')
140 + return
141 + } else {
142 + // 没有历史数据, 跳转到id-query, 继续查数据
143 + router.push('/recall/id-query')
144 + return
145 + }
146 + }
123 } 147 }
124 - // 如果不能查看历史数据, 则需要跳转到id-query, 继续查数据
125 - router.push('/recall/id-query')
126 } 148 }
127 </script> 149 </script>
128 150
......
...@@ -102,6 +102,7 @@ import { ref } from 'vue' ...@@ -102,6 +102,7 @@ import { ref } from 'vue'
102 import { useRoute, useRouter } from 'vue-router' 102 import { useRoute, useRouter } from 'vue-router'
103 import { useTitle } from '@vueuse/core' 103 import { useTitle } from '@vueuse/core'
104 import { showToast } from 'vant' 104 import { showToast } from 'vant'
105 +import { searchOldActivityAPI } from '@/api/recall_users'
105 106
106 // Assets 107 // Assets
107 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png' 108 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png'
...@@ -123,7 +124,7 @@ const validateIdCard = (id) => { ...@@ -123,7 +124,7 @@ const validateIdCard = (id) => {
123 return reg.test(id) 124 return reg.test(id)
124 } 125 }
125 126
126 -const handleConfirm = () => { 127 +const handleConfirm = async () => {
127 if (!name.value) { 128 if (!name.value) {
128 showToast('请输入姓名') 129 showToast('请输入姓名')
129 return 130 return
...@@ -149,15 +150,21 @@ const handleConfirm = () => { ...@@ -149,15 +150,21 @@ const handleConfirm = () => {
149 return 150 return
150 } 151 }
151 152
152 - // TODO: Submit logic 153 + const res = await searchOldActivityAPI({
153 - // showToast('验证通过') 154 + name: name.value,
154 - // TODO: 如果能查到数据, 则跳转到timeline, 否则弹出提示 155 + mobile: phone.value,
155 - const flag = true; 156 + idcard: idCard.value
156 - if (flag) { 157 + })
157 - router.push('/recall/timeline') 158 + if (res.code) {
158 - return 159 + const campaign_info = res.data?.campaign_info || []
160 + // 如果能查到数据, 则跳转到timeline, 否则弹出提示
161 + const flag = campaign_info.length > 0;
162 + if (flag) {
163 + router.push('/recall/timeline')
164 + return
165 + }
166 + showToast('时光机没有找到您的历史活动信息')
159 } 167 }
160 - showToast('时光机没有找到您的历史活动信息')
161 } 168 }
162 </script> 169 </script>
163 170
......
...@@ -81,7 +81,7 @@ ...@@ -81,7 +81,7 @@
81 <div class="mb-2 bg-white/10 backdrop-blur-md rounded-2xl p-4 text-center border border-white/20 shadow-lg flex-shrink-0"> 81 <div class="mb-2 bg-white/10 backdrop-blur-md rounded-2xl p-4 text-center border border-white/20 shadow-lg flex-shrink-0">
82 <p class="text-white text-sm mb-1 opacity-90">您成功收集到</p> 82 <p class="text-white text-sm mb-1 opacity-90">您成功收集到</p>
83 <div class="flex items-baseline justify-center mb-1"> 83 <div class="flex items-baseline justify-center mb-1">
84 - <span class="text-yellow-400 text-3xl font-bold mr-2 tracking-wider">15,800</span> 84 + <span class="text-yellow-400 text-3xl font-bold mr-2 tracking-wider">{{ totalPoints || 0 }}</span>
85 <span class="text-white text-sm opacity-90">星球币</span> 85 <span class="text-white text-sm opacity-90">星球币</span>
86 </div> 86 </div>
87 <p class="text-white text-[12px] scale-90">基于您的历史活动自动计算所得 1元积1分</p> 87 <p class="text-white text-[12px] scale-90">基于您的历史活动自动计算所得 1元积1分</p>
...@@ -117,6 +117,9 @@ ...@@ -117,6 +117,9 @@
117 <script setup> 117 <script setup>
118 import { useTitle } from '@vueuse/core' 118 import { useTitle } from '@vueuse/core'
119 import { useRouter } from 'vue-router' 119 import { useRouter } from 'vue-router'
120 +import { ref, onMounted } from 'vue'
121 +
122 +import { getPointsListAPI } from '@/api/points'
120 123
121 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png' 124 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png'
122 const ppImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/pp@2x.png' 125 const ppImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/pp@2x.png'
...@@ -141,14 +144,24 @@ const handleExplore = () => { ...@@ -141,14 +144,24 @@ const handleExplore = () => {
141 // 路由跳转逻辑 144 // 路由跳转逻辑
142 router.push('/') 145 router.push('/')
143 } 146 }
147 +
148 +const totalPoints = ref(0);
149 +
150 +onMounted(async () => {
151 + const res = await getPointsListAPI()
152 + if (res.code) {
153 + // 需要格式化一下, 变成千分号显示
154 + totalPoints.value = Number(res.data?.balance || 0).toLocaleString();
155 + }
156 +})
144 </script> 157 </script>
145 158
146 <style lang="less" scoped> 159 <style lang="less" scoped>
147 .submit-btn { 160 .submit-btn {
148 // Add a subtle gradient for better visual 161 // Add a subtle gradient for better visual
149 - background: linear-gradient(180deg, rgba(249, 243, 157, 0.19) 0%, rgba(219, 243, 48, 0.3) 100%) !important; 162 + background: linear-gradient(180deg, rgba(249, 243, 157, 0.19) 0%, rgba(219, 243, 48, 0.3) 100%) !important;
150 - backdrop-filter: blur(4px); 163 + backdrop-filter: blur(4px);
151 - box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); 164 + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
152 } 165 }
153 .shadow-text { 166 .shadow-text {
154 text-shadow: 0 2px 4px rgba(0,0,0,0.3); 167 text-shadow: 0 2px 4px rgba(0,0,0,0.3);
......
1 <!-- 1 <!--
2 * @Date: 2025-12-23 15:50:59 2 * @Date: 2025-12-23 15:50:59
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-12-23 17:07:47 4 + * @LastEditTime: 2025-12-24 17:47:23
5 * @FilePath: /mlaj/src/views/recall/PosterPage.vue 5 * @FilePath: /mlaj/src/views/recall/PosterPage.vue
6 * @Description: 分享海报页面 6 * @Description: 分享海报页面
7 --> 7 -->
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
33 </template> 33 </template>
34 34
35 <script setup> 35 <script setup>
36 -import { ref } from 'vue' 36 +import { ref, onMounted } from 'vue'
37 import { useRoute, useRouter } from 'vue-router' 37 import { useRoute, useRouter } from 'vue-router'
38 import { useTitle } from '@vueuse/core' 38 import { useTitle } from '@vueuse/core'
39 import RecallPoster from '@/components/ui/RecallPoster.vue' 39 import RecallPoster from '@/components/ui/RecallPoster.vue'
...@@ -42,6 +42,8 @@ import { showToast, showLoadingToast } from 'vant' ...@@ -42,6 +42,8 @@ import { showToast, showLoadingToast } from 'vant'
42 import { qiniuFileHash } from '@/utils/qiniuFileHash' 42 import { qiniuFileHash } from '@/utils/qiniuFileHash'
43 import { useAuth } from '@/contexts/auth' 43 import { useAuth } from '@/contexts/auth'
44 44
45 +import { getPosterAPI, editPosterAPI } from '@/api/recall'
46 +
45 const $route = useRoute(); 47 const $route = useRoute();
46 const $router = useRouter(); 48 const $router = useRouter();
47 const { currentUser } = useAuth(); 49 const { currentUser } = useAuth();
...@@ -103,6 +105,10 @@ const afterRead = async (file) => { ...@@ -103,6 +105,10 @@ const afterRead = async (file) => {
103 // 文件已存在,直接使用 105 // 文件已存在,直接使用
104 if (tokenResult.data) { 106 if (tokenResult.data) {
105 posterBg.value = tokenResult.data.src; 107 posterBg.value = tokenResult.data.src;
108 + // 编辑海报配置
109 + await editPosterAPI({
110 + background_image: tokenResult.data.src
111 + })
106 showToast('图片上传成功'); 112 showToast('图片上传成功');
107 return; 113 return;
108 } 114 }
...@@ -132,6 +138,10 @@ const afterRead = async (file) => { ...@@ -132,6 +138,10 @@ const afterRead = async (file) => {
132 138
133 if (data) { 139 if (data) {
134 posterBg.value = data.src; 140 posterBg.value = data.src;
141 + // 编辑海报配置
142 + await editPosterAPI({
143 + background_image: data.src
144 + })
135 showToast('图片上传成功'); 145 showToast('图片上传成功');
136 } 146 }
137 } 147 }
...@@ -143,6 +153,24 @@ const afterRead = async (file) => { ...@@ -143,6 +153,24 @@ const afterRead = async (file) => {
143 toast.close(); 153 toast.close();
144 } 154 }
145 } 155 }
156 +
157 +onMounted(async () => {
158 + const stu_uid = $route.query.stu_uid
159 + const campaign_id = $route.query.campaign_id
160 +
161 + if (stu_uid && campaign_id) {
162 + const { data } = await getPosterAPI({
163 + stu_uid,
164 + campaign_id
165 + })
166 +
167 + if (data) {
168 + title.value = $route.query.title || '活动海报'
169 + posterBg.value = data.background_image || defaultBg
170 + qrCodeUrl.value = data.qrcode
171 + }
172 + }
173 +})
146 </script> 174 </script>
147 175
148 <style lang="less" scoped> 176 <style lang="less" scoped>
......
...@@ -89,6 +89,7 @@ import { useRouter, useRoute } from 'vue-router' ...@@ -89,6 +89,7 @@ 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 import { smsAPI } from '@/api/common' 91 import { smsAPI } from '@/api/common'
92 +import { loginAPI, userInfoAPI } from '@/api/recall_users'
92 import { useTracking } from '@/composables/useTracking' 93 import { useTracking } from '@/composables/useTracking'
93 94
94 const titleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title01@2x.png' 95 const titleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title01@2x.png'
...@@ -161,7 +162,7 @@ const handleSendCode = async () => { ...@@ -161,7 +162,7 @@ const handleSendCode = async () => {
161 /** 162 /**
162 * @description 登录/下一步 163 * @description 登录/下一步
163 */ 164 */
164 -const handleLogin = () => { 165 +const handleLogin = async () => {
165 // TAG: 埋点, 登录/下一步按钮点击 166 // TAG: 埋点, 登录/下一步按钮点击
166 trackClick('login_next_btn', { phone: phone.value }) 167 trackClick('login_next_btn', { phone: phone.value })
167 168
...@@ -174,14 +175,29 @@ const handleLogin = () => { ...@@ -174,14 +175,29 @@ const handleLogin = () => {
174 return 175 return
175 } 176 }
176 177
177 - // showToast('登录成功') 178 + try {
178 - // TODO: 登录之后需要判断是否有完善个人信息, 如果没有进入完善流程, 如果完成直接跳到timeline 179 + const res = await loginAPI({ mobile: phone.value, sms_code: code.value })
179 - const info = false; 180 + if (res.code) {
180 - if (info) { 181 + const userInfo = await userInfoAPI()
181 - $router.push('/recall/timeline') 182 + // 登录之后需要判断是否有完善个人信息
182 - return 183 + if (userInfo.code) {
184 + const info = userInfo.data.user || '';
185 + if (!info.has_idcard) { // 如果【查询我的信息】没有填写过身份证(has_idcard 为 false),则进入完善个人信息页
186 + $router.push('/recall/boot')
187 + } else { // 如果【查询我的信息】填写过身份证(has_idcard 为 true)
188 + if (!info.has_activity_registration) { // 如果【查询我的信息】中没有兑换过星球币(has_activity_registration 为 false),则进入二次查询历史活动页
189 + $router.push('/recall/id-query')
190 + } else { // 如果【查询我的信息】中兑换过星球币(has_activity_registration 为 true),则进入时光机页面
191 + $router.push('/recall/timeline')
192 + }
193 + }
194 + }
195 + }
196 + }
197 + catch (error) {
198 + console.error('登录失败:', error)
199 + showToast('登录失败,请稍后重试')
183 } 200 }
184 - $router.push('/recall/boot')
185 } 201 }
186 202
187 // Agreement Content Structure 203 // Agreement Content Structure
......
...@@ -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">@{{ userName }}</h2> 15 + <h2 class="text-[#FFDD01] text-2xl font-bold mb-4 tracking-wider">@{{ userInfo.value.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">@{{ userName }}</h2> 49 + <h2 class="text-[#FFDD01] text-2xl font-bold tracking-wider">@{{ userInfo.value.name }}</h2>
50 </div> 50 </div>
51 51
52 <!-- Card Section --> 52 <!-- Card Section -->
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
86 </template> 86 </template>
87 87
88 <script setup> 88 <script setup>
89 -import { ref, computed } from 'vue' 89 +import { ref, computed, onMounted } from 'vue'
90 import { useRouter } from 'vue-router' 90 import { useRouter } from 'vue-router'
91 import { useTitle } from '@vueuse/core' 91 import { useTitle } from '@vueuse/core'
92 import dayjs from 'dayjs' 92 import dayjs from 'dayjs'
...@@ -94,6 +94,8 @@ import { Swiper, SwiperSlide } from 'swiper/vue' ...@@ -94,6 +94,8 @@ import { Swiper, SwiperSlide } from 'swiper/vue'
94 import { Mousewheel } from 'swiper/modules' 94 import { Mousewheel } from 'swiper/modules'
95 import 'swiper/css' 95 import 'swiper/css'
96 96
97 +import { userInfoAPI, searchOldActivityAPI } from '@/api/recall_users'
98 +
97 99
98 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png' 100 const bgImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/bg01@2x.png'
99 const title03 = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title03@2x.png' 101 const title03 = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title03@2x.png'
...@@ -122,9 +124,13 @@ const onSlideChange = () => { ...@@ -122,9 +124,13 @@ const onSlideChange = () => {
122 } 124 }
123 125
124 // Data Logic 126 // Data Logic
125 -const userName = computed(() => currentUser.value?.name || '张开心') // Fallback to design mock name 127 +const recordDate = ref('')
128 +const lastActivityDate = ref('')
129 +const activityCount = ref(0)
130 +const volunteerCount = ref(0)
131 +
126 const joinDate = computed(() => { 132 const joinDate = computed(() => {
127 - return currentUser.value?.created_at || currentUser.value?.reg_time || '2020-09-12' // Fallback to design mock date 133 + return recordDate.value || currentUser.value?.created_at || currentUser.value?.reg_time || '2020-09-12' // Fallback to design mock date
128 }) 134 })
129 135
130 const joinYear = computed(() => dayjs(joinDate.value).year()) 136 const joinYear = computed(() => dayjs(joinDate.value).year())
...@@ -135,12 +141,12 @@ const joinDateFormatted = computed(() => { ...@@ -135,12 +141,12 @@ const joinDateFormatted = computed(() => {
135 141
136 const durationString = computed(() => { 142 const durationString = computed(() => {
137 const start = dayjs(joinDate.value) 143 const start = dayjs(joinDate.value)
138 - const now = dayjs() 144 + const end = lastActivityDate.value ? dayjs(lastActivityDate.value) : dayjs()
139 - const years = now.diff(start, 'year') 145 + const years = end.diff(start, 'year')
140 - const months = now.diff(start, 'month') % 12 146 + const months = end.diff(start, 'month') % 12
141 147
142 if (years === 0 && months === 0) { 148 if (years === 0 && months === 0) {
143 - const days = now.diff(start, 'day'); 149 + const days = end.diff(start, 'day');
144 return `${days}天`; 150 return `${days}天`;
145 } 151 }
146 152
...@@ -151,14 +157,34 @@ const durationString = computed(() => { ...@@ -151,14 +157,34 @@ const durationString = computed(() => {
151 } 157 }
152 }) 158 })
153 159
154 -// Mock Stats
155 -const activityCount = ref(20)
156 -const volunteerCount = ref(12)
157 -
158 const handleViewHistory = () => { 160 const handleViewHistory = () => {
159 router.push('/recall/activity-history') 161 router.push('/recall/activity-history')
160 } 162 }
161 163
164 +const userInfo = ref({})
165 +
166 +onMounted(async () => {
167 + // 获取用户信息
168 + const res = await userInfoAPI()
169 + if (res.code) {
170 + userInfo.value = res.data || {};
171 + if (userInfo.value.name && userInfo.value.mobile && userInfo.value.idcard) {
172 + // 检查是否有历史数据
173 + const res = await searchOldActivityAPI({
174 + name: userInfo.value.name,
175 + mobile: userInfo.value.mobile,
176 + idcard: userInfo.value.idcard
177 + })
178 + if (res.code) {
179 + activityCount.value = res.data?.payment_qty || 0
180 + volunteerCount.value = res.data?.volunteer_qty || 0
181 + recordDate.value = res.data?.record_date || ''
182 + lastActivityDate.value = res.data?.last_activity_date || ''
183 + }
184 + }
185 + }
186 +})
187 +
162 </script> 188 </script>
163 189
164 <style lang="less" scoped> 190 <style lang="less" scoped>
......