文件
shanghaipeiwan/pwuniapp/pages/index/index.vue

984 行
27 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

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