feat: 初次提交我的项目代码
This commit is contained in:
526
pages/mall/consumer/category - 副本.uvue
Normal file
526
pages/mall/consumer/category - 副本.uvue
Normal file
@@ -0,0 +1,526 @@
|
||||
<!-- 商品分类页面 -->
|
||||
<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>
|
||||
Reference in New Issue
Block a user