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) {