482 lines
11 KiB
Vue
482 lines
11 KiB
Vue
<template>
|
||
<view class="discover-container">
|
||
<!-- 左上角返回按钮 -->
|
||
<view class="back-button" @click="handleBack">
|
||
<image class="back-icon" src="/static/icon/back.png" mode="aspectFit" />
|
||
</view>
|
||
|
||
<!-- 内容区域 -->
|
||
<view class="content-wrapper">
|
||
<!-- 模版铸造页 -->
|
||
<scroll-view v-show="activeTab === 0" class="tab-content" scroll-y>
|
||
<DiscoverFeed />
|
||
</scroll-view>
|
||
|
||
<!-- 自主化铸造页 -->
|
||
<view v-show="activeTab === 1" class="tab-content">
|
||
<CreateFeed />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部操作栏 -->
|
||
<view class="bottom-actions">
|
||
<!-- 切换按钮组 -->
|
||
<view class="toggle-buttons">
|
||
<view class="toggle-capsule">
|
||
<view class="toggle-btn" :class="{ active: activeTab === 0 }" @click="switchTab(0)">
|
||
<text class="toggle-text">自主化铸造</text>
|
||
</view>
|
||
<view class="toggle-btn" :class="{ active: activeTab === 1 }" @click="switchTab(1)">
|
||
<text class="toggle-text">模版铸造</text>
|
||
</view>
|
||
</view>
|
||
<view class="skip-btn" @click="handleSkip">
|
||
<text class="skip-text">跳过</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onUnmounted } from 'vue';
|
||
import { onLoad } from '@dcloudio/uni-app';
|
||
import DiscoverFeed from './discover-feed.vue';
|
||
import CreateFeed from './create-feed.vue';
|
||
// 导入API
|
||
import { getOssSignatureApi, createMintOrderApi } from '@/utils/api.js';
|
||
|
||
const activeTab = ref(0);
|
||
const formData = ref(null);
|
||
|
||
// 切换标签
|
||
const switchTab = (index) => {
|
||
activeTab.value = index;
|
||
};
|
||
|
||
// 返回按钮
|
||
const handleBack = () => {
|
||
uni.navigateBack();
|
||
};
|
||
|
||
// 将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, ossData) => {
|
||
const timestamp = Date.now();
|
||
const randomStr = Math.random().toString(36).substring(2, 8);
|
||
const fileName = `skip_upload_${timestamp}_${randomStr}.png`;
|
||
|
||
let imageUrl;
|
||
|
||
// #ifdef H5
|
||
// H5环境:使用FormData + Blob
|
||
const blob = base64ToBlob(base64Image);
|
||
|
||
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);
|
||
|
||
const response = await fetch(ossData.host, {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
if (response.ok || response.status === 204) {
|
||
imageUrl = `${ossData.host}/${ossData.dir}${fileName}`;
|
||
} else {
|
||
throw new Error(`上传失败,状态码: ${response.status}`);
|
||
}
|
||
// #endif
|
||
|
||
// #ifdef APP-PLUS
|
||
// App环境:使用plus.nativeObj.Bitmap
|
||
const tempFilePath = await new Promise((resolve, reject) => {
|
||
const bitmap = new plus.nativeObj.Bitmap('temp_skip');
|
||
bitmap.loadBase64Data(base64Image, () => {
|
||
const tempPath = '_doc/' + fileName;
|
||
bitmap.save(tempPath, {}, () => {
|
||
bitmap.clear();
|
||
resolve(tempPath);
|
||
}, (error) => {
|
||
bitmap.clear();
|
||
reject(new Error('图片保存失败'));
|
||
});
|
||
}, (error) => {
|
||
bitmap.clear();
|
||
reject(new Error('加载base64失败'));
|
||
});
|
||
});
|
||
|
||
// 上传到OSS
|
||
imageUrl = await new Promise((resolve, reject) => {
|
||
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) => {
|
||
if (uploadRes.statusCode === 200 || uploadRes.statusCode === 204) {
|
||
const url = `${ossData.host}/${ossData.dir}${fileName}`;
|
||
resolve(url);
|
||
} else {
|
||
reject(new Error(`上传失败,状态码: ${uploadRes.statusCode}`));
|
||
}
|
||
},
|
||
fail: (error) => {
|
||
reject(error);
|
||
}
|
||
});
|
||
});
|
||
// #endif
|
||
|
||
return imageUrl;
|
||
};
|
||
|
||
// 跳过按钮
|
||
const handleSkip = async () => {
|
||
try {
|
||
// 从storage获取表单数据
|
||
const formDataStr = uni.getStorageSync('castlove_form_data');
|
||
if (!formDataStr) {
|
||
uni.showToast({
|
||
title: '未找到表单数据',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
const orderValue = JSON.parse(formDataStr);
|
||
|
||
// 检查是否有图片
|
||
if (!orderValue.imageBase64) {
|
||
uni.showToast({
|
||
title: '未找到图片数据',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 显示加载提示
|
||
// uni.showLoading({ title: '上传图片中...', mask: true });
|
||
|
||
// 获取OSS签名
|
||
const signRes = await getOssSignatureApi('asset');
|
||
if (signRes.code !== 200) {
|
||
throw new Error(signRes.message || '获取签名失败');
|
||
}
|
||
|
||
const orderId = signRes.data.order_id || '';
|
||
|
||
// 上传原图到OSS
|
||
const imageUrl = await uploadImageToOss(orderValue.imageBase64, signRes.data);
|
||
|
||
// 更新加载提示
|
||
uni.showLoading({ title: '创建订单中...', mask: true });
|
||
|
||
// 构建订单数据
|
||
const orderData = {
|
||
name: orderValue.name,
|
||
event: orderValue.event,
|
||
description: orderValue.remark || '',
|
||
material_type: orderValue.materialType || 'image',
|
||
material_url: imageUrl,
|
||
rarity: 0,
|
||
tags: [],
|
||
order_id: orderId
|
||
};
|
||
|
||
// 调用创建铸造订单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,
|
||
name: orderValue.name,
|
||
event: orderValue.event,
|
||
description: orderValue.remark || '',
|
||
material_type: orderValue.materialType || 'image',
|
||
order_id: orderId
|
||
};
|
||
|
||
// 存储到storage
|
||
uni.setStorageSync('temp_nft_data', JSON.stringify(nftData));
|
||
|
||
// 跳转到成功页面
|
||
uni.navigateTo({
|
||
url: '/pages/castlove/success'
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('[Discover] 跳过失败:', error);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: error.message || '操作失败,请重试',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
};
|
||
|
||
// 底部操作
|
||
const handleHome = () => {
|
||
uni.navigateBack();
|
||
};
|
||
|
||
const handleMore = () => {
|
||
uni.showToast({
|
||
title: '更多功能',
|
||
icon: 'none'
|
||
});
|
||
};
|
||
|
||
// 监听子组件切换事件
|
||
const handleSwitchTab = (index) => {
|
||
switchTab(index);
|
||
};
|
||
|
||
onLoad(() => {
|
||
// 尝试从存储中读取表单数据
|
||
try {
|
||
const storedData = uni.getStorageSync('castlove_form_data');
|
||
if (storedData) {
|
||
// 默认显示自主化铸造
|
||
activeTab.value = 1;
|
||
}
|
||
} catch (e) {
|
||
console.error('[Discover] 读取表单数据失败:', e);
|
||
}
|
||
});
|
||
|
||
onMounted(() => {
|
||
uni.$on('discover:switchTab', handleSwitchTab);
|
||
console.log('[Discover] 页面加载完成');
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
uni.$off('discover:switchTab', handleSwitchTab);
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.discover-container {
|
||
position: relative;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
background-image: url('/static/background/starbook.jpg');
|
||
background-size: cover;
|
||
background-position: center;
|
||
background-repeat: no-repeat;
|
||
}
|
||
|
||
/* 左上角返回按钮 */
|
||
.back-button {
|
||
position: fixed;
|
||
top: calc(env(safe-area-inset-top) + 80rpx);
|
||
left: 32rpx;
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 101;
|
||
}
|
||
|
||
.back-icon {
|
||
width: 64rpx;
|
||
height: 64rpx;
|
||
}
|
||
|
||
.content-wrapper {
|
||
width: 100%;
|
||
height: 100%;
|
||
padding-bottom: 240rpx;
|
||
}
|
||
|
||
.tab-content {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
/* 底部操作栏 */
|
||
.bottom-actions {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 24rpx;
|
||
padding: 32rpx 48rpx;
|
||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||
z-index: 100;
|
||
}
|
||
|
||
/* 开启创作按钮 */
|
||
.create-button {
|
||
width: 100%;
|
||
height: 96rpx;
|
||
background: rgba(255, 255, 255, 0.95);
|
||
border-radius: 48rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 16rpx;
|
||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.12);
|
||
backdrop-filter: blur(10rpx);
|
||
}
|
||
|
||
.home-icon {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
|
||
.create-text {
|
||
font-size: 32rpx;
|
||
color: #333;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 切换按钮组 */
|
||
.toggle-buttons {
|
||
width: 100%;
|
||
display: flex;
|
||
gap: 24rpx;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
/* 胶囊容器 */
|
||
.toggle-capsule {
|
||
width: 420rpx;
|
||
height: 80rpx;
|
||
background: rgba(255, 255, 255, 0.6);
|
||
border-radius: 40rpx;
|
||
backdrop-filter: blur(10rpx);
|
||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
|
||
display: flex;
|
||
padding: 4rpx;
|
||
gap: 4rpx;
|
||
}
|
||
|
||
.toggle-btn {
|
||
flex: 1;
|
||
height: 80rpx;
|
||
border-radius: 36rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.3s ease;
|
||
background: transparent;
|
||
}
|
||
|
||
.toggle-btn.active {
|
||
background: linear-gradient(to bottom right,
|
||
#F0E4B1 0%, /* 左:浅橙粉 */
|
||
#F08399 50%,
|
||
#B94E73 100% /* 右:柔粉红 */
|
||
);
|
||
box-shadow:
|
||
/* 外层投影 - 让按钮浮起 */
|
||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||
0 2rpx 6rpx rgba(255, 143, 158, 0.15),
|
||
|
||
/* 内阴影 - 模拟顶部受光 + 底部凹陷 */
|
||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4), /* 顶部高光 */
|
||
inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.05); /* 底部暗部 */
|
||
|
||
}
|
||
|
||
.toggle-text {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
font-weight: 500;
|
||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.toggle-btn.active .toggle-text {
|
||
color: #ffffff;
|
||
font-weight: 600;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.toggle-btn:active {
|
||
transform: scale(0.98);
|
||
}
|
||
|
||
/* 跳过按钮 */
|
||
.skip-btn {
|
||
width: 120rpx;
|
||
height: 80rpx;
|
||
background: linear-gradient(to bottom right,
|
||
#F0E4B1 0%, /* 左:浅橙粉 */
|
||
#F08399 50%,
|
||
#B94E73 100% /* 右:柔粉红 */
|
||
);
|
||
box-shadow:
|
||
/* 外层投影 - 让按钮浮起 */
|
||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||
0 2rpx 6rpx rgba(255, 143, 158, 0.15),
|
||
|
||
/* 内阴影 - 模拟顶部受光 + 底部凹陷 */
|
||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4), /* 顶部高光 */
|
||
inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.05); /* 底部暗部 */
|
||
|
||
border-radius: 40rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.skip-text {
|
||
font-size: 28rpx;
|
||
color: #ffffff;
|
||
font-weight: 600;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.skip-btn:active {
|
||
transform: scale(0.98);
|
||
}
|
||
</style>
|