新增打手端新订单语音播报开关

This commit is contained in:
yml2213
2026-08-22 09:12:26 +08:00
parent 700bda8250
commit 1f7ff016df
4 changed files with 131 additions and 0 deletions
@@ -0,0 +1,84 @@
import { SoundOutlined } from '@ant-design/icons'
import { Switch, Typography } from 'antd'
import { useEffect, useRef, useState } from 'react'
import { useQueryClient } from '@tanstack/react-query'
import {
isWorkerSpeechEnabled,
setWorkerSpeechEnabled,
speakWorkerText,
} from '@/utils/worker-speech'
/**
* 打手端新订单语音播报:开关持久化到 localStorage(默认关闭),
* 通过对比抢单大厅缓存中新出现的工单 ID 触发浏览器语音播报。
*/
export default function WorkerNewOrderSpeech() {
const queryClient = useQueryClient()
const [enabled, setEnabled] = useState(isWorkerSpeechEnabled)
const enabledRef = useRef(enabled)
useEffect(() => {
enabledRef.current = enabled
}, [enabled])
function toggle(checked: boolean) {
setEnabled(checked)
setWorkerSpeechEnabled(checked)
if (checked) speakWorkerText('新订单播报已开启')
}
useEffect(() => {
if (!enabled) return
// 首次观察到的大厅快照全部视为已知订单,避免打开开关时播报存量订单。
let seenIds: Set<number> | null = null
let flushTimer: ReturnType<typeof setTimeout> | null = null
const readHallIds = () => {
const ids = new Set<number>()
for (const [, data] of queryClient.getQueriesData({ queryKey: ['worker-hall-orders'] })) {
const pages = (data as { pages?: unknown } | undefined)?.pages
if (!Array.isArray(pages)) continue
for (const page of pages) {
const items = (page as { data?: { items?: unknown } } | null)?.data?.items
if (!Array.isArray(items)) continue
for (const item of items) {
const id = Number((item as { workOrderId?: unknown } | null)?.workOrderId || 0)
if (id > 0) ids.add(id)
}
}
}
return ids
}
const unsubscribe = queryClient.getQueryCache().subscribe(() => {
if (!enabledRef.current) return
const ids = readHallIds()
if (!seenIds) {
seenIds = ids
return
}
let hasNew = false
for (const id of ids) {
if (!seenIds.has(id)) hasNew = true
}
seenIds = ids
// 大厅刷新事件可能连发,聚合 2 秒内的多批新订单只播报一次。
if (!hasNew || flushTimer) return
flushTimer = setTimeout(() => {
flushTimer = null
speakWorkerText('您有新的订单,请及时抢单')
}, 2000)
})
return () => {
if (flushTimer) clearTimeout(flushTimer)
unsubscribe()
}
}, [enabled, queryClient])
return (
<div className="worker-speech-toggle">
<SoundOutlined />
<Typography.Text></Typography.Text>
<Switch size="small" checked={enabled} onChange={toggle} />
</div>
)
}