feat: 修改铸造页面的样式
This commit is contained in:
parent
75e1222c55
commit
5f5ee0d7a1
@ -1,445 +1,635 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page">
|
||||||
<image class="bg-wrapper" src="/static/castlove/beijingban.png" mode="aspectFill"></image>
|
<image
|
||||||
<view class="main-container">
|
class="bg-wrapper"
|
||||||
<view class="cards-container" @touchstart="onTouchStart" @touchmove="onTouchMove" @touchend="onTouchEnd">
|
src="/static/castlove/beijingban.png"
|
||||||
<view v-if="currentCardList.length">
|
mode="aspectFill"
|
||||||
<view v-for="(card, index) in currentCardList" :key="index" class="card-item"
|
></image>
|
||||||
:class="{ 'no-transition': disableTransition }" :style="getCardStyle(index)">
|
<view class="main-container">
|
||||||
<view class="card-frame"
|
<view
|
||||||
:class="{ 'no-border': card.comingSoon, 'card-frame--tappable': !card.comingSoon }"
|
class="cards-container"
|
||||||
@tap.stop="onCardFrameTap(index)">
|
@touchstart="onTouchStart"
|
||||||
<image class="card-image" :src="card.image" mode="aspectFill" />
|
@touchmove="onTouchMove"
|
||||||
<image v-if="card.comingSoon" class="coming-soon-badge"
|
@touchend="onTouchEnd"
|
||||||
src="/static/castlove/jinqingqidai.png" />
|
>
|
||||||
</view>
|
<view v-if="currentCardList.length">
|
||||||
</view>
|
<view
|
||||||
</view>
|
v-for="(card, index) in currentCardList"
|
||||||
<!-- 空态:当前分类暂无卡片时显示 -->
|
:key="index"
|
||||||
<!-- <view v-else class="empty-state">
|
class="card-item"
|
||||||
|
:class="{ 'no-transition': disableTransition }"
|
||||||
|
: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"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<text class="card-name">{{ card.name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<!-- 空态:当前分类暂无卡片时显示 -->
|
||||||
|
<!-- <view v-else class="empty-state">
|
||||||
<image class="empty-state-icon" src="/static/castlove/jinqingqidai.png" mode="aspectFit" />
|
<image class="empty-state-icon" src="/static/castlove/jinqingqidai.png" mode="aspectFit" />
|
||||||
<text class="empty-state-title">{{ currentCategoryName }}敬请期待</text>
|
<text class="empty-state-title">{{ currentCategoryName }}敬请期待</text>
|
||||||
<text class="empty-state-desc">该分类正在打磨中,马上与您见面</text>
|
<text class="empty-state-desc">该分类正在打磨中,马上与您见面</text>
|
||||||
</view> -->
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="showMenu" class="text-panel">
|
<view v-if="showMenu" class="text-panel">
|
||||||
<view class="arrow-btn arrow-up" @click="scrollUp">
|
<view class="arrow-btn arrow-up" @click="scrollUp">
|
||||||
<image class="arrow-icon" src="/static/castlove/jiantou.png" style="transform:rotate(180deg)" />
|
<image
|
||||||
</view>
|
class="arrow-icon"
|
||||||
<view class="text-list">
|
src="/static/castlove/jiantou.png"
|
||||||
<view v-for="(category, index) in categoryList" :key="index" class="text-item"
|
style="transform: rotate(180deg)"
|
||||||
: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" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</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" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
// 作为组件被 mall.vue 引入时的入参
|
// 作为组件被 mall.vue 引入时的入参
|
||||||
// (作为页面被 navigateTo 直接打开时不传值,走 onLoad 读取 URL 参数)
|
// (作为页面被 navigateTo 直接打开时不传值,走 onLoad 读取 URL 参数)
|
||||||
props: {
|
props: {
|
||||||
// 初始分类:star_card | badge | poster
|
// 初始分类:star_card | badge | poster
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 作为组件时,父组件传 type 进来时自动定位
|
||||||
|
// 用 immediate + handler 显式写法,避免简写在某些 H5 场景下不触发
|
||||||
|
type: {
|
||||||
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
this.applyType(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// 组件创建时立即应用一次 type,避免 mall.vue 在 onLoad 才设置 initialType
|
||||||
|
// 导致首次渲染拿到默认 "" 而 watch 错过的情况
|
||||||
|
this.applyType(this.type);
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
// 作为页面被直接打开时,隐藏右侧分类菜单栏(根据用户需求)
|
||||||
|
this.showMenu = false;
|
||||||
|
// 接收从主Tab(square.vue / CastloveContent.vue)传入的 type,自动定位到对应分类
|
||||||
|
// type 取值:star_card | badge | poster
|
||||||
|
if (options && options.type) {
|
||||||
|
this.applyType(options.type);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
try {
|
||||||
|
uni.hideToast();
|
||||||
|
uni.hideLoading();
|
||||||
|
} catch (e) {}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 是否显示右侧分类菜单栏
|
||||||
|
// - 作为组件(mall.vue 中)使用时,默认 true
|
||||||
|
// - 作为页面被直接打开时,onLoad 会把它置为 false
|
||||||
|
showMenu: true,
|
||||||
|
selectedCategoryIndex: 0,
|
||||||
|
// 主Tab 的 type 值与 categoryList 索引的映射
|
||||||
|
// 与 square.vue / CastloveContent.vue 中 mainTabs 的 type 保持一致
|
||||||
|
categoryTypeMap: {
|
||||||
|
star_card: 0,
|
||||||
|
badge: 1,
|
||||||
|
poster: 2,
|
||||||
|
},
|
||||||
|
categoryList: [{ name: "星卡" }, { name: "吧唧" }, { name: "海报" }],
|
||||||
|
cardListMap: {
|
||||||
|
星卡: [
|
||||||
|
{
|
||||||
|
name: "光栅卡",
|
||||||
|
image: "/static/castlove/guangshanka.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "镭射卡",
|
||||||
|
image: "/static/castlove/leisheka.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "拍立得",
|
||||||
|
image: "/static/castlove/pailide.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "开发中",
|
||||||
|
image: "/static/castlove/daikaifa.png",
|
||||||
|
comingSoon: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "撕拉片",
|
||||||
|
image: "/static/castlove/silapian.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
吧唧: [
|
||||||
|
{
|
||||||
|
name: "超复古",
|
||||||
|
image: "/static/castlove/fugu.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "卡通刺绣",
|
||||||
|
image: "/static/castlove/katongchixiu.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "云母片",
|
||||||
|
image: "/static/castlove/yunmupian.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "开发中",
|
||||||
|
image: "/static/castlove/daikaifa.png",
|
||||||
|
comingSoon: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
海报: [
|
||||||
|
{
|
||||||
|
name: "拼豆",
|
||||||
|
image: "/static/castlove/pindou.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "简繁插画",
|
||||||
|
image: "/static/castlove/jinfanchahua.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "街头拼贴",
|
||||||
|
image: "/static/castlove/jietoupintie.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "开发中",
|
||||||
|
image: "/static/castlove/daikaifa.png",
|
||||||
|
comingSoon: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cardList: [
|
||||||
|
{
|
||||||
|
name: "光栅卡",
|
||||||
|
image: "/static/castlove/guangshanka.png",
|
||||||
|
comingSoon: false,
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
watch: {
|
name: "拍立得",
|
||||||
// 作为组件时,父组件传 type 进来时自动定位
|
image: "/static/castlove/pailide.png",
|
||||||
// 用 immediate + handler 显式写法,避免简写在某些 H5 场景下不触发
|
comingSoon: false,
|
||||||
type: {
|
|
||||||
immediate: true,
|
|
||||||
handler(val) {
|
|
||||||
this.applyType(val)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "镭射卡",
|
||||||
|
image: "/static/castlove/leisheka.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "撕拉片",
|
||||||
|
image: "/static/castlove/silapian.png",
|
||||||
|
comingSoon: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "开发中",
|
||||||
|
image: "/static/castlove/daikaifa.png",
|
||||||
|
comingSoon: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
cardRoutes: {
|
||||||
|
光栅卡: "/pages/castlove/lenticular/lenticular-create",
|
||||||
|
拍立得: "/pages/castlove/create",
|
||||||
|
镭射卡: "/pages/castlove/create",
|
||||||
|
撕拉片: "/pages/castlove/create",
|
||||||
|
},
|
||||||
|
|
||||||
|
totalCard: 5,
|
||||||
|
selectedIndex: 1, // 默认第2张
|
||||||
|
touchStartY: 0,
|
||||||
|
dragOffset: 0,
|
||||||
|
isDragging: false,
|
||||||
|
disableTransition: false,
|
||||||
|
SWIPE_STEP: 100,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentCategoryName() {
|
||||||
|
return this.categoryList[this.selectedCategoryIndex]?.name || "";
|
||||||
},
|
},
|
||||||
created() {
|
currentCardList() {
|
||||||
// 组件创建时立即应用一次 type,避免 mall.vue 在 onLoad 才设置 initialType
|
const name = this.categoryList[this.selectedCategoryIndex]?.name;
|
||||||
// 导致首次渲染拿到默认 "" 而 watch 错过的情况
|
return this.cardListMap[name] || this.cardList;
|
||||||
this.applyType(this.type)
|
|
||||||
},
|
},
|
||||||
onLoad(options) {
|
// 当前分类下的小类型卡片数量
|
||||||
// 作为页面被直接打开时,隐藏右侧分类菜单栏(根据用户需求)
|
// (与 cardListMap 中该类型数组的 length 一一对应,后续新增类型无需修改此处)
|
||||||
this.showMenu = false
|
currentCardCount() {
|
||||||
// 接收从主Tab(square.vue / CastloveContent.vue)传入的 type,自动定位到对应分类
|
return this.currentCardList.length;
|
||||||
// type 取值:star_card | badge | poster
|
|
||||||
if (options && options.type) {
|
|
||||||
this.applyType(options.type)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onShow() {
|
// 扇形中心索引(取整)
|
||||||
try {
|
// 奇数 N:正中;偶数 N:稍偏下的中间位
|
||||||
uni.hideToast()
|
currentCenterIndex() {
|
||||||
uni.hideLoading()
|
return Math.floor(this.currentCardCount / 2);
|
||||||
} catch (e) { }
|
|
||||||
},
|
},
|
||||||
data() {
|
},
|
||||||
|
methods: {
|
||||||
|
// 根据 type 字符串定位到对应分类索引
|
||||||
|
applyType(type) {
|
||||||
|
if (!type) return;
|
||||||
|
const idx = this.categoryTypeMap[type];
|
||||||
|
if (typeof idx === "number") {
|
||||||
|
this.selectedCategoryIndex = idx;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectCategory(index) {
|
||||||
|
this.selectedCategoryIndex = index;
|
||||||
|
// 切换分类后,根据新分类的卡片数量重置中心索引
|
||||||
|
// (直接读 cardListMap 避免依赖 computed 在同 tick 内的更新)
|
||||||
|
const newCount =
|
||||||
|
this.cardListMap[this.categoryList[index]?.name]?.length || 0;
|
||||||
|
this.selectedIndex = Math.floor(newCount / 2);
|
||||||
|
this.dragOffset = 0;
|
||||||
|
this.isDragging = false;
|
||||||
|
},
|
||||||
|
onTouchStart(e) {
|
||||||
|
this.touchStartY = e.touches[0].clientY;
|
||||||
|
this.isDragging = true;
|
||||||
|
this.disableTransition = true;
|
||||||
|
},
|
||||||
|
onTouchMove(e) {
|
||||||
|
if (!this.isDragging) return;
|
||||||
|
const moveY = e.touches[0].clientY;
|
||||||
|
this.dragOffset = moveY - this.touchStartY;
|
||||||
|
},
|
||||||
|
onTouchEnd() {
|
||||||
|
if (!this.isDragging) return;
|
||||||
|
this.isDragging = false;
|
||||||
|
this.disableTransition = false;
|
||||||
|
|
||||||
|
const N = this.currentCardCount;
|
||||||
|
if (N === 0) return;
|
||||||
|
|
||||||
|
const moveCount = Math.round(-this.dragOffset / this.SWIPE_STEP);
|
||||||
|
let newIdx = this.selectedIndex + moveCount;
|
||||||
|
newIdx = ((newIdx % N) + N) % N;
|
||||||
|
this.selectedIndex = newIdx;
|
||||||
|
this.dragOffset = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
// ====================== 核心修复:z-index + 层级 + 循环 ======================
|
||||||
|
// 返回当前分类对应的扇形位置
|
||||||
|
// 规则:基础 5 个位置保持原样不动
|
||||||
|
// - N <= 5:取前 N 个
|
||||||
|
// - N > 5:基础 5 个 + (N-5) 个在末尾沿用底部样式追加
|
||||||
|
// 这样新增/删减小类型卡片时,既有的位置不会被打乱重组
|
||||||
|
getStackPositions(count) {
|
||||||
|
if (count <= 0) return [];
|
||||||
|
// 基础 5 张卡片的扇形布局(保持原状)
|
||||||
|
const basePositions = [
|
||||||
|
{ left: 288, top: 64, rotate: 25, scale: 0.75 },
|
||||||
|
{ left: 120, top: 288, rotate: 12, scale: 0.95 },
|
||||||
|
{ left: 60, top: 580, rotate: 0, scale: 1 },
|
||||||
|
{ left: 120, top: 888, rotate: -12, scale: 0.95 },
|
||||||
|
{ left: 224, top: 1096, rotate: -25, scale: 0.75 },
|
||||||
|
];
|
||||||
|
if (count <= basePositions.length) {
|
||||||
|
// 卡片少于 5 张:直接取前 N 个,不动基础数组
|
||||||
|
return basePositions.slice(0, count);
|
||||||
|
}
|
||||||
|
// 卡片多于 5 张:在末尾追加,沿用第 5 张的样式继续向下延伸
|
||||||
|
const positions = [...basePositions];
|
||||||
|
const tail = basePositions[basePositions.length - 1];
|
||||||
|
for (let i = basePositions.length; i < count; i++) {
|
||||||
|
const extra = i - basePositions.length + 1;
|
||||||
|
positions.push({
|
||||||
|
left: tail.left,
|
||||||
|
top: tail.top + extra * 208, // 沿用 888→1096 的间距
|
||||||
|
rotate: tail.rotate - extra * 12, // 沿用 -12°→-25° 的旋转步长
|
||||||
|
scale: tail.scale,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return positions;
|
||||||
|
},
|
||||||
|
|
||||||
|
getCardStyle(index) {
|
||||||
|
const N = this.currentCardCount;
|
||||||
|
if (N === 0) return {};
|
||||||
|
|
||||||
|
const positions = this.getStackPositions(N);
|
||||||
|
const centerInt = this.currentCenterIndex; // 扇形中心索引(N=5→2;N=4→2;N=6→3)
|
||||||
|
const centerPos = positions[centerInt] ?? positions[0];
|
||||||
|
|
||||||
|
const progress = -this.dragOffset / this.SWIPE_STEP;
|
||||||
|
const centerIdx = this.selectedIndex + progress;
|
||||||
|
|
||||||
|
// 循环最短差值(关键):以 N/2 作为环绕阈值,适配任意 N
|
||||||
|
let diff = index - centerIdx;
|
||||||
|
const half = N / 2;
|
||||||
|
if (diff > half) diff -= N;
|
||||||
|
if (diff < -half) diff += N;
|
||||||
|
const cardPos = diff + centerInt;
|
||||||
|
|
||||||
|
// 插值计算(支持首尾环绕)
|
||||||
|
let pos;
|
||||||
|
const lowerRaw = Math.floor(cardPos);
|
||||||
|
const t = cardPos - lowerRaw;
|
||||||
|
const lowerIdx = ((lowerRaw % N) + N) % N;
|
||||||
|
const upperIdx = (lowerIdx + 1) % N;
|
||||||
|
|
||||||
|
if (t === 0 && lowerIdx >= 0 && lowerIdx < N) {
|
||||||
|
pos = positions[lowerIdx] ?? centerPos;
|
||||||
|
} else {
|
||||||
|
const p = positions[lowerIdx] ?? centerPos;
|
||||||
|
const n = positions[upperIdx] ?? centerPos;
|
||||||
|
pos = {
|
||||||
|
left: p.left + (n.left - p.left) * t,
|
||||||
|
top: p.top + (n.top - p.top) * t,
|
||||||
|
rotate: p.rotate + (n.rotate - p.rotate) * t,
|
||||||
|
scale: p.scale + (n.scale - p.scale) * t,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== Z‑INDEX 终极修复 ======================
|
||||||
|
const distance = Math.abs(cardPos - centerInt);
|
||||||
|
const zIndex = Math.round(100 - distance * 30); // 越靠近中间层级越高
|
||||||
|
const isCenter = distance < 0.02;
|
||||||
|
|
||||||
|
if (isCenter) {
|
||||||
return {
|
return {
|
||||||
// 是否显示右侧分类菜单栏
|
left: pos.left + "rpx",
|
||||||
// - 作为组件(mall.vue 中)使用时,默认 true
|
top: pos.top + "rpx",
|
||||||
// - 作为页面被直接打开时,onLoad 会把它置为 false
|
transform: `scale(${pos.scale * 1.15}) rotate(${pos.rotate}deg)`,
|
||||||
showMenu: true,
|
zIndex: 999, // 中心永远最高
|
||||||
selectedCategoryIndex: 0,
|
};
|
||||||
// 主Tab 的 type 值与 categoryList 索引的映射
|
}
|
||||||
// 与 square.vue / CastloveContent.vue 中 mainTabs 的 type 保持一致
|
|
||||||
categoryTypeMap: {
|
|
||||||
star_card: 0,
|
|
||||||
badge: 1,
|
|
||||||
poster: 2,
|
|
||||||
},
|
|
||||||
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 },
|
|
||||||
],
|
|
||||||
'吧唧': [],
|
|
||||||
'海报': []
|
|
||||||
},
|
|
||||||
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 }
|
|
||||||
],
|
|
||||||
cardRoutes: {
|
|
||||||
'光栅卡': '/pages/castlove/lenticular/lenticular-create',
|
|
||||||
'拍立得': '/pages/castlove/create',
|
|
||||||
'镭射卡': '/pages/castlove/create',
|
|
||||||
'撕拉片': '/pages/castlove/create',
|
|
||||||
},
|
|
||||||
|
|
||||||
totalCard: 5,
|
return {
|
||||||
selectedIndex: 1, // 默认第2张
|
left: pos.left + "rpx",
|
||||||
touchStartY: 0,
|
top: pos.top + "rpx",
|
||||||
dragOffset: 0,
|
transform: `scale(${pos.scale}) rotate(${pos.rotate}deg)`,
|
||||||
isDragging: false,
|
zIndex,
|
||||||
disableTransition: false,
|
};
|
||||||
SWIPE_STEP: 100,
|
},
|
||||||
|
|
||||||
|
getCardStackPosition(index) {
|
||||||
|
const N = this.currentCardCount;
|
||||||
|
if (N === 0) return 0;
|
||||||
|
const centerInt = this.currentCenterIndex;
|
||||||
|
const centerIdx = this.selectedIndex + -this.dragOffset / this.SWIPE_STEP;
|
||||||
|
let diff = index - centerIdx;
|
||||||
|
const half = N / 2;
|
||||||
|
if (diff > half) diff -= N;
|
||||||
|
if (diff < -half) diff += N;
|
||||||
|
return diff + centerInt;
|
||||||
|
},
|
||||||
|
|
||||||
|
onCardFrameTap(index) {
|
||||||
|
const card = this.currentCardList[index];
|
||||||
|
if (!card) return;
|
||||||
|
const pos = this.getCardStackPosition(index);
|
||||||
|
if (Math.abs(pos - this.currentCenterIndex) < 0.2) {
|
||||||
|
if (card.name === "光栅卡") {
|
||||||
|
uni.navigateTo({
|
||||||
|
url:
|
||||||
|
this.cardRoutes["光栅卡"] +
|
||||||
|
"?name=" +
|
||||||
|
encodeURIComponent(card.name),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: "激情开发中", icon: "none" });
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.selectedIndex = index;
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
currentCategoryName() {
|
scrollUp() {
|
||||||
return this.categoryList[this.selectedCategoryIndex]?.name || ''
|
if (this.selectedCategoryIndex > 0)
|
||||||
},
|
this.selectCategory(this.selectedCategoryIndex - 1);
|
||||||
currentCardList() {
|
|
||||||
const name = this.categoryList[this.selectedCategoryIndex]?.name
|
|
||||||
return this.cardListMap[name] || this.cardList
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
scrollDown() {
|
||||||
// 根据 type 字符串定位到对应分类索引
|
if (this.selectedCategoryIndex < this.categoryList.length - 1) {
|
||||||
applyType(type) {
|
this.selectCategory(this.selectedCategoryIndex + 1);
|
||||||
if (!type) return
|
}
|
||||||
const idx = this.categoryTypeMap[type]
|
|
||||||
if (typeof idx === 'number') {
|
|
||||||
this.selectedCategoryIndex = idx
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectCategory(index) {
|
|
||||||
this.selectedCategoryIndex = index
|
|
||||||
this.selectedIndex = 1
|
|
||||||
this.dragOffset = 0
|
|
||||||
this.isDragging = false
|
|
||||||
},
|
|
||||||
onTouchStart(e) {
|
|
||||||
this.touchStartY = e.touches[0].clientY
|
|
||||||
this.isDragging = true
|
|
||||||
this.disableTransition = true
|
|
||||||
},
|
|
||||||
onTouchMove(e) {
|
|
||||||
if (!this.isDragging) return
|
|
||||||
const moveY = e.touches[0].clientY
|
|
||||||
this.dragOffset = moveY - this.touchStartY
|
|
||||||
},
|
|
||||||
onTouchEnd() {
|
|
||||||
if (!this.isDragging) return
|
|
||||||
this.isDragging = false
|
|
||||||
this.disableTransition = false
|
|
||||||
|
|
||||||
const moveCount = Math.round(-this.dragOffset / this.SWIPE_STEP)
|
|
||||||
let newIdx = this.selectedIndex + moveCount
|
|
||||||
newIdx = ((newIdx % 5) + 5) % 5
|
|
||||||
this.selectedIndex = newIdx
|
|
||||||
this.dragOffset = 0
|
|
||||||
},
|
|
||||||
|
|
||||||
// ====================== 核心修复:z-index + 层级 + 循环 ======================
|
|
||||||
getCardStyle(index) {
|
|
||||||
const positions = [
|
|
||||||
{ left: 288, top: 64, rotate: 25, scale: 0.75 },
|
|
||||||
{ left: 120, top: 288, rotate: 12, scale: 0.95 },
|
|
||||||
{ left: 60, top: 580, rotate: 0, scale: 1 },
|
|
||||||
{ left: 120, top: 888, rotate: -12, scale: 0.95 },
|
|
||||||
{ left: 224, top: 1096, rotate: -25, scale: 0.75 },
|
|
||||||
]
|
|
||||||
|
|
||||||
const progress = -this.dragOffset / this.SWIPE_STEP
|
|
||||||
const centerIdx = this.selectedIndex + progress
|
|
||||||
|
|
||||||
// 循环最短差值(关键)
|
|
||||||
let diff = index - centerIdx
|
|
||||||
if (diff > 2) diff -= 5
|
|
||||||
if (diff < -2) diff += 5
|
|
||||||
const cardPos = diff + 2
|
|
||||||
|
|
||||||
// 插值计算
|
|
||||||
let pos
|
|
||||||
if (Number.isInteger(cardPos)) {
|
|
||||||
pos = positions[cardPos] ?? positions[2]
|
|
||||||
} else {
|
|
||||||
const prev = Math.floor(cardPos)
|
|
||||||
const next = (prev + 1) % 5
|
|
||||||
const t = cardPos - prev
|
|
||||||
const p = positions[prev] ?? positions[2]
|
|
||||||
const n = positions[next] ?? positions[2]
|
|
||||||
pos = {
|
|
||||||
left: p.left + (n.left - p.left) * t,
|
|
||||||
top: p.top + (n.top - p.top) * t,
|
|
||||||
rotate: p.rotate + (n.rotate - p.rotate) * t,
|
|
||||||
scale: p.scale + (n.scale - p.scale) * t,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====================== Z‑INDEX 终极修复 ======================
|
|
||||||
const distance = Math.abs(cardPos - 2)
|
|
||||||
const zIndex = Math.round(100 - distance * 30) // 越靠近中间层级越高
|
|
||||||
const isCenter = distance < 0.02
|
|
||||||
|
|
||||||
if (isCenter) {
|
|
||||||
return {
|
|
||||||
left: pos.left + 'rpx',
|
|
||||||
top: pos.top + 'rpx',
|
|
||||||
transform: `scale(${pos.scale * 1.15}) rotate(${pos.rotate}deg)`,
|
|
||||||
zIndex: 999, // 中心永远最高
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
left: pos.left + 'rpx',
|
|
||||||
top: pos.top + 'rpx',
|
|
||||||
transform: `scale(${pos.scale}) rotate(${pos.rotate}deg)`,
|
|
||||||
zIndex,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getCardStackPosition(index) {
|
|
||||||
const progress = -this.dragOffset / this.SWIPE_STEP
|
|
||||||
const centerIdx = this.selectedIndex + progress
|
|
||||||
let diff = index - centerIdx
|
|
||||||
if (diff > 2) diff -= 5
|
|
||||||
if (diff < -2) diff += 5
|
|
||||||
return diff + 2
|
|
||||||
},
|
|
||||||
|
|
||||||
onCardFrameTap(index) {
|
|
||||||
const card = this.currentCardList[index]
|
|
||||||
if (!card) return
|
|
||||||
const pos = this.getCardStackPosition(index)
|
|
||||||
if (Math.abs(pos - 2) < 0.2) {
|
|
||||||
if (card.name === '光栅卡') {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: this.cardRoutes['光栅卡'] + '?name=' + encodeURIComponent(card.name),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
uni.showToast({ title: '激情开发中', icon: 'none' })
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.selectedIndex = index
|
|
||||||
},
|
|
||||||
|
|
||||||
scrollUp() {
|
|
||||||
if (this.selectedCategoryIndex > 0) this.selectCategory(this.selectedCategoryIndex - 1)
|
|
||||||
},
|
|
||||||
scrollDown() {
|
|
||||||
if (this.selectedCategoryIndex < this.categoryList.length - 1) {
|
|
||||||
this.selectCategory(this.selectedCategoryIndex + 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.page {
|
.page {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-wrapper {
|
.bg-wrapper {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: -16rpx;
|
top: -16rpx;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 110%;
|
height: 110%;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-container {
|
.main-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cards-container {
|
.cards-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-item {
|
.card-item {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 344rpx;
|
width: 312rpx;
|
||||||
height: 344rpx;
|
height: 312rpx;
|
||||||
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
will-change: transform, z-index;
|
will-change: transform, z-index;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-item.no-transition {
|
.card-item.no-transition {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-frame {
|
.card-frame {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 20rpx;
|
border-radius: 24rpx;
|
||||||
padding: 10rpx;
|
padding: 24rpx;
|
||||||
background-image: url('/static/square/cangpinkuang1.png');
|
background-image: url("/static/square/cangpinkuang1.png");
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-frame.no-border {
|
.card-frame.no-border {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-image {
|
.card-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 14rpx;
|
border-radius: 24rpx;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.coming-soon-badge {
|
.coming-soon-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: 160rpx;
|
width: 160rpx;
|
||||||
height: 160rpx;
|
height: 160rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-name {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 32rpx;
|
||||||
|
color: #fffabd;
|
||||||
|
|
||||||
|
text-shadow: -1px 1px 4px #ce0909d6;
|
||||||
|
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-panel {
|
.text-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 30rpx;
|
right: 30rpx;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
width: 200rpx;
|
width: 200rpx;
|
||||||
height: 392rpx;
|
height: 392rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: url('/static/castlove/xiahualan.png') no-repeat center;
|
background: url("/static/castlove/xiahualan.png") no-repeat center;
|
||||||
background-size: 130%;
|
background-size: 130%;
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow-btn {
|
.arrow-btn {
|
||||||
width: 60rpx;
|
width: 60rpx;
|
||||||
height: 32rpx;
|
height: 32rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow-icon {
|
.arrow-icon {
|
||||||
width: 48rpx;
|
width: 48rpx;
|
||||||
height: 48rpx;
|
height: 48rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-list {
|
.text-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0 20rpx;
|
padding: 0 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-item {
|
.text-item {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding: 10rpx 20rpx;
|
padding: 10rpx 20rpx;
|
||||||
border-radius: 14rpx;
|
border-radius: 14rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-item.active {
|
.text-item.active {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background: url('/static/nft/dingbutubiao_liang.png') no-repeat center;
|
background: url("/static/nft/dingbutubiao_liang.png") no-repeat center;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-large {
|
.font-large {
|
||||||
font-size: 34rpx;
|
font-size: 34rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-mid {
|
.font-mid {
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
padding: 40rpx 0;
|
padding: 40rpx 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state-icon {
|
.empty-state-icon {
|
||||||
width: 200rpx;
|
width: 200rpx;
|
||||||
height: 200rpx;
|
height: 200rpx;
|
||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
margin-bottom: 32rpx;
|
margin-bottom: 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state-title {
|
.empty-state-title {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 36rpx;
|
font-size: 36rpx;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 16rpx;
|
margin-bottom: 16rpx;
|
||||||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.35);
|
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state-desc {
|
.empty-state-desc {
|
||||||
color: rgba(255, 255, 255, 0.7);
|
color: rgba(255, 255, 255, 0.7);
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -8,6 +8,10 @@
|
|||||||
:show-scrollbar="false"
|
:show-scrollbar="false"
|
||||||
>
|
>
|
||||||
<view class="dashboard-container">
|
<view class="dashboard-container">
|
||||||
|
<view class="nav-back" @tap="goBack">
|
||||||
|
<text class="nav-back-icon">←</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
<DashboardHeader
|
<DashboardHeader
|
||||||
:active-tab="activeTab"
|
:active-tab="activeTab"
|
||||||
@update:active-tab="handleTabChange"
|
@update:active-tab="handleTabChange"
|
||||||
@ -86,6 +90,18 @@ const { loading, error, data, refresh } = useDashboardData({
|
|||||||
starId: starId.value,
|
starId: starId.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const goBack = () => {
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
if (pages.length > 1) {
|
||||||
|
uni.navigateBack();
|
||||||
|
} else {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: "/pages/square/square",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Tab 切换:30 分钟内复用缓存;切回水晶 Tab 时 cache-aware 刷新
|
// Tab 切换:30 分钟内复用缓存;切回水晶 Tab 时 cache-aware 刷新
|
||||||
function handleTabChange(tab) {
|
function handleTabChange(tab) {
|
||||||
activeTab.value = tab;
|
activeTab.value = tab;
|
||||||
@ -128,6 +144,26 @@ onShow(() => {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-back {
|
||||||
|
position: fixed;
|
||||||
|
top: 96rpx;
|
||||||
|
left: 32rpx;
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
/* background: rgba(255,255,255,0.5);
|
||||||
|
border-radius: 50%; */
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-back-icon {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard-box {
|
.dashboard-box {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user