hookehuyr

feat(活动历史): 添加活动申请记录弹窗组件及API支持

新增ActivityApplyHistoryPopup组件用于展示和管理活动申请记录
添加supplement_list、supplement_add、supplement_edit、supplement_del等API接口
重构ActivityHistoryPage页面,替换原有补充信息弹窗为新的申请记录弹窗
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-31 11:42:58 4 + * @LastEditTime: 2026-01-04 14:05:54
5 * @FilePath: /mlaj/src/api/recall_users.js 5 * @FilePath: /mlaj/src/api/recall_users.js
6 * @Description: 引入外部接口, 召回旧用户相关接口 6 * @Description: 引入外部接口, 召回旧用户相关接口
7 */ 7 */
...@@ -17,6 +17,10 @@ const Api = { ...@@ -17,6 +17,10 @@ const Api = {
17 USER_EDIT_SUPPLEMENT: '/srv/?a=desk_calendar&t=edit_supplement', 17 USER_EDIT_SUPPLEMENT: '/srv/?a=desk_calendar&t=edit_supplement',
18 USER_TRACKING: '/srv/?a=desk_calendar&t=tracking', 18 USER_TRACKING: '/srv/?a=desk_calendar&t=tracking',
19 USER_GET_QRCODE: '/srv/?a=desk_calendar&t=get_qrcode', 19 USER_GET_QRCODE: '/srv/?a=desk_calendar&t=get_qrcode',
20 + USER_GET_SUPPLEMENT_LIST: '/srv/?a=desk_calendar&t=supplement_list',
21 + USER_SUPPLEMENT_ADD: '/srv/?a=desk_calendar&t=supplement_add',
22 + USER_SUPPLEMENT_EDIT: '/srv/?a=desk_calendar&t=supplement_edit',
23 + USER_SUPPLEMENT_DEL: '/srv/?a=desk_calendar&t=supplement_del',
20 } 24 }
21 25
22 /** 26 /**
...@@ -89,3 +93,34 @@ export const trackingAPI = (params) => request(fetch.post(Api.USER_TRACKING, par ...@@ -89,3 +93,34 @@ export const trackingAPI = (params) => request(fetch.post(Api.USER_TRACKING, par
89 * @return: data: { id, title, content_url} 93 * @return: data: { id, title, content_url}
90 */ 94 */
91 export const getQrcodeAPI = (params) => request(fetch.get(Api.USER_GET_QRCODE, params)); 95 export const getQrcodeAPI = (params) => request(fetch.get(Api.USER_GET_QRCODE, params));
96 +
97 +/**
98 + * @description: 找不到的活动的列表
99 + * @return: data: [{ id 补充信息ID, title 活动名称, year 活动年份, address 活动地址, amount 活动金额 }]
100 + */
101 +export const getSupplementListAPI = (params) => request(fetch.get(Api.USER_GET_SUPPLEMENT_LIST, params));
102 +
103 +/**
104 + * @description: 新建找不到的活动
105 + * @param: title 活动名称
106 + * @param: year 活动年份
107 + * @param: address 活动地址
108 + * @param: amount 活动金额
109 + */
110 +export const supplementAddAPI = (params) => request(fetch.post(Api.USER_SUPPLEMENT_ADD, params));
111 +
112 +/**
113 + * @description: 编辑找不到的活动
114 + * @param: id 补充信息ID
115 + * @param: title 活动名称
116 + * @param: year 活动年份
117 + * @param: address 活动地址
118 + * @param: amount 活动金额
119 + */
120 +export const supplementEditAPI = (params) => request(fetch.post(Api.USER_SUPPLEMENT_EDIT, params));
121 +
122 +/**
123 + * @description: 删除找不到的活动
124 + * @param: id 补充信息ID
125 + */
126 +export const supplementDelAPI = (params) => request(fetch.post(Api.USER_SUPPLEMENT_DEL, params));
......
...@@ -8,6 +8,7 @@ export {} ...@@ -8,6 +8,7 @@ export {}
8 /* prettier-ignore */ 8 /* prettier-ignore */
9 declare module 'vue' { 9 declare module 'vue' {
10 export interface GlobalComponents { 10 export interface GlobalComponents {
11 + ActivityApplyHistoryPopup: typeof import('./components/ui/ActivityApplyHistoryPopup.vue')['default']
11 ActivityCard: typeof import('./components/ui/ActivityCard.vue')['default'] 12 ActivityCard: typeof import('./components/ui/ActivityCard.vue')['default']
12 AddTargetDialog: typeof import('./components/count/AddTargetDialog.vue')['default'] 13 AddTargetDialog: typeof import('./components/count/AddTargetDialog.vue')['default']
13 AppLayout: typeof import('./components/layout/AppLayout.vue')['default'] 14 AppLayout: typeof import('./components/layout/AppLayout.vue')['default']
......
1 +<template>
2 + <van-popup
3 + :show="show"
4 + @update:show="(v) => emit('update:show', v)"
5 + position="bottom"
6 + :style="{ width: '100%', height: '100%' }"
7 + :lock-scroll="true"
8 + >
9 + <div class="h-full w-full flex flex-col bg-[#F7F8FA]">
10 + <div class="bg-white px-4 py-3 flex items-center justify-between shadow-sm">
11 + <div class="flex items-center gap-3">
12 + <!-- <van-icon name="arrow-left" class="text-[#333]" @click="emit('update:show', false)" /> -->
13 + <div class="text-[#333] font-bold text-base">我的申请记录</div>
14 + </div>
15 + <van-icon name="cross" class="text-[#999]" @click="emit('update:show', false)" />
16 + </div>
17 +
18 + <div class="bg-white px-4 py-3 flex justify-between items-center sticky top-0 z-20 shadow-sm">
19 + <span class="text-[#333] font-medium text-sm">活动记录</span>
20 + <div class="flex items-center text-[#666] text-sm">
21 + <span>{{ apply_records.length }}条</span>
22 + </div>
23 + </div>
24 +
25 + <div class="flex-1 overflow-y-auto px-4 py-4 space-y-4">
26 + <div
27 + v-for="item in apply_records"
28 + :key="item.id"
29 + class="bg-white rounded-xl py-4 pr-4 pl-0 shadow-sm relative overflow-hidden flex"
30 + >
31 + <div class="w-1 h-4 bg-[#0052D9] rounded-r-sm mt-1 shrink-0 self-start"></div>
32 +
33 + <div class="pl-3 flex-1">
34 + <h3 class="text-[#333] font-bold text-base leading-snug">
35 + {{ item.name }}
36 + </h3>
37 +
38 + <div class="border-t border-dashed border-gray-200 my-3"></div>
39 +
40 + <div class="flex justify-between items-center text-xs text-[#666] mb-2">
41 + <span>活动年份: {{ item.year }}</span>
42 + <span class="text-[#FF3B30] font-bold">实付金额: {{ format_paid_amount(item.paid_amount) }}</span>
43 + </div>
44 +
45 + <div class="flex justify-between items-center text-xs text-[#666] mb-4">
46 + <span>活动地点: {{ item.location }}</span>
47 + </div>
48 +
49 + <div class="flex justify-end gap-2">
50 + <van-button
51 + size="small"
52 + plain
53 + color="#0052D9"
54 + class="!rounded-full !px-4 !h-[28px] !text-xs"
55 + @click="open_edit(item)"
56 + >
57 + 修改
58 + </van-button>
59 + <van-button
60 + size="small"
61 + plain
62 + color="#FF3B30"
63 + class="!rounded-full !px-4 !h-[28px] !text-xs"
64 + @click="confirm_delete(item)"
65 + >
66 + 删除
67 + </van-button>
68 + </div>
69 + </div>
70 + </div>
71 +
72 + <div v-if="!apply_records.length" class="py-10 text-center text-sm text-[#999]">
73 + 暂无申请记录
74 + </div>
75 + </div>
76 +
77 + <div class="bg-white/60 backdrop-blur-md p-4 pb-8 shadow-[0_-2px_10px_rgba(0,0,0,0.05)]">
78 + <van-button
79 + block
80 + color="#0052D9"
81 + class="!rounded-lg !h-[44px] !text-base !font-bold"
82 + @click="open_add"
83 + >
84 + 新增记录
85 + </van-button>
86 + </div>
87 + </div>
88 +
89 + <van-popup
90 + v-model:show="show_form_popup"
91 + round
92 + position="bottom"
93 + :style="{ height: '50%' }"
94 + class="flex flex-col"
95 + >
96 + <div class="p-4 flex-1 flex flex-col">
97 + <div class="flex items-center justify-between mb-5">
98 + <h3 class="text-center font-bold text-lg text-[#333]">{{ form_mode === 'add' ? '新增记录' : '编辑记录' }}</h3>
99 + <van-icon name="cross" class="text-[#999]" @click="close_form_popup" />
100 + </div>
101 +
102 + <div class="bg-white rounded-xl overflow-hidden">
103 + <van-field
104 + v-model="form.name"
105 + label="活动名称"
106 + placeholder="例如: 2025年北京市海淀区活动"
107 + class="!py-3"
108 + type="textarea"
109 + />
110 + <van-field
111 + v-model="form.year"
112 + label="活动年份"
113 + placeholder="例如: 2025"
114 + class="!py-3"
115 + />
116 + <van-field
117 + v-model="form.location"
118 + label="活动地点"
119 + placeholder="例如: 北京市海淀区"
120 + class="!py-3"
121 + />
122 + <van-field
123 + v-model="form.paid_amount"
124 + label="实付金额"
125 + placeholder="例如: 1000"
126 + class="!py-3"
127 + />
128 + </div>
129 +
130 + <div class="mt-auto pt-6 flex gap-3">
131 + <van-button
132 + block
133 + plain
134 + color="#0052D9"
135 + class="!rounded-lg !h-[44px] !text-base !font-medium flex-1"
136 + @click="close_form_popup"
137 + >
138 + 取消
139 + </van-button>
140 + <van-button
141 + block
142 + color="#0052D9"
143 + class="!rounded-lg !h-[44px] !text-base !font-bold flex-1"
144 + @click="submit_form"
145 + >
146 + 保存
147 + </van-button>
148 + </div>
149 + </div>
150 + </van-popup>
151 + </van-popup>
152 +</template>
153 +
154 +<script setup>
155 +import { ref, onMounted } from 'vue'
156 +import { showToast, showConfirmDialog } from 'vant'
157 +import { getSupplementListAPI, supplementAddAPI, supplementEditAPI, supplementDelAPI } from '@/api/recall_users'
158 +
159 +const props = defineProps({
160 + show: {
161 + type: Boolean,
162 + default: false
163 + }
164 +})
165 +
166 +const emit = defineEmits(['update:show'])
167 +
168 +const apply_records = ref([])
169 +
170 +const show_form_popup = ref(false)
171 +const form_mode = ref('add')
172 +const editing_id = ref(null)
173 +const form = ref({
174 + name: '',
175 + year: '',
176 + location: '',
177 + paid_amount: ''
178 +})
179 +
180 +const reset_form = () => {
181 + form.value = {
182 + name: '',
183 + year: '',
184 + location: '',
185 + paid_amount: ''
186 + }
187 + editing_id.value = null
188 +}
189 +
190 +const fetch_records = async () => {
191 + const res = await getSupplementListAPI()
192 + if (res && res.code) {
193 + const list = Array.isArray(res.data) ? res.data : []
194 + apply_records.value = list.map((item) => ({
195 + id: item.id,
196 + name: item.title || '',
197 + year: item.year || '',
198 + location: item.address || '',
199 + paid_amount: item.amount ?? ''
200 + }))
201 + } else if (res && res.msg) {
202 + showToast(res.msg)
203 + }
204 +}
205 +
206 +const open_add = () => {
207 + form_mode.value = 'add'
208 + reset_form()
209 + show_form_popup.value = true
210 +}
211 +
212 +const open_edit = (item) => {
213 + form_mode.value = 'edit'
214 + editing_id.value = item.id
215 + form.value = {
216 + name: item.name || '',
217 + year: item.year || '',
218 + location: item.location || '',
219 + paid_amount: item.paid_amount || ''
220 + }
221 + show_form_popup.value = true
222 +}
223 +
224 +const close_form_popup = () => {
225 + show_form_popup.value = false
226 +}
227 +
228 +const submit_form = async () => {
229 + if (!String(form.value.name || '').trim()) {
230 + showToast('请输入活动名称')
231 + return
232 + }
233 + if (!String(form.value.year || '').trim()) {
234 + showToast('请输入活动年份')
235 + return
236 + }
237 + if (!String(form.value.location || '').trim()) {
238 + showToast('请输入活动地点')
239 + return
240 + }
241 + if (!String(form.value.paid_amount || '').trim()) {
242 + showToast('请输入实付金额')
243 + return
244 + }
245 +
246 + const params = {
247 + title: form.value.name,
248 + year: form.value.year,
249 + address: form.value.location,
250 + amount: form.value.paid_amount
251 + }
252 +
253 + if (form_mode.value === 'add') {
254 + const res = await supplementAddAPI(params)
255 + if (res && res.code) {
256 + showToast('新增成功')
257 + show_form_popup.value = false
258 + fetch_records()
259 + } else if (res && res.msg) {
260 + showToast(res.msg)
261 + } else {
262 + showToast('新增失败,请稍后重试')
263 + }
264 + } else {
265 + const res = await supplementEditAPI({
266 + id: editing_id.value,
267 + ...params
268 + })
269 + if (res && res.code) {
270 + showToast('修改成功')
271 + show_form_popup.value = false
272 + fetch_records()
273 + } else if (res && res.msg) {
274 + showToast(res.msg)
275 + } else {
276 + showToast('修改失败,请稍后重试')
277 + }
278 + }
279 +}
280 +
281 +const confirm_delete = async (item) => {
282 + try {
283 + await showConfirmDialog({
284 + title: '温馨提示',
285 + message: `确定要删除“${item.name || '该记录'}”吗?`,
286 + confirmButtonColor: '#FF3B30'
287 + })
288 + } catch (e) {
289 + return
290 + }
291 +
292 + const res = await supplementDelAPI({ id: item.id })
293 + if (res && res.code) {
294 + showToast('删除成功')
295 + fetch_records()
296 + } else if (res && res.msg) {
297 + showToast(res.msg)
298 + } else {
299 + showToast('删除失败,请稍后重试')
300 + }
301 +}
302 +
303 +const format_paid_amount = (val) => {
304 + const str = String(val ?? '').trim()
305 + if (!str) return '¥0.00'
306 +
307 + const pure = str.replace(/^¥/, '').trim()
308 + const num = Number(pure)
309 + if (Number.isFinite(num) && pure !== '') {
310 + return `¥${num.toFixed(2)}`
311 + }
312 + return str.startsWith('¥') ? str : `¥${str}`
313 +}
314 +
315 +onMounted(() => {
316 + fetch_records()
317 +})
318 +</script>
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
76 </van-button> 76 </van-button>
77 77
78 <van-button block plain color="#0052D9" class="!rounded-lg !h-[44px] !text-base !font-medium" 78 <van-button block plain color="#0052D9" class="!rounded-lg !h-[44px] !text-base !font-medium"
79 - @click="showMissingPopup = true"> 79 + @click="showApplyHistoryPopup = true">
80 <template #icon> 80 <template #icon>
81 <van-icon name="question-o" class="mr-1" /> 81 <van-icon name="question-o" class="mr-1" />
82 </template> 82 </template>
...@@ -84,23 +84,7 @@ ...@@ -84,23 +84,7 @@
84 </van-button> 84 </van-button>
85 </div> 85 </div>
86 86
87 - <!-- Missing Activity Popup --> 87 + <ActivityApplyHistoryPopup v-model:show="showApplyHistoryPopup" />
88 - <van-popup v-model:show="showMissingPopup" round position="bottom" :style="{ height: '60%' }" class="flex flex-col">
89 - <div class="p-4 flex-1 flex flex-col">
90 - <h3 class="text-center font-bold text-lg mb-6 text-[#333]">补充活动信息</h3>
91 -
92 - <div class="flex-1 bg-[#F7F8FA] rounded-lg p-3 mb-6">
93 - <textarea v-model="missingInfo"
94 - class="w-full h-full bg-transparent border-none outline-none resize-none text-sm text-[#333] placeholder:text-[#999]"
95 - placeholder="请把缺失的星球活动,补充在这里,留下活动的名称、时间、地点、参加人数等信息"></textarea>
96 - </div>
97 -
98 - <van-button block color="#0052D9" class="!rounded-lg !h-[44px] !text-base !font-bold"
99 - @click="handleSubmitMissing">
100 - 提交
101 - </van-button>
102 - </div>
103 - </van-popup>
104 88
105 </div> 89 </div>
106 </template> 90 </template>
...@@ -111,7 +95,8 @@ import { useRouter } from 'vue-router' ...@@ -111,7 +95,8 @@ import { useRouter } from 'vue-router'
111 import { useTitle } from '@vueuse/core' 95 import { useTitle } from '@vueuse/core'
112 import { showToast } from 'vant' 96 import { showToast } from 'vant'
113 97
114 -import { userInfoAPI, searchOldActivityAPI, getSupplementAPI, editSupplementAPI } from '@/api/recall_users' 98 +import ActivityApplyHistoryPopup from '@/components/ui/ActivityApplyHistoryPopup.vue'
99 +import { userInfoAPI, searchOldActivityAPI } from '@/api/recall_users'
115 import { oldActivityBatchActivityRegistrationAPI } from '@/api/points' 100 import { oldActivityBatchActivityRegistrationAPI } from '@/api/points'
116 101
117 const historyBg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/history_bg@2x.png' 102 const historyBg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/history_bg@2x.png'
...@@ -123,8 +108,7 @@ useTitle('活动历史') ...@@ -123,8 +108,7 @@ useTitle('活动历史')
123 const activities = ref([]) 108 const activities = ref([])
124 109
125 // State 110 // State
126 -const showMissingPopup = ref(false) 111 +const showApplyHistoryPopup = ref(false)
127 -const missingInfo = ref('')
128 112
129 // 处理生成海报 113 // 处理生成海报
130 const handleGeneratePoster = (item) => { 114 const handleGeneratePoster = (item) => {
...@@ -170,30 +154,6 @@ const handleViewCoins = () => { ...@@ -170,30 +154,6 @@ const handleViewCoins = () => {
170 router.push({ path: '/recall/points' }) 154 router.push({ path: '/recall/points' })
171 } 155 }
172 156
173 -const handleSubmitMissing = async () => {
174 - if (!missingInfo.value) {
175 - showToast('请输入补充信息')
176 - return
177 - }
178 -
179 - const res = await editSupplementAPI({
180 - note: missingInfo.value
181 - })
182 -
183 - if (res.code) {
184 - showToast('提交成功')
185 - showMissingPopup.value = false
186 - }
187 -}
188 -
189 -// 获取补充信息
190 -const fetchSupplementInfo = async () => {
191 - const res = await getSupplementAPI()
192 - if (res.code && res.data && res.data.note) {
193 - missingInfo.value = res.data.note
194 - }
195 -}
196 -
197 const userInfo = ref({}); 157 const userInfo = ref({});
198 const campaign_info = ref([]); 158 const campaign_info = ref([]);
199 const activityInfo = ref({}) 159 const activityInfo = ref({})
...@@ -274,9 +234,6 @@ const getCachedUserParams = () => { ...@@ -274,9 +234,6 @@ const getCachedUserParams = () => {
274 } 234 }
275 235
276 onMounted(async () => { 236 onMounted(async () => {
277 - // 获取补充信息
278 - fetchSupplementInfo()
279 -
280 // 从缓存获取用户信息 237 // 从缓存获取用户信息
281 const { has_cache, params } = getCachedUserParams() 238 const { has_cache, params } = getCachedUserParams()
282 if (has_cache) { 239 if (has_cache) {
......