topfans/frontend/pages/castlove/craft-select.vue

387 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="page">
<!-- 主体内容 -->
<view class="main-container">
<!-- 左侧图片卡片区域 - 半圆弧形布局 -->
<view class="cards-container">
<view v-for="(card, index) in cardList" :key="index" class="card-item"
:class="{ 'card-selected': selectedIndex === index }" :style="getCardStyle(index)">
<view class="card-frame"
:class="{ 'no-border': card.comingSoon, 'card-frame--tappable': !card.comingSoon }"
@tap.stop="onCardFrameTap(index)">
<image class="card-image" :src="card.image" mode="aspectFill" />
<image v-if="card.comingSoon" class="coming-soon-badge" src="/static/castlove/jinqingqidai.png"
mode="aspectFit" />
</view>
</view>
</view>
<!-- 右侧文字列表 -->
<view class="text-panel">
<!-- 向上按钮 -->
<view class="arrow-btn arrow-up" @click="scrollUp">
<image class="arrow-icon" src="/static/castlove/jiantou.png" mode="aspectFit"
style="transform: rotate(180deg);" />
</view>
<!-- 文字列表 -->
<view class="text-list">
<view v-for="(card, index) in cardList" :key="index" class="text-item"
:class="{ active: selectedIndex === index, 'font-large': index === 2, 'font-mid': index === 1 || index === 3, 'font-small': index === 0 || index === 4 }"
@click="selectCard(index)">
<text>{{ card.name }}</text>
</view>
</view>
<!-- 向下按钮 -->
<view class="arrow-btn arrow-down" @click="scrollDown">
<image class="arrow-icon" src="/static/castlove/jiantou.png" mode="aspectFit" />
</view>
</view>
</view>
<!-- 自定义底部导航 -->
<view class="nav-bar">
<button class="btn-secondary" @click="handleBack">返回</button>
<button class="btn-skip" @click="handleSkip">跳过</button>
</view>
</view>
</template>
<script>
export default {
onShow() {
try {
uni.hideToast();
} catch (e) {
/* noop */
}
try {
uni.hideLoading();
} catch (e) {
/* noop */
}
},
data() {
return {
selectedIndex: 2,
// 工艺名称 → 页面路由映射,方便扩展
cardRoutes: {
'光栅卡': '/pages/castlove/create',
'拍立得': '/pages/castlove/create',
'镭射卡': '/pages/castlove/create',
'撕拉片': '/pages/castlove/create',
},
cardList: [
{ name: '镭射卡', image: '/static/castlove/leisheka.png', comingSoon: false },
{ name: '拍立得', image: '/static/castlove/pailide.png', comingSoon: false },
{ name: '光栅卡', image: '/static/castlove/guangshanka.png', comingSoon: false },
{ name: '撕拉片', image: '/static/castlove/silapian.png', comingSoon: false },
{ name: '开发中', image: '/static/castlove/daikaifa.png', comingSoon: true }
]
}
},
methods: {
// 获取卡片样式 - 循环滚动布局
// positions 定义了5个位置的固定样式位置0最上位置2中间位置4最下
// 当选中某个卡片时该卡片显示在位置2中间其他卡片循环填充
getCardStyle(index) {
const positions = [
{ left: 9 * 32, top: 2 * 32, rotate: 25, scale: 0.75 }, // 位置0 - 最上
{ left: 3.75 * 32, top: 9 * 32, rotate: 12, scale: 0.95 }, // 位置1 - 中上
{ left: 60, top: 580, rotate: 0, scale: 1 }, // 位置2 - 中间
{ left: 3.75 * 32, top: 27.75 * 32, rotate: -12, scale: 0.95 }, // 位置3 - 中下
{ left: 7 * 32, top: 34.25 * 32, rotate: -25, scale: 0.75 } // 位置4 - 最下
];
// 计算当前卡片应该显示在哪个位置
// 循环移位selectedIndex 的卡片显示在位置2中间
const cardPos = (index - this.selectedIndex + 2 + 5) % 5;
const pos = positions[cardPos];
// 选中卡片在中间位置时放大
if (cardPos === 2) {
return {
left: `${pos.left}rpx`,
top: `${pos.top}rpx`,
transform: `scale(${pos.scale * 1.15}) rotate(0deg)`,
zIndex: 100
};
}
return {
left: `${pos.left}rpx`,
top: `${pos.top}rpx`,
transform: `scale(${pos.scale}) rotate(${pos.rotate}deg)`,
zIndex: 30 - Math.abs(cardPos - 2) * 10
};
},
// 选择卡片
selectCard(index) {
this.selectedIndex = index;
},
/** 当前叠卡在弧形布局中的槽位2=正中主图最大1=中上「第二张」叠层0最上… */
getCardStackPosition(index) {
return (index - this.selectedIndex + 2 + 5) % 5;
},
/**
* 点击卡图区域:
* - 正中主图(槽位 2→ 进入对应工艺创建页(光栅卡即进入已接入预览的 create
* - 中上叠层(槽位 1常为拍立得示意在选中光栅时 → 同样进入光栅卡创建(与设计稿「点第二张进光栅」一致)
* - 其余叠层 → 仅切换选中
*/
onCardFrameTap(index) {
const card = this.cardList[index];
if (!card || card.comingSoon) {
return;
}
const pos = this.getCardStackPosition(index);
// 只有中间位置的卡片点击才会进入创建页
// 其他位置点击只是切换选中(把卡片移到中间),再次点击中间卡片才进入
if (pos === 2) {
if (card.name === '撕拉片') {
const route = this.cardRoutes[card.name];
if (route) {
uni.navigateTo({
url: '/pages/castlove/mint/tear-card',
});
}
} else {
const route = this.cardRoutes[card.name];
if (route) {
uni.navigateTo({
url: `${route}?name=${encodeURIComponent(card.name)}`,
});
}
}
return;
}
this.selectCard(index);
},
handleBack() {
uni.navigateBack()
},
scrollUp() {
let newIndex = this.selectedIndex - 1;
if (newIndex >= 0) {
this.selectCard(newIndex);
}
},
scrollDown() {
let newIndex = this.selectedIndex + 1;
if (newIndex < this.cardList.length) {
this.selectCard(newIndex);
}
},
handleSkip() {
const card = this.cardList[this.selectedIndex]
if (!card || card.comingSoon) {
uni.showToast({ title: '请选择已开放的工艺', icon: 'none' })
return
}
const route = this.cardRoutes[card.name]
if (route) {
uni.navigateTo({
url: `${route}?name=${encodeURIComponent(card.name)}`
})
}
}
}
}
</script>
<style lang="scss" scoped>
.page {
height: 100vh;
background: url('/static/castlove/beijingban.png') no-repeat center center;
background-size: cover;
position: relative;
overflow: hidden;
}
.main-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
z-index: 10;
}
.cards-container {
flex: 1;
position: relative;
}
.card-item {
position: absolute;
width: 344rpx;
height: 344rpx;
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
.card-frame--tappable {
cursor: pointer;
}
.card-frame {
position: relative;
width: 100%;
height: 100%;
border-radius: 20rpx;
padding: 10rpx;
overflow: visible;
background-image: url('/static/square/cangpinkuang1.png');
background-size: cover;
box-shadow: 0 0 0 rgba(0, 0, 0, 0.5);
&.no-border {
background-image: none;
}
}
.card-image {
width: 100%;
height: 100%;
border-radius: 14rpx;
display: block;
object-fit: cover;
}
.coming-soon-badge {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 160rpx;
height: 160rpx;
}
// .card-selected .card-frame {
// border-color: #ffd700;
// box-shadow:
// 0 0 60rpx rgba(255, 215, 0, 0.8),
// 0 20rpx 60rpx rgba(0, 0, 0, 0.4),
// inset 0 2rpx 10rpx rgba(255, 255, 255, 0.6);
// }
.text-panel {
position: absolute;
right: 30rpx;
top: 50%;
transform: translateY(-50%);
width: 200rpx;
height: 392rpx; // 18rem = 360rpx
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: url('/static/castlove/xiahualan.png') no-repeat center center;
background-size: 130%;
border-radius: 20rpx;
}
.arrow-btn {
width: 60rpx;
height: 32rpx;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.8;
&:active {
opacity: 0.6;
}
}
.arrow-icon {
width: 48rpx;
height: 48rpx;
}
.text-list {
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 20rpx;
}
.text-item {
color: rgba(255, 255, 255);
font-size: 26rpx;
font-weight: 500;
transition: all 0.3s ease;
position: relative;
padding: 10rpx 20rpx;
border-radius: 14rpx;
display: flex;
justify-content: center;
&.active {
color: #fff;
font-weight: bold;
text-shadow: 0 0 10rpx rgba(0, 0, 0, 0.8);
background: url('/static/nft/dingbutubiao_liang.png') no-repeat center center;
background-size: 100% 100%;
}
&.font-large {
font-size: 34rpx;
}
&.font-mid {
font-size: 30rpx;
}
&.font-small {
font-size: 26rpx;
}
}
.nav-bar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
padding: 30rpx 40rpx;
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
z-index: 20;
}
.btn-secondary,
.btn-skip {
flex: 1;
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
font-size: 36rpx;
font-family: 'yt', 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);
margin-right: 20rpx;
}
.btn-secondary::after {
border: none;
}
.btn-skip {
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
color: #e6e6e6;
}
</style>