hookehuyr

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

新增ActivityApplyHistoryPopup组件用于展示和管理活动申请记录
添加supplement_list、supplement_add、supplement_edit、supplement_del等API接口
重构ActivityHistoryPage页面,替换原有补充信息弹窗为新的申请记录弹窗
/*
* @Date: 2025-12-19 10:43:09
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-12-31 11:42:58
* @LastEditTime: 2026-01-04 14:05:54
* @FilePath: /mlaj/src/api/recall_users.js
* @Description: 引入外部接口, 召回旧用户相关接口
*/
......@@ -17,6 +17,10 @@ const Api = {
USER_EDIT_SUPPLEMENT: '/srv/?a=desk_calendar&t=edit_supplement',
USER_TRACKING: '/srv/?a=desk_calendar&t=tracking',
USER_GET_QRCODE: '/srv/?a=desk_calendar&t=get_qrcode',
USER_GET_SUPPLEMENT_LIST: '/srv/?a=desk_calendar&t=supplement_list',
USER_SUPPLEMENT_ADD: '/srv/?a=desk_calendar&t=supplement_add',
USER_SUPPLEMENT_EDIT: '/srv/?a=desk_calendar&t=supplement_edit',
USER_SUPPLEMENT_DEL: '/srv/?a=desk_calendar&t=supplement_del',
}
/**
......@@ -89,3 +93,34 @@ export const trackingAPI = (params) => request(fetch.post(Api.USER_TRACKING, par
* @return: data: { id, title, content_url}
*/
export const getQrcodeAPI = (params) => request(fetch.get(Api.USER_GET_QRCODE, params));
/**
* @description: 找不到的活动的列表
* @return: data: [{ id 补充信息ID, title 活动名称, year 活动年份, address 活动地址, amount 活动金额 }]
*/
export const getSupplementListAPI = (params) => request(fetch.get(Api.USER_GET_SUPPLEMENT_LIST, params));
/**
* @description: 新建找不到的活动
* @param: title 活动名称
* @param: year 活动年份
* @param: address 活动地址
* @param: amount 活动金额
*/
export const supplementAddAPI = (params) => request(fetch.post(Api.USER_SUPPLEMENT_ADD, params));
/**
* @description: 编辑找不到的活动
* @param: id 补充信息ID
* @param: title 活动名称
* @param: year 活动年份
* @param: address 活动地址
* @param: amount 活动金额
*/
export const supplementEditAPI = (params) => request(fetch.post(Api.USER_SUPPLEMENT_EDIT, params));
/**
* @description: 删除找不到的活动
* @param: id 补充信息ID
*/
export const supplementDelAPI = (params) => request(fetch.post(Api.USER_SUPPLEMENT_DEL, params));
......
......@@ -8,6 +8,7 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
ActivityApplyHistoryPopup: typeof import('./components/ui/ActivityApplyHistoryPopup.vue')['default']
ActivityCard: typeof import('./components/ui/ActivityCard.vue')['default']
AddTargetDialog: typeof import('./components/count/AddTargetDialog.vue')['default']
AppLayout: typeof import('./components/layout/AppLayout.vue')['default']
......
<template>
<van-popup
:show="show"
@update:show="(v) => emit('update:show', v)"
position="bottom"
:style="{ width: '100%', height: '100%' }"
:lock-scroll="true"
>
<div class="h-full w-full flex flex-col bg-[#F7F8FA]">
<div class="bg-white px-4 py-3 flex items-center justify-between shadow-sm">
<div class="flex items-center gap-3">
<!-- <van-icon name="arrow-left" class="text-[#333]" @click="emit('update:show', false)" /> -->
<div class="text-[#333] font-bold text-base">我的申请记录</div>
</div>
<van-icon name="cross" class="text-[#999]" @click="emit('update:show', false)" />
</div>
<div class="bg-white px-4 py-3 flex justify-between items-center sticky top-0 z-20 shadow-sm">
<span class="text-[#333] font-medium text-sm">活动记录</span>
<div class="flex items-center text-[#666] text-sm">
<span>{{ apply_records.length }}条</span>
</div>
</div>
<div class="flex-1 overflow-y-auto px-4 py-4 space-y-4">
<div
v-for="item in apply_records"
:key="item.id"
class="bg-white rounded-xl py-4 pr-4 pl-0 shadow-sm relative overflow-hidden flex"
>
<div class="w-1 h-4 bg-[#0052D9] rounded-r-sm mt-1 shrink-0 self-start"></div>
<div class="pl-3 flex-1">
<h3 class="text-[#333] font-bold text-base leading-snug">
{{ item.name }}
</h3>
<div class="border-t border-dashed border-gray-200 my-3"></div>
<div class="flex justify-between items-center text-xs text-[#666] mb-2">
<span>活动年份: {{ item.year }}</span>
<span class="text-[#FF3B30] font-bold">实付金额: {{ format_paid_amount(item.paid_amount) }}</span>
</div>
<div class="flex justify-between items-center text-xs text-[#666] mb-4">
<span>活动地点: {{ item.location }}</span>
</div>
<div class="flex justify-end gap-2">
<van-button
size="small"
plain
color="#0052D9"
class="!rounded-full !px-4 !h-[28px] !text-xs"
@click="open_edit(item)"
>
修改
</van-button>
<van-button
size="small"
plain
color="#FF3B30"
class="!rounded-full !px-4 !h-[28px] !text-xs"
@click="confirm_delete(item)"
>
删除
</van-button>
</div>
</div>
</div>
<div v-if="!apply_records.length" class="py-10 text-center text-sm text-[#999]">
暂无申请记录
</div>
</div>
<div class="bg-white/60 backdrop-blur-md p-4 pb-8 shadow-[0_-2px_10px_rgba(0,0,0,0.05)]">
<van-button
block
color="#0052D9"
class="!rounded-lg !h-[44px] !text-base !font-bold"
@click="open_add"
>
新增记录
</van-button>
</div>
</div>
<van-popup
v-model:show="show_form_popup"
round
position="bottom"
:style="{ height: '50%' }"
class="flex flex-col"
>
<div class="p-4 flex-1 flex flex-col">
<div class="flex items-center justify-between mb-5">
<h3 class="text-center font-bold text-lg text-[#333]">{{ form_mode === 'add' ? '新增记录' : '编辑记录' }}</h3>
<van-icon name="cross" class="text-[#999]" @click="close_form_popup" />
</div>
<div class="bg-white rounded-xl overflow-hidden">
<van-field
v-model="form.name"
label="活动名称"
placeholder="例如: 2025年北京市海淀区活动"
class="!py-3"
type="textarea"
/>
<van-field
v-model="form.year"
label="活动年份"
placeholder="例如: 2025"
class="!py-3"
/>
<van-field
v-model="form.location"
label="活动地点"
placeholder="例如: 北京市海淀区"
class="!py-3"
/>
<van-field
v-model="form.paid_amount"
label="实付金额"
placeholder="例如: 1000"
class="!py-3"
/>
</div>
<div class="mt-auto pt-6 flex gap-3">
<van-button
block
plain
color="#0052D9"
class="!rounded-lg !h-[44px] !text-base !font-medium flex-1"
@click="close_form_popup"
>
取消
</van-button>
<van-button
block
color="#0052D9"
class="!rounded-lg !h-[44px] !text-base !font-bold flex-1"
@click="submit_form"
>
保存
</van-button>
</div>
</div>
</van-popup>
</van-popup>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { showToast, showConfirmDialog } from 'vant'
import { getSupplementListAPI, supplementAddAPI, supplementEditAPI, supplementDelAPI } from '@/api/recall_users'
const props = defineProps({
show: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:show'])
const apply_records = ref([])
const show_form_popup = ref(false)
const form_mode = ref('add')
const editing_id = ref(null)
const form = ref({
name: '',
year: '',
location: '',
paid_amount: ''
})
const reset_form = () => {
form.value = {
name: '',
year: '',
location: '',
paid_amount: ''
}
editing_id.value = null
}
const fetch_records = async () => {
const res = await getSupplementListAPI()
if (res && res.code) {
const list = Array.isArray(res.data) ? res.data : []
apply_records.value = list.map((item) => ({
id: item.id,
name: item.title || '',
year: item.year || '',
location: item.address || '',
paid_amount: item.amount ?? ''
}))
} else if (res && res.msg) {
showToast(res.msg)
}
}
const open_add = () => {
form_mode.value = 'add'
reset_form()
show_form_popup.value = true
}
const open_edit = (item) => {
form_mode.value = 'edit'
editing_id.value = item.id
form.value = {
name: item.name || '',
year: item.year || '',
location: item.location || '',
paid_amount: item.paid_amount || ''
}
show_form_popup.value = true
}
const close_form_popup = () => {
show_form_popup.value = false
}
const submit_form = async () => {
if (!String(form.value.name || '').trim()) {
showToast('请输入活动名称')
return
}
if (!String(form.value.year || '').trim()) {
showToast('请输入活动年份')
return
}
if (!String(form.value.location || '').trim()) {
showToast('请输入活动地点')
return
}
if (!String(form.value.paid_amount || '').trim()) {
showToast('请输入实付金额')
return
}
const params = {
title: form.value.name,
year: form.value.year,
address: form.value.location,
amount: form.value.paid_amount
}
if (form_mode.value === 'add') {
const res = await supplementAddAPI(params)
if (res && res.code) {
showToast('新增成功')
show_form_popup.value = false
fetch_records()
} else if (res && res.msg) {
showToast(res.msg)
} else {
showToast('新增失败,请稍后重试')
}
} else {
const res = await supplementEditAPI({
id: editing_id.value,
...params
})
if (res && res.code) {
showToast('修改成功')
show_form_popup.value = false
fetch_records()
} else if (res && res.msg) {
showToast(res.msg)
} else {
showToast('修改失败,请稍后重试')
}
}
}
const confirm_delete = async (item) => {
try {
await showConfirmDialog({
title: '温馨提示',
message: `确定要删除“${item.name || '该记录'}”吗?`,
confirmButtonColor: '#FF3B30'
})
} catch (e) {
return
}
const res = await supplementDelAPI({ id: item.id })
if (res && res.code) {
showToast('删除成功')
fetch_records()
} else if (res && res.msg) {
showToast(res.msg)
} else {
showToast('删除失败,请稍后重试')
}
}
const format_paid_amount = (val) => {
const str = String(val ?? '').trim()
if (!str) return '¥0.00'
const pure = str.replace(/^¥/, '').trim()
const num = Number(pure)
if (Number.isFinite(num) && pure !== '') {
return `¥${num.toFixed(2)}`
}
return str.startsWith('¥') ? str : `¥${str}`
}
onMounted(() => {
fetch_records()
})
</script>
......@@ -76,7 +76,7 @@
</van-button>
<van-button block plain color="#0052D9" class="!rounded-lg !h-[44px] !text-base !font-medium"
@click="showMissingPopup = true">
@click="showApplyHistoryPopup = true">
<template #icon>
<van-icon name="question-o" class="mr-1" />
</template>
......@@ -84,23 +84,7 @@
</van-button>
</div>
<!-- Missing Activity Popup -->
<van-popup v-model:show="showMissingPopup" round position="bottom" :style="{ height: '60%' }" class="flex flex-col">
<div class="p-4 flex-1 flex flex-col">
<h3 class="text-center font-bold text-lg mb-6 text-[#333]">补充活动信息</h3>
<div class="flex-1 bg-[#F7F8FA] rounded-lg p-3 mb-6">
<textarea v-model="missingInfo"
class="w-full h-full bg-transparent border-none outline-none resize-none text-sm text-[#333] placeholder:text-[#999]"
placeholder="请把缺失的星球活动,补充在这里,留下活动的名称、时间、地点、参加人数等信息"></textarea>
</div>
<van-button block color="#0052D9" class="!rounded-lg !h-[44px] !text-base !font-bold"
@click="handleSubmitMissing">
提交
</van-button>
</div>
</van-popup>
<ActivityApplyHistoryPopup v-model:show="showApplyHistoryPopup" />
</div>
</template>
......@@ -111,7 +95,8 @@ import { useRouter } from 'vue-router'
import { useTitle } from '@vueuse/core'
import { showToast } from 'vant'
import { userInfoAPI, searchOldActivityAPI, getSupplementAPI, editSupplementAPI } from '@/api/recall_users'
import ActivityApplyHistoryPopup from '@/components/ui/ActivityApplyHistoryPopup.vue'
import { userInfoAPI, searchOldActivityAPI } from '@/api/recall_users'
import { oldActivityBatchActivityRegistrationAPI } from '@/api/points'
const historyBg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/history_bg@2x.png'
......@@ -123,8 +108,7 @@ useTitle('活动历史')
const activities = ref([])
// State
const showMissingPopup = ref(false)
const missingInfo = ref('')
const showApplyHistoryPopup = ref(false)
// 处理生成海报
const handleGeneratePoster = (item) => {
......@@ -170,30 +154,6 @@ const handleViewCoins = () => {
router.push({ path: '/recall/points' })
}
const handleSubmitMissing = async () => {
if (!missingInfo.value) {
showToast('请输入补充信息')
return
}
const res = await editSupplementAPI({
note: missingInfo.value
})
if (res.code) {
showToast('提交成功')
showMissingPopup.value = false
}
}
// 获取补充信息
const fetchSupplementInfo = async () => {
const res = await getSupplementAPI()
if (res.code && res.data && res.data.note) {
missingInfo.value = res.data.note
}
}
const userInfo = ref({});
const campaign_info = ref([]);
const activityInfo = ref({})
......@@ -274,9 +234,6 @@ const getCachedUserParams = () => {
}
onMounted(async () => {
// 获取补充信息
fetchSupplementInfo()
// 从缓存获取用户信息
const { has_cache, params } = getCachedUserParams()
if (has_cache) {
......