feat: 样式调整
@ -11,8 +11,10 @@ import { getLaserApiBaseUrl } from '@/utils/api.js'
|
||||
import { buildRenderConfigs } from '@/utils/laser-card/laserPresets.js'
|
||||
|
||||
/**
|
||||
* 镭射专用 fetch(直接请求本地 Gateway,不走远程 DEV_BASE)
|
||||
* 镭射专用请求(直接请求本地 Gateway,不走远程 DEV_BASE)
|
||||
* 返回格式与 api.js 的 request() 一致:{ code, message, data }
|
||||
*
|
||||
* 注意:uni-app 移动端没有 fetch 全局,必须用 uni.request 封装 Promise
|
||||
*/
|
||||
async function laserRequest(opts) {
|
||||
const base = getLaserApiBaseUrl()
|
||||
@ -22,19 +24,26 @@ async function laserRequest(opts) {
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
const fetchOpts = {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url,
|
||||
method: opts.method || 'GET',
|
||||
headers,
|
||||
data: opts.data || {},
|
||||
header: headers,
|
||||
timeout: 60000,
|
||||
success: (res) => {
|
||||
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||
reject(new Error(res.data?.message || `HTTP ${res.statusCode}`))
|
||||
return
|
||||
}
|
||||
if (opts.data) {
|
||||
fetchOpts.body = JSON.stringify(opts.data)
|
||||
}
|
||||
const resp = await fetch(url, fetchOpts)
|
||||
const json = await resp.json()
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(json?.message || `HTTP ${resp.status}`)
|
||||
}
|
||||
return json
|
||||
// 兼容后端返回的两种结构:{code, message, data} 或 裸 data
|
||||
resolve(res.data)
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(new Error(err.errMsg || '网络请求失败'))
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function useLaserDifyGenerate() {
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"appid" : "__UNI__F199FF4",
|
||||
"description" : "",
|
||||
"versionName" : "1.0.5",
|
||||
"versionCode" : 106,
|
||||
"versionCode" : 107,
|
||||
"transformPx" : false,
|
||||
/* 5+App特有相关 */
|
||||
"app-plus" : {
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
<view v-if="isLaserCardCraft" class="laser-bg-veil" aria-hidden="true" />
|
||||
|
||||
<!-- 镭射卡:左上角关闭 -->
|
||||
<view v-if="isLaserCardCraft" class="laser-close-hit" @click="handleBack">
|
||||
<!-- <view v-if="isLaserCardCraft" class="laser-close-hit" @click="handleBack">
|
||||
<text class="laser-close-x">×</text>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<scroll-view
|
||||
|
||||
@ -508,7 +508,7 @@ defineExpose({
|
||||
.star-activity-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
/* gap: 8rpx; */
|
||||
/* width: 68rpx; */
|
||||
/* 控制整体宽度,根据图标大小调整 */
|
||||
height: 156rpx;
|
||||
|
||||
@ -11,7 +11,11 @@
|
||||
|
||||
<!-- 渐变标题 -->
|
||||
<view class="title-wrap">
|
||||
<text class="header-title">数据看板</text>
|
||||
<image
|
||||
class="title-icon"
|
||||
src="/static/dashboard/dashboard-title.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="header-content">
|
||||
@ -105,7 +109,7 @@ defineEmits(["update:activeTab"]);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0 32rpx 32rpx;
|
||||
top: 192rpx;
|
||||
top: 272rpx;
|
||||
}
|
||||
|
||||
.mascot {
|
||||
@ -122,18 +126,32 @@ defineEmits(["update:activeTab"]);
|
||||
|
||||
.title-wrap {
|
||||
margin-bottom: 24rpx;
|
||||
/* 让出 iOS 状态栏 + 顶部 nav 区域,避免标题被遮挡 */
|
||||
// padding-top: 64rpx;
|
||||
position: absolute;
|
||||
right: 40rpx;
|
||||
top: 160rpx;
|
||||
}
|
||||
|
||||
.title-icon{
|
||||
width: 240rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
/* 实色兜底:在不支持 -webkit-background-clip 的环境(微信小程序、部分 Android WebView、老 iOS WebView)也能看见 */
|
||||
color: #ffb199;
|
||||
/* 渐变文字效果:现代环境(H5/新版 WebView)会用渐变覆盖实色 */
|
||||
background: linear-gradient(90deg, #ffe5b4 0%, #ffb199 50%, #ff8a95 100%);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
-webkit-text-fill-color: transparent;
|
||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
|
||||
.header-tabs {
|
||||
display: flex;
|
||||
// background: rgba(0, 0, 0, 0.15);
|
||||
@ -172,9 +190,11 @@ defineEmits(["update:activeTab"]);
|
||||
.tab-icon {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
/* 修复:原来是 position: fixed; left: 0; top: 0; (典型复制粘贴 bug,会让两个 tab 图标都叠在屏幕左上角) */
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@ -145,7 +145,7 @@ onShow(() => {
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
top: 96rpx;
|
||||
left: 32rpx;
|
||||
width: 80rpx;
|
||||
|
||||
@ -1,28 +1,19 @@
|
||||
<template>
|
||||
<view class="banner-carousel" @click.stop>
|
||||
<swiper
|
||||
class="banner-swiper"
|
||||
:autoplay="true"
|
||||
:interval="4000"
|
||||
:duration="400"
|
||||
:circular="true"
|
||||
:indicator-dots="false"
|
||||
@change="onSwiperChange"
|
||||
>
|
||||
<swiper-item @click.stop="$emit('top3Click')" style="display: flex;
|
||||
<swiper class="banner-swiper" :autoplay="true" :interval="4000" :duration="400" :circular="true"
|
||||
:indicator-dots="false" @change="onSwiperChange">
|
||||
<!-- <swiper-item @click.stop="$emit('top3Click')" style="display: flex;
|
||||
align-items: center;">
|
||||
<BannerTop3 @dataLoaded="onTop3DataLoaded" @top3Click="$emit('top3Click')" />
|
||||
</swiper-item> -->
|
||||
<swiper-item v-for="item in bannerActivities" :key="item.id" @tap.stop="$emit('activityClick', item)">
|
||||
<image class="banner-activity-img" :src="item.cover_image || '/static/avatar/1.jpeg'" mode="aspectFill" />
|
||||
</swiper-item>
|
||||
<swiper-item
|
||||
v-for="item in bannerActivities"
|
||||
:key="item.id"
|
||||
@tap.stop="$emit('activityClick', item)"
|
||||
>
|
||||
<image
|
||||
class="banner-activity-img"
|
||||
:src="item.cover_image || '/static/avatar/1.jpeg'"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<swiper-item v-for="(banner, index) in banners" :key="banner.id || index" @click="emit('bannerClick', banner)">
|
||||
<image class="banner-image" :src="banner.image_url" mode="widthFill"></image>
|
||||
<!-- <view class="banner-overlay">
|
||||
<text class="banner-title">{{ banner.title }}</text>
|
||||
</view> -->
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
@ -33,9 +24,14 @@ import { ref } from 'vue'
|
||||
import BannerTop3 from '../../components/BannerTop3.vue'
|
||||
|
||||
defineProps({
|
||||
bannerActivities: { type: Array, default: () => [] }
|
||||
bannerActivities: { type: Array, default: () => [] },
|
||||
// 运营轮播图列表
|
||||
banners: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
defineEmits(['activityClick', 'top3Click'])
|
||||
const emit = defineEmits(['activityClick', 'top3Click', 'bannerClick'])
|
||||
|
||||
const currentIndex = ref(0)
|
||||
|
||||
@ -69,10 +65,34 @@ const onTop3DataLoaded = (items) => {
|
||||
|
||||
.banner-activity-img {
|
||||
width: 100%;
|
||||
height: 312rpx;
|
||||
height: 344rpx;
|
||||
display: block;
|
||||
border-radius:24rpx;
|
||||
border-radius: 24rpx;
|
||||
position: relative;
|
||||
top: 24rpx;
|
||||
/* top: 24rpx; */
|
||||
}
|
||||
|
||||
.banner-image {
|
||||
width: 100%;
|
||||
height: 356rpx;
|
||||
display: block;
|
||||
border-radius: 24rpx;
|
||||
position: relative;
|
||||
bottom: 8rpx;
|
||||
}
|
||||
|
||||
.banner-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 24rpx;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.6), transparent);
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
font-size: 28rpx;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -121,9 +121,9 @@ 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",
|
||||
0: "/static/square/top/TOP1biankuang1.png",
|
||||
1: "/static/square/top/TOP2biankuang2.png",
|
||||
2: "/static/square/top/TOP3biankuangpng3.png",
|
||||
};
|
||||
const TOP_ICON_MAP = {
|
||||
0: "/static/square/top/TOP1icon.png",
|
||||
@ -209,7 +209,7 @@ onUnmounted(() => {
|
||||
.hot-category-block {
|
||||
padding: 19rpx 9.5rpx;
|
||||
border-radius: 24rpx;
|
||||
opacity: 0.8;
|
||||
/* opacity: 0.8; */
|
||||
background: linear-gradient(
|
||||
161.28deg,
|
||||
rgba(255, 90, 93, 0.2) 16.63%,
|
||||
@ -337,11 +337,11 @@ onUnmounted(() => {
|
||||
|
||||
/* 第一排:3个大图突出显示 */
|
||||
.grid-card:nth-child(-n + 3) {
|
||||
width: calc(33% - 18rpx);
|
||||
width: calc(33% - 24rpx);
|
||||
}
|
||||
|
||||
.grid-card:nth-child(-n + 3) .card-image {
|
||||
height: 252rpx;
|
||||
height: 264rpx;
|
||||
box-shadow: 3px 3px 4.5px 2px rgba(0, 0, 0, 0.15);
|
||||
backdrop-filter: blur(0px);
|
||||
padding: 8rpx;
|
||||
@ -350,7 +350,7 @@ onUnmounted(() => {
|
||||
|
||||
.card-image {
|
||||
width: 100%;
|
||||
height: 192rpx;
|
||||
height: 224rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@ -401,6 +401,7 @@ onUnmounted(() => {
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-shadow: 2px 2px 4.5px 0px #F04B4B40;
|
||||
}
|
||||
|
||||
.card-bottom-1,
|
||||
|
||||
@ -1,9 +1,23 @@
|
||||
import { ref } from 'vue'
|
||||
import { getActivityListApi } from '@/utils/api.js'
|
||||
import { getActivityListApi, getMintingActivitiesApi } from '@/utils/api.js'
|
||||
|
||||
// 默认 banner(接口失败/无数据时兜底)
|
||||
const FALLBACK_BANNER = [
|
||||
{
|
||||
image_url: '/static/sucai/image-05.png',
|
||||
title: '这河非遇美学宇宙联名',
|
||||
link_type: 'activity',
|
||||
link_value: '1'
|
||||
}
|
||||
]
|
||||
|
||||
export function useBanner() {
|
||||
// 旧的活动 banner(square 顶部)
|
||||
const bannerActivities = ref([])
|
||||
|
||||
// 新的运营 banner(铸造活动)
|
||||
const banners = ref([])
|
||||
|
||||
const loadBannerActivities = async () => {
|
||||
try {
|
||||
const starId = uni.getStorageSync('star_id') || null
|
||||
@ -12,7 +26,6 @@ export function useBanner() {
|
||||
if (res.code === 200 && res.data?.activities) {
|
||||
const activities = res.data.activities
|
||||
// 过滤掉已过期的活动
|
||||
// bannerActivities.value = activities
|
||||
bannerActivities.value = activities.filter(item => item.status !== 'expired')
|
||||
}
|
||||
} catch (e) {
|
||||
@ -20,8 +33,38 @@ export function useBanner() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载运营 banner(铸造活动)
|
||||
const loadBanners = async () => {
|
||||
try {
|
||||
const res = await getMintingActivitiesApi(null, 1, 10)
|
||||
|
||||
if (res.code === 200 && res.data?.activities) {
|
||||
// 将后端数据映射为 BannerCarousel 约定的字段
|
||||
banners.value = res.data.activities.map(activity => ({
|
||||
id: activity.id,
|
||||
image_url: activity.cover_image,
|
||||
title: activity.title,
|
||||
link_type: 'activity',
|
||||
link_value: String(activity.id),
|
||||
description: activity.description,
|
||||
route: activity.route
|
||||
}))
|
||||
}
|
||||
|
||||
// 无数据兜底
|
||||
if (banners.value.length === 0) {
|
||||
banners.value = [...FALLBACK_BANNER]
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[useBanner] 加载运营 banner 失败', e?.message ?? e)
|
||||
banners.value = [...FALLBACK_BANNER]
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bannerActivities,
|
||||
banners,
|
||||
loadBannerActivities,
|
||||
loadBanners
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@
|
||||
/>
|
||||
<view class="creation-info">
|
||||
<view class="creation-id">
|
||||
<text class="id-text">#{{ item.certificate_id }}</text>
|
||||
<text class="id-text">链上编号: #{{ item.certificate_id }}</text>
|
||||
</view>
|
||||
<view class="creation-meta">
|
||||
<view class="creator-info">
|
||||
|
||||
@ -1,81 +1,46 @@
|
||||
<template>
|
||||
<view class="square-container">
|
||||
<!-- 背景图片 -->
|
||||
<image
|
||||
<!-- <image
|
||||
class="bg-wrapper"
|
||||
src="/static/square/squearbj1.png"
|
||||
src="/static/square/squearbj11.png"
|
||||
mode="aspectFill"
|
||||
></image>
|
||||
></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"
|
||||
:class="{ 'spotlight-ready': isH5 }"
|
||||
scroll-y
|
||||
:show-scrollbar="false"
|
||||
:bounce="false"
|
||||
@scroll="onScroll"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@scrolltolower="handleScrollToLower"
|
||||
>
|
||||
<scroll-view class="content-wrapper" :class="{ 'spotlight-ready': isH5 }" scroll-y :show-scrollbar="false"
|
||||
:bounce="false" @scroll="onScroll" @touchstart="onTouchStart" @touchmove="onTouchMove"
|
||||
@scrolltolower="handleScrollToLower">
|
||||
<!-- 区域一:顶部运营轮播图 -->
|
||||
<view ref="bannerSectionRef" class="banner-section">
|
||||
<BannerCarousel
|
||||
:bannerActivities="bannerActivities"
|
||||
@activityClick="handleActivityClick"
|
||||
@top3Click="showRankingModal = true"
|
||||
/>
|
||||
<BannerCarousel :bannerActivities="bannerActivities" :banners="banners"
|
||||
@activityClick="handleActivityClick" @top3Click="showRankingModal = true"
|
||||
@bannerClick="handleBannerClick" />
|
||||
</view>
|
||||
|
||||
<ContentTabs
|
||||
ref="contentTabsRef"
|
||||
class="tabs"
|
||||
:modelValue="activeContentTab"
|
||||
@update:modelValue="activeContentTab = $event"
|
||||
/>
|
||||
<ContentTabs ref="contentTabsRef" class="tabs" :modelValue="activeContentTab"
|
||||
@update:modelValue="activeContentTab = $event" />
|
||||
|
||||
<!-- 在线榜单区块 -->
|
||||
<view ref="hotCategoryRef" 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>
|
||||
@ -83,25 +48,15 @@
|
||||
|
||||
<!-- 区域二:主Tab标签区(星卡/吧唧/海报) -->
|
||||
<view ref="mainTabsRef" class="main-tab-section">
|
||||
<view
|
||||
v-for="(tab, index) in mainTabs"
|
||||
:key="index"
|
||||
class="tab-item"
|
||||
:style="{ '--tab-delay': index * 0.06 + 's' }"
|
||||
@click="handleMainTabClick(tab)"
|
||||
>
|
||||
<view v-for="(tab, index) in mainTabs" :key="index" class="tab-item"
|
||||
:style="{ '--tab-delay': index * 0.06 + 's' }" @click="handleMainTabClick(tab)">
|
||||
<view class="tab-icon-wrap">
|
||||
<image
|
||||
class="tab-icon"
|
||||
:src="tab.icon"
|
||||
mode="aspectFit"
|
||||
:style="{
|
||||
<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>
|
||||
</view>
|
||||
<text class="tab-name">{{ tab.name }}</text>
|
||||
@ -109,42 +64,23 @@
|
||||
</view>
|
||||
|
||||
<!-- 区域三:分类标签区 -->
|
||||
<view
|
||||
ref="categoryRef"
|
||||
id="category-section"
|
||||
class="category-section"
|
||||
:class="{ fixed: isFixed, 'just-fixed': justFixed }"
|
||||
>
|
||||
<view ref="categoryRef" id="category-section" class="category-section"
|
||||
:class="{ fixed: isFixed, 'just-fixed': justFixed }">
|
||||
<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>
|
||||
<!-- fixed 时占位:避免下方内容跳变 -->
|
||||
<view
|
||||
v-if="isFixed && categoryHeight > 0"
|
||||
class="category-placeholder"
|
||||
:style="{ height: categoryHeight + 'px' }"
|
||||
></view>
|
||||
<view v-if="isFixed && categoryHeight > 0" class="category-placeholder"
|
||||
:style="{ height: categoryHeight + 'px' }"></view>
|
||||
|
||||
<!-- 区域四:创作网格列表 -->
|
||||
<CreationGrid
|
||||
:screenWidth="screenWidth"
|
||||
:screenHeight="screenHeight"
|
||||
:bannerBottom="bannerBottomPx"
|
||||
:category="activeCategoryTab"
|
||||
:isActive="isActive"
|
||||
@cardClick="handleCardClick"
|
||||
@loaded="onGridLoaded"
|
||||
ref="creationGridRef"
|
||||
/>
|
||||
<CreationGrid :screenWidth="screenWidth" :screenHeight="screenHeight" :bannerBottom="bannerBottomPx"
|
||||
:category="activeCategoryTab" :isActive="isActive" @cardClick="handleCardClick" @loaded="onGridLoaded"
|
||||
ref="creationGridRef" />
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
@ -310,7 +246,7 @@ const screenWidth = ref(375);
|
||||
const screenHeight = ref(812);
|
||||
|
||||
// ========== Composables ==========
|
||||
const { bannerActivities, loadBannerActivities } = useBanner();
|
||||
const { bannerActivities, banners, loadBannerActivities, loadBanners } = useBanner();
|
||||
|
||||
// banner(216+360rpx) + tab栏(16+80rpx) + 间距(8rpx) ≈ 680rpx
|
||||
const bannerBottomPx = computed(() =>
|
||||
@ -361,6 +297,25 @@ const handleActivityClick = (item) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 运营 banner 点击(铸造活动)
|
||||
const handleBannerClick = (banner) => {
|
||||
console.log('[square] banner click', banner);
|
||||
// 优先使用自定义 route
|
||||
if (banner.route) {
|
||||
return uni.navigateTo({ url: banner.route });
|
||||
}
|
||||
if (banner.link_type === 'activity') {
|
||||
return uni.navigateTo({
|
||||
url: `/pages/castlove/detail?id=${banner.link_value}`,
|
||||
});
|
||||
}
|
||||
if (banner.link_type === 'topic') {
|
||||
return uni.navigateTo({
|
||||
url: `/pages/topic/detail?id=${banner.link_value}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleRankingVisit = ({ userId, nickname }) => {
|
||||
showRankingModal.value = false;
|
||||
uni.navigateTo({
|
||||
@ -422,10 +377,10 @@ const handleCategoryChange = (value) => {
|
||||
};
|
||||
|
||||
// ========== Tile Change Callback ==========
|
||||
const handleTileChange = () => {};
|
||||
const handleTileChange = () => { };
|
||||
|
||||
// ========== Reset Square ==========
|
||||
const resetSquare = async () => {};
|
||||
const resetSquare = async () => { };
|
||||
|
||||
// ========== Lifecycle ==========
|
||||
onMounted(() => {
|
||||
@ -435,6 +390,7 @@ onMounted(() => {
|
||||
|
||||
resetSquare();
|
||||
loadBannerActivities();
|
||||
loadBanners();
|
||||
|
||||
// 等首屏 DOM 稳定后,测量分类标签 section 的位置/高度 + 启动 spotlight 轮询
|
||||
nextTick(() => {
|
||||
@ -499,13 +455,26 @@ onUnmounted(() => {
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.square-container {
|
||||
position: relative;
|
||||
background: linear-gradient(179.98deg, rgba(255, 229, 229, 0.25) -32.49%, rgba(243, 160, 161, 0.25) -32.49%, rgba(255, 156, 156, 0.25) 86.46%, rgba(255, 32, 36, 0.25) 180.79%);
|
||||
backdrop-filter: blur(4px);
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: url("/static/dashboard/bj.png") center / cover no-repeat;
|
||||
opacity: 0.2; // ⬅ 调这个数控制图片透明度(0=完全透明,1=完全不透明)
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 背景图片 */
|
||||
@ -551,11 +520,9 @@ onUnmounted(() => {
|
||||
justify-content: space-around;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
background: linear-gradient(135deg,
|
||||
rgba(240, 228, 177, 0.3),
|
||||
rgba(240, 131, 153, 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;
|
||||
@ -652,11 +619,9 @@ onUnmounted(() => {
|
||||
border-radius: 20rpx;
|
||||
opacity: 0.66;
|
||||
padding: 8rpx 20rpx;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
background: linear-gradient(90deg,
|
||||
rgba(255, 222, 8, 0.61) -17.54%,
|
||||
rgba(255, 0, 25, 0.61) 116.67%
|
||||
);
|
||||
rgba(255, 0, 25, 0.61) 116.67%);
|
||||
box-shadow: 2px 2px 4px 0px #f2151578;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -757,6 +722,7 @@ onUnmounted(() => {
|
||||
|
||||
/* 动效减弱的可访问性兜底 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
.spotlight,
|
||||
.spotlight-ready .banner-section,
|
||||
.spotlight-ready .tabs,
|
||||
|
||||
|
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 261 KiB After Width: | Height: | Size: 336 KiB |
|
Before Width: | Height: | Size: 336 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 108 KiB |
BIN
frontend/static/square/top/TOP1biankuang1.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
frontend/static/square/top/TOP2biankuang2.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
frontend/static/square/top/TOP3biankuangpng3.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 870 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 502 KiB After Width: | Height: | Size: 70 KiB |
@ -713,6 +713,7 @@ function parseSegmentPortraitResponse(statusCode, rawBody) {
|
||||
return body
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
function isH5SpecialImagePath(path) {
|
||||
return /^blob:/i.test(path) || /^data:image\//i.test(path)
|
||||
}
|
||||
@ -747,6 +748,7 @@ async function segmentPortraitApiH5(filePath, imageBase64) {
|
||||
const text = await uploadRes.text()
|
||||
return parseSegmentPortraitResponse(uploadRes.status, text)
|
||||
}
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 人像抠图(multipart)— POST /api/v1/segment?scene=portrait
|
||||
|
||||