openxiangda 1.0.246 → 1.0.248
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/README.md +3 -1
- package/lib/cli.js +46 -4
- package/lib/role-resolution.js +57 -0
- package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +5 -0
- package/package.json +4 -1
- package/packages/sdk/dist/runtime/index.cjs +30 -11
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.mjs +30 -11
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/react.cjs +30 -11
- package/packages/sdk/dist/runtime/react.cjs.map +1 -1
- package/packages/sdk/dist/runtime/react.mjs +30 -11
- package/packages/sdk/dist/runtime/react.mjs.map +1 -1
- package/packages/sdk/source/admin-list/AdminList.tsx +36 -11
package/README.md
CHANGED
|
@@ -54,6 +54,8 @@ openxiangda automation list --profile dev
|
|
|
54
54
|
openxiangda automation executions daily_ticket_digest --profile dev
|
|
55
55
|
openxiangda automation diagnose daily_ticket_digest --profile dev
|
|
56
56
|
openxiangda permission role-list --profile dev
|
|
57
|
+
openxiangda permission role-users sales --profile dev
|
|
58
|
+
openxiangda permission role-add-users sales --user-ids <user-id> --change <change-id> --profile dev
|
|
57
59
|
openxiangda settings get customer --profile dev
|
|
58
60
|
openxiangda data-view list --profile dev
|
|
59
61
|
openxiangda data-view status ticket_with_customer --profile dev
|
|
@@ -133,7 +135,7 @@ React SPA workspaces publish their frontend with `openxiangda runtime deploy`. E
|
|
|
133
135
|
|
|
134
136
|
Environment-managed `release ship`, recovery `release deploy`, and `release promote` accept the same audited Runtime rollback pair. It is forwarded only to `runtime-stage`, never to resource stages or `app-finalize`. Ship freezes the intent in its private journal so production confirmation can inherit it automatically; without the explicit pair, lineage divergence remains blocked.
|
|
135
137
|
|
|
136
|
-
Before publishing the OpenXiangda npm package, run `npm run test:release` once on the final clean version commit.
|
|
138
|
+
Before publishing the OpenXiangda npm package, run `npm run test:release` once on the final clean version commit. For an AdminList-only SDK patch, use the fixed `npm run test:release:sdk-admin-list` profile instead; it runs the AdminList state and response contracts plus the release-evidence guard without exercising unrelated deployment orchestration. Both commands write hash-bound evidence under `.openxiangda/evidence/tests/<HEAD>/release.json`. `npm publish` validates that evidence against the exact Git commit, package version, Node version, and complete passing result, then runs the mainline guard; it does not execute the selected suite a second time. Unknown or cross-module changes should continue to use the full release suite. Any commit, version, runtime, result, or evidence-hash drift remains fail closed.
|
|
137
139
|
|
|
138
140
|
Parallel tasks develop and test in isolated worktrees, but feature worktrees do not publish. After approved commits are merged and pushed, `sdd bundle <release-change> --changes ...` unions their exact structured scope and preserves the source changes' common Git baseline instead of adopting the post-merge `HEAD`. Old imported changes that predate source-base metadata must pass `--source-base-ref <commit>` explicitly. Commit/push the bundle, then run `release publish --change <release-change> --profile <name>` from a clean local main/master whose commit exactly equals the authoritative remote tip. It verifies without rewriting reviewed SDD files, waits for the app lease, freezes one authoritative App capture, executes exact Form/Backend/Runtime staged steps, atomically finalizes the Root App release, and records a resumable local execution journal. Runtime checks use the narrow head endpoint; immutable Git-base artifact hashes are reused across plans.
|
|
139
141
|
|
package/lib/cli.js
CHANGED
|
@@ -207,6 +207,7 @@ const {
|
|
|
207
207
|
writeJson,
|
|
208
208
|
} = require('./utils');
|
|
209
209
|
const { version: CURRENT_VERSION } = require('../package.json');
|
|
210
|
+
const { resolveRoleMembershipId } = require('./role-resolution');
|
|
210
211
|
|
|
211
212
|
const NPM_PACKAGE_NAME = 'openxiangda';
|
|
212
213
|
const OFFICIAL_NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
@@ -390,7 +391,7 @@ Usage:
|
|
|
390
391
|
openxiangda connector list|get|create|update|upsert|delete|invoke|download-test [--json-file file]
|
|
391
392
|
openxiangda notification template-list|template-get|template-upsert|template-delete|type-list|type-get|type-upsert|type-delete|preview|send|batch-send
|
|
392
393
|
openxiangda organization capabilities|department-list|department-get|department-create|department-update|account-list|account-get|account-create|account-update|account-reset-password
|
|
393
|
-
openxiangda permission role-list|role-create|role-update|role-delete|role-bind|snapshot|audit
|
|
394
|
+
openxiangda permission role-list|role-create|role-update|role-delete|role-bind|role-users|role-add-users|snapshot|audit
|
|
394
395
|
openxiangda permission page-group-list|page-group-create|page-group-update|page-group-delete|page-group-bind
|
|
395
396
|
openxiangda permission form-group-list|form-group-create|form-group-update|form-group-delete|form-group-bind
|
|
396
397
|
openxiangda settings get|save|indexes|indexes-save|data-management|data-management-save|public-access
|
|
@@ -14105,7 +14106,10 @@ async function permission(args) {
|
|
|
14105
14106
|
const { flags, positional } = parseArgs(rest);
|
|
14106
14107
|
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
14107
14108
|
print(
|
|
14108
|
-
|
|
14109
|
+
[
|
|
14110
|
+
'用法: openxiangda permission role-list|role-create|role-update|role-delete|role-bind|role-users|role-add-users|page-group-list|page-group-create|page-group-update|page-group-delete|page-group-bind|form-group-list|form-group-create|form-group-update|form-group-delete|form-group-bind|form-summary|menu-permissions|snapshot|audit',
|
|
14111
|
+
'role-users/role-add-users 接受角色 code 或 UUID;本地未绑定的 code 会从当前应用角色列表精确解析,找不到或不唯一时显式失败。',
|
|
14112
|
+
].join('\n')
|
|
14109
14113
|
);
|
|
14110
14114
|
return;
|
|
14111
14115
|
}
|
|
@@ -14218,7 +14222,12 @@ async function permission(args) {
|
|
|
14218
14222
|
const [roleKey] = positional;
|
|
14219
14223
|
if (!roleKey) fail('用法: openxiangda permission role-users <roleCode|roleId>');
|
|
14220
14224
|
const target = getWorkspaceTarget(config, profileName, flags);
|
|
14221
|
-
const roleId =
|
|
14225
|
+
const roleId = await resolveRoleMembershipIdForCommand(
|
|
14226
|
+
config,
|
|
14227
|
+
target,
|
|
14228
|
+
roleKey,
|
|
14229
|
+
flags
|
|
14230
|
+
);
|
|
14222
14231
|
const data = await requestWithAuth(
|
|
14223
14232
|
config,
|
|
14224
14233
|
target.profileName,
|
|
@@ -14243,7 +14252,12 @@ async function permission(args) {
|
|
|
14243
14252
|
fail('用法: openxiangda permission role-add-users <roleCode|roleId> --user-ids <id1,id2>');
|
|
14244
14253
|
}
|
|
14245
14254
|
const target = getWorkspaceTarget(config, profileName, flags);
|
|
14246
|
-
const roleId =
|
|
14255
|
+
const roleId = await resolveRoleMembershipIdForCommand(
|
|
14256
|
+
config,
|
|
14257
|
+
target,
|
|
14258
|
+
roleKey,
|
|
14259
|
+
flags
|
|
14260
|
+
);
|
|
14247
14261
|
await ensureDirectMutationPublishContext(config, target, flags, 'permission role-add-users');
|
|
14248
14262
|
const data = await requestWithAuth(
|
|
14249
14263
|
config,
|
|
@@ -17471,6 +17485,34 @@ function resolveRoleId(bound, roleKey, flags = {}) {
|
|
|
17471
17485
|
return mapped || roleKey;
|
|
17472
17486
|
}
|
|
17473
17487
|
|
|
17488
|
+
async function resolveRoleMembershipIdForCommand(
|
|
17489
|
+
config,
|
|
17490
|
+
target,
|
|
17491
|
+
roleKey,
|
|
17492
|
+
flags = {}
|
|
17493
|
+
) {
|
|
17494
|
+
return resolveRoleMembershipId({
|
|
17495
|
+
roleKey,
|
|
17496
|
+
explicitRoleId: flags['role-id'],
|
|
17497
|
+
localRoleId: target.bound.resources?.roles?.[roleKey]?.roleId,
|
|
17498
|
+
listRoles: async roleCode => {
|
|
17499
|
+
const data = await requestWithAuth(
|
|
17500
|
+
config,
|
|
17501
|
+
target.profileName,
|
|
17502
|
+
apiPathWithQuery(
|
|
17503
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/roles`,
|
|
17504
|
+
{
|
|
17505
|
+
code: roleCode,
|
|
17506
|
+
page: 1,
|
|
17507
|
+
limit: 200,
|
|
17508
|
+
}
|
|
17509
|
+
)
|
|
17510
|
+
);
|
|
17511
|
+
return normalizeItems(data);
|
|
17512
|
+
},
|
|
17513
|
+
});
|
|
17514
|
+
}
|
|
17515
|
+
|
|
17474
17516
|
function resolvePagePermissionMenuTargets(bound, flags = {}) {
|
|
17475
17517
|
const direct = [
|
|
17476
17518
|
...splitList(flags['menu-form-uuids']),
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const ROLE_UUID_PATTERN =
|
|
2
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3
|
+
|
|
4
|
+
function isRoleUuid(value) {
|
|
5
|
+
return ROLE_UUID_PATTERN.test(String(value || '').trim());
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function resolveRoleMembershipId(options = {}) {
|
|
9
|
+
const roleKey = String(options.roleKey || '').trim();
|
|
10
|
+
const explicitRoleId = String(options.explicitRoleId || '').trim();
|
|
11
|
+
const localRoleId = String(options.localRoleId || '').trim();
|
|
12
|
+
|
|
13
|
+
if (explicitRoleId) return explicitRoleId;
|
|
14
|
+
if (isRoleUuid(roleKey)) return roleKey;
|
|
15
|
+
if (localRoleId) return localRoleId;
|
|
16
|
+
if (!roleKey) throw roleResolutionError('ROLE_KEY_REQUIRED', '角色代码或 UUID 不能为空');
|
|
17
|
+
if (typeof options.listRoles !== 'function') {
|
|
18
|
+
throw roleResolutionError('ROLE_LIST_UNAVAILABLE', '无法查询应用角色列表');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const roles = await options.listRoles(roleKey);
|
|
22
|
+
const matches = (Array.isArray(roles) ? roles : []).filter(
|
|
23
|
+
role => String(role?.code || role?.roleCode || '').trim() === roleKey
|
|
24
|
+
);
|
|
25
|
+
if (matches.length === 0) {
|
|
26
|
+
throw roleResolutionError(
|
|
27
|
+
'ROLE_CODE_NOT_FOUND',
|
|
28
|
+
`应用角色代码 ${roleKey} 不存在;请先创建角色或使用 role-bind/角色 UUID`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (matches.length > 1) {
|
|
32
|
+
throw roleResolutionError(
|
|
33
|
+
'ROLE_CODE_AMBIGUOUS',
|
|
34
|
+
`应用角色代码 ${roleKey} 匹配到 ${matches.length} 个角色,无法安全确定 role UUID`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const roleId = String(matches[0]?.id || matches[0]?.roleId || '').trim();
|
|
39
|
+
if (!roleId) {
|
|
40
|
+
throw roleResolutionError(
|
|
41
|
+
'ROLE_ID_MISSING',
|
|
42
|
+
`应用角色代码 ${roleKey} 的查询结果缺少 role UUID`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return roleId;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function roleResolutionError(code, message) {
|
|
49
|
+
const error = new Error(`${code}: ${message}`);
|
|
50
|
+
error.code = code;
|
|
51
|
+
return error;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
isRoleUuid,
|
|
56
|
+
resolveRoleMembershipId,
|
|
57
|
+
};
|
|
@@ -80,6 +80,11 @@ Use role codes in permission group JSON. Bind existing role IDs only inside the
|
|
|
80
80
|
openxiangda permission role-bind sales --role-id <id> --profile dev
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
`role-users` and `role-add-users` accept either a role UUID or a logical role
|
|
84
|
+
code. When a code has no profile-local binding, the CLI resolves it from the
|
|
85
|
+
current application's role list and requires exactly one matching code; missing
|
|
86
|
+
or ambiguous matches fail explicitly instead of sending the code as a role ID.
|
|
87
|
+
|
|
83
88
|
For dynamic multi-role apps, do not hardcode role behavior only in page code. Create a role maintenance form and sync it to platform roles with automation / JS_CODE. Visible scope fields should be maintainable controls: personnel/department fields for people and orgs, `SelectField` / `RadioField` for enums, and `SelectField` with `optionSource.type: "linkedForm"` for records maintained by other forms. When the source form can have many records, set `remoteSearch: true` and `searchFieldId` so typing in the dropdown triggers an SDK query instead of loading every record. If form permission groups need scalar matching, derive hidden keys such as `collegeScopeKey`, `classScopeKey`, `ownerDeptScopeKey`, `ownerUserScopeKey`, and `roleCode`; do not expose raw ID text fields to users. Use page permission groups for entry visibility and form permission groups with condition-based data permissions for real data isolation.
|
|
84
89
|
|
|
85
90
|
For managed platform account governance, use organization/account forms and sync
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.248",
|
|
4
4
|
"description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -72,6 +72,7 @@
|
|
|
72
72
|
"test:changed": "npm run check && node scripts/run-test-suite.mjs --changed --evidence",
|
|
73
73
|
"test:contract": "npm run check && node scripts/run-test-suite.mjs --contract --evidence",
|
|
74
74
|
"test:release": "npm run check && node scripts/run-test-suite.mjs --release --evidence",
|
|
75
|
+
"test:release:sdk-admin-list": "npm run check && node scripts/run-test-suite.mjs --release-profile sdk-admin-list --evidence",
|
|
75
76
|
"test:critical": "npm run test:release",
|
|
76
77
|
"test:all": "node scripts/run-test-suite.mjs --all --evidence",
|
|
77
78
|
"test:full": "npm run test:all",
|
|
@@ -90,6 +91,8 @@
|
|
|
90
91
|
"test:design-gates": "node scripts/design-gates-smoke.mjs",
|
|
91
92
|
"test:resource-cli": "node scripts/resource-cli-smoke.mjs",
|
|
92
93
|
"test:permission-snapshot": "node scripts/permission-snapshot-smoke.mjs",
|
|
94
|
+
"test:permission-role-users": "node scripts/permission-role-users-smoke.mjs",
|
|
95
|
+
"test:sdk-admin-list": "vitest run packages/sdk/src/runtime/react/admin-list/AdminList.state.test.tsx packages/sdk/src/runtime/react/admin-list/AdminList.selection.test.tsx",
|
|
93
96
|
"test:sdk-bundle": "node scripts/sdk-browser-bundle-smoke.mjs",
|
|
94
97
|
"test:sdk-list-response": "vitest run packages/sdk/src/runtime/react/admin-list/dataSources.test.ts packages/sdk/src/components/core/dataManagementApi.test.ts",
|
|
95
98
|
"test:api-contract": "node scripts/openxiangda-api-contract-smoke.mjs",
|
|
@@ -44484,6 +44484,7 @@ function AdminList(props) {
|
|
|
44484
44484
|
const [notificationApi, notificationHolder] = import_antd12.notification.useNotification();
|
|
44485
44485
|
const [modalApi, modalHolder] = import_antd12.Modal.useModal();
|
|
44486
44486
|
const sourceKey = dataSource.key || "anonymous";
|
|
44487
|
+
const listIdentity = JSON.stringify([listKey, sourceKey]);
|
|
44487
44488
|
const [rawPreference, setRawPreference] = (0, import_react21.useState)(null);
|
|
44488
44489
|
const [preference, setPreference] = (0, import_react21.useState)(
|
|
44489
44490
|
() => mergeAdminListPreference({
|
|
@@ -44503,7 +44504,8 @@ function AdminList(props) {
|
|
|
44503
44504
|
const [settingsTab, setSettingsTab] = (0, import_react21.useState)("search");
|
|
44504
44505
|
const [columnSearch, setColumnSearch] = (0, import_react21.useState)("");
|
|
44505
44506
|
const [expanded, setExpanded] = (0, import_react21.useState)(false);
|
|
44506
|
-
const [
|
|
44507
|
+
const [readyIdentity, setReadyIdentity] = (0, import_react21.useState)(null);
|
|
44508
|
+
const ready = readyIdentity === listIdentity;
|
|
44507
44509
|
const [rows, setRows] = (0, import_react21.useState)([]);
|
|
44508
44510
|
const [total, setTotal] = (0, import_react21.useState)(0);
|
|
44509
44511
|
const [allowedColumnKeys, setAllowedColumnKeys] = (0, import_react21.useState)(void 0);
|
|
@@ -44526,9 +44528,12 @@ function AdminList(props) {
|
|
|
44526
44528
|
const [draggedSearchKey, setDraggedSearchKey] = (0, import_react21.useState)("");
|
|
44527
44529
|
const selectedKeysRef = (0, import_react21.useRef)([]);
|
|
44528
44530
|
const dataSourceRef = (0, import_react21.useRef)(dataSource);
|
|
44531
|
+
const listIdentityRef = (0, import_react21.useRef)(listIdentity);
|
|
44532
|
+
const requestVersionRef = (0, import_react21.useRef)(0);
|
|
44529
44533
|
const onQueryChangeRef = (0, import_react21.useRef)(onQueryChange);
|
|
44530
44534
|
const onSelectionChangeRef = (0, import_react21.useRef)(onSelectionChange);
|
|
44531
44535
|
dataSourceRef.current = dataSource;
|
|
44536
|
+
listIdentityRef.current = listIdentity;
|
|
44532
44537
|
onQueryChangeRef.current = onQueryChange;
|
|
44533
44538
|
onSelectionChangeRef.current = onSelectionChange;
|
|
44534
44539
|
const definitionFingerprint = JSON.stringify({
|
|
@@ -44599,10 +44604,19 @@ function AdminList(props) {
|
|
|
44599
44604
|
}, [rowKey, rowSelectable, rows]);
|
|
44600
44605
|
(0, import_react21.useEffect)(() => {
|
|
44601
44606
|
let active = true;
|
|
44602
|
-
|
|
44607
|
+
const initializationVersion = ++requestVersionRef.current;
|
|
44608
|
+
setReadyIdentity(null);
|
|
44609
|
+
setRows([]);
|
|
44610
|
+
setTotal(0);
|
|
44611
|
+
setAllowedColumnKeys(void 0);
|
|
44612
|
+
setHiddenFieldsCount(0);
|
|
44613
|
+
setLoading(false);
|
|
44614
|
+
setError(null);
|
|
44603
44615
|
const load = async () => {
|
|
44604
44616
|
const saved = dataSourceRef.current.preferences ? await dataSourceRef.current.preferences.get(listKey).catch(() => null) : null;
|
|
44605
|
-
if (!active)
|
|
44617
|
+
if (!active || initializationVersion !== requestVersionRef.current || listIdentityRef.current !== listIdentity) {
|
|
44618
|
+
return;
|
|
44619
|
+
}
|
|
44606
44620
|
setRawPreference(saved);
|
|
44607
44621
|
const merged = mergeAdminListPreference({
|
|
44608
44622
|
version: configVersion,
|
|
@@ -44630,7 +44644,7 @@ function AdminList(props) {
|
|
|
44630
44644
|
selectedKeysRef.current = [];
|
|
44631
44645
|
setSelectedKeys([]);
|
|
44632
44646
|
previousFingerprintRef.current = "";
|
|
44633
|
-
|
|
44647
|
+
setReadyIdentity(listIdentity);
|
|
44634
44648
|
};
|
|
44635
44649
|
void load();
|
|
44636
44650
|
return () => {
|
|
@@ -44642,8 +44656,7 @@ function AdminList(props) {
|
|
|
44642
44656
|
defaultVisibleSearchCount,
|
|
44643
44657
|
definitionFingerprint,
|
|
44644
44658
|
form,
|
|
44645
|
-
|
|
44646
|
-
sourceKey
|
|
44659
|
+
listIdentity
|
|
44647
44660
|
]);
|
|
44648
44661
|
(0, import_react21.useEffect)(() => {
|
|
44649
44662
|
if (!allowedColumnKeys) return;
|
|
@@ -44678,28 +44691,34 @@ function AdminList(props) {
|
|
|
44678
44691
|
]);
|
|
44679
44692
|
const loadData = (0, import_react21.useCallback)(async () => {
|
|
44680
44693
|
if (!ready) return;
|
|
44694
|
+
const requestVersion = ++requestVersionRef.current;
|
|
44695
|
+
const requestedIdentity = listIdentity;
|
|
44696
|
+
const requestedDataSource = dataSourceRef.current;
|
|
44697
|
+
const isCurrentRequest = () => requestVersion === requestVersionRef.current && requestedIdentity === listIdentityRef.current;
|
|
44681
44698
|
setLoading(true);
|
|
44682
44699
|
setError(null);
|
|
44683
44700
|
onQueryChangeRef.current?.(query);
|
|
44684
44701
|
try {
|
|
44685
|
-
const result = await
|
|
44702
|
+
const result = await requestedDataSource.query(query);
|
|
44703
|
+
if (!isCurrentRequest()) return;
|
|
44686
44704
|
setRows(result.rows || []);
|
|
44687
44705
|
setTotal(Math.max(0, Number(result.total || 0)));
|
|
44688
44706
|
setAllowedColumnKeys(result.allowedColumnKeys);
|
|
44689
44707
|
setHiddenFieldsCount(Math.max(0, Number(result.hiddenFieldsCount || 0)));
|
|
44690
44708
|
} catch (requestError) {
|
|
44709
|
+
if (!isCurrentRequest()) return;
|
|
44691
44710
|
setRows([]);
|
|
44692
44711
|
setTotal(0);
|
|
44693
44712
|
setError(
|
|
44694
44713
|
requestError instanceof Error ? requestError : new Error(String(requestError || "\u6570\u636E\u52A0\u8F7D\u5931\u8D25"))
|
|
44695
44714
|
);
|
|
44696
44715
|
} finally {
|
|
44697
|
-
setLoading(false);
|
|
44716
|
+
if (isCurrentRequest()) setLoading(false);
|
|
44698
44717
|
}
|
|
44699
|
-
}, [query, ready]);
|
|
44718
|
+
}, [listIdentity, query, ready]);
|
|
44700
44719
|
(0, import_react21.useEffect)(() => {
|
|
44701
44720
|
if (!ready) return;
|
|
44702
|
-
const fingerprint = queryFingerprint(query,
|
|
44721
|
+
const fingerprint = queryFingerprint(query, listIdentity);
|
|
44703
44722
|
if (previousFingerprintRef.current && previousFingerprintRef.current !== fingerprint && selectedKeysRef.current.length) {
|
|
44704
44723
|
clearSelection("\u67E5\u8BE2\u6761\u4EF6\u6216\u6392\u5E8F\u5DF2\u53D8\u5316\uFF0C\u5DF2\u6E05\u7A7A\u8DE8\u9875\u9009\u62E9");
|
|
44705
44724
|
}
|
|
@@ -44710,7 +44729,7 @@ function AdminList(props) {
|
|
|
44710
44729
|
loadData,
|
|
44711
44730
|
query,
|
|
44712
44731
|
ready,
|
|
44713
|
-
|
|
44732
|
+
listIdentity
|
|
44714
44733
|
]);
|
|
44715
44734
|
const visibleSearchFields = (0, import_react21.useMemo)(() => {
|
|
44716
44735
|
const searchMap = new Map(searchFields.map((field) => [field.key, field]));
|