Files
medical-mall/pages/mall/consumer/category - 副本.uvue

526 lines
11 KiB
Plaintext
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="category-page">
<!-- 搜索栏 -->
<view class="search-header">
<view class="search-box" @click="goToSearch">
<text class="search-icon">🔍</text>
<text class="search-placeholder">搜索商品</text>
</view>
</view>
<view class="category-container">
<!-- 左侧分类导航 -->
<scroll-view class="category-nav" scroll-y>
<view v-for="category in categories"
:key="category.id"
:class="['nav-item', { active: activeCategoryId === category.id }]"
@click="selectCategory(category)">
<text class="nav-text">{{ category.name }}</text>
</view>
</scroll-view>
<!-- 右侧内容区域 -->
<scroll-view class="category-content" scroll-y>
<!-- 当前分类的banner -->
<view v-if="currentCategory" class="category-banner">
<image class="banner-image" :src="currentCategory.image_url || '/static/default-banner.png'" />
</view>
<!-- 子分类 -->
<view v-if="subCategories.length > 0" class="sub-category-section">
<view class="section-title">{{ currentCategory?.name }}分类</view>
<view class="sub-category-grid">
<view v-for="subCategory in subCategories"
:key="subCategory.id"
class="sub-category-item"
@click="navigateToSubCategory(subCategory)">
<image class="sub-category-icon" :src="subCategory.icon_url || '/static/default-category.png'" />
<text class="sub-category-name">{{ subCategory.name }}</text>
</view>
</view>
</view>
<!-- 品牌专区 -->
<view v-if="brands.length > 0" class="brand-section">
<view class="section-header">
<text class="section-title">品牌推荐</text>
<text class="more-btn" @click="viewAllBrands">更多 </text>
</view>
<scroll-view class="brand-scroll" scroll-x>
<view class="brand-list">
<view v-for="brand in brands"
:key="brand.id"
class="brand-item"
@click="viewBrandProducts(brand)">
<image class="brand-logo" :src="brand.logo_url || '/static/default-brand.png'" />
<text class="brand-name">{{ brand.name }}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 热销商品 -->
<view v-if="hotProducts.length > 0" class="hot-products-section">
<view class="section-title">热销商品</view>
<view class="hot-products-grid">
<view v-for="product in hotProducts"
:key="product.id"
class="product-item"
@click="viewProductDetail(product)">
<image class="product-image" :src="getProductFirstImage(product)" />
<view class="product-info">
<text class="product-name">{{ product.name }}</text>
<view class="product-price-row">
<text class="current-price">¥{{ product.price }}</text>
<text v-if="product.original_price && product.original_price > product.price"
class="original-price">¥{{ product.original_price }}</text>
</view>
<view class="sales-info">
<text class="sales-text">已售{{ product.sales }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<view v-if="isLoadingProducts" class="loading-more">
<text class="loading-text">加载中...</text>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, onMounted, watch } from 'vue'
import type { ProductType } from '@/types/mall-types.uts'
import supa from '@/components/supadb/aksupainstance.uts'
type CategoryType = {
id: string
name: string
parent_id: string | null
icon_url: string | null
image_url: string | null
sort_order: number
is_active: boolean
}
type BrandType = {
id: string
name: string
logo_url: string | null
description: string | null
}
const categories = ref<Array<CategoryType>>([])
const activeCategoryId = ref<string>('')
const currentCategory = ref<CategoryType | null>(null)
const subCategories = ref<Array<CategoryType>>([])
const brands = ref<Array<BrandType>>([])
const hotProducts = ref<Array<ProductType>>([])
const isLoadingProducts = ref<boolean>(false)
// 监听分类切换
watch(activeCategoryId, (newId) => {
if (newId) {
loadCategoryData(newId)
}
})
// 生命周期
onMounted(() => {
loadCategories()
})
// 加载一级分类
const loadCategories = async () => {
try {
const { data, error } = await supa
.from('categories')
.select('*')
.eq('is_active', true)
.is('parent_id', null)
.order('sort_order', { ascending: true })
if (error !== null) {
console.error('加载分类失败:', error)
return
}
categories.value = data ?? []
// 默认选中第一个分类
if (categories.value.length > 0) {
activeCategoryId.value = categories.value[0].id
}
} catch (err) {
console.error('加载分类异常:', err)
}
}
// 加载分类数据
const loadCategoryData = async (categoryId: string) => {
// 1. 获取当前分类信息
try {
const { data: categoryData, error: categoryError } = await supa
.from('categories')
.select('*')
.eq('id', categoryId)
.single()
if (categoryError !== null) {
console.error('加载分类详情失败:', categoryError)
return
}
currentCategory.value = categoryData
// 2. 加载子分类
const { data: subData, error: subError } = await supa
.from('categories')
.select('*')
.eq('parent_id', categoryId)
.eq('is_active', true)
.order('sort_order', { ascending: true })
if (subError !== null) {
console.error('加载子分类失败:', subError)
} else {
subCategories.value = subData ?? []
}
// 3. 加载品牌
const { data: brandData, error: brandError } = await supa
.from('brands')
.select('*')
.eq('is_active', true)
.order('sort_order', { ascending: true })
.limit(8)
if (brandError !== null) {
console.error('加载品牌失败:', brandError)
} else {
brands.value = brandData ?? []
}
// 4. 加载热销商品
loadHotProducts(categoryId)
} catch (err) {
console.error('加载分类数据异常:', err)
}
}
// 加载热销商品
const loadHotProducts = async (categoryId: string) => {
isLoadingProducts.value = true
try {
const { data, error } = await supa
.from('products')
.select('*')
.eq('status', 1)
.eq('category_id', categoryId)
.order('sales', { ascending: false })
.limit(12)
if (error !== null) {
console.error('加载热销商品失败:', error)
return
}
hotProducts.value = data ?? []
} catch (err) {
console.error('加载热销商品异常:', err)
} finally {
isLoadingProducts.value = false
}
}
// 获取商品第一张图片
const getProductFirstImage = (product: ProductType): string => {
return product.images?.[0] || '/static/default-product.png'
}
// 选择分类
const selectCategory = (category: CategoryType) => {
activeCategoryId.value = category.id
}
// 导航到子分类
const navigateToSubCategory = (subCategory: CategoryType) => {
// 可以跳转到子分类的商品列表页
uni.navigateTo({
url: `/pages/mall/consumer/product-list?categoryId=${subCategory.id}&title=${encodeURIComponent(subCategory.name)}`
})
}
// 查看品牌商品
const viewBrandProducts = (brand: BrandType) => {
uni.navigateTo({
url: `/pages/mall/consumer/product-list?brandId=${brand.id}&title=${encodeURIComponent(brand.name)}`
})
}
// 查看商品详情
const viewProductDetail = (product: ProductType) => {
uni.navigateTo({
url: `/pages/mall/consumer/product-detail?id=${product.id}`
})
}
// 查看所有品牌
const viewAllBrands = () => {
uni.navigateTo({
url: '/pages/mall/consumer/brands'
})
}
// 跳转到搜索页
const goToSearch = () => {
uni.navigateTo({
url: '/pages/mall/consumer/search'
})
}
</script>
<style scoped>
.category-page {
display: flex;
flex-direction: column;
height: 100vh;
background-color: #ffffff;
}
.search-header {
background-color: #ffffff;
padding: 10px 15px;
border-bottom: 1px solid #e5e5e5;
}
.search-box {
background-color: #f8f8f8;
border-radius: 20px;
padding: 10px 15px;
display: flex;
align-items: center;
}
.search-icon {
color: #999999;
margin-right: 8px;
}
.search-placeholder {
color: #999999;
font-size: 14px;
}
.category-container {
flex: 1;
display: flex;
}
.category-nav {
width: 100px;
background-color: #f8f8f8;
border-right: 1px solid #e5e5e5;
}
.nav-item {
padding: 15px 10px;
text-align: center;
border-bottom: 1px solid #e5e5e5;
}
.nav-item.active {
background-color: #ffffff;
color: #007aff;
border-left: 3px solid #007aff;
}
.nav-text {
font-size: 14px;
color: #333333;
}
.nav-item.active .nav-text {
color: #007aff;
font-weight: bold;
}
.category-content {
flex: 1;
}
.category-banner {
padding: 15px;
}
.banner-image {
width: 100%;
height: 120px;
border-radius: 8px;
}
.sub-category-section,
.brand-section,
.hot-products-section {
padding: 15px;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #333333;
margin-bottom: 15px;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.more-btn {
color: #007aff;
font-size: 14px;
}
.sub-category-grid {
display: flex;
flex-wrap: wrap;
margin: 0 -5px;
}
.sub-category-item {
width: 33.33%;
padding: 0 5px;
margin-bottom: 15px;
display: flex;
flex-direction: column;
align-items: center;
}
.sub-category-icon {
width: 50px;
height: 50px;
border-radius: 25px;
margin-bottom: 8px;
}
.sub-category-name {
font-size: 12px;
color: #666666;
text-align: center;
}
.brand-scroll {
width: 100%;
white-space: nowrap;
}
.brand-list {
display: inline-flex;
}
.brand-item {
width: 80px;
display: flex;
flex-direction: column;
align-items: center;
margin-right: 15px;
}
.brand-logo {
width: 60px;
height: 60px;
border-radius: 8px;
margin-bottom: 8px;
border: 1px solid #e5e5e5;
}
.brand-name {
font-size: 12px;
color: #333333;
text-align: center;
}
.hot-products-grid {
display: flex;
flex-wrap: wrap;
margin: 0 -5px;
}
.product-item {
width: 50%;
padding: 0 5px;
margin-bottom: 15px;
}
.product-image {
width: 100%;
height: 100px;
border-radius: 5px;
margin-bottom: 8px;
}
.product-info {
padding: 0 5px;
}
.product-name {
font-size: 13px;
color: #333333;
line-height: 1.4;
margin-bottom: 8px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.product-price-row {
display: flex;
align-items: baseline;
margin-bottom: 5px;
}
.current-price {
font-size: 14px;
color: #ff4757;
font-weight: bold;
margin-right: 5px;
}
.original-price {
font-size: 12px;
color: #999999;
text-decoration: line-through;
}
.sales-info {
display: flex;
justify-content: space-between;
align-items: center;
}
.sales-text {
font-size: 11px;
color: #999999;
}
.loading-more {
padding: 20px;
display: flex;
justify-content: center;
}
.loading-text {
color: #999999;
font-size: 14px;
}
</style>