{!navCollapsed &&
{group.title}
}
{group.items.map((item) => {
@@ -216,8 +318,43 @@ export default function Layout({ children }: { children: React.ReactNode }) {
))}
- {/* 底部:服务状态 + 工具 */}
+ {/* 底部:当前用户 + 服务状态 + 工具 */}
+ {showUser && (
+
+
+ {username} · {roleLabel}
+
+ setPasswordOpen(true)}>修改密码
+
+
+ 退出登录
+
+
+ }
+ >
+
+
+ )}
{!navCollapsed && (
@@ -242,7 +379,25 @@ export default function Layout({ children }: { children: React.ReactNode }) {
-
{children}
+
+ {pending ? (
+
+
+
+ ) : denied ? (
+ requiredPerm === 'user.manage' && me && !me.auth_enabled ? (
+
+ ) : (
+
+ )
+ ) : (
+ children
+ )}
+
+ {showUser &&
setPasswordOpen(false)} />}
)
}
diff --git a/app/(app)/streamers/page.tsx b/app/(app)/streamers/page.tsx
index 1fe63e1..36da6fe 100644
--- a/app/(app)/streamers/page.tsx
+++ b/app/(app)/streamers/page.tsx
@@ -43,6 +43,7 @@ import {
import StreamerCard, { LiveAvatar } from '@/app/ui/StreamerCard'
import { CardRateSwitch } from '@/app/ui/LiveRateChart'
import PageHeader from '../components/PageHeader'
+import { useMe } from '@/app/lib/use-me'
import styles from './page.module.scss'
const { Content } = Layout
@@ -73,6 +74,13 @@ export default function StreamersPage() {
return map
}, [infos])
+ const { can } = useMe()
+ const canEdit = can('streamer.edit')
+ const canControl = can('recording.control')
+ const canHooks = can('streamer.hooks')
+ // 批量操作至少要能暂停或删除其一,否则连勾选框都不显示
+ const canBatch = canEdit || canControl
+
// ---- 增删改 ----
const { trigger: deleteStreamers } = useSWRMutation('/v1/streamers', requestDelete)
const { trigger: updateStreamers } = useSWRMutation('/v1/streamers', put)
@@ -225,23 +233,33 @@ export default function StreamersPage() {
// 编辑 / 暂停 / 删除 / 高级四个操作,网格卡片与列表行共用,只是外层容器不同
// 返回数组而不是 Fragment:ButtonGroup 会对每个直接子元素 cloneElement 注入 disabled 等 props,
// React 19 起会对带这些 props 的 Fragment 报 "Invalid prop supplied to React.Fragment"
- const actionButtons = (item: LiveStreamerEntity) => [
-
- } aria-label="编辑" />
- ,
-
,
-
onConfirm(item.id)}>
- } aria-label="删除" />
- ,
-
- } aria-label="高级" />
- ,
- ]
- const renderActions = (item: LiveStreamerEntity) => (
-
- {actionButtons(item)}
-
- )
+ // 按角色只渲染有权限的按钮(只读观察者一个都没有)
+ const actionButtons = (item: LiveStreamerEntity) =>
+ [
+ canEdit && (
+
+ } aria-label="编辑" />
+
+ ),
+ canControl &&
,
+ canEdit && (
+
onConfirm(item.id)}>
+ } aria-label="删除" />
+
+ ),
+ canHooks && (
+
+ } aria-label="高级" />
+
+ ),
+ ].filter(Boolean)
+ const hasActions = canEdit || canControl || canHooks
+ const renderActions = (item: LiveStreamerEntity) =>
+ hasActions ? (
+
+ {actionButtons(item)}
+
+ ) : null
const renderRowActions = (item: LiveStreamerEntity) => (
{actionButtons(item)}
)
@@ -250,14 +268,18 @@ export default function StreamersPage() {
<>
}
actions={
-
- } theme="solid">
- 新建
-
-
+ canEdit && (
+
+ } theme="solid">
+ 新建
+
+
+ )
}
/>
@@ -330,15 +352,18 @@ export default function StreamersPage() {
{/* 批量操作条 */}
- {selected.size > 0 && (
+ {canBatch && selected.size > 0 && (
已选 {selected.size} 项
-
+ {canControl && (
+
+ )}
+ {canEdit && (
+ )}
@@ -361,7 +387,9 @@ export default function StreamersPage() {
description={
streamers && streamers.length > 0
? '调整搜索或筛选条件试试'
- : '点击右上角「新建」开始'
+ : canEdit
+ ? '点击右上角「新建」开始'
+ : '管理员添加直播间后会显示在这里'
}
/>
@@ -381,21 +409,23 @@ export default function StreamersPage() {