修改样式

This commit is contained in:
2026-06-05 16:13:54 +08:00
parent f2cde43bf4
commit 068b09d272
7 changed files with 815 additions and 417 deletions
+2
View File
@@ -232,6 +232,8 @@
"featured-on": "Featured on CHEFLINK",
"featured-dishes": "Featured Dishes",
"nearby-merchants": "Nearby Merchants",
"open-member": "Become a Member",
"recharge-now": "Recharge Now",
"quickTabs": {
"memberZone": "Member Zone",
"liveSeafoodAir": "Limited Live Seafood",
+2
View File
@@ -232,6 +232,8 @@
"featured-on": "精选商家",
"featured-dishes": "精选菜品",
"nearby-merchants": "附近商家",
"open-member": "开通会员",
"recharge-now": "立即充值",
"quickTabs": {
"memberZone": "会员专区",
"liveSeafoodAir": "限量空运活海鲜",
+2 -2
View File
@@ -2,8 +2,8 @@
"name" : "CHEFLINK delivery",
"appid" : "__UNI__06509BE",
"description" : "",
"versionName" : "3.1.9",
"versionCode" : 319,
"versionName" : "3.2.0",
"versionCode" : 320,
"transformPx" : false,
/* 5+App */
"app-plus" : {
@@ -0,0 +1,166 @@
<template>
<view class="store-main-sk">
<view class="sk-banner skeleton-item"></view>
<view class="sk-notice skeleton-item"></view>
<view class="sk-info-block">
<view class="sk-store-name skeleton-item"></view>
<view class="flex items-center gap-16rpx mt-12rpx">
<view class="sk-rating skeleton-item"></view>
<view class="sk-sales skeleton-item"></view>
</view>
<view class="flex items-center gap-16rpx mt-16rpx">
<view class="sk-coupon skeleton-item"></view>
<view class="sk-coupon skeleton-item"></view>
<view class="sk-coupon skeleton-item"></view>
</view>
</view>
<view class="flex items-center px-24rpx pb-24rpx gap-16rpx">
<view v-for="i in 4" :key="i" class="sk-tab skeleton-item"></view>
</view>
<view class="sk-dish-grid px-24rpx">
<view v-for="i in 4" :key="i" class="sk-dish-card">
<view class="sk-dish-img skeleton-item"></view>
<view class="sk-dish-body">
<view class="flex items-center justify-between mb-10rpx">
<view class="sk-price skeleton-item"></view>
<view class="sk-dish-sales skeleton-item"></view>
</view>
<view class="sk-title skeleton-item mb-12rpx"></view>
<view class="sk-title sk-title--short skeleton-item mb-14rpx"></view>
<view class="flex items-center justify-between">
<view class="sk-member skeleton-item"></view>
<view class="sk-add skeleton-item"></view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
</script>
<style scoped lang="scss">
.skeleton-item {
background: linear-gradient(90deg, #ebebeb 25%, #d6d6d6 50%, #ebebeb 75%);
background-size: 200% 100%;
animation: sk-shimmer 1.4s ease-in-out infinite;
}
@keyframes sk-shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
.store-main-sk {
width: 100%;
min-height: 100%;
background: #f6f6f6;
}
.sk-banner {
width: 100%;
height: 360rpx;
}
.sk-notice {
width: 100%;
height: 66rpx;
}
.sk-info-block {
background: #fff;
padding: 20rpx 24rpx 16rpx;
}
.sk-store-name {
width: 60%;
height: 36rpx;
border-radius: 8rpx;
}
.sk-rating {
width: 100rpx;
height: 24rpx;
border-radius: 6rpx;
}
.sk-sales {
width: 120rpx;
height: 24rpx;
border-radius: 6rpx;
}
.sk-coupon {
width: 100rpx;
height: 48rpx;
border-radius: 8rpx;
flex-shrink: 0;
}
.sk-tab {
width: 96rpx;
height: 56rpx;
border-radius: 28rpx;
flex-shrink: 0;
}
.sk-dish-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
}
.sk-dish-card {
background: #fff;
border-radius: 20rpx;
overflow: hidden;
}
.sk-dish-img {
width: 100%;
height: 270rpx;
}
.sk-dish-body {
padding: 14rpx 14rpx 16rpx;
}
.sk-price {
width: 90rpx;
height: 28rpx;
border-radius: 6rpx;
}
.sk-dish-sales {
width: 60rpx;
height: 22rpx;
border-radius: 6rpx;
}
.sk-title {
width: 90%;
height: 26rpx;
border-radius: 6rpx;
}
.sk-title--short {
width: 60%;
}
.sk-member {
width: 120rpx;
height: 36rpx;
border-radius: 20rpx;
}
.sk-add {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
}
</style>
@@ -1,227 +1,127 @@
<template>
<view class="store-skeleton">
<!-- 顶部导航栏 -->
<view class="fixed top-0 left-0 z-9 w-full pt-6rpx">
<view class="store-sk-layout">
<view class="store-sk-sidebar">
<status-bar />
<view class="nav-bar">
<view class="nav-btn skeleton-item"></view>
<view class="nav-btn skeleton-item"></view>
<view class="sk-back skeleton-item"></view>
<view class="sk-head">
<view class="sk-head-icon skeleton-item"></view>
<view class="sk-head-text skeleton-item"></view>
</view>
</view>
<!-- 占位 -->
<status-bar />
<view class="h-88rpx"></view>
<!-- 店铺 Logo -->
<view class="flex justify-center pt-20rpx">
<view class="logo-skeleton skeleton-item"></view>
</view>
<!-- 店铺信息区域 -->
<view class="px-30rpx pt-24rpx pb-30rpx">
<view class="flex flex-col items-center">
<!-- 店铺名称 -->
<view class="name-skeleton skeleton-item mb-16rpx"></view>
<!-- 评分 + CHEFLINK -->
<view class="flex items-center mb-12rpx">
<view class="rating-skeleton skeleton-item mr-16rpx"></view>
<view class="cheflink-skeleton skeleton-item"></view>
</view>
<!-- 总销量 -->
<view class="sales-text-skeleton skeleton-item"></view>
</view>
<!-- 配送信息卡片 -->
<view class="delivery-card-skeleton skeleton-item mt-30rpx"></view>
<!-- 优惠券标签行占位 -->
<view class="flex items-center mt-24rpx">
<view class="coupon-tag-skeleton skeleton-item mr-16rpx"></view>
<view class="coupon-tag-skeleton skeleton-item mr-16rpx"></view>
<view class="coupon-tag-skeleton skeleton-item"></view>
<view class="flex-1"></view>
<view class="claim-skeleton skeleton-item"></view>
</view>
</view>
<!-- 分类胶囊标签 -->
<view class="flex items-center px-30rpx pb-24rpx">
<view
v-for="i in 4"
:key="i"
class="tab-chip-skeleton skeleton-item"
></view>
</view>
<!-- 商品列表 -->
<view class="px-30rpx">
<view class="grid grid-cols-2 gap-24rpx">
<view
v-for="i in 4"
:key="i"
class="product-skeleton"
class="sk-merchant-item"
:class="{ 'sk-merchant-item--active': i === 1 }"
>
<!-- 商品图片 -->
<view class="product-img-skeleton skeleton-item"></view>
<!-- 价格 + 销量 -->
<view class="flex items-center justify-between mt-16rpx">
<view class="price-skeleton skeleton-item"></view>
<view class="sales-skeleton skeleton-item"></view>
</view>
<!-- 商品名称 -->
<view class="product-name-skeleton skeleton-item mt-8rpx"></view>
<!-- 会员价 + 加购按钮 -->
<view class="flex items-center justify-between mt-12rpx">
<view class="member-skeleton skeleton-item"></view>
<view class="add-btn-skeleton skeleton-item"></view>
</view>
<view class="sk-merchant-logo skeleton-item"></view>
<view class="sk-merchant-name skeleton-item"></view>
</view>
</view>
<store-main-skeleton />
</view>
</view>
</template>
<script setup lang="ts">
import StoreMainSkeleton from './store-main-skeleton.vue'
</script>
<style scoped lang="scss">
.skeleton-item {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background: linear-gradient(90deg, #ebebeb 25%, #d6d6d6 50%, #ebebeb 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
animation: sk-shimmer 1.4s ease-in-out infinite;
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.store-skeleton {
background-color: #fff;
min-height: 100vh;
}
/* 顶部导航 */
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 30rpx;
height: 88rpx;
}
.nav-btn {
width: 48rpx;
height: 48rpx;
border-radius: 24rpx;
}
/* 店铺 Logo */
.logo-skeleton {
width: 128rpx;
height: 128rpx;
border-radius: 24rpx;
}
/* 店铺信息 */
.name-skeleton {
width: 360rpx;
height: 44rpx;
border-radius: 8rpx;
}
.rating-skeleton {
width: 160rpx;
height: 24rpx;
border-radius: 6rpx;
}
.cheflink-skeleton {
width: 120rpx;
height: 24rpx;
border-radius: 6rpx;
}
.sales-text-skeleton {
width: 140rpx;
height: 24rpx;
border-radius: 6rpx;
}
/* 配送信息卡片 */
.delivery-card-skeleton {
width: 100%;
height: 140rpx;
border-radius: 20rpx;
}
/* 优惠券标签 */
.coupon-tag-skeleton {
width: 120rpx;
height: 52rpx;
border-radius: 8rpx;
flex-shrink: 0;
}
.claim-skeleton {
width: 100rpx;
height: 28rpx;
border-radius: 6rpx;
flex-shrink: 0;
}
/* 胶囊标签 */
.tab-chip-skeleton {
width: 100rpx;
height: 60rpx;
border-radius: 30rpx;
margin-right: 20rpx;
flex-shrink: 0;
&:last-child {
margin-right: 0;
@keyframes sk-shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
/* 商品卡片 */
.product-skeleton {
margin-bottom: 24rpx;
}
.product-img-skeleton {
.store-skeleton {
width: 100%;
height: 248rpx;
min-height: 100vh;
background: #f6f6f6;
overflow: hidden;
}
.store-sk-layout {
display: flex;
width: 100%;
height: 100vh;
}
.store-sk-sidebar {
width: 146rpx;
flex-shrink: 0;
height: 100%;
background: #ececec;
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 20rpx;
overflow: hidden;
}
.sk-back {
width: 48rpx;
height: 48rpx;
border-radius: 24rpx;
margin: 12rpx 0 8rpx;
}
.price-skeleton {
width: 120rpx;
height: 36rpx;
.sk-head {
display: flex;
flex-direction: column;
align-items: center;
gap: 10rpx;
padding: 8rpx 0 16rpx;
}
.sk-head-icon {
width: 26rpx;
height: 26rpx;
border-radius: 6rpx;
}
.sales-skeleton {
.sk-head-text {
width: 80rpx;
height: 22rpx;
height: 20rpx;
border-radius: 6rpx;
}
.product-name-skeleton {
width: 85%;
height: 34rpx;
.sk-merchant-item {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding: 16rpx 10rpx;
gap: 10rpx;
}
.sk-merchant-item--active {
background: #fff;
}
.sk-merchant-logo {
width: 68rpx;
height: 68rpx;
border-radius: 16rpx;
}
.sk-merchant-name {
width: 72rpx;
height: 20rpx;
border-radius: 6rpx;
}
.member-skeleton {
width: 160rpx;
height: 42rpx;
border-radius: 8rpx;
}
.add-btn-skeleton {
width: 52rpx;
height: 52rpx;
border-radius: 26rpx;
:deep(.store-main-sk) {
flex: 1;
min-width: 0;
}
</style>
+453 -175
View File
@@ -3,33 +3,129 @@ import { debounce } from 'throttle-debounce'
import dayjs from 'dayjs'
import Config from '@/config/index'
const { t, locale } = useI18n();
import StoreSkeleton from './components/store-skeleton.vue';
import { useScrollThreshold } from '@/hooks/useScrollThreshold'
import StoreSkeleton from './components/store-skeleton.vue'
import StoreMainSkeleton from './components/store-main-skeleton.vue'
import {
appCollectCollectPost, appMerchantCartCalculateSavingsPost, appMerchantCartListByMerchantIdPost,
appCollectCollectPost,
appMerchantCartCalculateSavingsPost,
appMerchantCartListByMerchantIdPost,
appMerchantCartListMerchantPost,
appMerchantDetailMerchantIdGet,
appMerchantDishDishIdGet,
appMerchantMenuMenuListByUserPost, type MerchantCartVo,
type MerchantVo
} from "@/service";
import {CollectionType} from "@/constant/enums";
import {useUserStore} from "@/store";
import { parseBusinessHoursUtils, getDistanceInMiles, parseMerchantCartPayload } from "@/utils/utils";
appMerchantMenuMenuListByUserPost,
type MerchantCartVo,
type MerchantVo,
} from '@/service'
import { CollectionType } from '@/constant/enums'
import { useConfigStore, useUserStore } from '@/store'
import { formatSalesCount, getDistanceInMiles, parseMerchantCartPayload } from '@/utils/utils'
import { formatDeliveryScheduleDays } from "@/utils/deliverySchedule";
import CouponPopup from './components/coupon-popup.vue'
import {getMerchantCouponReceiveListApi} from "@/pages-user/service";
// import type { MerchantVo } from '@/service/types'
// 页面加载状态
const loading = ref(true);
const userStore = useUserStore();
const loading = ref(true)
/** 切换店铺时右侧内容区骨架屏 */
const mainLoading = ref(false)
const userStore = useUserStore()
const configStore = useConfigStore()
const storeID = ref('')
onLoad((options: any)=> {
console.log(options)
if(options.id) {
storeID.value = options.id
const merchantList = ref<MerchantVo[]>([])
const activeMerchantId = ref<string | number>('')
const storeShareTopStyle = computed(() => ({
top: `${configStore.statusBarHeight + uni.upx2px(16)}px`,
}))
const storeBannerSrc = computed(() => {
const raw = storeDetail.value?.shopImages
if (typeof raw === 'string' && raw.trim()) {
return raw.split(',')[0].trim()
}
const logo = storeDetail.value?.logo
return typeof logo === 'string' ? logo : ''
})
const deliveryNoticeTexts = computed(() => {
const texts: string[] = []
if (+storeDetail.value?.deliveryService === 1 && deliveryMethod.value === 0) {
texts.push(
`${t('pages-store.store.tips4')} $ ${storeDetail.value?.minOrderPrice} ${t('pages-store.store.tips5')} $ ${storeDetail.value?.deliveryFee}${t('pages-store.store.start')}`,
)
const timeLine = `${storeDetail.value?.deliveryTime}${daySuffix(storeDetail.value?.deliveryTime)} · ${t('pages-store.store.earTime')}`
texts.push(deliveryScheduleDaysText.value || timeLine)
} else if (+storeDetail.value?.selfPickup === 1 && deliveryMethod.value === 1) {
if (cartDataList.value.length > 0 && cartSavingsData.value && (cartSavingsData.value as { savings?: number }).savings > 0) {
texts.push(
`${t('pages-store.store.use')} ${Config.appName} ${t('pages-store.store.tips3')} $${(cartSavingsData.value as { savings?: number }).savings} ${t('pages-store.store.discount')}`,
)
}
texts.push(
`${storeDetail.value?.pickupTime}${t('common.minutes')} · ${t('pages-store.store.earTime')}`,
)
}
return texts.filter(Boolean)
})
async function loadMerchantList() {
try {
const res = await appMerchantCartListMerchantPost({})
merchantList.value = Array.isArray(res?.data) ? res.data : []
} catch {
merchantList.value = []
}
}
function ensureMerchantInSidebarList() {
const id = String(storeID.value || '')
if (!id || !storeDetail.value?.id) return
const exists = merchantList.value.some((m) => String(m.id) === id)
if (!exists) {
merchantList.value = [
{
id: storeDetail.value.id,
logo: storeDetail.value.logo,
merchantName: storeDetail.value.merchantName,
} as MerchantVo,
...merchantList.value,
]
}
}
function onSidebarChange(payload: { value: string | number }) {
switchMerchant(String(payload.value))
}
function switchMerchant(merchantId: string) {
if (!merchantId || merchantId === storeID.value) return
stopClosingTimer()
storeID.value = merchantId
activeMerchantId.value = merchantId
activeTab.value = 0
tabs.value = []
dishListByQuery.value = []
storeCouponList.value = []
hasMore.value = true
pageNum.value = 1
mainLoading.value = true
getStoreDetail(true)
getCartInfo()
}
onLoad(async (options: any) => {
await loadMerchantList()
if (options?.id) {
storeID.value = String(options.id)
activeMerchantId.value = storeID.value
} else if (merchantList.value.length > 0) {
storeID.value = String(merchantList.value[0].id)
activeMerchantId.value = storeID.value
}
if (storeID.value) {
getStoreDetail()
// getMenuList()
} else {
loading.value = false
}
})
@@ -137,13 +233,17 @@ function parseBusinessHours(businessHours: string) {
}
const storeDistance = ref(null)
function getStoreDetail() {
function getStoreDetail(silent = false) {
if (!silent) {
loading.value = true
} else {
mainLoading.value = true
}
appMerchantDetailMerchantIdGet({
params: {
merchantId: storeID.value,
}
}).then((res: any) => {
}).then(async (res: any) => {
console.log('商家详情', res)
storeDetail.value = res.data as MerchantVo
@@ -184,7 +284,10 @@ function getStoreDetail() {
dishListByQuery.value = []
hasMore.value = true
pageNum.value = 1
nextTick(() => loadDishList(false))
await loadDishList(false)
} else {
tabs.value = [{ title: t('pages.store.all'), key: '' }]
dishListByQuery.value = []
}
// 商户的经纬度存在,并且用户的经纬度也存在
@@ -215,11 +318,13 @@ function getStoreDetail() {
showDeliverySwitch.value = true
}
ensureMerchantInSidebarList()
activeMerchantId.value = storeID.value
}).catch((error) => {
console.error('获取商家详情失败:', error);
}).finally(()=> {
}).finally(() => {
loading.value = false
mainLoading.value = false
})
}
@@ -406,17 +511,11 @@ watch(activeTab, () => {
loadDishList(false);
});
// 触底加载更多
onReachBottom(() => {
function onMainScrollToLower() {
if (hasMore.value && !isLoadingMore.value) {
loadDishList(true);
loadDishList(true)
}
});
const showStatusBar = useScrollThreshold()
onPageScroll((e) => {
uni.$emit('page-scroll', e)
})
}
function navigateToDishes(item: any) {
uni.navigateTo({
@@ -499,109 +598,110 @@ function handleShare() {
<StoreSkeleton/>
</view>
<view
class="animate-in fade-in animate-ease-in animate-duration-300"
class="animate-in fade-in animate-ease-in animate-duration-300 store-page"
v-if="!loading"
>
<view class="store-box">
<!-- 顶部导航栏 -->
<view class="fixed top-0 left-0 z-9 w-full transition-all pt-6rpx" :class="[showStatusBar ? 'bg-#fff' : '']">
<view class="store-layout">
<!-- 左侧返回 + Sidebar 切换店铺 -->
<view class="store-sidebar">
<status-bar />
<view class="flex-center-sb px-30rpx h-88rpx">
<view class="store-sidebar__back" @click="navigateBack">
<image
@click="navigateBack"
src="@img/chef/1327.png"
mode="aspectFill"
class="w-48rpx h-48rpx relative z-1"
class="store-sidebar__back-icon"
/>
</view>
<view class="store-sidebar__head">
<image src="@img-store/1339.png" class="store-sidebar__head-icon" mode="aspectFit" />
<text class="store-sidebar__head-text">{{ t('pages.home.featured-on') }}</text>
</view>
<scroll-view scroll-y class="store-sidebar__scroll" :show-scrollbar="false">
<wd-sidebar
v-model="activeMerchantId"
custom-class="store-wd-sidebar"
@change="onSidebarChange"
>
<wd-sidebar-item
v-for="merchant in merchantList"
:key="String(merchant.id)"
:value="merchant.id"
:label="merchant.merchantName || ''"
custom-class="store-sidebar-item"
>
<template #icon>
<view class="store-sidebar-item__inner">
<image
:src="merchant.logo"
class="store-sidebar-item__logo"
mode="aspectFill"
/>
<text class="store-sidebar-item__name">{{ merchant.merchantName }}</text>
</view>
</template>
</wd-sidebar-item>
</wd-sidebar>
</scroll-view>
</view>
<!-- 右侧分享绝对定位+ 海报 + 配送通知 + 分类 + 商品 -->
<view class="store-main-wrap">
<image
v-if="!mainLoading"
@click="handleShare"
src="@img-store/1335.png"
mode="aspectFill"
class="w-48rpx h-48rpx relative z-1"
class="store-share-btn"
:style="storeShareTopStyle"
/>
<view v-if="mainLoading" class="store-main store-main--loading">
<store-main-skeleton />
</view>
</view>
<!-- 占位 -->
<status-bar />
<view class="h-44rpx"></view>
<!-- 店铺 Logo -->
<view class="flex justify-center ">
<scroll-view
v-else
class="store-main"
scroll-y
:style="{ height: '100%' }"
:show-scrollbar="false"
@scrolltolower="onMainScrollToLower"
>
<view class="store-main__inner">
<view class="store-banner">
<image
:src="storeDetail?.shopImages?.split(',')[0]"
v-if="storeBannerSrc"
:src="storeBannerSrc"
class="store-banner__img"
mode="aspectFill"
class="w-128rpx h-128rpx rounded-24rpx bg-common"
/>
<view v-else class="store-banner__img store-banner__img--empty" />
</view>
<view
v-if="showDeliverySwitch && deliveryNoticeTexts.length"
class="store-delivery-notice"
>
<wd-notice-bar
:text="deliveryNoticeTexts"
direction="vertical"
:delay="2"
:speed="50"
color="#CE7138"
background-color="#FFF7ED"
custom-class="store-delivery-notice__bar"
/>
</view>
<!-- 店铺信息区域 -->
<view class="px-30rpx pt-24rpx pb-30rpx">
<view class="text-center">
<!-- 店铺名称 -->
<view class="text-36rpx lh-44rpx text-#333 font-bold">
{{ storeDetail?.merchantName }}
</view>
<!-- 评分 + CHEFLINK -->
<view class="center text-24rpx lh-24rpx mt-16rpx">
<view class="flex items-center">
<text class="text-#333 font-500">{{ storeDetail?.rating }}</text>
<image
src="@img/chef/124.png"
class="w-24rpx h-24rpx mx-4rpx"
></image>
<text class="text-#7D7D7D">({{ storeDetail?.commentCount }})</text>
</view>
<view class="flex items-center text-#CE7138 px-10rpx">
<image
src="@img-store/1339.png"
class="w-24rpx h-24rpx mr-4rpx"
></image>
CHEFLINK
</view>
</view>
<!-- 总销量 -->
<view class="text-24rpx lh-24rpx text-#7D7D7D text-center mt-12rpx">
<!-- <view class="store-info-block"> -->
<!-- <view class="store-info-block__title">{{ storeDetail?.merchantName }}</view>
<view class="store-info-block__meta">
<text class="store-info-block__rating">{{ storeDetail?.rating }}</text>
<text class="store-info-block__comment">({{ storeDetail?.commentCount }})</text>
<text class="store-info-block__sales">
{{ t('common.sales') }}{{ storeDetail?.totalOrderCount }}
</view>
</view>
</text>
</view> -->
<!-- 配送信息卡片 -->
<view v-if="showDeliverySwitch" class="delivery-info-card">
<template v-if="+storeDetail?.deliveryService === 1 && deliveryMethod === 0">
<view class="delivery-info-left">
<view>{{ t('pages-store.store.tips4') }} $ {{ storeDetail?.minOrderPrice }}</view>
<view>{{ t('pages-store.store.tips5') }} $ {{ storeDetail?.deliveryFee }}{{ t('pages-store.store.start') }}</view>
</view>
<view class="delivery-info-divider"></view>
<view class="delivery-info-right">
<view class="text-32rpx lh-40rpx text-#333 font-500">
{{ storeDetail?.deliveryTime }}{{ daySuffix(storeDetail?.deliveryTime) }}
</view>
<view class="text-24rpx lh-24rpx text-#7D7D7D mt-8rpx">{{ t('pages-store.store.earTime') }}</view>
<view
v-if="deliveryScheduleDaysText"
class="text-24rpx lh-32rpx text-#00A76D mt-8rpx"
>{{ deliveryScheduleDaysText }}</view>
</view>
</template>
<template v-if="+storeDetail?.selfPickup === 1 && deliveryMethod === 1">
<view class="delivery-info-left">
<view v-if="cartDataList.length > 0 && cartSavingsData && cartSavingsData.savings > 0">
{{ t('pages-store.store.use') }} {{ Config.appName }} {{ t('pages-store.store.tips3') }} ${{ cartSavingsData.savings }} {{ t('pages-store.store.discount') }}
</view>
<view v-else>--</view>
</view>
<view class="delivery-info-divider"></view>
<view class="delivery-info-right">
<view class="text-32rpx lh-40rpx text-#333 font-500">{{ storeDetail?.pickupTime }}{{ t('common.minutes') }}</view>
<view class="text-24rpx lh-24rpx text-#7D7D7D mt-8rpx">{{ t('pages-store.store.earTime') }}</view>
</view>
</template>
</view>
<!-- 优惠券标签行 -->
<view class="mt-24rpx flex items-center" v-if="storeCouponList.length">
<view v-if="storeCouponList.length" class="store-coupon-row">
<scroll-view
scroll-x
class="coupon-scroll flex-1"
@@ -625,11 +725,10 @@ function handleShare() {
<i class="i-carbon:chevron-right text-24rpx text-#CE7138"></i>
</view>
</view>
</view>
<!-- </view> -->
<!-- 分类标签胶囊样式 -->
<scroll-view scroll-x class="w-full" :show-scrollbar="false" enable-flex>
<view class="flex items-center px-30rpx pb-24rpx">
<view class="flex items-center px-24rpx pb-24rpx">
<view
v-for="(item, index) in tabs"
:key="item.key"
@@ -642,8 +741,7 @@ function handleShare() {
</view>
</scroll-view>
<!-- 商品列表 -->
<view v-if="tabs.length > 0" class="px-30rpx pb-180rpx">
<view v-if="tabs.length > 0" class="px-24rpx pb-180rpx">
<view v-if="currentDishList.length > 0" class="grid grid-cols-2 gap-24rpx items-start">
<template v-for="item in currentDishList" :key="item.id">
<view @click="navigateToDishes(item)" class="dish-card" :class="{ 'dish-card--soldout': isSoldOutStock(item?.stock) }">
@@ -682,15 +780,15 @@ function handleShare() {
<!-- 卡片信息区 -->
<view class="dish-card-body">
<!-- 价格 + 销量 -->
<view class="flex items-start justify-between gap-12rpx mb-14rpx">
<view class="flex items-center justify-between gap-12rpx mb-14rpx">
<view class="min-w-0 flex-1">
<text class="dish-price">$ {{ item.discountPrice }}</text>
<text
<text class="dish-price">${{ item.discountPrice }}</text>
<!-- <text
v-if="Number(item?.originalPrice) > Number(item?.discountPrice)"
class="dish-original-price"
>$ {{ item.originalPrice }}</text>
>$ {{ item.originalPrice }}</text> -->
</view>
<text class="dish-sales shrink-0">{{ t('pages-store.store.sales') }}{{ item.salesCount }}</text>
<view class="dish-sales shrink-0">{{ t('pages-store.store.sales') }}{{ formatSalesCount(item.salesCount) }}</view>
</view>
<!-- 商品名称 -->
<view class="dish-title line-clamp-2 mb-16rpx">
@@ -708,7 +806,7 @@ function handleShare() {
<view class="dish-add-btn center shrink-0">
<image
src="@img/chef/1285.png"
class="w-28rpx h-28rpx"
class="w-20rpx h-20rpx"
></image>
</view>
</view>
@@ -729,10 +827,14 @@ function handleShare() {
</view>
</template>
</view>
</view>
</scroll-view>
</view>
</view>
<!-- 底部购物车浮窗 -->
<view
v-if="userStore.isLogin && cartDataList.length > 0"
v-if="!mainLoading && userStore.isLogin && cartDataList.length > 0"
class="store-cart-float"
@click="navigateToCart"
>
@@ -749,7 +851,7 @@ function handleShare() {
<!-- 会员省钱提示条 -->
<view
@click="navigateTo('/pages-user/pages/member/index')"
v-if="cartDataList.length > 0 && cartSavingsData && cartSavingsData.savings > 0"
v-if="!mainLoading && cartDataList.length > 0 && cartSavingsData && cartSavingsData.savings > 0"
class="store-savings-bar"
>
<image
@@ -761,7 +863,6 @@ function handleShare() {
</text>
</view>
</view>
</view>
<coupon-popup
ref="couponPopupRef"
@@ -777,40 +878,217 @@ page {
</style>
<style scoped lang="scss">
/* ====== 配送信息卡片 ====== */
.delivery-info-card {
.store-page {
height: 100vh;
overflow: hidden;
background: #f6f6f6;
position: relative;
}
.store-layout {
display: flex;
width: 100%;
height: 100vh;
}
.store-sidebar {
width: 146rpx;
flex-shrink: 0;
height: 100%;
background: #ececec;
display: flex;
flex-direction: column;
}
.store-sidebar__back {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 30rpx;
padding: 24rpx 40rpx;
border: 2rpx solid #D8D8D8;
border-radius: 20rpx;
min-height: 140rpx;
justify-content: center;
padding: 12rpx 0 8rpx;
}
.delivery-info-left {
flex: 1;
text-align: center;
font-size: 24rpx;
line-height: 36rpx;
color: #CE7138;
padding-right: 30rpx;
.store-sidebar__back-icon {
width: 48rpx;
height: 48rpx;
}
.delivery-info-divider {
width: 2rpx;
height: 100rpx;
background: #D8D8D8;
.store-sidebar__head {
flex-shrink: 0;
}
.delivery-info-right {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding-left: 30rpx;
justify-content: center;
gap: 8rpx;
padding: 8rpx 12rpx 16rpx;
}
.store-sidebar__head-icon {
width: 26rpx;
height: 26rpx;
}
.store-sidebar__head-text {
font-size: 22rpx;
line-height: 28rpx;
color: #333;
font-weight: 600;
text-align: center;
}
.store-sidebar__scroll {
flex: 1;
height: 0;
}
:deep(.store-wd-sidebar) {
width: 100% !important;
height: auto !important;
background: transparent !important;
}
:deep(.store-sidebar-item) {
flex-direction: column !important;
align-items: center !important;
justify-content: flex-start !important;
padding: 16rpx 10rpx !important;
min-height: 168rpx !important;
background: transparent !important;
}
:deep(.store-sidebar-item.wd-sidebar-item--active) {
background: #fff !important;
}
:deep(.store-sidebar-item .wd-sidebar-item__icon) {
margin-right: 0 !important;
}
:deep(.store-sidebar-item .wd-badge) {
display: none;
}
.store-sidebar-item__inner {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.store-sidebar-item__logo {
width: 68rpx;
height: 68rpx;
border-radius: 16rpx;
background: #f0f0f0;
}
.store-sidebar-item__name {
margin-top: 10rpx;
width: 100%;
font-size: 20rpx;
line-height: 26rpx;
color: #333;
text-align: center;
word-break: break-word;
}
.store-main-wrap {
position: relative;
flex: 1;
min-width: 0;
height: 100%;
}
.store-share-btn {
position: absolute;
right: 24rpx;
width: 48rpx;
height: 48rpx;
z-index: 10;
}
.store-main {
flex: 1;
min-width: 0;
height: 100%;
background: #f6f6f6;
}
.store-main--loading {
overflow: hidden;
}
.store-main__inner {
min-height: 100%;
}
.store-banner {
position: relative;
width: 100%;
height: 360rpx;
background: #e8e8e8;
}
.store-banner__img {
width: 100%;
height: 100%;
display: block;
}
.store-banner__img--empty {
background: #e8e8e8;
}
.store-delivery-notice {
background: #fff7ed;
border-bottom: 1rpx solid #ffe8cc;
margin-bottom: 16rpx;
}
:deep(.store-delivery-notice__bar) {
padding: 16rpx 24rpx !important;
font-size: 24rpx !important;
line-height: 34rpx !important;
border-radius: 0 !important;
}
:deep(.store-delivery-notice__bar .wd-notice-bar__wrap) {
height: 34rpx;
line-height: 34rpx;
}
.store-info-block {
padding: 20rpx 24rpx 8rpx;
background: #fff;
}
.store-info-block__title {
font-size: 32rpx;
line-height: 40rpx;
font-weight: 700;
color: #1a1a1a;
}
.store-info-block__meta {
margin-top: 10rpx;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8rpx;
font-size: 22rpx;
line-height: 28rpx;
color: #7d7d7d;
}
.store-info-block__rating {
color: #333;
font-weight: 600;
}
.store-coupon-row {
margin-top: 20rpx;
display: flex;
align-items: center;
}
/* ====== 优惠券标签 ====== */
@@ -895,7 +1173,7 @@ page {
.dish-card-image {
position: relative;
width: 100%;
height: 330rpx;
height: 270rpx;
background: #f0f0f0;
overflow: hidden;
}
@@ -908,18 +1186,18 @@ page {
}
.dish-card-body {
padding: 20rpx 20rpx 22rpx;
padding: 14rpx 14rpx 16rpx;
}
.dish-price {
color: #e02e24;
font-size: 32rpx;
font-size: 24rpx;
font-weight: 600;
line-height: 1.2;
line-height: 1.3;
}
.dish-original-price {
margin-left: 10rpx;
margin-left: 6rpx;
color: #b3b3b3;
font-size: 22rpx;
font-weight: 400;
@@ -929,49 +1207,49 @@ page {
.dish-sales {
color: #999;
font-size: 24rpx;
line-height: 1.35;
max-width: 48%;
font-size: 22rpx;
line-height: 1.3;
// max-width: 48%;
text-align: right;
}
.dish-title {
color: #1a1a1a;
font-size: 28rpx;
font-size: 24rpx;
font-weight: 500;
line-height: 1.45;
line-height: 1.35;
}
.dish-member {
display: inline-flex;
align-items: center;
max-width: calc(100% - 88rpx);
max-width: calc(100% - 80rpx);
}
.dish-member-inner {
display: inline-block;
padding: 6rpx 18rpx;
padding: 2rpx 12rpx;
border-radius: 999rpx;
background: linear-gradient(180deg, #fff5eb 0%, #ffe8d6 100%);
color: #c45c1a;
font-size: 24rpx;
font-size: 22rpx;
font-weight: 500;
line-height: 1.35;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dish-add-btn {
width: 56rpx;
height: 56rpx;
width: 48rpx;
height: 48rpx;
border-radius: 50%;
background: #14181b;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.12);
}
.dish-promo {
padding: 12rpx 16rpx;
padding: 10rpx 14rpx;
border-radius: 12rpx;
background: #fff0f0;
}
@@ -980,7 +1258,7 @@ page {
color: #e02e24;
font-size: 22rpx;
font-weight: 500;
line-height: 1.4;
line-height: 1.35;
}
/* NEW 绑带标签 */
@@ -1000,11 +1278,11 @@ page {
left: -66rpx;
width: 240rpx;
text-align: center;
font-size: 22rpx;
font-size: 20rpx;
font-weight: 700;
color: #fff;
letter-spacing: 2rpx;
line-height: 44rpx;
line-height: 40rpx;
background: #E23636;
transform: rotate(-45deg);
}
@@ -1028,12 +1306,12 @@ page {
z-index: 3;
left: 16rpx;
top: 16rpx;
padding: 0 14rpx;
height: 48rpx;
border-radius: 24rpx;
padding: 0 12rpx;
height: 44rpx;
border-radius: 22rpx;
background: rgba(20, 24, 27, 0.75);
color: #fff;
font-size: 24rpx;
font-size: 22rpx;
font-weight: 500;
display: flex;
align-items: center;
@@ -44,8 +44,8 @@ const { t, locale } = useI18n();
/** 首页运营图:按语言切换(中文 / 英文) */
const HOME_PROMO_BANNERS = {
memberUpgrade: {
zh: 'https://www.howhowfresh.com/minio/ruoyi/2026/06/03/0b9a7f865aba442e84e51034a3ec900c.png',
en: 'https://www.howhowfresh.com/minio/ruoyi/2026/06/03/c5b9df3a922d4d17ae1b6eb8c1d7524a.png',
zh: 'https://www.howhowfresh.com/minio/ruoyi/2026/06/05/d4a0d40503ac4206a0387af1ab869de6.png',
en: 'https://www.howhowfresh.com/minio/ruoyi/2026/06/05/c01f1664626d417a9a7ca6165a20f06f.png',
},
deliveryTime: {
zh: 'https://www.howhowfresh.com/minio/ruoyi/2026/06/03/c5673a8874594755bdde7ed7fcbd1982.jpg',
@@ -407,19 +407,30 @@ const debouncedEmit = debounce(1300, (isCollected: boolean, id: string, type: Co
@change-type="tabsTypeChange"
/>
<image
:src="memberUpgradeBannerSrc"
class="w-100% h-[340rpx]"
mode="widthFix"
<view
class="home-member-banner"
:style="{ backgroundImage: `url(${memberUpgradeBannerSrc})` }"
>
<view class="home-member-banner__actions">
<view
class="home-member-banner__btn"
@click="navigateTo('/pages-user/pages/member/index')"
/>
>
<text class="home-member-banner__btn-text">{{ t('pages.home.open-member') }}</text>
</view>
<view
class="home-member-banner__btn"
@click="navigateTo('/pages-user/pages/balance/index')"
>
<text class="home-member-banner__btn-text">{{ t('pages.home.recharge-now') }}</text>
</view>
</view>
</view>
<image
:src="deliveryTimeBannerSrc"
class="w-100% h-[200rpx] rounded-24rpx mt-4rpx"
mode="widthFix"
/>
<!-- 精选商家和附近商家 -->
<view class="animate-in fade-in animate-ease-in animate-duration-300" v-if="isShowMerchant">
<!-- Featured on ChefLink 精选商家浅底 + 横向卡片对齐设计稿 -->
@@ -558,6 +569,45 @@ const debouncedEmit = debounce(1300, (isCollected: boolean, id: string, type: Co
padding-right: 8rpx;
}
.home-member-banner {
position: relative;
width: 100%;
height: 550rpx;
background-size: 100% 100%;
background-position: center;
background-repeat: no-repeat;
}
.home-member-banner__actions {
position: absolute;
left: 0;
right: 0;
bottom: 5rpx;
padding: 0 40rpx;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24rpx;
}
.home-member-banner__btn {
flex: 1;
height: 72rpx;
padding: 0 24rpx;
border-radius: 999rpx;
background: #21ae39;
display: flex;
align-items: center;
justify-content: center;
}
.home-member-banner__btn-text {
font-size: 28rpx;
font-weight: 600;
color: #ffffff;
line-height: 1.2;
}
.nearby-merchants-block {
background: #f2f2f2;
}