mrr.sj.front/pages/search/search-result.vue

1020 lines
30 KiB
Vue
Raw Permalink Normal View History

2026-03-24 11:45:13 +08:00
<template>
<view class="search-page">
<!-- 自定义导航栏 -->
<custom-navbar title="搜索" :leftImg="'https://mrrplus.oss-cn-beijing.aliyuncs.com/photo/system/e8f7c37f-b446-41da-b578-59098a56bc4d.png'" :showUser="true"
2026-06-02 11:39:23 +08:00
backgroundColor="linear-gradient( 134deg, #F52540 0%, #FF4767 100%)" titleColor="#fff"
2026-03-24 11:45:13 +08:00
borderBottom="none"></custom-navbar>
<!-- 顶部搜索和标签区域 -->
<view class="page-top">
<search-box v-model="listQuery.title" placeholder="请输入手艺人/服务/店铺等" @search="search" @confirm="search" />
<view class="tabList">
<view @click="changeType(tab.value)" :key="tab.value" class="tabItem"
:class="{ tabItem_active: tab.value == listQuery.type }" v-for="tab in tabList">
{{ tab.label }}
</view>
</view>
<!-- 筛选组件 - 只在服务类型(3)显示 -->
<view v-if="listQuery.type === 3" class="filter-container">
<filterVue @filter-change="handleFilterChange"
@open="onFilterShowChange(true)"
@close="onFilterShowChange(false)"
/>
</view>
</view>
<!-- 加载提示 -->
<view v-if="isLoading" class="simple-loading">
<view class="loading-spinner"></view>
<text class="loading-text">搜索中...</text>
</view>
<!-- 列表容器 -->
<view class="list-container" :class="{ 'loading-blur': isLoading }">
<CommonList background="transparent" ref="groupRef" :apiUrl="'/user/getSearchList'"
:listQuery="listQuery"
:listScrollHeight="`calc(100vh - ${totalTopHeight}rpx)`"
:key="listQuery.type + '_' + (listQuery._timestamp || '')">
<template #listData="{ list }">
<!-- 服务卡片 -->
<view v-if="listQuery.type == 3 && list[0] && list[0].search_type == 3">
<view v-for="(item, index) in list" :key="index" class="card-wrapper-service">
<serviceCardList class="service-card" :list="[item]"></serviceCardList>
</view>
</view>
<!-- 手艺人卡片 -->
<view v-if="listQuery.type == 1 && list[0] && list[0].search_type == 1">
<!-- <view v-for="(item, index) in list" :key="index" class="card-wrapper-syr">
<view class="inner-content">
<syrCardList :list="[item]"></syrCardList>
</view>
</view> -->
<syrCardList :list="list"></syrCardList>
</view>
<!-- 商家卡片 -->
<view v-if="listQuery.type == 2 && list[0] && list[0].search_type == 2" class="shop_box">
<view v-for="(item, index) in list" :key="index" class="card-wrapper">
<shopCard :shop="item"></shopCard>
</view>
</view>
</template>
<!-- 空数据状态 -->
<template #empty>
<noData></noData>
</template>
</CommonList>
</view>
</view>
</template>
<script>
import CommonList from "@/components/common/CommonList.vue";
import searchBox from "./components/search_box_result.vue";
import syrCardList from "./components/syrCardList.vue";
import serviceCardList from "./components/serviceCardList.vue";
import shopCard from "./components/shopCard.vue";
import filterVue from "./components/filter.vue";
import http from "@/utils/request.js";
export default {
components: {
searchBox,
CommonList,
syrCardList,
serviceCardList,
shopCard,
filterVue,
},
data() {
return {
totalTopHeight: 300, // 顶部区域总高度估算值会在onLoad中重新计算
listQuery: {
page: 1,
limit: 10,
type: 3,
title: "",
userLngz: "",
userLat: "",
// 筛选参数
serviceType: null,
distance: null,
minPrice: null,
maxPrice: null,
sortType: null,
// 用于强制刷新的时间戳
_timestamp: 0,
},
tabList: [
{
label: "服务",
value: 3
},
{
label: "手艺人",
value: 1
},
{
label: "门店",
value: 2
},
],
isSearching: false, // 防止重复搜索
searchTimer: null, // 防抖定时器
hasInitialSearch: false, // 标记是否已执行初始智能搜索
userChangedTab: false, // 标记用户是否手动切换过tab
lastSearchText: "", // 记录上一次的搜索词
forceNormalSearch: false, // 强制普通搜索标志
isLoading: false, // 加载状态
loadingTimer: null, // 加载计时器(防止加载时间过短闪烁)
minLoadingTime: 100, // 最小加载显示时间(毫秒)
};
},
onLoad(options) {
// 接收可能的搜索词参数
this.listQuery.title = options.searchText || '';
this.lastSearchText = this.listQuery.title; // 记录初始搜索词
// 获取用户地理位置
let userAdrees = uni.getStorageSync("userAdrees");
if (userAdrees) {
this.listQuery.userLngz = userAdrees.longitude;
this.listQuery.userLat = userAdrees.latitude;
} else {
// 默认位置(备用)
this.listQuery.userLngz = "117.330043";
this.listQuery.userLat = "38.372266";
}
this.$nextTick(() => {
this.showLoading();
// 只在第一次进入页面时执行智能搜索
this.performInitialSearch();
// 延迟计算高度确保DOM渲染完成
setTimeout(() => {
this.calculateTopHeight();
}, 100);
});
},
methods: {
// 计算顶部区域总高度(用于设置列表滚动区域高度)
calculateTopHeight() {
// 创建选择器查询
const query = uni.createSelectorQuery().in(this);
// 查询导航栏高度
query.select('.custom-navbar').boundingClientRect();
query.select('.page-top').boundingClientRect();
query.exec((res) => {
if (res[0] && res[1]) {
const navHeight = res[0].height;
const topHeight = res[1].height;
const systemInfo = uni.getSystemInfoSync();
const pxToRpx = 750 / systemInfo.windowWidth;
// 转换为rpx并四舍五入
const totalHeight = Math.ceil((navHeight + topHeight) * pxToRpx);
this.totalTopHeight = totalHeight;
console.log('导航栏高度(px):', navHeight);
console.log('顶部区域高度(px):', topHeight);
console.log('总高度(rpx):', totalHeight);
}
});
},
// 返回上一页
goBack() {
// #ifdef APP-PLUS
uni.navigateBack();
// #endif
// #ifdef MP-WEIXIN
uni.navigateBack({
delta: 1,
});
// #endif
},
// 切换tab类型
changeType(type) {
// 标记用户手动切换tab
this.userChangedTab = true;
// 切换到指定tab后后续搜索都使用普通搜索不检查其他tab
this.forceNormalSearch = true;
this.listQuery.type = type;
// 切换类型时重置筛选参数(只有服务类型才有筛选)
if (type !== 3) {
this.resetFilterParams();
}
// 显示加载
this.showLoading();
// 用户手动切换tab时只执行普通搜索
this.performNormalSearch();
// 重新计算高度(因为筛选组件可能显示/隐藏)
this.$nextTick(() => {
setTimeout(() => {
this.calculateTopHeight();
}, 50);
});
},
// 搜索方法(点击搜索按钮或确认搜索时触发)
search() {
// 防抖处理,避免频繁请求
if (this.searchTimer) {
clearTimeout(this.searchTimer);
}
// 如果正在搜索中,等待
if (this.isSearching) {
console.log('正在搜索中,稍后重试');
return;
}
this.searchTimer = setTimeout(() => {
// 检查搜索词是否有变化
const searchTextChanged = this.listQuery.title !== this.lastSearchText;
const isEmptySearch = !this.listQuery.title || this.listQuery.title.trim() === '';
// 如果搜索框为空,直接获取当前类型的全部数据
if (isEmptySearch) {
console.log('搜索框为空,直接获取全部数据');
this.lastSearchText = '';
this.showLoading();
// 清空搜索词,确保后端能正确返回全部数据
this.listQuery.title = '';
// 重置页码,从第一页开始
this.listQuery.page = 1;
// 添加时间戳强制刷新列表
this.listQuery._timestamp = Date.now();
// 执行搜索
this.performNormalSearch();
return;
}
// 如果搜索词有变化,重置智能搜索状态
if (searchTextChanged) {
console.log('搜索词变化,重置智能搜索状态');
this.lastSearchText = this.listQuery.title;
this.hasInitialSearch = false;
this.forceNormalSearch = false;
}
// 显示加载动画
this.showLoading();
// 根据当前状态决定使用哪种搜索方式
if (this.forceNormalSearch) {
console.log('强制使用普通搜索');
this.performNormalSearch();
} else if (this.userChangedTab) {
// 用户已经切换过tab但搜索词变化了可以考虑重新智能搜索
if (searchTextChanged && !this.hasInitialSearch) {
console.log('用户切换过tab但搜索词变化尝试智能搜索');
this.performSmartSearch();
} else {
console.log('用户切换过tab且搜索词未变使用普通搜索');
this.performNormalSearch();
}
} else if (!this.hasInitialSearch) {
console.log('首次或搜索词变化,使用智能搜索');
this.performSmartSearch();
} else {
console.log('已经执行过智能搜索,使用普通搜索');
// 否则执行普通搜索
this.performNormalSearch();
}
}, 300);
},
// 显示加载动画
showLoading() {
if (this.loadingTimer) {
clearTimeout(this.loadingTimer);
}
this.isLoading = true;
},
// 隐藏加载动画(带最小时间,避免闪烁)
hideLoading() {
if (this.loadingTimer) {
clearTimeout(this.loadingTimer);
}
// 确保至少显示一定时间,避免闪烁
this.loadingTimer = setTimeout(() => {
this.isLoading = false;
this.loadingTimer = null;
console.log('加载动画已隐藏');
}, this.minLoadingTime);
},
// 立即隐藏加载动画(用于错误情况)
forceHideLoading() {
if (this.loadingTimer) {
clearTimeout(this.loadingTimer);
this.loadingTimer = null;
}
this.isLoading = false;
console.log('强制隐藏加载动画');
},
// 首次进入页面时的智能搜索 - 哪个列表先有数据就显示哪个
async performInitialSearch() {
if (this.isSearching) return;
this.isSearching = true;
try {
// 如果没有搜索关键词,直接获取当前类型的全部数据
if (!this.listQuery.title || this.listQuery.title.trim() === '') {
console.log('搜索框为空,获取全部数据');
// 清空title参数让后端返回全部数据
this.listQuery.title = '';
// 重置页码
this.listQuery.page = 1;
// 添加时间戳强制刷新
this.listQuery._timestamp = Date.now();
// 刷新当前列表
this.refreshCurrentList();
this.hasInitialSearch = true;
// 延迟隐藏加载
setTimeout(() => this.hideLoading(), 100);
return;
}
// 记录原始类型
const originalType = this.listQuery.type;
console.log('初始智能搜索开始,当前类型:', originalType, '搜索词:', this.listQuery.title);
// 检查三个类型哪个先有数据
const targetType = await this.findFirstAvailableType();
console.log('初始智能搜索结束,找到的目标类型:', targetType, '当前类型:', this.listQuery.type);
// 如果找到有数据的类型,切换到该类型
if (targetType && targetType !== this.listQuery.type) {
console.log(`初始切换到类型 ${targetType}`);
this.listQuery.type = targetType;
// 如果是服务类型,保留筛选参数,否则重置
if (targetType !== 3) {
this.resetFilterParams();
}
// 重置页码
this.listQuery.page = 1;
// 添加时间戳
this.listQuery._timestamp = Date.now();
// 更新UI后刷新列表
this.$nextTick(() => {
this.refreshCurrentList();
});
} else if (targetType === this.listQuery.type) {
// 类型没变,直接刷新
console.log(`初始类型未变 ${targetType},直接刷新`);
// 重置页码
this.listQuery.page = 1;
// 添加时间戳
this.listQuery._timestamp = Date.now();
this.refreshCurrentList();
} else {
// 没找到有数据的类型,显示空状态
console.log(`初始未找到有数据的类型,保持当前类型 ${this.listQuery.type}`);
// 重置页码
this.listQuery.page = 1;
// 添加时间戳
this.listQuery._timestamp = Date.now();
this.refreshCurrentList();
}
} catch (error) {
console.error('初始智能搜索失败:', error);
// 出错时直接刷新当前列表
this.refreshCurrentList();
} finally {
this.isSearching = false;
this.hasInitialSearch = true; // 标记初始搜索已完成
}
},
// 普通搜索(用于用户手动操作后)
performNormalSearch() {
if (this.isSearching) return;
this.isSearching = true;
try {
console.log('普通搜索,类型:', this.listQuery.type, '搜索词:', this.listQuery.title);
// 确保空搜索时也能正确请求
if (!this.listQuery.title || this.listQuery.title.trim() === '') {
this.listQuery.title = '';
}
// 重置页码
this.listQuery.page = 1;
// 添加时间戳强制刷新
this.listQuery._timestamp = Date.now();
// 刷新列表
this.refreshCurrentList();
} catch (error) {
console.error('普通搜索失败:', error);
this.forceHideLoading();
} finally {
this.isSearching = false;
}
},
// 智能搜索(只在第一次搜索时使用)
async performSmartSearch() {
if (this.isSearching) return;
this.isSearching = true;
try {
// 如果没有搜索关键词,直接刷新当前列表(获取全部数据)
if (!this.listQuery.title || this.listQuery.title.trim() === '') {
this.listQuery.title = '';
// 重置页码
this.listQuery.page = 1;
// 添加时间戳
this.listQuery._timestamp = Date.now();
this.refreshCurrentList();
this.hasInitialSearch = true;
setTimeout(() => this.hideLoading(), 100);
return;
}
// 记录原始类型
const originalType = this.listQuery.type;
console.log('智能搜索开始,当前类型:', originalType, '搜索词:', this.listQuery.title);
// 先检查当前tab是否有数据
const currentTypeHasData = await this.checkTypeHasData(this.listQuery.type);
if (currentTypeHasData) {
// 当前tab有数据直接使用当前tab
console.log(`当前类型 ${this.listQuery.type} 有数据,直接刷新`);
// 重置页码
this.listQuery.page = 1;
// 添加时间戳
this.listQuery._timestamp = Date.now();
this.refreshCurrentList();
} else {
// 当前tab没有数据查找其他有数据的tab
const targetType = await this.findFirstAvailableType();
console.log('智能搜索结束,找到的目标类型:', targetType, '当前类型:', this.listQuery.type);
if (targetType && targetType !== this.listQuery.type) {
console.log(`切换到类型 ${targetType}`);
this.listQuery.type = targetType;
// 如果是服务类型,保留筛选参数,否则重置
if (targetType !== 3) {
this.resetFilterParams();
}
// 重置页码
this.listQuery.page = 1;
// 添加时间戳
this.listQuery._timestamp = Date.now();
// 更新UI后刷新列表
this.$nextTick(() => {
this.refreshCurrentList();
});
} else if (targetType === this.listQuery.type) {
console.log(`类型未变 ${targetType},直接刷新`);
// 重置页码
this.listQuery.page = 1;
// 添加时间戳
this.listQuery._timestamp = Date.now();
this.refreshCurrentList();
} else {
// 没找到有数据的类型,显示空状态
console.log(`未找到有数据的类型,保持当前类型 ${this.listQuery.type}`);
// 重置页码
this.listQuery.page = 1;
// 添加时间戳
this.listQuery._timestamp = Date.now();
this.refreshCurrentList();
}
}
} catch (error) {
console.error('智能搜索失败:', error);
this.refreshCurrentList();
} finally {
this.isSearching = false;
this.hasInitialSearch = true;
}
},
// 查找第一个有数据的类型
async findFirstAvailableType() {
const typesToCheck = [3, 1, 2]; // 服务 -> 手艺人 -> 店铺
// 智能判断优先检查哪个类型
let priorityTypes = typesToCheck;
// 如果当前搜索词是之前搜索过的并且用户已经选择了某个tab
// 则优先检查用户选择的tab
if (this.userChangedTab && this.listQuery.type) {
console.log(`用户已选择类型 ${this.listQuery.type},优先检查`);
priorityTypes = [
this.listQuery.type,
...typesToCheck.filter(type => type !== this.listQuery.type)
];
}
for (const type of priorityTypes) {
try {
console.log(`开始检查类型 ${type} 是否有数据`);
const hasData = await this.checkTypeHasData(type);
console.log(`类型 ${type} 检查结果: ${hasData ? '有数据' : '无数据'}`);
if (hasData) {
console.log(`类型 ${type} 有数据,将切换到该类型`);
return type;
}
} catch (error) {
console.error(`检查类型 ${type} 失败:`, error);
continue;
}
}
// 都没找到数据返回null
console.log('所有类型都没有数据');
return null;
},
// 检查指定类型是否有数据
async checkTypeHasData(type) {
try {
// 构建检查参数 注意这里title要传空字符串而不是不传
const params = {
page: 1,
limit: 5, // 只检查前5条减少请求数据量
type: type,
// 这里必须传title即使是空字符串也要传
title: this.listQuery.title || '',
userLngz: this.listQuery.userLngz,
userLat: this.listQuery.userLat,
};
// 如果是服务类型,添加筛选参数
if (type === 3) {
if (this.listQuery.serviceType !== null && this.listQuery.serviceType !== undefined) {
params.server_kind = this.listQuery.serviceType;
}
if (this.listQuery.distance !== null && this.listQuery.distance !== undefined) {
params.distance = this.listQuery.distance / 1000;
}
if (this.listQuery.minPrice !== null && this.listQuery.minPrice !== undefined) {
params.min_price = this.listQuery.minPrice;
}
if (this.listQuery.maxPrice !== null && this.listQuery.maxPrice !== undefined) {
params.max_price = this.listQuery.maxPrice;
}
if (this.listQuery.sortType !== null && this.listQuery.sortType !== undefined) {
const sortMap = {
'distance': 1,
'sales': 2,
'price_asc': 3,
'price_desc': 4,
};
params.sort_type = sortMap[this.listQuery.sortType] || 1;
}
}
// 清理空值参数但保留title为空字符串的情况
Object.keys(params).forEach(key => {
if (params[key] === null || params[key] === undefined) {
delete params[key];
}
});
console.log(`检查类型 ${type} 的请求参数:`, JSON.stringify(params));
// 使用项目的请求封装
const res = await http.post('/user/getSearchList', params, {
hideLoading: true
});
let hasData = false;
// 尝试多种可能的数据结构
// 可能性1: res 直接包含 list 数组
if (res && res.list && Array.isArray(res.list) && res.list.length > 0) {
console.log(`√类型 ${type}: 通过 res.list 找到数据, 长度: ${res.list.length}`);
hasData = true;
}
// 可能性2: res.data 中包含 list 数组
else if (res && res.data && res.data.list && Array.isArray(res.data.list) && res.data.list.length > 0) {
console.log(`√ 类型 ${type}: 通过 res.data.list 找到数据, 长度: ${res.data.list.length}`);
hasData = true;
}
// 可能性3: res 本身是数组
else if (res && Array.isArray(res) && res.length > 0) {
console.log(`√ 类型 ${type}: res 本身就是数组, 长度: ${res.length}`);
hasData = true;
}
// 可能性4: 根据 CommonList 的日志,可能是其他数据结构
else if (res && res.data && Array.isArray(res.data) && res.data.length > 0) {
console.log(`√类型 ${type}: 通过 res.data 数组找到数据, 长度: ${res.data.length}`);
hasData = true;
}
else {
console.log(`× 类型 ${type}: 未找到数据, 响应结构不符合预期`);
hasData = false;
}
return hasData;
} catch (error) {
console.error('检查数据失败:', error);
// 请求失败,认为没有数据
return false;
}
},
// 判断是否为可能的手艺人名称
isPossibleArtisanName(text) {
if (!text) return false;
// 手艺人名称通常是中文,且较短
const chineseRegex = /^[\u4e00-\u9fa5]{2,4}$/;
return chineseRegex.test(text);
},
// 刷新当前列表
refreshCurrentList() {
// 构建API请求参数
const apiParams = this.buildApiParams();
console.log('刷新列表,类型:', this.listQuery.type, '搜索词:', this.listQuery.title, 'API请求参数:', apiParams);
// 强制更新listQuery
this.listQuery = {
...this.listQuery,
...apiParams,
// 确保时间戳更新
_timestamp: Date.now()
};
console.log('更新后的listQuery:', JSON.parse(JSON.stringify(this.listQuery)));
// 检查CommonList组件是否存在
if (!this.$refs.groupRef) {
console.error('CommonList组件引用不存在');
this.forceHideLoading();
return;
}
console.log('开始调用manualRefresh...');
// 等待一个tick确保DOM更新
this.$nextTick(() => {
try {
// 尝试调用CommonList的刷新方法
if (this.$refs.groupRef.manualRefresh) {
console.log('调用manualRefresh方法参数:', apiParams);
this.$refs.groupRef.manualRefresh(apiParams);
} else {
console.warn('CommonList没有找到manualRefresh方法');
// 如果manualRefresh不存在尝试其他方式
this.$forceUpdate();
}
} catch (error) {
console.error('调用manualRefresh失败:', error);
this.forceHideLoading();
}
});
// 监听列表刷新完成(使用一个简单的超时机制)
// 因为CommonList可能没有提供完成事件我们设置一个合理的超时
setTimeout(() => {
this.hideLoading();
console.log('列表刷新超时检查,隐藏加载动画');
}, 300);
},
// 构建符合后端接口的参数
buildApiParams() {
const params = {
page: this.listQuery.page,
limit: this.listQuery.limit,
type: this.listQuery.type,
// 关键这里title必须传即使为空字符串也要传
// 因为后端可能依赖title参数来判断是搜索还是获取全部
// 如果后端不需要title参数来表示全部那么传空字符串应该也是可以的
title: this.listQuery.title || '',
userLngz: this.listQuery.userLngz,
userLat: this.listQuery.userLat,
};
// 只有服务类型(type=3)时才添加筛选参数
if (this.listQuery.type === 3) {
// 服务类型映射: serviceType -> server_kind
if (this.listQuery.serviceType !== null && this.listQuery.serviceType !== undefined) {
params.server_kind = this.listQuery.serviceType;
}
// 距离筛选: 前端是米,后端是公里
if (this.listQuery.distance !== null && this.listQuery.distance !== undefined) {
params.distance = this.listQuery.distance / 1000; // 米转公里
}
// 价格筛选
if (this.listQuery.minPrice !== null && this.listQuery.minPrice !== undefined) {
params.min_price = this.listQuery.minPrice;
}
if (this.listQuery.maxPrice !== null && this.listQuery.maxPrice !== undefined) {
params.max_price = this.listQuery.maxPrice;
}
// 排序类型映射
if (this.listQuery.sortType !== null && this.listQuery.sortType !== undefined) {
// 将前端的排序值映射为后端的数字
const sortMap = {
'distance': 1, // 离我最近
'sales': 2, // 销量最高
'price_asc': 3, // 价格最低
'price_desc': 4, // 价格最高
};
params.sort_type = sortMap[this.listQuery.sortType] || 1;
}
}else{
params.server_kind =null
params.distance =null
params.min_price =null
params.max_price =null
params.sortMap =null
}
// 清理空值参数但保留title为空字符串的情况
Object.keys(params).forEach(key => {
if (params[key] === null || params[key] === undefined) {
delete params[key];
}
});
console.log('最终构建的参数:', JSON.stringify(params));
return params;
},
// 重置筛选参数
resetFilterParams() {
this.$set(this.listQuery,"serviceType",null)
this.$set(this.listQuery,"distance",null)
this.$set(this.listQuery,"minPrice",null)
this.$set(this.listQuery,"maxPrice",null)
this.$set(this.listQuery,"sortType",null)
},
// 处理筛选变化
handleFilterChange(filterData) {
console.log('收到筛选数据:', filterData);
// 更新筛选参数
this.listQuery.serviceType = filterData.serviceType;
this.listQuery.distance = filterData.distance;
this.listQuery.minPrice = filterData.minPrice;
this.listQuery.maxPrice = filterData.maxPrice;
this.listQuery.sortType = filterData.sortType;
// 重置页码
this.listQuery.page = 1;
// 添加时间戳
this.listQuery._timestamp = Date.now();
// 显示加载
this.showLoading();
// 重新搜索
this.performNormalSearch();
},
// 筛选面板状态变化
onFilterShowChange(show) {
// 筛选面板显示/隐藏时重新计算高度
this.$nextTick(() => {
setTimeout(() => {
this.calculateTopHeight();
}, 300);
});
}
},
onUnload() {
// 页面卸载时清理计时器
if (this.searchTimer) {
clearTimeout(this.searchTimer);
this.searchTimer = null;
}
if (this.loadingTimer) {
clearTimeout(this.loadingTimer);
this.loadingTimer = null;
}
}
};
</script>
<style lang="less">
.search-page {
display: flex;
flex-direction: column;
height: 100vh;
background: #ffffff;
position: relative;
}
/* 自定义导航栏 */
.custom-navbar {
flex-shrink: 0;
position: relative;
z-index: 1000;
}
/* 顶部区域 */
.page-top {
// position: fixed;
flex-shrink: 0;
top: calc(88rpx + var(--status-bar-height));
left: 0;
right: 0;
z-index: 999;
background: #fff;
padding-bottom: 0;
width: 100%;
}
/* 筛选容器 */
.filter-container {
background: #fff;
}
.tabList {
display: flex;
justify-content: center;
align-items: center;
gap: 128rpx;
color: #333333;
padding-bottom: 24rpx;
background: #fff;
.tabItem {
padding: 30rpx 25rpx 12rpx 25rpx;
font-weight: 500;
font-size: 30rpx;
color: #333333;
line-height: 42rpx;
text-align: left;
font-style: normal;
}
.tabItem_active {
font-weight: 500;
font-size: 30rpx;
2026-06-02 11:39:23 +08:00
color: #FF4767;
2026-03-24 11:45:13 +08:00
line-height: 42rpx;
text-align: left;
font-style: normal;
position: relative;
}
.tabItem_active::after {
content: "";
position: absolute;
bottom: 0rpx;
left: 50%;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
width: 34rpx;
height: 6rpx;
2026-06-02 11:39:23 +08:00
background: linear-gradient(306deg, #FF4767 0%, #fc563b 100%);
2026-03-24 11:45:13 +08:00
border-radius: 2rpx;
}
}
/* 简单加载提示 */
.simple-loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1001;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.95);
padding: 40rpx 60rpx;
border-radius: 20rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.12);
min-width: 200rpx;
}
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #f3f3f3;
2026-06-02 11:39:23 +08:00
border-top: 4rpx solid #FF4767;
2026-03-24 11:45:13 +08:00
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 24rpx;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-size: 28rpx;
color: #666;
font-weight: 500;
}
/* 列表容器 */
.list-container {
flex: 1;
position: relative;
background: #f5f5f5;
overflow: hidden;
margin-top: 0;
padding: 0 !important;
transition: filter 0.3s ease;
}
.list-container.loading-blur {
filter: blur(2px);
pointer-events: none;
user-select: none;
}
.list-container ::v-deep .common-list {
height: 100% !important;
background-color: #ffffff !important;
padding: 0 !important;
margin: -13rpx 0 !important;
min-height: unset !important;
}
.list-container ::v-deep .list-scroll {
height: 100% !important;
.scroll-view {
height: 100% !important;
}
.list-container {
padding: 1rpx 0 0 0; // ???为什么
min-height: 100%;
background: #f5f5f5;
border-radius: 30rpx 30rpx 0 0;
}
.list-item {
display: block;
padding: 0;
background-color: transparent;
margin-bottom: 20rpx;
}
}
/* 卡片样式 */
.card-wrapper {
background: #ffffff;
border-radius: 20rpx;
padding: 24rpx;
margin: 20rpx 24rpx 0 24rpx;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.05);
overflow: hidden;
box-sizing: border-box;
}
/* 卡片样式 */
.card-wrapper {
background: #ffffff;
border-radius: 20rpx;
padding: 24rpx;
margin: 20rpx 24rpx 0 24rpx;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.05);
overflow: hidden;
box-sizing: border-box;
}
.card-wrapper-service {
background: #ffffff;
border-radius: 20rpx;
margin: 20rpx 24rpx;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.05);
overflow: hidden;
box-sizing: border-box;
}
.shop_box {
display: flex;
flex-direction: column;
align-items: center;
}
/* 空数据状态 */
.list-container ::v-deep .no-data {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
}
.scroll-locked {
// overflow: hidden !important;
// height: 100vh !important;
position: fixed !important;
width: 100% !important;
}
</style>