hookehuyr

feat(PosterCheckinDetail): 创建海报打卡详情页面

- 基于 PosterCheckin 创建新页面
- 完全使用 map_activity.js 新接口(getPosterDetailAPI)
- 支持从活动页面分享海报跳转

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
......@@ -167,7 +167,7 @@ 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_activity'
import { getPosterDetailAPI, savePosterBackgroundAPI } from '@/api/map'
// 默认背景图
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'
......
/*
* @Date: 2026-02-09
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2026-02-09
* @FilePath: /lls_program/src/pages/PosterCheckinDetail/index.config.js
* @Description: 海报打卡详情页面配置 - 完全使用 map_activity.js 新接口
*/
export default {
navigationBarTitleText: '海报打卡详情',
usingComponents: {},
}
<template>
<view class="poster-checkin-page bg-gray-50 h-screen flex flex-col">
<!-- 加载状态 -->
<view v-if="isLoading" class="flex-1 flex items-center justify-center">
<view class="text-center">
<view class="text-gray-500 mb-2">加载中...</view>
</view>
</view>
<!-- 错误状态 -->
<view v-else-if="apiError" class="flex-1 flex items-center justify-center">
<view class="text-center">
<view class="text-red-500 mb-2">{{ apiError }}</view>
<view @tap="fetchPosterDetail" class="bg-blue-500 text-white px-4 py-2 rounded">
重新加载
</view>
</view>
</view>
<!-- 正常内容 -->
<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 class="text-lg font-bold text-gray-800 mb-2">
{{ activityInfo.title }}
</view>
<!-- 打卡进度 -->
<view class="flex items-center mb-2">
<view class="flex items-center">
<view
v-for="(point, index) in activityInfo.checkPoints"
:key="index"
class="w-2 h-2 rounded-full mr-2 flex items-center justify-center"
:class="point.completed ? 'bg-orange-400' : 'bg-gray-300'"
>
</view>
</view>
<text class="text-xs text-gray-600"
>{{ activityInfo.completedCount }}/{{ activityInfo.totalCount }}</text
>
</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 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" />
<!-- 打卡点标题 -->
<!-- <view class="absolute bottom-2 left-2 bg-blue-500 text-white text-xs px-2 py-1 rounded">
{{ posterList[currentPosterIndex]?.title || '海报生成中' }}
</view> -->
<!-- 点击预览提示 -->
<!-- <view @tap="previewPoster" class="absolute bottom-2 right-2 bg-black bg-opacity-50 text-white text-xs px-2 py-1 rounded">
点击预览
</view> -->
</view>
</view>
<!-- 左箭头按钮 -->
<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"
@tap="previousPoster"
>
<Left size="16"></Left>
</view>
<!-- 右箭头按钮 -->
<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"
@tap="nextPoster"
>
<Right size="16"></Right>
</view>
</view>
<!-- 没有打卡信息的空状态 -->
<view v-if="pageState === 'no-checkin'" class="flex-1 mx-4 mb-2 flex justify-center">
<view class="rounded-lg shadow-sm p-8 text-center max-w-sm">
<view class="text-6xl mb-4">📸</view>
<view class="text-lg font-bold text-gray-800 mb-2">您还没有打卡记录</view>
<view class="text-sm text-orange-500 mb-4">完成打卡后即可生成专属海报</view>
</view>
</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 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"
@click="chooseBackgroundImage"
>
<text>{{ currentPosterHasCustomBackground ? '更改照片' : '上传照片' }}</text>
</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'
"
@click="handlePosterAction"
:disabled="!posterPath && !posterGenerateFailed"
>
<text>{{
posterPath ? '保存海报' : posterGenerateFailed ? '重新生成' : '生成中...'
}}</text>
</view>
</view>
</view>
</template>
<!-- 海报生成组件 - 仅在正常状态显示 -->
<PosterBuilder
v-if="shouldGeneratePoster && pageState === 'normal'"
:config="posterConfig"
:show-loading="true"
@success="onPosterSuccess"
@fail="onPosterFail"
/>
<!-- 图片预览 -->
<nut-image-preview
v-model:show="previewVisible"
:images="previewImages"
:init-no="0"
:show-index="false"
@close="closePreview"
/>
</view>
</template>
<script setup>
import { ref, onMounted, computed, watch } from 'vue'
import Taro from '@tarojs/taro'
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_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 posterPath = ref('') // 生成的海报路径
const backgroundImages = ref({}) // 每个海报的用户上传背景图 {posterIndex: imageUrl}
const shouldGeneratePoster = ref(false) // 是否应该生成海报
const currentPosterIndex = ref(0) // 当前显示的海报索引
const posterGenerateFailed = ref(false) // 海报生成是否失败
const posterGeneratedFlags = ref({}) // 每个海报的生成状态 {posterIndex: boolean}
const posterConfigHashes = ref({}) // 每个海报的配置哈希值,用于检测配置变化
// 页面参数
const pageParams = ref({
from: '',
id: '',
})
// API数据状态
const apiData = ref(null)
const isLoading = ref(false)
const apiError = ref(null)
// 图片预览相关
const previewVisible = ref(false)
const previewImages = ref([])
/**
* 获取海报详情数据
*/
const fetchPosterDetail = async () => {
try {
isLoading.value = true
apiError.value = null
const accountInfo = wx.getAccountInfoSync()
const envVersion = accountInfo.miniProgram.envVersion
// 小程序版本。正式版为 "release",体验版为 "trial"。默认是正式版
const env_version = envVersion === 'release' ? 'release' : 'trial'
const response = await getPosterDetailAPI({ env_version })
if (response.code === 1) {
apiData.value = response.data
console.log('获取海报详情成功:', response.data)
// 根据API数据更新活动信息
updateActivityInfo()
// 根据API数据更新海报列表
updatePosterList()
// 根据pageParams.id设置当前海报索引
setCurrentPosterIndex()
} else {
apiError.value = response.msg || '获取海报详情失败'
console.error('获取海报详情失败:', response.msg)
}
} catch (error) {
apiError.value = '网络请求失败'
console.error('获取海报详情异常:', error)
} finally {
isLoading.value = false
}
}
/**
* 根据API数据更新活动信息
*/
const updateActivityInfo = () => {
if (!apiData.value) {
return
}
const { title, end_date, details, show_detail_index } = apiData.value
// 转换details为checkPoints格式
const checkPoints = details.map((detail, index) => ({
id: detail.id,
name: detail.name,
completed: detail.is_checked === true,
}))
// 计算已完成数量
const completedCount = checkPoints.filter(point => point.completed).length
activityInfo.value = {
title: title || '海报打卡活动',
checkPoints,
completedCount,
totalCount: checkPoints.length,
endDate: end_date || '',
showDetailIndex: show_detail_index || 0,
}
}
/**
* 根据API数据更新海报列表
*/
const updatePosterList = () => {
if (!apiData.value) {
return
}
const { details, family, qrcode_url } = apiData.value
// 只显示is_checked为真的关卡
const checkedDetails = details.filter(detail => detail.is_checked === true)
posterList.value = checkedDetails.map((detail, index) => ({
id: detail.id,
title: detail.name,
path: '',
checkPointId: detail.id,
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: '用户昵称', // 默认昵称,后续可从用户信息获取
},
family: {
name: family?.name || '我的家庭',
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',
},
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',
name: detail.name || '海报打卡活动',
},
qrcode: qrcode_url,
qrcodeDesc: '长按识别,来,我们一起打卡!',
}))
}
/**
* 根据pageParams.id设置当前海报索引
*/
const setCurrentPosterIndex = () => {
if (!posterList.value.length) {
currentPosterIndex.value = 0
return
}
// 如果有指定的ID,查找对应的海报索引
if (pageParams.value.id) {
const targetIndex = posterList.value.findIndex(
poster => poster.checkPointId.toString() === pageParams.value.id.toString()
)
if (targetIndex !== -1) {
currentPosterIndex.value = targetIndex
return
}
}
// 如果没有ID或没找到对应的关卡ID,默认显示第一个已打卡的项(索引0)
// 因为posterList已经过滤了is_checked=true的项,所以第一个就是我们要的
currentPosterIndex.value = 0
}
// 活动信息数据 - 将由API数据填充
const activityInfo = ref({
title: '',
checkPoints: [],
completedCount: 0,
totalCount: 0,
endDate: '',
showDetailIndex: 0,
})
// 海报数据列表 - 将由API数据填充
const posterList = ref([])
// 数据状态检查
const hasActivityInfo = computed(() => {
return (
activityInfo.value &&
activityInfo.value.title &&
activityInfo.value.checkPoints &&
activityInfo.value.checkPoints.length > 0
)
})
const hasCheckinInfo = computed(() => {
return posterList.value && posterList.value.length > 0
})
// 页面显示状态
const pageState = computed(() => {
if (!hasActivityInfo.value) {
return 'no-activity' // 没有活动信息
}
if (!hasCheckinInfo.value) {
return 'no-checkin' // 有活动信息但没有打卡信息
}
return 'normal' // 正常状态
})
// 当前海报
const currentPoster = computed(() => {
return posterList.value[currentPosterIndex.value] || { path: '', title: '' }
})
// 当前海报的内容数据
const currentMockData = computed(() => {
const currentPoster = posterList.value[currentPosterIndex.value]
if (!currentPoster) {
return null
}
return {
user: currentPoster.user,
family: currentPoster.family,
activity: currentPoster.activity,
level: currentPoster.level,
qrcode: currentPoster.qrcode,
qrcodeDesc: currentPoster.qrcodeDesc,
}
})
// 当前海报是否有用户上传的背景图
const currentPosterHasCustomBackground = computed(() => {
return !!backgroundImages.value[currentPosterIndex.value]
})
// 海报配置
const posterConfig = computed(() => {
const currentPosterData = posterList.value[currentPosterIndex.value]
const bgImage =
backgroundImages.value[currentPosterIndex.value] ||
currentPosterData?.backgroundImage ||
defaultBackground
return {
width: 600, // 从750减少到600,减小Canvas尺寸以控制文件大小
height: 1067, // 按比例调整高度 (1334 * 600/750 = 1067)
backgroundColor: '#f5f5f5',
debug: false,
borderRadius: 12, // 按比例调整圆角 (15 * 600/750 = 12)
images: [
// 背景图
{
x: 0,
y: 0,
width: 600, // 按比例调整 (750 * 0.8 = 600)
height: 880, // 按比例调整 (1100 * 0.8 = 880)
url: bgImage,
zIndex: 0,
},
// 用户头像
{
x: 32, // 按比例调整 (40 * 0.8 = 32)
y: 32, // 按比例调整 (40 * 0.8 = 32)
width: 104, // 按比例调整 (130 * 0.8 = 104)
height: 104, // 按比例调整 (130 * 0.8 = 104)
url: currentMockData.value.user.avatar,
borderRadius: 52, // 按比例调整 (65 * 0.8 = 52)
zIndex: 2,
},
// 活动logo
{
x: 360, // 按比例调整 (450 * 0.8 = 360)
y: 32, // 按比例调整 (40 * 0.8 = 32)
width: 200, // 按比例调整 (250 * 0.8 = 200)
height: 64, // 按比例调整 (80 * 0.8 = 64)
url: currentMockData.value.activity.logo,
zIndex: 2,
},
// 关卡徽章
{
x: 8, // 按比例调整 (10 * 0.8 = 8)
y: 704, // 按比例调整 (880 * 0.8 = 704)
width: 304, // 按比例调整 (380 * 0.8 = 304)
height: 80, // 按比例调整 (100 * 0.8 = 80)
url: currentMockData.value.level.logo,
zIndex: 2,
},
// 小程序码
{
x: 24, // 按比例调整 (30 * 0.8 = 24)
y: 880, // 按比例调整 (1100 * 0.8 = 880)
width: 144, // 按比例调整 (180 * 0.8 = 144)
height: 144, // 按比例调整 (180 * 0.8 = 144)
url: currentMockData.value.qrcode,
zIndex: 1,
},
],
texts: [
// 家庭名称
{
x: 32, // 按比例调整 (40 * 0.8 = 32)
y: 152, // 按比例调整 (190 * 0.8 = 152)
text: currentMockData.value.family.name,
fontSize: 29, // 按比例调整 (36 * 0.8 = 29)
color: '#ffffff',
fontWeight: 'bold',
textAlign: 'left',
shadowColor: 'rgba(0, 0, 0, 0.6)',
shadowOffsetX: 2,
shadowOffsetY: 2,
shadowBlur: 4,
zIndex: 2,
},
// 家庭描述
// {
// x: 32, // 按比例调整 (40 * 0.8 = 32)
// y: 200, // 按比例调整 (250 * 0.8 = 200)
// text: currentMockData.value.family.description,
// fontSize: 22, // 按比例调整 (28 * 0.8 = 22)
// color: '#ffffff',
// textAlign: 'left',
// shadowColor: 'rgba(0, 0, 0, 0.6)',
// shadowOffsetX: 2,
// shadowOffsetY: 2,
// shadowBlur: 4,
// zIndex: 2
// },
// 小程序码描述
{
x: 208, // 按比例调整 (260 * 0.8 = 208)
y: 900, // 按比例调整 (1125 * 0.8 = 900)
text: currentMockData.value.qrcodeDesc,
fontSize: 24, // 按比例调整 (30 * 0.8 = 24)
color: '#333333',
lineHeight: 32, // 按比例调整 (40 * 0.8 = 32)
lineNum: 2,
width: 352, // 按比例调整 (440 * 0.8 = 352)
textAlign: 'left',
zIndex: 1,
},
// 关卡描述
{
x: 208, // 按比例调整 (260 * 0.8 = 208)
y: 944, // 按比例调整 (1180 * 0.8 = 944)
text: '打卡点: ' + currentMockData.value.level.name,
fontSize: 26, // 按比例调整 (32 * 0.8 = 26)
color: '#333333',
lineHeight: 40, // 按比例调整 (50 * 0.8 = 40)
lineNum: 2,
width: 352, // 按比例调整 (440 * 0.8 = 352)
textAlign: 'left',
zIndex: 1,
},
],
blocks: [
// 下半部分白色背景
{
x: 0,
y: 840, // 按比例调整 (1050 * 0.8 = 840)
width: 600, // 按比例调整 (750 * 0.8 = 600)
height: 427, // 按比例调整 (534 * 0.8 = 427)
backgroundColor: '#ffffff',
zIndex: 0,
},
// 用户信息背景遮罩
// {
// x: 24, // 按比例调整 (30 * 0.8 = 24)
// y: 144, // 按比例调整 (180 * 0.8 = 144)
// width: 360, // 按比例调整 (450 * 0.8 = 360)
// height: 112, // 按比例调整 (140 * 0.8 = 112)
// backgroundColor: 'rgba(0,0,0,0.3)',
// borderRadius: 8, // 按比例调整 (10 * 0.8 = 8)
// zIndex: 1
// }
],
}
})
/**
* 页面加载时初始化
*/
onMounted(async () => {
Taro.setNavigationBarTitle({ title: '海报打卡' })
// 获取页面参数
const instance = Taro.getCurrentInstance()
const params = instance.router?.params || {}
pageParams.value = {
id: params.id || '',
}
console.log('海报打卡页面接收到的参数:', pageParams.value)
// 获取海报详情数据
await fetchPosterDetail()
// 数据获取完成后检查页面状态
if (pageState.value === 'no-activity') {
// 没有活动信息,显示确认对话框
showNoActivityConfirm()
return
}
// 页面加载时检查是否需要生成当前海报
generateCurrentPosterIfNeeded()
})
/**
* 监听背景图变化,重新生成海报
*/
watch(
backgroundImages,
(newVal, oldVal) => {
// 只有当前海报的背景图发生变化时才重新生成
const currentIndex = currentPosterIndex.value
const newBgImage = newVal[currentIndex]
const oldBgImage = oldVal?.[currentIndex]
if (newBgImage !== oldBgImage) {
console.log('背景图发生变化:', { currentIndex, newBgImage, oldBgImage })
// 标记当前海报需要重新生成
posterGeneratedFlags.value[currentIndex] = false
delete posterConfigHashes.value[currentIndex]
// 清除当前海报路径
posterPath.value = ''
// 重新生成海报
generateCurrentPoster()
}
},
{ deep: true }
)
/**
* 监听当前海报索引变化,切换海报
*/
watch(currentPosterIndex, (newIndex, oldIndex) => {
// 切换海报时,检查新海报是否已生成
if (newIndex !== oldIndex) {
generateCurrentPosterIfNeeded()
}
})
/**
* 生成当前海报配置的哈希值
*/
const generateConfigHash = config => {
const configStr = JSON.stringify({
backgroundImage: backgroundImages.value[currentPosterIndex.value],
posterIndex: currentPosterIndex.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 & hash // 转换为32位整数
}
return hash.toString()
}
/**
* 检查是否需要生成当前海报
*/
const generateCurrentPosterIfNeeded = () => {
const currentIndex = currentPosterIndex.value
const currentHash = generateConfigHash()
const isGenerated = posterGeneratedFlags.value[currentIndex]
const lastHash = posterConfigHashes.value[currentIndex]
const hasCustomBackground = !!backgroundImages.value[currentIndex]
console.log('检查是否需要生成海报:', {
currentIndex,
isGenerated,
hasCustomBackground,
currentHash,
lastHash,
hashChanged: lastHash !== currentHash,
})
// 如果海报未生成过,或者配置发生了变化,则需要重新生成
if (!isGenerated || lastHash !== currentHash) {
console.log('需要重新生成海报')
posterConfigHashes.value[currentIndex] = currentHash
generateCurrentPoster()
} else {
// 海报已存在且配置未变化,直接使用缓存的海报
const cachedPoster = posterList.value[currentIndex]
if (cachedPoster && cachedPoster.path) {
console.log('使用缓存的海报:', cachedPoster.path)
posterPath.value = cachedPoster.path
posterGenerateFailed.value = false
} else {
console.log('缓存的海报路径不存在,重新生成')
generateCurrentPoster()
}
}
}
/**
* 生成当前海报
*/
const generateCurrentPoster = () => {
posterPath.value = ''
posterGenerateFailed.value = false
shouldGeneratePoster.value = false
// 延迟触发生成,确保配置更新
setTimeout(() => {
shouldGeneratePoster.value = true
}, 100)
}
/**
* 切换到上一张海报
*/
const previousPoster = () => {
if (currentPosterIndex.value > 0) {
currentPosterIndex.value = currentPosterIndex.value - 1
}
}
/**
* 切换到下一张海报
*/
const nextPoster = () => {
if (currentPosterIndex.value < posterList.value.length - 1) {
currentPosterIndex.value = currentPosterIndex.value + 1
}
}
/**
* 选择背景图片
*/
const chooseBackgroundImage = () => {
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: res => {
const tempFile = res.tempFiles[0]
if (tempFile.size > 5 * 1024 * 1024) {
Taro.showToast({
title: '图片大小不能超过5MB',
icon: 'none',
})
return
}
uploadBackgroundImage(tempFile.path)
},
fail: error => {
console.error('选择图片失败:', error)
// Taro.showToast({
// title: '选择图片失败,请重试',
// icon: 'none'
// })
},
})
}
/**
* 上传背景图片
*/
const uploadBackgroundImage = filePath => {
Taro.showLoading({ title: '上传中...' })
Taro.uploadFile({
url: BASE_URL + '/admin/?m=srv&a=upload',
filePath,
name: 'file',
success: async uploadRes => {
const data = JSON.parse(uploadRes.data)
if (data.code === 0 && data.data) {
const currentIndex = currentPosterIndex.value
const currentPosterData = posterList.value[currentIndex]
// 为当前海报设置背景图
backgroundImages.value[currentIndex] = data.data.src
// 调用保存海报背景接口
try {
const saveResult = await savePosterBackgroundAPI({
detail_id: currentPosterData.checkPointId,
poster_background_url: data.data.src,
})
if (saveResult.code === 1) {
console.log('海报背景保存成功:', saveResult.data)
} else {
console.warn('海报背景保存失败:', saveResult.msg)
// 即使保存失败,也继续生成海报,不影响用户体验
}
} catch (error) {
console.error('保存海报背景异常:', error)
// 即使保存失败,也继续生成海报,不影响用户体验
}
// 强制标记当前海报需要重新生成
posterGeneratedFlags.value[currentIndex] = false
delete posterConfigHashes.value[currentIndex]
// 清除当前海报路径,强制重新生成
posterPath.value = ''
// 立即重新生成海报
generateCurrentPoster()
Taro.hideLoading()
Taro.showToast({ title: '上传成功', icon: 'success' })
} else {
Taro.hideLoading()
Taro.showToast({ title: data.msg || '上传失败', icon: 'none' })
}
},
fail: error => {
console.error('上传文件失败:', error)
Taro.hideLoading()
Taro.showToast({ title: '上传失败,请稍后重试', icon: 'none' })
},
})
}
/**
* 海报生成成功
*/
const onPosterSuccess = result => {
const currentIndex = currentPosterIndex.value
posterPath.value = result.tempFilePath
posterGenerateFailed.value = false
// 更新当前海报的路径和生成状态
if (posterList.value[currentIndex]) {
posterList.value[currentIndex].path = result.tempFilePath
}
posterGeneratedFlags.value[currentIndex] = true
// 保存当前配置的哈希值
posterConfigHashes.value[currentIndex] = generateConfigHash()
shouldGeneratePoster.value = false
console.log('海报生成成功:', {
currentIndex,
posterPath: result.tempFilePath,
hasCustomBackground: !!backgroundImages.value[currentIndex],
})
Taro.showToast({ title: '海报生成成功', icon: 'success' })
}
/**
* 海报生成失败
*/
const onPosterFail = error => {
const currentIndex = currentPosterIndex.value
shouldGeneratePoster.value = false
posterGenerateFailed.value = true
posterPath.value = ''
// 标记当前海报生成失败,下次仍需重新生成
posterGeneratedFlags.value[currentIndex] = false
Taro.showToast({ title: '海报生成失败', icon: 'none' })
console.error('海报生成失败:', error)
}
/**
* 预览海报
*/
// const previewPoster = () => {
// const currentPath = currentPoster.value.path
// console.warn('预览海报', currentPath)
// if (!currentPath) return
// previewImages.value = [{ src: currentPath }]
// previewVisible.value = true
// }
/**
* 关闭预览
*/
const closePreview = () => {
previewVisible.value = false
}
/**
* 处理海报按钮点击事件
*/
const handlePosterAction = () => {
if (posterPath.value) {
// 如果海报已生成,执行保存操作
savePoster()
} else if (posterGenerateFailed.value) {
// 如果生成失败,重新生成海报
generateCurrentPoster()
}
}
/**
* 保存海报到相册
*/
const savePoster = () => {
const currentPath = currentPoster.value.path
if (!currentPath) {
Taro.showToast({ title: '请等待海报生成完成', icon: 'none' })
return
}
Taro.saveImageToPhotosAlbum({
filePath: currentPath,
success: () => {
Taro.showToast({ title: '保存成功', icon: 'success' })
},
fail: err => {
if (err.errMsg.includes('auth deny')) {
Taro.showModal({
title: '提示',
content: '需要您授权保存图片到相册',
showCancel: false,
confirmText: '去设置',
success: () => {
Taro.openSetting()
},
})
} else {
Taro.showToast({ title: '保存失败', icon: 'none' })
}
},
})
}
/**
* 显示没有活动信息的确认对话框
*/
const showNoActivityConfirm = () => {
Taro.showModal({
title: '温馨提示',
content: '您还没有参加过活动,请先参加活动后再来生成海报',
showCancel: false,
confirmText: '知道了',
success: res => {
if (res.confirm) {
// 返回上一页
Taro.navigateBack({
delta: 1,
})
}
},
})
}
</script>
<style scoped>
.safe-area-bottom {
padding-bottom: env(safe-area-inset-bottom);
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.animate-spin {
animation: spin 1s linear infinite;
}
</style>
<style lang="less">
.poster-checkin-page {
.aspect-\[3\/4\] {
aspect-ratio: 3/4;
}
}
</style>