feat: Z38 grant Yuheng agents ready-to-use access

Complete permissions during every bind and token path, expose effective access in the console, and keep the platform return path globally available.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-08-07 16:11:13 +08:00
parent 4f9087bd38
commit 43dc708f1e
11 changed files with 285 additions and 145 deletions

File diff suppressed because one or more lines are too long

2
web/dist/index.html vendored
View File

@@ -7,7 +7,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,600;9..144,700&family=Manrope:wght@400;500;600;700&display=swap" rel="stylesheet" />
<script type="module" crossorigin src="/assets/index-C41vYD6l.js"></script>
<script type="module" crossorigin src="/assets/index-aOZIXFeC.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CtqUfnO-.css">
</head>
<body>

View File

@@ -245,6 +245,23 @@ export function AgentUsersPage(props: {
title: "角色",
render: (_, r) => r.role_name || r.role_code || "—",
},
{
title: "权限",
dataIndex: "permissions",
width: 260,
render: (permissions: string[]) =>
(permissions || []).length ? (
<Space size={[4, 4]} wrap>
{(permissions || []).map((p) => (
<Tag key={p} color={["读取模块", "发布模块", "数据同步"].includes(permLabel(p)) ? "blue" : undefined}>
{permLabel(p)}
</Tag>
))}
</Space>
) : (
<Typography.Text type="secondary"></Typography.Text>
),
},
{
title: "可访问模块",
dataIndex: "app_slugs",

View File

@@ -676,7 +676,7 @@ export default function App() {
busy={busy}
onTabChange={onConsoleTabChange}
onOpenApp={onOpenApp}
onSession={(s) => setSession(s)}
onSession={persist}
onLogout={() => {
clearSession();
setSession(null);

View File

@@ -17,6 +17,7 @@ import {
ApartmentOutlined,
ApiOutlined,
AuditOutlined,
ArrowLeftOutlined,
BankOutlined,
CloudSyncOutlined,
KeyOutlined,
@@ -31,7 +32,7 @@ import {
MenuFoldOutlined,
MenuUnfoldOutlined,
} from "@ant-design/icons";
import { bindPhone, changePassword, saveSession, type Session } from "./api";
import { bindPhone, changePassword, exitPlatformTenant, saveSession, type Session } from "./api";
import { hasEntitlement, isCompanyAdmin, isPlatformAdmin, roleLabel } from "./agentPerms";
const { Header, Sider, Content } = Layout;
@@ -93,6 +94,7 @@ export function ConsoleLayout({
const [pwdBusy, setPwdBusy] = useState(false);
const [phoneOpen, setPhoneOpen] = useState(false);
const [phoneBusy, setPhoneBusy] = useState(false);
const [exitTenantBusy, setExitTenantBusy] = useState(false);
const [pwdForm] = Form.useForm<{ old_password: string; new_password: string; confirm: string }>();
const [phoneForm] = Form.useForm<{ phone: string }>();
const { token } = theme.useToken();
@@ -201,7 +203,30 @@ export function ConsoleLayout({
)}
<Tag color="green">{roleLabel(session.role || "管理员")}</Tag>
{isPlatformAdmin(session.role) && session.tenantId > 0 ? (
<Tag color="orange">{session.tenantName || `公司 #${session.tenantId}`}</Tag>
<>
<Tag color="orange">{session.tenantName || `公司 #${session.tenantId}`}</Tag>
<Button
size="small"
icon={<ArrowLeftOutlined />}
loading={exitTenantBusy}
onClick={async () => {
setExitTenantBusy(true);
try {
const next = await exitPlatformTenant(session);
saveSession(next);
onSession?.(next);
onTabChange("platform");
message.success("已回到平台工作台");
} catch (e: any) {
message.error(e.message || String(e));
} finally {
setExitTenantBusy(false);
}
}}
>
</Button>
</>
) : session.tenantId > 0 ? (
<Tag> #{session.tenantId}</Tag>
) : null}