1291 lines
31 KiB
Vue
1291 lines
31 KiB
Vue
<template>
|
||
<view class="generation-result">
|
||
<!-- 背景图 -->
|
||
<image class="background-image" src="/static/background/exhibitionSuccess.png" mode="aspectFill" />
|
||
|
||
<!-- 星星装饰 -->
|
||
<view class="stars-container">
|
||
<view v-for="i in 20" :key="i" class="star" :style="getStarStyle(i)"></view>
|
||
</view>
|
||
|
||
<!-- 顶部奖励提示 -->
|
||
<view class="reward-tips">
|
||
<view class="reward-item">
|
||
<text class="reward-text">恭喜您获得水晶</text>
|
||
<image class="reward-icon" src="/static/icon/crystal.png" mode="aspectFit" />
|
||
<text class="reward-text">X 32</text>
|
||
</view>
|
||
<view class="reward-item">
|
||
<text class="reward-text">恭喜您获小卡碎片</text>
|
||
<image class="reward-icon" src="/static/starcity/xiaoka.png" mode="aspectFit" />
|
||
<text class="reward-text">X 2</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 图片卡片区域 -->
|
||
<view class="cards-container" :class="{ 'cards-visible': isGiftOpened }">
|
||
<view
|
||
v-for="(image, index) in generatedImages"
|
||
:key="index"
|
||
class="card-item"
|
||
:class="{ 'card-selected': selectedIndex === index }"
|
||
:style="getCardStyle(index)"
|
||
@click="selectCard(index)"
|
||
>
|
||
<view class="card-frame">
|
||
<image
|
||
class="card-image"
|
||
:src="image"
|
||
mode="aspectFill"
|
||
/>
|
||
|
||
<!-- 选中标记 -->
|
||
<view v-if="selectedIndex === index" class="selected-mark">
|
||
<text class="check-icon">✓</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 礼盒 -->
|
||
<view class="gift-box" :class="{ 'gift-opened': isGiftOpened }">
|
||
<view class="gift-container">
|
||
<view class="gift-lid">
|
||
<view class="gift-bow"></view>
|
||
<view class="gift-lid-top">
|
||
<text class="gift-text">TOPFANS</text>
|
||
</view>
|
||
</view>
|
||
<view class="gift-body">
|
||
<view class="gift-window">
|
||
<text class="question-mark">?</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部按钮 -->
|
||
<view class="bottom-action">
|
||
<view class="action-button" @click="selectAsset" @tap="selectAsset">
|
||
<text class="button-text">选择您的藏品</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from 'vue';
|
||
import { getOssSignatureApi, createMintOrderApi } from '@/utils/api.js';
|
||
|
||
const generatedImages = ref([]);
|
||
const selectedIndex = ref(-1);
|
||
const isUploading = ref(false);
|
||
const currentOrderId = ref(''); // 保存当前订单ID
|
||
const isGiftOpened = ref(false); // 礼盒是否已打开
|
||
|
||
// 获取星星样式
|
||
const getStarStyle = (index) => {
|
||
const left = Math.random() * 100;
|
||
const top = Math.random() * 100;
|
||
const size = Math.random() * 3 + 1;
|
||
const delay = Math.random() * 3;
|
||
|
||
return {
|
||
left: `${left}%`,
|
||
top: `${top}%`,
|
||
width: `${size}rpx`,
|
||
height: `${size}rpx`,
|
||
animationDelay: `${delay}s`
|
||
};
|
||
};
|
||
|
||
// 获取卡片样式
|
||
const getCardStyle = (index) => {
|
||
const positions = [
|
||
{ left: '8%', top: '18%', rotate: '-12deg', scale: 1 },
|
||
{ left: '52%', top: '15%', rotate: '8deg', scale: 0.92 },
|
||
{ left: '5%', top: '42%', rotate: '-6deg', scale: 0.95 },
|
||
{ left: '50%', top: '45%', rotate: '10deg', scale: 0.93 }
|
||
];
|
||
|
||
const pos = positions[index] || positions[0];
|
||
|
||
// 如果是选中的卡片,添加高亮效果
|
||
if (selectedIndex.value === index) {
|
||
return {
|
||
left: pos.left,
|
||
top: pos.top,
|
||
transform: `rotate(${pos.rotate}) scale(${pos.scale * 1.05})`,
|
||
filter: 'brightness(1.2)',
|
||
zIndex: 100
|
||
};
|
||
}
|
||
|
||
return {
|
||
left: pos.left,
|
||
top: pos.top,
|
||
transform: `rotate(${pos.rotate}) scale(${pos.scale})`
|
||
};
|
||
};
|
||
|
||
// 选择卡片
|
||
const selectCard = (index) => {
|
||
selectedIndex.value = index;
|
||
|
||
// 可以添加选中效果
|
||
uni.showToast({
|
||
title: `已选择第${index + 1}张卡片`,
|
||
icon: 'none',
|
||
duration: 1000
|
||
});
|
||
};
|
||
|
||
// 将base64转换为Blob
|
||
const base64ToBlob = (base64Data) => {
|
||
const parts = base64Data.split(';base64,');
|
||
const contentType = parts[0].split(':')[1];
|
||
const raw = atob(parts[1]);
|
||
const rawLength = raw.length;
|
||
const uInt8Array = new Uint8Array(rawLength);
|
||
|
||
for (let i = 0; i < rawLength; i++) {
|
||
uInt8Array[i] = raw.charCodeAt(i);
|
||
}
|
||
|
||
return new Blob([uInt8Array], { type: contentType });
|
||
};
|
||
|
||
// 上传图片到OSS
|
||
const uploadImageToOss = async (base64Image) => {
|
||
console.log('[GenerationResult] uploadImageToOss 开始');
|
||
isUploading.value = true;
|
||
|
||
try {
|
||
uni.showLoading({ title: '上传中...', mask: true });
|
||
|
||
// 1. 获取OSS签名
|
||
const signRes = await getOssSignatureApi('asset');
|
||
console.log('[GenerationResult] 获取签名结果:', signRes);
|
||
|
||
if (signRes.code !== 200) {
|
||
throw new Error(signRes.message || '获取签名失败');
|
||
}
|
||
|
||
// 保存order_id
|
||
currentOrderId.value = signRes.data.order_id || '';
|
||
console.log('[GenerationResult] order_id:', currentOrderId.value);
|
||
|
||
// 2. 生成文件名
|
||
const timestamp = Date.now();
|
||
const randomStr = Math.random().toString(36).substring(2, 8);
|
||
const fileName = `ai_generated_${timestamp}_${randomStr}.png`;
|
||
|
||
let imageUrl;
|
||
|
||
// #ifdef H5
|
||
// H5环境:使用FormData + Blob
|
||
console.log('[GenerationResult] H5环境,使用Blob上传');
|
||
const blob = base64ToBlob(base64Image);
|
||
console.log('[GenerationResult] Blob创建成功,大小:', blob.size);
|
||
|
||
const formData = new FormData();
|
||
formData.append('key', signRes.data.dir + fileName);
|
||
formData.append('policy', signRes.data.policy);
|
||
formData.append('success_action_status', '200');
|
||
formData.append('x-oss-credential', signRes.data.x_oss_credential);
|
||
formData.append('x-oss-date', signRes.data.x_oss_date);
|
||
formData.append('x-oss-security-token', signRes.data.security_token);
|
||
formData.append('x-oss-signature', signRes.data.signature);
|
||
formData.append('x-oss-signature-version', signRes.data.x_oss_signature_version);
|
||
formData.append('file', blob, fileName);
|
||
|
||
const response = await fetch(signRes.data.host, {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
console.log('[GenerationResult] OSS响应状态:', response.status);
|
||
|
||
if (response.ok || response.status === 204) {
|
||
imageUrl = `${signRes.data.host}/${signRes.data.dir}${fileName}`;
|
||
} else {
|
||
throw new Error(`上传失败,状态码: ${response.status}`);
|
||
}
|
||
// #endif
|
||
|
||
// #ifndef H5
|
||
// App/小程序环境:使用uni.uploadFile
|
||
console.log('[GenerationResult] 非H5环境,使用uni.uploadFile');
|
||
|
||
let tempFilePath; // 声明临时文件路径变量
|
||
|
||
// #ifdef APP-PLUS
|
||
// App环境:直接使用base64上传(通过临时文件)
|
||
console.log('[GenerationResult] App环境,使用base64临时文件');
|
||
|
||
// 使用uni.saveFile保存base64为临时文件
|
||
const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, '');
|
||
|
||
// 先转换为临时文件路径
|
||
tempFilePath = await new Promise((resolve, reject) => {
|
||
// 使用uni的base64ToTempFilePath(如果可用)
|
||
if (uni.base64ToTempFilePath) {
|
||
uni.base64ToTempFilePath({
|
||
base64Data: base64Image,
|
||
success: (res) => {
|
||
console.log('[GenerationResult] base64转临时文件成功:', res.tempFilePath);
|
||
resolve(res.tempFilePath);
|
||
},
|
||
fail: (error) => {
|
||
console.error('[GenerationResult] base64转临时文件失败:', error);
|
||
reject(new Error('base64转临时文件失败'));
|
||
}
|
||
});
|
||
} else {
|
||
// 降级方案:使用plus.io
|
||
console.log('[GenerationResult] 使用plus.io方案');
|
||
const bitmap = new plus.nativeObj.Bitmap('temp');
|
||
bitmap.loadBase64Data(base64Image, () => {
|
||
const tempPath = '_doc/' + fileName;
|
||
bitmap.save(tempPath, {}, () => {
|
||
console.log('[GenerationResult] 图片保存成功:', tempPath);
|
||
bitmap.clear();
|
||
resolve(tempPath);
|
||
}, (error) => {
|
||
console.error('[GenerationResult] 图片保存失败:', error);
|
||
bitmap.clear();
|
||
reject(new Error('图片保存失败'));
|
||
});
|
||
}, (error) => {
|
||
console.error('[GenerationResult] 加载base64失败:', error);
|
||
bitmap.clear();
|
||
reject(new Error('加载base64失败'));
|
||
});
|
||
}
|
||
});
|
||
// #endif
|
||
|
||
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ
|
||
// 小程序环境:使用FileSystemManager
|
||
console.log('[GenerationResult] 小程序环境');
|
||
const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, '');
|
||
|
||
// #ifdef MP-WEIXIN
|
||
tempFilePath = `${wx.env.USER_DATA_PATH}/${fileName}`;
|
||
// #endif
|
||
// #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ
|
||
tempFilePath = `${uni.env.USER_DATA_PATH}/${fileName}`;
|
||
// #endif
|
||
|
||
console.log('[GenerationResult] 临时文件路径:', tempFilePath);
|
||
|
||
const fs = uni.getFileSystemManager();
|
||
await new Promise((resolve, reject) => {
|
||
fs.writeFile({
|
||
filePath: tempFilePath,
|
||
data: base64Data,
|
||
encoding: 'base64',
|
||
success: () => {
|
||
console.log('[GenerationResult] 文件写入成功');
|
||
resolve();
|
||
},
|
||
fail: (error) => {
|
||
console.error('[GenerationResult] 文件写入失败:', error);
|
||
reject(new Error('保存临时文件失败'));
|
||
}
|
||
});
|
||
});
|
||
// #endif
|
||
|
||
console.log('[GenerationResult] 开始上传到OSS,文件路径:', tempFilePath);
|
||
|
||
// 上传到OSS
|
||
imageUrl = await new Promise((resolve, reject) => {
|
||
uni.uploadFile({
|
||
url: signRes.data.host,
|
||
filePath: tempFilePath,
|
||
name: 'file',
|
||
formData: {
|
||
key: signRes.data.dir + fileName,
|
||
policy: signRes.data.policy,
|
||
success_action_status: '200',
|
||
'x-oss-credential': signRes.data.x_oss_credential,
|
||
'x-oss-date': signRes.data.x_oss_date,
|
||
'x-oss-security-token': signRes.data.security_token,
|
||
'x-oss-signature': signRes.data.signature,
|
||
'x-oss-signature-version': signRes.data.x_oss_signature_version
|
||
},
|
||
success: (uploadRes) => {
|
||
console.log('[GenerationResult] OSS上传响应:', uploadRes.statusCode);
|
||
console.log('[GenerationResult] OSS上传完整响应:', uploadRes);
|
||
|
||
if (uploadRes.statusCode === 200 || uploadRes.statusCode === 204) {
|
||
const url = `${signRes.data.host}/${signRes.data.dir}${fileName}`;
|
||
console.log('[GenerationResult] 上传成功,URL:', url);
|
||
resolve(url);
|
||
} else {
|
||
reject(new Error(`上传失败,状态码: ${uploadRes.statusCode}`));
|
||
}
|
||
},
|
||
fail: (error) => {
|
||
console.error('[GenerationResult] OSS上传失败:', error);
|
||
reject(error);
|
||
}
|
||
});
|
||
});
|
||
// #endif
|
||
|
||
console.log('[GenerationResult] 上传成功,URL:', imageUrl);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '上传成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
});
|
||
isUploading.value = false;
|
||
return imageUrl;
|
||
|
||
} catch (error) {
|
||
console.error('[GenerationResult] 上传失败:', error);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: error.message || '上传失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
isUploading.value = false;
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// H5环境上传
|
||
const uploadToOssH5 = async (base64Image, fileName, ossData) => {
|
||
return new Promise((resolve, reject) => {
|
||
try {
|
||
// 将base64转换为Blob
|
||
const blob = base64ToBlob(base64Image);
|
||
|
||
// 构建FormData
|
||
const formData = new FormData();
|
||
formData.append('key', ossData.dir + fileName);
|
||
formData.append('policy', ossData.policy);
|
||
formData.append('success_action_status', '200');
|
||
formData.append('x-oss-credential', ossData.x_oss_credential);
|
||
formData.append('x-oss-date', ossData.x_oss_date);
|
||
formData.append('x-oss-security-token', ossData.security_token);
|
||
formData.append('x-oss-signature', ossData.signature);
|
||
formData.append('x-oss-signature-version', ossData.x_oss_signature_version);
|
||
formData.append('file', blob, fileName);
|
||
|
||
// 使用fetch上传
|
||
fetch(ossData.host, {
|
||
method: 'POST',
|
||
body: formData
|
||
})
|
||
.then(response => {
|
||
if (response.ok) {
|
||
const imageUrl = `${ossData.host}/${ossData.dir}${fileName}`;
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '上传成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
});
|
||
isUploading.value = false;
|
||
resolve(imageUrl); // 只返回URL
|
||
} else {
|
||
throw new Error(`上传失败,状态码: ${response.status}`);
|
||
}
|
||
})
|
||
.catch(error => {
|
||
uni.hideLoading();
|
||
isUploading.value = false;
|
||
reject(error);
|
||
});
|
||
} catch (error) {
|
||
uni.hideLoading();
|
||
isUploading.value = false;
|
||
reject(error);
|
||
}
|
||
});
|
||
};
|
||
|
||
// App/小程序环境上传
|
||
const uploadToOssNative = async (base64Image, fileName, ossData) => {
|
||
console.log('[GenerationResult] uploadToOssNative 开始');
|
||
console.log('[GenerationResult] fileName:', fileName);
|
||
|
||
return new Promise((resolve, reject) => {
|
||
try {
|
||
// 去掉base64前缀
|
||
const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, '');
|
||
console.log('[GenerationResult] base64数据长度:', base64Data.length);
|
||
|
||
// #ifdef MP-WEIXIN
|
||
// 微信小程序环境
|
||
console.log('[GenerationResult] 使用微信小程序环境');
|
||
const fs = uni.getFileSystemManager();
|
||
const tempFilePath = `${wx.env.USER_DATA_PATH}/${fileName}`;
|
||
console.log('[GenerationResult] 临时文件路径:', tempFilePath);
|
||
|
||
fs.writeFile({
|
||
filePath: tempFilePath,
|
||
data: base64Data,
|
||
encoding: 'base64',
|
||
success: () => {
|
||
console.log('[GenerationResult] 文件写入成功');
|
||
uploadFileToOss(tempFilePath, fileName, ossData, resolve, reject, fs);
|
||
},
|
||
fail: (error) => {
|
||
console.error('[GenerationResult] 保存临时文件失败:', error);
|
||
uni.hideLoading();
|
||
isUploading.value = false;
|
||
reject(new Error('保存临时文件失败'));
|
||
}
|
||
});
|
||
// #endif
|
||
|
||
// #ifdef APP-PLUS
|
||
// App环境
|
||
console.log('[GenerationResult] 使用App环境');
|
||
const fs = plus.io.getFileSystemManager ? uni.getFileSystemManager() : null;
|
||
console.log('[GenerationResult] FileSystemManager:', fs ? '可用' : '不可用');
|
||
|
||
if (!fs) {
|
||
// 使用plus.io API
|
||
console.log('[GenerationResult] 使用plus.io API');
|
||
const tempFilePath = `_doc/${fileName}`;
|
||
console.log('[GenerationResult] 临时文件路径:', tempFilePath);
|
||
|
||
plus.io.resolveLocalFileSystemURL('_doc', (entry) => {
|
||
console.log('[GenerationResult] 获取文件系统成功');
|
||
entry.getFile(fileName, { create: true }, (fileEntry) => {
|
||
console.log('[GenerationResult] 创建文件成功');
|
||
fileEntry.createWriter((writer) => {
|
||
writer.onwrite = () => {
|
||
console.log('[GenerationResult] 文件写入成功');
|
||
uploadFileToOss(fileEntry.toLocalURL(), fileName, ossData, resolve, reject, null);
|
||
};
|
||
writer.onerror = (error) => {
|
||
console.error('[GenerationResult] 写入文件失败:', error);
|
||
uni.hideLoading();
|
||
isUploading.value = false;
|
||
reject(new Error('写入文件失败'));
|
||
};
|
||
// 将base64转换为Blob
|
||
console.log('[GenerationResult] 开始转换base64为Blob');
|
||
try {
|
||
const blob = base64ToBlob(base64Image);
|
||
console.log('[GenerationResult] Blob创建成功,大小:', blob.size);
|
||
writer.write(blob);
|
||
} catch (blobError) {
|
||
console.error('[GenerationResult] Blob转换失败:', blobError);
|
||
uni.hideLoading();
|
||
isUploading.value = false;
|
||
reject(new Error('Blob转换失败'));
|
||
}
|
||
});
|
||
}, (fileError) => {
|
||
console.error('[GenerationResult] 创建文件失败:', fileError);
|
||
uni.hideLoading();
|
||
isUploading.value = false;
|
||
reject(new Error('创建文件失败'));
|
||
});
|
||
}, (error) => {
|
||
console.error('[GenerationResult] 获取文件系统失败:', error);
|
||
uni.hideLoading();
|
||
isUploading.value = false;
|
||
reject(new Error('获取文件系统失败'));
|
||
});
|
||
} else {
|
||
// 使用FileSystemManager API
|
||
console.log('[GenerationResult] 使用FileSystemManager API');
|
||
const tempFilePath = `${plus.io.getStorageRootPath()}${fileName}`;
|
||
console.log('[GenerationResult] 临时文件路径:', tempFilePath);
|
||
|
||
fs.writeFile({
|
||
filePath: tempFilePath,
|
||
data: base64Data,
|
||
encoding: 'base64',
|
||
success: () => {
|
||
console.log('[GenerationResult] 文件写入成功');
|
||
uploadFileToOss(tempFilePath, fileName, ossData, resolve, reject, fs);
|
||
},
|
||
fail: (error) => {
|
||
console.error('[GenerationResult] 保存临时文件失败:', error);
|
||
uni.hideLoading();
|
||
isUploading.value = false;
|
||
reject(new Error('保存临时文件失败'));
|
||
}
|
||
});
|
||
}
|
||
// #endif
|
||
|
||
} catch (error) {
|
||
console.error('[GenerationResult] uploadToOssNative异常:', error);
|
||
uni.hideLoading();
|
||
isUploading.value = false;
|
||
reject(error);
|
||
}
|
||
});
|
||
};
|
||
|
||
// 统一的文件上传逻辑
|
||
const uploadFileToOss = (tempFilePath, fileName, ossData, resolve, reject, fs) => {
|
||
console.log('[GenerationResult] uploadFileToOss 开始');
|
||
console.log('[GenerationResult] tempFilePath:', tempFilePath);
|
||
console.log('[GenerationResult] OSS host:', ossData.host);
|
||
|
||
uni.uploadFile({
|
||
url: ossData.host,
|
||
filePath: tempFilePath,
|
||
name: 'file',
|
||
formData: {
|
||
key: ossData.dir + fileName,
|
||
policy: ossData.policy,
|
||
success_action_status: '200',
|
||
'x-oss-credential': ossData.x_oss_credential,
|
||
'x-oss-date': ossData.x_oss_date,
|
||
'x-oss-security-token': ossData.security_token,
|
||
'x-oss-signature': ossData.signature,
|
||
'x-oss-signature-version': ossData.x_oss_signature_version
|
||
},
|
||
success: (uploadRes) => {
|
||
console.log('[GenerationResult] OSS上传响应:', uploadRes);
|
||
console.log('[GenerationResult] 状态码:', uploadRes.statusCode);
|
||
|
||
if (uploadRes.statusCode === 200 || uploadRes.statusCode === 204) {
|
||
const imageUrl = `${ossData.host}/${ossData.dir}${fileName}`;
|
||
console.log('[GenerationResult] 上传成功,图片URL:', imageUrl);
|
||
|
||
// 删除临时文件
|
||
if (fs) {
|
||
fs.unlink({
|
||
filePath: tempFilePath,
|
||
success: () => console.log('[GenerationResult] 临时文件已删除'),
|
||
fail: (err) => console.error('[GenerationResult] 删除临时文件失败:', err)
|
||
});
|
||
}
|
||
|
||
resolve(imageUrl);
|
||
} else {
|
||
console.error('[GenerationResult] 上传失败,状态码:', uploadRes.statusCode);
|
||
reject(new Error(`上传失败,状态码: ${uploadRes.statusCode}`));
|
||
}
|
||
},
|
||
fail: (error) => {
|
||
console.error('[GenerationResult] OSS上传失败:', error);
|
||
reject(error);
|
||
}
|
||
});
|
||
};
|
||
|
||
// 选择藏品
|
||
const selectAsset = async () => {
|
||
if (selectedIndex.value === -1) {
|
||
uni.showToast({
|
||
title: '请先选择一张卡片',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (isUploading.value) {
|
||
uni.showToast({
|
||
title: '上传中,请稍候',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 获取选中的图片(base64格式)
|
||
const selectedImage = generatedImages.value[selectedIndex.value];
|
||
|
||
// 上传到OSS(会自动设置currentOrderId)
|
||
const imageUrl = await uploadImageToOss(selectedImage);
|
||
|
||
// 创建铸造订单
|
||
uni.showLoading({ title: '创建订单中...', mask: true });
|
||
|
||
// 从storage获取表单数据
|
||
const formDataStr = uni.getStorageSync('castlove_form_data');
|
||
const orderValue = formDataStr ? JSON.parse(formDataStr) : {};
|
||
|
||
// 构建订单数据
|
||
const orderData = {
|
||
name: orderValue.name,
|
||
event: orderValue.event,
|
||
description: orderValue.description,
|
||
material_type: orderValue.material_type,
|
||
material_url: imageUrl,
|
||
rarity: 0,
|
||
tags: [],
|
||
order_id: currentOrderId.value
|
||
};
|
||
|
||
// 调用创建铸造订单API
|
||
const response = await createMintOrderApi(orderData);
|
||
uni.hideLoading();
|
||
|
||
if (response.code !== 200) {
|
||
throw new Error(response.message || '创建订单失败');
|
||
}
|
||
uni.removeStorageSync('castlove_form_data');
|
||
// 构建藏品数据,存储到temp_nft_data
|
||
const nftData = {
|
||
image: imageUrl, // 使用OSS URL
|
||
name: orderValue.name,
|
||
event: orderValue.event,
|
||
description: orderValue.description,
|
||
material_type: orderValue.material_type,
|
||
order_id: currentOrderId.value // 使用ref保存的order_id
|
||
};
|
||
|
||
// 存储到storage
|
||
uni.setStorageSync('temp_nft_data', JSON.stringify(nftData));
|
||
|
||
// 跳转到成功页面
|
||
uni.redirectTo({
|
||
url: '/pages/castlove/success'
|
||
});
|
||
} catch (error) {
|
||
console.error('[GenerationResult] 选择藏品失败:', error);
|
||
uni.showToast({
|
||
title: error.message || '操作失败,请重试',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
};
|
||
|
||
onMounted(() => {
|
||
// 从存储中获取生成的图片
|
||
try {
|
||
const imagesData = uni.getStorageSync('generated_images');
|
||
if (imagesData) {
|
||
generatedImages.value = JSON.parse(imagesData);
|
||
console.log('[GenerationResult] 接收到生成的图片:', generatedImages.value.length);
|
||
|
||
// 清除存储
|
||
uni.removeStorageSync('generated_images');
|
||
|
||
// 延迟打开礼盒动画
|
||
setTimeout(() => {
|
||
isGiftOpened.value = true;
|
||
}, 800);
|
||
} else {
|
||
uni.showToast({
|
||
title: '未找到生成的图片',
|
||
icon: 'none'
|
||
});
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1500);
|
||
}
|
||
} catch (e) {
|
||
console.error('[GenerationResult] 读取图片数据失败:', e);
|
||
uni.showToast({
|
||
title: '数据错误',
|
||
icon: 'none'
|
||
});
|
||
setTimeout(() => {
|
||
uni.navigateBack();
|
||
}, 1500);
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.generation-result {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
background: #000;
|
||
}
|
||
|
||
.background-image {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 1;
|
||
}
|
||
|
||
.stars-container {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 2;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.star {
|
||
position: absolute;
|
||
background: #FFFFFF;
|
||
border-radius: 50%;
|
||
animation: starTwinkle 2s ease-in-out infinite;
|
||
box-shadow: 0 0 10rpx rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
@keyframes starTwinkle {
|
||
0%, 100% {
|
||
opacity: 0.3;
|
||
transform: scale(1);
|
||
}
|
||
50% {
|
||
opacity: 1;
|
||
transform: scale(1.2);
|
||
}
|
||
}
|
||
|
||
.reward-tips {
|
||
position: absolute;
|
||
top: 80rpx;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
z-index: 11;
|
||
padding: 0 40rpx;
|
||
}
|
||
|
||
.reward-item {
|
||
background: transparent;
|
||
white-space: nowrap;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.reward-icon {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.reward-text {
|
||
font-size: 28rpx;
|
||
color: #FFFFFF;
|
||
font-weight: 600;
|
||
text-shadow:
|
||
0 2rpx 8rpx rgba(0, 0, 0, 0.6),
|
||
0 0 20rpx rgba(255, 107, 157, 0.5);
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.cards-container {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 10;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.5s ease-in-out;
|
||
}
|
||
|
||
.cards-container.cards-visible {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.card-item {
|
||
position: absolute;
|
||
width: 280rpx;
|
||
height: 348rpx;
|
||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||
cursor: pointer;
|
||
filter: drop-shadow(0 10rpx 30rpx rgba(0, 0, 0, 0.4));
|
||
transform: scale(0) translateY(100rpx);
|
||
opacity: 0;
|
||
}
|
||
|
||
.cards-visible .card-item {
|
||
animation: cardAppear 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards,
|
||
cardFloat 4s ease-in-out infinite 0.8s;
|
||
opacity: 1;
|
||
}
|
||
|
||
@keyframes cardAppear {
|
||
0% {
|
||
transform: scale(0) translateY(100rpx);
|
||
opacity: 0;
|
||
}
|
||
100% {
|
||
transform: scale(1) translateY(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.card-item:active {
|
||
transform: scale(0.95) !important;
|
||
}
|
||
|
||
.cards-visible .card-item:nth-child(1) {
|
||
animation-delay: 0s, 0.8s;
|
||
}
|
||
|
||
.cards-visible .card-item:nth-child(2) {
|
||
animation-delay: 0.15s, 0.95s;
|
||
}
|
||
|
||
.cards-visible .card-item:nth-child(3) {
|
||
animation-delay: 0.3s, 1.1s;
|
||
}
|
||
|
||
.cards-visible .card-item:nth-child(4) {
|
||
animation-delay: 0.45s, 1.25s;
|
||
}
|
||
|
||
@keyframes cardFloat {
|
||
0%, 100% {
|
||
transform: translateY(0) rotate(var(--rotate, 0deg));
|
||
}
|
||
50% {
|
||
transform: translateY(-20rpx) rotate(var(--rotate, 0deg));
|
||
}
|
||
}
|
||
|
||
.card-frame {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(135deg,
|
||
#FFE5F0 0%,
|
||
#E8D4FF 25%,
|
||
#D4E8FF 50%,
|
||
#E8FFE5 75%,
|
||
#FFE5F0 100%);
|
||
background-size: 200% 200%;
|
||
animation: holographicShift 3s ease-in-out infinite;
|
||
border-radius: 20rpx;
|
||
padding: 12rpx;
|
||
box-shadow:
|
||
0 0 30rpx rgba(255, 107, 157, 0.4),
|
||
0 0 60rpx rgba(168, 107, 255, 0.3),
|
||
inset 0 0 20rpx rgba(255, 255, 255, 0.3);
|
||
overflow: hidden;
|
||
border: 4rpx solid transparent;
|
||
background-clip: padding-box;
|
||
}
|
||
|
||
.card-frame::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -4rpx;
|
||
left: -4rpx;
|
||
right: -4rpx;
|
||
bottom: -4rpx;
|
||
background: linear-gradient(45deg,
|
||
#FF6B9D,
|
||
#A86BFF,
|
||
#6BDDFF,
|
||
#6BFF9D,
|
||
#FFD66B,
|
||
#FF6B9D);
|
||
background-size: 300% 300%;
|
||
border-radius: 20rpx;
|
||
z-index: -1;
|
||
animation: rainbowBorder 4s linear infinite;
|
||
}
|
||
|
||
@keyframes holographicShift {
|
||
0%, 100% {
|
||
background-position: 0% 50%;
|
||
}
|
||
50% {
|
||
background-position: 100% 50%;
|
||
}
|
||
}
|
||
|
||
@keyframes rainbowBorder {
|
||
0% {
|
||
background-position: 0% 50%;
|
||
}
|
||
100% {
|
||
background-position: 300% 50%;
|
||
}
|
||
}
|
||
|
||
.card-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 12rpx;
|
||
display: block;
|
||
}
|
||
|
||
.card-shine {
|
||
position: absolute;
|
||
top: 0;
|
||
left: -100%;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(90deg,
|
||
transparent 0%,
|
||
rgba(255, 255, 255, 0.5) 50%,
|
||
transparent 100%);
|
||
animation: shine 3s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes shine {
|
||
0% {
|
||
left: -100%;
|
||
}
|
||
50%, 100% {
|
||
left: 100%;
|
||
}
|
||
}
|
||
|
||
.topfans-badge {
|
||
position: absolute;
|
||
top: 20rpx;
|
||
left: 20rpx;
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
filter: drop-shadow(0 2rpx 8rpx rgba(255, 107, 157, 0.5));
|
||
}
|
||
|
||
.card-label {
|
||
position: absolute;
|
||
top: 24rpx;
|
||
right: 20rpx;
|
||
padding: 8rpx 16rpx;
|
||
background: rgba(255, 107, 157, 0.9);
|
||
border-radius: 20rpx;
|
||
}
|
||
|
||
.label-text {
|
||
font-size: 20rpx;
|
||
color: #FFFFFF;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.card-title {
|
||
position: absolute;
|
||
bottom: 20rpx;
|
||
left: 20rpx;
|
||
right: 20rpx;
|
||
padding: 12rpx;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border-radius: 12rpx;
|
||
backdrop-filter: blur(10rpx);
|
||
}
|
||
|
||
.title-text {
|
||
font-size: 22rpx;
|
||
color: #FFFFFF;
|
||
font-weight: 600;
|
||
text-align: center;
|
||
display: block;
|
||
}
|
||
|
||
.heart-icon {
|
||
position: absolute;
|
||
top: 20rpx;
|
||
right: 20rpx;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
|
||
.selected-mark {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
background: rgba(255, 107, 157, 0.9);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 4rpx 20rpx rgba(255, 107, 157, 0.6);
|
||
animation: checkPop 0.3s ease-out;
|
||
}
|
||
|
||
@keyframes checkPop {
|
||
0% {
|
||
transform: translate(-50%, -50%) scale(0);
|
||
}
|
||
50% {
|
||
transform: translate(-50%, -50%) scale(1.2);
|
||
}
|
||
100% {
|
||
transform: translate(-50%, -50%) scale(1);
|
||
}
|
||
}
|
||
|
||
.check-icon {
|
||
font-size: 60rpx;
|
||
color: #FFFFFF;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.card-selected {
|
||
z-index: 100 !important;
|
||
}
|
||
|
||
.gift-box {
|
||
position: absolute;
|
||
bottom: 20%;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 420rpx;
|
||
height: 450rpx;
|
||
z-index: 3;
|
||
animation: giftFloat 3s ease-in-out infinite;
|
||
transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||
perspective: 1000rpx;
|
||
}
|
||
|
||
.gift-box.gift-opened {
|
||
bottom: 5%;
|
||
animation: none;
|
||
}
|
||
|
||
@keyframes giftFloat {
|
||
0%, 100% {
|
||
transform: translateX(-50%) translateY(0);
|
||
}
|
||
50% {
|
||
transform: translateX(-50%) translateY(-20rpx);
|
||
}
|
||
}
|
||
|
||
.gift-container {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.gift-lid {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 340rpx;
|
||
height: 140rpx;
|
||
z-index: 3;
|
||
transition: all 1s cubic-bezier(0.4, 0, 0.2, 1);
|
||
transform-style: preserve-3d;
|
||
transform-origin: bottom left;
|
||
}
|
||
|
||
.gift-opened .gift-lid {
|
||
transform: translateX(-70%) translateY(140rpx) rotateZ(-45deg);
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.gift-bow {
|
||
position: absolute;
|
||
top: -30rpx;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 200rpx;
|
||
height: 80rpx;
|
||
z-index: 4;
|
||
}
|
||
|
||
.gift-bow::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 20rpx;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 40rpx;
|
||
height: 60rpx;
|
||
background: linear-gradient(135deg, #FFB6D9 0%, #FF9DC5 100%);
|
||
border-radius: 8rpx;
|
||
box-shadow: 0 4rpx 15rpx rgba(255, 107, 157, 0.4);
|
||
}
|
||
|
||
.gift-bow::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 180rpx;
|
||
height: 50rpx;
|
||
background: linear-gradient(135deg, #FFB6D9 0%, #FF9DC5 50%, #FFB6D9 100%);
|
||
border-radius: 30rpx;
|
||
box-shadow: 0 6rpx 20rpx rgba(255, 107, 157, 0.5);
|
||
}
|
||
|
||
.gift-lid-top {
|
||
position: relative;
|
||
width: 340rpx;
|
||
height: 100rpx;
|
||
background: linear-gradient(180deg,
|
||
#FFF0F5 0%,
|
||
#FFE5F0 30%,
|
||
#FFD4E8 100%);
|
||
border-radius: 20rpx 20rpx 8rpx 8rpx;
|
||
box-shadow:
|
||
0 10rpx 40rpx rgba(255, 107, 157, 0.4),
|
||
inset 0 -5rpx 15rpx rgba(255, 182, 217, 0.3),
|
||
inset 0 5rpx 15rpx rgba(255, 255, 255, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: visible;
|
||
border: 3rpx solid rgba(255, 255, 255, 0.6);
|
||
}
|
||
|
||
.gift-lid-top::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 280rpx;
|
||
height: 60rpx;
|
||
background: linear-gradient(90deg,
|
||
transparent 0%,
|
||
rgba(255, 182, 217, 0.4) 50%,
|
||
transparent 100%);
|
||
border-radius: 30rpx;
|
||
}
|
||
|
||
.gift-text {
|
||
position: relative;
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #FF6B9D;
|
||
letter-spacing: 4rpx;
|
||
text-shadow:
|
||
0 2rpx 8rpx rgba(255, 107, 157, 0.5),
|
||
0 0 20rpx rgba(255, 182, 217, 0.3);
|
||
z-index: 2;
|
||
}
|
||
|
||
.gift-body {
|
||
position: relative;
|
||
width: 340rpx;
|
||
height: 280rpx;
|
||
background: linear-gradient(180deg,
|
||
#FFE5F0 0%,
|
||
#FFD4E8 50%,
|
||
#FFC9E3 100%);
|
||
border-radius: 8rpx 8rpx 20rpx 20rpx;
|
||
box-shadow:
|
||
0 15rpx 50rpx rgba(255, 107, 157, 0.4),
|
||
inset 0 5rpx 20rpx rgba(255, 255, 255, 0.3),
|
||
inset 0 -5rpx 20rpx rgba(255, 182, 217, 0.3);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
border: 3rpx solid rgba(255, 255, 255, 0.5);
|
||
border-top: none;
|
||
}
|
||
|
||
.gift-body::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 8rpx;
|
||
background: linear-gradient(90deg,
|
||
rgba(255, 107, 157, 0.3) 0%,
|
||
rgba(255, 182, 217, 0.5) 50%,
|
||
rgba(255, 107, 157, 0.3) 100%);
|
||
}
|
||
|
||
.gift-body::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 90%;
|
||
height: 90%;
|
||
background: radial-gradient(circle at center,
|
||
rgba(255, 255, 255, 0.2) 0%,
|
||
transparent 70%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.gift-window {
|
||
position: relative;
|
||
width: 180rpx;
|
||
height: 180rpx;
|
||
background: linear-gradient(135deg,
|
||
#FFF5FA 0%,
|
||
#FFEBF3 50%,
|
||
#FFE0ED 100%);
|
||
border-radius: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow:
|
||
inset 0 4rpx 15rpx rgba(255, 107, 157, 0.2),
|
||
0 4rpx 20rpx rgba(255, 182, 217, 0.3);
|
||
z-index: 2;
|
||
border: 2rpx solid rgba(255, 182, 217, 0.4);
|
||
}
|
||
|
||
.question-mark {
|
||
font-size: 120rpx;
|
||
font-weight: bold;
|
||
color: #FFB6D9;
|
||
text-shadow:
|
||
0 4rpx 15rpx rgba(255, 107, 157, 0.4),
|
||
0 0 30rpx rgba(255, 182, 217, 0.3);
|
||
}
|
||
|
||
.bottom-action {
|
||
position: absolute;
|
||
bottom: 80rpx;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
z-index: 11;
|
||
}
|
||
|
||
.action-button {
|
||
padding: 24rpx 80rpx;
|
||
background: linear-gradient(90deg, #FF6B9D 0%, #FFA8C5 100%);
|
||
border-radius: 50rpx;
|
||
box-shadow: 0 8rpx 24rpx rgba(255, 107, 157, 0.5);
|
||
cursor: pointer;
|
||
user-select: none;
|
||
-webkit-user-select: none;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.action-button:active {
|
||
transform: scale(0.95);
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.button-text {
|
||
font-size: 32rpx;
|
||
color: #FFFFFF;
|
||
font-weight: bold;
|
||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.hint-text {
|
||
font-size: 24rpx;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
</style>
|