984 行
27 KiB
Vue
984 行
27 KiB
Vue
<template>
|
||
<view class="index-container" :class="{ 'theme-warm': currentThemeKey === 'warm' }" :style="navStyle">
|
||
<!-- 顶部搜索栏 (矢量SVG搜索图标,交互聚焦微动效) -->
|
||
<view class="header-search">
|
||
<view class="search-box">
|
||
<image class="search-icon-img" :src="ICONS.search" mode="aspectFit" />
|
||
<input class="search-input" placeholder="搜索同城陪玩、美食、KTV嗨唱" placeholder-class="placeholder-text" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 首页骨架屏 (首屏加载中占位,防布局偏移与视觉空白) -->
|
||
<skeleton-screen v-if="isLoading" type="home" />
|
||
|
||
<!-- 真实内容 (加载完成平滑展示) -->
|
||
<view v-else class="index-body-wrapper">
|
||
<!-- 精彩活动横幅轮播 (流光投影) -->
|
||
<swiper class="banner-swiper" circular autoplay interval="4000" indicator-dots indicator-color="rgba(255,255,255,0.3)" indicator-active-color="#6366F1">
|
||
<swiper-item v-for="banner in banners" :key="banner.id" class="banner-item" @click="goToActivity(banner)">
|
||
<view class="banner-card" :style="{ background: banner.bg || 'linear-gradient(135deg, #F43F5E 0%, #FB7185 100%)' }">
|
||
<image v-if="banner.image" class="banner-bg-img" :src="banner.image" mode="aspectFill" />
|
||
<view v-if="banner.image" class="banner-overlay"></view>
|
||
<view class="banner-content">
|
||
<view class="banner-tag">{{ banner.tag || banner.badge || '热门陪伴' }}</view>
|
||
<view class="banner-title">{{ banner.title }}</view>
|
||
<view class="banner-sub">{{ banner.subtitle || '点击立即参与' }}</view>
|
||
</view>
|
||
</view>
|
||
</swiper-item>
|
||
</swiper>
|
||
|
||
|
||
<!-- 陪玩活动项目专属金刚区 (动态活动品类导航网格:纯图标+主标题+高亮角标) -->
|
||
<view class="quick-grid grid-card">
|
||
<view
|
||
v-for="item in navCategories"
|
||
:key="item.id || item.code"
|
||
class="quick-item btn-active"
|
||
@click="handleProjectClick(item)"
|
||
>
|
||
<view class="quick-icon-bg" :class="item.bgClass">
|
||
<image class="quick-svg" :src="resolveIcon(item.icon)" mode="aspectFit" />
|
||
</view>
|
||
<text class="quick-title">{{ item.name }}</text>
|
||
<text v-if="item.badgeText" class="quick-badge">{{ item.badgeText }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 陪玩大神瀑布流标题 -->
|
||
<view class="section-title-wrap">
|
||
<view class="section-title">
|
||
<view class="title-line"></view>
|
||
<text>精选在线大神</text>
|
||
</view>
|
||
<view class="section-more">
|
||
<view class="pulse-point"></view>
|
||
<text>实时在线</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 陪玩大神卡片网格 -->
|
||
<view class="expert-grid">
|
||
<view
|
||
v-for="expert in filteredExperts"
|
||
:key="expert.id"
|
||
class="expert-card card-active"
|
||
@click="goToExpert(expert.id)"
|
||
>
|
||
<!-- 封面图构图优化:高保真黄金比例与底部流光渐变 -->
|
||
<view class="card-cover-wrap">
|
||
<image class="card-cover" :src="expert.avatar" mode="aspectFill" />
|
||
<view class="cover-gradient"></view>
|
||
|
||
<view class="status-tag" :class="expert.isOnline ? 'online' : 'offline'">
|
||
<view class="pulse-dot" v-if="expert.isOnline"></view>
|
||
<text>{{ expert.isOnline ? '在线' : '离线' }}</text>
|
||
</view>
|
||
<view class="game-badge">{{ expert.game }}</view>
|
||
</view>
|
||
|
||
<!-- 信息区 -->
|
||
<view class="card-body">
|
||
<view class="row-nickname">
|
||
<text class="nickname">{{ expert.nickname }}</text>
|
||
<view class="gender-tag" :class="expert.gender">
|
||
<image class="gender-svg" :src="expert.gender === 'female' ? ICONS.female : ICONS.male" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
<view class="city-badge" v-if="expert.city">
|
||
<image class="city-pin-svg" :src="ICONS.mapPin" mode="aspectFit" />
|
||
<text class="city-text">{{ expert.city }}</text>
|
||
</view>
|
||
<view class="rank-badge" v-else>{{ expert.rankTag }}</view>
|
||
|
||
<!-- 语音/歌声试听条 (矢量SVG播放图标,带播放高亮光晕) -->
|
||
<view class="voice-bar btn-active" :class="{ playing: playingId === expert.id, 'song-bar': expert.voiceType === 'song' }" @click.stop="toggleVoice(expert.id)">
|
||
<image class="voice-icon-svg" :src="playingId === expert.id ? ICONS.pause : (expert.voiceType === 'song' ? ICONS.micVocal : ICONS.play)" mode="aspectFit" />
|
||
<text class="voice-text">{{ playingId === expert.id ? '播放中...' : (expert.voiceTitle || expert.voiceDuration + 's 原声试听') }}</text>
|
||
<view class="wave-lines" v-if="playingId === expert.id">
|
||
<view class="wave-item w1"></view>
|
||
<view class="wave-item w2"></view>
|
||
<view class="wave-item w3"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="card-footer">
|
||
<view class="price-wrap tabular-num">
|
||
<text class="price-symbol">¥</text>
|
||
<text class="price-val">{{ expert.price }}</text>
|
||
<text class="price-unit">/时</text>
|
||
</view>
|
||
<text class="order-count tabular-num">{{ expert.orderCount }}单</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部自定义导航栏 (发单人端:首页 / 活动中心 / 我要发单 / 友圈 / 个人中心) -->
|
||
<custom-tab-bar current="home" />
|
||
|
||
<!-- 微信小程序用户隐私保护指引弹窗 (微信合规) -->
|
||
<privacy-modal />
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||
import { onShow } from '@dcloudio/uni-app';
|
||
import CustomTabBar from '@/components/CustomTabBar.vue';
|
||
import SkeletonScreen from '@/components/SkeletonScreen.vue';
|
||
import PrivacyModal from '@/components/PrivacyModal.vue';
|
||
import { getCompanions, getBanners, getNavCategories } from '@/common/api.js';
|
||
import { ICONS } from '@/common/icons.js';
|
||
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
|
||
import { getNavBarInfo } from '@/common/nav.js';
|
||
|
||
const isLoading = ref(true);
|
||
const banners = ref([]);
|
||
const experts = ref([]);
|
||
const navCategories = ref([]);
|
||
const currentCategory = ref('all');
|
||
const playingId = ref(null);
|
||
const currentThemeKey = ref(getCurrentThemeKey());
|
||
const navInfo = ref(getNavBarInfo());
|
||
|
||
const navStyle = computed(() => {
|
||
return {
|
||
'--safe-header-gap': `${navInfo.value.safeHeaderGap}px`,
|
||
'--capsule-right-space': `${navInfo.value.capsuleRightSpace}px`,
|
||
'--capsule-height': `${navInfo.value.capsuleHeight}px`
|
||
};
|
||
});
|
||
|
||
const loadInitialData = async () => {
|
||
try {
|
||
await Promise.allSettled([
|
||
fetchLiveCompanions(),
|
||
fetchLiveBanners(),
|
||
fetchLiveNavCategories()
|
||
]);
|
||
} finally {
|
||
// 平滑延时关闭骨架屏,呈现优雅扫光转场
|
||
setTimeout(() => {
|
||
isLoading.value = false;
|
||
}, 200);
|
||
}
|
||
};
|
||
|
||
const fetchLiveCompanions = async () => {
|
||
try {
|
||
const list = await getCompanions();
|
||
if (Array.isArray(list) && list.length > 0) {
|
||
experts.value = list;
|
||
}
|
||
} catch (err) {
|
||
console.warn('[Index] 获取大神数据失败:', err);
|
||
}
|
||
};
|
||
|
||
const fetchLiveBanners = async () => {
|
||
try {
|
||
const list = await getBanners();
|
||
if (Array.isArray(list) && list.length > 0) {
|
||
banners.value = list;
|
||
}
|
||
} catch (err) {
|
||
console.warn('[Index] 获取Banner配置失败:', err);
|
||
}
|
||
};
|
||
|
||
const fetchLiveNavCategories = async () => {
|
||
try {
|
||
const list = await getNavCategories();
|
||
if (Array.isArray(list) && list.length > 0) {
|
||
navCategories.value = list;
|
||
}
|
||
} catch (err) {
|
||
console.warn('[Index] 获取金刚区品类导航失败:', err);
|
||
}
|
||
};
|
||
|
||
const resolveIcon = (icon) => {
|
||
if (!icon) return ICONS.gridAll;
|
||
if (ICONS[icon]) return ICONS[icon];
|
||
return icon;
|
||
};
|
||
|
||
const onThemeChanged = (key) => {
|
||
currentThemeKey.value = key;
|
||
applyThemeToNativeUI(key);
|
||
};
|
||
|
||
onMounted(() => {
|
||
navInfo.value = getNavBarInfo();
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
applyThemeToNativeUI(currentThemeKey.value);
|
||
uni.$on('theme-change', onThemeChanged);
|
||
loadInitialData();
|
||
});
|
||
|
||
onShow(() => {
|
||
navInfo.value = getNavBarInfo();
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
applyThemeToNativeUI(currentThemeKey.value);
|
||
uni.hideTabBar({ animation: false, fail: () => {} });
|
||
fetchLiveCompanions();
|
||
fetchLiveBanners();
|
||
fetchLiveNavCategories();
|
||
});
|
||
|
||
|
||
onUnmounted(() => {
|
||
uni.$off('theme-change', onThemeChanged);
|
||
});
|
||
|
||
const filteredExperts = computed(() => {
|
||
if (currentCategory.value === 'all') return experts.value;
|
||
const gameMap = {
|
||
offline_meal: '美食陪吃',
|
||
offline_drink: '下午茶陪喝',
|
||
offline_sing: '同城陪唱',
|
||
offline_play: '同城陪玩',
|
||
wzry: '王者荣耀',
|
||
hpjy: '和平精英',
|
||
lol: '英雄联盟',
|
||
val: '无畏契约'
|
||
};
|
||
const target = gameMap[currentCategory.value];
|
||
return target ? experts.value.filter(e => e.game === target) : experts.value;
|
||
});
|
||
|
||
const toggleVoice = (id) => {
|
||
if (playingId.value === id) {
|
||
playingId.value = null;
|
||
} else {
|
||
playingId.value = id;
|
||
uni.showToast({ title: '正在试听打招呼语音', icon: 'none' });
|
||
setTimeout(() => {
|
||
if (playingId.value === id) playingId.value = null;
|
||
}, 4000);
|
||
}
|
||
};
|
||
|
||
const goToExpert = (id) => {
|
||
uni.navigateTo({
|
||
url: `/pages/expert/detail?id=${id}`
|
||
});
|
||
};
|
||
|
||
const goToActivity = (banner) => {
|
||
if (banner && banner.link) {
|
||
if (banner.link.startsWith('/pages/activity/list') || banner.link.startsWith('/pages/order/list') || banner.link.startsWith('/pages/user/index')) {
|
||
uni.switchTab({ url: banner.link });
|
||
} else {
|
||
uni.navigateTo({ url: banner.link });
|
||
}
|
||
} else {
|
||
uni.switchTab({
|
||
url: '/pages/activity/list'
|
||
});
|
||
}
|
||
};
|
||
|
||
|
||
const activityProjects = [
|
||
{
|
||
id: 'esports',
|
||
name: '电子竞技',
|
||
icon: ICONS.gameAll,
|
||
bgClass: 'bg-indigo',
|
||
cat: 'all',
|
||
targetUrl: '/pages/activity/list?category=ALL'
|
||
},
|
||
{
|
||
id: 'meal',
|
||
name: '美食探店',
|
||
icon: ICONS.utensils,
|
||
bgClass: 'bg-amber',
|
||
cat: 'offline_meal',
|
||
targetUrl: '/pages/activity/list?category=MEAL'
|
||
},
|
||
{
|
||
id: 'sing',
|
||
name: 'KTV嗨唱',
|
||
icon: ICONS.micVocal,
|
||
bgClass: 'bg-rose',
|
||
cat: 'offline_sing',
|
||
targetUrl: '/pages/activity/list?category=SING'
|
||
},
|
||
{
|
||
id: 'billiards',
|
||
name: '同城台球',
|
||
icon: ICONS.billiards,
|
||
bgClass: 'bg-sky',
|
||
cat: 'offline_play',
|
||
targetUrl: '/pages/activity/list?category=ALL'
|
||
},
|
||
{
|
||
id: 'hiking',
|
||
name: '户外爬山',
|
||
icon: ICONS.mountain,
|
||
bgClass: 'bg-emerald',
|
||
cat: 'offline_play',
|
||
targetUrl: '/pages/activity/list?category=ALL'
|
||
},
|
||
{
|
||
id: 'drink',
|
||
name: '下午茶歇',
|
||
icon: ICONS.coffee,
|
||
bgClass: 'bg-teal',
|
||
cat: 'offline_drink',
|
||
targetUrl: '/pages/activity/list?category=DRINK'
|
||
},
|
||
{
|
||
id: 'boardgame',
|
||
name: '剧本桌游',
|
||
icon: ICONS.dice,
|
||
bgClass: 'bg-purple',
|
||
cat: 'offline_play',
|
||
targetUrl: '/pages/activity/list?category=ALL'
|
||
},
|
||
{
|
||
id: 'fitness',
|
||
name: '运动健身',
|
||
icon: ICONS.fitness,
|
||
bgClass: 'bg-orange',
|
||
cat: 'offline_play',
|
||
targetUrl: '/pages/activity/list?category=ALL'
|
||
},
|
||
{
|
||
id: 'bar',
|
||
name: '同城微醺',
|
||
icon: ICONS.warmFlame,
|
||
bgClass: 'bg-rose',
|
||
cat: 'offline_drink',
|
||
targetUrl: '/pages/activity/list?category=DRINK'
|
||
},
|
||
{
|
||
id: 'more',
|
||
name: '全部活动',
|
||
icon: ICONS.gridAll,
|
||
bgClass: 'bg-blue',
|
||
cat: 'all',
|
||
targetUrl: '/pages/activity/list?category=ALL'
|
||
}
|
||
];
|
||
|
||
const handleProjectClick = (item) => {
|
||
if (item.cat) {
|
||
currentCategory.value = item.cat;
|
||
}
|
||
if (item.targetUrl) {
|
||
uni.reLaunch({
|
||
url: item.targetUrl
|
||
});
|
||
}
|
||
};
|
||
|
||
const navTo = (url) => {
|
||
if (url === '/pages/activity/list' || url === '/pages/order/list' || url === '/pages/user/index') {
|
||
uni.switchTab({ url });
|
||
} else {
|
||
uni.navigateTo({ url });
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.index-container {
|
||
min-height: 100vh;
|
||
background-color: #0A0F1D;
|
||
padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.header-search {
|
||
padding-top: var(--safe-header-gap, 36rpx);
|
||
padding-right: calc(32rpx + var(--capsule-right-space, 0rpx));
|
||
padding-bottom: 20rpx;
|
||
padding-left: 32rpx;
|
||
background-color: #0A0F1D;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 50;
|
||
/* #ifdef MP-WEIXIN */
|
||
/* 微信小程序端胶囊按钮避让 (capsule-space) */
|
||
padding-right: calc(32rpx + var(--capsule-right-space, 210rpx));
|
||
/* #endif */
|
||
|
||
.search-box {
|
||
display: flex;
|
||
align-items: center;
|
||
background-color: #151D30;
|
||
height: 80rpx;
|
||
/* #ifdef MP-WEIXIN */
|
||
height: var(--capsule-height, 64rpx);
|
||
/* #endif */
|
||
border-radius: 40rpx;
|
||
padding: 0 28rpx;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
transition: all 0.2s ease;
|
||
|
||
&:focus-within {
|
||
border-color: #6366F1;
|
||
box-shadow: 0 0 16rpx rgba(99, 102, 241, 0.35);
|
||
}
|
||
|
||
.search-icon-img {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
margin-right: 16rpx;
|
||
opacity: 0.8;
|
||
}
|
||
.search-input {
|
||
flex: 1;
|
||
font-size: 26rpx;
|
||
color: #F8FAFC;
|
||
}
|
||
.placeholder-text {
|
||
color: #64748B;
|
||
font-size: 24rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.banner-swiper {
|
||
height: 200rpx;
|
||
margin: 10rpx 32rpx 24rpx;
|
||
|
||
.banner-card {
|
||
height: 100%;
|
||
border-radius: 20rpx;
|
||
padding: 28rpx 32rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.45);
|
||
position: relative;
|
||
overflow: hidden;
|
||
|
||
.banner-bg-img {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 1;
|
||
}
|
||
|
||
.banner-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: linear-gradient(135deg, rgba(15, 23, 42, 0.8) 0%, rgba(15, 23, 42, 0.4) 100%);
|
||
z-index: 2;
|
||
}
|
||
|
||
.banner-content {
|
||
position: relative;
|
||
z-index: 3;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
|
||
.banner-tag {
|
||
align-self: flex-start;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
backdrop-filter: blur(8px);
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 20rpx;
|
||
color: #FFF;
|
||
margin-bottom: 8rpx;
|
||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||
}
|
||
.banner-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #FFF;
|
||
margin-bottom: 6rpx;
|
||
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.6);
|
||
}
|
||
.banner-sub {
|
||
font-size: 22rpx;
|
||
color: rgba(255, 255, 255, 0.9);
|
||
text-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.5);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
.quick-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(5, 1fr);
|
||
row-gap: 24rpx;
|
||
column-gap: 6rpx;
|
||
margin: 0 32rpx 32rpx;
|
||
background-color: #151D30;
|
||
border-radius: 24rpx;
|
||
padding: 28rpx 10rpx 24rpx;
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
|
||
.quick-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
|
||
.quick-icon-bg {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: 10rpx;
|
||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||
|
||
.quick-svg {
|
||
width: 42rpx;
|
||
height: 42rpx;
|
||
}
|
||
|
||
&.bg-purple { background: rgba(139, 92, 246, 0.15); border: 1px solid rgba(139, 92, 246, 0.3); }
|
||
&.bg-green { background: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.3); }
|
||
&.bg-blue { background: rgba(99, 102, 241, 0.15); border: 1px solid rgba(99, 102, 241, 0.3); }
|
||
&.bg-orange { background: rgba(245, 158, 11, 0.15); border: 1px solid rgba(245, 158, 11, 0.3); }
|
||
&.bg-indigo { background: rgba(99, 102, 241, 0.15); border: 1px solid rgba(99, 102, 241, 0.3); }
|
||
&.bg-amber { background: rgba(245, 158, 11, 0.15); border: 1px solid rgba(245, 158, 11, 0.3); }
|
||
&.bg-rose { background: rgba(244, 63, 94, 0.15); border: 1px solid rgba(244, 63, 94, 0.3); }
|
||
&.bg-sky { background: rgba(14, 165, 233, 0.15); border: 1px solid rgba(14, 165, 233, 0.3); }
|
||
&.bg-emerald { background: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.3); }
|
||
&.bg-teal { background: rgba(20, 184, 166, 0.15); border: 1px solid rgba(20, 184, 166, 0.3); }
|
||
}
|
||
|
||
.quick-title {
|
||
font-size: 22rpx;
|
||
color: #F8FAFC;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.quick-badge {
|
||
position: absolute;
|
||
top: -6rpx;
|
||
right: 6rpx;
|
||
background: linear-gradient(135deg, #F43F5E 0%, #FB7185 100%);
|
||
color: #FFFFFF;
|
||
font-size: 16rpx;
|
||
padding: 2rpx 8rpx;
|
||
border-radius: 12rpx;
|
||
font-weight: 700;
|
||
box-shadow: 0 2rpx 6rpx rgba(244, 63, 94, 0.4);
|
||
transform: scale(0.9);
|
||
}
|
||
|
||
position: relative;
|
||
|
||
&:active .quick-icon-bg {
|
||
transform: scale(0.92);
|
||
}
|
||
}
|
||
}
|
||
|
||
.section-title-wrap {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 0 32rpx 20rpx;
|
||
|
||
.section-title {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
|
||
.title-line {
|
||
width: 8rpx;
|
||
height: 28rpx;
|
||
background: linear-gradient(180deg, #6366F1, #8B5CF6);
|
||
border-radius: 4rpx;
|
||
margin-right: 14rpx;
|
||
}
|
||
}
|
||
|
||
.section-more {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
font-size: 22rpx;
|
||
color: #10B981;
|
||
background: rgba(16, 185, 129, 0.12);
|
||
padding: 6rpx 16rpx;
|
||
border-radius: 16rpx;
|
||
|
||
.pulse-point {
|
||
width: 10rpx;
|
||
height: 10rpx;
|
||
border-radius: 50%;
|
||
background: #10B981;
|
||
box-shadow: 0 0 8rpx #10B981;
|
||
}
|
||
}
|
||
}
|
||
|
||
.expert-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 20rpx;
|
||
padding: 0 32rpx;
|
||
|
||
.expert-card {
|
||
background-color: #151D30;
|
||
border-radius: 20rpx;
|
||
overflow: hidden;
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
|
||
.card-cover-wrap {
|
||
position: relative;
|
||
height: 260rpx;
|
||
overflow: hidden;
|
||
background-color: #1E2942;
|
||
|
||
.card-cover {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
object-position: center top;
|
||
}
|
||
|
||
.cover-gradient {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(180deg, rgba(0,0,0,0.05) 0%, rgba(21, 29, 48, 0.85) 100%);
|
||
}
|
||
|
||
.status-tag {
|
||
position: absolute;
|
||
top: 14rpx;
|
||
left: 14rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6rpx;
|
||
padding: 4rpx 12rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 18rpx;
|
||
font-weight: 600;
|
||
|
||
&.online {
|
||
background: rgba(16, 185, 129, 0.9);
|
||
color: #FFF;
|
||
box-shadow: 0 0 10rpx rgba(16, 185, 129, 0.5);
|
||
}
|
||
&.offline {
|
||
background: rgba(100, 116, 139, 0.8);
|
||
color: #FFF;
|
||
}
|
||
|
||
.pulse-dot {
|
||
width: 8rpx;
|
||
height: 8rpx;
|
||
border-radius: 50%;
|
||
background: #FFF;
|
||
animation: pulse 1.5s infinite;
|
||
}
|
||
}
|
||
|
||
.game-badge {
|
||
position: absolute;
|
||
bottom: 14rpx;
|
||
right: 14rpx;
|
||
background: rgba(10, 15, 29, 0.8);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
backdrop-filter: blur(6px);
|
||
font-size: 18rpx;
|
||
color: #E2E8F0;
|
||
padding: 4rpx 10rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
}
|
||
|
||
.card-body {
|
||
padding: 18rpx;
|
||
|
||
.row-nickname {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 8rpx;
|
||
|
||
.nickname {
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
color: #F8FAFC;
|
||
max-width: 200rpx;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.gender-tag {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
border-radius: 8rpx;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
&.female {
|
||
background: rgba(236, 72, 153, 0.2);
|
||
}
|
||
&.male {
|
||
background: rgba(59, 130, 246, 0.2);
|
||
}
|
||
|
||
.gender-svg {
|
||
width: 20rpx;
|
||
height: 20rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.city-badge {
|
||
font-size: 20rpx;
|
||
color: #38BDF8;
|
||
background: rgba(56, 189, 248, 0.12);
|
||
padding: 4rpx 10rpx;
|
||
border-radius: 6rpx;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
margin-bottom: 12rpx;
|
||
border: 1px solid rgba(56, 189, 248, 0.2);
|
||
|
||
.city-pin-svg {
|
||
width: 20rpx;
|
||
height: 20rpx;
|
||
margin-right: 6rpx;
|
||
}
|
||
}
|
||
|
||
.rank-badge {
|
||
font-size: 20rpx;
|
||
color: #F59E0B;
|
||
background: rgba(245, 158, 11, 0.12);
|
||
padding: 4rpx 10rpx;
|
||
border-radius: 6rpx;
|
||
display: inline-block;
|
||
margin-bottom: 12rpx;
|
||
border: 1px solid rgba(245, 158, 11, 0.2);
|
||
}
|
||
|
||
.voice-bar {
|
||
background: rgba(99, 102, 241, 0.15);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
height: 48rpx;
|
||
border-radius: 24rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 16rpx;
|
||
margin-bottom: 16rpx;
|
||
transition: all 0.2s ease;
|
||
box-sizing: border-box;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
flex-shrink: 0;
|
||
|
||
.voice-icon-svg {
|
||
width: 20rpx;
|
||
height: 20rpx;
|
||
margin-right: 8rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.voice-text {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-size: 20rpx;
|
||
color: #818CF8;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.wave-lines {
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 4rpx;
|
||
margin-left: 8rpx;
|
||
flex-shrink: 0;
|
||
|
||
.wave-item {
|
||
width: 4rpx;
|
||
background: #818CF8;
|
||
border-radius: 2rpx;
|
||
|
||
&.w1 { height: 14rpx; animation: bounce 0.6s infinite ease-in-out; }
|
||
&.w2 { height: 22rpx; animation: bounce 0.6s infinite ease-in-out 0.2s; }
|
||
&.w3 { height: 10rpx; animation: bounce 0.6s infinite ease-in-out 0.4s; }
|
||
}
|
||
}
|
||
|
||
&.playing {
|
||
background: rgba(99, 102, 241, 0.3);
|
||
border-color: #818CF8;
|
||
box-shadow: 0 0 14rpx rgba(99, 102, 241, 0.4);
|
||
}
|
||
|
||
&.song-bar {
|
||
background: rgba(244, 63, 94, 0.15);
|
||
border-color: rgba(244, 63, 94, 0.35);
|
||
|
||
.voice-text {
|
||
color: #FB7185;
|
||
}
|
||
|
||
&.playing {
|
||
background: rgba(244, 63, 94, 0.3);
|
||
border-color: #F43F5E;
|
||
box-shadow: 0 0 14rpx rgba(244, 63, 94, 0.4);
|
||
}
|
||
|
||
.wave-lines .wave-item {
|
||
background: #FB7185;
|
||
}
|
||
}
|
||
}
|
||
|
||
.card-footer {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: baseline;
|
||
|
||
.price-wrap {
|
||
color: #10B981;
|
||
.price-symbol { font-size: 22rpx; font-weight: 600; }
|
||
.price-val { font-size: 32rpx; font-weight: 700; margin: 0 2rpx; }
|
||
.price-unit { font-size: 20rpx; color: #64748B; }
|
||
}
|
||
|
||
.order-count {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.index-container.theme-warm {
|
||
background-color: #FDFBF7;
|
||
|
||
.banner-swiper .banner-card {
|
||
box-shadow: 0 8rpx 28rpx rgba(28, 25, 23, 0.12);
|
||
}
|
||
|
||
.header-search {
|
||
background-color: #FDFBF7;
|
||
.search-box {
|
||
background-color: #FFFFFF;
|
||
border-color: #F0ECE4;
|
||
box-shadow: 0 4rpx 16rpx rgba(28, 25, 23, 0.04);
|
||
|
||
&:focus-within {
|
||
border-color: #EA580C;
|
||
box-shadow: 0 0 16rpx rgba(234, 88, 12, 0.2);
|
||
}
|
||
.search-input {
|
||
color: #1C1917;
|
||
}
|
||
.placeholder-text {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.quick-grid, .grid-card {
|
||
background-color: #FFFFFF;
|
||
border-color: #F0ECE4;
|
||
box-shadow: 0 4rpx 20rpx rgba(28, 25, 23, 0.04);
|
||
|
||
.quick-item {
|
||
.quick-title {
|
||
color: #1C1917;
|
||
}
|
||
|
||
.quick-icon-bg {
|
||
&.bg-purple { background: rgba(139, 92, 246, 0.1); border-color: rgba(139, 92, 246, 0.2); }
|
||
&.bg-green { background: rgba(16, 185, 129, 0.1); border-color: rgba(16, 185, 129, 0.2); }
|
||
&.bg-blue { background: rgba(59, 130, 246, 0.1); border-color: rgba(59, 130, 246, 0.2); }
|
||
&.bg-orange { background: rgba(249, 115, 22, 0.1); border-color: rgba(249, 115, 22, 0.2); }
|
||
&.bg-indigo { background: rgba(99, 102, 241, 0.1); border-color: rgba(99, 102, 241, 0.2); }
|
||
&.bg-amber { background: rgba(245, 158, 11, 0.1); border-color: rgba(245, 158, 11, 0.2); }
|
||
&.bg-rose { background: rgba(244, 63, 94, 0.1); border-color: rgba(244, 63, 94, 0.2); }
|
||
&.bg-sky { background: rgba(14, 165, 233, 0.1); border-color: rgba(14, 165, 233, 0.2); }
|
||
&.bg-emerald { background: rgba(16, 185, 129, 0.1); border-color: rgba(16, 185, 129, 0.2); }
|
||
&.bg-teal { background: rgba(20, 184, 166, 0.1); border-color: rgba(20, 184, 166, 0.2); }
|
||
}
|
||
}
|
||
}
|
||
|
||
.section-title-wrap .section-title text {
|
||
color: #1C1917;
|
||
}
|
||
|
||
.expert-grid .expert-card {
|
||
background-color: #FFFFFF;
|
||
border: 1px solid #F0ECE4;
|
||
box-shadow: 0 6rpx 24rpx rgba(28, 25, 23, 0.05);
|
||
|
||
.card-body {
|
||
.row-nickname .nickname, .nickname {
|
||
color: #1C1917;
|
||
}
|
||
|
||
.city-badge {
|
||
color: #0369A1;
|
||
background: rgba(3, 105, 161, 0.08);
|
||
border-color: rgba(3, 105, 161, 0.2);
|
||
}
|
||
|
||
.rank-badge {
|
||
color: #B45309;
|
||
background: rgba(180, 83, 9, 0.08);
|
||
border-color: rgba(180, 83, 9, 0.2);
|
||
}
|
||
|
||
.card-footer {
|
||
.price-wrap .price-unit {
|
||
color: #78716C;
|
||
}
|
||
.order-count {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.voice-bar {
|
||
background: rgba(234, 88, 12, 0.08);
|
||
border: 1px solid rgba(234, 88, 12, 0.2);
|
||
.voice-title, .voice-text {
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes pulse {
|
||
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); }
|
||
70% { transform: scale(1); box-shadow: 0 0 0 8rpx rgba(255, 255, 255, 0); }
|
||
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
|
||
}
|
||
|
||
@keyframes bounce {
|
||
0%, 100% { transform: scaleY(0.4); }
|
||
50% { transform: scaleY(1); }
|
||
}
|
||
</style>
|