1508 lines
38 KiB
Vue
1508 lines
38 KiB
Vue
<template>
|
||
<view class="detail-container">
|
||
<!-- 背景图片 -->
|
||
<image class="background-image" src="/static/square/squearbj.png" mode="aspectFill"></image>
|
||
|
||
<!-- Header -->
|
||
<view class="header-bar">
|
||
<view class="back-btn" @tap="handleBack">
|
||
<image class="back-icon" src="/static/starbookcontent/tuichu.png" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 铸爱:选图后确认铸造(光栅 / 镭射) -->
|
||
<scroll-view v-if="craftConfirmMode" scroll-y class="content-scroll craft-confirm-scroll">
|
||
<view class="content-wrapper craft-confirm-body">
|
||
<view class="card-section craft-card-section">
|
||
<view class="card-wrapper craft-card-wrapper">
|
||
<image
|
||
class="card-frame"
|
||
src="/static/square/gerenzhongxincangpinkuang.png"
|
||
mode="aspectFit"
|
||
/>
|
||
<view v-if="isCraftLenticular" class="craft-lenticular-slot">
|
||
<LenticularCard
|
||
class="craft-lenticular-card"
|
||
:layers="lenticularLayers"
|
||
:transforms="layerTransforms"
|
||
:gyro-source="gyroSourceLabel"
|
||
:skip-built-in-touch="true"
|
||
tilt-hint-text="晃动查看"
|
||
:shimmer-mid-opacity="0.16"
|
||
@simulate="simulate"
|
||
/>
|
||
</view>
|
||
<image
|
||
v-else
|
||
class="card-image craft-card-image"
|
||
:src="craftCoverUrl"
|
||
mode="aspectFill"
|
||
/>
|
||
</view>
|
||
<view class="card-meta-row">
|
||
<view class="earnings-area">
|
||
<image class="crystal-icon" src="/static/icon/crystal.png" mode="aspectFit" />
|
||
<text class="earnings-text">{{ craftEarningsHint }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="info-row craft-info-row">
|
||
<view class="info-col">
|
||
<view class="info-item">
|
||
<text class="info-label">分类</text>
|
||
<text class="info-value">{{ craftCategoryLabel }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="info-col">
|
||
<view class="info-item">
|
||
<text class="info-label">创作者</text>
|
||
<text class="info-value">{{ craftCreatorName }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="info-col">
|
||
<view class="info-item">
|
||
<text class="info-label">铸爱时间</text>
|
||
<text class="info-value">{{ craftMintDate }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="chain-section">
|
||
<image class="chain-logo" src="/static/logo/APPLOGO.png" mode="aspectFit" />
|
||
<view class="chain-left">
|
||
<view class="chain-row">
|
||
<text class="chain-label">数根名称</text>
|
||
<view class="chain-value-wrap">
|
||
<text class="chain-value">{{ craftAssetName }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="chain-row">
|
||
<text class="chain-label">数根发行方</text>
|
||
<view class="chain-value-wrap">
|
||
<text class="chain-value">TOPFANS</text>
|
||
</view>
|
||
</view>
|
||
<view class="chain-row">
|
||
<text class="chain-label">区块链编号</text>
|
||
<view class="chain-value-wrap">
|
||
<text class="chain-value">{{ craftBlockPlaceholder }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="chain-row">
|
||
<text class="chain-label">交易哈希</text>
|
||
<view class="chain-value-wrap">
|
||
<text class="chain-value chain-hash">{{ craftHashPlaceholder }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="craft-mint-bar">
|
||
<button class="craft-mint-btn" :disabled="craftMinting" @tap="handleCraftMint">
|
||
{{ craftMinting ? '提交中…' : '确认铸造' }}
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 加载中 -->
|
||
<view v-else-if="loading" class="loading-wrapper">
|
||
<!-- 旋转光环 -->
|
||
<view class="loading-ring-outer">
|
||
<view class="loading-ring"></view>
|
||
</view>
|
||
<!-- 三颗脉冲粒子 -->
|
||
<view class="loading-dots">
|
||
<view class="loading-dot dot-1"></view>
|
||
<view class="loading-dot dot-2"></view>
|
||
<view class="loading-dot dot-3"></view>
|
||
</view>
|
||
<!-- 文字淡入淡出 -->
|
||
<text class="loading-text">正在加载藏品...</text>
|
||
</view>
|
||
|
||
<!-- 错误状态 -->
|
||
<view v-else-if="loadError && !craftConfirmMode" class="error-wrapper">
|
||
<text class="error-text">{{ loadError }}</text>
|
||
<button class="retry-btn" @tap="loadData">重试</button>
|
||
</view>
|
||
|
||
<!-- 详情内容 -->
|
||
<scroll-view v-else scroll-y class="content-scroll" :show-scrollbar="false">
|
||
<view class="content-wrapper">
|
||
<!-- 藏品卡片区域 -->
|
||
<view class="card-section">
|
||
<view class="card-wrapper" :class="{ 'card-wrapper--lenticular': isLenticularAsset }">
|
||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFit">
|
||
</image>
|
||
<view v-if="isLenticularAsset" class="detail-lenticular-slot">
|
||
<LenticularCard
|
||
class="detail-lenticular-card"
|
||
:layers="lenticularLayers"
|
||
:transforms="layerTransforms"
|
||
:gyro-source="gyroSourceLabel"
|
||
:skip-built-in-touch="true"
|
||
tilt-hint-text="倾斜手机查看光栅效果"
|
||
:shimmer-mid-opacity="0.16"
|
||
@simulate="simulate"
|
||
/>
|
||
</view>
|
||
<image v-else class="card-image" :src="coverUrl" mode="aspectFill"></image>
|
||
<!-- 贴纸叠加层 -->
|
||
<image
|
||
v-for="sticker in activeStickers"
|
||
:key="sticker.id"
|
||
class="card-sticker"
|
||
:src="sticker.src"
|
||
mode="aspectFit"
|
||
:style="getStickerStyle(sticker)"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 贴纸合成导出隐藏画布 -->
|
||
<canvas
|
||
v-if="activeStickers.length"
|
||
canvas-id="stickerCompositCanvas"
|
||
class="export-canvas"
|
||
:style="{ position: 'fixed', left: '-9999px', top: '-9999px', width: '450px', height: '600px' }"
|
||
/>
|
||
|
||
<!-- 点赞 + 收益 + 倒计时行 -->
|
||
<view class="card-meta-row">
|
||
<!-- 点赞 -->
|
||
<view v-if="assetData.display_status === 1" class="like-area" @tap="handleLike">
|
||
<image :src="isLiked ? '/static/icon/like-after.png' : '/static/icon/like-before.png'"
|
||
class="like-icon" mode="aspectFit"></image>
|
||
<text class="like-num">{{ likeCount }}</text>
|
||
</view>
|
||
|
||
<!-- 收益 -->
|
||
<view class="earnings-area">
|
||
<image class="crystal-icon" src="/static/icon/crystal.png" mode="aspectFit"></image>
|
||
<text class="earnings-text">{{ assetData.earnings || 0 }}/时</text>
|
||
</view>
|
||
|
||
<!-- 倒计时(如有) -->
|
||
<view v-if="assetData.display_status === 1 && countdownText" class="countdown-area">
|
||
<view class="countdown-pill">
|
||
<text class="countdown-val">{{ countdownText }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 信息行(创作者 + 铸造时间 + 来源) -->
|
||
<view class="info-row">
|
||
<view class="info-col">
|
||
<view class="info-item">
|
||
<text class="info-label">来源</text>
|
||
<text class="info-value">{{ assetData.source || '铸造' }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="info-col">
|
||
<view class="info-item">
|
||
<text class="info-label">创作者</text>
|
||
<view class="info-nickname">
|
||
<image v-if="userAvatarUrl" class="info-avatar" :src="userAvatarUrl" mode="aspectFill"></image>
|
||
<text class="info-value">{{ assetData.owner_nickname || '未知' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="info-col">
|
||
<view class="info-item">
|
||
<text class="info-label">铸造时间</text>
|
||
<text class="info-value">{{ formattedAcquireTime }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 创作者信息模块 -->
|
||
<!-- <view v-if="likeCount > 0" class="creator-section" @tap="showLikeUsersModal = true"> -->
|
||
<!-- 点赞用户头像区域 -->
|
||
<!-- <view class="liked-users-area">
|
||
<view v-for="(user, index) in likedUsers" :key="index" class="liked-user-avatar" :style="{
|
||
transform: `translate(${user.ellipseX || 0}rpx, ${user.ellipseY || 0}rpx) scale(${user.size || 1})`,
|
||
zIndex: index < 2 ? index + 1 : (index < 4 ? index + 3 : index + 1)
|
||
}">
|
||
<image class="liked-user-image" :src="user.avatar" mode="aspectFill"></image>
|
||
</view>
|
||
</view>
|
||
<view class="creator-info">
|
||
<text class="creator-title">TA们最近为你点过赞</text>
|
||
<view class="creator-card">
|
||
<text class="creator-tip">点击查看</text>
|
||
<image class="creator-preview" src="/static/rank/activity-support-icon/tubiao.png"
|
||
mode="aspectFill"></image>
|
||
</view>
|
||
</view> -->
|
||
<!-- </view> -->
|
||
|
||
<!-- 完整链上哈希显示遮罩层 -->
|
||
<view v-if="showTxHash" class="txhash-mask" @tap="showTxHash = false">
|
||
<view class="txhash-popup" @tap.stop>
|
||
<view class="txhash-popup-header">
|
||
<text class="txhash-popup-title">链上哈希</text>
|
||
<view class="txhash-popup-close" @tap="showTxHash = false">
|
||
<image class="close-icon" src="/static/icon/hide.png" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
<text class="txhash-popup-content" selectable @longpress="copyHash">{{ displayTxHash }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 链上数据 -->
|
||
<view class="chain-section">
|
||
<image class="chain-logo" src="/static/logo/APPLOGO.png" mode="aspectFit"></image>
|
||
<view class="chain-left">
|
||
<view class="chain-row">
|
||
<text class="chain-label">数根名称</text>
|
||
<view class="chain-value-wrap">
|
||
<text class="chain-value">{{ assetData.name || '未知' }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="chain-row">
|
||
<text class="chain-label">数根发行方</text>
|
||
<view class="chain-value-wrap">
|
||
<text class="chain-value">TOPFANS</text>
|
||
</view>
|
||
</view>
|
||
<view class="chain-row">
|
||
<text class="chain-label">区块链编号</text>
|
||
<view class="chain-value-wrap">
|
||
<text class="chain-value">{{ showBlockNumber ? (assetData.block_number || '未知') :
|
||
hiddenBlockNumber }}</text>
|
||
<view class="toggle-btn" @tap="showBlockNumber = !showBlockNumber">
|
||
<image class="toggle-icon" :src="showBlockNumber ? '/static/icon/show.png' : '/static/icon/hide.png'" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="chain-row">
|
||
<text class="chain-label">链上哈希</text>
|
||
<view class="chain-value-wrap">
|
||
<text class="chain-value chain-hash" @longpress="copyHash">{{ showTxHash ? displayTxHash
|
||
: hiddenTxHash }}</text>
|
||
<view class="toggle-btn" @tap="showTxHash = !showTxHash">
|
||
<image class="toggle-icon" :src="showTxHash ? '/static/icon/show.png' : '/static/icon/hide.png'" mode="aspectFit"></image>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 点赞用户弹窗 -->
|
||
<LikeUsersModal :visible="showLikeUsersModal" :tabs="['今日', '历史']" @close="showLikeUsersModal = false"
|
||
@tab-change="handleLikeUsersTabChange">
|
||
<template #content>
|
||
<view class="like-users-list">
|
||
<view v-for="(user, index) in displayedLikeUsers" :key="index" class="like-user-item">
|
||
<image class="like-user-avatar" :src="user.avatar" mode="aspectFill"></image>
|
||
<view class="like-user-info">
|
||
<text class="like-user-name">{{ user.nickname }}</text>
|
||
</view>
|
||
<!-- 拜访按钮 -->
|
||
<view class="visit-button" @tap="handleVisit">
|
||
<image class="visit-icon" src="/static/square/dianjibaifang.png" mode="aspectFit" lazy-load>
|
||
</image>
|
||
<text class="visit-text">点击拜访</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</LikeUsersModal>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onUnmounted } from 'vue';
|
||
import { onLoad, onShow, onHide } from '@dcloudio/uni-app';
|
||
import { getAssetDetailApi, getMintOrderDetailApi, likeAssetApi, unlikeAssetApi } from '@/utils/api.js';
|
||
import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js';
|
||
import LikeUsersModal from '@/pages/components/LikeUsersModal.vue';
|
||
import LenticularCard from '@/components/lenticular/LenticularCard.vue';
|
||
import { useLenticularCraftTiltPreview } from '@/composables/useLenticularCraftTiltPreview.js';
|
||
import {
|
||
buildLenticularLayersTwo,
|
||
buildLenticularLayers,
|
||
LENTICULAR_STUDIO_STORAGE_KEY,
|
||
CRAFT_LENTICULAR_CN,
|
||
CRAFT_LASER_CARD_CN,
|
||
CRAFT_TAG_LENTICULAR,
|
||
} from '@/utils/castloveMintForm.js';
|
||
import {
|
||
CASTLOVE_FORM_KEY,
|
||
CRAFT_SELECTED_IMAGE_KEY,
|
||
STUDIO_LENTICULAR,
|
||
STUDIO_LASER,
|
||
} from '@/utils/castloveGenerationFlow.js';
|
||
import { submitCraftMintFromPath } from '@/utils/craftMintSubmit.js';
|
||
import { composeStickers, relationsToStickers } from '@/utils/sticker-compositor.js';
|
||
// 页面参数
|
||
const assetIdParam = ref('');
|
||
const orderIdParam = ref('');
|
||
const fromParam = ref('');
|
||
const studioKindParam = ref('');
|
||
const lastLoadKey = ref('');
|
||
|
||
const craftConfirmMode = ref(false);
|
||
const craftFormData = ref({});
|
||
const craftCoverUrl = ref('');
|
||
const craftMinting = ref(false);
|
||
const lenticularLayers = ref([]);
|
||
const {
|
||
layerTransforms,
|
||
simulate,
|
||
gyroSourceLabel,
|
||
scheduleTiltStart,
|
||
stopTiltPreview,
|
||
} = useLenticularCraftTiltPreview(lenticularLayers);
|
||
|
||
const isLenticularAsset = computed(() => {
|
||
if (craftConfirmMode.value) return false
|
||
const tags = assetData.value?.tags
|
||
if (Array.isArray(tags) && tags.includes(CRAFT_TAG_LENTICULAR)) return true
|
||
return false
|
||
})
|
||
|
||
const isLenticularDetail = ref(false)
|
||
|
||
const isCraftLenticular = computed(() => studioKindParam.value === STUDIO_LENTICULAR);
|
||
const craftCategoryLabel = computed(() => {
|
||
if (isCraftLenticular.value) return CRAFT_LENTICULAR_CN;
|
||
if (studioKindParam.value === STUDIO_LASER) return CRAFT_LASER_CARD_CN;
|
||
return craftFormData.value?.typeName || '星卡';
|
||
});
|
||
const craftAssetName = computed(() => craftFormData.value?.name || craftCategoryLabel.value);
|
||
const craftCreatorName = computed(() => '我');
|
||
const craftEarningsHint = computed(() => '21/时');
|
||
const craftMintDate = computed(() => {
|
||
const d = new Date();
|
||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||
const dd = String(d.getDate()).padStart(2, '0');
|
||
return `${d.getFullYear()}.${mm}.${dd}`;
|
||
});
|
||
const craftBlockPlaceholder = computed(() => '铸造后生成');
|
||
const craftHashPlaceholder = computed(() => '铸造后生成');
|
||
|
||
// 数据状态
|
||
const loading = ref(true);
|
||
const loadError = ref('');
|
||
const assetData = ref({});
|
||
const coverUrl = ref('');
|
||
const isLiked = ref(false);
|
||
const likeCount = ref(0);
|
||
const liking = ref(false);
|
||
|
||
// 当前用户头像
|
||
const userAvatarUrl = ref('');
|
||
|
||
// 加载当前用户信息
|
||
const loadCurrentUser = () => {
|
||
try {
|
||
const userStr = uni.getStorageSync('user');
|
||
if (userStr) {
|
||
const userInfo = JSON.parse(userStr);
|
||
userAvatarUrl.value = userInfo?.avatar_url || '';
|
||
}
|
||
} catch (e) {
|
||
console.error('解析用户信息失败:', e);
|
||
}
|
||
};
|
||
|
||
// 点赞用户头像数据(最多6个,按椭圆排列,每个头像有独立的偏移量配置)
|
||
// avatar: 头像地址, ellipseX: 椭圆X轴坐标, ellipseY: 椭圆Y轴坐标, size: 缩放比例
|
||
const likedUsers = ref([
|
||
{ avatar: '/static/sucai/image-01.png', ellipseX: -40, ellipseY: -40, size: 0.85 },
|
||
{ avatar: '/static/sucai/image-02.png', ellipseX: 40, ellipseY: -40, size: 0.85 },
|
||
{ avatar: '/static/sucai/image-03.png', ellipseX: -96, ellipseY: 0, size: 0.9 },
|
||
{ avatar: '/static/sucai/image-04.png', ellipseX: 120, ellipseY: -40, size: 0.9 },
|
||
{ avatar: '/static/sucai/image-05.png', ellipseX: 64, ellipseY: 32 },
|
||
{ avatar: '/static/sucai/image-06.png', ellipseX: -16, ellipseY: 32, size: 1.15 }
|
||
]);
|
||
|
||
// ---- 贴纸相关 ----
|
||
const activeStickers = ref([]);
|
||
const compositingStickers = ref(false);
|
||
|
||
function getStickerStyle(sticker) {
|
||
const ox = (sticker.pos_x != null ? sticker.pos_x : 0.5) * 100
|
||
const oy = (sticker.pos_y != null ? sticker.pos_y : 0.5) * 100
|
||
const rot = sticker.rotation != null ? sticker.rotation : 0
|
||
const scX = sticker.scale_x != null ? sticker.scale_x : 1
|
||
const scY = sticker.scale_y != null ? sticker.scale_y : 1
|
||
const op = sticker.opacity != null ? sticker.opacity : 1
|
||
return {
|
||
left: `${ox}%`,
|
||
top: `${oy}%`,
|
||
transform: `translate(-50%, -50%) rotate(${rot}deg) scale(${scX}, ${scY})`,
|
||
opacity: op,
|
||
}
|
||
}
|
||
|
||
async function loadStickersForAsset(materialRelations) {
|
||
if (!materialRelations || !materialRelations.length) {
|
||
activeStickers.value = []
|
||
return
|
||
}
|
||
const stickers = relationsToStickers(materialRelations)
|
||
const withIds = stickers.map((s, i) => ({ ...s, id: `sticker_${i}` }))
|
||
activeStickers.value = withIds
|
||
}
|
||
|
||
async function exportCompositeImage() {
|
||
if (!coverUrl.value || !activeStickers.value.length) return null
|
||
compositingStickers.value = true
|
||
try {
|
||
const result = await composeStickers({
|
||
baseImageSrc: coverUrl.value,
|
||
stickers: activeStickers.value,
|
||
exportW: 450,
|
||
exportH: 600,
|
||
canvasId: 'stickerCompositCanvas',
|
||
})
|
||
return result
|
||
} catch (e) {
|
||
console.error('[asset-detail] sticker compose failed:', e)
|
||
return null
|
||
} finally {
|
||
compositingStickers.value = false
|
||
}
|
||
}
|
||
|
||
// 弹窗相关
|
||
const showLikeUsersModal = ref(false);
|
||
const likeUsersActiveTab = ref(0);
|
||
|
||
// 今日/历史点赞用户数据(模拟)
|
||
const todayLikeUsers = ref([
|
||
{ userId: '1', nickname: '用户A', avatar: '/static/sucai/image-01.png', time: '10分钟前' },
|
||
{ userId: '2', nickname: '用户B', avatar: '/static/sucai/image-02.png', time: '30分钟前' },
|
||
{ userId: '3', nickname: '用户C', avatar: '/static/sucai/image-03.png', time: '1小时前' },
|
||
]);
|
||
const historyLikeUsers = ref([
|
||
{ userId: '4', nickname: '用户D', avatar: '/static/sucai/image-04.png', time: '昨天' },
|
||
{ userId: '5', nickname: '用户E', avatar: '/static/sucai/image-05.png', time: '3天前' },
|
||
]);
|
||
|
||
const displayedLikeUsers = computed(() => {
|
||
return likeUsersActiveTab.value === 0 ? todayLikeUsers.value : historyLikeUsers.value;
|
||
});
|
||
|
||
const handleLikeUsersTabChange = ({ tabIndex }) => {
|
||
likeUsersActiveTab.value = tabIndex;
|
||
};
|
||
|
||
// 拜访按钮
|
||
const handleVisit = () => {
|
||
uni.navigateTo({
|
||
url: `/pages/profile/hisWorks?userId=${user.userId}&nickname=${encodeURIComponent(user.nickname)}`
|
||
});
|
||
};
|
||
|
||
// 区块链编号和交易哈希的显示切换
|
||
const showBlockNumber = ref(false);
|
||
const showTxHash = ref(false);
|
||
|
||
// 倒计时
|
||
let countdownTimer = null;
|
||
|
||
// 倒计时状态
|
||
const countdowns = ref({});
|
||
|
||
// 计算剩余时间
|
||
const calculateRemainingTime = (item) => {
|
||
if (item.remainSeconds !== undefined) {
|
||
if (item.remainSeconds <= 0) {
|
||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||
}
|
||
const hours = Math.floor(item.remainSeconds / 3600);
|
||
const minutes = Math.floor((item.remainSeconds % 3600) / 60);
|
||
const seconds = Math.floor(item.remainSeconds % 60);
|
||
return { hours, minutes, seconds, expired: false };
|
||
}
|
||
if (item.exhibition_expire_at) {
|
||
const now = Date.now();
|
||
const expireTime = new Date(item.exhibition_expire_at).getTime();
|
||
const remaining = expireTime - now;
|
||
if (remaining <= 0) {
|
||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||
}
|
||
const hours = Math.floor(remaining / (60 * 60 * 1000));
|
||
const minutes = Math.floor((remaining % (60 * 60 * 1000)) / (60 * 1000));
|
||
const seconds = Math.floor((remaining % (60 * 1000)) / 1000);
|
||
return { hours, minutes, seconds, expired: false };
|
||
}
|
||
return { hours: 0, minutes: 0, seconds: 0, expired: true };
|
||
};
|
||
|
||
// 更新倒计时
|
||
const updateCountdowns = () => {
|
||
countdowns.value['currentAsset'] = calculateRemainingTime({ exhibition_expire_at: assetData.value.exhibition_expire_at });
|
||
};
|
||
|
||
const countdownText = computed(() => {
|
||
const countdown = countdowns.value['currentAsset'];
|
||
if (!countdown || countdown.expired) return '';
|
||
const h = String(countdown.hours).padStart(2, '0');
|
||
const m = String(countdown.minutes).padStart(2, '0');
|
||
const s = String(countdown.seconds).padStart(2, '0');
|
||
return `${h}:${m}:${s}`;
|
||
});
|
||
|
||
// 格式化获取时间(字段名 created_at,来自 getAssetDetailApi 响应)
|
||
const formattedAcquireTime = computed(() => {
|
||
const raw = assetData.value.created_at || '';
|
||
if (!raw) return '未知';
|
||
const d = new Date(raw);
|
||
if (isNaN(d.getTime())) return raw;
|
||
const yyyy = d.getFullYear();
|
||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||
const dd = String(d.getDate()).padStart(2, '0');
|
||
return `${yyyy}.${mm}.${dd}`;
|
||
});
|
||
|
||
// 截断交易哈希
|
||
const displayTxHash = computed(() => {
|
||
return assetData.value.tx_hash || '未知';
|
||
});
|
||
|
||
// 隐藏交易哈希(保留前6位 + 6个*)
|
||
const hiddenTxHash = computed(() => {
|
||
const hash = assetData.value.tx_hash;
|
||
if (!hash) return '******';
|
||
if (hash.length <= 6) return hash + '******';
|
||
return `${hash.substring(0, 6)}******`;
|
||
});
|
||
|
||
// 隐藏区块链编号(保留前6位 + 6个*)
|
||
const hiddenBlockNumber = computed(() => {
|
||
const num = assetData.value.block_number;
|
||
if (!num) return '******';
|
||
const str = String(num);
|
||
if (str.length <= 6) return str + '******';
|
||
return `${str.substring(0, 6)}******`;
|
||
});
|
||
|
||
// 复制完整哈希
|
||
const copyHash = () => {
|
||
const hash = assetData.value.tx_hash;
|
||
if (!hash) return;
|
||
uni.setClipboardData({
|
||
data: hash,
|
||
success: () => uni.showToast({ title: '已复制', icon: 'success', duration: 1500 })
|
||
});
|
||
};
|
||
|
||
// 加载数据
|
||
const loadData = async () => {
|
||
console.log('loadData 开始执行');
|
||
loading.value = true;
|
||
loadError.value = '';
|
||
|
||
try {
|
||
let assetId = assetIdParam.value;
|
||
console.log('assetIdParam:', assetIdParam.value, 'orderIdParam:', orderIdParam.value);
|
||
|
||
if (!assetId && orderIdParam.value) {
|
||
console.log('通过 order_id 获取资产信息');
|
||
const mintRes = await getMintOrderDetailApi(orderIdParam.value);
|
||
console.log('getMintOrderDetailApi 响应:', mintRes);
|
||
if (mintRes.code === 200 && mintRes.data?.asset?.asset_id) {
|
||
assetId = mintRes.data.asset.asset_id;
|
||
console.log('获取到 assetId:', assetId);
|
||
} else {
|
||
throw new Error('获取铸造订单详情失败');
|
||
}
|
||
}
|
||
|
||
if (!assetId) throw new Error('藏品信息不完整');
|
||
|
||
console.log('调用 getAssetDetailApi,assetId:', assetId);
|
||
const res = await getAssetDetailApi(assetId);
|
||
console.log('getAssetDetailApi 响应:', res);
|
||
if (res.code === 200 && res.data?.asset) {
|
||
const asset = res.data.asset;
|
||
assetData.value = asset;
|
||
isLiked.value = res.data.asset.is_liked || res.data.is_liked || false;
|
||
likeCount.value = asset.like_count || 0;
|
||
|
||
// 加载贴纸素材
|
||
if (asset.material_relations || asset.materials) {
|
||
loadStickersForAsset(asset.material_relations || asset.materials)
|
||
}
|
||
|
||
if (asset.exhibition_expire_at) {
|
||
startCountdown();
|
||
}
|
||
console.log(res.data)
|
||
// 异步加载封面图片,不阻塞页面渲染
|
||
console.log('开始加载封面图片:', asset.cover_url);
|
||
getAssetCoverRealUrl(asset.cover_url).then(url => {
|
||
console.log('封面图片加载成功:', url);
|
||
coverUrl.value = url;
|
||
if (isLenticularAsset.value) {
|
||
const materialUrl = asset.material_url || url
|
||
let subjectUrl = url
|
||
let bgUrl = ''
|
||
try {
|
||
const parsed = JSON.parse(materialUrl)
|
||
if (parsed.main) subjectUrl = parsed.main
|
||
if (parsed.bg) bgUrl = parsed.bg
|
||
} catch (e) { /* 非 JSON 格式,按单 URL 处理 */ }
|
||
lenticularLayers.value = bgUrl
|
||
? buildLenticularLayersTwo(bgUrl, subjectUrl)
|
||
: buildLenticularLayers(subjectUrl)
|
||
isLenticularDetail.value = true
|
||
scheduleTiltStart()
|
||
}
|
||
}).catch(err => {
|
||
console.error('加载封面图片失败:', err);
|
||
coverUrl.value = ''; // 失败时不显示默认图片
|
||
});
|
||
console.log('loadData 执行成功');
|
||
} else {
|
||
throw new Error(res.message || '获取藏品详情失败');
|
||
}
|
||
} catch (err) {
|
||
console.error('loadData 出错:', err);
|
||
loadError.value = err.message || '加载失败,请重试';
|
||
} finally {
|
||
loading.value = false;
|
||
console.log('loadData 执行完成,loading:', loading.value);
|
||
}
|
||
};
|
||
|
||
// 倒计时
|
||
const startCountdown = () => {
|
||
updateCountdowns(); // 立即显示,不等待1秒
|
||
if (countdownTimer) clearInterval(countdownTimer);
|
||
countdownTimer = setInterval(() => {
|
||
updateCountdowns();
|
||
}, 1000);
|
||
};
|
||
|
||
// 点赞
|
||
const handleLike = async () => {
|
||
if (liking.value || !assetData.value.asset_id) return;
|
||
liking.value = true;
|
||
try {
|
||
// if (isLiked.value) {
|
||
// await unlikeAssetApi(assetData.value.asset_id);
|
||
// isLiked.value = false;
|
||
// likeCount.value = Math.max(0, likeCount.value - 1);
|
||
// } else {
|
||
await likeAssetApi(assetData.value.asset_id);
|
||
isLiked.value = true;
|
||
likeCount.value += 1;
|
||
// }
|
||
// 通知展馆页面更新点赞数
|
||
uni.$emit('assetLikeChanged', {
|
||
asset_id: assetData.value.asset_id,
|
||
like_count: likeCount.value,
|
||
is_liked: isLiked.value
|
||
});
|
||
} catch (err) {
|
||
const errMsg = err?.message || '';
|
||
const errCode = err?.code || err?.data?.code || '';
|
||
if (errMsg.includes('未在展示中')) {
|
||
uni.showToast({ title: '未展览无法点赞', icon: 'none', duration: 2000 });
|
||
} else {
|
||
uni.showToast({ title: '今日已点赞', icon: 'none', duration: 2000 });
|
||
}
|
||
} finally {
|
||
liking.value = false;
|
||
}
|
||
};
|
||
|
||
const loadCraftConfirm = () => {
|
||
loading.value = false;
|
||
loadError.value = '';
|
||
try {
|
||
const formStr = uni.getStorageSync(CASTLOVE_FORM_KEY);
|
||
if (formStr) {
|
||
craftFormData.value = JSON.parse(formStr);
|
||
}
|
||
const selected = uni.getStorageSync(CRAFT_SELECTED_IMAGE_KEY) || '';
|
||
craftCoverUrl.value = selected;
|
||
if (isCraftLenticular.value) {
|
||
const raw = uni.getStorageSync(LENTICULAR_STUDIO_STORAGE_KEY);
|
||
if (raw) {
|
||
const p = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||
lenticularLayers.value = buildLenticularLayersTwo(p.bgPath || '', p.subjectPath || '');
|
||
scheduleTiltStart();
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('[asset-detail] craft confirm', e);
|
||
loadError.value = '加载确认页失败';
|
||
}
|
||
};
|
||
|
||
const handleCraftMint = async () => {
|
||
if (craftMinting.value) return;
|
||
const imagePath =
|
||
isCraftLenticular.value
|
||
? lenticularLayers.value.find((l) => l.id === 'mid')?.src || craftCoverUrl.value
|
||
: craftCoverUrl.value;
|
||
if (!imagePath) {
|
||
uni.showToast({ title: '缺少作品图', icon: 'none' });
|
||
return;
|
||
}
|
||
const bgImagePath = isCraftLenticular.value
|
||
? lenticularLayers.value.find((l) => l.id === 'base')?.src || ''
|
||
: undefined;
|
||
craftMinting.value = true;
|
||
uni.showLoading({ title: '铸造中…', mask: true });
|
||
try {
|
||
await submitCraftMintFromPath({ imagePath, bgImagePath, formData: craftFormData.value });
|
||
uni.hideLoading();
|
||
uni.navigateTo({ url: '/pages/castlove/success' });
|
||
} catch (e) {
|
||
uni.hideLoading();
|
||
uni.showToast({ title: e.message || '铸造失败', icon: 'none' });
|
||
} finally {
|
||
craftMinting.value = false;
|
||
}
|
||
};
|
||
|
||
// 返回逻辑
|
||
const handleBack = () => {
|
||
if (fromParam.value === 'castlove') {
|
||
uni.reLaunch({ url: '/pages/castlove/mall' });
|
||
} else {
|
||
uni.navigateBack();
|
||
}
|
||
};
|
||
|
||
onLoad((options) => {
|
||
console.log('onLoad 触发,参数:', options);
|
||
assetIdParam.value = options?.asset_id || '';
|
||
orderIdParam.value = options?.order_id || '';
|
||
fromParam.value = options?.from || '';
|
||
loadCurrentUser();
|
||
});
|
||
|
||
onShow(() => {
|
||
console.log('onShow 触发');
|
||
const currentKey = `${assetIdParam.value}_${orderIdParam.value}`;
|
||
console.log('当前key:', currentKey, '上次key:', lastLoadKey.value);
|
||
|
||
// 如果参数变化了,或者是首次加载,则重新加载数据
|
||
if ((assetIdParam.value || orderIdParam.value) && currentKey !== lastLoadKey.value) {
|
||
lastLoadKey.value = currentKey;
|
||
loadData();
|
||
}
|
||
});
|
||
|
||
onHide(() => {
|
||
if (craftConfirmMode.value && isCraftLenticular.value) {
|
||
stopTiltPreview();
|
||
}
|
||
if (isLenticularDetail.value) {
|
||
stopTiltPreview()
|
||
}
|
||
});
|
||
|
||
onUnmounted(() => {
|
||
if (countdownTimer) clearInterval(countdownTimer);
|
||
stopTiltPreview();
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.detail-container {
|
||
position: relative;
|
||
width: 100vw;
|
||
min-height: 100vh;
|
||
overflow: hidden;
|
||
background-color: #0d0820;
|
||
}
|
||
|
||
.background-image {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
}
|
||
|
||
/* Header */
|
||
.header-bar {
|
||
position: fixed;
|
||
top: 32rpx;
|
||
left: 32rpx;
|
||
z-index: 100;
|
||
}
|
||
|
||
.back-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.back-btn:active {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.back-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
}
|
||
|
||
/* 加载/错误 */
|
||
.loading-wrapper,
|
||
.error-wrapper {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
gap: 30rpx;
|
||
}
|
||
|
||
/* 旋转光环 */
|
||
.loading-ring-outer {
|
||
position: relative;
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.loading-ring {
|
||
width: 160rpx;
|
||
height: 160rpx;
|
||
border-radius: 50%;
|
||
border: 6rpx solid transparent;
|
||
border-top-color: #F0E4B1;
|
||
border-right-color: #F08399;
|
||
border-bottom-color: #B94E73;
|
||
border-left-color: transparent;
|
||
box-shadow:
|
||
0 0 24rpx rgba(240, 131, 153, 0.5),
|
||
inset 0 0 16rpx rgba(183, 78, 115, 0.2);
|
||
animation: ring-spin 1.2s linear infinite;
|
||
}
|
||
|
||
@keyframes ring-spin {
|
||
0% {
|
||
transform: rotate(0deg);
|
||
}
|
||
|
||
100% {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
/* 三颗脉冲粒子 */
|
||
.loading-dots {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.loading-dot {
|
||
width: 20rpx;
|
||
height: 20rpx;
|
||
border-radius: 50%;
|
||
animation: dot-pulse 1.4s ease-in-out infinite;
|
||
}
|
||
|
||
.dot-1 {
|
||
background: #F0E4B1;
|
||
box-shadow: 0 0 12rpx rgba(240, 228, 177, 0.8);
|
||
animation-delay: 0s;
|
||
}
|
||
|
||
.dot-2 {
|
||
background: #F08399;
|
||
box-shadow: 0 0 12rpx rgba(240, 131, 153, 0.8);
|
||
animation-delay: 0.2s;
|
||
}
|
||
|
||
.dot-3 {
|
||
background: #834B9E;
|
||
box-shadow: 0 0 12rpx rgba(131, 75, 158, 0.8);
|
||
animation-delay: 0.4s;
|
||
}
|
||
|
||
@keyframes dot-pulse {
|
||
|
||
0%,
|
||
100% {
|
||
transform: scale(0.6);
|
||
opacity: 0.4;
|
||
}
|
||
|
||
50% {
|
||
transform: scale(1.3);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* 加载文字 */
|
||
.loading-text {
|
||
font-size: 28rpx;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
font-family: 'yt', sans-serif;
|
||
letter-spacing: 4rpx;
|
||
animation: text-breathe 2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes text-breathe {
|
||
|
||
0%,
|
||
100% {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
50% {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.error-text {
|
||
font-size: 32rpx;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
font-family: 'yt', sans-serif;
|
||
}
|
||
|
||
.retry-btn {
|
||
padding: 16rpx 48rpx;
|
||
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
||
color: #fff;
|
||
border-radius: 40rpx;
|
||
font-size: 28rpx;
|
||
border: none;
|
||
}
|
||
|
||
.retry-btn::after {
|
||
border: none;
|
||
}
|
||
|
||
/* 滚动区 */
|
||
.content-scroll {
|
||
position: relative;
|
||
z-index: 1;
|
||
height: 100vh;
|
||
}
|
||
|
||
.content-wrapper {
|
||
min-height: 90%;
|
||
padding: 104rpx 40rpx 80rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 0;
|
||
}
|
||
|
||
/* 卡片区域 */
|
||
.card-section {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.card-wrapper {
|
||
position: relative;
|
||
width: 352rpx;
|
||
height: 520rpx;
|
||
margin-bottom: 32rpx;
|
||
}
|
||
|
||
.card-wrapper--lenticular {
|
||
width: 520rpx;
|
||
height: 680rpx;
|
||
}
|
||
|
||
.detail-lenticular-slot {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
width: 78%;
|
||
height: 82%;
|
||
transform: translate(-50%, -50%) rotate(-10deg);
|
||
z-index: 2;
|
||
}
|
||
|
||
.detail-lenticular-card {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.card-image {
|
||
width: 88%;
|
||
height: 96%;
|
||
left: 6%;
|
||
top: 1.5%;
|
||
border-radius: 48rpx;
|
||
transform-origin: center center;
|
||
position: absolute;
|
||
z-index: 3;
|
||
overflow: hidden;
|
||
transform: rotate(-10deg);
|
||
}
|
||
|
||
.card-frame {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 2;
|
||
transform: rotate(-10deg);
|
||
|
||
}
|
||
|
||
.card-sticker {
|
||
position: absolute;
|
||
z-index: 5;
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
pointer-events: none;
|
||
transform-origin: center center;
|
||
filter: drop-shadow(0 2rpx 6rpx rgba(0, 0, 0, 0.35));
|
||
}
|
||
|
||
.export-canvas {
|
||
position: fixed;
|
||
left: -9999px;
|
||
top: -9999px;
|
||
}
|
||
|
||
.card-meta-row {
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
/* 点赞 */
|
||
.like-area {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
background: linear-gradient(to bottom right,
|
||
#F0E4B1 0%,
|
||
#F08399 50%,
|
||
#B94E73 100%);
|
||
border-radius: 999rpx;
|
||
box-shadow:
|
||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
|
||
padding: 10rpx 28rpx;
|
||
}
|
||
|
||
.like-area:active {
|
||
opacity: 0.75;
|
||
}
|
||
|
||
.like-icon,
|
||
.heart-icon,
|
||
.crystal-icon {
|
||
width: 44rpx;
|
||
height: 44rpx;
|
||
}
|
||
|
||
.like-num {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
font-family: 'yt', sans-serif;
|
||
font-variant-numeric: tabular-nums;
|
||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
|
||
}
|
||
|
||
/* 收益 */
|
||
.earnings-area {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
background: linear-gradient(to bottom right,
|
||
#F0E4B1 0%,
|
||
#F08399 50%,
|
||
#B94E73 100%);
|
||
border-radius: 999rpx;
|
||
box-shadow:
|
||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
|
||
padding: 10rpx 28rpx;
|
||
}
|
||
|
||
.earnings-text {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
font-family: 'yt', sans-serif;
|
||
font-variant-numeric: tabular-nums;
|
||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
|
||
}
|
||
|
||
/* 倒计时 */
|
||
.countdown-area {
|
||
display: flex;
|
||
align-items: center;
|
||
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
|
||
border-radius: 999rpx;
|
||
padding: 10rpx 28rpx;
|
||
}
|
||
|
||
.countdown-pill {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
font-family: 'yt', sans-serif;
|
||
font-variant-numeric: tabular-nums;
|
||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
|
||
}
|
||
|
||
.countdown-val {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
font-family: 'yt', sans-serif;
|
||
font-variant-numeric: tabular-nums;
|
||
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
|
||
}
|
||
|
||
/* 信息行(持有人 + 铸造时间) */
|
||
.info-row {
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: stretch;
|
||
justify-content: center;
|
||
gap: 20rpx;
|
||
padding: 24rpx 32rpx;
|
||
border-radius: 24rpx;
|
||
margin-bottom: 40rpx;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.info-col {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.info-item {
|
||
display: flex;
|
||
gap: 8rpx;
|
||
width: 416rpx;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 32rpx;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
font-family: 'yt', sans-serif;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #ffffff;
|
||
font-family: 'yt', sans-serif;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.7);
|
||
}
|
||
|
||
.info-avatar {
|
||
width: 48rpx;
|
||
height: 48rpx;
|
||
border-radius: 50%;
|
||
margin-left: 8rpx;
|
||
}
|
||
|
||
.info-nickname {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
border-radius: 24rpx;
|
||
}
|
||
|
||
/* 点赞用户头像区域 */
|
||
.liked-users-area {
|
||
position: relative;
|
||
flex: 1;
|
||
height: 184rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.liked-user-avatar {
|
||
position: absolute;
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
border: 3rpx solid #F0E4B1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.liked-user-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
/* 创作者信息模块 */
|
||
.creator-section {
|
||
width: calc(100% - 80rpx);
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 24rpx;
|
||
padding: 16rpx;
|
||
background: url('/static/rank/activity-support-icon/beijingkuang.png') no-repeat center center;
|
||
background-size: 105% 120%;
|
||
border-radius: 24rpx;
|
||
margin-bottom: 64rpx;
|
||
}
|
||
|
||
.creator-avatar {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.creator-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
}
|
||
|
||
.creator-title {
|
||
font-size: 28rpx;
|
||
color: #fff;
|
||
font-family: 'yt', sans-serif;
|
||
}
|
||
|
||
.creator-card {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-direction: column;
|
||
gap: 16rpx;
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
.creator-tip {
|
||
font-size: 32rpx;
|
||
color: #ffcc00;
|
||
font-family: 'yt', sans-serif;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.7);
|
||
}
|
||
|
||
.creator-preview {
|
||
width: 112rpx;
|
||
height: 80rpx;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
/* 链上数据 */
|
||
.chain-section {
|
||
width: calc(100% - 80rpx);
|
||
background: url('/static/rank/activity-support-icon/beijingkuang.png') no-repeat center center;
|
||
background-size: 105% 120%;
|
||
border-radius: 24rpx;
|
||
padding: 30rpx 16rpx;
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.chain-left {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.chain-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.chain-logo {
|
||
width: 156rpx;
|
||
height: 156rpx;
|
||
}
|
||
|
||
.chain-label {
|
||
font-size: 28rpx;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
font-family: 'yt', sans-serif;
|
||
flex-shrink: 0;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.chain-value {
|
||
font-size: 28rpx;
|
||
color: #ffffff;
|
||
font-family: 'yt', sans-serif;
|
||
flex: 1;
|
||
word-break: break-all;
|
||
display: flex
|
||
}
|
||
|
||
.chain-value-wrap {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12rpx;
|
||
width: 240rpx;
|
||
}
|
||
|
||
.toggle-btn {
|
||
padding: 4rpx 8rpx;
|
||
}
|
||
|
||
.toggle-icon {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
}
|
||
|
||
.chain-hash {
|
||
color: rgba(255, 255, 255, 0.75);
|
||
}
|
||
|
||
/* 链上哈希遮罩层 */
|
||
.txhash-mask {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 24rpx;
|
||
z-index: 999;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0 64rpx;
|
||
|
||
}
|
||
|
||
.txhash-popup {
|
||
border-radius: 24rpx;
|
||
padding: 32rpx;
|
||
background: url('/static/rank/activity-support-icon/beijingkuang.png') no-repeat center center;
|
||
background-size: 105% 120%;
|
||
}
|
||
|
||
.txhash-popup-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.txhash-popup-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #fff;
|
||
font-family: 'yt', sans-serif;
|
||
}
|
||
|
||
.txhash-popup-close {
|
||
padding: 8rpx;
|
||
}
|
||
|
||
.close-icon {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
|
||
.txhash-popup-content {
|
||
display: block;
|
||
font-size: 26rpx;
|
||
color: #fff;
|
||
font-family: 'yt', sans-serif;
|
||
word-break: break-all;
|
||
line-height: 1.6;
|
||
background-color: rgba(0, 0, 0, 0.2);
|
||
padding: 20rpx;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
/* 点赞用户列表 */
|
||
.like-users-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 24rpx;
|
||
padding: 16rpx;
|
||
}
|
||
|
||
.like-user-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 0 16rpx;
|
||
background: linear-gradient(to bottom right,
|
||
#F0E4B1 0%,
|
||
#F08399 50%,
|
||
#B94E73 100%);
|
||
border-radius: 999rpx;
|
||
box-shadow:
|
||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
|
||
}
|
||
|
||
.like-user-avatar {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.like-user-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin: 0 24rpx;
|
||
}
|
||
|
||
.like-user-name {
|
||
font-size: 28rpx;
|
||
color: #fff;
|
||
font-family: 'yt', sans-serif;
|
||
}
|
||
|
||
.like-user-time {
|
||
font-size: 24rpx;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
font-family: 'yt', sans-serif;
|
||
}
|
||
|
||
/* 拜访按钮 - 只显示图标 */
|
||
.visit-button {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
padding: 8rpx;
|
||
flex-direction: column;
|
||
}
|
||
|
||
|
||
.visit-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
margin-bottom: 8rpx;
|
||
/* transform: scale(1.2); */
|
||
}
|
||
.visit-text{
|
||
color: #FFFFFF;
|
||
font-size: 16rpx;
|
||
}
|
||
|
||
</style>
|