完善居家服务商品详情页
This commit is contained in:
@@ -1,18 +1,33 @@
|
||||
<template>
|
||||
<view class="hmall-content">
|
||||
<view v-if="currentCategory != 'recommend' && secondaryCategoryDisplay.length > 0" class="hmall-secondary-panel">
|
||||
<view
|
||||
v-for="(item, index) in secondaryCategoryDisplay"
|
||||
:key="item.id + '-' + index"
|
||||
:class="['hmall-secondary-item', selectedSubCategoryId == item.id ? 'hmall-secondary-item-active' : '']"
|
||||
@click="emit('secondary-category-click', item)"
|
||||
<view v-if="currentCategory != 'recommend' && secondaryCategoryPages.length > 0" class="hmall-secondary-shell">
|
||||
<scroll-view
|
||||
class="hmall-secondary-scroll"
|
||||
direction="horizontal"
|
||||
:show-scrollbar="false"
|
||||
:scroll-with-animation="true"
|
||||
>
|
||||
<view class="hmall-secondary-icon-wrap">
|
||||
<image v-if="isImageIcon(item.icon)" class="hmall-secondary-image" :src="item.icon" mode="aspectFill" />
|
||||
<text v-else class="hmall-secondary-icon-text">{{ getCategoryDisplayIcon(item) }}</text>
|
||||
<view class="hmall-secondary-pages">
|
||||
<view
|
||||
v-for="(page, pageIndex) in secondaryCategoryPages"
|
||||
:key="page.id + '-' + pageIndex"
|
||||
class="hmall-secondary-page"
|
||||
>
|
||||
<view
|
||||
v-for="(item, itemIndex) in page.items"
|
||||
:key="item.id + '-' + itemIndex"
|
||||
:class="['hmall-secondary-item', selectedSubCategoryId == item.id ? 'hmall-secondary-item-active' : '']"
|
||||
@click="emit('secondary-category-click', item)"
|
||||
>
|
||||
<view class="hmall-secondary-icon-wrap">
|
||||
<image v-if="item.icon != null && isImageIcon(item.icon)" class="hmall-secondary-image" :src="item.icon" mode="aspectFit" />
|
||||
<text v-else class="hmall-secondary-icon-text">{{ getCategoryDisplayIcon(item) }}</text>
|
||||
</view>
|
||||
<text class="hmall-secondary-name">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="hmall-secondary-name">{{ item.name }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<view v-if="currentCategory == 'recommend' && marketingChannels.length > 0" class="hmall-recommend-section">
|
||||
@@ -73,77 +88,86 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="hmall-feed-divider"></view>
|
||||
<view class="hmall-products-section">
|
||||
<view class="hmall-feed-divider"></view>
|
||||
|
||||
<view v-if="hotProducts.length > 0" class="hmall-products-grid hmall-products-grid-dense">
|
||||
<view
|
||||
v-for="(product, index) in hotProducts"
|
||||
:key="product.id + '-' + index"
|
||||
class="hmall-product-card hmall-product-card-skeleton"
|
||||
@click="emit('select-product', product)"
|
||||
>
|
||||
<view class="hmall-product-image-wrapper hmall-product-image-wrapper-fixed">
|
||||
<image
|
||||
class="hmall-product-image"
|
||||
:src="getProductCover(product)"
|
||||
@error="() => handleProductImageError(product.id)"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<view class="hmall-product-body">
|
||||
<view v-if="getProductCardTags(product).length > 0" class="hmall-product-card-tags">
|
||||
<text
|
||||
v-for="(tag, tagIndex) in getProductCardTags(product)"
|
||||
:key="product.id + '-card-tag-' + tagIndex"
|
||||
class="hmall-product-card-tag"
|
||||
>
|
||||
{{ tag }}
|
||||
</text>
|
||||
<view v-if="hotProducts.length > 0" class="hmall-products-grid hmall-products-grid-dense">
|
||||
<view
|
||||
v-for="(product, index) in hotProducts"
|
||||
:key="product.id + '-' + index"
|
||||
class="hmall-product-card hmall-product-card-skeleton"
|
||||
@click="emit('select-product', product)"
|
||||
>
|
||||
<view class="hmall-product-image-wrapper hmall-product-image-wrapper-fixed">
|
||||
<image
|
||||
class="hmall-product-image"
|
||||
:src="getProductCover(product)"
|
||||
@error="() => handleProductImageError(product.id)"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<text class="hmall-product-title">{{ getProductTitle(product) }}</text>
|
||||
<text v-if="getProductHighlight(product) != ''" class="hmall-product-highlight">{{ getProductHighlight(product) }}</text>
|
||||
<view v-if="getProductServiceTags(product).length > 0" class="hmall-product-service-tags">
|
||||
<text
|
||||
v-for="(tag, tagIndex) in getProductServiceTags(product)"
|
||||
:key="product.id + '-service-tag-' + tagIndex"
|
||||
class="hmall-product-service-tag"
|
||||
>
|
||||
{{ tag }}
|
||||
</text>
|
||||
<view class="hmall-product-body">
|
||||
<view v-if="getProductCardTags(product).length > 0" class="hmall-product-card-tags">
|
||||
<text
|
||||
v-for="(tag, tagIndex) in getProductCardTags(product)"
|
||||
:key="product.id + '-card-tag-' + tagIndex"
|
||||
class="hmall-product-card-tag"
|
||||
>
|
||||
{{ tag }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="hmall-product-title">{{ getProductTitle(product) }}</text>
|
||||
<text v-if="getProductHighlight(product) != ''" class="hmall-product-highlight">{{ getProductHighlight(product) }}</text>
|
||||
<view v-if="getProductServiceTags(product).length > 0" class="hmall-product-service-tags">
|
||||
<text
|
||||
v-for="(tag, tagIndex) in getProductServiceTags(product)"
|
||||
:key="product.id + '-service-tag-' + tagIndex"
|
||||
class="hmall-product-service-tag"
|
||||
>
|
||||
{{ tag }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="hmall-product-price-row">
|
||||
<text class="hmall-product-price-value">¥{{ formatProductPrice(product) }}</text>
|
||||
<text v-if="showMarketPrice(product)" class="hmall-product-market-price">¥{{ formatMarketPrice(product) }}</text>
|
||||
</view>
|
||||
<text v-if="getProductSalesText(product) != ''" class="hmall-product-sales">{{ getProductSalesText(product) }}</text>
|
||||
</view>
|
||||
<view class="hmall-product-price-row">
|
||||
<text class="hmall-product-price-value">¥{{ formatProductPrice(product) }}</text>
|
||||
<text v-if="showMarketPrice(product)" class="hmall-product-market-price">¥{{ formatMarketPrice(product) }}</text>
|
||||
</view>
|
||||
<text v-if="getProductSalesText(product) != ''" class="hmall-product-sales">{{ getProductSalesText(product) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="loading" class="hmall-loading-state">
|
||||
<text class="hmall-loading-text">正在加载商品...</text>
|
||||
</view>
|
||||
<view v-else-if="loading" class="hmall-loading-state">
|
||||
<text class="hmall-loading-text">正在加载商品...</text>
|
||||
</view>
|
||||
|
||||
<EmptyState v-else text="当前分类暂无商品"></EmptyState>
|
||||
<view v-else class="hmall-empty-wrap">
|
||||
<EmptyState text="当前分类暂无商品"></EmptyState>
|
||||
</view>
|
||||
|
||||
<view v-if="loading || showLoadMore" class="hmall-load-more-status">
|
||||
<text class="hmall-loading-text">正在加载更多商品...</text>
|
||||
</view>
|
||||
<view v-if="loading || showLoadMore" class="hmall-load-more-status">
|
||||
<text class="hmall-loading-text">正在加载更多商品...</text>
|
||||
</view>
|
||||
|
||||
<view v-if="!hasMore && hotProducts.length > 0" class="hmall-feed-end-state">
|
||||
<text class="hmall-feed-end-text">已经到底了</text>
|
||||
<view v-if="!hasMore && hotProducts.length > 0" class="hmall-feed-end-state">
|
||||
<text class="hmall-feed-end-text">已经到底了</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import EmptyState from '@/components/consumer/EmptyState.uvue'
|
||||
import type { Category, Product } from '@/utils/supabaseService.uts'
|
||||
import type { MarketingChannel, ChannelProduct, SimpleCategoryChannel } from '@/utils/mockChannelData.uts'
|
||||
|
||||
const failedProductImageIds = ref<string[]>([])
|
||||
|
||||
type SecondaryCategoryPage = {
|
||||
id: string
|
||||
items: Array<Category>
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
currentCategory: {
|
||||
type: String,
|
||||
@@ -190,6 +214,28 @@ const emit = defineEmits<{
|
||||
(e: 'select-product', product: Product): void
|
||||
}>()
|
||||
|
||||
const secondaryCategoryPages = computed((): Array<SecondaryCategoryPage> => {
|
||||
const pages: Array<SecondaryCategoryPage> = []
|
||||
const source = props.secondaryCategoryDisplay
|
||||
const pageSize = 10
|
||||
let pageIndex = 0
|
||||
|
||||
while (pageIndex * pageSize < source.length) {
|
||||
const startIndex = pageIndex * pageSize
|
||||
const pageItems: Array<Category> = []
|
||||
for (let i = startIndex; i < source.length && i < startIndex + pageSize; i++) {
|
||||
pageItems.push(source[i])
|
||||
}
|
||||
pages.push({
|
||||
id: 'secondary-page-' + pageIndex.toString(),
|
||||
items: pageItems
|
||||
} as SecondaryCategoryPage)
|
||||
pageIndex = pageIndex + 1
|
||||
}
|
||||
|
||||
return pages
|
||||
})
|
||||
|
||||
function isImageIcon(icon: string): boolean {
|
||||
if (icon == '') {
|
||||
return false
|
||||
@@ -315,57 +361,83 @@ function showMarketPrice(product: Product): boolean {
|
||||
|
||||
<style scoped>
|
||||
.hmall-content {
|
||||
padding: 0 20rpx 0;
|
||||
padding: 0 16rpx 0;
|
||||
box-sizing: border-box;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.hmall-secondary-panel {
|
||||
|
||||
.hmall-secondary-shell {
|
||||
margin-top: 16rpx;
|
||||
margin-bottom: 14rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
padding: 18rpx 12rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 12rpx;
|
||||
border-radius: 18rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.hmall-secondary-scroll {
|
||||
width: 100%;
|
||||
height: 176rpx;
|
||||
}
|
||||
|
||||
.hmall-secondary-pages {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 176rpx;
|
||||
}
|
||||
|
||||
.hmall-secondary-page {
|
||||
width: 718rpx;
|
||||
height: 176rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
padding-left: 12rpx;
|
||||
padding-right: 12rpx;
|
||||
padding-top: 12rpx;
|
||||
padding-bottom: 8rpx;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hmall-secondary-item {
|
||||
width: 124rpx;
|
||||
width: 20%;
|
||||
height: 78rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16rpx 8rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #f7f7f7;
|
||||
gap: 10rpx;
|
||||
justify-content: center;
|
||||
padding: 0 4rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.hmall-secondary-item-active {
|
||||
background: #fff2f2;
|
||||
|
||||
}
|
||||
|
||||
.hmall-secondary-icon-wrap {
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
border-radius: 34rpx;
|
||||
background: #ffffff;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #f6f6f6;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 6rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hmall-secondary-image {
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
border-radius: 34rpx;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.hmall-secondary-icon-text {
|
||||
font-size: 28rpx;
|
||||
color: #e2231a;
|
||||
font-weight: 700;
|
||||
font-size: 26rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.hmall-secondary-name {
|
||||
@@ -379,6 +451,11 @@ function showMarketPrice(product: Product): boolean {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hmall-secondary-item-active .hmall-secondary-name {
|
||||
color: #e1251b;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hmall-recommend-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -606,11 +683,17 @@ function showMarketPrice(product: Product): boolean {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.hmall-products-section {
|
||||
background: #f5f5f5;
|
||||
padding-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.hmall-products-grid {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
background: #f5f5f5;
|
||||
margin-top: 0;
|
||||
min-height: 0;
|
||||
padding-bottom: 20rpx;
|
||||
@@ -634,9 +717,9 @@ function showMarketPrice(product: Product): boolean {
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
border-radius: 18rpx 18rpx 0 0;
|
||||
overflow: hidden;
|
||||
background: #f5f5f5;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.hmall-product-image {
|
||||
@@ -759,6 +842,12 @@ function showMarketPrice(product: Product): boolean {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.hmall-empty-wrap {
|
||||
background: #ffffff;
|
||||
border-radius: 18rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hmall-loading-text,
|
||||
.hmall-feed-end-text {
|
||||
font-size: 24rpx;
|
||||
|
||||
Reference in New Issue
Block a user