@morehao/ark-iam-react 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of @morehao/ark-iam-react might be problematic. Click here for more details.

Files changed (66) hide show
  1. package/README.md +83 -2
  2. package/dist/_types/api/index.d.ts +5 -0
  3. package/dist/_types/api/index.js +185 -0
  4. package/dist/_types/api/index.js.map +1 -0
  5. package/dist/_types/api/request.d.ts +34 -0
  6. package/dist/_types/api/resources/auth.d.ts +13 -0
  7. package/dist/_types/api/resources/platform.d.ts +80 -0
  8. package/dist/_types/api/sessionProbe.d.ts +13 -0
  9. package/dist/_types/auth/AuthProvider.d.ts +27 -0
  10. package/dist/_types/auth/config.d.ts +23 -0
  11. package/dist/_types/auth/guards.d.ts +1 -0
  12. package/dist/_types/auth/hooks.d.ts +11 -0
  13. package/dist/_types/auth/index.d.ts +8 -0
  14. package/dist/_types/auth/index.js +190 -0
  15. package/dist/_types/auth/index.js.map +1 -0
  16. package/dist/_types/auth/tenant.d.ts +3 -0
  17. package/dist/_types/types/auth.d.ts +62 -0
  18. package/dist/_types/types/department.d.ts +52 -0
  19. package/dist/_types/types/index.d.ts +5 -0
  20. package/dist/_types/types/index.js +14 -0
  21. package/dist/_types/types/index.js.map +1 -0
  22. package/dist/_types/types/platform.d.ts +347 -0
  23. package/dist/_types/types/response.d.ts +14 -0
  24. package/dist/_types/types/tenant.d.ts +243 -0
  25. package/dist/_types/ui/AppShell.d.ts +16 -0
  26. package/dist/_types/ui/EllipsisCell.d.ts +16 -0
  27. package/dist/_types/ui/IDCell.d.ts +14 -0
  28. package/dist/_types/ui/InitialPasswordModal.d.ts +18 -0
  29. package/dist/_types/ui/LoginPage.d.ts +6 -0
  30. package/dist/_types/ui/MainLayout.d.ts +14 -0
  31. package/dist/_types/ui/NameLink.d.ts +18 -0
  32. package/dist/_types/ui/PageContainer.d.ts +15 -0
  33. package/dist/_types/ui/ProfileCenter.d.ts +6 -0
  34. package/dist/_types/ui/RemoteSelect.d.ts +57 -0
  35. package/dist/_types/ui/RowActions.d.ts +36 -0
  36. package/dist/_types/ui/TenantSwitcher.d.ts +1 -0
  37. package/dist/_types/ui/TimeCell.d.ts +53 -0
  38. package/dist/_types/ui/columns.d.ts +116 -0
  39. package/dist/_types/ui/format.d.ts +20 -0
  40. package/dist/_types/ui/index.d.ts +25 -0
  41. package/dist/_types/ui/index.js +1233 -0
  42. package/dist/_types/ui/index.js.map +1 -0
  43. package/dist/_types/ui/status.d.ts +38 -0
  44. package/dist/_types/ui/theme.d.ts +49 -0
  45. package/dist/api.d.ts +1 -0
  46. package/dist/api.js +50 -0
  47. package/dist/api.js.map +1 -0
  48. package/dist/auth.d.ts +1 -0
  49. package/dist/auth.js +17 -0
  50. package/dist/auth.js.map +1 -0
  51. package/dist/index-BQuRl5A6.js +185 -0
  52. package/dist/index-BQuRl5A6.js.map +1 -0
  53. package/dist/index-D3uUqZ5c.js +190 -0
  54. package/dist/index-D3uUqZ5c.js.map +1 -0
  55. package/dist/index-DnhMUOxg.js +1233 -0
  56. package/dist/index-DnhMUOxg.js.map +1 -0
  57. package/dist/index.d.ts +4 -0
  58. package/dist/index.js +109 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/types.d.ts +1 -0
  61. package/dist/types.js +14 -0
  62. package/dist/types.js.map +1 -0
  63. package/dist/ui.d.ts +1 -0
  64. package/dist/ui.js +46 -0
  65. package/dist/ui.js.map +1 -0
  66. package/package.json +80 -3
