topfans/frontend/pages/castlove/craft-select.vue
2026-05-26 19:26:27 +08:00

471 lines
15 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">
<image
class="bg-wrapper"
src="/static/castlove/beijingban.png"
mode="aspectFill"
></image>
<!-- 主体内容 -->
<view class="main-container">
<!-- 左侧图片卡片区域 - 半圆弧形布局 -->
<view class="cards-container"
@touchstart="onTouchStart"
@touchend="onTouchEnd">
<view v-for="(card, index) in currentCardList" :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="(category, index) in categoryList" :key="index" class="text-item"
:class="{ active: selectedCategoryIndex === index, 'font-large': index === 1, 'font-mid': index === 0 || index === 2 }"
@click="selectCategory(index)">
<text>{{ category.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 {
selectedCategoryIndex: 0,
selectedIndex: 0,
touchStartY: 0,
// 大分类名称 → 页面路由映射,方便扩展
cardRoutes: {
'光栅卡': '/pages/castlove/lenticular/lenticular-create',
'拍立得': '/pages/castlove/create',
'镭射卡': '/pages/castlove/create',
'撕拉片': '/pages/castlove/create',
},
// 大分类列表
categoryList: [
{ name: '星卡' },
{ name: '吧唧' },
{ name: '海报' },
],
// 各分类下的工艺卡片
cardListMap: {
'星卡': [
{ name: '光栅卡', image: '/static/castlove/guangshanka.png', comingSoon: false },
{ name: '拍立得', image: '/static/castlove/pailide.png', comingSoon: false },
{ name: '开发中', image: '/static/castlove/daikaifa.png', comingSoon: true },
{ name: '镭射卡', image: '/static/castlove/leisheka.png', comingSoon: false },
{ name: '撕拉片', image: '/static/castlove/silapian.png', comingSoon: false },
],
'吧唧': [
// { name: '光栅卡', image: '/static/castlove/guangshanka.png', comingSoon: false },
// { name: '拍立得', image: '/static/castlove/pailide.png', comingSoon: false },
// { name: '镭射卡', image: '/static/castlove/leisheka.png', comingSoon: false },
// { name: '撕拉片', image: '/static/castlove/silapian.png', comingSoon: false },
// { name: '开发中', image: '/static/castlove/daikaifa.png', comingSoon: true }
],
'海报': [
// { name: '光栅卡', image: '/static/castlove/guangshanka.png', comingSoon: false },
// { name: '拍立得', image: '/static/castlove/pailide.png', comingSoon: false },
// { name: '镭射卡', image: '/static/castlove/leisheka.png', comingSoon: false },
// { name: '撕拉片', image: '/static/castlove/silapian.png', comingSoon: false },
// { name: '开发中', image: '/static/castlove/daikaifa.png', comingSoon: true }
]
},
cardList: [
{ name: '光栅卡', image: '/static/castlove/guangshanka.png', comingSoon: false },
{ name: '拍立得', image: '/static/castlove/pailide.png', comingSoon: false },
{ name: '镭射卡', image: '/static/castlove/leisheka.png', comingSoon: false },
{ name: '撕拉片', image: '/static/castlove/silapian.png', comingSoon: false },
{ name: '开发中', image: '/static/castlove/daikaifa.png', comingSoon: true }
]
}
},
computed: {
currentCardList() {
const categoryName = this.categoryList[this.selectedCategoryIndex].name
return this.cardListMap[categoryName] || this.cardList
}
},
methods: {
// 选择大分类
selectCategory(index) {
this.selectedCategoryIndex = index
this.selectedIndex = 0 // 重置子选项选中
},
// 触摸开始
onTouchStart(e) {
this.touchStartY = e.touches[0].clientY
},
// 触摸结束 - 滑动切换工艺卡片
onTouchEnd(e) {
const touchEndY = e.changedTouches[0].clientY
const diff = this.touchStartY - touchEndY
const threshold = 50 // 滑动阈值
if (diff > threshold) {
// 向上滑动 → 下一个工艺卡片
let newIndex = this.selectedIndex + 1
if (newIndex < this.currentCardList.length) {
this.selectCard(newIndex)
}
} else if (diff < -threshold) {
// 向下滑动 → 上一个工艺卡片
let newIndex = this.selectedIndex - 1
if (newIndex >= 0) {
this.selectCard(newIndex)
}
}
},
// 获取卡片样式 - 循环滚动布局
// 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.currentCardList[index];
if (!card) {
return;
}
const pos = this.getCardStackPosition(index);
// 只有中间位置的卡片点击才会进入创建页
// 其他位置点击只是切换选中(把卡片移到中间),再次点击中间卡片才进入
if (pos === 2) {
if (card.name === '光栅卡') {
const route = this.cardRoutes[card.name];
if (route) {
uni.navigateTo({
url: `${route}?name=${encodeURIComponent(card.name)}`,
});
}
} else {
uni.showToast({
title: '激情开发中',
icon: 'none',
});
}
return;
}
this.selectCard(index);
},
handleBack() {
uni.navigateBack()
},
scrollUp() {
let newIndex = this.selectedCategoryIndex - 1;
if (newIndex >= 0) {
this.selectCategory(newIndex);
}
},
scrollDown() {
let newIndex = this.selectedCategoryIndex + 1;
if (newIndex < this.categoryList.length) {
this.selectCategory(newIndex);
}
},
handleSkip() {
const card = this.cardList[this.selectedIndex]
if (card.name === '光栅卡') {
const route = this.cardRoutes[card.name]
if (route) {
uni.navigateTo({
url: `${route}?name=${encodeURIComponent(card.name)}`
})
}
} else {
uni.showToast({
title: '激情开发中',
icon: 'none',
});
}
}
}
}
</script>
<style lang="scss" scoped>
.page {
height: 100vh;
position: relative;
overflow: hidden;
}
/* 背景图片 */
.bg-wrapper {
position: fixed;
top: -16rpx;
left: 0;
width: 100%;
height: 110%;
z-index: 0;
}
.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>