fix(order): 修复订单列表页登录后仍看到别人订单问题

- 使用 userIdRef 避免闭包过期,确保加载订单时 userId 正确
- loadOrders 函数开头增加硬拦截,没有 userId 则不加载订单
- useEffect 中添加 user?.userId 判断,等待有效 userId 再加载
- useEffect 依赖数组加入 user?.userId,userId 变化时重新加载订单

feat(index): 首页铃铛接入新订单轮询与提示

- 登录后获取店员身份,使用 isClerkRef 避免闭包问题
- 通过 useNewOrderDetector 每30秒轮询门店订单,仅店员有效
- 新订单时触发震动和提示,铃铛数字角标动态显示新订单数
- 铃铛点击区分店员和普通用户,店员跳门店订单页并清除未读
- 页面展示时刷新店员身份,确保身份状态及时更新
This commit is contained in:
2026-07-14 02:05:26 +08:00
parent 596fa27d60
commit 7a14272fe8
3 changed files with 124 additions and 6 deletions

View File

@@ -131,3 +131,38 @@ adapter `/list` 接口忽略分页,一次返回全部收藏;前端 `listShop
4. 新增 `handleOrderTabClick(status)``handleViewAllOrders()`:订单入口 `requireLogin` 拦截,并加 `redirect: '/pages/order/list'` 让登录后回跳订单页 4. 新增 `handleOrderTabClick(status)``handleViewAllOrders()`:订单入口 `requireLogin` 拦截,并加 `redirect: '/pages/order/list'` 让登录后回跳订单页
5. 未登录时需登录的菜单项和订单按钮加 `opacity-50` 视觉降级 5. 未登录时需登录的菜单项和订单按钮加 `opacity-50` 视觉降级
6. 「帮助中心」「设置」不需要登录,保持原样 6. 「帮助中心」「设置」不需要登录,保持原样
## 订单列表页登录后仍看到别人订单 Bug 修复2026-07-14
### 问题
`pages/order/list` 刚登录成功后仍能看到别人的订单,刷新重进后才正常。
### 根因
`loadOrders``useCallback` 依赖数组为 `[updateTabState]`,未包含 `user`,导致**闭包过期**——登录后 `isLoggedIn``true` 触发 effect 调用 `loadOrders`,但闭包中的 `user` 仍是初始渲染时的 `null``userId: user?.userId` 传了 `undefined`,后端未按用户过滤返回了全部订单。
### 修改
- **`src/pages/order/list.tsx`**
1. 新增 `userIdRef``useRef(user?.userId)`),每渲染同步 `userIdRef.current = user?.userId`,避免闭包过期
2. `loadOrders` 开头从 `userIdRef.current` 取 userId`if (!userId) return` 硬拦截——没有当前登录用户 userId 就禁止加载
3. `useEffect` 中增加 `if (!user?.userId) return` 判断,已登录但 userId 还没拿到时等待下次渲染
4. `useEffect` 依赖数组加 `user?.userId`userId 变化时重新触发加载
## 首页铃铛接入新订单轮询2026-07-14
### 需求
首页顶部搜索栏右侧铃铛图标,门店店员有新订单时能收到提醒。
### 改动
- **`src/pages/index/index.tsx`**
1. 导入 `useNewOrderDetector``getMyClerk``pageShopOrder``useDidShow``useCallback``useRef`
2. 登录后调 `getMyClerk()` 检测店员身份,存 `isClerk` state + `isClerkRef` refref 用于轮询回调中读取最新值,避免闭包过期)
3. `useDidShow` 时也刷新店员身份
4. 接入 `useNewOrderDetector`30秒轮询 `pageShopOrder({page:1,limit:5})``fetchLatestOrders` 内用 `isClerkRef.current` 网关——非店员直接返回空数组(不建 baseline无副作用
5. 新订单回调:`Taro.vibrateShort` 震动 + `Taro.showToast` 提示"您有 N 条新订单"
6. 铃铛角标从静态红点改为动态:`newOrderCount > 0` 显示数字角标(>99 显示 99+`=== 0` 时显示小圆点
7. 点击处理:店员 → `navigateTo` 门店订单页(有新订单时先 `clearUnread()`);普通用户 → 通知页
### 设计要点
- `isClerkRef` 解决 hooks 闭包过期问题——`fetchLatestOrders``useCallback` 依赖空数组,但通过 ref 读取最新店员状态
- 非店员时 `fetchLatestOrders` 返回 `[]`hook 内 `doCheck``list.length === 0` 时 return`knownOrderIdsRef` 保持 `null` 不建 baseline店员身份确认后首次真实请求才建 baseline不会误报
- `useNewOrderDetector` 内部已处理 `useDidShow`/`useDidHide` 生命周期tabBar 页切走自动停轮询

View File

@@ -1,9 +1,12 @@
import React, { useState, useEffect } from 'react' import React, { useState, useEffect, useCallback, useRef } from 'react'
import { View, Text, ScrollView, Swiper, SwiperItem, Image } from '@tarojs/components' import { View, Text, ScrollView, Swiper, SwiperItem, Image } from '@tarojs/components'
import Taro from '@tarojs/taro' import Taro, { useDidShow } from '@tarojs/taro'
import { useUser } from '@/hooks/useUser' import { useUser } from '@/hooks/useUser'
import { useShare } from '@/hooks/useShare' import { useShare } from '@/hooks/useShare'
import { useScrollHeight } from '@/hooks/useScrollHeight' import { useScrollHeight } from '@/hooks/useScrollHeight'
import { useNewOrderDetector } from '@/hooks/useNewOrderDetector'
import { getMyClerk } from '@/api/shop/shopStoreUser'
import { pageShopOrder } from '@/api/shop/shopOrder'
import { listCmsAd } from '@/api/cms/cmsAd' import { listCmsAd } from '@/api/cms/cmsAd'
import { listCmsArticle } from '@/api/cms/cmsArticle' import { listCmsArticle } from '@/api/cms/cmsArticle'
import { pageShopGoods } from '@/api/shop/shopGoods' import { pageShopGoods } from '@/api/shop/shopGoods'
@@ -35,6 +38,62 @@ const IndexPage: React.FC = () => {
enableCopyUrl: true, enableCopyUrl: true,
}) })
// ─── 门店店员身份检测 ──────────────────────────────────────────
const [isClerk, setIsClerk] = useState(false)
const isClerkRef = useRef(false)
// 登录后检查是否为门店店员
useEffect(() => {
if (!isLoggedIn) {
setIsClerk(false)
isClerkRef.current = false
return
}
getMyClerk()
.then(data => {
const clerk = !!data
setIsClerk(clerk)
isClerkRef.current = clerk
})
.catch(() => {
setIsClerk(false)
isClerkRef.current = false
})
}, [isLoggedIn])
// 页面重新显示时也刷新店员身份(可能从其他页面回来时状态变了)
useDidShow(() => {
if (isLoggedIn) {
getMyClerk()
.then(data => {
const clerk = !!data
setIsClerk(clerk)
isClerkRef.current = clerk
})
.catch(() => {
isClerkRef.current = false
})
}
})
// ─── 新订单轮询检测(仅店员生效)────────────────────────────────
const { newOrderCount, clearUnread } = useNewOrderDetector({
interval: 30000, // 30 秒轮询一次
fetchLatestOrders: useCallback(() => {
// 非店员不请求返回空数组baseline 不会建立,无副作用)
if (!isClerkRef.current) return Promise.resolve([])
return pageShopOrder({ page: 1, limit: 5 }).then(r => r?.list || [])
}, []),
onNewOrders: useCallback((count) => {
Taro.vibrateShort({ type: 'medium' })
Taro.showToast({
title: `您有 ${count} 条新订单`,
icon: 'none',
duration: 2500,
})
}, []),
})
const categories = ['全部', '球拍', '球鞋', '服装', '配件'] const categories = ['全部', '球拍', '球鞋', '服装', '配件']
@@ -94,6 +153,13 @@ const IndexPage: React.FC = () => {
Taro.navigateTo({ url: '/passport/login' }) Taro.navigateTo({ url: '/passport/login' })
return return
} }
// 店员 → 跳转门店订单管理页,有新订单时清除未读计数
if (isClerk) {
if (newOrderCount > 0) clearUnread()
Taro.navigateTo({ url: '/pages/store/orders/index' })
return
}
// 普通用户 → 跳消息通知页
Taro.navigateTo({ url: '/pages/index/notification' }) Taro.navigateTo({ url: '/pages/index/notification' })
} }
@@ -121,9 +187,16 @@ const IndexPage: React.FC = () => {
<Text className='text-gray-400 text-sm flex-1'></Text> <Text className='text-gray-400 text-sm flex-1'></Text>
</View> </View>
</View> </View>
<View onClick={handleMessageClick}> <View onClick={handleMessageClick} className='relative'>
<Text className='text-xl'>🔔</Text> <Text className='text-xl'>🔔</Text>
{isLoggedIn && ( {isLoggedIn && newOrderCount > 0 && (
<View className='absolute -top-1 -right-1 min-w-[16px] h-4 px-1 bg-red-500 rounded-full flex items-center justify-center'>
<Text className='text-white text-[10px] leading-none font-medium'>
{newOrderCount > 99 ? '99+' : newOrderCount}
</Text>
</View>
)}
{isLoggedIn && newOrderCount === 0 && (
<View className='absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full' /> <View className='absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full' />
)} )}
</View> </View>

View File

@@ -58,6 +58,10 @@ const OrderListPage: React.FC = () => {
const tabStatesRef = useRef(tabStates) const tabStatesRef = useRef(tabStates)
tabStatesRef.current = tabStates tabStatesRef.current = tabStates
// userId 也用 ref防止闭包过期导致登录后仍传 undefined
const userIdRef = useRef(user?.userId)
userIdRef.current = user?.userId
const updateTabState = useCallback( const updateTabState = useCallback(
(index: number, partial: Partial<TabState>) => { (index: number, partial: Partial<TabState>) => {
setTabStates(prev => { setTabStates(prev => {
@@ -71,13 +75,17 @@ const OrderListPage: React.FC = () => {
const loadOrders = useCallback( const loadOrders = useCallback(
async (targetIndex: number, p: number) => { async (targetIndex: number, p: number) => {
// 没有当前登录用户 userId禁止加载防止看到别人的订单
const userId = userIdRef.current
if (!userId) return
const state = tabStatesRef.current[targetIndex] const state = tabStatesRef.current[targetIndex]
if (state.loading) return if (state.loading) return
updateTabState(targetIndex, { loading: true }) updateTabState(targetIndex, { loading: true })
try { try {
const params: ShopOrderParam = { page: p, limit: 10, userId: user?.userId } const params: ShopOrderParam = { page: p, limit: 10, userId }
const status = tabList[targetIndex]?.status const status = tabList[targetIndex]?.status
// 使用后端 statusFilter 字段进行筛选 // 使用后端 statusFilter 字段进行筛选
@@ -109,10 +117,12 @@ const OrderListPage: React.FC = () => {
requireLogin({ action: 'viewOrder', redirect: '/pages/order/list', content: '登录后才能查看订单,是否前往登录?' }) requireLogin({ action: 'viewOrder', redirect: '/pages/order/list', content: '登录后才能查看订单,是否前往登录?' })
return return
} }
// 已登录但 userId 还没拿到,等下次渲染再加载
if (!user?.userId) return
if (!tabStates[tabIndex].initialized) { if (!tabStates[tabIndex].initialized) {
loadOrders(tabIndex, 1) loadOrders(tabIndex, 1)
} }
}, [tabIndex, isLoggedIn]) }, [tabIndex, isLoggedIn, user?.userId])
const handleLoadMore = () => { const handleLoadMore = () => {
if (!currentState.finished && !currentState.loading) { if (!currentState.finished && !currentState.loading) {