Files
anmo/uniapp/src/bundle/pages/search/search.vue
2025-08-19 14:16:51 +08:00

411 lines
14 KiB
Vue

<template>
<page-meta :page-style="$theme.pageStyle">
<!-- #ifndef H5 -->
<navigation-bar
:front-color="$theme.navColor"
:background-color="$theme.navBgColor"
/>
<!-- #endif -->
</page-meta>
<view class="search flex flex-col min-h-0 h-full">
<!-- #ifndef H5 -->
<u-sticky h5-nav-height="0" bg-color="transparent">
<u-navbar
:is-back="true"
:is-fixed="true"
:border-bottom="false"
title="搜索"
title-color="#000"
:title-bold="false"
:background="{ background: `rgba(256,256, 256, ${search.searching ? 1 : 0})` }"
>
</u-navbar>
</u-sticky>
<!-- #endif -->
<!-- 搜索框 --- 未搜索 -->
<view
class="p-[30rpx]"
:style="{
'background-color': !search.searching ? 'transparent' : '#ffffff'
}"
>
<view
class="search-input flex"
:style="{
'background-color': !search.searching ? '#ffffff' : '#F6F7F8'
}"
>
<view class="search-input-icon flex items-center flex-none">
<u-icon name="search"></u-icon>
</view>
<view class="search-input-wrapper flex items-center flex-1">
<input
class="search-input-inner flex-1"
placeholder="请输入关键词"
v-model="keyword"
@confirm="handleSearch"
@change="handleSearch"
:focus="true"
/>
<view
v-if="keyword.length"
class="flex-none px-3 text-muted"
@click="clearSearch"
>
<u-icon name="close-circle" size="34"></u-icon>
</view>
</view>
<button
class="search-input-btn flex-none"
@click="handleSearch"
>
搜索
</button>
</view>
</view>
<!-- 搜索 -->
<view class="search-content flex flex-col min-h-0 h-full">
<!-- 未搜索 -->
<suggest
v-show="!search.searching"
:hot_search="search.hot_search"
:his_search="search.his_search"
@clear="handleClear"
@search="(val) => {
keyword = val;
handleSearch()
}"
></suggest>
<!-- 搜索结果 -->
<view
class="flex flex-col min-h-0 h-full"
v-show="search.searching"
>
<!-- 检索下拉组件 列表 -->
<view class="bg-white">
<u-dropdown
ref="uDropdownRef"
menu-icon="arrow-down-fill"
menu-icon-size="20"
border-radius="18"
duration="0"
:active-color="$theme.primaryColor"
>
<u-dropdown-item :title="sortOptions[sort].label">
<view class="bg-white rounded-b-xl pt-4">
<view
v-for="(item, index) in sortOptions"
:key="index"
class="pb-4 text-content"
:class="{
'text-primary': index === sort
}"
@click="handleFiltrate('sort', index)"
>
<text class="ml-4">
{{ item.label }}
</text>
</view>
</view>
</u-dropdown-item>
<u-dropdown-item
title="销量"
>
<view class="bg-white rounded-b-xl pt-4">
<view
v-for="(item, index) in salesOptions"
:key="index"
class="pb-4 text-content"
:class="{
'text-primary': item.value === order_sales
}"
@click="handleFiltrate('order_sales', item.value)"
>
<text class="ml-4">
{{ item.label }}
</text>
</view>
</view>
</u-dropdown-item>
<u-dropdown-item
title="价格"
>
<view class="bg-white rounded-b-xl pt-4">
<view
v-for="(item, index) in salesOptions"
:key="index"
class="pb-4 text-content"
:class="{
'text-primary': item.value === price
}"
@click="handleFiltrate('price', item.value)"
>
<text class="ml-4">
{{ item.label }}
</text>
</view>
</view>
</u-dropdown-item>
<u-dropdown-item
:title="listOptions[list].label"
>
<view class="bg-white rounded-b-xl pt-4">
<view
v-for="(item, index) in listOptions"
:key="index"
class="pb-4 pl-4 text-content"
:class="{
'text-primary': item.value === list
}"
@click="handleFiltrate('list', item.value)"
>
<u-icon
:name="item.icon"
size="28"
></u-icon>
<text class="ml-2">
{{ item.label }}展示
</text>
</view>
</view>
</u-dropdown-item>
</u-dropdown>
</view>
<!-- 显示页面 列表 -->
<view class="h-full">
<z-paging
ref="paging"
v-model="search.result.content.goods_list"
:fixed="false"
height="100%"
:empty-view-img="EmptySearch"
@query="queryList"
>
<area-goods
:content="search.result.content"
:styles="search.result.styles"
/>
</z-paging>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import {ref, reactive, shallowRef} from 'vue'
import Suggest from './component/suggest.vue'
import {HISTORY} from '@/enums/constantEnums'
import {getHotSearch, getServiceList} from '@/api/shop'
import {locationState} from "@/hooks/useLocation";
import cache from '@/utils/cache'
import AreaGoods from "@/components/widgets/area-goods/area-goods.vue";
import EmptySearch from "@/static/images/empty/search.png";
interface Search {
hot_search: {
data: any[]
status: number
}
his_search: string[]
result: any
searching: boolean
}
const search = reactive<Search>({
hot_search: {
data: [],
status: 1
},
his_search: [],
// 这里复用了首页装修的商品组组件数据。
result: {
content: {
enabled: 1, // 是否启用
type: 1, // 1-自动获取
goods_type: 1, // 1-单列 2-双列 3-横向滑动 4-大图模式
show_title: 1, // 商品标题
show_server: 1, // 服务
show_price: 1, // 商品价格
show_scribing_price: 1, // 划线价格
show_sales: 1, // 销量
btn_text: '立即下单', // 按钮文字
show_num: 1, // 显示数量
goods_list: [] // 商品数据
},
styles: {
title_color: '#000000', // 标题颜色
server_color: '#888888', // 服务颜色
price_color: '#fc5447', // 价格颜色
scribing_price_color: '#888888', // 划线价格颜色
sales_color: '#888888', // 销量颜色
btn_radius: 10, // 按钮圆角
root_bg_color: '', // 根背景颜色
component_bg_color: '#ffffff', // 组件背景颜色
goods_bg_color: '', // 商品背景颜色
padding_top: 12, // 上内边距
padding_horizontal: 12, // 左右内边距
padding_bottom: 12, // 下内边距
goods_horizontal: 12, // 商品左右内边距
goods_vertical: 12, // 商品上下内边距
border_top_radius: 20,
border_bottom_radius: 20,
image_radius: 8
}
},
searching: false
})
const keyword = ref<string>('')
const order_sales = ref('') // 销量排序
const price = ref('') // 价格排序
const sort = ref('') // 综合排序
const list = ref('card') // 列表展示
const paging = shallowRef()
const uDropdownRef = shallowRef()
const sortOptions = {
'': {value: '', label: '综合排序'},
// 'desc': { value: 'desc', label:'距离优先'},
// 'asc': {value: 'asc', label: '销量优先'},
'comment_sales': {value: 'desc', label: '好评优先'}
}
const salesOptions = [
{value: '', label: '默认'},
{value: 'desc', label: '从高到低'},
{value: 'asc', label: '从低到高'}
]
const listOptions = {
card: {value: 'card', icon: 'grid', label: '卡片'},
list: {value: 'list', icon: 'list-dot', label: '列表'}
}
const handleFiltrate = (type: string, value: string) => {
switch (type) {
case 'sort':
sort.value = value
paging.value.refresh()
break
case 'order_sales':
order_sales.value = value
paging.value.refresh()
break
case 'price':
price.value = value
paging.value.refresh()
break
case 'list':
list.value = value
value === 'card' ? (search.result.content.goods_type = 1) : (search.result.content.goods_type = 2)
break
}
uDropdownRef.value?.close()
}
const handleSearch = () => {
if (keyword.value.trim() === '') {
return
}
if (keyword.value) {
if (!search.his_search.includes(keyword.value)) {
search.his_search.unshift(keyword.value)
cache.set(HISTORY, search.his_search)
}
}
paging.value.reload()
search.searching = true
}
const clearSearch = () => {
keyword.value = ''
search.searching = false
}
const getHotSearchFunc = async () => {
try {
search.hot_search = await getHotSearch()
} catch (e) {
//TODO handle the exception
console.log('获取热门搜索失败=>', e)
}
}
const handleClear = async (): Promise<void> => {
const resModel: any = await uni.showModal({
title: '温馨提示',
content: '是否清空历史记录?'
})
if (resModel.confirm) {
cache.set(HISTORY, '')
search.his_search = []
}
}
const queryList = async (page_no: number, page_size: number) => {
try {
const {lists} = await getServiceList({
keyword: keyword.value,
city_id: locationState.city_id,
order_sales: order_sales.value,
price: price.value,
comment_sales: sort.value,
page_no,
page_size
})
paging.value.complete(lists)
} catch (e) {
console.log('报错=>', e)
//TODO handle the exception
paging.value.complete(false)
}
}
getHotSearchFunc()
search.his_search = cache.get(HISTORY) || []
</script>
<style lang="scss" scoped>
.search {
.search-input {
background-color: #ffffff;
border-radius: 50px;
padding: 10rpx 20rpx;
.search-input-icon {
color: #666666;
padding: 0 14rpx 0 14rpx;
}
.search-input-wrapper {
}
.search-input-inner {
}
.search-input-btn {
height: 60rpx;
line-height: 60rpx;
font-size: 28rpx;
@apply bg-primary text-white rounded-full;
}
}
&-content {
&-s {
height: 100%;
}
}
}
</style>