JewelryMall/miniprogram/pages/search/index.vue
2026-03-02 23:35:11 +08:00

231 lines
5.7 KiB
Vue
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="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="custom-navbar__content" :style="{ height: navBarHeight + 'px' }">
<image class="custom-navbar__back" src="/static/ic_back.png" mode="aspectFit" @click="goBack" />
<text class="custom-navbar__title">全局搜索:{{ keyword }}</text>
<view class="custom-navbar__placeholder" />
</view>
</view>
<view :style="{ height: (statusBarHeight + navBarHeight) + 'px' }" />
<!-- 搜索结果统计 -->
<view class="result-tip" v-if="!loading && products.length > 0">
<text>共计搜到:{{ total }}条结果</text>
</view>
<!-- 结果列表 -->
<view class="result-list">
<view
v-for="item in products"
:key="item.id"
class="result-item"
@click="goDetail(item.id)"
>
<image class="result-item__img" :src="fullUrl(item.thumb || (item.bannerImages && item.bannerImages[0]) || '')" mode="aspectFill" />
<view class="result-item__info">
<text class="result-item__name">{{ item.name }}</text>
<view class="result-item__meta">
<text v-if="item.styleNo">款号:{{ item.styleNo }}</text>
<text v-if="item.barcode">条码号:{{ item.barcode }}</text>
</view>
<view class="result-item__bottom">
<text class="result-item__price">¥{{ item.basePrice }}元</text>
<text class="result-item__stock">×{{ item.stock || 1 }}</text>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<view v-if="loading" class="loading-tip">
<text>加载中...</text>
</view>
<view v-if="!loading && noMore && products.length > 0" class="loading-tip">
<text style="color: #FF6D9B">没有更多商品了</text>
</view>
<view v-if="!loading && products.length === 0" class="empty-tip">
<image class="empty-tip__img" src="/static/ic_empty.png" mode="aspectFit" />
<text>没有搜索到相关商品</text>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import type { Product } from '../../types'
import { getProducts } from '../../api/product'
import { BASE_URL } from '../../utils/request'
const keyword = ref('')
const products = ref<Product[]>([])
const loading = ref(false)
const total = ref(0)
const page = ref(1)
const pageSize = 20
const noMore = ref(false)
const statusBarHeight = ref(20)
const navBarHeight = ref(44)
try {
const sysInfo = uni.getSystemInfoSync()
statusBarHeight.value = sysInfo.statusBarHeight || 20
// #ifdef MP-WEIXIN
const menuBtn = uni.getMenuButtonBoundingClientRect()
navBarHeight.value = (menuBtn.top - (sysInfo.statusBarHeight || 20)) * 2 + menuBtn.height
// #endif
} catch { /* fallback */ }
function goBack() {
uni.navigateBack({ delta: 1 })
}
function fullUrl(path: string): string {
if (!path) return ''
if (path.startsWith('http')) return path
return BASE_URL + path
}
function goDetail(id: number) {
uni.navigateTo({ url: `/pages/product/detail?id=${id}` })
}
async function search(reset = false) {
if (reset) { page.value = 1; products.value = [] }
loading.value = true
try {
const data = await getProducts({ page: page.value, pageSize, keyword: keyword.value } as any)
if (reset) {
products.value = data.list
} else {
products.value.push(...data.list)
}
total.value = data.total || data.list.length
noMore.value = data.list.length < pageSize
} catch {
uni.showToast({ title: '搜索失败', icon: 'none' })
} finally {
loading.value = false
}
}
onMounted(() => {
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] as { options?: { keyword?: string } }
keyword.value = decodeURIComponent(currentPage.options?.keyword || '')
if (keyword.value) search(true)
})
</script>
<style scoped>
.search-page {
min-height: 100vh;
background: #f5f5f5;
}
.custom-navbar {
background: linear-gradient(to right, #FFCFDE, #FFA6C4);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
}
.custom-navbar__content {
display: flex;
align-items: center;
padding: 0 24rpx;
}
.custom-navbar__back {
width: 44rpx;
height: 44rpx;
}
.custom-navbar__title {
flex: 1;
text-align: center;
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.custom-navbar__placeholder {
width: 44rpx;
}
.result-tip {
text-align: center;
padding: 20rpx 0;
font-size: 26rpx;
color: #FF6D9B;
}
.result-list {
padding: 0 24rpx;
}
.result-item {
display: flex;
background: #fff;
border-radius: 12rpx;
padding: 24rpx;
margin-bottom: 16rpx;
}
.result-item__img {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
flex-shrink: 0;
background: #f5f5f5;
}
.result-item__info {
flex: 1;
margin-left: 20rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.result-item__name {
font-size: 30rpx;
font-weight: bold;
color: #333;
}
.result-item__meta {
display: flex;
gap: 24rpx;
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
.result-item__bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12rpx;
}
.result-item__price {
font-size: 32rpx;
color: #FF6D9B;
font-weight: bold;
}
.result-item__stock {
font-size: 26rpx;
color: #999;
}
.empty-tip {
text-align: center;
padding: 120rpx 0 60rpx;
color: #999;
font-size: 28rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.empty-tip__img {
width: 300rpx;
height: 300rpx;
margin-bottom: 24rpx;
}
.loading-tip {
text-align: center;
padding: 60rpx 0;
color: #999;
font-size: 28rpx;
}
</style>