hookehuyr

refactor(map_activity): 重命名 posterAPI 为 getPosterDetailAPI

- 将 posterAPI 重命名为 getPosterDetailAPI,命名更清晰
- 修复 PosterCheckin 页面的 API 导入路径(@/api/map → @/api/map_activity)
- 更新 CHANGELOG 记录海报接口联调功能

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
......@@ -118,7 +118,7 @@ export const listAPI = params => fn(fetch.get(Api.List, params))
* };
* }>}
*/
export const posterAPI = params => fn(fetch.get(Api.Poster, params))
export const getPosterDetailAPI = params => fn(fetch.get(Api.Poster, params))
/**
* @description 上传海报背景
......
......@@ -20,7 +20,10 @@
<!-- 正常内容 -->
<template v-else>
<!-- 活动信息区域 -->
<view v-if="pageState === 'normal' || pageState === 'no-checkin'" class="bg-white mx-4 mt-4 mb-2 rounded-lg shadow-sm p-4">
<view
v-if="pageState === 'normal' || pageState === 'no-checkin'"
class="bg-white mx-4 mt-4 mb-2 rounded-lg shadow-sm p-4"
>
<!-- 活动主题 -->
<view class="text-lg font-bold text-gray-800 mb-2">
{{ activityInfo.title }}
......@@ -37,24 +40,24 @@
>
</view>
</view>
<text class="text-xs text-gray-600">{{ activityInfo.completedCount }}/{{ activityInfo.totalCount }}</text>
<text class="text-xs text-gray-600"
>{{ activityInfo.completedCount }}/{{ activityInfo.totalCount }}</text
>
</view>
<!-- 活动截止日期 -->
<view class="text-sm text-gray-500">
活动截止日期:{{ activityInfo.endDate }}
</view>
<view class="text-sm text-gray-500"> 活动截止日期:{{ activityInfo.endDate }} </view>
</view>
<!-- 海报预览区域 - 正常状态 -->
<view v-if="pageState === 'normal'" class="flex-1 mx-4 relative" style="overflow: visible; padding-bottom: 110rpx;">
<view
v-if="pageState === 'normal'"
class="flex-1 mx-4 relative"
style="overflow: visible; padding-bottom: 110rpx"
>
<view class="h-full relative flex items-center justify-center">
<view v-if="currentPoster.path" class="w-full h-full relative">
<image
:src="currentPoster.path"
mode="widthFix"
class="w-full h-full"
/>
<image :src="currentPoster.path" mode="widthFix" class="w-full h-full" />
<!-- 打卡点标题 -->
<!-- <view class="absolute bottom-2 left-2 bg-blue-500 text-white text-xs px-2 py-1 rounded">
{{ posterList[currentPosterIndex]?.title || '海报生成中' }}
......@@ -69,8 +72,12 @@
<!-- 左箭头按钮 -->
<view
class="absolute top-1/2 transform -translate-y-1/2 w-10 h-10 rounded-full flex items-center justify-center transition-all duration-200 z-10"
:class="currentPosterIndex > 0 ? 'bg-orange-400 text-white shadow-lg' : 'bg-gray-300 text-gray-500'"
style="left: -20rpx;"
:class="
currentPosterIndex > 0
? 'bg-orange-400 text-white shadow-lg'
: 'bg-gray-300 text-gray-500'
"
style="left: -20rpx"
@tap="previousPoster"
>
<Left size="16"></Left>
......@@ -79,8 +86,12 @@
<!-- 右箭头按钮 -->
<view
class="absolute top-1/2 transform -translate-y-1/2 w-10 h-10 rounded-full flex items-center justify-center transition-all duration-200 z-10"
:class="currentPosterIndex < posterList.length - 1 ? 'bg-orange-400 text-white shadow-lg' : 'bg-gray-300 text-gray-500'"
style="right: -20rpx;"
:class="
currentPosterIndex < posterList.length - 1
? 'bg-orange-400 text-white shadow-lg'
: 'bg-gray-300 text-gray-500'
"
style="right: -20rpx"
@tap="nextPoster"
>
<Right size="16"></Right>
......@@ -97,7 +108,11 @@
</view>
<!-- 底部操作按钮 - 仅在正常状态显示 -->
<view v-if="pageState === 'normal'" class="bg-white border-t border-gray-200 p-4 safe-area-bottom z-50" style="position: fixed; bottom: 0; left: 0; right: 0;">
<view
v-if="pageState === 'normal'"
class="bg-white border-t border-gray-200 p-4 safe-area-bottom z-50"
style="position: fixed; bottom: 0; left: 0; right: 0"
>
<view class="flex gap-4">
<view
class="flex-1 bg-gradient-to-r from-orange-400 to-orange-500 text-white text-sm py-3 px-6 rounded-lg font-medium shadow-lg active:scale-95 transition-transform duration-150 flex items-center justify-center gap-2"
......@@ -107,11 +122,19 @@
</view>
<view
class="flex-1 text-white py-3 px-6 rounded-lg font-medium text-sm shadow-lg active:scale-95 transition-transform duration-150 flex items-center justify-center gap-2"
:class="posterPath ? 'bg-gradient-to-r from-green-400 to-green-500' : (posterGenerateFailed ? 'bg-gradient-to-r from-orange-400 to-orange-500' : 'bg-gray-400')"
:class="
posterPath
? 'bg-gradient-to-r from-green-400 to-green-500'
: posterGenerateFailed
? 'bg-gradient-to-r from-orange-400 to-orange-500'
: 'bg-gray-400'
"
@click="handlePosterAction"
:disabled="!posterPath && !posterGenerateFailed"
>
<text>{{ posterPath ? '保存海报' : (posterGenerateFailed ? '重新生成' : '生成中...') }}</text>
<text>{{
posterPath ? '保存海报' : posterGenerateFailed ? '重新生成' : '生成中...'
}}</text>
</view>
</view>
</view>
......@@ -134,7 +157,6 @@
:show-index="false"
@close="closePreview"
/>
</view>
</template>
......@@ -145,9 +167,10 @@ import { Left, Right } from '@nutui/icons-vue-taro'
import PosterBuilder from '@/components/PosterBuilder/index.vue'
import BASE_URL from '@/utils/config'
// 导入获取海报详情的API
import { getPosterDetailAPI, savePosterBackgroundAPI } from '@/api/map'
import { getPosterDetailAPI, savePosterBackgroundAPI } from '@/api/map_activity'
// 默认背景图
const defaultBackground = 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E9%BB%98%E8%AE%A4%E8%83%8C%E6%99%AF%E5%9B%BE1.png?imageMogr2/strip/quality/60'
const defaultBackground =
'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E9%BB%98%E8%AE%A4%E8%83%8C%E6%99%AF%E5%9B%BE1.png?imageMogr2/strip/quality/60'
// 页面状态
const posterPath = ref('') // 生成的海报路径
......@@ -181,10 +204,10 @@ const fetchPosterDetail = async () => {
isLoading.value = true
apiError.value = null
const accountInfo = wx.getAccountInfoSync();
const envVersion = accountInfo.miniProgram.envVersion;
const accountInfo = wx.getAccountInfoSync()
const envVersion = accountInfo.miniProgram.envVersion
// 小程序版本。正式版为 "release",体验版为 "trial"。默认是正式版
const env_version = envVersion === 'release' ? 'release' : 'trial';
const env_version = envVersion === 'release' ? 'release' : 'trial'
const response = await getPosterDetailAPI({ env_version })
......@@ -200,7 +223,6 @@ const fetchPosterDetail = async () => {
// 根据pageParams.id设置当前海报索引
setCurrentPosterIndex()
} else {
apiError.value = response.msg || '获取海报详情失败'
console.error('获取海报详情失败:', response.msg)
......@@ -217,7 +239,9 @@ const fetchPosterDetail = async () => {
* 根据API数据更新活动信息
*/
const updateActivityInfo = () => {
if (!apiData.value) return
if (!apiData.value) {
return
}
const { title, end_date, details, show_detail_index } = apiData.value
......@@ -225,7 +249,7 @@ const updateActivityInfo = () => {
const checkPoints = details.map((detail, index) => ({
id: detail.id,
name: detail.name,
completed: detail.is_checked === true
completed: detail.is_checked === true,
}))
// 计算已完成数量
......@@ -237,7 +261,7 @@ const updateActivityInfo = () => {
completedCount,
totalCount: checkPoints.length,
endDate: end_date || '',
showDetailIndex: show_detail_index || 0
showDetailIndex: show_detail_index || 0,
}
}
......@@ -245,14 +269,14 @@ const updateActivityInfo = () => {
* 根据API数据更新海报列表
*/
const updatePosterList = () => {
if (!apiData.value) return
if (!apiData.value) {
return
}
const { details, family, qrcode_url } = apiData.value
// 只显示is_checked为真的关卡
const checkedDetails = details.filter(detail =>
detail.is_checked === true
)
const checkedDetails = details.filter(detail => detail.is_checked === true)
posterList.value = checkedDetails.map((detail, index) => ({
id: detail.id,
......@@ -262,22 +286,28 @@ const updatePosterList = () => {
backgroundImage: detail.background_url || defaultBackground,
// 海报内容数据
user: {
avatar: family?.avatar_url || 'https://cdn.ipadbiz.cn/lls_prog/images/%E5%85%A8%E5%AE%B6%E7%A6%8F3_%E5%89%AF%E6%9C%AC.jpg?imageMogr2/strip/quality/60',
nickname: '用户昵称' // 默认昵称,后续可从用户信息获取
avatar:
family?.avatar_url ||
'https://cdn.ipadbiz.cn/lls_prog/images/%E5%85%A8%E5%AE%B6%E7%A6%8F3_%E5%89%AF%E6%9C%AC.jpg?imageMogr2/strip/quality/60',
nickname: '用户昵称', // 默认昵称,后续可从用户信息获取
},
family: {
name: family?.name || '我的家庭',
description: ''
description: '',
},
activity: {
logo: detail.main_slogan || 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E5%B7%A6%E4%B8%8A%E8%A7%92logo.png?imageMogr2/strip/quality/60',
logo:
detail.main_slogan ||
'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E5%B7%A6%E4%B8%8A%E8%A7%92logo.png?imageMogr2/strip/quality/60',
},
level: {
logo: detail.sub_slogan || 'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E5%8F%B3%E4%B8%8B%E8%A7%92icon.png?imageMogr2/strip/quality/60',
logo:
detail.sub_slogan ||
'https://cdn.ipadbiz.cn/lls_prog/images/%E6%B5%B7%E6%8A%A5%E5%8F%B3%E4%B8%8B%E8%A7%92icon.png?imageMogr2/strip/quality/60',
name: detail.name || '海报打卡活动',
},
qrcode: qrcode_url,
qrcodeDesc: '长按识别,来,我们一起打卡!'
qrcodeDesc: '长按识别,来,我们一起打卡!',
}))
}
......@@ -292,8 +322,8 @@ const setCurrentPosterIndex = () => {
// 如果有指定的ID,查找对应的海报索引
if (pageParams.value.id) {
const targetIndex = posterList.value.findIndex(poster =>
poster.checkPointId.toString() === pageParams.value.id.toString()
const targetIndex = posterList.value.findIndex(
poster => poster.checkPointId.toString() === pageParams.value.id.toString()
)
if (targetIndex !== -1) {
......@@ -314,7 +344,7 @@ const activityInfo = ref({
completedCount: 0,
totalCount: 0,
endDate: '',
showDetailIndex: 0
showDetailIndex: 0,
})
// 海报数据列表 - 将由API数据填充
......@@ -322,7 +352,12 @@ const posterList = ref([])
// 数据状态检查
const hasActivityInfo = computed(() => {
return activityInfo.value && activityInfo.value.title && activityInfo.value.checkPoints && activityInfo.value.checkPoints.length > 0
return (
activityInfo.value &&
activityInfo.value.title &&
activityInfo.value.checkPoints &&
activityInfo.value.checkPoints.length > 0
)
})
const hasCheckinInfo = computed(() => {
......@@ -340,8 +375,6 @@ const pageState = computed(() => {
return 'normal' // 正常状态
})
// 当前海报
const currentPoster = computed(() => {
return posterList.value[currentPosterIndex.value] || { path: '', title: '' }
......@@ -350,7 +383,9 @@ const currentPoster = computed(() => {
// 当前海报的内容数据
const currentMockData = computed(() => {
const currentPoster = posterList.value[currentPosterIndex.value]
if (!currentPoster) return null
if (!currentPoster) {
return null
}
return {
user: currentPoster.user,
......@@ -358,7 +393,7 @@ const currentMockData = computed(() => {
activity: currentPoster.activity,
level: currentPoster.level,
qrcode: currentPoster.qrcode,
qrcodeDesc: currentPoster.qrcodeDesc
qrcodeDesc: currentPoster.qrcodeDesc,
}
})
......@@ -370,7 +405,10 @@ const currentPosterHasCustomBackground = computed(() => {
// 海报配置
const posterConfig = computed(() => {
const currentPosterData = posterList.value[currentPosterIndex.value]
const bgImage = backgroundImages.value[currentPosterIndex.value] || currentPosterData?.backgroundImage || defaultBackground
const bgImage =
backgroundImages.value[currentPosterIndex.value] ||
currentPosterData?.backgroundImage ||
defaultBackground
return {
width: 600, // 从750减少到600,减小Canvas尺寸以控制文件大小
......@@ -386,7 +424,7 @@ const posterConfig = computed(() => {
width: 600, // 按比例调整 (750 * 0.8 = 600)
height: 880, // 按比例调整 (1100 * 0.8 = 880)
url: bgImage,
zIndex: 0
zIndex: 0,
},
// 用户头像
{
......@@ -396,7 +434,7 @@ const posterConfig = computed(() => {
height: 104, // 按比例调整 (130 * 0.8 = 104)
url: currentMockData.value.user.avatar,
borderRadius: 52, // 按比例调整 (65 * 0.8 = 52)
zIndex: 2
zIndex: 2,
},
// 活动logo
{
......@@ -405,7 +443,7 @@ const posterConfig = computed(() => {
width: 200, // 按比例调整 (250 * 0.8 = 200)
height: 64, // 按比例调整 (80 * 0.8 = 64)
url: currentMockData.value.activity.logo,
zIndex: 2
zIndex: 2,
},
// 关卡徽章
{
......@@ -414,7 +452,7 @@ const posterConfig = computed(() => {
width: 304, // 按比例调整 (380 * 0.8 = 304)
height: 80, // 按比例调整 (100 * 0.8 = 80)
url: currentMockData.value.level.logo,
zIndex: 2
zIndex: 2,
},
// 小程序码
{
......@@ -423,8 +461,8 @@ const posterConfig = computed(() => {
width: 144, // 按比例调整 (180 * 0.8 = 144)
height: 144, // 按比例调整 (180 * 0.8 = 144)
url: currentMockData.value.qrcode,
zIndex: 1
}
zIndex: 1,
},
],
texts: [
// 家庭名称
......@@ -440,7 +478,7 @@ const posterConfig = computed(() => {
shadowOffsetX: 2,
shadowOffsetY: 2,
shadowBlur: 4,
zIndex: 2
zIndex: 2,
},
// 家庭描述
// {
......@@ -467,7 +505,7 @@ const posterConfig = computed(() => {
lineNum: 2,
width: 352, // 按比例调整 (440 * 0.8 = 352)
textAlign: 'left',
zIndex: 1
zIndex: 1,
},
// 关卡描述
{
......@@ -480,7 +518,7 @@ const posterConfig = computed(() => {
lineNum: 2,
width: 352, // 按比例调整 (440 * 0.8 = 352)
textAlign: 'left',
zIndex: 1
zIndex: 1,
},
],
blocks: [
......@@ -491,7 +529,7 @@ const posterConfig = computed(() => {
width: 600, // 按比例调整 (750 * 0.8 = 600)
height: 427, // 按比例调整 (534 * 0.8 = 427)
backgroundColor: '#ffffff',
zIndex: 0
zIndex: 0,
},
// 用户信息背景遮罩
// {
......@@ -503,7 +541,7 @@ const posterConfig = computed(() => {
// borderRadius: 8, // 按比例调整 (10 * 0.8 = 8)
// zIndex: 1
// }
]
],
}
})
......@@ -540,7 +578,9 @@ onMounted(async () => {
/**
* 监听背景图变化,重新生成海报
*/
watch(backgroundImages, (newVal, oldVal) => {
watch(
backgroundImages,
(newVal, oldVal) => {
// 只有当前海报的背景图发生变化时才重新生成
const currentIndex = currentPosterIndex.value
const newBgImage = newVal[currentIndex]
......@@ -559,7 +599,9 @@ watch(backgroundImages, (newVal, oldVal) => {
// 重新生成海报
generateCurrentPoster()
}
}, { deep: true })
},
{ deep: true }
)
/**
* 监听当前海报索引变化,切换海报
......@@ -574,17 +616,17 @@ watch(currentPosterIndex, (newIndex, oldIndex) => {
/**
* 生成当前海报配置的哈希值
*/
const generateConfigHash = (config) => {
const generateConfigHash = config => {
const configStr = JSON.stringify({
backgroundImage: backgroundImages.value[currentPosterIndex.value],
posterIndex: currentPosterIndex.value,
mockData: currentMockData.value
mockData: currentMockData.value,
})
// 简单哈希函数
let hash = 0
for (let i = 0; i < configStr.length; i++) {
const char = configStr.charCodeAt(i)
hash = ((hash << 5) - hash) + char
hash = (hash << 5) - hash + char
hash = hash & hash // 转换为32位整数
}
return hash.toString()
......@@ -606,7 +648,7 @@ const generateCurrentPosterIfNeeded = () => {
hasCustomBackground,
currentHash,
lastHash,
hashChanged: lastHash !== currentHash
hashChanged: lastHash !== currentHash,
})
// 如果海报未生成过,或者配置发生了变化,则需要重新生成
......@@ -668,39 +710,39 @@ const chooseBackgroundImage = () => {
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
success: res => {
const tempFile = res.tempFiles[0]
if (tempFile.size > 5 * 1024 * 1024) {
Taro.showToast({
title: '图片大小不能超过5MB',
icon: 'none'
icon: 'none',
})
return
}
uploadBackgroundImage(tempFile.path)
},
fail: (error) => {
fail: error => {
console.error('选择图片失败:', error)
// Taro.showToast({
// title: '选择图片失败,请重试',
// icon: 'none'
// })
}
},
})
}
/**
* 上传背景图片
*/
const uploadBackgroundImage = (filePath) => {
const uploadBackgroundImage = filePath => {
Taro.showLoading({ title: '上传中...' })
Taro.uploadFile({
url: BASE_URL + '/admin/?m=srv&a=upload',
filePath,
name: 'file',
success: async (uploadRes) => {
success: async uploadRes => {
const data = JSON.parse(uploadRes.data)
if (data.code === 0 && data.data) {
const currentIndex = currentPosterIndex.value
......@@ -713,7 +755,7 @@ const uploadBackgroundImage = (filePath) => {
try {
const saveResult = await savePosterBackgroundAPI({
detail_id: currentPosterData.checkPointId,
poster_background_url: data.data.src
poster_background_url: data.data.src,
})
if (saveResult.code === 1) {
......@@ -744,18 +786,18 @@ const uploadBackgroundImage = (filePath) => {
Taro.showToast({ title: data.msg || '上传失败', icon: 'none' })
}
},
fail: (error) => {
fail: error => {
console.error('上传文件失败:', error)
Taro.hideLoading()
Taro.showToast({ title: '上传失败,请稍后重试', icon: 'none' })
}
},
})
}
/**
* 海报生成成功
*/
const onPosterSuccess = (result) => {
const onPosterSuccess = result => {
const currentIndex = currentPosterIndex.value
posterPath.value = result.tempFilePath
posterGenerateFailed.value = false
......@@ -774,7 +816,7 @@ const onPosterSuccess = (result) => {
console.log('海报生成成功:', {
currentIndex,
posterPath: result.tempFilePath,
hasCustomBackground: !!backgroundImages.value[currentIndex]
hasCustomBackground: !!backgroundImages.value[currentIndex],
})
Taro.showToast({ title: '海报生成成功', icon: 'success' })
......@@ -783,7 +825,7 @@ const onPosterSuccess = (result) => {
/**
* 海报生成失败
*/
const onPosterFail = (error) => {
const onPosterFail = error => {
const currentIndex = currentPosterIndex.value
shouldGeneratePoster.value = false
posterGenerateFailed.value = true
......@@ -843,7 +885,7 @@ const savePoster = () => {
success: () => {
Taro.showToast({ title: '保存成功', icon: 'success' })
},
fail: (err) => {
fail: err => {
if (err.errMsg.includes('auth deny')) {
Taro.showModal({
title: '提示',
......@@ -852,12 +894,12 @@ const savePoster = () => {
confirmText: '去设置',
success: () => {
Taro.openSetting()
}
},
})
} else {
Taro.showToast({ title: '保存失败', icon: 'none' })
}
}
},
})
}
......@@ -870,18 +912,16 @@ const showNoActivityConfirm = () => {
content: '您还没有参加过活动,请先参加活动后再来生成海报',
showCancel: false,
confirmText: '知道了',
success: (res) => {
success: res => {
if (res.confirm) {
// 返回上一页
Taro.navigateBack({
delta: 1
delta: 1,
})
}
}
},
})
}
</script>
<style scoped>
......