openxiangda 1.0.13 → 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.
- package/lib/cli.js +13 -11
- package/openxiangda-skills/references/openxiangda-api.md +2 -2
- package/openxiangda-skills/references/permissions-settings.md +20 -8
- package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +1 -1
- package/package.json +6 -2
- package/packages/sdk/dist/components/index.cjs +7 -3
- package/packages/sdk/dist/components/index.cjs.map +1 -1
- package/packages/sdk/dist/components/index.mjs +7 -3
- package/packages/sdk/dist/components/index.mjs.map +1 -1
package/lib/cli.js
CHANGED
|
@@ -1322,12 +1322,14 @@ async function permission(args) {
|
|
|
1322
1322
|
const [groupCode] = positional;
|
|
1323
1323
|
const name = flags.name || positional.slice(1).join(' ') || groupCode;
|
|
1324
1324
|
if (!groupCode || !name) {
|
|
1325
|
-
fail('用法: openxiangda permission page-group-create <groupCode> --name <text> [--menu-codes <menuCode...>|--page-codes <pageCode...>|--form-codes <formCode...>|--menu-ids <id...>]');
|
|
1325
|
+
fail('用法: openxiangda permission page-group-create <groupCode> --name <text> [--menu-codes <menuCode...>|--page-codes <pageCode...>|--form-codes <formCode...>|--menu-ids <id...>] [--no-runtime-aliases]');
|
|
1326
1326
|
}
|
|
1327
1327
|
const target = getWorkspaceTarget(config, profileName, flags);
|
|
1328
1328
|
const roles = splitList(flags.roles);
|
|
1329
|
-
const
|
|
1330
|
-
const runtimeAliasTargets =
|
|
1329
|
+
const editableTargets = resolvePagePermissionMenuTargets(target.bound, flags);
|
|
1330
|
+
const runtimeAliasTargets = flags['no-runtime-aliases']
|
|
1331
|
+
? []
|
|
1332
|
+
: resolvePagePermissionRuntimeAliasTargets(target.bound, flags);
|
|
1331
1333
|
const data = await requestWithAuth(
|
|
1332
1334
|
config,
|
|
1333
1335
|
target.profileName,
|
|
@@ -1337,14 +1339,13 @@ async function permission(args) {
|
|
|
1337
1339
|
body: {
|
|
1338
1340
|
name,
|
|
1339
1341
|
roles,
|
|
1340
|
-
menuFormUuids,
|
|
1342
|
+
menuFormUuids: editableTargets,
|
|
1341
1343
|
},
|
|
1342
1344
|
}
|
|
1343
1345
|
);
|
|
1344
1346
|
if (data?.id) savePagePermissionGroupResource(target, groupCode, data.id, { name });
|
|
1345
1347
|
let runtimeAliasData = null;
|
|
1346
1348
|
if (runtimeAliasTargets.length > 0) {
|
|
1347
|
-
const runtimeAliasGroupCode = `${groupCode}__runtime_aliases`;
|
|
1348
1349
|
const runtimeAliasName = `${name}(运行时别名)`;
|
|
1349
1350
|
runtimeAliasData = await requestWithAuth(
|
|
1350
1351
|
config,
|
|
@@ -1360,12 +1361,14 @@ async function permission(args) {
|
|
|
1360
1361
|
}
|
|
1361
1362
|
);
|
|
1362
1363
|
if (runtimeAliasData?.id) {
|
|
1363
|
-
savePagePermissionGroupResource(target,
|
|
1364
|
+
savePagePermissionGroupResource(target, `${groupCode}__runtime_aliases`, runtimeAliasData.id, {
|
|
1364
1365
|
name: runtimeAliasName,
|
|
1365
1366
|
});
|
|
1366
1367
|
}
|
|
1367
1368
|
}
|
|
1368
|
-
const output = runtimeAliasData
|
|
1369
|
+
const output = runtimeAliasData
|
|
1370
|
+
? { editableGroup: data, runtimeAliasGroup: runtimeAliasData }
|
|
1371
|
+
: data;
|
|
1369
1372
|
if (flags.json) return writeJson(output);
|
|
1370
1373
|
print(JSON.stringify(output, null, 2));
|
|
1371
1374
|
return;
|
|
@@ -1946,14 +1949,13 @@ function resolvePagePermissionMenuTargets(bound, flags = {}) {
|
|
|
1946
1949
|
function resolveMenuPermissionTargets(bound, menuCode) {
|
|
1947
1950
|
const menuEntry = bound.resources?.menus?.[menuCode];
|
|
1948
1951
|
if (menuEntry?.formUuid) return [menuEntry.formUuid];
|
|
1949
|
-
if (menuEntry?.pageId) return [menuEntry.pageId];
|
|
1950
1952
|
return [resolveMenuId(bound, menuCode)];
|
|
1951
1953
|
}
|
|
1952
1954
|
|
|
1953
1955
|
function resolvePagePermissionTargets(bound, pageCode) {
|
|
1954
1956
|
const menuEntry = findPagePermissionMenuEntry(bound, pageCode);
|
|
1955
|
-
if (menuEntry?.
|
|
1956
|
-
return [menuEntry.
|
|
1957
|
+
if (menuEntry?.menuId) {
|
|
1958
|
+
return [menuEntry.menuId];
|
|
1957
1959
|
}
|
|
1958
1960
|
fail(`页面 ${pageCode} 未找到已绑定代码页菜单。请先发布/创建菜单,或直接传 --menu-form-uuids/--menu-ids。`);
|
|
1959
1961
|
}
|
|
@@ -2001,7 +2003,7 @@ function resolveCodePagePermissionAliases(bound, menuEntry = {}, pageCode) {
|
|
|
2001
2003
|
});
|
|
2002
2004
|
return unique([
|
|
2003
2005
|
pageCode,
|
|
2004
|
-
menuEntry.
|
|
2006
|
+
menuEntry.pageId,
|
|
2005
2007
|
menuEntry.pageCode,
|
|
2006
2008
|
menuEntry.routeKey,
|
|
2007
2009
|
menuEntry.legacyFormUuid,
|
|
@@ -369,11 +369,11 @@ Body:
|
|
|
369
369
|
{
|
|
370
370
|
"name": "销售可见页面",
|
|
371
371
|
"roles": ["sales"],
|
|
372
|
-
"menuFormUuids": ["FORM_XXX", "
|
|
372
|
+
"menuFormUuids": ["FORM_XXX", "MENU_ID_FOR_CODE_PAGE"]
|
|
373
373
|
}
|
|
374
374
|
```
|
|
375
375
|
|
|
376
|
-
An empty `menuFormUuids` array means all menus/pages are visible to the matched roles. For form menus this field can contain form UUIDs. For custom code page menus, the
|
|
376
|
+
An empty `menuFormUuids` array means all menus/pages are visible to the matched roles. For form menus this field can contain form UUIDs. For custom code page menus, the editable permission group should contain the menu ID, which is what the platform permission editor uses for tree check state. OpenXiangda CLI resolves `--page-codes` / `--menu-codes` to an editable menu-ID group and creates a companion `(运行时别名)` group for required page ID, route key, and legacy `PAGE_...` runtime aliases by default. This keeps the main group editable while satisfying `/view` runtime guards. Use `--no-runtime-aliases` only for deployments whose runtime checks menu IDs directly.
|
|
377
377
|
|
|
378
378
|
### GET `/apps/:appType/page-permission-groups/:groupId`
|
|
379
379
|
|
|
@@ -19,26 +19,38 @@ Role IDs are platform-specific. Store them only in `.openxiangda/state.json` und
|
|
|
19
19
|
## Page Permission Groups
|
|
20
20
|
|
|
21
21
|
Page permission groups map role codes to visible menu targets. Form menus use their `FORM_...`
|
|
22
|
-
form UUIDs. Custom code page/display menus
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
form UUIDs. Custom code page/display menus should include menu IDs because the platform
|
|
23
|
+
permission editor uses menu IDs for tree check state. Some `/view` runtime guards also require
|
|
24
|
+
page ID, route key, and legacy `PAGE_...` aliases; keep those aliases in the same permission
|
|
25
|
+
group so the platform tree can still round-trip the menu selection.
|
|
25
26
|
|
|
26
27
|
```json
|
|
27
28
|
{
|
|
28
29
|
"name": "销售页面",
|
|
29
30
|
"roles": ["sales"],
|
|
30
|
-
"menuFormUuids": ["FORM_CUSTOMER", "FORM_ORDER", "
|
|
31
|
+
"menuFormUuids": ["FORM_CUSTOMER", "FORM_ORDER", "MENU_ID_FOR_CODE_PAGE"]
|
|
31
32
|
}
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
For custom code pages, keep the
|
|
35
|
-
|
|
35
|
+
For custom code pages, keep the editable menu tree targets separate from runtime aliases. The
|
|
36
|
+
platform permission editor only reliably checks real menu IDs, while `/view` runtime guards on
|
|
37
|
+
some private deployments still check the code page id, route key, or legacy `PAGE_...` id. When
|
|
38
|
+
`--page-codes` or `--menu-codes` is used, OpenXiangda creates an editable group plus a companion
|
|
39
|
+
runtime alias group:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"name": "销售页面",
|
|
44
|
+
"roles": ["sales"],
|
|
45
|
+
"menuFormUuids": ["MENU_ID_FOR_CODE_PAGE"]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
36
48
|
|
|
37
49
|
```json
|
|
38
50
|
{
|
|
39
51
|
"name": "销售页面(运行时别名)",
|
|
40
52
|
"roles": ["sales"],
|
|
41
|
-
"menuFormUuids": ["
|
|
53
|
+
"menuFormUuids": ["PAGE_ID_FOR_CODE_PAGE", "CODE_PAGE_ROUTE_KEY", "PAGE_LEGACY_FORM_UUID"]
|
|
42
54
|
}
|
|
43
55
|
```
|
|
44
56
|
|
|
@@ -47,7 +59,7 @@ Rules:
|
|
|
47
59
|
- `roles: []` means all roles can match.
|
|
48
60
|
- `menuFormUuids: []` means all menus/pages are visible to matched roles.
|
|
49
61
|
- Prefer `--form-codes` in CLI for form menus so each profile resolves its own form UUIDs.
|
|
50
|
-
- Prefer `--page-codes` or `--menu-codes` in CLI for custom code page menus. The
|
|
62
|
+
- Prefer `--page-codes` or `--menu-codes` in CLI for custom code page menus. The primary group remains editable because it contains only menu IDs. Pass `--no-runtime-aliases` only after confirming the target platform checks menu IDs directly at runtime.
|
|
51
63
|
|
|
52
64
|
## Form Permission Groups
|
|
53
65
|
|
|
@@ -43,7 +43,7 @@ openxiangda permission page-group-create sales_pages --name "销售页面" --rol
|
|
|
43
43
|
openxiangda permission page-group-create portal_pages --name "门户页面" --page-codes portal_pc,portal_mobile --profile dev
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
Use `--page-codes` or `--menu-codes` for custom code page menus; the CLI resolves the
|
|
46
|
+
Use `--page-codes` or `--menu-codes` for custom code page menus; the CLI resolves the permission group to the published menu ID and keeps required page ID, route key, and legacy `PAGE_...` runtime aliases in the same group so the platform editor can still round-trip the selection. Use `--form-codes` for form menus; the CLI resolves forms to profile-local form UUIDs. Empty target lists mean all menus/pages are visible to matched roles. Only pass `--no-runtime-aliases` after confirming the target platform checks menu IDs directly at runtime.
|
|
47
47
|
|
|
48
48
|
## Form Permission Groups
|
|
49
49
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -107,13 +107,17 @@
|
|
|
107
107
|
"react-dom": ">=18"
|
|
108
108
|
},
|
|
109
109
|
"devDependencies": {
|
|
110
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
111
|
+
"@testing-library/react": "^16.3.2",
|
|
110
112
|
"@types/node": "^22.0.0",
|
|
111
113
|
"@types/react": "^18.3.18",
|
|
112
114
|
"@types/react-dom": "^18.3.5",
|
|
115
|
+
"jsdom": "^29.1.1",
|
|
113
116
|
"react": "18.3.1",
|
|
114
117
|
"react-dom": "18.3.1",
|
|
115
118
|
"tsup": "^8.3.0",
|
|
116
|
-
"typescript": "^5.7.0"
|
|
119
|
+
"typescript": "^5.7.0",
|
|
120
|
+
"vitest": "^4.1.7"
|
|
117
121
|
},
|
|
118
122
|
"engines": {
|
|
119
123
|
"node": ">=18"
|
|
@@ -1091,10 +1091,11 @@ function FieldWrapper({
|
|
|
1091
1091
|
}
|
|
1092
1092
|
) : void 0;
|
|
1093
1093
|
const errorNode = error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sy-field-error", role: "alert", "data-testid": `error-${fieldId}`, children: error }) : void 0;
|
|
1094
|
+
const requiredMark = required4 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sy-field-required", "aria-hidden": "true", children: "*" }) : null;
|
|
1094
1095
|
if (!AntFormItem) {
|
|
1095
1096
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("sy-field-wrapper", className), "data-field-id": fieldId, children: [
|
|
1096
1097
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: cn("sy-field-label", labelClassName), children: [
|
|
1097
|
-
|
|
1098
|
+
requiredMark,
|
|
1098
1099
|
label
|
|
1099
1100
|
] }),
|
|
1100
1101
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "sy-field-control", children }),
|
|
@@ -1106,8 +1107,11 @@ function FieldWrapper({
|
|
|
1106
1107
|
AntFormItem,
|
|
1107
1108
|
{
|
|
1108
1109
|
name: fieldId,
|
|
1109
|
-
label: /* @__PURE__ */ (0, import_jsx_runtime.
|
|
1110
|
-
|
|
1110
|
+
label: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: cn("sy-field-label", labelClassName), children: [
|
|
1111
|
+
requiredMark,
|
|
1112
|
+
label
|
|
1113
|
+
] }),
|
|
1114
|
+
required: false,
|
|
1111
1115
|
validateStatus: error ? "error" : void 0,
|
|
1112
1116
|
help: errorNode,
|
|
1113
1117
|
extra,
|