feat(store): 新增门店新订单提醒功能

- 新增 useNewOrderDetector Hook,实现30秒轮询检测新订单
- 订单管理页集成该 Hook,新增订单时震动+Toast提醒
- “全部”Tab 显示新订单红点,切换Tab清空未读计数
- 门店中心页增加待处理订单角标,统一待审核和待处理显示逻辑
- 底部新增“接收新订单提醒”订阅消息授权入口,调用微信接口申请授权
- 文档中补充订阅消息模板ID及字段说明,待后端配合公众号推送消息
This commit is contained in:
2026-07-04 12:10:02 +08:00
parent 994695c405
commit 5c8acfd19c
6 changed files with 286 additions and 14 deletions

View File

@@ -26,3 +26,5 @@ export { useCoupon } from './useCoupon'
export { usePagination } from './usePagination'
export { usePayment } from './usePayment'
export { useScrollHeight } from './useScrollHeight'
export { useNewOrderDetector } from './useNewOrderDetector'
export type { NewOrderItem, UseNewOrderDetectorOptions, UseNewOrderDetectorReturn } from './useNewOrderDetector'

View File

@@ -0,0 +1,129 @@
import { useRef, useState, useCallback } from 'react'
import { useDidShow, useDidHide } from '@tarojs/taro'
export interface NewOrderItem {
orderId?: number
orderNo?: string
[key: string]: unknown
}
export interface UseNewOrderDetectorOptions {
/** 轮询间隔(毫秒),默认 3000030秒 */
interval?: number
/** 获取最新订单列表(按时间降序),返回前几条用于对比 */
fetchLatestOrders: () => Promise<NewOrderItem[]>
/** 发现新订单时的回调 */
onNewOrders?: (count: number, newOrders: NewOrderItem[]) => void
}
export interface UseNewOrderDetectorReturn {
/** 未读新订单数 */
newOrderCount: number
/** 新订单列表 */
newOrders: NewOrderItem[]
/** 清除未读计数(用户查看后调用) */
clearUnread: () => void
/** 手动触发一次检测 */
checkNow: () => Promise<void>
}
/**
* 新订单轮询检测 Hook
*
* 用于门店订单管理页面,轮询检测是否有新订单产生。
* 页面显示时自动启动轮询,隐藏时暂停,避免无效请求。
*
* @example
* const { newOrderCount, newOrders, clearUnread } = useNewOrderDetector({
* fetchLatestOrders: () => pageShopOrder({ page: 1, limit: 5 }),
* onNewOrders: (count) => Taro.vibrateShort({ type: 'medium' }),
* })
*/
export function useNewOrderDetector(
options: UseNewOrderDetectorOptions,
): UseNewOrderDetectorReturn {
const { interval = 30000, fetchLatestOrders, onNewOrders } = options
// 已见过的所有 orderId 集合(初始化时需要先加载一次建立基准)
const knownOrderIdsRef = useRef<Set<number> | null>(null)
// 轮询定时器
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
// 是否正在检测中(防止并发)
const detectingRef = useRef(false)
const [newOrderCount, setNewOrderCount] = useState(0)
const [newOrders, setNewOrders] = useState<NewOrderItem[]>([])
/** 执行一次检测 */
const doCheck = useCallback(async () => {
if (detectingRef.current) return
detectingRef.current = true
try {
const list = await fetchLatestOrders()
if (!list || list.length === 0) return
// 首次加载:建立基准线,不触发提醒
if (knownOrderIdsRef.current === null) {
knownOrderIdsRef.current = new Set(
list.map(o => o.orderId).filter(Boolean) as number[],
)
return
}
// 对比检测新订单
const freshOrders: NewOrderItem[] = []
const newIds: number[] = []
for (const order of list) {
const id = order.orderId
if (id != null && !knownOrderIdsRef.current.has(id)) {
freshOrders.push(order)
newIds.push(id)
knownOrderIdsRef.current.add(id)
}
}
if (freshOrders.length > 0) {
setNewOrderCount(prev => prev + freshOrders.length)
setNewOrders(prev => [...freshOrders, ...prev])
onNewOrders?.(freshOrders.length, freshOrders)
}
} catch {
// 静默失败,不影响页面正常使用
} finally {
detectingRef.current = false
}
}, [fetchLatestOrders, onNewOrders])
/** 清除未读 */
const clearUnread = useCallback(() => {
setNewOrderCount(0)
setNewOrders([])
}, [])
/** 手动触发检测 */
const checkNow = useCallback(async () => {
await doCheck()
}, [doCheck])
// 页面显示时:初始化基准线 + 启动轮询
useDidShow(() => {
// 首次加载建立基准(不触发提醒)
doCheck()
// 启动定时轮询
if (timerRef.current) clearInterval(timerRef.current)
timerRef.current = setInterval(doCheck, interval)
})
// 页面隐藏时:停止轮询
useDidHide(() => {
if (timerRef.current) {
clearInterval(timerRef.current)
timerRef.current = null
}
})
return { newOrderCount, newOrders, clearUnread, checkNow }
}