完善居家服务商品详情页

This commit is contained in:
2026-05-19 23:10:27 +08:00
parent b5ab00ab12
commit b8b0b453e0
5 changed files with 2870 additions and 358 deletions

View File

@@ -0,0 +1,461 @@
{
"pages": [
{
"path": "pages/main/index",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/user/boot",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/user/login",
"style": {
"navigationBarTitleText": "用户登录",
"navigationStyle": "custom"
}
},
{
"path": "pages/user/register",
"style": {
"navigationBarTitleText": "注册"
}
},
{
"path": "pages/user/forgot-password",
"style": {
"navigationBarTitleText": "忘记密码"
}
},
{
"path": "pages/user/terms",
"style": {
"navigationBarTitleText": "用户协议与隐私政策"
}
},
{
"path": "pages/user/center",
"style": {
"navigationBarTitleText": "用户中心"
}
},
{
"path": "pages/user/profile",
"style": {
"navigationBarTitleText": "个人资料"
}
},
{
"path": "pages/user/change-password",
"style": {
"navigationBarTitleText": "修改密码"
}
},
{
"path": "pages/user/bind-phone",
"style": {
"navigationBarTitleText": "绑定手机"
}
},
{
"path": "pages/user/bind-email",
"style": {
"navigationBarTitleText": "绑定邮箱"
}
},
{
"path": "pages/main/messages",
"style": {
"navigationBarTitleText": "消息",
"enablePullDownRefresh": true
}
},
{
"path": "pages/main/cart",
"style": {
"navigationBarTitleText": "购物车",
"navigationStyle": "custom"
}
},
{
"path": "pages/main/profile",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/main/category",
"style": {
"navigationBarTitleText": "分类",
"navigationStyle": "custom"
}
}
],
"subPackages": [
{
"root": "pages/mall/consumer",
"pages": [
{
"path": "settings",
"style": {
"navigationBarTitleText": "设置"
}
},
{
"path": "edit-profile",
"style": {
"navigationBarTitleText": "编辑资料"
}
},
{
"path": "wallet",
"style": {
"navigationBarTitleText": "我的钱包"
}
},
{
"path": "withdraw",
"style": {
"navigationBarTitleText": "余额提现"
}
},
{
"path": "search",
"style": {
"navigationBarTitleText": "搜索",
"navigationStyle": "custom"
}
},
{
"path": "product-detail",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "channel-detail",
"style": {
"navigationBarTitleText": "频道详情",
"navigationStyle": "custom"
}
},
{
"path": "shop-detail",
"style": {
"navigationBarTitleText": "店铺详情"
}
},
{
"path": "coupons",
"style": {
"navigationBarTitleText": "我的优惠券"
}
},
{
"path": "favorites",
"style": {
"navigationBarTitleText": "我的收藏"
}
},
{
"path": "footprint",
"style": {
"navigationBarTitleText": "我的足迹"
}
},
{
"path": "address",
"style": {
"navigationBarTitleText": "地址"
}
},
{
"path": "address-list",
"style": {
"navigationBarTitleText": "收货地址"
}
},
{
"path": "address-edit",
"style": {
"navigationBarTitleText": "编辑地址"
}
},
{
"path": "checkout",
"style": {
"navigationBarTitleText": "确认订单"
}
},
{
"path": "payment",
"style": {
"navigationBarTitleText": "收银台"
}
},
{
"path": "payment-success",
"style": {
"navigationBarTitleText": "支付成功",
"navigationStyle": "custom"
}
},
{
"path": "orders",
"style": {
"navigationBarTitleText": "我的订单",
"enablePullDownRefresh": true
}
},
{
"path": "order-detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "logistics",
"style": {
"navigationBarTitleText": "物流详情"
}
},
{
"path": "review",
"style": {
"navigationBarTitleText": "评价晒单"
}
},
{
"path": "refund",
"style": {
"navigationBarTitleText": "退款/售后"
}
},
{
"path": "apply-refund",
"style": {
"navigationBarTitleText": "申请售后"
}
},
{
"path": "refund-review",
"style": {
"navigationBarTitleText": "服务评价"
}
},
{
"path": "chat",
"style": {
"navigationBarTitleText": "客服聊天",
"navigationStyle": "custom"
}
},
{
"path": "chat_new",
"style": {
"navigationBarTitleText": "客服聊天(新版)"
}
},
{
"path": "subscription/plan-list",
"style": {
"navigationBarTitleText": "软件订阅"
}
},
{
"path": "subscription/plan-detail",
"style": {
"navigationBarTitleText": "订阅详情"
}
},
{
"path": "subscription/subscribe-checkout",
"style": {
"navigationBarTitleText": "确认订阅"
}
},
{
"path": "subscription/my-subscriptions",
"style": {
"navigationBarTitleText": "我的订阅"
}
},
{
"path": "subscription/followed-shops",
"style": {
"navigationBarTitleText": "关注店铺"
}
},
{
"path": "points/index",
"style": {
"navigationBarTitleText": "积分管理"
}
},
{
"path": "points/signin",
"style": {
"navigationBarTitleText": "签到"
}
},
{
"path": "points/exchange",
"style": {
"navigationBarTitleText": "积分兑换"
}
},
{
"path": "points/exchange-records",
"style": {
"navigationBarTitleText": "兑换记录"
}
},
{
"path": "red-packets/index",
"style": {
"navigationBarTitleText": "我的红包"
}
},
{
"path": "bank-cards/index",
"style": {
"navigationBarTitleText": "银行卡管理"
}
},
{
"path": "bank-cards/add",
"style": {
"navigationBarTitleText": "添加银行卡"
}
},
{
"path": "home-service/index",
"style": {
"navigationBarTitleText": "居家上门服务",
"navigationStyle": "custom"
}
},
{
"path": "home-service/apply",
"style": {
"navigationBarTitleText": "提交服务申请",
"navigationStyle": "custom"
}
},
{
"path": "home-service/service-detail",
"style": {
"navigationBarTitleText": "预约服务",
"navigationStyle": "custom"
}
},
{
"path": "home-service/order-detail",
"style": {
"navigationBarTitleText": "服务单详情",
"navigationStyle": "custom"
}
},
{
"path": "home-service/feedback",
"style": {
"navigationBarTitleText": "验收反馈",
"navigationStyle": "custom"
}
},
{
"path": "bank-cards/verify",
"style": {
"navigationBarTitleText": "银行卡验证"
}
},
{
"path": "balance/index",
"style": {
"navigationBarTitleText": "余额"
}
},
{
"path": "my-reviews",
"style": {
"navigationBarTitleText": "我的评价"
}
},
{
"path": "message-detail",
"style": {
"navigationBarTitleText": "消息详情"
}
},
{
"path": "member/index",
"style": {
"navigationBarTitleText": "会员中心"
}
},
{
"path": "product-reviews",
"style": {
"navigationBarTitleText": "商品评价"
}
}
]
}
],
"tabBar": {
"color": "#999999",
"selectedColor": "#ff5000",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/main/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/main/messages",
"text": "消息",
"iconPath": "static/tabbar/message.png",
"selectedIconPath": "static/tabbar/message.png"
},
{
"pagePath": "pages/main/cart",
"text": "购物车",
"iconPath": "static/tabbar/cart.png",
"selectedIconPath": "static/tabbar/cart.png"
},
{
"pagePath": "pages/main/profile",
"text": "我的",
"iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user.png"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "mall",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F8F8F8"
},
"condition": {
"current": 0,
"list": [
{
"name": "consumer端",
"path": "pages/main/index",
"query": "role=consumer"
}
]
}
}

