hookehuyr

refactor(api): 提取地图活动API并新增展位地图画廊页面

迁移扫码打卡相关API至独立的map_activity.js文件,更新ScanCheckinDetail和ScanCheckinList页面的导入路径。新增BoothMapGallery页面,支持加载状态、空数据提示、网格展位图展示与图片预览,完善新API的文档注释。
......@@ -12,9 +12,6 @@ const Api = {
GET_POSTER_DETAIL: '/srv/?a=map&t=poster',
GET_ACTIVITY_STATUS: '/srv/?a=map&t=get_map_url',
SAVE_POSTER_BACKGROUND: '/srv/?a=map&t=save_poster_background',
GET_SCAN_STAGE_LIST: '/srv/?a=map_activity&t=scan_stage_list',
GET_SCAN_STAGE_DETAIL: '/srv/?a=map_activity&t=scan_stage_detail',
SUBMIT_SCAN_CHECKIN: '/srv/?a=map_activity&t=checkin',
}
/**
......@@ -72,51 +69,3 @@ export const getActivityStatusAPI = params => fn(fetch.get(Api.GET_ACTIVITY_STAT
* @returns {Object} response.data - 响应数据
*/
export const savePosterBackgroundAPI = params => fn(fetch.post(Api.SAVE_POSTER_BACKGROUND, params))
/**
* @description: 获取扫码关卡列表
* @param {Object} params - 请求参数
* @param {string} [params.page] - 页码,从 0 开始
* @param {string} [params.limit] - 每页条数
* @param {string} [params.activity_id] - 活动ID
* @returns {number} response.code - 响应状态码
* @returns {string} response.msg - 响应消息
* @returns {Object} response.data - 响应数据
* @returns {Array} response.data.stages - 关卡列表
* @returns {number} response.data.stages[].id - 关卡ID
* @returns {string} response.data.stages[].title - 关卡标题
* @returns {boolean} response.data.stages[].is_checked - 是否已打卡
*/
export const getScanStageListAPI = params => fn(fetch.get(Api.GET_SCAN_STAGE_LIST, params))
/**
* @description: 获取扫码关卡详情
* @param {Object} params - 请求参数
* @param {string} params.id - 关卡ID
* @returns {number} response.code - 响应状态码
* @returns {string} response.msg - 响应消息
* @returns {Object} response.data - 响应数据
* @returns {number} response.data.id - 关卡ID
* @returns {string} response.data.title - 关卡标题
* @returns {Array<string>} response.data.banner - 轮播图
* @returns {boolean} response.data.is_checked - 是否已打卡
* @returns {string} response.data.discount_title - 打卡点底部优惠标题
* @returns {string} response.data.note - 简介
* @returns {string} response.data.introduction - 底部富文本
* @returns {boolean} response.data.geo_enabled - 是否启用地理位置限制
* @returns {number} response.data.center_lng - 经度
* @returns {number} response.data.center_lat - 纬度
* @returns {number} response.data.radius_meters - 半径,单位米
*/
export const getScanStageDetailAPI = params => fn(fetch.get(Api.GET_SCAN_STAGE_DETAIL, params))
/**
* @description: 提交扫码打卡
* @param {Object} params - 请求参数
* @param {string} params.activity_id - 活动ID
* @param {string} params.detail_id - 关卡ID
* @param {string} [params.openid] - 用户openid,接口定义中为可选
* @returns {number} response.code - 响应状态码
* @returns {string} response.msg - 响应消息
*/
export const submitScanCheckinAPI = params => fn(fetch.post(Api.SUBMIT_SCAN_CHECKIN, params))
......
......@@ -7,6 +7,9 @@ const Api = {
List: '/srv/?a=map_activity&t=list',
Poster: '/srv/?a=map_activity&t=poster',
SavePosterBackground: '/srv/?a=map_activity&t=save_poster_background',
ScanStageList: '/srv/?a=map_activity&t=scan_stage_list',
ScanStageDetail: '/srv/?a=map_activity&t=scan_stage_detail',
SubmitScanCheckin: '/srv/?a=map_activity&t=checkin',
}
/**
......@@ -26,7 +29,7 @@ export const checkinAPI = params => fn(fetch.post(Api.Checkin, params))
/**
* @description 地图活动详情
* @remark
* @remark
* @param {Object} params 请求参数
* @param {string} params.id (可选) 活动ID
* @returns {Promise<{
......@@ -36,6 +39,7 @@ export const checkinAPI = params => fn(fetch.post(Api.Checkin, params))
url: string; // 地图网址
id: integer; // 活动ID
type: string; // 打卡类型,MAP=地图打卡,QR_CODE=扫码打卡
booth_images: Array<string>; // 打卡点图片列表
cover: string; // 封面图
begin_date: string; // 开始时间
end_date: string; // 结束时间
......@@ -53,10 +57,10 @@ export const detailAPI = params => fn(fetch.get(Api.Detail, params))
/**
* @description 是否已经打卡
* @remark
* @remark
* @param {Object} params 请求参数
* @param {string} params.detail_id (可选) 打卡点ID
* @param {string} params.openid (可选)
* @param {string} params.openid (可选)
* @param {string} params.activity_id (可选) 活动ID
* @returns {Promise<{
* code: number; // 状态码
......@@ -70,7 +74,7 @@ export const isCheckedAPI = params => fn(fetch.get(Api.IsChecked, params))
/**
* @description 地图活动列表
* @remark
* @remark
* @param {Object} params 请求参数
* @returns {Promise<{
* code: number; // 状态码
......@@ -88,8 +92,66 @@ export const isCheckedAPI = params => fn(fetch.get(Api.IsChecked, params))
export const listAPI = params => fn(fetch.get(Api.List, params))
/**
* @description 获取扫码关卡列表
* @param {Object} params 请求参数
* @param {string} [params.page] 页码,从 0 开始
* @param {string} [params.limit] 每页条数
* @param {string} [params.activity_id] 活动ID
* @returns {Promise<{
* code: number;
* msg: string;
* data: {
* stages: Array<{
* id: number;
* title: string;
* is_checked: boolean;
* }>;
* };
* }>}
*/
export const getScanStageListAPI = params => fn(fetch.get(Api.ScanStageList, params))
/**
* @description 获取扫码关卡详情
* @param {Object} params 请求参数
* @param {string} params.id 关卡ID
* @returns {Promise<{
* code: number;
* msg: string;
* data: {
* id: number;
* title: string;
* banner: Array<string>;
* is_checked: boolean;
* discount_title: string;
* note: string;
* introduction: string;
* geo_enabled: boolean;
* center_lng: number;
* center_lat: number;
* radius_meters: number;
* };
* }>}
*/
export const getScanStageDetailAPI = params => fn(fetch.get(Api.ScanStageDetail, params))
/**
* @description 提交扫码打卡
* @param {Object} params 请求参数
* @param {string} params.activity_id 活动ID
* @param {string} params.detail_id 关卡ID
* @param {string} [params.openid] 用户openid
* @returns {Promise<{
* code: number;
* msg: string;
* data: any;
* }>}
*/
export const submitScanCheckinAPI = params => fn(fetch.post(Api.SubmitScanCheckin, params))
/**
* @description 获取海报
* @remark
* @remark
* @param {Object} params 请求参数
* @param {string} params.activity_id (可选) 活动ID
* @param {string} params.detail_id (可选) 关卡ID
......@@ -102,8 +164,8 @@ export const listAPI = params => fn(fetch.get(Api.List, params))
id: integer; // 关卡ID
name: string; // 关卡名称
background_url: string; // 关卡背景图
main_slogan: string; //
sub_slogan: string; //
main_slogan: string; //
sub_slogan: string; //
is_checked: boolean; // 是否已经打卡
}>;
family: {
......
......@@ -5,6 +5,13 @@
box-sizing: border-box;
}
.booth-map-gallery-status {
padding: 120rpx 24rpx;
text-align: center;
font-size: 28rpx;
color: #6b7280;
}
.booth-map-gallery-grid {
column-count: 2;
column-gap: 20rpx;
......
<template>
<view class="booth-map-gallery-page">
<view class="booth-map-gallery-grid">
<view v-if="loading" class="booth-map-gallery-status">加载中...</view>
<view v-else-if="imageList.length === 0" class="booth-map-gallery-status"> 暂无展位图 </view>
<view v-else class="booth-map-gallery-grid">
<view
v-for="(item, index) in imageList"
:key="item.id"
......@@ -15,28 +19,72 @@
<script setup>
import { ref } from 'vue'
import Taro from '@tarojs/taro'
import Taro, { useLoad } from '@tarojs/taro'
import './index.less'
import { detailAPI } from '@/api/map_activity'
const imageList = ref([])
const loading = ref(false)
const activityId = ref('')
const isApiSuccess = code => Number(code) === 1
const mapBoothImages = boothImages =>
boothImages
.filter(item => typeof item === 'string' && item.trim() !== '')
.map((url, index) => ({
id: `booth-${index}`,
url,
mode: 'widthFix',
}))
const fetchBoothImages = async () => {
if (!activityId.value) {
imageList.value = []
Taro.showToast({
title: '缺少活动信息',
icon: 'none',
})
return
}
const imageList = ref([
{
id: 'booth-01',
url: 'https://cdn.ipadbiz.cn/lls_prog/images/check_detail_img.png?imageMogr2/strip/quality/60',
mode: 'widthFix',
},
{
id: 'booth-02',
url: 'https://cdn.ipadbiz.cn/lls_prog/images/welcome_8.jpg?imageMogr2/strip/quality/60',
mode: 'widthFix',
},
{
id: 'booth-03',
url: 'https://cdn.ipadbiz.cn/lls_prog/images/check_detail_img.png?imageMogr2/strip/quality/60',
mode: 'widthFix',
},
])
loading.value = true
try {
const result = await detailAPI({ id: activityId.value })
if (!isApiSuccess(result?.code)) {
Taro.showToast({
title: result?.msg || '获取展位图失败',
icon: 'none',
})
imageList.value = []
return
}
imageList.value = mapBoothImages(result?.data?.booth_images || [])
} catch (error) {
console.error('[BoothMapGallery] 获取展位图失败:', error)
imageList.value = []
Taro.showToast({
title: '获取展位图失败',
icon: 'none',
})
} finally {
loading.value = false
}
}
useLoad(options => {
activityId.value = options.activityId || options.activity_id || options.id || ''
fetchBoothImages()
})
const previewImage = index => {
if (imageList.value.length === 0) {
return
}
Taro.previewImage({
current: imageList.value[index].url,
urls: imageList.value.map(item => item.url),
......
......@@ -57,7 +57,7 @@ import './index.less'
import RichTextRenderer from '@/components/RichTextRenderer.vue'
import { getCurrentPageFullPath } from '@/utils/authRedirect'
import { getMyFamiliesAPI } from '@/api/family'
import { getScanStageDetailAPI, submitScanCheckinAPI } from '@/api/map'
import { getScanStageDetailAPI, submitScanCheckinAPI } from '@/api/map_activity'
import { getUserProfileAPI } from '@/api/user'
import { verifyCheckinRangeWithCurrentLocation } from '@/utils/checkinLocation'
import {
......
<!--
* @Date: 2026-05-19 14:40:21
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2026-05-19 15:03:23
* @LastEditTime: 2026-05-21 10:25:21
* @FilePath: /lls_program/src/pages/ScanCheckinList/index.vue
* @Description: 文件描述
-->
......@@ -68,7 +68,7 @@ import Taro, { useLoad } from '@tarojs/taro'
import { IconFont, Scan2 } from '@nutui/icons-vue-taro'
import './index.less'
import BottomNav from '@/components/BottomNav.vue'
import { getScanStageListAPI } from '@/api/map'
import { getScanStageListAPI } from '@/api/map_activity'
const pointList = ref([])
const activityId = ref('')
......