com-angel-authorization 1.0.14 → 1.0.15

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.
@@ -1957,8 +1957,852 @@ var styles2 = {
1957
1957
  }
1958
1958
  };
1959
1959
 
1960
+ // src/react/RoleManager.tsx
1961
+ import {
1962
+ useCallback as useCallback4,
1963
+ useEffect as useEffect4,
1964
+ useRef,
1965
+ useState as useState4
1966
+ } from "react";
1967
+
1968
+ // src/roles/index.ts
1969
+ function mapSystemRole(item) {
1970
+ if (!item || typeof item !== "object") return null;
1971
+ const row = item;
1972
+ const roleId = row.roleId ?? row.role_id ?? row.id;
1973
+ if (roleId === void 0 || roleId === null || roleId === "") return null;
1974
+ return {
1975
+ roleId: String(roleId),
1976
+ name: String(row.name ?? ""),
1977
+ description: String(row.description ?? row.desc ?? "")
1978
+ };
1979
+ }
1980
+ function mapPermissionTreeNode(item) {
1981
+ if (!item || typeof item !== "object") return null;
1982
+ const row = item;
1983
+ const resourceId = row.resourceId ?? row.resource_id ?? row.id;
1984
+ if (resourceId === void 0 || resourceId === null || resourceId === "") return null;
1985
+ const rawChildren = row.children ?? row.childList ?? row.child_list ?? [];
1986
+ const children = Array.isArray(rawChildren) ? rawChildren.map(mapPermissionTreeNode).filter((node) => node !== null) : [];
1987
+ return {
1988
+ resourceId: String(resourceId),
1989
+ name: String(row.name ?? ""),
1990
+ children
1991
+ };
1992
+ }
1993
+ function asObject(value) {
1994
+ return value && typeof value === "object" ? value : null;
1995
+ }
1996
+ function mapRolePermissions(payload) {
1997
+ const envelope = asObject(payload) ?? {};
1998
+ const data = asObject(envelope.data) ?? envelope;
1999
+ const rawIds = data.resourceIds ?? data.resource_ids ?? [];
2000
+ const resourceIds = Array.isArray(rawIds) ? rawIds.map((id) => id === void 0 || id === null ? "" : String(id)).filter(Boolean) : [];
2001
+ const rawTree = data.resourceTree ?? data.resource_tree ?? [];
2002
+ const resourceTree = Array.isArray(rawTree) ? rawTree.map(mapPermissionTreeNode).filter((node) => node !== null) : [];
2003
+ return { resourceIds, resourceTree };
2004
+ }
2005
+ function collectTreeResourceIds(node) {
2006
+ return [node.resourceId, ...node.children.flatMap(collectTreeResourceIds)];
2007
+ }
2008
+ function buildListUrl2(params) {
2009
+ const parts = [];
2010
+ appendQueryParam(parts, "keyword", params?.keyword ?? "");
2011
+ appendQueryParam(parts, "page-token", params?.pagination?.pageToken ?? "");
2012
+ appendQueryParam(parts, "page-size", params?.pagination?.pageSize ?? "20");
2013
+ appendQueryParam(parts, "page-direction", params?.pagination?.pageDirection ?? "current");
2014
+ return parts.length ? `/system/roles?${parts.join("&")}` : "/system/roles";
2015
+ }
2016
+ function buildCreateRoleBody(values) {
2017
+ return {
2018
+ name: values.name.trim(),
2019
+ description: values.description.trim()
2020
+ };
2021
+ }
2022
+ function buildUpdateRoleBody(values) {
2023
+ return {
2024
+ name: values.name.trim(),
2025
+ description: values.description.trim()
2026
+ };
2027
+ }
2028
+ function buildSavePermissionsBody(resourceIds) {
2029
+ return {
2030
+ resourceIds: [...new Set(resourceIds.map(String).filter(Boolean))]
2031
+ };
2032
+ }
2033
+ function createSystemRoleApi(request) {
2034
+ return {
2035
+ async list(params, signal) {
2036
+ const json = await request({
2037
+ method: "GET",
2038
+ url: buildListUrl2(params),
2039
+ signal
2040
+ });
2041
+ const records = extractRecords(json).map(mapSystemRole).filter((item) => item !== null);
2042
+ return {
2043
+ records,
2044
+ ...extractPagination(json)
2045
+ };
2046
+ },
2047
+ async create(values, signal) {
2048
+ return request({
2049
+ method: "POST",
2050
+ url: "/system/roles",
2051
+ body: buildCreateRoleBody(values),
2052
+ signal
2053
+ });
2054
+ },
2055
+ async update(roleId, values, signal) {
2056
+ return request({
2057
+ method: "PATCH",
2058
+ url: `/system/roles/${encodeURIComponent(roleId)}`,
2059
+ body: buildUpdateRoleBody(values),
2060
+ signal
2061
+ });
2062
+ },
2063
+ async remove(roleId, signal) {
2064
+ return request({
2065
+ method: "DELETE",
2066
+ url: `/system/roles/${encodeURIComponent(roleId)}`,
2067
+ signal
2068
+ });
2069
+ },
2070
+ async getPermissions(roleId, signal) {
2071
+ const json = await request({
2072
+ method: "GET",
2073
+ url: `/system/roles/${encodeURIComponent(roleId)}/permissions`,
2074
+ signal
2075
+ });
2076
+ return mapRolePermissions(json);
2077
+ },
2078
+ async savePermissions(roleId, resourceIds, signal) {
2079
+ return request({
2080
+ method: "POST",
2081
+ url: `/system/roles/${encodeURIComponent(roleId)}/permissions`,
2082
+ body: buildSavePermissionsBody(resourceIds),
2083
+ signal
2084
+ });
2085
+ }
2086
+ };
2087
+ }
2088
+
2089
+ // src/react/RoleManager.tsx
2090
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2091
+ var PAGE_SIZE_OPTIONS3 = ["10", "20", "50", "100"];
2092
+ var emptyForm3 = () => ({
2093
+ name: "",
2094
+ description: ""
2095
+ });
2096
+ function countCheckedDescendants(node, checked) {
2097
+ let total = 0;
2098
+ let checkedCount = 0;
2099
+ const walk = (n) => {
2100
+ for (const child of n.children) {
2101
+ total += 1;
2102
+ if (checked.has(child.resourceId)) checkedCount += 1;
2103
+ walk(child);
2104
+ }
2105
+ };
2106
+ walk(node);
2107
+ return { total, checkedCount };
2108
+ }
2109
+ function RoleManager({
2110
+ api,
2111
+ title = "\u89D2\u8272\u7BA1\u7406",
2112
+ pageSize: initialPageSize = "20",
2113
+ toolbarExtra
2114
+ }) {
2115
+ const [records, setRecords] = useState4([]);
2116
+ const [loading, setLoading] = useState4(false);
2117
+ const [error, setError] = useState4("");
2118
+ const [keyword, setKeyword] = useState4("");
2119
+ const [keywordInput, setKeywordInput] = useState4("");
2120
+ const [pageSize, setPageSize] = useState4(initialPageSize);
2121
+ const [pageToken, setPageToken] = useState4("");
2122
+ const [hasPreviousPage, setHasPreviousPage] = useState4(false);
2123
+ const [hasNextPage, setHasNextPage] = useState4(false);
2124
+ const [dialogOpen, setDialogOpen] = useState4(false);
2125
+ const [editing, setEditing] = useState4(null);
2126
+ const [form, setForm] = useState4(emptyForm3);
2127
+ const [saving, setSaving] = useState4(false);
2128
+ const [deletingRow, setDeletingRow] = useState4(null);
2129
+ const [deleting, setDeleting] = useState4(false);
2130
+ const [authRole, setAuthRole] = useState4(null);
2131
+ const [authTree, setAuthTree] = useState4([]);
2132
+ const [authChecked, setAuthChecked] = useState4(() => /* @__PURE__ */ new Set());
2133
+ const [authExpanded, setAuthExpanded] = useState4({});
2134
+ const [authLoading, setAuthLoading] = useState4(false);
2135
+ const [authSaving, setAuthSaving] = useState4(false);
2136
+ const [authError, setAuthError] = useState4("");
2137
+ const loadList = useCallback4(
2138
+ async (direction = "current", token = pageToken) => {
2139
+ setLoading(true);
2140
+ setError("");
2141
+ try {
2142
+ const result = await api.list({
2143
+ keyword,
2144
+ pagination: {
2145
+ pageToken: token,
2146
+ pageSize,
2147
+ pageDirection: direction
2148
+ }
2149
+ });
2150
+ setRecords(result.records);
2151
+ setPageToken(result.pageToken);
2152
+ setHasPreviousPage(result.hasPreviousPage);
2153
+ setHasNextPage(result.hasNextPage);
2154
+ } catch (err) {
2155
+ setError(err instanceof Error ? err.message : "\u52A0\u8F7D\u5931\u8D25");
2156
+ } finally {
2157
+ setLoading(false);
2158
+ }
2159
+ },
2160
+ [api, keyword, pageSize, pageToken]
2161
+ );
2162
+ useEffect4(() => {
2163
+ void loadList("current", "");
2164
+ }, [api, pageSize, keyword]);
2165
+ const handleSearch = () => {
2166
+ setPageToken("");
2167
+ setKeyword(keywordInput.trim());
2168
+ };
2169
+ const openCreate = () => {
2170
+ setEditing(null);
2171
+ setForm(emptyForm3());
2172
+ setDialogOpen(true);
2173
+ };
2174
+ const openEdit = (row) => {
2175
+ setEditing(row);
2176
+ setForm({
2177
+ name: row.name,
2178
+ description: row.description
2179
+ });
2180
+ setDialogOpen(true);
2181
+ };
2182
+ const closeDialog = () => {
2183
+ if (!saving) setDialogOpen(false);
2184
+ };
2185
+ const handleSubmit = async (event) => {
2186
+ event.preventDefault();
2187
+ if (!form.name.trim()) {
2188
+ setError("\u8BF7\u586B\u5199\u89D2\u8272\u540D\u79F0");
2189
+ return;
2190
+ }
2191
+ setSaving(true);
2192
+ setError("");
2193
+ try {
2194
+ if (editing) {
2195
+ await api.update(editing.roleId, form);
2196
+ } else {
2197
+ await api.create(form);
2198
+ }
2199
+ setDialogOpen(false);
2200
+ await loadList("current", "");
2201
+ } catch (err) {
2202
+ setError(err instanceof Error ? err.message : "\u4FDD\u5B58\u5931\u8D25");
2203
+ } finally {
2204
+ setSaving(false);
2205
+ }
2206
+ };
2207
+ const askDelete = (row) => setDeletingRow(row);
2208
+ const closeDeleteDialog = () => {
2209
+ if (!deleting) setDeletingRow(null);
2210
+ };
2211
+ const confirmDelete = async () => {
2212
+ if (!deletingRow) return;
2213
+ setDeleting(true);
2214
+ setError("");
2215
+ try {
2216
+ await api.remove(deletingRow.roleId);
2217
+ setDeletingRow(null);
2218
+ await loadList("current", "");
2219
+ } catch (err) {
2220
+ setError(err instanceof Error ? err.message : "\u5220\u9664\u5931\u8D25");
2221
+ } finally {
2222
+ setDeleting(false);
2223
+ }
2224
+ };
2225
+ const openAuthorize = async (row) => {
2226
+ setAuthRole(row);
2227
+ setAuthTree([]);
2228
+ setAuthChecked(/* @__PURE__ */ new Set());
2229
+ setAuthExpanded({});
2230
+ setAuthError("");
2231
+ setAuthLoading(true);
2232
+ try {
2233
+ const result = await api.getPermissions(row.roleId);
2234
+ setAuthTree(result.resourceTree);
2235
+ setAuthChecked(new Set(result.resourceIds));
2236
+ const expanded = {};
2237
+ const walk = (nodes) => {
2238
+ for (const node of nodes) {
2239
+ if (node.children.length > 0) {
2240
+ expanded[node.resourceId] = true;
2241
+ walk(node.children);
2242
+ }
2243
+ }
2244
+ };
2245
+ walk(result.resourceTree);
2246
+ setAuthExpanded(expanded);
2247
+ } catch (err) {
2248
+ setAuthError(err instanceof Error ? err.message : "\u52A0\u8F7D\u6388\u6743\u6811\u5931\u8D25");
2249
+ } finally {
2250
+ setAuthLoading(false);
2251
+ }
2252
+ };
2253
+ const closeAuthorize = () => {
2254
+ if (!authSaving) {
2255
+ setAuthRole(null);
2256
+ setAuthError("");
2257
+ }
2258
+ };
2259
+ const toggleExpand = (resourceId) => {
2260
+ setAuthExpanded((prev) => ({ ...prev, [resourceId]: !prev[resourceId] }));
2261
+ };
2262
+ const toggleCheck = (node) => {
2263
+ setAuthChecked((prev) => {
2264
+ const next = new Set(prev);
2265
+ const ids = collectTreeResourceIds(node);
2266
+ const shouldCheck = !prev.has(node.resourceId);
2267
+ if (shouldCheck) {
2268
+ ids.forEach((id) => next.add(id));
2269
+ } else {
2270
+ ids.forEach((id) => next.delete(id));
2271
+ }
2272
+ return next;
2273
+ });
2274
+ };
2275
+ const saveAuthorize = async () => {
2276
+ if (!authRole) return;
2277
+ setAuthSaving(true);
2278
+ setAuthError("");
2279
+ try {
2280
+ await api.savePermissions(authRole.roleId, Array.from(authChecked));
2281
+ setAuthRole(null);
2282
+ } catch (err) {
2283
+ setAuthError(err instanceof Error ? err.message : "\u4FDD\u5B58\u6388\u6743\u5931\u8D25");
2284
+ } finally {
2285
+ setAuthSaving(false);
2286
+ }
2287
+ };
2288
+ const renderTreeNodes = (nodes, depth = 0) => nodes.map((node) => {
2289
+ const hasChildren = node.children.length > 0;
2290
+ const expanded = Boolean(authExpanded[node.resourceId]);
2291
+ const checked = authChecked.has(node.resourceId);
2292
+ const { total, checkedCount } = countCheckedDescendants(node, authChecked);
2293
+ const indeterminate = hasChildren && checkedCount > 0 && checkedCount < total && !checked;
2294
+ return /* @__PURE__ */ jsxs3("div", { children: [
2295
+ /* @__PURE__ */ jsxs3("div", { style: { ...styles3.treeRow, paddingLeft: 12 + depth * 20 }, children: [
2296
+ hasChildren ? /* @__PURE__ */ jsx4(
2297
+ "button",
2298
+ {
2299
+ type: "button",
2300
+ style: styles3.treeToggle,
2301
+ onClick: () => toggleExpand(node.resourceId),
2302
+ "aria-label": expanded ? "\u6536\u8D77" : "\u5C55\u5F00",
2303
+ children: expanded ? "\u25BC" : "\u25B6"
2304
+ }
2305
+ ) : /* @__PURE__ */ jsx4("span", { style: styles3.treeSpacer }),
2306
+ /* @__PURE__ */ jsxs3("label", { style: styles3.treeLabel, children: [
2307
+ /* @__PURE__ */ jsx4(
2308
+ TreeCheckbox,
2309
+ {
2310
+ checked,
2311
+ indeterminate,
2312
+ onChange: () => toggleCheck(node)
2313
+ }
2314
+ ),
2315
+ /* @__PURE__ */ jsx4("span", { children: node.name || node.resourceId })
2316
+ ] })
2317
+ ] }),
2318
+ hasChildren && expanded ? renderTreeNodes(node.children, depth + 1) : null
2319
+ ] }, node.resourceId);
2320
+ });
2321
+ return /* @__PURE__ */ jsxs3("div", { style: styles3.root, children: [
2322
+ /* @__PURE__ */ jsxs3("div", { style: styles3.toolbar, children: [
2323
+ /* @__PURE__ */ jsx4("h2", { style: styles3.title, children: title }),
2324
+ /* @__PURE__ */ jsxs3("div", { style: styles3.toolbarRight, children: [
2325
+ toolbarExtra,
2326
+ /* @__PURE__ */ jsx4("button", { type: "button", style: styles3.primaryBtn, onClick: openCreate, children: "\u65B0\u589E\u89D2\u8272" })
2327
+ ] })
2328
+ ] }),
2329
+ /* @__PURE__ */ jsxs3("div", { style: styles3.searchBar, children: [
2330
+ /* @__PURE__ */ jsx4(
2331
+ "input",
2332
+ {
2333
+ style: { ...styles3.input, maxWidth: 260 },
2334
+ value: keywordInput,
2335
+ onChange: (e) => setKeywordInput(e.target.value),
2336
+ placeholder: "\u641C\u7D22\u89D2\u8272\u540D\u79F0 / \u63CF\u8FF0",
2337
+ onKeyDown: (e) => {
2338
+ if (e.key === "Enter") handleSearch();
2339
+ }
2340
+ }
2341
+ ),
2342
+ /* @__PURE__ */ jsx4("button", { type: "button", style: styles3.secondaryBtn, onClick: handleSearch, children: "\u67E5\u8BE2" }),
2343
+ /* @__PURE__ */ jsx4(
2344
+ "button",
2345
+ {
2346
+ type: "button",
2347
+ style: styles3.secondaryBtn,
2348
+ onClick: () => {
2349
+ setKeywordInput("");
2350
+ setKeyword("");
2351
+ setPageToken("");
2352
+ },
2353
+ children: "\u91CD\u7F6E"
2354
+ }
2355
+ )
2356
+ ] }),
2357
+ error ? /* @__PURE__ */ jsx4("div", { style: styles3.error, children: error }) : null,
2358
+ /* @__PURE__ */ jsx4("div", { style: styles3.tableWrap, children: /* @__PURE__ */ jsxs3("table", { style: styles3.table, children: [
2359
+ /* @__PURE__ */ jsx4("thead", { children: /* @__PURE__ */ jsxs3("tr", { children: [
2360
+ /* @__PURE__ */ jsx4("th", { style: styles3.th, children: "\u89D2\u8272\u540D\u79F0" }),
2361
+ /* @__PURE__ */ jsx4("th", { style: styles3.th, children: "\u89D2\u8272\u63CF\u8FF0" }),
2362
+ /* @__PURE__ */ jsx4("th", { style: { ...styles3.th, width: 200 }, children: "\u64CD\u4F5C" })
2363
+ ] }) }),
2364
+ /* @__PURE__ */ jsx4("tbody", { children: loading ? /* @__PURE__ */ jsx4("tr", { children: /* @__PURE__ */ jsx4("td", { colSpan: 3, style: styles3.empty, children: "\u52A0\u8F7D\u4E2D\u2026" }) }) : records.length === 0 ? /* @__PURE__ */ jsx4("tr", { children: /* @__PURE__ */ jsx4("td", { colSpan: 3, style: styles3.empty, children: "\u6682\u65E0\u6570\u636E" }) }) : records.map((row) => /* @__PURE__ */ jsxs3("tr", { children: [
2365
+ /* @__PURE__ */ jsx4("td", { style: styles3.td, children: row.name }),
2366
+ /* @__PURE__ */ jsx4("td", { style: styles3.td, children: row.description || "\u2014" }),
2367
+ /* @__PURE__ */ jsxs3("td", { style: styles3.td, children: [
2368
+ /* @__PURE__ */ jsx4("button", { type: "button", style: styles3.linkBtn, onClick: () => openEdit(row), children: "\u7F16\u8F91" }),
2369
+ /* @__PURE__ */ jsx4(
2370
+ "button",
2371
+ {
2372
+ type: "button",
2373
+ style: styles3.linkBtn,
2374
+ onClick: () => void openAuthorize(row),
2375
+ children: "\u6388\u6743"
2376
+ }
2377
+ ),
2378
+ /* @__PURE__ */ jsx4(
2379
+ "button",
2380
+ {
2381
+ type: "button",
2382
+ style: { ...styles3.linkBtn, color: "#d92d20" },
2383
+ onClick: () => askDelete(row),
2384
+ children: "\u5220\u9664"
2385
+ }
2386
+ )
2387
+ ] })
2388
+ ] }, row.roleId)) })
2389
+ ] }) }),
2390
+ /* @__PURE__ */ jsxs3("div", { style: styles3.pagination, children: [
2391
+ /* @__PURE__ */ jsxs3("label", { style: styles3.pageSize, children: [
2392
+ "\u6BCF\u9875",
2393
+ /* @__PURE__ */ jsx4(
2394
+ "select",
2395
+ {
2396
+ value: pageSize,
2397
+ onChange: (e) => {
2398
+ setPageToken("");
2399
+ setPageSize(e.target.value);
2400
+ },
2401
+ style: styles3.select,
2402
+ children: PAGE_SIZE_OPTIONS3.map((size) => /* @__PURE__ */ jsx4("option", { value: size, children: size }, size))
2403
+ }
2404
+ )
2405
+ ] }),
2406
+ /* @__PURE__ */ jsxs3("div", { style: styles3.pageBtns, children: [
2407
+ /* @__PURE__ */ jsx4(
2408
+ "button",
2409
+ {
2410
+ type: "button",
2411
+ style: styles3.secondaryBtn,
2412
+ disabled: !hasPreviousPage || loading,
2413
+ onClick: () => void loadList("previous"),
2414
+ children: "\u4E0A\u4E00\u9875"
2415
+ }
2416
+ ),
2417
+ /* @__PURE__ */ jsx4(
2418
+ "button",
2419
+ {
2420
+ type: "button",
2421
+ style: styles3.secondaryBtn,
2422
+ disabled: !hasNextPage || loading,
2423
+ onClick: () => void loadList("next"),
2424
+ children: "\u4E0B\u4E00\u9875"
2425
+ }
2426
+ )
2427
+ ] })
2428
+ ] }),
2429
+ dialogOpen ? /* @__PURE__ */ jsx4("div", { style: styles3.mask, onClick: closeDialog, children: /* @__PURE__ */ jsxs3(
2430
+ "div",
2431
+ {
2432
+ style: styles3.dialog,
2433
+ onClick: (e) => e.stopPropagation(),
2434
+ role: "dialog",
2435
+ "aria-modal": "true",
2436
+ children: [
2437
+ /* @__PURE__ */ jsxs3("div", { style: styles3.dialogHeader, children: [
2438
+ /* @__PURE__ */ jsx4("h3", { style: styles3.dialogTitle, children: editing ? "\u7F16\u8F91\u89D2\u8272" : "\u65B0\u589E\u89D2\u8272" }),
2439
+ /* @__PURE__ */ jsx4("button", { type: "button", style: styles3.iconBtn, onClick: closeDialog, "aria-label": "\u5173\u95ED", children: "\xD7" })
2440
+ ] }),
2441
+ /* @__PURE__ */ jsxs3("form", { style: styles3.form, onSubmit: (e) => void handleSubmit(e), children: [
2442
+ /* @__PURE__ */ jsxs3("label", { style: styles3.field, children: [
2443
+ /* @__PURE__ */ jsx4("span", { style: styles3.label, children: "\u89D2\u8272\u540D\u79F0" }),
2444
+ /* @__PURE__ */ jsx4(
2445
+ "input",
2446
+ {
2447
+ style: styles3.input,
2448
+ value: form.name,
2449
+ onChange: (e) => setForm((prev) => ({ ...prev, name: e.target.value })),
2450
+ placeholder: "\u8BF7\u8F93\u5165\u89D2\u8272\u540D\u79F0",
2451
+ required: true
2452
+ }
2453
+ )
2454
+ ] }),
2455
+ /* @__PURE__ */ jsxs3("label", { style: styles3.field, children: [
2456
+ /* @__PURE__ */ jsx4("span", { style: styles3.label, children: "\u89D2\u8272\u63CF\u8FF0" }),
2457
+ /* @__PURE__ */ jsx4(
2458
+ "textarea",
2459
+ {
2460
+ style: { ...styles3.input, minHeight: 88, resize: "vertical" },
2461
+ value: form.description,
2462
+ onChange: (e) => setForm((prev) => ({ ...prev, description: e.target.value })),
2463
+ placeholder: "\u8BF7\u8F93\u5165\u89D2\u8272\u63CF\u8FF0"
2464
+ }
2465
+ )
2466
+ ] }),
2467
+ /* @__PURE__ */ jsxs3("div", { style: styles3.dialogFooter, children: [
2468
+ /* @__PURE__ */ jsx4("button", { type: "button", style: styles3.secondaryBtn, onClick: closeDialog, children: "\u53D6\u6D88" }),
2469
+ /* @__PURE__ */ jsx4("button", { type: "submit", style: styles3.primaryBtn, disabled: saving, children: saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58" })
2470
+ ] })
2471
+ ] })
2472
+ ]
2473
+ }
2474
+ ) }) : null,
2475
+ deletingRow ? /* @__PURE__ */ jsx4("div", { style: styles3.mask, onClick: closeDeleteDialog, children: /* @__PURE__ */ jsxs3(
2476
+ "div",
2477
+ {
2478
+ style: { ...styles3.dialog, maxWidth: 420 },
2479
+ onClick: (e) => e.stopPropagation(),
2480
+ role: "dialog",
2481
+ "aria-modal": "true",
2482
+ children: [
2483
+ /* @__PURE__ */ jsxs3("div", { style: styles3.dialogHeader, children: [
2484
+ /* @__PURE__ */ jsx4("h3", { style: styles3.dialogTitle, children: "\u786E\u8BA4\u5220\u9664" }),
2485
+ /* @__PURE__ */ jsx4(
2486
+ "button",
2487
+ {
2488
+ type: "button",
2489
+ style: styles3.iconBtn,
2490
+ onClick: closeDeleteDialog,
2491
+ "aria-label": "\u5173\u95ED",
2492
+ children: "\xD7"
2493
+ }
2494
+ )
2495
+ ] }),
2496
+ /* @__PURE__ */ jsxs3("div", { style: styles3.confirmBody, children: [
2497
+ /* @__PURE__ */ jsxs3("p", { style: styles3.confirmText, children: [
2498
+ "\u786E\u8BA4\u5220\u9664\u89D2\u8272\u300C",
2499
+ /* @__PURE__ */ jsx4("strong", { children: deletingRow.name }),
2500
+ "\u300D\u5417\uFF1F\u5220\u9664\u540E\u4E0D\u53EF\u6062\u590D\u3002"
2501
+ ] }),
2502
+ /* @__PURE__ */ jsxs3("div", { style: styles3.dialogFooter, children: [
2503
+ /* @__PURE__ */ jsx4(
2504
+ "button",
2505
+ {
2506
+ type: "button",
2507
+ style: styles3.secondaryBtn,
2508
+ onClick: closeDeleteDialog,
2509
+ disabled: deleting,
2510
+ children: "\u53D6\u6D88"
2511
+ }
2512
+ ),
2513
+ /* @__PURE__ */ jsx4(
2514
+ "button",
2515
+ {
2516
+ type: "button",
2517
+ style: styles3.dangerBtn,
2518
+ onClick: () => void confirmDelete(),
2519
+ disabled: deleting,
2520
+ children: deleting ? "\u5220\u9664\u4E2D\u2026" : "\u786E\u8BA4\u5220\u9664"
2521
+ }
2522
+ )
2523
+ ] })
2524
+ ] })
2525
+ ]
2526
+ }
2527
+ ) }) : null,
2528
+ authRole ? /* @__PURE__ */ jsx4("div", { style: styles3.mask, onClick: closeAuthorize, children: /* @__PURE__ */ jsxs3(
2529
+ "div",
2530
+ {
2531
+ style: { ...styles3.dialog, maxWidth: 560 },
2532
+ onClick: (e) => e.stopPropagation(),
2533
+ role: "dialog",
2534
+ "aria-modal": "true",
2535
+ children: [
2536
+ /* @__PURE__ */ jsxs3("div", { style: styles3.dialogHeader, children: [
2537
+ /* @__PURE__ */ jsxs3("h3", { style: styles3.dialogTitle, children: [
2538
+ "\u89D2\u8272\u6388\u6743 \u2014 ",
2539
+ authRole.name
2540
+ ] }),
2541
+ /* @__PURE__ */ jsx4(
2542
+ "button",
2543
+ {
2544
+ type: "button",
2545
+ style: styles3.iconBtn,
2546
+ onClick: closeAuthorize,
2547
+ "aria-label": "\u5173\u95ED",
2548
+ children: "\xD7"
2549
+ }
2550
+ )
2551
+ ] }),
2552
+ /* @__PURE__ */ jsxs3("div", { style: styles3.authBody, children: [
2553
+ authError ? /* @__PURE__ */ jsx4("div", { style: styles3.error, children: authError }) : null,
2554
+ authLoading ? /* @__PURE__ */ jsx4("div", { style: styles3.empty, children: "\u52A0\u8F7D\u4E2D\u2026" }) : authTree.length === 0 ? /* @__PURE__ */ jsx4("div", { style: styles3.empty, children: "\u6682\u65E0\u53EF\u6388\u6743\u8D44\u6E90" }) : /* @__PURE__ */ jsx4("div", { style: styles3.treePanel, children: renderTreeNodes(authTree) }),
2555
+ /* @__PURE__ */ jsxs3("div", { style: styles3.dialogFooter, children: [
2556
+ /* @__PURE__ */ jsx4(
2557
+ "button",
2558
+ {
2559
+ type: "button",
2560
+ style: styles3.secondaryBtn,
2561
+ onClick: closeAuthorize,
2562
+ disabled: authSaving,
2563
+ children: "\u53D6\u6D88"
2564
+ }
2565
+ ),
2566
+ /* @__PURE__ */ jsx4(
2567
+ "button",
2568
+ {
2569
+ type: "button",
2570
+ style: styles3.primaryBtn,
2571
+ onClick: () => void saveAuthorize(),
2572
+ disabled: authLoading || authSaving,
2573
+ children: authSaving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58"
2574
+ }
2575
+ )
2576
+ ] })
2577
+ ] })
2578
+ ]
2579
+ }
2580
+ ) }) : null
2581
+ ] });
2582
+ }
2583
+ function TreeCheckbox({
2584
+ checked,
2585
+ indeterminate,
2586
+ onChange
2587
+ }) {
2588
+ const ref = useRef(null);
2589
+ useEffect4(() => {
2590
+ if (ref.current) ref.current.indeterminate = indeterminate;
2591
+ }, [indeterminate]);
2592
+ return /* @__PURE__ */ jsx4(
2593
+ "input",
2594
+ {
2595
+ ref,
2596
+ type: "checkbox",
2597
+ checked,
2598
+ onChange,
2599
+ style: styles3.checkbox
2600
+ }
2601
+ );
2602
+ }
2603
+ var styles3 = {
2604
+ root: {
2605
+ display: "flex",
2606
+ flexDirection: "column",
2607
+ gap: 16,
2608
+ padding: 16,
2609
+ color: "#101828",
2610
+ fontFamily: '"PingFang SC", "Microsoft YaHei", -apple-system, BlinkMacSystemFont, sans-serif'
2611
+ },
2612
+ toolbar: {
2613
+ display: "flex",
2614
+ alignItems: "center",
2615
+ justifyContent: "space-between",
2616
+ gap: 12
2617
+ },
2618
+ title: { margin: 0, fontSize: 18, fontWeight: 600 },
2619
+ toolbarRight: { display: "flex", alignItems: "center", gap: 8 },
2620
+ searchBar: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" },
2621
+ error: {
2622
+ padding: "10px 12px",
2623
+ borderRadius: 8,
2624
+ background: "#fef3f2",
2625
+ color: "#b42318",
2626
+ fontSize: 13
2627
+ },
2628
+ tableWrap: {
2629
+ overflow: "auto",
2630
+ border: "1px solid #eaecf0",
2631
+ borderRadius: 10,
2632
+ background: "#fff"
2633
+ },
2634
+ table: { width: "100%", borderCollapse: "collapse", minWidth: 560 },
2635
+ th: {
2636
+ textAlign: "left",
2637
+ padding: "12px 14px",
2638
+ fontSize: 13,
2639
+ fontWeight: 600,
2640
+ color: "#475467",
2641
+ background: "#f9fafb",
2642
+ borderBottom: "1px solid #eaecf0"
2643
+ },
2644
+ td: {
2645
+ padding: "12px 14px",
2646
+ fontSize: 14,
2647
+ borderBottom: "1px solid #f2f4f7",
2648
+ verticalAlign: "middle"
2649
+ },
2650
+ empty: { padding: 28, textAlign: "center", color: "#98a2b3", fontSize: 14 },
2651
+ linkBtn: {
2652
+ border: "none",
2653
+ background: "transparent",
2654
+ color: "#049BAD",
2655
+ cursor: "pointer",
2656
+ padding: "0 8px 0 0",
2657
+ fontSize: 13
2658
+ },
2659
+ pagination: {
2660
+ display: "flex",
2661
+ alignItems: "center",
2662
+ justifyContent: "space-between",
2663
+ gap: 12
2664
+ },
2665
+ pageSize: { display: "flex", alignItems: "center", gap: 8, fontSize: 13, color: "#475467" },
2666
+ pageBtns: { display: "flex", gap: 8 },
2667
+ primaryBtn: {
2668
+ border: "none",
2669
+ background: "#049BAD",
2670
+ color: "#fff",
2671
+ borderRadius: 8,
2672
+ padding: "8px 14px",
2673
+ cursor: "pointer",
2674
+ fontSize: 14
2675
+ },
2676
+ secondaryBtn: {
2677
+ border: "1px solid #d0d5dd",
2678
+ background: "#fff",
2679
+ color: "#344054",
2680
+ borderRadius: 8,
2681
+ padding: "8px 14px",
2682
+ cursor: "pointer",
2683
+ fontSize: 14
2684
+ },
2685
+ dangerBtn: {
2686
+ border: "none",
2687
+ background: "#d92d20",
2688
+ color: "#fff",
2689
+ borderRadius: 8,
2690
+ padding: "8px 14px",
2691
+ cursor: "pointer",
2692
+ fontSize: 14
2693
+ },
2694
+ select: {
2695
+ border: "1px solid #d0d5dd",
2696
+ borderRadius: 6,
2697
+ padding: "4px 8px",
2698
+ fontSize: 13,
2699
+ backgroundColor: "#ffffff",
2700
+ color: "#101828"
2701
+ },
2702
+ mask: {
2703
+ position: "fixed",
2704
+ inset: 0,
2705
+ background: "rgba(16, 24, 40, 0.45)",
2706
+ display: "flex",
2707
+ alignItems: "center",
2708
+ justifyContent: "center",
2709
+ zIndex: 1e3,
2710
+ padding: 16
2711
+ },
2712
+ dialog: {
2713
+ width: "100%",
2714
+ maxWidth: 480,
2715
+ background: "#fff",
2716
+ borderRadius: 12,
2717
+ boxShadow: "0 20px 40px rgba(16,24,40,0.18)"
2718
+ },
2719
+ dialogHeader: {
2720
+ display: "flex",
2721
+ alignItems: "center",
2722
+ justifyContent: "space-between",
2723
+ padding: "14px 16px",
2724
+ borderBottom: "1px solid #eaecf0"
2725
+ },
2726
+ dialogTitle: { margin: 0, fontSize: 16, fontWeight: 600 },
2727
+ iconBtn: {
2728
+ border: "none",
2729
+ background: "transparent",
2730
+ fontSize: 22,
2731
+ lineHeight: 1,
2732
+ cursor: "pointer",
2733
+ color: "#667085"
2734
+ },
2735
+ form: { display: "flex", flexDirection: "column", gap: 12, padding: 16 },
2736
+ confirmBody: { display: "flex", flexDirection: "column", gap: 16, padding: 16 },
2737
+ confirmText: { margin: 0, fontSize: 14, color: "#344054", lineHeight: 1.6 },
2738
+ authBody: { display: "flex", flexDirection: "column", gap: 12, padding: 16 },
2739
+ treePanel: {
2740
+ maxHeight: 420,
2741
+ overflow: "auto",
2742
+ border: "1px solid #eaecf0",
2743
+ borderRadius: 8,
2744
+ padding: "8px 0",
2745
+ background: "#fff"
2746
+ },
2747
+ treeRow: {
2748
+ display: "flex",
2749
+ alignItems: "center",
2750
+ gap: 6,
2751
+ minHeight: 32,
2752
+ paddingRight: 12
2753
+ },
2754
+ treeToggle: {
2755
+ border: "none",
2756
+ background: "transparent",
2757
+ width: 20,
2758
+ height: 20,
2759
+ padding: 0,
2760
+ cursor: "pointer",
2761
+ color: "#667085",
2762
+ fontSize: 10,
2763
+ flexShrink: 0
2764
+ },
2765
+ treeSpacer: { width: 20, flexShrink: 0 },
2766
+ treeLabel: {
2767
+ display: "inline-flex",
2768
+ alignItems: "center",
2769
+ gap: 8,
2770
+ fontSize: 14,
2771
+ color: "#101828",
2772
+ cursor: "pointer",
2773
+ userSelect: "none"
2774
+ },
2775
+ checkbox: {
2776
+ width: 16,
2777
+ height: 16,
2778
+ accentColor: "#049BAD",
2779
+ cursor: "pointer",
2780
+ colorScheme: "light",
2781
+ backgroundColor: "#ffffff"
2782
+ },
2783
+ field: { display: "flex", flexDirection: "column", gap: 6 },
2784
+ label: { fontSize: 13, color: "#344054", fontWeight: 500 },
2785
+ input: {
2786
+ border: "1px solid #d0d5dd",
2787
+ borderRadius: 8,
2788
+ padding: "8px 10px",
2789
+ fontSize: 14,
2790
+ outline: "none",
2791
+ backgroundColor: "#ffffff",
2792
+ color: "#101828",
2793
+ boxSizing: "border-box",
2794
+ width: "100%"
2795
+ },
2796
+ dialogFooter: {
2797
+ display: "flex",
2798
+ justifyContent: "flex-end",
2799
+ gap: 8,
2800
+ paddingTop: 4
2801
+ }
2802
+ };
2803
+
1960
2804
  // src/react/SystemAdmin.tsx
1961
- import { useMemo as useMemo4, useState as useState4 } from "react";
2805
+ import { useMemo as useMemo4, useState as useState5 } from "react";
1962
2806
 
1963
2807
  // src/admin/menu.ts
1964
2808
  var SYSTEM_ADMIN_MENU = {
@@ -1966,23 +2810,25 @@ var SYSTEM_ADMIN_MENU = {
1966
2810
  label: "\u7CFB\u7EDF\u7BA1\u7406",
1967
2811
  children: [
1968
2812
  { key: "menu", label: "\u83DC\u5355\u7BA1\u7406" },
1969
- { key: "resource", label: "\u6743\u9650\u70B9\u7BA1\u7406" }
2813
+ { key: "resource", label: "\u6743\u9650\u70B9\u7BA1\u7406" },
2814
+ { key: "role", label: "\u89D2\u8272\u7BA1\u7406" }
1970
2815
  ]
1971
2816
  };
1972
2817
  var SYSTEM_ADMIN_DEFAULT_KEY = "menu";
1973
2818
 
1974
2819
  // src/react/SystemAdmin.tsx
1975
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2820
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1976
2821
  function SystemAdmin({
1977
2822
  menuApi,
1978
2823
  resourceApi,
2824
+ roleApi,
1979
2825
  defaultActiveKey = SYSTEM_ADMIN_DEFAULT_KEY,
1980
2826
  activeKey: controlledKey,
1981
2827
  onActiveKeyChange,
1982
2828
  title = SYSTEM_ADMIN_MENU.label,
1983
2829
  toolbarExtra
1984
2830
  }) {
1985
- const [innerKey, setInnerKey] = useState4(defaultActiveKey);
2831
+ const [innerKey, setInnerKey] = useState5(defaultActiveKey);
1986
2832
  const activeKey = controlledKey ?? innerKey;
1987
2833
  const setActiveKey = (key) => {
1988
2834
  if (controlledKey === void 0) setInnerKey(key);
@@ -1992,18 +2838,18 @@ function SystemAdmin({
1992
2838
  () => SYSTEM_ADMIN_MENU.children.find((item) => item.key === activeKey)?.label ?? "",
1993
2839
  [activeKey]
1994
2840
  );
1995
- return /* @__PURE__ */ jsxs3("div", { style: styles3.root, children: [
1996
- /* @__PURE__ */ jsxs3("aside", { style: styles3.sidebar, children: [
1997
- /* @__PURE__ */ jsx4("div", { style: styles3.sidebarTitle, children: title }),
1998
- /* @__PURE__ */ jsx4("nav", { style: styles3.nav, children: SYSTEM_ADMIN_MENU.children.map((item) => {
2841
+ return /* @__PURE__ */ jsxs4("div", { style: styles4.root, children: [
2842
+ /* @__PURE__ */ jsxs4("aside", { style: styles4.sidebar, children: [
2843
+ /* @__PURE__ */ jsx5("div", { style: styles4.sidebarTitle, children: title }),
2844
+ /* @__PURE__ */ jsx5("nav", { style: styles4.nav, children: SYSTEM_ADMIN_MENU.children.map((item) => {
1999
2845
  const active = item.key === activeKey;
2000
- return /* @__PURE__ */ jsx4(
2846
+ return /* @__PURE__ */ jsx5(
2001
2847
  "button",
2002
2848
  {
2003
2849
  type: "button",
2004
2850
  style: {
2005
- ...styles3.navItem,
2006
- ...active ? styles3.navItemActive : null
2851
+ ...styles4.navItem,
2852
+ ...active ? styles4.navItemActive : null
2007
2853
  },
2008
2854
  onClick: () => setActiveKey(item.key),
2009
2855
  children: item.label
@@ -2012,20 +2858,20 @@ function SystemAdmin({
2012
2858
  );
2013
2859
  }) })
2014
2860
  ] }),
2015
- /* @__PURE__ */ jsxs3("main", { style: styles3.content, children: [
2016
- /* @__PURE__ */ jsxs3("div", { style: styles3.contentHeader, children: [
2017
- /* @__PURE__ */ jsxs3("div", { style: styles3.breadcrumb, children: [
2018
- /* @__PURE__ */ jsx4("span", { style: styles3.breadcrumbParent, children: title }),
2019
- /* @__PURE__ */ jsx4("span", { style: styles3.breadcrumbSep, children: "/" }),
2020
- /* @__PURE__ */ jsx4("span", { style: styles3.breadcrumbCurrent, children: activeLabel })
2861
+ /* @__PURE__ */ jsxs4("main", { style: styles4.content, children: [
2862
+ /* @__PURE__ */ jsxs4("div", { style: styles4.contentHeader, children: [
2863
+ /* @__PURE__ */ jsxs4("div", { style: styles4.breadcrumb, children: [
2864
+ /* @__PURE__ */ jsx5("span", { style: styles4.breadcrumbParent, children: title }),
2865
+ /* @__PURE__ */ jsx5("span", { style: styles4.breadcrumbSep, children: "/" }),
2866
+ /* @__PURE__ */ jsx5("span", { style: styles4.breadcrumbCurrent, children: activeLabel })
2021
2867
  ] }),
2022
2868
  toolbarExtra
2023
2869
  ] }),
2024
- /* @__PURE__ */ jsx4("div", { style: styles3.panel, children: activeKey === "menu" ? /* @__PURE__ */ jsx4(MenuManager, { api: menuApi, title: "\u83DC\u5355\u7BA1\u7406" }) : /* @__PURE__ */ jsx4(ResourceManager, { api: resourceApi, title: "\u6743\u9650\u70B9\u7BA1\u7406" }) })
2870
+ /* @__PURE__ */ jsx5("div", { style: styles4.panel, children: activeKey === "menu" ? /* @__PURE__ */ jsx5(MenuManager, { api: menuApi, title: "\u83DC\u5355\u7BA1\u7406" }) : activeKey === "resource" ? /* @__PURE__ */ jsx5(ResourceManager, { api: resourceApi, title: "\u6743\u9650\u70B9\u7BA1\u7406" }) : /* @__PURE__ */ jsx5(RoleManager, { api: roleApi, title: "\u89D2\u8272\u7BA1\u7406" }) })
2025
2871
  ] })
2026
2872
  ] });
2027
2873
  }
2028
- var styles3 = {
2874
+ var styles4 = {
2029
2875
  root: {
2030
2876
  display: "flex",
2031
2877
  minHeight: 560,
@@ -2121,18 +2967,24 @@ export {
2121
2967
  ROOT_MENU_DEPTH,
2122
2968
  ROOT_PARENT_ID,
2123
2969
  ResourceManager,
2970
+ RoleManager,
2124
2971
  SYSTEM_ADMIN_DEFAULT_KEY,
2125
2972
  SYSTEM_ADMIN_MENU,
2126
2973
  SystemAdmin,
2127
2974
  appendQueryParam,
2128
2975
  buildCreateBody,
2129
2976
  buildCreateMenuBody,
2977
+ buildCreateRoleBody,
2978
+ buildSavePermissionsBody,
2130
2979
  buildUpdateBody,
2131
2980
  buildUpdateMenuBody,
2981
+ buildUpdateRoleBody,
2982
+ collectTreeResourceIds,
2132
2983
  createAuthorizationResourceApi,
2133
2984
  createDefaultResourceRequest,
2134
2985
  createMenuResourceApi,
2135
2986
  createPermissionStore,
2987
+ createSystemRoleApi,
2136
2988
  extractPagination,
2137
2989
  extractRecords,
2138
2990
  getAppClientId,
@@ -2140,6 +2992,9 @@ export {
2140
2992
  isMenuLeaf,
2141
2993
  mapAuthorizationResource,
2142
2994
  mapMenuResource,
2995
+ mapPermissionTreeNode,
2996
+ mapRolePermissions,
2997
+ mapSystemRole,
2143
2998
  resolveMenuDepth,
2144
2999
  snowyflake,
2145
3000
  useHasPermission,