View File

@@ -0,0 +1,461 @@
{
"pages": [
{
"path": "pages/main/index",
"style": {
"navigationBarTitleText": "首页",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
},
{
"path": "pages/user/boot",
"style": {
"navigationBarTitleText": ""
}
},
{
"path": "pages/user/login",
"style": {
"navigationBarTitleText": "用户登录",
"navigationStyle": "custom"
}
},
{
"path": "pages/user/register",
"style": {
"navigationBarTitleText": "注册"
}
},
{
"path": "pages/user/forgot-password",
"style": {
"navigationBarTitleText": "忘记密码"
}
},
{
"path": "pages/user/terms",
"style": {
"navigationBarTitleText": "用户协议与隐私政策"
}
},
{
"path": "pages/user/center",
"style": {
"navigationBarTitleText": "用户中心"
}
},
{
"path": "pages/user/profile",
"style": {
"navigationBarTitleText": "个人资料"
}
},
{
"path": "pages/user/change-password",
"style": {
"navigationBarTitleText": "修改密码"
}
},
{
"path": "pages/user/bind-phone",
"style": {
"navigationBarTitleText": "绑定手机"
}
},
{
"path": "pages/user/bind-email",
"style": {
"navigationBarTitleText": "绑定邮箱"
}
},
{
"path": "pages/main/messages",
"style": {
"navigationBarTitleText": "消息",
"enablePullDownRefresh": true
}
},
{
"path": "pages/main/cart",
"style": {
"navigationBarTitleText": "购物车",
"navigationStyle": "custom"
}
},
{
"path": "pages/main/profile",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom"
}
},
{
"path": "pages/main/category",
"style": {
"navigationBarTitleText": "分类",
"navigationStyle": "custom"
}
}
],
"subPackages": [
{
"root": "pages/mall/consumer",
"pages": [
{
"path": "settings",
"style": {
"navigationBarTitleText": "设置"
}
},
{
"path": "edit-profile",
"style": {
"navigationBarTitleText": "编辑资料"
}
},
{
"path": "wallet",
"style": {
"navigationBarTitleText": "我的钱包"
}
},
{
"path": "withdraw",
"style": {
"navigationBarTitleText": "余额提现"
}
},
{
"path": "search",
"style": {
"navigationBarTitleText": "搜索",
"navigationStyle": "custom"
}
},
{
"path": "product-detail",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
}
},
{
"path": "channel-detail",
"style": {
"navigationBarTitleText": "频道详情",
"navigationStyle": "custom"
}
},
{
"path": "shop-detail",
"style": {
"navigationBarTitleText": "店铺详情"
}
},
{
"path": "coupons",
"style": {
"navigationBarTitleText": "我的优惠券"
}
},
{
"path": "favorites",
"style": {
"navigationBarTitleText": "我的收藏"
}
},
{
"path": "footprint",
"style": {
"navigationBarTitleText": "我的足迹"
}
},
{
"path": "address",
"style": {
"navigationBarTitleText": "地址"
}
},
{
"path": "address-list",
"style": {
"navigationBarTitleText": "收货地址"
}
},
{
"path": "address-edit",
"style": {
"navigationBarTitleText": "编辑地址"
}
},
{
"path": "checkout",
"style": {
"navigationBarTitleText": "确认订单"
}
},
{
"path": "payment",
"style": {
"navigationBarTitleText": "收银台"
}
},
{
"path": "payment-success",
"style": {
"navigationBarTitleText": "支付成功",
"navigationStyle": "custom"
}
},
{
"path": "orders",
"style": {
"navigationBarTitleText": "我的订单",
"enablePullDownRefresh": true
}
},
{
"path": "order-detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "logistics",
"style": {
"navigationBarTitleText": "物流详情"
}
},
{
"path": "review",
"style": {
"navigationBarTitleText": "评价晒单"
}
},
{
"path": "refund",
"style": {
"navigationBarTitleText": "退款/售后"
}
},
{
"path": "apply-refund",
"style": {
"navigationBarTitleText": "申请售后"
}
},
{
"path": "refund-review",
"style": {
"navigationBarTitleText": "服务评价"
}
},
{
"path": "chat",
"style": {
"navigationBarTitleText": "客服聊天",
"navigationStyle": "custom"
}
},
{
"path": "chat_new",
"style": {
"navigationBarTitleText": "客服聊天(新版)"
}
},
{
"path": "subscription/plan-list",
"style": {
"navigationBarTitleText": "软件订阅"
}
},
{
"path": "subscription/plan-detail",
"style": {
"navigationBarTitleText": "订阅详情"
}
},
{
"path": "subscription/subscribe-checkout",
"style": {
"navigationBarTitleText": "确认订阅"
}
},
{
"path": "subscription/my-subscriptions",
"style": {
"navigationBarTitleText": "我的订阅"
}
},
{
"path": "subscription/followed-shops",
"style": {
"navigationBarTitleText": "关注店铺"
}
},
{
"path": "points/index",
"style": {
"navigationBarTitleText": "积分管理"
}
},
{
"path": "points/signin",
"style": {
"navigationBarTitleText": "签到"
}
},
{
"path": "points/exchange",
"style": {
"navigationBarTitleText": "积分兑换"
}
},
{
"path": "points/exchange-records",
"style": {
"navigationBarTitleText": "兑换记录"
}
},
{
"path": "red-packets/index",
"style": {
"navigationBarTitleText": "我的红包"
}
},
{
"path": "bank-cards/index",
"style": {
"navigationBarTitleText": "银行卡管理"
}
},
{
"path": "bank-cards/add",
"style": {
"navigationBarTitleText": "添加银行卡"
}
},
{
"path": "home-service/index",
"style": {
"navigationBarTitleText": "居家上门服务",
"navigationStyle": "custom"
}
},
{
"path": "home-service/apply",
"style": {
"navigationBarTitleText": "提交服务申请",
"navigationStyle": "custom"
}
},
{
"path": "home-service/service-detail",
"style": {
"navigationBarTitleText": "预约服务",
"navigationStyle": "custom"
}
},
{
"path": "home-service/order-detail",
"style": {
"navigationBarTitleText": "服务单详情",
"navigationStyle": "custom"
}
},
{
"path": "home-service/feedback",
"style": {
"navigationBarTitleText": "验收反馈",
"navigationStyle": "custom"
}
},
{
"path": "bank-cards/verify",
"style": {
"navigationBarTitleText": "银行卡验证"
}
},
{
"path": "balance/index",
"style": {
"navigationBarTitleText": "余额"
}
},
{
"path": "my-reviews",
"style": {
"navigationBarTitleText": "我的评价"
}
},
{
"path": "message-detail",
"style": {
"navigationBarTitleText": "消息详情"
}
},
{
"path": "member/index",
"style": {
"navigationBarTitleText": "会员中心"
}
},
{
"path": "product-reviews",
"style": {
"navigationBarTitleText": "商品评价"
}
}
]
}
],
"tabBar": {
"color": "#999999",
"selectedColor": "#ff5000",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/main/index",
"text": "首页",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png"
},
{
"pagePath": "pages/main/messages",
"text": "消息",
"iconPath": "static/tabbar/message.png",
"selectedIconPath": "static/tabbar/message.png"
},
{
"pagePath": "pages/main/cart",
"text": "购物车",
"iconPath": "static/tabbar/cart.png",
"selectedIconPath": "static/tabbar/cart.png"
},
{
"pagePath": "pages/main/profile",
"text": "我的",
"iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user.png"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "mall",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F8F8F8"
},
"condition": {
"current": 0,
"list": [
{
"name": "consumer端",
"path": "pages/main/index",
"query": "role=consumer"
}
]
}
}

