321 lines
7.5 KiB
Vue
321 lines
7.5 KiB
Vue
<template>
|
|
<view class="home-page">
|
|
<!-- 自定义导航栏 -->
|
|
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
|
|
<view class="custom-navbar__content" :style="{ height: navBarHeight + 'px' }">
|
|
<text class="custom-navbar__title">凯缘钻之城</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 搜索栏 -->
|
|
<view class="search-bar">
|
|
<view class="search-bar__input">
|
|
<image class="search-bar__icon" src="/static/ic_search.png" mode="aspectFit" />
|
|
<input
|
|
class="search-bar__field"
|
|
type="text"
|
|
placeholder="请输入产品名称、款号、条码号或款式"
|
|
:value="keyword"
|
|
@input="(e: any) => keyword = e.detail.value"
|
|
@confirm="goSearch"
|
|
/>
|
|
</view>
|
|
<text class="search-bar__btn" @click="goSearch">搜索</text>
|
|
</view>
|
|
|
|
<!-- 分类图标区 -->
|
|
<scroll-view class="category-section" scroll-x>
|
|
<view class="category-section__inner">
|
|
<view v-for="cat in categories" :key="cat.id" class="category-icon"
|
|
:class="{ 'category-icon--active': activeCategoryId === cat.id }" @click="selectCategory(cat.id)">
|
|
<view class="category-icon__circle">
|
|
<image v-if="cat.icon" class="category-icon__img" :src="fullUrl(cat.icon)" mode="aspectFill" />
|
|
<text v-else class="category-icon__emoji">💎</text>
|
|
</view>
|
|
<text class="category-icon__label">{{ cat.name }}</text>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<!-- 快捷入口 -->
|
|
<view class="quick-actions">
|
|
<view class="quick-action quick-action--calc" @click="goCalculator">
|
|
<image class="quick-action__icon" src="/static/ic_jz.png" mode="aspectFit" />
|
|
<text class="quick-action__text">钻戒计算器</text>
|
|
</view>
|
|
<view class="quick-action quick-action--service" @click="showQrcode = true">
|
|
<image class="quick-action__icon" src="/static/ic_kf.png" mode="aspectFit" />
|
|
<text class="quick-action__text">客服找款</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 商品列表 -->
|
|
<view class="product-grid">
|
|
<view v-for="product in products" :key="product.id" class="product-grid__item">
|
|
<ProductCard :product="product" />
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 空状态 -->
|
|
<view v-if="!loading && products.length === 0" class="empty-tip">
|
|
<text>暂无商品</text>
|
|
</view>
|
|
|
|
<!-- 加载更多 -->
|
|
<view v-if="loading" class="loading-tip">
|
|
<text>加载中...</text>
|
|
</view>
|
|
|
|
<!-- 客服二维码弹窗 -->
|
|
<CustomerServiceBtn v-if="showQrcode" mode="qrcode" @close="showQrcode = false" />
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, onMounted } from 'vue'
|
|
import type { Product, Category } from '../../types'
|
|
import { getProducts, getCategories } from '../../api/product'
|
|
import { BASE_URL } from '../../utils/request'
|
|
import ProductCard from '../../components/ProductCard.vue'
|
|
import CustomerServiceBtn from '../../components/CustomerServiceBtn.vue'
|
|
|
|
const products = ref<Product[]>([])
|
|
const categories = ref<Category[]>([])
|
|
const activeCategoryId = ref<number | undefined>(undefined)
|
|
const loading = ref(false)
|
|
const showQrcode = ref(false)
|
|
const page = ref(1)
|
|
const keyword = ref('')
|
|
const pageSize = 20
|
|
|
|
// 自定义导航栏高度
|
|
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 fullUrl(path : string) : string {
|
|
if (!path) return ''
|
|
if (path.startsWith('http')) return path
|
|
return BASE_URL + path
|
|
}
|
|
|
|
async function loadCategories() {
|
|
try {
|
|
const data = await getCategories()
|
|
categories.value = data
|
|
} catch { /* handled */ }
|
|
}
|
|
|
|
async function loadProducts(reset = false) {
|
|
if (reset) { page.value = 1; products.value = [] }
|
|
loading.value = true
|
|
try {
|
|
const params : Record<string, unknown> = { page: page.value, pageSize }
|
|
if (activeCategoryId.value) params.categoryId = activeCategoryId.value
|
|
if (keyword.value) params.keyword = keyword.value
|
|
const data = await getProducts(params as any)
|
|
if (reset) { products.value = data.list } else { products.value.push(...data.list) }
|
|
} catch { /* handled */ }
|
|
finally { loading.value = false }
|
|
}
|
|
|
|
function selectCategory(id : number) {
|
|
const cat = categories.value.find((c: any) => c.id === id)
|
|
const name = cat ? encodeURIComponent(cat.name) : ''
|
|
uni.navigateTo({ url: `/pages/category/index?id=${id}&name=${name}` })
|
|
}
|
|
|
|
function goSearch() {
|
|
if (!keyword.value.trim()) return
|
|
uni.navigateTo({ url: `/pages/search/index?keyword=${encodeURIComponent(keyword.value.trim())}` })
|
|
}
|
|
|
|
function goCalculator() {
|
|
uni.navigateTo({ url: '/pages/calculator/index' })
|
|
}
|
|
|
|
onMounted(() => { loadCategories(); loadProducts(true) })
|
|
</script>
|
|
|
|
<style scoped>
|
|
.home-page {
|
|
min-height: 100vh;
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
/* 自定义导航栏 */
|
|
.custom-navbar {
|
|
background: linear-gradient(to right, #FFCFDE, #FFA6C4);
|
|
}
|
|
|
|
.custom-navbar__content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.custom-navbar__title {
|
|
font-size: 34rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
|
|
/* 搜索栏 */
|
|
.search-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 16rpx 24rpx;
|
|
}
|
|
|
|
.search-bar__input {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
background: #fff;
|
|
border-radius: 40rpx;
|
|
padding: 16rpx 24rpx;
|
|
height: 72rpx;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.search-bar__icon {
|
|
width: 32rpx;
|
|
height: 32rpx;
|
|
margin-right: 12rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.search-bar__field {
|
|
flex: 1;
|
|
font-size: 24rpx;
|
|
color: #333;
|
|
}
|
|
|
|
.search-bar__btn {
|
|
margin-left: 16rpx;
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* 分类图标 */
|
|
.category-section {
|
|
white-space: nowrap;
|
|
padding: 32rpx 0 24rpx;
|
|
background: #FFFFFF;
|
|
}
|
|
|
|
.category-section__inner {
|
|
display: inline-flex;
|
|
padding: 0 24rpx;
|
|
gap: 32rpx;
|
|
}
|
|
|
|
.category-icon {
|
|
display: inline-flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 12rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.category-icon__circle {
|
|
width: 120rpx;
|
|
height: 120rpx;
|
|
border-radius: 30rpx;
|
|
background: linear-gradient(135deg, #fce4ec, #f8bbd0);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.category-icon--active .category-icon__circle {
|
|
background: linear-gradient(135deg, #f48fb1, #e91e63);
|
|
box-shadow: 0 4rpx 16rpx rgba(233, 30, 99, 0.3);
|
|
}
|
|
|
|
.category-icon__emoji {
|
|
font-size: 48rpx;
|
|
}
|
|
|
|
.category-icon__img {
|
|
width: 90rpx;
|
|
height: 90rpx;
|
|
}
|
|
|
|
.category-icon__label {
|
|
font-size: 24rpx;
|
|
color: #333;
|
|
}
|
|
|
|
.category-icon--active .category-icon__label {
|
|
color: #e91e63;
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* 快捷入口 */
|
|
.quick-actions {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
padding: 0 24rpx 24rpx;
|
|
background-color: #FFFFFF;
|
|
}
|
|
|
|
.quick-action {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 16rpx;
|
|
padding: 28rpx 0;
|
|
border-radius: 16rpx;
|
|
background: #fff;
|
|
}
|
|
|
|
.quick-action--calc {
|
|
background: linear-gradient(135deg, #FFA4C3, #FFD2E0);
|
|
}
|
|
|
|
.quick-action--service {
|
|
background: linear-gradient(135deg, #e8f5e9, #fff);
|
|
}
|
|
|
|
.quick-action__icon {
|
|
width: 44rpx;
|
|
height: 44rpx;
|
|
}
|
|
|
|
.quick-action__text {
|
|
font-size: 30rpx;
|
|
color: #333;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* 商品列表 */
|
|
.product-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
padding: 0 24rpx;
|
|
gap: 16rpx;
|
|
}
|
|
|
|
.product-grid__item {
|
|
width: calc(50% - 8rpx);
|
|
}
|
|
|
|
.empty-tip,
|
|
.loading-tip {
|
|
text-align: center;
|
|
padding: 60rpx 0;
|
|
color: #999;
|
|
font-size: 28rpx;
|
|
}
|
|
</style> |