1091 lines
31 KiB
Vue
1091 lines
31 KiB
Vue
<script setup lang="ts">
|
||
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 {
|
||
appCollectCollectPost, appMerchantCartCalculateSavingsPost, appMerchantCartListByMerchantIdPost,
|
||
appMerchantDetailMerchantIdGet,
|
||
appMerchantDishDishIdGet,
|
||
appMerchantMenuMenuListByUserPost, type MerchantCartVo,
|
||
type MerchantVo
|
||
} from "@/service";
|
||
import {CollectionType} from "@/constant/enums";
|
||
import {useUserStore} from "@/store";
|
||
import { parseBusinessHoursUtils, getDistanceInMiles } from "@/utils/utils";
|
||
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 storeID = ref('')
|
||
onLoad((options: any)=> {
|
||
console.log(options)
|
||
if(options.id) {
|
||
storeID.value = options.id
|
||
getStoreDetail()
|
||
// getMenuList()
|
||
}
|
||
})
|
||
|
||
// 定时器用于更新闭店提示
|
||
let closingTimer: NodeJS.Timeout | null = null
|
||
|
||
// 启动闭店提示定时器
|
||
function startClosingTimer() {
|
||
if (closingTimer) {
|
||
clearInterval(closingTimer)
|
||
}
|
||
|
||
closingTimer = setInterval(() => {
|
||
if (storeDetail.value.businessHours) {
|
||
closingInfo.value = parseBusinessHours(storeDetail.value.businessHours)
|
||
// 如果不再显示提示,清除定时器
|
||
if (!closingInfo.value.show) {
|
||
clearInterval(closingTimer!)
|
||
closingTimer = null
|
||
}
|
||
}
|
||
}, 60000) // 每分钟更新一次
|
||
}
|
||
|
||
// 停止闭店提示定时器
|
||
function stopClosingTimer() {
|
||
if (closingTimer) {
|
||
clearInterval(closingTimer)
|
||
closingTimer = null
|
||
}
|
||
}
|
||
|
||
onShow(()=> {
|
||
nextTick(()=> {
|
||
if(storeID.value) {
|
||
// 查询当前店铺购物车信息
|
||
getCartInfo()
|
||
}
|
||
})
|
||
})
|
||
|
||
// 页面卸载时清除定时器
|
||
onUnmounted(() => {
|
||
stopClosingTimer()
|
||
})
|
||
|
||
// 获取商家详情信息
|
||
const storeDetail = ref<MerchantVo>({})
|
||
// 闭店提示信息
|
||
const closingInfo = ref({ show: false, minutes: 0 })
|
||
|
||
// 解析营业时间并判断是否在闭店前30分钟
|
||
function parseBusinessHours(businessHours: string) {
|
||
if (!businessHours) return { show: false, minutes: 0 }
|
||
|
||
const now = dayjs()
|
||
const currentDay = now.day() // 0=Sunday, 1=Monday, ..., 6=Saturday
|
||
const dayNames = [t('pages-store.store.weekdays.sunday'), t('pages-store.store.weekdays.monday'), t('pages-store.store.weekdays.tuesday'), t('pages-store.store.weekdays.wednesday'), t('pages-store.store.weekdays.thursday'), t('pages-store.store.weekdays.friday'), t('pages-store.store.weekdays.saturday')]
|
||
const todayName = dayNames[currentDay]
|
||
|
||
// 检查今天是否营业
|
||
if (!businessHours.includes(todayName)) {
|
||
return { show: false, minutes: 0 }
|
||
}
|
||
|
||
// 提取时间范围,格式如:05:00-16:00
|
||
const timeMatch = businessHours.match(/(\d{2}):(\d{2})-(\d{2}):(\d{2})/)
|
||
if (!timeMatch) {
|
||
return { show: false, minutes: 0 }
|
||
}
|
||
|
||
const [, startHour, startMin, endHour, endMin] = timeMatch
|
||
|
||
// 使用dayjs创建今天的开店和闭店时间
|
||
const openTime = now.hour(parseInt(startHour)).minute(parseInt(startMin)).second(0)
|
||
const closeTime = now.hour(parseInt(endHour)).minute(parseInt(endMin)).second(0)
|
||
|
||
// 如果闭店时间小于开店时间,说明跨天营业,闭店时间应该是明天
|
||
const actualCloseTime = closeTime.isBefore(openTime) ? closeTime.add(1, 'day') : closeTime
|
||
|
||
// 检查是否在营业时间内
|
||
const isOpen = now.isAfter(openTime) && now.isBefore(actualCloseTime)
|
||
|
||
if (!isOpen) {
|
||
return { show: false, minutes: 0 }
|
||
}
|
||
|
||
// 计算闭店前30分钟的时间点
|
||
const thirtyMinsBefore = actualCloseTime.subtract(30, 'minute')
|
||
|
||
// 判断是否在闭店前30分钟内
|
||
if (now.isAfter(thirtyMinsBefore) && now.isBefore(actualCloseTime)) {
|
||
const minutesLeft = actualCloseTime.diff(now, 'minute')
|
||
return { show: true, minutes: minutesLeft }
|
||
}
|
||
|
||
return { show: false, minutes: 0 }
|
||
}
|
||
|
||
const storeDistance = ref(null)
|
||
function getStoreDetail() {
|
||
loading.value = true
|
||
appMerchantDetailMerchantIdGet({
|
||
params: {
|
||
merchantId: storeID.value,
|
||
}
|
||
}).then((res: any) => {
|
||
console.log('商家详情', res)
|
||
storeDetail.value = res.data as MerchantVo
|
||
|
||
getMerchantCouponReceiveList()
|
||
|
||
// 解析营业时间并判断闭店提示
|
||
if (res.data.businessHours) {
|
||
closingInfo.value = parseBusinessHours(res.data.businessHours)
|
||
// 如果需要显示闭店提示,启动定时器
|
||
if (closingInfo.value.show) {
|
||
startClosingTimer()
|
||
}
|
||
}
|
||
|
||
if(res.data.merchantCategoryIds && res.data.merchantCategoryIds.length > 0) {
|
||
tabs.value = [
|
||
{
|
||
title: t('pages.store.all'),
|
||
key: ''
|
||
},
|
||
...res.data.merchantCategoryIds.map((item) => {
|
||
return {
|
||
title: item.menuName,
|
||
key: item.id
|
||
}
|
||
})
|
||
]
|
||
}
|
||
|
||
// 用详情中的首屏菜品初始化列表,并让下一次触底从第 2 页开始,避免仍请求第 1 页导致与详情数据重复
|
||
const firstRecords = res.data?.dishPage?.records
|
||
if (firstRecords && firstRecords.length > 0) {
|
||
dishListByQuery.value = [...firstRecords]
|
||
const gotFullPage = firstRecords.length >= pageSize.value
|
||
hasMore.value = gotFullPage
|
||
pageNum.value = gotFullPage ? 2 : 1
|
||
} else if (tabs.value.length > 0) {
|
||
dishListByQuery.value = []
|
||
hasMore.value = true
|
||
pageNum.value = 1
|
||
nextTick(() => loadDishList(false))
|
||
}
|
||
|
||
// 商户的经纬度存在,并且用户的经纬度也存在
|
||
if(res.data.latitude && res.data.longitude && userStore.userLocation.latitude && userStore.userLocation.longitude) {
|
||
let distance = getDistanceInMiles(res.data.latitude, res.data.longitude, userStore.userLocation.latitude, userStore.userLocation.longitude)
|
||
console.log('距离商家距离', distance)
|
||
storeDistance.value = distance
|
||
}
|
||
|
||
// 判断配送和自取的开通状态
|
||
const hasDelivery = +storeDetail.value.deliveryService === 1
|
||
const hasPickup = +storeDetail.value.selfPickup === 1
|
||
|
||
if (!hasDelivery && !hasPickup) {
|
||
// 两个都没开通,不显示切换组件
|
||
showDeliverySwitch.value = false
|
||
} else if (!hasDelivery && hasPickup) {
|
||
// 只开通自取,默认选中自取
|
||
deliveryMethod.value = 1
|
||
showDeliverySwitch.value = true
|
||
} else if (hasDelivery && !hasPickup) {
|
||
// 只开通配送,默认选中配送
|
||
deliveryMethod.value = 0
|
||
showDeliverySwitch.value = true
|
||
} else {
|
||
// 两个都开通,默认选中配送
|
||
deliveryMethod.value = 0
|
||
showDeliverySwitch.value = true
|
||
}
|
||
|
||
|
||
}).catch((error) => {
|
||
console.error('获取商家详情失败:', error);
|
||
}).finally(()=> {
|
||
loading.value = false
|
||
})
|
||
}
|
||
|
||
const storeCouponList = ref([])
|
||
function getMerchantCouponReceiveList() {
|
||
getMerchantCouponReceiveListApi(storeID.value).then((res: any)=> {
|
||
console.log('商家优惠券列表', res)
|
||
storeCouponList.value = res.data
|
||
})
|
||
}
|
||
|
||
const cartDataList = ref<MerchantCartVo[]>([])
|
||
function getCartInfo() {
|
||
if(!userStore.isLogin) return
|
||
appMerchantCartListByMerchantIdPost({
|
||
params: {
|
||
merchantId: storeID.value,
|
||
}
|
||
}).then((res: any)=> {
|
||
console.log('购物车列表', res)
|
||
cartDataList.value = res.data
|
||
|
||
// 购物车有菜品,查询菜品会员折扣价
|
||
if(cartDataList.value.length > 0) {
|
||
appMerchantCartCalculateSavings()
|
||
}
|
||
})
|
||
}
|
||
// 查询菜品会员折扣价
|
||
const cartSavingsData = ref({})
|
||
function appMerchantCartCalculateSavings() {
|
||
appMerchantCartCalculateSavingsPost({
|
||
body: cartDataList.value.map(item => item.id)
|
||
}).then(res=> {
|
||
console.log('菜品会员折扣价', res)
|
||
cartSavingsData.value = res.data
|
||
})
|
||
}
|
||
|
||
// 优惠券列表(静态数据)
|
||
const couponList = ref([
|
||
{ id: 1, text: '20% Off' },
|
||
{ id: 2, text: '30% Off' },
|
||
{ id: 3, text: '$5 Off' },
|
||
{ id: 4, text: '40% Off' },
|
||
])
|
||
|
||
// 跳转到优惠券页面
|
||
const couponPopupRef = ref()
|
||
function handleClaimNow() {
|
||
couponPopupRef.value.init()
|
||
}
|
||
|
||
|
||
// 用户查询菜单列表
|
||
function getMenuList() {
|
||
appMerchantMenuMenuListByUserPost({
|
||
params: {
|
||
merchantId: storeID.value,
|
||
}
|
||
}).then(res=> {
|
||
console.log('商家菜单列表', res)
|
||
// 这里可以处理商家菜单列表数据
|
||
}).catch(error => {
|
||
console.error('获取商家菜单列表失败:', error);
|
||
})
|
||
}
|
||
|
||
// 配送方式
|
||
const deliveryMethod = ref(0);
|
||
const deliveryMethodOptions = [t('pages-store.store.delivery'), t('pages-store.store.pickup')];
|
||
const showDeliverySwitch = ref(true); // 是否显示配送方式切换组件
|
||
|
||
function handleClickSegmented(index: number) {
|
||
console.log("切换配送方式:", index);
|
||
if(+storeDetail.value.deliveryService !== 1 && index === 0) {
|
||
uni.showToast({
|
||
title: t('pages-store.store.toast.deliveryService'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
if(+storeDetail.value.selfPickup !== 1 && index === 1) {
|
||
uni.showToast({
|
||
title: t('pages-store.store.toast.selfPickup'),
|
||
icon: 'none'
|
||
})
|
||
return
|
||
}
|
||
deliveryMethod.value = index
|
||
}
|
||
|
||
const activeTab = ref(0);
|
||
const tabs = ref<any[]>([]);
|
||
|
||
function daySuffix(value: unknown) {
|
||
const n = Number(value)
|
||
const isEn = String(locale.value || '').toLowerCase().startsWith('en')
|
||
if (isEn) {
|
||
return n === 1 ? ` ${t('pages-store.store.day')}` : ` ${t('pages-store.store.days')}`
|
||
}
|
||
return t('pages-store.store.days')
|
||
}
|
||
|
||
// 分页相关状态
|
||
const pageNum = ref(1);
|
||
const pageSize = ref(10);
|
||
const hasMore = ref(true);
|
||
const isLoadingMore = ref(false);
|
||
const dishListByQuery = ref<any[]>([]);
|
||
|
||
// 计算当前显示的商品列表(统一走 dishListByQuery,避免「全部」首屏用详情、加载更多再拼第 1 页造成重复)
|
||
const currentDishList = computed(() => dishListByQuery.value || [])
|
||
|
||
// 加载菜品列表
|
||
async function loadDishList(isLoadMore = false) {
|
||
if (isLoadingMore.value) return;
|
||
|
||
const currentTab = tabs.value[activeTab.value];
|
||
if (!currentTab) return;
|
||
|
||
try {
|
||
isLoadingMore.value = true;
|
||
|
||
// 非加载更多场景下,固定先请求第 1 页,并将内部 pageNum 重置为 1
|
||
if (!isLoadMore) {
|
||
pageNum.value = 1;
|
||
}
|
||
|
||
// 构建请求参数
|
||
const body: any = {
|
||
merchantId: storeID.value,
|
||
pageNum: isLoadMore ? pageNum.value : 1,
|
||
pageSize: pageSize.value
|
||
};
|
||
|
||
// 如果选中的 tab 的 key 不为空,则传递 menuId
|
||
if (currentTab.key !== '') {
|
||
body.menuId = currentTab.key;
|
||
}
|
||
|
||
console.log('加载菜品列表参数', body);
|
||
|
||
const res = await appMerchantDishDishIdGet({ body });
|
||
|
||
if (res.data && res.data.rows) {
|
||
if (isLoadMore) {
|
||
// 加载更多:按 id 去重,防止接口分页重叠或重复请求时的重复项
|
||
const existingIds = new Set(dishListByQuery.value.map((r: any) => r.id))
|
||
const nextRows = res.data.rows.filter((r: any) => r != null && !existingIds.has(r.id))
|
||
dishListByQuery.value = [...dishListByQuery.value, ...nextRows]
|
||
} else {
|
||
// 首次加载或刷新
|
||
dishListByQuery.value = res.data.rows;
|
||
}
|
||
|
||
const gotFullPage = res.data.rows.length >= pageSize.value;
|
||
// 更新分页信息
|
||
hasMore.value = gotFullPage;
|
||
|
||
if (isLoadMore) {
|
||
// 下一次请求再使用下一页页码
|
||
if (gotFullPage) {
|
||
pageNum.value++;
|
||
}
|
||
} else {
|
||
// 首次/刷新后,下一次「加载更多」应该从第 2 页开始
|
||
pageNum.value = gotFullPage ? 2 : 1;
|
||
}
|
||
} else {
|
||
hasMore.value = false;
|
||
}
|
||
} catch (error) {
|
||
console.error('加载菜品列表失败:', error);
|
||
} finally {
|
||
isLoadingMore.value = false;
|
||
}
|
||
}
|
||
|
||
// 监听 tab 切换,重置数据并加载当前 tab 的第一页
|
||
watch(activeTab, () => {
|
||
hasMore.value = true;
|
||
dishListByQuery.value = [];
|
||
loadDishList(false);
|
||
});
|
||
|
||
// 触底加载更多
|
||
onReachBottom(() => {
|
||
if (hasMore.value && !isLoadingMore.value) {
|
||
loadDishList(true);
|
||
}
|
||
});
|
||
|
||
const showStatusBar = useScrollThreshold()
|
||
onPageScroll((e) => {
|
||
uni.$emit('page-scroll', e)
|
||
})
|
||
|
||
function navigateToDishes(item: any) {
|
||
uni.navigateTo({
|
||
url: '/pages-store/pages/store/dishes?id=' + item.id + '&storeId=' + storeID.value,
|
||
})
|
||
}
|
||
function navigateBack() {
|
||
uni.navigateBack({
|
||
delta: 1,
|
||
})
|
||
}
|
||
|
||
function navigateToCart() {
|
||
uni.navigateTo({
|
||
url:
|
||
'/pages-user/pages/cart/store-cart'
|
||
+ '?storeId=' + storeID.value
|
||
+ '&storeName=' + encodeURIComponent(storeDetail.value.merchantName),
|
||
})
|
||
}
|
||
|
||
// 收藏店铺
|
||
function handleCollectionClick() {
|
||
debouncedEmit(storeDetail.value.isCollect, storeDetail.value.id, CollectionType.STORE, ()=> {
|
||
storeDetail.value.isCollect = !storeDetail.value.isCollect
|
||
})
|
||
}
|
||
// 收藏菜品
|
||
function handleDishCollectionClick(item: any) {
|
||
debouncedEmit(item.isCollect, item.id, CollectionType.DISH, ()=> {
|
||
item.isCollect = !item.isCollect
|
||
})
|
||
}
|
||
// 防抖处理函数
|
||
const debouncedEmit = debounce(1300, (isCollected: boolean, id: string, type: CollectionType, callback: ()=> void) => {
|
||
// 收藏接口
|
||
appCollectCollectPost({
|
||
body: {
|
||
targetId: id,
|
||
targetType: type
|
||
}
|
||
}).then(res=> {
|
||
callback()
|
||
})
|
||
}, {
|
||
atBegin: true, // 立即触发
|
||
})
|
||
|
||
function navigateTo(url: string) {
|
||
uni.navigateTo({ url })
|
||
}
|
||
|
||
function isSoldOutStock(stockLike: unknown) {
|
||
const n = Number(stockLike)
|
||
return !Number.isNaN(n) && n <= 0
|
||
}
|
||
|
||
function getDishPromoLabel(item: any): string {
|
||
const raw = item?.marketingLabel ?? item?.hotSaleTag ?? item?.rankTag ?? item?.promotionLabel
|
||
return typeof raw === 'string' && raw.trim() ? raw.trim() : ''
|
||
}
|
||
|
||
// 分享商家
|
||
function handleShare() {
|
||
uni.shareWithSystem({
|
||
summary: '',
|
||
href: `${Config.shareLink}pages-store/pages/store/index?id=${storeID.value}`,
|
||
success(){
|
||
// 分享完成,请注意此时不一定是成功分享
|
||
},
|
||
fail(){
|
||
// 分享失败
|
||
}
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<view
|
||
class="animate-in fade-in animate-ease-out animate-duration-300"
|
||
v-show="loading"
|
||
>
|
||
<StoreSkeleton/>
|
||
</view>
|
||
<view
|
||
class="animate-in fade-in animate-ease-in animate-duration-300"
|
||
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' : '']">
|
||
<status-bar />
|
||
<view class="flex-center-sb px-30rpx h-88rpx">
|
||
<image
|
||
@click="navigateBack"
|
||
src="@img/chef/1327.png"
|
||
mode="aspectFill"
|
||
class="w-48rpx h-48rpx relative z-1"
|
||
/>
|
||
<image
|
||
@click="handleShare"
|
||
src="@img-store/1335.png"
|
||
mode="aspectFill"
|
||
class="w-48rpx h-48rpx relative z-1"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 占位 -->
|
||
<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 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>
|
||
</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">
|
||
<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-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">
|
||
<template v-for="item in currentDishList" :key="item.id">
|
||
<view @click="navigateToDishes(item)" class="dish-card" :class="{ 'dish-card--soldout': isSoldOutStock(item?.stock) }">
|
||
<!-- 商品图片 -->
|
||
<view class="dish-card-image">
|
||
<!-- NEW 绑带标签 -->
|
||
<view v-if="item.isNew == 1" class="dish-new-ribbon">
|
||
<text class="dish-new-ribbon__text">NEW</text>
|
||
</view>
|
||
<view @click.stop="handleDishCollectionClick(item)" class="w-56rpx h-56rpx absolute z-4 top-12rpx right-12rpx center">
|
||
<image
|
||
v-if="!item.isCollect"
|
||
src="@img-store/1334.png"
|
||
mode="aspectFill"
|
||
class="w-44rpx h-44rpx"
|
||
style="filter: drop-shadow(0 2rpx 6rpx rgba(0,0,0,0.18))"
|
||
/>
|
||
<image
|
||
v-else
|
||
src="@img-store/1337.png"
|
||
mode="aspectFill"
|
||
class="w-44rpx h-44rpx"
|
||
style="filter: drop-shadow(0 2rpx 6rpx rgba(0,0,0,0.18))"
|
||
/>
|
||
</view>
|
||
<!-- 已售完遮罩 -->
|
||
<view v-if="isSoldOutStock(item?.stock)" class="dish-sold-dim"></view>
|
||
<!-- 已售完标签 -->
|
||
<view v-if="isSoldOutStock(item?.stock)" class="dish-sold-tag">{{ t('common.prompt.soldOut') }}</view>
|
||
<image
|
||
:src="item?.dishImage?.split(',')[0]"
|
||
mode="aspectFill"
|
||
class="dish-card-img"
|
||
/>
|
||
</view>
|
||
<!-- 卡片信息区 -->
|
||
<view class="dish-card-body">
|
||
<!-- 价格 + 销量 -->
|
||
<view class="flex items-start justify-between gap-12rpx mb-14rpx">
|
||
<view class="min-w-0 flex-1">
|
||
<text class="dish-price">$ {{ item.discountPrice }}</text>
|
||
<text
|
||
v-if="Number(item?.originalPrice) > Number(item?.discountPrice)"
|
||
class="dish-original-price"
|
||
>$ {{ item.originalPrice }}</text>
|
||
</view>
|
||
<text class="dish-sales shrink-0">{{ t('pages-store.store.sales') }}:{{ item.salesCount }}</text>
|
||
</view>
|
||
<!-- 商品名称 -->
|
||
<view class="dish-title line-clamp-2 mb-16rpx">
|
||
{{ item.dishName }}
|
||
</view>
|
||
<!-- 会员价 + 加购按钮 -->
|
||
<view class="flex items-center justify-between gap-12rpx">
|
||
<view
|
||
v-if="Number(item.memberPrice) > 0"
|
||
class="dish-member shrink min-w-0"
|
||
>
|
||
<text class="dish-member-inner">{{ t('pages-store.store.members') }}: $ {{ item.memberPrice }}</text>
|
||
</view>
|
||
<view v-else class="flex-1 min-w-0"></view>
|
||
<view class="dish-add-btn center shrink-0">
|
||
<image
|
||
src="@img/chef/1285.png"
|
||
class="w-28rpx h-28rpx"
|
||
></image>
|
||
</view>
|
||
</view>
|
||
<!-- 营销标签 -->
|
||
<view
|
||
v-if="getDishPromoLabel(item)"
|
||
class="dish-promo mt-16rpx"
|
||
>
|
||
<text class="dish-promo-text">{{ getDishPromoLabel(item) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
<template v-else>
|
||
<view class="py-100rpx center">
|
||
<image class="w-250rpx h-250rpx" src="@img/chef/100.png"></image>
|
||
</view>
|
||
</template>
|
||
</view>
|
||
|
||
<!-- 底部购物车浮窗 -->
|
||
<view
|
||
v-if="userStore.isLogin && cartDataList.length > 0"
|
||
class="store-cart-float"
|
||
@click="navigateToCart"
|
||
>
|
||
<view class="store-cart-float-inner">
|
||
<view class="relative mr-16rpx">
|
||
<view class="i-carbon:shopping-cart text-44rpx text-#333"></view>
|
||
<view class="store-cart-badge">{{ cartDataList.length > 99 ? '99+' : cartDataList.length }}</view>
|
||
</view>
|
||
<text class="text-28rpx lh-28rpx text-#333 font-500">{{ t('pages-user.cart.viewCart') }}</text>
|
||
<view class="text-28rpx text-#999 ml-55rpx"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 会员省钱提示条 -->
|
||
<view
|
||
@click="navigateTo('/pages-user/pages/member/index')"
|
||
v-if="cartDataList.length > 0 && cartSavingsData && cartSavingsData.savings > 0"
|
||
class="store-savings-bar"
|
||
>
|
||
<image
|
||
src="@img/chef/1289.png"
|
||
class="mr-10rpx w-32rpx h-32rpx shrink-0"
|
||
></image>
|
||
<text class="text-[#fff] text-24rpx lh-24rpx">
|
||
{{ t('pages-store.store.use') }} {{ Config.appName }} {{ t('pages-store.store.tips3') }} ${{ cartSavingsData.savings }} {{ t('pages-store.store.discount') }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<coupon-popup
|
||
ref="couponPopupRef"
|
||
:coupon-list="storeCouponList"
|
||
@confirm="getMerchantCouponReceiveList"
|
||
/>
|
||
</template>
|
||
|
||
<style>
|
||
page {
|
||
background-color: #F6F6F6;
|
||
}
|
||
</style>
|
||
|
||
<style scoped lang="scss">
|
||
/* ====== 配送信息卡片 ====== */
|
||
.delivery-info-card {
|
||
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;
|
||
}
|
||
|
||
.delivery-info-left {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 24rpx;
|
||
line-height: 36rpx;
|
||
color: #CE7138;
|
||
padding-right: 30rpx;
|
||
}
|
||
|
||
.delivery-info-divider {
|
||
width: 2rpx;
|
||
height: 100rpx;
|
||
background: #D8D8D8;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.delivery-info-right {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding-left: 30rpx;
|
||
}
|
||
|
||
/* ====== 优惠券标签 ====== */
|
||
.coupon-scroll {
|
||
width: 100%;
|
||
}
|
||
|
||
.coupon-container {
|
||
display: flex;
|
||
align-items: center;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.coupon-item {
|
||
margin-right: 15rpx;
|
||
flex-shrink: 0;
|
||
|
||
&:last-child {
|
||
margin-right: 0;
|
||
}
|
||
}
|
||
|
||
.coupon-tag {
|
||
min-width: 110rpx;
|
||
height: 52rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0 18rpx;
|
||
position: relative;
|
||
background-image: url("/static/images/5008.png");
|
||
background-size: 100% 100%;
|
||
background-repeat: no-repeat;
|
||
}
|
||
|
||
.coupon-text {
|
||
font-size: 24rpx;
|
||
line-height: 24rpx;
|
||
color: #CE7138;
|
||
font-weight: 400;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* ====== 胶囊标签 ====== */
|
||
.tab-chip {
|
||
height: 60rpx;
|
||
line-height: 60rpx;
|
||
padding: 0 32rpx;
|
||
border-radius: 30rpx;
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
border: 2rpx solid #E0E0E0;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
margin-right: 20rpx;
|
||
text-align: center;
|
||
|
||
&:last-child {
|
||
margin-right: 0;
|
||
}
|
||
|
||
&--active {
|
||
background: #333;
|
||
color: #fff;
|
||
border-color: #333;
|
||
}
|
||
}
|
||
|
||
/* ====== 商品卡片 ====== */
|
||
.dish-card {
|
||
width: 100%;
|
||
border-radius: 24rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||
background: #fff;
|
||
|
||
&--soldout {
|
||
opacity: 0.7;
|
||
}
|
||
}
|
||
|
||
.dish-card-image {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 330rpx;
|
||
background: #f0f0f0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.dish-card-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
z-index: 1;
|
||
}
|
||
|
||
.dish-card-body {
|
||
padding: 20rpx 20rpx 22rpx;
|
||
}
|
||
|
||
.dish-price {
|
||
color: #e02e24;
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.dish-original-price {
|
||
margin-left: 10rpx;
|
||
color: #b3b3b3;
|
||
font-size: 22rpx;
|
||
font-weight: 400;
|
||
text-decoration: line-through;
|
||
vertical-align: baseline;
|
||
}
|
||
|
||
.dish-sales {
|
||
color: #999;
|
||
font-size: 24rpx;
|
||
line-height: 1.35;
|
||
max-width: 48%;
|
||
text-align: right;
|
||
}
|
||
|
||
.dish-title {
|
||
color: #1a1a1a;
|
||
font-size: 28rpx;
|
||
font-weight: 500;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.dish-member {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
max-width: calc(100% - 88rpx);
|
||
}
|
||
|
||
.dish-member-inner {
|
||
display: inline-block;
|
||
padding: 6rpx 18rpx;
|
||
border-radius: 999rpx;
|
||
background: linear-gradient(180deg, #fff5eb 0%, #ffe8d6 100%);
|
||
color: #c45c1a;
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
line-height: 1.35;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.dish-add-btn {
|
||
width: 56rpx;
|
||
height: 56rpx;
|
||
border-radius: 50%;
|
||
background: #14181b;
|
||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.12);
|
||
}
|
||
|
||
.dish-promo {
|
||
padding: 12rpx 16rpx;
|
||
border-radius: 12rpx;
|
||
background: #fff0f0;
|
||
}
|
||
|
||
.dish-promo-text {
|
||
color: #e02e24;
|
||
font-size: 22rpx;
|
||
font-weight: 500;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* NEW 绑带标签 */
|
||
.dish-new-ribbon {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 184rpx;
|
||
height: 184rpx;
|
||
overflow: hidden;
|
||
z-index: 5;
|
||
pointer-events: none;
|
||
|
||
&__text {
|
||
position: absolute;
|
||
top: 26rpx;
|
||
left: -66rpx;
|
||
width: 240rpx;
|
||
text-align: center;
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
letter-spacing: 2rpx;
|
||
line-height: 44rpx;
|
||
background: #E23636;
|
||
transform: rotate(-45deg);
|
||
}
|
||
}
|
||
|
||
/* 已售完遮罩 */
|
||
.dish-sold-dim {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 2;
|
||
background: rgba(20, 24, 27, 0.42);
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* 已售完标签 */
|
||
.dish-sold-tag {
|
||
position: absolute;
|
||
z-index: 3;
|
||
left: 16rpx;
|
||
top: 16rpx;
|
||
padding: 0 14rpx;
|
||
height: 48rpx;
|
||
border-radius: 24rpx;
|
||
background: rgba(20, 24, 27, 0.75);
|
||
color: #fff;
|
||
font-size: 24rpx;
|
||
font-weight: 500;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* ====== 底部购物车浮窗 ====== */
|
||
.store-cart-float {
|
||
position: fixed;
|
||
bottom: calc(40rpx + env(safe-area-inset-bottom));
|
||
left: 50%;
|
||
width: 90%;
|
||
transform: translateX(-50%);
|
||
z-index: 9;
|
||
}
|
||
|
||
.store-cart-float-inner {
|
||
justify-content: space-between;
|
||
display: flex;
|
||
align-items: center;
|
||
height: 96rpx;
|
||
padding: 0 36rpx;
|
||
border-radius: 48rpx;
|
||
background: rgba(255, 255, 255, 0.75);
|
||
backdrop-filter: blur(20px);
|
||
-webkit-backdrop-filter: blur(20px);
|
||
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.12);
|
||
border: 1rpx solid rgba(255, 255, 255, 0.6);
|
||
}
|
||
|
||
.store-cart-badge {
|
||
position: absolute;
|
||
top: -8rpx;
|
||
right: -12rpx;
|
||
min-width: 32rpx;
|
||
height: 32rpx;
|
||
padding: 0 8rpx;
|
||
font-size: 20rpx;
|
||
line-height: 32rpx;
|
||
font-weight: 600;
|
||
color: #fff;
|
||
text-align: center;
|
||
background: #e23636;
|
||
border-radius: 999rpx;
|
||
}
|
||
|
||
/* ====== 会员省钱提示条 ====== */
|
||
.store-savings-bar {
|
||
position: fixed;
|
||
bottom: calc(160rpx + env(safe-area-inset-bottom));
|
||
left: 50%;
|
||
width: 90%;
|
||
transform: translateX(-50%);
|
||
z-index: 8;
|
||
height: 64rpx;
|
||
border-radius: 32rpx;
|
||
background: #CE7138;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 32rpx;
|
||
white-space: nowrap;
|
||
}
|
||
</style> |