996 lines
23 KiB
Vue
996 lines
23 KiB
Vue
<template>
|
||
<view class="page-container">
|
||
<Header :showBack="true" backIconColor="#e6e6e6" />
|
||
|
||
<view class="castlove-content">
|
||
<!-- 背景图片 -->
|
||
<image class="background-image" src="/static/background/profile-bg.png" mode="aspectFill"></image>
|
||
|
||
<!-- 蒙层 -->
|
||
<view class="background-overlay"></view>
|
||
|
||
<!-- 内容区域 -->
|
||
<view class="content-wrapper">
|
||
<scroll-view class="content-wrapper" scroll-y="true" :show-scrollbar="false" :enhanced="true">
|
||
<!-- 图片上传区域 -->
|
||
<view class="upload-section">
|
||
<view class="upload-box" @click="chooseImage">
|
||
<image v-if="uploadedImage" class="uploaded-image" :src="uploadedImage" mode="aspectFit">
|
||
</image>
|
||
<view v-else class="upload-placeholder">
|
||
<image class="upload-icon" src="/static/icon/add.png" mode="aspectFit"></image>
|
||
<text class="upload-text">点击上传藏品图片</text>
|
||
</view>
|
||
</view>
|
||
<view class="upload-hint">支持JPG、PNG格式,大小不超过5MB</view>
|
||
</view>
|
||
|
||
<!-- 表单区域 -->
|
||
<view class="form-section">
|
||
<!-- 素材类型选择器 -->
|
||
<view class="form-item form-item-picker">
|
||
<text class="form-label">素材类型</text>
|
||
<view class="custom-picker">
|
||
<view class="picker-display" @click="toggleMaterialTypePicker">
|
||
<text class="picker-text">{{ materialTypes[materialTypeIndex] }}</text>
|
||
<text class="picker-arrow"
|
||
:class="{ 'picker-arrow-up': showMaterialTypePicker }">›</text>
|
||
</view>
|
||
<!-- 自定义下拉选项列表 -->
|
||
<view v-if="showMaterialTypePicker" class="picker-options">
|
||
<view v-for="(type, index) in materialTypes" :key="index" class="picker-option"
|
||
:class="{ 'picker-option-active': materialTypeIndex === index }"
|
||
@click.stop="selectMaterialType(index)">
|
||
<text class="picker-option-text">{{ type }}</text>
|
||
<text v-if="materialTypeIndex === index" class="picker-option-check">✓</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 藏品名称输入框 -->
|
||
<view class="form-item">
|
||
<text class="form-label">藏品名称</text>
|
||
<input class="form-input" v-model="nftName" placeholder="请输入藏品名称"
|
||
placeholder-class="input-placeholder" />
|
||
</view>
|
||
|
||
<!-- 藏品事件输入框 -->
|
||
<view class="form-item">
|
||
<text class="form-label">藏品事件</text>
|
||
<input class="form-input" v-model="nftEvent" placeholder="请输入藏品事件"
|
||
placeholder-class="input-placeholder" />
|
||
</view>
|
||
|
||
<!-- 备注输入框 -->
|
||
<view class="form-item">
|
||
<text class="form-label">备注</text>
|
||
<textarea class="form-textarea" v-model="nftRemark" placeholder="请输入备注信息"
|
||
placeholder-class="textarea-placeholder" maxlength="200" auto-height
|
||
:show-confirm-bar="false" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部按钮区域 -->
|
||
<view class="button-section">
|
||
<button class="btn-secondary" @click="handleBack">返回</button>
|
||
<button class="btn-primary" @click="handleConfirm">开始铸造</button>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 蒙层 - 导航栏展开时显示 -->
|
||
<view v-if="navExpanded" class="nav-mask" @click="navExpanded = false"></view>
|
||
|
||
<BottomNav :activeTab="2" @update:activeTab="handleTabChange" :isExpanded="navExpanded" @update:isExpanded="navExpanded = $event" />
|
||
</view>
|
||
|
||
<!-- 通用确认弹窗 -->
|
||
<ConfirmModal
|
||
:visible="confirmModal.visible"
|
||
:title="confirmModal.title"
|
||
:content="confirmModal.content"
|
||
:confirmText="confirmModal.confirmText"
|
||
:cancelText="confirmModal.cancelText"
|
||
:showCancel="confirmModal.showCancel"
|
||
@confirm="onConfirmModal"
|
||
@cancel="onCancelModal"
|
||
/>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref } from 'vue';
|
||
import Header from "../components/Header.vue";
|
||
import ConfirmModal from '@/components/ConfirmModal.vue';
|
||
import BottomNav from "../components/BottomNav.vue";
|
||
import { getOssSignatureApi, deleteMintOrderApi } from '@/utils/api.js';
|
||
|
||
const navExpanded = ref(false);
|
||
|
||
// 通用确认弹窗状态
|
||
const confirmModal = ref({
|
||
visible: false,
|
||
title: '',
|
||
content: '',
|
||
confirmText: '确认',
|
||
cancelText: '取消',
|
||
showCancel: true,
|
||
confirmCallback: null
|
||
});
|
||
|
||
// 弹窗确认回调
|
||
const onConfirmModal = () => {
|
||
if (confirmModal.value.confirmCallback) {
|
||
confirmModal.value.confirmCallback({ confirm: true });
|
||
}
|
||
confirmModal.value.visible = false;
|
||
};
|
||
|
||
// 弹窗取消回调
|
||
const onCancelModal = () => {
|
||
if (confirmModal.value.confirmCallback) {
|
||
confirmModal.value.confirmCallback({ confirm: false });
|
||
}
|
||
confirmModal.value.visible = false;
|
||
};
|
||
|
||
// 显示通用确认弹窗
|
||
const showConfirmModal = (options) => {
|
||
confirmModal.value = {
|
||
visible: true,
|
||
title: options.title || '',
|
||
content: options.content || '',
|
||
confirmText: options.confirmText || '确认',
|
||
cancelText: options.cancelText || '取消',
|
||
showCancel: options.showCancel !== false,
|
||
confirmCallback: options.success || null
|
||
};
|
||
};
|
||
|
||
// 表单数据
|
||
const uploadedImage = ref(''); // 本地预览路径
|
||
const uploadedImageUrl = ref(''); // OSS完整URL
|
||
const uploadedImageBase64 = ref(''); // Base64格式
|
||
const originalFileName = ref(''); // 原始文件名
|
||
const currentOrderId = ref(''); // 当前订单ID
|
||
const isUploading = ref(false); // 上传中状态
|
||
const materialTypes = ['粉丝自制', '热爱痕迹', '其他'];
|
||
const materialTypeIndex = ref(0);
|
||
const showMaterialTypePicker = ref(false);
|
||
const nftName = ref('');
|
||
const nftEvent = ref('');
|
||
const nftRemark = ref('');
|
||
|
||
// 选择图片
|
||
const chooseImage = () => {
|
||
// 如果正在上传,禁止选择新图片
|
||
if (isUploading.value) {
|
||
uni.showToast({
|
||
title: '图片上传中,请稍候',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
uni.chooseImage({
|
||
count: 1,
|
||
sourceType: ['album', 'camera'],
|
||
success: (res) => {
|
||
const filePath = res.tempFilePaths[0];
|
||
const tempFile = res.tempFiles && res.tempFiles[0];
|
||
|
||
// 获取文件信息进行验证
|
||
uni.getFileInfo({
|
||
filePath: filePath,
|
||
success: (fileInfo) => {
|
||
// 验证文件大小(5MB = 5 * 1024 * 1024 bytes)
|
||
const maxSize = 5 * 1024 * 1024;
|
||
if (fileInfo.size > maxSize) {
|
||
uni.showToast({
|
||
title: '图片大小不能超过5MB',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 验证文件格式
|
||
const mimeType = (tempFile && tempFile.type) ? tempFile.type.toLowerCase() : '';
|
||
const pathLower = filePath.toLowerCase();
|
||
const validByMime = mimeType === 'image/jpeg' || mimeType === 'image/png';
|
||
const validByExt = pathLower.endsWith('.jpg') || pathLower.endsWith('.jpeg') || pathLower.endsWith('.png');
|
||
if (!validByMime && !validByExt) {
|
||
uni.showToast({
|
||
title: '只支持JPG和PNG格式',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 提取原始文件名
|
||
const rawName = (tempFile && tempFile.name)
|
||
? tempFile.name
|
||
: filePath.split('/').pop();
|
||
originalFileName.value = rawName;
|
||
|
||
// 验证通过,转换为 base64
|
||
convertImageToBase64(filePath, originalFileName.value);
|
||
},
|
||
fail: (error) => {
|
||
console.error('获取文件信息失败:', error);
|
||
uni.showToast({
|
||
title: '获取文件信息失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
});
|
||
},
|
||
fail: (err) => {
|
||
console.error('选择图片失败:', err);
|
||
uni.showToast({
|
||
title: '选择图片失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
});
|
||
};
|
||
|
||
// 将图片转换为 base64(兼容 H5、App、小程序)
|
||
const convertImageToBase64 = (filePath, fileName) => {
|
||
isUploading.value = true;
|
||
uni.showLoading({ title: '处理中...', mask: true });
|
||
|
||
// 判断运行环境
|
||
// #ifdef H5
|
||
// H5 环境:使用 FileReader
|
||
convertImageToBase64H5(filePath, fileName);
|
||
// #endif
|
||
|
||
// #ifndef H5
|
||
// App/小程序环境:使用 FileSystemManager
|
||
convertImageToBase64Native(filePath, fileName);
|
||
// #endif
|
||
};
|
||
|
||
// H5 环境的 base64 转换
|
||
const convertImageToBase64H5 = (filePath, fileName) => {
|
||
// H5 环境下,filePath 是 blob URL
|
||
// 需要通过 fetch 获取 blob,然后用 FileReader 转换
|
||
fetch(filePath)
|
||
.then(res => res.blob())
|
||
.then(blob => {
|
||
const reader = new FileReader();
|
||
reader.onload = (e) => {
|
||
// e.target.result 已经是完整的 data:image/xxx;base64,xxx 格式
|
||
uploadedImageBase64.value = e.target.result;
|
||
uploadedImage.value = filePath;
|
||
|
||
console.log('[CastloveContent] Base64转换成功 (H5)');
|
||
console.log('[CastloveContent] Base64长度:', uploadedImageBase64.value.length);
|
||
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '图片加载成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
});
|
||
|
||
isUploading.value = false;
|
||
};
|
||
reader.onerror = (error) => {
|
||
console.error('Base64转换失败 (H5):', error);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '图片处理失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
isUploading.value = false;
|
||
};
|
||
reader.readAsDataURL(blob);
|
||
})
|
||
.catch(error => {
|
||
console.error('获取图片失败 (H5):', error);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '图片处理失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
isUploading.value = false;
|
||
});
|
||
};
|
||
|
||
// App/小程序环境的 base64 转换
|
||
const convertImageToBase64Native = (filePath, fileName) => {
|
||
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || MP-QQ
|
||
// 小程序环境
|
||
const fs = uni.getFileSystemManager();
|
||
fs.readFile({
|
||
filePath: filePath,
|
||
encoding: 'base64',
|
||
success: (res) => {
|
||
const ext = fileName.toLowerCase().split('.').pop();
|
||
let mimeType = 'image/jpeg';
|
||
if (ext === 'png') {
|
||
mimeType = 'image/png';
|
||
}
|
||
|
||
uploadedImageBase64.value = `data:${mimeType};base64,${res.data}`;
|
||
uploadedImage.value = filePath;
|
||
|
||
console.log('[CastloveContent] Base64转换成功 (小程序)');
|
||
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '图片加载成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
});
|
||
|
||
isUploading.value = false;
|
||
},
|
||
fail: (error) => {
|
||
console.error('[CastloveContent] Base64转换失败:', error);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '图片处理失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
isUploading.value = false;
|
||
}
|
||
});
|
||
// #endif
|
||
|
||
// #ifdef APP-PLUS
|
||
// App环境:使用plus.io API
|
||
plus.io.resolveLocalFileSystemURL(filePath, (entry) => {
|
||
entry.file((file) => {
|
||
const reader = new plus.io.FileReader();
|
||
reader.onloadend = (e) => {
|
||
uploadedImageBase64.value = e.target.result;
|
||
uploadedImage.value = filePath;
|
||
|
||
console.log('[CastloveContent] Base64转换成功 (App)');
|
||
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '图片加载成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
});
|
||
|
||
isUploading.value = false;
|
||
};
|
||
reader.onerror = (error) => {
|
||
console.error('[CastloveContent] Base64转换失败:', error);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '图片处理失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
isUploading.value = false;
|
||
};
|
||
reader.readAsDataURL(file);
|
||
});
|
||
}, (error) => {
|
||
console.error('[CastloveContent] 读取文件失败:', error);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '图片处理失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
isUploading.value = false;
|
||
});
|
||
// #endif
|
||
};
|
||
|
||
// 上传图片到OSS
|
||
const uploadImageToOss = async (filePath, fileName) => {
|
||
try {
|
||
isUploading.value = true;
|
||
uni.showLoading({ title: '上传中...', mask: true });
|
||
|
||
// 1. 获取OSS签名(type='asset'),后端返回order_id
|
||
const signRes = await getOssSignatureApi('asset');
|
||
if (signRes.code !== 200) {
|
||
throw new Error(signRes.message || '获取签名失败');
|
||
}
|
||
|
||
// 保存order_id
|
||
currentOrderId.value = signRes.data.order_id;
|
||
|
||
// 2. 构建FormData并上传到OSS
|
||
uni.uploadFile({
|
||
url: signRes.data.host,
|
||
filePath: filePath,
|
||
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) => {
|
||
try {
|
||
if (uploadRes.statusCode === 200 || uploadRes.statusCode === 204) {
|
||
// 3. 拼接完整URL
|
||
uploadedImageUrl.value = `${signRes.data.host}/${signRes.data.dir}${fileName}`;
|
||
|
||
// 4. 显示预览图(使用本地路径)
|
||
uploadedImage.value = filePath;
|
||
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '上传成功',
|
||
icon: 'success',
|
||
duration: 1500
|
||
});
|
||
} else {
|
||
throw new Error(`上传失败,状态码: ${uploadRes.statusCode}`);
|
||
}
|
||
} catch (error) {
|
||
console.error('处理上传结果失败:', error);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: error.message || '上传失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
} finally {
|
||
isUploading.value = false;
|
||
}
|
||
},
|
||
fail: (error) => {
|
||
console.error('OSS上传失败:', error);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: '上传失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
isUploading.value = false;
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('上传图片失败:', error);
|
||
uni.hideLoading();
|
||
uni.showToast({
|
||
title: error.message || '上传失败',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
isUploading.value = false;
|
||
}
|
||
};
|
||
|
||
// 切换素材类型选择器显示
|
||
const toggleMaterialTypePicker = () => {
|
||
showMaterialTypePicker.value = !showMaterialTypePicker.value;
|
||
};
|
||
|
||
// 选择素材类型
|
||
const selectMaterialType = (index) => {
|
||
materialTypeIndex.value = index;
|
||
showMaterialTypePicker.value = false;
|
||
};
|
||
|
||
// 返回按钮
|
||
const handleBack = async () => {
|
||
// 如果有未保存的数据,提示用户
|
||
if (uploadedImage.value || nftName.value || nftEvent.value || nftRemark.value) {
|
||
showConfirmModal({
|
||
title: '提示',
|
||
content: '确定要返回吗?未保存的数据将会丢失',
|
||
success: async (res) => {
|
||
if (res.confirm) {
|
||
// 如果有订单ID,需要删除订单
|
||
if (currentOrderId.value) {
|
||
try {
|
||
await deleteMintOrderApi(currentOrderId.value);
|
||
} catch (error) {
|
||
console.error('删除订单失败:', error);
|
||
}
|
||
}
|
||
|
||
// 清空表单数据
|
||
resetForm();
|
||
// 返回广场页面
|
||
uni.navigateBack();
|
||
}
|
||
}
|
||
});
|
||
} else {
|
||
// 没有未保存数据,直接返回
|
||
uni.navigateBack();
|
||
}
|
||
};
|
||
|
||
// 底部导航切换
|
||
const handleTabChange = (newTab) => {
|
||
const routes = [
|
||
'/pages/ai-dazi/index',
|
||
'/pages/starbook/index',
|
||
'/pages/castlove/mall',
|
||
'/pages/starcity/index',
|
||
'/pages/square/square'
|
||
];
|
||
|
||
if (newTab >= 0 && newTab < routes.length) {
|
||
uni.navigateTo({
|
||
url: routes[newTab]
|
||
});
|
||
}
|
||
};
|
||
|
||
// 确认铸造按钮
|
||
const handleConfirm = async () => {
|
||
// 表单验证
|
||
if (!uploadedImage.value) {
|
||
uni.showToast({
|
||
title: '请上传藏品图片',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!uploadedImageBase64.value) {
|
||
uni.showToast({
|
||
title: '图片尚未处理完成',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!nftName.value.trim()) {
|
||
uni.showToast({
|
||
title: '请输入藏品名称',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!nftEvent.value.trim()) {
|
||
uni.showToast({
|
||
title: '请输入藏品事件',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 保存表单数据到全局存储
|
||
const formData = {
|
||
image: uploadedImage.value,
|
||
imageBase64: uploadedImageBase64.value, // Base64格式
|
||
name: nftName.value.trim(),
|
||
event: nftEvent.value.trim(),
|
||
remark: nftRemark.value.trim(),
|
||
materialType: materialTypes[materialTypeIndex.value]
|
||
};
|
||
|
||
try {
|
||
uni.setStorageSync('castlove_form_data', JSON.stringify(formData));
|
||
console.log('[CastloveContent] 表单数据已保存,Base64长度:', uploadedImageBase64.value.length);
|
||
} catch (e) {
|
||
console.error('保存表单数据失败:', e);
|
||
uni.showToast({
|
||
title: '保存数据失败',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
// 清空表单
|
||
resetForm();
|
||
|
||
// 跳转到发现页面
|
||
uni.navigateTo({
|
||
url: '/pages/discover/discover'
|
||
});
|
||
};
|
||
|
||
// 重置表单
|
||
const resetForm = () => {
|
||
uploadedImage.value = '';
|
||
uploadedImageUrl.value = '';
|
||
uploadedImageBase64.value = '';
|
||
originalFileName.value = '';
|
||
currentOrderId.value = '';
|
||
isUploading.value = false;
|
||
materialTypeIndex.value = 0;
|
||
nftName.value = '';
|
||
nftEvent.value = '';
|
||
nftRemark.value = '';
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page-container {
|
||
position: relative;
|
||
width: 100vw;
|
||
height: 100vh;
|
||
min-height: 100vh;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.castlove-content {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 1;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.background-image {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
object-fit: cover;
|
||
min-width: 100%;
|
||
min-height: 100%;
|
||
}
|
||
|
||
.background-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.content-wrapper {
|
||
position: relative;
|
||
z-index: 1;
|
||
width: 100%;
|
||
min-height: 100%;
|
||
padding: 100rpx 40rpx 40rpx 32rpx;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* 图片上传区域 */
|
||
.upload-section {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-bottom: 40rpx;
|
||
}
|
||
|
||
.upload-box {
|
||
width: 500rpx;
|
||
height: 500rpx;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
backdrop-filter: blur(10rpx);
|
||
border-radius: 30rpx;
|
||
border: 4rpx dashed rgba(255, 255, 255, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.upload-hint {
|
||
margin-top: 20rpx;
|
||
font-size: 24rpx;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
text-align: center;
|
||
}
|
||
|
||
.uploaded-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.upload-placeholder {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.upload-icon {
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.upload-text {
|
||
font-size: 28rpx;
|
||
color: rgba(255, 255, 255, 0.9);
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 表单区域 */
|
||
.form-section {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 30rpx;
|
||
margin-bottom: 40rpx;
|
||
}
|
||
|
||
.form-item {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.form-item-picker {
|
||
position: relative;
|
||
z-index: 10;
|
||
}
|
||
|
||
.form-label {
|
||
font-size: 36rpx;
|
||
color: #e6e6e6;
|
||
font-weight: 500;
|
||
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
|
||
padding: 18rpx 18rpx;
|
||
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
||
border-radius: 44rpx;
|
||
display: inline-block;
|
||
align-self: flex-start;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.4);
|
||
}
|
||
|
||
/* 自定义选择器 */
|
||
.custom-picker {
|
||
position: relative;
|
||
width: 100%;
|
||
z-index: 10;
|
||
}
|
||
|
||
.picker-display {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
backdrop-filter: blur(10rpx);
|
||
border-radius: 20rpx;
|
||
padding: 0 30rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-sizing: border-box;
|
||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||
transition: all 0.3s ease;
|
||
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.picker-display:active {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.picker-text {
|
||
font-size: 32rpx;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.picker-arrow {
|
||
font-size: 48rpx;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
font-weight: bold;
|
||
transform: rotate(90deg);
|
||
transition: transform 0.3s ease;
|
||
}
|
||
|
||
.picker-arrow-up {
|
||
transform: rotate(-90deg);
|
||
}
|
||
|
||
/* 下拉选项列表 */
|
||
.picker-options {
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 0;
|
||
right: 0;
|
||
width: 100%;
|
||
margin-top: 10rpx;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
backdrop-filter: blur(15rpx);
|
||
border-radius: 20rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2);
|
||
z-index: 10;
|
||
animation: slideDown 0.3s ease-out;
|
||
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
@keyframes slideDown {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(-10rpx);
|
||
}
|
||
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.picker-option {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
padding: 0 30rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
box-sizing: border-box;
|
||
transition: background 0.2s ease;
|
||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.picker-option:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.picker-option:active {
|
||
background: rgba(255, 255, 255, 0.15);
|
||
}
|
||
|
||
.picker-option-active {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.picker-option-text {
|
||
font-size: 32rpx;
|
||
color: #e6e6e6;
|
||
}
|
||
|
||
.picker-option-check {
|
||
font-size: 36rpx;
|
||
color: #e6e6e6;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.form-input {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
backdrop-filter: blur(10rpx);
|
||
border-radius: 20rpx;
|
||
padding: 0 30rpx;
|
||
font-size: 32rpx;
|
||
color: #e6e6e6;
|
||
box-sizing: border-box;
|
||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.input-placeholder {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
font-size: 32rpx;
|
||
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.form-textarea {
|
||
width: 100%;
|
||
min-height: 88rpx;
|
||
max-height: 400rpx;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
backdrop-filter: blur(10rpx);
|
||
border-radius: 20rpx;
|
||
padding: 20rpx 30rpx;
|
||
font-size: 32rpx;
|
||
color: #e6e6e6;
|
||
box-sizing: border-box;
|
||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.textarea-placeholder {
|
||
color: rgba(255, 255, 255, 0.85);
|
||
font-size: 32rpx;
|
||
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
/* 底部按钮区域 */
|
||
.button-section {
|
||
width: 100%;
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-top: auto;
|
||
padding-top: 40rpx;
|
||
padding-bottom: 40rpx;
|
||
}
|
||
|
||
.btn-secondary,
|
||
.btn-primary {
|
||
flex: 1;
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
border-radius: 44rpx;
|
||
font-size: 36rpx;
|
||
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
|
||
font-weight: 600;
|
||
border: none;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
||
text-shadow: 0 4rpx 4rpx rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
backdrop-filter: blur(10rpx);
|
||
color: #e6e6e6;
|
||
border: 2rpx solid rgba(255, 255, 255, 0.4);
|
||
}
|
||
|
||
.btn-secondary::after {
|
||
border: none;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
||
color: #e6e6e6;
|
||
|
||
|
||
}
|
||
|
||
.btn-primary::after {
|
||
border: none;
|
||
}
|
||
|
||
.btn-secondary:active {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
}
|
||
|
||
.btn-primary:active {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.nav-mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
z-index: 999;
|
||
animation: fadeIn 0.3s ease-out;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
</style>
|