Files
medical-mall/pages/mall/consumer/search.uvue

1408 lines
31 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="search-page">
<!-- 搜索头部 -->
<view class="search-header" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="search-bar-container">
<!-- 返回按钮:小于号加粗 -->
<view class="back-btn" @click="goBack">
<text class="back-icon">&lt;</text>
</view>
<!-- 搜索框 -->
<view class="search-input-container">
<input
class="search-input"
type="text"
:value="searchKeyword"
@input="onInput"
@confirm="onSearch"
placeholder="请输入商品名称、店铺"
placeholder-class="placeholder"
:focus="autoFocus"
/>
<!-- 清除按钮 -->
<view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
<text class="clear-icon">×</text>
</view>
<!-- 相机图标 -->
<view class="camera-btn" @click="openCamera">
<text class="camera-icon">📷</text>
</view>
<!-- 搜索按钮:移入输入框内部 -->
<view class="inner-search-btn" @click="onSearch">
<text class="inner-search-text">搜索</text>
</view>
</view>
</view>
</view>
<!-- 错误状态(模拟服务器超时) -->
<view v-if="isError" class="error-state" @click="retryLoad">
<view class="error-content">
<text class="error-icon">⚠️</text>
<text class="error-title">加载服务器超时</text>
<text class="error-desc">请点击屏幕重试</text>
</view>
</view>
<!-- 主内容区域 -->
<scroll-view
v-else
scroll-y
class="main-content"
:style="{ height: scrollHeight + 'px' }"
@scrolltolower="loadMore"
>
<!-- 初始状态(无搜索词) -->
<view v-if="!searchKeyword && !showResults">
<!-- 搜索历史 -->
<view v-if="searchHistory.length > 0" class="search-history">
<view class="section-header">
<text class="section-title">搜索历史</text>
<view class="header-right" @click="clearHistory">
<text class="clear-text">清空</text>
<text class="clear-icon-trash">🗑️</text>
</view>
</view>
<view class="history-tags">
<view
v-for="(item, index) in searchHistory"
:key="index"
class="history-tag"
@click="searchFromHistory(item)"
>
<text class="history-text">{{ item }}</text>
<view class="delete-tag-btn" @click.stop="deleteHistoryItem(index)">
<text class="delete-icon">×</text>
</view>
</view>
</view>
</view>
<!-- 热门搜索 -->
<view class="hot-search">
<view class="section-header">
<text class="section-title">热门搜索</text>
</view>
<view class="hot-tags">
<view
v-for="(item, index) in hotSearchList"
:key="index"
class="hot-tag"
:class="{ 'hot': item.hot }"
@click="searchFromHot(item.keyword)"
>
<text class="hot-rank" :class="{ 'top-three': index < 3 }">{{ index + 1 }}</text>
<text class="hot-text">{{ item.keyword }}</text>
<text v-if="item.hot" class="hot-icon">🔥</text>
</view>
</view>
</view>
<!-- 猜你需要 (新增功能) -->
<view class="guess-you-like">
<view class="section-header">
<view class="title-with-icon">
<text class="section-icon">✨</text>
<text class="section-title">猜你需要</text>
</view>
<text class="refresh-btn" @click="refreshGuessList">换一批</text>
</view>
<view class="guess-grid">
<view
v-for="item in guessList"
:key="item.id"
class="guess-item"
@click="viewProductDetail(item)"
>
<view class="guess-img-box">
<image class="guess-img" :src="item.image" mode="aspectFill" />
</view>
<view class="guess-info">
<text class="guess-name">{{ item.name }}</text>
<view class="guess-price-row">
<text class="price-symbol">¥</text>
<text class="price-num">{{ item.price }}</text>
<text class="sales-text">已售{{ item.sales }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 搜索建议 -->
<view v-if="searchKeyword && !showResults" class="search-suggestions">
<view class="suggestions-list">
<view
v-for="(suggestion, index) in searchSuggestions"
:key="index"
class="suggestion-item"
@click="selectSuggestion(suggestion)"
>
<view class="suggestion-icon">🔍</view>
<text class="suggestion-text">{{ suggestion }}</text>
</view>
</view>
</view>
<!-- 搜索结果 -->
<view v-if="showResults" class="search-results">
<!-- 店铺搜索结果 -->
<view v-if="searchShopResults.length > 0" class="shop-results-section">
<view class="section-top">
<text class="result-title-sm">相关店铺</text>
</view>
<scroll-view scroll-x class="shop-list-scroll">
<view class="shop-list-row">
<view
v-for="shop in searchShopResults"
:key="shop.id"
class="shop-card"
@click="viewShopDetail(shop)"
>
<image class="shop-logo" :src="shop.logo" mode="aspectFill" />
<view class="shop-info">
<text class="shop-name-txt">{{ shop.name }}</text>
<text class="shop-products-txt">共{{ shop.productCount }}件商品</text>
</view>
</view>
</view>
</scroll-view>
</view>
<view class="results-header">
<text class="results-title">商品结果</text>
<view class="filter-tabs">
<text
class="filter-tab"
:class="{ active: activeSort === 'default' }"
@click="switchSort('default')"
>综合</text>
<text
class="filter-tab"
:class="{ active: activeSort === 'sales' }"
@click="switchSort('sales')"
>销量</text>
<text
class="filter-tab"
:class="{ active: activeSort === 'price' }"
@click="switchSort('price')"
>
价格 {{ activeSort === 'price' ? (priceSortAsc ? '↑' : '↓') : '' }}
</text>
</view>
</view>
<view v-if="searchResults.length > 0" class="results-list">
<view
v-for="product in searchResults"
:key="product.id"
class="result-item"
@click="viewProductDetail(product)"
>
<image class="product-image" :src="product.image" mode="aspectFill" />
<view class="product-info">
<text class="product-name">{{ product.name }}</text>
<view class="product-tags-row" v-if="product.tag">
<text class="product-tag">{{ product.tag }}</text>
</view>
<text class="product-spec">{{ product.specification }}</text>
<view class="product-bottom">
<view class="price-box">
<text class="price-symbol">¥</text>
<text class="price-value">{{ product.price }}</text>
</view>
<view class="add-cart-btn" @click.stop="addToCart(product)">
<text class="cart-icon">+</text>
</view>
</view>
</view>
</view>
</view>
<!-- 空结果 - 仅在非加载状态且无结果时显示 -->
<view v-if="!loading && searchResults.length === 0" class="empty-result">
<text class="empty-icon">🤔</text>
<text class="empty-text">未找到相关商品</text>
<text class="empty-sub">换个关键词试试吧</text>
</view>
<!-- 加载更多/加载中 - 在加载状态或有更多数据时显示 -->
<view v-if="loading" class="loading-more">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<view v-if="!hasMore && searchResults.length > 0" class="no-more">
<text class="no-more-text">--- 到底了 ---</text>
</view>
</view>
<!-- 底部安全区域 -->
<view class="safe-area"></view>
</scroll-view>
</view>
</template>
<script setup lang="uts">
import { ref, reactive, onMounted, computed } from 'vue'
import { supabaseService } from '@/utils/supabaseService.uts'
import type { Product } from '@/utils/supabaseService.uts'
// 状态定义
const statusBarHeight = ref(0)
const scrollHeight = ref(0)
const searchKeyword = ref('')
const showResults = ref(false)
const loading = ref(false)
const hasMore = ref(true)
const isError = ref(false) // 错误状态控制
const autoFocus = ref(true)
const activeSort = ref('default') // 当前排序方式: default, sales, price
const priceSortAsc = ref(false) // 价格排序是否为升序
// 数据定义
const searchHistory = ref<string[]>([])
const hotSearchList = ref<any[]>([])
const guessList = ref<any[]>([])
const allGuessItems = ref<any[]>([]) // 缓存所有猜你喜欢商品
const searchResults = ref<any[]>([])
const searchShopResults = ref<any[]>([]) // 搜索到的店铺
onMounted(() => {
initPage()
})
const initPage = () => {
try {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight ?? 0
const windowHeight = systemInfo.windowHeight
// 减去头部高度 (约60px + statusBarHeight)
scrollHeight.value = windowHeight - (60 + statusBarHeight.value)
loadData()
// 检查页面参数
const pages = getCurrentPages()
if (pages.length > 0) {
const currentPage = pages[pages.length - 1]
// @ts-ignore
const options = currentPage.options
if (options && options['keyword']) {
const keyword = decodeURIComponent(options['keyword'])
searchKeyword.value = keyword
if (options['type'] === 'family' || options['type'] === 'brand') {
// 如果是家庭常备药或品牌类型,直接添加到历史并搜索
if (options['type'] === 'family') {
addToHistory(keyword)
}
// 立即显示结果区域并设置为加载中
showResults.value = true
loading.value = true
// 确保searchResults不为空数组导致闪烁虽然loading=true已经拦截了empty-result但双重保险
// 此时不要置空searchResults或者给一个初始值
// 直接调用搜索移除setTimeout防止中间状态
performSearch()
}
}
}
} catch (e) {
console.error('初始化失败', e)
isError.value = true
}
}
// 加载基础数据
const loadData = async () => {
isError.value = false
try {
loadSearchHistory()
// 获取热门商品作为热门搜索推荐和猜你喜欢
// 获取更多数据以便"换一批"
const hotProducts = await supabaseService.getHotProducts(30)
hotSearchList.value = hotProducts.slice(0, 10).map((p: any) => ({
keyword: p.name,
hot: true
}))
allGuessItems.value = hotProducts.map((p: any) => ({
id: p.id,
name: p.name,
price: p.base_price,
image: p.main_image_url ?? '/static/default.jpg',
sales: typeof p.sale_count === 'number' ? p.sale_count : 0
}))
// 初始显示随机6个
refreshGuessListItems()
} catch (e) {
console.error('Load data failed', e)
isError.value = true
}
}
// 点击重试
const retryLoad = () => {
uni.showLoading({ title: '重新加载中' })
setTimeout(() => {
uni.hideLoading()
loadData()
}, 1000)
}
// 历史记录管理
const loadSearchHistory = () => {
const history = uni.getStorageSync('searchHistory')
if (history) {
try {
// 确保是数组
const parsed = JSON.parse(history as string)
if (Array.isArray(parsed)) {
searchHistory.value = parsed as string[]
}
} catch (e) {
searchHistory.value = []
}
}
}
const saveSearchHistory = () => {
uni.setStorageSync('searchHistory', JSON.stringify(searchHistory.value))
}
const addToHistory = (keyword: string) => {
if (keyword == '') return
const index = searchHistory.value.indexOf(keyword)
if (index > -1) {
searchHistory.value.splice(index, 1)
}
searchHistory.value.unshift(keyword)
if (searchHistory.value.length > 10) searchHistory.value.pop()
saveSearchHistory()
}
const clearHistory = () => {
uni.showModal({
title: '提示',
content: '确定清空搜索历史吗?',
success: (res) => {
if (res.confirm) {
searchHistory.value = []
uni.removeStorageSync('searchHistory')
}
}
})
}
const deleteHistoryItem = (index: number) => {
searchHistory.value.splice(index, 1)
saveSearchHistory()
}
// 搜索建议 - 改为实时获取
const searchSuggestions = ref<string[]>([])
let suggestTimer = 0
// 搜索逻辑
const onInput = (e: any) => {
const val = e.detail.value
searchKeyword.value = val
if (val == '') {
showResults.value = false
searchSuggestions.value = []
return
}
// Debounce suggestion search
if (suggestTimer > 0) clearTimeout(suggestTimer)
suggestTimer = setTimeout(() => {
fetchSuggestions(val)
}, 300)
}
const fetchSuggestions = async (kw: string) => {
if (kw == '' || showResults.value) return
// 简单搜索前5个相关商品作为建议
try {
const res = await supabaseService.searchProducts(kw.trim(), 1, 5)
if (Array.isArray(res.data) && res.data.length > 0) {
// 去重
const names = res.data.map((p:any) :string => {
if(p instanceof UTSJSONObject){
return p.getString('name') ?? ''
}
return p['name'] as string
})
// @ts-ignore
searchSuggestions.value = Array.from(new Set(names))
} else {
searchSuggestions.value = []
}
} catch(e) {
searchSuggestions.value = []
}
}
const clearSearch = () => {
searchKeyword.value = ''
showResults.value = false
}
const onSearch = () => {
if (searchKeyword.value.trim() == '') return
addToHistory(searchKeyword.value.trim())
performSearch()
}
const searchFromHistory = (keyword: string) => {
searchKeyword.value = keyword
performSearch()
}
const searchFromHot = (keyword: string) => {
searchKeyword.value = keyword
addToHistory(keyword)
performSearch()
}
const selectSuggestion = (suggestion: string) => {
searchKeyword.value = suggestion
addToHistory(suggestion)
performSearch()
}
const currentPage = ref(1)
const performSearch = async () => {
// 再次强制设置状态,确保万无一失
showResults.value = true
loading.value = true
// 重置页码
currentPage.value = 1
// 使用 Supabase 搜索真实数据
const keyword = searchKeyword.value.trim()
if (keyword == '') {
loading.value = false
return
}
// 确定排序方式
let sortBy = 'sales'
let ascending = false
if (activeSort.value === 'price') {
sortBy = 'price'
ascending = priceSortAsc.value
} else if (activeSort.value === 'default') {
sortBy = 'default'
}
try {
// 并行请求:商品搜索 + 店铺搜索
const [prodResp, shopResp] = await Promise.all([
supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending),
// 只有第一页搜索且非价格排序时搜索店铺,避免重复和无关搜索
currentPage.value === 1 && activeSort.value === 'default'
? supabaseService.searchShops(keyword)
: Promise.resolve({ data: [], total: 0, page: 1, limit: 0, hasmore: false })
])
// 处理店铺结果
if (shopResp.data.length > 0) {
searchShopResults.value = shopResp.data.map((s: any) => ({
id: s.id,
name: s.shop_name,
logo: s.shop_logo ?? '/static/shop_logo_default.png',
productCount: s.product_count ?? 0
}))
} else {
searchShopResults.value = []
}
// 处理商品结果
searchResults.value = prodResp.data.map((p: any) => {
let tag = ''
if (p.tags) {
try {
const tags = (typeof p.tags === 'string') ? JSON.parse(p.tags) : p.tags
if (Array.isArray(tags) && tags.length > 0) tag = String(tags[0])
} catch(e) {}
}
return {
id: p.id,
name: p.name,
image: p.main_image_url ?? '/static/default.jpg',
price: p.base_price,
specification: p.specification ?? '标准规格',
tag: tag,
sales: p.sale_count ?? 0
}
})
hasMore.value = prodResp.hasmore
} catch(e) {
console.error('Search failed', e)
} finally {
loading.value = false
}
}
// 切换排序
const switchSort = (type: string) => {
if (type === 'price') {
if (activeSort.value === 'price') {
priceSortAsc.value = !priceSortAsc.value
} else {
activeSort.value = 'price'
priceSortAsc.value = true // 默认升序
}
} else {
activeSort.value = type
}
// 重新执行搜索以获取正确排序的数据
performSearch()
}
const loadMore = async () => {
if (loading.value || !hasMore.value || searchKeyword.value.trim() == '') return
loading.value = true
// 增加页码
currentPage.value++
const keyword = searchKeyword.value.trim()
// 确定排序方式
let sortBy = 'sales'
let ascending = false
if (activeSort.value === 'price') {
sortBy = 'price'
ascending = priceSortAsc.value
} else if (activeSort.value === 'default') {
sortBy = 'default'
}
try {
const response = await supabaseService.searchProducts(keyword, currentPage.value, 20, sortBy, ascending)
const newItems = response.data.map((p: any) => {
let tag = ''
if (p.tags) {
try {
const tags = (typeof p.tags === 'string') ? JSON.parse(p.tags) : p.tags
if (Array.isArray(tags) && tags.length > 0) tag = String(tags[0])
} catch(e) {}
}
return {
id: p.id,
name: p.name,
image: p.main_image_url ?? '/static/default.jpg',
price: p.base_price,
specification: p.specification ?? '标准规格',
tag: tag,
sales: p.sale_count ?? 0
}
})
searchResults.value.push(...newItems)
hasMore.value = response.hasmore
} catch(e) {
console.error('Load more failed', e)
hasMore.value = false
} finally {
loading.value = false
}
}
const refreshGuessList = () => {
uni.showLoading({ title: '刷新中' })
setTimeout(() => {
refreshGuessListItems()
uni.hideLoading()
}, 500)
}
const refreshGuessListItems = () => {
if (allGuessItems.value.length > 0) {
// 简单的随机乱序并取前6个
const shuffled = [...allGuessItems.value].sort(() => Math.random() - 0.5)
guessList.value = shuffled.slice(0, 6)
}
}
const viewProductDetail = (item: any) => {
// 跳转详情页逻辑 - 传递必要的参数作为预加载/fallback
uni.navigateTo({
url: `/pages/mall/consumer/product-detail?productId=${item.id}&price=${item.price}&name=${encodeURIComponent(item.name)}`
})
}
const viewShopDetail = (shop: any) => {
uni.navigateTo({
url: `/pages/mall/consumer/shop-detail?id=${shop.id}`
})
}
// 添加到购物车 - 搜索列表无法选择规格,跳转详情页
const addToCart = (product: any) => {
uni.showToast({ title: '请选择规格', icon: 'none' })
setTimeout(() => {
viewProductDetail(product)
}, 800)
}
const openCamera = () => {
uni.chooseImage({
count: 1,
sourceType: ['camera'],
success: (res) => {
console.log('拍摄图片路径:', res.tempFilePaths[0])
uni.showToast({ title: '已启用相机', icon: 'none' })
},
fail: (err) => {
console.error('启用相机失败', err)
}
})
}
const goBack = () => {
if (showResults.value) {
// 如果在搜索结果页,先返回到搜索初始页
showResults.value = false
searchKeyword.value = ''
} else {
// 如果在搜索初始页,则返回上一页
const pages = getCurrentPages()
if (pages.length > 1) {
uni.navigateBack()
} else {
// 如果只有一页(由于深链接或重定向),返回首页
uni.switchTab({
url: '/pages/mall/consumer/index'
})
}
}
}
</script>
<style>
.search-page {
width: 100%;
flex: 1; /* Fixed 100vh */
background-color: #f5f5f5;
display: flex;
flex-direction: column;
}
/* 店铺搜索结果 */
.shop-results-section {
background-color: #fff;
margin-bottom: 10px;
padding: 10px 0;
}
.section-top {
padding: 0 12px 10px;
}
.result-title-sm {
font-size: 14px;
font-weight: bold;
color: #333;
}
.shop-list-scroll {
width: 100%;
white-space: nowrap;
}
.shop-list-row {
display: flex;
flex-direction: row;
padding: 0 12px;
}
.shop-card {
display: flex;
flex-direction: column;
align-items: center;
width: 80px;
margin-right: 15px;
background-color: #f9f9f9;
padding: 10px 5px;
border-radius: 8px;
}
.shop-logo {
width: 48px;
height: 48px;
border-radius: 24px;
margin-bottom: 5px;
border: 1px solid #f0f0f0;
background-color: white;
}
.shop-info {
width: 100%;
text-align: center;
}
.shop-name-txt {
font-size: 12px;
color: #333;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
/* display: block; REMOVED */
margin-bottom: 2px;
}
.shop-products-txt {
font-size: 10px;
color: #999;
}
/* 头部样式 */
.search-header {
background-color: #ffffff;
padding-bottom: 10px;
/* #ifdef APP-PLUS */
padding-top: 0; /* 在App端由style动态控制 */
/* #endif */
}
.search-bar-container {
display: flex;
flex-direction: row; /* UVUE 必须显式设置 row */
align-items: center;
padding: 10px 16px;
width: 100%; /* 确保占满宽度 */
}
.back-btn {
padding: 4px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 32px; /* 固定宽度防止压缩 */
height: 32px;
margin-right: 12px;
}
.back-icon {
font-size: 24px;
color: #333;
font-weight: bold;
font-family: monospace;
}
.search-input-container {
flex: 1; /* 占据剩余空间 */
height: 40px; /*稍微增高一点以容纳按钮*/
background-color: #f0f0f0;
border-radius: 20px;
display: flex;
flex-direction: row; /* UVUE 必须显式设置 row */
align-items: center;
padding: 0 4px 0 12px;
}
.search-input {
flex: 1;
font-size: 14px;
color: #333;
height: 100%;
background-color: transparent; /* 确保背景透明 */
}
.placeholder {
color: #999;
}
.clear-btn {
padding: 4px;
margin-right: 2px;
display: flex;
align-items: center;
justify-content: center;
}
.clear-icon {
font-size: 16px;
color: #999;
}
.camera-btn {
padding: 4px 8px 4px 4px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
border-right-width: 1px; /* UVUE 边框写法 */
border-right-style: solid;
border-right-color: #ddd;
margin-right: 8px;
}
.camera-icon {
font-size: 20px;
}
/* 内部搜索按钮样式 */
.inner-search-btn {
padding: 0 16px;
background-color: #87CEEB;
border-radius: 16px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 32px;
}
.inner-search-text {
font-size: 13px;
color: #ffffff;
font-weight: bold;
}
/* 内容区域 */
.main-content {
flex: 1;
padding: 12px;
box-sizing: border-box;
}
/* 模块通用头部 */
.section-header {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
margin-top: 8px;
width: 100%;
}
.section-title {
font-size: 15px;
font-weight: bold;
color: #333;
flex: 1; /* 占据左侧空间 */
}
.header-right {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
align-items: center;
/* gap: 4px; REMOVED */
flex-shrink: 0; /* 防止被压缩 */
}
.clear-text {
margin-right: 4px; /* REPLACED gap */
font-size: 12px;
color: #999;
}
.clear-icon-trash {
font-size: 14px;
}
/* 搜索历史 */
.search-history {
margin-bottom: 24px;
padding: 0 4px; /* 微调内边距 */
}
.history-tags {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
/* gap: 10px; REMOVED */
flex-wrap: wrap; /* 允许换行 */
padding: 0 4px;
align-items: center;
}
.history-tag {
background-color: #fff;
padding: 6px 12px;
border-radius: 16px;
display: flex;
align-items: center;
/* gap: 6px; REMOVED */
flex-shrink: 0; /* 防止被压缩 */
margin-right: 10px; /* REPLACED gap */
margin-bottom: 10px; /* REPLACED gap */
}
.history-text {
font-size: 13px;
color: #666;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 6px; /* REPLACED gap */
}
.delete-tag-btn {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border-radius: 8px;
background-color: #f0f0f0;
}
.delete-icon {
font-size: 12px;
color: #999;
line-height: 1;
}
/* 热门搜索 */
.hot-search {
margin-bottom: 24px;
}
.hot-tags {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
flex-wrap: wrap; /* 允许换行 */
/* gap: 10px; REMOVED */
padding: 0 4px;
}
.hot-tag {
/* ... existing styles ... */
margin-right: 10px; /* REPLACED gap */
margin-bottom: 10px; /* REPLACED gap */
}
.hot-tag {
background-color: #fff;
padding: 6px 12px;
border-radius: 16px; /* 增加圆角,像胶囊一样 */
display: flex;
flex-direction: row;
align-items: center;
flex-shrink: 0; /* 防止被压缩 */
margin-right: 10px; /* REPLACED gap */
margin-bottom: 10px; /* REPLACED gap */
}
.hot-tag.hot {
background-color: #fff0f0;
}
.hot-rank {
font-size: 12px;
color: #999;
font-weight: bold;
margin-right: 6px;
}
.hot-rank.top-three {
color: #ff5000;
}
.hot-text {
font-size: 13px;
color: #333;
}
.hot-icon {
font-size: 12px;
margin-left: 4px;
}
/* 猜你需要 */
.guess-you-like {
margin-bottom: 20px;
}
.title-with-icon {
display: flex;
align-items: center;
}
.section-icon {
font-size: 16px;
margin-right: 6px;
}
.refresh-btn {
font-size: 12px;
color: #4CAF50;
}
.guess-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
padding: 0 4px;
}
.guess-item {
background-color: #fff;
border-radius: 8px;
overflow: hidden;
padding-bottom: 8px;
width: 48%;
margin-bottom: 10px;
}
.guess-img-box {
width: 100%;
height: 0;
padding-bottom: 100%;
position: relative;
background-color: #f0f0f0;
}
.guess-img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.guess-info {
padding: 8px;
}
.guess-name {
font-size: 13px;
color: #333;
margin-bottom: 6px;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.3;
height: 34px; /* 限制2行高度 */
}
.guess-price-row {
display: flex;
align-items: flex-end; /* REPLACED baseline */
}
.price-symbol {
font-size: 12px;
color: #ff5000;
}
.price-num {
font-size: 16px;
color: #ff5000;
font-weight: bold;
margin-right: 6px;
}
.sales-text {
font-size: 10px;
color: #999;
}
/* 搜索建议列表 */
.search-suggestions {
background-color: #fff;
border-radius: 8px;
padding: 0 12px;
}
.suggestion-item {
display: flex;
align-items: center;
height: 44px;
border-bottom: 1px solid #f5f5f5;
}
.suggestion-icon {
margin-right: 10px;
font-size: 14px;
color: #999;
}
.suggestion-text {
font-size: 14px;
color: #333;
}
/* 搜索结果 */
.search-results {
padding-bottom: 20px;
}
.results-header {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
flex-wrap: wrap; /* 允许换行以适应小屏 */
}
.results-title {
font-size: 15px;
font-weight: bold;
color: #333;
margin-right: 8px;
}
.filter-tabs {
display: flex;
flex-direction: row; /* UVUE 显式设置 row */
flex: 1; /* 自适应填充剩余空间 */
justify-content: flex-end; /* 靠右对齐 */
}
.filter-tab {
font-size: 13px;
color: #666;
padding: 4px 8px; /* 增加点击区域 */
margin-left: 16px;
}
.filter-tab.active {
color: #4CAF50;
font-weight: bold; /* REPLACED 500 */
}
.results-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
padding: 0 4px;
}
/* 响应式布局 */
/* 平板设备 (768px以上) */
@media screen and (min-width: 768px) {
.results-list {
padding: 0 16px;
}
.result-item {
width: 32%;
}
.guess-item {
width: 24%; /* 猜你喜欢在平板上显示4列 */
}
}
/* 桌面设备 (1024px以上) */
@media screen and (min-width: 1024px) {
.results-list {
padding: 0 24px;
}
.result-item {
width: 24%;
}
.guess-item {
width: 16%; /* 猜你喜欢在桌面上显示6列 */
}
/* 桌面端调整图片高度 */
.product-image {
height: 160px;
}
}
.result-item {
background-color: #fff;
border-radius: 8px;
padding: 8px;
display: flex;
flex-direction: column; /* 垂直排列 */
width: 48%;
margin-bottom: 10px;
}
.product-image {
width: 100%;
height: 120px; /* 调整图片高度 */
border-radius: 4px;
background-color: #f0f0f0;
}
.product-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
margin-top: 8px;
}
.product-name {
font-size: 13px; /* 减小字号 */
color: #333;
font-weight: bold;
line-height: 1.3;
height: 34px; /* 限制高度 */
overflow: hidden;
text-overflow: ellipsis;
}
.product-tags-row {
margin-top: 2px;
display: none; /* 隐藏标签以保持简洁 */
}
.product-spec {
display: none; /* 隐藏规格 */
}
.product-bottom {
display: flex;
justify-content: space-between;
align-items: center; /* 垂直居中 */
margin-top: 4px;
}
.price-box {
color: #ff5000;
display: flex;
align-items: flex-end;
}
.price-symbol {
font-size: 10px;
}
.price-value {
font-size: 16px; /* 减小价格字号 */
font-weight: 700;
}
.add-cart-btn {
width: 24px;
height: 24px;
background-color: #4CAF50;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
}
.cart-icon {
color: #fff;
font-size: 14px;
font-weight: bold;
}
/* 错误状态 */
.error-state {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
}
.error-content {
display: flex;
flex-direction: column;
align-items: center;
}
.error-icon {
font-size: 48px;
margin-bottom: 12px;
}
.error-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 12px;
}
.error-desc {
font-size: 14px;
color: #999;
}
/* 加载更多 */
.loading-more {
padding: 20px 0;
display: flex;
flex-direction: column;
align-items: center;
}
.loading-spinner {
width: 24px;
height: 24px;
border: 2px solid #f0f0f0;
border-top-color: #4CAF50;
border-radius: 12px;
margin-bottom: 8px;
}
.loading-text {
font-size: 12px;
color: #999;
}
.no-more {
padding: 20px 0;
text-align: center;
}
.no-more-text {
font-size: 12px;
color: #ccc;
}
.empty-result {
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 0;
}
.empty-icon {
font-size: 40px;
margin-bottom: 12px;
}
.empty-text {
font-size: 14px;
color: #666;
margin-bottom: 4px;
}
.empty-sub {
font-size: 12px;
color: #999;
}
.safe-area {
height: 20px;
}
</style>