topfans/frontend/pages/profile/selectRole.vue
2026-04-13 16:16:41 +08:00

621 lines
12 KiB
Vue
Raw 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="select-role-container">
<!-- 背景图片 -->
<image class="background-image" src="/static/background/starbook.jpg" mode="aspectFill"></image>
<view class="background-overlay"></view>
<!-- 内容区域 -->
<view class="content-wrapper">
<!-- 导航栏 -->
<view class="nav-bar">
<view v-if="!isWelcome" class="nav-back" @click="goBack">
<text class="back-icon">←</text>
</view>
<text class="nav-title">选择身份</text>
</view>
<!-- 明星轮播区域 -->
<view class="carousel-container">
<view class="carousel-wrapper" @touchstart="onTouchStart" @touchmove="onTouchMove"
@touchend="onTouchEnd">
<view class="star-card" v-for="(star, index) in stars" :key="star.id"
:class="{ 'active': currentIndex === index }" :style="getCardStyle(index)"
@click="selectStar(index)">
<image class="star-image" :src="star.image" mode="aspectFill"></image>
<view class="star-info">
<text class="star-name-cn">{{ star.nameCn }}</text>
<text class="star-name-en">{{ star.nameEn }}</text>
</view>
<view v-if="currentIndex === index" class="selected-badge">
<text class="check-icon">✓</text>
</view>
</view>
</view>
</view>
<!-- 底部文字说明 -->
<view class="promo-section">
<view class="promo-text-wrapper">
<text class="superstar-text">Superstar</text>
<view class="promo-title">
<text class="promo-title-line">选择您</text>
<text class="promo-title-line">最喜爱的明星</text>
</view>
</view>
<text class="promo-desc">{{ getPromoText() }}</text>
<!-- 下一步按钮 -->
<view class="next-button-wrapper">
<button class="btn-next" @click="handleNext">
<text class="arrow-icon"></text>
</button>
</view>
</view>
</view>
</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, computed } from 'vue';
import { useStore } from 'vuex';
import ConfirmModal from '@/components/ConfirmModal.vue';
const props = defineProps({
isWelcome: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['confirm']);
const store = useStore();
// 响应式数据
const currentIndex = ref(0);
const startX = ref(0);
const startY = ref(0);
const isDragging = 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 stars = ref([
{
id: 1,
star_id: 87,
nameCn: '肖战',
nameEn: 'Xiao Zhan',
image: '/static/idols/xiaozhan.png'
},
{
id: 2,
star_id: 88,
nameCn: '王一博',
nameEn: 'Wang Yibo',
image: '/static/idols/wangyibo.png'
},
{
id: 3,
star_id: 89,
nameCn: '杨洋',
nameEn: 'Yang Yang',
image: '/static/idols/yangyang.png'
}
]);
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
// 获取卡片样式
const getCardStyle = (index) => {
const total = stars.value.length;
const diff = index - currentIndex.value;
let adjustedDiff = diff;
// 处理循环
if (adjustedDiff > total / 2) {
adjustedDiff -= total;
} else if (adjustedDiff < -total / 2) {
adjustedDiff += total;
}
const absDiff = Math.abs(adjustedDiff);
// 如果是当前选中的卡片
if (absDiff === 0) {
return {
transform: `translateX(0rpx) scale(1) rotateZ(0deg)`,
opacity: 1,
zIndex: 10
};
}
// 左右两侧的卡片
const scale = 0.75;
const translateX = adjustedDiff * 320;
const rotateZ = adjustedDiff * 8;
const opacity = 1;
const zIndex = total - absDiff;
return {
transform: `translateX(${translateX}rpx) scale(${scale}) rotateZ(${rotateZ}deg)`,
opacity: opacity,
zIndex: zIndex
};
};
// 触摸开始
const onTouchStart = (e) => {
startX.value = e.touches[0].clientX;
startY.value = e.touches[0].clientY;
isDragging.value = false;
};
// 触摸移动
const onTouchMove = (e) => {
const deltaX = e.touches[0].clientX - startX.value;
const deltaY = e.touches[0].clientY - startY.value;
// 判断是否为横向拖动
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 10) {
isDragging.value = true;
}
};
// 触摸结束
const onTouchEnd = (e) => {
if (!isDragging.value) return;
const deltaX = e.changedTouches[0].clientX - startX.value;
const threshold = 50;
if (Math.abs(deltaX) > threshold) {
if (deltaX > 0) {
prevStar();
} else {
nextStar();
}
}
isDragging.value = false;
};
// 选择明星
const selectStar = (index) => {
currentIndex.value = index;
};
// 上一个明星
const prevStar = () => {
currentIndex.value = (currentIndex.value - 1 + stars.value.length) % stars.value.length;
};
// 下一个明星
const nextStar = () => {
currentIndex.value = (currentIndex.value + 1) % stars.value.length;
};
// 获取宣传文字
const getPromoText = () => {
const currentStar = stars.value[currentIndex.value];
return `成为${currentStar.nameCn}的粉丝,铸造关于他的藏品,及关注他的最新相关讯息,一起成为头号粉丝吧!`;
};
// 下一步
const handleNext = async () => {
// 欢迎动画模式下,触发确认事件并传递选中的明星
if (props.isWelcome) {
const currentStar = stars.value[currentIndex.value];
emit('confirm', currentStar);
return;
}
try {
// 获取临时存储的注册信息
const mobile = uni.getStorageSync('temp_register_mobile');
const password = uni.getStorageSync('temp_register_password');
const nickname = uni.getStorageSync('temp_register_nickname');
const currentStar = stars.value[currentIndex.value];
const star_id = currentStar.star_id;
// 验证数据完整性
if (!mobile || !password || !nickname || !star_id) {
uni.showToast({
title: '注册信息不完整,请重新注册',
icon: 'none'
});
// 清除临时数据
uni.removeStorageSync('temp_register_mobile');
uni.removeStorageSync('temp_register_password');
uni.removeStorageSync('temp_register_nickname');
// 跳转回注册页
setTimeout(() => {
uni.reLaunch({
url: '/pages/register/register'
});
}, 1500);
return;
}
// 显示加载提示
uni.showLoading({
title: '注册中...',
mask: true
});
// 调用注册 API
await store.dispatch('user/register', {
mobile,
password,
star_id,
nickname
});
uni.hideLoading();
// 清除临时数据
uni.removeStorageSync('temp_register_mobile');
uni.removeStorageSync('temp_register_password');
uni.removeStorageSync('temp_register_nickname');
// 添加这行:设置新用户标记
uni.setStorageSync('is_new_user', true);
// 跳转到主页
uni.reLaunch({
url: '/pages/square/square'
});
} catch (error) {
uni.hideLoading();
// 处理昵称已存在的情况409错误
if (error.code === 409 || error.message.includes('昵称') || error.message.includes('已存在')) {
showConfirmModal({
title: '昵称已存在',
content: '该昵称已被使用,请返回修改昵称',
showCancel: true,
confirmText: '返回修改',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
uni.navigateBack();
}
}
});
} else {
uni.showToast({
title: error.message || '注册失败,请重试',
icon: 'none',
duration: 2000
});
}
}
};
</script>
<style scoped>
.select-role-container {
position: relative;
width: 100%;
min-height: 100vh;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.background-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
.background-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.2);
z-index: 1;
}
.content-wrapper {
position: relative;
z-index: 10;
width: 100%;
flex: 1;
min-height: 100%;
display: flex;
flex-direction: column;
padding-top: 80rpx;
box-sizing: border-box;
overflow: hidden;
}
/* 导航栏 */
.nav-bar {
display: flex;
align-items: center;
padding: 0 40rpx 40rpx 40rpx;
position: relative;
}
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
font-size: 48rpx;
color: #000000;
font-weight: bold;
}
.nav-title {
flex: 1;
text-align: center;
font-size: 36rpx;
font-weight: 500;
color: #000000;
margin-right: 60rpx;
}
/* 轮播容器 */
.carousel-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: visible;
padding: 0 100rpx;
min-height: 0;
}
.carousel-wrapper {
position: relative;
width: 100%;
height: 800rpx;
max-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
}
.star-card {
position: absolute;
width: 480rpx;
height: 680rpx;
border-radius: 30rpx;
overflow: hidden;
background: #e6e6e6;
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.3);
transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
transform-origin: center center;
}
.star-card.active {
box-shadow: 0 30rpx 80rpx rgba(138, 43, 226, 0.5);
}
.star-image {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
top: 0;
left: 0;
}
.star-info {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
padding: 40rpx 30rpx 30rpx 30rpx;
display: flex;
flex-direction: column;
}
.star-name-cn {
font-size: 40rpx;
font-weight: bold;
color: #e6e6e6;
margin-bottom: 10rpx;
}
.star-name-en {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.9);
}
.selected-badge {
position: absolute;
top: 20rpx;
right: 20rpx;
width: 60rpx;
height: 60rpx;
background: #000000;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
}
.check-icon {
font-size: 36rpx;
color: #e6e6e6;
font-weight: bold;
}
/* 底部宣传区域 */
.promo-section {
padding: 30rpx 40rpx 40rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
min-height: 0;
margin-top: auto;
}
.promo-text-wrapper {
position: relative;
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
margin-bottom: 20rpx;
min-height: 160rpx;
max-height: 200rpx;
padding-left: 40rpx;
}
.superstar-text {
font-size: 120rpx;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
color: rgba(255, 182, 193, 0.3);
letter-spacing: 8rpx;
text-align: left;
line-height: 1;
position: absolute;
top: -65rpx;
left: 0;
z-index: 1;
white-space: nowrap;
}
@media screen and (max-height: 667px) {
.superstar-text {
font-size: 100rpx;
}
}
.promo-title {
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
z-index: 2;
margin-top: -50rpx;
}
.promo-title-line {
font-size: 48rpx;
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
color: #000000;
line-height: 1.4;
letter-spacing: 6rpx;
}
.promo-desc {
font-size: 28rpx;
color: #333333;
line-height: 1.8;
text-align: center;
padding: 0 40rpx;
margin-bottom: 30rpx;
flex-shrink: 0;
max-height: 120rpx;
overflow: hidden;
}
.next-button-wrapper {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
margin-top: auto;
}
.btn-next {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background: #000000;
border: none;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.3);
padding: 0;
transition: transform 0.2s ease;
}
.btn-next:active {
transform: scale(1.15);
}
.btn-next::after {
border: none;
}
.arrow-icon {
font-size: 48rpx;
font-weight: 900;
color: #e6e6e6;
}
</style>