feat(recall): 实现用户召回功能相关接口和逻辑
- 添加用户信息获取接口和登录接口 - 完善个人信息页面增加身份证验证和提交逻辑 - 活动历史页面对接真实数据接口 - 时间轴页面显示用户真实数据和活动统计 - 海报页面支持上传和编辑背景图片 - 积分页面显示用户实际积分数据
Showing
10 changed files
with
210 additions
and
89 deletions
| 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> | ... | ... |
-
Please register or login to post a comment