|
|
<template>
|
|
|
<view class="page-menu">
|
|
|
<view class="top-bar">
|
|
|
<view class="top-left">
|
|
|
<text class="top-logo">🍸</text>
|
|
|
<text class="top-brand">就是纯瘾大</text>
|
|
|
<text v-if="card.cardNo" class="card-badge">🎫 {{ card.cardNo }}</text>
|
|
|
</view>
|
|
|
<view class="top-right">
|
|
|
<text class="my-btn" @tap="goMyOrders">我的</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<scroll-view class="menu-scroll" scroll-y>
|
|
|
<BannerSwiper />
|
|
|
<CategoryTab :categories="categories" :active="activeCate" @change="onCateChange" />
|
|
|
<view class="product-grid" v-if="products.length > 0">
|
|
|
<ProductCard v-for="p in products" :key="p.id" :product="p" @add="onAddToCart" />
|
|
|
</view>
|
|
|
<view v-else class="empty-state">
|
|
|
<text class="empty-icon">🍹</text>
|
|
|
<text class="empty-text">暂无商品</text>
|
|
|
</view>
|
|
|
<view style="height:200rpx"></view>
|
|
|
</scroll-view>
|
|
|
|
|
|
<!-- 底部栏: 红酒杯(左) + 出餐铃(中) -->
|
|
|
<view class="bottom-bar">
|
|
|
<view class="wine-glass" @tap="showCart = true">
|
|
|
<text class="glass-icon">🍷</text>
|
|
|
<text v-if="cart.totalCount > 0" class="glass-count">{{ cart.totalCount }}</text>
|
|
|
</view>
|
|
|
<view class="press-bell-wrapper" @tap="onBellPress">
|
|
|
<view class="press-bell-ring" :class="{ waving: bellWaving }"></view>
|
|
|
<view class="press-bell-dome" :class="{ pressed: bellPressed }"></view>
|
|
|
<view class="press-bell-base"></view>
|
|
|
<text class="bell-label">出餐铃</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<!-- 购物车弹层 (点击遮罩关闭) -->
|
|
|
<view class="cart-overlay" :class="{ show: showCart }" @tap="showCart = false">
|
|
|
<view class="cart-popup" @tap.stop>
|
|
|
<view class="cart-header">
|
|
|
<text class="cart-title">购物车 ({{ cart.items.length }})</text>
|
|
|
<text class="clear-btn" @tap="cart.clearCart()">清空</text>
|
|
|
</view>
|
|
|
<scroll-view class="cart-items" scroll-y>
|
|
|
<view v-for="item in cart.items" :key="item.product.id" class="cart-item">
|
|
|
<view class="item-img"><text>{{ item.product.emoji }}</text></view>
|
|
|
<view class="item-info">
|
|
|
<text class="item-name">{{ item.product.name }}</text>
|
|
|
</view>
|
|
|
<view class="qty-control">
|
|
|
<text class="qty-btn" @tap="cart.updateQty(item.product.id, item.qty - 1)">−</text>
|
|
|
<text class="qty-num">{{ item.qty }}</text>
|
|
|
<text class="qty-btn" @tap="cart.updateQty(item.product.id, item.qty + 1)">+</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</scroll-view>
|
|
|
<view class="cart-footer">
|
|
|
<button class="next-btn" @tap="onGoConfirm" :disabled="cart.items.length===0">去呼唤调酒师</button>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import { ref, onMounted } from 'vue'
|
|
|
import { useCardStore } from '@/stores/card'
|
|
|
import { useCartStore } from '@/stores/cart'
|
|
|
import { get } from '@/utils/request'
|
|
|
import { API } from '@/utils/constants'
|
|
|
import BannerSwiper from '@/components/BannerSwiper.vue'
|
|
|
import CategoryTab from '@/components/CategoryTab.vue'
|
|
|
import ProductCard from '@/components/ProductCard.vue'
|
|
|
|
|
|
export default {
|
|
|
components: { BannerSwiper, CategoryTab, ProductCard },
|
|
|
setup() {
|
|
|
const card = useCardStore()
|
|
|
const cart = useCartStore()
|
|
|
const categories = ref(['all'])
|
|
|
const activeCate = ref('all')
|
|
|
const products = ref([])
|
|
|
const showCart = ref(false)
|
|
|
const bellPressed = ref(false)
|
|
|
const bellWaving = ref(false)
|
|
|
|
|
|
async function loadCategories() {
|
|
|
try {
|
|
|
const res = await get(API.MENU_CATEGORIES)
|
|
|
if (Array.isArray(res)) {
|
|
|
// 智能排序:全部 + 鸡尾酒优先,其余按后端顺序
|
|
|
const cocktail = res.filter(c => c.includes('鸡尾酒'))
|
|
|
const others = res.filter(c => !c.includes('鸡尾酒'))
|
|
|
const sorted = ['all', ...cocktail, ...others]
|
|
|
categories.value = sorted
|
|
|
// 默认选中鸡尾酒;没有则选全部
|
|
|
if (cocktail.length > 0) {
|
|
|
activeCate.value = cocktail[0]
|
|
|
await loadProducts(cocktail[0])
|
|
|
} else {
|
|
|
await loadProducts('all')
|
|
|
}
|
|
|
}
|
|
|
} catch (e) {}
|
|
|
}
|
|
|
|
|
|
async function loadProducts(cate) {
|
|
|
try {
|
|
|
const res = await get(API.MENU_PRODUCTS, { cate: cate === 'all' ? '' : cate })
|
|
|
if (Array.isArray(res)) products.value = res
|
|
|
} catch (e) {}
|
|
|
}
|
|
|
|
|
|
function onCateChange(cate) { activeCate.value = cate; loadProducts(cate) }
|
|
|
|
|
|
function onAddToCart(product) {
|
|
|
cart.addItem(product)
|
|
|
uni.showToast({ title: '已加入 🍷', icon: 'none', duration: 800 })
|
|
|
}
|
|
|
|
|
|
function onBellPress() {
|
|
|
if (cart.items.length === 0) {
|
|
|
uni.showToast({ title: '购物车空空', icon: 'none' })
|
|
|
return
|
|
|
}
|
|
|
bellPressed.value = true
|
|
|
bellWaving.value = true
|
|
|
setTimeout(() => { bellPressed.value = false; bellWaving.value = false }, 600)
|
|
|
setTimeout(() => { uni.navigateTo({ url: '/pages/confirm/confirm' }) }, 400)
|
|
|
}
|
|
|
|
|
|
function onGoConfirm() {
|
|
|
showCart.value = false
|
|
|
uni.navigateTo({ url: '/pages/confirm/confirm' })
|
|
|
}
|
|
|
|
|
|
function goMyOrders() { uni.navigateTo({ url: '/pages/orders/orders' }) }
|
|
|
|
|
|
onMounted(async () => {
|
|
|
await loadCategories()
|
|
|
})
|
|
|
|
|
|
return { card, cart, categories, activeCate, products, showCart, bellPressed, bellWaving, onCateChange, onAddToCart, onBellPress, onGoConfirm, goMyOrders }
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.page-menu{min-height:100vh;display:flex;flex-direction:column;background:var(--bg);width:100%}
|
|
|
.top-bar{height:100rpx;display:flex;align-items:center;justify-content:space-between;padding:0 28rpx;border-bottom:1px solid var(--border);flex-shrink:0}
|
|
|
.top-left{display:flex;align-items:center;gap:16rpx}
|
|
|
.top-logo{font-size:44rpx}
|
|
|
.top-brand{font-size:36rpx;font-weight:900;color:var(--gold);letter-spacing:4rpx}
|
|
|
.card-badge{background:linear-gradient(135deg,var(--gold-dark),var(--gold));color:#1A1A1A;padding:10rpx 24rpx;border-radius:28rpx;font-weight:800;font-size:26rpx;letter-spacing:6rpx;animation:card-appear-top .5s ease}
|
|
|
.my-btn{font-size:26rpx;color:var(--gold);font-weight:600}
|
|
|
.menu-scroll{flex:1}
|
|
|
.product-grid{padding:16rpx 20rpx}
|
|
|
.empty-state{display:flex;flex-direction:column;align-items:center;padding:120rpx 0}
|
|
|
.empty-icon{font-size:112rpx;opacity:.3}
|
|
|
.empty-text{font-size:28rpx;color:var(--text-muted);margin-top:16rpx}
|
|
|
|
|
|
/* 底部栏: 出餐铃 */
|
|
|
.bottom-bar{position:sticky;bottom:0;background:var(--bg-card);border-top:1px solid var(--border);padding:24rpx 32rpx;padding-bottom:calc(24rpx + var(--safe-b));display:flex;align-items:center;justify-content:center;z-index:100;min-height:190rpx;flex-shrink:0}
|
|
|
.wine-glass{position:absolute;left:40rpx;bottom:calc(40rpx + var(--safe-b));display:flex;flex-direction:column;align-items:center}
|
|
|
.glass-icon{font-size:64rpx;transition:.3s}
|
|
|
.glass-count{position:absolute;top:-8rpx;right:-16rpx;min-width:36rpx;height:36rpx;border-radius:18rpx;background:var(--red);color:#fff;font-size:20rpx;font-weight:700;display:flex;align-items:center;justify-content:center;padding:0 8rpx}
|
|
|
|
|
|
.press-bell-wrapper{display:flex;flex-direction:column;align-items:center;position:relative}
|
|
|
.press-bell-base{width:140rpx;height:28rpx;border-radius:14rpx;background:linear-gradient(180deg,#8B7355,#5C4033);box-shadow:0 4rpx 12rpx rgba(0,0,0,.5)}
|
|
|
.press-bell-dome{width:120rpx;height:96rpx;border-radius:60rpx 60rpx 12rpx 12rpx;background:linear-gradient(180deg,#E8C84A,#C8962A,#A07020);margin-bottom:-4rpx;position:relative;box-shadow:0 8rpx 24rpx rgba(200,150,42,.5),inset 0 4rpx 0 rgba(255,255,255,.3);transition:transform .15s ease;transform-origin:bottom center}
|
|
|
.press-bell-dome::after{content:'';position:absolute;top:16rpx;left:50%;transform:translateX(-50%);width:32rpx;height:36rpx;border-radius:50%;background:linear-gradient(180deg,#F5DEB3,#D4A030);box-shadow:0 2rpx 6rpx rgba(0,0,0,.3)}
|
|
|
.press-bell-dome.pressed{animation:bell-press .4s ease}
|
|
|
.press-bell-ring{position:absolute;top:8rpx;left:50%;transform:translateX(-50%);width:100rpx;height:100rpx;border-radius:50%;pointer-events:none;border:4rpx solid rgba(245,166,35,.4)}
|
|
|
.press-bell-ring.waving{animation:bell-ring-wave .6s ease-out forwards}
|
|
|
.bell-label{font-size:22rpx;font-weight:700;color:var(--gold);letter-spacing:2rpx;margin-top:12rpx}
|
|
|
|
|
|
/* 购物车弹层 */
|
|
|
.cart-overlay{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.6);z-index:200}
|
|
|
.cart-overlay.show{display:block}
|
|
|
.cart-popup{position:fixed;bottom:0;left:0;right:0;background:var(--bg-card);border-radius:40rpx 40rpx 0 0;max-height:55vh;display:flex;flex-direction:column;transform:translateY(100%);transition:transform .35s cubic-bezier(.4,0,.2,1);border-top:4rpx solid var(--gold)}
|
|
|
.cart-overlay.show .cart-popup{transform:translateY(0)}
|
|
|
.cart-header{display:flex;align-items:center;justify-content:space-between;padding:32rpx 40rpx 24rpx;flex-shrink:0}
|
|
|
.cart-title{font-size:32rpx;font-weight:700;color:var(--gold)}
|
|
|
.clear-btn{font-size:26rpx;color:var(--text-dim)}
|
|
|
.cart-items{flex:1;overflow-y:auto;padding:0 40rpx;max-height:400rpx}
|
|
|
.cart-item{display:flex;align-items:center;padding:24rpx 0;border-bottom:1px solid var(--border);gap:20rpx}
|
|
|
.item-img{width:80rpx;height:80rpx;border-radius:16rpx;background:var(--bg);display:flex;align-items:center;justify-content:center;font-size:40rpx;flex-shrink:0}
|
|
|
.item-info{flex:1;min-width:0}
|
|
|
.item-name{font-size:28rpx;font-weight:600;color:var(--text)}
|
|
|
.qty-control{display:flex;align-items:center;gap:0}
|
|
|
.qty-btn{width:52rpx;height:52rpx;border-radius:50%;border:1px solid var(--border);background:var(--bg);font-size:30rpx;display:flex;align-items:center;justify-content:center;color:var(--text-dim)}
|
|
|
.qty-num{width:56rpx;text-align:center;font-size:28rpx;font-weight:700;color:var(--gold)}
|
|
|
.cart-footer{padding:24rpx 40rpx 40rpx;flex-shrink:0}
|
|
|
.next-btn{width:100%;height:96rpx;border-radius:28rpx;background:linear-gradient(135deg,var(--orange),var(--red));color:#fff;border:none;font-size:32rpx;font-weight:800;box-shadow:0 8rpx 40rpx rgba(255,107,53,.35)}
|
|
|
</style>
|