fix(admin): 顶栏常驻显示当前商户上下文并提供退出入口

问题:管理员的商户上下文存在 localStorage,之前只会在「平台商户」页点「成员」时被
写入,界面上没有任何提示,而且只在登录/登出时清除。管理员点过一次商户后,订单/商品/
钱包/成员等商户后台页面会一直停在该商户视角,而首页看板仍是平台口径——两个数字
看起来互相矛盾却无法解释。

- MainLayout 顶栏为管理员常驻显示「当前商户:xxx」,并用 Tooltip 说明哪些页面受它影响
- 提供「退出商户视角」按钮:清除 localStorage 后整页刷新,保证当前页已加载的数据
  也按新上下文重新取一遍(只在上下文确实存在时出现)
- README 管理端章节同步说明

未做静默回退的取舍:商户上下文解析失败时后端仍返回 403。因为商户后台存在写操作
(人工下单、取消、成员管理),若自动回退到默认商户,写入会落到错误的商户上——
这是比"多点一下退出"严重得多的问题。
This commit is contained in:
yml2213
2026-10-06 20:40:09 +08:00
parent e692790aa7
commit 04c3369e6d
2 changed files with 27 additions and 0 deletions
+1
View File
@@ -205,6 +205,7 @@ POST /api/open/v1/orders/ship-notify
实际上订单页只显示了自营商户这一家。
商户上下文的来源是前端 `localStorage.merchant_id`,只会在「平台商户」页点「成员」时被写入。
顶栏会常驻显示「当前商户:xxx」,点旁边的「退出商户视角」即可回到默认商户(自营商户)。
需要看某个下游商户的订单时,用「下游订单」页的商户筛选即可,不必切换上下文。
## 目录结构
+26
View File
@@ -229,6 +229,16 @@ export default function MainLayout() {
return localStorage.getItem(SIDEBAR_COLLAPSED_KEY) === 'true'
})
const { user, logout, refreshProfile, isAdmin, merchant, merchantPermissions } = useAuth()
// 管理员的商户上下文来自 localStorage(即请求头 X-Merchant-ID 的来源),
// 之前只会在「平台商户」页点「成员」时被写入,界面上没有任何提示——
// 管理员很容易以为自己看的是全平台数据,实际被静默框定在某个商户里。
const [activeMerchantId] = useState<string | null>(() => localStorage.getItem('merchant_id'))
const merchantContextLabel = merchant?.name || (activeMerchantId ? `商户 #${activeMerchantId}` : '默认商户')
const exitMerchantContext = () => {
localStorage.removeItem('merchant_id')
// 整页刷新:上下文变了,当前页已经加载的数据也必须按新上下文重新取一遍。
window.location.reload()
}
const [passwordOpen, setPasswordOpen] = useState(false)
const [passwordSaving, setPasswordSaving] = useState(false)
const [passwordForm] = Form.useForm()
@@ -421,6 +431,22 @@ export default function MainLayout() {
<div className="app-header__right">
{isAdmin && (
<Space size="small">
<Tooltip title="商户后台页面(发货订单 / 商品 / 钱包 / 成员等)都按这个商户隔离;首页看板与「下游订单」是平台口径,不受它影响">
<Tag
color={activeMerchantId ? 'orange' : 'default'}
bordered={false}
style={{ margin: 0, fontSize: 11.5, fontWeight: 600 }}
>
当前商户:{merchantContextLabel}
</Tag>
</Tooltip>
{activeMerchantId && (
<Tooltip title="回到你的默认商户上下文(自营商户)">
<Button type="text" size="small" onClick={exitMerchantContext}>
退出商户视角
</Button>
</Tooltip>
)}
<Tooltip title="开放 API 接口文档">
<Button
type="text"