openxiangda 1.0.129 → 1.0.131
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 +1 -0
- package/lib/cli.js +68 -8
- package/openxiangda-skills/references/pages/page-sdk.md +4 -0
- package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +6 -0
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +1 -1
- package/package.json +2 -1
- package/packages/sdk/dist/components/index.cjs +168 -17
- package/packages/sdk/dist/components/index.cjs.map +1 -1
- package/packages/sdk/dist/components/index.mjs +301 -150
- package/packages/sdk/dist/components/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/index.cjs +168 -17
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.mjs +285 -134
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
package/README.md
CHANGED
|
@@ -108,6 +108,7 @@ openxiangda auth-config methods --json
|
|
|
108
108
|
openxiangda function invoke submit_public_registration --body-json '{"input":{}}'
|
|
109
109
|
openxiangda connector invoke sms.sendCode --body-json '{"body":{"phone":"13800000000"}}'
|
|
110
110
|
openxiangda notification preview public_register_notice --body-json '{"payload":{"title":"测试"}}'
|
|
111
|
+
openxiangda permission snapshot --form-codes customer,orders --json
|
|
111
112
|
openxiangda permission audit --json
|
|
112
113
|
```
|
|
113
114
|
|
package/lib/cli.js
CHANGED
|
@@ -150,7 +150,7 @@ Usage:
|
|
|
150
150
|
openxiangda function list|get|create|update|upsert|delete|invoke [--json-file file]
|
|
151
151
|
openxiangda connector list|get|create|update|upsert|delete|invoke|download-test [--json-file file]
|
|
152
152
|
openxiangda notification template-list|template-get|template-upsert|template-delete|type-list|type-get|type-upsert|type-delete|preview|send|batch-send
|
|
153
|
-
openxiangda permission role-list|role-create|role-update|role-delete|role-bind|audit
|
|
153
|
+
openxiangda permission role-list|role-create|role-update|role-delete|role-bind|snapshot|audit
|
|
154
154
|
openxiangda permission page-group-list|page-group-create|page-group-update|page-group-delete|page-group-bind
|
|
155
155
|
openxiangda permission form-group-list|form-group-create|form-group-update|form-group-delete|form-group-bind
|
|
156
156
|
openxiangda settings get|save|indexes|indexes-save|data-management|data-management-save|public-access
|
|
@@ -3140,7 +3140,7 @@ async function permission(args) {
|
|
|
3140
3140
|
const { flags, positional } = parseArgs(rest);
|
|
3141
3141
|
if (wantsSubcommandHelp(subcommand, flags)) {
|
|
3142
3142
|
print(
|
|
3143
|
-
'用法: 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|audit'
|
|
3143
|
+
'用法: 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'
|
|
3144
3144
|
);
|
|
3145
3145
|
return;
|
|
3146
3146
|
}
|
|
@@ -3579,6 +3579,36 @@ async function permission(args) {
|
|
|
3579
3579
|
return;
|
|
3580
3580
|
}
|
|
3581
3581
|
|
|
3582
|
+
if (subcommand === 'snapshot') {
|
|
3583
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
3584
|
+
const formUuids = resolvePermissionSnapshotFormUuids(target.bound, flags);
|
|
3585
|
+
const data = await requestWithAuth(
|
|
3586
|
+
config,
|
|
3587
|
+
target.profileName,
|
|
3588
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/permissions/snapshot`,
|
|
3589
|
+
{
|
|
3590
|
+
method: 'POST',
|
|
3591
|
+
body: {
|
|
3592
|
+
formUuids,
|
|
3593
|
+
includeRoles: flags['no-roles'] ? false : undefined,
|
|
3594
|
+
includePageGroups: flags['no-page-groups'] ? false : undefined,
|
|
3595
|
+
includeFormGroups: flags['no-form-groups'] ? false : undefined,
|
|
3596
|
+
},
|
|
3597
|
+
}
|
|
3598
|
+
);
|
|
3599
|
+
if (flags.json) return writeJson(data);
|
|
3600
|
+
const formGroups = data.formPermissionGroups || {};
|
|
3601
|
+
print(
|
|
3602
|
+
[
|
|
3603
|
+
`Permission snapshot appType=${data.appType || target.appType}`,
|
|
3604
|
+
`roles=${Array.isArray(data.roles) ? data.roles.length : 0}`,
|
|
3605
|
+
`pageGroups=${Array.isArray(data.pagePermissionGroups) ? data.pagePermissionGroups.length : 0}`,
|
|
3606
|
+
`formGroups=${Number(formGroups.total || 0)}`,
|
|
3607
|
+
].join(' ')
|
|
3608
|
+
);
|
|
3609
|
+
return;
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3582
3612
|
if (subcommand === 'audit') {
|
|
3583
3613
|
const target = getWorkspaceTarget(config, profileName, flags);
|
|
3584
3614
|
const result = await buildPermissionAudit(config, target, flags);
|
|
@@ -3589,7 +3619,7 @@ async function permission(args) {
|
|
|
3589
3619
|
}
|
|
3590
3620
|
|
|
3591
3621
|
fail(
|
|
3592
|
-
'用法: 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|audit'
|
|
3622
|
+
'用法: 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'
|
|
3593
3623
|
);
|
|
3594
3624
|
}
|
|
3595
3625
|
|
|
@@ -4526,7 +4556,7 @@ async function commands(args) {
|
|
|
4526
4556
|
'function list|get|create|update|upsert|delete|invoke',
|
|
4527
4557
|
'connector list|get|create|update|upsert|delete|invoke|download-test',
|
|
4528
4558
|
'notification template-list|template-get|template-upsert|template-delete|type-list|type-get|type-upsert|type-delete|preview|send|batch-send',
|
|
4529
|
-
'permission role-list|role-create|role-update|role-delete|role-bind|role-users|role-add-users|audit',
|
|
4559
|
+
'permission role-list|role-create|role-update|role-delete|role-bind|role-users|role-add-users|snapshot|audit',
|
|
4530
4560
|
'permission page-group-list|page-group-create|page-group-update|page-group-delete|page-group-bind',
|
|
4531
4561
|
'permission form-group-list|form-group-create|form-group-update|form-group-delete|form-group-bind|form-summary|menu-permissions',
|
|
4532
4562
|
'settings get|save|indexes|indexes-save|data-management|data-management-save|public-access|public-access-save|public-access-delete',
|
|
@@ -5075,10 +5105,21 @@ async function buildPermissionAudit(config, target, flags = {}) {
|
|
|
5075
5105
|
warnings,
|
|
5076
5106
|
};
|
|
5077
5107
|
if (flags.live) {
|
|
5078
|
-
result.live =
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5108
|
+
result.live = await requestWithAuth(
|
|
5109
|
+
config,
|
|
5110
|
+
target.profileName,
|
|
5111
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(target.appType)}/permissions/snapshot`,
|
|
5112
|
+
{
|
|
5113
|
+
method: 'POST',
|
|
5114
|
+
body: {
|
|
5115
|
+
formUuids: unique(
|
|
5116
|
+
(manifest.formPermissionGroups || [])
|
|
5117
|
+
.map(group => resolveManifestFormUuid(target.bound, group))
|
|
5118
|
+
.filter(Boolean)
|
|
5119
|
+
),
|
|
5120
|
+
},
|
|
5121
|
+
}
|
|
5122
|
+
);
|
|
5082
5123
|
}
|
|
5083
5124
|
return result;
|
|
5084
5125
|
}
|
|
@@ -5117,6 +5158,25 @@ function resolveOptionalFormUuid(bound, formCode) {
|
|
|
5117
5158
|
return bound.resources?.forms?.[formCode]?.formUuid || formCode;
|
|
5118
5159
|
}
|
|
5119
5160
|
|
|
5161
|
+
function resolvePermissionSnapshotFormUuids(bound, flags = {}) {
|
|
5162
|
+
const directFormUuids = [
|
|
5163
|
+
...splitList(flags['form-uuids']),
|
|
5164
|
+
...splitList(flags['form-uuid']),
|
|
5165
|
+
];
|
|
5166
|
+
const mappedFormUuids = [
|
|
5167
|
+
...splitList(flags['form-codes']),
|
|
5168
|
+
...splitList(flags['form-code']),
|
|
5169
|
+
]
|
|
5170
|
+
.map(formCode => resolveOptionalFormUuid(bound, formCode))
|
|
5171
|
+
.filter(Boolean);
|
|
5172
|
+
const allFormUuids = flags['all-forms']
|
|
5173
|
+
? Object.values(bound.resources?.forms || {})
|
|
5174
|
+
.map(item => item?.formUuid)
|
|
5175
|
+
.filter(Boolean)
|
|
5176
|
+
: [];
|
|
5177
|
+
return unique([...directFormUuids, ...mappedFormUuids, ...allFormUuids]);
|
|
5178
|
+
}
|
|
5179
|
+
|
|
5120
5180
|
function resolveOptionalPageId(bound, pageCode) {
|
|
5121
5181
|
if (!pageCode) return undefined;
|
|
5122
5182
|
return bound.resources?.pages?.[pageCode]?.pageId || pageCode;
|
|
@@ -107,6 +107,10 @@ const result = await sdk.function.invoke("reservation_reminder_summary", {
|
|
|
107
107
|
})
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
App Function source should prefer `export default async function(ctx, input) {}`.
|
|
111
|
+
The second argument is the `input` passed by `sdk.function.invoke`, and the
|
|
112
|
+
same value is also available as `ctx.input` for compatibility.
|
|
113
|
+
|
|
110
114
|
Use App Functions when the logic must run server-side and be reusable by pages, automations, or workflows. Runtime invocation requires app automation management permission by default. To let ordinary app users call a function from a page, declare the allowed current app roles in `definitionJson.runtimeInvoke.roleCodes`; keep sensitive admin-only functions without broad grants. Inside the function, authorize sensitive actions with trusted runtime context such as `ctx.operator.roleCodes`, `ctx.operator.currentRoleCode`, `ctx.operator.hasFullAccess`, `ctx.currentUser`, or `ctx.permissions`; do not trust role codes sent in the page input. When the function writes forms with `ctx.form.createOne/updateOne/updateById`, treat the write as a trusted backend operation over declared `resources.forms`; do not open direct submit permission on internal forms just for that page action.
|
|
111
115
|
|
|
112
116
|
Work center:
|
|
@@ -127,8 +127,14 @@ for real backend read/write field access (`edit`, `readonly`, `hidden`) with
|
|
|
127
127
|
```bash
|
|
128
128
|
openxiangda permission form-summary --form-code customer --profile dev --json
|
|
129
129
|
openxiangda permission menu-permissions --profile dev --json
|
|
130
|
+
openxiangda permission snapshot --profile dev --form-codes customer,orders --json
|
|
130
131
|
```
|
|
131
132
|
|
|
133
|
+
When auditing many roles or permission groups, prefer `openxiangda permission
|
|
134
|
+
snapshot` over looping `role-list`, `page-group-list`, or `form-group-list`.
|
|
135
|
+
The snapshot command uses the platform batch endpoint and returns roles, page
|
|
136
|
+
permission groups, and form permission groups in one request.
|
|
137
|
+
|
|
132
138
|
## Form Settings
|
|
133
139
|
|
|
134
140
|
Use settings commands for form runtime settings, field indexes, data management pages, and public access:
|
|
@@ -103,7 +103,7 @@ Use `workflow pull` to inspect the live definition. Use `workflow list --json` a
|
|
|
103
103
|
|
|
104
104
|
JS_CODE is the backend execution escape hatch for workflow and automation. Use it when the logic must run on the server after a backend trigger, such as a fixed cron schedule, a form date-field schedule, a form submit/update/delete/field-change event, or a workflow approval/process event. It is appropriate for cross-form data queries, create/update/batch update operations, process termination, platform API calls, external HTTP calls, and complex orchestration that the frontend cannot handle reliably.
|
|
105
105
|
|
|
106
|
-
App Function is the reusable backend execution model. Use it when the logic should be called by custom pages, multiple automations, workflows, or the runtime API. Source lives in `src/functions/<functionCode>/index.ts`; manifest lives in `src/resources/functions/<functionCode>.json`. Call it from pages with `sdk.function.invoke(code, { input })`, from graph definitions with `function_call`, or from the runtime endpoint `/:appType/v1/functions/:code/invoke.json` when the caller has app automation management permission. Current MVP exposes controlled helpers only and does not expose raw SQL or Redis. `ctx.form.createOne/updateOne/updateById` are trusted backend writes governed by declared `resources.forms` and function invocation authorization; keep internal forms closed to direct user submit unless the business explicitly needs raw form submission.
|
|
106
|
+
App Function is the reusable backend execution model. Use it when the logic should be called by custom pages, multiple automations, workflows, or the runtime API. Source lives in `src/functions/<functionCode>/index.ts`; manifest lives in `src/resources/functions/<functionCode>.json`. Call it from pages with `sdk.function.invoke(code, { input })`, from graph definitions with `function_call`, or from the runtime endpoint `/:appType/v1/functions/:code/invoke.json` when the caller has app automation management permission. Prefer `export default async function(ctx, input) {}` for App Function source; the second argument is the invoke input and the same value is available as `ctx.input`. Current MVP exposes controlled helpers only and does not expose raw SQL or Redis. `ctx.form.createOne/updateOne/updateById` are trusted backend writes governed by declared `resources.forms` and function invocation authorization; keep internal forms closed to direct user submit unless the business explicitly needs raw form submission.
|
|
107
107
|
|
|
108
108
|
For new AI-authored automations, prefer code-first `automation_code_ts` resources instead of visual v3 graph definitions. Put the source in `src/automations/<resourceCode>/index.ts`, define `definition.code.json` with `kind: "automation_code_ts"`, and provide `preview.json` for read-only frontend display. Use `ctx.logger.debug/info/warn/error(message, data?)` at every important step; OpenXiangda can inspect logs with `automation executions`, `automation logs`, and `automation diagnose`.
|
|
109
109
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.131",
|
|
4
4
|
"description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -67,6 +67,7 @@
|
|
|
67
67
|
"test:resource-plan": "node scripts/resource-plan-smoke.mjs",
|
|
68
68
|
"test:design-gates": "node scripts/design-gates-smoke.mjs",
|
|
69
69
|
"test:resource-cli": "node scripts/resource-cli-smoke.mjs",
|
|
70
|
+
"test:permission-snapshot": "node scripts/permission-snapshot-smoke.mjs",
|
|
70
71
|
"test:sdk-bundle": "node scripts/sdk-browser-bundle-smoke.mjs",
|
|
71
72
|
"test:api-contract": "node scripts/openxiangda-api-contract-smoke.mjs",
|
|
72
73
|
"test:help-no-side-effects": "node scripts/help-no-side-effects-smoke.mjs",
|
|
@@ -7026,7 +7026,42 @@ var modeDepth = (mode) => {
|
|
|
7026
7026
|
return 3;
|
|
7027
7027
|
};
|
|
7028
7028
|
var activeLevels = (mode) => LEVELS.slice(0, modeDepth(mode));
|
|
7029
|
-
var
|
|
7029
|
+
var stringifyValue = (value) => {
|
|
7030
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
7031
|
+
return String(value);
|
|
7032
|
+
};
|
|
7033
|
+
var normalizeAddressPart = (part) => {
|
|
7034
|
+
if (part === void 0 || part === null || part === "") return void 0;
|
|
7035
|
+
if (typeof part === "object") {
|
|
7036
|
+
const value2 = stringifyValue(part.value ?? part.adcode ?? part.code ?? part.id ?? part.label);
|
|
7037
|
+
if (!value2) return void 0;
|
|
7038
|
+
const label = stringifyValue(part.label ?? part.name ?? part.title) ?? "";
|
|
7039
|
+
return { label, value: value2 };
|
|
7040
|
+
}
|
|
7041
|
+
const value = stringifyValue(part);
|
|
7042
|
+
return value ? { label: "", value } : void 0;
|
|
7043
|
+
};
|
|
7044
|
+
var normalizeAddressValue = (raw) => {
|
|
7045
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
7046
|
+
const next = {};
|
|
7047
|
+
LEVELS.forEach((level) => {
|
|
7048
|
+
const part = normalizeAddressPart(raw[level]);
|
|
7049
|
+
if (part) next[level] = part;
|
|
7050
|
+
});
|
|
7051
|
+
if (raw.detail !== void 0 && raw.detail !== null) {
|
|
7052
|
+
next.detail = String(raw.detail);
|
|
7053
|
+
}
|
|
7054
|
+
if (typeof raw.fullAddress === "string" && raw.fullAddress) {
|
|
7055
|
+
next.fullAddress = raw.fullAddress;
|
|
7056
|
+
}
|
|
7057
|
+
return Object.keys(next).length > 0 ? next : void 0;
|
|
7058
|
+
};
|
|
7059
|
+
var getDisplayLabel = (part) => {
|
|
7060
|
+
const label = part?.label?.trim();
|
|
7061
|
+
if (!label) return void 0;
|
|
7062
|
+
return label === part?.value ? void 0 : label;
|
|
7063
|
+
};
|
|
7064
|
+
var valueToPath = (value, levels) => levels.map((level) => value?.[level]?.value).filter(Boolean);
|
|
7030
7065
|
var normalizeDivision = (item, level, depth, index) => ({
|
|
7031
7066
|
label: String(item.name || item.label || item.adcode || item.value),
|
|
7032
7067
|
value: String(item.adcode || item.value || item.id),
|
|
@@ -7035,7 +7070,66 @@ var normalizeDivision = (item, level, depth, index) => ({
|
|
|
7035
7070
|
});
|
|
7036
7071
|
var composeFullAddress = (value, levels = LEVELS) => {
|
|
7037
7072
|
if (!value) return "";
|
|
7038
|
-
return [...levels.map((level) => value[level]
|
|
7073
|
+
return [...levels.map((level) => getDisplayLabel(value[level])), value.detail].filter(Boolean).join("");
|
|
7074
|
+
};
|
|
7075
|
+
var mergeValuePathIntoOptions = (source, value, levels, depth) => {
|
|
7076
|
+
if (!value) return source;
|
|
7077
|
+
const mergeAt = (items, index) => {
|
|
7078
|
+
const level = levels[index];
|
|
7079
|
+
const part = level ? value[level] : void 0;
|
|
7080
|
+
if (!level || !part?.value) return items;
|
|
7081
|
+
let matched = false;
|
|
7082
|
+
const nextItems = items.map((item) => {
|
|
7083
|
+
if (String(item.value) !== part.value) return item;
|
|
7084
|
+
matched = true;
|
|
7085
|
+
const label = getDisplayLabel(part) ?? getDisplayLabel(item) ?? "\u5730\u5740\u52A0\u8F7D\u4E2D";
|
|
7086
|
+
const hasNextPath = Boolean(levels[index + 1] && value[levels[index + 1]]?.value);
|
|
7087
|
+
return {
|
|
7088
|
+
...item,
|
|
7089
|
+
label,
|
|
7090
|
+
level: item.level ?? level,
|
|
7091
|
+
isLeaf: index + 1 >= depth || !hasNextPath && item.isLeaf === true,
|
|
7092
|
+
children: hasNextPath ? mergeAt(item.children ?? [], index + 1) : item.children
|
|
7093
|
+
};
|
|
7094
|
+
});
|
|
7095
|
+
if (!matched) {
|
|
7096
|
+
const hasNextPath = Boolean(levels[index + 1] && value[levels[index + 1]]?.value);
|
|
7097
|
+
const option = {
|
|
7098
|
+
label: getDisplayLabel(part) ?? "\u5730\u5740\u52A0\u8F7D\u4E2D",
|
|
7099
|
+
value: part.value,
|
|
7100
|
+
level,
|
|
7101
|
+
isLeaf: index + 1 >= depth
|
|
7102
|
+
};
|
|
7103
|
+
if (hasNextPath) option.children = mergeAt([], index + 1);
|
|
7104
|
+
nextItems.push(option);
|
|
7105
|
+
}
|
|
7106
|
+
return nextItems;
|
|
7107
|
+
};
|
|
7108
|
+
return mergeAt(source, 0);
|
|
7109
|
+
};
|
|
7110
|
+
var setNestedChildren = (source, selectedOptions, children) => {
|
|
7111
|
+
const [current, ...rest] = selectedOptions;
|
|
7112
|
+
if (!current) return source;
|
|
7113
|
+
const currentValue = String(current.value);
|
|
7114
|
+
let matched = false;
|
|
7115
|
+
const next = source.map((item) => {
|
|
7116
|
+
if (String(item.value) !== currentValue) return item;
|
|
7117
|
+
matched = true;
|
|
7118
|
+
return {
|
|
7119
|
+
...item,
|
|
7120
|
+
children: rest.length ? setNestedChildren(item.children ?? [], rest, children) : children
|
|
7121
|
+
};
|
|
7122
|
+
});
|
|
7123
|
+
if (!matched) {
|
|
7124
|
+
next.push({
|
|
7125
|
+
label: String(current.label || current.value),
|
|
7126
|
+
value: currentValue,
|
|
7127
|
+
level: current.level,
|
|
7128
|
+
isLeaf: false,
|
|
7129
|
+
children: rest.length ? setNestedChildren([], rest, children) : children
|
|
7130
|
+
});
|
|
7131
|
+
}
|
|
7132
|
+
return next;
|
|
7039
7133
|
};
|
|
7040
7134
|
function AddressField(props) {
|
|
7041
7135
|
const {
|
|
@@ -7057,7 +7151,8 @@ function AddressField(props) {
|
|
|
7057
7151
|
const { formData, fieldBehaviors, setFieldValue, registerField, unregisterField, api } = useFormContext();
|
|
7058
7152
|
const { isMobile } = useDeviceDetect();
|
|
7059
7153
|
const behavior = propBehavior ?? fieldBehaviors[fieldId] ?? "NORMAL";
|
|
7060
|
-
const
|
|
7154
|
+
const rawValue = formData[fieldId];
|
|
7155
|
+
const value = (0, import_react42.useMemo)(() => normalizeAddressValue(rawValue), [rawValue]);
|
|
7061
7156
|
const [options, setOptions] = (0, import_react42.useState)([]);
|
|
7062
7157
|
const [loadingRoots, setLoadingRoots] = (0, import_react42.useState)(false);
|
|
7063
7158
|
const [mobileOpen, setMobileOpen] = (0, import_react42.useState)(false);
|
|
@@ -7067,24 +7162,78 @@ function AddressField(props) {
|
|
|
7067
7162
|
const [tempValue, setTempValue] = (0, import_react42.useState)();
|
|
7068
7163
|
const depth = modeDepth(mode);
|
|
7069
7164
|
const levels = (0, import_react42.useMemo)(() => activeLevels(mode), [mode]);
|
|
7165
|
+
const valuePath = (0, import_react42.useMemo)(() => valueToPath(value, levels), [levels, value]);
|
|
7166
|
+
const displayOptions = (0, import_react42.useMemo)(
|
|
7167
|
+
() => mergeValuePathIntoOptions(options, value, levels, depth),
|
|
7168
|
+
[depth, levels, options, value]
|
|
7169
|
+
);
|
|
7070
7170
|
const detailEnabled = mode === "province-city-district-street-detail";
|
|
7071
7171
|
const disabled = behavior === "DISABLED";
|
|
7072
7172
|
const MobilePopup = getMobileComponent3("Popup");
|
|
7073
7173
|
(0, import_react42.useEffect)(() => {
|
|
7074
7174
|
registerField(fieldId);
|
|
7075
|
-
|
|
7175
|
+
return () => unregisterField(fieldId);
|
|
7176
|
+
}, [fieldId, registerField, unregisterField]);
|
|
7177
|
+
(0, import_react42.useEffect)(() => {
|
|
7178
|
+
if (defaultValue !== void 0 && rawValue === void 0) {
|
|
7076
7179
|
setFieldValue(fieldId, defaultValue);
|
|
7077
7180
|
}
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7181
|
+
}, [defaultValue, fieldId, rawValue, setFieldValue]);
|
|
7182
|
+
const loadDivisions = (0, import_react42.useCallback)(
|
|
7183
|
+
async (parentAdcode, level, index) => {
|
|
7184
|
+
const list = await api.getChinaDivisions(parentAdcode);
|
|
7185
|
+
return list.map((item) => normalizeDivision(item, level, depth, index));
|
|
7186
|
+
},
|
|
7187
|
+
[api, depth]
|
|
7188
|
+
);
|
|
7084
7189
|
(0, import_react42.useEffect)(() => {
|
|
7190
|
+
let mounted = true;
|
|
7085
7191
|
setLoadingRoots(true);
|
|
7086
|
-
loadDivisions(void 0, "province", 0).then(
|
|
7087
|
-
|
|
7192
|
+
loadDivisions(void 0, "province", 0).then((nextOptions) => {
|
|
7193
|
+
if (mounted) setOptions(nextOptions);
|
|
7194
|
+
}).finally(() => {
|
|
7195
|
+
if (mounted) setLoadingRoots(false);
|
|
7196
|
+
});
|
|
7197
|
+
return () => {
|
|
7198
|
+
mounted = false;
|
|
7199
|
+
};
|
|
7200
|
+
}, [loadDivisions]);
|
|
7201
|
+
(0, import_react42.useEffect)(() => {
|
|
7202
|
+
if (!value) return;
|
|
7203
|
+
let cancelled = false;
|
|
7204
|
+
const hydrateMissingLabels = async () => {
|
|
7205
|
+
let nextValue = { ...value };
|
|
7206
|
+
let changed = false;
|
|
7207
|
+
let parentAdcode;
|
|
7208
|
+
for (let index = 0; index < levels.length; index += 1) {
|
|
7209
|
+
const level = levels[index];
|
|
7210
|
+
const part = nextValue[level];
|
|
7211
|
+
if (!part?.value) break;
|
|
7212
|
+
if (!getDisplayLabel(part)) {
|
|
7213
|
+
const divisions = await loadDivisions(parentAdcode, level, index);
|
|
7214
|
+
const matched = divisions.find((option) => option.value === part.value);
|
|
7215
|
+
if (matched) {
|
|
7216
|
+
nextValue = { ...nextValue, [level]: { label: matched.label, value: matched.value } };
|
|
7217
|
+
changed = true;
|
|
7218
|
+
}
|
|
7219
|
+
}
|
|
7220
|
+
parentAdcode = part.value;
|
|
7221
|
+
}
|
|
7222
|
+
const fullAddress = composeFullAddress(nextValue, levels);
|
|
7223
|
+
if (fullAddress && nextValue.fullAddress !== fullAddress) {
|
|
7224
|
+
nextValue = { ...nextValue, fullAddress };
|
|
7225
|
+
changed = true;
|
|
7226
|
+
}
|
|
7227
|
+
if (!cancelled && changed) {
|
|
7228
|
+
setFieldValue(fieldId, nextValue);
|
|
7229
|
+
}
|
|
7230
|
+
};
|
|
7231
|
+
hydrateMissingLabels().catch(() => {
|
|
7232
|
+
});
|
|
7233
|
+
return () => {
|
|
7234
|
+
cancelled = true;
|
|
7235
|
+
};
|
|
7236
|
+
}, [fieldId, levels, loadDivisions, setFieldValue, value]);
|
|
7088
7237
|
(0, import_react42.useEffect)(() => {
|
|
7089
7238
|
if (!mobileOpen) return;
|
|
7090
7239
|
const level = levels[mobileLevelIndex] || "province";
|
|
@@ -7092,7 +7241,7 @@ function AddressField(props) {
|
|
|
7092
7241
|
const parentAdcode = parentLevel ? tempValue?.[parentLevel]?.value : void 0;
|
|
7093
7242
|
setMobileLoading(true);
|
|
7094
7243
|
loadDivisions(parentAdcode, level, mobileLevelIndex).then(setMobileOptions).finally(() => setMobileLoading(false));
|
|
7095
|
-
}, [mobileOpen, mobileLevelIndex, tempValue, levels]);
|
|
7244
|
+
}, [loadDivisions, mobileOpen, mobileLevelIndex, tempValue, levels]);
|
|
7096
7245
|
if (behavior === "HIDDEN") return null;
|
|
7097
7246
|
const setAddressValue = (next) => {
|
|
7098
7247
|
const normalized = next ? { ...next, fullAddress: composeFullAddress(next, levels) } : void 0;
|
|
@@ -7228,18 +7377,20 @@ function AddressField(props) {
|
|
|
7228
7377
|
import_antd18.Cascader,
|
|
7229
7378
|
{
|
|
7230
7379
|
style: { width: "100%" },
|
|
7231
|
-
options,
|
|
7380
|
+
options: displayOptions,
|
|
7232
7381
|
allowClear,
|
|
7233
7382
|
disabled,
|
|
7234
|
-
value:
|
|
7383
|
+
value: valuePath,
|
|
7235
7384
|
placeholder: props.placeholder || "\u8BF7\u9009\u62E9\u5730\u5740",
|
|
7236
7385
|
loading: loadingRoots,
|
|
7386
|
+
displayRender: (labels) => labels.filter(Boolean).join(" / ") || (valuePath.length ? "\u5730\u5740\u52A0\u8F7D\u4E2D" : ""),
|
|
7237
7387
|
loadData: async (selectedOptions) => {
|
|
7238
7388
|
const target = selectedOptions[selectedOptions.length - 1];
|
|
7239
7389
|
const nextLevel = levels[selectedOptions.length] ?? "street";
|
|
7240
7390
|
const children = await loadDivisions(target.value, nextLevel, selectedOptions.length);
|
|
7241
|
-
|
|
7242
|
-
|
|
7391
|
+
setOptions(
|
|
7392
|
+
(currentOptions) => setNestedChildren(currentOptions, selectedOptions, children)
|
|
7393
|
+
);
|
|
7243
7394
|
},
|
|
7244
7395
|
onChange: (_path, selectedOptions) => {
|
|
7245
7396
|
if (!selectedOptions?.length) {
|