|
|
@@ -28,6 +28,21 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
+ <!-- ==================== 新订单语音播报栏 ==================== -->
|
|
|
+ <div v-if="latestPendingOrder" class="voice-broadcast-banner mb-4">
|
|
|
+ <div class="banner-content flex items-center gap-4">
|
|
|
+ <i class="i-mdi:volume-high text-3xl text-white animate-pulse" />
|
|
|
+ <div class="flex-1">
|
|
|
+ <div class="text-sm text-white/80 mb-1 font-medium">
|
|
|
+ 新订单语音播报
|
|
|
+ </div>
|
|
|
+ <div class="text-base text-white font-semibold">
|
|
|
+ [{{ latestPendingOrder.roomNo }}房间, {{ formatItemsBrief(latestPendingOrder.items) }}]
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<!-- 统计卡片 -->
|
|
|
<div class="stats-row mb-4 flex gap-4">
|
|
|
<div class="stat-card stat-pending">
|
|
|
@@ -49,19 +64,20 @@
|
|
|
<div v-if="pendingOrders.length > 0" class="order-section">
|
|
|
<h3 class="section-title">
|
|
|
<span class="section-dot pending" />
|
|
|
- 待备餐
|
|
|
+ 待制作(按时间排序)
|
|
|
<span class="section-count">{{ pendingOrders.length }}</span>
|
|
|
</h3>
|
|
|
<div class="order-list">
|
|
|
<div
|
|
|
- v-for="order in pendingOrders"
|
|
|
+ v-for="(order, index) in pendingOrders"
|
|
|
:key="order.id"
|
|
|
class="order-row row-pending"
|
|
|
+ :class="{ 'first-order-zoom': index === 0 }"
|
|
|
@click="openDetail(order)"
|
|
|
>
|
|
|
<div class="row-color-bar" />
|
|
|
<div class="row-content flex-1">
|
|
|
- <div class="row-header flex items-center justify-between mb-2">
|
|
|
+ <div class="row-header mb-2 flex items-center justify-between">
|
|
|
<div class="flex items-center gap-3">
|
|
|
<span class="room-no">{{ order.roomNo || '--' }}</span>
|
|
|
<span class="order-no">{{ order.orderNo }}</span>
|
|
|
@@ -80,7 +96,9 @@
|
|
|
</div>
|
|
|
<div class="row-actions">
|
|
|
<div class="action-wrapper">
|
|
|
- <NButton type="warning" size="medium" @click.stop="handlePrepare(order)">开始备餐</NButton>
|
|
|
+ <NButton type="warning" size="medium" @click.stop="handlePrepare(order)">
|
|
|
+ 开始备餐
|
|
|
+ </NButton>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -103,7 +121,7 @@
|
|
|
>
|
|
|
<div class="row-color-bar" />
|
|
|
<div class="row-content flex-1">
|
|
|
- <div class="row-header flex items-center justify-between mb-2">
|
|
|
+ <div class="row-header mb-2 flex items-center justify-between">
|
|
|
<div class="flex items-center gap-3">
|
|
|
<span class="room-no">{{ order.roomNo || '--' }}</span>
|
|
|
<span class="order-no">{{ order.orderNo }}</span>
|
|
|
@@ -122,7 +140,9 @@
|
|
|
</div>
|
|
|
<div class="row-actions">
|
|
|
<div class="action-wrapper">
|
|
|
- <NButton type="success" size="medium" @click.stop="handleReady(order)">备餐完成·出餐</NButton>
|
|
|
+ <NButton type="success" size="medium" @click.stop="handleReady(order)">
|
|
|
+ 备餐完成·出餐
|
|
|
+ </NButton>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -145,7 +165,7 @@
|
|
|
>
|
|
|
<div class="row-color-bar" />
|
|
|
<div class="row-content flex-1">
|
|
|
- <div class="row-header flex items-center justify-between mb-2">
|
|
|
+ <div class="row-header mb-2 flex items-center justify-between">
|
|
|
<div class="flex items-center gap-3">
|
|
|
<span class="room-no">{{ order.roomNo || '--' }}</span>
|
|
|
<span class="order-no">{{ order.orderNo }}</span>
|
|
|
@@ -187,19 +207,19 @@
|
|
|
>
|
|
|
<div v-if="detailOrder" class="receipt-full">
|
|
|
<!-- 顶部深绿色标题栏 -->
|
|
|
- <div class="receipt-header-bar mb-6 rounded-t-lg p-4 flex items-center justify-between">
|
|
|
+ <div class="receipt-header-bar mb-6 flex items-center justify-between rounded-t-lg p-4">
|
|
|
<div class="flex items-center gap-3">
|
|
|
<i class="i-mdi:arrow-left text-xl cursor-pointer hover:text-white/80" @click="showDetail = false" />
|
|
|
- <span class="text-white text-base font-semibold">订单详情</span>
|
|
|
+ <span class="text-base text-white font-semibold">订单详情</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 房间号大图标区域 -->
|
|
|
- <div class="room-hero-section text-center mb-6">
|
|
|
- <div class="room-icon-wrapper inline-flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-3">
|
|
|
+ <div class="room-hero-section mb-6 text-center">
|
|
|
+ <div class="room-icon-wrapper mb-3 h-16 w-16 inline-flex items-center justify-center rounded-full bg-green-100">
|
|
|
<i class="i-mdi:home-city text-3xl text-green-600" />
|
|
|
</div>
|
|
|
- <div class="room-number text-5xl font-bold text-gray-800 mb-2">
|
|
|
+ <div class="room-number mb-2 text-5xl text-gray-800 font-bold">
|
|
|
{{ detailOrder.roomNo || '--' }}
|
|
|
</div>
|
|
|
<div class="room-meta text-sm text-gray-500">
|
|
|
@@ -208,12 +228,14 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 语音播报区 -->
|
|
|
- <div class="voice-broadcast-section mb-6 rounded-lg bg-green-50 border border-green-200 p-4 flex items-center justify-between">
|
|
|
- <div class="flex items-center gap-3 flex-1 min-w-0">
|
|
|
- <i class="i-mdi:volume-high text-2xl text-green-600 flex-shrink-0" />
|
|
|
+ <div class="voice-broadcast-section mb-6 flex items-center justify-between border border-green-200 rounded-lg bg-green-50 p-4">
|
|
|
+ <div class="min-w-0 flex flex-1 items-center gap-3">
|
|
|
+ <i class="i-mdi:volume-high flex-shrink-0 text-2xl text-green-600" />
|
|
|
<div class="min-w-0">
|
|
|
- <div class="text-xs text-green-700 font-medium mb-1">语音播报</div>
|
|
|
- <div class="text-sm text-gray-700 truncate">
|
|
|
+ <div class="text-xs mb-1 text-green-700 font-medium">
|
|
|
+ 语音播报
|
|
|
+ </div>
|
|
|
+ <div class="text-sm truncate text-gray-700">
|
|
|
{{ detailOrder.roomNo }}房间 · {{ formatItemsBrief(detailOrder.items) }}
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -227,20 +249,26 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 小票主体(虚线边框) -->
|
|
|
- <div class="receipt-body mb-6 rounded-lg border-2 border-dashed border-gray-300 p-6 bg-white">
|
|
|
+ <div class="receipt-body mb-6 border-2 border-gray-300 rounded-lg border-dashed bg-white p-6">
|
|
|
<!-- 小票头部 -->
|
|
|
- <div class="receipt-title text-center mb-4 pb-4 border-b border-dashed border-gray-300">
|
|
|
- <div class="text-lg font-bold text-gray-800 mb-1">璞石酒店 · 厨房联</div>
|
|
|
- <div class="text-sm text-gray-500">{{ detailOrder.orderNo }}</div>
|
|
|
- <div class="text-xs text-gray-400 mt-1">{{ formatDateTime(detailOrder.createTime) }}</div>
|
|
|
+ <div class="receipt-title mb-4 border-b border-gray-300 border-dashed pb-4 text-center">
|
|
|
+ <div class="text-lg mb-1 text-gray-800 font-bold">
|
|
|
+ 璞石酒店 · 厨房联
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-500">
|
|
|
+ {{ detailOrder.orderNo }}
|
|
|
+ </div>
|
|
|
+ <div class="text-xs mt-1 text-gray-400">
|
|
|
+ {{ formatDateTime(detailOrder.createTime) }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 桌号与备注 -->
|
|
|
- <div class="receipt-info mb-4 pb-4 border-b border-dashed border-gray-300">
|
|
|
- <div class="flex items-center gap-2 mb-2">
|
|
|
+ <div class="receipt-info mb-4 border-b border-gray-300 border-dashed pb-4">
|
|
|
+ <div class="mb-2 flex items-center gap-2">
|
|
|
<span class="text-sm text-gray-600">桌号:</span>
|
|
|
- <span class="text-base font-semibold text-gray-800">{{ detailOrder.roomNo }}</span>
|
|
|
- <span class="text-sm text-gray-600 ml-2">{{ detailOrder.roomType || '豪华大床房' }}</span>
|
|
|
+ <span class="text-base text-gray-800 font-semibold">{{ detailOrder.roomNo }}</span>
|
|
|
+ <span class="text-sm ml-2 text-gray-600">{{ detailOrder.roomType || '豪华大床房' }}</span>
|
|
|
</div>
|
|
|
<div class="flex items-center gap-2">
|
|
|
<span class="text-sm text-gray-600">备注:</span>
|
|
|
@@ -249,38 +277,38 @@
|
|
|
</div>
|
|
|
|
|
|
<!-- 菜品明细 -->
|
|
|
- <div class="receipt-items mb-4 pb-4 border-b border-dashed border-gray-300">
|
|
|
+ <div class="receipt-items mb-4 border-b border-gray-300 border-dashed pb-4">
|
|
|
<div v-for="(item, idx) in (detailOrder.items || [])" :key="item.id" class="receipt-item-row mb-3 last:mb-0">
|
|
|
- <div class="item-header flex justify-between items-start mb-1">
|
|
|
+ <div class="item-header mb-1 flex items-start justify-between">
|
|
|
<div class="flex-1">
|
|
|
- <span class="item-index text-sm text-gray-500 mr-2">[{{ idx + 1 }}]</span>
|
|
|
- <span class="item-name text-base font-medium text-gray-800">{{ item.dishName }}</span>
|
|
|
+ <span class="item-index text-sm mr-2 text-gray-500">[{{ idx + 1 }}]</span>
|
|
|
+ <span class="item-name text-base text-gray-800 font-medium">{{ item.dishName }}</span>
|
|
|
</div>
|
|
|
- <span class="item-price text-right font-semibold text-gray-800 whitespace-nowrap ml-3">
|
|
|
+ <span class="item-price ml-3 whitespace-nowrap text-right text-gray-800 font-semibold">
|
|
|
¥{{ item.subtotal || '0.00' }}
|
|
|
</span>
|
|
|
</div>
|
|
|
<!-- 规格与加料 -->
|
|
|
- <div v-if="item.specDesc || item.additionsDesc" class="item-details text-xs text-gray-500 ml-6">
|
|
|
+ <div v-if="item.specDesc || item.additionsDesc" class="item-details text-xs ml-6 text-gray-500">
|
|
|
<span v-if="item.specDesc">{{ item.specDesc }}</span>
|
|
|
<span v-if="item.specDesc && item.additionsDesc"> / </span>
|
|
|
<span v-if="item.additionsDesc">{{ item.additionsDesc }}</span>
|
|
|
</div>
|
|
|
<!-- 单条备注 -->
|
|
|
- <div v-if="item.note" class="item-note text-xs text-orange-600 ml-6 mt-1">
|
|
|
+ <div v-if="item.note" class="item-note text-xs ml-6 mt-1 text-orange-600">
|
|
|
备注: {{ item.note }}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 合计 -->
|
|
|
- <div class="receipt-total text-right text-lg font-bold text-gray-800">
|
|
|
+ <div class="receipt-total text-lg text-right text-gray-800 font-bold">
|
|
|
合计:¥{{ detailOrder.totalAmount || '0.00' }}
|
|
|
</div>
|
|
|
|
|
|
<!-- 自动打印时间 -->
|
|
|
- <div class="receipt-footer text-center mt-4 pt-4 border-t border-dashed border-gray-300">
|
|
|
- <div class="text-xs text-gray-400 flex items-center justify-center gap-1">
|
|
|
+ <div class="receipt-footer mt-4 border-t border-gray-300 border-dashed pt-4 text-center">
|
|
|
+ <div class="text-xs flex items-center justify-center gap-1 text-gray-400">
|
|
|
<i class="i-mdi:printer" />
|
|
|
自动打印 · {{ formatDateTime(detailOrder.createTime) }}
|
|
|
</div>
|
|
|
@@ -324,7 +352,7 @@
|
|
|
>
|
|
|
已出餐,等待配送
|
|
|
</NButton>
|
|
|
- <div class="action-hint text-center text-xs text-gray-400 mt-2">
|
|
|
+ <div class="action-hint text-xs mt-2 text-center text-gray-400">
|
|
|
点击后将通知餐厅前台安排送餐
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -345,6 +373,8 @@ import {
|
|
|
import { useOrderNotification } from '@/composables/useOrderNotification'
|
|
|
|
|
|
const message = useMessage()
|
|
|
+// 使用全局 $notification(与 restaurantOrder.vue 保持一致)
|
|
|
+const $notification = window.$notification
|
|
|
|
|
|
// ==================== 数据状态 ====================
|
|
|
const orderList = ref([])
|
|
|
@@ -352,17 +382,27 @@ const stats = ref({ pendingCount: 0, preparingCount: 0, todayCompletedCount: 0 }
|
|
|
const showDetail = ref(false)
|
|
|
const detailOrder = ref(null)
|
|
|
|
|
|
+// 标记是否首次加载(用于控制首次加载时播放语音提示)
|
|
|
+let isFirstLoad = true
|
|
|
+
|
|
|
// ==================== 分组过滤 ====================
|
|
|
const pendingOrders = computed(() => orderList.value.filter(o => o.status === 2))
|
|
|
const preparingOrders = computed(() => orderList.value.filter(o => o.status === 3))
|
|
|
+// 已完成:包含 status >= 4 的订单(已出餐、配送中、已完成),与后端 kitchenStats 统计逻辑保持一致
|
|
|
const completedOrders = computed(() => orderList.value.filter(o => o.status >= 4))
|
|
|
|
|
|
+// 最新待备餐订单(用于顶部语音播报栏)
|
|
|
+const latestPendingOrder = computed(() => {
|
|
|
+ return pendingOrders.value.length > 0 ? pendingOrders.value[0] : null
|
|
|
+})
|
|
|
+
|
|
|
// 日期筛选(默认今天,NDatePicker 使用毫秒时间戳)
|
|
|
const selectedDate = ref(Date.now())
|
|
|
|
|
|
/** 将毫秒时间戳格式化为 yyyy-MM-dd */
|
|
|
function formatDateParam(ts) {
|
|
|
- if (!ts) return null
|
|
|
+ if (!ts)
|
|
|
+ return null
|
|
|
const d = new Date(ts)
|
|
|
const yyyy = d.getFullYear()
|
|
|
const mm = String(d.getMonth() + 1).padStart(2, '0')
|
|
|
@@ -375,13 +415,43 @@ function onDateChange() {
|
|
|
}
|
|
|
|
|
|
// ==================== SSE 通知 ====================
|
|
|
+// 厨房端监听 NEW_ORDER 和 ORDER_ACCEPTED
|
|
|
+// ORDER_READY 是餐厅端专用事件(出餐完成通知前厅配送),厨房端静默处理
|
|
|
const { connected, connect, disconnect } = useOrderNotification({
|
|
|
- onNewOrder() {
|
|
|
- loadData()
|
|
|
- speakNewOrder(detailOrder.value)
|
|
|
+ async onNewOrder(sseData) {
|
|
|
+ await loadData()
|
|
|
+ // SSE 数据不含菜品,从已加载列表中查找完整订单以获取菜品名
|
|
|
+ const fullOrder = orderList.value.find(o => o.id === sseData.orderId) || sseData
|
|
|
+ speakNewOrder(fullOrder)
|
|
|
+ },
|
|
|
+ async onOrderAccepted(sseData) {
|
|
|
+ await loadData()
|
|
|
+ const fullOrder = orderList.value.find(o => o.id === sseData.orderId) || sseData
|
|
|
+ speakOrderAccepted(fullOrder)
|
|
|
},
|
|
|
+ // 厨房端静默处理出餐完成事件,避免自己显示自己的操作通知
|
|
|
+ silentEvents: ['order-ready'],
|
|
|
})
|
|
|
|
|
|
+// ==================== 处理页面内通知事件 ====================
|
|
|
+function handleHotelNotification(e) {
|
|
|
+ const { type, message: msg, level } = e.detail
|
|
|
+ const titleMap = {
|
|
|
+ 'new-order': '新订单',
|
|
|
+ 'order-accepted': '订单已接单',
|
|
|
+ 'refund': '退单申请',
|
|
|
+ 'order-ready': '出餐完成',
|
|
|
+ }
|
|
|
+ const nType = level === 'warning' ? 'warning' : level === 'success' ? 'success' : 'info'
|
|
|
+ if ($notification && $notification[nType]) {
|
|
|
+ $notification[nType]({
|
|
|
+ title: titleMap[type] || '订单通知',
|
|
|
+ content: msg,
|
|
|
+ duration: 5000,
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// ==================== 格式化 ====================
|
|
|
function formatDateTime(dateStr) {
|
|
|
if (!dateStr)
|
|
|
@@ -464,10 +534,14 @@ function statusLabel(status) {
|
|
|
}
|
|
|
|
|
|
function statusBadgeClass(status) {
|
|
|
- if (status <= 1) return 'badge-gray'
|
|
|
- if (status === 2) return 'badge-warning'
|
|
|
- if (status === 3) return 'badge-info'
|
|
|
- if (status >= 4 && status <= 6) return 'badge-success'
|
|
|
+ if (status <= 1)
|
|
|
+ return 'badge-gray'
|
|
|
+ if (status === 2)
|
|
|
+ return 'badge-warning'
|
|
|
+ if (status === 3)
|
|
|
+ return 'badge-info'
|
|
|
+ if (status >= 4 && status <= 6)
|
|
|
+ return 'badge-success'
|
|
|
return 'badge-gray'
|
|
|
}
|
|
|
|
|
|
@@ -475,6 +549,7 @@ function statusBadgeClass(status) {
|
|
|
async function loadData() {
|
|
|
try {
|
|
|
const dateStr = formatDateParam(selectedDate.value)
|
|
|
+ console.log('[厨房订单] 传入后端的日期参数:', dateStr, 'selectedDate.value:', selectedDate.value)
|
|
|
const [listRes, statsRes] = await Promise.all([
|
|
|
getKitchenOrderList(dateStr),
|
|
|
getKitchenStats(dateStr),
|
|
|
@@ -484,6 +559,45 @@ async function loadData() {
|
|
|
if (listRes.code === 200 || listRes.code === 0) {
|
|
|
orderList.value = listRes.data || []
|
|
|
console.log('[厨房订单] orderList.value 赋值后:', orderList.value, '长度:', orderList.value.length)
|
|
|
+
|
|
|
+ // 调试:打印所有订单的状态分布
|
|
|
+ const statusCounts = {}
|
|
|
+ orderList.value.forEach(o => {
|
|
|
+ const s = o.status
|
|
|
+ statusCounts[s] = (statusCounts[s] || 0) + 1
|
|
|
+ })
|
|
|
+ console.log('[厨房订单] 订单状态分布:', statusCounts)
|
|
|
+ console.log('[厨房订单] completedOrders 数量:', orderList.value.filter(o => o.status >= 4).length)
|
|
|
+
|
|
|
+ // 首次加载时,播放第一个待备餐订单的语音提示并显示顶部通知栏
|
|
|
+ if (isFirstLoad) {
|
|
|
+ isFirstLoad = false
|
|
|
+ const firstPending = orderList.value.find(o => o.status === 2)
|
|
|
+ if (firstPending) {
|
|
|
+ console.log('[厨房订单] 首次加载,播放第一个待备餐订单语音:', firstPending.roomNo)
|
|
|
+
|
|
|
+ // 1. 播放语音播报
|
|
|
+ speakOrderAccepted(firstPending)
|
|
|
+
|
|
|
+ // 2. 显示顶部通知栏(通过 showInPageNotification 触发)
|
|
|
+ try {
|
|
|
+ const items = firstPending.items || []
|
|
|
+ const dishNames = items.map(i => `${i.dishName}×${i.quantity}`).join('、')
|
|
|
+ const contentText = dishNames
|
|
|
+ ? `${firstPending.roomNo || '未知'}房间订单已接单,请开始备餐 菜品:${dishNames}`
|
|
|
+ : `${firstPending.roomNo || '未知'}房间订单已接单,请开始备餐`
|
|
|
+
|
|
|
+ // 派发 DOM 事件,触发顶部通知栏显示
|
|
|
+ window.dispatchEvent(new CustomEvent('hotel-notification', {
|
|
|
+ detail: { type: 'order-accepted', message: contentText, level: 'info' },
|
|
|
+ }))
|
|
|
+ console.log('[厨房订单] 顶部通知栏已显示')
|
|
|
+ }
|
|
|
+ catch (e) {
|
|
|
+ console.error('[厨房订单] 顶部通知栏失败:', e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
else {
|
|
|
console.error('[厨房订单] listRes.code !== 200:', listRes)
|
|
|
@@ -534,8 +648,31 @@ function speakNewOrder(order) {
|
|
|
return
|
|
|
try {
|
|
|
const items = order.items || []
|
|
|
- const dishNames = items.map(i => i.dishName).join('、')
|
|
|
- const text = `新订单,${order.roomNo || '未知'}房间,${dishNames}`
|
|
|
+ const dishNames = items.map(i => `${i.dishName}×${i.quantity}`).join('、')
|
|
|
+ const text = dishNames
|
|
|
+ ? `新订单,${order.roomNo || '未知'}房间,${dishNames}`
|
|
|
+ : `新订单,${order.roomNo || '未知'}房间`
|
|
|
+ const utterance = new SpeechSynthesisUtterance(text)
|
|
|
+ utterance.lang = 'zh-CN'
|
|
|
+ utterance.rate = 0.9
|
|
|
+ utterance.volume = 1.0
|
|
|
+ window.speechSynthesis.speak(utterance)
|
|
|
+ }
|
|
|
+ catch (e) {
|
|
|
+ console.error('语音播报失败:', e)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 接单语音播报
|
|
|
+function speakOrderAccepted(order) {
|
|
|
+ if (!order || !('speechSynthesis' in window))
|
|
|
+ return
|
|
|
+ try {
|
|
|
+ const items = order.items || []
|
|
|
+ const dishNames = items.map(i => `${i.dishName}×${i.quantity}`).join('、')
|
|
|
+ const text = dishNames
|
|
|
+ ? `${order.roomNo || '未知'}房间订单已接单,请开始备餐,${dishNames}`
|
|
|
+ : `${order.roomNo || '未知'}房间订单已接单,请开始备餐`
|
|
|
const utterance = new SpeechSynthesisUtterance(text)
|
|
|
utterance.lang = 'zh-CN'
|
|
|
utterance.rate = 0.9
|
|
|
@@ -549,7 +686,8 @@ function speakNewOrder(order) {
|
|
|
|
|
|
// 重播语音播报
|
|
|
function playVoiceBroadcast() {
|
|
|
- if (!detailOrder.value) return
|
|
|
+ if (!detailOrder.value)
|
|
|
+ return
|
|
|
speakNewOrder(detailOrder.value)
|
|
|
message.success('已重播语音通知')
|
|
|
}
|
|
|
@@ -558,10 +696,12 @@ function playVoiceBroadcast() {
|
|
|
onMounted(() => {
|
|
|
loadData()
|
|
|
connect()
|
|
|
+ window.addEventListener('hotel-notification', handleHotelNotification)
|
|
|
})
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
disconnect()
|
|
|
+ window.removeEventListener('hotel-notification', handleHotelNotification)
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
@@ -642,6 +782,14 @@ onUnmounted(() => {
|
|
|
color: #2080f0;
|
|
|
}
|
|
|
|
|
|
+/* ==================== 新订单语音播报栏 ==================== */
|
|
|
+.voice-broadcast-banner {
|
|
|
+ background: linear-gradient(135deg, #1e7e5a 0%, #2d9b6e 100%);
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 16px 24px;
|
|
|
+ box-shadow: 0 4px 12px rgba(30, 126, 90, 0.3);
|
|
|
+}
|
|
|
+
|
|
|
/* ==================== 订单分组列表 ==================== */
|
|
|
.order-section {
|
|
|
margin-bottom: 20px;
|
|
|
@@ -664,9 +812,15 @@ onUnmounted(() => {
|
|
|
border-radius: 50%;
|
|
|
}
|
|
|
|
|
|
-.section-dot.pending { background: #f0a020; }
|
|
|
-.section-dot.preparing { background: #18a058; }
|
|
|
-.section-dot.completed { background: #2080f0; }
|
|
|
+.section-dot.pending {
|
|
|
+ background: #f0a020;
|
|
|
+}
|
|
|
+.section-dot.preparing {
|
|
|
+ background: #18a058;
|
|
|
+}
|
|
|
+.section-dot.completed {
|
|
|
+ background: #2080f0;
|
|
|
+}
|
|
|
|
|
|
.section-count {
|
|
|
font-size: 12px;
|
|
|
@@ -684,7 +838,9 @@ onUnmounted(() => {
|
|
|
.row-completed {
|
|
|
opacity: 0.85;
|
|
|
}
|
|
|
-.row-completed .row-color-bar { background: #2080f0; }
|
|
|
+.row-completed .row-color-bar {
|
|
|
+ background: #2080f0;
|
|
|
+}
|
|
|
|
|
|
.order-row {
|
|
|
display: flex;
|
|
|
@@ -693,7 +849,9 @@ onUnmounted(() => {
|
|
|
border: 1px solid #e8e8e8;
|
|
|
overflow: hidden;
|
|
|
cursor: pointer;
|
|
|
- transition: box-shadow 0.2s, transform 0.15s;
|
|
|
+ transition:
|
|
|
+ box-shadow 0.2s,
|
|
|
+ transform 0.15s;
|
|
|
min-height: 140px; /* 增加高度,容纳更多内容 */
|
|
|
}
|
|
|
|
|
|
@@ -702,15 +860,38 @@ onUnmounted(() => {
|
|
|
transform: translateY(-2px);
|
|
|
}
|
|
|
|
|
|
+/* 第一个待备餐订单的放大动画 */
|
|
|
+.first-order-zoom {
|
|
|
+ animation: first-order-pulse 2s ease-in-out infinite;
|
|
|
+ border: 2px solid #f0a020 !important;
|
|
|
+ box-shadow: 0 8px 24px rgba(240, 160, 32, 0.3) !important;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes first-order-pulse {
|
|
|
+ 0%,
|
|
|
+ 100% {
|
|
|
+ transform: scale(1);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ transform: scale(1.03);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/* 左侧色条 - 加宽 */
|
|
|
.row-color-bar {
|
|
|
width: 8px; /* 从 6px 增加到 8px */
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
-.row-pending .row-color-bar { background: #f0a020; }
|
|
|
-.row-preparing .row-color-bar { background: #18a058; }
|
|
|
-.row-ready .row-color-bar { background: #18a058; }
|
|
|
+.row-pending .row-color-bar {
|
|
|
+ background: #f0a020;
|
|
|
+}
|
|
|
+.row-preparing .row-color-bar {
|
|
|
+ background: #18a058;
|
|
|
+}
|
|
|
+.row-ready .row-color-bar {
|
|
|
+ background: #18a058;
|
|
|
+}
|
|
|
|
|
|
/* 状态标签 */
|
|
|
.status-badge {
|
|
|
@@ -775,8 +956,14 @@ onUnmounted(() => {
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
-.elapsed-time.time-warning { color: #f0a020; font-weight: 600; }
|
|
|
-.elapsed-time.time-overdue { color: #d03050; font-weight: 600; }
|
|
|
+.elapsed-time.time-warning {
|
|
|
+ color: #f0a020;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.elapsed-time.time-overdue {
|
|
|
+ color: #d03050;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
|
|
|
/* 菜品标签 */
|
|
|
.row-items {
|