Vendored
+1
-1
@@ -5,6 +5,6 @@ VITE_DELETE_CONSOLE=false
|
|||||||
|
|
||||||
#本地环境
|
#本地环境
|
||||||
VITE_SERVER_BASEURL=https://howhowfresh.com/prod-api
|
VITE_SERVER_BASEURL=https://howhowfresh.com/prod-api
|
||||||
#VITE_SERVER_BASEURL=http://192.168.5.200:8080
|
#VITE_SERVER_BASEURL=http://192.168.5.4:8080
|
||||||
#VITE_SERVER_BASEURL=http://192.168.0.148:8888
|
#VITE_SERVER_BASEURL=http://192.168.0.148:8888
|
||||||
#VITE_SERVER_BASEURL=http://liuyao.nat100.top/meiguowaimai
|
#VITE_SERVER_BASEURL=http://liuyao.nat100.top/meiguowaimai
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
// import {upload} from '@/utils/upload/alioss'
|
import { upload } from '@/utils/upload/alioss'
|
||||||
import { uploadToS3 } from '@/utils/upload/ymx'
|
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -59,8 +58,7 @@ function chooseImage(type: 'album' | 'camera') {
|
|||||||
// })
|
// })
|
||||||
//
|
//
|
||||||
// for (let i = 0; i < files.length; i++) {
|
// for (let i = 0; i < files.length; i++) {
|
||||||
// // asyncList.push(upload(files[i]))
|
// asyncList.push(upload(files[i]))
|
||||||
// asyncList.push(uploadToS3(files[i]))
|
|
||||||
// console.log(asyncList)
|
// console.log(asyncList)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
@@ -101,9 +99,7 @@ function chooseImage(type: 'album' | 'camera') {
|
|||||||
mask: true,
|
mask: true,
|
||||||
})
|
})
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
// asyncList.push(upload(files[i]))
|
asyncList.push(upload(files[i]))
|
||||||
asyncList.push(uploadToS3(files[i]))
|
|
||||||
console.log(asyncList)
|
|
||||||
}
|
}
|
||||||
Promise.all(asyncList)
|
Promise.all(asyncList)
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
@@ -116,6 +112,38 @@ function chooseImage(type: 'album' | 'camera') {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
|
// #ifndef APP-PLUS
|
||||||
|
uni.chooseImage({
|
||||||
|
count: props.count,
|
||||||
|
sizeType: ['compressed'],
|
||||||
|
sourceType: type === 'album' ? ['album'] : ['camera'],
|
||||||
|
success: async (res) => {
|
||||||
|
let files = [...res.tempFilePaths]
|
||||||
|
if (props.count > 0) {
|
||||||
|
files = files.slice(0, props.count)
|
||||||
|
}
|
||||||
|
if (!props.isUpload) {
|
||||||
|
return emits('change', files)
|
||||||
|
}
|
||||||
|
await uni.showLoading({
|
||||||
|
title: t('common.loading') + '...',
|
||||||
|
mask: true,
|
||||||
|
})
|
||||||
|
const asyncList: Promise<unknown>[] = []
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
asyncList.push(upload(files[i]))
|
||||||
|
}
|
||||||
|
Promise.all(asyncList)
|
||||||
|
.then((results) => {
|
||||||
|
emits('change', results)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
uni.hideLoading()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted } from 'vue'
|
||||||
import Config from '@/config'
|
import Config from '@/config'
|
||||||
import {throttle} from 'throttle-debounce'
|
import {throttle} from 'throttle-debounce'
|
||||||
|
import {useUserStore} from '@/store'
|
||||||
|
|
||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -10,11 +13,17 @@ const props = withDefaults(
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
focus?: boolean
|
focus?: boolean
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
|
/** 搜索落地页:右侧圆形购物车,与返回键对称 */
|
||||||
|
trailingCart?: boolean
|
||||||
|
/** trailingCart 为 true 时:gray=搜索首页浅底;white=结果页白底(与设计稿一致) */
|
||||||
|
trailingSurface?: 'gray' | 'white'
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
modelValue: '',
|
modelValue: '',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
focus: false,
|
focus: false,
|
||||||
|
trailingCart: false,
|
||||||
|
trailingSurface: 'gray',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,6 +48,35 @@ function handleClickLeft() {
|
|||||||
uni.navigateBack()
|
uni.navigateBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goCart() {
|
||||||
|
if (userStore.checkLogin()) {
|
||||||
|
uni.navigateTo({ url: '/pages-user/pages/cart/index' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cartBadgeTotal = computed(() => {
|
||||||
|
const list = userStore.userCartAllData
|
||||||
|
if (!Array.isArray(list) || list.length === 0) return 0
|
||||||
|
let n = 0
|
||||||
|
for (const m of list) {
|
||||||
|
n += (m as { merchantCartVoList?: unknown[] })?.merchantCartVoList?.length || 0
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.trailingCart) {
|
||||||
|
userStore.getUserCartAllData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const headerRootClass = computed(() => {
|
||||||
|
if (!props.trailingCart) return 'header-search header-search--default'
|
||||||
|
return props.trailingSurface === 'white'
|
||||||
|
? 'header-search header-search--trailing-white'
|
||||||
|
: 'header-search header-search--landing'
|
||||||
|
})
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'HeaderSearch',
|
name: 'HeaderSearch',
|
||||||
|
|
||||||
@@ -46,10 +84,52 @@ defineOptions({
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<view
|
<view
|
||||||
class="relative z-1 bg-#fff"
|
:class="headerRootClass"
|
||||||
>
|
>
|
||||||
<status-bar/>
|
<status-bar/>
|
||||||
<view class="flex-center-sb px-30rpx h-88rpx">
|
<view
|
||||||
|
v-if="trailingCart"
|
||||||
|
class="header-search__row header-search__row--triple px-24rpx pt-8rpx pb-12rpx"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="header-search__round-btn center shrink-0"
|
||||||
|
:class="trailingSurface === 'white' ? 'header-search__round-btn--muted' : ''"
|
||||||
|
@click="handleClickLeft"
|
||||||
|
>
|
||||||
|
<view class="i-carbon:chevron-left text-34rpx text-#14181b"></view>
|
||||||
|
</view>
|
||||||
|
<view class="header-search__pill flex-1 min-w-0 h-80rpx flex items-center px-28rpx">
|
||||||
|
<image src="@img/chef/100222.png" class="w-28rpx h-28rpx mr-14rpx shrink-0"></image>
|
||||||
|
<wd-input
|
||||||
|
no-border
|
||||||
|
clearable
|
||||||
|
:focus-when-clear="false"
|
||||||
|
:disabled="disabled"
|
||||||
|
:focus="focus"
|
||||||
|
confirm-type="search"
|
||||||
|
custom-class="flex items-center !text-28rpx !bg-transparent flex-1 min-w-0"
|
||||||
|
placeholderStyle="font-size: 28rpx;color: #999999; font-weight: 500;"
|
||||||
|
:modelValue="modelValue"
|
||||||
|
:placeholder="placeholder || t('common.prompt.please-enter-keyword-search')"
|
||||||
|
@update:modelValue="handleInputUpdateModelValue"
|
||||||
|
@confirm="handleSearch"
|
||||||
|
>
|
||||||
|
</wd-input>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="header-search__round-btn header-search__round-btn--cart center shrink-0"
|
||||||
|
:class="trailingSurface === 'white' ? 'header-search__round-btn--muted' : ''"
|
||||||
|
@click="goCart"
|
||||||
|
>
|
||||||
|
<view class="i-carbon:shopping-cart text-34rpx text-#14181b"></view>
|
||||||
|
<view
|
||||||
|
v-if="userStore.isLogin && cartBadgeTotal > 0"
|
||||||
|
class="header-search__cart-badge"
|
||||||
|
>{{ cartBadgeTotal > 99 ? '99+' : cartBadgeTotal }}</view
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-else class="flex-center-sb px-30rpx h-88rpx">
|
||||||
<view class="shrink-0" @click="handleClickLeft">
|
<view class="shrink-0" @click="handleClickLeft">
|
||||||
<view class="i-fluent:ios-arrow-ltr-24-filled text-36rpx text-#333"></view>
|
<view class="i-fluent:ios-arrow-ltr-24-filled text-36rpx text-#333"></view>
|
||||||
</view>
|
</view>
|
||||||
@@ -79,6 +159,56 @@ defineOptions({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.header-search--landing {
|
||||||
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
|
.header-search--trailing-white {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.header-search--default {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.header-search__row--triple {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
.header-search__round-btn {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.header-search__round-btn--muted {
|
||||||
|
background: #f0f0f0;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
.header-search__round-btn--cart {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.header-search__cart-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 6rpx;
|
||||||
|
right: 6rpx;
|
||||||
|
min-width: 28rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
padding: 0 6rpx;
|
||||||
|
font-size: 18rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
background: #e23636;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
}
|
||||||
|
.header-search__pill {
|
||||||
|
background: #f2f2f2;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
}
|
||||||
:deep(.wd-input__clear) {
|
:deep(.wd-input__clear) {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ const props = withDefaults(defineProps<{
|
|||||||
fixed?: boolean
|
fixed?: boolean
|
||||||
showLeft?: boolean
|
showLeft?: boolean
|
||||||
customClass?: string
|
customClass?: string
|
||||||
|
/** 左侧返回为白底圆形+阴影(用于预约日期等浅色页) */
|
||||||
|
circleBack?: boolean
|
||||||
}>(), {
|
}>(), {
|
||||||
fixed: true,
|
fixed: true,
|
||||||
showLeft: true
|
showLeft: true,
|
||||||
|
circleBack: false
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleClickLeft() {
|
function handleClickLeft() {
|
||||||
@@ -31,7 +34,17 @@ function handleClickLeft() {
|
|||||||
@click-left="handleClickLeft">
|
@click-left="handleClickLeft">
|
||||||
<template #left>
|
<template #left>
|
||||||
<view class="shrink-0" v-if="showLeft">
|
<view class="shrink-0" v-if="showLeft">
|
||||||
<view class="i-carbon:chevron-left text-50rpx text-primary ml-[-10rpx]"></view>
|
<view
|
||||||
|
class="flex items-center justify-center"
|
||||||
|
:class="circleBack ? 'navbar-circle-back' : ''"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
:class="[
|
||||||
|
'i-carbon:chevron-left text-50rpx',
|
||||||
|
circleBack ? 'ml-0 text-#111' : 'ml-[-10rpx] text-primary',
|
||||||
|
]"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
@@ -41,6 +54,14 @@ function handleClickLeft() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.navbar-circle-back {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.wd-navbar) {
|
:deep(.wd-navbar) {
|
||||||
z-index: 2 !important;
|
z-index: 2 !important;
|
||||||
.wd-navbar__title {
|
.wd-navbar__title {
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ const Config = {
|
|||||||
shareLink: "https://www.howhowfresh.com/h5/",
|
shareLink: "https://www.howhowfresh.com/h5/",
|
||||||
shareImage: "https://hanguomcn.oss-ap-northeast-2.aliyuncs.com/images/20250901105756_1756695476183_56ac07ac.png",
|
shareImage: "https://hanguomcn.oss-ap-northeast-2.aliyuncs.com/images/20250901105756_1756695476183_56ac07ac.png",
|
||||||
shareDesc: "分享专属二维码邀请新用户注册即邀请成功。一起来注册体验点餐功能。",
|
shareDesc: "分享专属二维码邀请新用户注册即邀请成功。一起来注册体验点餐功能。",
|
||||||
|
// Zelle 支付二维码图片地址
|
||||||
|
zellePayPath: "https://www.howhowfresh.com/minio/ruoyi/2026/04/14/ef120c97e50c419da2263a59d6679229.png",
|
||||||
// 登录端口
|
// 登录端口
|
||||||
userPort: 1,
|
userPort: 1,
|
||||||
// 手机区号数组
|
// 手机区号数组
|
||||||
|
|||||||
+90
-8
@@ -171,7 +171,7 @@
|
|||||||
"officeDescription": "Workplaces with entry restrictions",
|
"officeDescription": "Workplaces with entry restrictions",
|
||||||
"other": "Other",
|
"other": "Other",
|
||||||
"otherDescription": "Hospitals, parks, outdoors, etc",
|
"otherDescription": "Hospitals, parks, outdoors, etc",
|
||||||
"title": "Select building type"
|
"title": "Building type selection"
|
||||||
},
|
},
|
||||||
"deliveryInstructions": "Delivery Instructions",
|
"deliveryInstructions": "Delivery Instructions",
|
||||||
"deliveryPointInfo": "Delivery Point Information",
|
"deliveryPointInfo": "Delivery Point Information",
|
||||||
@@ -206,14 +206,19 @@
|
|||||||
"dateNotSelectable": "This date is not selectable",
|
"dateNotSelectable": "This date is not selectable",
|
||||||
"noAvailableTime": "No available time for current date, automatically selected next business day",
|
"noAvailableTime": "No available time for current date, automatically selected next business day",
|
||||||
"notAvailable": "Not available",
|
"notAvailable": "Not available",
|
||||||
|
"nextWeek": "Next week",
|
||||||
|
"pageTitle": "Delivery date",
|
||||||
"reservationSuccess": "Reservation successful",
|
"reservationSuccess": "Reservation successful",
|
||||||
"selectTimeSlot": "Please select a time slot"
|
"selectTimeSlot": "Please select a time slot",
|
||||||
|
"thisWeek": "This week"
|
||||||
},
|
},
|
||||||
"savedAddresses": "Saved addresses",
|
"savedAddresses": "Saved addresses",
|
||||||
"title": "Address",
|
"title": "Address",
|
||||||
"titleDetail": "Address Details"
|
"titleDetail": "Address Details"
|
||||||
},
|
},
|
||||||
"browse": {
|
"browse": {
|
||||||
|
"brandTag": "CHEFLINK Selected",
|
||||||
|
"moreRecipes": "More Recipes",
|
||||||
"titleCuisine": "Nearby Cuisine",
|
"titleCuisine": "Nearby Cuisine",
|
||||||
"titleRecipes": "Selected Recipes"
|
"titleRecipes": "Selected Recipes"
|
||||||
},
|
},
|
||||||
@@ -228,7 +233,10 @@
|
|||||||
"mine": {
|
"mine": {
|
||||||
"activity-description": "Activity description",
|
"activity-description": "Activity description",
|
||||||
"activity-description-tip": "Share the QR code and invite new users to register successfully. Successful invitation",
|
"activity-description-tip": "Share the QR code and invite new users to register successfully. Successful invitation",
|
||||||
"collection": "Collection",
|
"collection": "My Collection",
|
||||||
|
"collectionBatchDeleteConfirm": "Remove {count} selected favorite(s)?",
|
||||||
|
"collectionBatchModeHint": "Select items to remove, then tap the trash again to delete",
|
||||||
|
"collectionSelectFirst": "Please select favorites to remove",
|
||||||
"complaintsAndSuggestions": "Complaints and suggestions",
|
"complaintsAndSuggestions": "Complaints and suggestions",
|
||||||
"customer-service-phone": "Customer service phone",
|
"customer-service-phone": "Customer service phone",
|
||||||
"dial": "Dial",
|
"dial": "Dial",
|
||||||
@@ -261,17 +269,22 @@
|
|||||||
"PU": "PU",
|
"PU": "PU",
|
||||||
"accept": "ACPT",
|
"accept": "ACPT",
|
||||||
"all": "ALL",
|
"all": "ALL",
|
||||||
|
"cancelOrder": "Cancel",
|
||||||
"completed": "DCOMP",
|
"completed": "DCOMP",
|
||||||
"confirmReady": "CONF/READY",
|
"confirmReady": "CONF/READY",
|
||||||
"onTheWay": "OTW",
|
"onTheWay": "OTW",
|
||||||
"title": "History"
|
"title": "History",
|
||||||
|
"totalItemCount": "{count} items"
|
||||||
},
|
},
|
||||||
"store": {
|
"store": {
|
||||||
"all": "All"
|
"all": "All"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hot-title": "Popular Categories",
|
"hot-title": "Hot searches",
|
||||||
"recently": "Recently",
|
"clear-history": "Clear",
|
||||||
|
"recently": "Search history",
|
||||||
|
"weekly-sales": "Weekly sales",
|
||||||
|
"member-price-line": "Member",
|
||||||
"result": {
|
"result": {
|
||||||
"food": "Food",
|
"food": "Food",
|
||||||
"recipe": "Recipe",
|
"recipe": "Recipe",
|
||||||
@@ -364,6 +377,36 @@
|
|||||||
"checkout": {
|
"checkout": {
|
||||||
"addAddress": "Add address",
|
"addAddress": "Add address",
|
||||||
"addCoupon": "Add coupon",
|
"addCoupon": "Add coupon",
|
||||||
|
"confirmOrder": "Confirm order",
|
||||||
|
"couponInputPlaceholder": "Enter coupon code",
|
||||||
|
"deliverBefore": "Arriving before {time}",
|
||||||
|
"deliveryInfo": "Delivery",
|
||||||
|
"driverTip": "Driver tip",
|
||||||
|
"driverTipNote": "100% of your tip goes to the driver.",
|
||||||
|
"fillAddressHint": "Add delivery address and contact",
|
||||||
|
"freeTag": "Free",
|
||||||
|
"localDelivery": "Local delivery",
|
||||||
|
"localDeliverySubtitle": "Delivered by {name}.",
|
||||||
|
"memberThanks": "Thanks for being a {name} member.",
|
||||||
|
"orderTotalLine": "Order total",
|
||||||
|
"orderBreakdown": "Order details",
|
||||||
|
"pay": "Pay",
|
||||||
|
"payMethodSection": "Payment method",
|
||||||
|
"pickupSubtitle": "Pick up at the store at your scheduled time.",
|
||||||
|
"pickupTitle": "Pickup",
|
||||||
|
"scheduledDeliveryHint": "Delivery fee refunded if we miss the promised window.",
|
||||||
|
"scheduledDeliveryTitle": "Scheduled delivery",
|
||||||
|
"scheduledDeliveryWindow": "By {time}",
|
||||||
|
"scheduledDeliveryAddon": "+ $ 2.99",
|
||||||
|
"scheduledDeliveryStandardTitle": "Standard delivery",
|
||||||
|
"scheduledDeliveryStandardDesc": "Delivered in your selected window. No rush fee.",
|
||||||
|
"serviceFee": "Service fee",
|
||||||
|
"shippingFee": "Delivery fee",
|
||||||
|
"subtotalBar": "Subtotal",
|
||||||
|
"subtotalOneLine": "Subtotal: $ {amount}",
|
||||||
|
"subtotalWithPieces": "Items total ({count})",
|
||||||
|
"itemsGoodsTotalWithPrice": "{count} items total ${amount}",
|
||||||
|
"useDiscount": "Discounts",
|
||||||
"appointmentDelivery": "Appointment delivery",
|
"appointmentDelivery": "Appointment delivery",
|
||||||
"appointmentPickup": "Appointment pickup",
|
"appointmentPickup": "Appointment pickup",
|
||||||
"chooseTime": "Choose the time",
|
"chooseTime": "Choose the time",
|
||||||
@@ -406,6 +449,7 @@
|
|||||||
"deliveryAddress": "Delivery address",
|
"deliveryAddress": "Delivery address",
|
||||||
"deliveryPhotos": "Delivery of photos",
|
"deliveryPhotos": "Delivery of photos",
|
||||||
"deliveryTime": "Delivery time",
|
"deliveryTime": "Delivery time",
|
||||||
|
"beforeDeadline": " before",
|
||||||
"estimatedDeliveryTime": "Estimated delivery time",
|
"estimatedDeliveryTime": "Estimated delivery time",
|
||||||
"orderInfo": "Ordering Information",
|
"orderInfo": "Ordering Information",
|
||||||
"orderNumber": "Order number",
|
"orderNumber": "Order number",
|
||||||
@@ -435,7 +479,11 @@
|
|||||||
"total": "Total",
|
"total": "Total",
|
||||||
"upTime": "Estimated self-pickup time",
|
"upTime": "Estimated self-pickup time",
|
||||||
"useCode": "Use code",
|
"useCode": "Use code",
|
||||||
"writeOff": "Meal Code"
|
"writeOff": "Meal Code",
|
||||||
|
"uploadPaidVoucher": "I've paid — upload proof",
|
||||||
|
"voucherSubmitSuccess": "Proof submitted",
|
||||||
|
"voucherSubmitFailed": "Submit failed, please try again",
|
||||||
|
"voucherUploadFailed": "Upload failed, please try again"
|
||||||
},
|
},
|
||||||
"store": {
|
"store": {
|
||||||
"addToCart": "Add to cart",
|
"addToCart": "Add to cart",
|
||||||
@@ -490,6 +538,23 @@
|
|||||||
"recommend": "Recommended for you",
|
"recommend": "Recommended for you",
|
||||||
"required": "Required",
|
"required": "Required",
|
||||||
"sales": "Sales",
|
"sales": "Sales",
|
||||||
|
"dishDetail": {
|
||||||
|
"productDetails": "Product details",
|
||||||
|
"productIntro": "Introduction",
|
||||||
|
"origin": "Origin",
|
||||||
|
"unitQty": "Unit qty.",
|
||||||
|
"category": "Category",
|
||||||
|
"brand": "Brand",
|
||||||
|
"allergen": "Allergen",
|
||||||
|
"weeklySales": "Weekly sales",
|
||||||
|
"forYou": "Recommended for you",
|
||||||
|
"memberPriceLabel": "{name} member price",
|
||||||
|
"dash": "—",
|
||||||
|
"lbUnit": "lb",
|
||||||
|
"hotSalesBadge": "Fresh picks",
|
||||||
|
"stockLabel": "Stock",
|
||||||
|
"loadMoreRecommend": "Load more"
|
||||||
|
},
|
||||||
"securityCode": {
|
"securityCode": {
|
||||||
"cantSee": "Can't see clearly? Change one",
|
"cantSee": "Can't see clearly? Change one",
|
||||||
"confirm": "Confirm"
|
"confirm": "Confirm"
|
||||||
@@ -557,7 +622,23 @@
|
|||||||
"title": "Cart",
|
"title": "Cart",
|
||||||
"totalPrice": "Total price",
|
"totalPrice": "Total price",
|
||||||
"viewCart": "View Shopping Cart",
|
"viewCart": "View Shopping Cart",
|
||||||
"viewStore": "View Store"
|
"viewStore": "View Store",
|
||||||
|
"localDelivery": "Local delivery",
|
||||||
|
"deliveryUnified": "Orders are delivered by {name}.",
|
||||||
|
"itemsTotalWithPrice": "{count} items total ${amount}",
|
||||||
|
"deliveryFeeLine": "Delivery",
|
||||||
|
"serviceFeeLine": "Service fee",
|
||||||
|
"subtotalLine": "Subtotal",
|
||||||
|
"selectAll": "Select all",
|
||||||
|
"forYou": "Recommended for you",
|
||||||
|
"calculating": "Calculating...",
|
||||||
|
"selectItemsHint": "Select items to checkout",
|
||||||
|
"removeCartTitle": "Delete shopping cart?",
|
||||||
|
"removeCartDesc": "Are you sure you want to delete this cart?",
|
||||||
|
"removeProductTitle": "Remove the product?",
|
||||||
|
"removeProductDesc": "Are you sure you want to remove {name} from your shopping cart?",
|
||||||
|
"emptyTitle": "Your cart is empty",
|
||||||
|
"emptyAction": "Explore Restaurants"
|
||||||
},
|
},
|
||||||
"choosePaymethod": {
|
"choosePaymethod": {
|
||||||
"creditCard": "Credit card payment",
|
"creditCard": "Credit card payment",
|
||||||
@@ -585,6 +666,7 @@
|
|||||||
"coupon": {
|
"coupon": {
|
||||||
"all-merchants": "Applicable to all merchants",
|
"all-merchants": "Applicable to all merchants",
|
||||||
"expiry-date": "Expiry date: ",
|
"expiry-date": "Expiry date: ",
|
||||||
|
"merchant-only": "Only available at {name}",
|
||||||
"merchant-specific": "For specific merchant use",
|
"merchant-specific": "For specific merchant use",
|
||||||
"no-coupons": "You currently do not have any coupons",
|
"no-coupons": "You currently do not have any coupons",
|
||||||
"redeem-now": "Redeem now",
|
"redeem-now": "Redeem now",
|
||||||
|
|||||||
+90
-8
@@ -171,7 +171,7 @@
|
|||||||
"officeDescription": "有进入限制的工作场所",
|
"officeDescription": "有进入限制的工作场所",
|
||||||
"other": "其他",
|
"other": "其他",
|
||||||
"otherDescription": "医院、公园、户外等",
|
"otherDescription": "医院、公园、户外等",
|
||||||
"title": "选择建筑类型"
|
"title": "建筑类型选择"
|
||||||
},
|
},
|
||||||
"deliveryInstructions": "配送说明",
|
"deliveryInstructions": "配送说明",
|
||||||
"deliveryPointInfo": "交货点信息",
|
"deliveryPointInfo": "交货点信息",
|
||||||
@@ -206,14 +206,19 @@
|
|||||||
"dateNotSelectable": "该日期不可选择",
|
"dateNotSelectable": "该日期不可选择",
|
||||||
"noAvailableTime": "当前日期无可用时间,已自动选择下一个营业日",
|
"noAvailableTime": "当前日期无可用时间,已自动选择下一个营业日",
|
||||||
"notAvailable": "不营业",
|
"notAvailable": "不营业",
|
||||||
|
"nextWeek": "下周",
|
||||||
|
"pageTitle": "送货日",
|
||||||
"reservationSuccess": "预约成功",
|
"reservationSuccess": "预约成功",
|
||||||
"selectTimeSlot": "请选择时间段"
|
"selectTimeSlot": "请选择时间段",
|
||||||
|
"thisWeek": "本周"
|
||||||
},
|
},
|
||||||
"savedAddresses": "保存的地址",
|
"savedAddresses": "保存的地址",
|
||||||
"title": "地址管理",
|
"title": "地址管理",
|
||||||
"titleDetail": "地址详情"
|
"titleDetail": "地址详情"
|
||||||
},
|
},
|
||||||
"browse": {
|
"browse": {
|
||||||
|
"brandTag": "CHEFLINK 严选",
|
||||||
|
"moreRecipes": "更多食谱",
|
||||||
"titleCuisine": "附近的美食",
|
"titleCuisine": "附近的美食",
|
||||||
"titleRecipes": "选择食谱"
|
"titleRecipes": "选择食谱"
|
||||||
},
|
},
|
||||||
@@ -228,7 +233,10 @@
|
|||||||
"mine": {
|
"mine": {
|
||||||
"activity-description": "活动说明:",
|
"activity-description": "活动说明:",
|
||||||
"activity-description-tip": "分享专属二维码邀请新用户注册即邀请成功。一起来注册体验点餐功能。",
|
"activity-description-tip": "分享专属二维码邀请新用户注册即邀请成功。一起来注册体验点餐功能。",
|
||||||
"collection": "收藏",
|
"collection": "我的收藏",
|
||||||
|
"collectionBatchDeleteConfirm": "确定删除已选中的 {count} 条收藏?",
|
||||||
|
"collectionBatchModeHint": "请选择要删除的收藏,再次点击垃圾桶执行删除",
|
||||||
|
"collectionSelectFirst": "请先选择要删除的收藏",
|
||||||
"complaintsAndSuggestions": "投诉建议",
|
"complaintsAndSuggestions": "投诉建议",
|
||||||
"customer-service-phone": "客服电话",
|
"customer-service-phone": "客服电话",
|
||||||
"dial": "拨号",
|
"dial": "拨号",
|
||||||
@@ -261,17 +269,22 @@
|
|||||||
"PU": "自取",
|
"PU": "自取",
|
||||||
"accept": "接受",
|
"accept": "接受",
|
||||||
"all": "全部",
|
"all": "全部",
|
||||||
|
"cancelOrder": "取消订单",
|
||||||
"completed": "已完成",
|
"completed": "已完成",
|
||||||
"confirmReady": "确认/已准备",
|
"confirmReady": "确认/已准备",
|
||||||
"onTheWay": "在路上",
|
"onTheWay": "在路上",
|
||||||
"title": "订单"
|
"title": "订单",
|
||||||
|
"totalItemCount": "共{count}件"
|
||||||
},
|
},
|
||||||
"store": {
|
"store": {
|
||||||
"all": "全部"
|
"all": "全部"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hot-title": "热搜",
|
"hot-title": "热门搜索",
|
||||||
"recently": "历史记录",
|
"clear-history": "清空",
|
||||||
|
"recently": "搜索记录",
|
||||||
|
"weekly-sales": "周销量",
|
||||||
|
"member-price-line": "会员价",
|
||||||
"result": {
|
"result": {
|
||||||
"food": "美食",
|
"food": "美食",
|
||||||
"recipe": "菜谱",
|
"recipe": "菜谱",
|
||||||
@@ -364,6 +377,36 @@
|
|||||||
"checkout": {
|
"checkout": {
|
||||||
"addAddress": "新增地址",
|
"addAddress": "新增地址",
|
||||||
"addCoupon": "添加优惠券",
|
"addCoupon": "添加优惠券",
|
||||||
|
"confirmOrder": "确认订单",
|
||||||
|
"couponInputPlaceholder": "请输入优惠券",
|
||||||
|
"deliverBefore": "将在 {time} 之前送达",
|
||||||
|
"deliveryInfo": "配送信息",
|
||||||
|
"driverTip": "司机小费",
|
||||||
|
"driverTipNote": "您所支付的小费 100% 归司机所有",
|
||||||
|
"fillAddressHint": "请填写您的送货地址及联系方式",
|
||||||
|
"freeTag": "免费",
|
||||||
|
"localDelivery": "本地配送",
|
||||||
|
"localDeliverySubtitle": "商品将由 {name} 统一配送!",
|
||||||
|
"memberThanks": "感谢您成为 {name} 会员。",
|
||||||
|
"orderTotalLine": "订单总计",
|
||||||
|
"orderBreakdown": "订单明细",
|
||||||
|
"pay": "付款",
|
||||||
|
"payMethodSection": "支付方式",
|
||||||
|
"pickupSubtitle": "请按预约时间到店取餐。",
|
||||||
|
"pickupTitle": "门店自取",
|
||||||
|
"scheduledDeliveryHint": "若未在承诺时间内送达,运费将予以退还。",
|
||||||
|
"scheduledDeliveryTitle": "定时送达服务",
|
||||||
|
"scheduledDeliveryWindow": "{time} 前送达",
|
||||||
|
"scheduledDeliveryAddon": "+ $ 2.99",
|
||||||
|
"scheduledDeliveryStandardTitle": "标准送达",
|
||||||
|
"scheduledDeliveryStandardDesc": "按预约时段配送,无加急费用。",
|
||||||
|
"serviceFee": "服务费",
|
||||||
|
"shippingFee": "运费",
|
||||||
|
"subtotalBar": "小计",
|
||||||
|
"subtotalOneLine": "小计: $ {amount}",
|
||||||
|
"subtotalWithPieces": "总计({count}件)",
|
||||||
|
"itemsGoodsTotalWithPrice": "{count}件商品总共${amount}",
|
||||||
|
"useDiscount": "使用优惠",
|
||||||
"appointmentDelivery": "预约配送",
|
"appointmentDelivery": "预约配送",
|
||||||
"appointmentPickup": "预约自取",
|
"appointmentPickup": "预约自取",
|
||||||
"chooseTime": "选择时间",
|
"chooseTime": "选择时间",
|
||||||
@@ -406,6 +449,7 @@
|
|||||||
"deliveryAddress": "配送地址",
|
"deliveryAddress": "配送地址",
|
||||||
"deliveryPhotos": "送达照片",
|
"deliveryPhotos": "送达照片",
|
||||||
"deliveryTime": "送达时间",
|
"deliveryTime": "送达时间",
|
||||||
|
"beforeDeadline": "前",
|
||||||
"estimatedDeliveryTime": "预计送达时间",
|
"estimatedDeliveryTime": "预计送达时间",
|
||||||
"orderInfo": "订单信息",
|
"orderInfo": "订单信息",
|
||||||
"orderNumber": "订单号",
|
"orderNumber": "订单号",
|
||||||
@@ -435,7 +479,11 @@
|
|||||||
"total": "总计",
|
"total": "总计",
|
||||||
"upTime": "预计自取时间",
|
"upTime": "预计自取时间",
|
||||||
"useCode": "核销码",
|
"useCode": "核销码",
|
||||||
"writeOff": "核销"
|
"writeOff": "核销",
|
||||||
|
"uploadPaidVoucher": "我已支付,上传凭证",
|
||||||
|
"voucherSubmitSuccess": "凭证已提交",
|
||||||
|
"voucherSubmitFailed": "提交失败,请重试",
|
||||||
|
"voucherUploadFailed": "上传失败,请重试"
|
||||||
},
|
},
|
||||||
"store": {
|
"store": {
|
||||||
"addToCart": "加入购物车",
|
"addToCart": "加入购物车",
|
||||||
@@ -490,6 +538,23 @@
|
|||||||
"recommend": "推荐给您",
|
"recommend": "推荐给您",
|
||||||
"required": "必选项",
|
"required": "必选项",
|
||||||
"sales": "销量",
|
"sales": "销量",
|
||||||
|
"dishDetail": {
|
||||||
|
"productDetails": "产品详情",
|
||||||
|
"productIntro": "产品简介",
|
||||||
|
"origin": "产地",
|
||||||
|
"unitQty": "单位数量",
|
||||||
|
"category": "分类",
|
||||||
|
"brand": "品牌",
|
||||||
|
"allergen": "过敏源",
|
||||||
|
"weeklySales": "周销量",
|
||||||
|
"forYou": "为您推荐",
|
||||||
|
"memberPriceLabel": "{name}会员价",
|
||||||
|
"dash": "—",
|
||||||
|
"lbUnit": "磅",
|
||||||
|
"hotSalesBadge": "生鲜热销榜",
|
||||||
|
"stockLabel": "库存",
|
||||||
|
"loadMoreRecommend": "加载更多"
|
||||||
|
},
|
||||||
"securityCode": {
|
"securityCode": {
|
||||||
"cantSee": "看不清?换一张",
|
"cantSee": "看不清?换一张",
|
||||||
"confirm": "确定"
|
"confirm": "确定"
|
||||||
@@ -557,7 +622,23 @@
|
|||||||
"title": "购物车",
|
"title": "购物车",
|
||||||
"totalPrice": "总计",
|
"totalPrice": "总计",
|
||||||
"viewCart": "查看购物车",
|
"viewCart": "查看购物车",
|
||||||
"viewStore": "查看店铺"
|
"viewStore": "查看店铺",
|
||||||
|
"localDelivery": "本地配送",
|
||||||
|
"deliveryUnified": "商品将由 {name} 统一配送!",
|
||||||
|
"itemsTotalWithPrice": "{count}件商品总共${amount}",
|
||||||
|
"deliveryFeeLine": "运费",
|
||||||
|
"serviceFeeLine": "服务费",
|
||||||
|
"subtotalLine": "小计",
|
||||||
|
"selectAll": "全选",
|
||||||
|
"forYou": "为您推荐",
|
||||||
|
"calculating": "计算中...",
|
||||||
|
"selectItemsHint": "请选择要结算的商品",
|
||||||
|
"removeCartTitle": "删除购物车?",
|
||||||
|
"removeCartDesc": "确定要删除该店铺购物车吗?",
|
||||||
|
"removeProductTitle": "移除商品?",
|
||||||
|
"removeProductDesc": "确定要将 {name} 从购物车中移除吗?",
|
||||||
|
"emptyTitle": "购物车为空",
|
||||||
|
"emptyAction": "去逛逛餐厅"
|
||||||
},
|
},
|
||||||
"choosePaymethod": {
|
"choosePaymethod": {
|
||||||
"creditCard": "信用卡支付",
|
"creditCard": "信用卡支付",
|
||||||
@@ -585,6 +666,7 @@
|
|||||||
"coupon": {
|
"coupon": {
|
||||||
"all-merchants": "适用于所有商户使用",
|
"all-merchants": "适用于所有商户使用",
|
||||||
"expiry-date": "到期日:",
|
"expiry-date": "到期日:",
|
||||||
|
"merchant-only": "仅{name}可用",
|
||||||
"merchant-specific": "指定商户使用",
|
"merchant-specific": "指定商户使用",
|
||||||
"no-coupons": "您目前没有任何优惠券",
|
"no-coupons": "您目前没有任何优惠券",
|
||||||
"redeem-now": "立即兑换",
|
"redeem-now": "立即兑换",
|
||||||
|
|||||||
+9
-3
@@ -2,8 +2,8 @@
|
|||||||
"name" : "CHEFLINK delivery",
|
"name" : "CHEFLINK delivery",
|
||||||
"appid" : "__UNI__06509BE",
|
"appid" : "__UNI__06509BE",
|
||||||
"description" : "",
|
"description" : "",
|
||||||
"versionName" : "1.0.29",
|
"versionName" : "3.1.2",
|
||||||
"versionCode" : 129,
|
"versionCode" : 312,
|
||||||
"transformPx" : false,
|
"transformPx" : false,
|
||||||
/* 5+App特有相关 */
|
/* 5+App特有相关 */
|
||||||
"app-plus" : {
|
"app-plus" : {
|
||||||
@@ -203,5 +203,11 @@
|
|||||||
"enable" : false
|
"enable" : false
|
||||||
},
|
},
|
||||||
"vueVersion" : "3",
|
"vueVersion" : "3",
|
||||||
"locale" : "en"
|
"locale" : "en",
|
||||||
|
"h5" : {
|
||||||
|
"router" : {
|
||||||
|
"mode" : "history",
|
||||||
|
"base" : "/h5/"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const {t} = useI18n()
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
const logicStore = useLogicStore()
|
const logicStore = useLogicStore()
|
||||||
|
const invitationCode = ref('')
|
||||||
|
|
||||||
const areaCode = ref<string>(logicStore.registerForm.areaCode || '+1');
|
const areaCode = ref<string>(logicStore.registerForm.areaCode || '+1');
|
||||||
const columns = ref<string[]>(Config.phoneCodeList);
|
const columns = ref<string[]>(Config.phoneCodeList);
|
||||||
@@ -85,8 +86,7 @@ function codeSubmit() {
|
|||||||
confirmLoginPwd,
|
confirmLoginPwd,
|
||||||
...rest
|
...rest
|
||||||
} = logicStore.registerForm as any
|
} = logicStore.registerForm as any
|
||||||
appUserRegisterPost({
|
const requestBody: Record<string, any> = {
|
||||||
body: {
|
|
||||||
...rest,
|
...rest,
|
||||||
// 后端接收“确认密码”字段名为 newPwd
|
// 后端接收“确认密码”字段名为 newPwd
|
||||||
newPwd: confirmLoginPwd,
|
newPwd: confirmLoginPwd,
|
||||||
@@ -94,7 +94,24 @@ function codeSubmit() {
|
|||||||
// captcha: data.code,
|
// captcha: data.code,
|
||||||
// uuid: data.uuid,
|
// uuid: data.uuid,
|
||||||
userPort: Config.userPort, // 登录端口2 商户端
|
userPort: Config.userPort, // 登录端口2 商户端
|
||||||
} as any
|
}
|
||||||
|
if (invitationCode.value) {
|
||||||
|
requestBody.invitationCode = invitationCode.value
|
||||||
|
}
|
||||||
|
if (requestBody.email == null || requestBody.email === '') {
|
||||||
|
delete requestBody.email
|
||||||
|
}
|
||||||
|
const phoneVal = requestBody.phone
|
||||||
|
if (
|
||||||
|
phoneVal == null ||
|
||||||
|
phoneVal === '' ||
|
||||||
|
(typeof phoneVal === 'string' && phoneVal.trim() === '')
|
||||||
|
) {
|
||||||
|
delete requestBody.phone
|
||||||
|
delete requestBody.areaCode
|
||||||
|
}
|
||||||
|
appUserRegisterPost({
|
||||||
|
body: requestBody as any
|
||||||
}).then((res) => {
|
}).then((res) => {
|
||||||
userStore.token = res.data.token;
|
userStore.token = res.data.token;
|
||||||
logicStore.reset()
|
logicStore.reset()
|
||||||
@@ -142,6 +159,15 @@ function navigateTo(url: string) {
|
|||||||
|
|
||||||
const emailIsReadonly = ref(false)
|
const emailIsReadonly = ref(false)
|
||||||
const phoneIsReadonly = ref(false)
|
const phoneIsReadonly = ref(false)
|
||||||
|
onLoad((options?: Record<string, any>) => {
|
||||||
|
const codeRaw = options?.invitationCode
|
||||||
|
const code = typeof codeRaw === 'string' ? decodeURIComponent(codeRaw).trim() : ''
|
||||||
|
invitationCode.value = code
|
||||||
|
if (code) {
|
||||||
|
userStore.invitationCode = code
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const {email, confirmEmail, phone, type} = logicStore.registerForm
|
const {email, confirmEmail, phone, type} = logicStore.registerForm
|
||||||
if (type === 'phone') {
|
if (type === 'phone') {
|
||||||
@@ -374,3 +400,4 @@ page {
|
|||||||
height: 94rpx !important;
|
height: 94rpx !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -51,11 +51,16 @@ function navigateTo(url: string) {
|
|||||||
<view class="grid grid-cols-2 gap-x-30rpx gap-y-46rpx px-32rpx">
|
<view class="grid grid-cols-2 gap-x-30rpx gap-y-46rpx px-32rpx">
|
||||||
<template v-for="(item,index) in dataList">
|
<template v-for="(item,index) in dataList">
|
||||||
<view @click="handleClickDish(item)" class="w-330rpx overflow-hidden">
|
<view @click="handleClickDish(item)" class="w-330rpx overflow-hidden">
|
||||||
|
<view class="search-result-img-wrap">
|
||||||
|
<view v-if="item.isNew == 1" class="dish-new-ribbon">
|
||||||
|
<text class="dish-new-ribbon__text">NEW</text>
|
||||||
|
</view>
|
||||||
<image
|
<image
|
||||||
:src="thumbnailImg(item?.dishImage?.split(',')[0])"
|
:src="thumbnailImg(item?.dishImage?.split(',')[0])"
|
||||||
class="w-full h-186rpx rounded-24rpx mb-16rpx"
|
class="w-full h-186rpx rounded-24rpx"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
></image>
|
></image>
|
||||||
|
</view>
|
||||||
<text class="text-30rpx lh-30rpx text-#333 line-clamp-1 tracking-[.04em] font-500"
|
<text class="text-30rpx lh-30rpx text-#333 line-clamp-1 tracking-[.04em] font-500"
|
||||||
>{{ item.dishName }}</text>
|
>{{ item.dishName }}</text>
|
||||||
</view>
|
</view>
|
||||||
@@ -65,5 +70,36 @@ function navigateTo(url: string) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.search-result-img-wrap {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,25 +1,24 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import usePage from "@/hooks/usePage";
|
import usePage from "@/hooks/usePage";
|
||||||
import {appMerchantRecommendListPost} from "@/service";
|
import { appMerchantRecommendListPost, type MerchantVo } from "@/service";
|
||||||
import { useUserStore } from "@/store";
|
import { useUserStore } from "@/store";
|
||||||
import FiltrateTool from "@/components/filtrate-tool/index.vue";
|
import FiltrateTool from "@/components/filtrate-tool/index.vue";
|
||||||
import PriceChoose from "@/components/filtrate-tool/components/price-choose.vue";
|
import PriceChoose from "@/components/filtrate-tool/components/price-choose.vue";
|
||||||
import Score from "@/components/filtrate-tool/components/score.vue";
|
import Score from "@/components/filtrate-tool/components/score.vue";
|
||||||
import FoodBox from "@/pages/home/components/tabbar-home/components/food-box/index.vue";
|
|
||||||
const userStore = useUserStore();
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
const userStore = useUserStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
merchantCategoryIds: {
|
merchantCategoryIds: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
default: null
|
default: null,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const {paging, dataList, queryList} = usePage(getList)
|
const { paging, dataList, queryList } = usePage<MerchantVo>(getList);
|
||||||
function getList(pageNum: number, pageSize: number) {
|
function getList(pageNum: number, pageSize: number) {
|
||||||
return new Promise(resolve => {
|
return new Promise((resolve) => {
|
||||||
appMerchantRecommendListPost({
|
appMerchantRecommendListPost({
|
||||||
params: {
|
params: {
|
||||||
pageNum: pageNum,
|
pageNum: pageNum,
|
||||||
@@ -28,75 +27,344 @@ function getList(pageNum: number, pageSize: number) {
|
|||||||
body: {
|
body: {
|
||||||
lat: userStore.userLocation.latitude,
|
lat: userStore.userLocation.latitude,
|
||||||
lng: userStore.userLocation.longitude,
|
lng: userStore.userLocation.longitude,
|
||||||
selfPickup: selfPickup.value, // 是否自提
|
selfPickup: selfPickup.value,
|
||||||
discount: discount.value, // 是否有折扣 1是 2 否
|
discount: discount.value,
|
||||||
scoreRange: scoreRange.value || null, // 评分范围 比如 3-4
|
scoreRange: scoreRange.value || null,
|
||||||
priceRange: price.value || null, // 价格范围 比如 10-30
|
priceRange: price.value || null,
|
||||||
merchantCategoryIds: props.merchantCategoryIds ? [props.merchantCategoryIds] : [], // 商家分类id集合(首页中间)
|
merchantCategoryIds: props.merchantCategoryIds
|
||||||
}
|
? [props.merchantCategoryIds]
|
||||||
}).then(res => {
|
: [],
|
||||||
console.log(res)
|
},
|
||||||
resolve({rows: res.rows})
|
}).then((res) => {
|
||||||
})
|
console.log(res);
|
||||||
})
|
resolve({ rows: res.rows });
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否自提
|
const selfPickup = ref<number | null>(null);
|
||||||
const selfPickup = ref<number | null>(null)
|
|
||||||
function togglePickup(value: number) {
|
function togglePickup(value: number) {
|
||||||
selfPickup.value = value;
|
selfPickup.value = value;
|
||||||
paging.value.refresh()
|
paging.value?.refresh();
|
||||||
}
|
|
||||||
// 是否有折扣
|
|
||||||
const discount = ref<number | null>(null)
|
|
||||||
function toggleDiscount(value: number) {
|
|
||||||
discount.value = value;
|
|
||||||
paging.value.refresh()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const scoreRef = ref<any>()
|
const discount = ref<number | null>(null);
|
||||||
const scoreRange = ref<string | null>(null)
|
function toggleDiscount(value: number) {
|
||||||
const priceChooseRef = ref<any>()
|
discount.value = value;
|
||||||
const price = ref<string | null>(null)
|
paging.value?.refresh();
|
||||||
function toggleScore() {
|
|
||||||
if (scoreRef.value) {
|
|
||||||
scoreRef.value.onOpen();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scoreRef = ref<any>();
|
||||||
|
const scoreRange = ref<string | null>(null);
|
||||||
|
const priceChooseRef = ref<any>();
|
||||||
|
const price = ref<string | null>(null);
|
||||||
|
function toggleScore() {
|
||||||
|
scoreRef.value?.onOpen();
|
||||||
}
|
}
|
||||||
function togglePrice() {
|
function togglePrice() {
|
||||||
if (priceChooseRef.value) {
|
priceChooseRef.value?.onOpen();
|
||||||
priceChooseRef.value.onOpen();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyScore(value: string) {
|
function applyScore(value: string) {
|
||||||
scoreRange.value = value;
|
scoreRange.value = value;
|
||||||
|
nextTick(() => paging.value?.refresh());
|
||||||
}
|
}
|
||||||
function applyPrice(value: string) {
|
function applyPrice(value: string) {
|
||||||
price.value = value;
|
price.value = value;
|
||||||
|
nextTick(() => paging.value?.refresh());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 左侧双图:优先店铺图,不足则用 logo 补齐 */
|
||||||
|
function merchantImagePair(item: MerchantVo): [string, string] {
|
||||||
|
const shop = (item.shopImages || "")
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const logo = (item.logo || "").trim();
|
||||||
|
let a = shop[0] || logo || "";
|
||||||
|
let b = shop[1] || shop[0] || logo || "";
|
||||||
|
if (!a && !b) return ["", ""];
|
||||||
|
if (!b) b = a;
|
||||||
|
if (!a) a = b;
|
||||||
|
return [a, b];
|
||||||
|
}
|
||||||
|
|
||||||
|
function merchantCategoryLine(item: MerchantVo): string {
|
||||||
|
const zh = item.merchantCategoryNamesZh;
|
||||||
|
const en = item.merchantCategoryNamesEn;
|
||||||
|
if (uni.getLocale() === "zh-Hans" && zh?.length) return zh[0] || "";
|
||||||
|
if (en?.length) return en[0] || "";
|
||||||
|
if (zh?.length) return zh[0] || "";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function ratingStars(rating?: number) {
|
||||||
|
const n = Math.min(5, Math.max(0, Math.round(Number(rating) || 0)));
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ratingText(rating?: number) {
|
||||||
|
const n = Number(rating);
|
||||||
|
if (Number.isFinite(n)) return n.toFixed(1);
|
||||||
|
return "0.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openMerchant(item: MerchantVo) {
|
||||||
|
if (item?.id == null) return;
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages-store/pages/store/index?id=${item.id}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<z-paging ref="paging" v-model="dataList" @query="queryList">
|
<view class="home-store-page">
|
||||||
|
<z-paging
|
||||||
|
ref="paging"
|
||||||
|
v-model="dataList"
|
||||||
|
bg-color="#F2F3F5"
|
||||||
|
@query="queryList"
|
||||||
|
>
|
||||||
<template #top>
|
<template #top>
|
||||||
<view class="bg-white pb-24rpx">
|
<view class="home-store-header">
|
||||||
<navbar :title="t('pages.home.featured-on')"/>
|
<navbar
|
||||||
<!-- 筛选工具 -->
|
:title="t('pages.home.featured-on')"
|
||||||
<filtrate-tool class="mt-32rpx" @togglePickup="togglePickup" @toggleDiscount="toggleDiscount" @toggleScore="toggleScore" @togglePrice="togglePrice" />
|
circle-back
|
||||||
|
custom-class="!bg-white"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="home-store-filter-strip">
|
||||||
|
<filtrate-tool
|
||||||
|
class="home-store-filters"
|
||||||
|
@togglePickup="togglePickup"
|
||||||
|
@toggleDiscount="toggleDiscount"
|
||||||
|
@toggleScore="toggleScore"
|
||||||
|
@togglePrice="togglePrice"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<view class="p-30rpx">
|
|
||||||
<template v-for="(item, index) in dataList" :key="index">
|
<view class="home-store-list">
|
||||||
<food-box :item="item" />
|
<view
|
||||||
</template>
|
v-for="(item, index) in dataList"
|
||||||
|
:key="item.id ?? index"
|
||||||
|
class="merchant-card"
|
||||||
|
@click="openMerchant(item)"
|
||||||
|
>
|
||||||
|
<view class="merchant-card__imgs">
|
||||||
|
<view
|
||||||
|
v-for="(src, ii) in merchantImagePair(item)"
|
||||||
|
:key="ii"
|
||||||
|
class="merchant-card__img-slot"
|
||||||
|
:class="
|
||||||
|
ii === 0
|
||||||
|
? 'merchant-card__img-slot--first'
|
||||||
|
: 'merchant-card__img-slot--last'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
v-if="src"
|
||||||
|
class="merchant-card__img"
|
||||||
|
:src="src"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
<view v-else class="merchant-card__img merchant-card__img--empty" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="merchant-card__body">
|
||||||
|
<text class="merchant-card__name">{{ item.merchantName }}</text>
|
||||||
|
<text
|
||||||
|
v-if="merchantCategoryLine(item)"
|
||||||
|
class="merchant-card__cate"
|
||||||
|
>{{ merchantCategoryLine(item) }}</text
|
||||||
|
>
|
||||||
|
<view class="merchant-card__rating">
|
||||||
|
<text
|
||||||
|
v-for="si in [1, 2, 3, 4, 5]"
|
||||||
|
:key="si"
|
||||||
|
class="merchant-card__star"
|
||||||
|
:class="{
|
||||||
|
'merchant-card__star--on': si <= ratingStars(item.rating),
|
||||||
|
}"
|
||||||
|
>★</text
|
||||||
|
>
|
||||||
|
<text class="merchant-card__score">{{
|
||||||
|
ratingText(item.rating)
|
||||||
|
}}</text>
|
||||||
|
</view>
|
||||||
|
<text class="merchant-card__tag">{{ t("pages.home.brandTag") }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="merchant-card__go" @click.stop="openMerchant(item)">
|
||||||
|
<view class="i-carbon:chevron-right merchant-card__go-icon"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</z-paging>
|
</z-paging>
|
||||||
|
|
||||||
<score @applyScore="applyScore" ref="scoreRef" />
|
<score @applyScore="applyScore" ref="scoreRef" />
|
||||||
<price-choose @applyPrice="applyPrice" ref="priceChooseRef" />
|
<price-choose @applyPrice="applyPrice" ref="priceChooseRef" />
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.home-store-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-store-header {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-store-filter-strip {
|
||||||
|
background: #f2f3f5;
|
||||||
|
padding-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-store-filters {
|
||||||
|
display: block;
|
||||||
|
padding: 16rpx 0 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-store-list {
|
||||||
|
padding: 16rpx 24rpx 40rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__imgs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12rpx;
|
||||||
|
width: 152rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__img-slot {
|
||||||
|
width: 152rpx;
|
||||||
|
height: 96rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 仅外侧圆角;靠两张图之间的分割一侧为直角 */
|
||||||
|
.merchant-card__img-slot--first {
|
||||||
|
border-radius: 12rpx 12rpx 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__img-slot--last {
|
||||||
|
border-radius: 0 0 12rpx 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
background: #e8eaed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__img--empty {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0 20rpx 0 8rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__name {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #14181b;
|
||||||
|
line-height: 1.35;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__cate {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.3;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__rating {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__star {
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 1;
|
||||||
|
color: #e0e0e0;
|
||||||
|
letter-spacing: -2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__star--on {
|
||||||
|
color: #14181b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__score {
|
||||||
|
margin-left: 10rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #14181b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__tag {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #b8860b;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__go {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #14181b;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merchant-card__go-icon {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #fff;
|
||||||
|
margin-left: 2rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
page {
|
||||||
|
background-color: #f2f3f5;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,61 +1,751 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {appMerchantRecommendListPost,getDishListByCategoryId} from "@/service";
|
import { debounce } from "throttle-debounce";
|
||||||
import FoodBox from "@/pages/home/components/tabbar-home/components/food-box/index.vue";
|
import Config from "@/config";
|
||||||
import {useUserStore} from "@/store";
|
import usePage from "@/hooks/usePage";
|
||||||
|
import {
|
||||||
|
appRecipeCategoryListGet,
|
||||||
|
appCollectCollectPost,
|
||||||
|
appMerchantCartAddCartPost,
|
||||||
|
getDishListByCategoryId,
|
||||||
|
} from "@/service";
|
||||||
|
import { CollectionType } from "@/constant/enums";
|
||||||
|
import {
|
||||||
|
useCategoryNavStore,
|
||||||
|
useConfigStore,
|
||||||
|
useUserStore,
|
||||||
|
} from "@/store";
|
||||||
|
import { formatSalesCount } from "@/utils/utils";
|
||||||
|
import { onShow } from "@dcloudio/uni-app";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const configStore = useConfigStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const categoryNavStore = useCategoryNavStore();
|
||||||
|
|
||||||
const props = defineProps<{
|
/** 菜品瀑布流项(接口字段较多,用宽松类型避免模板断言) */
|
||||||
id?: string
|
type DishGridItem = Record<string, any>;
|
||||||
}>()
|
|
||||||
|
/** 菜谱分类(与首页 class-bullet 同源:appRecipeCategoryListGet) */
|
||||||
|
const recipeCategoryList = ref<Record<string, unknown>[]>([]);
|
||||||
|
/** 当前选中的菜谱分类 id,仅由用户点击大分类或首页写入的 pending 决定,不用路由 options */
|
||||||
|
const recipeCategoryId = ref<string>("");
|
||||||
|
|
||||||
|
/** 横向分类 scroll-view 滚动到选中项(与 :id="'recipe-cat-'+id" 对应) */
|
||||||
|
const recipeCategoryScrollInto = ref("");
|
||||||
|
|
||||||
|
const pageReady = ref(false);
|
||||||
|
|
||||||
|
const { paging, dataList, queryList } = usePage(getList);
|
||||||
|
|
||||||
function getList(pageNum: number, pageSize: number) {
|
function getList(pageNum: number, pageSize: number) {
|
||||||
return new Promise(resolve => {
|
return new Promise<{ rows: unknown[]; total?: number }>((resolve) => {
|
||||||
|
const rid = recipeCategoryId.value;
|
||||||
|
if (!rid) {
|
||||||
|
resolve({ rows: [], total: 0 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
getDishListByCategoryId({
|
getDishListByCategoryId({
|
||||||
params: {
|
params: {
|
||||||
pageNum,
|
pageNum,
|
||||||
pageSize,
|
pageSize,
|
||||||
recipeCategoryId:props.id
|
recipeCategoryId: rid,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
lat: userStore.userLocation.latitude,
|
pageNum,
|
||||||
lng: userStore.userLocation.longitude,
|
pageSize,
|
||||||
selfPickup: null, // 是否自提
|
lat: userStore.userLocation.latitude || undefined,
|
||||||
discount: null, // 是否有折扣 1是 2 否
|
lng: userStore.userLocation.longitude || undefined,
|
||||||
scoreRange: null, // 评分范围 比如 3-4
|
recipeCategoryId: rid,
|
||||||
priceRange: null, // 价格范围 比如 10-30
|
|
||||||
merchantCategoryIds: [],
|
merchantCategoryIds: [],
|
||||||
merchantLabelIds: props.id ? [props.id] : [],
|
merchantLabelIds: [],
|
||||||
recipeCategoryId:props.id
|
|
||||||
},
|
},
|
||||||
}).then(res => {
|
|
||||||
resolve(res.data)
|
|
||||||
})
|
})
|
||||||
|
.then((res: any) => {
|
||||||
|
resolve({
|
||||||
|
rows: res?.rows ?? res?.data?.rows ?? [],
|
||||||
|
total: res?.total ?? res?.data?.total,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
.catch(() => resolve({ rows: [] }));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const {paging, loading, firstLoaded, dataList, queryList} = usePage(getList)
|
const dishColumns = computed(() => {
|
||||||
|
const list = (dataList.value || []) as DishGridItem[];
|
||||||
|
return [
|
||||||
|
list.filter((_, i) => i % 2 === 0),
|
||||||
|
list.filter((_, i) => i % 2 === 1),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
function getDishPromoLabel(item: DishGridItem): string {
|
||||||
|
const raw =
|
||||||
|
item.marketingLabel ??
|
||||||
|
item.hotSaleTag ??
|
||||||
|
item.rankTag ??
|
||||||
|
item.promotionLabel;
|
||||||
|
return typeof raw === "string" && raw.trim() ? raw.trim() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSoldOutStock(stockLike: unknown) {
|
||||||
|
const n = Number(stockLike);
|
||||||
|
return !Number.isNaN(n) && n <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToDishes(item: DishGridItem) {
|
||||||
|
const id = item?.id;
|
||||||
|
const storeId = item?.merchantId;
|
||||||
|
if (id == null || storeId == null) return;
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages-store/pages/store/dishes?id=${id}&storeId=${storeId}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function goSearch() {
|
||||||
|
uni.navigateTo({ url: "/pages/search/index" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function goCart() {
|
||||||
|
uni.navigateTo({ url: "/pages-user/pages/cart/index" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBack() {
|
||||||
|
const pages = getCurrentPages?.() || [];
|
||||||
|
if (pages.length <= 1) {
|
||||||
|
uni.switchTab({ url: "/pages/home/index" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.navigateBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
const cartBadgeTotal = computed(() => {
|
||||||
|
const list = userStore.userCartAllData;
|
||||||
|
if (!Array.isArray(list) || list.length === 0) return 0;
|
||||||
|
let n = 0;
|
||||||
|
for (const m of list) {
|
||||||
|
n +=
|
||||||
|
(m as { merchantCartVoList?: unknown[] })?.merchantCartVoList
|
||||||
|
?.length || 0;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
|
||||||
|
const addingDishId = ref<string | number | null>(null);
|
||||||
|
|
||||||
|
async function onAddCartClick(item: DishGridItem) {
|
||||||
|
if (isSoldOutStock(item?.stock)) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t("common.prompt.soldOut"),
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!userStore.isLogin) {
|
||||||
|
uni.showToast({ title: t("common.pleaseLogin"), icon: "none" });
|
||||||
|
setTimeout(() => uni.navigateTo({ url: Config.loginPath }), 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dishId = item?.id;
|
||||||
|
const merchantId = item?.merchantId;
|
||||||
|
if (dishId == null || merchantId == null) return;
|
||||||
|
if (addingDishId.value != null) return;
|
||||||
|
addingDishId.value = dishId as string | number;
|
||||||
|
try {
|
||||||
|
await appMerchantCartAddCartPost({
|
||||||
|
body: {
|
||||||
|
merchantId,
|
||||||
|
dishId,
|
||||||
|
count: 1,
|
||||||
|
merchantCartSideDishBoList: [],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
uni.showToast({ title: t("toast.addCartSuccess"), icon: "none" });
|
||||||
|
userStore.getUserCartAllData();
|
||||||
|
} finally {
|
||||||
|
addingDishId.value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedCollect = debounce(
|
||||||
|
1300,
|
||||||
|
(
|
||||||
|
isCollected: boolean,
|
||||||
|
id: string,
|
||||||
|
type: CollectionType,
|
||||||
|
cb: () => void
|
||||||
|
) => {
|
||||||
|
appCollectCollectPost({
|
||||||
|
body: { targetId: id, targetType: type },
|
||||||
|
}).then(() => cb());
|
||||||
|
},
|
||||||
|
{ atBegin: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleDishCollectionClick(item: DishGridItem) {
|
||||||
|
if (!userStore.isLogin) {
|
||||||
|
uni.showToast({ title: t("common.pleaseLogin"), icon: "none" });
|
||||||
|
setTimeout(() => uni.navigateTo({ url: Config.loginPath }), 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const id = String(item?.id ?? "");
|
||||||
|
if (!id) return;
|
||||||
|
debouncedCollect(Boolean(item.isCollect), id, CollectionType.DISH, () => {
|
||||||
|
item.isCollect = !item.isCollect;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCategoryId(v: unknown): string {
|
||||||
|
if (v === null || v === undefined) return "";
|
||||||
|
return String(v).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 与列表项 id 比对(兼容数字 / 字符串) */
|
||||||
|
function categoryRowIdEquals(
|
||||||
|
cat: Record<string, unknown>,
|
||||||
|
target: string
|
||||||
|
): boolean {
|
||||||
|
const t = normalizeCategoryId(target);
|
||||||
|
if (!t) return false;
|
||||||
|
const a = normalizeCategoryId(cat.id);
|
||||||
|
if (a === t) return true;
|
||||||
|
const na = Number(a);
|
||||||
|
const nt = Number(t);
|
||||||
|
if (!Number.isNaN(na) && !Number.isNaN(nt) && na === nt) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 在接口返回的分类列表中解析应选中的 id(与首页点击项对齐) */
|
||||||
|
function resolveRecipeCategoryIdFromPending(pendingRaw: string | null): string {
|
||||||
|
const list = recipeCategoryList.value;
|
||||||
|
if (!list.length) return "";
|
||||||
|
|
||||||
|
const pending = pendingRaw ? normalizeCategoryId(pendingRaw) : "";
|
||||||
|
if (pending) {
|
||||||
|
const hit = list.find((c) => categoryRowIdEquals(c, pending));
|
||||||
|
if (hit != null) return normalizeCategoryId(hit.id);
|
||||||
|
}
|
||||||
|
return normalizeCategoryId(list[0].id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollRecipeTabIntoView() {
|
||||||
|
const id = recipeCategoryId.value;
|
||||||
|
if (!id) return;
|
||||||
|
recipeCategoryScrollInto.value = "";
|
||||||
|
nextTick(() => {
|
||||||
|
recipeCategoryScrollInto.value = `recipe-cat-${id}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSelectRecipe(cat: Record<string, unknown>) {
|
||||||
|
const id = normalizeCategoryId(cat?.id);
|
||||||
|
if (!id || id === recipeCategoryId.value) return;
|
||||||
|
recipeCategoryId.value = id;
|
||||||
|
nextTick(() => scrollRecipeTabIntoView());
|
||||||
|
paging.value?.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadRecipeCategories() {
|
||||||
|
const recipeRes: any = await appRecipeCategoryListGet({});
|
||||||
|
recipeCategoryList.value = recipeRes?.data || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveInitialRecipeCategoryId() {
|
||||||
|
const pending = categoryNavStore.consumePendingRecipeCategoryId();
|
||||||
|
recipeCategoryId.value = resolveRecipeCategoryIdFromPending(pending);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await loadRecipeCategories();
|
||||||
|
resolveInitialRecipeCategoryId();
|
||||||
|
pageReady.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
scrollRecipeTabIntoView();
|
||||||
|
paging.value?.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
userStore.getUserCartAllData();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<z-paging
|
<z-paging
|
||||||
ref="paging"
|
ref="paging"
|
||||||
v-model="dataList"
|
v-model="dataList"
|
||||||
|
:auto="false"
|
||||||
@query="queryList"
|
@query="queryList"
|
||||||
bg-color="#fff"
|
bg-color="#f5f5f5"
|
||||||
>
|
>
|
||||||
<template #top>
|
<template #top>
|
||||||
<navbar />
|
<view class="cat-top" :style="{ paddingTop: configStore.statusBarHeight + 'px' }">
|
||||||
|
<view class="cat-header-row">
|
||||||
|
<view class="cat-icon-btn" @click="handleBack">
|
||||||
|
<view class="i-carbon:chevron-left text-40rpx text-#111" />
|
||||||
|
</view>
|
||||||
|
<view class="cat-search" @click="goSearch">
|
||||||
|
<image
|
||||||
|
src="@img/chef/100222.png"
|
||||||
|
class="cat-search-icon"
|
||||||
|
mode="aspectFit"
|
||||||
|
/>
|
||||||
|
<text class="cat-search-placeholder">{{
|
||||||
|
t("components.search.placeholder")
|
||||||
|
}}</text>
|
||||||
|
</view>
|
||||||
|
<view class="cat-icon-btn cat-icon-btn--cart" @click="goCart">
|
||||||
|
<view class="i-carbon:shopping-cart text-34rpx text-#14181b" />
|
||||||
|
<view
|
||||||
|
v-if="userStore.isLogin && cartBadgeTotal > 0"
|
||||||
|
class="cat-cart-badge"
|
||||||
|
>{{ cartBadgeTotal > 99 ? "99+" : cartBadgeTotal }}</view
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 第一行:菜谱分类(圆图 + 文案) -->
|
||||||
|
<scroll-view
|
||||||
|
class="cat-scroll-x"
|
||||||
|
scroll-x
|
||||||
|
scroll-with-animation
|
||||||
|
:scroll-into-view="recipeCategoryScrollInto"
|
||||||
|
:show-scrollbar="false"
|
||||||
|
enable-flex
|
||||||
|
>
|
||||||
|
<view class="cat-recipe-track">
|
||||||
|
<view
|
||||||
|
v-for="cat in recipeCategoryList"
|
||||||
|
:key="String(cat.id)"
|
||||||
|
:id="'recipe-cat-' + String(cat.id)"
|
||||||
|
class="cat-recipe-item"
|
||||||
|
@click="onSelectRecipe(cat)"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="cat-recipe-ring"
|
||||||
|
:class="{
|
||||||
|
'cat-recipe-ring--on': categoryRowIdEquals(
|
||||||
|
cat,
|
||||||
|
recipeCategoryId,
|
||||||
|
),
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
class="cat-recipe-img"
|
||||||
|
:src="String(cat.categoryImageUrl || cat.categoryImage || '')"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<text
|
||||||
|
class="cat-recipe-label"
|
||||||
|
:class="{
|
||||||
|
'cat-recipe-label--on': categoryRowIdEquals(
|
||||||
|
cat,
|
||||||
|
recipeCategoryId,
|
||||||
|
),
|
||||||
|
}"
|
||||||
|
>{{ cat.categoryName }}</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<view class="p-32rpx">
|
<view v-if="pageReady" class="cat-waterfall px-24rpx pb-32rpx">
|
||||||
<template v-for="(item, index) in dataList" :key="index">
|
<view class="waterfall-row flex gap-16rpx items-start">
|
||||||
<food-box :item="item" />
|
<view
|
||||||
</template>
|
v-for="(col, colIndex) in dishColumns"
|
||||||
|
:key="colIndex"
|
||||||
|
class="waterfall-col flex-1 min-w-0 flex flex-col gap-16rpx"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="item in col"
|
||||||
|
:key="String(item.id) + '-' + String(item.merchantId)"
|
||||||
|
class="cat-dish-card w-full"
|
||||||
|
@click="navigateToDishes(item)"
|
||||||
|
>
|
||||||
|
<view class="cat-dish-image relative w-full bg-#f0f0f0">
|
||||||
|
<view v-if="item.isNew == 1" class="dish-new-ribbon">
|
||||||
|
<text class="dish-new-ribbon__text">NEW</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view
|
||||||
|
class="cat-dish-collect"
|
||||||
|
@click.stop="handleDishCollectionClick(item)"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
v-if="!item.isCollect"
|
||||||
|
src="@img-store/1334.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
class="cat-dish-collect-icon"
|
||||||
|
/>
|
||||||
|
<image
|
||||||
|
v-else
|
||||||
|
src="@img-store/1337.png"
|
||||||
|
mode="aspectFit"
|
||||||
|
class="cat-dish-collect-icon"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="isSoldOutStock(item.stock)"
|
||||||
|
class="cat-dish-soldout"
|
||||||
|
>{{ t("common.prompt.soldOut") }}</view
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
:src="String(item.dishImage || '').split(',')[0]"
|
||||||
|
mode="widthFix"
|
||||||
|
class="w-full block"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="cat-dish-body">
|
||||||
|
<view class="cat-dish-meta flex items-start justify-between gap-12rpx mb-14rpx">
|
||||||
|
<view class="min-w-0 flex-1">
|
||||||
|
<text class="cat-dish-price"
|
||||||
|
>US${{ item.discountPrice }}</text
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
v-if="
|
||||||
|
Number(item.originalPrice) > Number(item.discountPrice)
|
||||||
|
"
|
||||||
|
class="cat-dish-original"
|
||||||
|
>US${{ item.originalPrice }}</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<text class="cat-dish-sales shrink-0"
|
||||||
|
>{{ t("pages-store.store.sales") }}:
|
||||||
|
{{ formatSalesCount(item.salesCount) }}</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view class="cat-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="cat-dish-member shrink min-w-0"
|
||||||
|
>
|
||||||
|
<text class="cat-dish-member-inner"
|
||||||
|
>{{ t("pages-store.store.members") }}: US${{
|
||||||
|
item.memberPrice
|
||||||
|
}}</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view v-else class="flex-1 min-w-0" />
|
||||||
|
<view
|
||||||
|
class="cat-dish-add center shrink-0"
|
||||||
|
:class="{
|
||||||
|
'cat-dish-add--busy': addingDishId === item.id,
|
||||||
|
}"
|
||||||
|
@click.stop="onAddCartClick(item)"
|
||||||
|
>
|
||||||
|
<image src="@img/chef/1285.png" class="w-28rpx h-28rpx" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="getDishPromoLabel(item)"
|
||||||
|
class="cat-dish-promo mt-16rpx"
|
||||||
|
>
|
||||||
|
<text class="cat-dish-promo-text">{{
|
||||||
|
getDishPromoLabel(item)
|
||||||
|
}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<template #bottom>
|
||||||
|
<view class="h-24rpx" />
|
||||||
|
<view :style="[configStore.iosSafeBottomPlaceholder]" />
|
||||||
|
</template>
|
||||||
</z-paging>
|
</z-paging>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.cat-top {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-header-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding: 12rpx 24rpx 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-icon-btn {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-icon-btn--cart {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-cart-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 2rpx;
|
||||||
|
right: 2rpx;
|
||||||
|
min-width: 28rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
padding: 0 6rpx;
|
||||||
|
font-size: 18rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
background: #e23636;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-search {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 36rpx;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-search-icon {
|
||||||
|
width: 28rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-search-placeholder {
|
||||||
|
margin-left: 16rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #999;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-scroll-x {
|
||||||
|
width: 100%;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-recipe-track {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 8rpx 24rpx 16rpx;
|
||||||
|
gap: 36rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-recipe-item {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 120rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-recipe-ring {
|
||||||
|
width: 102rpx;
|
||||||
|
height: 102rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 0;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.07);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-recipe-ring--on {
|
||||||
|
border: 4rpx solid #14181b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-recipe-img {
|
||||||
|
width: 94rpx;
|
||||||
|
height: 94rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-recipe-label {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-recipe-label--on {
|
||||||
|
color: #14181b;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-waterfall {
|
||||||
|
min-height: 200rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-collect {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
top: 12rpx;
|
||||||
|
right: 12rpx;
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-collect-icon {
|
||||||
|
width: 44rpx;
|
||||||
|
height: 44rpx;
|
||||||
|
filter: drop-shadow(0 2rpx 6rpx rgba(0, 0, 0, 0.18));
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-soldout {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-body {
|
||||||
|
padding: 20rpx 20rpx 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-price {
|
||||||
|
color: #e02e24;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-original {
|
||||||
|
margin-left: 10rpx;
|
||||||
|
color: #b3b3b3;
|
||||||
|
font-size: 22rpx;
|
||||||
|
text-decoration: line-through;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-sales {
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1.35;
|
||||||
|
max-width: 48%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-title {
|
||||||
|
color: #1a1a1a;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-member {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: calc(100% - 88rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-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;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-add {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #14181b;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-add--busy {
|
||||||
|
opacity: 0.45;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-promo {
|
||||||
|
padding: 12rpx 16rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: #fff0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-dish-promo-text {
|
||||||
|
color: #e02e24;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -56,16 +56,18 @@ function handleClickSearch() {
|
|||||||
// 已存在:直接选择这个地址,走原有 chooseAddress 流程
|
// 已存在:直接选择这个地址,走原有 chooseAddress 流程
|
||||||
chooseAddress(exist)
|
chooseAddress(exist)
|
||||||
} else {
|
} else {
|
||||||
// 不存在:走新增地址流程
|
// 不存在:走新增地址流程(建筑类型首次在保存页以弹窗呈现)
|
||||||
|
addressStore.clearAddressInfo()
|
||||||
addressStore.setAddressLocation({
|
addressStore.setAddressLocation({
|
||||||
displayName: data.displayName,
|
displayName: data.displayName,
|
||||||
formattedAddress: data.formattedAddress,
|
formattedAddress: data.formattedAddress,
|
||||||
longitude: data.location.lng,
|
longitude: data.location.lng,
|
||||||
latitude: data.location.lat,
|
latitude: data.location.lat,
|
||||||
})
|
})
|
||||||
|
addressStore.pendingIntroBuildingType = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/address/choose-type'
|
url: '/pages/address/save-address/other'
|
||||||
})
|
})
|
||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,35 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
appMerchantOrderDetailPost,
|
appMerchantOrderDetailPost,
|
||||||
appCollectCollectPost,
|
|
||||||
type MerchantOrderVo,
|
type MerchantOrderVo,
|
||||||
appUserCardSelectDefaultPost, appMerchantOrderPayOrderPost, appMerchantOrderCancelOrderPost
|
appUserCardSelectDefaultPost,
|
||||||
|
appMerchantOrderPayOrderPost,
|
||||||
|
appMerchantOrderCancelOrderPost,
|
||||||
|
appMerchantOrderZipPayVoucherPost,
|
||||||
} from "@/service";
|
} from "@/service";
|
||||||
import { debounce } from 'throttle-debounce'
|
import ChooseImage from '@/components/choose-image/choose-image.vue'
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
import OrderProgress from './components/order-progress.vue'
|
import OrderProgress from './components/order-progress.vue'
|
||||||
import OrderDetailSkeleton from './components/order-detail-skeleton.vue'
|
import OrderDetailSkeleton from './components/order-detail-skeleton.vue'
|
||||||
import Collection from "@/components/collection/index.vue";
|
|
||||||
import PriceDetail from "@/pages-store/pages/order/components/price-detail.vue";
|
import PriceDetail from "@/pages-store/pages/order/components/price-detail.vue";
|
||||||
import CancelOrder from "@/pages-store/pages/order/components/cancel-order.vue";
|
import CancelOrder from "@/pages-store/pages/order/components/cancel-order.vue";
|
||||||
import {useConfigStore} from "@/store";
|
import {useConfigStore} from "@/store";
|
||||||
import {CollectionType, OrderStatus, EventEnum, OrderCancelStatus} from "@/constant/enums";
|
import {OrderStatus, EventEnum, OrderCancelStatus} from "@/constant/enums";
|
||||||
import {formatTimestampWithMonthName, formatTimestampShort, navGoogleMap, callPhone} from "@/utils/utils";
|
import {formatTimestampWithMonthName, formatTimestampShort, navGoogleMap, callPhone} from "@/utils/utils";
|
||||||
|
import dayjs from 'dayjs'
|
||||||
import useEventEmit from "@/hooks/useEventEmit";
|
import useEventEmit from "@/hooks/useEventEmit";
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
|
|
||||||
|
function fillI18nParams(template: string, params: Record<string, string | number>) {
|
||||||
|
let text = template
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
const value = String(params[key] ?? '')
|
||||||
|
text = text.replace(new RegExp(`\\{${key}\\}`, 'g'), value)
|
||||||
|
text = text.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value)
|
||||||
|
})
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
// 价格明细
|
// 价格明细
|
||||||
const priceDetailRef = ref<InstanceType<typeof PriceDetail>>();
|
const priceDetailRef = ref<InstanceType<typeof PriceDetail>>();
|
||||||
// 打开价格明细
|
// 打开价格明细
|
||||||
@@ -115,6 +127,45 @@ const orderStatus = computed(() => {
|
|||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ====== 为设计稿准备的数据结构(商品缩略卡/总件数/总价等)======
|
||||||
|
const orderDishList = computed(() => {
|
||||||
|
const list = orderDetail.value?.merchantOrderDishVoList as unknown as Array<any> | null | undefined
|
||||||
|
if (!Array.isArray(list)) return []
|
||||||
|
return list
|
||||||
|
})
|
||||||
|
|
||||||
|
const orderTotalItemCount = computed(() => {
|
||||||
|
return orderDishList.value.reduce((sum, item) => sum + (Number(item?.count) || 0), 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const orderTotalItemCountText = computed(() => {
|
||||||
|
return fillI18nParams(t('pages.order.totalItemCount'), {
|
||||||
|
count: orderTotalItemCount.value,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const orderTotalPrice = computed(() => {
|
||||||
|
const n = orderDetail.value?.paidAmount ?? orderDetail.value?.actualPrice ?? 0
|
||||||
|
return Number(n).toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
function dishCover(dishItem: any) {
|
||||||
|
const img = dishItem?.merchantDishVo?.dishImage
|
||||||
|
if (!img || typeof img !== 'string') return ''
|
||||||
|
return img.split(',')[0] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function dishTitle(dishItem: any) {
|
||||||
|
return dishItem?.merchantDishVo?.dishName ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const payMethodText = computed(() => {
|
||||||
|
// 1-信用卡 2-余额(wallet)
|
||||||
|
return orderDetail.value?.payMethod === 1
|
||||||
|
? t('pages-user.choosePaymethod.creditCard')
|
||||||
|
: t('pages-user.choosePaymethod.wallet')
|
||||||
|
})
|
||||||
|
|
||||||
// 复制订单号
|
// 复制订单号
|
||||||
const copyOrderNumber = (text: string) => {
|
const copyOrderNumber = (text: string) => {
|
||||||
if(!text) return
|
if(!text) return
|
||||||
@@ -133,29 +184,6 @@ const callPhoneFn = (phone: string) => {
|
|||||||
callPhone(phone)
|
callPhone(phone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏店铺
|
|
||||||
function handleCollectionClick() {
|
|
||||||
debouncedEmit(orderDetail.value?.merchantVo?.id, CollectionType.STORE, ()=> {
|
|
||||||
if (orderDetail.value?.merchantVo) {
|
|
||||||
orderDetail.value.merchantVo.isCollect = !orderDetail.value.merchantVo.isCollect
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 防抖处理函数
|
|
||||||
const debouncedEmit = debounce(1300, (id: any, type: CollectionType, callback: ()=> void) => {
|
|
||||||
// 收藏接口
|
|
||||||
appCollectCollectPost({
|
|
||||||
body: {
|
|
||||||
targetId: id,
|
|
||||||
targetType: type
|
|
||||||
}
|
|
||||||
}).then(res=> {
|
|
||||||
callback()
|
|
||||||
})
|
|
||||||
}, {
|
|
||||||
atBegin: true, // 立即触发
|
|
||||||
})
|
|
||||||
|
|
||||||
// 支付参数
|
// 支付参数
|
||||||
const payMethodOptions = ref({
|
const payMethodOptions = ref({
|
||||||
orderId: '',
|
orderId: '',
|
||||||
@@ -227,10 +255,130 @@ function openQrCode() {
|
|||||||
function navigateTo(url: string) {
|
function navigateTo(url: string) {
|
||||||
uni.navigateTo({ url })
|
uni.navigateTo({ url })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 订单详情顶栏:与设计稿一致的 MM/DD HH:mm */
|
||||||
|
function formatOrderNavScheduleTime(timestamp?: number | string | null) {
|
||||||
|
if (timestamp == null || timestamp === '') return ''
|
||||||
|
const n = Number(timestamp)
|
||||||
|
if (!Number.isFinite(n)) return ''
|
||||||
|
return dayjs(n).format('MM/DD HH:mm')
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOrderDetailBack() {
|
||||||
|
const pages = getCurrentPages?.() || []
|
||||||
|
if (pages.length <= 1) {
|
||||||
|
uni.switchTab({ url: '/pages/home/index' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 顶栏居中展示「预计送达 / 下单时间」:非取消、非退款申请中、非商家拒绝 */
|
||||||
|
const showOrderNavTimeBlock = computed(() => {
|
||||||
|
if (!orderDetail.value) return false
|
||||||
|
if (orderStatus.value === OrderStatus.CANCELLED) return false
|
||||||
|
if (orderDetail.value.refundStatus === OrderCancelStatus.APPLIED) return false
|
||||||
|
if (orderStatus.value === OrderStatus.MERCHANT_REJECTED) return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
const orderNavPrimaryLine = computed(() => {
|
||||||
|
const d = orderDetail.value
|
||||||
|
if (!d) return ''
|
||||||
|
const isDelivery = +(d.receiveMethod ?? 0) === 1
|
||||||
|
const label = isDelivery
|
||||||
|
? t('pages-store.order.estimatedDeliveryTime')
|
||||||
|
: t('pages-store.order.upTime')
|
||||||
|
const timePart = formatOrderNavScheduleTime(d.endScheduledTime) || '--'
|
||||||
|
const suffix = isDelivery ? t('pages-store.order.beforeDeadline') : ''
|
||||||
|
return `${label} : ${timePart}${suffix}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const orderNavSecondaryLine = computed(() => {
|
||||||
|
const d = orderDetail.value
|
||||||
|
if (!d) return ''
|
||||||
|
const ct = d.createTime
|
||||||
|
const timeStr =
|
||||||
|
formatOrderNavScheduleTime(ct) ||
|
||||||
|
(ct != null && ct !== '' ? formatTimestampShort(Number(ct)) : '')
|
||||||
|
return `${t('pages-store.order.orderTime')}: ${timeStr || '--'}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// 已支付上传凭证(Zip 等线下支付)
|
||||||
|
const voucherChooseRef = ref<InstanceType<typeof ChooseImage>>()
|
||||||
|
const voucherSubmitting = ref(false)
|
||||||
|
|
||||||
|
function openUploadVoucher() {
|
||||||
|
voucherChooseRef.value?.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeVoucherUrl(payload: unknown): string {
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
const first = payload[0]
|
||||||
|
return typeof first === 'string' ? first : ''
|
||||||
|
}
|
||||||
|
return typeof payload === 'string' ? payload : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onVoucherImageUploaded(urls: unknown) {
|
||||||
|
const zipPayVoucher = normalizeVoucherUrl(urls)
|
||||||
|
if (!zipPayVoucher) {
|
||||||
|
uni.showToast({ title: t('pages-store.order.voucherUploadFailed'), icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const id = orderDetail.value?.id
|
||||||
|
if (id == null || String(id).trim() === '') {
|
||||||
|
uni.showToast({ title: t('pages-store.order.voucherUploadFailed'), icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (voucherSubmitting.value) return
|
||||||
|
voucherSubmitting.value = true
|
||||||
|
try {
|
||||||
|
await appMerchantOrderZipPayVoucherPost({
|
||||||
|
body: {
|
||||||
|
orderId: id,
|
||||||
|
zipPayVoucher,
|
||||||
|
},
|
||||||
|
options: { hideErrorToast: true },
|
||||||
|
})
|
||||||
|
uni.showToast({ title: t('pages-store.order.voucherSubmitSuccess'), icon: 'none' })
|
||||||
|
setTimeout(() => {
|
||||||
|
appMerchantOrderDetail()
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
uni.showToast({ title: t('pages-store.order.voucherSubmitFailed'), icon: 'none' })
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
voucherSubmitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<navbar />
|
<view class="order-detail-page">
|
||||||
|
<wd-navbar
|
||||||
|
safeAreaInsetTop
|
||||||
|
fixed
|
||||||
|
placeholder
|
||||||
|
:bordered="false"
|
||||||
|
custom-class="order-detail-navbar !bg-white"
|
||||||
|
@click-left="handleOrderDetailBack"
|
||||||
|
>
|
||||||
|
<template #left>
|
||||||
|
<view class="shrink-0">
|
||||||
|
<view class="order-detail-navbar-circle center">
|
||||||
|
<view class="i-carbon:chevron-left text-44rpx text-#111"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<view v-if="!loading && orderDetail && showOrderNavTimeBlock" class="order-detail-nav-times">
|
||||||
|
<text class="order-detail-nav-primary">{{ orderNavPrimaryLine }}</text>
|
||||||
|
<text class="order-detail-nav-secondary">{{ orderNavSecondaryLine }}</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</wd-navbar>
|
||||||
<view
|
<view
|
||||||
class="animate-in fade-in animate-ease-out animate-duration-300"
|
class="animate-in fade-in animate-ease-out animate-duration-300"
|
||||||
v-show="loading"
|
v-show="loading"
|
||||||
@@ -239,7 +387,7 @@ function navigateTo(url: string) {
|
|||||||
<OrderDetailSkeleton />
|
<OrderDetailSkeleton />
|
||||||
</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 bg-white"
|
||||||
v-if="!loading"
|
v-if="!loading"
|
||||||
>
|
>
|
||||||
<!-- 已取消-成功取消 -->
|
<!-- 已取消-成功取消 -->
|
||||||
@@ -262,34 +410,27 @@ function navigateTo(url: string) {
|
|||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<!-- 商家拒绝订单 -->
|
<!-- 商家拒绝订单 -->
|
||||||
<template v-if="orderStatus === OrderStatus.MERCHANT_REJECTED">
|
<template v-else-if="orderStatus === OrderStatus.MERCHANT_REJECTED">
|
||||||
<view class="px-30rpx pt-20rpx pb-50rpx">
|
<view class="px-30rpx pt-8rpx pb-40rpx">
|
||||||
<view class="text-40rpx lh-36rpx text-#333 font-500">{{ t('pages-store.order.orderStatus.merchantRejected') }}</view>
|
<view class="text-40rpx lh-36rpx text-#333 font-500">{{ t('pages-store.order.orderStatus.merchantRejected') }}</view>
|
||||||
<view class="text-28rpx lh-28rpx text-#7D7D7D mt-26rpx">{{ t('pages-store.order.orderStatus.merchantRejectedDesc') }}</view>
|
<view class="text-28rpx lh-28rpx text-#7D7D7D mt-26rpx">{{ t('pages-store.order.orderStatus.merchantRejectedDesc') }}</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<view class="px-30rpx pt-20rpx pb-50rpx">
|
<!-- 顶栏已展示预计送达/下单时间;此处仅保留提示类副文案 -->
|
||||||
<view class="text-40rpx lh-36rpx text-#333 font-500">
|
<view class="px-30rpx pt-8rpx pb-24rpx" v-if="orderStatus === OrderStatus.PENDING_PAYMENT || orderDetail?.refundStatus === OrderCancelStatus.REJECTED">
|
||||||
<template v-if="orderDetail?.receiveMethod === 1">
|
<view class="text-28rpx lh-36rpx text-#7D7D7D" v-if="orderStatus === OrderStatus.PENDING_PAYMENT">
|
||||||
{{ t('pages-store.order.estimatedDeliveryTime') }}
|
{{ t('pages-store.order.autoCancellation') }}
|
||||||
</template>
|
|
||||||
<template v-else>{{ t('pages-store.order.upTime') }}</template>
|
|
||||||
:
|
|
||||||
<!-- 订单预计送达时间-->
|
|
||||||
{{ formatTimestampShort(orderDetail?.endScheduledTime) }}
|
|
||||||
</view>
|
</view>
|
||||||
<view class="text-28rpx lh-28rpx text-#7D7D7D mt-26rpx">
|
<view class="text-28rpx lh-36rpx text-#7D7D7D mt-16rpx" v-if="orderDetail?.refundStatus === OrderCancelStatus.REJECTED">
|
||||||
<view>{{ t('pages-store.order.orderTime') }}: {{ formatTimestampShort(orderDetail?.createTime) }}</view>
|
|
||||||
<!-- 订单30分钟自动取消--待支付 -->
|
|
||||||
<view v-if="orderStatus === OrderStatus.PENDING_PAYMENT">{{ t('pages-store.order.autoCancellation') }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="text-28rpx lh-28rpx text-#7D7D7D mt-26rpx" v-if="orderDetail?.refundStatus === OrderCancelStatus.REJECTED">
|
|
||||||
{{ t('pages-store.order.rejectReason') }}:{{ orderDetail?.rejectReason }}
|
{{ t('pages-store.order.rejectReason') }}:{{ orderDetail?.rejectReason }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 订单进度 -->
|
<!-- 订单进度(紧接顶栏时间信息) -->
|
||||||
<view v-if="orderDetail?.refundStatus !== OrderCancelStatus.APPLIED || orderDetail?.refundStatus !== OrderCancelStatus.APPROVED" class="mb-52rpx px-30rpx">
|
<view
|
||||||
|
v-if="orderDetail?.refundStatus !== OrderCancelStatus.APPLIED && orderDetail?.refundStatus !== OrderCancelStatus.APPROVED"
|
||||||
|
class="order-detail-progress-wrap mb-52rpx px-30rpx"
|
||||||
|
>
|
||||||
<OrderProgress
|
<OrderProgress
|
||||||
:steps="orderSteps"
|
:steps="orderSteps"
|
||||||
:current-status="orderStatus"
|
:current-status="orderStatus"
|
||||||
@@ -350,58 +491,77 @@ function navigateTo(url: string) {
|
|||||||
</view>
|
</view>
|
||||||
<view class="text-30rpx lh-30rpx text-#333 mb-24rpx mt-36rpx">{{ t('pages-store.order.deliveryPhotos') }}</view>
|
<view class="text-30rpx lh-30rpx text-#333 mb-24rpx mt-36rpx">{{ t('pages-store.order.deliveryPhotos') }}</view>
|
||||||
<view class="flex items-center gap-20rpx">
|
<view class="flex items-center gap-20rpx">
|
||||||
<template v-for="item in orderDetail?.deliveryPhotos?.split(',')">
|
<view
|
||||||
|
v-for="(item, idx) in orderDetail?.deliveryPhotos?.split(',')"
|
||||||
|
:key="`${idx}-${item}`"
|
||||||
|
>
|
||||||
<wd-img width="158rpx" height="158rpx" radius="16rpx" mode="aspectFill" :src="item" :enable-preview="true" />
|
<wd-img width="158rpx" height="158rpx" radius="16rpx" mode="aspectFill" :src="item" :enable-preview="true" />
|
||||||
</template>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="w-full h-16rpx bg-#F6F6F6"></view>
|
<view class="w-full h-16rpx bg-#F6F6F6"></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 商品列表 -->
|
<!-- 商品列表 -->
|
||||||
<view class="px-30rpx py-36rpx">
|
<view class="goods-section px-30rpx py-24rpx">
|
||||||
<!-- 商家信息 -->
|
<view class="goods-layout">
|
||||||
<view @click="navigateTo('/pages-store/pages/store/index?id=' + orderDetail?.merchantVo?.id)" class="flex-center-sb h-80rpx mb-36rpx">
|
<view class="goods-left">
|
||||||
<view class="flex items-center">
|
<!-- 门店行:图标 + 店铺名 + 右箭头 -->
|
||||||
|
<view
|
||||||
|
@click="navigateTo('/pages-store/pages/store/index?id=' + orderDetail?.merchantVo?.id)"
|
||||||
|
class="store-row-detail flex-center-sb mb-20rpx"
|
||||||
|
>
|
||||||
|
<view class="flex items-center min-w-0">
|
||||||
<image
|
<image
|
||||||
:src="orderDetail?.merchantVo?.logo"
|
src="@img/chef/126.png"
|
||||||
mode="aspectFill"
|
class="w-40rpx h-40rpx shrink-0 mr-16rpx"
|
||||||
class="w-80rpx h-80rpx rounded-full bg-#F2F2F2 mr-24rpx shrink-0"
|
mode="aspectFit"
|
||||||
/>
|
/>
|
||||||
<text class="text-30rpx lh-30rpx text-#333 font-500">{{ orderDetail?.merchantVo?.merchantName }}</text>
|
<text class="text-30rpx lh-36rpx text-#333 font-500 line-clamp-1">
|
||||||
|
{{ orderDetail?.merchantVo?.merchantName }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
<image
|
<image
|
||||||
src="@img/chef/142.png"
|
src="@img/chef/142.png"
|
||||||
class="w-32rpx h-32rpx shrink-0 ml-10rpx"
|
class="w-28rpx h-28rpx shrink-0"
|
||||||
></image>
|
mode="aspectFit"
|
||||||
</view>
|
|
||||||
<collection :is-collected="orderDetail?.merchantVo?.isCollect" @collectionChange="handleCollectionClick" />
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view
|
|
||||||
v-for="item in orderDetail?.merchantOrderDishVoList"
|
|
||||||
:key="item.id"
|
|
||||||
class="flex items-start mb-32rpx last:mb-0"
|
|
||||||
>
|
|
||||||
<!-- 商品图片 -->
|
|
||||||
<view class="w-136rpx h-136rpx rounded-16rpx overflow-hidden mr-20rpx shrink-0">
|
|
||||||
<image
|
|
||||||
:src="item.merchantDishVo?.dishImage?.split(',')[0]"
|
|
||||||
mode="aspectFill"
|
|
||||||
class="w-full h-full bg-#F2F2F2"
|
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 商品信息 -->
|
<!-- 商品缩略卡横向滚动 -->
|
||||||
<view class="flex-1">
|
<scroll-view
|
||||||
<text class="text-30rpx lh-30rpx text-#333 font-500 block mb-20rpx">{{ item.merchantDishVo?.dishName }}</text>
|
scroll-x
|
||||||
<view class="flex-center-sb text-24rpx lh-24rpx text-#7D7D7D mb-24rpx">
|
class="goods-scroll"
|
||||||
<text class="line-clamp-1">{{ item.merchantSideDishVo?.sideDishName }} {{ item.merchantSideDishItemVo?.name }}</text>
|
:show-scrollbar="false"
|
||||||
<!-- 数量 -->
|
:enable-flex="true"
|
||||||
<view class="shrink-0 text-28rpx lh-28rpx text-#333 text-right font-500">
|
>
|
||||||
X {{ item.count }}
|
<view class="goods-track">
|
||||||
|
<view
|
||||||
|
v-for="(item, di) in orderDishList"
|
||||||
|
:key="item.id ?? di"
|
||||||
|
class="goods-cell"
|
||||||
|
>
|
||||||
|
<view class="goods-img-wrap">
|
||||||
|
<image
|
||||||
|
:src="dishCover(item)"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="goods-img"
|
||||||
|
/>
|
||||||
|
<view v-if="Number(item?.count) > 1" class="goods-qty">
|
||||||
|
x{{ item?.count }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<text class="text-30rpx lh-30rpx text-#333 font-500">{{ `$${item.merchantDishVo?.discountPrice}` }}</text>
|
<text class="goods-caption line-clamp-2">{{ dishTitle(item) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="goods-price-right">
|
||||||
|
<text class="goods-price">${{ orderTotalPrice }}</text>
|
||||||
|
<text class="goods-count">
|
||||||
|
{{ orderTotalItemCountText }}
|
||||||
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -409,7 +569,7 @@ function navigateTo(url: string) {
|
|||||||
<!-- 分隔线 -->
|
<!-- 分隔线 -->
|
||||||
<view class="w-full h-16rpx bg-#F6F6F6"></view>
|
<view class="w-full h-16rpx bg-#F6F6F6"></view>
|
||||||
|
|
||||||
<view v-if="orderDetail?.receiveMethod === 1" class="pt-36rpx">
|
<view v-if="orderDetail?.receiveMethod === 1" class="pt-36rpx bg-white">
|
||||||
<view class="text-36rpx lh-36rpx text-#333 font-500 pl-30rpx mb-4rpx">{{ t('pages-store.order.deliveryAddress') }}</view>
|
<view class="text-36rpx lh-36rpx text-#333 font-500 pl-30rpx mb-4rpx">{{ t('pages-store.order.deliveryAddress') }}</view>
|
||||||
|
|
||||||
<!-- 收货地址 -->
|
<!-- 收货地址 -->
|
||||||
@@ -441,7 +601,6 @@ function navigateTo(url: string) {
|
|||||||
{{ orderDetail?.deliveryMethod }}
|
{{ orderDetail?.deliveryMethod }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 联系电话 -->
|
|
||||||
<view
|
<view
|
||||||
class="flex items-center py-36rpx px-30rpx"
|
class="flex items-center py-36rpx px-30rpx"
|
||||||
>
|
>
|
||||||
@@ -457,7 +616,7 @@ function navigateTo(url: string) {
|
|||||||
</view>
|
</view>
|
||||||
<!-- 分隔线 -->
|
<!-- 分隔线 -->
|
||||||
|
|
||||||
<view v-else class="px-30rpx pt-36rpx">
|
<view v-else class="px-30rpx pt-36rpx bg-white">
|
||||||
<view class="text-36rpx lh-36rpx text-#333 font-bold">{{ t('pickupAddress') }}</view>
|
<view class="text-36rpx lh-36rpx text-#333 font-bold">{{ t('pickupAddress') }}</view>
|
||||||
<view class="flex-center-sb py-40rpx">
|
<view class="flex-center-sb py-40rpx">
|
||||||
<view class="flex items-center">
|
<view class="flex items-center">
|
||||||
@@ -486,7 +645,7 @@ function navigateTo(url: string) {
|
|||||||
|
|
||||||
<view class="w-full h-16rpx bg-#F6F6F6"></view>
|
<view class="w-full h-16rpx bg-#F6F6F6"></view>
|
||||||
|
|
||||||
<view class="border-bottom px-30rpx py-36rpx text-36rpx lh-36rpx text-#333 ">
|
<view class="bg-white border-bottom px-30rpx py-36rpx text-36rpx lh-36rpx text-#333 ">
|
||||||
<view class="font-500 text-36rpx lh-36rpx mb-40rpx">{{ t('pages-store.order.orderInfo') }}</view>
|
<view class="font-500 text-36rpx lh-36rpx mb-40rpx">{{ t('pages-store.order.orderInfo') }}</view>
|
||||||
<!-- 订单编号 -->
|
<!-- 订单编号 -->
|
||||||
<view @click="copyOrderNumber(orderDetail?.orderNo)" class="flex-center-sb text-30rpx lh-30rpx mb-40rpx">
|
<view @click="copyOrderNumber(orderDetail?.orderNo)" class="flex-center-sb text-30rpx lh-30rpx mb-40rpx">
|
||||||
@@ -501,11 +660,10 @@ function navigateTo(url: string) {
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 下单时间 mb-40rpx根据支付状态显示 -->
|
<!-- 下单时间 mb-40rpx根据支付状态显示 -->
|
||||||
<view class="flex-center-sb text-30rpx lh-30rpx" :class="[orderStatus !== OrderStatus.PENDING_PAYMENT ? 'mb-40rpx' : '']">
|
<view class="flex-center-sb text-30rpx lh-30rpx mb-40rpx">
|
||||||
<text>{{ t('pages-store.order.orderTime') }}</text>
|
<text>{{ t('pages-store.order.orderTime') }}</text>
|
||||||
<text>{{ formatTimestampWithMonthName(orderDetail?.createTime) }}</text>
|
<text>{{ formatTimestampWithMonthName(orderDetail?.createTime) }}</text>
|
||||||
</view>
|
</view>
|
||||||
<!-- 待支付 -->
|
|
||||||
<template v-if="orderStatus !== OrderStatus.PENDING_PAYMENT">
|
<template v-if="orderStatus !== OrderStatus.PENDING_PAYMENT">
|
||||||
<!-- 支付方式 -->
|
<!-- 支付方式 -->
|
||||||
<view class="flex-center-sb text-30rpx lh-30rpx mb-40rpx">
|
<view class="flex-center-sb text-30rpx lh-30rpx mb-40rpx">
|
||||||
@@ -516,14 +674,9 @@ function navigateTo(url: string) {
|
|||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
class="w-44rpx h-44rpx shrink-0 mr-10rpx"
|
class="w-44rpx h-44rpx shrink-0 mr-10rpx"
|
||||||
/>
|
/>
|
||||||
<text>{{ orderDetail?.payMethod === 1 ? t('pages-user.choosePaymethod.creditCard') : t('pages-user.choosePaymethod.wallet') }}</text>
|
<text>{{ payMethodText }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<!-- 支付时间 -->
|
|
||||||
<view class="flex-center-sb text-30rpx lh-30rpx">
|
|
||||||
<text>{{ t('pages-user.member.payTime') }}</text>
|
|
||||||
<text>{{ formatTimestampWithMonthName(orderDetail?.payTime) }}</text>
|
|
||||||
</view>
|
|
||||||
</template>
|
</template>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -606,12 +759,21 @@ function navigateTo(url: string) {
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- 待支付 -->
|
|
||||||
<template v-if="orderStatus === OrderStatus.PENDING_PAYMENT">
|
<template v-if="orderStatus === OrderStatus.PENDING_PAYMENT">
|
||||||
<wd-button @click="goPay" custom-class="!h-108rpx !bg-14181B !text-36rpx !lh-108rpx !text-#fff !rounded-16rpx" block>
|
<!-- 上传支付凭证 -->
|
||||||
|
<wd-button
|
||||||
|
:loading="voucherSubmitting"
|
||||||
|
:disabled="voucherSubmitting"
|
||||||
|
@click="openUploadVoucher"
|
||||||
|
custom-class="!h-108rpx !bg-transparent !text-32rpx !font-500 !lh-108rpx !text-#333 !rounded-46rpx !border-#666666 !border-solid !border-1rpx"
|
||||||
|
block
|
||||||
|
>
|
||||||
|
{{ t('pages-store.order.uploadPaidVoucher') }}
|
||||||
|
</wd-button>
|
||||||
|
<wd-button @click="goPay" custom-class="!h-108rpx !bg-14181B !text-32rpx !lh-108rpx !text-#fff !rounded-46rpx !mt-22rpx" block>
|
||||||
{{ t('common.goPay') }}
|
{{ t('common.goPay') }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
<view @click="openCancelOrder" class="text-center mt-52rpx text-36rpx lh-36rpx text-#333 font-500">
|
<view @click="openCancelOrder" class="text-center mt-22rpx text-28rpx lh-28rpx text-#333 font-500">
|
||||||
{{ t('common.cancel') }}
|
{{ t('common.cancel') }}
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -679,12 +841,153 @@ function navigateTo(url: string) {
|
|||||||
<password-container @success="payPawSuccess" ref="passwordInputRef" />
|
<password-container @success="payPawSuccess" ref="passwordInputRef" />
|
||||||
<!-- 核销订单 -->
|
<!-- 核销订单 -->
|
||||||
<use-code ref="useCodeRef" />
|
<use-code ref="useCodeRef" />
|
||||||
|
<!-- 支付凭证:相册/拍照,上传后回调真实 URL -->
|
||||||
|
<choose-image ref="voucherChooseRef" :count="1" @change="onVoucherImageUploaded" />
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<style>
|
<style>
|
||||||
page {
|
page {
|
||||||
background-color: #fff;
|
background-color: #F6F6F6;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 订单详情:商品缩略卡横向滚动(对齐设计稿) */
|
||||||
|
.goods-section {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-layout {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-left {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-row-detail {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-price-right {
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
padding-left: 24rpx;
|
||||||
|
padding-bottom: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-price {
|
||||||
|
display: block;
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #14181B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-count {
|
||||||
|
display: block;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 30rpx;
|
||||||
|
color: #7D7D7D;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-scroll {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-track {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding-right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-cell {
|
||||||
|
width: 128rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-img-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 128rpx;
|
||||||
|
height: 128rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #F2F2F2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-qty {
|
||||||
|
position: absolute;
|
||||||
|
right: 6rpx;
|
||||||
|
bottom: 6rpx;
|
||||||
|
min-width: 36rpx;
|
||||||
|
padding: 4rpx 10rpx;
|
||||||
|
background: rgba(20, 24, 27, 0.85);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-caption {
|
||||||
|
display: block;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-navbar-circle {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-nav-times {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 8rpx;
|
||||||
|
max-width: 480rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-nav-primary {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #14181b;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-nav-secondary {
|
||||||
|
margin-top: 6rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 30rpx;
|
||||||
|
color: #7d7d7d;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-detail-progress-wrap {
|
||||||
|
padding-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.order-detail-navbar.wd-navbar .wd-navbar__title) {
|
||||||
|
max-width: 520rpx;
|
||||||
|
overflow: visible;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useConfigStore} from "@/store";
|
import {useConfigStore} from "@/store";
|
||||||
import {receiveCouponApi} from "@/pages-user/service";
|
import {receiveCouponApi} from "@/pages-user/service";
|
||||||
|
import couponBg from '@img-store/coupon.png'
|
||||||
|
import couponBg2 from '@img-store/coupon-2.png'
|
||||||
|
import couponRightBg from '@img-store/coupon-right.png'
|
||||||
|
import couponRightBg2 from '@img-store/coupon-right-2.png'
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
@@ -57,17 +61,16 @@ function formatMoney(value: unknown) {
|
|||||||
return n.toFixed(2)
|
return n.toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
function couponTitleText(item: any) {
|
function couponAmountText(item: any) {
|
||||||
const type = Number(item?.couponType)
|
const type = Number(item?.couponType)
|
||||||
const discountValue = Number(item?.discountValue)
|
const discountValue = Number(item?.discountValue)
|
||||||
|
|
||||||
// 标题保持原样:折扣券显示百分比,满减券只显示减免金额(不展示门槛)
|
|
||||||
if (type === 1) {
|
if (type === 1) {
|
||||||
const pct = Number.isFinite(discountValue) ? Number(discountValue * 100).toFixed(0) : '0'
|
const pct = Number.isFinite(discountValue) ? Number(discountValue * 100).toFixed(0) : '0'
|
||||||
return `${pct}% ${t('pages-store.store.couponOff')}`
|
return `${pct}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
return `$${formatMoney(discountValue)} ${t('pages-store.store.couponOff')}`
|
return `$${formatMoney(discountValue)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function couponBenefitText(item: any) {
|
function couponBenefitText(item: any) {
|
||||||
@@ -91,44 +94,58 @@ function couponBenefitText(item: any) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<wd-popup v-model="show" custom-style="border-radius:0;" position="bottom" @close="handleClose">
|
<wd-popup v-model="show" custom-style="border-radius: 24rpx 24rpx 0 0;" position="bottom" @close="handleClose">
|
||||||
<view class="bg-#F5F5F5 px-32rpx pt-30rpx">
|
<view class="coupon-popup-wrap">
|
||||||
<view class="text-40rpx lh-40rpx text-#333 font-bold text-center mb-60rpx">{{ t('pages-store.store.claimCoupon') }}</view>
|
<view class="text-36rpx lh-36rpx text-#333 font-bold text-center pt-40rpx pb-36rpx">
|
||||||
<scroll-view scroll-y class="h-1000rpx">
|
{{ t('pages-store.store.claimCoupon') }}
|
||||||
<template v-for="item in couponList" :key="item.id">
|
|
||||||
<view class="coupon-item h-328rpx flex flex-col mb-30rpx last:mb-0">
|
|
||||||
<view class="flex-1 pt-40rpx px-58rpx">
|
|
||||||
<view class="line-clamp-1 text-34rpx lh-34rpx text-#333 font-bold">
|
|
||||||
<!-- couponType 1-折扣券, 2-满减券-->
|
|
||||||
{{ couponTitleText(item) }}
|
|
||||||
</view>
|
</view>
|
||||||
<view class="text-24rpx lh-32rpx text-#999 my-18rpx">
|
|
||||||
{{ t('pages-store.store.validDays') }}: {{ item.validDays }}{{ daySuffix(item.validDays) }}
|
<scroll-view scroll-y class="coupon-popup-list">
|
||||||
</view>
|
<view
|
||||||
<view class="text-24rpx lh-32rpx text-#999 my-18rpx">
|
v-for="item in couponList"
|
||||||
{{ item.nameZh }}
|
:key="item.id"
|
||||||
</view>
|
class="coupon-card"
|
||||||
<view class="text-28rpx lh-28rpx text-#333 flex items-center">
|
>
|
||||||
<image
|
<image
|
||||||
class="w-36rpx h-36rpx shrink-0 mr-10rpx"
|
class="coupon-card-bg"
|
||||||
|
:src="item.userCouponVo ? couponBg2 : couponBg"
|
||||||
|
mode="scaleToFill"
|
||||||
|
/>
|
||||||
|
<view class="coupon-card-body">
|
||||||
|
<view class="coupon-card-info">
|
||||||
|
<view class="coupon-card-amount">
|
||||||
|
<text class="coupon-amount-value" :class="{ 'coupon-amount-value--disabled': item.userCouponVo }">{{ couponAmountText(item) }}</text>
|
||||||
|
<text class="coupon-amount-label" :class="{ 'coupon-amount-label--disabled': item.userCouponVo }">{{ t('pages-store.store.couponOff') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="coupon-card-desc">
|
||||||
|
{{ item.nameZh }}
|
||||||
|
<text class="ml-16rpx">{{ t('pages-store.store.validDays') }}:{{ item.validDays }}{{ daySuffix(item.validDays) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="coupon-card-benefit">
|
||||||
|
<image
|
||||||
|
class="coupon-benefit-icon"
|
||||||
src="@img/chef/106.png"
|
src="@img/chef/106.png"
|
||||||
></image>
|
></image>
|
||||||
{{ t('pages-store.store.get') }} {{ couponBenefitText(item) }}
|
<text>{{ t('pages-store.store.get') }} {{ couponBenefitText(item) }}</text>
|
||||||
<template v-if="Number(item?.couponType) === 1"> {{ t('pages-store.store.couponOff') }}</template>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<template v-if="item.userCouponVo">
|
|
||||||
<view class="h-86rpx bg-#e6e6e6 lh-84rpx text-center text-28rpx text-#fff rounded-br-18rpx rounded-bl-18rpx">
|
|
||||||
{{ t('pages-store.store.claimed') }}
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
<view
|
||||||
<template v-else>
|
v-if="item.userCouponVo"
|
||||||
<view @click="confirmCoupon(item)" class="h-84rpx lh-84rpx text-center text-28rpx text-#fff">
|
class="coupon-claim-wrap"
|
||||||
{{ t('pages-store.store.claimNow') }}
|
>
|
||||||
|
<image class="coupon-claim-bg" :src="couponRightBg2" mode="scaleToFill" />
|
||||||
|
<text class="coupon-claim-text coupon-claim-text--disabled">{{ t('pages-store.store.claimed') }}</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-else
|
||||||
|
class="coupon-claim-wrap"
|
||||||
|
@click="confirmCoupon(item)"
|
||||||
|
>
|
||||||
|
<image class="coupon-claim-bg" :src="couponRightBg" mode="scaleToFill" />
|
||||||
|
<text class="coupon-claim-text">{{ t('pages-store.store.claimNow') }}</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<view :style="[configStore.iosSafeBottomPlaceholder]" />
|
<view :style="[configStore.iosSafeBottomPlaceholder]" />
|
||||||
@@ -137,10 +154,126 @@ function couponBenefitText(item: any) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.coupon-item {
|
.coupon-popup-wrap {
|
||||||
background-image: url('@img/chef/103.png');
|
background: #F5F5F5;
|
||||||
background-size: 100% 100%;
|
padding: 0 32rpx;
|
||||||
background-repeat: no-repeat;
|
}
|
||||||
background-position: center;
|
|
||||||
|
.coupon-popup-list {
|
||||||
|
max-height: 860rpx;
|
||||||
|
padding-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-card {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-card-bg {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-card-body {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 32rpx 180rpx 32rpx 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-card-info {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-card-amount {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-amount-value {
|
||||||
|
font-size: 48rpx;
|
||||||
|
line-height: 56rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #CE7138;
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
color: #BFBFBF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-amount-label {
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 26rpx;
|
||||||
|
color: #CE7138;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-left: 10rpx;
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
color: #BFBFBF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-card-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 14rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-card-benefit {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
color: #333;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-benefit-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-claim-wrap {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 168rpx;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-claim-bg {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coupon-claim-text {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 26rpx;
|
||||||
|
// font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,94 +1,80 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="store-skeleton">
|
<view class="store-skeleton">
|
||||||
<!-- 顶部图片区域 -->
|
<!-- 顶部导航栏 -->
|
||||||
<view class="relative h-562rpx">
|
|
||||||
<!-- 状态栏 -->
|
|
||||||
<view class="fixed top-0 left-0 z-9 w-full pt-6rpx">
|
<view class="fixed top-0 left-0 z-9 w-full pt-6rpx">
|
||||||
<view class="flex-center-sb px-30rpx">
|
<status-bar />
|
||||||
<view class="back-button-skeleton skeleton-item"></view>
|
<view class="nav-bar">
|
||||||
<view class="flex">
|
<view class="nav-btn skeleton-item"></view>
|
||||||
<view class="action-button-skeleton skeleton-item mr-20rpx"></view>
|
<view class="nav-btn skeleton-item"></view>
|
||||||
<view class="action-button-skeleton skeleton-item mr-20rpx"></view>
|
|
||||||
<view class="action-button-skeleton skeleton-item"></view>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 主图骨架 -->
|
<!-- 占位 -->
|
||||||
<view class="main-image-skeleton skeleton-item w-750rpx h-562rpx absolute top-0 left-0"></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>
|
||||||
|
|
||||||
<!-- 店铺信息区域 -->
|
<!-- 店铺信息区域 -->
|
||||||
<view class="px-30rpx pt-40rpx pb-42rpx">
|
<view class="px-30rpx pt-24rpx pb-30rpx">
|
||||||
|
<view class="flex flex-col items-center">
|
||||||
<!-- 店铺名称 -->
|
<!-- 店铺名称 -->
|
||||||
<view class="text-center">
|
<view class="name-skeleton skeleton-item mb-16rpx"></view>
|
||||||
<view class="store-name-skeleton skeleton-item mx-auto mb-16rpx"></view>
|
<!-- 评分 + CHEFLINK -->
|
||||||
|
<view class="flex items-center mb-12rpx">
|
||||||
<!-- 评分和信息 -->
|
<view class="rating-skeleton skeleton-item mr-16rpx"></view>
|
||||||
<view class="center mb-16rpx">
|
<view class="cheflink-skeleton skeleton-item"></view>
|
||||||
<view class="rating-skeleton skeleton-item mr-20rpx"></view>
|
|
||||||
<view class="chef-link-skeleton skeleton-item mr-20rpx"></view>
|
|
||||||
<view class="distance-skeleton skeleton-item"></view>
|
|
||||||
</view>
|
</view>
|
||||||
|
<!-- 总销量 -->
|
||||||
<!-- 描述信息 -->
|
<view class="sales-text-skeleton skeleton-item"></view>
|
||||||
<view class="description-skeleton skeleton-item mx-auto mb-16rpx"></view>
|
|
||||||
<view class="description-skeleton skeleton-item mx-auto mb-16rpx"></view>
|
|
||||||
|
|
||||||
<!-- 标签 -->
|
|
||||||
<view class="tag-skeleton skeleton-item mx-auto"></view>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 配送方式切换 -->
|
|
||||||
<view class="delivery-switch-skeleton skeleton-item mt-40rpx"></view>
|
|
||||||
|
|
||||||
<!-- 配送信息卡片 -->
|
<!-- 配送信息卡片 -->
|
||||||
<view class="delivery-info-skeleton skeleton-item mt-36rpx"></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>
|
||||||
|
|
||||||
<!-- 标签页导航 -->
|
<!-- 分类胶囊标签 -->
|
||||||
<view class="tabs-skeleton">
|
<view class="flex items-center px-30rpx pb-24rpx">
|
||||||
<view class="flex">
|
|
||||||
<view
|
<view
|
||||||
v-for="i in 5"
|
v-for="i in 4"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="tab-item-skeleton skeleton-item mr-40rpx"
|
class="tab-chip-skeleton skeleton-item"
|
||||||
></view>
|
></view>
|
||||||
</view>
|
</view>
|
||||||
<view class="tabs-divider skeleton-item"></view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 商品列表区域 -->
|
<!-- 商品列表 -->
|
||||||
<view class="px-30rpx">
|
<view class="px-30rpx">
|
||||||
<!-- 分类标题 -->
|
<view class="grid grid-cols-2 gap-24rpx">
|
||||||
<view class="section-title-skeleton skeleton-item my-36rpx"></view>
|
|
||||||
|
|
||||||
<!-- 商品网格 -->
|
|
||||||
<view class="grid grid-cols-2 gap-30rpx">
|
|
||||||
<view
|
<view
|
||||||
v-for="i in 6"
|
v-for="i in 4"
|
||||||
:key="i"
|
:key="i"
|
||||||
class="product-item-skeleton"
|
class="product-skeleton"
|
||||||
>
|
>
|
||||||
<!-- 商品图片 -->
|
<!-- 商品图片 -->
|
||||||
<view class="product-image-skeleton skeleton-item mb-28rpx"></view>
|
<view class="product-img-skeleton skeleton-item"></view>
|
||||||
|
<!-- 价格 + 销量 -->
|
||||||
<!-- 商品名称 -->
|
<view class="flex items-center justify-between mt-16rpx">
|
||||||
<view class="product-name-skeleton skeleton-item mb-12rpx"></view>
|
|
||||||
|
|
||||||
<!-- 价格行 -->
|
|
||||||
<view class="flex-center-sb mb-12rpx">
|
|
||||||
<view class="price-skeleton skeleton-item"></view>
|
<view class="price-skeleton skeleton-item"></view>
|
||||||
<view class="member-price-skeleton skeleton-item"></view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 原价和销量 -->
|
|
||||||
<view class="flex-center-sb">
|
|
||||||
<view>
|
|
||||||
<view class="original-price-skeleton skeleton-item mb-8rpx"></view>
|
|
||||||
<view class="sales-skeleton skeleton-item"></view>
|
<view class="sales-skeleton skeleton-item"></view>
|
||||||
</view>
|
</view>
|
||||||
<view class="add-button-skeleton skeleton-item"></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>
|
</view>
|
||||||
@@ -97,25 +83,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// Store页面骨架屏组件
|
|
||||||
</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, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||||
background-size: 200% 100%;
|
background-size: 200% 100%;
|
||||||
animation: shimmer 1.5s infinite;
|
animation: shimmer 1.5s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 闪烁动画
|
|
||||||
@keyframes shimmer {
|
@keyframes shimmer {
|
||||||
0% {
|
0% { background-position: -200% 0; }
|
||||||
background-position: -200% 0;
|
100% { background-position: 200% 0; }
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 200% 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.store-skeleton {
|
.store-skeleton {
|
||||||
@@ -123,186 +102,126 @@
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通用布局类
|
/* 顶部导航 */
|
||||||
.flex-center-sb {
|
.nav-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
padding: 0 30rpx;
|
||||||
|
height: 88rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
.nav-btn {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态栏区域
|
|
||||||
.status-bar-skeleton {
|
|
||||||
width: 100%;
|
|
||||||
height: 44rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-button-skeleton {
|
|
||||||
width: 48rpx;
|
width: 48rpx;
|
||||||
height: 48rpx;
|
height: 48rpx;
|
||||||
border-radius: 24rpx;
|
border-radius: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-button-skeleton {
|
/* 店铺 Logo */
|
||||||
width: 68rpx;
|
.logo-skeleton {
|
||||||
height: 68rpx;
|
width: 128rpx;
|
||||||
border-radius: 34rpx;
|
height: 128rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主图区域
|
/* 店铺信息 */
|
||||||
.main-image-skeleton {
|
.name-skeleton {
|
||||||
border-radius: 0;
|
width: 360rpx;
|
||||||
}
|
height: 44rpx;
|
||||||
|
|
||||||
// 店铺信息区域
|
|
||||||
.store-name-skeleton {
|
|
||||||
width: 400rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rating-skeleton {
|
.rating-skeleton {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cheflink-skeleton {
|
||||||
width: 120rpx;
|
width: 120rpx;
|
||||||
height: 24rpx;
|
height: 24rpx;
|
||||||
border-radius: 6rpx;
|
border-radius: 6rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chef-link-skeleton {
|
.sales-text-skeleton {
|
||||||
width: 100rpx;
|
width: 140rpx;
|
||||||
height: 24rpx;
|
height: 24rpx;
|
||||||
border-radius: 6rpx;
|
border-radius: 6rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.distance-skeleton {
|
/* 配送信息卡片 */
|
||||||
width: 80rpx;
|
.delivery-card-skeleton {
|
||||||
height: 24rpx;
|
|
||||||
border-radius: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-skeleton {
|
|
||||||
width: 500rpx;
|
|
||||||
height: 24rpx;
|
|
||||||
border-radius: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-skeleton {
|
|
||||||
width: 300rpx;
|
|
||||||
height: 48rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 配送方式
|
|
||||||
.delivery-switch-skeleton {
|
|
||||||
width: 318rpx;
|
|
||||||
height: 60rpx;
|
|
||||||
border-radius: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delivery-info-skeleton {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 164rpx;
|
height: 140rpx;
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标签页导航
|
/* 优惠券标签 */
|
||||||
.tabs-skeleton {
|
.coupon-tag-skeleton {
|
||||||
padding: 0 30rpx;
|
|
||||||
|
|
||||||
.tab-item-skeleton {
|
|
||||||
width: 120rpx;
|
width: 120rpx;
|
||||||
height: 30rpx;
|
height: 52rpx;
|
||||||
border-radius: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-divider {
|
|
||||||
width: 100%;
|
|
||||||
height: 10rpx;
|
|
||||||
margin-top: 32rpx;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分类标题
|
|
||||||
.section-title-skeleton {
|
|
||||||
width: 200rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 商品项
|
.claim-skeleton {
|
||||||
.product-item-skeleton {
|
width: 100rpx;
|
||||||
.product-image-skeleton {
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 商品卡片 */
|
||||||
|
.product-skeleton {
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-img-skeleton {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 248rpx;
|
height: 248rpx;
|
||||||
border-radius: 24rpx;
|
border-radius: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.product-name-skeleton {
|
|
||||||
width: 80%;
|
|
||||||
height: 30rpx;
|
|
||||||
border-radius: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.price-skeleton {
|
.price-skeleton {
|
||||||
width: 100rpx;
|
width: 120rpx;
|
||||||
height: 30rpx;
|
height: 36rpx;
|
||||||
border-radius: 6rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.member-price-skeleton {
|
|
||||||
width: 170rpx;
|
|
||||||
height: 28rpx;
|
|
||||||
border-radius: 14rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.original-price-skeleton {
|
|
||||||
width: 80rpx;
|
|
||||||
height: 28rpx;
|
|
||||||
border-radius: 6rpx;
|
border-radius: 6rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sales-skeleton {
|
.sales-skeleton {
|
||||||
width: 100rpx;
|
width: 80rpx;
|
||||||
height: 28rpx;
|
height: 22rpx;
|
||||||
border-radius: 6rpx;
|
border-radius: 6rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-button-skeleton {
|
.product-name-skeleton {
|
||||||
width: 64rpx;
|
width: 85%;
|
||||||
height: 64rpx;
|
height: 34rpx;
|
||||||
border-radius: 32rpx;
|
border-radius: 6rpx;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分隔线
|
.member-skeleton {
|
||||||
.divider-skeleton {
|
width: 160rpx;
|
||||||
width: 100%;
|
height: 42rpx;
|
||||||
height: 10rpx;
|
border-radius: 8rpx;
|
||||||
border-radius: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 底部提示
|
.add-btn-skeleton {
|
||||||
.bottom-tip-skeleton {
|
width: 52rpx;
|
||||||
height: 96rpx;
|
height: 52rpx;
|
||||||
border-radius: 0;
|
border-radius: 26rpx;
|
||||||
}
|
|
||||||
|
|
||||||
// 响应式设计
|
|
||||||
@media (max-width: 750rpx) {
|
|
||||||
.grid {
|
|
||||||
gap: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.product-item-skeleton {
|
|
||||||
.product-image-skeleton {
|
|
||||||
height: 200rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
+1460
-349
File diff suppressed because it is too large
Load Diff
@@ -165,6 +165,20 @@ function getStoreDetail() {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用详情中的首屏菜品初始化列表,并让下一次触底从第 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) {
|
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)
|
let distance = getDistanceInMiles(res.data.latitude, res.data.longitude, userStore.userLocation.latitude, userStore.userLocation.longitude)
|
||||||
@@ -309,15 +323,8 @@ const hasMore = ref(true);
|
|||||||
const isLoadingMore = ref(false);
|
const isLoadingMore = ref(false);
|
||||||
const dishListByQuery = ref<any[]>([]);
|
const dishListByQuery = ref<any[]>([]);
|
||||||
|
|
||||||
// 计算当前显示的商品列表
|
// 计算当前显示的商品列表(统一走 dishListByQuery,避免「全部」首屏用详情、加载更多再拼第 1 页造成重复)
|
||||||
const currentDishList = computed(() => {
|
const currentDishList = computed(() => dishListByQuery.value || [])
|
||||||
if(tabs.value[activeTab.value].key==''&&pageNum.value===1){
|
|
||||||
return storeDetail.value?.dishPage?.records
|
|
||||||
}
|
|
||||||
console.log(tabs.value[activeTab.value].key=='');
|
|
||||||
// 使用 dishListByQuery 作为数据源
|
|
||||||
return dishListByQuery.value || []
|
|
||||||
})
|
|
||||||
|
|
||||||
// 加载菜品列表
|
// 加载菜品列表
|
||||||
async function loadDishList(isLoadMore = false) {
|
async function loadDishList(isLoadMore = false) {
|
||||||
@@ -352,11 +359,10 @@ async function loadDishList(isLoadMore = false) {
|
|||||||
|
|
||||||
if (res.data && res.data.rows) {
|
if (res.data && res.data.rows) {
|
||||||
if (isLoadMore) {
|
if (isLoadMore) {
|
||||||
// 加载更多,追加数据
|
// 加载更多:按 id 去重,防止接口分页重叠或重复请求时的重复项
|
||||||
dishListByQuery.value = [
|
const existingIds = new Set(dishListByQuery.value.map((r: any) => r.id))
|
||||||
...dishListByQuery.value,
|
const nextRows = res.data.rows.filter((r: any) => r != null && !existingIds.has(r.id))
|
||||||
...res.data.rows
|
dishListByQuery.value = [...dishListByQuery.value, ...nextRows]
|
||||||
];
|
|
||||||
} else {
|
} else {
|
||||||
// 首次加载或刷新
|
// 首次加载或刷新
|
||||||
dishListByQuery.value = res.data.rows;
|
dishListByQuery.value = res.data.rows;
|
||||||
@@ -417,10 +423,7 @@ function navigateBack() {
|
|||||||
|
|
||||||
function navigateToCart() {
|
function navigateToCart() {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url:
|
url: '/pages-user/pages/cart/index',
|
||||||
'/pages-user/pages/cart/store-cart'
|
|
||||||
+ '?storeId=' + storeID.value
|
|
||||||
+ '&storeName=' + encodeURIComponent(storeDetail.value.merchantName),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,6 +458,16 @@ function navigateTo(url: string) {
|
|||||||
uni.navigateTo({ url })
|
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() {
|
function handleShare() {
|
||||||
uni.shareWithSystem({
|
uni.shareWithSystem({
|
||||||
@@ -475,74 +488,54 @@ function handleShare() {
|
|||||||
class="animate-in fade-in animate-ease-out animate-duration-300"
|
class="animate-in fade-in animate-ease-out animate-duration-300"
|
||||||
v-show="loading"
|
v-show="loading"
|
||||||
>
|
>
|
||||||
<!-- 骨架屏 -->
|
|
||||||
<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"
|
||||||
v-if="!loading"
|
v-if="!loading"
|
||||||
>
|
>
|
||||||
<!-- 页面内容 -->
|
|
||||||
<view class="store-box">
|
<view class="store-box">
|
||||||
<view class="relative">
|
<!-- 顶部导航栏 -->
|
||||||
<!-- 状态栏 -->
|
|
||||||
<view class="fixed top-0 left-0 z-9 w-full transition-all pt-6rpx" :class="[showStatusBar ? 'bg-#fff' : '']">
|
<view class="fixed top-0 left-0 z-9 w-full transition-all pt-6rpx" :class="[showStatusBar ? 'bg-#fff' : '']">
|
||||||
<status-bar />
|
<status-bar />
|
||||||
<view class="flex-center-sb px-30rpx">
|
<view class="flex-center-sb px-30rpx h-88rpx">
|
||||||
<image
|
<image
|
||||||
@click="navigateBack"
|
@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="w-48rpx h-48rpx relative z-1"
|
||||||
/>
|
/>
|
||||||
<view class="flex items-center">
|
|
||||||
<image
|
|
||||||
@click="navigateTo('/pages-store/pages/store/search/index?id=' + storeID)"
|
|
||||||
src="@img-store/1333.png"
|
|
||||||
mode="aspectFill"
|
|
||||||
class="w-68rpx h-68rpx relative z-1 mr-20rpx"
|
|
||||||
/>
|
|
||||||
<view @click="handleCollectionClick" class="w-68rpx h-68rpx relative z-1 mr-20rpx">
|
|
||||||
<image
|
|
||||||
v-if="!storeDetail?.isCollect"
|
|
||||||
src="@img-store/1334.png"
|
|
||||||
mode="aspectFill"
|
|
||||||
class="w-68rpx h-68rpx"
|
|
||||||
/>
|
|
||||||
<image
|
|
||||||
v-else
|
|
||||||
src="@img-store/1337.png"
|
|
||||||
mode="aspectFill"
|
|
||||||
class="w-68rpx h-68rpx"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<image
|
<image
|
||||||
@click="handleShare"
|
@click="handleShare"
|
||||||
src="@img-store/1335.png"
|
src="@img-store/1335.png"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
class="w-68rpx h-68rpx relative z-1"
|
class="w-48rpx h-48rpx relative z-1"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
<view class="anchors"></view>
|
<!-- 占位 -->
|
||||||
<!-- 主图 -->
|
|
||||||
<!-- <image-->
|
|
||||||
<!-- :src="storeDetail?.shopImages?.split(',')[0]"-->
|
|
||||||
<!-- mode="aspectFill"-->
|
|
||||||
<!-- class="w-750rpx h-562rpx absolute top-0 left-0"-->
|
|
||||||
<!-- />-->
|
|
||||||
<status-bar />
|
<status-bar />
|
||||||
<wd-swiper class="bg-common" v-if="storeDetail && storeDetail?.shopImages?.split(',').length > 0" :list="storeDetail?.shopImages?.split(',')" height="562rpx" autoplay></wd-swiper>
|
<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>
|
||||||
|
|
||||||
<view class="px-30rpx pt-40rpx pb-42rpx">
|
<!-- 店铺信息区域 -->
|
||||||
<!-- 店铺信息 -->
|
<view class="px-30rpx pt-24rpx pb-30rpx">
|
||||||
<view class="text-center">
|
<view class="text-center">
|
||||||
<view class="text-center text-40rpx lh-40rpx text-#333 font-bold">
|
<!-- 店铺名称 -->
|
||||||
|
<view class="text-36rpx lh-44rpx text-#333 font-bold">
|
||||||
{{ storeDetail?.merchantName }}
|
{{ storeDetail?.merchantName }}
|
||||||
</view>
|
</view>
|
||||||
<view class="center text-24rpx lh-24rpx my-16rpx">
|
<!-- 评分 + CHEFLINK -->
|
||||||
|
<view class="center text-24rpx lh-24rpx mt-16rpx">
|
||||||
<view class="flex items-center">
|
<view class="flex items-center">
|
||||||
<text class="text-#333 font-500">{{ storeDetail?.rating }}</text>
|
<text class="text-#333 font-500">{{ storeDetail?.rating }}</text>
|
||||||
<image
|
<image
|
||||||
@@ -558,46 +551,48 @@ function handleShare() {
|
|||||||
></image>
|
></image>
|
||||||
CHEFLINK
|
CHEFLINK
|
||||||
</view>
|
</view>
|
||||||
<text v-if="+storeDetail?.deliveryService === 1" class="text-#7D7D7D">
|
|
||||||
• {{ storeDetail?.deliveryTime }}{{ daySuffix(storeDetail?.deliveryTime) }}
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="text-24rpx lh-24rpx text-#7D7D7D text-center mt-16rpx">
|
<!-- 总销量 -->
|
||||||
{{ t('pages-store.store.title') }} US${{ storeDetail?.minOrderPrice }}
|
<view class="text-24rpx lh-24rpx text-#7D7D7D text-center mt-12rpx">
|
||||||
</view>
|
|
||||||
<!--根据商家营业时间计算处理-->
|
|
||||||
<view class="text-24rpx lh-24rpx text-#7D7D7D text-center mt-16rpx">
|
|
||||||
<template v-if="closingInfo.show">
|
|
||||||
{{ t('pages-store.store.tips') }} {{ closingInfo.minutes }} {{ t('pages-store.store.tips1') }}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
{{ parseBusinessHoursUtils(storeDetail?.businessHours) }}
|
|
||||||
</template>
|
|
||||||
<text v-if="storeDistance" class="ml-10rpx">
|
|
||||||
{{ t('common.distance') }} {{ storeDistance }} {{ t('common.mile') }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
<text v-if="storeDetail?.totalOrderCount > 0" class="mt-16rpx h-48rpx bg-#00A76D/18 rounded-8rpx px-22rpx mx-auto text-24rpx lh-24rpx text-#00A76D mr-20rpx">
|
|
||||||
{{ t('common.sales') }}:{{ storeDetail?.totalOrderCount }}
|
{{ t('common.sales') }}:{{ storeDetail?.totalOrderCount }}
|
||||||
</text>
|
</view>
|
||||||
<text v-if="storeDetail?.reorderedCount > 0" class="mt-16rpx h-48rpx bg-#00A76D/18 rounded-8rpx px-22rpx mx-auto text-24rpx lh-24rpx text-#00A76D">
|
|
||||||
{{ storeDetail?.reorderedCount }} {{ t('pages-store.store.tips2') }}
|
|
||||||
</text>
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 新功能插入区域 start -->
|
<!-- 配送信息卡片 -->
|
||||||
<!-- 商家优惠券 -->
|
<view v-if="showDeliverySwitch" class="delivery-info-card">
|
||||||
<view class="py-24rpx mt-40rpx border-top border-bottom" v-if="storeCouponList.length">
|
<template v-if="+storeDetail?.deliveryService === 1 && deliveryMethod === 0">
|
||||||
<view class="flex items-center justify-between mb-24rpx">
|
<view class="delivery-info-left">
|
||||||
<text class="text-28rpx lh-28rpx font-500 text-#333">{{ t('pages-store.store.merchantDiscounts') }}</text>
|
<view>{{ t('pages-store.store.tips4') }} $ {{ storeDetail?.minOrderPrice }}</view>
|
||||||
<view @click="handleClaimNow" class="flex items-center">
|
<view>{{ t('pages-store.store.tips5') }} $ {{ storeDetail?.deliveryFee }}{{ t('pages-store.store.start') }}</view>
|
||||||
<text class="text-28rpx lh-28rpx font-500 text-#CE7138 mr-8rpx">{{ t('pages-store.store.claimNow') }}</text>
|
|
||||||
<i class="i-carbon:chevron-right text-24rpx text-#CE7138"></i>
|
|
||||||
</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>
|
||||||
|
<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-view
|
||||||
scroll-x
|
scroll-x
|
||||||
class="coupon-scroll"
|
class="coupon-scroll flex-1"
|
||||||
:show-scrollbar="false"
|
:show-scrollbar="false"
|
||||||
enable-flex
|
enable-flex
|
||||||
>
|
>
|
||||||
@@ -613,109 +608,106 @@ function handleShare() {
|
|||||||
</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>
|
||||||
<!-- 新功能插入区域 end -->
|
|
||||||
|
|
||||||
<!-- 自取 配送切换 -->
|
<!-- 分类标签(胶囊样式) -->
|
||||||
<!-- <view v-if="showDeliverySwitch" class="w-318rpx mt-40rpx">
|
<scroll-view scroll-x class="w-full" :show-scrollbar="false" enable-flex>
|
||||||
<l-segmented :value="deliveryMethod" :options="deliveryMethodOptions" @click="handleClickSegmented" shape="round" bg-color="#F2F2F2" active-color="#333" />
|
<view class="flex items-center px-30rpx pb-24rpx">
|
||||||
</view> -->
|
<view
|
||||||
<view v-if="showDeliverySwitch" class="border-#D8D8D8 border-solid border-1px rounded-20rpx min-h-164rpx mt-36rpx px-45rpx py-18rpx text-24rpx lh-24rpx flex-center-sb">
|
v-for="(item, index) in tabs"
|
||||||
<template v-if="+storeDetail?.deliveryService === 1 && deliveryMethod === 0">
|
:key="item.key"
|
||||||
<view class="w-full h-full flex-1 text-center text-#CE7138 pr-44rpx">
|
@click="activeTab = index"
|
||||||
<view>{{ t('pages-store.store.tips4') }} ${{ storeDetail?.minOrderPrice }}</view>
|
class="tab-chip"
|
||||||
<view>{{ t('pages-store.store.tips5') }} ${{ storeDetail?.deliveryFee }}{{ t('pages-store.store.start') }}</view>
|
:class="activeTab === index ? 'tab-chip--active' : ''"
|
||||||
</view>
|
>
|
||||||
<view class="h-128rpx w-1rpx rotate-0 bg-#D8D8D8"></view>
|
{{ item.title }}
|
||||||
<view class="w-full flex-1 center flex-col pl-44rpx">
|
|
||||||
<view class="text-#333 mb-8rpx">
|
|
||||||
{{ storeDetail?.deliveryTime }}{{ daySuffix(storeDetail?.deliveryTime) }}
|
|
||||||
</view>
|
|
||||||
<view class="text-#7D7D7D">{{ t('pages-store.store.earTime') }}</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
<template v-if="+storeDetail?.selfPickup === 1 && deliveryMethod === 1">
|
|
||||||
<view class="w-full h-full flex-1 text-center text-#CE7138 pr-44rpx">
|
|
||||||
<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 class="">--</view>
|
|
||||||
</view>
|
|
||||||
<view class="h-128rpx w-1rpx rotate-0 bg-#D8D8D8"></view>
|
|
||||||
<view class="w-full flex-1 center flex-col pl-44rpx">
|
|
||||||
<view class="text-#333 mb-8rpx">{{ storeDetail?.pickupTime }}{{ t('common.minutes') }}</view>
|
|
||||||
<view class="text-#7D7D7D">{{ t('pages-store.store.earTime') }}</view>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<wd-tabs v-model="activeTab" slidable="always" autoLineWidth>
|
</scroll-view>
|
||||||
<block v-for="item in tabs" :key="item">
|
|
||||||
<wd-tab :title="item.title">
|
|
||||||
</wd-tab>
|
|
||||||
</block>
|
|
||||||
</wd-tabs>
|
|
||||||
<view class="box mt--6px"></view>
|
|
||||||
|
|
||||||
<view v-if="tabs.length > 0" class="px-30rpx pb-120rpx">
|
<!-- 商品列表 -->
|
||||||
<view v-if="currentDishList.length > 0" class="text-40rpx lh-40rpx font-500 my-36rpx">{{ t('pages-store.store.recommend') }}</view>
|
<view v-if="tabs.length > 0" class="px-30rpx pb-180rpx">
|
||||||
<view v-if="currentDishList.length > 0" class="grid grid-cols-2 gap-30rpx">
|
<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="w-100% mb-10rpx">
|
<view @click="navigateToDishes(item)" class="dish-card" :class="{ 'dish-card--soldout': isSoldOutStock(item?.stock) }">
|
||||||
<view class="relative h-248rpx rounded-24rpx mb-28rpx">
|
<!-- 商品图片 -->
|
||||||
<view @click.stop="handleDishCollectionClick(item)" class="w-68rpx h-68rpx absolute z-2 top-0 right-0">
|
<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
|
<image
|
||||||
v-if="!item.isCollect"
|
v-if="!item.isCollect"
|
||||||
src="@img-store/1334.png"
|
src="@img-store/1334.png"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
class="w-full h-full"
|
class="w-44rpx h-44rpx"
|
||||||
|
style="filter: drop-shadow(0 2rpx 6rpx rgba(0,0,0,0.18))"
|
||||||
/>
|
/>
|
||||||
<image
|
<image
|
||||||
v-else
|
v-else
|
||||||
src="@img-store/1337.png"
|
src="@img-store/1337.png"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
class="w-full h-full"
|
class="w-44rpx h-44rpx"
|
||||||
|
style="filter: drop-shadow(0 2rpx 6rpx rgba(0,0,0,0.18))"
|
||||||
/>
|
/>
|
||||||
</view>
|
</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
|
<image
|
||||||
:src="item?.dishImage?.split(',')[0]"
|
:src="item?.dishImage?.split(',')[0]"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
class="w-full h-full rounded-24rpx bg-common"
|
class="dish-card-img"
|
||||||
v-if="item.dishImage.split(',').length > 0"
|
|
||||||
/>
|
|
||||||
<image
|
|
||||||
:src="item?.dishImage"
|
|
||||||
mode="aspectFill"
|
|
||||||
class="w-full h-full rounded-24rpx bg-common"
|
|
||||||
v-else
|
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
<view class="line-clamp-1 text-30rpx text-#333 font-500">
|
<!-- 卡片信息区 -->
|
||||||
|
<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 }}
|
{{ item.dishName }}
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-center-sb mt-12rpx">
|
<!-- 会员价 + 加购按钮 -->
|
||||||
<text class="text-26rpx lh-30rpx text-#333 font-500">US${{ item.discountPrice }}</text>
|
<view class="flex items-center justify-between gap-12rpx">
|
||||||
<view
|
<view
|
||||||
v-if="Number(item.memberPrice) > 0"
|
v-if="Number(item.memberPrice) > 0"
|
||||||
class="member-price-tag text-[#FBE3C3] font-500 text-28rpx lh-28rpx center pl-6rpx break-all"
|
class="dish-member shrink min-w-0"
|
||||||
>
|
>
|
||||||
<text class="!text-24rpx">{{ t('pages-store.store.members') }}: </text>
|
<text class="dish-member-inner">{{ t('pages-store.store.members') }}: $ {{ item.memberPrice }}</text>
|
||||||
${{ item.memberPrice }}
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<view v-else class="flex-1 min-w-0"></view>
|
||||||
<view class="flex-center-sb mt-12rpx">
|
<view class="dish-add-btn center shrink-0">
|
||||||
<view class="text-28rpx text-#999">
|
|
||||||
<view class="line-through">US${{ item.originalPrice }}</view>
|
|
||||||
<view>{{ t('pages-store.store.sales') }}:{{ item.salesCount }}</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="center w-64rpx h-64rpx rounded-50% bg-white shadow-lg">
|
|
||||||
<image
|
<image
|
||||||
src="@img/chef/1285.png"
|
src="@img/chef/1285.png"
|
||||||
class="w-30rpx h-30rpx shrink-0"
|
class="w-28rpx h-28rpx"
|
||||||
></image>
|
></image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<!-- 营销标签 -->
|
||||||
|
<view
|
||||||
|
v-if="getDishPromoLabel(item)"
|
||||||
|
class="dish-promo mt-16rpx"
|
||||||
|
>
|
||||||
|
<text class="dish-promo-text">{{ getDishPromoLabel(item) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
</view>
|
</view>
|
||||||
@@ -725,7 +717,29 @@ function handleShare() {
|
|||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
</view>
|
</view>
|
||||||
<view @click="navigateTo('/pages-user/pages/member/index')" v-if="cartDataList.length > 0 && cartSavingsData && cartSavingsData.savings > 0" class="h-96rpx bg-#CE7138 pl-56rpx flex items-center fixed bottom-0 left-0 w-full z-9">
|
|
||||||
|
<!-- 底部购物车浮窗 -->
|
||||||
|
<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
|
<image
|
||||||
src="@img/chef/1289.png"
|
src="@img/chef/1289.png"
|
||||||
class="mr-10rpx w-32rpx h-32rpx shrink-0"
|
class="mr-10rpx w-32rpx h-32rpx shrink-0"
|
||||||
@@ -735,16 +749,7 @@ function handleShare() {
|
|||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view v-if="cartDataList.length > 0" @click="navigateToCart" class="fixed z-9 bottom-138rpx left-50% translate-x--50% px-26rpx h-88rpx bg-#14181B rounded-44rpx center text-28rpx text-#fff font-500">
|
|
||||||
<image src="@img/chef/125.png" class="w-28rpx h-28rpx shrink-0"></image>
|
|
||||||
<view class="ml-10rpx whitespace-nowrap line-clamp-1">{{ storeDetail.merchantName }}</view>
|
|
||||||
<view class="w-8rpx h-8rpx rounded-50% bg-white mx-8rpx"></view>
|
|
||||||
<text>{{ cartDataList.length }}</text>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<coupon-popup
|
<coupon-popup
|
||||||
ref="couponPopupRef"
|
ref="couponPopupRef"
|
||||||
@@ -752,43 +757,51 @@ function handleShare() {
|
|||||||
@confirm="getMerchantCouponReceiveList"
|
@confirm="getMerchantCouponReceiveList"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
page {
|
page {
|
||||||
background-color: #fff;
|
background-color: #F6F6F6;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
:deep(.wd-swiper__track) {
|
/* ====== 配送信息卡片 ====== */
|
||||||
border-radius: 0;
|
.delivery-info-card {
|
||||||
}
|
display: flex;
|
||||||
:deep(.wd-tabs__nav-container) {
|
align-items: center;
|
||||||
.is-active {
|
justify-content: space-between;
|
||||||
color: #333 !important;
|
margin-top: 30rpx;
|
||||||
font-weight: 500 !important;
|
padding: 24rpx 40rpx;
|
||||||
}
|
border: 2rpx solid #D8D8D8;
|
||||||
}
|
border-radius: 20rpx;
|
||||||
:deep(.wd-tabs__nav-item-text) {
|
min-height: 140rpx;
|
||||||
font-size: 30rpx !important;
|
|
||||||
//color: #7D7D7D;
|
|
||||||
padding-bottom: 32rpx !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.wd-tabs__line) {
|
.delivery-info-left {
|
||||||
border-radius: 0 !important;
|
flex: 1;
|
||||||
height: 10rpx !important;
|
text-align: center;
|
||||||
background-color: #333333 !important;
|
font-size: 24rpx;
|
||||||
}
|
line-height: 36rpx;
|
||||||
.box {
|
color: #CE7138;
|
||||||
border-bottom: 10rpx solid #F6F6F6 !important;
|
padding-right: 30rpx;
|
||||||
}
|
|
||||||
.member-price-tag {
|
|
||||||
min-width: 190rpx;
|
|
||||||
height: 42rpx;
|
|
||||||
background-image: url("/static/images/chef/1282.png");
|
|
||||||
background-size: 100% 100%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
.coupon-scroll {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -800,7 +813,7 @@ page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.coupon-item {
|
.coupon-item {
|
||||||
margin-right: 20rpx;
|
margin-right: 15rpx;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
@@ -809,12 +822,12 @@ page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.coupon-tag {
|
.coupon-tag {
|
||||||
min-width: 120rpx;
|
min-width: 110rpx;
|
||||||
height: 52rpx;
|
height: 52rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0 28rpx;
|
padding: 0 18rpx;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-image: url("/static/images/5008.png");
|
background-image: url("/static/images/5008.png");
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
@@ -828,4 +841,248 @@ page {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
white-space: nowrap;
|
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>
|
</style>
|
||||||
@@ -45,11 +45,16 @@ function navigateToDishes(item: any) {
|
|||||||
<view class="grid grid-cols-2 gap-x-30rpx gap-y-46rpx pb-40rpx px-30rpx">
|
<view class="grid grid-cols-2 gap-x-30rpx gap-y-46rpx pb-40rpx px-30rpx">
|
||||||
<template v-for="item in dataList">
|
<template v-for="item in dataList">
|
||||||
<view @click="navigateToDishes(item)" class="w-330rpx overflow-hidden">
|
<view @click="navigateToDishes(item)" class="w-330rpx overflow-hidden">
|
||||||
|
<view class="search-result-img-wrap">
|
||||||
|
<view v-if="item.isNew == 1" class="dish-new-ribbon">
|
||||||
|
<text class="dish-new-ribbon__text">NEW</text>
|
||||||
|
</view>
|
||||||
<image
|
<image
|
||||||
:src="item?.dishImage?.split(',')[0]"
|
:src="item?.dishImage?.split(',')[0]"
|
||||||
class="w-full h-186rpx rounded-24rpx mb-16rpx"
|
class="w-full h-186rpx rounded-24rpx"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
></image>
|
></image>
|
||||||
|
</view>
|
||||||
<text class="text-30rpx lh-30rpx text-#333 line-clamp-1 tracking-[.04em] font-500"
|
<text class="text-30rpx lh-30rpx text-#333 line-clamp-1 tracking-[.04em] font-500"
|
||||||
>{{ item.dishName }}</text
|
>{{ item.dishName }}</text
|
||||||
>
|
>
|
||||||
@@ -60,5 +65,36 @@ function navigateToDishes(item: any) {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.search-result-img-wrap {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -25,9 +25,9 @@ defineExpose({
|
|||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
>
|
>
|
||||||
<view class="px-30rpx text-#333 text-center pt-48rpx pb-60rpx">
|
<view class="px-30rpx text-#333 text-center pt-48rpx pb-60rpx">
|
||||||
<view class="text-40rpx lh-40rpx font-500">Delete shopping cart?</view>
|
<view class="text-40rpx lh-40rpx font-500">{{ t("pages-user.cart.removeCartTitle") }}</view>
|
||||||
<view class="text-28rpx lh-28rpx mt-36rpx">
|
<view class="text-28rpx lh-28rpx mt-36rpx">
|
||||||
Are you sure you want to delete the shopping cart?
|
{{ t("pages-user.cart.removeCartDesc") }}
|
||||||
</view>
|
</view>
|
||||||
<view class="mt-70rpx">
|
<view class="mt-70rpx">
|
||||||
<wd-button @click="handleDelete" custom-class="!h-108rpx !bg-14181B !text-36rpx !lh-108rpx !text-#fff !rounded-16rpx" block>
|
<wd-button @click="handleDelete" custom-class="!h-108rpx !bg-14181B !text-36rpx !lh-108rpx !text-#fff !rounded-16rpx" block>
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ const emit = defineEmits(['confirm','close'])
|
|||||||
|
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
const goodsName = ref('');
|
const goodsName = ref('');
|
||||||
|
|
||||||
|
function fillI18nParams(template: string, params: Record<string, string | number>) {
|
||||||
|
let text = template;
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
const value = String(params[key] ?? "");
|
||||||
|
text = text.replace(new RegExp(`\\{${key}\\}`, "g"), value);
|
||||||
|
text = text.replace(new RegExp(`\\$\\{${key}\\}`, "g"), value);
|
||||||
|
});
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeProductDescText = computed(() =>
|
||||||
|
fillI18nParams(t("pages-user.cart.removeProductDesc"), { name: goodsName.value })
|
||||||
|
);
|
||||||
function onOpen(title: string) {
|
function onOpen(title: string) {
|
||||||
if (title) {
|
if (title) {
|
||||||
goodsName.value = title;
|
goodsName.value = title;
|
||||||
@@ -31,9 +45,9 @@ defineExpose({
|
|||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
>
|
>
|
||||||
<view class="px-30rpx text-#333 text-center pt-48rpx pb-60rpx">
|
<view class="px-30rpx text-#333 text-center pt-48rpx pb-60rpx">
|
||||||
<view class="text-40rpx lh-40rpx font-500">Remove the product?</view>
|
<view class="text-40rpx lh-40rpx font-500">{{ t("pages-user.cart.removeProductTitle") }}</view>
|
||||||
<view class="text-28rpx lh-28rpx mt-36rpx">
|
<view class="text-28rpx lh-28rpx mt-36rpx">
|
||||||
Are you sure you want to remove {{ goodsName }} from your shopping cart?
|
{{ removeProductDescText }}
|
||||||
</view>
|
</view>
|
||||||
<view class="mt-70rpx">
|
<view class="mt-70rpx">
|
||||||
<wd-button @click="confirmRemove" custom-class="!h-108rpx !bg-14181B !text-36rpx !lh-108rpx !text-#fff !rounded-16rpx" block>
|
<wd-button @click="confirmRemove" custom-class="!h-108rpx !bg-14181B !text-36rpx !lh-108rpx !text-#fff !rounded-16rpx" block>
|
||||||
|
|||||||
+1034
-244
File diff suppressed because it is too large
Load Diff
@@ -115,20 +115,14 @@ function scheduleCartUpdate(item: MerchantCartVo, targetValue: number) {
|
|||||||
pendingUpdateTimers.set(key, timer)
|
pendingUpdateTimers.set(key, timer)
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeInputNumberValue(payload: any): number {
|
/** wd-input-number change 为 { value },兼容 detail.value */
|
||||||
if (typeof payload === 'number') {
|
function normalizeInputNumberValue(payload: unknown): number {
|
||||||
return payload
|
const raw =
|
||||||
}
|
typeof payload === "number"
|
||||||
if (payload && typeof payload === 'object') {
|
? payload
|
||||||
if (typeof payload.detail?.value === 'number') {
|
: (payload as any)?.value ?? (payload as any)?.detail?.value ?? payload;
|
||||||
return payload.detail.value
|
const n = Number(raw);
|
||||||
}
|
return Number.isFinite(n) ? n : 0;
|
||||||
if (typeof payload.value === 'number') {
|
|
||||||
return payload.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const numeric = Number(payload)
|
|
||||||
return Number.isNaN(numeric) ? 0 : numeric
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const delItemData = ref<MerchantCartVo | null>(null)
|
const delItemData = ref<MerchantCartVo | null>(null)
|
||||||
@@ -247,6 +241,18 @@ function appMerchantCartCalculateSavings() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCartItemDiscountPrice(item: MerchantCartVo) {
|
||||||
|
return item.discountPrice ?? item.merchantDishVo?.discountPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCartItemOriginalPrice(item: MerchantCartVo) {
|
||||||
|
return item.originalPrice ?? item.merchantDishVo?.originalPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCartItemMemberPrice(item: MerchantCartVo) {
|
||||||
|
return item.memberPrice ?? item.merchantDishVo?.memberPrice
|
||||||
|
}
|
||||||
|
|
||||||
function navigateTo(url: string) {
|
function navigateTo(url: string) {
|
||||||
uni.navigateTo({ url })
|
uni.navigateTo({ url })
|
||||||
}
|
}
|
||||||
@@ -301,21 +307,21 @@ function navigateTo(url: string) {
|
|||||||
|
|
||||||
<view class="price-row flex items-center mt-18rpx">
|
<view class="price-row flex items-center mt-18rpx">
|
||||||
<text class="current-price text-[#333333] text-30rpx font-normal"
|
<text class="current-price text-[#333333] text-30rpx font-normal"
|
||||||
>${{ item.merchantDishVo.discountPrice }}</text
|
>${{ getCartItemDiscountPrice(item) }}</text
|
||||||
>
|
>
|
||||||
<text
|
<text
|
||||||
v-if="item.merchantDishVo.originalPrice"
|
v-if="getCartItemOriginalPrice(item)"
|
||||||
class="original-price text-[#7D7D7D] text-24rpx font-normal line-through ml-10rpx"
|
class="original-price text-[#7D7D7D] text-24rpx font-normal line-through ml-10rpx"
|
||||||
>${{ item.merchantDishVo.originalPrice }}</text
|
>${{ getCartItemOriginalPrice(item) }}</text
|
||||||
>
|
>
|
||||||
|
|
||||||
<!-- 会员价标签 -->
|
<!-- 会员价标签 -->
|
||||||
<view
|
<view
|
||||||
v-if="Number(item.merchantDishVo.memberPrice) > 0"
|
v-if="Number(getCartItemMemberPrice(item)) > 0"
|
||||||
class="member-price-tag ml-10rpx center pl-16rpx pr-6rpx pb-2rpx"
|
class="member-price-tag ml-10rpx center pl-16rpx pr-6rpx pb-2rpx"
|
||||||
>
|
>
|
||||||
<text class="text-[#FBE3C3] text-20rpx"
|
<text class="text-[#FBE3C3] text-20rpx"
|
||||||
>{{ t('pages-store.store.members') }}: ${{ item.merchantDishVo.memberPrice }}</text
|
>{{ t('pages-store.store.members') }}: ${{ getCartItemMemberPrice(item) }}</text
|
||||||
>
|
>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
+284
-62
@@ -1,11 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { debounce } from 'throttle-debounce'
|
import { debounce } from 'throttle-debounce'
|
||||||
import {appCollectListPost, appCollectCollectPost} from "@/service";
|
import {appCollectListPost, appCollectCollectPost} from "@/service";
|
||||||
import FoodBox from "@/pages/home/components/tabbar-home/components/food-box/index.vue";
|
|
||||||
import Collection from "@/components/collection/index.vue";
|
import Collection from "@/components/collection/index.vue";
|
||||||
import {CollectionType} from "@/constant/enums";
|
import {CollectionType} from "@/constant/enums";
|
||||||
|
|
||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'batch-delete-success'): void
|
||||||
|
}>()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
currentIndex: {
|
currentIndex: {
|
||||||
type: Number,
|
type: Number,
|
||||||
@@ -15,6 +17,18 @@ const props = defineProps({
|
|||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
batchDeleteMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedIds = ref<string[]>([])
|
||||||
|
|
||||||
|
watch(() => props.batchDeleteMode, (v) => {
|
||||||
|
if (!v) {
|
||||||
|
selectedIds.value = []
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// (1, "菜谱")(2, "菜品")(3, "配菜")(4, "商家")
|
// (1, "菜谱")(2, "菜品")(3, "配菜")(4, "商家")
|
||||||
@@ -35,7 +49,7 @@ const {paging, dataList, queryList, loading} = usePage<any>((pageNum: number, pa
|
|||||||
pageSize,
|
pageSize,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
targetType: typeList[props.currentIndex]
|
targetType: typeList[props.index]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -51,6 +65,106 @@ function navigateToDishes(item: any) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDishInfo(item: any) {
|
||||||
|
return item?.merchantDishVo || item || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDishImage(item: any) {
|
||||||
|
const dish = getDishInfo(item)
|
||||||
|
return dish?.dishImage?.split?.(',')?.[0] || dish?.dishImage || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前列表 tab 对应的收藏目标 id(与 appCollectCollectPost 一致) */
|
||||||
|
function getCollectTargetId(item: any): string {
|
||||||
|
const i = props.index
|
||||||
|
if (i === 0) {
|
||||||
|
return String(item.merchantVo?.id ?? item.merchantId ?? getDishInfo(item).merchantId ?? item.id ?? '')
|
||||||
|
}
|
||||||
|
if (i === 1) {
|
||||||
|
return String(item.merchantDishVo?.id ?? getDishInfo(item).id ?? item.id ?? '')
|
||||||
|
}
|
||||||
|
if (i === 2) {
|
||||||
|
return String(item.merchantRecipeVo?.id ?? item.id ?? '')
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelect(item: any) {
|
||||||
|
if (!props.batchDeleteMode) return
|
||||||
|
const id = getCollectTargetId(item)
|
||||||
|
if (!id) return
|
||||||
|
const idx = selectedIds.value.indexOf(id)
|
||||||
|
if (idx >= 0) {
|
||||||
|
selectedIds.value.splice(idx, 1)
|
||||||
|
} else {
|
||||||
|
selectedIds.value.push(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelected(item: any) {
|
||||||
|
const id = getCollectTargetId(item)
|
||||||
|
return id ? selectedIds.value.includes(id) : false
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStoreOrDishRowClick(item: any) {
|
||||||
|
if (props.batchDeleteMode) {
|
||||||
|
toggleSelect(item)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
navigateToDishes(getDishInfo(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRecipeCardClick(item: any) {
|
||||||
|
if (props.batchDeleteMode) {
|
||||||
|
toggleSelect(item)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
navigateToRecipeDetail(item.merchantRecipeVo?.id ?? item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function runBatchDelete() {
|
||||||
|
if (selectedIds.value.length === 0) {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('pages.mine.collectionSelectFirst'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.showModal({
|
||||||
|
title: t('common.prompt.system-prompt'),
|
||||||
|
content: t('pages.mine.collectionBatchDeleteConfirm', { count: selectedIds.value.length }),
|
||||||
|
success: (res) => {
|
||||||
|
if (!res.confirm) return
|
||||||
|
const targetType = typeList[props.index] as number
|
||||||
|
const ids = [...selectedIds.value]
|
||||||
|
;(async () => {
|
||||||
|
try {
|
||||||
|
for (const targetId of ids) {
|
||||||
|
await appCollectCollectPost({
|
||||||
|
body: {
|
||||||
|
targetId,
|
||||||
|
targetType,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
selectedIds.value = []
|
||||||
|
emit('batch-delete-success')
|
||||||
|
paging.value?.reload()
|
||||||
|
uni.showToast({
|
||||||
|
title: t('toast.deleteSuccess'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
uni.showToast({
|
||||||
|
title: t('common.prompt.request-incorrect'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 收藏菜谱
|
// 收藏菜谱
|
||||||
function handleSubmitCollectRecipe(item:any) {
|
function handleSubmitCollectRecipe(item:any) {
|
||||||
debouncedEmit(item.merchantRecipeVo?.isCollect, item.merchantRecipeVo?.id, CollectionType.RECIPE, ()=> {
|
debouncedEmit(item.merchantRecipeVo?.isCollect, item.merchantRecipeVo?.id, CollectionType.RECIPE, ()=> {
|
||||||
@@ -58,12 +172,6 @@ function handleSubmitCollectRecipe(item:any) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏菜品
|
|
||||||
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) => {
|
const debouncedEmit = debounce(1300, (isCollected: boolean, id: string, type: CollectionType, callback: ()=> void) => {
|
||||||
// 收藏接口
|
// 收藏接口
|
||||||
@@ -94,6 +202,7 @@ function refresh() {
|
|||||||
defineExpose({
|
defineExpose({
|
||||||
reload,
|
reload,
|
||||||
refresh,
|
refresh,
|
||||||
|
runBatchDelete,
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -101,71 +210,66 @@ defineExpose({
|
|||||||
<view class="h-full">
|
<view class="h-full">
|
||||||
<z-paging ref="paging" v-model="dataList" @query="queryList" :fixed="false" :auto="false">
|
<z-paging ref="paging" v-model="dataList" @query="queryList" :fixed="false" :auto="false">
|
||||||
<view class="p-30rpx">
|
<view class="p-30rpx">
|
||||||
<template v-if="currentIndex == 0">
|
<template v-if="currentIndex == 0 || currentIndex == 1">
|
||||||
<!--商家-->
|
<view class="collection-list">
|
||||||
<view v-for="item in dataList" :key="item.id">
|
|
||||||
<food-box :item="item.merchantVo" />
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
<template v-if="currentIndex == 1">
|
|
||||||
<!--菜品-->
|
|
||||||
<view class="grid grid-cols-2 gap-30rpx">
|
|
||||||
<template v-for="item in dataList">
|
|
||||||
<view @click="navigateToDishes(item.merchantDishVo)" class="w-100% mb-10rpx rounded-16rpx">
|
|
||||||
<view class="relative h-248rpx rounded-24rpx mb-28rpx">
|
|
||||||
<view @click.stop="handleDishCollectionClick(item.merchantDishVo)" class="w-68rpx h-68rpx absolute z-2 top-0 right-0">
|
|
||||||
<image
|
|
||||||
v-if="!item.merchantDishVo.isCollect"
|
|
||||||
src="@img-store/1334.png"
|
|
||||||
mode="aspectFill"
|
|
||||||
class="w-full h-full"
|
|
||||||
/>
|
|
||||||
<image
|
|
||||||
v-else
|
|
||||||
src="@img-store/1337.png"
|
|
||||||
mode="aspectFill"
|
|
||||||
class="w-full h-full"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<image
|
|
||||||
:src="item.merchantDishVo?.dishImage?.split(',')[0]"
|
|
||||||
mode="aspectFill"
|
|
||||||
class="w-full h-full rounded-24rpx"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<view class="line-clamp-1 text-30rpx lh-30rpx text-#333 font-500 mb-12rpx">
|
|
||||||
{{ item.merchantDishVo.dishName }}
|
|
||||||
</view>
|
|
||||||
<view class="flex-center-sb">
|
|
||||||
<text class="text-30rpx lh-30rpx text-#333 font-500">US${{ item.merchantDishVo.discountPrice }}</text>
|
|
||||||
<view
|
<view
|
||||||
v-if="Number(item.merchantDishVo.memberPrice) > 0"
|
v-for="item in dataList"
|
||||||
class="member-price-tag text-[#FBE3C3] text-18rpx center pl-6rpx"
|
:key="item.id"
|
||||||
|
class="collection-item"
|
||||||
|
@click="onStoreOrDishRowClick(item)"
|
||||||
>
|
>
|
||||||
{{ t('pages-store.store.members') }}: ${{ item.merchantDishVo.memberPrice }}
|
<view
|
||||||
|
v-if="batchDeleteMode"
|
||||||
|
class="collection-item__check"
|
||||||
|
@click.stop="toggleSelect(item)"
|
||||||
|
>
|
||||||
|
<view class="check-box" :class="{ 'is-on': isSelected(item) }">
|
||||||
|
<text v-if="isSelected(item)" class="check-tick">✓</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-center-sb mt-12rpx">
|
|
||||||
<view class="text-28rpx text-#999">
|
|
||||||
<view class="line-through">US${{ item.merchantDishVo.originalPrice }}</view>
|
|
||||||
<view>{{ t('pages-store.store.sales') }}:{{ item.merchantDishVo.salesCount }}</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="center w-64rpx h-64rpx rounded-50% bg-white shadow-lg">
|
|
||||||
<image
|
<image
|
||||||
src="@img/chef/1285.png"
|
:src="getDishImage(item)"
|
||||||
class="w-30rpx h-30rpx shrink-0"
|
mode="aspectFill"
|
||||||
></image>
|
class="collection-item__image"
|
||||||
|
/>
|
||||||
|
<view class="collection-item__content">
|
||||||
|
<view class="collection-item__name line-clamp-1">{{ getDishInfo(item).dishName }}</view>
|
||||||
|
<view class="collection-item__price-row mt-10rpx">
|
||||||
|
<text class="collection-item__price">${{ getDishInfo(item).discountPrice }}</text>
|
||||||
|
<text
|
||||||
|
v-if="Number(getDishInfo(item).originalPrice) > 0"
|
||||||
|
class="collection-item__original ml-10rpx"
|
||||||
|
>
|
||||||
|
${{ getDishInfo(item).originalPrice }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="Number(getDishInfo(item).memberPrice) > 0" class="collection-item__member mt-12rpx">
|
||||||
|
<image src="@img-store/1339.png" class="w-22rpx h-22rpx mr-6rpx" />
|
||||||
|
{{ t('pages.browse.brandTag') }} {{ t('pages-store.store.members') }}: ${{ getDishInfo(item).memberPrice }}
|
||||||
|
</view>
|
||||||
|
<view class="collection-item__sales mt-10rpx">
|
||||||
|
{{ t('pages-store.store.sales') }} : {{ getDishInfo(item).salesCount || 0 }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="!batchDeleteMode" class="collection-item__add center">
|
||||||
|
<text class="text-42rpx text-#333">+</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="currentIndex == 2">
|
<template v-if="currentIndex == 2">
|
||||||
<view class="grid grid-cols-2 gap-30rpx">
|
<view class="grid grid-cols-2 gap-30rpx">
|
||||||
<view v-for="item in dataList">
|
<view v-for="item in dataList" :key="item.id" class="recipe-card-wrap">
|
||||||
<view @click="navigateToRecipeDetail(item.id)" class="w-312rpx">
|
<view
|
||||||
|
v-if="batchDeleteMode"
|
||||||
|
class="recipe-card__check"
|
||||||
|
@click.stop="toggleSelect(item)"
|
||||||
|
>
|
||||||
|
<view class="check-box" :class="{ 'is-on': isSelected(item) }">
|
||||||
|
<text v-if="isSelected(item)" class="check-tick">✓</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view @click="onRecipeCardClick(item)" class="w-312rpx">
|
||||||
<image
|
<image
|
||||||
:src="item.merchantRecipeVo?.recipeImage?.split(',')[0]"
|
:src="item.merchantRecipeVo?.recipeImage?.split(',')[0]"
|
||||||
class="w-310rpx h-296rpx rounded-24rpx mb-26rpx"
|
class="w-310rpx h-296rpx rounded-24rpx mb-26rpx"
|
||||||
@@ -175,7 +279,7 @@ defineExpose({
|
|||||||
<text class="text-30rpx lh-30rpx text-#333 line-clamp-1 tracking-[.04em] font-500"
|
<text class="text-30rpx lh-30rpx text-#333 line-clamp-1 tracking-[.04em] font-500"
|
||||||
>{{ item.merchantRecipeVo?.recipeName }}</text
|
>{{ item.merchantRecipeVo?.recipeName }}</text
|
||||||
>
|
>
|
||||||
<view class="w-40rpx h-40rpx ml-14rpx shrink-0">
|
<view v-if="!batchDeleteMode" class="w-40rpx h-40rpx ml-14rpx shrink-0">
|
||||||
<collection
|
<collection
|
||||||
:is-collected="item.merchantRecipeVo?.isCollect"
|
:is-collected="item.merchantRecipeVo?.isCollect"
|
||||||
@collectionChange="handleSubmitCollectRecipe(item)"
|
@collectionChange="handleSubmitCollectRecipe(item)"
|
||||||
@@ -192,5 +296,123 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.collection-list {
|
||||||
|
.collection-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
border-bottom: 1rpx solid #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-item__image {
|
||||||
|
width: 168rpx;
|
||||||
|
height: 168rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: #f4f4f4;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-item__content {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-item__name {
|
||||||
|
font-size: 30rpx;
|
||||||
|
line-height: 38rpx;
|
||||||
|
color: #222;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-item__price-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-item__price {
|
||||||
|
color: #e7362f;
|
||||||
|
font-size: 42rpx;
|
||||||
|
line-height: 42rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-item__original {
|
||||||
|
color: #a5a5a5;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-item__member {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: #c5883f;
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-item__sales {
|
||||||
|
width: fit-content;
|
||||||
|
height: 42rpx;
|
||||||
|
padding: 0 14rpx;
|
||||||
|
border-radius: 10rpx;
|
||||||
|
background: #f1f2f4;
|
||||||
|
color: #9b9b9b;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 42rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-item__add {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1rpx solid #e6e6e6;
|
||||||
|
margin-left: 14rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-item__check {
|
||||||
|
width: 44rpx;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-box {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2rpx solid #c8c8c8;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-box.is-on {
|
||||||
|
border-color: #111;
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
.check-tick {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-card-wrap {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-card__check {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
left: 8rpx;
|
||||||
|
top: 8rpx;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,6 +1,26 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import OrderSwiperList from "./components/order-swiper-list/order-swiper-list.vue";
|
import OrderSwiperList from "./components/order-swiper-list/order-swiper-list.vue";
|
||||||
|
import { useUserStore } from "@/store";
|
||||||
|
import { onShow } from "@dcloudio/uni-app";
|
||||||
|
|
||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const cartBadgeTotal = computed(() => {
|
||||||
|
const list = userStore.userCartAllData;
|
||||||
|
if (!Array.isArray(list) || list.length === 0) return 0;
|
||||||
|
let n = 0;
|
||||||
|
for (const m of list) {
|
||||||
|
n +=
|
||||||
|
(m as { merchantCartVoList?: unknown[] })?.merchantCartVoList
|
||||||
|
?.length || 0;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
|
||||||
|
onShow(() => {
|
||||||
|
userStore.getUserCartAllData();
|
||||||
|
});
|
||||||
const segmentedValue = ref(0);
|
const segmentedValue = ref(0);
|
||||||
const segmentedList = [
|
const segmentedList = [
|
||||||
'Store',
|
'Store',
|
||||||
@@ -8,26 +28,106 @@ const segmentedList = [
|
|||||||
'Recipe',
|
'Recipe',
|
||||||
]
|
]
|
||||||
|
|
||||||
function handleSwiperChange(e) {
|
function handleSwiperChange(e: any) {
|
||||||
segmentedValue.value = e.detail.current;
|
segmentedValue.value = e.detail.current;
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderSwiperListRef = ref()
|
const orderSwiperListRef = ref()
|
||||||
|
|
||||||
|
const batchDeleteMode = ref(false)
|
||||||
|
|
||||||
onMounted(()=> {
|
onMounted(()=> {
|
||||||
nextTick(()=> {
|
nextTick(()=> {
|
||||||
orderSwiperListRef.value[segmentedValue.value].reload()
|
orderSwiperListRef.value[segmentedValue.value].reload()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function exitBatchDeleteMode() {
|
||||||
|
batchDeleteMode.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBatchDeleteSuccess() {
|
||||||
|
batchDeleteMode.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateBack() {
|
||||||
|
uni.navigateBack({
|
||||||
|
delta: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteCollection() {
|
||||||
|
if (!batchDeleteMode.value) {
|
||||||
|
batchDeleteMode.value = true
|
||||||
|
uni.showToast({
|
||||||
|
title: t('pages.mine.collectionBatchModeHint'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const inst = orderSwiperListRef.value?.[segmentedValue.value]
|
||||||
|
inst?.runBatchDelete?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigateToCart() {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages-user/pages/cart/index'
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view>
|
<view class="collection-page">
|
||||||
<z-paging-swiper>
|
<z-paging-swiper>
|
||||||
<template #top>
|
<template #top>
|
||||||
<navbar/>
|
<status-bar />
|
||||||
<view class="px-30rpx">
|
<view class="header-wrap px-30rpx pt-14rpx">
|
||||||
<view class="text-46rpx lh-46rpx text-#333 font-bold mb-52rpx">{{ t('pages.mine.collection') }}</view>
|
<view class="flex items-center justify-between mb-34rpx">
|
||||||
<l-segmented v-model="segmentedValue" :options="segmentedList" shape="round" bg-color="#F2F2F2" active-color="#333" />
|
<image
|
||||||
|
@click="navigateBack"
|
||||||
|
src="@img/chef/1327.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="w-48rpx h-48rpx"
|
||||||
|
/>
|
||||||
|
<view class="text-38rpx lh-38rpx text-#111 font-600">{{ t('pages.mine.collection') }}</view>
|
||||||
|
<view class="flex items-center gap-16rpx">
|
||||||
|
<text
|
||||||
|
v-if="batchDeleteMode"
|
||||||
|
class="text-28rpx text-#666"
|
||||||
|
@click="exitBatchDeleteMode"
|
||||||
|
>{{ t('common.cancel') }}</text>
|
||||||
|
<view
|
||||||
|
v-if="!batchDeleteMode"
|
||||||
|
class="collection-header-cart w-66rpx h-66rpx rounded-50% bg-#F5F5F5 center shrink-0"
|
||||||
|
@click="navigateToCart"
|
||||||
|
>
|
||||||
|
<view class="i-carbon:shopping-cart text-36rpx text-#14181b"></view>
|
||||||
|
<view
|
||||||
|
v-if="userStore.isLogin && cartBadgeTotal > 0"
|
||||||
|
class="collection-header-cart-badge"
|
||||||
|
>{{ cartBadgeTotal > 99 ? "99+" : cartBadgeTotal }}</view
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="!batchDeleteMode"
|
||||||
|
class="w-66rpx h-66rpx rounded-50% bg-#F5F5F5 center"
|
||||||
|
@click="handleDeleteCollection"
|
||||||
|
>
|
||||||
|
<image src="@img/chef/1278.png" mode="aspectFill" class="w-66rpx h-66rpx" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<l-segmented
|
||||||
|
v-model="segmentedValue"
|
||||||
|
:options="segmentedList"
|
||||||
|
shape="round"
|
||||||
|
bg-color="#F2F3F5"
|
||||||
|
active-color="#fff"
|
||||||
|
color="#5E5E5E"
|
||||||
|
padding="0rpx"
|
||||||
|
slider-color="#000"
|
||||||
|
height="62rpx"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -35,15 +135,95 @@ onMounted(()=> {
|
|||||||
:current="segmentedValue"
|
:current="segmentedValue"
|
||||||
@change="handleSwiperChange">
|
@change="handleSwiperChange">
|
||||||
<swiper-item class="swiper-item" v-for="(item, index) in segmentedList" :key="index">
|
<swiper-item class="swiper-item" v-for="(item, index) in segmentedList" :key="index">
|
||||||
<order-swiper-list ref="orderSwiperListRef" :currentIndex="segmentedValue" :index="index"></order-swiper-list>
|
<order-swiper-list
|
||||||
|
ref="orderSwiperListRef"
|
||||||
|
:currentIndex="segmentedValue"
|
||||||
|
:index="index"
|
||||||
|
:batch-delete-mode="batchDeleteMode"
|
||||||
|
@batch-delete-success="handleBatchDeleteSuccess"
|
||||||
|
/>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
</z-paging-swiper>
|
</z-paging-swiper>
|
||||||
|
|
||||||
|
<view class="cart-bar-wrap px-24rpx pb-24rpx" v-if="batchDeleteMode">
|
||||||
|
<view class="delete-bar">
|
||||||
|
<view class="text-30rpx text-#1d1d1d ml-20rpx" style="color: #fff;">{{ t('common.delete') || '删除' }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style scoped lang="scss">
|
||||||
page {
|
page {
|
||||||
background-color: white;
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #fff;
|
||||||
|
padding-bottom: constant(safe-area-inset-bottom);
|
||||||
|
padding-bottom: env(safe-area-inset-bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-wrap {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.l-segmented) {
|
||||||
|
height: 62rpx !important;
|
||||||
|
border-radius: 34rpx !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.l-segmented-item) {
|
||||||
|
font-size: 30rpx !important;
|
||||||
|
color: #666 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.l-segmented-item--active) {
|
||||||
|
color: #fff !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
background: #111 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-bar-wrap {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 20;
|
||||||
|
background: linear-gradient(to top, #fff 65%, rgba(255, 255, 255, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-header-cart {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collection-header-cart-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 2rpx;
|
||||||
|
right: 2rpx;
|
||||||
|
min-width: 28rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
padding: 0 6rpx;
|
||||||
|
font-size: 18rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
background: #e23636;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-bar {
|
||||||
|
height: 96rpx;
|
||||||
|
border-radius: 48rpx;
|
||||||
|
background: #000;
|
||||||
|
box-shadow: 0 -2rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||||
|
border: 1rpx solid #efefef;
|
||||||
|
padding: 0 26rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -42,6 +42,26 @@ function formatCouponDetail(item: any) {
|
|||||||
return t('pages-store.store.discount')
|
return t('pages-store.store.discount')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fillI18nParams(template: string, params: Record<string, string | number>) {
|
||||||
|
let text = template
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
const value = String(params[key] ?? '')
|
||||||
|
text = text.replace(new RegExp(`\\{${key}\\}`, 'g'), value)
|
||||||
|
text = text.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value)
|
||||||
|
})
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCouponMerchantText(item: any) {
|
||||||
|
const name = String(item?.merchantVo?.merchantName || '').trim()
|
||||||
|
if (name) {
|
||||||
|
return fillI18nParams(t('pages-user.coupon.merchant-only'), { name })
|
||||||
|
}
|
||||||
|
return item?.snapshotMerchantId
|
||||||
|
? t('pages-user.coupon.merchant-specific')
|
||||||
|
: t('pages-user.coupon.all-merchants')
|
||||||
|
}
|
||||||
|
|
||||||
function getList(pageNum: number, pageSize: number) {
|
function getList(pageNum: number, pageSize: number) {
|
||||||
return appCouponUserCouponListPost({
|
return appCouponUserCouponListPost({
|
||||||
params: {
|
params: {
|
||||||
@@ -150,7 +170,7 @@ function handleSubmit() {
|
|||||||
{{ item.snapshotNameZh }}
|
{{ item.snapshotNameZh }}
|
||||||
</view>
|
</view>
|
||||||
<view class="text-24rpx lh-32rpx text-#999 my-18rpx">
|
<view class="text-24rpx lh-32rpx text-#999 my-18rpx">
|
||||||
{{ item.snapshotMerchantId ? t('pages-user.coupon.merchant-specific') : t('pages-user.coupon.all-merchants') }}
|
{{ formatCouponMerchantText(item) }}
|
||||||
</view>
|
</view>
|
||||||
<view class="text-24rpx lh-32rpx text-#999 my-18rpx">
|
<view class="text-24rpx lh-32rpx text-#999 my-18rpx">
|
||||||
{{ dayjs(Number(item.snapshotValidEnd)).format('YYYY-MM-DD HH:mm') }}{{ isEnLocale() ? ' expires' : '到期' }}
|
{{ dayjs(Number(item.snapshotValidEnd)).format('YYYY-MM-DD HH:mm') }}{{ isEnLocale() ? ' expires' : '到期' }}
|
||||||
|
|||||||
@@ -42,8 +42,13 @@ function handleClickLeft() {
|
|||||||
<view class="i-carbon:chevron-left text-50rpx text-#3D3D3D ml-[-10rpx]"></view>
|
<view class="i-carbon:chevron-left text-50rpx text-#3D3D3D ml-[-10rpx]"></view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
<template #title>
|
||||||
|
<view class="invite-navbar-title-wrap">
|
||||||
|
<text class="invite-navbar-title">{{ t('pages.mine.the-person-invited') }}</text>
|
||||||
|
<text class="invite-navbar-count">({{ dataList.length }})</text>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
</wd-navbar>
|
</wd-navbar>
|
||||||
<view class="mb-46rpx mt-18rpx pl-30rpx text-#333 text-46rpx lh-46rpx font-bold">{{ t('pages.mine.the-person-invited') }} ({{ dataList.length }})</view>
|
|
||||||
</template>
|
</template>
|
||||||
<view class="px-18rpx">
|
<view class="px-18rpx">
|
||||||
<view
|
<view
|
||||||
@@ -111,4 +116,30 @@ function handleClickLeft() {
|
|||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
<style lang="scss" scoped>
|
||||||
|
.invite-navbar-title-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
align-items: center;
|
||||||
|
align-content: center;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 8rpx;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-navbar-title {
|
||||||
|
font-size: 34rpx;
|
||||||
|
line-height: 42rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invite-navbar-count {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -15,6 +15,29 @@ import {formatTimestamp, formatTimestampWithMonthName} from "@/utils/utils";
|
|||||||
import {useUserStore, useConfigStore} from "@/store";
|
import {useUserStore, useConfigStore} from "@/store";
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
|
function normalizeTimestamp(input: unknown): number | null {
|
||||||
|
if (input == null || input === '') return null
|
||||||
|
const raw = Number(input)
|
||||||
|
if (!Number.isFinite(raw) || raw <= 0) return null
|
||||||
|
|
||||||
|
// 10位秒级、13位毫秒级、16位微秒级都做兼容
|
||||||
|
if (raw < 1e11) return Math.trunc(raw * 1000)
|
||||||
|
if (raw > 1e14) return Math.trunc(raw / 1000)
|
||||||
|
return Math.trunc(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRecipeTime(value: unknown): string {
|
||||||
|
const ts = normalizeTimestamp(value)
|
||||||
|
if (!ts) return '--'
|
||||||
|
return formatTimestamp(ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCommentTime(value: unknown): string {
|
||||||
|
const ts = normalizeTimestamp(value)
|
||||||
|
if (!ts) return '--'
|
||||||
|
return formatTimestampWithMonthName(ts)
|
||||||
|
}
|
||||||
// 加载状态
|
// 加载状态
|
||||||
const loading = ref(true);
|
const loading = ref(true);
|
||||||
// 获取菜谱详情
|
// 获取菜谱详情
|
||||||
@@ -108,7 +131,7 @@ function getCommentList() {
|
|||||||
user_name: userInfo.user_name, // 用户名
|
user_name: userInfo.user_name, // 用户名
|
||||||
user_avatar: userInfo.user_avatar, // 用户头像地址
|
user_avatar: userInfo.user_avatar, // 用户头像地址
|
||||||
user_content: item.content, // 用户评论内容
|
user_content: item.content, // 用户评论内容
|
||||||
create_time: formatTimestampWithMonthName(item.createTime), // 创建时间
|
create_time: formatCommentTime(item.createTime), // 创建时间
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
tableTotal.value = res.total
|
tableTotal.value = res.total
|
||||||
@@ -192,7 +215,7 @@ function handleSend() {
|
|||||||
>{{ recipeDetail?.recipeName || '' }}</view
|
>{{ recipeDetail?.recipeName || '' }}</view
|
||||||
>
|
>
|
||||||
<view class="flex-center-sb text-28rpx text-#fff">
|
<view class="flex-center-sb text-28rpx text-#fff">
|
||||||
<text>{{ formatTimestamp(recipeDetail?.createTime) }}</text>
|
<text>{{ formatRecipeTime(recipeDetail?.createTime) }}</text>
|
||||||
<view class="flex items-center">
|
<view class="flex items-center">
|
||||||
<image
|
<image
|
||||||
src="@img/chef/1326.png"
|
src="@img/chef/1326.png"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const useLogicStore = defineStore('store-list-logic', () => {
|
|||||||
const searchLoading = ref(false)
|
const searchLoading = ref(false)
|
||||||
|
|
||||||
const setPlacesList = (list: any) => {
|
const setPlacesList = (list: any) => {
|
||||||
|
console.log('setPlacesList', list)
|
||||||
if (Array.isArray(list)) {
|
if (Array.isArray(list)) {
|
||||||
placesList.value = list
|
placesList.value = list
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+4
-1
@@ -229,7 +229,10 @@
|
|||||||
"path": "pages/store/search/result"
|
"path": "pages/store/search/result"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/store/dishes"
|
"path": "pages/store/dishes",
|
||||||
|
"style": {
|
||||||
|
"onReachBottomDistance": 80
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/order/checkout"
|
"path": "pages/order/checkout"
|
||||||
|
|||||||
@@ -18,15 +18,17 @@ function handleClickSearch() {
|
|||||||
chooseAddress: (data: any) => {
|
chooseAddress: (data: any) => {
|
||||||
console.log('搜索的地址信息', data)
|
console.log('搜索的地址信息', data)
|
||||||
if (data) {
|
if (data) {
|
||||||
|
addressStore.clearAddressInfo()
|
||||||
addressStore.setAddressLocation({
|
addressStore.setAddressLocation({
|
||||||
displayName: data.displayName,
|
displayName: data.displayName,
|
||||||
formattedAddress: data.formattedAddress,
|
formattedAddress: data.formattedAddress,
|
||||||
longitude: data.location.lng,
|
longitude: data.location.lng,
|
||||||
latitude: data.location.lat
|
latitude: data.location.lat
|
||||||
})
|
})
|
||||||
|
addressStore.pendingIntroBuildingType = true
|
||||||
setTimeout(()=> {
|
setTimeout(()=> {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/address/choose-type'
|
url: '/pages/address/save-address/other'
|
||||||
})
|
})
|
||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,13 +137,12 @@ const isDateSelectable = (date: Date): boolean => {
|
|||||||
return hasAvailableTimeSlots(date);
|
return hasAvailableTimeSlots(date);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生成未来的日期(显示所有日期,但标记营业状态)
|
// 生成未来 7 天:设计稿为「本周」5 个圆 +「下周」2 个圆
|
||||||
const dateOptions = computed(() => {
|
const dateOptions = computed(() => {
|
||||||
const dates: Date[] = [];
|
const dates: Date[] = [];
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
|
||||||
// 生成连续的5天日期(包括不营业的日期)
|
for (let i = 0; i < 7; i++) {
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
const date = new Date(today);
|
const date = new Date(today);
|
||||||
date.setDate(today.getDate() + i);
|
date.setDate(today.getDate() + i);
|
||||||
dates.push(date);
|
dates.push(date);
|
||||||
@@ -152,6 +151,9 @@ const dateOptions = computed(() => {
|
|||||||
return dates;
|
return dates;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const thisWeekDates = computed(() => dateOptions.value.slice(0, 5));
|
||||||
|
const nextWeekDates = computed(() => dateOptions.value.slice(5, 7));
|
||||||
|
|
||||||
// 状态管理 - 初始化为第一个营业日期
|
// 状态管理 - 初始化为第一个营业日期
|
||||||
const selectedDate = ref<Date>();
|
const selectedDate = ref<Date>();
|
||||||
|
|
||||||
@@ -246,7 +248,6 @@ const initializeSelectedDate = () => {
|
|||||||
);
|
);
|
||||||
const firstAllowed = firstOpen || dateOptions.value.find((d) => isAllowedDay(d));
|
const firstAllowed = firstOpen || dateOptions.value.find((d) => isAllowedDay(d));
|
||||||
selectedDate.value = firstAllowed || dateOptions.value[0];
|
selectedDate.value = firstAllowed || dateOptions.value[0];
|
||||||
nextTick(() => updateScrollPosition());
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,9 +255,6 @@ const initializeSelectedDate = () => {
|
|||||||
for (const date of dateOptions.value) {
|
for (const date of dateOptions.value) {
|
||||||
if (isDateSelectable(date)) {
|
if (isDateSelectable(date)) {
|
||||||
selectedDate.value = date;
|
selectedDate.value = date;
|
||||||
nextTick(() => {
|
|
||||||
updateScrollPosition();
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,7 +264,6 @@ const initializeSelectedDate = () => {
|
|||||||
if (nextBusinessDate) {
|
if (nextBusinessDate) {
|
||||||
selectedDate.value = nextBusinessDate;
|
selectedDate.value = nextBusinessDate;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
updateScrollPosition();
|
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: t('pages.address.reservationTime.currentTimeExpired'),
|
title: t('pages.address.reservationTime.currentTimeExpired'),
|
||||||
icon: "none",
|
icon: "none",
|
||||||
@@ -275,9 +272,6 @@ const initializeSelectedDate = () => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
selectedDate.value = dateOptions.value[0];
|
selectedDate.value = dateOptions.value[0];
|
||||||
nextTick(() => {
|
|
||||||
updateScrollPosition();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -304,59 +298,15 @@ watch(
|
|||||||
);
|
);
|
||||||
const selectedTimeSlot = ref<string>("");
|
const selectedTimeSlot = ref<string>("");
|
||||||
|
|
||||||
// 横向滚动距离
|
/** 圆圈内上行:星期(与全局 dayjs 语言一致) */
|
||||||
const scrollLeft = ref<number>(0);
|
const formatWeekdayCircle = (date: Date) => dayjs(date).format("dddd");
|
||||||
|
|
||||||
// 计算并设置横向滚动距离
|
/** 圆圈内下行:MM/DD;不可选时显示文案 */
|
||||||
const updateScrollPosition = () => {
|
const formatCircleSubLine = (date: Date) => {
|
||||||
if (!selectedDate.value) return;
|
|
||||||
|
|
||||||
// 找到选中日期在 dateOptions 中的索引
|
|
||||||
const selectedIndex = dateOptions.value.findIndex(date =>
|
|
||||||
dayjs(date).isSame(dayjs(selectedDate.value), 'day')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (selectedIndex === -1) return;
|
|
||||||
|
|
||||||
// 每个日期卡片的宽度:240rpx + 28rpx 间距 = 268rpx
|
|
||||||
// 但第一个卡片没有左边距,所以需要特殊处理
|
|
||||||
const cardWidth = 240; // rpx
|
|
||||||
const cardMargin = 28; // rpx
|
|
||||||
|
|
||||||
// 计算滚动距离,让选中的卡片尽量居中显示
|
|
||||||
let scrollDistance = 0;
|
|
||||||
if (selectedIndex > 0) {
|
|
||||||
// 第一个卡片没有左边距,从第二个开始每个卡片占用 240 + 28 = 268rpx
|
|
||||||
scrollDistance = selectedIndex * (cardWidth + cardMargin);
|
|
||||||
|
|
||||||
// 减去一些距离让选中项更居中(可根据屏幕宽度调整)
|
|
||||||
scrollDistance = Math.max(0, scrollDistance - 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollLeft.value = scrollDistance;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 格式化日期显示
|
|
||||||
const formatDateDisplay = (date: Date) => {
|
|
||||||
const today = dayjs();
|
|
||||||
const targetDate = dayjs(date);
|
|
||||||
|
|
||||||
if (targetDate.isSame(today, "day")) {
|
|
||||||
return "Today";
|
|
||||||
} else if (targetDate.isSame(today.add(1, "day"), "day")) {
|
|
||||||
return "Tomorrow";
|
|
||||||
} else {
|
|
||||||
// 返回星期几
|
|
||||||
return targetDate.format("dddd");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 格式化日期为月份和日期(不包含年份),不可选择日期显示"不营业"
|
|
||||||
const formatDateOnly = (date: Date) => {
|
|
||||||
if (!isDateSelectable(date)) {
|
if (!isDateSelectable(date)) {
|
||||||
return t('pages.address.reservationTime.notAvailable')
|
return t("pages.address.reservationTime.notAvailable");
|
||||||
}
|
}
|
||||||
return dayjs(date).format('MMMM D')
|
return dayjs(date).format("MM/DD");
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -536,8 +486,16 @@ const selectTimeSlot = (timeSlot: string) => {
|
|||||||
selectedTimeSlot.value = timeSlot;
|
selectedTimeSlot.value = timeSlot;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isDateSelected = (date: Date) =>
|
||||||
|
!!selectedDate.value && dayjs(selectedDate.value).isSame(dayjs(date), "day");
|
||||||
|
|
||||||
// 提交预约
|
// 提交预约
|
||||||
const submitReservation = () => {
|
const submitReservation = () => {
|
||||||
|
const dateVal = selectedDate.value;
|
||||||
|
if (!dateVal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 非仅选日期模式,需要选择时间段
|
// 非仅选日期模式,需要选择时间段
|
||||||
if (!onlySelectDay.value) {
|
if (!onlySelectDay.value) {
|
||||||
if (!selectedTimeSlot.value) {
|
if (!selectedTimeSlot.value) {
|
||||||
@@ -550,13 +508,13 @@ const submitReservation = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 计算开始/结束时间
|
// 计算开始/结束时间
|
||||||
const selectedDateDayjs = dayjs(selectedDate.value);
|
const selectedDateDayjs = dayjs(dateVal);
|
||||||
let startTime: dayjs.Dayjs;
|
let startTime: dayjs.Dayjs;
|
||||||
let endTime: dayjs.Dayjs;
|
let endTime: dayjs.Dayjs;
|
||||||
|
|
||||||
if (onlySelectDay.value) {
|
if (onlySelectDay.value) {
|
||||||
// 仅选日期:优先使用营业时间范围;若无营业时间限制,则使用当天起止
|
// 仅选日期:优先使用营业时间范围;若无营业时间限制,则使用当天起止
|
||||||
const bh = getBusinessHoursForDate(selectedDate.value);
|
const bh = getBusinessHoursForDate(dateVal);
|
||||||
if (bh) {
|
if (bh) {
|
||||||
const [startHour, startMinute] = bh.startTime.split(':').map(Number);
|
const [startHour, startMinute] = bh.startTime.split(':').map(Number);
|
||||||
const [endHour, endMinute] = bh.endTime.split(':').map(Number);
|
const [endHour, endMinute] = bh.endTime.split(':').map(Number);
|
||||||
@@ -592,14 +550,14 @@ const submitReservation = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log("预约信息:", {
|
console.log("预约信息:", {
|
||||||
date: selectedDate.value,
|
date: dateVal,
|
||||||
timeSlot: onlySelectDay.value ? '' : selectedTimeSlot.value,
|
timeSlot: onlySelectDay.value ? '' : selectedTimeSlot.value,
|
||||||
startTime: startTime.valueOf(),
|
startTime: startTime.valueOf(),
|
||||||
endTime: endTime.valueOf(),
|
endTime: endTime.valueOf(),
|
||||||
});
|
});
|
||||||
|
|
||||||
uni.$emit(EventEnum.CHOOSE_APPOINTMENT_TIME, {
|
uni.$emit(EventEnum.CHOOSE_APPOINTMENT_TIME, {
|
||||||
date: selectedDate.value,
|
date: dateVal,
|
||||||
timeSlot: onlySelectDay.value ? '' : selectedTimeSlot.value,
|
timeSlot: onlySelectDay.value ? '' : selectedTimeSlot.value,
|
||||||
startTime: startTime.valueOf(),
|
startTime: startTime.valueOf(),
|
||||||
endTime: endTime.valueOf(),
|
endTime: endTime.valueOf(),
|
||||||
@@ -633,45 +591,52 @@ onLoad((options: any) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="">
|
<view class="reservation-time-page min-h-screen pb-180rpx">
|
||||||
<navbar />
|
<navbar
|
||||||
<view class="mt-20rpx px-30rpx text-46rpx lh-46rpx text-#333 font-bold">
|
:title="t('pages.address.reservationTime.pageTitle')"
|
||||||
{{ t("pages.address.appTime") }}
|
circle-back
|
||||||
</view>
|
custom-class="reservation-time-navbar"
|
||||||
<view class="px-30rpx pt-52rpx pb-50rpx w-screen bg-white">
|
/>
|
||||||
<scroll-view class="w-full whitespace-nowrap" scroll-x="true" :scroll-left="scrollLeft">
|
<view class="reservation-time-body px-40rpx pt-40rpx">
|
||||||
<template v-for="(item, index) in dateOptions" :key="index">
|
<view class="date-section">
|
||||||
|
<text class="section-label">{{ t("pages.address.reservationTime.thisWeek") }}</text>
|
||||||
|
<view class="date-row">
|
||||||
<view
|
<view
|
||||||
|
v-for="(item, index) in thisWeekDates"
|
||||||
|
:key="index"
|
||||||
|
class="date-circle"
|
||||||
|
:class="{
|
||||||
|
'date-circle--selected': isDateSelected(item),
|
||||||
|
'date-circle--disabled': !isDateSelectable(item),
|
||||||
|
}"
|
||||||
@click="selectDate(item)"
|
@click="selectDate(item)"
|
||||||
:class="[
|
|
||||||
index === 0 ? '' : 'ml-28rpx',
|
|
||||||
selectedDate && dayjs(selectedDate).isSame(dayjs(item), 'day')
|
|
||||||
? 'border-#333'
|
|
||||||
: 'border-#D8D8D8',
|
|
||||||
!isDateSelectable(item)
|
|
||||||
? 'opacity-50 cursor-not-allowed'
|
|
||||||
: 'cursor-pointer',
|
|
||||||
]"
|
|
||||||
class="inline-block border-solid border-1px w-240rpx h-140rpx rounded-20rpx px-32rpx py-36rpx"
|
|
||||||
>
|
>
|
||||||
|
<text class="date-circle__weekday">{{ formatWeekdayCircle(item) }}</text>
|
||||||
|
<text class="date-circle__sub">{{ formatCircleSubLine(item) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="nextWeekDates.length" class="date-section date-section--next">
|
||||||
|
<text class="section-label">{{ t("pages.address.reservationTime.nextWeek") }}</text>
|
||||||
|
<view class="date-row">
|
||||||
<view
|
<view
|
||||||
:class="!isDateSelectable(item) ? 'text-#999' : 'text-#333'"
|
v-for="(item, index) in nextWeekDates"
|
||||||
class="text-28rpx lh-28rpx mb-12rpx"
|
:key="index"
|
||||||
|
class="date-circle"
|
||||||
|
:class="{
|
||||||
|
'date-circle--selected': isDateSelected(item),
|
||||||
|
'date-circle--disabled': !isDateSelectable(item),
|
||||||
|
}"
|
||||||
|
@click="selectDate(item)"
|
||||||
>
|
>
|
||||||
{{ formatDateDisplay(item) }}
|
<text class="date-circle__weekday">{{ formatWeekdayCircle(item) }}</text>
|
||||||
</view>
|
<text class="date-circle__sub">{{ formatCircleSubLine(item) }}</text>
|
||||||
<view
|
</view>
|
||||||
:class="!isDateSelectable(item) ? 'text-#CCC' : 'text-#7D7D7D'"
|
|
||||||
class="text-28rpx"
|
|
||||||
>
|
|
||||||
{{ formatDateOnly(item) }}
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
|
||||||
</scroll-view>
|
|
||||||
</view>
|
</view>
|
||||||
<!-- 时间段选择区域:在仅选日期模式下隐藏 -->
|
<!-- 时间段选择区域:在仅选日期模式下隐藏 -->
|
||||||
<view v-if="!onlySelectDay" class="pb-138rpx">
|
<view v-if="!onlySelectDay" class="pb-138rpx mx-40rpx bg-white rounded-24rpx overflow-hidden mt-24rpx">
|
||||||
<view
|
<view
|
||||||
v-for="(timeSlot, index) in timeSlots"
|
v-for="(timeSlot, index) in timeSlots"
|
||||||
:key="index"
|
:key="index"
|
||||||
@@ -705,8 +670,91 @@ onLoad((options: any) => {
|
|||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style scoped lang="scss">
|
||||||
page {
|
.reservation-time-body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-section {
|
||||||
|
margin-bottom: 48rpx;
|
||||||
|
|
||||||
|
&--next {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 44rpx;
|
||||||
|
color: #111;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 20rpx;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-circle {
|
||||||
|
width: 118rpx;
|
||||||
|
height: 118rpx;
|
||||||
|
border-radius: 50%;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-circle__weekday {
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
color: #111;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-circle__sub {
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
margin-top: 6rpx;
|
||||||
|
color: #111;
|
||||||
|
opacity: 0.88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-circle--selected {
|
||||||
|
background-color: #111;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
.date-circle__weekday,
|
||||||
|
.date-circle__sub {
|
||||||
|
color: #fff;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-circle--disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-circle--disabled.date-circle--selected {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
page {
|
||||||
|
background-color: #f7f7f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reservation-time-page :deep(.reservation-time-navbar.wd-navbar) {
|
||||||
|
background-color: #f7f7f9 !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -99,6 +99,13 @@ onLoad(()=> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onReady(() => {
|
||||||
|
if (addressStore.pendingIntroBuildingType && !addressStore.addressInfo.id) {
|
||||||
|
buildingTypeRef.value?.openIntroSheet?.()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onUnload(()=> {
|
onUnload(()=> {
|
||||||
if(!isSwitch.value) {
|
if(!isSwitch.value) {
|
||||||
addressStore.clearAddressInfo()
|
addressStore.clearAddressInfo()
|
||||||
@@ -176,7 +183,11 @@ function chooseStateConfirm(data: any) {
|
|||||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
||||||
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||||
<text class="text-30rpx lh-30rpx text-#333">
|
<text class="text-30rpx lh-30rpx text-#333">
|
||||||
{{ `${t(`pages.address.choose-type.${addressStore.addressInfo.type}`)}` }}
|
{{
|
||||||
|
addressStore.addressInfo.type
|
||||||
|
? t(`pages.address.choose-type.${addressStore.addressInfo.type}`)
|
||||||
|
: t('common.placeholder.pleaseSelect')
|
||||||
|
}}
|
||||||
</text>
|
</text>
|
||||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -1,70 +1,228 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {UserAddressType} from "@/constant/enums";
|
import { UserAddressType } from '@/constant/enums';
|
||||||
|
import { useAddressStore } from '@/pages/address/store/address';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const show = ref(false);
|
const addressStore = useAddressStore();
|
||||||
const value = ref('house')
|
|
||||||
|
|
||||||
const emit = defineEmits(['submit'])
|
/** 新地址首次进入保存页:与原先 choose-type 全页等价的介绍弹窗 */
|
||||||
|
const showIntro = ref(false);
|
||||||
|
/** 点击表单「建筑类型」行:原有滚轮选择器 */
|
||||||
|
const showPicker = ref(false);
|
||||||
|
|
||||||
function onOpen() {
|
const emit = defineEmits<{
|
||||||
show.value = true;
|
submit: [value: string];
|
||||||
}
|
}>();
|
||||||
function handleClose() {
|
|
||||||
show.value = false;
|
|
||||||
}
|
|
||||||
function handleSubmit() {
|
|
||||||
console.log('value', value.value)
|
|
||||||
emit('submit', value.value)
|
|
||||||
handleClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = ref(
|
const typeList = computed(() => [
|
||||||
[
|
|
||||||
{
|
{
|
||||||
label: t('pages.address.choose-type.house'),
|
label: t('pages.address.choose-type.house'),
|
||||||
|
desc: t('pages.address.choose-type.houseDescription'),
|
||||||
value: UserAddressType.HOUSE,
|
value: UserAddressType.HOUSE,
|
||||||
|
icon: '/static/images/chef/147.png',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('pages.address.choose-type.apartment'),
|
label: t('pages.address.choose-type.apartment'),
|
||||||
|
desc: t('pages.address.choose-type.apartmentDescription'),
|
||||||
value: UserAddressType.APARTMENT,
|
value: UserAddressType.APARTMENT,
|
||||||
|
icon: '/static/images/chef/148.png',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('pages.address.choose-type.office'),
|
label: t('pages.address.choose-type.office'),
|
||||||
|
desc: t('pages.address.choose-type.officeDescription'),
|
||||||
value: UserAddressType.OFFICE,
|
value: UserAddressType.OFFICE,
|
||||||
|
icon: '/static/images/chef/149.png',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('pages.address.choose-type.hotel'),
|
label: t('pages.address.choose-type.hotel'),
|
||||||
|
desc: t('pages.address.choose-type.hotelDescription'),
|
||||||
value: UserAddressType.HOTEL,
|
value: UserAddressType.HOTEL,
|
||||||
|
icon: '/static/images/chef/150.png',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t('pages.address.choose-type.other'),
|
label: t('pages.address.choose-type.other'),
|
||||||
|
desc: t('pages.address.choose-type.otherDescription'),
|
||||||
value: UserAddressType.OTHER,
|
value: UserAddressType.OTHER,
|
||||||
|
icon: '/static/images/chef/151.png',
|
||||||
},
|
},
|
||||||
]
|
]);
|
||||||
)
|
|
||||||
function onChange({picker, value, index}) {
|
const columns = computed(() => [
|
||||||
|
{ label: t('pages.address.choose-type.house'), value: UserAddressType.HOUSE },
|
||||||
|
{ label: t('pages.address.choose-type.apartment'), value: UserAddressType.APARTMENT },
|
||||||
|
{ label: t('pages.address.choose-type.office'), value: UserAddressType.OFFICE },
|
||||||
|
{ label: t('pages.address.choose-type.hotel'), value: UserAddressType.HOTEL },
|
||||||
|
{ label: t('pages.address.choose-type.other'), value: UserAddressType.OTHER },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const pickerValue = ref<string>(UserAddressType.HOUSE);
|
||||||
|
|
||||||
|
function routeForType(type: string): string {
|
||||||
|
switch (type) {
|
||||||
|
case UserAddressType.HOUSE:
|
||||||
|
return 'pages/address/save-address/house';
|
||||||
|
case UserAddressType.APARTMENT:
|
||||||
|
return 'pages/address/save-address/apartment';
|
||||||
|
case UserAddressType.OFFICE:
|
||||||
|
return 'pages/address/save-address/office';
|
||||||
|
case UserAddressType.HOTEL:
|
||||||
|
return 'pages/address/save-address/hotel';
|
||||||
|
case UserAddressType.OTHER:
|
||||||
|
return 'pages/address/save-address/other';
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentSaveAddressRoute(): string {
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
return pages[pages.length - 1]?.route ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 点击表单行:滚轮选择器(原效果) */
|
||||||
|
function onOpen() {
|
||||||
|
pickerValue.value =
|
||||||
|
addressStore.addressInfo.type && `${addressStore.addressInfo.type}`.length > 0
|
||||||
|
? (addressStore.addressInfo.type as string)
|
||||||
|
: UserAddressType.HOUSE;
|
||||||
|
showPicker.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePickerClose() {
|
||||||
|
showPicker.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePickerCancel() {
|
||||||
|
showPicker.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePickerConfirm() {
|
||||||
|
addressStore.addressInfo.type = pickerValue.value;
|
||||||
|
showPicker.value = false;
|
||||||
|
const next = routeForType(pickerValue.value);
|
||||||
|
if (next && currentSaveAddressRoute() !== next) {
|
||||||
|
emit('submit', pickerValue.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 新地址首次进入页面时由父级调用 */
|
||||||
|
function openIntroSheet() {
|
||||||
|
showIntro.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleIntroClose() {
|
||||||
|
showIntro.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 蒙层关闭且尚未完成选择时,与「跳过」一致 */
|
||||||
|
function onIntroPopupClosed() {
|
||||||
|
if (!addressStore.pendingIntroBuildingType) return;
|
||||||
|
addressStore.pendingIntroBuildingType = false;
|
||||||
|
addressStore.addressInfo.type = UserAddressType.OTHER;
|
||||||
|
const next = routeForType(UserAddressType.OTHER);
|
||||||
|
if (next && currentSaveAddressRoute() !== next) {
|
||||||
|
emit('submit', UserAddressType.OTHER);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function chooseIntroType(item: { value: string }) {
|
||||||
|
addressStore.pendingIntroBuildingType = false;
|
||||||
|
addressStore.addressInfo.type = item.value;
|
||||||
|
showIntro.value = false;
|
||||||
|
const next = routeForType(item.value);
|
||||||
|
if (next && currentSaveAddressRoute() !== next) {
|
||||||
|
emit('submit', item.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleIntroSkip() {
|
||||||
|
addressStore.pendingIntroBuildingType = false;
|
||||||
|
addressStore.addressInfo.type = UserAddressType.OTHER;
|
||||||
|
showIntro.value = false;
|
||||||
|
const next = routeForType(UserAddressType.OTHER);
|
||||||
|
if (next && currentSaveAddressRoute() !== next) {
|
||||||
|
emit('submit', UserAddressType.OTHER);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
onOpen,
|
onOpen,
|
||||||
|
openIntroSheet,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!-- 新地址首次:建筑类型介绍底部弹窗 -->
|
||||||
<wd-popup
|
<wd-popup
|
||||||
v-model="show"
|
v-model="showIntro"
|
||||||
position="bottom"
|
position="bottom"
|
||||||
@close="handleClose"
|
:z-index="120"
|
||||||
|
custom-style="background: transparent;"
|
||||||
|
@close="onIntroPopupClosed"
|
||||||
|
>
|
||||||
|
<view class="bg-white rounded-t-32rpx overflow-hidden pb-[calc(24rpx+env(safe-area-inset-bottom))]">
|
||||||
|
<view class="relative px-30rpx pt-40rpx pb-8rpx">
|
||||||
|
<text
|
||||||
|
class="absolute right-30rpx top-40rpx z-1 text-32rpx lh-32rpx text-#CE7138"
|
||||||
|
@click="handleIntroSkip"
|
||||||
|
>
|
||||||
|
{{ t('common.skip') }}
|
||||||
|
</text>
|
||||||
|
<view class="text-center text-36rpx lh-44rpx text-#333 font-500 pr-100rpx pl-100rpx">
|
||||||
|
{{ t('pages.address.choose-type.title') }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-24rpx text-center text-28rpx lh-36rpx text-#666">
|
||||||
|
{{ t('pages.address.choose-type.description') }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<scroll-view scroll-y class="box-border max-h-[62vh] px-30rpx pb-8rpx pt-28rpx">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in typeList"
|
||||||
|
:key="item.value"
|
||||||
|
:class="[
|
||||||
|
index === 0 ? '' : 'mt-28rpx',
|
||||||
|
item.value === addressStore.addressInfo.type
|
||||||
|
? 'border-#000 border-4rpx'
|
||||||
|
: 'border-#DFDFDF border-2rpx',
|
||||||
|
'border-solid rounded-16rpx px-28rpx py-34rpx flex items-center',
|
||||||
|
]"
|
||||||
|
@click="chooseIntroType(item)"
|
||||||
|
>
|
||||||
|
<image :src="item.icon" class="h-48rpx mr-28rpx shrink-0 w-48rpx" />
|
||||||
|
<view class="min-w-0 flex-1">
|
||||||
|
<view class="text-36rpx lh-40rpx text-#333 font-500">
|
||||||
|
{{ item.label }}
|
||||||
|
</view>
|
||||||
|
<view class="mt-18rpx text-28rpx lh-32rpx text-#6D6D6D">
|
||||||
|
{{ item.desc }}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</wd-popup>
|
||||||
|
|
||||||
|
<!-- 点击建筑类型行:原滚轮选择 -->
|
||||||
|
<wd-popup
|
||||||
|
v-model="showPicker"
|
||||||
|
position="bottom"
|
||||||
|
:z-index="121"
|
||||||
|
@close="handlePickerClose"
|
||||||
>
|
>
|
||||||
<view>
|
<view>
|
||||||
<view class="flex-center-sb h-102rpx bg-#F7F7F7 px-30rpx">
|
<view class="flex-center-sb h-102rpx bg-#F7F7F7 px-30rpx">
|
||||||
<view @click="handleClose" class="text-30rpx text-#999">{{ t('common.cancel') }}</view>
|
<view class="text-30rpx text-#999" @click="handlePickerCancel">{{ t('common.cancel') }}</view>
|
||||||
<view class="text-34rpx text-#333">{{ t('common.buildingType') }}</view>
|
<view class="text-34rpx text-#333">{{ t('common.buildingType') }}</view>
|
||||||
<view @click="handleSubmit" class="text-30rpx text-#FF6106">{{ t('common.confirm') }}</view>
|
<view class="text-30rpx text-#FF6106" @click="handlePickerConfirm">{{ t('common.confirm') }}</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="bg-#fff px-54rpx py-56rpx">
|
<view class="bg-#fff px-54rpx py-56rpx">
|
||||||
<wd-picker-view :columns="columns" v-model="value" @change="onChange" label-key="label" value-key="value" />
|
<wd-picker-view
|
||||||
|
:columns="columns"
|
||||||
|
v-model="pickerValue"
|
||||||
|
label-key="label"
|
||||||
|
value-key="value"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</wd-popup>
|
</wd-popup>
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ onLoad(()=> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onReady(() => {
|
||||||
|
if (addressStore.pendingIntroBuildingType && !addressStore.addressInfo.id) {
|
||||||
|
buildingTypeRef.value?.openIntroSheet?.()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onUnload(()=> {
|
onUnload(()=> {
|
||||||
if(!isSwitch.value) {
|
if(!isSwitch.value) {
|
||||||
addressStore.clearAddressInfo()
|
addressStore.clearAddressInfo()
|
||||||
@@ -173,7 +180,11 @@ function chooseStateConfirm(data: any) {
|
|||||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
||||||
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||||
<text class="text-30rpx lh-30rpx text-#333">
|
<text class="text-30rpx lh-30rpx text-#333">
|
||||||
{{ `${t(`pages.address.choose-type.${addressStore.addressInfo.type}`)}` }}
|
{{
|
||||||
|
addressStore.addressInfo.type
|
||||||
|
? t(`pages.address.choose-type.${addressStore.addressInfo.type}`)
|
||||||
|
: t('common.placeholder.pleaseSelect')
|
||||||
|
}}
|
||||||
</text>
|
</text>
|
||||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ onLoad(()=> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onReady(() => {
|
||||||
|
if (addressStore.pendingIntroBuildingType && !addressStore.addressInfo.id) {
|
||||||
|
buildingTypeRef.value?.openIntroSheet?.()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onUnload(()=> {
|
onUnload(()=> {
|
||||||
if(!isSwitch.value) {
|
if(!isSwitch.value) {
|
||||||
addressStore.clearAddressInfo()
|
addressStore.clearAddressInfo()
|
||||||
@@ -173,7 +180,11 @@ function chooseStateConfirm(data: any) {
|
|||||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
||||||
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||||
<text class="text-30rpx lh-30rpx text-#333">
|
<text class="text-30rpx lh-30rpx text-#333">
|
||||||
{{ `${t(`pages.address.choose-type.${addressStore.addressInfo.type}`)}` }}
|
{{
|
||||||
|
addressStore.addressInfo.type
|
||||||
|
? t(`pages.address.choose-type.${addressStore.addressInfo.type}`)
|
||||||
|
: t('common.placeholder.pleaseSelect')
|
||||||
|
}}
|
||||||
</text>
|
</text>
|
||||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ onLoad(()=> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onReady(() => {
|
||||||
|
if (addressStore.pendingIntroBuildingType && !addressStore.addressInfo.id) {
|
||||||
|
buildingTypeRef.value?.openIntroSheet?.()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onUnload(()=> {
|
onUnload(()=> {
|
||||||
if(!isSwitch.value) {
|
if(!isSwitch.value) {
|
||||||
addressStore.clearAddressInfo()
|
addressStore.clearAddressInfo()
|
||||||
@@ -173,7 +180,11 @@ function chooseStateConfirm(data: any) {
|
|||||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
||||||
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||||
<text class="text-30rpx lh-30rpx text-#333">
|
<text class="text-30rpx lh-30rpx text-#333">
|
||||||
{{ `${t(`pages.address.choose-type.${addressStore.addressInfo.type}`)}` }}
|
{{
|
||||||
|
addressStore.addressInfo.type
|
||||||
|
? t(`pages.address.choose-type.${addressStore.addressInfo.type}`)
|
||||||
|
: t('common.placeholder.pleaseSelect')
|
||||||
|
}}
|
||||||
</text>
|
</text>
|
||||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -98,6 +98,13 @@ onLoad(()=> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onReady(() => {
|
||||||
|
if (addressStore.pendingIntroBuildingType && !addressStore.addressInfo.id) {
|
||||||
|
buildingTypeRef.value?.openIntroSheet?.()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onUnload(()=> {
|
onUnload(()=> {
|
||||||
if(!isSwitch.value) {
|
if(!isSwitch.value) {
|
||||||
addressStore.clearAddressInfo()
|
addressStore.clearAddressInfo()
|
||||||
@@ -173,7 +180,11 @@ function chooseStateConfirm(data: any) {
|
|||||||
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
<view class="mb-20rpx text-36rpx lh-36rpx text-#333 font-500">{{ t('pages.address.choose-type.navTitle') }}</view>
|
||||||
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
<view @click="openBuildingType" class="bg-#F6F6F6 rounded-16rpx px-24rpx h-98rpx flex-center-sb">
|
||||||
<text class="text-30rpx lh-30rpx text-#333">
|
<text class="text-30rpx lh-30rpx text-#333">
|
||||||
{{ `${t(`pages.address.choose-type.${addressStore.addressInfo.type}`)}` }}
|
{{
|
||||||
|
addressStore.addressInfo.type
|
||||||
|
? t(`pages.address.choose-type.${addressStore.addressInfo.type}`)
|
||||||
|
: t('common.placeholder.pleaseSelect')
|
||||||
|
}}
|
||||||
</text>
|
</text>
|
||||||
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
<image src="@img/chef/142.png" class="w-32rpx h-32rpx shrink-0 rotate-90"></image>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import type { UserAddressBo } from '@/service/types';
|
import type { UserAddressBo } from '@/service/types';
|
||||||
export const useAddressStore = defineStore('store-address', () => {
|
export const useAddressStore = defineStore('store-address', () => {
|
||||||
|
/** 从地图搜索新增地址进入保存页时,首次需弹出建筑类型介绍层(与原先进入 choose-type 页等价) */
|
||||||
|
const pendingIntroBuildingType = ref(false)
|
||||||
|
|
||||||
const addressInfo = ref<UserAddressBo>({
|
const addressInfo = ref<UserAddressBo>({
|
||||||
type: '',
|
type: '',
|
||||||
/** 配送类型 1-亲自送达 2-放门口,默认放门口 */
|
/** 配送类型 1-亲自送达 2-放门口,默认放门口 */
|
||||||
@@ -57,6 +60,7 @@ export const useAddressStore = defineStore('store-address', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearAddressInfo() {
|
function clearAddressInfo() {
|
||||||
|
pendingIntroBuildingType.value = false
|
||||||
addressInfo.value = {
|
addressInfo.value = {
|
||||||
id: '',
|
id: '',
|
||||||
type: '',
|
type: '',
|
||||||
@@ -102,6 +106,7 @@ export const useAddressStore = defineStore('store-address', () => {
|
|||||||
return {
|
return {
|
||||||
addressInfo,
|
addressInfo,
|
||||||
addressLocation,
|
addressLocation,
|
||||||
|
pendingIntroBuildingType,
|
||||||
setAddressLocation,
|
setAddressLocation,
|
||||||
clearAddressInfo
|
clearAddressInfo
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useEventEmit from "@/hooks/useEventEmit";
|
|
||||||
import {CollectionType, EventEnum} from "@/constant/enums";
|
|
||||||
import { debounce } from 'throttle-debounce'
|
|
||||||
import Search from "../tabbar-home/components/search.vue";
|
import Search from "../tabbar-home/components/search.vue";
|
||||||
import { useConfigStore, useUserStore } from "@/store";
|
import { useConfigStore, useUserStore } from "@/store";
|
||||||
import MsgBox from "../tabbar-home/components/msg-box.vue";
|
|
||||||
import Collection from "@/components/collection/index.vue";
|
|
||||||
import BrowseSkeleton from "./components/browse-skeleton.vue";
|
import BrowseSkeleton from "./components/browse-skeleton.vue";
|
||||||
import {
|
import {
|
||||||
appMerchantDishNearbyListPost,
|
appMerchantDishNearbyListPost,
|
||||||
appSearchSearchRecipePost,
|
appSearchSearchRecipePost,
|
||||||
appCollectCollectPost,
|
|
||||||
} from "@/service";
|
} from "@/service";
|
||||||
import {thumbnailImg} from "@/utils/utils";
|
import {thumbnailImg} from "@/utils/utils";
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const emit = defineEmits(["toggleNotOpen"]);
|
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const recipePreviewLimit = 4;
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -29,10 +23,6 @@ function navigateTo(url: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleNotOpen() {
|
|
||||||
emit("toggleNotOpen");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initData() {
|
async function initData() {
|
||||||
if(!recipeData.value) {
|
if(!recipeData.value) {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -43,13 +33,14 @@ async function initData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取菜谱数据
|
// 获取菜谱数据
|
||||||
const recipeData = ref([]);
|
const recipeData = ref<any[]>([]);
|
||||||
function getRecipeData() {
|
function getRecipeData() {
|
||||||
appSearchSearchRecipePost({
|
appSearchSearchRecipePost({
|
||||||
body: {
|
params: {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
}
|
},
|
||||||
|
body: {}
|
||||||
}).then(res=> {
|
}).then(res=> {
|
||||||
console.log('菜谱数据', res)
|
console.log('菜谱数据', res)
|
||||||
recipeData.value = res.rows;
|
recipeData.value = res.rows;
|
||||||
@@ -57,30 +48,16 @@ function getRecipeData() {
|
|||||||
loading.value = false;
|
loading.value = false;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 收藏菜品
|
|
||||||
function handleSubmitCollectRecipe(item: any) {
|
|
||||||
collectRecipe(item)
|
|
||||||
}
|
|
||||||
// 防抖处理函数
|
|
||||||
const collectRecipe = debounce(1000, (item: any) => {
|
|
||||||
appCollectCollectPost({
|
|
||||||
body: {
|
|
||||||
targetId: item.id,
|
|
||||||
targetType: CollectionType.RECIPE
|
|
||||||
}
|
|
||||||
}).then(res=> {
|
|
||||||
item.isCollect = !item.isCollect;
|
|
||||||
})
|
|
||||||
}, {
|
|
||||||
atBegin: true, // 立即触发
|
|
||||||
});
|
|
||||||
|
|
||||||
function navigateToRecipeDetail(id: string | number) {
|
function navigateToRecipeDetail(id: string | number) {
|
||||||
navigateTo(`/pages-user/pages/recipe/index?id=${id}`)
|
navigateTo(`/pages-user/pages/recipe/index?id=${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function navigateToRecipeList() {
|
||||||
|
navigateTo('/pages-user/pages/recipe/list')
|
||||||
|
}
|
||||||
|
|
||||||
// 获取附近的菜品
|
// 获取附近的菜品
|
||||||
const dishData = ref([]);
|
const dishData = ref<any[]>([]);
|
||||||
function appMerchantDishNearbyList() {
|
function appMerchantDishNearbyList() {
|
||||||
appMerchantDishNearbyListPost({
|
appMerchantDishNearbyListPost({
|
||||||
params: {
|
params: {
|
||||||
@@ -88,8 +65,8 @@ function appMerchantDishNearbyList() {
|
|||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
lat: userStore.userLocation.latitude,
|
lat: String(userStore.userLocation.latitude ?? ''),
|
||||||
lng: userStore.userLocation.longitude,
|
lng: String(userStore.userLocation.longitude ?? ''),
|
||||||
}
|
}
|
||||||
}).then(res=> {
|
}).then(res=> {
|
||||||
console.log('菜品数据', res)
|
console.log('菜品数据', res)
|
||||||
@@ -100,6 +77,27 @@ function handleClickDish(item: any) {
|
|||||||
navigateTo(`/pages-store/pages/store/index?id=${item.merchantId}`)
|
navigateTo(`/pages-store/pages/store/index?id=${item.merchantId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMerchantName(item: any) {
|
||||||
|
return item?.merchantVo?.merchantName || item?.merchantName||item?.dishName || '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMerchantLogo(item: any) {
|
||||||
|
return item?.merchantVo?.logo || item?.logo || item?.dishImage?.split?.(',')?.[0] || item?.dishImage || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMerchantRate(item: any) {
|
||||||
|
const rating = Number(item?.merchantVo?.rating ?? item?.rating ?? 0)
|
||||||
|
return Number.isFinite(rating) && rating > 0 ? rating.toFixed(1) : '5.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPreviewRecipeList() {
|
||||||
|
return recipeData.value.slice(0, recipePreviewLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showRecipeMore() {
|
||||||
|
return recipeData.value.length > recipePreviewLimit
|
||||||
|
}
|
||||||
|
|
||||||
async function getPlatformDefaultStoreInfo() {}
|
async function getPlatformDefaultStoreInfo() {}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
@@ -131,67 +129,59 @@ defineExpose({
|
|||||||
v-show="!loading"
|
v-show="!loading"
|
||||||
class="animate-in fade-in animate-ease-in animate-duration-300"
|
class="animate-in fade-in animate-ease-in animate-duration-300"
|
||||||
>
|
>
|
||||||
<view class="flex-center-sb px-30rpx pt-16rpx">
|
<view class="px-24rpx pt-16rpx">
|
||||||
<view class="text-56rpx text-#333 lh-56rpx font-bold">{{
|
|
||||||
t("tabBar.browse")
|
|
||||||
}}</view>
|
|
||||||
<msg-box @toggleNotOpen="toggleNotOpen" />
|
|
||||||
</view>
|
|
||||||
<view class="px-30rpx mt-44rpx">
|
|
||||||
<search />
|
<search />
|
||||||
</view>
|
</view>
|
||||||
<view class="mt-50rpx px-30rpx">
|
<view class="browse-wrap px-24rpx">
|
||||||
<view @click="navigateTo('/pages-user/pages/recipe/list')" class="flex-center-sb">
|
<view class="section-title mt-36rpx">{{ t("pages.browse.titleRecipes") }}</view>
|
||||||
<text class="text-36rpx lh-36rpx text-#333 font-bold">{{
|
<view class="mt-28rpx">
|
||||||
t("pages.browse.titleRecipes")
|
<scroll-view scroll-x class="recipe-scroll" :show-scrollbar="false" :enable-flex="true">
|
||||||
}}</text>
|
<view class="recipe-track">
|
||||||
<image src="@img/chef/116.png" class="w-64rpx h-64rpx"></image>
|
<view
|
||||||
</view>
|
v-for="item in getPreviewRecipeList()"
|
||||||
<scroll-view scroll-x="true" class="mt-16rpx">
|
:key="item.id"
|
||||||
<view class="flex gap-30rpx">
|
class="recipe-item"
|
||||||
<template v-for="item in recipeData">
|
@click="navigateToRecipeDetail(item.id)"
|
||||||
<view @click="navigateToRecipeDetail(item.id)" class="w-312rpx">
|
>
|
||||||
<image
|
<image
|
||||||
:src="thumbnailImg(item?.recipeImage?.split(',')[0])"
|
:src="thumbnailImg(item?.recipeImage?.split(',')[0])"
|
||||||
class="w-310rpx h-296rpx rounded-24rpx mb-26rpx bg-common"
|
class="recipe-avatar"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
></image>
|
|
||||||
<view class="flex-center-sb">
|
|
||||||
<text class="text-30rpx lh-30rpx text-#333 line-clamp-1 tracking-[.04em] font-500"
|
|
||||||
>{{ item.recipeName }}</text
|
|
||||||
>
|
|
||||||
<view class="w-40rpx h-40rpx ml-14rpx shrink-0">
|
|
||||||
<collection
|
|
||||||
:is-collected="item.isCollect"
|
|
||||||
@collectionChange="handleSubmitCollectRecipe(item)"
|
|
||||||
/>
|
/>
|
||||||
|
<text class="recipe-name line-clamp-1">{{ item.recipeName }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="showRecipeMore()" class="recipe-more" @click="navigateToRecipeList">
|
||||||
|
<view class="recipe-more__text-wrap">
|
||||||
|
<text class="recipe-more__text">{{ t("pages.browse.moreRecipes") }}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<i class="i-carbon:chevron-right recipe-more__icon"></i>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view
|
<view class="section-title mt-54rpx">{{ t("pages.browse.titleCuisine") }}</view>
|
||||||
class="mt-50rpx mb-28rpx text-36rpx lh-36rpx text-#333 font-bold"
|
<scroll-view scroll-x class="store-scroll mt-28rpx pb-40rpx" :show-scrollbar="false" :enable-flex="true">
|
||||||
>{{ t("pages.browse.titleCuisine") }}</view
|
<view class="store-track">
|
||||||
>
|
<view v-for="item in dishData" :key="item.id" @click="handleClickDish(item)" class="store-card">
|
||||||
<view class="grid grid-cols-2 gap-x-30rpx gap-y-46rpx pb-40rpx">
|
|
||||||
<template v-for="item in dishData">
|
|
||||||
<view @click="handleClickDish(item)" class="w-330rpx overflow-hidden">
|
|
||||||
<image
|
<image
|
||||||
:src="thumbnailImg(item?.dishImage?.split(',')[0])"
|
:src="thumbnailImg(getMerchantLogo(item))"
|
||||||
class="w-full h-186rpx rounded-24rpx mb-16rpx bg-common"
|
class="store-card__cover"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
></image>
|
/>
|
||||||
<text class="text-30rpx lh-30rpx text-#333 line-clamp-1 tracking-[.04em] font-500"
|
<view class="store-card__right">
|
||||||
>{{ item.dishName }}</text
|
<view class="store-card__name line-clamp-2">{{ getMerchantName(item) }}</view>
|
||||||
>
|
<view class="store-card__rating">★★★★★ {{ getMerchantRate(item) }}</view>
|
||||||
|
<view class="store-card__brand">{{ t("pages.browse.brandTag") }}</view>
|
||||||
|
<view class="store-card__arrow center">
|
||||||
|
<i class="i-carbon:chevron-right text-30rpx text-white"></i>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<template #bottom>
|
<template #bottom>
|
||||||
<view class="h-50px"></view>
|
<view class="h-50px"></view>
|
||||||
@@ -201,4 +191,148 @@ defineExpose({
|
|||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss">
|
||||||
|
.browse-wrap {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 48rpx;
|
||||||
|
// font-weight: 700;
|
||||||
|
color: #1c1c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-scroll,
|
||||||
|
.store-scroll {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-track {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 22rpx;
|
||||||
|
padding-right: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-item {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-avatar {
|
||||||
|
width: 112rpx;
|
||||||
|
height: 112rpx;
|
||||||
|
border-radius: 56rpx;
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-name {
|
||||||
|
max-width: 140rpx;
|
||||||
|
margin-top: 14rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
color: #222;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-more {
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 112rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 4rpx;
|
||||||
|
padding: 4rpx 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-more__text-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-more__text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
color: #8a8a8a;
|
||||||
|
font-weight: 500;
|
||||||
|
writing-mode: vertical-rl;
|
||||||
|
text-orientation: upright;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-more__icon {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #8a8a8a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-track {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 22rpx;
|
||||||
|
padding-right: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card {
|
||||||
|
width: 404rpx;
|
||||||
|
height: 220rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card__cover {
|
||||||
|
width: 184rpx;
|
||||||
|
height: 220rpx;
|
||||||
|
background: #f2f2f2;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card__right {
|
||||||
|
flex: 1;
|
||||||
|
padding: 16rpx 14rpx 14rpx 16rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card__name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 34rpx;
|
||||||
|
color: #191919;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card__rating {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
color: #111;
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 30rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card__brand {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
color: #d39a48;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card__arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 10rpx;
|
||||||
|
bottom: 12rpx;
|
||||||
|
width: 52rpx;
|
||||||
|
height: 52rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!-- 第一行:自动缓慢滚动 + 可手动滑动 -->
|
<!-- 第一行:自动缓慢滚动 + 可手动滑动 -->
|
||||||
<view class="scroll-row first-row">
|
<view class="scroll-row first-row">
|
||||||
<scroll-view
|
<scroll-view
|
||||||
class="ab-scroll mb-22rpx"
|
class="ab-scroll mb-10rpx"
|
||||||
scroll-x
|
scroll-x
|
||||||
show-scrollbar="false"
|
show-scrollbar="false"
|
||||||
:scroll-left="scrollLeft1"
|
:scroll-left="scrollLeft1"
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, onMounted, onUnmounted, nextTick } from 'vue'
|
import { computed, ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
|
import { useCategoryNavStore } from '@/store'
|
||||||
|
|
||||||
// 定义分类项接口(与模板字段一致)
|
// 定义分类项接口(与模板字段一致)
|
||||||
interface CategoryItem {
|
interface CategoryItem {
|
||||||
@@ -97,6 +98,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
categories: () => []
|
categories: () => []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const categoryNavStore = useCategoryNavStore()
|
||||||
|
|
||||||
// 定义事件
|
// 定义事件
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
/** 点击分类项事件 */
|
/** 点击分类项事件 */
|
||||||
@@ -241,10 +244,11 @@ function onScroll2(e: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击处理
|
// 点击处理(列表页菜谱 id 来自 store,不放在 URL query)
|
||||||
const handleItemClick = (item: CategoryItem) => {
|
const handleItemClick = (item: CategoryItem) => {
|
||||||
emit('itemClick', item)
|
emit('itemClick', item)
|
||||||
navigateTo('/pages-store/pages/list/index?id=' + item.id)
|
categoryNavStore.setPendingRecipeCategoryId(item.id)
|
||||||
|
navigateTo('/pages-store/pages/list/index')
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigateTo(url: string) {
|
function navigateTo(url: string) {
|
||||||
@@ -301,14 +305,16 @@ onUnmounted(() => {
|
|||||||
min-width: 120rpx;
|
min-width: 120rpx;
|
||||||
height: 60rpx;
|
height: 60rpx;
|
||||||
padding: 0 20rpx;
|
padding: 0 20rpx;
|
||||||
border: 1px solid #C8C8C8;
|
background: #fff;
|
||||||
|
border: none;
|
||||||
border-radius: 30rpx;
|
border-radius: 30rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
transform: translateY(0) scale(0.95);
|
transform: translateY(0) scale(0.96);
|
||||||
}
|
}
|
||||||
|
|
||||||
.category-icon {
|
.category-icon {
|
||||||
|
|||||||
@@ -1,40 +1,277 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {thumbnailImg} from "@/utils/utils";
|
import { thumbnailImg } from '@/utils/utils'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
list: any[];
|
list: any[]
|
||||||
}>();
|
}>()
|
||||||
const { t } = useI18n();
|
const { t } = useI18n()
|
||||||
|
|
||||||
function handleClickFood(item: any) {
|
function handleClickFood(item: any) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages-store/pages/store/index?id=' + item.id
|
url: '/pages-store/pages/store/index?id=' + item.id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCardImages(item: any): string[] {
|
||||||
|
const raw = item?.shopImages
|
||||||
|
let urls: string[] = []
|
||||||
|
if (raw && typeof raw === 'string') {
|
||||||
|
urls = raw
|
||||||
|
.split(',')
|
||||||
|
.map((s: string) => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((u: string) => thumbnailImg(u) || u)
|
||||||
|
}
|
||||||
|
if (urls.length === 0 && item?.logo) {
|
||||||
|
const logo = thumbnailImg(item.logo) || item.logo
|
||||||
|
urls = logo ? [logo] : []
|
||||||
|
}
|
||||||
|
return urls
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTitleLines(item: any): { line1: string; line2: string } {
|
||||||
|
const en = item?.merchantCategoryNamesEn?.[0]
|
||||||
|
const zh = item?.merchantCategoryNamesZh?.[0]
|
||||||
|
const name = String(item?.merchantName || '').trim()
|
||||||
|
if (en && zh) return { line1: String(en), line2: String(zh) }
|
||||||
|
if (en && !zh) return { line1: String(en), line2: name && name !== String(en) ? name : '' }
|
||||||
|
if (zh && !en) return { line1: String(zh), line2: name && name !== String(zh) ? name : '' }
|
||||||
|
const parts = name.split(/\n|\r|\||/).map((s) => s.trim()).filter(Boolean)
|
||||||
|
if (parts.length >= 2) return { line1: parts[0], line2: parts[1] }
|
||||||
|
return { line1: name, line2: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMerchantRating(item: any): string {
|
||||||
|
const r = Number(item?.rating)
|
||||||
|
if (Number.isFinite(r) && r > 0) return r.toFixed(1)
|
||||||
|
return '5.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
function subtitleLine(item: any): string {
|
||||||
|
const comments = item?.commentCount ?? 0
|
||||||
|
const dt = item?.deliveryTime
|
||||||
|
if (dt === undefined || dt === null || dt === '') {
|
||||||
|
return `(${comments})`
|
||||||
|
}
|
||||||
|
const n = Number(dt)
|
||||||
|
if (Number.isFinite(n) && n > 0) {
|
||||||
|
const unit = n === 1 ? t('common.day') : t('common.days')
|
||||||
|
return `(${comments}) · ${n}${unit}`
|
||||||
|
}
|
||||||
|
return `(${comments}) · ${dt}`
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<scroll-view scroll-x="true">
|
<scroll-view class="featured-scroll" scroll-x enable-flex :show-scrollbar="false">
|
||||||
<view class="flex">
|
<view class="featured-track">
|
||||||
<view class="w-30rpx shrink-0"></view>
|
<view class="featured-track__pad" />
|
||||||
<template v-for="(item, index) in list" :key="item?.id ?? index">
|
<view
|
||||||
<view @click="handleClickFood(item)" :class="[index === 0 ? '' : 'ml-28rpx']">
|
v-for="(item, index) in list"
|
||||||
<image :src="thumbnailImg(item?.shopImages?.split(',')[0])" class="w-448rpx h-252rpx rounded-24rpx mb-20rpx bg-common" mode="aspectFill"></image>
|
:key="item?.id ?? index"
|
||||||
<text class="text-30rpx lh-30rpx text-#333 font-500 line-clamp-1">{{ item?.merchantName }}</text>
|
class="featured-card"
|
||||||
<view v-if="+item.deliveryService === 1" class="text-#CE7138 text-24rpx lh-24rpx mt-12rpx">
|
:class="{ 'featured-card--first': index === 0 }"
|
||||||
|
@click="handleClickFood(item)"
|
||||||
|
>
|
||||||
|
<!-- 左侧图:单图或与示例一致的双图上下分栏 -->
|
||||||
|
<view class="featured-card__media">
|
||||||
|
<template v-if="getCardImages(item).length >= 2">
|
||||||
|
<image
|
||||||
|
:src="getCardImages(item)[0]"
|
||||||
|
class="featured-card__media-half featured-card__media-half--top"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<image
|
||||||
|
v-else-if="getCardImages(item).length >= 1"
|
||||||
|
:src="getCardImages(item)[0]"
|
||||||
|
class="featured-card__media-single"
|
||||||
|
mode="aspectFill"
|
||||||
|
/>
|
||||||
|
<view v-else class="featured-card__media-single featured-card__media-placeholder" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="featured-card__body">
|
||||||
|
<view class="featured-card__titles">
|
||||||
|
<text v-if="getTitleLines(item).line1" class="featured-card__name featured-card__name--primary">{{
|
||||||
|
getTitleLines(item).line1
|
||||||
|
}}</text>
|
||||||
|
<text v-if="getTitleLines(item).line2" class="featured-card__name featured-card__name--secondary">{{
|
||||||
|
getTitleLines(item).line2
|
||||||
|
}}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-if="+item.deliveryService === 1"
|
||||||
|
class="featured-card__fee"
|
||||||
|
>
|
||||||
{{ t('pages-store.store.tips5') }} ${{ item.deliveryFee }}{{ t('pages-store.store.start') }}
|
{{ t('pages-store.store.tips5') }} ${{ item.deliveryFee }}{{ t('pages-store.store.start') }}
|
||||||
</view>
|
</view>
|
||||||
<view class="text-24rpx lh-24rpx flex items-center mt-12rpx">
|
|
||||||
<text class="text-#333 font-500">{{ item.rating }}</text>
|
<view class="featured-card__rating">
|
||||||
<image src="@img/chef/124.png" class="w-24rpx h-24rpx mx-4rpx mt-2rpx"></image>
|
<text class="featured-card__stars">★★★★★</text>
|
||||||
<text class="text-#7D7D7D">({{ item.commentCount }}) • {{ item.deliveryTime }} {{ Number(item.deliveryTime) === 1 ? t('common.day') : t('common.days') }}</text>
|
<text class="featured-card__rate-num">{{ formatMerchantRating(item) }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<text class="featured-card__sub">{{ subtitleLine(item) }}</text>
|
||||||
|
|
||||||
|
<view class="featured-card__arrow center">
|
||||||
|
<view class="i-carbon:chevron-right text-32rpx text-white"></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</view>
|
||||||
<view class="w-30rpx shrink-0 op-0">1</view>
|
<view class="featured-track__pad featured-track__pad--end" />
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.featured-scroll {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-track {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
padding-bottom: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-track__pad {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 6rpx;
|
||||||
|
}
|
||||||
|
.featured-track__pad--end {
|
||||||
|
width: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 620rpx;
|
||||||
|
height: 256rpx;
|
||||||
|
min-height: 256rpx;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.featured-card--first {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__media {
|
||||||
|
width: 232rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__media-single {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 256rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__media-half {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.featured-card__media-half--top {
|
||||||
|
border-bottom: 3rpx solid #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 20rpx 20rpx 18rpx 20rpx;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__titles {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 34rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
.featured-card__name--primary {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.featured-card__name--secondary {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 34rpx;
|
||||||
|
color: #1a1a1a;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__media-placeholder {
|
||||||
|
min-height: 256rpx;
|
||||||
|
background: #e8e8e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__fee {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 30rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #d48806;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__rating {
|
||||||
|
margin-top: 14rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__stars {
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 1;
|
||||||
|
color: #1a1a1a;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__rate-num {
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__sub {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-card__arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 16rpx;
|
||||||
|
bottom: 16rpx;
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #14181b;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -61,11 +61,16 @@ function handleCollectionChange(value: boolean) {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<view @click="handleClickFood" class="mb-52rpx">
|
<view @click="handleClickFood" class="mb-52rpx">
|
||||||
|
<view class="food-box-img-wrap">
|
||||||
|
<view v-if="item.isNew == 1" class="dish-new-ribbon">
|
||||||
|
<text class="dish-new-ribbon__text">NEW</text>
|
||||||
|
</view>
|
||||||
<image
|
<image
|
||||||
:src="item?.dishImage?.split(',')[0]||item?.logo"
|
:src="item?.dishImage?.split(',')[0]||item?.logo"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
class="w-100% h-400rpx rounded-24rpx bg-common"
|
class="w-100% h-400rpx rounded-24rpx bg-common"
|
||||||
></image>
|
></image>
|
||||||
|
</view>
|
||||||
<view class="flex justify-between items-start mt-14rpx">
|
<view class="flex justify-between items-start mt-14rpx">
|
||||||
<view>
|
<view>
|
||||||
<text class="text-30rpx lh-30rpx text-#333 font-500 line-clamp-1"
|
<text class="text-30rpx lh-30rpx text-#333 font-500 line-clamp-1"
|
||||||
@@ -86,3 +91,37 @@ function handleCollectionChange(value: boolean) {
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.food-box-img-wrap {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useUserStore } from "@/store";
|
import { useUserStore } from "@/store";
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
/** 首页顶栏紧凑模式:更小图标与间距 */
|
||||||
|
compact?: boolean
|
||||||
|
}>(),
|
||||||
|
{ compact: false }
|
||||||
|
)
|
||||||
const emit = defineEmits(['toggleNotOpen']);
|
const emit = defineEmits(['toggleNotOpen']);
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
function navigateTo(url: string) {
|
function navigateTo(url: string) {
|
||||||
@@ -12,12 +19,25 @@ function navigateTo(url: string) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="flex items-center">
|
<view class="flex items-center" :class="compact ? 'gap-20rpx' : ''">
|
||||||
<view @click="navigateTo('/pages-user/pages/message/index')" class="w-40rpx h-40rpx mr-42rpx relative">
|
<view
|
||||||
<view v-if="userStore.isLogin && userStore.unreadMessageCount > 0" class="w-32rpx h-32rpx bg-#E23636 absolute z-2 top--16rpx right--16rpx rounded-50% text-24rpx text-#fff text-center line-height-32rpx">{{ userStore.unreadMessageCount }}</view>
|
@click="navigateTo('/pages-user/pages/message/index')"
|
||||||
<image src="@img/chef/114.png" class="w-40rpx h-40rpx"></image>
|
:class="compact ? 'w-34rpx h-34rpx mr-0' : 'w-40rpx h-40rpx mr-42rpx'"
|
||||||
|
class="relative shrink-0"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-if="userStore.isLogin && userStore.unreadMessageCount > 0"
|
||||||
|
:class="compact ? 'h-26rpx top--10rpx right--10rpx text-20rpx px-8rpx' : 'w-32rpx h-32rpx top--16rpx right--16rpx text-24rpx line-height-32rpx'"
|
||||||
|
class="bg-#E23636 absolute z-2 rounded-50% text-#fff text-center font-500"
|
||||||
|
>{{ userStore.unreadMessageCount }}</view>
|
||||||
|
<image src="@img/chef/114.png" :class="compact ? 'w-34rpx h-34rpx' : 'w-40rpx h-40rpx'"></image>
|
||||||
</view>
|
</view>
|
||||||
<image @click="emit('toggleNotOpen')" src="@img/chef/115.png" class="w-40rpx h-40rpx"></image>
|
<image
|
||||||
|
@click="emit('toggleNotOpen')"
|
||||||
|
src="@img/chef/115.png"
|
||||||
|
:class="compact ? 'w-34rpx h-34rpx' : 'w-40rpx h-40rpx'"
|
||||||
|
class="shrink-0"
|
||||||
|
></image>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,22 @@ function handleClickSearch() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view @click="handleClickSearch" class="flex items-center h-88rpx bg-#F2F3F6 rounded-44rpx pl-36rpx">
|
<view
|
||||||
<image src="@img/chef/100222.png" class="w-28rpx h-28rpx"></image>
|
@click="handleClickSearch"
|
||||||
<text class="text-30rpx text-#434343 ml-16rpx tracking-[.04em] font-500">{{ t('components.search.placeholder') }}</text>
|
class="home-search-bar flex items-center h-88rpx rounded-44rpx pl-36rpx pr-28rpx bg-white"
|
||||||
|
>
|
||||||
|
<image src="@img/chef/100222.png" class="w-28rpx h-28rpx shrink-0"></image>
|
||||||
|
<text class="home-search-placeholder text-28rpx ml-16rpx tracking-[.04em] font-500 truncate">{{
|
||||||
|
t('components.search.placeholder')
|
||||||
|
}}</text>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.home-search-bar {
|
||||||
|
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.home-search-placeholder {
|
||||||
|
color: #9a9a9a;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -75,8 +75,11 @@ function selectTab(item: any) {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s;
|
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.2s;
|
||||||
width: 132rpx;
|
width: 102rpx;
|
||||||
height: 132rpx;
|
height: 102rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.07);
|
||||||
}
|
}
|
||||||
.img-selected {
|
.img-selected {
|
||||||
border: 4rpx solid #ce7138;
|
border: 4rpx solid #ce7138;
|
||||||
@@ -86,8 +89,8 @@ function selectTab(item: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tab-img {
|
.tab-img {
|
||||||
width: 112rpx;
|
width: 98rpx;
|
||||||
height: 112rpx;
|
height: 98rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
} from "@/service";
|
} from "@/service";
|
||||||
import usePage from "@/hooks/usePage";
|
import usePage from "@/hooks/usePage";
|
||||||
import {getFeaturedDishList} from "@/pages-store/service";
|
import {getFeaturedDishList} from "@/pages-store/service";
|
||||||
|
import { formatSalesCount } from "@/utils/utils";
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -41,6 +42,19 @@ function isSoldOutStock(stockLike: unknown) {
|
|||||||
return !Number.isNaN(n) && n <= 0
|
return !Number.isNaN(n) && n <= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 底部营销条文案(接口若返回 marketingLabel / hotSaleTag 等则展示) */
|
||||||
|
function getDishPromoLabel(item: Record<string, unknown>): string {
|
||||||
|
const raw = item.marketingLabel ?? item.hotSaleTag ?? item.rankTag ?? item.promotionLabel
|
||||||
|
return typeof raw === 'string' && raw.trim() ? raw.trim() : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFeaturedDishDisplayPrice(item: Record<string, any>) {
|
||||||
|
const firstSpecPrice = item?.merchantSideDishVoList?.[0]?.merchantSideDishItemVoList?.[0]?.actualSalePrice
|
||||||
|
if (firstSpecPrice != null && String(firstSpecPrice) !== '') return firstSpecPrice
|
||||||
|
if (item?.actualSalePrice != null && String(item.actualSalePrice) !== '') return item.actualSalePrice
|
||||||
|
return item?.discountPrice ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
function navigateTo(url: string) {
|
function navigateTo(url: string) {
|
||||||
if(userStore.checkLogin()) {
|
if(userStore.checkLogin()) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
@@ -209,6 +223,30 @@ const isShowMerchant = computed(()=> {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 精选菜品瀑布流:按序均分到两列(0,2,4… / 1,3,5…),分页追加后仍交错排列
|
||||||
|
const featuredDishColumns = computed(() => {
|
||||||
|
const list = dataList.value
|
||||||
|
return [
|
||||||
|
list.filter((_, i) => i % 2 === 0),
|
||||||
|
list.filter((_, i) => i % 2 === 1),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 顶栏购物车角标(多商家购物车汇总件数) */
|
||||||
|
const cartBadgeTotal = computed(() => {
|
||||||
|
const list = userStore.userCartAllData
|
||||||
|
if (!Array.isArray(list) || list.length === 0) return 0
|
||||||
|
let n = 0
|
||||||
|
for (const m of list) {
|
||||||
|
n += (m as { merchantCartVoList?: unknown[] })?.merchantCartVoList?.length || 0
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
})
|
||||||
|
|
||||||
|
function goCart() {
|
||||||
|
navigateTo('/pages-user/pages/cart/index')
|
||||||
|
}
|
||||||
|
|
||||||
// 手动触发下拉刷新了
|
// 手动触发下拉刷新了
|
||||||
function onRefresh() {
|
function onRefresh() {
|
||||||
console.log('手动触发下拉刷新了')
|
console.log('手动触发下拉刷新了')
|
||||||
@@ -221,7 +259,7 @@ function handleClickSwiper(item: any) {
|
|||||||
console.log(item, '点击轮播图')
|
console.log(item, '点击轮播图')
|
||||||
switch (Number(item.activityType)) {
|
switch (Number(item.activityType)) {
|
||||||
case 1: // 商家列表
|
case 1: // 商家列表
|
||||||
navigateTo('/pages-store/pages/list/index?id=')
|
navigateTo('/pages-store/pages/list/index')
|
||||||
break
|
break
|
||||||
case 2: // 活动菜品列表
|
case 2: // 活动菜品列表
|
||||||
navigateTo('/pages-store/pages/dishes/index?id=' + item.id)
|
navigateTo('/pages-store/pages/dishes/index?id=' + item.id)
|
||||||
@@ -261,7 +299,7 @@ const debouncedEmit = debounce(1300, (isCollected: boolean, id: string, type: Co
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view
|
<view
|
||||||
class="bg-#fff"
|
class="home-page-root"
|
||||||
:style="[
|
:style="[
|
||||||
{
|
{
|
||||||
height: configStore.windowHeight + 'px',
|
height: configStore.windowHeight + 'px',
|
||||||
@@ -271,20 +309,34 @@ const debouncedEmit = debounce(1300, (isCollected: boolean, id: string, type: Co
|
|||||||
<z-paging @onRefresh="onRefresh" ref="paging" v-model="dataList" :auto="false" @query="queryList" @scroll="onPageScroll" :refresher-enabled="true" :auto-show-back-to-top="false">
|
<z-paging @onRefresh="onRefresh" ref="paging" v-model="dataList" :auto="false" @query="queryList" @scroll="onPageScroll" :refresher-enabled="true" :auto-show-back-to-top="false">
|
||||||
<template #top>
|
<template #top>
|
||||||
<status-bar />
|
<status-bar />
|
||||||
<view class="flex items-center pt-18rpx px-30rpx pb-20rpx">
|
<!-- 设计稿:品牌行 + 右侧地址胶囊、消息/客服、购物车 -->
|
||||||
<!-- <text class="text-52rpx lh-52rpx text-#333 font-bold shrink-0">{{Config.appName}}</text>-->
|
<view class="home-top-header px-24rpx pt-12rpx pb-8rpx">
|
||||||
<image
|
<view class="flex items-center justify-between gap-12rpx">
|
||||||
src="@img/logo.png"
|
<view class="flex items-center gap-14rpx min-w-0 flex-1">
|
||||||
class="w-52rpx h-52rpx shrink-0"
|
<image src="@img/logo.png" class="w-52rpx h-52rpx shrink-0" style="border-radius: 50%;"></image>
|
||||||
></image>
|
<text class="text-32rpx lh-36rpx text-#1a1a1a font-bold tracking-tight shrink-0">{{ Config.appName }}</text>
|
||||||
<view class="bg-#D8D8D8 w-1rpx h-40rpx mx-14rpx"></view>
|
</view>
|
||||||
<view @click="navigateTo('/pages/address/index')" class="text-#00A76D text-28rpx lh-28rpx flex items-center">
|
<view class="flex items-center gap-10rpx shrink-0">
|
||||||
|
<view class="home-loc-pill" @click="navigateTo('/pages-user/pages/search-address/index')">
|
||||||
|
<!-- <image src="@img/chef/101.png" class="home-loc-pill__pin w-22rpx h-22rpx shrink-0"></image> -->
|
||||||
|
<text class="home-loc-pill__text line-clamp-1">{{
|
||||||
|
userStore.userLocation.location || t('pages.home.default-location')
|
||||||
|
}}</text>
|
||||||
|
<image src="@img/chef/119.png" class="home-loc-pill__arrow w-20rpx h-20rpx shrink-0 op-50 mt-2rpx"></image>
|
||||||
|
</view>
|
||||||
|
<view class="home-cart-btn" @click="goCart">
|
||||||
|
<view class="i-carbon:shopping-cart text-36rpx text-#14181b"></view>
|
||||||
|
<view v-if="userStore.isLogin && cartBadgeTotal > 0" class="home-cart-badge">{{ cartBadgeTotal > 99 ? '99+' : cartBadgeTotal }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="home-delivery-actions-row mt-14rpx flex items-center justify-between gap-16rpx">
|
||||||
|
<view @click="navigateTo('/pages/address/index')" class="home-delivery-row flex items-center min-w-0 flex-1 text-26rpx lh-32rpx text-#00A76D">
|
||||||
<text v-if="userStore.appointmentTimeShow">{{ t('pages.address.appTime') }}: {{ userStore.appointmentTimeShow }}</text>
|
<text v-if="userStore.appointmentTimeShow">{{ t('pages.address.appTime') }}: {{ userStore.appointmentTimeShow }}</text>
|
||||||
<text v-else>{{ t('pages.address.reservation') }}</text>
|
<text v-else>{{ t('pages.address.reservation') }}</text>
|
||||||
<image
|
<image src="@img/chef/119.png" class="w-22rpx h-22rpx ml-8rpx shrink-0"></image>
|
||||||
src="@img/chef/119.png"
|
</view>
|
||||||
class="w-24rpx h-24rpx ml-6rpx mt-4rpx shrink-0"
|
<msg-box compact class="shrink-0" @toggleNotOpen="toggleNotOpen" />
|
||||||
></image>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -294,169 +346,415 @@ const debouncedEmit = debounce(1300, (isCollected: boolean, id: string, type: Co
|
|||||||
>
|
>
|
||||||
<home-skeleton />
|
<home-skeleton />
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-center-sb px-30rpx pt-34rpx">
|
<view class="px-24rpx pt-12rpx pb-8rpx">
|
||||||
<!--展示用户的定位城市,如果用户没有使用定位则展示选择的城市,用户选择城市后,需要更新定位城市-->
|
|
||||||
<view @click="navigateTo('/pages-user/pages/search-address/index')" class="flex items-center text-30rpx text-#333 font-500">
|
|
||||||
<text class="line-clamp-1">
|
|
||||||
{{ userStore.userLocation.location || t('pages.home.default-location') }}
|
|
||||||
</text>
|
|
||||||
<image
|
|
||||||
src="@img/chef/101.png"
|
|
||||||
class="w-24rpx h-24rpx ml-10rpx mt-6rpx shrink-0"
|
|
||||||
></image>
|
|
||||||
</view>
|
|
||||||
<view class="shrink-0 ml-40rpx">
|
|
||||||
<msg-box @toggleNotOpen="toggleNotOpen" />
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<view class="px-30rpx mt-32rpx pb-22rpx">
|
|
||||||
<search />
|
<search />
|
||||||
<!-- 分类滚动区域 -->
|
<!-- 分类标签(双行横滑) -->
|
||||||
<view class="mt-40rpx" v-if="appMerchantLabelList.length > 0">
|
<view class="mt-28rpx" v-if="appMerchantLabelList.length > 0">
|
||||||
<class-bullet :categories="appMerchantLabelList" @itemClick="handleItemClick" />
|
<class-bullet :categories="appMerchantLabelList" @itemClick="handleItemClick" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<swiper
|
<swiper
|
||||||
class="card-swiper"
|
class="home-promo-swiper card-swiper"
|
||||||
:circular="true"
|
:circular="true"
|
||||||
:autoplay="true"
|
:autoplay="true"
|
||||||
previous-margin="60rpx"
|
previous-margin="48rpx"
|
||||||
next-margin="60rpx"
|
next-margin="48rpx"
|
||||||
|
>
|
||||||
|
<swiper-item
|
||||||
|
v-for="(item, sIdx) in swiperList"
|
||||||
|
:key="item.id ?? sIdx"
|
||||||
|
@click="handleClickSwiper(item)"
|
||||||
>
|
>
|
||||||
<template v-for="item in swiperList" :key="item.id">
|
|
||||||
<swiper-item @click="handleClickSwiper(item)" class="">
|
|
||||||
<image
|
<image
|
||||||
:src="item.activityImage"
|
:src="item.activityImage"
|
||||||
class="swiper-item-content w-full h-100% rounded-24rpx bg-common"
|
class="swiper-item-content w-full h-100% rounded-32rpx bg-common"
|
||||||
></image>
|
></image>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
</template>
|
|
||||||
</swiper>
|
</swiper>
|
||||||
|
|
||||||
<!-- 分类滚动区域 -->
|
<!-- 快捷入口(圆形分类) -->
|
||||||
<tabs-type @changeType="tabsTypeChange" v-if="appMerchantCategoryList.length > 0" :list="appMerchantCategoryList" :currentId="currentCategory" class="mt-22rpx" />
|
<tabs-type @changeType="tabsTypeChange" v-if="appMerchantCategoryList.length > 0" :list="appMerchantCategoryList" :currentId="currentCategory" class="mt-28rpx home-tabs-quick" />
|
||||||
|
|
||||||
<!-- 筛选工具 -->
|
<!-- 筛选工具 -->
|
||||||
<!-- <filtrate-tool class="mt-32rpx" @togglePickup="togglePickup" @toggleDiscount="toggleDiscount" @toggleScore="toggleScore" @togglePrice="togglePrice" />-->
|
<!-- <filtrate-tool class="mt-32rpx" @togglePickup="togglePickup" @toggleDiscount="toggleDiscount" @toggleScore="toggleScore" @togglePrice="togglePrice" />-->
|
||||||
|
|
||||||
<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 精选商家(浅底 + 横向卡片,对齐设计稿) -->
|
||||||
<view v-if="featuredList.length > 0" class="mt-56rpx">
|
<view v-if="featuredList.length > 0" class="featured-merchants-section mt-36rpx px-24rpx pt-8rpx pb-16rpx">
|
||||||
<view
|
<view class="mb-24rpx px-6rpx text-32rpx lh-44rpx text-#1a1a1a">
|
||||||
class="mb-30rpx pl-30rpx text-36rpx lh-36rpx text-#333 font-bold"
|
{{ t('pages.home.featured-on') }}
|
||||||
>{{ t('pages.home.featured-on') }}</view>
|
</view>
|
||||||
<featured-on :list="featuredList" />
|
<featured-on :list="featuredList" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- Nearby Merchants 附近商家 -->
|
<!-- Nearby Merchants 附近商家 -->
|
||||||
<view v-if="nearbyList.length > 0" class="mt-56rpx">
|
<view v-if="nearbyList.length > 0" class="nearby-merchants-block mt-36rpx px-24rpx pb-16rpx">
|
||||||
<view
|
<view class="mb-24rpx px-6rpx text-32rpx lh-44rpx text-#1a1a1a ">
|
||||||
class="mb-32rpx pl-30rpx text-36rpx lh-36rpx text-#333 font-bold"
|
{{ t('pages.home.nearby-merchants') }}
|
||||||
>{{ t('pages.home.nearby-merchants') }}</view>
|
</view>
|
||||||
<nearby-merchants :list="nearbyList" />
|
<nearby-merchants :list="nearbyList" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- List -->
|
<!-- List 精选菜品瀑布流(浅底 + 白卡片 + 阴影,结构对齐设计稿) -->
|
||||||
<view class="mt-56rpx px-30rpx">
|
<view class="featured-dishes-section mt-36rpx px-24rpx pb-40rpx">
|
||||||
<view class="mb-32rpx text-36rpx lh-36rpx text-#333 font-bold"
|
<view class="mb-24rpx px-6rpx text-32rpx lh-44rpx text-#1a1a1a "
|
||||||
>{{ t('pages.home.featured-dishes') }}</view>
|
>{{ t('pages.home.featured-dishes') }}</view>
|
||||||
<template v-for="(item, index) in dataList" :key="index">
|
<view class="waterfall-row flex gap-16rpx items-start">
|
||||||
<view @click="navigateToDishes(item)" class="w-100% mb-30rpx">
|
<view
|
||||||
<view class="relative h-448rpx rounded-24rpx mb-28rpx">
|
v-for="(col, colIndex) in featuredDishColumns"
|
||||||
<view @click.stop="handleDishCollectionClick(item)" class="w-68rpx h-68rpx absolute z-2 top-0 right-0">
|
:key="colIndex"
|
||||||
|
class="waterfall-col flex-1 min-w-0 flex flex-col gap-16rpx"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="item in col"
|
||||||
|
:key="item.id || String(item.merchantId) + '-' + item.dishName"
|
||||||
|
@click="navigateToDishes(item)"
|
||||||
|
class="featured-dish-card w-full"
|
||||||
|
>
|
||||||
|
<view class="featured-dish-image">
|
||||||
|
<view v-if="item.isNew == 1" class="dish-new-ribbon">
|
||||||
|
<text class="dish-new-ribbon__text">NEW</text>
|
||||||
|
</view>
|
||||||
|
<image
|
||||||
|
:src="item?.dishImage?.split(',')[0]"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="featured-dish-img"
|
||||||
|
/>
|
||||||
|
<view
|
||||||
|
v-if="isSoldOutStock(item?.stock)"
|
||||||
|
class="featured-dish-sold-dim"
|
||||||
|
/>
|
||||||
|
<image
|
||||||
|
v-if="isSoldOutStock(item?.stock)"
|
||||||
|
src="/static/app/images/SoldOut.png"
|
||||||
|
mode="aspectFill"
|
||||||
|
class="featured-dish-sold-overlay"
|
||||||
|
/>
|
||||||
|
<view
|
||||||
|
@click.stop="handleDishCollectionClick(item)"
|
||||||
|
class="featured-dish-collect w-56rpx h-56rpx absolute z-4 top-12rpx right-12rpx center"
|
||||||
|
>
|
||||||
<image
|
<image
|
||||||
v-if="!item.isCollect"
|
v-if="!item.isCollect"
|
||||||
src="@img-store/1334.png"
|
src="@img-store/1334.png"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
class="w-full h-full"
|
class="w-44rpx h-44rpx featured-dish-collect-icon"
|
||||||
/>
|
/>
|
||||||
<image
|
<image
|
||||||
v-else
|
v-else
|
||||||
src="@img-store/1337.png"
|
src="@img-store/1337.png"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
class="w-full h-full"
|
class="w-44rpx h-44rpx featured-dish-collect-icon"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
<view
|
|
||||||
v-if="isSoldOutStock(item?.stock)"
|
|
||||||
class="absolute z-2 left-20rpx top-20rpx px-16rpx h-52rpx rounded-26rpx bg-#14181B/70 center text-26rpx text-#fff font-500"
|
|
||||||
>
|
|
||||||
已售完
|
|
||||||
</view>
|
</view>
|
||||||
<image
|
<view class="featured-dish-body">
|
||||||
:src="item?.dishImage?.split(',')[0]"
|
<view class="featured-dish-meta flex items-start justify-between gap-12rpx mb-14rpx">
|
||||||
mode="aspectFill"
|
<view class="min-w-0 flex-1">
|
||||||
class="w-full h-full rounded-24rpx bg-common"
|
<text class="featured-dish-price">US${{ getFeaturedDishDisplayPrice(item) }}</text>
|
||||||
/>
|
<!-- <text
|
||||||
|
v-if="Number(item?.originalPrice) > Number(item?.discountPrice)"
|
||||||
|
class="featured-dish-original"
|
||||||
|
>US${{ item?.originalPrice }}</text> -->
|
||||||
</view>
|
</view>
|
||||||
<view class="line-clamp-1 text-30rpx text-#333 font-500">
|
<text class="featured-dish-sales shrink-0">{{ t('pages-store.store.sales') }}: {{ formatSalesCount(item?.salesCount) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="featured-dish-title line-clamp-2 mb-16rpx">
|
||||||
{{ item?.dishName }}
|
{{ item?.dishName }}
|
||||||
</view>
|
</view>
|
||||||
<view class="flex-center-sb mt-12rpx">
|
<view class="flex items-center justify-between gap-12rpx">
|
||||||
<text class="text-32rpx lh-30rpx text-#333 font-500">US${{ item?.discountPrice }}</text>
|
|
||||||
<view
|
<view
|
||||||
v-if="Number(item?.memberPrice) > 0"
|
v-if="Number(item?.memberPrice) > 0"
|
||||||
class="member-price-tag text-[#FBE3C3] font-500 text-30rpx lh-30rpx center pl-6rpx break-all"
|
class="featured-dish-member shrink min-w-0"
|
||||||
>
|
>
|
||||||
<text>{{ t('pages-store.store.members') }}: </text>
|
<text class="featured-dish-member-inner">{{ t('pages-store.store.members') }}: US${{ item?.memberPrice }}</text>
|
||||||
${{ item?.memberPrice }}
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<view v-else class="flex-1 min-w-0"></view>
|
||||||
<view class="flex-center-sb mt-12rpx">
|
<view class="featured-dish-add center shrink-0">
|
||||||
<view class="text-28rpx text-#999">
|
|
||||||
<view class="line-through">US${{ item?.originalPrice }}</view>
|
|
||||||
<view>{{ t('pages-store.store.sales') }}:{{ item?.salesCount }}</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="center w-64rpx h-64rpx rounded-50% bg-white shadow-lg">
|
|
||||||
<image
|
<image
|
||||||
src="@img/chef/1285.png"
|
src="@img/chef/1285.png"
|
||||||
class="w-30rpx h-30rpx shrink-0"
|
class="w-28rpx h-28rpx"
|
||||||
></image>
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="getDishPromoLabel(item as Record<string, unknown>)"
|
||||||
|
class="featured-dish-promo mt-16rpx"
|
||||||
|
>
|
||||||
|
<text class="featured-dish-promo-text">{{ getDishPromoLabel(item as Record<string, unknown>) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
|
||||||
</view>
|
</view>
|
||||||
<template #bottom>
|
<template #bottom>
|
||||||
<view class="h-50px"></view>
|
<view class="h-50px"></view>
|
||||||
<view :style="[configStore.iosSafeBottomPlaceholder]"></view>
|
<view :style="[configStore.iosSafeBottomPlaceholder]"></view>
|
||||||
</template>
|
</template>
|
||||||
</z-paging>
|
</z-paging>
|
||||||
<view v-if="userStore.isLogin && userStore.userCartAllData.length > 0" @click="navigateTo('/pages-user/pages/cart/index')" class="fixed bottom-138rpx left-50% translate-x--50% px-26rpx h-88rpx bg-#14181B rounded-44rpx center text-28rpx text-#fff font-500">
|
<!-- 回到顶部(购物车已并入顶栏,不再使用底部浮条) -->
|
||||||
<image src="@img/chef/125.png" class="w-28rpx h-28rpx shrink-0"></image>
|
<view v-if="showBackToTop" @click="scrollToTop" class="home-back-top fixed left-30rpx w-88rpx h-88rpx bg-#14181B rounded-50% center shadow-lg">
|
||||||
<view class="ml-10rpx whitespace-nowrap">{{ userStore.userCartAllData[0]?.merchantName }}</view>
|
|
||||||
<view class="w-8rpx h-8rpx rounded-50% bg-white mx-8rpx"></view>
|
|
||||||
<text>{{ userStore.userCartAllData[0]?.merchantCartVoList?.length || 0 }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- 回到顶部按钮 -->
|
|
||||||
<view v-if="showBackToTop" @click="scrollToTop" class="fixed bottom-148rpx left-30rpx w-88rpx h-88rpx bg-#14181B rounded-50% center shadow-lg">
|
|
||||||
<image src="@img/chef/119.png" class="w-40rpx h-40rpx shrink-0 rotate-180"></image>
|
<image src="@img/chef/119.png" class="w-40rpx h-40rpx shrink-0 rotate-180"></image>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.home-page-root {
|
||||||
|
background: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-top-header {
|
||||||
|
background: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-loc-pill {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
max-width: 200rpx;
|
||||||
|
padding: 12rpx 18rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-loc-pill__text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-cart-btn {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-cart-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 4rpx;
|
||||||
|
right: 4rpx;
|
||||||
|
min-width: 28rpx;
|
||||||
|
height: 28rpx;
|
||||||
|
padding: 0 6rpx;
|
||||||
|
font-size: 18rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
background: #e23636;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-delivery-row {
|
||||||
|
padding-left: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-promo-swiper {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-tabs-quick {
|
||||||
|
padding-left: 8rpx;
|
||||||
|
padding-right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nearby-merchants-block {
|
||||||
|
background: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 为底栏 Tab 预留空间,避免被遮挡 */
|
||||||
|
.home-back-top {
|
||||||
|
bottom: calc(100rpx + env(safe-area-inset-bottom, 0px));
|
||||||
|
}
|
||||||
|
|
||||||
.card-swiper {
|
.card-swiper {
|
||||||
height: 420rpx;
|
height: 400rpx;
|
||||||
}
|
}
|
||||||
.swiper-item-content {
|
.swiper-item-content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
transform: scale(0.95);
|
transform: scale(0.94);
|
||||||
border-radius: 20rpx;
|
border-radius: 32rpx;
|
||||||
transition: transform 0.3s;
|
box-shadow: 0 8rpx 28rpx rgba(0, 0, 0, 0.08);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
.swiper-item-active .swiper-item-content {
|
.swiper-item-active .swiper-item-content {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
.member-price-tag {
|
|
||||||
min-width: 220rpx;
|
/* 精选商家 / 精选菜品 区块(与页面背景统一) */
|
||||||
height: 42rpx;
|
.featured-merchants-section,
|
||||||
background-image: url("/static/images/chef/1282.png");
|
.featured-dishes-section {
|
||||||
background-size: 100% 100%;
|
background: #f2f2f2;
|
||||||
background-repeat: no-repeat;
|
}
|
||||||
|
|
||||||
|
.featured-dish-collect-icon {
|
||||||
|
filter: drop-shadow(0 2rpx 6rpx rgba(0, 0, 0, 0.18));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 瀑布流商品图:固定 1:1 区域,便于「已售完」等角标统一 */
|
||||||
|
.featured-dish-image {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
background: #f0f0f0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-dish-img {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-dish-sold-overlay {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 3;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-dish-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-dish-body {
|
||||||
|
padding: 20rpx 20rpx 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-dish-price {
|
||||||
|
color: #e02e24;
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-dish-original {
|
||||||
|
margin-left: 10rpx;
|
||||||
|
color: #b3b3b3;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 400;
|
||||||
|
text-decoration: line-through;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-dish-sales {
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1.35;
|
||||||
|
max-width: 48%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-dish-title {
|
||||||
|
color: #1a1a1a;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 会员价:暖色胶囊,替代长条形切图 */
|
||||||
|
.featured-dish-member {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: calc(100% - 88rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-dish-add {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #14181b;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可选营销条(淡红底 + 文案) */
|
||||||
|
.featured-dish-promo {
|
||||||
|
padding: 12rpx 16rpx;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
background: #fff0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-dish-promo-text {
|
||||||
|
color: #e02e24;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ const poster = computed(() => ({
|
|||||||
type: 'view',
|
type: 'view',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'http://192.168.5.118:8011/h5/'+ `pages-login/pages/sign-up/index?invitationCode=${userStore.userInfo.invitationCode}`,
|
text: 'http://www.howhowfresh.com/h5/'+ `pages-login/pages/sign-up/index?invitationCode=${userStore.userInfo.invitationCode}`,
|
||||||
type: 'qrcode',
|
type: 'qrcode',
|
||||||
css: {
|
css: {
|
||||||
width: '124rpx',
|
width: '124rpx',
|
||||||
|
|||||||
@@ -83,10 +83,16 @@ defineExpose({
|
|||||||
</view>
|
</view>
|
||||||
<view class="shrink-0 ml-20rpx" v-if="userStore.userInfo.invitationCode">
|
<view class="shrink-0 ml-20rpx" v-if="userStore.userInfo.invitationCode">
|
||||||
<uqrcode ref="uqrcode" canvas-id="qrcode"
|
<uqrcode ref="uqrcode" canvas-id="qrcode"
|
||||||
:value="'http://192.168.5.118:8011/h5/' + `pages-login/pages/sign-up/index?invitationCode=${userStore.userInfo.invitationCode}`"
|
:value="'http://www.howhowfresh.com/h5/' + `pages-login/pages/sign-up/index?invitationCode=${userStore.userInfo.invitationCode}`"
|
||||||
:size="124"
|
:size="124"
|
||||||
sizeUnit="rpx"
|
sizeUnit="rpx"
|
||||||
:options="{}"></uqrcode>
|
:options="{}"></uqrcode>
|
||||||
|
|
||||||
|
<!-- <uqrcode ref="uqrcode" canvas-id="qrcode"
|
||||||
|
:value="'http://192.168.5.118:8011/h5/' + `pages-login/pages/sign-up/index?invitationCode=${userStore.userInfo.invitationCode}`"
|
||||||
|
:size="124"
|
||||||
|
sizeUnit="rpx"
|
||||||
|
:options="{}"></uqrcode> -->
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
@@ -499,8 +499,8 @@ margin: 0 30rpx;
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
width: 44rpx;
|
width: 38rpx;
|
||||||
height: 44rpx;
|
height: 38rpx;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+439
-88
@@ -3,10 +3,11 @@ import {
|
|||||||
appMerchantOrderOrderListPost,
|
appMerchantOrderOrderListPost,
|
||||||
type MerchantOrderVo
|
type MerchantOrderVo
|
||||||
} from "@/service";
|
} from "@/service";
|
||||||
import {callPhone, formatTimestampWithWeekday} from "@/utils/utils";
|
import {callPhone} from "@/utils/utils";
|
||||||
import {OrderCancelStatus, OrderStatus} from "@/constant/enums";
|
import {OrderCancelStatus, OrderStatus} from "@/constant/enums";
|
||||||
import {useUserStore} from "@/store";
|
import {useUserStore} from "@/store";
|
||||||
import HomeSkeleton from "@/pages/home/components/tabbar-home/components/home-skeleton.vue";
|
import {dayjs} from "@/plugin";
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -31,7 +32,7 @@ const {paging, dataList, loading, queryList, firstLoaded} = usePage<MerchantOrde
|
|||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
userPort: 1,
|
userPort: 1,
|
||||||
orderStatusList: props.status ? [Number(props.status + 1)] : [],
|
orderStatusList: props.status !== null && props.status !== '' ? [Number(props.status)] : [],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -41,7 +42,118 @@ const {paging, dataList, loading, queryList, firstLoaded} = usePage<MerchantOrde
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function handleClick(item: any) {
|
function fillI18nParams(template: string, params: Record<string, string | number>) {
|
||||||
|
let text = template
|
||||||
|
Object.keys(params).forEach((key) => {
|
||||||
|
const value = String(params[key] ?? '')
|
||||||
|
text = text.replace(new RegExp(`\\{${key}\\}`, 'g'), value)
|
||||||
|
text = text.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value)
|
||||||
|
})
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeTimestamp(input: unknown): number | null {
|
||||||
|
if (input == null || input === '') return null
|
||||||
|
|
||||||
|
let value: number
|
||||||
|
if (typeof input === 'string') {
|
||||||
|
const trimmed = input.trim()
|
||||||
|
if (!trimmed) return null
|
||||||
|
value = Number(trimmed)
|
||||||
|
} else {
|
||||||
|
value = Number(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isFinite(value) || value <= 0) return null
|
||||||
|
|
||||||
|
// 兼容后端可能返回秒级时间戳
|
||||||
|
if (value < 1e12) {
|
||||||
|
value = value * 1000
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatOrderCardTime(item: MerchantOrderVo) {
|
||||||
|
// 优先使用下单时间 createTime;没有则回退到预约结束时间
|
||||||
|
const ts = normalizeTimestamp((item as any)?.createTime ?? item?.endScheduledTime)
|
||||||
|
if (!ts) return '--'
|
||||||
|
return dayjs(ts).format('YYYY/MM/DD HH:mm')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatOrderPrice(item: MerchantOrderVo) {
|
||||||
|
const raw = item?.paidAmount ?? item?.actualPrice ?? 0
|
||||||
|
if (raw == null || raw === '') return '0.00'
|
||||||
|
if (typeof raw === 'number') return raw.toFixed(2)
|
||||||
|
const str = String(raw).trim()
|
||||||
|
if (!str) return '0.00'
|
||||||
|
if (!str.includes('.')) return `${str}.00`
|
||||||
|
const [intPart, decimalPart = ''] = str.split('.')
|
||||||
|
return `${intPart}.${(decimalPart + '00').slice(0, 2)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrderDishes(item: MerchantOrderVo): Array<Record<string, any>> {
|
||||||
|
const list = item.merchantOrderDishVoList as unknown as Array<Record<string, any>> | null | undefined
|
||||||
|
if (!list || !Array.isArray(list)) return []
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
function dishCover(dish: Record<string, any>) {
|
||||||
|
const img = dish?.merchantDishVo?.dishImage
|
||||||
|
if (!img || typeof img !== 'string') return ''
|
||||||
|
return img.split(',')[0] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function dishTitle(dish: Record<string, any>) {
|
||||||
|
return dish?.merchantDishVo?.dishName ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTotalDishCount(item: MerchantOrderVo) {
|
||||||
|
return getOrderDishes(item).reduce((s, d) => s + (d.count || 0), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTotalDishCountText(item: MerchantOrderVo) {
|
||||||
|
return fillI18nParams(t('pages.order.totalItemCount'), {
|
||||||
|
count: getTotalDishCount(item),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSameCode(value: unknown, code: unknown) {
|
||||||
|
return String(value ?? '') === String(code ?? '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRefundApplied(item: MerchantOrderVo) {
|
||||||
|
return isSameCode(item.refundStatus, OrderCancelStatus.APPLIED)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRefundApproved(item: MerchantOrderVo) {
|
||||||
|
return isSameCode(item.refundStatus, OrderCancelStatus.APPROVED)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRefundRejected(item: MerchantOrderVo) {
|
||||||
|
return isSameCode(item.refundStatus, OrderCancelStatus.REJECTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOrderStatusText(item: MerchantOrderVo) {
|
||||||
|
if (isRefundApplied(item)) return t('pages-store.order.orderStatus.refund')
|
||||||
|
if (isRefundApproved(item)) return t('pages-store.order.orderStatus.agreeRefund')
|
||||||
|
if (isRefundRejected(item)) return t('pages-store.order.orderStatus.rejectRefund')
|
||||||
|
if (isSameCode(item.orderStatus, OrderStatus.CANCELLED)) return t('pages-store.order.orderStatus.cancelled')
|
||||||
|
if (isSameCode(item.orderStatus, OrderStatus.PENDING_PAYMENT)) return t('pages-store.order.orderStatus.pendingPayment')
|
||||||
|
if (isSameCode(item.orderStatus, OrderStatus.HAS_PENDING_PAYMENT)) return t('pages-store.order.orderStatus.hasPendingPayment')
|
||||||
|
if (isSameCode(item.orderStatus, OrderStatus.MERCHANT_ACCEPTED)) return t('pages-store.order.orderStatus.received')
|
||||||
|
if (isSameCode(item.receiveMethod, 1) && isSameCode(item.orderStatus, OrderStatus.DELIVERING)) return t('pages-store.order.orderStatus.delivering')
|
||||||
|
if (isSameCode(item.orderStatus, OrderStatus.COMPLETED)) return t('pages-store.order.orderStatus.delivered')
|
||||||
|
if (isSameCode(item.orderStatus, OrderStatus.MERCHANT_REJECTED)) return t('pages-store.store.orderStatus.rejected')
|
||||||
|
return '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(item: MerchantOrderVo) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages-store/pages/order/index?id=' + item.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelClick(item: MerchantOrderVo) {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages-store/pages/order/index?id=' + item.id
|
url: '/pages-store/pages/order/index?id=' + item.id
|
||||||
})
|
})
|
||||||
@@ -66,121 +178,132 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="h-full">
|
<view class="list-root h-full">
|
||||||
<z-paging ref="paging" v-model="dataList" @query="queryList" :fixed="false" :auto="false">
|
<z-paging ref="paging" v-model="dataList" @query="queryList" :fixed="false" :auto="false">
|
||||||
<view class="p-30rpx">
|
<view class="page-pad">
|
||||||
<view
|
<view
|
||||||
v-show="loading"
|
v-show="loading"
|
||||||
class="animate-in fade-in animate-ease-out animate-duration-300"
|
class="animate-in fade-in animate-ease-out animate-duration-300"
|
||||||
>
|
>
|
||||||
<template v-for="item in 3">
|
<view v-for="sk in 3" :key="'sk-' + sk" class="order-card order-card--skeleton mb-24rpx">
|
||||||
<view class="w-full px-50rpx pt-43rpx pb-36rpx bg-white rounded-36rpx mb-30rpx last:mb-0">
|
<view class="flex-center-sb mb-24rpx">
|
||||||
<!-- 头部时间和状态骨架屏 -->
|
<view class="w-280rpx h-32rpx skeleton-item rounded-8rpx"></view>
|
||||||
<view class="flex-center-sb mb-28rpx">
|
<view class="w-120rpx h-32rpx skeleton-item rounded-8rpx"></view>
|
||||||
<view class="w-280rpx h-34rpx skeleton-item rounded-8rpx"></view>
|
|
||||||
<view class="w-120rpx h-34rpx skeleton-item rounded-8rpx"></view>
|
|
||||||
</view>
|
</view>
|
||||||
|
<view class="flex items-center mb-24rpx">
|
||||||
<!-- 商家信息骨架屏 -->
|
|
||||||
<view class="flex items-center my-28rpx">
|
|
||||||
<view class="w-32rpx h-32rpx skeleton-item rounded-6rpx mr-10rpx"></view>
|
<view class="w-32rpx h-32rpx skeleton-item rounded-6rpx mr-10rpx"></view>
|
||||||
<view class="w-200rpx h-30rpx skeleton-item rounded-6rpx"></view>
|
<view class="w-240rpx h-28rpx skeleton-item rounded-6rpx"></view>
|
||||||
<view class="w-60rpx h-32rpx skeleton-item rounded-6rpx ml-20rpx"></view>
|
<view class="w-72rpx h-28rpx skeleton-item rounded-20rpx ml-16rpx"></view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="flex gap-16rpx mb-24rpx">
|
||||||
<!-- 轮播图骨架屏 -->
|
<view v-for="i in 3" :key="i" class="sk-thumb">
|
||||||
<view class="mb-30rpx">
|
<view class="sk-thumb-img skeleton-item"></view>
|
||||||
<view class="w-100% h-508rpx skeleton-item rounded-36rpx mb-10rpx"></view>
|
<view class="w-100rpx h-22rpx skeleton-item rounded-4rpx mt-10rpx"></view>
|
||||||
<view class="w-180rpx h-30rpx skeleton-item rounded-6rpx"></view>
|
|
||||||
</view>
|
</view>
|
||||||
|
<view class="flex-1"></view>
|
||||||
<!-- 底部按钮区域骨架屏 -->
|
<view class="w-100rpx flex flex-col items-end gap-12rpx pt-8rpx">
|
||||||
<view class="flex-center-sb">
|
<view class="w-88rpx h-30rpx skeleton-item rounded-4rpx"></view>
|
||||||
<view class="w-56rpx h-56rpx skeleton-item rounded-28rpx"></view>
|
<view class="w-72rpx h-22rpx skeleton-item rounded-4rpx"></view>
|
||||||
<view class="flex items-center gap-20rpx">
|
|
||||||
<view class="w-170rpx h-64rpx skeleton-item rounded-64rpx"></view>
|
|
||||||
<view class="w-170rpx h-64rpx skeleton-item rounded-64rpx"></view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="flex justify-end">
|
||||||
|
<view class="sk-pill skeleton-item"></view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
|
||||||
</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"
|
||||||
v-if="!loading"
|
v-if="!loading"
|
||||||
>
|
>
|
||||||
<template v-for="(item,index) in dataList">
|
<view
|
||||||
<view @click="handleClick(item)" :class="[index === 0 ? '' : 'mt-30rpx']" class="w-full px-50rpx pt-43rpx pb-36rpx bg-white rounded-36rpx">
|
v-for="(item,index) in dataList"
|
||||||
<view class="flex-center-sb text-34rpx lh-34rpx font-bold">
|
:key="item.id"
|
||||||
<text class="text-#333 tracking-[.04em]">{{ formatTimestampWithWeekday(item.endScheduledTime) }}</text>
|
:class="[index === 0 ? '' : 'mt-24rpx']"
|
||||||
<text class="text-#00A76D tracking-[.04em] shrink-0">
|
class="order-card"
|
||||||
<template v-if="
|
@click="handleClick(item)"
|
||||||
+item.refundStatus === OrderCancelStatus.APPLIED ||
|
>
|
||||||
+item.refundStatus === OrderCancelStatus.APPROVED ||
|
<view class="flex-center-sb items-start gap-16rpx">
|
||||||
+item.refundStatus === OrderCancelStatus.REJECTED
|
<text class="order-time">{{ formatOrderCardTime(item) }}</text>
|
||||||
">
|
<text class="order-status shrink-0" :class="{ 'text-#FF6106': isSameCode(item.orderStatus, OrderStatus.MERCHANT_REJECTED) }">
|
||||||
<template v-if="+item.refundStatus === OrderCancelStatus.APPLIED">
|
{{ getOrderStatusText(item) }}
|
||||||
{{ t('pages-store.order.orderStatus.refund') }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="+item.refundStatus === OrderCancelStatus.APPROVED">
|
|
||||||
{{ t('pages-store.order.orderStatus.agreeRefund') }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="+item.refundStatus === OrderCancelStatus.REJECTED">
|
|
||||||
{{ t('pages-store.order.orderStatus.rejectRefund') }}
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<template v-if="item.orderStatus === OrderStatus.CANCELLED">{{ t('pages-store.order.orderStatus.cancelled') }}</template>
|
|
||||||
<template v-if="item.orderStatus === OrderStatus.PENDING_PAYMENT">{{ t('pages-store.order.orderStatus.pendingPayment') }}</template>
|
|
||||||
<template v-if="item.orderStatus === OrderStatus.HAS_PENDING_PAYMENT">{{ t('pages-store.order.orderStatus.hasPendingPayment') }}</template>
|
|
||||||
<!--配送订单-->
|
|
||||||
<template v-if="item.orderStatus === OrderStatus.MERCHANT_ACCEPTED">{{ t('pages-store.order.orderStatus.received') }}</template>
|
|
||||||
<template v-if="+item.receiveMethod === 1 && item.orderStatus === OrderStatus.DELIVERING">{{ t('pages-store.order.orderStatus.delivering') }}</template>
|
|
||||||
<template v-if="item.orderStatus === OrderStatus.COMPLETED">{{ t('pages-store.order.orderStatus.delivered') }}</template>
|
|
||||||
|
|
||||||
<!--商家拒绝接单-->
|
|
||||||
<template v-if="item.orderStatus === OrderStatus.MERCHANT_REJECTED">
|
|
||||||
<text class="text-#FF6106">{{ t('pages-store.store.orderStatus.rejected') }}</text>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="text-30rpx lh-30rpx text-#333 font-500 flex items-center my-28rpx ">
|
|
||||||
<image src="@img/chef/126.png" class="w-32rpx h-32rpx mr-10rpx"></image>
|
<view class="store-row">
|
||||||
{{ item.merchantVo.merchantName }}
|
<image src="@img/chef/126.png" class="store-icon" mode="aspectFit"></image>
|
||||||
<!--收货方式(1-派送 2-自取)-->
|
<text class="store-name line-clamp-1">{{ item.merchantVo?.merchantName }}</text>
|
||||||
<view v-if="+item.receiveMethod === 1" class="bg-#FF6106 rounded-6rpx h-32rpx text-#fff text-22rpx center px-10rpx ml-20rpx">
|
<view v-if="isSameCode(item.receiveMethod, 1)" class="recv-tag recv-tag--del">
|
||||||
{{ t('pages.order.DEL') }}
|
{{ t('pages.order.DEL') }}
|
||||||
</view>
|
</view>
|
||||||
<view v-if="+item.receiveMethod === 2" class="bg-#00A76D rounded-6rpx h-32rpx text-#fff text-22rpx center px-10rpx ml-20rpx">
|
<view v-if="isSameCode(item.receiveMethod, 2)" class="recv-tag recv-tag--pu">
|
||||||
{{ t('pages.order.PU') }}
|
{{ t('pages.order.PU') }}
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<swiper class="h-568rpx mb-30rpx" :autoplay="true">
|
|
||||||
<swiper-item v-for="img in item.merchantOrderDishVoList">
|
<view class="goods-row">
|
||||||
|
<scroll-view scroll-x class="goods-scroll" :show-scrollbar="false" :enable-flex="true">
|
||||||
|
<view class="goods-track">
|
||||||
|
<view
|
||||||
|
v-for="(dish, di) in getOrderDishes(item)"
|
||||||
|
:key="dish.id ?? di"
|
||||||
|
class="goods-cell"
|
||||||
|
>
|
||||||
|
<view class="goods-img-wrap">
|
||||||
<image
|
<image
|
||||||
:src="img.merchantDishVo.dishImage?.split(',')[0]"
|
:src="dishCover(dish)"
|
||||||
mode="aspectFill"
|
mode="aspectFill"
|
||||||
class="w-100% h-508rpx rounded-36rpx bg-common"
|
class="goods-img"
|
||||||
></image>
|
/>
|
||||||
<view class="mt-10rpx text-30rpx font-500 line-clamp-1">{{ img.merchantDishVo.dishName }}</view>
|
<view
|
||||||
</swiper-item>
|
v-if="dish.count > 1"
|
||||||
</swiper>
|
class="goods-qty"
|
||||||
<view class="flex-center-sb">
|
>x{{ dish.count }}</view>
|
||||||
<view>
|
|
||||||
<image v-if="+item.receiveMethod === 2" src="@img/chef/127.png" class="w-56rpx h-56rpx"></image>
|
|
||||||
<image @click.stop="callPhone(item.merchantVo.phone)" v-if="item.orderStatus >= OrderStatus.MERCHANT_ACCEPTED && +item.receiveMethod === 1" src="@img/chef/128.png" class="w-56rpx h-56rpx"></image>
|
|
||||||
</view>
|
</view>
|
||||||
<view class="flex items-center gap-20rpx font-500">
|
<text class="goods-caption">{{ dishTitle(dish) }}</text>
|
||||||
<template v-if="item.refundStatus !== OrderCancelStatus.APPLIED && item.refundStatus !== OrderCancelStatus.APPROVED">
|
</view>
|
||||||
<view v-if="item.orderStatus !== OrderStatus.CANCELLED && +item.orderStatus !== OrderStatus.COMPLETED && item.orderStatus !== OrderStatus.MERCHANT_REJECTED" class="w-170rpx h-64rpx center rounded-64rpx border-solid border-#666666 border-1rpx">{{ t('common.cancel') }}</view>
|
</view>
|
||||||
<view v-if="+item.receiveMethod === 2 && +item.orderStatus === OrderStatus.MERCHANT_ACCEPTED" class="w-170rpx h-64rpx center rounded-64rpx bg-#14181B text-28rpx text-#fff">{{ t('pages-store.order.writeOff') }}</view>
|
</scroll-view>
|
||||||
<view v-if="+item.orderStatus === OrderStatus.COMPLETED && item?.dishReviewVoList.length === 0" class="w-170rpx h-64rpx center rounded-64rpx bg-#14181B text-28rpx text-#fff">{{ t('common.evaluate') }}</view>
|
<view class="price-block shrink-0">
|
||||||
|
<text class="price-num">${{ formatOrderPrice(item) }}</text>
|
||||||
|
<text class="price-meta">{{ getTotalDishCountText(item) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="action-row" @click.stop>
|
||||||
|
<view class="action-left">
|
||||||
|
<image
|
||||||
|
v-if="isSameCode(item.receiveMethod, 2) && isSameCode(item.orderStatus, OrderStatus.MERCHANT_ACCEPTED)"
|
||||||
|
src="@img/chef/127.png"
|
||||||
|
class="action-icon"
|
||||||
|
/>
|
||||||
|
<image
|
||||||
|
v-if="!isSameCode(item.orderStatus, OrderStatus.PENDING_PAYMENT) && !isSameCode(item.orderStatus, OrderStatus.CANCELLED) && isSameCode(item.receiveMethod, 1)"
|
||||||
|
src="@img/chef/128.png"
|
||||||
|
class="action-icon"
|
||||||
|
@click="callPhone(item.merchantVo?.phone)"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="action-btns">
|
||||||
|
<template v-if="!isRefundApplied(item) && !isRefundApproved(item)">
|
||||||
|
<view
|
||||||
|
v-if="!isSameCode(item.orderStatus, OrderStatus.CANCELLED) && !isSameCode(item.orderStatus, OrderStatus.COMPLETED) && !isSameCode(item.orderStatus, OrderStatus.MERCHANT_REJECTED)"
|
||||||
|
class="btn-outline"
|
||||||
|
@click="handleCancelClick(item)"
|
||||||
|
>{{ t('pages.order.cancelOrder') }}</view>
|
||||||
|
<view
|
||||||
|
v-if="isSameCode(item.receiveMethod, 2) && isSameCode(item.orderStatus, OrderStatus.MERCHANT_ACCEPTED)"
|
||||||
|
class="btn-primary"
|
||||||
|
@click="handleClick(item)"
|
||||||
|
>{{ t('pages-store.order.writeOff') }}</view>
|
||||||
|
<view
|
||||||
|
v-if="isSameCode(item.orderStatus, OrderStatus.COMPLETED) && item?.dishReviewVoList?.length === 0"
|
||||||
|
class="btn-primary"
|
||||||
|
@click="handleClick(item)"
|
||||||
|
>{{ t('common.evaluate') }}</view>
|
||||||
</template>
|
</template>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</z-paging>
|
</z-paging>
|
||||||
@@ -188,5 +311,233 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.list-root {
|
||||||
|
background: #F5F5F5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-pad {
|
||||||
|
padding: 24rpx 24rpx 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
padding: 28rpx 24rpx 24rpx;
|
||||||
|
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-card--skeleton {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-time {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
color: #14181B;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
color: #00A76D;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
margin-right: 10rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-name {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
color: #14181B;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recv-tag {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 16rpx;
|
||||||
|
padding: 4rpx 14rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recv-tag--del {
|
||||||
|
background: #FF6106;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recv-tag--pu {
|
||||||
|
background: #00A76D;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-scroll {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-track {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16rpx;
|
||||||
|
padding-right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-cell {
|
||||||
|
width: 128rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-img-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 128rpx;
|
||||||
|
height: 128rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #F2F2F2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-qty {
|
||||||
|
position: absolute;
|
||||||
|
right: 6rpx;
|
||||||
|
bottom: 6rpx;
|
||||||
|
min-width: 36rpx;
|
||||||
|
padding: 4rpx 10rpx;
|
||||||
|
background: rgba(20, 24, 27, 0.85);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20rpx;
|
||||||
|
line-height: 24rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-caption {
|
||||||
|
display: block;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
min-height: 56rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-block {
|
||||||
|
text-align: right;
|
||||||
|
padding-top: 8rpx;
|
||||||
|
max-width: 200rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-num {
|
||||||
|
display: block;
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #14181B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-meta {
|
||||||
|
display: block;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-icon {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btns {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 16rpx;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline {
|
||||||
|
padding: 16rpx 32rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
border: 2rpx solid #14181B;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #14181B;
|
||||||
|
background: #fff;
|
||||||
|
line-height: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
padding: 16rpx 32rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: #14181B;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #fff;
|
||||||
|
line-height: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-thumb {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 128rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-thumb-img {
|
||||||
|
width: 128rpx;
|
||||||
|
height: 128rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sk-pill {
|
||||||
|
width: 176rpx;
|
||||||
|
height: 60rpx;
|
||||||
|
border-radius: 9999rpx;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -67,32 +67,30 @@ defineExpose({
|
|||||||
<z-paging-swiper>
|
<z-paging-swiper>
|
||||||
<template #top>
|
<template #top>
|
||||||
<status-bar/>
|
<status-bar/>
|
||||||
<view class="pl-30rpx pt-18rpx text-56rpx lh-56rpx text-#333 font-bold tracking-[.04em]">{{ t('pages.order.title') }}</view>
|
<!-- <view class="pl-30rpx pt-18rpx text-56rpx lh-56rpx text-#333 font-bold tracking-[.04em]">{{ t('pages.order.title') }}</view> -->
|
||||||
<view class="tab pl-20rpx mt-24rpx">
|
<view class="tab mt-16rpx bg-white">
|
||||||
<wd-tabs
|
<wd-tabs
|
||||||
slidable="always"
|
slidable="always"
|
||||||
key="tab"
|
key="tab"
|
||||||
color="#333"
|
color="#14181B"
|
||||||
inactiveColor="#999"
|
inactiveColor="#B0B0B0"
|
||||||
:line-height="3"
|
:line-height="6"
|
||||||
:line-width="30"
|
:line-width="48"
|
||||||
v-model="currentIndex"
|
v-model="currentIndex"
|
||||||
@click="handleClickTab"
|
@click="handleClickTab"
|
||||||
>
|
>
|
||||||
<template v-for="(item, index) in tabList" :key="index">
|
<wd-tab v-for="(item, index) in tabList" :key="index" :title="item.name"></wd-tab>
|
||||||
<wd-tab :title="item.name"></wd-tab>
|
|
||||||
</template>
|
|
||||||
</wd-tabs>
|
</wd-tabs>
|
||||||
<view class="px-20rpx mt--2rpx">
|
<!-- <view class="px-20rpx mt--2rpx">
|
||||||
<view class="border-b-solid border-b-4rpx border-b-#999"></view>
|
<view class="border-b-solid border-b-4rpx border-b-#999"></view>
|
||||||
</view>
|
</view> -->
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
<swiper class="h-full"
|
<swiper class="h-full order-swiper"
|
||||||
:current="currentIndex"
|
:current="currentIndex"
|
||||||
@change="handleSwiperChange">
|
@change="handleSwiperChange">
|
||||||
<swiper-item class="swiper-item" v-for="(item, index) in tabList" :key="index">
|
<swiper-item class="swiper-item" v-for="(item, index) in tabList" :key="index">
|
||||||
<order-swiper-list ref="orderSwiperListRef" :currentIndex="currentIndex" :status="index"></order-swiper-list>
|
<order-swiper-list ref="orderSwiperListRef" :currentIndex="currentIndex" :status="item.value"></order-swiper-list>
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
<template #bottom>
|
<template #bottom>
|
||||||
@@ -106,22 +104,44 @@ defineExpose({
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.tab {
|
.tab {
|
||||||
:deep(.wd-tabs) {
|
:deep(.wd-tabs) {
|
||||||
|
background: #fff !important;
|
||||||
.wd-tabs__line {
|
.wd-tabs__line {
|
||||||
bottom: 0 !important;
|
bottom: 0 !important;
|
||||||
z-index: 99 !important;
|
z-index: 99 !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 2rpx !important;
|
||||||
|
background: #14181B !important;
|
||||||
}
|
}
|
||||||
.wd-tabs__nav-item {
|
.wd-tabs__nav-item {
|
||||||
padding: 0 22rpx !important;
|
padding: 0 !important;
|
||||||
|
flex: 1 1 0 !important;
|
||||||
|
display: flex !important;
|
||||||
|
justify-content: center !important;
|
||||||
}
|
}
|
||||||
.wd-tabs__nav-item-text {
|
.wd-tabs__nav-item-text {
|
||||||
font-size: 32rpx !important;
|
font-size: 26rpx !important;
|
||||||
text-overflow: unset !important;
|
text-overflow: unset !important;
|
||||||
font-family: 'UberMove', sans-serif !important;
|
font-family: 'UberMove', sans-serif !important;
|
||||||
|
// font-weight: 500 !important;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.wd-tabs__nav-item.is-active .wd-tabs__nav-item-text {
|
||||||
|
font-weight: 700 !important;
|
||||||
|
color: #14181B !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.wd-tabs__nav-container) {
|
||||||
|
display: flex !important;
|
||||||
|
width: 100% !important;
|
||||||
|
justify-content: space-between !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.bg {
|
.bg {
|
||||||
background: linear-gradient(180deg, #FFFFFF 0%, #FFFFFF 51%, rgba(255, 255, 255, 0) 100%);
|
background: linear-gradient(180deg, #FFFFFF 0%, #FFFFFF 40%, rgba(245, 245, 245, 0) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-swiper {
|
||||||
|
background-color: #F5F5F5;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,230 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import Config from '@/config/index'
|
||||||
|
import { formatSalesCount } from '@/utils/utils'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
type DishItem = {
|
||||||
|
id: number | string
|
||||||
|
merchantId?: number | string
|
||||||
|
dishName?: string
|
||||||
|
dishImage?: string
|
||||||
|
discountPrice?: number | string
|
||||||
|
originalPrice?: number | string
|
||||||
|
memberPrice?: number | string
|
||||||
|
salesCount?: number | string
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
item: DishItem
|
||||||
|
}>()
|
||||||
|
|
||||||
|
function goDetail() {
|
||||||
|
const { item } = props
|
||||||
|
if (item.merchantId) {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages-store/pages/store/dishes?id=${item.id}&storeId=${item.merchantId}`,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages-store/pages/store/index?id=${item.id}`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cover = computed(() => {
|
||||||
|
const raw = props.item?.dishImage
|
||||||
|
if (typeof raw === 'string' && raw) return raw.split(',')[0]
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view class="search-dish-item" @click="goDetail">
|
||||||
|
<view class="search-dish-item__img-wrap">
|
||||||
|
<view v-if="item.isNew == 1" class="dish-new-ribbon">
|
||||||
|
<text class="dish-new-ribbon__text">NEW</text>
|
||||||
|
</view>
|
||||||
|
<image :src="cover" mode="aspectFill" class="search-dish-item__img" />
|
||||||
|
</view>
|
||||||
|
<view class="search-dish-item__main">
|
||||||
|
<text class="search-dish-item__title line-clamp-2">{{ item.dishName || '' }}</text>
|
||||||
|
<view class="search-dish-item__price-row">
|
||||||
|
<text class="search-dish-item__price-current">
|
||||||
|
${{ item.discountPrice ?? item.originalPrice }}
|
||||||
|
</text>
|
||||||
|
<text
|
||||||
|
v-if="
|
||||||
|
Number(item.originalPrice) > 0 &&
|
||||||
|
Number(item.originalPrice) > Number(item.discountPrice ?? item.originalPrice)
|
||||||
|
"
|
||||||
|
class="search-dish-item__price-old"
|
||||||
|
>${{ item.originalPrice }}</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-if="Number(item.memberPrice) > 0"
|
||||||
|
class="search-dish-item__member"
|
||||||
|
>
|
||||||
|
<text class="search-dish-item__member-diamond">◆</text>
|
||||||
|
<text class="search-dish-item__member-text">
|
||||||
|
{{ Config.appName }} {{ t('pages.search.member-price-line') }}: ${{ item.memberPrice }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="search-dish-item__sales-wrap">
|
||||||
|
<text class="search-dish-item__sales-tag">
|
||||||
|
{{ t('pages.search.weekly-sales') }}:{{ formatSalesCount(item.salesCount) }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="search-dish-item__add center" @click.stop="goDetail">
|
||||||
|
<image src="@img/chef/1285.png" class="search-dish-item__add-icon" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.search-dish-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 28rpx 24rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-bottom: 1rpx solid #ebebeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__img {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
background: #f2f2f2;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
margin-left: 24rpx;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__price-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__price-current {
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 36rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #e02e24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__price-old {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__member {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
padding: 6rpx 0;
|
||||||
|
gap: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__member-diamond {
|
||||||
|
font-size: 20rpx;
|
||||||
|
line-height: 1;
|
||||||
|
color: #b8860b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__member-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #8b6914;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__sales-wrap {
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__sales-tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 8rpx 18rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
background: #f5f5f5;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 28rpx;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__add {
|
||||||
|
align-self: flex-end;
|
||||||
|
width: 64rpx;
|
||||||
|
height: 64rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f2f2f2;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__add-icon {
|
||||||
|
width: 30rpx;
|
||||||
|
height: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-dish-item__img-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
<template>
|
|
||||||
<view class="pt-52rpx pl-30rpx bg-#fff" v-if="searchStore.historyList?.length">
|
|
||||||
<view class="text-40rpx lh-40rpx text-#333 font-bold mb-24rpx tracking-[.04em]">{{ t('pages.search.recently') }}</view>
|
|
||||||
<view class="">
|
|
||||||
<template v-for="(item, index) in searchStore.historyList" :key="index">
|
|
||||||
<wd-swipe-action>
|
|
||||||
<view @click="handleSearch(item,index)" class="w-full h-110rpx flex items-center">
|
|
||||||
<image src="@img/chef/130.png" class="w-28rpx h-28rpx mr-44rpx"></image>
|
|
||||||
<view class="flex-1 border-b-solid border-b-1rpx border-b-#DFDFDF h-full flex items-center text-30rpx text-primary font-500 tracking-[.06em] line-clamp-1">{{ item.text }}</view>
|
|
||||||
</view>
|
|
||||||
<template #right>
|
|
||||||
<view @click="handleRemove(index)" class="w-110rpx h-110rpx bg-#FF4848 center">
|
|
||||||
<image src="@img/chef/129.png" class="w-44rpx h-50rpx"></image>
|
|
||||||
</view>
|
|
||||||
</template>
|
|
||||||
</wd-swipe-action>
|
|
||||||
</template>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
<wd-message-box/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {useSearchStore} from "@/store";
|
|
||||||
import {useMessage,} from "wot-design-uni";
|
|
||||||
|
|
||||||
const {t} = useI18n()
|
|
||||||
|
|
||||||
const message = useMessage()
|
|
||||||
const searchStore = useSearchStore()
|
|
||||||
const isRemove = ref(false)
|
|
||||||
|
|
||||||
|
|
||||||
function handleSearch(item: { text: string }, index: number) {
|
|
||||||
if (isRemove.value) {
|
|
||||||
handleRemove(index)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.log(item)
|
|
||||||
nextTick(() => {
|
|
||||||
searchStore.setHistoryList(item.text)
|
|
||||||
uni.navigateTo({
|
|
||||||
url: `/pages/search/result?keyword=${item.text}`,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleRemove(index: number) {
|
|
||||||
searchStore.historyList.splice(index, 1)
|
|
||||||
if (searchStore.historyList.length === 0) {
|
|
||||||
isRemove.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
@@ -1,49 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="search-skeleton pt-88rpx">
|
<view class="search-skeleton">
|
||||||
<!-- 头部区域 -->
|
<view class="search-skeleton__header px-24rpx pt-8rpx pb-12rpx flex items-center justify-between gap-16rpx">
|
||||||
<view class="header-section">
|
<view class="search-skeleton__circle skeleton-item"></view>
|
||||||
<view class="back-button-skeleton skeleton-item"></view>
|
<view class="search-skeleton__pill flex-1 skeleton-item"></view>
|
||||||
<view class="search-bar-skeleton skeleton-item"></view>
|
<view class="search-skeleton__circle skeleton-item"></view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="search-skeleton__section">
|
||||||
<!-- 最近搜索区域 -->
|
<view class="search-skeleton__title-row">
|
||||||
<view class="recent-section">
|
<view class="search-skeleton__title skeleton-item"></view>
|
||||||
<view class="section-title-skeleton skeleton-item"></view>
|
<view class="search-skeleton__clear skeleton-item"></view>
|
||||||
<view class="recent-list">
|
</view>
|
||||||
<view
|
<view class="search-skeleton__tags">
|
||||||
v-for="i in 3"
|
<view v-for="i in 5" :key="'r-' + i" class="search-skeleton__tag skeleton-item"></view>
|
||||||
:key="i"
|
|
||||||
class="recent-item-skeleton skeleton-item"
|
|
||||||
></view>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="search-skeleton__section">
|
||||||
<!-- 热门分类区域 -->
|
<view class="search-skeleton__title skeleton-item search-skeleton__title--wide"></view>
|
||||||
<view class="popular-section">
|
<view class="search-skeleton__tags">
|
||||||
<view class="section-title-skeleton skeleton-item"></view>
|
<view v-for="i in 8" :key="'h-' + i" class="search-skeleton__tag skeleton-item"></view>
|
||||||
<template v-for="item in 6">
|
|
||||||
<view class="flex items-center h-144rpx">
|
|
||||||
<view class="w-64rpx h-64rpx mr-28rpx rounded-50% skeleton-item"></view>
|
|
||||||
<view class="w-420rpx h-60rpx rounded-10rpx skeleton-item"></view>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// 搜索页面骨架屏组件
|
|
||||||
</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, #ececec 25%, #e0e0e0 50%, #ececec 75%);
|
||||||
background-size: 200% 100%;
|
background-size: 200% 100%;
|
||||||
animation: shimmer 1.5s infinite;
|
animation: shimmer 1.5s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 闪烁动画
|
|
||||||
@keyframes shimmer {
|
@keyframes shimmer {
|
||||||
0% {
|
0% {
|
||||||
background-position: -200% 0;
|
background-position: -200% 0;
|
||||||
@@ -54,75 +43,60 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search-skeleton {
|
.search-skeleton {
|
||||||
background-color: #fff;
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
background-color: #f7f8fa;
|
||||||
|
padding-top: env(safe-area-inset-top, 0px);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 状态栏
|
.search-skeleton__circle {
|
||||||
.status-bar {
|
width: 72rpx;
|
||||||
height: 88rpx;
|
height: 72rpx;
|
||||||
width: 100%;
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 头部区域
|
.search-skeleton__pill {
|
||||||
.header-section {
|
height: 80rpx;
|
||||||
padding: 0 30rpx 40rpx;
|
border-radius: 999rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-skeleton__section {
|
||||||
|
padding: 28rpx 24rpx 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-skeleton__title-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20rpx;
|
justify-content: space-between;
|
||||||
|
margin-bottom: 28rpx;
|
||||||
.back-button-skeleton {
|
|
||||||
width: 44rpx;
|
|
||||||
height: 44rpx;
|
|
||||||
border-radius: 22rpx;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-bar-skeleton {
|
.search-skeleton__title {
|
||||||
flex: 1;
|
width: 180rpx;
|
||||||
height: 88rpx;
|
|
||||||
border-radius: 44rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最近搜索区域
|
|
||||||
.recent-section {
|
|
||||||
padding: 0 30rpx 40rpx;
|
|
||||||
|
|
||||||
.section-title-skeleton {
|
|
||||||
width: 200rpx;
|
|
||||||
height: 40rpx;
|
height: 40rpx;
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
margin-bottom: 30rpx;
|
}
|
||||||
|
.search-skeleton__title--wide {
|
||||||
|
width: 220rpx;
|
||||||
|
margin-bottom: 28rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recent-list {
|
.search-skeleton__clear {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-skeleton__tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
gap: 20rpx;
|
flex-wrap: wrap;
|
||||||
|
gap: 16rpx 14rpx;
|
||||||
|
}
|
||||||
|
|
||||||
.recent-item-skeleton {
|
.search-skeleton__tag {
|
||||||
|
width: 132rpx;
|
||||||
height: 60rpx;
|
height: 60rpx;
|
||||||
border-radius: 8rpx;
|
border-radius: 999rpx;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 热门分类区域
|
|
||||||
.popular-section {
|
|
||||||
padding: 40rpx 30rpx 0;
|
|
||||||
|
|
||||||
.section-title-skeleton {
|
|
||||||
width: 300rpx;
|
|
||||||
height: 40rpx;
|
|
||||||
border-radius: 8rpx;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 响应式设计
|
|
||||||
@media (max-width: 750rpx) {
|
|
||||||
.category-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface SearchTagItem {
|
||||||
|
key: string | number
|
||||||
|
text: string
|
||||||
|
/** 左侧「热门」火焰标 */
|
||||||
|
hot?: boolean
|
||||||
|
/** 显示右侧删除(搜索记录) */
|
||||||
|
removable?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
title: string
|
||||||
|
tags: SearchTagItem[]
|
||||||
|
/** 标题右侧「清空」 */
|
||||||
|
showClear?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
showClear: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
select: [item: SearchTagItem]
|
||||||
|
remove: [index: number]
|
||||||
|
clear: []
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view v-if="tags.length > 0" class="search-tag-cloud">
|
||||||
|
<view class="search-tag-cloud__head">
|
||||||
|
<text class="search-tag-cloud__title">{{ title }}</text>
|
||||||
|
<text v-if="showClear" class="search-tag-cloud__clear" @click="emit('clear')">{{ t('pages.search.clear-history') }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="search-tag-cloud__wrap">
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in tags"
|
||||||
|
:key="item.key"
|
||||||
|
class="search-tag-cloud__pill"
|
||||||
|
@click="emit('select', item)"
|
||||||
|
>
|
||||||
|
<view v-if="item.hot" class="i-carbon:fire search-tag-cloud__flame shrink-0"></view>
|
||||||
|
<text class="search-tag-cloud__text">{{ item.text }}</text>
|
||||||
|
<view
|
||||||
|
v-if="item.removable"
|
||||||
|
class="search-tag-cloud__remove center shrink-0"
|
||||||
|
@click.stop="emit('remove', index)"
|
||||||
|
>
|
||||||
|
<text class="search-tag-cloud__remove-x">×</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.search-tag-cloud {
|
||||||
|
padding: 36rpx 24rpx 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-tag-cloud__head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-tag-cloud__title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 40rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-tag-cloud__clear {
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-tag-cloud__wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 16rpx 14rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-tag-cloud__pill {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 14rpx 22rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
box-shadow: 0 4rpx 14rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-tag-cloud__flame {
|
||||||
|
margin-right: 8rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #e02020;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-tag-cloud__text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 320rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-tag-cloud__remove {
|
||||||
|
margin-left: 10rpx;
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-tag-cloud__remove-x {
|
||||||
|
font-size: 32rpx;
|
||||||
|
line-height: 32rpx;
|
||||||
|
color: #666;
|
||||||
|
font-weight: 300;
|
||||||
|
margin-top: -4rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import FiltrateTool from "@/components/filtrate-tool/index.vue";
|
import FiltrateTool from "@/components/filtrate-tool/index.vue";
|
||||||
import FoodBox from "@/pages/home/components/tabbar-home/components/food-box/index.vue";
|
import SearchDishResultItem from "@/pages/search/components/search-dish-result-item.vue";
|
||||||
import TabsType from "@/pages/home/components/tabbar-home/components/tabs-type.vue";
|
import TabsType from "@/pages/home/components/tabbar-home/components/tabs-type.vue";
|
||||||
import AnimatedButton from "../animated-button/animated-button.vue";
|
import AnimatedButton from "../animated-button/animated-button.vue";
|
||||||
import Collection from "@/components/collection/index.vue";
|
import Collection from "@/components/collection/index.vue";
|
||||||
@@ -189,14 +189,11 @@ defineExpose({
|
|||||||
>
|
>
|
||||||
<!-- 筛选工具 -->
|
<!-- 筛选工具 -->
|
||||||
<!-- <filtrate-tool class="my-36rpx" @togglePickup="togglePickup" @toggleDiscount="toggleDiscount" @toggleScore="emit('toggleScore')" @togglePrice="emit('togglePrice')" /> -->
|
<!-- <filtrate-tool class="my-36rpx" @togglePickup="togglePickup" @toggleDiscount="toggleDiscount" @toggleScore="emit('toggleScore')" @togglePrice="emit('togglePrice')" /> -->
|
||||||
<view
|
<view class="search-food-toolbar">
|
||||||
class="pl-30rpx pb-36rpx text-36rpx lh-36rpx text-#333 font-500 tracking-[.04em]"
|
<text class="search-food-toolbar__count">{{ foodTotal }} {{ t('pages.search.result.result') }}</text>
|
||||||
>{{ foodTotal }} {{ t('pages.search.result.result') }}</view
|
</view>
|
||||||
>
|
<view class="search-food-list">
|
||||||
<view class="px-30rpx">
|
<search-dish-result-item v-for="item in dataList" :key="String(item.id)" :item="item" />
|
||||||
<template v-for="item in dataList">
|
|
||||||
<food-box :item="item" />
|
|
||||||
</template>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
@@ -282,6 +279,22 @@ defineExpose({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.search-food-toolbar {
|
||||||
|
padding: 20rpx 24rpx 8rpx;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.search-food-toolbar__count {
|
||||||
|
font-size: 26rpx;
|
||||||
|
line-height: 34rpx;
|
||||||
|
color: #999;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.search-food-list {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.search-food-list :deep(.search-dish-item:last-child) {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
// 确保过渡动画平滑
|
// 确保过渡动画平滑
|
||||||
.transition-transform {
|
.transition-transform {
|
||||||
transition: transform 0.6s ease-in-out;
|
transition: transform 0.6s ease-in-out;
|
||||||
|
|||||||
+107
-33
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SearchHistory from './components/search-history/index.vue'
|
|
||||||
import SearchSkeleton from './components/search-skeleton.vue'
|
import SearchSkeleton from './components/search-skeleton.vue'
|
||||||
|
import SearchTagCloud from './components/search-tag-cloud.vue'
|
||||||
import { useSearchStore } from '@/store'
|
import { useSearchStore } from '@/store'
|
||||||
import {appSearchListPost,appRecipeCategoryListGet} from '@/service'
|
import { appRecipeCategoryListGet } from '@/service'
|
||||||
|
import { decodeRouteQueryValue } from '@/utils/utils'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const searchStore = useSearchStore()
|
const searchStore = useSearchStore()
|
||||||
@@ -11,45 +12,94 @@ const keyword = ref('')
|
|||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (!keyword.value) {
|
if (!keyword.value) {
|
||||||
return uni.showToast({title: t('common.prompt.please-enter-keyword-search'), icon: 'none'})
|
return uni.showToast({
|
||||||
|
title: t('common.prompt.please-enter-keyword-search'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
searchStore.setHistoryList(keyword.value)
|
const kw = decodeRouteQueryValue(keyword.value)
|
||||||
|
if (!kw) {
|
||||||
|
return uni.showToast({
|
||||||
|
title: t('common.prompt.please-enter-keyword-search'),
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
searchStore.setHistoryList(kw)
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/search/result?keyword=${keyword.value}`,
|
url: `/pages/search/result?keyword=${encodeURIComponent(kw)}`,
|
||||||
})
|
})
|
||||||
keyword.value = ''
|
keyword.value = ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleHotSearch(item: any) {
|
function goResultWithKeyword(k: string) {
|
||||||
nextTick(() => {
|
const kw = decodeRouteQueryValue(k)
|
||||||
searchStore.setHistoryList(item.categoryName)
|
if (!kw) return
|
||||||
|
searchStore.setHistoryList(kw)
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: `/pages/search/result?keyword=${item.categoryName}`,
|
url: `/pages/search/result?keyword=${encodeURIComponent(kw)}`,
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleHotSearch(item: Record<string, unknown>) {
|
||||||
|
const name = String(item?.categoryName ?? '')
|
||||||
|
goResultWithKeyword(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHistorySelect(tag: { text: string }) {
|
||||||
|
goResultWithKeyword(tag.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHistoryRemove(index: number) {
|
||||||
|
searchStore.historyList.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHistoryClear() {
|
||||||
|
searchStore.clearHistory()
|
||||||
|
}
|
||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
// 查询热门搜索词列表
|
|
||||||
getHotSearchList()
|
getHotSearchList()
|
||||||
})
|
})
|
||||||
|
|
||||||
const hotSearchList = ref([])
|
const hotSearchList = ref<Record<string, unknown>[]>([])
|
||||||
function getHotSearchList() {
|
function getHotSearchList() {
|
||||||
appRecipeCategoryListGet({}).then(res=> {
|
appRecipeCategoryListGet({})
|
||||||
console.log('热门搜索词列表', res)
|
.then((res) => {
|
||||||
hotSearchList.value = res.data
|
hotSearchList.value = (res.data as Record<string, unknown>[]) || []
|
||||||
}).finally(() => {
|
})
|
||||||
|
.finally(() => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const historyTags = computed(() =>
|
||||||
|
searchStore.historyList.map((h, i) => ({
|
||||||
|
key: `${h.time}-${i}`,
|
||||||
|
text: decodeRouteQueryValue(h.text),
|
||||||
|
removable: true,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
const hotTags = computed(() =>
|
||||||
|
hotSearchList.value.map((item, i) => ({
|
||||||
|
key: String(item.id ?? `hot-${i}`),
|
||||||
|
text: String(item.categoryName ?? ''),
|
||||||
|
hot: i < 3,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
function onHotSelect(tag: { key: string | number }) {
|
||||||
|
const idx = hotTags.value.findIndex((x) => x.key === tag.key)
|
||||||
|
const raw = idx >= 0 ? hotSearchList.value[idx] : null
|
||||||
|
if (raw) handleHotSearch(raw)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="">
|
<view class="search-page">
|
||||||
<view
|
<view
|
||||||
class="animate-in fade-in animate-ease-out animate-duration-300"
|
class="animate-in fade-in animate-ease-out animate-duration-300"
|
||||||
v-show="loading"
|
v-show="loading"
|
||||||
@@ -57,23 +107,33 @@ function getHotSearchList() {
|
|||||||
<search-skeleton />
|
<search-skeleton />
|
||||||
</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 search-page__content"
|
||||||
v-show="!loading"
|
v-show="!loading"
|
||||||
>
|
>
|
||||||
<header-search focus class="" v-model="keyword" :placeholder="t('components.search.placeholder')" @search="handleSearch"/>
|
<header-search
|
||||||
<search-history/>
|
v-model="keyword"
|
||||||
<view class="pl-30rpx mt-52rpx">
|
focus
|
||||||
<view class="text-40rpx lh-40rpx text-#333 font-bold mb-24rpx tracking-[.04em]">
|
trailing-cart
|
||||||
{{ t('pages.search.hot-title') }}
|
class="search-page__header"
|
||||||
</view>
|
:placeholder="t('components.search.placeholder')"
|
||||||
<template v-for="item in hotSearchList">
|
@search="handleSearch"
|
||||||
<view @click="handleHotSearch(item)" class="w-full h-144rpx flex items-center">
|
/>
|
||||||
<image :src="item?.categoryImage" class="w-64rpx h-64rpx mr-28rpx rounded-50%" mode="aspectFill"></image>
|
<view class="search-page__body">
|
||||||
<view class="flex-1 border-b-solid border-b-1rpx border-b-#DFDFDF h-full flex items-center text-30rpx text-primary font-500 tracking-[.06em]">
|
<search-tag-cloud
|
||||||
{{ item?.categoryName }}
|
v-if="historyTags.length > 0"
|
||||||
</view>
|
:title="t('pages.search.recently')"
|
||||||
</view>
|
:tags="historyTags"
|
||||||
</template>
|
show-clear
|
||||||
|
@select="onHistorySelect"
|
||||||
|
@remove="onHistoryRemove"
|
||||||
|
@clear="onHistoryClear"
|
||||||
|
/>
|
||||||
|
<search-tag-cloud
|
||||||
|
v-if="hotTags.length > 0"
|
||||||
|
:title="t('pages.search.hot-title')"
|
||||||
|
:tags="hotTags"
|
||||||
|
@select="onHotSelect"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -81,6 +141,20 @@ function getHotSearchList() {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
page {
|
page {
|
||||||
background-color: #fff;
|
background-color: #f7f8fa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.search-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
|
.search-page__content {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f7f8fa;
|
||||||
|
}
|
||||||
|
.search-page__body {
|
||||||
|
padding-bottom: 48rpx;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useSearchStore } from "@/store";
|
import { useSearchStore } from "@/store";
|
||||||
import TabSwitcher from "./components/tab-switcher.vue";
|
import { decodeRouteQueryValue } from "@/utils/utils";
|
||||||
import SwiperList from "./components/swiper-list/swiper-list.vue";
|
import SwiperList from "./components/swiper-list/swiper-list.vue";
|
||||||
import Score from "@/components/filtrate-tool/components/score.vue";
|
import Score from "@/components/filtrate-tool/components/score.vue";
|
||||||
import PriceChoose from "@/components/filtrate-tool/components/price-choose.vue";
|
import PriceChoose from "@/components/filtrate-tool/components/price-choose.vue";
|
||||||
import SortPopup from "@/pages/search/components/sort-popup.vue";
|
import SortPopup from "@/pages/search/components/sort-popup.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const _props = defineProps<{
|
||||||
|
/** 少数构建下可能由路由注入;实际以 onLoad 的 query 为准 */
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@@ -14,7 +15,15 @@ const { t } = useI18n();
|
|||||||
|
|
||||||
const searchStore = useSearchStore();
|
const searchStore = useSearchStore();
|
||||||
|
|
||||||
const keyword = ref(props.keyword || "");
|
const keyword = ref(decodeRouteQueryValue(_props.keyword) || '');
|
||||||
|
|
||||||
|
/** uni-app:?keyword= 只在 onLoad 的 options 里;部分端会对已编码参数再编一层,需循环 decode */
|
||||||
|
onLoad((options?: Record<string, string | undefined>) => {
|
||||||
|
const q = options?.keyword
|
||||||
|
if (q == null || String(q).trim() === '') return
|
||||||
|
const plain = decodeRouteQueryValue(q)
|
||||||
|
if (plain) keyword.value = plain
|
||||||
|
})
|
||||||
|
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -99,12 +108,15 @@ onMounted(()=> {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<view class="">
|
<view class="search-result-page">
|
||||||
<z-paging-swiper>
|
<z-paging-swiper>
|
||||||
<template #top>
|
<template #top>
|
||||||
<header-search
|
<header-search
|
||||||
class="pb-42rpx"
|
|
||||||
v-model="keyword"
|
v-model="keyword"
|
||||||
|
trailing-cart
|
||||||
|
trailing-surface="white"
|
||||||
|
class="search-result-page__header"
|
||||||
|
:placeholder="t('components.search.placeholder')"
|
||||||
@search="handleSearch"
|
@search="handleSearch"
|
||||||
/>
|
/>
|
||||||
<!-- <tab-switcher
|
<!-- <tab-switcher
|
||||||
@@ -147,7 +159,22 @@ onMounted(()=> {
|
|||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
page {
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.search-result-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-page__header {
|
||||||
|
padding-bottom: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.wd-tabs__nav-item) {
|
:deep(.wd-tabs__nav-item) {
|
||||||
font-size: 40rpx;
|
font-size: 40rpx;
|
||||||
color: #666666;
|
color: #666666;
|
||||||
|
|||||||
@@ -467,6 +467,24 @@ export async function appMerchantOrderPayOrderBatchPost({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 提交 Zip 支付凭证 POST /app/merchantOrder/zipPayVoucher */
|
||||||
|
export async function appMerchantOrderZipPayVoucherPost({
|
||||||
|
body,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
body: { orderId: number; zipPayVoucher: string };
|
||||||
|
options?: CustomRequestOptions;
|
||||||
|
}) {
|
||||||
|
return request<API.R>('/app/merchantOrder/zipPayVoucher', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
data: body,
|
||||||
|
...(options || {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PayOrderBatchBo - 批量订单支付参数
|
* PayOrderBatchBo - 批量订单支付参数
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2898,6 +2898,8 @@
|
|||||||
'merchantVo'?: MerchantVo;
|
'merchantVo'?: MerchantVo;
|
||||||
/** 份数 */
|
/** 份数 */
|
||||||
'copies'?: number;
|
'copies'?: number;
|
||||||
|
|
||||||
|
'actualSalePrice'?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -3031,6 +3033,7 @@
|
|||||||
'dishList'?: MerchantDishVo[];
|
'dishList'?: MerchantDishVo[];
|
||||||
/** 商家视图对象 t_merchant */
|
/** 商家视图对象 t_merchant */
|
||||||
'merchantVo'?: MerchantVo;
|
'merchantVo'?: MerchantVo;
|
||||||
|
'isNew'?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 7.2 KiB |
@@ -17,3 +17,4 @@ export { Pinia, store }
|
|||||||
export * from './module/user'
|
export * from './module/user'
|
||||||
export * from './module/search'
|
export * from './module/search'
|
||||||
export * from './module/config'
|
export * from './module/config'
|
||||||
|
export * from './module/categoryNav'
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜谱分类列表页入口:由首页分类标签写入待选 id,列表页取出后清空,避免依赖路由 query。
|
||||||
|
*/
|
||||||
|
export const useCategoryNavStore = defineStore('categoryNav', () => {
|
||||||
|
const pendingRecipeCategoryId = ref<string | null>(null)
|
||||||
|
|
||||||
|
function setPendingRecipeCategoryId(id: string | number | null | undefined) {
|
||||||
|
if (id == null || id === '') {
|
||||||
|
pendingRecipeCategoryId.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pendingRecipeCategoryId.value = String(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 读取并清空,保证仅消费一次 */
|
||||||
|
function consumePendingRecipeCategoryId(): string | null {
|
||||||
|
const v = pendingRecipeCategoryId.value
|
||||||
|
pendingRecipeCategoryId.value = null
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
pendingRecipeCategoryId,
|
||||||
|
setPendingRecipeCategoryId,
|
||||||
|
consumePendingRecipeCategoryId,
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -34,10 +34,14 @@ export const useSearchStore = defineStore(
|
|||||||
historyList.value = historyListCopy
|
historyList.value = historyListCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearHistory() {
|
||||||
|
historyList.value = []
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
historyList,
|
historyList,
|
||||||
setHistoryList,
|
setHistoryList,
|
||||||
|
clearHistory,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+48
-4
@@ -5,6 +5,38 @@ import {dayjs} from "@/plugin";
|
|||||||
import {useConfigStore} from "@/store";
|
import {useConfigStore} from "@/store";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析路由/query 中的关键词:支持被多次 URL 编码(部分端 navigate 会二次编码,得到 %25E8%25...)。
|
||||||
|
* 直至串中不再含 %XX 或 decode 失败为止。
|
||||||
|
*/
|
||||||
|
export function decodeRouteQueryValue(raw: unknown): string {
|
||||||
|
let s = String(raw ?? '').trim()
|
||||||
|
if (!s) return ''
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
if (!/%[0-9A-Fa-f]{2}/i.test(s)) break
|
||||||
|
try {
|
||||||
|
const next = decodeURIComponent(s.replace(/\+/g, ' '))
|
||||||
|
if (next === s) break
|
||||||
|
s = next
|
||||||
|
} catch {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 销量展示:千级 k+、万级 w+(与首页精选菜品一致) */
|
||||||
|
export function formatSalesCount(stockLike: unknown): string {
|
||||||
|
const n = Number(stockLike)
|
||||||
|
if (!Number.isFinite(n) || n < 0) return '0'
|
||||||
|
if (n >= 10000) {
|
||||||
|
const w = n / 10000
|
||||||
|
return (w >= 10 ? `${Math.round(w)}w+` : `${w.toFixed(1).replace(/\.0$/, '')}w+`)
|
||||||
|
}
|
||||||
|
if (n >= 1000) return `${Math.floor(n / 1000)}k+`
|
||||||
|
return String(Math.floor(n))
|
||||||
|
}
|
||||||
|
|
||||||
// 缩略图
|
// 缩略图
|
||||||
export function thumbnailImg(url: string, width = 400, height = 400) {
|
export function thumbnailImg(url: string, width = 400, height = 400) {
|
||||||
if (!url || !url.startsWith("http") || !url.startsWith("https")) {
|
if (!url || !url.startsWith("http") || !url.startsWith("https")) {
|
||||||
@@ -175,14 +207,26 @@ export function navGoogleMap(latLng: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将时间戳格式化为 "MMM.DD YYYY" 格式(例如:Mar.06 2025)
|
* 将时间戳格式化为简短日期(随语言变化)
|
||||||
|
* - 中文:M月D日 YYYY(例:4月8日 2026)
|
||||||
|
* - 英文:MMM D, YYYY(例:Apr 8, 2026)
|
||||||
|
* 注意:不能使用 MMM.DD YYYY,在 zh-cn 下 MMM 会变为「4月」,与中间的点拼接会出现「4月.08」错乱
|
||||||
* @param {number} timestamp - 毫秒级时间戳
|
* @param {number} timestamp - 毫秒级时间戳
|
||||||
* @returns {string} 格式化后的日期字符串
|
* @returns {string} 格式化后的日期字符串
|
||||||
*/
|
*/
|
||||||
export function formatTimestamp(timestamp: number): string {
|
export function formatTimestamp(timestamp: number): string {
|
||||||
return dayjs(Number(timestamp))
|
const locale = uni.getLocale()
|
||||||
.format('MMM.DD YYYY') // 核心格式化指令
|
const date = dayjs(Number(timestamp))
|
||||||
.replace(/\b([a-z])/, match => match.toUpperCase()); // 确保月份首字母大写
|
|
||||||
|
if (locale === 'zh-Hans') {
|
||||||
|
date.locale('zh-cn')
|
||||||
|
return date.format('M月D日 YYYY')
|
||||||
|
}
|
||||||
|
|
||||||
|
date.locale('en')
|
||||||
|
return date
|
||||||
|
.format('MMM D, YYYY')
|
||||||
|
.replace(/\b([a-z])/, match => match.toUpperCase())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user