- 移除只写不读的 verifyCodeCache(无界内存增长) - 大神收益走势由 20 次逐日/逐周/逐月查询改为 3 次 GROUP BY 聚合 - 派单池地理编码改为并行,避免串行等待外部接口 - 补齐 orders/activities/participants/user_notifications/finance/withdrawals/posts 组合索引;修复 withdrawals 使用不存在 created_at 的失效索引 - 数据库连接池支持 MYSQL_CONNECTION_LIMIT/MYSQL_QUEUE_LIMIT - 首页/弹窗定时器卸载清理,活动列表参与者按活动 id 查询,大厅组局按页取数,图片 lazy-load - 121 套件/792 用例全绿;已部署并验证新索引与 9ms 走势查询
2282 行
59 KiB
Vue
2282 行
59 KiB
Vue
<template>
|
||
<view class="index-container" :class="{ 'theme-warm': currentThemeKey === 'warm' }" :style="navStyle">
|
||
<!-- 顶部搜索栏 (带双向绑定、一键清空、热搜关键词胶囊) -->
|
||
<view class="header-search">
|
||
<view class="search-box">
|
||
<image class="search-icon-img" :src="ICONS.search" mode="aspectFit" />
|
||
<input
|
||
class="search-input"
|
||
v-model="searchKeyword"
|
||
placeholder="搜索大神ID、昵称、游戏或生活陪伴"
|
||
placeholder-class="placeholder-text"
|
||
confirm-type="search"
|
||
/>
|
||
<view class="search-clear-btn btn-active" v-if="searchKeyword" @click="searchKeyword = ''">
|
||
<image class="clear-svg" :src="ICONS.close" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
<!-- 热搜关键词推荐栏 -->
|
||
<view class="hot-search-tags" v-if="!searchKeyword">
|
||
<text class="hot-label">热搜:</text>
|
||
<view
|
||
v-for="word in hotSearchWords"
|
||
:key="word"
|
||
class="hot-tag btn-active"
|
||
@click="searchKeyword = word"
|
||
>
|
||
{{ word }}
|
||
</view>
|
||
</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 || banner?.title" 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="resolveBannerImage(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>
|
||
<view class="title-copy">
|
||
<text class="title-main">精选大神</text>
|
||
<text class="title-sub">DISCOVER · 同城高评分陪伴</text>
|
||
</view>
|
||
</view>
|
||
<view class="section-more btn-active" @click="startInstantMatch">
|
||
<view class="pulse-point"></view>
|
||
<text class="instant-match-txt">雷达即时速配</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 多维度复合筛选工具栏 -->
|
||
<view class="filter-toolbar">
|
||
<view
|
||
class="filter-pill btn-active"
|
||
:class="{ active: filterOnlyOnline }"
|
||
@click="filterOnlyOnline = !filterOnlyOnline"
|
||
>
|
||
<view class="filter-dot"></view>
|
||
<text>仅看在线</text>
|
||
</view>
|
||
|
||
<view
|
||
class="filter-pill btn-active"
|
||
:class="{ active: filterGender !== 'all' }"
|
||
@click="toggleGenderFilter"
|
||
>
|
||
<text>{{ genderFilterLabel }}</text>
|
||
</view>
|
||
|
||
<view
|
||
class="filter-pill btn-active"
|
||
:class="{ active: filterSort !== 'default' }"
|
||
@click="toggleSortFilter"
|
||
>
|
||
<text>{{ sortFilterLabel }}</text>
|
||
</view>
|
||
|
||
<view
|
||
class="filter-pill btn-active"
|
||
:class="{ active: filterPriceRange !== 'all' }"
|
||
@click="showPriceModal = true"
|
||
>
|
||
<text>{{ priceFilterLabel }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 陪玩大神卡片网格 -->
|
||
<view class="expert-grid">
|
||
<view
|
||
v-for="(expert, index) in filteredExperts"
|
||
:key="expert.id"
|
||
class="expert-card card-active"
|
||
:style="{ animationDelay: `${Math.min(index, 8) * 40}ms` }"
|
||
@click="goToExpert(expert.id)"
|
||
>
|
||
<!-- 封面图构图优化:高保真黄金比例与底部流光渐变 -->
|
||
<view class="card-cover-wrap">
|
||
<image class="card-cover" :src="expert.avatar" mode="aspectFill" lazy-load />
|
||
<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>
|
||
<text v-if="expert.levelTag" class="verified-chip">{{ expert.levelTag }}</text>
|
||
</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)">
|
||
<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 class="empty-search-box" v-if="filteredExperts.length === 0">
|
||
<image class="empty-search-icon" :src="ICONS.search" mode="aspectFit" />
|
||
<text class="empty-title">未匹配到符合条件的大神</text>
|
||
<text class="empty-desc">尝试更换关键词或重置筛选条件</text>
|
||
<button class="btn-reset-filters btn-active" @click="resetAllFilters">重置全部筛选</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部自定义导航栏 (发单人端:首页 / 活动中心 / 我要发单 / 友圈 / 个人中心) -->
|
||
<AppTabBar current="home" />
|
||
|
||
<!-- 微信小程序用户隐私保护指引弹窗 (微信合规) -->
|
||
<privacy-modal />
|
||
|
||
<!-- 首页运营全局活动营销弹窗 (配置化可控) -->
|
||
<home-popup-modal />
|
||
|
||
<!-- 价格区间筛选弹窗 -->
|
||
<view class="price-modal-mask" v-if="showPriceModal" @click="showPriceModal = false">
|
||
<view class="price-modal-box" @click.stop>
|
||
<view class="modal-title-row">
|
||
<text class="modal-title">时薪价格区间筛选</text>
|
||
<view class="btn-close btn-active" @click="showPriceModal = false">
|
||
<image class="close-svg" :src="ICONS.close" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
<view class="price-options-grid">
|
||
<view
|
||
v-for="opt in priceOptions"
|
||
:key="opt.val"
|
||
class="price-opt-btn btn-active"
|
||
:class="{ active: filterPriceRange === opt.val }"
|
||
@click="selectPriceOption(opt.val)"
|
||
>
|
||
{{ opt.label }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 雷达即时速配在线大神弹窗 -->
|
||
<view class="price-modal-mask match-modal-mask" v-if="showMatchModal" @click="closeMatchModal">
|
||
<view class="price-modal-box match-modal-box" @click.stop>
|
||
<view class="modal-title-row">
|
||
<text class="modal-title">在线大神即时匹配</text>
|
||
<view class="btn-close btn-active" @click="closeMatchModal">
|
||
<image class="close-svg" :src="ICONS.close" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
|
||
<view class="match-content-body">
|
||
<!-- 雷达扫描态 -->
|
||
<view class="radar-scan-box" v-if="isMatching">
|
||
<view class="radar-wave wave-1"></view>
|
||
<view class="radar-wave wave-2"></view>
|
||
<view class="radar-wave wave-3"></view>
|
||
<view class="radar-center-icon">
|
||
<image class="center-svg" :src="ICONS.flame" mode="aspectFit" />
|
||
</view>
|
||
<text class="radar-status-text">正在全网扫描空闲在线大神...</text>
|
||
</view>
|
||
|
||
<!-- 匹配命中结果展示 -->
|
||
<view class="match-result-box" v-else-if="matchedExpert">
|
||
<view class="match-success-badge">已为你匹配到在线大神</view>
|
||
<view class="matched-expert-card">
|
||
<image class="matched-avatar" :src="matchedExpert.avatar" mode="aspectFill" />
|
||
<view class="matched-info">
|
||
<view class="matched-name-row">
|
||
<text class="matched-name">{{ matchedExpert.nickname }}</text>
|
||
<text class="matched-online-tag" :class="{ offline: !matchedExpert.isOnline }">
|
||
{{ matchedExpert.isOnline ? '接单中' : '休息中' }}
|
||
</text>
|
||
</view>
|
||
<text class="matched-game">{{ matchedMeta }}</text>
|
||
<text class="matched-price">¥{{ matchedExpert.price }}/小时</text>
|
||
</view>
|
||
</view>
|
||
<view class="match-actions-row">
|
||
<button class="btn-match-action secondary btn-active" @click="reMatch">
|
||
<image class="match-btn-icon" :src="ICONS.radarWave" mode="aspectFit" />
|
||
<text>换一位</text>
|
||
</button>
|
||
<button class="btn-match-action primary btn-active" @click="confirmMatchedOrder">
|
||
<text>查看资料并预约</text>
|
||
<image class="match-btn-icon" :src="ICONS.chevronRight" mode="aspectFit" />
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 无在线大神匹配 -->
|
||
<view class="match-empty-box" v-else>
|
||
<text class="empty-txt">暂无可匹配的接单大神,请稍后再试</text>
|
||
<button class="btn-match-action primary btn-active" @click="closeMatchModal">返回大厅</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, watch, onMounted, onUnmounted } from 'vue';
|
||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||
import AppTabBar from '@/components/CustomTabBar.vue';
|
||
import SkeletonScreen from '@/components/SkeletonScreen.vue';
|
||
import PrivacyModal from '@/components/PrivacyModal.vue';
|
||
import HomePopupModal from '@/components/HomePopupModal.vue';
|
||
import { getCompanions, matchCompanions, getBanners, getNavCategories, bindInviteCode } from '@/common/api.js';
|
||
import { getUserLocation } from '@/common/location.js';
|
||
import { ICONS } from '@/common/icons.js';
|
||
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
|
||
import { getNavBarInfo } from '@/common/nav.js';
|
||
import { getCurrentRole, ROLE_TYPES, isExpertCertified, safeRedirectToExpertHall } from '@/common/role.js';
|
||
import { useShare } from '@/common/share.js';
|
||
|
||
useShare({
|
||
title: '同城陪伴 · 专业游戏陪玩与生活陪伴',
|
||
timelineTitle: '同城陪伴 · 发现附近精选大神与热门组局',
|
||
path: '/pages/index/index'
|
||
});
|
||
|
||
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 searchKeyword = ref('');
|
||
const hotSearchWords = ['王者荣耀', '和平精英', '甜美陪伴', '同城微醺', 'KTV嗨唱'];
|
||
|
||
// 多维复合筛选与排序
|
||
const filterOnlyOnline = ref(false);
|
||
const filterGender = ref('all'); // all | female | male
|
||
const filterSort = ref('default'); // default | price_asc | rating_desc | orders_desc
|
||
const filterPriceRange = ref('all'); // all | under_50 | 50_80 | 80_120 | above_120
|
||
const showPriceModal = ref(false);
|
||
|
||
const priceOptions = [
|
||
{ val: 'all', label: '全部时薪' },
|
||
{ val: 'under_50', label: '¥50 以下/时' },
|
||
{ val: '50_80', label: '¥50 - 80/时' },
|
||
{ val: '80_120', label: '¥80 - 120/时' },
|
||
{ val: 'above_120', label: '¥120 以上/时' }
|
||
];
|
||
|
||
const genderFilterLabel = computed(() => {
|
||
if (filterGender.value === 'female') return '仅女生';
|
||
if (filterGender.value === 'male') return '仅男生';
|
||
return '性别不限';
|
||
});
|
||
|
||
const sortFilterLabel = computed(() => {
|
||
if (filterSort.value === 'price_asc') return '价格最低';
|
||
if (filterSort.value === 'rating_desc') return '评分最高';
|
||
if (filterSort.value === 'orders_desc') return '单量最多';
|
||
return '综合推荐';
|
||
});
|
||
|
||
const priceFilterLabel = computed(() => {
|
||
const found = priceOptions.find(o => o.val === filterPriceRange.value);
|
||
return found ? found.label : '时薪价格';
|
||
});
|
||
|
||
// 雷达即时速配状态
|
||
const showMatchModal = ref(false);
|
||
const isMatching = ref(false);
|
||
const matchedExpert = ref(null);
|
||
|
||
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 (keyword = '') => {
|
||
try {
|
||
const kw = String(keyword || '').trim();
|
||
const list = await getCompanions(kw);
|
||
if (Array.isArray(list)) {
|
||
if (kw) {
|
||
// 服务端搜索:以服务端结果为准(含空结果),避免仅过滤已加载的一页数据
|
||
experts.value = list;
|
||
} else if (list.length > 0) {
|
||
experts.value = list;
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.warn('[Index] 获取大神数据失败:', err);
|
||
}
|
||
};
|
||
|
||
// 关键词变化时防抖走服务端搜索,覆盖超出当前加载范围的候选
|
||
let searchTimer = null;
|
||
watch(searchKeyword, (val) => {
|
||
if (searchTimer) clearTimeout(searchTimer);
|
||
searchTimer = setTimeout(() => {
|
||
fetchLiveCompanions(String(val || '').trim());
|
||
}, 350);
|
||
});
|
||
|
||
const fetchLiveBanners = async () => {
|
||
try {
|
||
const list = await getBanners();
|
||
if (Array.isArray(list) && list.length > 0) {
|
||
banners.value = list.filter(b => b && typeof b === 'object');
|
||
}
|
||
} 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 resolveBannerImage = (img) => {
|
||
if (!img) return '';
|
||
if (img.startsWith('/uploads/')) {
|
||
return `https://peiww.gynhc.com${img}`;
|
||
}
|
||
return img;
|
||
};
|
||
|
||
const resolveIcon = (icon) => {
|
||
if (!icon) return ICONS.gridAll;
|
||
if (ICONS[icon]) return ICONS[icon];
|
||
return icon;
|
||
};
|
||
|
||
const onThemeChanged = (key) => {
|
||
currentThemeKey.value = key;
|
||
applyThemeToNativeUI(key);
|
||
};
|
||
|
||
let hasHandledRedirect = false;
|
||
const checkExpertRedirect = () => {
|
||
if (hasHandledRedirect) return true;
|
||
if (getCurrentRole() === ROLE_TYPES.EXPERT && isExpertCertified()) {
|
||
hasHandledRedirect = true;
|
||
safeRedirectToExpertHall('/pages/expert/hall');
|
||
return true;
|
||
}
|
||
return false;
|
||
};
|
||
|
||
onLoad((options) => {
|
||
const rawInvite = options && (options.inviter_id || options.inviterId || options.code || options.scene);
|
||
if (rawInvite) {
|
||
const decoded = decodeURIComponent(String(rawInvite));
|
||
uni.setStorageSync('PENDING_INVITER_CODE', decoded);
|
||
const uid = uni.getStorageSync('userId');
|
||
if (uid) {
|
||
bindInviteCode(uid, decoded).then(() => {
|
||
uni.removeStorageSync('PENDING_INVITER_CODE');
|
||
}).catch(() => {});
|
||
}
|
||
}
|
||
if (checkExpertRedirect()) return;
|
||
});
|
||
|
||
onMounted(() => {
|
||
if (checkExpertRedirect()) return;
|
||
navInfo.value = getNavBarInfo();
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
applyThemeToNativeUI(currentThemeKey.value);
|
||
uni.$on('theme-change', onThemeChanged);
|
||
loadInitialData();
|
||
});
|
||
|
||
onShow(() => {
|
||
if (checkExpertRedirect()) return;
|
||
navInfo.value = getNavBarInfo();
|
||
currentThemeKey.value = getCurrentThemeKey();
|
||
applyThemeToNativeUI(currentThemeKey.value);
|
||
uni.hideTabBar({ animation: false, fail: () => {} });
|
||
fetchLiveCompanions();
|
||
fetchLiveBanners();
|
||
fetchLiveNavCategories();
|
||
});
|
||
|
||
|
||
onUnmounted(() => {
|
||
uni.$off('theme-change', onThemeChanged);
|
||
if (searchTimer) {
|
||
clearTimeout(searchTimer);
|
||
searchTimer = null;
|
||
}
|
||
if (matchTimer) {
|
||
clearTimeout(matchTimer);
|
||
matchTimer = null;
|
||
}
|
||
matchSeq += 1;
|
||
try { if (homeVoiceAudio) { homeVoiceAudio.destroy(); homeVoiceAudio = null; } } catch (_) {}
|
||
});
|
||
|
||
const filteredExperts = computed(() => {
|
||
let list = experts.value;
|
||
|
||
// 1. 关键词搜索 (ID、昵称、擅长游戏、城市、段位)
|
||
if (searchKeyword.value.trim()) {
|
||
const kw = searchKeyword.value.trim().toLowerCase();
|
||
list = list.filter(e => {
|
||
const matchId = String(e.id).toLowerCase().includes(kw);
|
||
const matchName = (e.nickname || '').toLowerCase().includes(kw);
|
||
const matchGame = (e.game || '').toLowerCase().includes(kw);
|
||
const matchCity = (e.city || '').toLowerCase().includes(kw);
|
||
const matchRank = (e.rankTag || '').toLowerCase().includes(kw);
|
||
return matchId || matchName || matchGame || matchCity || matchRank;
|
||
});
|
||
}
|
||
|
||
// 2. 金刚区分类过滤
|
||
if (currentCategory.value !== 'all') {
|
||
const gameMap = {
|
||
offline_meal: '美食陪吃',
|
||
offline_drink: '下午茶陪喝',
|
||
offline_sing: '同城陪唱',
|
||
offline_play: '同城陪玩',
|
||
wzry: '王者荣耀',
|
||
hpjy: '和平精英',
|
||
lol: '英雄联盟',
|
||
val: '无畏契约'
|
||
};
|
||
const target = gameMap[currentCategory.value];
|
||
if (target) {
|
||
list = list.filter(e => e.game === target);
|
||
}
|
||
}
|
||
|
||
// 3. 仅看当前在线
|
||
if (filterOnlyOnline.value) {
|
||
list = list.filter(e => e.isOnline);
|
||
}
|
||
|
||
// 4. 性别筛选 (female: 女生, male: 男生)
|
||
if (filterGender.value !== 'all') {
|
||
list = list.filter(e => e.gender === filterGender.value);
|
||
}
|
||
|
||
// 5. 价格区间过滤
|
||
if (filterPriceRange.value === 'under_50') {
|
||
list = list.filter(e => Number(e.price) < 50);
|
||
} else if (filterPriceRange.value === '50_80') {
|
||
list = list.filter(e => Number(e.price) >= 50 && Number(e.price) <= 80);
|
||
} else if (filterPriceRange.value === '80_120') {
|
||
list = list.filter(e => Number(e.price) > 80 && Number(e.price) <= 120);
|
||
} else if (filterPriceRange.value === 'above_120') {
|
||
list = list.filter(e => Number(e.price) > 120);
|
||
}
|
||
|
||
// 6. 排序逻辑
|
||
if (filterSort.value === 'price_asc') {
|
||
list = [...list].sort((a, b) => Number(a.price) - Number(b.price));
|
||
} else if (filterSort.value === 'rating_desc') {
|
||
list = [...list].sort((a, b) => Number(b.rating || 5) - Number(a.rating || 5));
|
||
} else if (filterSort.value === 'orders_desc') {
|
||
list = [...list].sort((a, b) => Number(b.orderCount || 0) - Number(a.orderCount || 0));
|
||
}
|
||
|
||
return list;
|
||
});
|
||
|
||
// 筛选切换
|
||
const toggleGenderFilter = () => {
|
||
if (filterGender.value === 'all') {
|
||
filterGender.value = 'female';
|
||
} else if (filterGender.value === 'female') {
|
||
filterGender.value = 'male';
|
||
} else {
|
||
filterGender.value = 'all';
|
||
}
|
||
};
|
||
|
||
const toggleSortFilter = () => {
|
||
if (filterSort.value === 'default') {
|
||
filterSort.value = 'price_asc';
|
||
} else if (filterSort.value === 'price_asc') {
|
||
filterSort.value = 'rating_desc';
|
||
} else if (filterSort.value === 'rating_desc') {
|
||
filterSort.value = 'orders_desc';
|
||
} else {
|
||
filterSort.value = 'default';
|
||
}
|
||
};
|
||
|
||
const selectPriceOption = (val) => {
|
||
filterPriceRange.value = val;
|
||
showPriceModal.value = false;
|
||
};
|
||
|
||
const resetAllFilters = () => {
|
||
searchKeyword.value = '';
|
||
currentCategory.value = 'all';
|
||
filterOnlyOnline.value = false;
|
||
filterGender.value = 'all';
|
||
filterSort.value = 'default';
|
||
filterPriceRange.value = 'all';
|
||
};
|
||
|
||
// 即时速配:每次重新拉取大神状态,只匹配「真实注册 + 已通过审核 + 当前接单中」的大神,
|
||
// 排除自己;换一位时优先避开上一位,避免长期命中同一条种子数据。
|
||
let matchTimer = null;
|
||
let matchSeq = 0;
|
||
let lastMatchedId = '';
|
||
const startInstantMatch = async () => {
|
||
const seq = ++matchSeq;
|
||
showMatchModal.value = true;
|
||
isMatching.value = true;
|
||
matchedExpert.value = null;
|
||
if (matchTimer) clearTimeout(matchTimer);
|
||
const startedAt = Date.now();
|
||
|
||
let pos = null;
|
||
try {
|
||
const loc = await getUserLocation();
|
||
pos = { lat: loc.latitude, lng: loc.longitude };
|
||
} catch (_) {
|
||
// 未授权定位时仍可匹配在线大神,只是不展示距离
|
||
}
|
||
|
||
let freshList = [];
|
||
try {
|
||
freshList = await matchCompanions({ ...(pos || {}), radius: 5, limit: 20 });
|
||
} catch (_) {}
|
||
if (seq !== matchSeq) return;
|
||
|
||
const wait = Math.max(0, 600 - (Date.now() - startedAt));
|
||
matchTimer = setTimeout(() => {
|
||
if (seq !== matchSeq) return;
|
||
isMatching.value = false;
|
||
const rotated = freshList.filter((e) => String(e.id) !== String(lastMatchedId));
|
||
const pool = rotated.length > 0 ? rotated : freshList;
|
||
if (pool.length === 0) {
|
||
matchedExpert.value = null;
|
||
return;
|
||
}
|
||
// 服务端已按真实距离升序;换一位取下一位最近的大神
|
||
const picked = pool[0];
|
||
matchedExpert.value = picked;
|
||
lastMatchedId = String(picked.id);
|
||
}, wait);
|
||
};
|
||
|
||
const matchedMeta = computed(() => {
|
||
const e = matchedExpert.value;
|
||
if (!e) return '';
|
||
const parts = [e.game, e.rankTag].filter(Boolean);
|
||
if (e.distanceText && e.distanceText !== '同城') parts.push(`距你 ${e.distanceText}`);
|
||
return parts.join(' · ');
|
||
});
|
||
|
||
const closeMatchModal = () => {
|
||
showMatchModal.value = false;
|
||
matchSeq += 1;
|
||
if (matchTimer) clearTimeout(matchTimer);
|
||
};
|
||
|
||
const reMatch = () => {
|
||
startInstantMatch();
|
||
};
|
||
|
||
const confirmMatchedOrder = () => {
|
||
if (matchedExpert.value) {
|
||
const id = matchedExpert.value.id;
|
||
closeMatchModal();
|
||
goToExpert(id);
|
||
}
|
||
};
|
||
|
||
let homeVoiceAudio = null;
|
||
const toggleVoice = (expertItem) => {
|
||
const id = expertItem && expertItem.id !== undefined ? expertItem.id : expertItem;
|
||
const url = expertItem && expertItem.voiceIntroUrl;
|
||
if (!url) {
|
||
uni.showToast({ title: '该大神还未上传语音介绍', icon: 'none' });
|
||
return;
|
||
}
|
||
if (playingId.value === id) {
|
||
try { homeVoiceAudio && homeVoiceAudio.stop(); } catch (_) {}
|
||
playingId.value = null;
|
||
return;
|
||
}
|
||
try { homeVoiceAudio && homeVoiceAudio.stop(); } catch (_) {}
|
||
if (!homeVoiceAudio) {
|
||
homeVoiceAudio = uni.createInnerAudioContext();
|
||
homeVoiceAudio.onEnded(() => { playingId.value = null; });
|
||
homeVoiceAudio.onError(() => {
|
||
playingId.value = null;
|
||
uni.showToast({ title: '语音播放失败', icon: 'none' });
|
||
});
|
||
}
|
||
homeVoiceAudio.src = url;
|
||
homeVoiceAudio.play();
|
||
playingId.value = id;
|
||
};
|
||
|
||
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">
|
||
.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); }
|
||
}
|
||
|
||
.search-clear-btn {
|
||
padding: 8rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.clear-svg {
|
||
width: 28rpx;
|
||
height: 28rpx;
|
||
}
|
||
}
|
||
|
||
.hot-search-tags {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 12rpx;
|
||
margin-top: 14rpx;
|
||
|
||
.hot-label {
|
||
font-size: 20rpx;
|
||
color: #64748B;
|
||
}
|
||
|
||
.hot-tag {
|
||
font-size: 20rpx;
|
||
color: #94A3B8;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
padding: 4rpx 14rpx;
|
||
border-radius: 20rpx;
|
||
transition: all 0.2s;
|
||
}
|
||
}
|
||
|
||
.instant-match-txt {
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
color: #10B981;
|
||
text-decoration: underline;
|
||
}
|
||
|
||
.filter-toolbar {
|
||
display: flex;
|
||
gap: 12rpx;
|
||
padding: 0 32rpx;
|
||
margin-bottom: 24rpx;
|
||
overflow-x: auto;
|
||
white-space: nowrap;
|
||
|
||
.filter-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8rpx;
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
padding: 10rpx 20rpx;
|
||
border-radius: 28rpx;
|
||
transition: all 0.2s;
|
||
|
||
.filter-dot {
|
||
width: 10rpx;
|
||
height: 10rpx;
|
||
border-radius: 50%;
|
||
background: #64748B;
|
||
}
|
||
|
||
&.active {
|
||
color: #FFFFFF;
|
||
background: rgba(99, 102, 241, 0.25);
|
||
border-color: #6366F1;
|
||
font-weight: 600;
|
||
|
||
.filter-dot {
|
||
background: #10B981;
|
||
box-shadow: 0 0 8rpx #10B981;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.empty-search-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 80rpx 32rpx;
|
||
text-align: center;
|
||
|
||
.empty-search-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
opacity: 0.3;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.empty-title {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #E2E8F0;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.empty-desc {
|
||
font-size: 22rpx;
|
||
color: #64748B;
|
||
margin-bottom: 32rpx;
|
||
}
|
||
|
||
.btn-reset-filters {
|
||
font-size: 24rpx;
|
||
color: #6366F1;
|
||
background: rgba(99, 102, 241, 0.12);
|
||
border: 1px solid rgba(99, 102, 241, 0.3);
|
||
border-radius: 30rpx;
|
||
height: 64rpx;
|
||
line-height: 62rpx;
|
||
padding: 0 36rpx;
|
||
margin: 0;
|
||
}
|
||
}
|
||
|
||
.price-modal-mask {
|
||
position: fixed;
|
||
inset: 0;
|
||
background-color: rgba(0, 0, 0, 0.7);
|
||
backdrop-filter: blur(8px);
|
||
z-index: 999;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 32rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.price-modal-box {
|
||
width: 100%;
|
||
max-width: 560rpx;
|
||
background: #151D30;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 28rpx;
|
||
padding: 32rpx;
|
||
box-sizing: border-box;
|
||
|
||
.modal-title-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 24rpx;
|
||
|
||
.modal-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
}
|
||
|
||
.btn-close {
|
||
width: 44rpx;
|
||
height: 44rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.close-svg {
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.price-options-grid {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12rpx;
|
||
|
||
.price-opt-btn {
|
||
text-align: center;
|
||
padding: 18rpx 0;
|
||
font-size: 24rpx;
|
||
color: #94A3B8;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 14rpx;
|
||
transition: all 0.2s;
|
||
|
||
&.active {
|
||
color: #FFFFFF;
|
||
background: rgba(99, 102, 241, 0.3);
|
||
border-color: #6366F1;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.match-modal-box {
|
||
max-width: 600rpx;
|
||
|
||
.match-content-body {
|
||
padding: 20rpx 0 10rpx;
|
||
}
|
||
|
||
.radar-scan-box {
|
||
position: relative;
|
||
height: 320rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.radar-wave {
|
||
position: absolute;
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
border-radius: 50%;
|
||
border: 2px solid #6366F1;
|
||
animation: radarRipples 2s infinite cubic-bezier(0.1, 0.2, 0.3, 1);
|
||
|
||
&.wave-2 {
|
||
animation-delay: 0.6s;
|
||
}
|
||
&.wave-3 {
|
||
animation-delay: 1.2s;
|
||
}
|
||
}
|
||
|
||
.radar-center-icon {
|
||
width: 72rpx;
|
||
height: 72rpx;
|
||
border-radius: 36rpx;
|
||
background: linear-gradient(135deg, #6366F1, #8B5CF6);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 2;
|
||
|
||
.center-svg {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
}
|
||
}
|
||
|
||
.radar-status-text {
|
||
position: absolute;
|
||
bottom: 10rpx;
|
||
font-size: 22rpx;
|
||
color: #94A3B8;
|
||
}
|
||
}
|
||
|
||
.match-result-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
|
||
.match-success-badge {
|
||
font-size: 20rpx;
|
||
color: #10B981;
|
||
background: rgba(16, 185, 129, 0.15);
|
||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 16rpx;
|
||
font-weight: 600;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.matched-expert-card {
|
||
width: 100%;
|
||
display: flex;
|
||
gap: 16rpx;
|
||
align-items: center;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 16rpx;
|
||
padding: 16rpx;
|
||
box-sizing: border-box;
|
||
margin-bottom: 24rpx;
|
||
|
||
.matched-avatar {
|
||
width: 100rpx;
|
||
height: 100rpx;
|
||
border-radius: 50rpx;
|
||
border: 2rpx solid #10B981;
|
||
}
|
||
|
||
.matched-info {
|
||
flex: 1;
|
||
|
||
.matched-name-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
margin-bottom: 4rpx;
|
||
|
||
.matched-name {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #F8FAFC;
|
||
}
|
||
|
||
.matched-online-tag {
|
||
font-size: 16rpx;
|
||
color: #FFFFFF;
|
||
background: #059669;
|
||
padding: 2rpx 8rpx;
|
||
border-radius: 6rpx;
|
||
|
||
&.offline {
|
||
background: #64748B;
|
||
}
|
||
}
|
||
}
|
||
|
||
.matched-game {
|
||
font-size: 20rpx;
|
||
color: #94A3B8;
|
||
display: block;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.matched-price {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #10B981;
|
||
}
|
||
}
|
||
}
|
||
|
||
.match-actions-row {
|
||
width: 100%;
|
||
display: flex;
|
||
gap: 20rpx;
|
||
|
||
.btn-match-action {
|
||
height: 84rpx;
|
||
line-height: 82rpx;
|
||
font-size: 26rpx;
|
||
font-weight: 600;
|
||
border-radius: 42rpx;
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8rpx;
|
||
box-sizing: border-box;
|
||
|
||
&.secondary {
|
||
flex: 1;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
color: #CBD5E1;
|
||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||
}
|
||
|
||
&.primary {
|
||
flex: 1.5;
|
||
background: linear-gradient(135deg, #10B981, #059669);
|
||
color: #FFFFFF;
|
||
border: none;
|
||
box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.28);
|
||
}
|
||
|
||
.match-btn-icon {
|
||
width: 30rpx;
|
||
height: 30rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.match-empty-box {
|
||
padding: 40rpx 0;
|
||
text-align: center;
|
||
|
||
.empty-txt {
|
||
font-size: 24rpx;
|
||
color: #94A3B8;
|
||
display: block;
|
||
margin-bottom: 24rpx;
|
||
}
|
||
|
||
.btn-match-action {
|
||
height: 70rpx;
|
||
line-height: 68rpx;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
border-radius: 35rpx;
|
||
background: linear-gradient(135deg, #6366F1, #4F46E5);
|
||
color: #FFFFFF;
|
||
}
|
||
}
|
||
}
|
||
|
||
@keyframes radarRipples {
|
||
0% {
|
||
transform: scale(0.6);
|
||
opacity: 0.9;
|
||
}
|
||
100% {
|
||
transform: scale(2.2);
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
// 暖色模式补充
|
||
.theme-warm {
|
||
.hot-search-tags .hot-tag {
|
||
background: #F5F2EC;
|
||
border-color: #E7E5E4;
|
||
color: #78716C;
|
||
}
|
||
|
||
.filter-toolbar .filter-pill {
|
||
background: #F5F2EC;
|
||
border-color: #E7E5E4;
|
||
color: #78716C;
|
||
|
||
&.active {
|
||
background: #FFF7ED;
|
||
border-color: #EA580C;
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
|
||
.price-modal-box {
|
||
background: #FFFFFF;
|
||
border-color: #E7E5E4;
|
||
|
||
.modal-title-row .modal-title {
|
||
color: #1C1917;
|
||
}
|
||
|
||
.price-options-grid .price-opt-btn {
|
||
background: #FAFAF9;
|
||
border-color: #E7E5E4;
|
||
color: #78716C;
|
||
|
||
&.active {
|
||
background: #FFF7ED;
|
||
border-color: #EA580C;
|
||
color: #EA580C;
|
||
}
|
||
}
|
||
}
|
||
|
||
.price-modal-box.match-modal-box {
|
||
.matched-expert-card {
|
||
background: #FDFBF7;
|
||
border-color: #E7E5E4;
|
||
}
|
||
|
||
.matched-info .matched-name-row .matched-name {
|
||
color: #1C1917;
|
||
}
|
||
|
||
.matched-info .matched-game {
|
||
color: #78716C;
|
||
}
|
||
|
||
.match-actions-row .btn-match-action.secondary {
|
||
background: #F5F2EC;
|
||
border-color: #E7E5E4;
|
||
color: #57534E;
|
||
}
|
||
|
||
.match-empty-box .empty-txt,
|
||
.radar-status-text {
|
||
color: #78716C;
|
||
}
|
||
}
|
||
}
|
||
/* ===== Premium visual layer: 轻奢夜色 · 克制霓虹 ===== */
|
||
.index-container {
|
||
background:
|
||
radial-gradient(110% 48% at 50% -8%, rgba(99, 102, 241, 0.20), transparent 62%),
|
||
radial-gradient(90% 40% at 110% 28%, rgba(217, 164, 65, 0.06), transparent 70%),
|
||
var(--surface-0);
|
||
}
|
||
|
||
.index-container .header-search {
|
||
background: rgba(7, 11, 20, 0.88);
|
||
backdrop-filter: blur(18px);
|
||
border-bottom: 1rpx solid rgba(255, 255, 255, 0.04);
|
||
padding-bottom: 24rpx;
|
||
|
||
.search-box {
|
||
height: 84rpx;
|
||
background: rgba(22, 31, 51, 0.9);
|
||
border-color: rgba(255, 255, 255, 0.1);
|
||
box-shadow:
|
||
inset 0 1rpx 0 rgba(255, 255, 255, 0.06),
|
||
0 10rpx 28rpx rgba(0, 0, 0, 0.18);
|
||
|
||
&:focus-within {
|
||
border-color: rgba(129, 140, 248, 0.65);
|
||
box-shadow:
|
||
inset 0 1rpx 0 rgba(255, 255, 255, 0.08),
|
||
0 0 0 4rpx rgba(99, 102, 241, 0.12);
|
||
}
|
||
}
|
||
|
||
.hot-tag {
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1rpx solid rgba(255, 255, 255, 0.07);
|
||
color: #A7B3CB;
|
||
}
|
||
}
|
||
|
||
.index-container .banner-swiper {
|
||
height: 236rpx;
|
||
margin-bottom: 26rpx;
|
||
|
||
.banner-card {
|
||
border-radius: 28rpx;
|
||
padding: 30rpx 32rpx;
|
||
box-shadow: 0 18rpx 44rpx rgba(0, 0, 0, 0.32);
|
||
}
|
||
|
||
.banner-overlay {
|
||
background: linear-gradient(100deg, rgba(5, 9, 18, 0.88) 0%, rgba(5, 9, 18, 0.42) 55%, rgba(5, 9, 18, 0.18) 100%);
|
||
}
|
||
|
||
.banner-content {
|
||
.banner-tag {
|
||
background: rgba(7, 11, 20, 0.42);
|
||
border-color: rgba(255, 255, 255, 0.2);
|
||
letter-spacing: 1rpx;
|
||
}
|
||
.banner-title {
|
||
font-size: 34rpx;
|
||
letter-spacing: 0.5rpx;
|
||
line-height: 1.35;
|
||
}
|
||
.banner-sub {
|
||
color: rgba(255, 255, 255, 0.78);
|
||
letter-spacing: 0.3rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
.index-container .quick-grid.grid-card {
|
||
background: rgba(15, 22, 38, 0.78);
|
||
border: 1rpx solid var(--hairline);
|
||
border-radius: 26rpx;
|
||
box-shadow:
|
||
inset 0 1rpx 0 rgba(255, 255, 255, 0.05),
|
||
0 14rpx 36rpx rgba(0, 0, 0, 0.2);
|
||
|
||
.quick-icon-bg {
|
||
border-radius: 26rpx;
|
||
box-shadow: inset 0 0 0 1rpx rgba(255, 255, 255, 0.06);
|
||
}
|
||
|
||
.quick-title {
|
||
font-size: 21rpx;
|
||
color: #CBD5E1;
|
||
font-weight: 600;
|
||
letter-spacing: 0.2rpx;
|
||
}
|
||
|
||
.quick-badge {
|
||
background: linear-gradient(135deg, #D9A441, #B7791F);
|
||
box-shadow: 0 3rpx 10rpx rgba(217, 164, 65, 0.3);
|
||
}
|
||
}
|
||
|
||
.index-container .section-title-wrap {
|
||
padding-bottom: 18rpx;
|
||
|
||
.section-title {
|
||
.title-line {
|
||
width: 6rpx;
|
||
height: 36rpx;
|
||
border-radius: 3rpx;
|
||
background: linear-gradient(180deg, #F1C878, #D9A441);
|
||
box-shadow: 0 0 14rpx rgba(217, 164, 65, 0.25);
|
||
}
|
||
|
||
.title-copy {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4rpx;
|
||
}
|
||
|
||
.title-main {
|
||
font-size: 32rpx;
|
||
line-height: 1.2;
|
||
font-weight: 800;
|
||
letter-spacing: 0.3rpx;
|
||
}
|
||
|
||
.title-sub {
|
||
font-size: 17rpx;
|
||
line-height: 1.2;
|
||
color: #64748B;
|
||
letter-spacing: 2rpx;
|
||
font-weight: 500;
|
||
}
|
||
}
|
||
|
||
.section-more {
|
||
background: rgba(16, 185, 129, 0.08);
|
||
border: 1rpx solid rgba(16, 185, 129, 0.22);
|
||
border-radius: 999rpx;
|
||
padding: 8rpx 18rpx;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.index-container .filter-toolbar {
|
||
padding: 0 32rpx 20rpx;
|
||
gap: 12rpx;
|
||
flex-wrap: wrap;
|
||
|
||
.filter-pill {
|
||
flex-shrink: 0;
|
||
background: rgba(255, 255, 255, 0.04);
|
||
border: 1rpx solid var(--hairline);
|
||
border-radius: 999rpx;
|
||
padding: 10rpx 20rpx;
|
||
color: #A7B3CB;
|
||
|
||
&.active {
|
||
background: rgba(99, 102, 241, 0.14);
|
||
border-color: rgba(129, 140, 248, 0.38);
|
||
color: #C7D2FE;
|
||
}
|
||
}
|
||
}
|
||
|
||
.index-container .expert-grid {
|
||
gap: 22rpx;
|
||
|
||
.expert-card {
|
||
background: linear-gradient(180deg, rgba(22, 31, 51, 0.96) 0%, rgba(13, 20, 36, 0.98) 100%);
|
||
border: 1rpx solid var(--hairline);
|
||
border-radius: 24rpx;
|
||
box-shadow: 0 12rpx 30rpx rgba(0, 0, 0, 0.24);
|
||
animation: premiumCardEnter 0.5s var(--ease-out) both;
|
||
|
||
.card-cover-wrap {
|
||
height: 300rpx;
|
||
}
|
||
|
||
.card-cover {
|
||
transition: transform 0.45s var(--ease-out);
|
||
}
|
||
|
||
&:active .card-cover {
|
||
transform: scale(1.035);
|
||
}
|
||
|
||
.cover-gradient {
|
||
background: linear-gradient(180deg, transparent 45%, rgba(5, 9, 18, 0.72) 100%);
|
||
}
|
||
|
||
.status-tag {
|
||
background: rgba(7, 11, 20, 0.55);
|
||
border: 1rpx solid rgba(255, 255, 255, 0.16);
|
||
backdrop-filter: blur(10px);
|
||
|
||
&.online {
|
||
color: #6EE7B7;
|
||
}
|
||
}
|
||
|
||
.game-badge {
|
||
background: rgba(7, 11, 20, 0.48);
|
||
border: 1rpx solid rgba(255, 255, 255, 0.12);
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
|
||
.card-body {
|
||
padding: 20rpx 18rpx 18rpx;
|
||
}
|
||
|
||
.nickname {
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
letter-spacing: 0.2rpx;
|
||
}
|
||
|
||
.verified-chip {
|
||
flex-shrink: 0;
|
||
font-size: 15rpx;
|
||
color: #F1C878;
|
||
background: rgba(217, 164, 65, 0.1);
|
||
border: 1rpx solid rgba(217, 164, 65, 0.3);
|
||
border-radius: 6rpx;
|
||
padding: 2rpx 8rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.rank-badge,
|
||
.city-badge {
|
||
color: #94A3B8;
|
||
}
|
||
|
||
.voice-bar {
|
||
background: rgba(99, 102, 241, 0.09);
|
||
border: 1rpx solid rgba(99, 102, 241, 0.16);
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
.price-val {
|
||
font-size: 36rpx;
|
||
letter-spacing: 0.3rpx;
|
||
}
|
||
|
||
.order-count {
|
||
color: #7C89A6;
|
||
}
|
||
}
|
||
}
|
||
|
||
.index-container .match-modal-mask {
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.index-container .match-modal-mask .match-modal-box {
|
||
width: 100%;
|
||
max-width: 450px;
|
||
border-radius: 32rpx 32rpx 0 0;
|
||
border-bottom: none;
|
||
padding: 22rpx 36rpx calc(38rpx + env(safe-area-inset-bottom));
|
||
animation: premiumSheetUp 0.32s var(--ease-out) both;
|
||
}
|
||
|
||
.index-container .match-modal-mask .match-modal-box::before {
|
||
content: '';
|
||
display: block;
|
||
width: 72rpx;
|
||
height: 8rpx;
|
||
border-radius: 8rpx;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
margin: 0 auto 20rpx;
|
||
}
|
||
|
||
/* 暖色主题 */
|
||
.index-container.theme-warm {
|
||
background:
|
||
radial-gradient(100% 46% at 50% -8%, rgba(249, 115, 22, 0.10), transparent 62%),
|
||
#FDFBF7;
|
||
|
||
.header-search {
|
||
background: rgba(253, 251, 247, 0.92);
|
||
border-bottom-color: rgba(28, 25, 23, 0.05);
|
||
|
||
.search-box {
|
||
background: #FFFFFF;
|
||
border-color: #EEEAE3;
|
||
box-shadow: 0 10rpx 26rpx rgba(28, 25, 23, 0.05);
|
||
}
|
||
|
||
.hot-tag {
|
||
background: #F7F3EC;
|
||
border-color: #EEEAE3;
|
||
color: #78716C;
|
||
}
|
||
}
|
||
|
||
.quick-grid.grid-card {
|
||
background: #FFFFFF;
|
||
border-color: #F0ECE4;
|
||
box-shadow: 0 12rpx 28rpx rgba(28, 25, 23, 0.05);
|
||
}
|
||
|
||
.section-title-wrap {
|
||
.title-main {
|
||
color: #1C1917;
|
||
}
|
||
.title-sub {
|
||
color: #A8A29E;
|
||
}
|
||
}
|
||
|
||
.filter-toolbar .filter-pill {
|
||
background: #F8F5F0;
|
||
border-color: #EEEAE3;
|
||
color: #78716C;
|
||
|
||
&.active {
|
||
background: #FFF7ED;
|
||
border-color: rgba(234, 88, 12, 0.35);
|
||
color: #C2410C;
|
||
}
|
||
}
|
||
|
||
.expert-grid .expert-card {
|
||
background: #FFFFFF;
|
||
border-color: #F0ECE4;
|
||
box-shadow: 0 12rpx 30rpx rgba(28, 25, 23, 0.06);
|
||
|
||
.cover-gradient {
|
||
background: linear-gradient(180deg, transparent 50%, rgba(28, 25, 23, 0.55) 100%);
|
||
}
|
||
|
||
.status-tag {
|
||
background: rgba(255, 255, 255, 0.82);
|
||
border-color: rgba(28, 25, 23, 0.08);
|
||
color: #57534E;
|
||
backdrop-filter: blur(10px);
|
||
|
||
&.online {
|
||
color: #047857;
|
||
}
|
||
}
|
||
|
||
.game-badge {
|
||
background: rgba(255, 255, 255, 0.84);
|
||
border-color: rgba(28, 25, 23, 0.08);
|
||
color: #57534E;
|
||
}
|
||
|
||
.voice-bar {
|
||
background: #FFF7ED;
|
||
border-color: #FED7AA;
|
||
}
|
||
|
||
.nickname {
|
||
color: #1C1917;
|
||
}
|
||
}
|
||
|
||
.match-modal-mask .match-modal-box {
|
||
background: #FFFFFF;
|
||
border-color: #E7E5E4;
|
||
}
|
||
|
||
.match-modal-mask .match-modal-box::before {
|
||
background: rgba(28, 25, 23, 0.14);
|
||
}
|
||
}
|
||
|
||
@keyframes premiumCardEnter {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(16rpx) scale(0.985);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0) scale(1);
|
||
}
|
||
}
|
||
|
||
@keyframes premiumSheetUp {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(48rpx);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
</style>
|