feat: 样式调整
@ -11,8 +11,10 @@ import { getLaserApiBaseUrl } from '@/utils/api.js'
|
|||||||
import { buildRenderConfigs } from '@/utils/laser-card/laserPresets.js'
|
import { buildRenderConfigs } from '@/utils/laser-card/laserPresets.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 镭射专用 fetch(直接请求本地 Gateway,不走远程 DEV_BASE)
|
* 镭射专用请求(直接请求本地 Gateway,不走远程 DEV_BASE)
|
||||||
* 返回格式与 api.js 的 request() 一致:{ code, message, data }
|
* 返回格式与 api.js 的 request() 一致:{ code, message, data }
|
||||||
|
*
|
||||||
|
* 注意:uni-app 移动端没有 fetch 全局,必须用 uni.request 封装 Promise
|
||||||
*/
|
*/
|
||||||
async function laserRequest(opts) {
|
async function laserRequest(opts) {
|
||||||
const base = getLaserApiBaseUrl()
|
const base = getLaserApiBaseUrl()
|
||||||
@ -22,19 +24,26 @@ async function laserRequest(opts) {
|
|||||||
if (token) {
|
if (token) {
|
||||||
headers['Authorization'] = `Bearer ${token}`
|
headers['Authorization'] = `Bearer ${token}`
|
||||||
}
|
}
|
||||||
const fetchOpts = {
|
return new Promise((resolve, reject) => {
|
||||||
method: opts.method || 'GET',
|
uni.request({
|
||||||
headers,
|
url,
|
||||||
}
|
method: opts.method || 'GET',
|
||||||
if (opts.data) {
|
data: opts.data || {},
|
||||||
fetchOpts.body = JSON.stringify(opts.data)
|
header: headers,
|
||||||
}
|
timeout: 60000,
|
||||||
const resp = await fetch(url, fetchOpts)
|
success: (res) => {
|
||||||
const json = await resp.json()
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
||||||
if (resp.status < 200 || resp.status >= 300) {
|
reject(new Error(res.data?.message || `HTTP ${res.statusCode}`))
|
||||||
throw new Error(json?.message || `HTTP ${resp.status}`)
|
return
|
||||||
}
|
}
|
||||||
return json
|
// 兼容后端返回的两种结构:{code, message, data} 或 裸 data
|
||||||
|
resolve(res.data)
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject(new Error(err.errMsg || '网络请求失败'))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLaserDifyGenerate() {
|
export function useLaserDifyGenerate() {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"appid" : "__UNI__F199FF4",
|
"appid" : "__UNI__F199FF4",
|
||||||
"description" : "",
|
"description" : "",
|
||||||
"versionName" : "1.0.5",
|
"versionName" : "1.0.5",
|
||||||
"versionCode" : 106,
|
"versionCode" : 107,
|
||||||
"transformPx" : false,
|
"transformPx" : false,
|
||||||
/* 5+App特有相关 */
|
/* 5+App特有相关 */
|
||||||
"app-plus" : {
|
"app-plus" : {
|
||||||
|
|||||||
@ -6,9 +6,9 @@
|
|||||||
<view v-if="isLaserCardCraft" class="laser-bg-veil" aria-hidden="true" />
|
<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>
|
<text class="laser-close-x">×</text>
|
||||||
</view>
|
</view> -->
|
||||||
|
|
||||||
<!-- 主内容区域 -->
|
<!-- 主内容区域 -->
|
||||||
<scroll-view
|
<scroll-view
|
||||||
|
|||||||
@ -508,7 +508,7 @@ defineExpose({
|
|||||||
.star-activity-list {
|
.star-activity-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8rpx;
|
/* gap: 8rpx; */
|
||||||
/* width: 68rpx; */
|
/* width: 68rpx; */
|
||||||
/* 控制整体宽度,根据图标大小调整 */
|
/* 控制整体宽度,根据图标大小调整 */
|
||||||
height: 156rpx;
|
height: 156rpx;
|
||||||
|
|||||||
@ -11,7 +11,11 @@
|
|||||||
|
|
||||||
<!-- 渐变标题 -->
|
<!-- 渐变标题 -->
|
||||||
<view class="title-wrap">
|
<view class="title-wrap">
|
||||||
<text class="header-title">数据看板</text>
|
<image
|
||||||
|
class="title-icon"
|
||||||
|
src="/static/dashboard/dashboard-title.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="header-content">
|
<view class="header-content">
|
||||||
@ -105,7 +109,7 @@ defineEmits(["update:activeTab"]);
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
padding: 0 32rpx 32rpx;
|
padding: 0 32rpx 32rpx;
|
||||||
top: 192rpx;
|
top: 272rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mascot {
|
.mascot {
|
||||||
@ -122,18 +126,32 @@ defineEmits(["update:activeTab"]);
|
|||||||
|
|
||||||
.title-wrap {
|
.title-wrap {
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
|
/* 让出 iOS 状态栏 + 顶部 nav 区域,避免标题被遮挡 */
|
||||||
|
// padding-top: 64rpx;
|
||||||
|
position: absolute;
|
||||||
|
right: 40rpx;
|
||||||
|
top: 160rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-icon{
|
||||||
|
width: 240rpx;
|
||||||
|
height: 64rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-title {
|
.header-title {
|
||||||
font-size: 48rpx;
|
font-size: 48rpx;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
/* 实色兜底:在不支持 -webkit-background-clip 的环境(微信小程序、部分 Android WebView、老 iOS WebView)也能看见 */
|
||||||
|
color: #ffb199;
|
||||||
|
/* 渐变文字效果:现代环境(H5/新版 WebView)会用渐变覆盖实色 */
|
||||||
background: linear-gradient(90deg, #ffe5b4 0%, #ffb199 50%, #ff8a95 100%);
|
background: linear-gradient(90deg, #ffe5b4 0%, #ffb199 50%, #ff8a95 100%);
|
||||||
-webkit-background-clip: text;
|
-webkit-background-clip: text;
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
color: transparent;
|
-webkit-text-fill-color: transparent;
|
||||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4);
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.header-tabs {
|
.header-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
// background: rgba(0, 0, 0, 0.15);
|
// background: rgba(0, 0, 0, 0.15);
|
||||||
@ -172,9 +190,11 @@ defineEmits(["update:activeTab"]);
|
|||||||
.tab-icon {
|
.tab-icon {
|
||||||
width: 160rpx;
|
width: 160rpx;
|
||||||
height: 160rpx;
|
height: 160rpx;
|
||||||
position: fixed;
|
/* 修复:原来是 position: fixed; left: 0; top: 0; (典型复制粘贴 bug,会让两个 tab 图标都叠在屏幕左上角) */
|
||||||
left: 0;
|
position: absolute;
|
||||||
top: 0;
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -145,7 +145,7 @@ onShow(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-back {
|
.nav-back {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 96rpx;
|
top: 96rpx;
|
||||||
left: 32rpx;
|
left: 32rpx;
|
||||||
width: 80rpx;
|
width: 80rpx;
|
||||||
|
|||||||
@ -1,28 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="banner-carousel" @click.stop>
|
<view class="banner-carousel" @click.stop>
|
||||||
<swiper
|
<swiper class="banner-swiper" :autoplay="true" :interval="4000" :duration="400" :circular="true"
|
||||||
class="banner-swiper"
|
:indicator-dots="false" @change="onSwiperChange">
|
||||||
:autoplay="true"
|
<!-- <swiper-item @click.stop="$emit('top3Click')" style="display: flex;
|
||||||
:interval="4000"
|
|
||||||
:duration="400"
|
|
||||||
:circular="true"
|
|
||||||
:indicator-dots="false"
|
|
||||||
@change="onSwiperChange"
|
|
||||||
>
|
|
||||||
<swiper-item @click.stop="$emit('top3Click')" style="display: flex;
|
|
||||||
align-items: center;">
|
align-items: center;">
|
||||||
<BannerTop3 @dataLoaded="onTop3DataLoaded" @top3Click="$emit('top3Click')" />
|
<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>
|
||||||
<swiper-item
|
<swiper-item v-for="(banner, index) in banners" :key="banner.id || index" @click="emit('bannerClick', banner)">
|
||||||
v-for="item in bannerActivities"
|
<image class="banner-image" :src="banner.image_url" mode="widthFill"></image>
|
||||||
:key="item.id"
|
<!-- <view class="banner-overlay">
|
||||||
@tap.stop="$emit('activityClick', item)"
|
<text class="banner-title">{{ banner.title }}</text>
|
||||||
>
|
</view> -->
|
||||||
<image
|
|
||||||
class="banner-activity-img"
|
|
||||||
:src="item.cover_image || '/static/avatar/1.jpeg'"
|
|
||||||
mode="aspectFill"
|
|
||||||
/>
|
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
</view>
|
</view>
|
||||||
@ -33,9 +24,14 @@ import { ref } from 'vue'
|
|||||||
import BannerTop3 from '../../components/BannerTop3.vue'
|
import BannerTop3 from '../../components/BannerTop3.vue'
|
||||||
|
|
||||||
defineProps({
|
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)
|
const currentIndex = ref(0)
|
||||||
|
|
||||||
@ -69,10 +65,34 @@ const onTop3DataLoaded = (items) => {
|
|||||||
|
|
||||||
.banner-activity-img {
|
.banner-activity-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 312rpx;
|
height: 344rpx;
|
||||||
display: block;
|
display: block;
|
||||||
border-radius:24rpx;
|
border-radius: 24rpx;
|
||||||
position: relative;
|
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>
|
</style>
|
||||||
|
|||||||
@ -121,9 +121,9 @@ const likingMap = ref({});
|
|||||||
|
|
||||||
// 前三名对应的边框图与右上角装饰图
|
// 前三名对应的边框图与右上角装饰图
|
||||||
const TOP_FRAME_MAP = {
|
const TOP_FRAME_MAP = {
|
||||||
0: "/static/square/top/TOP1biankuang.png",
|
0: "/static/square/top/TOP1biankuang1.png",
|
||||||
1: "/static/square/top/TOP2biankuang.png",
|
1: "/static/square/top/TOP2biankuang2.png",
|
||||||
2: "/static/square/top/TOP3biankuangpng.png",
|
2: "/static/square/top/TOP3biankuangpng3.png",
|
||||||
};
|
};
|
||||||
const TOP_ICON_MAP = {
|
const TOP_ICON_MAP = {
|
||||||
0: "/static/square/top/TOP1icon.png",
|
0: "/static/square/top/TOP1icon.png",
|
||||||
@ -209,7 +209,7 @@ onUnmounted(() => {
|
|||||||
.hot-category-block {
|
.hot-category-block {
|
||||||
padding: 19rpx 9.5rpx;
|
padding: 19rpx 9.5rpx;
|
||||||
border-radius: 24rpx;
|
border-radius: 24rpx;
|
||||||
opacity: 0.8;
|
/* opacity: 0.8; */
|
||||||
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%,
|
||||||
@ -337,11 +337,11 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
/* 第一排:3个大图突出显示 */
|
/* 第一排:3个大图突出显示 */
|
||||||
.grid-card:nth-child(-n + 3) {
|
.grid-card:nth-child(-n + 3) {
|
||||||
width: calc(33% - 18rpx);
|
width: calc(33% - 24rpx);
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-card:nth-child(-n + 3) .card-image {
|
.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);
|
box-shadow: 3px 3px 4.5px 2px rgba(0, 0, 0, 0.15);
|
||||||
backdrop-filter: blur(0px);
|
backdrop-filter: blur(0px);
|
||||||
padding: 8rpx;
|
padding: 8rpx;
|
||||||
@ -350,7 +350,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.card-image {
|
.card-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 192rpx;
|
height: 224rpx;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -401,6 +401,7 @@ onUnmounted(() => {
|
|||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
box-shadow: 2px 2px 4.5px 0px #F04B4B40;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-bottom-1,
|
.card-bottom-1,
|
||||||
|
|||||||
@ -1,9 +1,23 @@
|
|||||||
import { ref } from 'vue'
|
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() {
|
export function useBanner() {
|
||||||
|
// 旧的活动 banner(square 顶部)
|
||||||
const bannerActivities = ref([])
|
const bannerActivities = ref([])
|
||||||
|
|
||||||
|
// 新的运营 banner(铸造活动)
|
||||||
|
const banners = ref([])
|
||||||
|
|
||||||
const loadBannerActivities = async () => {
|
const loadBannerActivities = async () => {
|
||||||
try {
|
try {
|
||||||
const starId = uni.getStorageSync('star_id') || null
|
const starId = uni.getStorageSync('star_id') || null
|
||||||
@ -12,7 +26,6 @@ export function useBanner() {
|
|||||||
if (res.code === 200 && res.data?.activities) {
|
if (res.code === 200 && res.data?.activities) {
|
||||||
const activities = res.data.activities
|
const activities = res.data.activities
|
||||||
// 过滤掉已过期的活动
|
// 过滤掉已过期的活动
|
||||||
// bannerActivities.value = activities
|
|
||||||
bannerActivities.value = activities.filter(item => item.status !== 'expired')
|
bannerActivities.value = activities.filter(item => item.status !== 'expired')
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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 {
|
return {
|
||||||
bannerActivities,
|
bannerActivities,
|
||||||
|
banners,
|
||||||
loadBannerActivities,
|
loadBannerActivities,
|
||||||
|
loadBanners
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,7 +63,7 @@
|
|||||||
/>
|
/>
|
||||||
<view class="creation-info">
|
<view class="creation-info">
|
||||||
<view class="creation-id">
|
<view class="creation-id">
|
||||||
<text class="id-text">#{{ item.certificate_id }}</text>
|
<text class="id-text">链上编号: #{{ item.certificate_id }}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="creation-meta">
|
<view class="creation-meta">
|
||||||
<view class="creator-info">
|
<view class="creator-info">
|
||||||
|
|||||||
@ -1,81 +1,46 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="square-container">
|
<view class="square-container">
|
||||||
<!-- 背景图片 -->
|
<!-- 背景图片 -->
|
||||||
<image
|
<!-- <image
|
||||||
class="bg-wrapper"
|
class="bg-wrapper"
|
||||||
src="/static/square/squearbj1.png"
|
src="/static/square/squearbj11.png"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
></image>
|
></image> -->
|
||||||
|
|
||||||
<!-- Header组件 -->
|
<!-- Header组件 -->
|
||||||
<Header
|
<Header :showGuideIcon="true" :showTaskIcon="true" :showStarActivityIcon="true" backIconColor="#e6e6e6" />
|
||||||
:showGuideIcon="true"
|
|
||||||
:showTaskIcon="true"
|
|
||||||
:showStarActivityIcon="true"
|
|
||||||
backIconColor="#e6e6e6"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 蒙层 - 导航栏展开时显示 -->
|
<!-- 蒙层 - 导航栏展开时显示 -->
|
||||||
<view
|
<view v-if="navExpanded" class="nav-mask" @click="navExpanded = false"></view>
|
||||||
v-if="navExpanded"
|
|
||||||
class="nav-mask"
|
|
||||||
@click="navExpanded = false"
|
|
||||||
></view>
|
|
||||||
|
|
||||||
<!-- 排行榜弹窗 -->
|
<!-- 排行榜弹窗 -->
|
||||||
<RankingModal
|
<RankingModal :visible="showRankingModal" :parent-active="true" :star-id="currentStarId"
|
||||||
:visible="showRankingModal"
|
@update:visible="handleRankingModalClose" @visit="handleRankingVisit" />
|
||||||
:parent-active="true"
|
|
||||||
:star-id="currentStarId"
|
|
||||||
@update:visible="handleRankingModalClose"
|
|
||||||
@visit="handleRankingVisit"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 底部导航栏 -->
|
<!-- 底部导航栏 -->
|
||||||
<BottomNav
|
<BottomNav :activeTab="4" :isExpanded="navExpanded" @update:activeTab="handleTabChange"
|
||||||
:activeTab="4"
|
@update:isExpanded="navExpanded = $event" />
|
||||||
:isExpanded="navExpanded"
|
|
||||||
@update:activeTab="handleTabChange"
|
|
||||||
@update:isExpanded="navExpanded = $event"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 全局引导遮罩 -->
|
<!-- 全局引导遮罩 -->
|
||||||
<GuideOverlay />
|
<GuideOverlay />
|
||||||
|
|
||||||
<!-- 内容区域 -->
|
<!-- 内容区域 -->
|
||||||
<scroll-view
|
<scroll-view class="content-wrapper" :class="{ 'spotlight-ready': isH5 }" scroll-y :show-scrollbar="false"
|
||||||
class="content-wrapper"
|
:bounce="false" @scroll="onScroll" @touchstart="onTouchStart" @touchmove="onTouchMove"
|
||||||
:class="{ 'spotlight-ready': isH5 }"
|
@scrolltolower="handleScrollToLower">
|
||||||
scroll-y
|
|
||||||
:show-scrollbar="false"
|
|
||||||
:bounce="false"
|
|
||||||
@scroll="onScroll"
|
|
||||||
@touchstart="onTouchStart"
|
|
||||||
@touchmove="onTouchMove"
|
|
||||||
@scrolltolower="handleScrollToLower"
|
|
||||||
>
|
|
||||||
<!-- 区域一:顶部运营轮播图 -->
|
<!-- 区域一:顶部运营轮播图 -->
|
||||||
<view ref="bannerSectionRef" class="banner-section">
|
<view ref="bannerSectionRef" class="banner-section">
|
||||||
<BannerCarousel
|
<BannerCarousel :bannerActivities="bannerActivities" :banners="banners"
|
||||||
:bannerActivities="bannerActivities"
|
@activityClick="handleActivityClick" @top3Click="showRankingModal = true"
|
||||||
@activityClick="handleActivityClick"
|
@bannerClick="handleBannerClick" />
|
||||||
@top3Click="showRankingModal = true"
|
|
||||||
/>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<ContentTabs
|
<ContentTabs ref="contentTabsRef" class="tabs" :modelValue="activeContentTab"
|
||||||
ref="contentTabsRef"
|
@update:modelValue="activeContentTab = $event" />
|
||||||
class="tabs"
|
|
||||||
:modelValue="activeContentTab"
|
|
||||||
@update:modelValue="activeContentTab = $event"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 在线榜单区块 -->
|
<!-- 在线榜单区块 -->
|
||||||
<view ref="hotCategoryRef" class="hot-category-wrapper">
|
<view ref="hotCategoryRef" class="hot-category-wrapper">
|
||||||
<HotCategoryBlock
|
<HotCategoryBlock :dimension="activeContentTab" @cardClick="handleCardClick" />
|
||||||
:dimension="activeContentTab"
|
|
||||||
@cardClick="handleCardClick"
|
|
||||||
/>
|
|
||||||
<view class="hot-more-btn" @click="goToHotCategoryMore">
|
<view class="hot-more-btn" @click="goToHotCategoryMore">
|
||||||
<text class="hot-more-text">查看更多</text>
|
<text class="hot-more-text">查看更多</text>
|
||||||
</view>
|
</view>
|
||||||
@ -83,25 +48,15 @@
|
|||||||
|
|
||||||
<!-- 区域二:主Tab标签区(星卡/吧唧/海报) -->
|
<!-- 区域二:主Tab标签区(星卡/吧唧/海报) -->
|
||||||
<view ref="mainTabsRef" class="main-tab-section">
|
<view ref="mainTabsRef" class="main-tab-section">
|
||||||
<view
|
<view v-for="(tab, index) in mainTabs" :key="index" class="tab-item"
|
||||||
v-for="(tab, index) in mainTabs"
|
:style="{ '--tab-delay': index * 0.06 + 's' }" @click="handleMainTabClick(tab)">
|
||||||
:key="index"
|
|
||||||
class="tab-item"
|
|
||||||
:style="{ '--tab-delay': index * 0.06 + 's' }"
|
|
||||||
@click="handleMainTabClick(tab)"
|
|
||||||
>
|
|
||||||
<view class="tab-icon-wrap">
|
<view class="tab-icon-wrap">
|
||||||
<image
|
<image class="tab-icon" :src="tab.icon" mode="aspectFit" :style="{
|
||||||
class="tab-icon"
|
width: tab.width + 'rpx',
|
||||||
:src="tab.icon"
|
height: tab.height + 'rpx',
|
||||||
mode="aspectFit"
|
borderRadius: tab.type === 'badge' ? '50%' : '0',
|
||||||
:style="{
|
transform: 'rotate(' + tab.rotate + 'deg)',
|
||||||
width: tab.width + 'rpx',
|
}">
|
||||||
height: tab.height + 'rpx',
|
|
||||||
borderRadius: tab.type === 'badge' ? '50%' : '0',
|
|
||||||
transform: 'rotate(' + tab.rotate + 'deg)',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
</image>
|
</image>
|
||||||
</view>
|
</view>
|
||||||
<text class="tab-name">{{ tab.name }}</text>
|
<text class="tab-name">{{ tab.name }}</text>
|
||||||
@ -109,42 +64,23 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 区域三:分类标签区 -->
|
<!-- 区域三:分类标签区 -->
|
||||||
<view
|
<view ref="categoryRef" id="category-section" class="category-section"
|
||||||
ref="categoryRef"
|
:class="{ fixed: isFixed, 'just-fixed': justFixed }">
|
||||||
id="category-section"
|
|
||||||
class="category-section"
|
|
||||||
:class="{ fixed: isFixed, 'just-fixed': justFixed }"
|
|
||||||
>
|
|
||||||
<scroll-view class="category-scroll" scroll-x :show-scrollbar="false">
|
<scroll-view class="category-scroll" scroll-x :show-scrollbar="false">
|
||||||
<view
|
<view v-for="(category, index) in categories" :key="index" class="category-item"
|
||||||
v-for="(category, index) in categories"
|
:class="{ active: activeCategoryTab === category.value }" @click="handleCategoryChange(category.value)">
|
||||||
:key="index"
|
|
||||||
class="category-item"
|
|
||||||
:class="{ active: activeCategoryTab === category.value }"
|
|
||||||
@click="handleCategoryChange(category.value)"
|
|
||||||
>
|
|
||||||
<text class="category-text">{{ category.label }}</text>
|
<text class="category-text">{{ category.label }}</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
<!-- fixed 时占位:避免下方内容跳变 -->
|
<!-- fixed 时占位:避免下方内容跳变 -->
|
||||||
<view
|
<view v-if="isFixed && categoryHeight > 0" class="category-placeholder"
|
||||||
v-if="isFixed && categoryHeight > 0"
|
:style="{ height: categoryHeight + 'px' }"></view>
|
||||||
class="category-placeholder"
|
|
||||||
:style="{ height: categoryHeight + 'px' }"
|
|
||||||
></view>
|
|
||||||
|
|
||||||
<!-- 区域四:创作网格列表 -->
|
<!-- 区域四:创作网格列表 -->
|
||||||
<CreationGrid
|
<CreationGrid :screenWidth="screenWidth" :screenHeight="screenHeight" :bannerBottom="bannerBottomPx"
|
||||||
:screenWidth="screenWidth"
|
:category="activeCategoryTab" :isActive="isActive" @cardClick="handleCardClick" @loaded="onGridLoaded"
|
||||||
:screenHeight="screenHeight"
|
ref="creationGridRef" />
|
||||||
:bannerBottom="bannerBottomPx"
|
|
||||||
:category="activeCategoryTab"
|
|
||||||
:isActive="isActive"
|
|
||||||
@cardClick="handleCardClick"
|
|
||||||
@loaded="onGridLoaded"
|
|
||||||
ref="creationGridRef"
|
|
||||||
/>
|
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@ -310,7 +246,7 @@ const screenWidth = ref(375);
|
|||||||
const screenHeight = ref(812);
|
const screenHeight = ref(812);
|
||||||
|
|
||||||
// ========== Composables ==========
|
// ========== Composables ==========
|
||||||
const { bannerActivities, loadBannerActivities } = useBanner();
|
const { bannerActivities, banners, loadBannerActivities, loadBanners } = useBanner();
|
||||||
|
|
||||||
// banner(216+360rpx) + tab栏(16+80rpx) + 间距(8rpx) ≈ 680rpx
|
// banner(216+360rpx) + tab栏(16+80rpx) + 间距(8rpx) ≈ 680rpx
|
||||||
const bannerBottomPx = computed(() =>
|
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 }) => {
|
const handleRankingVisit = ({ userId, nickname }) => {
|
||||||
showRankingModal.value = false;
|
showRankingModal.value = false;
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
@ -422,10 +377,10 @@ const handleCategoryChange = (value) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ========== Tile Change Callback ==========
|
// ========== Tile Change Callback ==========
|
||||||
const handleTileChange = () => {};
|
const handleTileChange = () => { };
|
||||||
|
|
||||||
// ========== Reset Square ==========
|
// ========== Reset Square ==========
|
||||||
const resetSquare = async () => {};
|
const resetSquare = async () => { };
|
||||||
|
|
||||||
// ========== Lifecycle ==========
|
// ========== Lifecycle ==========
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -435,6 +390,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
resetSquare();
|
resetSquare();
|
||||||
loadBannerActivities();
|
loadBannerActivities();
|
||||||
|
loadBanners();
|
||||||
|
|
||||||
// 等首屏 DOM 稳定后,测量分类标签 section 的位置/高度 + 启动 spotlight 轮询
|
// 等首屏 DOM 稳定后,测量分类标签 section 的位置/高度 + 启动 spotlight 轮询
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@ -499,13 +455,26 @@ onUnmounted(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="scss" scoped>
|
||||||
.square-container {
|
.square-container {
|
||||||
position: relative;
|
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;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
overflow: hidden;
|
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;
|
justify-content: space-around;
|
||||||
width: 200rpx;
|
width: 200rpx;
|
||||||
height: 200rpx;
|
height: 200rpx;
|
||||||
background: linear-gradient(
|
background: linear-gradient(135deg,
|
||||||
135deg,
|
rgba(240, 228, 177, 0.3),
|
||||||
rgba(240, 228, 177, 0.3),
|
rgba(240, 131, 153, 0.3));
|
||||||
rgba(240, 131, 153, 0.3)
|
|
||||||
);
|
|
||||||
border-radius: 24rpx;
|
border-radius: 24rpx;
|
||||||
box-shadow: 0 8rpx 24rpx rgba(255, 107, 157, 0.2);
|
box-shadow: 0 8rpx 24rpx rgba(255, 107, 157, 0.2);
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
@ -652,11 +619,9 @@ onUnmounted(() => {
|
|||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
opacity: 0.66;
|
opacity: 0.66;
|
||||||
padding: 8rpx 20rpx;
|
padding: 8rpx 20rpx;
|
||||||
background: linear-gradient(
|
background: linear-gradient(90deg,
|
||||||
90deg,
|
rgba(255, 222, 8, 0.61) -17.54%,
|
||||||
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;
|
box-shadow: 2px 2px 4px 0px #f2151578;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -757,6 +722,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
/* 动效减弱的可访问性兜底 */
|
/* 动效减弱的可访问性兜底 */
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
|
||||||
.spotlight,
|
.spotlight,
|
||||||
.spotlight-ready .banner-section,
|
.spotlight-ready .banner-section,
|
||||||
.spotlight-ready .tabs,
|
.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
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #ifdef H5
|
||||||
function isH5SpecialImagePath(path) {
|
function isH5SpecialImagePath(path) {
|
||||||
return /^blob:/i.test(path) || /^data:image\//i.test(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()
|
const text = await uploadRes.text()
|
||||||
return parseSegmentPortraitResponse(uploadRes.status, text)
|
return parseSegmentPortraitResponse(uploadRes.status, text)
|
||||||
}
|
}
|
||||||
|
// #endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 人像抠图(multipart)— POST /api/v1/segment?scene=portrait
|
* 人像抠图(multipart)— POST /api/v1/segment?scene=portrait
|
||||||
|
|||||||