711 lines
20 KiB
Vue
711 lines
20 KiB
Vue
<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"
|
||
@touchmove="onTouchMove"
|
||
@touchend="onTouchEnd"
|
||
>
|
||
<!-- 1. 加载中:背景图照常显示,卡片区显示半透明 loading,不要白屏 -->
|
||
<view v-if="loading" class="loading-state">
|
||
<image class="loading-spinner" src="/static/common/loading.png" />
|
||
</view>
|
||
|
||
<!-- 2. 加载失败:点击重试 -->
|
||
<view v-else-if="loadError" class="error-state" @tap="loadConfig">
|
||
<text class="error-text">{{ loadError }}</text>
|
||
<text class="error-hint">点击重试</text>
|
||
</view>
|
||
|
||
<!-- 3. 全空:后台把所有分类都软删了 -->
|
||
<view v-else-if="categories.length === 0" class="empty-state">
|
||
<image class="empty-state-icon" src="/static/castlove/jinqingqidai.png" mode="aspectFit" />
|
||
<text class="empty-state-title">暂无内容</text>
|
||
</view>
|
||
|
||
<!-- 4. 某分类下卡片为空 -->
|
||
<view v-else-if="currentCardList.length === 0" class="empty-state">
|
||
<image class="empty-state-icon" src="/static/castlove/jinqingqidai.png" mode="aspectFit" />
|
||
<text class="empty-state-title">{{ currentCategoryName }}敬请期待</text>
|
||
</view>
|
||
|
||
<!-- 5. 正常:渲染卡片 -->
|
||
<view v-else>
|
||
<view
|
||
v-for="(card, index) in currentCardList"
|
||
:key="card.id"
|
||
class="card-item"
|
||
:class="{ 'no-transition': disableTransition }"
|
||
:style="getCardStyle(index)"
|
||
>
|
||
<view
|
||
class="card-frame"
|
||
:class="{
|
||
'no-border': !card.route_path,
|
||
'card-frame--tappable': !!card.route_path,
|
||
}"
|
||
@tap.stop="onCardFrameTap(card)"
|
||
>
|
||
<image
|
||
class="card-image"
|
||
:src="card.image_url"
|
||
mode="aspectFill"
|
||
@error="onImageError"
|
||
/>
|
||
<!-- "开发中"角标:仅当卡片名就是"开发中"时才显示(避免对其他未配置路由的卡片误显示) -->
|
||
<image
|
||
v-if="card.name === '开发中'"
|
||
class="coming-soon-badge"
|
||
src="/static/castlove/jinqingqidai.png"
|
||
/>
|
||
</view>
|
||
<text class="card-name">{{ card.name }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 右侧分类菜单:仅在数据正常时显示 -->
|
||
<view
|
||
v-if="!loading && !loadError && categories.length"
|
||
class="text-panel"
|
||
>
|
||
<view
|
||
class="arrow-btn arrow-up"
|
||
:class="{ 'arrow-btn--disabled': selectedCategoryIndex === 0 }"
|
||
@click="scrollUp"
|
||
>
|
||
<image
|
||
class="arrow-icon"
|
||
src="/static/castlove/jiantou.png"
|
||
style="transform: rotate(180deg)"
|
||
/>
|
||
</view>
|
||
<view class="text-list">
|
||
<view
|
||
v-for="(category, index) in categories"
|
||
:key="category.id"
|
||
class="text-item"
|
||
:class="{ active: selectedCategoryIndex === index }"
|
||
@click="selectCategory(index)"
|
||
>
|
||
<text>{{ category.name }}</text>
|
||
</view>
|
||
</view>
|
||
<view
|
||
class="arrow-btn arrow-down"
|
||
:class="{
|
||
'arrow-btn--disabled':
|
||
selectedCategoryIndex === categories.length - 1,
|
||
}"
|
||
@click="scrollDown"
|
||
>
|
||
<image class="arrow-icon" src="/static/castlove/jiantou.png" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { getCastloveConfigApi } from "@/utils/api.js";
|
||
|
||
export default {
|
||
// 作为组件被 mall.vue 引入时的入参
|
||
// (作为页面被 navigateTo 直接打开时不传值,走 onLoad 读取 URL 参数)
|
||
props: {
|
||
// 初始分类:star_card | badge | poster
|
||
type: {
|
||
type: String,
|
||
default: "",
|
||
},
|
||
},
|
||
watch: {
|
||
// 关键入口:组件模式下(被 mall.vue 引用),onLoad 不会触发
|
||
// 必须在这里同时拉配置 + 应用 type。immediate: true 保证 mount 时就跑一次
|
||
type: {
|
||
immediate: true,
|
||
async handler(val) {
|
||
// 先等数据回来,再定位分类(findIndex 需要 categories 已加载)
|
||
await this.loadConfig();
|
||
this.applyType(val);
|
||
},
|
||
},
|
||
},
|
||
|
||
onShow() {
|
||
try {
|
||
uni.hideToast();
|
||
uni.hideLoading();
|
||
} catch (e) {}
|
||
},
|
||
|
||
// 作为页面被直接打开(不走 mall.vue)的兜底入口
|
||
// 作为组件被 mall.vue 引入时,onLoad 不会触发,由 watch.type 接管
|
||
async onLoad(options) {
|
||
await this.loadConfig();
|
||
if (options && options.type) {
|
||
this.applyType(options.type);
|
||
}
|
||
},
|
||
|
||
data() {
|
||
return {
|
||
// 是否显示右侧分类菜单栏
|
||
// - 作为组件(mall.vue 中)使用时,默认 true
|
||
// - 作为页面被直接打开时,onLoad 会把它置为 false
|
||
showMenu: true,
|
||
selectedCategoryIndex: 0,
|
||
|
||
// ↓ 全部来自后端,含 route_path / route_params
|
||
// [{ id, name, type_key, sort_order, crafts: [{ id, name, image_url, route_path, route_params, sort_order }] }]
|
||
categories: [],
|
||
loading: true,
|
||
loadError: "",
|
||
|
||
// 原有交互/动效状态保留
|
||
selectedIndex: 1, // 默认第 2 张(切换分类时由 defaultSelectedIndex 动态重算)
|
||
touchStartY: 0,
|
||
dragOffset: 0,
|
||
isDragging: false,
|
||
disableTransition: false,
|
||
SWIPE_STEP: 100,
|
||
};
|
||
},
|
||
|
||
computed: {
|
||
currentCategoryName() {
|
||
return this.categories[this.selectedCategoryIndex]?.name || "";
|
||
},
|
||
currentCardList() {
|
||
return this.categories[this.selectedCategoryIndex]?.crafts || [];
|
||
},
|
||
currentCardCount() {
|
||
return this.currentCardList.length;
|
||
},
|
||
currentCenterIndex() {
|
||
return Math.floor(this.currentCardCount / 2);
|
||
},
|
||
// 切换分类时的默认选中卡片
|
||
// 原本固定 = 1(默认第 2 张),但卡片数 < 2 时会越界
|
||
// 卡片 0 张 → 0;卡片 1 张 → 0;卡片 ≥ 2 张 → 1
|
||
// 后台动态删卡也安全:卡片数变化后重新计算
|
||
defaultSelectedIndex() {
|
||
return Math.min(1, Math.max(0, this.currentCardCount - 1));
|
||
},
|
||
},
|
||
|
||
methods: {
|
||
/**
|
||
* 拉取后端配置(进入页面 / 点击重试时调用)
|
||
* 强制鉴权(token 由 utils/api.js 统一注入)
|
||
*/
|
||
async loadConfig() {
|
||
try {
|
||
this.loading = true;
|
||
this.loadError = "";
|
||
const res = await getCastloveConfigApi();
|
||
this.categories = (res && res.data && res.data.categories) || [];
|
||
} catch (e) {
|
||
this.loadError = e?.message || "配置加载失败";
|
||
this.categories = [];
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 根据 type 字符串定位到对应分类索引
|
||
* 在线 findIndex(不再依赖前端硬编码的 categoryTypeMap)
|
||
*/
|
||
applyType(type) {
|
||
if (!type) return;
|
||
const idx = this.categories.findIndex((c) => c.type_key === type);
|
||
if (idx >= 0) this.selectedCategoryIndex = idx;
|
||
// 找不到则保持 0,不报错
|
||
},
|
||
|
||
/** 切换大分类 */
|
||
selectCategory(index) {
|
||
this.selectedCategoryIndex = index;
|
||
// 用计算属性而不是写死 1(防止卡片数 < 2 越界)
|
||
this.selectedIndex = this.defaultSelectedIndex;
|
||
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 {
|
||
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,
|
||
};
|
||
},
|
||
|
||
/** 当前叠卡在弧形布局中的槽位(2=正中,1=中上,0=最上...) */
|
||
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;
|
||
},
|
||
|
||
/**
|
||
* 点击卡图区域
|
||
* - 正中主图(槽位 ≈ 中心)→ 读 card.route_path / route_params 跳转
|
||
* - 其余叠层 → 仅切换选中(把卡片移到中间)
|
||
*
|
||
* 跳转规则:
|
||
* 1. route_path 为空/null → 弹 "激情开发中" toast,不跳转
|
||
* 2. route_path 非空 → 拼上 route_params 作为 query
|
||
* 目标页面必须在 pages.json 中已注册,否则 uni.navigateTo 失败 → 弹 "页面不存在" toast
|
||
*/
|
||
onCardFrameTap(card) {
|
||
if (!card) return;
|
||
const pos = this.getCardStackPosition(this.currentCardList.indexOf(card));
|
||
// 只处理正中位置
|
||
if (Math.abs(pos - this.currentCenterIndex) >= 0.2) {
|
||
this.selectedIndex = this.currentCardList.indexOf(card);
|
||
return;
|
||
}
|
||
// 1. route_path 为空/null → toast
|
||
if (!card.route_path) {
|
||
uni.showToast({ title: "激情开发中", icon: "none" });
|
||
return;
|
||
}
|
||
// 2. 拼 query
|
||
// const query = this.buildQueryString(card.route_params);
|
||
// const url = query ? `${card.route_path}?name=${encodeURIComponent(card.name)}` : card.route_path;
|
||
const url = `${card.route_path}?name=${encodeURIComponent(card.name)}`;
|
||
console.log(url)
|
||
uni.navigateTo({
|
||
url,
|
||
fail: (err) => {
|
||
// 路径写错 / pages.json 未注册 → 兜底 toast,不闪退
|
||
console.error("[castlove] navigateTo fail", card.route_path, err);
|
||
uni.showToast({ title: "页面不存在", icon: "none" });
|
||
},
|
||
});
|
||
},
|
||
|
||
/**
|
||
* 把 route_params(JSON 字符串或对象)转成 query string
|
||
* - null/undefined/空对象 → ""
|
||
* - 嵌套对象/数组已被后端兜底清空,这里再做一次防御性过滤
|
||
*/
|
||
buildQueryString(params) {
|
||
if (!params) return "";
|
||
// 后端统一返回 JSON 字符串;同时兼容前端 mock 或老代码传对象的情况
|
||
let obj = params;
|
||
if (typeof params === "string") {
|
||
try {
|
||
obj = JSON.parse(params);
|
||
} catch (e) {
|
||
return "";
|
||
}
|
||
}
|
||
if (!obj || typeof obj !== "object" || Array.isArray(obj)) return "";
|
||
return Object.keys(obj)
|
||
.filter(
|
||
(k) =>
|
||
obj[k] !== undefined &&
|
||
obj[k] !== null &&
|
||
typeof obj[k] !== "object"
|
||
)
|
||
.map(
|
||
(k) =>
|
||
`${encodeURIComponent(k)}=${encodeURIComponent(String(obj[k]))}`
|
||
)
|
||
.join("&");
|
||
},
|
||
|
||
/** 图片加载失败兜底 */
|
||
onImageError(e) {
|
||
console.warn("[craft-select] image load failed", e);
|
||
// 这里如果以后想换占位图,直接换 src
|
||
},
|
||
|
||
/** 右侧菜单上箭头 */
|
||
scrollUp() {
|
||
if (this.selectedCategoryIndex > 0) {
|
||
this.selectCategory(this.selectedCategoryIndex - 1);
|
||
}
|
||
},
|
||
|
||
/** 右侧菜单下箭头 */
|
||
scrollDown() {
|
||
if (this.selectedCategoryIndex < this.categories.length - 1) {
|
||
this.selectCategory(this.selectedCategoryIndex + 1);
|
||
}
|
||
},
|
||
},
|
||
};
|
||
</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: 312rpx;
|
||
height: 312rpx;
|
||
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
||
will-change: transform, z-index;
|
||
}
|
||
|
||
.card-item.no-transition {
|
||
transition: none !important;
|
||
}
|
||
|
||
.card-frame {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 24rpx;
|
||
padding: 24rpx;
|
||
background-image: url("/static/square/cangpinkuang1.png");
|
||
background-size: cover;
|
||
}
|
||
|
||
.card-frame.no-border {
|
||
background-image: none;
|
||
}
|
||
|
||
.card-frame--tappable {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.card-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 24rpx;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.coming-soon-badge {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
}
|
||
|
||
.card-name {
|
||
position: absolute;
|
||
bottom: -16rpx;
|
||
left: 32rpx;
|
||
background: linear-gradient(
|
||
93.1deg,
|
||
rgba(224, 180, 247, 0.71) -12.06%,
|
||
rgba(178, 246, 204, 0.71) 52.09%,
|
||
rgba(98, 178, 244, 0.71) 163.5%
|
||
);
|
||
backdrop-filter: blur(11.699999809265137px);
|
||
color: #fffabd;
|
||
text-shadow: -1px 1px 4px #ce0909d6;
|
||
font-size: 28rpx;
|
||
font-weight: 400;
|
||
padding: 0 16rpx;
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
// ===== 5 个状态分支(loading / error / empty / 正常)=====
|
||
.loading-state {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.loading-spinner {
|
||
width: 96rpx;
|
||
height: 96rpx;
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.error-state {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 80%;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.error-text {
|
||
color: #fff;
|
||
font-size: 32rpx;
|
||
margin-bottom: 16rpx;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.35);
|
||
}
|
||
|
||
.error-hint {
|
||
color: rgba(255, 255, 255, 0.7);
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
// ===== 右侧分类菜单(自适应高度)=====
|
||
.text-panel {
|
||
position: absolute;
|
||
right: 30rpx;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 200rpx;
|
||
min-height: 392rpx; // 保留 3 项时的视觉高度
|
||
max-height: 80vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: url("/static/castlove/xiahualan.png") no-repeat center;
|
||
background-size: 130%;
|
||
border-radius: 20rpx;
|
||
padding: 16rpx 0;
|
||
}
|
||
|
||
.arrow-btn {
|
||
width: 60rpx;
|
||
height: 32rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.arrow-btn--disabled {
|
||
opacity: 0.25;
|
||
pointer-events: none; // 已禁用时不再触发点击
|
||
}
|
||
|
||
.arrow-icon {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
}
|
||
|
||
.text-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 0 20rpx;
|
||
// 超过 5 项时启用滚动(spec §6.7)
|
||
max-height: 60vh;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.text-item {
|
||
color: #fff;
|
||
font-size: 28rpx; // 统一字号,不再写 font-large / font-mid
|
||
font-weight: 500;
|
||
padding: 14rpx 20rpx;
|
||
border-radius: 14rpx;
|
||
display: flex;
|
||
justify-content: center;
|
||
transition: font-size 0.2s;
|
||
}
|
||
|
||
.text-item.active {
|
||
font-weight: bold;
|
||
font-size: 34rpx; // 选中项放大(spec §6.7)
|
||
background: url("/static/nft/dingbutubiao_liang.png") no-repeat center;
|
||
background-size: 100% 100%;
|
||
}
|
||
|
||
.empty-state {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 80%;
|
||
padding: 40rpx 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.empty-state-icon {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
opacity: 0.85;
|
||
margin-bottom: 32rpx;
|
||
}
|
||
|
||
.empty-state-title {
|
||
color: #fff;
|
||
font-size: 36rpx;
|
||
font-weight: 600;
|
||
margin-bottom: 16rpx;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.35);
|
||
}
|
||
|
||
.empty-state-desc {
|
||
color: rgba(255, 255, 255, 0.7);
|
||
font-size: 26rpx;
|
||
text-align: center;
|
||
}
|
||
</style>
|