You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

190 lines
9.8 KiB

<template>
<view class="page-menu">
<view class="nav-bar" :style="{ top: statusBarHeight + 'px' }">
<view class="nav-left">
<text class="nav-logo">🍸</text>
<text class="nav-brand">纯瘾大</text>
<text v-if="card.cardNo" class="card-badge">🎫 {{ card.cardNo }}</text>
</view>
<view class="nav-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)
const statusBarHeight = ref(0)
async function loadCategories() {
try {
const res = await get(API.MENU_CATEGORIES)
if (Array.isArray(res)) categories.value = ['all', ...res]
} 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 () => {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight
await loadCategories()
await loadProducts('all')
})
return { card, cart, categories, activeCate, products, showCart, bellPressed, bellWaving, statusBarHeight, onCateChange, onAddToCart, onBellPress, onGoConfirm, goMyOrders }
}
}
</script>
<style scoped>
.page-menu{min-height:100vh;display:flex;flex-direction:column;background:var(--bg);width:100%}
.nav-bar{height:100rpx;position:fixed;top:0;z-index:100;background:var(--bg);display:flex;align-items:center;justify-content:space-between;padding:0 28rpx;border-bottom:1px solid var(--border);width:100%}
.nav-left{display:flex;align-items:center;gap:16rpx}
.nav-logo{font-size:44rpx}
.nav-brand{font-size:40rpx;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;margin-top:100rpx}
.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>