View File

@@ -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;

View File

@@ -72,9 +72,118 @@
@select-product="navigateToProduct"
></HomeMallContent>
<HomeServiceContent
v-else
></HomeServiceContent>
<view v-else class="service-home-section">
<view class="service-hero-banner">
<view class="service-hero-content">
<text class="service-hero-tag">康养到家</text>
<text class="service-hero-title">专业康养服务到家</text>
<text class="service-hero-subtitle">护理照护|康复指导|陪诊陪护</text>
<view class="service-hero-tags">
<text class="service-hero-chip">平台认证</text>
<text class="service-hero-chip">上门服务</text>
<text class="service-hero-chip">服务可追溯</text>
</view>
</view>
<view class="service-hero-visual-wrap">
<view class="service-hero-visual service-hero-visual-primary">
<text class="service-hero-visual-text">护</text>
</view>
<view class="service-hero-visual service-hero-visual-secondary">
<text class="service-hero-visual-subtext">康</text>
</view>
</view>
</view>
<view class="service-category-card">
<view
v-for="(item, index) in serviceCategories"
:key="buildListItemKey('service-category', item.id, index)"
class="service-category-item"
@tap="handleServiceCategoryClick(item)"
>
<view
:class="['service-category-icon', selectedServiceCategory == item.id ? 'service-category-icon-active' : '']"
:style="{ backgroundColor: item.color }"
>
<text class="service-category-icon-text">{{ item.iconText }}</text>
</view>
<text :class="['service-category-name', selectedServiceCategory == item.id ? 'service-category-name-active' : '']">{{ item.name }}</text>
</view>
</view>
<view class="service-shortcut-row">
<view
v-for="(item, index) in serviceShortcuts"
:key="buildListItemKey('service-shortcut', item.id, index)"
class="service-shortcut-card"
@tap="handleServiceShortcut(item.id)"
>
<text class="service-shortcut-icon">{{ item.iconText }}</text>
<view class="service-shortcut-body">
<text class="service-shortcut-title">{{ item.title }}</text>
<text class="service-shortcut-desc">{{ item.desc }}</text>
</view>
</view>
</view>
<view class="service-products-section">
<view class="service-section-title-row">
<view>
<text class="service-section-title">推荐服务</text>
<text class="service-section-subtitle">{{ serviceSelectedCategoryLabel }}</text>
</view>
<text class="service-section-more" @tap="goServiceHall">更多</text>
</view>
<view v-if="serviceLoading" class="service-state-card">
<text class="service-state-title">正在加载服务...</text>
<text class="service-state-desc">请稍候,正在整理适合居家康养的服务内容。</text>
</view>
<view v-else-if="serviceProducts.length > 0" class="service-products-grid">
<view
v-for="(item, index) in serviceProducts"
:key="buildListItemKey('service-product', item.id, index)"
class="service-product-card"
@tap="goServiceDetail(item)"
>
<view class="service-product-cover" :style="{ background: item.coverGradient }">
<text class="service-product-cover-badge">到家服务</text>
<text class="service-product-cover-text">{{ item.imageText }}</text>
</view>
<view class="service-product-body">
<text class="service-product-title">{{ item.title }}</text>
<text class="service-product-subtitle">{{ item.subtitle }}</text>
<view class="service-product-tags">
<text
v-for="(tag, tagIndex) in item.tags"
:key="buildListItemKey(item.id + '-tag', tag, tagIndex)"
class="service-product-tag"
>
{{ tag }}
</text>
</view>
<view class="service-product-price-row">
<text class="service-product-price-symbol">¥</text>
<text class="service-product-price">{{ item.price }}</text>
<text class="service-product-unit">起 / {{ item.unit }}</text>
</view>
<text class="service-product-sales">{{ item.salesText }}</text>
<view class="service-product-action-row">
<view class="service-product-secondary-btn" @tap.stop="showServicePreview(item)">查看详情</view>
<view class="service-product-primary-btn" @tap.stop="goServiceDetail(item)">立即预约</view>
</view>
</view>
</view>
</view>
<view v-else class="service-state-card">
<text class="service-state-title">该分类服务正在完善</text>
<text class="service-state-desc">可先查看全部服务,或进入服务大厅了解更多上门服务。</text>
<view class="service-state-action" @tap="goServiceHall">进入服务大厅</view>
</view>
</view>
</view>
<view class="safe-area" :style="{ height: bottomSafeArea + 88 + 'px' }"></view>
</scroll-view>
@@ -115,8 +224,9 @@
import { ref, reactive, onMounted, onUnmounted, computed } from 'vue'
import { onShow, onLoad, onHide } from '@dcloudio/uni-app'
import HomeMallContent from '@/components/home/HomeMallContent.uvue'
import HomeServiceContent from '@/components/home/HomeServiceContent.uvue'
import JdLikeHomeHeader from '@/components/home/JdLikeHomeHeader.uvue'
import { fetchHomeServiceCatalog } from '@/services/homeServiceService.uts'
import type { HomeServiceCatalogType } from '@/types/home-service.uts'
import supabaseService from '@/utils/supabaseService.uts'
import type { Product, Category, Brand, PaginatedResponse } from '@/utils/supabaseService.uts'
import { getRecommendMarketingChannels } from '@/utils/mockChannelData.uts'
@@ -178,6 +288,314 @@ const headerSearchPlaceholder = computed((): string => {
return currentPlaceholderKeyword.value != '' ? currentPlaceholderKeyword.value : '感冒药 / 康复护理 / 居家护理 / 血压计'
})
type HomeCareCategoryType = {
id: string
name: string
iconText: string
color: string
}
type HomeCareServiceProductType = {
id: string
title: string
subtitle: string
categoryId: string
price: number
unit: string
tags: Array<string>
salesText: string
imageText: string
coverGradient: string
detailPath: string
bookingPath: string
}
type ServiceShortcutType = {
id: string
title: string
desc: string
iconText: string
}
const serviceCategories: Array<HomeCareCategoryType> = [
{ id: 'basic_care', name: '基础照护', iconText: '护', color: '#EAFBF7' },
{ id: 'rehab', name: '康复指导', iconText: '康', color: '#EFF6FF' },
{ id: 'escort', name: '陪诊服务', iconText: '陪', color: '#FFF7ED' },
{ id: 'nursing', name: '上门护理', iconText: '医', color: '#F0FDFA' },
{ id: 'chronic', name: '慢病随访', iconText: '访', color: '#F5F3FF' },
{ id: 'assessment', name: '健康评估', iconText: '评', color: '#ECFEFF' },
{ id: 'elderly', name: '适老改造', iconText: '老', color: '#FEF3C7' },
{ id: 'cleaning', name: '居家保洁', iconText: '洁', color: '#F1F5F9' },
{ id: 'medicine', name: '用药提醒', iconText: '药', color: '#FCE7F3' },
{ id: 'all', name: '全部服务', iconText: '全', color: '#F3F4F6' }
]
const serviceShortcuts: Array<ServiceShortcutType> = [
{ id: 'guarantee', title: '服务保障', desc: '认证机构与过程留痕', iconText: '保' },
{ id: 'hall', title: '服务大厅', desc: '查看全部居家服务', iconText: '厅' },
{ id: 'tracking', title: '服务单跟踪', desc: '预约进度随时可查', iconText: '单' }
]
const serviceLoading = ref(false)
const selectedServiceCategory = ref('all')
const allServiceProducts = ref<Array<HomeCareServiceProductType>>([])
const serviceProducts = computed((): Array<HomeCareServiceProductType> => {
if (selectedServiceCategory.value == 'all') {
return allServiceProducts.value
}
const result: Array<HomeCareServiceProductType> = []
for (let i = 0; i < allServiceProducts.value.length; i++) {
const item = allServiceProducts.value[i]
if (item.categoryId == selectedServiceCategory.value) {
result.push(item)
}
}
return result
})
const serviceSelectedCategoryLabel = computed((): string => {
for (let i = 0; i < serviceCategories.length; i++) {
if (serviceCategories[i].id == selectedServiceCategory.value) {
return serviceCategories[i].name
}
}
return '全部服务'
})
function getServiceGradient(categoryId: string): string {
if (categoryId == 'basic_care') {
return 'linear-gradient(135deg, #e0f7f5 0%, #f0fdfb 100%)'
}
if (categoryId == 'rehab') {
return 'linear-gradient(135deg, #e8f1ff 0%, #f5f9ff 100%)'
}
if (categoryId == 'escort') {
return 'linear-gradient(135deg, #fff1e4 0%, #fffaf5 100%)'
}
if (categoryId == 'nursing') {
return 'linear-gradient(135deg, #def7f3 0%, #eefcf9 100%)'
}
if (categoryId == 'chronic') {
return 'linear-gradient(135deg, #efe9ff 0%, #f8f5ff 100%)'
}
if (categoryId == 'assessment') {
return 'linear-gradient(135deg, #ebfbff 0%, #f6fdff 100%)'
}
return 'linear-gradient(135deg, #eef4f8 0%, #f7fafc 100%)'
}
function mapServiceCatalogCategory(category: string): string {
if (category == '日常照护') {
return 'basic_care'
}
if (category == '康复支持') {
return 'rehab'
}
if (category == '健康管理') {
return 'chronic'
}
return 'all'
}
function buildServiceSalesText(serviceId: string): string {
if (serviceId == 'svc-001') {
return '已服务230+'
}
if (serviceId == 'svc-002') {
return '已服务180+'
}
if (serviceId == 'svc-003') {
return '已服务150+'
}
return '已服务99+'
}
function buildServiceImageText(categoryId: string): string {
if (categoryId == 'basic_care') {
return '护'
}
if (categoryId == 'rehab') {
return '康'
}
if (categoryId == 'escort') {
return '陪'
}
if (categoryId == 'nursing') {
return '医'
}
if (categoryId == 'chronic') {
return '访'
}
if (categoryId == 'assessment') {
return '评'
}
return '服'
}
function buildMockServiceProducts(): Array<HomeCareServiceProductType> {
// TODO: 后续替换为服务首页专用接口,当前仅在真实服务目录为空时兜底。
return [
{
id: 'svc-001',
title: '基础上门照护',
subtitle: '协助起居、日常陪护、健康观察',
categoryId: 'basic_care',
price: 99,
unit: '次',
tags: ['平台认证', '可预约'],
salesText: '已服务230+',
imageText: '护',
coverGradient: getServiceGradient('basic_care'),
detailPath: '/pages/mall/consumer/home-service/service-detail?id=svc-001',
bookingPath: '/pages/mall/consumer/home-service/service-detail?id=svc-001&mode=booking'
},
{
id: 'svc-002',
title: '居家康复指导',
subtitle: '术后恢复、动作训练、康复评估',
categoryId: 'rehab',
price: 129,
unit: '次',
tags: ['康复指导', '上门服务'],
salesText: '已服务180+',
imageText: '康',
coverGradient: getServiceGradient('rehab'),
detailPath: '/pages/mall/consumer/home-service/service-detail?id=svc-002',
bookingPath: '/pages/mall/consumer/home-service/service-detail?id=svc-002&mode=booking'
},
{
id: 'svc-mock-escort',
title: '陪诊陪护服务',
subtitle: '挂号陪同、检查陪同、取药协助',
categoryId: 'escort',
price: 168,
unit: '次',
tags: ['陪诊服务', '安心陪护'],
salesText: '已服务320+',
imageText: '陪',
coverGradient: getServiceGradient('escort'),
detailPath: '',
bookingPath: ''
},
{
id: 'svc-003',
title: '慢病随访服务',
subtitle: '血压血糖记录、健康建议、定期回访',
categoryId: 'chronic',
price: 79,
unit: '次',
tags: ['慢病管理', '健康随访'],
salesText: '已服务150+',
imageText: '访',
coverGradient: getServiceGradient('chronic'),
detailPath: '/pages/mall/consumer/home-service/service-detail?id=svc-003',
bookingPath: '/pages/mall/consumer/home-service/service-detail?id=svc-003&mode=booking'
}
]
}
function buildServiceProductsFromCatalog(catalog: Array<HomeServiceCatalogType>): Array<HomeCareServiceProductType> {
const result: Array<HomeCareServiceProductType> = []
for (let i = 0; i < catalog.length; i++) {
const item = catalog[i]
const categoryId = mapServiceCatalogCategory(item.category)
result.push({
id: item.id,
title: item.name,
subtitle: item.summary,
categoryId,
price: item.price,
unit: '次',
tags: item.tags.length > 0 ? item.tags.slice(0, 2) : ['平台认证', '可预约'],
salesText: buildServiceSalesText(item.id),
imageText: buildServiceImageText(categoryId),
coverGradient: getServiceGradient(categoryId),
detailPath: '/pages/mall/consumer/home-service/service-detail?id=' + encodeURIComponent(item.id),
bookingPath: '/pages/mall/consumer/home-service/service-detail?id=' + encodeURIComponent(item.id) + '&mode=booking'
} as HomeCareServiceProductType)
}
return result
}
async function loadServiceHomeData(): Promise<void> {
serviceLoading.value = true
try {
const catalog = await fetchHomeServiceCatalog()
if (catalog.length > 0) {
allServiceProducts.value = buildServiceProductsFromCatalog(catalog)
} else {
allServiceProducts.value = buildMockServiceProducts()
}
} catch (error) {
console.error('加载服务首页数据失败', error)
allServiceProducts.value = buildMockServiceProducts()
} finally {
serviceLoading.value = false
}
}
function handleServiceCategoryClick(item: HomeCareCategoryType): void {
selectedServiceCategory.value = item.id
if (item.id == 'all') {
return
}
let hasMatched = false
for (let i = 0; i < allServiceProducts.value.length; i++) {
if (allServiceProducts.value[i].categoryId == item.id) {
hasMatched = true
break
}
}
if (!hasMatched) {
uni.showToast({
title: '该类服务正在完善',
icon: 'none'
})
}
}
function handleServiceShortcut(shortcutId: string): void {
if (shortcutId == 'hall') {
goServiceHall()
return
}
if (shortcutId == 'tracking') {
uni.navigateTo({ url: '/pages/mall/consumer/home-service/index' })
return
}
uni.showToast({
title: '服务保障体系建设中',
icon: 'none'
})
}
function showServicePreview(item: HomeCareServiceProductType): void {
if (item.detailPath != '') {
uni.navigateTo({ url: item.detailPath })
return
}
uni.showToast({
title: '服务详情建设中',
icon: 'none'
})
}
function goServiceDetail(item: HomeCareServiceProductType): void {
if (item.bookingPath != '') {
uni.navigateTo({ url: item.bookingPath })
return
}
uni.showToast({
title: '服务详情建设中',
icon: 'none'
})
}
function goServiceHall(): void {
uni.navigateTo({ url: '/pages/mall/consumer/home-service/index' })
}
// 分类标签栏相关
type CategoryItem = {
id: string
@@ -343,6 +761,9 @@ function handleHeaderSearch(keyword: string) {
function handleTopModuleChange(moduleKey: string) {
activeTopModule.value = moduleKey
showCategoryPanel.value = false
if (moduleKey == 'service' && allServiceProducts.value.length == 0 && !serviceLoading.value) {
void loadServiceHomeData()
}
}
function handleMainScrollToLower() {
@@ -483,9 +904,22 @@ function buildSimpleCategoryChannels(categoryId: string): SimpleCategoryChannel[
return []
}
function buildVisibleRecommendChannels(): MarketingChannel[] {
const source = getRecommendMarketingChannels()
const visible: MarketingChannel[] = []
for (let i = 0; i < source.length; i++) {
const channel = source[i]
if (channel.title == '排行榜' || channel.title == '品质优选') {
continue
}
visible.push(channel)
}
return visible
}
function applyChannelDisplay(categoryId: string): void {
if (categoryId === 'recommend') {
marketingChannels.value = getRecommendMarketingChannels()
marketingChannels.value = buildVisibleRecommendChannels()
categorySimpleChannels.value = []
return
}
@@ -1161,6 +1595,7 @@ const initData = async () => {
await loadCategories()
await loadBrands()
await loadHotKeywords()
await loadServiceHomeData()
if (await consumeSelectedCategoryFromStorage()) {
await loadRecommendedProducts(defaultLoadLimit)
return
@@ -1687,6 +2122,462 @@ const navigateToReminders = () => uni.navigateTo({ url: '/pages/user/reminders'
width: 100%;
}
.service-home-section {
background-color: #f4f8fb;
padding: 16rpx 16rpx 32rpx;
box-sizing: border-box;
min-height: 100%;
}
.service-hero-banner {
min-height: 240rpx;
border-radius: 28rpx;
padding: 28rpx;
background: linear-gradient(135deg, #e0f7f5 0%, #eff6ff 55%, #fff7ed 100%);
flex-direction: row;
align-items: center;
justify-content: space-between;
overflow: hidden;
margin-bottom: 18rpx;
box-sizing: border-box;
}
.service-hero-content {
flex: 1;
flex-direction: column;
padding-right: 12rpx;
box-sizing: border-box;
}
.service-hero-tag {
width: 128rpx;
height: 38rpx;
line-height: 38rpx;
text-align: center;
border-radius: 999rpx;
background-color: #ffffff;
color: #0f766e;
font-size: 22rpx;
font-weight: 700;
margin-bottom: 12rpx;
}
.service-hero-title {
font-size: 40rpx;
font-weight: 700;
color: #16324f;
margin-bottom: 10rpx;
line-height: 1.3;
}
.service-hero-subtitle {
font-size: 24rpx;
color: #64748b;
margin-bottom: 16rpx;
line-height: 34rpx;
}
.service-hero-tags {
flex-direction: row;
flex-wrap: wrap;
align-items: center;
margin-right: -8rpx;
margin-bottom: -8rpx;
}
.service-hero-chip {
font-size: 22rpx;
color: #0f766e;
background-color: rgba(255, 255, 255, 0.86);
border-radius: 999rpx;
padding: 6rpx 14rpx;
margin-right: 8rpx;
margin-bottom: 8rpx;
box-sizing: border-box;
}
.service-hero-visual-wrap {
width: 156rpx;
height: 156rpx;
position: relative;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.service-hero-visual {
position: absolute;
align-items: center;
justify-content: center;
border-radius: 999rpx;
background-color: rgba(255, 255, 255, 0.72);
box-shadow: 0 12rpx 24rpx rgba(14, 165, 164, 0.12);
}
.service-hero-visual-primary {
width: 128rpx;
height: 128rpx;
right: 0;
top: 0;
}
.service-hero-visual-secondary {
width: 74rpx;
height: 74rpx;
left: 0;
bottom: 6rpx;
background-color: rgba(15, 118, 110, 0.12);
box-shadow: none;
}
.service-hero-visual-text {
font-size: 56rpx;
font-weight: 700;
color: #0f766e;
}
.service-hero-visual-subtext {
font-size: 30rpx;
font-weight: 700;
color: #0f766e;
}
.service-category-card {
background-color: #ffffff;
border-radius: 28rpx;
padding: 22rpx 12rpx 10rpx;
flex-direction: row;
flex-wrap: wrap;
margin-bottom: 18rpx;
box-shadow: 0 10rpx 24rpx rgba(15, 23, 42, 0.04);
box-sizing: border-box;
}
.service-category-item {
width: 20%;
align-items: center;
margin-bottom: 20rpx;
box-sizing: border-box;
}
.service-category-icon {
width: 76rpx;
height: 76rpx;
border-radius: 24rpx;
align-items: center;
justify-content: center;
margin-bottom: 10rpx;
border-width: 2rpx;
border-style: solid;
border-color: transparent;
box-sizing: border-box;
}
.service-category-icon-active {
border-color: #14b8a6;
box-shadow: 0 8rpx 18rpx rgba(20, 184, 166, 0.14);
}
.service-category-icon-text {
font-size: 30rpx;
font-weight: 700;
color: #0f766e;
}
.service-category-name {
font-size: 23rpx;
color: #334155;
text-align: center;
line-height: 30rpx;
padding: 0 4rpx;
box-sizing: border-box;
}
.service-category-name-active {
color: #0f766e;
font-weight: 700;
}
.service-shortcut-row {
flex-direction: row;
justify-content: space-between;
margin-bottom: 18rpx;
}
.service-shortcut-card {
width: 32%;
background-color: #ffffff;
border-radius: 22rpx;
padding: 18rpx 16rpx;
flex-direction: row;
align-items: center;
box-sizing: border-box;
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.04);
min-height: 116rpx;
}
.service-shortcut-icon {
width: 52rpx;
height: 52rpx;
line-height: 52rpx;
text-align: center;
border-radius: 18rpx;
background-color: #e8f7f6;
font-size: 24rpx;
font-weight: 700;
color: #0f766e;
flex-shrink: 0;
}
.service-shortcut-body {
flex: 1;
margin-left: 12rpx;
min-width: 0;
flex-direction: column;
}
.service-shortcut-title {
font-size: 24rpx;
font-weight: 700;
color: #16324f;
line-height: 1.3;
}
.service-shortcut-desc {
margin-top: 6rpx;
font-size: 20rpx;
color: #64748b;
line-height: 28rpx;
}
.service-products-section {
background-color: #f4f8fb;
}
.service-section-title-row {
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 14rpx;
}
.service-section-title {
font-size: 32rpx;
font-weight: 700;
color: #16324f;
line-height: 1.3;
}
.service-section-subtitle {
margin-top: 6rpx;
font-size: 22rpx;
color: #94a3b8;
line-height: 30rpx;
}
.service-section-more {
font-size: 24rpx;
color: #94a3b8;
padding: 8rpx 0 8rpx 12rpx;
}
.service-products-grid {
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.service-product-card {
width: 49%;
background-color: #ffffff;
border-radius: 22rpx;
overflow: hidden;
margin-bottom: 14rpx;
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.05);
box-sizing: border-box;
}
.service-product-cover {
height: 220rpx;
align-items: center;
justify-content: center;
position: relative;
padding-top: 28rpx;
box-sizing: border-box;
}
.service-product-cover-badge {
position: absolute;
top: 14rpx;
left: 14rpx;
font-size: 20rpx;
color: #0f766e;
background-color: rgba(255, 255, 255, 0.82);
border-radius: 999rpx;
padding: 4rpx 10rpx;
box-sizing: border-box;
}
.service-product-cover-text {
font-size: 64rpx;
font-weight: 700;
color: #0f766e;
}
.service-product-body {
padding: 18rpx;
flex-direction: column;
box-sizing: border-box;
}
.service-product-title {
font-size: 28rpx;
font-weight: 700;
color: #1f2937;
line-height: 36rpx;
min-height: 72rpx;
max-height: 72rpx;
overflow: hidden;
}
.service-product-subtitle {
margin-top: 8rpx;
font-size: 22rpx;
color: #64748b;
line-height: 32rpx;
min-height: 64rpx;
max-height: 64rpx;
overflow: hidden;
}
.service-product-tags {
flex-direction: row;
flex-wrap: wrap;
margin-top: 10rpx;
margin-right: -6rpx;
margin-bottom: -6rpx;
}
.service-product-tag {
font-size: 20rpx;
color: #0f766e;
background-color: #e0f2f1;
border-radius: 8rpx;
padding: 4rpx 8rpx;
margin-right: 6rpx;
margin-bottom: 6rpx;
box-sizing: border-box;
}
.service-product-price-row {
margin-top: 12rpx;
flex-direction: row;
align-items: flex-end;
}
.service-product-price-symbol {
font-size: 22rpx;
color: #e1251b;
font-weight: 700;
line-height: 1;
margin-bottom: 6rpx;
}
.service-product-price {
font-size: 36rpx;
color: #e1251b;
font-weight: 700;
line-height: 1;
}
.service-product-unit {
font-size: 22rpx;
color: #64748b;
margin-left: 4rpx;
margin-bottom: 4rpx;
line-height: 1;
}
.service-product-sales {
margin-top: 6rpx;
font-size: 21rpx;
color: #94a3b8;
line-height: 30rpx;
}
.service-product-action-row {
margin-top: 14rpx;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.service-product-secondary-btn,
.service-product-primary-btn {
height: 58rpx;
border-radius: 999rpx;
align-items: center;
justify-content: center;
font-size: 22rpx;
font-weight: 700;
padding: 0 16rpx;
box-sizing: border-box;
}
.service-product-secondary-btn {
width: 47%;
border-width: 1rpx;
border-style: solid;
border-color: #cbd5e1;
color: #476072;
background-color: #ffffff;
}
.service-product-primary-btn {
width: 49%;
background-color: #16a085;
color: #ffffff;
}
.service-state-card {
background-color: #ffffff;
border-radius: 24rpx;
padding: 28rpx 24rpx;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.04);
box-sizing: border-box;
}
.service-state-title {
font-size: 28rpx;
font-weight: 700;
color: #16324f;
line-height: 1.3;
}
.service-state-desc {
margin-top: 10rpx;
font-size: 22rpx;
color: #64748b;
line-height: 32rpx;
text-align: center;
}
.service-state-action {
margin-top: 18rpx;
height: 68rpx;
line-height: 68rpx;
padding: 0 28rpx;
border-radius: 999rpx;
background-color: #16a085;
color: #ffffff;
font-size: 24rpx;
font-weight: 700;
text-align: center;
box-sizing: border-box;
}
.pdd-home-header {
position: fixed;
top: 0;

File diff suppressed because it is too large Load Diff