修改样式

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-on": "Featured on CHEFLINK",
"featured-dishes": "Featured Dishes", "featured-dishes": "Featured Dishes",
"nearby-merchants": "Nearby Merchants", "nearby-merchants": "Nearby Merchants",
"open-member": "Become a Member",
"recharge-now": "Recharge Now",
"quickTabs": { "quickTabs": {
"memberZone": "Member Zone", "memberZone": "Member Zone",
"liveSeafoodAir": "Limited Live Seafood", "liveSeafoodAir": "Limited Live Seafood",
+2
View File
@@ -232,6 +232,8 @@
"featured-on": "精选商家", "featured-on": "精选商家",
"featured-dishes": "精选菜品", "featured-dishes": "精选菜品",
"nearby-merchants": "附近商家", "nearby-merchants": "附近商家",
"open-member": "开通会员",
"recharge-now": "立即充值",
"quickTabs": { "quickTabs": {
"memberZone": "会员专区", "memberZone": "会员专区",
"liveSeafoodAir": "限量空运活海鲜", "liveSeafoodAir": "限量空运活海鲜",
+2 -2
View File
@@ -2,8 +2,8 @@
"name" : "CHEFLINK delivery", "name" : "CHEFLINK delivery",
"appid" : "__UNI__06509BE", "appid" : "__UNI__06509BE",
"description" : "", "description" : "",
"versionName" : "3.1.9", "versionName" : "3.2.0",
"versionCode" : 319, "versionCode" : 320,
"transformPx" : false, "transformPx" : false,
/* 5+App */ /* 5+App */
"app-plus" : { "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> <template>
<view class="store-skeleton"> <view class="store-skeleton">
<!-- 顶部导航栏 --> <view class="store-sk-layout">
<view class="fixed top-0 left-0 z-9 w-full pt-6rpx"> <view class="store-sk-sidebar">
<status-bar /> <status-bar />
<view class="nav-bar"> <view class="sk-back skeleton-item"></view>
<view class="nav-btn skeleton-item"></view> <view class="sk-head">
<view class="nav-btn skeleton-item"></view> <view class="sk-head-icon skeleton-item"></view>
</view> <view class="sk-head-text skeleton-item"></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>
<!-- 总销量 -->
<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 <view
v-for="i in 4" v-for="i in 4"
:key="i" :key="i"
class="product-skeleton" class="sk-merchant-item"
:class="{ 'sk-merchant-item--active': i === 1 }"
> >
<!-- 商品图片 --> <view class="sk-merchant-logo skeleton-item"></view>
<view class="product-img-skeleton skeleton-item"></view> <view class="sk-merchant-name 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> </view>
</view> </view>
<store-main-skeleton />
</view> </view>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import StoreMainSkeleton from './store-main-skeleton.vue'
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.skeleton-item { .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%; background-size: 200% 100%;
animation: shimmer 1.5s infinite; animation: sk-shimmer 1.4s ease-in-out infinite;
} }
@keyframes shimmer { @keyframes sk-shimmer {
0% { background-position: -200% 0; } 0% {
100% { background-position: 200% 0; } background-position: -200% 0;
} }
100% {
.store-skeleton { background-position: 200% 0;
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;
} }
} }
/* 商品卡片 */ .store-skeleton {
.product-skeleton {
margin-bottom: 24rpx;
}
.product-img-skeleton {
width: 100%; 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; border-radius: 24rpx;
margin: 12rpx 0 8rpx;
} }
.price-skeleton { .sk-head {
width: 120rpx; display: flex;
height: 36rpx; flex-direction: column;
align-items: center;
gap: 10rpx;
padding: 8rpx 0 16rpx;
}
.sk-head-icon {
width: 26rpx;
height: 26rpx;
border-radius: 6rpx; border-radius: 6rpx;
} }
.sales-skeleton { .sk-head-text {
width: 80rpx; width: 80rpx;
height: 22rpx; height: 20rpx;
border-radius: 6rpx; border-radius: 6rpx;
} }
.product-name-skeleton { .sk-merchant-item {
width: 85%; width: 100%;
height: 34rpx; 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; border-radius: 6rpx;
} }
.member-skeleton { :deep(.store-main-sk) {
width: 160rpx; flex: 1;
height: 42rpx; min-width: 0;
border-radius: 8rpx;
}
.add-btn-skeleton {
width: 52rpx;
height: 52rpx;
border-radius: 26rpx;
} }
</style> </style>
+496 -218
View File
@@ -3,33 +3,129 @@ import { debounce } from 'throttle-debounce'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import Config from '@/config/index' import Config from '@/config/index'
const { t, locale } = useI18n(); const { t, locale } = useI18n();
import StoreSkeleton from './components/store-skeleton.vue'; import StoreSkeleton from './components/store-skeleton.vue'
import { useScrollThreshold } from '@/hooks/useScrollThreshold' import StoreMainSkeleton from './components/store-main-skeleton.vue'
import { import {
appCollectCollectPost, appMerchantCartCalculateSavingsPost, appMerchantCartListByMerchantIdPost, appCollectCollectPost,
appMerchantCartCalculateSavingsPost,
appMerchantCartListByMerchantIdPost,
appMerchantCartListMerchantPost,
appMerchantDetailMerchantIdGet, appMerchantDetailMerchantIdGet,
appMerchantDishDishIdGet, appMerchantDishDishIdGet,
appMerchantMenuMenuListByUserPost, type MerchantCartVo, appMerchantMenuMenuListByUserPost,
type MerchantVo type MerchantCartVo,
} from "@/service"; type MerchantVo,
import {CollectionType} from "@/constant/enums"; } from '@/service'
import {useUserStore} from "@/store"; import { CollectionType } from '@/constant/enums'
import { parseBusinessHoursUtils, getDistanceInMiles, parseMerchantCartPayload } from "@/utils/utils"; import { useConfigStore, useUserStore } from '@/store'
import { formatSalesCount, getDistanceInMiles, parseMerchantCartPayload } from '@/utils/utils'
import { formatDeliveryScheduleDays } from "@/utils/deliverySchedule"; import { formatDeliveryScheduleDays } from "@/utils/deliverySchedule";
import CouponPopup from './components/coupon-popup.vue' import CouponPopup from './components/coupon-popup.vue'
import {getMerchantCouponReceiveListApi} from "@/pages-user/service"; import {getMerchantCouponReceiveListApi} from "@/pages-user/service";
// import type { MerchantVo } from '@/service/types' // import type { MerchantVo } from '@/service/types'
// 页面加载状态 // 页面加载状态
const loading = ref(true); const loading = ref(true)
const userStore = useUserStore(); /** 切换店铺时右侧内容区骨架屏 */
const mainLoading = ref(false)
const userStore = useUserStore()
const configStore = useConfigStore()
const storeID = ref('') const storeID = ref('')
onLoad((options: any)=> { const merchantList = ref<MerchantVo[]>([])
console.log(options) const activeMerchantId = ref<string | number>('')
if(options.id) {
storeID.value = options.id 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() getStoreDetail()
// getMenuList() } else {
loading.value = false
} }
}) })
@@ -137,13 +233,17 @@ function parseBusinessHours(businessHours: string) {
} }
const storeDistance = ref(null) const storeDistance = ref(null)
function getStoreDetail() { function getStoreDetail(silent = false) {
loading.value = true if (!silent) {
loading.value = true
} else {
mainLoading.value = true
}
appMerchantDetailMerchantIdGet({ appMerchantDetailMerchantIdGet({
params: { params: {
merchantId: storeID.value, merchantId: storeID.value,
} }
}).then((res: any) => { }).then(async (res: any) => {
console.log('商家详情', res) console.log('商家详情', res)
storeDetail.value = res.data as MerchantVo storeDetail.value = res.data as MerchantVo
@@ -184,7 +284,10 @@ function getStoreDetail() {
dishListByQuery.value = [] dishListByQuery.value = []
hasMore.value = true hasMore.value = true
pageNum.value = 1 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 showDeliverySwitch.value = true
} }
ensureMerchantInSidebarList()
activeMerchantId.value = storeID.value
}).catch((error) => { }).catch((error) => {
console.error('获取商家详情失败:', error); console.error('获取商家详情失败:', error);
}).finally(()=> { }).finally(() => {
loading.value = false loading.value = false
mainLoading.value = false
}) })
} }
@@ -406,17 +511,11 @@ watch(activeTab, () => {
loadDishList(false); loadDishList(false);
}); });
// 触底加载更多 function onMainScrollToLower() {
onReachBottom(() => {
if (hasMore.value && !isLoadingMore.value) { if (hasMore.value && !isLoadingMore.value) {
loadDishList(true); loadDishList(true)
} }
}); }
const showStatusBar = useScrollThreshold()
onPageScroll((e) => {
uni.$emit('page-scroll', e)
})
function navigateToDishes(item: any) { function navigateToDishes(item: any) {
uni.navigateTo({ uni.navigateTo({
@@ -499,151 +598,150 @@ function handleShare() {
<StoreSkeleton/> <StoreSkeleton/>
</view> </view>
<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" v-if="!loading"
> >
<view class="store-box"> <view class="store-layout">
<!-- 顶部导航栏 --> <!-- 左侧返回 + Sidebar 切换店铺 -->
<view class="fixed top-0 left-0 z-9 w-full transition-all pt-6rpx" :class="[showStatusBar ? 'bg-#fff' : '']"> <view class="store-sidebar">
<status-bar /> <status-bar />
<view class="flex-center-sb px-30rpx h-88rpx"> <view class="store-sidebar__back" @click="navigateBack">
<image <image
@click="navigateBack"
src="@img/chef/1327.png" src="@img/chef/1327.png"
mode="aspectFill" mode="aspectFill"
class="w-48rpx h-48rpx relative z-1" class="store-sidebar__back-icon"
/>
<image
@click="handleShare"
src="@img-store/1335.png"
mode="aspectFill"
class="w-48rpx h-48rpx relative z-1"
/> />
</view> </view>
</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>
<status-bar />
<view class="h-44rpx"></view>
<!-- 店铺 Logo -->
<view class="flex justify-center ">
<image
:src="storeDetail?.shopImages?.split(',')[0]"
mode="aspectFill"
class="w-128rpx h-128rpx rounded-24rpx bg-common"
/>
</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">
{{ t('common.sales') }}{{ storeDetail?.totalOrderCount }}
</view>
</view> </view>
<scroll-view scroll-y class="store-sidebar__scroll" :show-scrollbar="false">
<!-- 配送信息卡片 --> <wd-sidebar
<view v-if="showDeliverySwitch" class="delivery-info-card"> v-model="activeMerchantId"
<template v-if="+storeDetail?.deliveryService === 1 && deliveryMethod === 0"> custom-class="store-wd-sidebar"
<view class="delivery-info-left"> @change="onSidebarChange"
<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">
<scroll-view
scroll-x
class="coupon-scroll flex-1"
:show-scrollbar="false"
enable-flex
> >
<view class="coupon-container"> <wd-sidebar-item
<view v-for="merchant in merchantList"
v-for="(coupon, index) in storeCouponList" :key="String(merchant.id)"
:key="coupon.id || index" :value="merchant.id"
class="coupon-item" :label="merchant.merchantName || ''"
> custom-class="store-sidebar-item"
<view class="coupon-tag"> >
<text class="coupon-text">{{ coupon.nameZh }}</text> <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> </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="store-share-btn"
:style="storeShareTopStyle"
/>
<view v-if="mainLoading" class="store-main store-main--loading">
<store-main-skeleton />
</view>
<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
v-if="storeBannerSrc"
:src="storeBannerSrc"
class="store-banner__img"
mode="aspectFill"
/>
<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="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 }}
</text>
</view> -->
<view v-if="storeCouponList.length" class="store-coupon-row">
<scroll-view
scroll-x
class="coupon-scroll flex-1"
:show-scrollbar="false"
enable-flex
>
<view class="coupon-container">
<view
v-for="(coupon, index) in storeCouponList"
:key="coupon.id || index"
class="coupon-item"
>
<view class="coupon-tag">
<text class="coupon-text">{{ coupon.nameZh }}</text>
</view>
</view>
</view>
</scroll-view>
<view @click="handleClaimNow" class="flex items-center shrink-0 ml-16rpx">
<text class="text-28rpx lh-28rpx font-500 text-#CE7138 mr-4rpx">{{ t('pages-store.store.claimNow') }}</text>
<i class="i-carbon:chevron-right text-24rpx text-#CE7138"></i>
</view>
</view>
<!-- </view> -->
<scroll-view scroll-x class="w-full" :show-scrollbar="false" enable-flex>
<view class="flex items-center px-24rpx pb-24rpx">
<view
v-for="(item, index) in tabs"
:key="item.key"
@click="activeTab = index"
class="tab-chip"
:class="activeTab === index ? 'tab-chip--active' : ''"
>
{{ item.title }}
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
<view @click="handleClaimNow" class="flex items-center shrink-0 ml-16rpx">
<text class="text-28rpx lh-28rpx font-500 text-#CE7138 mr-4rpx">{{ t('pages-store.store.claimNow') }}</text>
<i class="i-carbon:chevron-right text-24rpx text-#CE7138"></i>
</view>
</view>
</view>
<!-- 分类标签胶囊样式 --> <view v-if="tabs.length > 0" class="px-24rpx pb-180rpx">
<scroll-view scroll-x class="w-full" :show-scrollbar="false" enable-flex>
<view class="flex items-center px-30rpx pb-24rpx">
<view
v-for="(item, index) in tabs"
:key="item.key"
@click="activeTab = index"
class="tab-chip"
:class="activeTab === index ? 'tab-chip--active' : ''"
>
{{ item.title }}
</view>
</view>
</scroll-view>
<!-- 商品列表 -->
<view v-if="tabs.length > 0" class="px-30rpx pb-180rpx">
<view v-if="currentDishList.length > 0" class="grid grid-cols-2 gap-24rpx items-start"> <view v-if="currentDishList.length > 0" class="grid grid-cols-2 gap-24rpx items-start">
<template v-for="item in currentDishList" :key="item.id"> <template v-for="item in currentDishList" :key="item.id">
<view @click="navigateToDishes(item)" class="dish-card" :class="{ 'dish-card--soldout': isSoldOutStock(item?.stock) }"> <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="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"> <view class="min-w-0 flex-1">
<text class="dish-price">$ {{ item.discountPrice }}</text> <text class="dish-price">${{ item.discountPrice }}</text>
<text <!-- <text
v-if="Number(item?.originalPrice) > Number(item?.discountPrice)" v-if="Number(item?.originalPrice) > Number(item?.discountPrice)"
class="dish-original-price" class="dish-original-price"
>$ {{ item.originalPrice }}</text> >$ {{ item.originalPrice }}</text> -->
</view> </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>
<!-- 商品名称 --> <!-- 商品名称 -->
<view class="dish-title line-clamp-2 mb-16rpx"> <view class="dish-title line-clamp-2 mb-16rpx">
@@ -708,7 +806,7 @@ function handleShare() {
<view class="dish-add-btn center shrink-0"> <view class="dish-add-btn center shrink-0">
<image <image
src="@img/chef/1285.png" src="@img/chef/1285.png"
class="w-28rpx h-28rpx" class="w-20rpx h-20rpx"
></image> ></image>
</view> </view>
</view> </view>
@@ -728,11 +826,15 @@ function handleShare() {
<image class="w-250rpx h-250rpx" src="@img/chef/100.png"></image> <image class="w-250rpx h-250rpx" src="@img/chef/100.png"></image>
</view> </view>
</template> </template>
</view>
</view>
</scroll-view>
</view> </view>
</view>
<!-- 底部购物车浮窗 --> <!-- 底部购物车浮窗 -->
<view <view
v-if="userStore.isLogin && cartDataList.length > 0" v-if="!mainLoading && userStore.isLogin && cartDataList.length > 0"
class="store-cart-float" class="store-cart-float"
@click="navigateToCart" @click="navigateToCart"
> >
@@ -749,7 +851,7 @@ function handleShare() {
<!-- 会员省钱提示条 --> <!-- 会员省钱提示条 -->
<view <view
@click="navigateTo('/pages-user/pages/member/index')" @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" class="store-savings-bar"
> >
<image <image
@@ -761,7 +863,6 @@ function handleShare() {
</text> </text>
</view> </view>
</view> </view>
</view>
<coupon-popup <coupon-popup
ref="couponPopupRef" ref="couponPopupRef"
@@ -777,40 +878,217 @@ page {
</style> </style>
<style scoped lang="scss"> <style scoped lang="scss">
/* ====== 配送信息卡片 ====== */ .store-page {
.delivery-info-card { 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; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: center;
margin-top: 30rpx; padding: 12rpx 0 8rpx;
padding: 24rpx 40rpx;
border: 2rpx solid #D8D8D8;
border-radius: 20rpx;
min-height: 140rpx;
} }
.delivery-info-left { .store-sidebar__back-icon {
flex: 1; width: 48rpx;
text-align: center; height: 48rpx;
font-size: 24rpx;
line-height: 36rpx;
color: #CE7138;
padding-right: 30rpx;
} }
.delivery-info-divider { .store-sidebar__head {
width: 2rpx;
height: 100rpx;
background: #D8D8D8;
flex-shrink: 0; flex-shrink: 0;
}
.delivery-info-right {
flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; 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 { .dish-card-image {
position: relative; position: relative;
width: 100%; width: 100%;
height: 330rpx; height: 270rpx;
background: #f0f0f0; background: #f0f0f0;
overflow: hidden; overflow: hidden;
} }
@@ -908,18 +1186,18 @@ page {
} }
.dish-card-body { .dish-card-body {
padding: 20rpx 20rpx 22rpx; padding: 14rpx 14rpx 16rpx;
} }
.dish-price { .dish-price {
color: #e02e24; color: #e02e24;
font-size: 32rpx; font-size: 24rpx;
font-weight: 600; font-weight: 600;
line-height: 1.2; line-height: 1.3;
} }
.dish-original-price { .dish-original-price {
margin-left: 10rpx; margin-left: 6rpx;
color: #b3b3b3; color: #b3b3b3;
font-size: 22rpx; font-size: 22rpx;
font-weight: 400; font-weight: 400;
@@ -929,49 +1207,49 @@ page {
.dish-sales { .dish-sales {
color: #999; color: #999;
font-size: 24rpx; font-size: 22rpx;
line-height: 1.35; line-height: 1.3;
max-width: 48%; // max-width: 48%;
text-align: right; text-align: right;
} }
.dish-title { .dish-title {
color: #1a1a1a; color: #1a1a1a;
font-size: 28rpx; font-size: 24rpx;
font-weight: 500; font-weight: 500;
line-height: 1.45; line-height: 1.35;
} }
.dish-member { .dish-member {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
max-width: calc(100% - 88rpx); max-width: calc(100% - 80rpx);
} }
.dish-member-inner { .dish-member-inner {
display: inline-block; display: inline-block;
padding: 6rpx 18rpx; padding: 2rpx 12rpx;
border-radius: 999rpx; border-radius: 999rpx;
background: linear-gradient(180deg, #fff5eb 0%, #ffe8d6 100%); background: linear-gradient(180deg, #fff5eb 0%, #ffe8d6 100%);
color: #c45c1a; color: #c45c1a;
font-size: 24rpx; font-size: 22rpx;
font-weight: 500; font-weight: 500;
line-height: 1.35; line-height: 1.3;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.dish-add-btn { .dish-add-btn {
width: 56rpx; width: 48rpx;
height: 56rpx; height: 48rpx;
border-radius: 50%; border-radius: 50%;
background: #14181b; background: #14181b;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.12); box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.12);
} }
.dish-promo { .dish-promo {
padding: 12rpx 16rpx; padding: 10rpx 14rpx;
border-radius: 12rpx; border-radius: 12rpx;
background: #fff0f0; background: #fff0f0;
} }
@@ -980,7 +1258,7 @@ page {
color: #e02e24; color: #e02e24;
font-size: 22rpx; font-size: 22rpx;
font-weight: 500; font-weight: 500;
line-height: 1.4; line-height: 1.35;
} }
/* NEW 绑带标签 */ /* NEW 绑带标签 */
@@ -1000,11 +1278,11 @@ page {
left: -66rpx; left: -66rpx;
width: 240rpx; width: 240rpx;
text-align: center; text-align: center;
font-size: 22rpx; font-size: 20rpx;
font-weight: 700; font-weight: 700;
color: #fff; color: #fff;
letter-spacing: 2rpx; letter-spacing: 2rpx;
line-height: 44rpx; line-height: 40rpx;
background: #E23636; background: #E23636;
transform: rotate(-45deg); transform: rotate(-45deg);
} }
@@ -1028,12 +1306,12 @@ page {
z-index: 3; z-index: 3;
left: 16rpx; left: 16rpx;
top: 16rpx; top: 16rpx;
padding: 0 14rpx; padding: 0 12rpx;
height: 48rpx; height: 44rpx;
border-radius: 24rpx; border-radius: 22rpx;
background: rgba(20, 24, 27, 0.75); background: rgba(20, 24, 27, 0.75);
color: #fff; color: #fff;
font-size: 24rpx; font-size: 22rpx;
font-weight: 500; font-weight: 500;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -44,8 +44,8 @@ const { t, locale } = useI18n();
/** 首页运营图:按语言切换(中文 / 英文) */ /** 首页运营图:按语言切换(中文 / 英文) */
const HOME_PROMO_BANNERS = { const HOME_PROMO_BANNERS = {
memberUpgrade: { memberUpgrade: {
zh: 'https://www.howhowfresh.com/minio/ruoyi/2026/06/03/0b9a7f865aba442e84e51034a3ec900c.png', zh: 'https://www.howhowfresh.com/minio/ruoyi/2026/06/05/d4a0d40503ac4206a0387af1ab869de6.png',
en: 'https://www.howhowfresh.com/minio/ruoyi/2026/06/03/c5b9df3a922d4d17ae1b6eb8c1d7524a.png', en: 'https://www.howhowfresh.com/minio/ruoyi/2026/06/05/c01f1664626d417a9a7ca6165a20f06f.png',
}, },
deliveryTime: { deliveryTime: {
zh: 'https://www.howhowfresh.com/minio/ruoyi/2026/06/03/c5673a8874594755bdde7ed7fcbd1982.jpg', 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" @change-type="tabsTypeChange"
/> />
<view
<image class="home-member-banner"
:src="memberUpgradeBannerSrc" :style="{ backgroundImage: `url(${memberUpgradeBannerSrc})` }"
class="w-100% h-[340rpx]" >
mode="widthFix" <view class="home-member-banner__actions">
@click="navigateTo('/pages-user/pages/member/index')" <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 <image
:src="deliveryTimeBannerSrc" :src="deliveryTimeBannerSrc"
class="w-100% h-[200rpx] rounded-24rpx mt-4rpx" class="w-100% h-[200rpx] rounded-24rpx mt-4rpx"
mode="widthFix" mode="widthFix"
/> />
<!-- 精选商家和附近商家 --> <!-- 精选商家和附近商家 -->
<view class="animate-in fade-in animate-ease-in animate-duration-300" v-if="isShowMerchant"> <view class="animate-in fade-in animate-ease-in animate-duration-300" v-if="isShowMerchant">
<!-- Featured on ChefLink 精选商家浅底 + 横向卡片对齐设计稿 --> <!-- Featured on ChefLink 精选商家浅底 + 横向卡片对齐设计稿 -->
@@ -558,6 +569,45 @@ const debouncedEmit = debounce(1300, (isCollected: boolean, id: string, type: Co
padding-right: 8rpx; 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 { .nearby-merchants-block {
background: #f2f2f2; background: #f2f2f2;
} }