topfans/frontend/pages/square/components/HotCategoryBlock.vue

585 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="hot-category-block">
<!-- 标题 -->
<!-- <view class="block-title">
<text class="recommend-text"> {{ title }}</text>
</view> -->
<!-- 骨架屏 -->
<view v-if="loading" class="grid-skeleton">
<view v-for="i in 11" :key="i" class="skeleton-card">
<view class="skeleton-image"></view>
<view class="skeleton-info">
<view class="skeleton-avatar"></view>
<view class="skeleton-name"></view>
</view>
</view>
</view>
<!-- 内容网格 -->
<view v-else class="items-grid">
<view
v-for="(item, index) in items"
:key="item.id || index"
class="grid-card"
@click="handleCardClick(item)"
>
<!-- 点赞动效波纹 -->
<view
class="wf-like-wave wf-like-wave-outer"
:class="{ 'wf-like-wave-active': likingMap[item.id] }"
/>
<view
class="wf-like-wave wf-like-wave-inner"
:class="{ 'wf-like-wave-active': likingMap[item.id] }"
/>
<!-- 底部信息模块:独立于图片 -->
<view class="card-bottom" :class="`card-bottom-${index + 1}`">
<image
class="card-image"
:src="item.cover_url || item.cover_image || ''"
mode="aspectFill"
></image>
<view class="like-badge" :class="`like-badge-${index + 1}`">
<view class="like-icon-wrapper">
<image
class="like-icon"
:src="
item.is_liked
? '/static/icon/heart-icon.png'
: '/static/icon/heart-icon-false.png'
"
mode="aspectFit"
>
</image>
<text class="like-count">{{ formatCount(item.like_count) }}</text>
</view>
</view>
<!-- 用户信息 -->
<view class="card-info" :class="`card-info-${index + 1}`">
<view class="user-info">
<image
class="user-avatar"
:src="item.owner_avatar || item.creator_avatar || ''"
mode="aspectFill"
>
</image>
<text class="user-name">{{
item.owner_nickname || item.creator_name || item.name || ""
}}</text>
</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>
</template>
<script setup>
import { ref, watch, onMounted, onUnmounted } from "vue";
import { onShow } from "@dcloudio/uni-app";
import { getHotRankingApi } from "@/utils/api.js";
const props = defineProps({
title: {
type: String,
default: "在线榜单",
},
dimension: {
type: String,
default: "displaying",
},
});
const emit = defineEmits(["cardClick"]);
const items = ref([]);
const loading = ref(false);
const likingMap = ref({});
// 前三名对应的边框图与右上角装饰图
const TOP_FRAME_MAP = {
0: "/static/square/top/TOP1biankuang.png",
1: "/static/square/top/TOP2biankuang.png",
2: "/static/square/top/TOP3biankuangpng.png",
};
const TOP_ICON_MAP = {
0: "/static/square/top/TOP1icon.png",
1: "/static/square/top/TOP2icon.png",
2: "/static/square/top/TOP3icon.png",
};
// 监听 dimension 变化,重新加载数据
watch(
() => props.dimension,
() => {
loadData();
},
);
// 格式化数量
const formatCount = (count) => {
if (!count) return "0";
if (count >= 10000) return (count / 10000).toFixed(1) + "w";
if (count >= 1000) return (count / 1000).toFixed(1) + "k";
return count.toString();
};
const handleCardClick = (item) => {
emit("cardClick", item);
};
// 监听全局点赞事件,更新状态
const onAssetLiked = ({ asset_id, data }) => {
const index = items.value.findIndex(
(item) => (item.asset_id || item.id) === asset_id,
);
if (index !== -1) {
const updatedItems = [...items.value];
updatedItems[index] = {
...updatedItems[index],
is_liked: data?.is_liked ?? true,
like_count:
data?.new_like_count ?? (updatedItems[index].like_count || 0) + 1,
};
items.value = updatedItems;
// 触发动画
likingMap.value = { ...likingMap.value, [asset_id]: true };
setTimeout(() => {
likingMap.value = { ...likingMap.value, [asset_id]: false };
}, 600);
}
};
// 加载数据
const loadData = async () => {
loading.value = true;
try {
const res = await getHotRankingApi(props.dimension, null, 1, 11);
if (res.code === 200 && res.data?.items) {
items.value = res.data.items.map((item) => ({
...item,
id: item.id || item.asset_id,
}));
}
} catch (e) {
console.error("[HotCategoryBlock] 加载数据失败", e?.message ?? e);
} finally {
loading.value = false;
}
};
onMounted(() => {
uni.$on("assetLiked", onAssetLiked);
});
onShow(() => {
loadData();
});
onUnmounted(() => {
uni.$off("assetLiked", onAssetLiked);
});
</script>
<style scoped>
.hot-category-block {
padding: 19rpx 9.5rpx;
border-radius: 24rpx;
opacity: 0.8;
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);
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;
}
/* 骨架屏第一排3个大图 */
.skeleton-card:nth-child(-n + 3) {
width: calc(33.333% - 12rpx);
}
.skeleton-image {
width: 100%;
height: 192rpx;
background: linear-gradient(90deg, #3a3a4a 25%, #4a4a5a 50%, #3a3a4a 75%);
background-size: 200% 100%;
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;
align-items: center;
padding: 16rpx;
}
.skeleton-avatar {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #3a3a4a;
margin-right: 8rpx;
}
.skeleton-name {
width: 100rpx;
height: 24rpx;
background: #3a3a4a;
border-radius: 8rpx;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
/* 内容网格 */
.items-grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.grid-card {
width: calc(25% - 12rpx);
border-radius: 16rpx;
/* overflow: hidden; */
position: relative;
}
/* 第一排3个大图突出显示 */
.grid-card:nth-child(-n + 3) {
width: calc(33.333% - 12rpx);
}
.grid-card:nth-child(-n + 3) .card-image {
height: 236rpx;
box-shadow: 3px 3px 4.5px 2px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(0px);
padding: 8rpx;
box-sizing: border-box;
}
.card-image {
width: 100%;
height: 192rpx;
display: block;
}
/* 前三名专属:包裹 card-bottom 的边框 */
.card-frame {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 4;
pointer-events: none;
}
.frame-image {
width: 100%;
height: 100%;
display: block;
}
.corner-decoration {
position: absolute;
top: -16rpx;
right: -16rpx;
width: 64rpx;
height: 64rpx;
z-index: 6;
pointer-events: none;
transform: rotate(60deg);
}
.corner-decoration.top-corner-decoration {
left: -24rpx;
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;
}
.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;
align-items: center;
justify-content: center;
text-shadow: -1px 1px 4px #ce0909d6;
}
/* 用户信息 */
.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 {
font-size: 18rpx;
font-weight: 400;
color: #554545;
max-width: 120rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.like-badge {
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;
align-items: center;
padding: 8rpx;
}
.like-badge .like-icon {
width: 24rpx;
height: 24rpx;
margin-right: 6rpx;
}
.like-badge .like-count {
font-size: 20rpx;
font-weight: 400;
line-height: 100%;
letter-spacing: 0%;
color: #fffabd;
text-shadow:
-1px 1px 4px #ce0909d6,
0px 0px 10px #fffabd;
}
/* 点赞动效波纹 */
.wf-like-wave {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0;
z-index: 1;
}
.wf-like-wave-outer {
background: radial-gradient(
circle,
rgba(255, 107, 107, 0.8) 0%,
transparent 70%
);
}
.wf-like-wave-inner {
background: radial-gradient(
circle,
rgba(255, 184, 0, 0.6) 0%,
transparent 70%
);
}
.wf-like-wave-active {
animation: likeWave 0.6s ease-out forwards;
}
@keyframes likeWave {
0% {
opacity: 0.9;
transform: scale(0.8);
}
100% {
opacity: 0;
transform: scale(1.5);
}
}
</style>