1388 lines
30 KiB
Plaintext
1388 lines
30 KiB
Plaintext
<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"><</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 fetchSuggestions = async (kw: string) => {
|
||
if (!kw || showResults.value) return
|
||
|
||
// 简单搜索前5个相关商品作为建议
|
||
try {
|
||
const res = await supabaseService.searchProducts(kw.trim(), 1, 5)
|
||
if (res.data.length > 0) {
|
||
// 去重
|
||
const names = res.data.map((p:any) => p.name as string)
|
||
// @ts-ignore
|
||
searchSuggestions.value = [...new Set(names)]
|
||
} else {
|
||
searchSuggestions.value = []
|
||
}
|
||
} catch(e) {
|
||
searchSuggestions.value = []
|
||
}
|
||
}
|
||
|
||
// 搜索逻辑
|
||
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 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%;
|
||
height: 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;
|
||
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;
|
||
gap: 12px;
|
||
width: 100%; /* 确保占满宽度 */
|
||
}
|
||
|
||
.back-btn {
|
||
padding: 4px;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 32px; /* 固定宽度防止压缩 */
|
||
height: 32px;
|
||
}
|
||
|
||
.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: 500;
|
||
}
|
||
|
||
/* 内容区域 */
|
||
.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;
|
||
flex-shrink: 0; /* 防止被压缩 */
|
||
}
|
||
|
||
.clear-text {
|
||
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;
|
||
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;
|
||
flex-shrink: 0; /* 防止被压缩 */
|
||
}
|
||
|
||
.history-text {
|
||
font-size: 13px;
|
||
color: #666;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.delete-tag-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 16px;
|
||
height: 16px;
|
||
border-radius: 50%;
|
||
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;
|
||
padding: 0 4px;
|
||
}
|
||
|
||
.hot-tag {
|
||
background-color: #fff;
|
||
padding: 6px 12px;
|
||
border-radius: 16px; /* 增加圆角,像胶囊一样 */
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 4px;
|
||
flex-shrink: 0; /* 防止被压缩 */
|
||
}
|
||
|
||
.hot-tag.hot {
|
||
background-color: #fff0f0;
|
||
}
|
||
|
||
.hot-rank {
|
||
font-size: 12px;
|
||
color: #999;
|
||
font-weight: bold;
|
||
margin-right: 2px;
|
||
}
|
||
|
||
.hot-rank.top-three {
|
||
color: #ff5000;
|
||
}
|
||
|
||
.hot-text {
|
||
font-size: 13px;
|
||
color: #333;
|
||
}
|
||
|
||
.hot-icon {
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 猜你需要 */
|
||
.guess-you-like {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.title-with-icon {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.section-icon {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.refresh-btn {
|
||
font-size: 12px;
|
||
color: #4CAF50;
|
||
}
|
||
|
||
.guess-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 10px;
|
||
}
|
||
|
||
.guess-item {
|
||
background-color: #fff;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
padding-bottom: 8px;
|
||
}
|
||
|
||
.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;
|
||
display: -webkit-box;
|
||
-webkit-box-orient: vertical;
|
||
-webkit-line-clamp: 2;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.guess-price-row {
|
||
display: flex;
|
||
align-items: 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; /* 允许换行以适应小屏 */
|
||
gap: 8px;
|
||
}
|
||
|
||
.results-title {
|
||
font-size: 15px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.filter-tabs {
|
||
display: flex;
|
||
flex-direction: row; /* UVUE 显式设置 row */
|
||
gap: 16px;
|
||
flex: 1; /* 自适应填充剩余空间 */
|
||
justify-content: flex-end; /* 靠右对齐 */
|
||
}
|
||
|
||
.filter-tab {
|
||
font-size: 13px;
|
||
color: #666;
|
||
padding: 4px 8px; /* 增加点击区域 */
|
||
}
|
||
|
||
.filter-tab.active {
|
||
color: #4CAF50;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.results-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr); /* 默认移动端双列 */
|
||
gap: 10px;
|
||
padding: 0 4px;
|
||
}
|
||
|
||
/* 响应式布局 */
|
||
/* 平板设备 (768px以上) */
|
||
@media screen and (min-width: 768px) {
|
||
.results-list {
|
||
grid-template-columns: repeat(3, 1fr); /* 平板显示3列 */
|
||
gap: 16px;
|
||
padding: 0 16px;
|
||
}
|
||
|
||
.guess-grid {
|
||
grid-template-columns: repeat(4, 1fr); /* 猜你喜欢在平板上显示4列 */
|
||
}
|
||
}
|
||
|
||
/* 桌面设备 (1024px以上) */
|
||
@media screen and (min-width: 1024px) {
|
||
.results-list {
|
||
grid-template-columns: repeat(4, 1fr); /* 桌面显示4列 */
|
||
gap: 20px;
|
||
padding: 0 24px;
|
||
}
|
||
|
||
.guess-grid {
|
||
grid-template-columns: repeat(6, 1fr); /* 猜你喜欢在桌面上显示6列 */
|
||
}
|
||
|
||
/* 桌面端调整图片高度 */
|
||
.product-image {
|
||
height: 160px;
|
||
}
|
||
}
|
||
|
||
.result-item {
|
||
background-color: #fff;
|
||
border-radius: 8px;
|
||
padding: 8px;
|
||
display: flex;
|
||
flex-direction: column; /* 垂直排列 */
|
||
gap: 8px;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.product-name {
|
||
font-size: 13px; /* 减小字号 */
|
||
color: #333;
|
||
font-weight: 500;
|
||
line-height: 1.3;
|
||
height: 34px; /* 限制高度 */
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
}
|
||
|
||
.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: baseline;
|
||
}
|
||
|
||
.price-symbol {
|
||
font-size: 10px;
|
||
}
|
||
|
||
.price-value {
|
||
font-size: 16px; /* 减小价格字号 */
|
||
font-weight: 600;
|
||
}
|
||
|
||
.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;
|
||
gap: 12px;
|
||
}
|
||
|
||
.error-icon {
|
||
font-size: 48px;
|
||
}
|
||
|
||
.error-title {
|
||
font-size: 16px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.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: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.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>
|