topfans/frontend/pages/castlove/index.vue
2026-04-13 11:30:05 +08:00

1000 lines
23 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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.redirectTo({
url: '/pages/square/square'
});
}
}
});
} else {
// 没有未保存数据,直接返回
uni.redirectTo({
url: '/pages/square/square'
});
}
};
// 底部导航切换
const handleTabChange = (newTab) => {
const routes = [
'/pages/square/square',
'/pages/starbook/index',
'/pages/castlove/mall',
'/pages/starcity/index',
'/pages/friends/index'
];
if (newTab >= 0 && newTab < routes.length) {
uni.redirectTo({
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>