| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- <template>
- <div class="buffet-select">
- <van-nav-bar :title="$t('buffet.select.title')" left-arrow @click-left="$router.back()" fixed placeholder />
- <div class="intro-banner">
- <van-icon name="fire-o" size="36" color="#ff6b6b" />
- <h2>{{ $t('buffet.select.subtitle') }}</h2>
- <p>{{ $t('buffet.select.description') }}</p>
- </div>
- <div class="plans-container">
- <div
- v-for="plan in activePlans"
- :key="plan.id"
- class="plan-option"
- :class="{ selected: selectedPlan === plan.id }"
- @click="selectPlan(plan)"
- >
- <div class="plan-badge" v-if="plan.duration >= 120">
- <van-tag type="danger">{{ $t('buffet.select.recommended') }}</van-tag>
- </div>
- <div class="plan-header">
- <h3>{{ plan.name }}</h3>
- <div class="plan-price">
- <span class="currency">¥</span>
- <span class="amount">{{ plan.price }}</span>
- <span class="unit">{{ $t('buffet.select.perPerson') }}</span>
- </div>
- </div>
- <div class="plan-duration">
- <van-icon name="clock-o" />
- <span>{{ plan.duration }}{{ $t('buffet.select.minutes') }}</span>
- </div>
- <div class="plan-description" v-if="plan.description">
- {{ plan.description }}
- </div>
- <div class="plan-features">
- <div class="feature-item">
- <van-icon name="success" color="#07c160" />
- <span>{{ $t('buffet.select.unlimitedOrder') }}</span>
- </div>
- <div class="feature-item" v-if="plan.maxOrders">
- <van-icon name="success" color="#07c160" />
- <span>{{ $t('buffet.select.maxOrders', { count: plan.maxOrders }) }}</span>
- </div>
- <div class="feature-item">
- <van-icon name="success" color="#07c160" />
- <span>{{ $t('buffet.select.drinksIncluded') }}</span>
- </div>
- </div>
- <van-icon name="checked" class="check-icon" v-if="selectedPlan === plan.id" />
- </div>
- </div>
- <div class="guest-selector">
- <div class="selector-label">{{ $t('buffet.select.guestCount') }}</div>
- <van-stepper v-model="guestCount" min="1" max="20" integer />
- </div>
- <div class="total-price">
- <div class="price-breakdown">
- <span>{{ $t('buffet.select.subtotal') }}</span>
- <span>¥{{ subtotal }}</span>
- </div>
- <div class="price-breakdown total">
- <span>{{ $t('buffet.select.total') }}</span>
- <span>¥{{ totalAmount }}</span>
- </div>
- </div>
- <div class="action-bar">
- <van-button type="primary" block round size="large" @click="confirmSelection" :disabled="!selectedPlan">
- {{ $t('buffet.select.startBuffet') }}
- </van-button>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, computed, onMounted } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import { useI18n } from 'vue-i18n'
- import { showToast, showConfirmDialog } from 'vant'
- import { useBuffetStore, type BuffetPlan } from '@/store/modules/buffet'
- const { t } = useI18n()
- const route = useRoute()
- const router = useRouter()
- const buffetStore = useBuffetStore()
- const selectedPlan = ref('')
- const guestCount = ref(2)
- const activePlans = computed(() => buffetStore.activePlans)
- const selectedPlanData = computed(() =>
- activePlans.value.find(p => p.id === selectedPlan.value)
- )
- const subtotal = computed(() =>
- selectedPlanData.value ? selectedPlanData.value.price * guestCount.value : 0
- )
- const totalAmount = computed(() => subtotal.value)
- const selectPlan = (plan: BuffetPlan) => {
- selectedPlan.value = plan.id
- }
- const confirmSelection = async () => {
- if (!selectedPlanData.value) return
- try {
- await showConfirmDialog({
- title: t('buffet.select.confirmTitle'),
- message: t('buffet.select.confirmMessage', {
- plan: selectedPlanData.value.name,
- count: guestCount.value,
- amount: totalAmount.value
- })
- })
- // 获取桌位信息(从路由或store)
- const tableId = route.query.tableId as string || 'table_1'
- const tableName = route.query.tableName as string || 'A1'
- // 开始放题会话
- buffetStore.startSession(selectedPlanData.value, tableId, tableName)
- showToast(t('buffet.orderSuccess'))
- // 跳转到菜单页面
- router.push({
- path: '/menu',
- query: { buffet: '1' }
- })
- } catch {
- // 用户取消
- }
- }
- onMounted(() => {
- buffetStore.loadPlans()
- })
- </script>
- <style scoped>
- .buffet-select {
- min-height: 100vh;
- background: #f5f5f5;
- padding-bottom: 100px;
- }
- .intro-banner {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- color: #fff;
- text-align: center;
- padding: 30px 20px;
- }
- .intro-banner h2 {
- margin: 12px 0 8px;
- font-size: 22px;
- }
- .intro-banner p {
- margin: 0;
- font-size: 14px;
- opacity: 0.9;
- }
- .plans-container {
- padding: 16px 12px;
- }
- .plan-option {
- position: relative;
- background: #fff;
- border-radius: 16px;
- padding: 20px;
- margin-bottom: 16px;
- border: 2px solid transparent;
- transition: all 0.3s;
- cursor: pointer;
- }
- .plan-option.selected {
- border-color: #1989fa;
- box-shadow: 0 4px 12px rgba(25, 137, 250, 0.2);
- }
- .plan-badge {
- position: absolute;
- top: 12px;
- right: 12px;
- }
- .plan-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
- }
- .plan-header h3 {
- margin: 0;
- font-size: 18px;
- font-weight: bold;
- }
- .plan-price {
- display: flex;
- align-items: baseline;
- }
- .plan-price .currency {
- font-size: 14px;
- color: #ee0a24;
- }
- .plan-price .amount {
- font-size: 24px;
- font-weight: bold;
- color: #ee0a24;
- margin: 0 2px;
- }
- .plan-price .unit {
- font-size: 12px;
- color: #999;
- }
- .plan-duration {
- display: flex;
- align-items: center;
- gap: 4px;
- font-size: 14px;
- color: #666;
- margin-bottom: 12px;
- }
- .plan-description {
- font-size: 13px;
- color: #999;
- margin-bottom: 12px;
- line-height: 1.5;
- }
- .plan-features {
- display: flex;
- flex-wrap: wrap;
- gap: 12px;
- }
- .feature-item {
- display: flex;
- align-items: center;
- gap: 4px;
- font-size: 13px;
- color: #666;
- }
- .check-icon {
- position: absolute;
- bottom: 16px;
- right: 16px;
- font-size: 24px;
- color: #1989fa;
- }
- .guest-selector {
- background: #fff;
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 16px;
- margin: 0 12px 16px;
- border-radius: 12px;
- }
- .selector-label {
- font-size: 15px;
- font-weight: 500;
- }
- .total-price {
- background: #fff;
- padding: 16px;
- margin: 0 12px 16px;
- border-radius: 12px;
- }
- .price-breakdown {
- display: flex;
- justify-content: space-between;
- font-size: 14px;
- margin-bottom: 8px;
- }
- .price-breakdown.total {
- font-size: 18px;
- font-weight: bold;
- color: #ee0a24;
- padding-top: 8px;
- border-top: 1px dashed #eee;
- margin-bottom: 0;
- }
- .action-bar {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- background: #fff;
- padding: 12px 16px;
- box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
- }
- </style>
|