修改效果
This commit is contained in:
+8
-1
@@ -241,7 +241,8 @@
|
|||||||
"mustEatList": "CHEFLINK Must-Eat",
|
"mustEatList": "CHEFLINK Must-Eat",
|
||||||
"newCalendar": "New Arrival Calendar",
|
"newCalendar": "New Arrival Calendar",
|
||||||
"newCalendarNav": "Today's New",
|
"newCalendarNav": "Today's New",
|
||||||
"freshSeafoodToday": "Fresh Seafood Today"
|
"freshSeafoodToday": "Fresh Seafood Today",
|
||||||
|
"energyMeal": "Energy Meals"
|
||||||
},
|
},
|
||||||
"mustEatListTabs": {
|
"mustEatListTabs": {
|
||||||
"merchant": "Merchant Rank",
|
"merchant": "Merchant Rank",
|
||||||
@@ -589,6 +590,12 @@
|
|||||||
"voucherUploadFailed": "Upload failed, please try again",
|
"voucherUploadFailed": "Upload failed, please try again",
|
||||||
"pleaseBindCreditCard": "No bank card linked. Please add a card before paying."
|
"pleaseBindCreditCard": "No bank card linked. Please add a card before paying."
|
||||||
},
|
},
|
||||||
|
"energyMeal": {
|
||||||
|
"title": "Energy Meals",
|
||||||
|
"addAllToCart": "Add all to cart",
|
||||||
|
"empty": "No energy meals yet",
|
||||||
|
"unavailable": "This bundle is unavailable"
|
||||||
|
},
|
||||||
"store": {
|
"store": {
|
||||||
"addToCart": "Add to cart",
|
"addToCart": "Add to cart",
|
||||||
"appetizers": "Appetizers",
|
"appetizers": "Appetizers",
|
||||||
|
|||||||
@@ -241,7 +241,8 @@
|
|||||||
"mustEatList": "CHEFLINK必吃榜",
|
"mustEatList": "CHEFLINK必吃榜",
|
||||||
"newCalendar": "上新日历",
|
"newCalendar": "上新日历",
|
||||||
"newCalendarNav": "今日上新",
|
"newCalendarNav": "今日上新",
|
||||||
"freshSeafoodToday": "今日现打海鲜"
|
"freshSeafoodToday": "今日现打海鲜",
|
||||||
|
"energyMeal": "能量餐"
|
||||||
},
|
},
|
||||||
"mustEatListTabs": {
|
"mustEatListTabs": {
|
||||||
"merchant": "商家榜单",
|
"merchant": "商家榜单",
|
||||||
@@ -589,6 +590,12 @@
|
|||||||
"voucherUploadFailed": "上传失败,请重试",
|
"voucherUploadFailed": "上传失败,请重试",
|
||||||
"pleaseBindCreditCard": "未绑定银行卡,请先绑定后再支付"
|
"pleaseBindCreditCard": "未绑定银行卡,请先绑定后再支付"
|
||||||
},
|
},
|
||||||
|
"energyMeal": {
|
||||||
|
"title": "能量餐",
|
||||||
|
"addAllToCart": "一键加入购物车",
|
||||||
|
"empty": "暂无能量餐",
|
||||||
|
"unavailable": "套餐暂不可购买"
|
||||||
|
},
|
||||||
"store": {
|
"store": {
|
||||||
"addToCart": "加入购物车",
|
"addToCart": "加入购物车",
|
||||||
"appetizers": "开胃菜",
|
"appetizers": "开胃菜",
|
||||||
|
|||||||
+2
-2
@@ -2,8 +2,8 @@
|
|||||||
"name" : "CHEFLINK delivery",
|
"name" : "CHEFLINK delivery",
|
||||||
"appid" : "__UNI__06509BE",
|
"appid" : "__UNI__06509BE",
|
||||||
"description" : "",
|
"description" : "",
|
||||||
"versionName" : "3.2.4",
|
"versionName" : "3.2.5",
|
||||||
"versionCode" : 324,
|
"versionCode" : 325,
|
||||||
"transformPx" : false,
|
"transformPx" : false,
|
||||||
/* 5+App特有相关 */
|
/* 5+App特有相关 */
|
||||||
"app-plus" : {
|
"app-plus" : {
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ export type FeaturedDishQueryBody = {
|
|||||||
|
|
||||||
/** 运营入口固定参数(精选菜品列表 operationalEntry) */
|
/** 运营入口固定参数(精选菜品列表 operationalEntry) */
|
||||||
export const OPERATIONAL_ENTRY = {
|
export const OPERATIONAL_ENTRY = {
|
||||||
|
FEATURED: 'featured',
|
||||||
FRESH_SEAFOOD_TODAY: 'fresh-seafood-today',
|
FRESH_SEAFOOD_TODAY: 'fresh-seafood-today',
|
||||||
LIMITED_AIR_SEAFOOD: 'limited-air-seafood',
|
LIMITED_AIR_SEAFOOD: 'limited-air-seafood',
|
||||||
|
NEW_DISH_CALENDAR: 'new-dish-calendar',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type RouteQueryMap = Record<string, string | undefined>
|
export type RouteQueryMap = Record<string, string | undefined>
|
||||||
@@ -63,41 +65,10 @@ export function buildMustEatListQuery(routeQuery?: RouteQueryMap): FeaturedDishQ
|
|||||||
return mergeRouteQueryIntoBody({ salesSort: 'desc' }, routeQuery)
|
return mergeRouteQueryIntoBody({ salesSort: 'desc' }, routeQuery)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 当天 00:00:00 ~ 23:59:59(本地时区,10 位秒级时间戳) */
|
/** 今日上新/上新日历:运营入口 new-dish-calendar(全量新品,按上架时间倒序) */
|
||||||
export function getTodayCreateTimeRange(): { createBeginTime: number; createEndTime: number } {
|
|
||||||
const now = new Date()
|
|
||||||
const createBeginTime = Math.floor(
|
|
||||||
new Date(
|
|
||||||
now.getFullYear(),
|
|
||||||
now.getMonth(),
|
|
||||||
now.getDate(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
).getTime() / 1000,
|
|
||||||
)
|
|
||||||
const createEndTime = Math.floor(
|
|
||||||
new Date(
|
|
||||||
now.getFullYear(),
|
|
||||||
now.getMonth(),
|
|
||||||
now.getDate(),
|
|
||||||
23,
|
|
||||||
59,
|
|
||||||
59,
|
|
||||||
0,
|
|
||||||
).getTime() / 1000,
|
|
||||||
)
|
|
||||||
return { createBeginTime, createEndTime }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 上新日历:当天创建 + 新品(isNew = 1) */
|
|
||||||
export function buildNewCalendarQuery(routeQuery?: RouteQueryMap): FeaturedDishQueryBody {
|
export function buildNewCalendarQuery(routeQuery?: RouteQueryMap): FeaturedDishQueryBody {
|
||||||
return mergeRouteQueryIntoBody(
|
return mergeRouteQueryIntoBody(
|
||||||
{
|
{ operationalEntry: OPERATIONAL_ENTRY.NEW_DISH_CALENDAR },
|
||||||
...getTodayCreateTimeRange(),
|
|
||||||
isNew: 1,
|
|
||||||
},
|
|
||||||
routeQuery,
|
routeQuery,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export function resolveQuickTopicFromMerchantCategory(
|
|||||||
/** 快捷入口 topic 与 operationalEntry 固定映射 */
|
/** 快捷入口 topic 与 operationalEntry 固定映射 */
|
||||||
export const TOPIC_OPERATIONAL_ENTRY: Partial<Record<QuickTopicSlug, string>> = {
|
export const TOPIC_OPERATIONAL_ENTRY: Partial<Record<QuickTopicSlug, string>> = {
|
||||||
'live-seafood-air': 'limited-air-seafood',
|
'live-seafood-air': 'limited-air-seafood',
|
||||||
|
'new-calendar': 'new-dish-calendar',
|
||||||
'fresh-seafood-today': 'fresh-seafood-today',
|
'fresh-seafood-today': 'fresh-seafood-today',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<view class="energy-meal-skeleton">
|
||||||
|
<view v-for="i in 2" :key="i" class="energy-meal-skeleton__card">
|
||||||
|
<view class="energy-meal-skeleton__head">
|
||||||
|
<view class="energy-meal-skeleton__title skeleton-item"></view>
|
||||||
|
<view class="energy-meal-skeleton__merchant skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-for="j in 2"
|
||||||
|
:key="j"
|
||||||
|
class="energy-meal-skeleton__row"
|
||||||
|
:class="{ 'energy-meal-skeleton__row--last': j === 2 }"
|
||||||
|
>
|
||||||
|
<view class="energy-meal-skeleton__img skeleton-item"></view>
|
||||||
|
<view class="energy-meal-skeleton__main">
|
||||||
|
<view class="energy-meal-skeleton__name skeleton-item"></view>
|
||||||
|
<view class="energy-meal-skeleton__price skeleton-item"></view>
|
||||||
|
<view class="energy-meal-skeleton__member skeleton-item"></view>
|
||||||
|
<view class="energy-meal-skeleton__sales skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="energy-meal-skeleton__action">
|
||||||
|
<view class="energy-meal-skeleton__btn skeleton-item"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.skeleton-item {
|
||||||
|
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: shimmer 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: -200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton {
|
||||||
|
padding: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__card {
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__head {
|
||||||
|
padding: 24rpx 24rpx 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__title {
|
||||||
|
width: 280rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__merchant {
|
||||||
|
width: 180rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__row {
|
||||||
|
display: flex;
|
||||||
|
padding: 28rpx 24rpx;
|
||||||
|
border-bottom: 1rpx solid #ebebeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__row--last {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__img {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__name {
|
||||||
|
width: 100%;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__price {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__member {
|
||||||
|
width: 320rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__sales {
|
||||||
|
width: 180rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 8rpx 24rpx 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-skeleton__btn {
|
||||||
|
width: 280rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,436 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Config from '@/config/index'
|
||||||
|
import { useUserStore } from '@/store'
|
||||||
|
import EnergyMealSkeleton from './components/energy-meal-skeleton.vue'
|
||||||
|
import {
|
||||||
|
appEnergyMealAddCartPost,
|
||||||
|
appEnergyMealListPost,
|
||||||
|
type EnergyMealItemVo,
|
||||||
|
type EnergyMealVo,
|
||||||
|
} from '@/service'
|
||||||
|
import { formatSalesCount, thumbnailImg } from '@/utils/utils'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const filterMerchantId = ref<string>('')
|
||||||
|
const mealList = ref<EnergyMealVo[]>([])
|
||||||
|
const paging = ref<ZPagingInstance | null>(null)
|
||||||
|
const addingMealId = ref<string | number | null>(null)
|
||||||
|
const loading = ref(true)
|
||||||
|
|
||||||
|
onLoad((options: Record<string, string | undefined>) => {
|
||||||
|
if (options?.merchantId) {
|
||||||
|
filterMerchantId.value = String(options.merchantId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function firstImage(raw?: string) {
|
||||||
|
if (typeof raw !== 'string' || !raw.trim()) return ''
|
||||||
|
return raw.split(',')[0].trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortedItems(meal: EnergyMealVo): EnergyMealItemVo[] {
|
||||||
|
const list = Array.isArray(meal.itemList) ? [...meal.itemList] : []
|
||||||
|
return list.sort((a, b) => Number(a.sort ?? 0) - Number(b.sort ?? 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
function canAddMeal(meal: EnergyMealVo) {
|
||||||
|
return sortedItems(meal).some((item) => item.merchantDishVo?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function dishCover(item: EnergyMealItemVo) {
|
||||||
|
return firstImage(item.merchantDishVo?.dishImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
function dishName(item: EnergyMealItemVo) {
|
||||||
|
const name = String(item.merchantDishVo?.dishName ?? '').trim()
|
||||||
|
const qty = Number(item.quantity) || 1
|
||||||
|
if (!name) return '--'
|
||||||
|
if (qty > 1) {
|
||||||
|
return `${name} × ${qty}`
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
function dishPrice(item: EnergyMealItemVo) {
|
||||||
|
const dish = item.merchantDishVo ?? {}
|
||||||
|
const discount = Number(dish.discountPrice)
|
||||||
|
const original = Number(dish.originalPrice)
|
||||||
|
if (Number.isFinite(discount) && discount > 0) return discount.toFixed(2)
|
||||||
|
if (Number.isFinite(original) && original > 0) return original.toFixed(2)
|
||||||
|
return '0.00'
|
||||||
|
}
|
||||||
|
|
||||||
|
function dishOriginalPrice(item: EnergyMealItemVo) {
|
||||||
|
const dish = item.merchantDishVo ?? {}
|
||||||
|
const discount = Number(dish.discountPrice)
|
||||||
|
const original = Number(dish.originalPrice)
|
||||||
|
if (
|
||||||
|
Number.isFinite(original) &&
|
||||||
|
original > 0 &&
|
||||||
|
Number.isFinite(discount) &&
|
||||||
|
original > discount
|
||||||
|
) {
|
||||||
|
return original.toFixed(2)
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function dishMemberPrice(item: EnergyMealItemVo) {
|
||||||
|
const member = Number(item.merchantDishVo?.memberPrice)
|
||||||
|
return Number.isFinite(member) && member > 0 ? member.toFixed(2) : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDishDetail(item: EnergyMealItemVo, meal: EnergyMealVo) {
|
||||||
|
const dishId = item.merchantDishVo?.id ?? item.dishId
|
||||||
|
const merchantId = meal.merchantId ?? item.merchantDishVo?.merchantId
|
||||||
|
if (!dishId || !merchantId) return
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages-store/pages/store/dishes?id=${dishId}&storeId=${merchantId}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onQuery(pageNum: number, pageSize: number) {
|
||||||
|
if (pageNum === 1) {
|
||||||
|
loading.value = true
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const body: Record<string, string> = {}
|
||||||
|
if (filterMerchantId.value) {
|
||||||
|
body.merchantId = filterMerchantId.value
|
||||||
|
}
|
||||||
|
const res = await appEnergyMealListPost({
|
||||||
|
params: { pageNum, pageSize },
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
const rows = Array.isArray(res.rows) ? res.rows : []
|
||||||
|
const total = Number(res.total ?? rows.length)
|
||||||
|
await paging.value?.completeByTotal(rows, total)
|
||||||
|
} catch {
|
||||||
|
await paging.value?.complete(false)
|
||||||
|
} finally {
|
||||||
|
if (pageNum === 1) {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAddAllToCart(meal: EnergyMealVo) {
|
||||||
|
if (!userStore.checkLogin()) return
|
||||||
|
if (!meal.id) return
|
||||||
|
if (!canAddMeal(meal)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('pages-store.energyMeal.unavailable'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (addingMealId.value != null) return
|
||||||
|
|
||||||
|
addingMealId.value = meal.id
|
||||||
|
try {
|
||||||
|
await appEnergyMealAddCartPost({
|
||||||
|
body: {
|
||||||
|
energyMealId: meal.id,
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
options: { hideErrorToast: true },
|
||||||
|
})
|
||||||
|
uni.showToast({
|
||||||
|
title: t('toast.addCartSuccess'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
userStore.getUserCartAllData()
|
||||||
|
} catch (err: any) {
|
||||||
|
uni.showToast({
|
||||||
|
title: err?.msg || err?.message || t('common.prompt.request-failed-please-try-again-later'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
if (addingMealId.value === meal.id) {
|
||||||
|
addingMealId.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<z-paging
|
||||||
|
ref="paging"
|
||||||
|
v-model="mealList"
|
||||||
|
bg-color="#f2f2f2"
|
||||||
|
:auto="true"
|
||||||
|
:hide-empty-view="loading"
|
||||||
|
@query="onQuery"
|
||||||
|
>
|
||||||
|
<template #top>
|
||||||
|
<navbar :title="t('pages-store.energyMeal.title')" circle-back />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-show="loading"
|
||||||
|
class="animate-in fade-in animate-ease-out animate-duration-300"
|
||||||
|
>
|
||||||
|
<energy-meal-skeleton />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-show="!loading"
|
||||||
|
class="animate-in fade-in animate-ease-in animate-duration-300 energy-meal-page"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="meal in mealList"
|
||||||
|
:key="meal.id"
|
||||||
|
class="energy-meal-card"
|
||||||
|
>
|
||||||
|
<view v-if="meal.mealName" class="energy-meal-card__head">
|
||||||
|
<text class="energy-meal-card__title line-clamp-1">{{ meal.mealName }}</text>
|
||||||
|
<text
|
||||||
|
v-if="meal.merchant?.merchantName"
|
||||||
|
class="energy-meal-card__merchant line-clamp-1"
|
||||||
|
>
|
||||||
|
{{ meal.merchant.merchantName }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in sortedItems(meal)"
|
||||||
|
:key="item.id || `${meal.id}-${index}`"
|
||||||
|
class="energy-dish-row"
|
||||||
|
:class="{ 'energy-dish-row--last': index === sortedItems(meal).length - 1 }"
|
||||||
|
@click="openDishDetail(item, meal)"
|
||||||
|
>
|
||||||
|
<view class="energy-dish-row__img-wrap">
|
||||||
|
<image
|
||||||
|
:src="thumbnailImg(dishCover(item))"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="energy-dish-row__img"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="energy-dish-row__main">
|
||||||
|
<text class="energy-dish-row__name line-clamp-2">{{ dishName(item) }}</text>
|
||||||
|
<view class="energy-dish-row__price-row">
|
||||||
|
<text class="energy-dish-row__price-current">${{ dishPrice(item) }}</text>
|
||||||
|
<text
|
||||||
|
v-if="dishOriginalPrice(item)"
|
||||||
|
class="energy-dish-row__price-old"
|
||||||
|
>
|
||||||
|
${{ dishOriginalPrice(item) }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="dishMemberPrice(item)"
|
||||||
|
class="energy-dish-row__member"
|
||||||
|
>
|
||||||
|
<text class="energy-dish-row__member-diamond">◆</text>
|
||||||
|
<text class="energy-dish-row__member-text">
|
||||||
|
{{ Config.appName }} {{ t('pages.search.member-price-line') }}: ${{ dishMemberPrice(item) }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="energy-dish-row__sales-wrap">
|
||||||
|
<text class="energy-dish-row__sales-tag">
|
||||||
|
{{ t('pages.search.weekly-sales') }}:{{ formatSalesCount(item.merchantDishVo?.salesCount) }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-if="canAddMeal(meal)"
|
||||||
|
class="energy-meal-card__action"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="energy-meal-card__btn"
|
||||||
|
:class="{ 'energy-meal-card__btn--loading': addingMealId === meal.id }"
|
||||||
|
@click.stop="handleAddAllToCart(meal)"
|
||||||
|
>
|
||||||
|
{{ t('pages-store.energyMeal.addAllToCart') }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<template #empty>
|
||||||
|
<view v-if="!loading" class="energy-meal-empty">
|
||||||
|
<image class="energy-meal-empty__img" src="@img/chef/100.png" mode="aspectFit" />
|
||||||
|
<text class="energy-meal-empty__text">{{ t('pages-store.energyMeal.empty') }}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</z-paging>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.energy-meal-page {
|
||||||
|
padding: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-card {
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-card__head {
|
||||||
|
padding: 24rpx 24rpx 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-card__title {
|
||||||
|
display: block;
|
||||||
|
font-size: 30rpx;
|
||||||
|
line-height: 42rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-card__merchant {
|
||||||
|
display: block;
|
||||||
|
margin-top: 6rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 34rpx;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 28rpx 24rpx;
|
||||||
|
border-bottom: 1rpx solid #ebebeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row--last {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__img-wrap {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__price-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__price-current {
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #e02e24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__price-old {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__member {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__member-diamond {
|
||||||
|
font-size: 20rpx;
|
||||||
|
line-height: 1;
|
||||||
|
color: #b8860b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__member-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #8b6914;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__sales-wrap {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-dish-row__sales-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8rpx 18rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: #f5f5f5;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-card__action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 8rpx 24rpx 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-card__btn {
|
||||||
|
min-width: 280rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
padding: 0 32rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: #111;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 72rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-card__btn--loading {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 120rpx 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-empty__img {
|
||||||
|
width: 250rpx;
|
||||||
|
height: 250rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.energy-meal-empty__text {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
color: #8a8a8a;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -284,6 +284,12 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/home-store/index"
|
"path": "pages/home-store/index"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/energy-meal/index",
|
||||||
|
"style": {
|
||||||
|
"onReachBottomDistance": 80
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { computed } from 'vue'
|
|||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
/** 外部列表(菜谱页等);首页不传则使用固定五项 */
|
/** 外部列表(菜谱页等);首页不传则使用固定六项 */
|
||||||
list: {
|
list: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
@@ -55,6 +55,11 @@ const fixedTabs = [
|
|||||||
nameKey: 'pages.home.quickTabs.freshSeafoodToday',
|
nameKey: 'pages.home.quickTabs.freshSeafoodToday',
|
||||||
logoUrl: '/static/app/images/home/xiandahaixian.png',
|
logoUrl: '/static/app/images/home/xiandahaixian.png',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'energy-meal',
|
||||||
|
nameKey: 'pages.home.quickTabs.energyMeal',
|
||||||
|
logoUrl: '/static/app/images/home/nengliangcan.png',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const useFixedTabs = computed(() => !props.list || props.list.length === 0)
|
const useFixedTabs = computed(() => !props.list || props.list.length === 0)
|
||||||
@@ -63,10 +68,6 @@ function selectTab(item: Record<string, unknown>) {
|
|||||||
emit('changeType', item[props.valueKey])
|
emit('changeType', item[props.valueKey])
|
||||||
}
|
}
|
||||||
|
|
||||||
function imageWidthByIndex(index: number) {
|
|
||||||
return (index + 1) % 2 === 1 ? '104rpx' : '210rpx'
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTabName(item: Record<string, unknown>) {
|
function getTabName(item: Record<string, unknown>) {
|
||||||
if (useFixedTabs.value && item.nameKey) {
|
if (useFixedTabs.value && item.nameKey) {
|
||||||
return t(String(item.nameKey))
|
return t(String(item.nameKey))
|
||||||
@@ -97,8 +98,7 @@ const displayList = computed(() =>
|
|||||||
<image
|
<image
|
||||||
class="tab-img"
|
class="tab-img"
|
||||||
:src="getImage(item as Record<string, unknown>)"
|
:src="getImage(item as Record<string, unknown>)"
|
||||||
mode="scaleToFill"
|
mode="heightFix"
|
||||||
:style="{ width: imageWidthByIndex(index) }"
|
|
||||||
/>
|
/>
|
||||||
<text class="tab-label line-clamp-1">{{ getTabName(item as Record<string, unknown>) }}</text>
|
<text class="tab-label line-clamp-1">{{ getTabName(item as Record<string, unknown>) }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -122,7 +122,6 @@ const displayList = computed(() =>
|
|||||||
|
|
||||||
.tab-img {
|
.tab-img {
|
||||||
height: 144rpx;
|
height: 144rpx;
|
||||||
width: auto;
|
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 10rpx;
|
margin-bottom: 10rpx;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -236,6 +236,10 @@ function handleItemClick(e) {
|
|||||||
}
|
}
|
||||||
function tabsTypeChange(id: string | number) {
|
function tabsTypeChange(id: string | number) {
|
||||||
const topic = String(id)
|
const topic = String(id)
|
||||||
|
if (topic === 'energy-meal') {
|
||||||
|
navigateTo('/pages-store/pages/energy-meal/index')
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!isQuickTopicSlug(topic)) {
|
if (!isQuickTopicSlug(topic)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -310,6 +314,9 @@ function handleClickSwiper(item: any) {
|
|||||||
case 3: // 会员
|
case 3: // 会员
|
||||||
navigateTo('/pages-user/pages/member/index')
|
navigateTo('/pages-user/pages/member/index')
|
||||||
break
|
break
|
||||||
|
case 5: // 钱包
|
||||||
|
navigateTo('/pages-user/pages/balance/index')
|
||||||
|
break
|
||||||
// case 4:
|
// case 4:
|
||||||
// navigateTo('/pages/ai/chat/index')
|
// navigateTo('/pages/ai/chat/index')
|
||||||
// break
|
// break
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// @ts-ignore
|
||||||
|
import request from '@/http/vue-query';
|
||||||
|
import type { CustomRequestOptions } from '@/http/types';
|
||||||
|
|
||||||
|
export interface EnergyMealListQueryBo {
|
||||||
|
merchantId?: number | string;
|
||||||
|
mealName?: string;
|
||||||
|
merchantName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnergyMealItemVo {
|
||||||
|
id?: number | string;
|
||||||
|
energyMealId?: number | string;
|
||||||
|
dishId?: number | string;
|
||||||
|
quantity?: number;
|
||||||
|
sort?: number;
|
||||||
|
delFlag?: number;
|
||||||
|
merchantDishVo?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnergyMealVo {
|
||||||
|
id?: number | string;
|
||||||
|
merchantId?: number | string;
|
||||||
|
mealName?: string;
|
||||||
|
coverImage?: string;
|
||||||
|
sort?: number;
|
||||||
|
delFlag?: number;
|
||||||
|
remark?: string;
|
||||||
|
merchant?: {
|
||||||
|
id?: number | string;
|
||||||
|
merchantName?: string;
|
||||||
|
logo?: string;
|
||||||
|
merchantAddress?: string;
|
||||||
|
};
|
||||||
|
itemList?: EnergyMealItemVo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnergyMealAddCartBo {
|
||||||
|
energyMealId: number | string;
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 能量餐分页列表 POST /app/energyMeal/list */
|
||||||
|
export async function appEnergyMealListPost({
|
||||||
|
params,
|
||||||
|
body,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
params: { pageNum: number; pageSize: number };
|
||||||
|
body?: EnergyMealListQueryBo;
|
||||||
|
options?: CustomRequestOptions;
|
||||||
|
}) {
|
||||||
|
return request<{ rows?: EnergyMealVo[]; total?: number }>(
|
||||||
|
'/app/energyMeal/list',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
data: body ?? {},
|
||||||
|
...(options || {}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 能量餐一键加入购物车 POST /app/energyMeal/addCart */
|
||||||
|
export async function appEnergyMealAddCartPost({
|
||||||
|
body,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
body: EnergyMealAddCartBo;
|
||||||
|
options?: CustomRequestOptions;
|
||||||
|
}) {
|
||||||
|
return request<{ data?: number[] }>('/app/energyMeal/addCart', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -44,3 +44,4 @@ export * from './automaticCookingMachine';
|
|||||||
export * from './userCoupon';
|
export * from './userCoupon';
|
||||||
export * from './marketingActivity';
|
export * from './marketingActivity';
|
||||||
export * from './marketActivity';
|
export * from './marketActivity';
|
||||||
|
export * from './energyMeal';
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
Reference in New Issue
Block a user