JewelryMall/miniprogram/pages/index/index.vue
2026-03-02 23:41:58 +08:00

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>