@@ -0,0 +1,1233 @@
1
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
+ import { useState, useCallback, useEffect, useMemo, forwardRef, useRef } from "react";
3
+ import { Dropdown, Tag, Tooltip, App, Space, Button, Popconfirm, Form, Modal, Tabs, Avatar, Descriptions, Input, message, Table, theme, Layout, Menu, Badge, Typography, Card, Alert, Select, Spin, ConfigProvider } from "antd";
4
+ import { SwapOutlined, DownOutlined, UserSwitchOutlined, LogoutOutlined, SafetyCertificateOutlined, UserOutlined, MenuUnfoldOutlined, MenuFoldOutlined, ArrowRightOutlined, CopyOutlined } from "@ant-design/icons";
5
+ import { useNavigate, useLocation, Outlet } from "react-router-dom";
6
+ import { useTenantSwitching, getCurrentTenantId, useAuthGuard, useLogout } from "@ark-iam/auth";
7
+ import { useAuth } from "react-oidc-context";
8
+ import { getPersonDetail, getSessionList, revokeAllSessions, updatePassword, revokeSession } from "@ark-iam/api";
9
+ import zhCN from "antd/locale/zh_CN";
10
+ import dayjs from "dayjs";
11
+ import "dayjs/locale/zh-cn";
12
+ const tokens = {
13
+ // 品牌 Brand:靛蓝 → 紫,仅用于主按钮 / 链接 / 选中态 / 登录与 Logo 品牌区
14
+ primary: "#4f6ef7",
15
+ primaryHover: "#6b86ff",
16
+ primaryActive: "#3a55d6",
17
+ gradient: "linear-gradient(135deg, #4f6ef7 0%, #7a5af8 55%, #a855f7 100%)",
18
+ gradientSoft: "linear-gradient(135deg, #eef2ff 0%, #f5f0ff 100%)",
19
+ purple: "#7a5af8",
20
+ // 品牌紫:分类图标/点缀强调(极少量)
21
+ selectedBg: "rgba(79, 110, 247, 0.14)",
22
+ // 深色导航选中项柔和底(非整块亮色)
23
+ // 表面 Surface(中性灰阶,禁用靛蓝淡底 tint)
24
+ bg: "#f6f7f9",
25
+ // 页面布局底色(冷白)
26
+ cardBg: "#ffffff",
27
+ sidebarBg: "#0f172a",
28
+ headerBg: "#ffffff",
29
+ tableHeaderBg: "#f7f8fa",
30
+ rowHoverBg: "#f3f4f6",
31
+ softFill: "#f1f3f5",
32
+ // 中性填充底(统计卡图标底、应用固定条等)
33
+ codeBg: "#fafafa",
34
+ // 代码/内容块底色
35
+ // 边框 Border(冷调 hairline,卡片分层的主语言)
36
+ border: "#e6e9ef",
37
+ borderStrong: "#d3d8e0",
38
+ // 更强的分割线(Header 内分割线等)
39
+ // 文字 Text(三级 hex 灰阶)
40
+ text: "#1f2430",
41
+ textSecondary: "#64748d",
42
+ // 借鉴 Stripe ink-mute
43
+ textPlaceholder: "#94a3b8",
44
+ // 空态/占位辅助文字
45
+ // 语义色 Semantic(与 antd colorSuccess/Warning/Error 对齐,仅状态 Tag/告警使用)
46
+ success: "#22c55e",
47
+ warning: "#f59e0b",
48
+ error: "#ef4444",
49
+ warningBg: "#fffbe6",
50
+ // 轻量告警底色
51
+ warningBorder: "#ffe58f"
52
+ // 轻量告警描边
53
+ };
54
+ const brand = {
55
+ primary: tokens.primary,
56
+ primaryHover: tokens.primaryHover,
57
+ primaryActive: tokens.primaryActive,
58
+ gradient: tokens.gradient,
59
+ gradientSoft: tokens.gradientSoft,
60
+ bg: tokens.bg,
61
+ sidebarBg: tokens.sidebarBg,
62
+ text: tokens.text,
63
+ textSecondary: tokens.textSecondary
64
+ };
65
+ const themeConfig = {
66
+ token: {
67
+ colorPrimary: tokens.primary,
68
+ colorInfo: tokens.primary,
69
+ colorLink: tokens.primary,
70
+ colorSuccess: tokens.success,
71
+ colorWarning: tokens.warning,
72
+ colorError: tokens.error,
73
+ colorBgLayout: tokens.bg,
74
+ colorBorderSecondary: tokens.border,
75
+ // hairline 统一为冷调
76
+ borderRadius: 8,
77
+ borderRadiusLG: 12,
78
+ colorText: tokens.text,
79
+ colorTextSecondary: tokens.textSecondary,
80
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif",
81
+ boxShadowSecondary: "0 6px 24px rgba(15, 23, 42, 0.08)"
82
+ // 仅浮层/Modal 使用
83
+ },
84
+ components: {
85
+ Layout: {
86
+ headerBg: tokens.headerBg,
87
+ headerHeight: 56,
88
+ siderBg: tokens.sidebarBg,
89
+ bodyBg: tokens.bg
90
+ },
91
+ Menu: {
92
+ darkItemBg: tokens.sidebarBg,
93
+ darkItemSelectedBg: tokens.selectedBg,
94
+ darkItemSelectedColor: "#ffffff",
95
+ darkItemHoverBg: "rgba(255,255,255,0.08)",
96
+ itemBorderRadius: 8,
97
+ itemHeight: 42
98
+ },
99
+ Card: {
100
+ headerFontSize: 15
101
+ },
102
+ Table: {
103
+ headerBg: tokens.tableHeaderBg,
104
+ headerColor: tokens.textSecondary,
105
+ rowHoverBg: tokens.rowHoverBg
106
+ },
107
+ Modal: {
108
+ borderRadiusLG: 14
109
+ }
110
+ }
111
+ };
112
+ function TenantSwitcher() {
113
+ var _a;
114
+ const { tenants, loadTenants, handleSwitchTenant } = useTenantSwitching();
115
+ const currentId = getCurrentTenantId();
116
+ const currentName = ((_a = tenants.find((t) => String(t.tenantID) === currentId)) == null ? void 0 : _a.name) || "当前租户";
117
+ return /* @__PURE__ */ jsx(
118
+ Dropdown,
119
+ {
120
+ menu: {
121
+ items: tenants.map((t) => ({
122
+ key: `tenant-${t.tenantID}`,
123
+ label: /* @__PURE__ */ jsxs("span", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
124
+ t.name,
125
+ String(t.tenantID) === currentId && /* @__PURE__ */ jsx(Tag, { color: "blue", style: { marginInlineEnd: 0 }, children: "当前" })
126
+ ] }),
127
+ disabled: String(t.tenantID) === currentId,
128
+ onClick: () => handleSwitchTenant(t.tenantID)
129
+ }))
130
+ },
131
+ onOpenChange: (open) => {
132
+ if (open) void loadTenants();
133
+ },
134
+ children: /* @__PURE__ */ jsxs(
135
+ "span",
136
+ {
137
+ style: {
138
+ color: tokens.text,
139
+ cursor: "pointer",
140
+ fontSize: 13,
141
+ lineHeight: 1,
142
+ display: "inline-flex",
143
+ alignItems: "center",
144
+ height: 32,
145
+ padding: "0 12px",
146
+ boxSizing: "border-box",
147
+ borderRadius: 8,
148
+ border: `1px solid ${tokens.borderStrong}`,
149
+ background: tokens.tableHeaderBg,
150
+ transition: "all 0.2s"
151
+ },
152
+ className: "tenant-switcher",
153
+ children: [
154
+ /* @__PURE__ */ jsx(SwapOutlined, { style: { color: tokens.primary } }),
155
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500, lineHeight: 1 }, children: currentName })
156
+ ]
157
+ }
158
+ )
159
+ }
160
+ );
161
+ }
162
+ function IDCell({ value, prefix = 8, suffix = 4 }) {
163
+ const text = value == null ? "" : String(value);
164
+ if (!text) return /* @__PURE__ */ jsx("span", { children: "-" });
165
+ const short = text.length > prefix + suffix + 1 ? `${text.slice(0, prefix)}…${text.slice(-suffix)}` : text;
166
+ return /* @__PURE__ */ jsx(
167
+ Tooltip,
168
+ {
169
+ title: text,
170
+ mouseEnterDelay: 0.3,
171
+ mouseLeaveDelay: 0.6,
172
+ styles: { body: { userSelect: "text" } },
173
+ children: /* @__PURE__ */ jsx("span", { style: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }, children: short })
174
+ }
175
+ );
176
+ }
177
+ function EllipsisCell({ value, limit, monospace = false, placeholder = "-" }) {
178
+ const text = value == null ? "" : String(value);
179
+ if (!text) return /* @__PURE__ */ jsx("span", { children: placeholder });
180
+ const display = limit && text.length > limit ? `${text.slice(0, limit)}…` : text;
181
+ return /* @__PURE__ */ jsx(
182
+ Tooltip,
183
+ {
184
+ title: text,
185
+ mouseEnterDelay: 0.3,
186
+ mouseLeaveDelay: 0.6,
187
+ styles: { body: { userSelect: "text" } },
188
+ children: /* @__PURE__ */ jsx(
189
+ "span",
190
+ {
191
+ style: {
192
+ display: "inline-block",
193
+ maxWidth: "100%",
194
+ overflow: "hidden",
195
+ textOverflow: "ellipsis",
196
+ whiteSpace: "nowrap",
197
+ verticalAlign: "bottom",
198
+ ...monospace ? { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 } : {}
199
+ },
200
+ children: display
201
+ }
202
+ )
203
+ }
204
+ );
205
+ }
206
+ function RowActions({ actions, max = 3 }) {
207
+ const { modal } = App.useApp();
208
+ const visible = actions.filter((action) => !action.hidden);
209
+ if (visible.length === 0) return null;
210
+ const renderInline = (action) => {
211
+ if (!action.confirm) {
212
+ return /* @__PURE__ */ jsx(
213
+ Button,
214
+ {
215
+ type: "link",
216
+ size: "small",
217
+ danger: action.danger,
218
+ disabled: action.disabled,
219
+ icon: action.icon,
220
+ onClick: action.onClick,
221
+ children: action.label
222
+ },
223
+ action.key
224
+ );
225
+ }
226
+ return /* @__PURE__ */ jsx(
227
+ Popconfirm,
228
+ {
229
+ title: action.confirm,
230
+ disabled: action.disabled,
231
+ onConfirm: action.onClick,
232
+ children: /* @__PURE__ */ jsx(Button, { type: "link", size: "small", danger: action.danger, disabled: action.disabled, icon: action.icon, children: action.label })
233
+ },
234
+ action.key
235
+ );
236
+ };
237
+ if (visible.length <= max) {
238
+ return /* @__PURE__ */ jsx(Space, { size: 4, children: visible.map(renderInline) });
239
+ }
240
+ const inline = visible.slice(0, Math.max(1, max - 1));
241
+ const overflow = visible.slice(Math.max(1, max - 1));
242
+ const items = overflow.map((action) => ({
243
+ key: action.key,
244
+ label: action.label,
245
+ icon: action.icon,
246
+ danger: action.danger,
247
+ disabled: action.disabled
248
+ }));
249
+ const handleMenuClick = ({ key }) => {
250
+ var _a;
251
+ const action = overflow.find((item) => item.key === key);
252
+ if (!action) return;
253
+ if (action.confirm) {
254
+ modal.confirm({
255
+ title: action.confirm,
256
+ okButtonProps: { danger: action.danger },
257
+ onOk: () => {
258
+ var _a2;
259
+ return (_a2 = action.onClick) == null ? void 0 : _a2.call(action);
260
+ }
261
+ });
262
+ return;
263
+ }
264
+ (_a = action.onClick) == null ? void 0 : _a.call(action);
265
+ };
266
+ return /* @__PURE__ */ jsxs(Space, { size: 4, children: [
267
+ inline.map(renderInline),
268
+ /* @__PURE__ */ jsx(Dropdown, { menu: { items, onClick: handleMenuClick }, trigger: ["click"], children: /* @__PURE__ */ jsxs(Button, { type: "link", size: "small", children: [
269
+ "更多 ",
270
+ /* @__PURE__ */ jsx(DownOutlined, { style: { fontSize: 10 } })
271
+ ] }) })
272
+ ] });
273
+ }
274
+ function fmtTime(value) {
275
+ if (value == null || value === "" || value === 0) return "-";
276
+ if (typeof value === "number") {
277
+ const ms = toMillis(value);
278
+ const d = new Date(ms);
279
+ if (Number.isNaN(d.getTime())) return String(value);
280
+ const pad = (n) => String(n).padStart(2, "0");
281
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
282
+ }
283
+ return value;
284
+ }
285
+ function toMillis(value) {
286
+ return value < 1e12 ? value * 1e3 : value;
287
+ }
288
+ const REL_MINUTE = 60;
289
+ const REL_HOUR = 60 * REL_MINUTE;
290
+ const REL_DAY = 24 * REL_HOUR;
291
+ const REL_MONTH = 30 * REL_DAY;
292
+ const REL_YEAR = 365 * REL_DAY;
293
+ function fmtRelativeTime(value, now = Date.now()) {
294
+ if (value == null || value === "" || value === 0) return "";
295
+ if (typeof value !== "number") return String(value);
296
+ const ms = toMillis(value);
297
+ if (Number.isNaN(ms)) return String(value);
298
+ const diff = Math.floor((now - ms) / 1e3);
299
+ if (diff < 0) return fmtTime(value);
300
+ if (diff < REL_MINUTE) return "刚刚";
301
+ if (diff < REL_HOUR) return `${Math.floor(diff / REL_MINUTE)} 分钟前`;
302
+ if (diff < REL_DAY) return `${Math.floor(diff / REL_HOUR)} 小时前`;
303
+ if (diff < REL_MONTH) return `${Math.floor(diff / REL_DAY)} 天前`;
304
+ if (diff < REL_YEAR) return `${Math.floor(diff / REL_MONTH)} 个月前`;
305
+ return `${Math.floor(diff / REL_YEAR)} 年前`;
306
+ }
307
+ const TIME_COL_WIDTH = 180;
308
+ const TIME_COL_WIDTH_RELATIVE = 120;
309
+ function TimeCell({ value, placeholder = "-", relative = false }) {
310
+ if (value == null || value === "" || value === 0) {
311
+ return /* @__PURE__ */ jsx("span", { style: { color: tokens.textPlaceholder }, children: placeholder });
312
+ }
313
+ const absolute = fmtTime(value);
314
+ if (!relative) return /* @__PURE__ */ jsx("span", { style: { whiteSpace: "nowrap" }, children: absolute });
315
+ const text = fmtRelativeTime(value);
316
+ return /* @__PURE__ */ jsx(Tooltip, { title: absolute, mouseEnterDelay: 0.3, children: /* @__PURE__ */ jsx("span", { style: { whiteSpace: "nowrap" }, children: text || absolute }) });
317
+ }
318
+ function timeColumn({
319
+ title,
320
+ dataIndex,
321
+ width,
322
+ placeholder,
323
+ relative = false
324
+ }) {
325
+ return {
326
+ title,
327
+ dataIndex,
328
+ key: dataIndex,
329
+ width: width ?? (relative ? TIME_COL_WIDTH_RELATIVE : TIME_COL_WIDTH),
330
+ render: (value) => /* @__PURE__ */ jsx(TimeCell, { value, placeholder, relative })
331
+ };
332
+ }
333
+ function ProfileCenter({ open, onClose }) {
334
+ var _a, _b;
335
+ const [person, setPerson] = useState(null);
336
+ const [sessions, setSessions] = useState([]);
337
+ const [loading, setLoading] = useState(false);
338
+ const [form] = Form.useForm();
339
+ const loadPerson = useCallback(async () => {
340
+ try {
341
+ setPerson(await getPersonDetail());
342
+ } catch {
343
+ }
344
+ }, []);
345
+ const loadSessions = useCallback(async () => {
346
+ setLoading(true);
347
+ try {
348
+ const pages = await Promise.all(
349
+ [1, 2, 3, 4, 5].map((p) => getSessionList({ page: p, pageSize: 10 }).catch(() => ({ list: [], total: 0 })))
350
+ );
351
+ const seen = /* @__PURE__ */ new Set();
352
+ const merged = [];
353
+ for (const resp of pages) {
354
+ for (const s of resp.list || []) {
355
+ if (!seen.has(s.id)) {
356
+ seen.add(s.id);
357
+ merged.push(s);
358
+ }
359
+ }
360
+ }
361
+ setSessions(merged);
362
+ } catch {
363
+ } finally {
364
+ setLoading(false);
365
+ }
366
+ }, []);
367
+ useEffect(() => {
368
+ if (open) {
369
+ void loadPerson();
370
+ void loadSessions();
371
+ }
372
+ }, [open, loadPerson, loadSessions]);
373
+ const submitPassword = async () => {
374
+ try {
375
+ const values = await form.validateFields();
376
+ if (values.newPassword !== values.confirmPassword) {
377
+ message.warning("两次输入的新密码不一致");
378
+ return;
379
+ }
380
+ await updatePassword({ oldPassword: values.oldPassword, newPassword: values.newPassword });
381
+ message.success("密码已修改");
382
+ form.resetFields();
383
+ } catch {
384
+ }
385
+ };
386
+ const columns = [
387
+ { title: "会话ID", dataIndex: "sessionID", key: "sessionID", width: 160, render: (v) => /* @__PURE__ */ jsx(IDCell, { value: v }) },
388
+ { title: "客户端", dataIndex: "clientType", key: "clientType", width: 110 },
389
+ { title: "IP", dataIndex: "clientIP", key: "clientIP", width: 130 },
390
+ {
391
+ title: "状态",
392
+ dataIndex: "isActive",
393
+ key: "isActive",
394
+ width: 90,
395
+ render: (v) => v ? /* @__PURE__ */ jsx(Tag, { color: "success", children: "活跃" }) : /* @__PURE__ */ jsx(Tag, { color: "default", children: "已失效" })
396
+ },
397
+ {
398
+ title: "UserAgent",
399
+ dataIndex: "userAgent",
400
+ key: "userAgent",
401
+ render: (v) => /* @__PURE__ */ jsx(EllipsisCell, { value: v })
402
+ },
403
+ timeColumn({ title: "创建时间", dataIndex: "createdAt" }),
404
+ {
405
+ title: "操作",
406
+ key: "action",
407
+ width: 90,
408
+ render: (_, record) => /* @__PURE__ */ jsx(
409
+ RowActions,
410
+ {
411
+ actions: [
412
+ {
413
+ key: "revoke",
414
+ label: "撤销",
415
+ danger: true,
416
+ confirm: "确认撤销该会话?",
417
+ onClick: () => {
418
+ void revokeSession(record.id).then(() => {
419
+ message.success("已撤销");
420
+ void loadSessions();
421
+ }).catch(() => message.error("撤销会话失败"));
422
+ }
423
+ }
424
+ ]
425
+ }
426
+ )
427
+ }
428
+ ];
429
+ return /* @__PURE__ */ jsx(Modal, { title: "个人中心", open, onCancel: onClose, footer: null, width: 760, styles: { body: { paddingTop: 12 } }, children: /* @__PURE__ */ jsx(
430
+ Tabs,
431
+ {
432
+ items: [
433
+ {
434
+ key: "info",
435
+ label: "个人信息",
436
+ children: /* @__PURE__ */ jsxs("div", { children: [
437
+ /* @__PURE__ */ jsxs(Space, { size: 20, align: "center", style: { marginBottom: 20 }, children: [
438
+ /* @__PURE__ */ jsx(Avatar, { size: 56, style: { background: brand.gradient, fontSize: 22 }, children: ((_b = (_a = person == null ? void 0 : person.name) == null ? void 0 : _a.charAt(0)) == null ? void 0 : _b.toUpperCase()) || "U" }),
439
+ /* @__PURE__ */ jsxs("div", { children: [
440
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 18, fontWeight: 700 }, children: [
441
+ (person == null ? void 0 : person.name) || "-",
442
+ (person == null ? void 0 : person.status) === "suspended" && /* @__PURE__ */ jsx(Tag, { color: "error", style: { marginLeft: 8 }, children: "已挂起" })
443
+ ] }),
444
+ /* @__PURE__ */ jsxs("div", { style: { color: brand.textSecondary }, children: [
445
+ "@",
446
+ (person == null ? void 0 : person.username) || "-"
447
+ ] })
448
+ ] })
449
+ ] }),
450
+ /* @__PURE__ */ jsxs(Descriptions, { column: 1, bordered: true, size: "small", children: [
451
+ /* @__PURE__ */ jsx(Descriptions.Item, { label: "账号", children: (person == null ? void 0 : person.username) ?? "-" }),
452
+ /* @__PURE__ */ jsx(Descriptions.Item, { label: "姓名", children: (person == null ? void 0 : person.name) ?? "-" }),
453
+ /* @__PURE__ */ jsx(Descriptions.Item, { label: "邮箱", children: (person == null ? void 0 : person.primaryEmail) ?? "-" }),
454
+ /* @__PURE__ */ jsx(Descriptions.Item, { label: "手机号", children: (person == null ? void 0 : person.primaryPhone) ?? "-" })
455
+ ] })
456
+ ] })
457
+ },
458
+ {
459
+ key: "password",
460
+ label: "修改密码",
461
+ children: /* @__PURE__ */ jsxs(Form, { form, layout: "vertical", style: { maxWidth: 420 }, children: [
462
+ /* @__PURE__ */ jsx(Form.Item, { name: "oldPassword", label: "原密码", rules: [{ required: true, message: "请输入原密码" }], children: /* @__PURE__ */ jsx(Input.Password, {}) }),
463
+ /* @__PURE__ */ jsx(
464
+ Form.Item,
465
+ {
466
+ name: "newPassword",
467
+ label: "新密码",
468
+ rules: [
469
+ { required: true, message: "请输入新密码" },
470
+ { min: 8, message: "至少 8 位,需含大小写字母和数字" }
471
+ ],
472
+ children: /* @__PURE__ */ jsx(Input.Password, {})
473
+ }
474
+ ),
475
+ /* @__PURE__ */ jsx(
476
+ Form.Item,
477
+ {
478
+ name: "confirmPassword",
479
+ label: "确认新密码",
480
+ rules: [{ required: true, message: "请再次输入新密码" }],
481
+ children: /* @__PURE__ */ jsx(Input.Password, {})
482
+ }
483
+ ),
484
+ /* @__PURE__ */ jsx(Button, { type: "primary", onClick: () => void submitPassword(), children: "确认修改" })
485
+ ] })
486
+ },
487
+ {
488
+ key: "session",
489
+ label: "会话管理",
490
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
491
+ /* @__PURE__ */ jsxs(Space, { style: { marginBottom: 12 }, children: [
492
+ /* @__PURE__ */ jsx(
493
+ Button,
494
+ {
495
+ danger: true,
496
+ onClick: () => void revokeAllSessions().then(() => {
497
+ message.success("已撤销全部会话");
498
+ void loadSessions();
499
+ }).catch(() => message.error("撤销全部会话失败")),
500
+ children: "撤销全部会话"
501
+ }
502
+ ),
503
+ /* @__PURE__ */ jsxs("span", { style: { color: brand.textSecondary, fontSize: 12 }, children: [
504
+ "共 ",
505
+ sessions.length,
506
+ " 条(含历史失效会话)"
507
+ ] })
508
+ ] }),
509
+ /* @__PURE__ */ jsx(
510
+ Table,
511
+ {
512
+ rowKey: "id",
513
+ columns,
514
+ dataSource: sessions,
515
+ loading,
516
+ pagination: { pageSize: 10, showSizeChanger: false, showTotal: (t) => `共 ${t} 条` },
517
+ scroll: { x: 820 }
518
+ }
519
+ )
520
+ ] })
521
+ }
522
+ ]
523
+ }
524
+ ) });
525
+ }
526
+ const { Header, Sider, Content } = Layout;
527
+ function MainLayout({ title, subtitle, menuItems, hasTenantSwitch = true }) {
528
+ const [collapsed, setCollapsed] = useState(false);
529
+ const [profileOpen, setProfileOpen] = useState(false);
530
+ const navigate = useNavigate();
531
+ const location = useLocation();
532
+ const auth = useAuth();
533
+ useAuthGuard();
534
+ const logout = useLogout();
535
+ const { token } = theme.useToken();
536
+ const displayName = useMemo(() => {
537
+ var _a;
538
+ const profile = (_a = auth.user) == null ? void 0 : _a.profile;
539
+ return (profile == null ? void 0 : profile.name) || (profile == null ? void 0 : profile.preferred_username) || "IAM 用户";
540
+ }, [auth.user]);
541
+ const userMenuItems = useMemo(
542
+ () => [
543
+ { key: "profile", icon: /* @__PURE__ */ jsx(UserSwitchOutlined, {}), label: "个人中心", onClick: () => setProfileOpen(true) },
544
+ { type: "divider" },
545
+ { key: "logout", icon: /* @__PURE__ */ jsx(LogoutOutlined, {}), label: "退出登录", onClick: () => void logout() }
546
+ ],
547
+ [logout]
548
+ );
549
+ const selectedKey = useMemo(() => {
550
+ const parts = location.pathname.split("/");
551
+ const level1 = parts[1] ? `/${parts[1]}` : "/";
552
+ const level2 = parts.length > 2 ? `/${parts[1]}/${parts[2]}` : "";
553
+ const flat = (items) => items.flatMap((i) => [i.key, ...i.children ? flat(i.children) : []]);
554
+ const all = flat(menuItems);
555
+ if (all.includes(level2)) return level2;
556
+ if (all.includes(level1)) return level1;
557
+ return "/";
558
+ }, [location.pathname, menuItems]);
559
+ return /* @__PURE__ */ jsxs(Layout, { style: { minHeight: "100vh" }, children: [
560
+ /* @__PURE__ */ jsxs(
561
+ Sider,
562
+ {
563
+ trigger: null,
564
+ collapsible: true,
565
+ collapsed,
566
+ width: 232,
567
+ theme: "dark",
568
+ style: { position: "sticky", top: 0, height: "100vh" },
569
+ children: [
570
+ /* @__PURE__ */ jsxs(
571
+ "div",
572
+ {
573
+ style: {
574
+ height: 64,
575
+ display: "flex",
576
+ alignItems: "center",
577
+ justifyContent: "center",
578
+ gap: 10,
579
+ background: brand.gradient,
580
+ color: "#fff",
581
+ overflow: "hidden"
582
+ },
583
+ children: [
584
+ /* @__PURE__ */ jsx(SafetyCertificateOutlined, { style: { fontSize: collapsed ? 20 : 24 } }),
585
+ !collapsed && /* @__PURE__ */ jsxs("div", { style: { lineHeight: 1.1 }, children: [
586
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 16, fontWeight: 700, letterSpacing: 0.5 }, children: title }),
587
+ subtitle && /* @__PURE__ */ jsx("div", { style: { fontSize: 11, opacity: 0.8 }, children: subtitle })
588
+ ] })
589
+ ]
590
+ }
591
+ ),
592
+ /* @__PURE__ */ jsx(
593
+ Menu,
594
+ {
595
+ theme: "dark",
596
+ mode: "inline",
597
+ selectedKeys: [selectedKey],
598
+ defaultOpenKeys: [selectedKey],
599
+ items: menuItems,
600
+ onClick: ({ key }) => {
601
+ if (key !== location.pathname) navigate(key);
602
+ },
603
+ style: { padding: "8px 10px", borderInlineEnd: "none" }
604
+ }
605
+ )
606
+ ]
607
+ }
608
+ ),
609
+ /* @__PURE__ */ jsxs(Layout, { children: [
610
+ /* @__PURE__ */ jsxs(
611
+ Header,
612
+ {
613
+ style: {
614
+ padding: "0 20px",
615
+ background: "#fff",
616
+ display: "flex",
617
+ alignItems: "center",
618
+ justifyContent: "space-between",
619
+ borderBottom: `1px solid ${tokens.border}`,
620
+ position: "sticky",
621
+ top: 0,
622
+ zIndex: 10,
623
+ lineHeight: 1
624
+ },
625
+ children: [
626
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, lineHeight: "normal" }, children: [
627
+ /* @__PURE__ */ jsx(Tooltip, { title: collapsed ? "展开菜单" : "收起菜单", children: /* @__PURE__ */ jsx(ButtonIcon, { collapsed, onClick: () => setCollapsed(!collapsed) }) }),
628
+ hasTenantSwitch && /* @__PURE__ */ jsxs(Fragment, { children: [
629
+ /* @__PURE__ */ jsx("div", { style: { width: 1, height: 22, background: tokens.borderStrong } }),
630
+ /* @__PURE__ */ jsx(TenantSwitcher, {})
631
+ ] })
632
+ ] }),
633
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", gap: 14, lineHeight: "normal" }, children: /* @__PURE__ */ jsx(Dropdown, { menu: { items: userMenuItems }, placement: "bottomRight", trigger: ["click"], children: /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10, height: 42, padding: "0 8px", lineHeight: 1, borderRadius: 8, cursor: "pointer" }, className: "user-entry", children: [
634
+ /* @__PURE__ */ jsx(Badge, { dot: true, color: brand.primary, offset: [-2, 2], children: /* @__PURE__ */ jsx(
635
+ Avatar,
636
+ {
637
+ size: 34,
638
+ style: { background: brand.gradient, fontWeight: 600, fontSize: 14 },
639
+ icon: !displayName ? /* @__PURE__ */ jsx(UserOutlined, {}) : void 0,
640
+ children: displayName.charAt(0).toUpperCase()
641
+ }
642
+ ) }),
643
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 14, color: token.colorText, fontWeight: 500, lineHeight: 1 }, children: displayName })
644
+ ] }) }) })
645
+ ]
646
+ }
647
+ ),
648
+ /* @__PURE__ */ jsx(Content, { style: { margin: 20 }, children: /* @__PURE__ */ jsx(Outlet, {}) })
649
+ ] }),
650
+ /* @__PURE__ */ jsx(ProfileCenter, { open: profileOpen, onClose: () => setProfileOpen(false) })
651
+ ] });
652
+ }
653
+ const ButtonIcon = forwardRef(function ButtonIcon2({ collapsed, onClick }, ref) {
654
+ return /* @__PURE__ */ jsx(
655
+ "button",
656
+ {
657
+ ref,
658
+ onClick,
659
+ style: {
660
+ width: 36,
661
+ height: 36,
662
+ display: "flex",
663
+ alignItems: "center",
664
+ justifyContent: "center",
665
+ border: "none",
666
+ borderRadius: 8,
667
+ background: "transparent",
668
+ cursor: "pointer",
669
+ fontSize: 16,
670
+ color: brand.textSecondary
671
+ },
672
+ children: collapsed ? /* @__PURE__ */ jsx(MenuUnfoldOutlined, {}) : /* @__PURE__ */ jsx(MenuFoldOutlined, {})
673
+ }
674
+ );
675
+ });
676
+ function LoginPage({ title, subtitle }) {
677
+ const auth = useAuth();
678
+ return /* @__PURE__ */ jsxs(
679
+ "div",
680
+ {
681
+ style: {
682
+ minHeight: "100vh",
683
+ display: "flex",
684
+ alignItems: "stretch",
685
+ background: brand.gradientSoft
686
+ },
687
+ children: [
688
+ /* @__PURE__ */ jsxs(
689
+ "div",
690
+ {
691
+ style: {
692
+ flex: 1.2,
693
+ display: "flex",
694
+ flexDirection: "column",
695
+ justifyContent: "center",
696
+ padding: "0 8vw",
697
+ background: brand.gradient,
698
+ color: "#fff",
699
+ position: "relative",
700
+ overflow: "hidden"
701
+ },
702
+ children: [
703
+ /* @__PURE__ */ jsx(
704
+ "div",
705
+ {
706
+ style: {
707
+ position: "absolute",
708
+ width: 420,
709
+ height: 420,
710
+ borderRadius: "50%",
711
+ background: "rgba(255,255,255,0.08)",
712
+ top: -120,
713
+ right: -120
714
+ }
715
+ }
716
+ ),
717
+ /* @__PURE__ */ jsx(
718
+ "div",
719
+ {
720
+ style: {
721
+ position: "absolute",
722
+ width: 260,
723
+ height: 260,
724
+ borderRadius: "50%",
725
+ background: "rgba(255,255,255,0.06)",
726
+ bottom: -60,
727
+ left: -60
728
+ }
729
+ }
730
+ ),
731
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 14, marginBottom: 28, position: "relative" }, children: [
732
+ /* @__PURE__ */ jsx(
733
+ "div",
734
+ {
735
+ style: {
736
+ width: 52,
737
+ height: 52,
738
+ borderRadius: 14,
739
+ background: "rgba(255,255,255,0.18)",
740
+ display: "flex",
741
+ alignItems: "center",
742
+ justifyContent: "center",
743
+ fontSize: 26,
744
+ backdropFilter: "blur(4px)"
745
+ },
746
+ children: /* @__PURE__ */ jsx(SafetyCertificateOutlined, {})
747
+ }
748
+ ),
749
+ /* @__PURE__ */ jsxs("div", { children: [
750
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 22, fontWeight: 700 }, children: title }),
751
+ subtitle && /* @__PURE__ */ jsx("div", { style: { fontSize: 13, opacity: 0.85 }, children: subtitle })
752
+ ] })
753
+ ] }),
754
+ /* @__PURE__ */ jsx(Typography.Title, { level: 2, style: { color: "#fff", marginBottom: 12, position: "relative" }, children: "统一身份认证平台" }),
755
+ /* @__PURE__ */ jsx(Typography.Paragraph, { style: { color: "rgba(255,255,255,0.82)", fontSize: 15, maxWidth: 480, position: "relative" }, children: "一个平台管理多应用的账号、权限与安全策略。基于标准 OIDC/OAuth2 协议, 支持单点登录、多租户切换与细粒度权限控制。" }),
756
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 24, marginTop: 28, position: "relative" }, children: ["单点登录 SSO", "多租户隔离", "细粒度授权"].map((f) => /* @__PURE__ */ jsx(
757
+ "div",
758
+ {
759
+ style: {
760
+ padding: "8px 16px",
761
+ borderRadius: 20,
762
+ background: "rgba(255,255,255,0.14)",
763
+ fontSize: 13,
764
+ backdropFilter: "blur(4px)"
765
+ },
766
+ children: f
767
+ },
768
+ f
769
+ )) })
770
+ ]
771
+ }
772
+ ),
773
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", padding: 40 }, children: /* @__PURE__ */ jsxs(
774
+ "div",
775
+ {
776
+ style: {
777
+ width: 380,
778
+ background: "#fff",
779
+ borderRadius: 16,
780
+ boxShadow: "0 20px 60px rgba(15,23,42,0.12)",
781
+ padding: "40px 36px"
782
+ },
783
+ children: [
784
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 20, fontWeight: 700, marginBottom: 8 }, children: "欢迎回来" }),
785
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 14, color: brand.textSecondary, marginBottom: 32 }, children: [
786
+ "使用统一身份账号登录 ",
787
+ title
788
+ ] }),
789
+ /* @__PURE__ */ jsxs(
790
+ Button,
791
+ {
792
+ type: "primary",
793
+ size: "large",
794
+ block: true,
795
+ loading: auth.isLoading,
796
+ onClick: () => void auth.signinRedirect(),
797
+ style: { height: 46, borderRadius: 10, fontWeight: 600, background: brand.gradient, border: "none", boxShadow: "0 8px 20px rgba(79,110,247,0.35)" },
798
+ children: [
799
+ "IAM 账号登录 ",
800
+ /* @__PURE__ */ jsx(ArrowRightOutlined, {})
801
+ ]
802
+ }
803
+ ),
804
+ /* @__PURE__ */ jsx("div", { style: { marginTop: 24, textAlign: "center", fontSize: 12, color: brand.textSecondary }, children: "登录即代表同意平台的安全与隐私策略" })
805
+ ]
806
+ }
807
+ ) })
808
+ ]
809
+ }
810
+ );
811
+ }
812
+ function PageContainer({ title, description, extra, children, loading }) {
813
+ return /* @__PURE__ */ jsxs("div", { children: [
814
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 16 }, children: [
815
+ /* @__PURE__ */ jsxs("div", { children: [
816
+ /* @__PURE__ */ jsx(Typography.Title, { level: 4, style: { margin: 0, fontWeight: 700 }, children: title }),
817
+ description && /* @__PURE__ */ jsx(Typography.Text, { type: "secondary", style: { fontSize: 13 }, children: description })
818
+ ] }),
819
+ extra && /* @__PURE__ */ jsx(Space, { children: extra })
820
+ ] }),
821
+ /* @__PURE__ */ jsx(
822
+ Card,
823
+ {
824
+ loading,
825
+ styles: {
826
+ body: { padding: 20 }
827
+ },
828
+ style: { borderRadius: 12, border: `1px solid ${tokens.border}` },
829
+ children
830
+ }
831
+ )
832
+ ] });
833
+ }
834
+ const monospaceStyle = { fontFamily: "Consolas, Monaco, monospace" };
835
+ function InitialPasswordModal({ password, title, description, onClose }) {
836
+ const handleCopy = async () => {
837
+ try {
838
+ await navigator.clipboard.writeText(password);
839
+ message.success("已复制");
840
+ } catch {
841
+ message.error("复制失败,请手动复制");
842
+ }
843
+ };
844
+ return /* @__PURE__ */ jsx(
845
+ Modal,
846
+ {
847
+ title: title || "初始密码已生成",
848
+ open: !!password,
849
+ onCancel: onClose,
850
+ footer: /* @__PURE__ */ jsx(Button, { type: "primary", onClick: onClose, children: "我已保存" }),
851
+ destroyOnClose: true,
852
+ width: 560,
853
+ children: /* @__PURE__ */ jsx(
854
+ Alert,
855
+ {
856
+ type: "warning",
857
+ showIcon: true,
858
+ message: "请立即保存,关闭后不再显示",
859
+ description: /* @__PURE__ */ jsxs(Space, { direction: "vertical", size: 12, style: { width: "100%" }, children: [
860
+ description && /* @__PURE__ */ jsx("div", { style: { color: tokens.textSecondary }, children: description }),
861
+ /* @__PURE__ */ jsx(
862
+ "div",
863
+ {
864
+ style: {
865
+ ...monospaceStyle,
866
+ wordBreak: "break-all",
867
+ background: tokens.warningBg,
868
+ border: `1px solid ${tokens.warningBorder}`,
869
+ borderRadius: 8,
870
+ padding: "10px 12px"
871
+ },
872
+ children: password
873
+ }
874
+ ),
875
+ /* @__PURE__ */ jsx(Button, { size: "small", icon: /* @__PURE__ */ jsx(CopyOutlined, {}), onClick: () => void handleCopy(), children: "复制" })
876
+ ] })
877
+ }
878
+ )
879
+ }
880
+ );
881
+ }
882
+ function NameLink({ value, onClick, title, monospace = false }) {
883
+ const empty = value == null || value === "";
884
+ const text = empty ? "-" : value;
885
+ const style = {
886
+ display: "inline-block",
887
+ fontWeight: 500,
888
+ maxWidth: "100%",
889
+ overflow: "hidden",
890
+ textOverflow: "ellipsis",
891
+ whiteSpace: "nowrap",
892
+ verticalAlign: "bottom",
893
+ ...monospace ? { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 } : {}
894
+ };
895
+ if (empty || !onClick) {
896
+ return /* @__PURE__ */ jsx("span", { style, children: text });
897
+ }
898
+ const tooltip = title ?? (typeof value === "string" || typeof value === "number" ? String(value) : void 0);
899
+ return /* @__PURE__ */ jsx(Tooltip, { title: tooltip, mouseEnterDelay: 0.3, children: /* @__PURE__ */ jsx(Typography.Link, { onClick, style, children: text }) });
900
+ }
901
+ const ID_COL_WIDTH = 130;
902
+ const NAME_COL_WIDTH = 180;
903
+ const CODE_COL_WIDTH = 150;
904
+ const TAG_COL_WIDTH = 110;
905
+ const STATUS_COL_WIDTH = 100;
906
+ const COUNT_COL_WIDTH = 90;
907
+ const TEXT_COL_WIDTH = 200;
908
+ const LONG_TEXT_COL_WIDTH = 320;
909
+ const CELL_PADDING = 32;
910
+ const ACTION_BUTTON_BUDGET = 56;
911
+ const ACTION_GAP = 4;
912
+ function idColumn({ dataIndex, title = "ID", width }) {
913
+ return {
914
+ title,
915
+ dataIndex,
916
+ key: dataIndex,
917
+ width: width ?? ID_COL_WIDTH,
918
+ render: (value) => /* @__PURE__ */ jsx(IDCell, { value })
919
+ };
920
+ }
921
+ function textColumn({
922
+ title,
923
+ dataIndex,
924
+ width,
925
+ placeholder,
926
+ monospace
927
+ }) {
928
+ return {
929
+ title,
930
+ dataIndex,
931
+ key: dataIndex,
932
+ width: width ?? TEXT_COL_WIDTH,
933
+ render: (value) => /* @__PURE__ */ jsx(
934
+ EllipsisCell,
935
+ {
936
+ value,
937
+ placeholder,
938
+ monospace
939
+ }
940
+ )
941
+ };
942
+ }
943
+ function nameColumn({
944
+ title,
945
+ dataIndex,
946
+ width,
947
+ monospace,
948
+ onClick
949
+ }) {
950
+ return {
951
+ title,
952
+ dataIndex,
953
+ key: dataIndex,
954
+ width: width ?? NAME_COL_WIDTH,
955
+ render: (value, record) => /* @__PURE__ */ jsx(
956
+ NameLink,
957
+ {
958
+ value,
959
+ monospace,
960
+ onClick: onClick ? () => onClick(record) : void 0
961
+ }
962
+ )
963
+ };
964
+ }
965
+ function actionColumn({ actions, max = 3, width }) {
966
+ return {
967
+ title: "操作",
968
+ key: "action",
969
+ fixed: "right",
970
+ width: width ?? actionColumnWidth(max),
971
+ render: (_, record) => /* @__PURE__ */ jsx(RowActions, { actions: actions(record), max })
972
+ };
973
+ }
974
+ function actionColumnWidth(max) {
975
+ const inline = Math.max(1, max);
976
+ const raw = CELL_PADDING + inline * ACTION_BUTTON_BUDGET + (inline - 1) * ACTION_GAP;
977
+ return Math.ceil(raw / 10) * 10;
978
+ }
979
+ function tableScrollX(columns) {
980
+ let total = 0;
981
+ const walk = (list) => {
982
+ var _a;
983
+ for (const column of list) {
984
+ if ((_a = column.children) == null ? void 0 : _a.length) {
985
+ walk(column.children);
986
+ continue;
987
+ }
988
+ total += typeof column.width === "number" ? column.width : 0;
989
+ }
990
+ };
991
+ walk(columns);
992
+ return { x: total };
993
+ }
994
+ function useRemoteOptions({ fetchOptions, debounceMs = 300, labelCache }) {
995
+ const [options, setOptions] = useState([]);
996
+ const [loading, setLoading] = useState(false);
997
+ const internalLabelsRef = useRef(/* @__PURE__ */ new Map());
998
+ const labels = labelCache ?? internalLabelsRef.current;
999
+ const timerRef = useRef(null);
1000
+ const seqRef = useRef(0);
1001
+ const search = useCallback(
1002
+ async (keyword) => {
1003
+ const seq = ++seqRef.current;
1004
+ setLoading(true);
1005
+ try {
1006
+ const list = await fetchOptions(keyword);
1007
+ if (seq !== seqRef.current) return;
1008
+ for (const item of list) labels.set(item.value, item.label);
1009
+ setOptions(list);
1010
+ } catch {
1011
+ } finally {
1012
+ if (seq === seqRef.current) setLoading(false);
1013
+ }
1014
+ },
1015
+ [fetchOptions, labels]
1016
+ );
1017
+ useEffect(
1018
+ () => () => {
1019
+ if (timerRef.current) clearTimeout(timerRef.current);
1020
+ },
1021
+ []
1022
+ );
1023
+ const handleSearch = useCallback(
1024
+ (keyword) => {
1025
+ if (timerRef.current) clearTimeout(timerRef.current);
1026
+ timerRef.current = setTimeout(() => void search(keyword), debounceMs);
1027
+ },
1028
+ [search, debounceMs]
1029
+ );
1030
+ const labelOf = useCallback(
1031
+ (v) => {
1032
+ var _a;
1033
+ return labels.get(v) ?? ((_a = options.find((o) => o.value === v)) == null ? void 0 : _a.label) ?? v;
1034
+ },
1035
+ [labels, options]
1036
+ );
1037
+ const withSelected = useCallback(
1038
+ (values) => {
1039
+ const missing = values.filter((v) => !options.some((o) => o.value === v));
1040
+ return missing.length ? [...missing.map((v) => ({ value: v, label: labelOf(v) })), ...options] : options;
1041
+ },
1042
+ [labelOf, options]
1043
+ );
1044
+ const remember = useCallback(
1045
+ (values) => values.map((v) => {
1046
+ const label = labelOf(v);
1047
+ labels.set(v, label);
1048
+ return { value: v, label };
1049
+ }),
1050
+ [labelOf, labels]
1051
+ );
1052
+ return { loading, search, handleSearch, withSelected, remember, labels };
1053
+ }
1054
+ function RemoteSelect({
1055
+ value,
1056
+ onChange,
1057
+ initialLabel,
1058
+ placeholder,
1059
+ disabled,
1060
+ allowClear,
1061
+ width,
1062
+ style,
1063
+ id,
1064
+ ...rest
1065
+ }) {
1066
+ const remote = useRemoteOptions(rest);
1067
+ useEffect(() => {
1068
+ if (value && initialLabel) remote.labels.set(value, initialLabel);
1069
+ }, [value, initialLabel, remote.labels]);
1070
+ useEffect(() => {
1071
+ if (disabled) return;
1072
+ void remote.search("");
1073
+ }, [remote.search, disabled]);
1074
+ const handleChange = (next) => {
1075
+ onChange == null ? void 0 : onChange(next, next ? remote.remember([next])[0] : void 0);
1076
+ };
1077
+ return /* @__PURE__ */ jsx(
1078
+ Select,
1079
+ {
1080
+ id,
1081
+ showSearch: true,
1082
+ filterOption: false,
1083
+ allowClear,
1084
+ placeholder,
1085
+ disabled,
1086
+ loading: remote.loading,
1087
+ style: width ? { width, ...style } : style,
1088
+ value,
1089
+ onChange: handleChange,
1090
+ onSearch: remote.handleSearch,
1091
+ options: remote.withSelected(value ? [value] : []),
1092
+ notFoundContent: remote.loading ? /* @__PURE__ */ jsx(Spin, { size: "small" }) : null
1093
+ }
1094
+ );
1095
+ }
1096
+ function RemoteMultiSelect({
1097
+ value,
1098
+ onChange,
1099
+ initialLabels,
1100
+ placeholder,
1101
+ disabled,
1102
+ allowClear,
1103
+ width,
1104
+ style,
1105
+ id,
1106
+ ...rest
1107
+ }) {
1108
+ const remote = useRemoteOptions(rest);
1109
+ const values = value ?? [];
1110
+ useEffect(() => {
1111
+ if (!initialLabels) return;
1112
+ Object.entries(initialLabels).forEach(([v, label]) => {
1113
+ if (v && label) remote.labels.set(v, label);
1114
+ });
1115
+ }, [initialLabels, remote.labels]);
1116
+ useEffect(() => {
1117
+ if (disabled) return;
1118
+ void remote.search("");
1119
+ }, [remote.search, disabled]);
1120
+ const handleChange = (next) => {
1121
+ const nextValues = next ?? [];
1122
+ onChange == null ? void 0 : onChange(nextValues, nextValues.length ? remote.remember(nextValues) : []);
1123
+ };
1124
+ return /* @__PURE__ */ jsx(
1125
+ Select,
1126
+ {
1127
+ id,
1128
+ mode: "multiple",
1129
+ showSearch: true,
1130
+ filterOption: false,
1131
+ allowClear,
1132
+ placeholder,
1133
+ disabled,
1134
+ loading: remote.loading,
1135
+ style: width ? { width, ...style } : style,
1136
+ value: values,
1137
+ onChange: handleChange,
1138
+ onSearch: remote.handleSearch,
1139
+ options: remote.withSelected(values),
1140
+ notFoundContent: remote.loading ? /* @__PURE__ */ jsx(Spin, { size: "small" }) : null
1141
+ }
1142
+ );
1143
+ }
1144
+ dayjs.locale("zh-cn");
1145
+ function AppShell({ children }) {
1146
+ return /* @__PURE__ */ jsxs("div", { style: { fontVariantNumeric: "tabular-nums" }, children: [
1147
+ /* @__PURE__ */ jsx("style", { children: `
1148
+ .ant-select-dropdown .ant-select-tree .ant-select-tree-switcher {
1149
+ width: 0;
1150
+ overflow: hidden;
1151
+ }
1152
+ ` }),
1153
+ /* @__PURE__ */ jsx(ConfigProvider, { locale: zhCN, theme: themeConfig, children: /* @__PURE__ */ jsx(App, { children }) })
1154
+ ] });
1155
+ }
1156
+ function EnableTag({ value }) {
1157
+ if (value === "enable") {
1158
+ return /* @__PURE__ */ jsx(Tag, { color: "success", children: "启用" });
1159
+ }
1160
+ if (value === "disable") {
1161
+ return /* @__PURE__ */ jsx(Tag, { color: "default", children: "停用" });
1162
+ }
1163
+ return /* @__PURE__ */ jsx(Tag, { children: value || "-" });
1164
+ }
1165
+ function SuspendedTag({ value }) {
1166
+ return value === "suspended" ? /* @__PURE__ */ jsx(Tag, { color: "error", children: "挂起" }) : /* @__PURE__ */ jsx(Tag, { color: "success", children: "正常" });
1167
+ }
1168
+ function VerifiedTag({ value }) {
1169
+ return value === "verified" ? /* @__PURE__ */ jsx(Tag, { color: "success", children: "已验证" }) : /* @__PURE__ */ jsx(Tag, { color: "warning", children: "未验证" });
1170
+ }
1171
+ const TYPE_META = {
1172
+ platform: { label: "平台", color: "geekblue" },
1173
+ customer: { label: "客户", color: "cyan" }
1174
+ };
1175
+ function TypeTag({ value }) {
1176
+ const meta = TYPE_META[value ?? ""];
1177
+ return meta ? /* @__PURE__ */ jsx(Tag, { color: meta.color, children: meta.label }) : /* @__PURE__ */ jsx(Tag, { children: value || "-" });
1178
+ }
1179
+ const SOURCE_META = {
1180
+ builtin: { label: "内置", color: "gold" },
1181
+ first_party: { label: "第一方", color: "blue" },
1182
+ third_party: { label: "第三方", color: "orange" },
1183
+ custom: { label: "自定义", color: "blue" }
1184
+ };
1185
+ function SourceTag({ value }) {
1186
+ const meta = SOURCE_META[value ?? ""];
1187
+ return meta ? /* @__PURE__ */ jsx(Tag, { color: meta.color, children: meta.label }) : /* @__PURE__ */ jsx(Tag, { children: value || "-" });
1188
+ }
1189
+ export {
1190
+ AppShell,
1191
+ CODE_COL_WIDTH,
1192
+ COUNT_COL_WIDTH,
1193
+ EllipsisCell,
1194
+ EnableTag,
1195
+ IDCell,
1196
+ ID_COL_WIDTH,
1197
+ InitialPasswordModal,
1198
+ LONG_TEXT_COL_WIDTH,
1199
+ LoginPage,
1200
+ MainLayout,
1201
+ NAME_COL_WIDTH,
1202
+ NameLink,
1203
+ PageContainer,
1204
+ ProfileCenter,
1205
+ RemoteMultiSelect,
1206
+ RemoteSelect,
1207
+ RowActions,
1208
+ STATUS_COL_WIDTH,
1209
+ SourceTag,
1210
+ SuspendedTag,
1211
+ TAG_COL_WIDTH,
1212
+ TEXT_COL_WIDTH,
1213
+ TIME_COL_WIDTH,
1214
+ TIME_COL_WIDTH_RELATIVE,
1215
+ TenantSwitcher,
1216
+ TimeCell,
1217
+ TypeTag,
1218
+ VerifiedTag,
1219
+ actionColumn,
1220
+ actionColumnWidth,
1221
+ brand,
1222
+ fmtRelativeTime,
1223
+ fmtTime,
1224
+ idColumn,
1225
+ nameColumn,
1226
+ tableScrollX,
1227
+ textColumn,
1228
+ themeConfig,
1229
+ timeColumn,
1230
+ toMillis,
1231
+ tokens
1232
+ };
1233
+ //# sourceMappingURL=index.js.map