From 7a14272fe83a3bac3d8f00c3cd6b90af5ae2c3cf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com>
Date: Tue, 14 Jul 2026 02:05:26 +0800
Subject: [PATCH] =?UTF-8?q?fix(order):=20=E4=BF=AE=E5=A4=8D=E8=AE=A2?=
=?UTF-8?q?=E5=8D=95=E5=88=97=E8=A1=A8=E9=A1=B5=E7=99=BB=E5=BD=95=E5=90=8E?=
=?UTF-8?q?=E4=BB=8D=E7=9C=8B=E5=88=B0=E5=88=AB=E4=BA=BA=E8=AE=A2=E5=8D=95?=
=?UTF-8?q?=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 使用 userIdRef 避免闭包过期,确保加载订单时 userId 正确
- loadOrders 函数开头增加硬拦截,没有 userId 则不加载订单
- useEffect 中添加 user?.userId 判断,等待有效 userId 再加载
- useEffect 依赖数组加入 user?.userId,userId 变化时重新加载订单
feat(index): 首页铃铛接入新订单轮询与提示
- 登录后获取店员身份,使用 isClerkRef 避免闭包问题
- 通过 useNewOrderDetector 每30秒轮询门店订单,仅店员有效
- 新订单时触发震动和提示,铃铛数字角标动态显示新订单数
- 铃铛点击区分店员和普通用户,店员跳门店订单页并清除未读
- 页面展示时刷新店员身份,确保身份状态及时更新
---
.workbuddy/memory/2026-07-14.md | 35 ++++++++++++++
src/pages/index/index.tsx | 81 +++++++++++++++++++++++++++++++--
src/pages/order/list.tsx | 14 +++++-
3 files changed, 124 insertions(+), 6 deletions(-)
diff --git a/.workbuddy/memory/2026-07-14.md b/.workbuddy/memory/2026-07-14.md
index 47e8c8c..ec1369e 100644
--- a/.workbuddy/memory/2026-07-14.md
+++ b/.workbuddy/memory/2026-07-14.md
@@ -131,3 +131,38 @@ adapter `/list` 接口忽略分页,一次返回全部收藏;前端 `listShop
4. 新增 `handleOrderTabClick(status)` 和 `handleViewAllOrders()`:订单入口 `requireLogin` 拦截,并加 `redirect: '/pages/order/list'` 让登录后回跳订单页
5. 未登录时需登录的菜单项和订单按钮加 `opacity-50` 视觉降级
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` ref(ref 用于轮询回调中读取最新值,避免闭包过期)
+ 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 页切走自动停轮询
diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx
index eeb5b18..8771df3 100644
--- a/src/pages/index/index.tsx
+++ b/src/pages/index/index.tsx
@@ -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 Taro from '@tarojs/taro'
+import Taro, { useDidShow } from '@tarojs/taro'
import { useUser } from '@/hooks/useUser'
import { useShare } from '@/hooks/useShare'
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 { listCmsArticle } from '@/api/cms/cmsArticle'
import { pageShopGoods } from '@/api/shop/shopGoods'
@@ -35,6 +38,62 @@ const IndexPage: React.FC = () => {
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 = ['全部', '球拍', '球鞋', '服装', '配件']
@@ -94,6 +153,13 @@ const IndexPage: React.FC = () => {
Taro.navigateTo({ url: '/passport/login' })
return
}
+ // 店员 → 跳转门店订单管理页,有新订单时清除未读计数
+ if (isClerk) {
+ if (newOrderCount > 0) clearUnread()
+ Taro.navigateTo({ url: '/pages/store/orders/index' })
+ return
+ }
+ // 普通用户 → 跳消息通知页
Taro.navigateTo({ url: '/pages/index/notification' })
}
@@ -121,9 +187,16 @@ const IndexPage: React.FC = () => {
搜索商品
-
+
🔔
- {isLoggedIn && (
+ {isLoggedIn && newOrderCount > 0 && (
+
+
+ {newOrderCount > 99 ? '99+' : newOrderCount}
+
+
+ )}
+ {isLoggedIn && newOrderCount === 0 && (
)}
diff --git a/src/pages/order/list.tsx b/src/pages/order/list.tsx
index 2cfd7e2..9d79cd3 100644
--- a/src/pages/order/list.tsx
+++ b/src/pages/order/list.tsx
@@ -58,6 +58,10 @@ const OrderListPage: React.FC = () => {
const tabStatesRef = useRef(tabStates)
tabStatesRef.current = tabStates
+ // userId 也用 ref,防止闭包过期导致登录后仍传 undefined
+ const userIdRef = useRef(user?.userId)
+ userIdRef.current = user?.userId
+
const updateTabState = useCallback(
(index: number, partial: Partial) => {
setTabStates(prev => {
@@ -71,13 +75,17 @@ const OrderListPage: React.FC = () => {
const loadOrders = useCallback(
async (targetIndex: number, p: number) => {
+ // 没有当前登录用户 userId,禁止加载(防止看到别人的订单)
+ const userId = userIdRef.current
+ if (!userId) return
+
const state = tabStatesRef.current[targetIndex]
if (state.loading) return
updateTabState(targetIndex, { loading: true })
try {
- const params: ShopOrderParam = { page: p, limit: 10, userId: user?.userId }
+ const params: ShopOrderParam = { page: p, limit: 10, userId }
const status = tabList[targetIndex]?.status
// 使用后端 statusFilter 字段进行筛选
@@ -109,10 +117,12 @@ const OrderListPage: React.FC = () => {
requireLogin({ action: 'viewOrder', redirect: '/pages/order/list', content: '登录后才能查看订单,是否前往登录?' })
return
}
+ // 已登录但 userId 还没拿到,等下次渲染再加载
+ if (!user?.userId) return
if (!tabStates[tabIndex].initialized) {
loadOrders(tabIndex, 1)
}
- }, [tabIndex, isLoggedIn])
+ }, [tabIndex, isLoggedIn, user?.userId])
const handleLoadMore = () => {
if (!currentState.finished && !currentState.loading) {