feat: 修改square组件
This commit is contained in:
parent
b14cc119b4
commit
b00f7dabf0
@ -1,15 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="content-tabs">
|
<view class="content-tabs">
|
||||||
<view v-for="(tab, index) in tabs" :key="tab.key" class="tab-item" :class="{ active: modelValue === tab.key }"
|
<view
|
||||||
:data-key="tab.key" @click="handleTabClick">
|
v-for="(tab, index) in tabs"
|
||||||
|
:key="tab.key"
|
||||||
|
class="tab-item"
|
||||||
|
:class="{ active: modelValue === tab.key }"
|
||||||
|
:data-key="tab.key"
|
||||||
|
@click="handleTabClick"
|
||||||
|
>
|
||||||
<!-- 背景图片 -->
|
<!-- 背景图片 -->
|
||||||
<!-- <image class="tab-bg" :class="{ 'tab-bg-inactive': modelValue !== tab.key }"
|
<!-- <image class="tab-bg" :class="{ 'tab-bg-inactive': modelValue !== tab.key }"
|
||||||
src="/static/nft/dingbutubiao_liang.png" mode="scaleToFill" /> -->
|
src="/static/nft/dingbutubiao_liang.png" mode="scaleToFill" /> -->
|
||||||
<!-- 左侧图标,绝对浮动覆盖背景左侧色块,不影响文字布局 -->
|
<!-- 左侧图标,绝对浮动覆盖背景左侧色块,不影响文字布局 -->
|
||||||
<view class="tab-left" :class="{ 'tab-left-first': index === 2 }">
|
<view class="tab-left">
|
||||||
<text v-if="tab.emoji" class="tab-emoji">{{ tab.emoji }}</text>
|
<!-- <text v-if="tab.emoji" class="tab-emoji">{{ tab.emoji }}</text>
|
||||||
<image v-else class="tab-icon" :src="tab.icon" mode="aspectFill"
|
<image v-else class="tab-icon" :src="tab.icon" mode="aspectFill"
|
||||||
:style="{ width: tab.iconWidth + 'rpx', height: tab.iconHeight + 'rpx' }" />
|
:style="{ width: tab.iconWidth + 'rpx', height: tab.iconHeight + 'rpx' }" /> -->
|
||||||
<!-- 右侧文字 -->
|
<!-- 右侧文字 -->
|
||||||
<text class="tab-label">{{ tab.label }}</text>
|
<text class="tab-label">{{ tab.label }}</text>
|
||||||
</view>
|
</view>
|
||||||
@ -19,56 +25,75 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: String, default: 'hot' }
|
modelValue: { type: String, default: "hot" },
|
||||||
})
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
const handleTabClick = (e) => {
|
const handleTabClick = (e) => {
|
||||||
const key = e.currentTarget.dataset.key
|
const key = e.currentTarget.dataset.key;
|
||||||
console.log(key)
|
console.log(key);
|
||||||
emit('update:modelValue', key)
|
emit("update:modelValue", key);
|
||||||
}
|
};
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ key: 'displaying', label: '日榜', emoji: null, icon: '/static/square/1.png', iconWidth: 32, iconHeight: 40 },
|
{
|
||||||
// { key: 'week', label: '周榜', emoji: null, icon: '/static/square/1.png', iconWidth: 32, iconHeight: 40 },
|
key: "xinghe",
|
||||||
// { key: 'month', label: '月榜', emoji: null, icon: '/static/square/1.png', iconWidth: 32, iconHeight: 40 },
|
label: "星河",
|
||||||
]
|
emoji: null,
|
||||||
|
icon: "/static/square/1.png",
|
||||||
|
iconWidth: 32,
|
||||||
|
iconHeight: 40,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "xingbang",
|
||||||
|
label: "星榜",
|
||||||
|
emoji: null,
|
||||||
|
icon: "/static/square/1.png",
|
||||||
|
iconWidth: 32,
|
||||||
|
iconHeight: 40,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "guangchang",
|
||||||
|
label: "广场",
|
||||||
|
emoji: null,
|
||||||
|
icon: "/static/square/1.png",
|
||||||
|
iconWidth: 32,
|
||||||
|
iconHeight: 40,
|
||||||
|
},
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.content-tabs {
|
.content-tabs {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: unset;
|
left: 50%;
|
||||||
left: unset;
|
bottom: 0.25rem;
|
||||||
right: unset;
|
transform: translateX(-50%);
|
||||||
bottom: 16rpx;
|
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 8rpx 16rpx 0;
|
margin: 8rpx 16rpx;
|
||||||
padding: 0 4rpx;
|
padding: 0 8rpx;
|
||||||
height: 56rpx;
|
width: 336rpx;
|
||||||
background: transparent;
|
height: 64rpx;
|
||||||
|
border-radius: 16px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
background: #d9d9d91c;
|
||||||
|
box-shadow: 0px 4px 4px 0px #b3323240;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item {
|
.tab-item {
|
||||||
max-width: 134rpx;
|
width: 112rpx;
|
||||||
height: 42rpx;
|
height: 42rpx;
|
||||||
border-radius: 21rpx;
|
border-radius: 21rpx;
|
||||||
opacity: 0.78;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
background: linear-gradient(90deg, rgba(255, 222, 8, 0.2989) -17.54%, rgba(252, 100, 102, 0.61) 64.4%, rgba(244, 88, 104, 0.61) 116.67%);
|
|
||||||
box-shadow: 2px 2px 4px 0px #F2151578;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 背景图片铺满整个 tab */
|
/* 背景图片铺满整个 tab */
|
||||||
@ -92,7 +117,6 @@ const tabs = [
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 4rpx 30rpx 4rpx 20rpx ;
|
|
||||||
transition: transform 0.25s ease;
|
transition: transform 0.25s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +147,13 @@ const tabs = [
|
|||||||
margin-left: 14rpx;
|
margin-left: 14rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-item.active .tab-label {
|
.tab-item.active{
|
||||||
color: #ffffff;
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(255, 222, 8, 0.2989) -17.54%,
|
||||||
|
rgba(252, 100, 102, 0.61) 64.4%,
|
||||||
|
rgba(244, 88, 104, 0.61) 116.67%
|
||||||
|
);
|
||||||
|
box-shadow: 2px 2px 4px 0px #f2151578;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
v-for="(category, index) in categories"
|
v-for="(category, index) in categories"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="category-item"
|
class="category-item"
|
||||||
:class="{ active: category === category.value }"
|
:class="{ active: category.value === activeCategory }"
|
||||||
@click="handleCategoryChange(category.value)"
|
@click="handleCategoryChange(category.value)"
|
||||||
>
|
>
|
||||||
<text class="category-text">{{ category.label }}</text>
|
<text class="category-text">{{ category.label }}</text>
|
||||||
@ -110,26 +110,21 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
import { onShow } from "@dcloudio/uni-app";
|
import { onShow, onHide } from "@dcloudio/uni-app";
|
||||||
import { getInspirationFlowApi } from '@/utils/api.js'
|
import { getInspirationFlowApi } from '@/utils/api.js'
|
||||||
|
import { getAssetCoverRealUrl } from '@/utils/assetImageHelper.js'
|
||||||
|
|
||||||
const props = defineProps({
|
// 组件不接收任何 prop,所有数据/状态内部管理
|
||||||
screenWidth: { type: Number, default: 375 },
|
const emit = defineEmits(['cardClick', 'loaded'])
|
||||||
screenHeight: { type: Number, default: 812 },
|
|
||||||
bannerBottom: { type: Number, default: 200 },
|
|
||||||
useMockData: { type: Boolean, default: false },
|
|
||||||
category: { type: String, default: '' },
|
|
||||||
isActive: { type: Boolean, default: true },
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['cardClick', 'scroll', 'loaded', 'mainTabClick', 'categoryChange'])
|
|
||||||
|
|
||||||
|
// ========== 内部状态 ==========
|
||||||
const creationList = ref([])
|
const creationList = ref([])
|
||||||
const cursor = ref('')
|
const cursor = ref('')
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const noMore = ref(false)
|
const noMore = ref(false)
|
||||||
let isComponentMounted = false
|
const likingMap = ref({})
|
||||||
const likingMap = ref({});
|
const activeCategory = ref('hot') // 当前选中的分类
|
||||||
|
const isComponentActive = ref(true) // 组件是否处于激活态
|
||||||
|
|
||||||
// ========== 区域 A:主Tab 配置(内化) ==========
|
// ========== 区域 A:主Tab 配置(内化) ==========
|
||||||
const mainTabs = [
|
const mainTabs = [
|
||||||
@ -168,7 +163,7 @@ const categories = [
|
|||||||
{ label: '海报', value: 'poster' },
|
{ label: '海报', value: 'poster' },
|
||||||
]
|
]
|
||||||
|
|
||||||
// ========== 模板 ref(expose 给父组件 spotlight 系统 + 占位元素) ==========
|
// ========== 模板 ref(暴露给父组件 spotlight 系统 + 占位元素) ==========
|
||||||
const mainTabsRef = ref(null)
|
const mainTabsRef = ref(null)
|
||||||
const categoryRef = ref(null)
|
const categoryRef = ref(null)
|
||||||
|
|
||||||
@ -188,14 +183,19 @@ watch(isFixed, (val) => {
|
|||||||
}, 450)
|
}, 450)
|
||||||
})
|
})
|
||||||
|
|
||||||
// ========== 内部事件 ==========
|
// ========== 内部事件处理(不再冒泡给父组件) ==========
|
||||||
|
|
||||||
|
// 主Tab点击 - 内部直接跳转铸造页
|
||||||
const handleMainTabClick = (tab) => {
|
const handleMainTabClick = (tab) => {
|
||||||
emit('mainTabClick', tab)
|
uni.navigateTo({
|
||||||
|
url: `/pages/castlove/mall?type=${encodeURIComponent(tab.type)}`,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 分类切换 - 内部更新 activeCategory
|
||||||
const handleCategoryChange = (value) => {
|
const handleCategoryChange = (value) => {
|
||||||
if (props.category === value) return
|
if (activeCategory.value === value) return
|
||||||
emit('categoryChange', value)
|
activeCategory.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 父级调用的方法(滚动 → fixed 切换) ==========
|
// ========== 父级调用的方法(滚动 → fixed 切换) ==========
|
||||||
@ -224,7 +224,7 @@ function remeasure() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 原有:创作网格逻辑(保留) ==========
|
// ========== 创作网格逻辑(保留) ==========
|
||||||
const formatCount = (count) => {
|
const formatCount = (count) => {
|
||||||
if (!count) return '0'
|
if (!count) return '0'
|
||||||
if (count >= 10000) return (count / 10000).toFixed(1) + 'w'
|
if (count >= 10000) return (count / 10000).toFixed(1) + 'w'
|
||||||
@ -246,28 +246,40 @@ const handleCardClick = (item) => {
|
|||||||
emit('cardClick', item)
|
emit('cardClick', item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 把后端返回的 cover_url 转成真实可访问的 URL(OSS 预签名前端实现)
|
||||||
|
async function resolveItemImage(item) {
|
||||||
|
if (!item) return item
|
||||||
|
const cover = item.cover_url || item.cover_image || ''
|
||||||
|
if (cover) {
|
||||||
|
item.cover_image = await getAssetCoverRealUrl(cover)
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
const loadUsers = async () => {
|
const loadUsers = async () => {
|
||||||
if (!isComponentMounted) return Promise.resolve()
|
|
||||||
cursor.value = ''
|
cursor.value = ''
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
noMore.value = false
|
noMore.value = false
|
||||||
try {
|
try {
|
||||||
const res = await getInspirationFlowApi({ limit: 20, type: props.category, cursor: '' })
|
const res = await getInspirationFlowApi({ limit: 20, type: activeCategory.value, cursor: '' })
|
||||||
if (!isComponentMounted) return
|
|
||||||
if (res.code === 200 && res.data?.items && res.data.items.length > 0) {
|
if (res.code === 200 && res.data?.items && res.data.items.length > 0) {
|
||||||
const items = res.data.items
|
const items = res.data.items
|
||||||
cursor.value = res.data.cursor || ''
|
cursor.value = res.data.cursor || ''
|
||||||
creationList.value = items.map((item) => {
|
// 逐个把 cover_url 转成真实可访问的 URL
|
||||||
return {
|
creationList.value = await Promise.all(
|
||||||
id: item.asset_id,
|
items.map(async (item) => {
|
||||||
certificate_id: item.asset_id,
|
const resolved = await resolveItemImage({ ...item })
|
||||||
cover_image: item.cover_url || '',
|
return {
|
||||||
creator_avatar: item.owner_avatar || '',
|
id: item.asset_id,
|
||||||
creator_name: item.owner_nickname || item.name || '',
|
certificate_id: item.asset_id,
|
||||||
like_count: item.likes || item.like_count || 0,
|
cover_image: resolved.cover_image,
|
||||||
is_liked: item.is_liked || false,
|
creator_avatar: item.owner_avatar || '',
|
||||||
}
|
creator_name: item.owner_nickname || item.name || '',
|
||||||
})
|
like_count: item.likes || item.like_count || 0,
|
||||||
|
is_liked: item.is_liked || false,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
noMore.value = true
|
noMore.value = true
|
||||||
}
|
}
|
||||||
@ -280,25 +292,27 @@ const loadUsers = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadMore = async () => {
|
const loadMore = async () => {
|
||||||
if (!isComponentMounted || isLoading.value || noMore.value) return
|
if (isLoading.value || noMore.value) return
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await getInspirationFlowApi({ limit: 20, type: props.category, cursor: cursor.value })
|
const res = await getInspirationFlowApi({ limit: 20, type: activeCategory.value, cursor: cursor.value })
|
||||||
if (!isComponentMounted) return
|
|
||||||
if (res.code === 200 && res.data?.items && res.data.items.length > 0) {
|
if (res.code === 200 && res.data?.items && res.data.items.length > 0) {
|
||||||
const items = res.data.items
|
const items = res.data.items
|
||||||
cursor.value = res.data.cursor || ''
|
cursor.value = res.data.cursor || ''
|
||||||
const newItems = items.map((item) => {
|
const newItems = await Promise.all(
|
||||||
return {
|
items.map(async (item) => {
|
||||||
id: item.asset_id,
|
const resolved = await resolveItemImage({ ...item })
|
||||||
certificate_id: item.asset_id,
|
return {
|
||||||
cover_image: item.cover_url || '',
|
id: item.asset_id,
|
||||||
creator_avatar: item.owner_avatar || '',
|
certificate_id: item.asset_id,
|
||||||
creator_name: item.owner_nickname || item.name || '',
|
cover_image: resolved.cover_image,
|
||||||
like_count: item.likes || item.like_count || 0,
|
creator_avatar: item.owner_avatar || '',
|
||||||
is_liked: item.is_liked || false,
|
creator_name: item.owner_nickname || item.name || '',
|
||||||
}
|
like_count: item.likes || item.like_count || 0,
|
||||||
})
|
is_liked: item.is_liked || false,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
creationList.value = [...creationList.value, ...newItems]
|
creationList.value = [...creationList.value, ...newItems]
|
||||||
} else {
|
} else {
|
||||||
noMore.value = true
|
noMore.value = true
|
||||||
@ -311,25 +325,21 @@ const loadMore = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
// 分类变化 → 重新加载
|
||||||
() => props.category,
|
watch(activeCategory, () => {
|
||||||
() => {
|
loadUsers()
|
||||||
loadUsers()
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
// 激活态变化 → 首次激活时拉数据
|
||||||
() => props.isActive,
|
watch(isComponentActive, (active) => {
|
||||||
(active) => {
|
if (active && creationList.value.length === 0) {
|
||||||
if (active && creationList.value.length === 0) {
|
loadUsers()
|
||||||
loadUsers()
|
}
|
||||||
}
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadUsers()
|
loadUsers()
|
||||||
isComponentMounted = true
|
|
||||||
uni.$on('assetLiked', ({ asset_id, data }) => {
|
uni.$on('assetLiked', ({ asset_id, data }) => {
|
||||||
likingMap.value = { ...likingMap.value, [asset_id]: true }
|
likingMap.value = { ...likingMap.value, [asset_id]: true }
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -360,20 +370,21 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
// loadUsers()
|
isComponentActive.value = true
|
||||||
|
})
|
||||||
|
|
||||||
|
onHide(() => {
|
||||||
|
isComponentActive.value = false
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
isComponentMounted = false
|
|
||||||
uni.$off('assetLiked')
|
uni.$off('assetLiked')
|
||||||
})
|
})
|
||||||
|
|
||||||
// ========== 对外暴露 ==========
|
// ========== 对外暴露(仅 spotlight / 滚动需要的方法) ==========
|
||||||
defineExpose({
|
defineExpose({
|
||||||
// 原有的
|
|
||||||
loadMore,
|
loadMore,
|
||||||
getCardRefs,
|
getCardRefs,
|
||||||
// 新增:spotlight 系统 + 滚动 fixed + 占位
|
|
||||||
mainTabsRef,
|
mainTabsRef,
|
||||||
categoryRef,
|
categoryRef,
|
||||||
categoryHeight,
|
categoryHeight,
|
||||||
|
|||||||
@ -1,9 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="hot-category-block">
|
<view class="hot-category-block">
|
||||||
<!-- 标题 -->
|
<!-- Tab 栏 -->
|
||||||
<!-- <view class="block-title">
|
<view class="ranking-tabs">
|
||||||
<text class="recommend-text"> {{ title }}</text>
|
<view
|
||||||
</view> -->
|
v-for="tab in tabs"
|
||||||
|
:key="tab.key"
|
||||||
|
class="ranking-tab-item"
|
||||||
|
:class="{ active: activeTabKey === tab.key }"
|
||||||
|
:data-key="tab.key"
|
||||||
|
@click="handleTabClick"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
v-if="tab.icon"
|
||||||
|
class="ranking-tab-icon"
|
||||||
|
:src="tab.icon"
|
||||||
|
mode="aspectFit"
|
||||||
|
:style="{
|
||||||
|
width: (tab.iconWidth || 32) + 'rpx',
|
||||||
|
height: (tab.iconHeight || 40) + 'rpx',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<!-- <text class="ranking-tab-label">{{ tab.label }}</text> -->
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<!-- 骨架屏 -->
|
<!-- 骨架屏 -->
|
||||||
<view v-if="loading" class="grid-skeleton">
|
<view v-if="loading" class="grid-skeleton">
|
||||||
@ -22,6 +41,10 @@
|
|||||||
v-for="(item, index) in items"
|
v-for="(item, index) in items"
|
||||||
:key="item.id || index"
|
:key="item.id || index"
|
||||||
class="grid-card"
|
class="grid-card"
|
||||||
|
:class="{
|
||||||
|
'grid-card-top': index < 3,
|
||||||
|
[`grid-card-top-${index + 1}`]: index < 3,
|
||||||
|
}"
|
||||||
@click="handleCardClick(item)"
|
@click="handleCardClick(item)"
|
||||||
>
|
>
|
||||||
<!-- 点赞动效波纹 -->
|
<!-- 点赞动效波纹 -->
|
||||||
@ -33,15 +56,35 @@
|
|||||||
class="wf-like-wave wf-like-wave-inner"
|
class="wf-like-wave wf-like-wave-inner"
|
||||||
:class="{ 'wf-like-wave-active': likingMap[item.id] }"
|
:class="{ 'wf-like-wave-active': likingMap[item.id] }"
|
||||||
/>
|
/>
|
||||||
<!-- 底部信息模块:独立于图片 -->
|
|
||||||
<view class="card-bottom" :class="`card-bottom-${index + 1}`">
|
<!-- 单行布局:藏品图片 + 头像 + 点赞数 + TOP 标签 -->
|
||||||
<image
|
<view class="card-row">
|
||||||
class="card-image"
|
<view class="card-image-wrap">
|
||||||
:src="item.cover_url || item.cover_image || ''"
|
<image
|
||||||
mode="aspectFill"
|
class="card-image"
|
||||||
></image>
|
:src="item.cover_url || item.cover_image || ''"
|
||||||
<view class="like-badge" :class="`like-badge-${index + 1}`">
|
mode="aspectFill"
|
||||||
<view class="like-icon-wrapper">
|
/>
|
||||||
|
<!-- 前 3 名专属:包裹整个卡片的边框图 -->
|
||||||
|
<image
|
||||||
|
v-if="index < 3"
|
||||||
|
class="frame-image"
|
||||||
|
:src="TOP_FRAME_MAP[index]"
|
||||||
|
mode="scaleToFill"
|
||||||
|
/>
|
||||||
|
<!-- 前 3 名专属:左上角奖牌装饰 -->
|
||||||
|
<image
|
||||||
|
v-if="index < 3"
|
||||||
|
class="card-medal"
|
||||||
|
:src="MEDAL_MAP[index]"
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="like-info"
|
||||||
|
:class="{ [`like-info-top-${index + 1}`]: index < 3 }"
|
||||||
|
>
|
||||||
|
<view class="like-row">
|
||||||
<image
|
<image
|
||||||
class="like-icon"
|
class="like-icon"
|
||||||
:src="
|
:src="
|
||||||
@ -50,47 +93,24 @@
|
|||||||
: '/static/icon/heart-icon-false.png'
|
: '/static/icon/heart-icon-false.png'
|
||||||
"
|
"
|
||||||
mode="aspectFit"
|
mode="aspectFit"
|
||||||
>
|
/>
|
||||||
</image>
|
|
||||||
<text class="like-count">{{ formatCount(item.like_count) }}</text>
|
<text class="like-count">{{ formatCount(item.like_count) }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<text class="user-name">{{
|
||||||
|
item.owner_nickname || item.creator_name || item.name || ""
|
||||||
|
}}</text>
|
||||||
|
<text class="user-number">No.{{ item.owner_uid || "" }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 用户信息 -->
|
<view v-if="index >= 3" class="top-badge">
|
||||||
<view class="card-info" :class="`card-info-${index + 1}`">
|
<view class="badge-rank">
|
||||||
<view class="user-info">
|
|
||||||
<image
|
<image
|
||||||
class="user-avatar"
|
class="badge-rank-icon"
|
||||||
:src="item.owner_avatar || item.creator_avatar || ''"
|
src="/static/square/top/top.png"
|
||||||
mode="aspectFill"
|
mode="aspectFit"
|
||||||
>
|
/>
|
||||||
</image>
|
<text class="badge-rank-number">{{ index + 1 }}</text>
|
||||||
<text class="user-name">{{
|
|
||||||
item.owner_nickname || item.creator_name || item.name || ""
|
|
||||||
}}</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 前三名专属:包裹 card-bottom 的边框 -->
|
|
||||||
<view v-if="index < 3" class="card-frame">
|
|
||||||
<image
|
|
||||||
class="frame-image"
|
|
||||||
:src="TOP_FRAME_MAP[index]"
|
|
||||||
mode="scaleToFill"
|
|
||||||
></image>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<!-- 前三名专属:右上角装饰图(位于 grid-card 层级,避免被 card-bottom 的 overflow 裁切) -->
|
|
||||||
<view v-if="index < 3" class="corner-decoration">
|
|
||||||
<image :src="TOP_ICON_MAP[index]" mode="aspectFit"></image>
|
|
||||||
</view>
|
|
||||||
<!-- Top 排名标签 -->
|
|
||||||
<view class="top-badge" :class="`top-badge-${index + 1}`">
|
|
||||||
<view
|
|
||||||
v-if="index < 3"
|
|
||||||
class="corner-decoration top-corner-decoration"
|
|
||||||
>
|
|
||||||
<image :src="TOP_ICON_MAP[index]" mode="aspectFit"></image>
|
|
||||||
</view>
|
|
||||||
<view class="badge-rank">TOP {{ index + 1 }}</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -98,7 +118,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, onMounted, onUnmounted } from "vue";
|
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
|
||||||
import { onShow } from "@dcloudio/uni-app";
|
import { onShow } from "@dcloudio/uni-app";
|
||||||
import { getHotRankingApi } from "@/utils/api.js";
|
import { getHotRankingApi } from "@/utils/api.js";
|
||||||
import { getAssetCoverRealUrl } from "@/utils/assetImageHelper.js";
|
import { getAssetCoverRealUrl } from "@/utils/assetImageHelper.js";
|
||||||
@ -106,51 +126,79 @@ import { getAssetCoverRealUrl } from "@/utils/assetImageHelper.js";
|
|||||||
// 把后端返回的 cover_url / cover_image 转成真实可访问的 URL
|
// 把后端返回的 cover_url / cover_image 转成真实可访问的 URL
|
||||||
// 处理 3 种形态:/static/... (本地)、相对路径 (需 presign)、完整 URL (可能过期)
|
// 处理 3 种形态:/static/... (本地)、相对路径 (需 presign)、完整 URL (可能过期)
|
||||||
async function resolveItemUrls(item) {
|
async function resolveItemUrls(item) {
|
||||||
if (!item) return item
|
if (!item) return item;
|
||||||
const cover = item.cover_url || item.cover_image || ""
|
const cover = item.cover_url || item.cover_image || "";
|
||||||
if (cover) {
|
if (cover) {
|
||||||
item.cover_url = await getAssetCoverRealUrl(cover)
|
item.cover_url = await getAssetCoverRealUrl(cover);
|
||||||
}
|
}
|
||||||
return item
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: "在线榜单",
|
|
||||||
},
|
|
||||||
dimension: {
|
|
||||||
type: String,
|
|
||||||
default: "displaying",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(["cardClick"]);
|
const emit = defineEmits(["cardClick"]);
|
||||||
|
|
||||||
const items = ref([]);
|
const items = ref([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const likingMap = ref({});
|
const likingMap = ref({});
|
||||||
|
const activeTabKey = ref("");
|
||||||
|
|
||||||
// 前三名对应的边框图与右上角装饰图
|
// Tab 配置(直接写死在组件内)
|
||||||
const TOP_FRAME_MAP = {
|
// 新增 tab 在这里 push 一项即可:{ key, label, icon, iconWidth, iconHeight, fetch }
|
||||||
0: "/static/square/top/TOP1biankuang1.png",
|
const tabs = [
|
||||||
1: "/static/square/top/TOP2biankuang2.png",
|
{
|
||||||
2: "/static/square/top/TOP3biankuangpng3.png",
|
key: "hot",
|
||||||
};
|
label: "热度榜",
|
||||||
const TOP_ICON_MAP = {
|
icon: "/static/square/rementubiao.png",
|
||||||
|
iconWidth: 32,
|
||||||
|
iconHeight: 40,
|
||||||
|
fetch: () => getHotRankingApi("displaying", null, 1, 11),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 前 3 名奖牌图标
|
||||||
|
const MEDAL_MAP = {
|
||||||
0: "/static/square/top/TOP1icon.png",
|
0: "/static/square/top/TOP1icon.png",
|
||||||
1: "/static/square/top/TOP2icon.png",
|
1: "/static/square/top/TOP2icon.png",
|
||||||
2: "/static/square/top/TOP3icon.png",
|
2: "/static/square/top/TOP3icon.png",
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听 dimension 变化,重新加载数据
|
// 前 3 名对应的边框图
|
||||||
watch(
|
const TOP_FRAME_MAP = {
|
||||||
() => props.dimension,
|
0: "/static/square/top/TOP1biankuang1.png",
|
||||||
() => {
|
1: "/static/square/top/TOP2biankuang2.png",
|
||||||
loadData();
|
2: "/static/square/top/TOP3biankuangpng3.png",
|
||||||
},
|
};
|
||||||
|
|
||||||
|
const activeTab = computed(
|
||||||
|
() => tabs.find((t) => t.key === activeTabKey.value) || tabs[0],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 初始化 activeTabKey:默认选第一个 tab
|
||||||
|
watch(
|
||||||
|
() => tabs,
|
||||||
|
(newTabs) => {
|
||||||
|
if (
|
||||||
|
newTabs.length > 0 &&
|
||||||
|
!newTabs.some((t) => t.key === activeTabKey.value)
|
||||||
|
) {
|
||||||
|
activeTabKey.value = newTabs[0].key;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
// 切换 tab
|
||||||
|
const handleTabClick = (e) => {
|
||||||
|
const key = e.currentTarget.dataset.key;
|
||||||
|
if (key && key !== activeTabKey.value) {
|
||||||
|
activeTabKey.value = key;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// activeTab 变化时重新加载数据
|
||||||
|
watch(activeTab, () => {
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
|
||||||
// 格式化数量
|
// 格式化数量
|
||||||
const formatCount = (count) => {
|
const formatCount = (count) => {
|
||||||
if (!count) return "0";
|
if (!count) return "0";
|
||||||
@ -187,22 +235,31 @@ const onAssetLiked = ({ asset_id, data }) => {
|
|||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
|
const tab = activeTab.value;
|
||||||
|
if (!tab || typeof tab.fetch !== "function") {
|
||||||
|
console.warn("[HotCategoryBlock] 当前 tab 未配置 fetch:", tab);
|
||||||
|
items.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await getHotRankingApi(props.dimension, null, 1, 11);
|
const res = await tab.fetch();
|
||||||
if (res.code === 200 && res.data?.items) {
|
if (res && res.code === 200 && res.data?.items) {
|
||||||
// 逐个把 cover_url 转换成真实可访问 URL(OSS 预签名前端实现)
|
// 逐个把 cover_url 转换成真实可访问 URL(OSS 预签名前端实现)
|
||||||
items.value = await Promise.all(
|
items.value = await Promise.all(
|
||||||
res.data.items.map(async (item) => {
|
res.data.items.map(async (item) => {
|
||||||
return await resolveItemUrls({
|
return await resolveItemUrls({
|
||||||
...item,
|
...item,
|
||||||
id: item.id || item.asset_id,
|
id: item.id || item.asset_id,
|
||||||
})
|
});
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
items.value = [];
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[HotCategoryBlock] 加载数据失败", e?.message ?? e);
|
console.error("[HotCategoryBlock] 加载数据失败", e?.message ?? e);
|
||||||
|
items.value = [];
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
@ -224,9 +281,81 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.hot-category-block {
|
.hot-category-block {
|
||||||
padding: 19rpx 9.5rpx;
|
padding: 0 9.5rpx;
|
||||||
border-radius: 24rpx;
|
border-radius: 24rpx;
|
||||||
/* opacity: 0.8; */
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab 栏 */
|
||||||
|
.ranking-tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
position: relative;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%);
|
||||||
|
opacity: 0.8;
|
||||||
|
border-top-left-radius: 14px;
|
||||||
|
border-top-right-radius: 13px;
|
||||||
|
border-bottom-right-radius: 8px;
|
||||||
|
border-bottom-left-radius: 7px;
|
||||||
|
z-index: 1; /* 在内容网格之下 */
|
||||||
|
width: 480rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
background: linear-gradient(183.58deg, #ff5a5d -36.55%, #c2ebff 121.2%);
|
||||||
|
backdrop-filter: blur(11.699999809265137px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ranking-tab-item {
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ranking-tab-item.active {
|
||||||
|
background: linear-gradient(
|
||||||
|
185.9deg,
|
||||||
|
rgba(255, 90, 140, 0.98) 5.54%,
|
||||||
|
rgba(194, 235, 255, 0.98) 184.09%
|
||||||
|
);
|
||||||
|
backdrop-filter: blur(1.7999999523162842px);
|
||||||
|
height: 144rpx;
|
||||||
|
width: 88rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ranking-tab-label {
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.4);
|
||||||
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ranking-tab-icon {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: -8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ranking-tab-item.active .ranking-tab-label {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 骨架屏 */
|
||||||
|
.grid-skeleton {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column; /* 与 items-grid 保持一致:纵向 */
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
161.28deg,
|
161.28deg,
|
||||||
rgba(255, 90, 93, 0.2) 16.63%,
|
rgba(255, 90, 93, 0.2) 16.63%,
|
||||||
@ -234,98 +363,39 @@ onUnmounted(() => {
|
|||||||
rgba(255, 122, 124, 0.2) 83.71%
|
rgba(255, 122, 124, 0.2) 83.71%
|
||||||
);
|
);
|
||||||
backdrop-filter: blur(9.300000190734863px);
|
backdrop-filter: blur(9.300000190734863px);
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block-title {
|
|
||||||
position: absolute;
|
|
||||||
top: -24rpx;
|
|
||||||
left: 4rpx;
|
|
||||||
width: 188rpx;
|
|
||||||
height: 56rpx;
|
|
||||||
border-top-left-radius: 44rpx;
|
|
||||||
border-top-right-radius: 8rpx;
|
|
||||||
border-bottom-right-radius: 44rpx;
|
|
||||||
border-bottom-left-radius: 4rpx;
|
|
||||||
background: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
rgba(255, 0, 4, 0.73) -3.96%,
|
|
||||||
rgba(254, 141, 103, 0.73) 57.95%,
|
|
||||||
rgba(252, 228, 75, 0.73) 97%
|
|
||||||
);
|
|
||||||
box-shadow: 2px 2px 4px 0px #d9262640;
|
|
||||||
backdrop-filter: blur(7.599999904632568px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recommend-text {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0px 2px 8px #00000074;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 100%;
|
|
||||||
letter-spacing: 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 骨架屏 */
|
|
||||||
.grid-skeleton {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-card {
|
|
||||||
width: calc(25% - 12rpx);
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 16rpx;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 骨架屏:第一排3个大图 */
|
.skeleton-card {
|
||||||
.skeleton-card:nth-child(-n + 3) {
|
|
||||||
width: calc(33.333% - 12rpx);
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-image {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 192rpx;
|
height: 120rpx; /* 与新卡片 row 高度一致 */
|
||||||
background: linear-gradient(90deg, #3a3a4a 25%, #4a4a5a 50%, #3a3a4a 75%);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
background-size: 200% 100%;
|
border-radius: 16rpx;
|
||||||
animation: shimmer 1.5s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 骨架屏:第一排图片更高 */
|
|
||||||
.skeleton-card:nth-child(-n + 3) .skeleton-image {
|
|
||||||
height: 236rpx;
|
|
||||||
border-top-left-radius: 8px;
|
|
||||||
border-top-right-radius: 24px;
|
|
||||||
border-bottom-right-radius: 8px;
|
|
||||||
border-bottom-left-radius: 21px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-info {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 16rpx;
|
padding: 12rpx 16rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-avatar {
|
.skeleton-avatar {
|
||||||
width: 40rpx;
|
width: 56rpx;
|
||||||
height: 40rpx;
|
height: 56rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #3a3a4a;
|
background: #3a3a4a;
|
||||||
margin-right: 8rpx;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-name {
|
.skeleton-name {
|
||||||
width: 100rpx;
|
flex: 1;
|
||||||
height: 24rpx;
|
height: 28rpx;
|
||||||
background: #3a3a4a;
|
background: #3a3a4a;
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-image,
|
||||||
|
.skeleton-info {
|
||||||
|
display: none; /* 新布局下不再使用 */
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes shimmer {
|
@keyframes shimmer {
|
||||||
@ -341,222 +411,182 @@ onUnmounted(() => {
|
|||||||
/* 内容网格 */
|
/* 内容网格 */
|
||||||
.items-grid {
|
.items-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-direction: column; /* 改为纵向排列,每张卡片独占一行 */
|
||||||
justify-content: space-between;
|
position: relative;
|
||||||
|
z-index: 2; /* 在 tab 栏之上 */
|
||||||
|
/* 背景与父容器一致,看上去与 tab 栏融成一体 */
|
||||||
|
background: linear-gradient(
|
||||||
|
161.28deg,
|
||||||
|
rgba(255, 90, 93, 0.2) 16.63%,
|
||||||
|
rgba(76, 237, 255, 0.2) 48.19%,
|
||||||
|
rgba(255, 122, 124, 0.2) 83.71%
|
||||||
|
);
|
||||||
|
backdrop-filter: blur(9.300000190734863px);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 40rpx 20rpx 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-card {
|
.grid-card {
|
||||||
width: calc(25% - 12rpx);
|
width: 100%;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
/* overflow: hidden; */
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
/* background: rgba(255, 255, 255, 0.15); */
|
||||||
|
/* box-shadow: 2px 2px 4.5px 0px #f04b4b40; */
|
||||||
|
box-shadow: 2px 4px 4px 0px #c92f2f5c;
|
||||||
|
margin-bottom: 48rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 第一排:3个大图突出显示 */
|
/* 单行布局:藏品图片 + 头像 + 点赞信息 + TOP 标签 */
|
||||||
.grid-card:nth-child(-n + 3) {
|
.card-row {
|
||||||
width: calc(33% - 20rpx);
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
|
padding: 0 16rpx;
|
||||||
.grid-card:nth-child(-n + 3) .card-image {
|
width: 100%;
|
||||||
height: 264rpx;
|
height: 104rpx;
|
||||||
box-shadow: 3px 3px 4.5px 2px rgba(0, 0, 0, 0.15);
|
|
||||||
backdrop-filter: blur(0px);
|
|
||||||
/* padding: 8rpx; */
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-image-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 90rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
|
||||||
|
}
|
||||||
.card-image {
|
.card-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 224rpx;
|
height: 100%;
|
||||||
|
border-radius: 12rpx;
|
||||||
display: block;
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: -16rpx;
|
||||||
|
left: 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 前三名专属:包裹 card-bottom 的边框 */
|
/* 前 3 名专属:左上角奖牌装饰 */
|
||||||
.card-frame {
|
.card-medal {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: -32rpx;
|
||||||
left: 0;
|
left: 96rpx;
|
||||||
|
width: 48rpx;
|
||||||
|
height: 48rpx;
|
||||||
|
z-index: 5;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 前 3 名专属:卡片整体突出 */
|
||||||
|
.grid-card-top {
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-card-top-1 {
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-card-top-2 {
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-card-top-3 {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 前 3 名专属:包裹藏品图的边框图(叠加在 card-image 之上) */
|
||||||
|
.frame-image {
|
||||||
|
position: absolute;
|
||||||
|
top: -16rpx;
|
||||||
|
left: 32rpx;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
|
||||||
|
|
||||||
.frame-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.corner-decoration {
|
.like-info {
|
||||||
position: absolute;
|
|
||||||
top: -16rpx;
|
|
||||||
right: -16rpx;
|
|
||||||
width: 64rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
z-index: 6;
|
|
||||||
pointer-events: none;
|
|
||||||
transform: rotate(60deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.corner-decoration.top-corner-decoration {
|
|
||||||
width: 56rpx;
|
|
||||||
height: 56rpx;
|
|
||||||
left: -16rpx;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.corner-decoration image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 底部信息模块 - 独立模块,有背景色和圆角 */
|
|
||||||
.card-bottom {
|
|
||||||
background: rgba(255, 255, 255, 0.15);
|
|
||||||
border-radius: 16rpx;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
box-shadow: 2px 2px 4.5px 0px #F04B4B40;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-bottom-1,
|
|
||||||
.card-bottom-2,
|
|
||||||
.card-bottom-3 {
|
|
||||||
border-radius: 28rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Top 排名标签 */
|
|
||||||
.top-badge {
|
|
||||||
width: 80rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
margin: 16rpx auto;
|
|
||||||
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);
|
|
||||||
/* overflow: hidden; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-badge-1,
|
|
||||||
.top-badge-2,
|
|
||||||
.top-badge-3 {
|
|
||||||
padding-left: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-rank {
|
|
||||||
width: 80rpx;
|
|
||||||
height: 32rpx;
|
|
||||||
color: #fffabd;
|
|
||||||
font-size: 18rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 16rpx;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column; /* 从上往下:用户名 → 编号 → 点赞 */
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-shadow: -1px 1px 4px #ce0909d6;
|
margin-left: 64rpx; /* 距头像 10rpx */
|
||||||
}
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
/* 用户信息 */
|
|
||||||
.card-info {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8rpx;
|
|
||||||
border-top-left-radius: 7px;
|
|
||||||
border-top-right-radius: 7px;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
background: linear-gradient(
|
|
||||||
177.83deg,
|
|
||||||
rgba(235, 228, 219, 0) 11.38%,
|
|
||||||
rgba(138, 135, 131, 0.4) 23.67%,
|
|
||||||
rgba(255, 231, 231, 0.6) 43.04%,
|
|
||||||
rgba(255, 255, 255, 0.9) 67.52%,
|
|
||||||
#ffffff 98.2%
|
|
||||||
);
|
|
||||||
backdrop-filter: blur(0px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-info-1,
|
|
||||||
.card-info-2,
|
|
||||||
.card-info-3 {
|
|
||||||
padding-bottom: 16rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-avatar {
|
|
||||||
width: 40rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 8rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-name {
|
.user-name {
|
||||||
font-size: 18rpx;
|
font-size: 20rpx;
|
||||||
font-weight: 400;
|
font-weight: 800;
|
||||||
color: #554545;
|
color: #fffabd;
|
||||||
max-width: 120rpx;
|
text-shadow: -1px 1px 4px #ce0909d6;
|
||||||
overflow: hidden;
|
white-space: nowrap;
|
||||||
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-number {
|
||||||
|
font-size: 20rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.3;
|
||||||
|
color: #fffabd;
|
||||||
|
text-shadow: -1px 1px 4px #ce0909d6;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.like-badge {
|
.like-row {
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 80rpx;
|
|
||||||
height: 64rpx;
|
|
||||||
opacity: 1;
|
|
||||||
border-top-left-radius: 7px;
|
|
||||||
border-bottom-right-radius: 21.5px;
|
|
||||||
background: linear-gradient(
|
|
||||||
177.83deg,
|
|
||||||
rgba(83, 244, 211, 0.2) 2.52%,
|
|
||||||
rgba(15, 9, 0, 0) 69.07%
|
|
||||||
);
|
|
||||||
|
|
||||||
backdrop-filter: blur(0px);
|
|
||||||
z-index: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.like-badge-1,
|
|
||||||
.like-badge-2,
|
|
||||||
.like-badge-3 {
|
|
||||||
padding: 10rpx 0 0 10rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.like-icon-wrapper {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8rpx;
|
margin-top: 6rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.like-badge .like-icon {
|
.like-info .like-icon {
|
||||||
width: 24rpx;
|
width: 24rpx;
|
||||||
height: 24rpx;
|
height: 24rpx;
|
||||||
margin-right: 6rpx;
|
margin-right: 4rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.like-badge .like-count {
|
.like-info .like-count {
|
||||||
font-size: 20rpx;
|
font-size: 28rpx;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 100%;
|
|
||||||
letter-spacing: 0%;
|
|
||||||
color: #fffabd;
|
color: #fffabd;
|
||||||
text-shadow:
|
text-shadow: -1px 1px 4px #ce0909d6;
|
||||||
-1px 1px 4px #ce0909d6,
|
white-space: nowrap;
|
||||||
0px 0px 10px #fffabd;
|
}
|
||||||
|
|
||||||
|
/* Top 排名标签(顶到右边) */
|
||||||
|
.top-badge {
|
||||||
|
margin-left: auto; /* 推到右侧 */
|
||||||
|
min-width: 100rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border-radius: 18rpx;
|
||||||
|
padding: 0 16rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-rank {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-rank-icon {
|
||||||
|
width: 90.24rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-rank-number {
|
||||||
|
font-size: 84rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
/* 渐变填充到文字 */
|
||||||
|
background: linear-gradient(
|
||||||
|
181.98deg,
|
||||||
|
#fcfcf8 23.78%,
|
||||||
|
#ffb3eb 66.88%,
|
||||||
|
#ffeded 94.93%
|
||||||
|
);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
color: transparent;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 点赞动效波纹 */
|
/* 点赞动效波纹 */
|
||||||
|
|||||||
@ -38,27 +38,29 @@
|
|||||||
<ContentTabs ref="contentTabsRef" class="tabs" :modelValue="activeContentTab"
|
<ContentTabs ref="contentTabsRef" class="tabs" :modelValue="activeContentTab"
|
||||||
@update:modelValue="activeContentTab = $event" />
|
@update:modelValue="activeContentTab = $event" />
|
||||||
|
|
||||||
<!-- 在线榜单区块 -->
|
<!-- 在线榜单区块 - 仅在 星榜 时显示 -->
|
||||||
<view ref="hotCategoryRef" class="hot-category-wrapper">
|
<view
|
||||||
<HotCategoryBlock :dimension="activeContentTab" @cardClick="handleCardClick" />
|
v-if="activeContentTab === 'xingbang'"
|
||||||
<view class="hot-more-btn" @click="goToHotCategoryMore">
|
ref="hotCategoryRef"
|
||||||
<text class="hot-more-text">查看更多</text>
|
class="hot-category-wrapper"
|
||||||
</view>
|
>
|
||||||
|
<HotCategoryBlock @cardClick="handleCardClick" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 区域二/三/四已合并到 CreationGrid 组件(主Tab + 分类标签 + 网格列表) -->
|
<!-- CreationGrid 组件(主Tab + 分类标签 + 网格列表) - 仅在 广场 时显示 -->
|
||||||
<CreationGrid :screenWidth="screenWidth" :screenHeight="screenHeight" :bannerBottom="bannerBottomPx"
|
<CreationGrid
|
||||||
:category="activeCategoryTab" :isActive="isActive"
|
v-if="activeContentTab === 'guangchang'"
|
||||||
@cardClick="handleCardClick" @loaded="onGridLoaded"
|
@cardClick="handleCardClick"
|
||||||
@mainTabClick="handleMainTabClick" @categoryChange="handleCategoryChange"
|
@loaded="onGridLoaded"
|
||||||
ref="creationGridRef" />
|
ref="creationGridRef"
|
||||||
|
/>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
|
import { ref, watch, onMounted, onUnmounted, nextTick } from "vue";
|
||||||
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
|
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||||
import { useStore } from "vuex";
|
import { useStore } from "vuex";
|
||||||
import Header from "../components/Header.vue";
|
import Header from "../components/Header.vue";
|
||||||
import BottomNav from "../components/BottomNav.vue";
|
import BottomNav from "../components/BottomNav.vue";
|
||||||
@ -79,11 +81,9 @@ const store = useStore();
|
|||||||
const currentStarId = ref(uni.getStorageSync("star_id") || null);
|
const currentStarId = ref(uni.getStorageSync("star_id") || null);
|
||||||
|
|
||||||
// ========== UI State ==========
|
// ========== UI State ==========
|
||||||
const activeContentTab = ref("displaying");
|
const activeContentTab = ref("xinghe");
|
||||||
const activeCategoryTab = ref("hot");
|
|
||||||
const navExpanded = ref(false);
|
const navExpanded = ref(false);
|
||||||
const showRankingModal = ref(false);
|
const showRankingModal = ref(false);
|
||||||
const isActive = ref(true);
|
|
||||||
const creationGridRef = ref(null);
|
const creationGridRef = ref(null);
|
||||||
const cardTapTimers = {};
|
const cardTapTimers = {};
|
||||||
const likingMap = ref({});
|
const likingMap = ref({});
|
||||||
@ -99,7 +99,7 @@ const allSpotlightRefs = () => {
|
|||||||
const sections = [
|
const sections = [
|
||||||
bannerSectionRef.value,
|
bannerSectionRef.value,
|
||||||
contentTabsRef.value?.$el || contentTabsRef.value,
|
contentTabsRef.value?.$el || contentTabsRef.value,
|
||||||
hotCategoryRef.value,
|
// hotCategoryRef.value,
|
||||||
creationGridRef.value?.mainTabsRef?.value,
|
creationGridRef.value?.mainTabsRef?.value,
|
||||||
creationGridRef.value?.categoryRef?.value,
|
creationGridRef.value?.categoryRef?.value,
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
@ -129,13 +129,10 @@ const onTouchMove = () => {
|
|||||||
update()
|
update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// tab / 分类变化 → 新内容可能进入视口,重做一次 spotlight 计算
|
// tab 变化 → 新内容可能进入视口,重做一次 spotlight 计算
|
||||||
watch(activeContentTab, () => {
|
watch(activeContentTab, () => {
|
||||||
nextTick(() => setTimeout(update, 50));
|
nextTick(() => setTimeout(update, 50));
|
||||||
});
|
});
|
||||||
watch(activeCategoryTab, () => {
|
|
||||||
nextTick(() => setTimeout(update, 50));
|
|
||||||
});
|
|
||||||
|
|
||||||
// 卡片加载到位 → 重新算一次 spotlight(卡片异步加载,不在初始 200ms update 范围内)
|
// 卡片加载到位 → 重新算一次 spotlight(卡片异步加载,不在初始 200ms update 范围内)
|
||||||
const onGridLoaded = (count) => {
|
const onGridLoaded = (count) => {
|
||||||
@ -144,18 +141,9 @@ const onGridLoaded = (count) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== Screen Info ==========
|
|
||||||
const screenWidth = ref(375);
|
|
||||||
const screenHeight = ref(812);
|
|
||||||
|
|
||||||
// ========== Composables ==========
|
// ========== Composables ==========
|
||||||
const { bannerActivities, banners, loadBannerActivities, loadBanners } = useBanner();
|
const { bannerActivities, banners, loadBannerActivities, loadBanners } = useBanner();
|
||||||
|
|
||||||
// banner(216+360rpx) + tab栏(16+80rpx) + 间距(8rpx) ≈ 680rpx
|
|
||||||
const bannerBottomPx = computed(() =>
|
|
||||||
Math.round((screenWidth.value / 750) * 715),
|
|
||||||
);
|
|
||||||
|
|
||||||
// ========== Handlers ==========
|
// ========== Handlers ==========
|
||||||
|
|
||||||
const handleCardClick = (card) => {
|
const handleCardClick = (card) => {
|
||||||
@ -233,18 +221,6 @@ const handleRankingModalClose = (visible) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToHotCategoryMore = () => {
|
|
||||||
const title =
|
|
||||||
activeContentTab.value === "displaying"
|
|
||||||
? "日榜"
|
|
||||||
: activeContentTab.value === "week"
|
|
||||||
? "周榜"
|
|
||||||
: "月榜";
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/square/hot-category-more?dimension=${activeContentTab.value}&title=${encodeURIComponent(title)}`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTabChange = (newTab) => {
|
const handleTabChange = (newTab) => {
|
||||||
// if (newTab === 4) {
|
// if (newTab === 4) {
|
||||||
// navExpanded.value = false;
|
// navExpanded.value = false;
|
||||||
@ -266,18 +242,7 @@ const handleTabChange = (newTab) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 主Tab点击 - 进入铸造页面
|
// 主Tab点击 / 分类切换 已在 CreationGrid 内部处理,不再冒泡
|
||||||
const handleMainTabClick = (tab) => {
|
|
||||||
// 跳转到 mall 页(mall 内嵌 craft-select 组件,带菜单),并带上 type
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/castlove/mall?type=${encodeURIComponent(tab.type)}`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCategoryChange = (value) => {
|
|
||||||
if (activeCategoryTab.value === value) return;
|
|
||||||
activeCategoryTab.value = value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ========== Tile Change Callback ==========
|
// ========== Tile Change Callback ==========
|
||||||
const handleTileChange = () => { };
|
const handleTileChange = () => { };
|
||||||
@ -287,10 +252,6 @@ const resetSquare = async () => { };
|
|||||||
|
|
||||||
// ========== Lifecycle ==========
|
// ========== Lifecycle ==========
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const info = uni.getSystemInfoSync();
|
|
||||||
screenWidth.value = info.windowWidth;
|
|
||||||
screenHeight.value = info.windowHeight;
|
|
||||||
|
|
||||||
resetSquare();
|
resetSquare();
|
||||||
loadBannerActivities();
|
loadBannerActivities();
|
||||||
loadBanners();
|
loadBanners();
|
||||||
@ -303,18 +264,12 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onShow(() => {
|
onShow(() => {
|
||||||
isActive.value = true;
|
activeContentTab.value = "xinghe";
|
||||||
activeContentTab.value = "displaying";
|
|
||||||
activeCategoryTab.value = "hot";
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setTimeout(update, 80);
|
setTimeout(update, 80);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onHide(() => {
|
|
||||||
isActive.value = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// onLoad((options) => {
|
// onLoad((options) => {
|
||||||
// if (options && 'guide_debug' in options) {
|
// if (options && 'guide_debug' in options) {
|
||||||
// const debugValue = options.guide_debug
|
// const debugValue = options.guide_debug
|
||||||
@ -414,31 +369,6 @@ onUnmounted(() => {
|
|||||||
padding-bottom: 80rpx;
|
padding-bottom: 80rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hot-more-btn {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
right: 16rpx;
|
|
||||||
z-index: 10;
|
|
||||||
width: 104rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
opacity: 0.66;
|
|
||||||
padding: 8rpx 20rpx;
|
|
||||||
background: linear-gradient(90deg,
|
|
||||||
rgba(255, 222, 8, 0.61) -17.54%,
|
|
||||||
rgba(255, 0, 25, 0.61) 116.67%);
|
|
||||||
box-shadow: 2px 2px 4px 0px #f2151578;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hot-more-text {
|
|
||||||
font-size: 22rpx;
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 蒙层 */
|
/* 蒙层 */
|
||||||
.nav-mask {
|
.nav-mask {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
BIN
frontend/static/square/top/top.png
Normal file
BIN
frontend/static/square/top/top.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 441 KiB |
Loading…
Reference in New Issue
Block a user