style:修改前端样式

This commit is contained in:
zheng020 2026-06-01 15:06:35 +08:00
parent 3a2575c78e
commit 60db94e377
11 changed files with 1375 additions and 694 deletions

File diff suppressed because it is too large Load Diff

View File

@ -22,39 +22,76 @@
</view>
<view class="exhibition-grid">
<view v-for="(item, index) in exhibitionWorks" :key="item.id" class="exhibition-card"
:class="index % 2 === 0 ? 'card-tilt-left' : 'card-tilt-right'" @tap="goToAssetDetail(item.id)">
<image class="card-image" :src="item.cover_url || '/static/nft/placeholder.png'"
mode="aspectFill"></image>
<!-- 左边展位 (slot_index=1) -->
<view v-if="exhibitionAtSlot[0]" class="exhibition-card card-tilt-left"
@tap="goToAssetDetail(exhibitionAtSlot[0].id)">
<LenticularCard v-if="exhibitionAtSlot[0].is_lenticular" class="card-lenticular"
:layers="getLenticularLayers(exhibitionAtSlot[0].id)"
:transforms="getLenticularTransforms(exhibitionAtSlot[0].id)" :gyro-source="gyroSourceLabel"
:skip-built-in-touch="true" :shimmer-mid-opacity="0.16"
@simulate="(x, y) => onLenticularSimulate(exhibitionAtSlot[0].id, x, y)" />
<image v-else class="card-image"
:src="exhibitionAtSlot[0].cover_url || '/static/nft/placeholder.png'" mode="aspectFill">
</image>
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill">
</image>
<!-- 点赞数 -->
<view class="card-rate-badge">
<image class="heart-icon" src="/static/icon/heart-icon.png" mode="aspectFit"></image>
<view class="card-rate-text-wrap">
<text class="card-rate-text">{{ item.like_count || 0 }}</text>
<text class="card-rate-text">{{ exhibitionAtSlot[0].like_count || 0 }}</text>
</view>
</view>
<!-- 倒计时背景 -->
<view class="countdown-background" :style="getCountdownBackgroundStyle()">
<!-- 倒计时文字 -->
<text class="countdown-text">
{{ formatCountdown(item.id) }}
</text>
<text class="countdown-text">{{ formatCountdown(exhibitionAtSlot[0].id) }}</text>
</view>
<!-- 图片下方收益 -->
<view class="card-income-row"
:class="index % 2 === 0 ? 'income-tilt-right' : 'income-tilt-left'">
<view class="card-income-row income-tilt-right">
<image class="topfans-icon" src="/static/icon/crystal.png" mode="aspectFit"></image>
<view class="card-income-text-wrap">
<text class="card-income-text">{{ item.earnings || 0 }}/</text>
<text class="card-income-text">{{ exhibitionAtSlot[0].earnings || 0 }}/</text>
</view>
</view>
</view>
<view v-else class="empty-card empty-card-left">
<image class="empty-cover" src="/static/nft/placeholder.png" mode="aspectFill"></image>
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill">
</image>
</view>
<!-- 空状态占位 -->
<view v-if="exhibitionWorks.length === 0" class="empty-exhibition">
<text class="empty-text">该用户暂无在展作品</text>
<!-- 右边展位 (slot_index=2) -->
<view v-if="exhibitionAtSlot[1]" class="exhibition-card card-tilt-right"
@tap="goToAssetDetail(exhibitionAtSlot[1].id)">
<LenticularCard v-if="exhibitionAtSlot[1].is_lenticular" class="card-lenticular"
:layers="getLenticularLayers(exhibitionAtSlot[1].id)"
:transforms="getLenticularTransforms(exhibitionAtSlot[1].id)" :gyro-source="gyroSourceLabel"
:skip-built-in-touch="true" :shimmer-mid-opacity="0.16"
@simulate="(x, y) => onLenticularSimulate(exhibitionAtSlot[1].id, x, y)" />
<image v-else class="card-image"
:src="exhibitionAtSlot[1].cover_url || '/static/nft/placeholder.png'" mode="aspectFill">
</image>
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill">
</image>
<!-- 点赞数 -->
<view class="card-rate-badge">
<image class="heart-icon" src="/static/icon/heart-icon.png" mode="aspectFit"></image>
<view class="card-rate-text-wrap">
<text class="card-rate-text">{{ exhibitionAtSlot[1].like_count || 0 }}</text>
</view>
</view>
<view class="countdown-background" :style="getCountdownBackgroundStyle()">
<text class="countdown-text">{{ formatCountdown(exhibitionAtSlot[1].id) }}</text>
</view>
<view class="card-income-row income-tilt-left">
<image class="topfans-icon" src="/static/icon/crystal.png" mode="aspectFit"></image>
<view class="card-income-text-wrap">
<text class="card-income-text">{{ exhibitionAtSlot[1].earnings || 0 }}/</text>
</view>
</view>
</view>
<view v-else class="empty-card empty-card-right">
<image class="empty-cover" src="/static/nft/placeholder.png" mode="aspectFill"></image>
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill">
</image>
</view>
</view>
</view>
@ -68,18 +105,6 @@
</image>
<text class="section-label-text">当前点赞作品</text>
</view>
<!-- <view class="section-label" :class="{ 'tab-active': likedTab === 'today' }"
@tap="switchLikedTab('today')">
<image class="section-label-bg" src="/static/nft/dingbutubiao_liang.png" mode="aspectFill">
</image>
<text class="section-label-text">今日点赞作品</text>
</view>
<view class="section-label" :class="{ 'tab-active': likedTab === 'week' }"
@tap="switchLikedTab('week')">
<image class="section-label-bg" src="/static/nft/dingbutubiao_liang.png" mode="aspectFill">
</image>
<text class="section-label-text">本周点赞作品</text>
</view> -->
</view>
<scroll-view class="liked-list" scroll-y="true" :show-scrollbar="false">
@ -90,11 +115,15 @@
mode="aspectFit"></image>
<!-- 卡片主体 -->
<view class="liked-item" :class="index === 0 ? 'liked-item-first' : ''">
<!-- 作品封面 -->
<view class="liked-cover-wrap" :class="index === 0 ? 'liked-cover-wrap-first' : ''">
<image class="liked-cover" :src="item.cover_url || '/static/nft/placeholder.png'"
<LenticularCard v-if="item.is_lenticular" class="liked-lenticular"
:layers="getLikedLenticularLayers(item.id)"
:transforms="getLikedLenticularTransforms(item.id)" :gyro-source="gyroSourceLabel"
:skip-built-in-touch="true" :shimmer-mid-opacity="0.16"
@simulate="(x, y) => onLikedLenticularSimulate(item.id, x, y)" />
<image v-else class="liked-cover" :src="item.cover_url || '/static/nft/placeholder.png'"
mode="aspectFill"></image>
<image class="liked-cover-frame" src="/static/square/cangpinkuang1.png"
mode="aspectFill"></image>
@ -116,7 +145,7 @@
:src="item.earnings > 10 ? '/static/square/shuijingtubiao.png' : '/static/icon/crystal.png'"
mode="aspectFit">
</image>
<text class="reward-amount">+{{ item.reward }}</text>
<text class="reward-amount">{{ item.reward }}</text>
</view>
</view>
</view>
@ -132,9 +161,13 @@
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { ref, onMounted, onUnmounted, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { getUserGalleriesApi, getUserLikedAssetsApi, getUserProfileApi } from '@/utils/api.js';
import { getUserGalleriesApi, getUserLikedAssetsApi, getAssetMaterialsApi } from '@/utils/api.js';
import LenticularCard from '@/components/lenticular/LenticularCard.vue';
import { useLenticularCraftTiltPreview } from '@/composables/useLenticularCraftTiltPreview.js';
import { buildLenticularLayers, buildLenticularLayersTwo } from '@/utils/castloveMintForm.js';
import { LenticularEngine, DEFAULT_PHYSICS } from '@/utils/lenticular-engine.js';
const userId = ref('');
const nickname = ref('');
@ -226,7 +259,6 @@ const getCountdownBackgroundStyle = () => {
};
};
const rankIcons = [
'/static/square/icon1.png',
'/static/square/icon2.png',
@ -245,11 +277,269 @@ const likedWorks = ref([]);
// : current-, today-, week-
const likedTab = ref('current');
//
// transforms asset id
const lenticularTransformsMap = ref({});
const lenticularLayersByAsset = ref({});
const activeLenticularId = ref(null);
const gyroSourceLabel = ref('device');
//
const likedLenticularLayersByAsset = ref({});
const likedLenticularTransformsMap = ref({});
// 使
const lenticularPhysics = ref(null);
const lenticularEngine = ref(null);
let lenticularRafId = null;
// 使 useLenticularPreview
const lenticularLayersRef = ref([]);
function getLenticularLayers(assetId) {
return lenticularLayersByAsset.value[assetId] || [];
}
function getLenticularTransforms(assetId) {
return lenticularTransformsMap.value[assetId] || {};
}
//
function getLikedLenticularLayers(assetId) {
return likedLenticularLayersByAsset.value[assetId] || [];
}
function getLikedLenticularTransforms(assetId) {
return likedLenticularTransformsMap.value[assetId] || {};
}
function onLikedLenticularSimulate(assetId, x, y) {
simulateLikedLenticularTilt(assetId, x, y);
}
function simulateLikedLenticularTilt(assetId, x, y) {
if (!lenticularEngine.value) return;
const layers = likedLenticularLayersByAsset.value[assetId];
if (!layers || layers.length === 0) return;
lenticularEngine.value.setLayers(layers);
const renderState = lenticularEngine.value.feedSimulatedTilt(x, y ? y : 0);
const transforms = {};
for (const l of layers) {
const rs = renderState.layerOffsets.get(l.id);
const op = renderState.layerOpacities.get(l.id);
transforms[l.id] = {
x: rs ? rs.x : 0,
y: rs ? rs.y : 0,
opacity: op != null ? op : l.opacity,
};
}
likedLenticularTransformsMap.value = { ...likedLenticularTransformsMap.value, [assetId]: transforms };
}
async function loadLikedLenticularLayersForAsset(assetId) {
const item = likedWorks.value.find(w => w.id === assetId);
if (!item) return;
try {
const materialsRes = await getAssetMaterialsApi(assetId);
if (materialsRes.code === 200 && materialsRes.data) {
const materialsList = Array.isArray(materialsRes.data) ? materialsRes.data : materialsRes.data.materials || [];
let subjectUrl = item.cover_url;
let bgUrl = '';
for (const mat of materialsList) {
if (mat.material_type === 'main' && mat.material_url_signed) {
subjectUrl = mat.material_url_signed;
}
if (mat.material_type === 'bg' && mat.material_url_signed) {
bgUrl = mat.material_url_signed;
}
}
if (bgUrl) {
likedLenticularLayersByAsset.value[assetId] = buildLenticularLayersTwo(bgUrl, subjectUrl);
} else {
likedLenticularLayersByAsset.value[assetId] = buildLenticularLayers(subjectUrl);
}
initLikedTransformsForAsset(assetId);
} else {
likedLenticularLayersByAsset.value[assetId] = buildLenticularLayers(item.cover_url);
initLikedTransformsForAsset(assetId);
}
} catch (e) {
console.error('[hisWorks] 获取点赞作品素材列表失败:', e);
likedLenticularLayersByAsset.value[assetId] = buildLenticularLayers(item.cover_url);
initLikedTransformsForAsset(assetId);
}
}
function initLikedTransformsForAsset(assetId) {
const layers = likedLenticularLayersByAsset.value[assetId];
if (!layers || layers.length === 0) return;
const transforms = {};
for (const l of layers) {
transforms[l.id] = { x: 0, y: 0, opacity: l.opacity || 1 };
}
likedLenticularTransformsMap.value = { ...likedLenticularTransformsMap.value, [assetId]: transforms };
}
async function loadLenticularLayersForAsset(assetId) {
const item = exhibitionWorks.value.find(w => w.id === assetId);
if (!item) return;
try {
const materialsRes = await getAssetMaterialsApi(assetId);
if (materialsRes.code === 200 && materialsRes.data) {
const materialsList = Array.isArray(materialsRes.data) ? materialsRes.data : materialsRes.data.materials || [];
let subjectUrl = item.cover_url;
let bgUrl = '';
for (const mat of materialsList) {
if (mat.material_type === 'main' && mat.material_url_signed) {
subjectUrl = mat.material_url_signed;
}
if (mat.material_type === 'bg' && mat.material_url_signed) {
bgUrl = mat.material_url_signed;
}
}
if (bgUrl) {
lenticularLayersByAsset.value[assetId] = buildLenticularLayersTwo(bgUrl, subjectUrl);
} else {
lenticularLayersByAsset.value[assetId] = buildLenticularLayers(subjectUrl);
}
initTransformsForAsset(assetId);
} else {
lenticularLayersByAsset.value[assetId] = buildLenticularLayers(item.cover_url);
initTransformsForAsset(assetId);
}
} catch (e) {
console.error('[hisWorks] 获取素材列表失败:', e);
lenticularLayersByAsset.value[assetId] = buildLenticularLayers(item.cover_url);
}
}
//
function onLenticularSimulate(assetId, x, y) {
simulateLenticularTilt(assetId, x, y);
}
//
function simulateLenticularTilt(assetId, x, y) {
if (!lenticularEngine.value) return;
const layers = lenticularLayersByAsset.value[assetId];
if (!layers || layers.length === 0) return;
//
lenticularEngine.value.setLayers(layers);
//
const renderState = lenticularEngine.value.feedSimulatedTilt(x, y ? y : 0);
const transforms = {};
for (const l of layers) {
const rs = renderState.layerOffsets.get(l.id);
const op = renderState.layerOpacities.get(l.id);
transforms[l.id] = {
x: rs ? rs.x : 0,
y: rs ? rs.y : 0,
opacity: op != null ? op : l.opacity,
};
}
// Vue
lenticularTransformsMap.value = { ...lenticularTransformsMap.value, [assetId]: transforms };
}
//
function initLenticularEngine() {
if (lenticularEngine.value) return;
const physics = { ...DEFAULT_PHYSICS, parallaxDepth: 18 };
physics.gyroSimEnabled = false;
lenticularPhysics.value = physics;
lenticularEngine.value = new LenticularEngine(physics);
}
// transforms
function initTransformsForAsset(assetId) {
const layers = lenticularLayersByAsset.value[assetId];
if (!layers || layers.length === 0) return;
const transforms = {};
for (const l of layers) {
transforms[l.id] = { x: 0, y: 0, opacity: l.opacity || 1 };
}
lenticularTransformsMap.value = { ...lenticularTransformsMap.value, [assetId]: transforms };
}
// transforms
function startLenticularRenderLoop() {
if (lenticularRafId !== null) return;
const tick = () => {
// transforms
for (const assetId of Object.keys(lenticularLayersByAsset.value)) {
const layers = lenticularLayersByAsset.value[assetId];
if (!layers || layers.length === 0) continue;
lenticularEngine.value.setLayers(layers);
const renderState = lenticularEngine.value.feedSimulatedTilt(0, 0);
const transforms = {};
for (const l of layers) {
const rs = renderState.layerOffsets.get(l.id);
const op = renderState.layerOpacities.get(l.id);
transforms[l.id] = {
x: rs ? rs.x : 0,
y: rs ? rs.y : 0,
opacity: op != null ? op : l.opacity,
};
}
lenticularTransformsMap.value = { ...lenticularTransformsMap.value, [assetId]: transforms };
}
//
for (const assetId of Object.keys(likedLenticularLayersByAsset.value)) {
const layers = likedLenticularLayersByAsset.value[assetId];
if (!layers || layers.length === 0) continue;
lenticularEngine.value.setLayers(layers);
const renderState = lenticularEngine.value.feedSimulatedTilt(0, 0);
const transforms = {};
for (const l of layers) {
const rs = renderState.layerOffsets.get(l.id);
const op = renderState.layerOpacities.get(l.id);
transforms[l.id] = {
x: rs ? rs.x : 0,
y: rs ? rs.y : 0,
opacity: op != null ? op : l.opacity,
};
}
likedLenticularTransformsMap.value = { ...likedLenticularTransformsMap.value, [assetId]: transforms };
}
lenticularRafId = requestAnimationFrame(tick);
};
lenticularRafId = requestAnimationFrame(tick);
}
function stopLenticularRenderLoop() {
if (lenticularRafId !== null) {
cancelAnimationFrame(lenticularRafId);
lenticularRafId = null;
}
}
// slot 1=, slot 2=
const exhibitionAtSlot = computed(() => {
const slots = [null, null];
for (const item of exhibitionWorks.value) {
const pos = (item.slot_index ?? 0) - 1;
if (pos >= 0 && pos < 2) {
slots[pos] = item;
}
}
return slots;
});
//
const switchLikedTab = async (tab) => {
if (likedTab.value === tab) return;
likedTab.value = tab;
likedWorks.value = [];
//
likedLenticularLayersByAsset.value = {};
likedLenticularTransformsMap.value = {};
await loadLikedAssets();
};
@ -267,7 +557,17 @@ const loadExhibitedAssets = async () => {
like_count: slot.asset.like_count,
earnings: slot.asset.earnings || 0,
name: slot.asset.name,
}));
slot_index: slot.slot_index ?? 0,
is_lenticular: slot.asset.is_lenticular ?? false,
}))
.sort((a, b) => (a.slot_index ?? 0) - (b.slot_index ?? 0));
//
for (const item of exhibitionWorks.value) {
if (item.is_lenticular) {
loadLenticularLayersForAsset(item.id);
}
}
}
} catch (err) {
console.error('加载展出作品失败:', err);
@ -297,10 +597,18 @@ const loadLikedAssets = async () => {
like_count: item.like_count,
earnings: item.earnings,
name: item.name,
is_lenticular: item.is_lenticular ?? false,
status_text: index < 3 ? '排名进榜' : '潜力待挖',
score: item.like_count,
reward: Math.floor(item.earnings || 0),
}));
//
for (const item of likedWorks.value) {
if (item.is_lenticular) {
loadLikedLenticularLayersForAsset(item.id);
}
}
}
} catch (err) {
console.error('加载点赞作品失败:', err);
@ -308,6 +616,8 @@ const loadLikedAssets = async () => {
};
onMounted(() => {
initLenticularEngine();
startLenticularRenderLoop();
loadExhibitedAssets();
loadLikedAssets();
@ -323,6 +633,7 @@ onUnmounted(() => {
if (countdownTimer) {
clearInterval(countdownTimer);
}
stopLenticularRenderLoop();
});
</script>
@ -332,6 +643,7 @@ onUnmounted(() => {
position: relative;
}
/* 背景图片 */
.bg-image {
position: fixed;
top: 0;
@ -341,13 +653,14 @@ onUnmounted(() => {
z-index: 0;
}
/* 导航栏 */
.nav-bar {
position: relative;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
padding: 80rpx 32rpx 16rpx;
padding: 96rpx 32rpx 16rpx;
}
.nav-back {
@ -365,28 +678,39 @@ onUnmounted(() => {
}
.nav-title {
font-size: 48rpx;
font-size: 36rpx;
font-weight: 700;
color: #fff;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.9);
color: #5a2d82;
letter-spacing: 2rpx;
}
.nav-placeholder {
width: 80rpx;
width: 64rpx;
}
/* 内容区域 */
.scroll-content {
position: relative;
z-index: 1;
}
.section-block {
/* background: rgb(249 159 192 / 45%);
border-radius: 48rpx; */
padding: 16rpx;
}
/* 区块 */
.section-1 {
margin-bottom: 8rpx;
}
/* 点赞标签容器 */
.liked-tabs {
display: flex;
gap: 16rpx;
margin-bottom: 16rpx;
}
/* 区块标签 */
.section-label {
position: relative;
display: inline-flex;
@ -406,12 +730,6 @@ onUnmounted(() => {
opacity: 1;
}
.liked-tabs {
display: flex;
gap: 16rpx;
margin-bottom: 16rpx;
}
.section-label-bg {
position: absolute;
top: 0;
@ -430,6 +748,7 @@ onUnmounted(() => {
padding: 0 28rpx;
}
/* 在展作品网格 */
.exhibition-grid {
display: flex;
flex-direction: row;
@ -454,6 +773,7 @@ onUnmounted(() => {
.card-tilt-right {
transform: rotate(4deg) translateY(10rpx);
margin-left: 32rpx;
border-radius: 32rpx;
box-shadow: 16rpx 16rpx 16rpx rgba(229, 76, 93, 0.9);
}
@ -486,6 +806,18 @@ onUnmounted(() => {
z-index: 2;
}
.card-lenticular {
width: 88%;
height: 93%;
left: 5%;
top: 4%;
border-radius: 24rpx;
transform-origin: center center;
position: relative;
z-index: 3;
overflow: hidden;
}
.card-rate-badge {
position: absolute;
top: 16rpx;
@ -498,6 +830,7 @@ onUnmounted(() => {
z-index: 9;
}
/* 图片下方收益 */
.card-income-row {
position: absolute;
bottom: -88rpx;
@ -516,7 +849,6 @@ onUnmounted(() => {
z-index: 2;
margin-right: -16rpx;
left: 20rpx;
top: 8rpx;
}
.card-income-text-wrap {
@ -527,7 +859,8 @@ onUnmounted(() => {
#B94E73 100%);
border-radius: 999rpx;
padding: 8rpx 20rpx 8rpx 40rpx;
box-shadow: 0 4rpx 12rpx rgba(255, 143, 158, 0.2),
box-shadow:
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
display: flex;
align-items: center;
@ -541,6 +874,7 @@ onUnmounted(() => {
text-align: center;
}
.heart-icon {
width: 28rpx;
height: 28rpx;
@ -553,7 +887,8 @@ onUnmounted(() => {
#B94E73 100%);
border-radius: 999rpx;
padding: 2rpx 16rpx;
box-shadow: 0 4rpx 12rpx rgba(255, 143, 158, 0.2),
box-shadow:
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
display: flex;
}
@ -566,10 +901,6 @@ onUnmounted(() => {
/* 倒计时背景 */
.countdown-background {
/* position: absolute;
bottom: 20rpx;
right: 30%;
transform: translateX(-50%); */
width: 140rpx;
height: 36rpx;
background: linear-gradient(165deg, #F0E4B1 0%, #F08399 50%, #B94E73 90%, #834B9E 100%);
@ -593,11 +924,32 @@ onUnmounted(() => {
justify-content: center;
}
.empty-exhibition {
display: flex;
align-items: center;
justify-content: center;
padding: 80rpx 0;
/* 空状态 */
.empty-card {
width: 248rpx;
height: 380rpx;
border-radius: 20rpx;
overflow: visible;
position: relative;
margin: 0 32rpx;
}
.empty-card-left {
transform: rotate(-4deg) translateY(10rpx);
}
.empty-card-right {
transform: rotate(4deg) translateY(10rpx);
}
.empty-cover {
width: 88%;
height: 92%;
border-radius: 80rpx;
position: relative;
z-index: 3;
padding: 16rpx;
opacity: 0.5;
}
.empty-text {
@ -605,6 +957,7 @@ onUnmounted(() => {
color: #b09cc0;
}
/* 当前点赞列表 */
.liked-list {
max-height: 732rpx;
}
@ -620,13 +973,12 @@ onUnmounted(() => {
display: flex;
align-items: center;
background: #ffffff50;
border-radius: 32rpx;
border-radius: 48rpx;
padding: 24rpx 20rpx;
gap: 16rpx;
overflow: hidden;
box-sizing: border-box;
width: 80%;
padding-left: 13%;
justify-content: space-between;
}
.liked-item-first {
@ -642,7 +994,6 @@ onUnmounted(() => {
/* 排名图标 - 排名越靠前越大 */
.rank-icon {
flex-shrink: 0;
/* margin-right: 8rpx; */
position: relative;
left: 32rpx;
}
@ -662,12 +1013,12 @@ onUnmounted(() => {
height: 88rpx;
}
/* 作品封面 */
.liked-cover-wrap {
width: 88rpx;
height: 88rpx;
flex-shrink: 0;
margin-left: -18rpx;
margin-right: 48rpx;
position: relative;
}
@ -682,6 +1033,17 @@ onUnmounted(() => {
padding: 0.25rem;
}
.liked-lenticular {
width: 90%;
height: 90%;
border-radius: 24rpx;
transform: rotate(-22deg);
transform-origin: center center;
position: relative;
z-index: 3;
overflow: hidden;
}
.liked-cover-frame {
position: absolute;
top: 0;
@ -693,12 +1055,12 @@ onUnmounted(() => {
transform-origin: center center;
}
/* 作品信息 */
.liked-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
overflow: hidden;
margin: 0 16rpx 0 32rpx;
}
.liked-status {
@ -731,26 +1093,31 @@ onUnmounted(() => {
margin-top: 4rpx;
}
/* 右侧奖励 */
.liked-reward {
min-width: 136rpx;
padding: 8rpx 16rpx;
background: rgba(255, 255, 255, 0.3);
border-radius: 999rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 8rpx;
flex-shrink: 0;
}
.reward-token-icon {
width: 56rpx;
height: 56rpx;
margin-right: 8rpx;
}
.reward-amount {
font-size: 28rpx;
color: #c060e0;
font-weight: 700;
color: #fff;
font-weight: 600;
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.7);
}
/* 空状态 */
.empty-liked {
padding: 60rpx 0;
display: flex;

View File

@ -1448,6 +1448,7 @@ onShow(() => {
box-sizing: border-box;
width: 80%;
padding-left: 13%;
justify-content: space-between;
}
.liked-item-first {

View File

@ -7,7 +7,7 @@
<!-- 骨架屏 -->
<view v-if="loading" class="grid-skeleton">
<view v-for="i in 12" :key="i" class="skeleton-card">
<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>
@ -18,137 +18,203 @@
<!-- 内容网格 -->
<view v-else class="items-grid">
<view v-for="(item, index) in items" :key="item.id || index" class="grid-card" @click="handleCardClick(item)">
<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="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">
<image class="card-image" :src="item.cover_url || item.cover_image || ''" mode="aspectFill"></image>
<view class="like-badge">
<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
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">
<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
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>
<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">
<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 { ref, watch, onMounted, onUnmounted } from "vue";
import { onShow } from "@dcloudio/uni-app";
import { getHotRankingApi } from '@/utils/api.js'
import { getHotRankingApi } from "@/utils/api.js";
const props = defineProps({
title: {
type: String,
default: '在线榜单'
default: "在线榜单",
},
dimension: {
type: String,
default: 'displaying'
}
})
default: "displaying",
},
});
const emit = defineEmits(['cardClick'])
const emit = defineEmits(["cardClick"]);
const items = ref([])
const loading = ref(false)
const likingMap = ref({})
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()
})
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()
}
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)
}
emit("cardClick", item);
};
//
const onAssetLiked = ({ asset_id, data }) => {
const index = items.value.findIndex(item => (item.asset_id || item.id) === asset_id)
const index = items.value.findIndex(
(item) => (item.asset_id || item.id) === asset_id,
);
if (index !== -1) {
const updatedItems = [...items.value]
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
like_count:
data?.new_like_count ?? (updatedItems[index].like_count || 0) + 1,
};
items.value = updatedItems;
//
likingMap.value = { ...likingMap.value, [asset_id]: true }
likingMap.value = { ...likingMap.value, [asset_id]: true };
setTimeout(() => {
likingMap.value = { ...likingMap.value, [asset_id]: false }
}, 600)
likingMap.value = { ...likingMap.value, [asset_id]: false };
}, 600);
}
}
};
//
const loadData = async () => {
loading.value = true
loading.value = true;
try {
const res = await getHotRankingApi(props.dimension, null, 1, 12)
const res = await getHotRankingApi(props.dimension, null, 1, 11);
if (res.code === 200 && res.data?.items) {
items.value = res.data.items.map(item => ({
items.value = res.data.items.map((item) => ({
...item,
id: item.id || item.asset_id
}))
id: item.id || item.asset_id,
}));
}
} catch (e) {
console.error('[HotCategoryBlock] 加载数据失败', e?.message ?? e)
console.error("[HotCategoryBlock] 加载数据失败", e?.message ?? e);
} finally {
loading.value = false
loading.value = false;
}
}
};
onMounted(() => {
uni.$on('assetLiked', onAssetLiked)
})
uni.$on("assetLiked", onAssetLiked);
});
onShow(()=>{
loadData()
})
onShow(() => {
loadData();
});
onUnmounted(() => {
uni.$off('assetLiked', onAssetLiked)
})
uni.$off("assetLiked", onAssetLiked);
});
</script>
<style scoped>
.hot-category-block {
padding: 14rpx 14rpx 0;
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%);
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;
}
@ -163,8 +229,13 @@ onUnmounted(() => {
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;
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;
@ -196,14 +267,28 @@ onUnmounted(() => {
overflow: hidden;
}
/* 骨架屏第一排3个大图 */
.skeleton-card:nth-child(-n + 3) {
width: calc(33.333% - 12rpx);
}
.skeleton-image {
width: 100%;
height: 320rpx;
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;
@ -240,22 +325,73 @@ onUnmounted(() => {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.grid-card {
width: calc(25% - 12rpx);
border-radius: 16rpx;
overflow: hidden;
/* 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);
@ -264,42 +400,77 @@ onUnmounted(() => {
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%);
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;
/* overflow: hidden; */
}
.top-badge-1,
.top-badge-2,
.top-badge-3 {
padding-left: 24rpx;
}
.badge-rank {
width: 80rpx;
height: 32rpx;
color: #FFFABD;
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;
;
text-shadow: -1px 1px 4px #ce0909d6;
}
/* 用户信息 */
.card-info {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16rpx 16rpx;
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: center;
align-items: flex-end;
}
.user-avatar {
@ -310,8 +481,9 @@ onUnmounted(() => {
}
.user-name {
font-size: 22rpx;
color: #fff;
font-size: 18rpx;
font-weight: 400;
color: #554545;
max-width: 120rpx;
overflow: hidden;
text-overflow: ellipsis;
@ -327,12 +499,22 @@ onUnmounted(() => {
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%);
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;
@ -369,15 +551,19 @@ onUnmounted(() => {
}
.wf-like-wave-outer {
background: radial-gradient(circle,
rgba(255, 107, 107, 0.8) 0%,
transparent 70%);
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%);
background: radial-gradient(
circle,
rgba(255, 184, 0, 0.6) 0%,
transparent 70%
);
}
.wf-like-wave-active {

View File

@ -1,39 +1,76 @@
<template>
<view class="square-container">
<!-- 背景图片 -->
<image class="bg-wrapper" src="/static/square/squearbj1.png" mode="aspectFill"></image>
<image
class="bg-wrapper"
src="/static/square/squearbj1.png"
mode="aspectFill"
></image>
<!-- Header组件 -->
<Header :showGuideIcon="true" :showTaskIcon="true" :showStarActivityIcon="true" backIconColor="#e6e6e6" />
<Header
:showGuideIcon="true"
:showTaskIcon="true"
:showStarActivityIcon="true"
backIconColor="#e6e6e6"
/>
<!-- 蒙层 - 导航栏展开时显示 -->
<view v-if="navExpanded" class="nav-mask" @click="navExpanded = false"></view>
<view
v-if="navExpanded"
class="nav-mask"
@click="navExpanded = false"
></view>
<!-- 排行榜弹窗 -->
<RankingModal :visible="showRankingModal" :parent-active="true" :star-id="currentStarId"
@update:visible="handleRankingModalClose" @visit="handleRankingVisit" />
<RankingModal
:visible="showRankingModal"
:parent-active="true"
:star-id="currentStarId"
@update:visible="handleRankingModalClose"
@visit="handleRankingVisit"
/>
<!-- 底部导航栏 -->
<BottomNav :activeTab="4" :isExpanded="navExpanded" @update:activeTab="handleTabChange"
@update:isExpanded="navExpanded = $event" />
<BottomNav
:activeTab="4"
:isExpanded="navExpanded"
@update:activeTab="handleTabChange"
@update:isExpanded="navExpanded = $event"
/>
<!-- 全局引导遮罩 -->
<GuideOverlay />
<!-- 内容区域 -->
<scroll-view class="content-wrapper" scroll-y :show-scrollbar="false" :bounce="false"
@scrolltolower="handleScrollToLower">
<scroll-view
class="content-wrapper"
scroll-y
:show-scrollbar="false"
:bounce="false"
@scrolltolower="handleScrollToLower"
>
<!-- 区域一顶部运营轮播图 -->
<view class="banner-section">
<BannerCarousel :bannerActivities="bannerActivities" @activityClick="handleActivityClick"
@top3Click="showRankingModal = true" />
<BannerCarousel
:bannerActivities="bannerActivities"
@activityClick="handleActivityClick"
@top3Click="showRankingModal = true"
/>
</view>
<ContentTabs class="tabs" :modelValue="activeContentTab" @update:modelValue="activeContentTab = $event" />
<ContentTabs
class="tabs"
:modelValue="activeContentTab"
@update:modelValue="activeContentTab = $event"
/>
<!-- 在线榜单区块 -->
<view class="hot-category-wrapper">
<HotCategoryBlock :dimension="activeContentTab" @cardClick="handleCardClick" />
<HotCategoryBlock
:dimension="activeContentTab"
@cardClick="handleCardClick"
/>
<view class="hot-more-btn" @click="goToHotCategoryMore">
<text class="hot-more-text">查看更多</text>
</view>
@ -41,99 +78,129 @@
<!-- 区域二主Tab标签区星卡/吧唧/海报 -->
<view class="main-tab-section">
<view v-for="(tab, index) in mainTabs" :key="index" class="tab-item" @click="handleMainTabClick(tab)">
<image class="tab-icon" :src="tab.icon" mode="aspectFit"
:style="{ width: tab.width + 'rpx', height: tab.height + 'rpx', borderRadius: tab.type === 'badge' ? '50%' : '0', transform: 'rotate(' + tab.rotate + 'deg)' }">
<view
v-for="(tab, index) in mainTabs"
:key="index"
class="tab-item"
@click="handleMainTabClick(tab)"
>
<image
class="tab-icon"
:src="tab.icon"
mode="aspectFit"
:style="{
width: tab.width + 'rpx',
height: tab.height + 'rpx',
borderRadius: tab.type === 'badge' ? '50%' : '0',
transform: 'rotate(' + tab.rotate + 'deg)',
}"
>
</image>
<text class="tab-name">{{ tab.name }}</text>
</view>
</view>
<!-- 区域三分类标签区 -->
<view id="category-section" class="category-section" :class="{ fixed: isFixed }">
<view
id="category-section"
class="category-section"
:class="{ fixed: isFixed }"
>
<scroll-view class="category-scroll" scroll-x :show-scrollbar="false">
<view v-for="(category, index) in categories" :key="index" class="category-item"
:class="{ active: activeCategoryTab === category.value }" @click="handleCategoryChange(category.value)">
<view
v-for="(category, index) in categories"
:key="index"
class="category-item"
:class="{ active: activeCategoryTab === category.value }"
@click="handleCategoryChange(category.value)"
>
<text class="category-text">{{ category.label }}</text>
</view>
</scroll-view>
</view>
<!-- 区域四创作网格列表 -->
<CreationGrid :screenWidth="screenWidth" :screenHeight="screenHeight" :bannerBottom="bannerBottomPx"
:category="activeCategoryTab" :isActive="isActive" @cardClick="handleCardClick" ref="creationGridRef" />
<CreationGrid
:screenWidth="screenWidth"
:screenHeight="screenHeight"
:bannerBottom="bannerBottomPx"
:category="activeCategoryTab"
:isActive="isActive"
@cardClick="handleCardClick"
ref="creationGridRef"
/>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { onLoad, onShow, onHide } from '@dcloudio/uni-app'
import { useStore } from 'vuex'
import Header from '../components/Header.vue'
import BottomNav from '../components/BottomNav.vue'
import GuideOverlay from '@/components/GuideOverlay.vue'
import RankingModal from '../components/RankingModal.vue'
import BannerCarousel from './components/BannerCarousel.vue'
import ContentTabs from './components/ContentTabs.vue'
import HotCategoryBlock from './components/HotCategoryBlock.vue'
import CreationGrid from './components/CreationGrid.vue'
import { ref, computed, watch, onMounted, onUnmounted } from "vue";
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
import { useStore } from "vuex";
import Header from "../components/Header.vue";
import BottomNav from "../components/BottomNav.vue";
import GuideOverlay from "@/components/GuideOverlay.vue";
import RankingModal from "../components/RankingModal.vue";
import BannerCarousel from "./components/BannerCarousel.vue";
import ContentTabs from "./components/ContentTabs.vue";
import HotCategoryBlock from "./components/HotCategoryBlock.vue";
import CreationGrid from "./components/CreationGrid.vue";
// import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
import { useBanner } from './composables/useBanner.js'
import { doubleTapLike } from '@/utils/likeHelper.js'
import { useBanner } from "./composables/useBanner.js";
import { doubleTapLike } from "@/utils/likeHelper.js";
// ========== Store & User Info ==========
const store = useStore()
const store = useStore();
// const currentUserNickname = computed(() => store.state.user?.userInfo?.nickname || '')
const currentStarId = ref(uni.getStorageSync('star_id') || null)
const currentStarId = ref(uni.getStorageSync("star_id") || null);
// ========== UI State ==========
const activeContentTab = ref('displaying')
const activeCategoryTab = ref('hot')
const navExpanded = ref(false)
const showRankingModal = ref(false)
const isActive = ref(true)
const isFixed = ref(false)
const creationGridRef = ref(null)
const cardTapTimers = {}
const likingMap = ref({})
const activeContentTab = ref("displaying");
const activeCategoryTab = ref("hot");
const navExpanded = ref(false);
const showRankingModal = ref(false);
const isActive = ref(true);
const isFixed = ref(false);
const creationGridRef = ref(null);
const cardTapTimers = {};
const likingMap = ref({});
// Tab
const mainTabs = ref([
{
name: '星卡',
type: 'star_card',
icon: '/static/square/xingka.png',
name: "星卡",
type: "star_card",
icon: "/static/square/xingka.png",
width: 96,
height: 96,
rotate: -15
rotate: -15,
},
{
name: '吧唧',
type: 'badge',
icon: '/static/square/baji.png',
name: "吧唧",
type: "badge",
icon: "/static/square/baji.png",
width: 100,
height: 100,
rotate: 0
rotate: 0,
},
{
name: '海报',
type: 'poster',
icon: '/static/square/haibao.png',
name: "海报",
type: "poster",
icon: "/static/square/haibao.png",
width: 100,
height: 108,
rotate: -15
}
rotate: -15,
},
]);
// ========== ==========
const categories = ref([
{ label: '热门作品', value: 'hot' },
{ label: '最新作品', value: 'latest' },
{ label: '星卡', value: 'star_card' },
{ label: '吧唧', value: 'badge' },
{ label: '海报', value: 'poster' }
])
{ label: "热门作品", value: "hot" },
{ label: "最新作品", value: "latest" },
{ label: "星卡", value: "star_card" },
{ label: "吧唧", value: "badge" },
{ label: "海报", value: "poster" },
]);
// ========== Watch activeContentTab ==========
// watch(activeContentTab, (newTab) => {
@ -144,17 +211,16 @@ const categories = ref([
// })
// ========== Screen Info ==========
const screenWidth = ref(375)
const screenHeight = ref(812)
const screenWidth = ref(375);
const screenHeight = ref(812);
// ========== Composables ==========
const {
bannerActivities,
loadBannerActivities,
} = useBanner()
const { bannerActivities, loadBannerActivities } = useBanner();
// banner(216+360rpx) + tab(16+80rpx) + (8rpx) 680rpx
const bannerBottomPx = computed(() => Math.round(screenWidth.value / 750 * 715))
const bannerBottomPx = computed(() =>
Math.round((screenWidth.value / 750) * 715),
);
// ========== Handlers ==========
@ -164,7 +230,6 @@ const handleCardClick = (card) => {
clearTimeout(cardTapTimers[card.id]);
delete cardTapTimers[card.id];
//
likingMap.value = { ...likingMap.value, [card.id]: true };
setTimeout(() => {
@ -173,7 +238,7 @@ const handleCardClick = (card) => {
doubleTapLike(card.id, card.exhibition_id || 0, (success) => {
if (success) {
uni.showToast({ title: '点赞成功', icon: 'success' })
uni.showToast({ title: "点赞成功", icon: "success" });
}
});
} else {
@ -181,96 +246,103 @@ const handleCardClick = (card) => {
if (card.id) {
cardTapTimers[card.id] = setTimeout(() => {
delete cardTapTimers[card.id];
uni.navigateTo({ url: `/pages/asset-detail/asset-detail?asset_id=${card.id}` });
uni.navigateTo({
url: `/pages/asset-detail/asset-detail?asset_id=${card.id}`,
});
}, 300);
}
}
}
};
const handleScrollToLower = () => {
if (creationGridRef.value) {
creationGridRef.value.loadMore()
creationGridRef.value.loadMore();
}
}
};
const handleActivityClick = (item) => {
uni.navigateTo({
url: `/pages/support-activity/index?id=${item.id}`,
})
}
});
};
const handleRankingVisit = ({ userId, nickname }) => {
showRankingModal.value = false
showRankingModal.value = false;
uni.navigateTo({
url: `/pages/profile/hisWorks?userId=${userId}&nickname=${encodeURIComponent(nickname)}`
url: `/pages/profile/hisWorks?userId=${userId}&nickname=${encodeURIComponent(nickname)}`,
});
}
};
const handleRankingModalClose = (visible) => {
showRankingModal.value = visible
showRankingModal.value = visible;
if (!visible && store.state.guide.componentMode) {
uni.$emit('guide:closeComponent')
uni.$emit("guide:closeComponent");
}
}
};
const goToHotCategoryMore = () => {
const title = activeContentTab.value === 'displaying' ? '日榜' : activeContentTab.value === 'week' ? '周榜' : '月榜'
const title =
activeContentTab.value === "displaying"
? "日榜"
: activeContentTab.value === "week"
? "周榜"
: "月榜";
uni.navigateTo({
url: `/pages/square/hot-category-more?dimension=${activeContentTab.value}&title=${encodeURIComponent(title)}`
})
}
url: `/pages/square/hot-category-more?dimension=${activeContentTab.value}&title=${encodeURIComponent(title)}`,
});
};
const handleTabChange = (newTab) => {
if (newTab === 4) {
navExpanded.value = false
return
navExpanded.value = false;
return;
}
const routes = [
'/pages/ai-dazi/index',
'/pages/starbook/index',
'/pages/castlove/mall',
'/pages/starcity/index',
'/pages/square/square'
]
"/pages/ai-dazi/index",
"/pages/starbook/index",
"/pages/castlove/mall",
"/pages/starcity/index",
"/pages/square/square",
];
if (newTab >= 0 && newTab < routes.length) {
uni.navigateTo({
url: routes[newTab]
})
url: routes[newTab],
});
}
}
};
const handleCategoryChange = (value) => {
if (activeCategoryTab.value === value) return
activeCategoryTab.value = value
}
if (activeCategoryTab.value === value) return;
activeCategoryTab.value = value;
};
// ========== Tile Change Callback ==========
const handleTileChange = () => { }
const handleTileChange = () => {};
// ========== Reset Square ==========
const resetSquare = async () => { }
const resetSquare = async () => {};
// ========== Lifecycle ==========
onMounted(() => {
const info = uni.getSystemInfoSync()
screenWidth.value = info.windowWidth
screenHeight.value = info.windowHeight
const info = uni.getSystemInfoSync();
screenWidth.value = info.windowWidth;
screenHeight.value = info.windowHeight;
resetSquare()
loadBannerActivities()
})
resetSquare();
loadBannerActivities();
});
onShow(() => {
isActive.value = true
activeContentTab.value = 'displaying'
activeCategoryTab.value = 'hot'
})
isActive.value = true;
activeContentTab.value = "displaying";
activeCategoryTab.value = "hot";
});
onHide(() => {
isActive.value = false
})
isActive.value = false;
});
// onLoad((options) => {
// if (options && 'guide_debug' in options) {
@ -298,15 +370,15 @@ onHide(() => {
// })
//
uni.$on('guide:openComponent', (componentName) => {
if (componentName === 'RankingModal') {
showRankingModal.value = true
uni.$on("guide:openComponent", (componentName) => {
if (componentName === "RankingModal") {
showRankingModal.value = true;
}
})
});
onUnmounted(() => {
uni.$off('guide:openComponent')
})
uni.$off("guide:openComponent");
});
</script>
<style scoped>
@ -361,7 +433,11 @@ onUnmounted(() => {
justify-content: space-around;
width: 200rpx;
height: 200rpx;
background: linear-gradient(135deg, rgba(240, 228, 177, 0.3), rgba(240, 131, 153, 0.3));
background: linear-gradient(
135deg,
rgba(240, 228, 177, 0.3),
rgba(240, 131, 153, 0.3)
);
border-radius: 24rpx;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 157, 0.2);
transition: all 0.3s;
@ -416,7 +492,7 @@ onUnmounted(() => {
}
.category-item.active {
background: linear-gradient(135deg, #F0E4B1, #F08399);
background: linear-gradient(135deg, #f0e4b1, #f08399);
box-shadow: 0 4rpx 12rpx rgba(255, 107, 157, 0.4);
}
@ -446,8 +522,15 @@ onUnmounted(() => {
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;
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 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB