工作台: 新订单提醒

This commit is contained in:
Kroytz committed 2026-07-08 21:18:21 +08:00
1 parent d6743b94d3
commit aef417409a
2 files changed
+121 -1

No files matched your search

@@ -4549,6 +4549,9 @@ INSERT INTO `infra_config` VALUES (7, 'url', 2, 'MySQL 监控的地址', 'url.dr
INSERT INTO `infra_config` VALUES (8, 'url', 2, 'SkyWalking 监控的地址', 'url.skywalking', '', b'1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:57:03', b'0');
INSERT INTO `infra_config` VALUES (9, 'url', 2, 'Spring Boot Admin 监控的地址', 'url.spring-boot-admin', '', b'1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:52:07', b'0');
INSERT INTO `infra_config` VALUES (10, 'url', 2, 'Swagger 接口文档的地址', 'url.swagger', '', b'1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:59:00', b'0');
INSERT INTO `infra_config` VALUES (11, 'order', 2, '订单工作台-轮询间隔', 'order.work.poll.interval', '10000', b'1', '订单工作台自动刷新轮询间隔(毫秒),范围 3000-300000,默认 10000', '1', '2026-07-08 00:00:00', '1', '2026-07-08 00:00:00', b'0');
INSERT INTO `infra_config` VALUES (12, 'order', 2, '订单工作台-新订单通知开关', 'order.work.notify.enabled', 'true', b'1', '订单工作台来新订单时是否通知(true/false),默认 true', '1', '2026-07-08 00:00:00', '1', '2026-07-08 00:00:00', b'0');
INSERT INTO `infra_config` VALUES (13, 'order', 2, '订单工作台-通知声音开关', 'order.work.notify.sound', 'true', b'1', '订单工作台新订单通知是否播放声音(true/false),默认 true', '1', '2026-07-08 00:00:00', '1', '2026-07-08 00:00:00', b'0');
COMMIT;
-- ----------------------------
@@ -109,6 +109,7 @@ import StoreOrderRemark from './StoreOrderRemark.vue'
import OrderDetail from './OrderDetail.vue'
import OrderRecord from './OrderRecord.vue'
import type { TabsPaneContext } from 'element-plus'
import { ElNotification } from 'element-plus'
import { formatDate } from '@/utils/formatTime'
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
@@ -140,14 +141,115 @@ const handleClick = (tab: TabsPaneContext, event: Event) => {
}
/** 新订单通知(可开关,由 infra 配置 order.work.notify.* 控制) */
const NOTIFY_ENABLED_KEY = 'order.work.notify.enabled'
const NOTIFY_SOUND_KEY = 'order.work.notify.sound'
let notifyEnabled = true // 通知总开关,默认开
let soundEnabled = true // 声音开关,默认开
let lastMaxOrderId: number | null = null // 上次轮询的最大订单 id,用于判断新订单
const newOrderCount = ref(0) // 未读新订单数(用于标题提示)
const originalTitle = typeof document !== 'undefined' ? document.title : ''
/** 读取通知配置 */
const loadNotifyConfig = async () => {
try {
const [enabled, sound] = await Promise.all([
ConfigApi.getConfigKey(NOTIFY_ENABLED_KEY),
ConfigApi.getConfigKey(NOTIFY_SOUND_KEY)
])
// 默认 true,仅显式 'false' 才关闭
notifyEnabled = enabled !== 'false'
soundEnabled = sound !== 'false'
} catch {}
}
/** 更新浏览器标题显示未读数 */
const updateTitle = () => {
if (!document) return
document.title = newOrderCount.value > 0
? `(${newOrderCount.value}) 新订单 - ${originalTitle}`
: originalTitle
}
/** 播放提示音(Web Audio API 合成,无需音频文件) */
let audioCtx: AudioContext | null = null
const playBeep = () => {
try {
const Ctx = window.AudioContext || (window as any).webkitAudioContext
if (!Ctx) return
if (!audioCtx) audioCtx = new Ctx()
// 连响两声,更易引起注意
const beepAt = (startAt: number) => {
const osc = audioCtx!.createOscillator()
const gain = audioCtx!.createGain()
osc.connect(gain)
gain.connect(audioCtx!.destination)
osc.type = 'sine'
osc.frequency.value = 880
gain.gain.setValueAtTime(0.3, audioCtx!.currentTime + startAt)
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx!.currentTime + startAt + 0.3)
osc.start(audioCtx!.currentTime + startAt)
osc.stop(audioCtx!.currentTime + startAt + 0.3)
}
beepAt(0)
beepAt(0.35)
} catch {}
}
/** 系统通知(页面隐藏时使用,需已授权) */
const notifySystem = (count: number) => {
if (!('Notification' in window)) return
if (Notification.permission !== 'granted') return
new Notification('新订单提醒', { body: `收到 ${count} 个新订单,请及时处理` })
}
/** 首次点击时请求桌面通知授权(浏览器要求用户手势,故挂在 pointerdown 上,一次性) */
const requestNotifyPermissionOnce = () => {
if (!('Notification' in window)) return
if (Notification.permission === 'default') {
Notification.requestPermission()
}
}
/** 触发新订单通知 */
const notifyNewOrders = (count: number) => {
newOrderCount.value += count
updateTitle()
if (!notifyEnabled) return
if (soundEnabled) playBeep()
if (document.hidden) {
notifySystem(count)
} else {
ElNotification({
title: '新订单提醒',
message: `收到 ${count} 个新订单,请及时处理`,
type: 'success',
duration: 5000
})
}
}
/** 检测新订单:对比本次与上次的订单 id */
const checkNewOrders = (orders: any[]) => {
if (!orders || orders.length === 0) return
const ids = orders.map((o) => Number(o.id))
const currentMaxId = Math.max(...ids)
if (lastMaxOrderId !== null && currentMaxId > lastMaxOrderId) {
const newCount = ids.filter((id) => id > lastMaxOrderId!).length
if (newCount > 0) notifyNewOrders(newCount)
}
lastMaxOrderId = currentMaxId
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await StoreOrderApi.getStoreOrderPage(queryParams)
list.value = data.list
//console.log("aa:",list.value)
total.value = data.total
checkNewOrders(data.list)
} finally {
loading.value = false
}
@@ -260,6 +362,12 @@ const handleVisibilityChange = async () => {
} else {
getList()
await loadPollInterval()
await loadNotifyConfig()
// 回到页面,清零未读新订单计数
if (newOrderCount.value > 0) {
newOrderCount.value = 0
updateTitle()
}
startPolling()
}
}
@@ -268,13 +376,22 @@ const handleVisibilityChange = async () => {
onMounted(async () => {
getList()
await loadPollInterval()
await loadNotifyConfig()
startPolling()
document.addEventListener('visibilitychange', handleVisibilityChange)
// 首次点击请求桌面通知授权(浏览器要求用户手势)
document.addEventListener('pointerdown', requestNotifyPermissionOnce, { once: true })
})
onUnmounted(() => {
stopPolling()
document.removeEventListener('visibilitychange', handleVisibilityChange)
document.removeEventListener('pointerdown', requestNotifyPermissionOnce)
// 离开页面恢复标题
if (newOrderCount.value > 0) {
newOrderCount.value = 0
document.title = originalTitle
}
})
</script>