feat: 样式调整

This commit is contained in:
zerosaturation 2026-06-04 15:57:42 +08:00
parent 6c18bb7564
commit 71b8e454ee
28 changed files with 234 additions and 173 deletions

View File

@ -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() {

View File

@ -3,7 +3,7 @@
"appid" : "__UNI__F199FF4",
"description" : "",
"versionName" : "1.0.5",
"versionCode" : 106,
"versionCode" : 107,
"transformPx" : false,
/* 5+App */
"app-plus" : {

View File

@ -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

View File

@ -508,7 +508,7 @@ defineExpose({
.star-activity-list {
display: flex;
align-items: center;
gap: 8rpx;
/* gap: 8rpx; */
/* width: 68rpx; */
/* 控制整体宽度,根据图标大小调整 */
height: 156rpx;

View File

@ -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;
}

View File

@ -145,7 +145,7 @@ onShow(() => {
}
.nav-back {
position: fixed;
position: absolute;
top: 96rpx;
left: 32rpx;
width: 80rpx;

View File

@ -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;
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>

View File

@ -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,

View File

@ -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
}
}

View File

@ -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">

View File

@ -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({
@ -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,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 359 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -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