openyida 2026.10.19 → 2026.10.20-2
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 +43 -2
- package/bin/yida.js +28 -3
- package/lib/app/agent-app-type.js +12 -0
- package/lib/app/app-settings.js +38 -0
- package/lib/app/application-entry-urls.js +9 -1
- package/lib/app/cool-card.js +207 -0
- package/lib/app/create-form.js +19 -46
- package/lib/app/custom-button.js +372 -0
- package/lib/app/data-card-builder.js +188 -0
- package/lib/app/data-card-release.js +191 -0
- package/lib/app/data-card.js +251 -0
- package/lib/app/form-open-guard.js +2 -2
- package/lib/app/services/canvas-page-schema-builder.js +6 -1
- package/lib/app/services/form-compiler.js +3 -0
- package/lib/app/upgrade-app-theme.js +2 -1
- package/lib/core/command-manifest.js +169 -10
- package/lib/core/locales/en.js +138 -1
- package/lib/core/locales/zh.js +138 -1
- package/lib/core/query-data.js +30 -20
- package/lib/core/sample.js +3 -0
- package/lib/core/task-center.js +68 -4
- package/lib/dws/dws-wrapper.js +7 -56
- package/lib/formula/catalog.json +1 -0
- package/lib/formula/cli-contract.js +46 -0
- package/lib/formula/commands.js +165 -0
- package/lib/formula/config.js +68 -0
- package/lib/formula/contracts.js +26 -0
- package/lib/formula/evaluate.js +47 -67
- package/lib/formula/metadata.js +44 -0
- package/lib/formula/runtime.js +212 -0
- package/lib/formula/syntax.js +162 -0
- package/lib/integration/integration-create.js +2 -1
- package/lib/integration/integration-data-retrieve.js +12 -5
- package/lib/integration/integration-process-builder.js +10 -4
- package/lib/integration/integration-spec-builder.js +45 -11
- package/lib/report/chart-builder.js +7 -1
- package/lib/report/configure.js +151 -0
- package/lib/report/i18n-messages.js +22 -0
- package/lib/report/inspect.js +7 -0
- package/lib/report/validation.js +3 -0
- package/lib/report/zero-values.js +148 -0
- package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +14 -0
- package/lib/samples/yida-cool-card/project-brief.json +5 -0
- package/lib/samples/yida-task-center/center.canvas.jsx +169 -0
- package/lib/samples/yida-task-center/filters.jsx +59 -0
- package/lib/samples/yida-task-center/progress.canvas.jsx +91 -0
- package/lib/task-center/contract.js +249 -0
- package/lib/task-center/loader.js +63 -0
- package/lib/task-center/lookups.js +82 -0
- package/lib/task-center/scaffold.js +178 -0
- package/lib/task-center/workflow-loader.js +72 -0
- package/lib/task-center/workflow.js +118 -0
- package/package.json +2 -1
- package/scripts/postinstall.js +5 -2
- package/yida-skills/SKILL.md +21 -7
- package/yida-skills/references/formula-functions.md +151 -315
- package/yida-skills/references/routing-supplement.md +4 -0
- package/yida-skills/skills/yida-app/SKILL.md +2 -0
- package/yida-skills/skills/yida-app/workflow/plan/workflow.md +2 -0
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +2 -0
- package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +3 -1
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +2 -0
- package/yida-skills/skills/yida-business-rule/SKILL.md +2 -0
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +8 -0
- package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +11 -1
- package/yida-skills/skills/yida-canvas-data-binding/references/workflow-capabilities.md +79 -0
- package/yida-skills/skills/yida-cool-card/SKILL.md +66 -0
- package/yida-skills/skills/yida-cool-card/references/configuration.md +80 -0
- package/yida-skills/skills/yida-cool-card/references/design.md +90 -0
- package/yida-skills/skills/yida-create-form-page/SKILL.md +4 -0
- package/yida-skills/skills/yida-create-form-page/references/advanced-form-modes.md +6 -0
- package/yida-skills/skills/yida-custom-button/SKILL.md +71 -0
- package/yida-skills/skills/yida-custom-page/SKILL.md +2 -0
- package/yida-skills/skills/yida-data-card/SKILL.md +94 -0
- package/yida-skills/skills/yida-data-card/references/chart-config.md +73 -0
- package/yida-skills/skills/yida-data-card/references/release.md +77 -0
- package/yida-skills/skills/yida-data-management/SKILL.md +7 -1
- package/yida-skills/skills/yida-design/SKILL.md +2 -0
- package/yida-skills/skills/yida-flow-node-config/SKILL.md +1 -0
- package/yida-skills/skills/yida-flow-node-config/references/conditions-variables.md +6 -1
- package/yida-skills/skills/yida-flow-node-config/references/control.md +6 -0
- package/yida-skills/skills/yida-flow-node-config/references/data.md +8 -0
- package/yida-skills/skills/yida-formula/SKILL.md +152 -373
- package/yida-skills/skills/yida-formula/references/automation-formulas.md +100 -0
- package/yida-skills/skills/yida-formula/references/examples.md +3 -3
- package/yida-skills/skills/yida-formula/references/expression-formats.md +85 -0
- package/yida-skills/skills/yida-formula/references/field-values.md +205 -0
- package/yida-skills/skills/yida-formula/references/nested-examples.md +189 -0
- package/yida-skills/skills/yida-formula/references/node-examples.md +243 -0
- package/yida-skills/skills/yida-formula/references/runtime-examples.md +131 -0
- package/yida-skills/skills/yida-formula-evaluate/SKILL.md +137 -39
- package/yida-skills/skills/yida-integration/SKILL.md +4 -2
- package/yida-skills/skills/yida-integration/references/integration-node-schemas.md +4 -2
- package/yida-skills/skills/yida-integration/references/spec-authoring.md +2 -0
- package/yida-skills/skills/yida-process-rule/SKILL.md +2 -0
- package/yida-skills/skills/yida-report/SKILL.md +37 -1
- package/yida-skills/skills/yida-report/references/zero-values.md +59 -0
- package/yida-skills/skills/yida-task-center/SKILL.md +23 -0
- package/yida-skills/skills/yida-task-center/references/task-filters.md +128 -0
- package/yida-skills/skills-index.json +315 -32
package/README.md
CHANGED
|
@@ -95,6 +95,22 @@ Local terminals and AI tools share the same policy. Managed cloud Agent runtimes
|
|
|
95
95
|
|
|
96
96
|
If Codex is already installed, OpenYida also imports a local Codex plugin during postinstall. Restart Codex after installation, then type `@宜搭` or `@openyida` in the composer to attach the OpenYida context.
|
|
97
97
|
|
|
98
|
+
<details>
|
|
99
|
+
<summary>Optional: install DingTalk Workspace CLI (dws)</summary>
|
|
100
|
+
|
|
101
|
+
For DingTalk A2UI authoring, validation or messaging, install the official [DingTalk Workspace CLI](https://github.com/DingTalk-Real-AI/dingtalk-workspace-cli#installation) separately:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npm install -g dingtalk-workspace-cli
|
|
105
|
+
dws version
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Alternatively, `openyida dws install` displays official instructions for your platform without running an installer. Once installed, `openyida dws <command> [args]` forwards arguments to your system's dws.
|
|
109
|
+
|
|
110
|
+
OpenYida does not bundle or automatically install dws. Dws manages its own version, updates and login. Use `dws auth login` for DingTalk business access and verify the target organization and account. Native YiDA cool-card creation, reads and editor links work without dws; updating a chat card through dws does not save a YiDA template. See the official instructions for other installation methods and check the installed command's `--help` before use.
|
|
111
|
+
|
|
112
|
+
</details>
|
|
113
|
+
|
|
98
114
|
### 2. Check Your Environment
|
|
99
115
|
|
|
100
116
|
Run this from the AI coding workspace where you want OpenYida to operate:
|
|
@@ -447,6 +463,9 @@ Your AI prepares the summary from observed evidence and selected reproduction sc
|
|
|
447
463
|
|
|
448
464
|
| Command | Description |
|
|
449
465
|
|---------|-------------|
|
|
466
|
+
| `openyida app-settings <appType> [--section theme\|basic\|data-card\|cool-card] [--json]` | Read theme, frontend/backend, native data card and cool card management links |
|
|
467
|
+
| `openyida cool-card <list\|templates\|create\|get> <appType> [cardPageCode] [--type im\|onebox] [--name <title>] [--page <n>] [--size <n>] [--search <text>] [--out <file>] [--json]` | Create and read native YiDA cool cards (interactive and pinned) |
|
|
468
|
+
| `openyida data-card <list\|create\|get\|save\|publish\|probe\|release-get\|release-save\|release-check\|release-test\|refs> <appType> [cardCode] [--name <title>] [--description <text>] [--schema <file>\|--config <file>] [--expected-hash <hash>] [--backup <file>] [--out <file>] [--published\|--version <version>] [--page <n>] [--size <n>] [--search <text>] [--allow-send] [--json]` | Create, configure, publish and distribute native YiDA data cards |
|
|
450
469
|
| `openyida app-entry get <appType> [--json]` | Read frontend and management runtime entries |
|
|
451
470
|
| `openyida app-entry set <appType> [--frontend <url>\|--frontend-page <formUuid>] [--management <url>\|--management-page <formUuid>] [--clear-frontend] [--clear-management] [--json]` | Register application entries with partial update and readback |
|
|
452
471
|
| `openyida app-list [--type managed\|created] [--page N] [--size N]` | Page through apps I manage or created |
|
|
@@ -491,6 +510,7 @@ Your AI prepares the summary from observed evidence and selected reproduction sc
|
|
|
491
510
|
| `openyida list-forms <appType> [--keyword <text>]` | List forms/pages in an app |
|
|
492
511
|
| `openyida aggregate-table <list\|create-empty\|inspect\|preview\|save\|publish\|status> <appType> ...` | Manage aggregate tables (virtualView) |
|
|
493
512
|
| `openyida form-view <list\|inspect\|save\|create\|order> <appType> <formUuid> [viewUuid] [--config <file>] [--name <title>] [--with-config] [--expected-hash <hash>] [--backup <file>] [--replace] [--json]` | Native form views: list, inspect and configure columns, widths, frozen columns and filters |
|
|
513
|
+
| `openyida custom-button <list\|inspect\|options\|create\|update\|delete> <appType> <formUuid> [buttonUuid] [--config <file>] [--expected-hash <hash>] [--backup <file>] [--yes] [--json]` | Native custom buttons: inspect and configure form edits, automation flows and page links |
|
|
494
514
|
| `openyida get-schema <appType> <formUuid\|--all> [--summary-json\|--field-map-json\|--analysis-json]` | Get one form Schema or all form Schemas |
|
|
495
515
|
| `openyida check-prd-completeness <prd.md> --app-type <appType> [--build-manifest <file>] [--json]` | Check resources, frontend menu hiding and application theme delivery |
|
|
496
516
|
| `openyida check-design <design.md> [--prd <prd.md>] [--base-dir <dir>] [--json]` | Validate design documents, theme tokens and PRD handoff |
|
|
@@ -508,7 +528,7 @@ Your AI prepares the summary from observed evidence and selected reproduction sc
|
|
|
508
528
|
| Command | Description |
|
|
509
529
|
|---------|-------------|
|
|
510
530
|
| `openyida data <query\|get\|create\|update> <resource> ... \| delete form <appType> <formUuid> --inst-id <id> --expect-form-name <name> --expect-form-type receipt --confirm [--json]` | Unified data management (form/process/task/subform) |
|
|
511
|
-
| `openyida task-center <
|
|
531
|
+
| `openyida task-center <todo\|created\|done\|cc\|proxy\|capabilities\|scaffold\|options\|inspect\|count> [--spec <file.json> --output <file.canvas.jsx>] [options]` | Global task center (todo/processed/cc etc.) |
|
|
512
532
|
| `openyida basic-info <overview\|commodity\|grant\|capacity\|quota\|abs-path\|dataflow\|i18n\|domain>` | Query organization basic info, capacity, quotas, and domain settings |
|
|
513
533
|
| `openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]` | Fetch Markdown content from a DingTalk document |
|
|
514
534
|
| `openyida read-dingtalk-tingji <taskUuid> [--json]` | Fetch DingTalk Tingji details by task UUID |
|
|
@@ -545,6 +565,7 @@ Your AI prepares the summary from observed evidence and selected reproduction sc
|
|
|
545
565
|
| `openyida create-report <appType> "<name>" ... [--json] [--open\|--no-open]` | Create a Yida report |
|
|
546
566
|
| `openyida append-chart <appType> <reportId> ... [--json] [--open\|--no-open]` | Append chart to existing report |
|
|
547
567
|
| `openyida report inspect <appType> <reportId> --json` | Inspect report runtime bindings (read-only) |
|
|
568
|
+
| `openyida report configure <appType> <reportId> <cid> --hide-zero-values <true\|false> [--hide-zero-categories <true\|false>] [--hide-zero-legends <true\|false>] [--dry-run] [--json]` | Configure dynamic zero-value hiding on a native bar chart (explicit opt-in) |
|
|
548
569
|
|
|
549
570
|
### Connectors
|
|
550
571
|
|
|
@@ -598,7 +619,13 @@ Your AI prepares the summary from observed evidence and selected reproduction sc
|
|
|
598
619
|
| `openyida sample [--list] [<skill> <name>] [--output <file-or-directory>] [--var KEY=VALUE ...] [--design-file <design.md>] [--style-id <themeId\|free-creative>] [--theme-ids <id,...>] [--pop-art-context <explicit-request\|showcase>]` | Output code samples/templates |
|
|
599
620
|
| `openyida doctor [--fix]` | Environment diagnostics & auto-fix |
|
|
600
621
|
| `openyida db-seq-fix [--fix]` | Detect and repair PostgreSQL sequence drift |
|
|
601
|
-
| `openyida formula evaluate <formula\|file> [--schema file]` | Static-check Yida formula syntax and field refs |
|
|
622
|
+
| `openyida formula evaluate <formula\|file> [--schema file] [--contract field-value\|validation-rule\|business-action\|node-expression] [--context form\|validation\|automation\|business] [--target-type NumberField] [--metadata file] [--strict] [--json]` | Static-check Yida formula syntax and field refs |
|
|
623
|
+
| `openyida formula check <formula\|file> [--schema file] [--contract field-value\|validation-rule\|business-action\|node-expression] [--context form\|validation\|automation\|business] [--target-type NumberField] [--metadata file] [--strict] [--json]` | Check formula syntax, fields, arguments and types |
|
|
624
|
+
| `openyida formula contracts [--json]` | Show formula formats and checker coverage |
|
|
625
|
+
| `openyida formula functions <appType> [--name IF] [--context form\|validation\|automation\|business] [--output file] [--json]` | Read Yida formula functions and signatures |
|
|
626
|
+
| `openyida formula types <appType> [--component NumberField] [--json]` | Query formula component value types |
|
|
627
|
+
| `openyida formula inspect <appType> <formUuid> [--field fieldId] [--json]` | Inspect configured form field formulas |
|
|
628
|
+
| `openyida formula test <appType> <formUuid> [formula\|file] [--data-file file \| --cases file] [--field fieldId] [--timezone-offset minutes] [--context form\|validation] [--binding platform\|numeric-literals] [--expect JSON] [--json]` | Evaluate a non-writing formula in Yida and compare its result |
|
|
602
629
|
| `openyida update` | Check and update to latest version |
|
|
603
630
|
| `openyida export-conversation [options]` | Export AI conversation records |
|
|
604
631
|
| `openyida feedback <report\|setup\|url\|dismiss\|status> [options]` | Prepare local issue reports, configure feedback forms and reminders |
|
|
@@ -612,6 +639,20 @@ Your AI prepares the summary from observed evidence and selected reproduction sc
|
|
|
612
639
|
|
|
613
640
|
<!-- OPENYIDA_COMMANDS_END -->
|
|
614
641
|
|
|
642
|
+
### Personal tasks and process progress
|
|
643
|
+
|
|
644
|
+
Query application tasks with `openyida data query tasks <appType> --type todo --normalized`; `created`, `done`, and `cc` select the other personal task lists. Use `openyida task-center capabilities --json` for supported filters and the page configuration example.
|
|
645
|
+
|
|
646
|
+
The default `app-v1` transport preserves the existing queries. Select `--transport app-query` for additional server filters while keeping the query fixed to the target application: `--originators '["USER_ID"]'` accepts one originator, `--process-codes '["PROCESS_CODE"]'` selects processes, and date bounds use Unix milliseconds. Available fields vary by task kind; unsupported fields fail before a request. `openyida task-center options <appType> --type originators|processes|forms --keyword TEXT --json` returns readable lookup options. The CLI never silently falls back to an unfiltered query.
|
|
647
|
+
|
|
648
|
+
After explicitly choosing a custom task page, run `openyida task-center scaffold --spec <file.json> --output <new-file.canvas.jsx>`. The configuration requires the choice record and the application's existing design file. Generated pages follow the application theme, read the current visitor's tasks, and open native pages for processing. `embedded` mode provides the same task section for an existing workbench. Compile and publish the page using the usual commands.
|
|
649
|
+
|
|
650
|
+
Page configurations can explicitly set `transport: "app-query"` and per-kind initial filters, for example `filters: { "todo": { "originators": ["USER_ID"] } }`. Generated pages include the same `filterSchema`, `normalizeFilters`, task loader, and option lookup helpers as the CLI.
|
|
651
|
+
|
|
652
|
+
Use `task-center count <appType> --type todo --transport app-query --filters-json '{"keyword":"purchase"}' --json` for a one-record query with a matching server total; an unknown total remains `null`. `task-center inspect <appType> --process-inst-id <id> [--form-uuid <id>] [--history] [--task-id <id>] --json` reads an authorized linked process and optional operation history. Personal task outcomes, selected-task outcomes and final process results remain separate. Read commands do not create custom pages or require a custom-page choice.
|
|
653
|
+
|
|
654
|
+
Scaffolds select only the needed task kinds, `fields` and `filterFields`; the default is a single todo list. `view: "progress", mode: "embedded"` provides a progress component for an existing business page, using its real linked `processInstanceId`. History and task-row expansion are opt-in. See the [workflow capability reference](yida-skills/skills/yida-canvas-data-binding/references/workflow-capabilities.md) for configuration and integration examples.
|
|
655
|
+
|
|
615
656
|
### CLI Notes
|
|
616
657
|
|
|
617
658
|
#### Connector Safety and Real E2E
|
package/bin/yida.js
CHANGED
|
@@ -761,6 +761,23 @@ async function main() {
|
|
|
761
761
|
break;
|
|
762
762
|
}
|
|
763
763
|
|
|
764
|
+
case 'cool-card': {
|
|
765
|
+
const { run } = require('../lib/app/cool-card');
|
|
766
|
+
await run(args);
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
case 'data-card': {
|
|
770
|
+
const { run } = require('../lib/app/data-card');
|
|
771
|
+
await run(args);
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
case 'app-settings': {
|
|
776
|
+
const { run } = require('../lib/app/app-settings');
|
|
777
|
+
await run(args);
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
|
|
764
781
|
case 'app-entry': {
|
|
765
782
|
const { run } = require('../lib/app/app-entry');
|
|
766
783
|
await run(args);
|
|
@@ -815,6 +832,11 @@ async function main() {
|
|
|
815
832
|
break;
|
|
816
833
|
}
|
|
817
834
|
|
|
835
|
+
case 'custom-button': {
|
|
836
|
+
const { run } = require('../lib/app/custom-button');
|
|
837
|
+
await run(args);
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
818
840
|
case 'form-view': {
|
|
819
841
|
const { run } = require('../lib/app/form-view');
|
|
820
842
|
await run(args);
|
|
@@ -856,6 +878,8 @@ async function main() {
|
|
|
856
878
|
if (subCommand === 'evaluate' || subCommand === 'check') {
|
|
857
879
|
const { run } = require('../lib/formula/evaluate');
|
|
858
880
|
await run(subArgs);
|
|
881
|
+
} else if (['contracts', 'functions', 'types', 'inspect', 'test'].includes(subCommand)) {
|
|
882
|
+
await require('../lib/formula/commands').run(subCommand, subArgs);
|
|
859
883
|
} else {
|
|
860
884
|
throwCliUsage(t('cli.formula_usage'), t('cli.formula_example'));
|
|
861
885
|
}
|
|
@@ -1115,10 +1139,11 @@ async function main() {
|
|
|
1115
1139
|
|
|
1116
1140
|
case 'report': {
|
|
1117
1141
|
const subCommand = args[0];
|
|
1118
|
-
if (
|
|
1119
|
-
throwCliUsage('用法: openyida report inspect
|
|
1142
|
+
if (!['inspect', 'configure'].includes(subCommand)) {
|
|
1143
|
+
throwCliUsage('用法: openyida report <inspect|configure> ... --json');
|
|
1120
1144
|
}
|
|
1121
|
-
const { run } =
|
|
1145
|
+
const { run } = subCommand === 'inspect'
|
|
1146
|
+
? require('../lib/report/inspect') : require('../lib/report/configure');
|
|
1122
1147
|
await run(args.slice(1));
|
|
1123
1148
|
break;
|
|
1124
1149
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Server-derived provenance: channels may grow; only absence or "none" means non-AI.
|
|
4
|
+
function isAgentAppType(value) {
|
|
5
|
+
if (typeof value !== 'string') {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const type = value.trim().toLowerCase();
|
|
9
|
+
return type !== '' && type !== 'none';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = { isAgentAppType };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CliError } = require('../core/cli-error');
|
|
4
|
+
const { t } = require('../core/i18n');
|
|
5
|
+
const { createAuthRef, createYidaClient } = require('../core/yida-client');
|
|
6
|
+
const { buildApplicationSettingsUrls } = require('./application-entry-urls');
|
|
7
|
+
|
|
8
|
+
function parseArgs(args = []) {
|
|
9
|
+
if (args.includes('--help') || args.includes('-h')) { return { help: true }; }
|
|
10
|
+
const [appType, ...rest] = args;
|
|
11
|
+
let section;
|
|
12
|
+
let json = false;
|
|
13
|
+
for (let i = 0; i < rest.length; i++) {
|
|
14
|
+
if (rest[i] === '--json' && !json) { json = true; }
|
|
15
|
+
else if (rest[i] === '--section' && section === undefined) { section = rest[++i]; }
|
|
16
|
+
else { throw new CliError(t('app_settings.usage'), { code: 'CLI_USAGE_ERROR' }); }
|
|
17
|
+
}
|
|
18
|
+
if (!/^APP_[A-Za-z0-9_-]+$/.test(appType || '') || (rest.includes('--section') && !['theme', 'basic', 'data-card', 'cool-card'].includes(section))) {
|
|
19
|
+
throw new CliError(t('app_settings.usage'), { code: 'CLI_USAGE_ERROR' });
|
|
20
|
+
}
|
|
21
|
+
return { appType, section };
|
|
22
|
+
}
|
|
23
|
+
async function execute({ appType, section }, client = createYidaClient({ authRef: createAuthRef() })) {
|
|
24
|
+
const response = await client.get(`/${appType}/query/app/getAppIncludingAecpInfo.json`, { appKey: appType });
|
|
25
|
+
if (response?.success !== true || response.__needLogin || response.__csrfExpired || !response.content
|
|
26
|
+
|| response.content.appType !== appType) {
|
|
27
|
+
throw new CliError(t('app_settings.failed', response?.errorMsg || appType), { code: 'APP_SETTINGS_READ_FAILED' });
|
|
28
|
+
}
|
|
29
|
+
const urls = buildApplicationSettingsUrls({ appType, baseUrl: client.getAuthRef().baseUrl, systemLink: response.content.systemLink });
|
|
30
|
+
const key = { theme: 'themeSettingsUrl', basic: 'basicSettingsUrl', 'data-card': 'dataCardManageUrl', 'cool-card': 'coolCardManageUrl' }[section];
|
|
31
|
+
return { success: true, appType, urls, ...(key ? { url: urls[key] } : {}), runtimeVerified: false };
|
|
32
|
+
}
|
|
33
|
+
async function run(args = []) {
|
|
34
|
+
const parsed = parseArgs(args);
|
|
35
|
+
if (parsed.help) { console.log(t('app_settings.usage')); return; }
|
|
36
|
+
console.log(JSON.stringify(await execute(parsed), null, 2));
|
|
37
|
+
}
|
|
38
|
+
module.exports = { run, execute, parseArgs };
|
|
@@ -36,6 +36,14 @@ function readRenderNav(config) {
|
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function buildApplicationSettingsUrls(options) {
|
|
40
|
+
const { adminUrl } = buildApplicationEntryUrls(options);
|
|
41
|
+
if (!adminUrl) { return {}; }
|
|
42
|
+
const url = new URL(adminUrl);
|
|
43
|
+
const settings = suffix => `${url.origin}${url.pathname}/appSetting/${suffix}${url.search}`;
|
|
44
|
+
return { themeSettingsUrl: settings('themeSetting'), basicSettingsUrl: settings('basicSetting'), dataCardManageUrl: settings('dataCardManage'), coolCardManageUrl: settings('coolCardManage') };
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
function buildDisplayPageEntryUrls({ appType, formUuid, baseUrl, config, formType = config?.formType }) {
|
|
40
48
|
if (formType !== 'display' || !/^FORM[-_][A-Za-z0-9_-]+$/.test(formUuid || '')) {return {};}
|
|
41
49
|
if (config?.formType && config.formType !== 'display') {return {};}
|
|
@@ -54,4 +62,4 @@ function buildDisplayPageEntryUrls({ appType, formUuid, baseUrl, config, formTyp
|
|
|
54
62
|
};
|
|
55
63
|
}
|
|
56
64
|
|
|
57
|
-
module.exports = { buildApplicationEntryUrls, buildDisplayPageEntryUrls, readRenderNav };
|
|
65
|
+
module.exports = { buildApplicationEntryUrls, buildApplicationSettingsUrls, buildDisplayPageEntryUrls, readRenderNav };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const { CliError } = require('../core/cli-error');
|
|
7
|
+
const { t } = require('../core/i18n');
|
|
8
|
+
const { createAuthRef, createYidaClient } = require('../core/yida-client');
|
|
9
|
+
const { buildApplicationSettingsUrls } = require('./application-entry-urls');
|
|
10
|
+
|
|
11
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
12
|
+
const validApp = value => /^APP_[A-Za-z0-9_-]+$/.test(value || '');
|
|
13
|
+
const validCard = value => /^form[A-Za-z0-9_-]+$/.test(value || '');
|
|
14
|
+
const validType = value => ['im', 'onebox'].includes(value);
|
|
15
|
+
const capabilities = Object.freeze({ create: true, read: true, templates: true, save: false, publish: false, send: false });
|
|
16
|
+
|
|
17
|
+
function fail(code, message, details) {
|
|
18
|
+
throw new CliError(t('cool_card.failed', message), { code: `COOL_CARD_${code}`, details });
|
|
19
|
+
}
|
|
20
|
+
function parseArgs(args = []) {
|
|
21
|
+
const positional = [], flags = {};
|
|
22
|
+
const allowed = {
|
|
23
|
+
list: ['--type', '--page', '--size', '--search'],
|
|
24
|
+
templates: ['--type', '--page', '--size', '--search'],
|
|
25
|
+
create: ['--type', '--name', '--out'], get: ['--out'],
|
|
26
|
+
};
|
|
27
|
+
for (let i = 0; i < args.length; i++) {
|
|
28
|
+
const arg = args[i];
|
|
29
|
+
if (['--help', '-h'].includes(arg)) { return { help: true }; }
|
|
30
|
+
if (arg === '--json' || ['--type', '--name', '--out', '--page', '--size', '--search'].includes(arg)) {
|
|
31
|
+
if (Object.hasOwn(flags, arg)) { fail('USAGE', t('cool_card.usage')); }
|
|
32
|
+
if (arg === '--json') { flags[arg] = true; }
|
|
33
|
+
else {
|
|
34
|
+
if (!args[i + 1] || args[i + 1].startsWith('-')) { fail('USAGE', t('cool_card.usage')); }
|
|
35
|
+
flags[arg] = args[++i];
|
|
36
|
+
}
|
|
37
|
+
} else if (arg.startsWith('-')) { fail('USAGE', t('cool_card.usage')); }
|
|
38
|
+
else { positional.push(arg); }
|
|
39
|
+
}
|
|
40
|
+
const [action, appType, cardPageCode] = positional;
|
|
41
|
+
if (!allowed[action] || !validApp(appType) || positional.length !== (action === 'get' ? 3 : 2)
|
|
42
|
+
|| (cardPageCode && !validCard(cardPageCode))
|
|
43
|
+
|| Object.keys(flags).some(key => key !== '--json' && !allowed[action].includes(key))
|
|
44
|
+
|| (flags['--type'] !== undefined && !validType(flags['--type']))
|
|
45
|
+
|| (['create', 'templates'].includes(action) && !validType(flags['--type']))
|
|
46
|
+
|| (action === 'create' && !flags['--name']?.trim())) { fail('USAGE', t('cool_card.usage')); }
|
|
47
|
+
for (const [flag, max] of [['--page', 1000], ['--size', 100]]) {
|
|
48
|
+
if (flags[flag] !== undefined && (!/^\d+$/.test(flags[flag]) || +flags[flag] < 1 || +flags[flag] > max)) {
|
|
49
|
+
fail('USAGE', t('cool_card.usage'));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { action, appType, cardPageCode, flags };
|
|
53
|
+
}
|
|
54
|
+
function unwrap(response, endpoint) {
|
|
55
|
+
if (response?.success !== true || response.__needLogin || response.__csrfExpired) {
|
|
56
|
+
fail('API_ERROR', response?.errorMsg || response?.message || endpoint, { endpoint, platformErrorCode: response?.errorCode });
|
|
57
|
+
}
|
|
58
|
+
return response.content;
|
|
59
|
+
}
|
|
60
|
+
function parseObject(value, label) {
|
|
61
|
+
let result = value;
|
|
62
|
+
if (typeof value === 'string') {
|
|
63
|
+
try { result = JSON.parse(value); } catch { fail('INVALID_RESPONSE', label); }
|
|
64
|
+
}
|
|
65
|
+
if (!object(result)) { fail('INVALID_RESPONSE', label); }
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
function validateCard(card, appType, cardPageCode) {
|
|
69
|
+
if (!object(card) || card.appId !== appType || !validCard(card.cardPageCode)
|
|
70
|
+
|| (cardPageCode && card.cardPageCode !== cardPageCode) || !validType(card.cardType)) {
|
|
71
|
+
fail('TARGET_MISMATCH', cardPageCode || appType);
|
|
72
|
+
}
|
|
73
|
+
return card;
|
|
74
|
+
}
|
|
75
|
+
function decodeCard(card) {
|
|
76
|
+
// New blank resources can have no template yet. Do not call that a completed configuration.
|
|
77
|
+
const template = card.cardSchema ? parseObject(card.cardSchema, 'cardSchema') : null;
|
|
78
|
+
const editorData = template?.editorData ? parseObject(template.editorData, 'editorData') : null;
|
|
79
|
+
if (editorData?.schema?.componentsTree !== undefined && !Array.isArray(editorData.schema.componentsTree)) {
|
|
80
|
+
fail('INVALID_RESPONSE', 'componentsTree');
|
|
81
|
+
}
|
|
82
|
+
const components = new Set();
|
|
83
|
+
const visit = node => {
|
|
84
|
+
if (!object(node)) { return; }
|
|
85
|
+
if (typeof node.componentName === 'string') { components.add(node.componentName); }
|
|
86
|
+
if (Array.isArray(node.children)) { node.children.forEach(visit); }
|
|
87
|
+
};
|
|
88
|
+
(editorData?.schema?.componentsTree || []).forEach(visit);
|
|
89
|
+
return { template, editorData, components: [...components].sort(), variableList: editorData?.variableList || [],
|
|
90
|
+
templateIdsMatch: template?.templateId ? template.templateId === card.cardTemplateId : null,
|
|
91
|
+
publishStatus: template?.publishStatus || null };
|
|
92
|
+
}
|
|
93
|
+
function cardUrls(appType, card, baseUrl) {
|
|
94
|
+
const settings = buildApplicationSettingsUrls({ appType, baseUrl });
|
|
95
|
+
if (!settings.coolCardManageUrl) { fail('INVALID_TARGET', 'baseUrl'); }
|
|
96
|
+
// Link to the official editor; this command does not establish its DingTalk session.
|
|
97
|
+
const templateId = card.cardTemplateId;
|
|
98
|
+
const editorUrl = /(^|\.)aliwork\.com$/.test(new URL(baseUrl).hostname) && /^[A-Za-z0-9-]+$/.test(templateId || '')
|
|
99
|
+
? `https://editor.dingtalk.com/editor?templateId=${encodeURIComponent(templateId)}&scene=yida_card&bizId=${encodeURIComponent(`${appType}.${card.cardPageCode}`)}#/` : null;
|
|
100
|
+
return { coolCardManageUrl: settings.coolCardManageUrl, editorUrl };
|
|
101
|
+
}
|
|
102
|
+
function userHandoff(action, appType, card, urls) {
|
|
103
|
+
// Existing cards enter through management so the platform can prepare the editable version.
|
|
104
|
+
const directEditor = action === 'create' && urls.editorUrl;
|
|
105
|
+
return { actor: 'user', action: action === 'create' ? 'configure-and-save' : 'review-or-edit',
|
|
106
|
+
target: directEditor ? 'official-editor' : 'cool-card-management',
|
|
107
|
+
url: directEditor || urls.coolCardManageUrl,
|
|
108
|
+
verificationCommand: `openyida cool-card get ${appType} ${card.cardPageCode} --json` };
|
|
109
|
+
}
|
|
110
|
+
function exportFile(filename, snapshot) {
|
|
111
|
+
const output = path.resolve(filename);
|
|
112
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
113
|
+
fs.writeFileSync(output, JSON.stringify(snapshot, null, 2), { mode: 0o600, flag: 'wx' });
|
|
114
|
+
return output;
|
|
115
|
+
}
|
|
116
|
+
async function execute(parsed, client) {
|
|
117
|
+
// Revalidate programmatic callers before auth or any network operation.
|
|
118
|
+
const flags = parsed.flags || {};
|
|
119
|
+
const argv = [parsed.action, parsed.appType, ...(parsed.cardPageCode ? [parsed.cardPageCode] : [])];
|
|
120
|
+
for (const [key, value] of Object.entries(flags)) { argv.push(key, ...(value === true ? [] : [value])); }
|
|
121
|
+
const checked = parseArgs(argv);
|
|
122
|
+
if (checked.help) { fail('USAGE', t('cool_card.usage')); }
|
|
123
|
+
const { action, appType } = checked;
|
|
124
|
+
let { cardPageCode } = checked;
|
|
125
|
+
if (flags['--out'] && fs.existsSync(path.resolve(flags['--out']))) { fail('OUTPUT_EXISTS', flags['--out']); }
|
|
126
|
+
client = client || createYidaClient({ authRef: createAuthRef() });
|
|
127
|
+
const prefix = `/${appType}/query/cardDesign/`;
|
|
128
|
+
const get = async (name, params) => unwrap(await client.get(prefix + name + '.json', params), name);
|
|
129
|
+
const base = { success: true, action, appType, resourceType: 'native-cool-card', capabilities };
|
|
130
|
+
if (['list', 'templates'].includes(action)) {
|
|
131
|
+
const content = await get(action === 'list' ? 'listCards' : 'listTpls', {
|
|
132
|
+
cardType: flags['--type'] || '', currentPage: +(flags['--page'] || 1), pageSize: +(flags['--size'] || 50),
|
|
133
|
+
searchName: flags['--search'] || '', ...(action === 'list' ? { excludeOfficialCard: false } : {}),
|
|
134
|
+
});
|
|
135
|
+
if (!object(content) || !Array.isArray(content.data) || !Number.isFinite(+content.totalCount) || +content.totalCount < 0) {
|
|
136
|
+
fail('INVALID_RESPONSE', action);
|
|
137
|
+
}
|
|
138
|
+
// Template catalog entries may be owned by a different application; listing does not grant mutation rights.
|
|
139
|
+
const data = content.data.map(card => {
|
|
140
|
+
if (action === 'list') { validateCard(card, appType); }
|
|
141
|
+
return Object.fromEntries(Object.entries(card).filter(([key]) => !['cardSchema', 'variableConfig'].includes(key)));
|
|
142
|
+
});
|
|
143
|
+
return { ...content, ...base, data };
|
|
144
|
+
}
|
|
145
|
+
let created;
|
|
146
|
+
let listedCard;
|
|
147
|
+
if (action === 'create') {
|
|
148
|
+
// Allowlist the complete write body. No entitlements, app tags, official-card switches or send options.
|
|
149
|
+
created = unwrap(await client.postFormOnce(prefix + 'createCard.json', {
|
|
150
|
+
cardName: flags['--name'], cardType: flags['--type'],
|
|
151
|
+
}), 'createCard');
|
|
152
|
+
try { validateCard(created, appType); }
|
|
153
|
+
catch (error) {
|
|
154
|
+
error.details = { ...error.details, mutationAccepted: true, inspectBeforeRetry: true, appType,
|
|
155
|
+
...(validCard(created?.cardPageCode) ? { cardPageCode: created.cardPageCode } : {}) };
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
cardPageCode = created.cardPageCode;
|
|
159
|
+
try {
|
|
160
|
+
const list = await get('listCards', { cardType: flags['--type'], currentPage: 1, pageSize: 100,
|
|
161
|
+
searchName: flags['--name'], excludeOfficialCard: false });
|
|
162
|
+
listedCard = Array.isArray(list?.data) ? list.data.find(item => item?.cardPageCode === cardPageCode) : null;
|
|
163
|
+
validateCard(listedCard, appType, cardPageCode);
|
|
164
|
+
if (listedCard.cardName !== flags['--name'] || listedCard.cardType !== flags['--type']) { fail('READBACK_MISMATCH', cardPageCode); }
|
|
165
|
+
} catch (error) {
|
|
166
|
+
error.details = { ...error.details, appType, cardPageCode, resourceCreated: true, inspectBeforeRetry: true };
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
let card;
|
|
171
|
+
try {
|
|
172
|
+
card = validateCard(await get('getCardSchema', { cardAppId: appType, cardPageCode }), appType, cardPageCode);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (!created) { throw error; }
|
|
175
|
+
// A real newly-created resource may have a template ID before that template exists.
|
|
176
|
+
// Verify its resource metadata independently; retain the failed schema read rather than inventing an empty schema.
|
|
177
|
+
const urls = cardUrls(appType, listedCard, client.getAuthRef().baseUrl);
|
|
178
|
+
const result = { ...base, cardPageCode, card: listedCard, completion: 'resource-only', readbackVerified: true,
|
|
179
|
+
schemaReadbackVerified: false, configurationVerified: false, deliveryVerified: false,
|
|
180
|
+
schemaReadbackError: { code: error.code || 'READ_FAILED', message: error.message, details: error.details },
|
|
181
|
+
urls, nextAction: userHandoff(action, appType, listedCard, urls) };
|
|
182
|
+
if (flags['--out']) { result.snapshotFile = exportFile(flags['--out'], { ...result }); }
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
if (created && (card.cardType !== flags['--type'] || card.cardName !== flags['--name'])) { fail('READBACK_MISMATCH', cardPageCode); }
|
|
187
|
+
const decoded = decodeCard(card);
|
|
188
|
+
const snapshot = { resourceType: base.resourceType, appType, cardPageCode, card };
|
|
189
|
+
const configHash = crypto.createHash('sha256').update(JSON.stringify(snapshot)).digest('hex');
|
|
190
|
+
const urls = cardUrls(appType, card, client.getAuthRef().baseUrl);
|
|
191
|
+
const result = { ...base, cardPageCode, card, ...decoded, configHash,
|
|
192
|
+
readbackVerified: true, schemaReadbackVerified: decoded.template !== null,
|
|
193
|
+
...(decoded.template ? {} : { completion: 'resource-only' }), configurationVerified: false, deliveryVerified: false,
|
|
194
|
+
urls, nextAction: userHandoff(action, appType, card, urls) };
|
|
195
|
+
if (flags['--out']) { result.snapshotFile = exportFile(flags['--out'], snapshot); }
|
|
196
|
+
return result;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (created) { error.details = { ...error.details, appType, cardPageCode, resourceCreated: true, inspectBeforeRetry: true }; }
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async function run(args = []) {
|
|
203
|
+
const parsed = parseArgs(args);
|
|
204
|
+
if (parsed.help) { console.log(t('cool_card.usage')); return; }
|
|
205
|
+
console.log(JSON.stringify(await execute(parsed), null, 2));
|
|
206
|
+
}
|
|
207
|
+
module.exports = { parseArgs, execute, run, decodeCard, capabilities };
|
package/lib/app/create-form.js
CHANGED
|
@@ -78,6 +78,7 @@ const { banner, step, label, success, fail, warn, info, error, result, usage, hi
|
|
|
78
78
|
const { parseOpenOption: parseBrowserOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
|
|
79
79
|
const { default: babelTransform } = require('../core/babel-transform');
|
|
80
80
|
const { normalizeFormulaFieldRefs } = require('../formula/evaluate');
|
|
81
|
+
const { applyFieldFormula, formulaFields, validateSchemaFormulas } = require('../formula/config');
|
|
81
82
|
const { createParseArgs } = require('./create-form/args');
|
|
82
83
|
const { createDefinitionReaders } = require('./create-form/definition-reader');
|
|
83
84
|
const { dispatchCreateFormCommand } = require('./create-form/commands');
|
|
@@ -458,6 +459,14 @@ function validateFormFieldDefinitions(fields, rootPath) {
|
|
|
458
459
|
rootPath: rootPath || 'fields',
|
|
459
460
|
});
|
|
460
461
|
throwFieldValidationDiagnostics(fields, diagnostics, rootPath || 'fields');
|
|
462
|
+
function validateFormulas(items) {
|
|
463
|
+
for (const field of items || []) {
|
|
464
|
+
applyFieldFormula({}, field, normalizeFormDefinitionType(field));
|
|
465
|
+
if (Array.isArray(field.children)) { validateFormulas(field.children); }
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
validateFormulas(fields);
|
|
469
|
+
if (!rootPath) { validateSchemaFormulas(buildFormNodeComponents(fields)); }
|
|
461
470
|
}
|
|
462
471
|
|
|
463
472
|
function throwFieldValidationDiagnostics(fields, diagnostics, rootPath) {
|
|
@@ -729,7 +738,7 @@ function buildFieldComponent(field) {
|
|
|
729
738
|
props.__gridSpan = 1;
|
|
730
739
|
props.tips = i18n('', '');
|
|
731
740
|
props.linkage = '';
|
|
732
|
-
props.precision = 0;
|
|
741
|
+
props.precision = field.precision === undefined ? 0 : field.precision;
|
|
733
742
|
props.step = 1;
|
|
734
743
|
props.thousandsSeparators = false;
|
|
735
744
|
props.innerAfter = field.innerAfter || '';
|
|
@@ -1240,6 +1249,8 @@ function buildFieldComponent(field) {
|
|
|
1240
1249
|
props.placeholder = i18n(field.placeholder);
|
|
1241
1250
|
}
|
|
1242
1251
|
|
|
1252
|
+
applyFieldFormula(props, field, componentName);
|
|
1253
|
+
|
|
1243
1254
|
const component = {
|
|
1244
1255
|
componentName: componentName,
|
|
1245
1256
|
id: nodeId,
|
|
@@ -1429,6 +1440,7 @@ function resolveFieldIdReferences(fieldComponents) {
|
|
|
1429
1440
|
if (resolvedId) {
|
|
1430
1441
|
info(t('create_form.filling_rule_resolved', targetLabel, resolvedId));
|
|
1431
1442
|
rule.target = resolvedId;
|
|
1443
|
+
rule.targetFieldId = resolvedId;
|
|
1432
1444
|
} else {
|
|
1433
1445
|
warn(t('create_form.filling_rule_failed', targetLabel));
|
|
1434
1446
|
}
|
|
@@ -1440,6 +1452,7 @@ function resolveFieldIdReferences(fieldComponents) {
|
|
|
1440
1452
|
if (resolvedSourceId) {
|
|
1441
1453
|
info(t('create_form.filling_rule_resolved', sourceLabel, resolvedSourceId));
|
|
1442
1454
|
rule.source = resolvedSourceId;
|
|
1455
|
+
rule.sourceFieldId = resolvedSourceId;
|
|
1443
1456
|
} else {
|
|
1444
1457
|
warn(t('create_form.filling_rule_failed', sourceLabel));
|
|
1445
1458
|
}
|
|
@@ -1465,6 +1478,7 @@ function resolveFieldIdReferences(fieldComponents) {
|
|
|
1465
1478
|
if (resolvedId) {
|
|
1466
1479
|
info(t('create_form.table_rule_resolved', ruleIndex + 1, targetLabel, resolvedId));
|
|
1467
1480
|
rule.target = resolvedId;
|
|
1481
|
+
rule.targetFieldId = resolvedId;
|
|
1468
1482
|
} else {
|
|
1469
1483
|
warn(t('create_form.table_rule_failed', targetLabel));
|
|
1470
1484
|
}
|
|
@@ -1650,6 +1664,7 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, t
|
|
|
1650
1664
|
// 解析主题配置
|
|
1651
1665
|
const themeConfig = getThemeConfig(theme || 'default', labelAlign || 'top');
|
|
1652
1666
|
const fieldComponents = buildFormNodeComponents(fields);
|
|
1667
|
+
validateSchemaFormulas(fieldComponents);
|
|
1653
1668
|
const formContainerChildren = layoutConfig.groupFields
|
|
1654
1669
|
? buildGroupedFieldComponents(fields, layoutConfig.formLayout)
|
|
1655
1670
|
: fieldComponents;
|
|
@@ -2038,15 +2053,6 @@ function findFormContainer(node) {
|
|
|
2038
2053
|
return null;
|
|
2039
2054
|
}
|
|
2040
2055
|
|
|
2041
|
-
function findFieldIndexByLabel(fields, label) {
|
|
2042
|
-
for (let fieldIndex = 0; fieldIndex < fields.length; fieldIndex++) {
|
|
2043
|
-
if (extractLabelText(fields[fieldIndex]) === label) {
|
|
2044
|
-
return fieldIndex;
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
return -1;
|
|
2048
|
-
}
|
|
2049
|
-
|
|
2050
2056
|
function findFieldByLabelDeep(fields, label) {
|
|
2051
2057
|
if (!Array.isArray(fields)) {
|
|
2052
2058
|
return null;
|
|
@@ -2166,30 +2172,6 @@ function collectFieldReferences(fields, options) {
|
|
|
2166
2172
|
return refs;
|
|
2167
2173
|
}
|
|
2168
2174
|
|
|
2169
|
-
function collectFieldMatchesByLabelDeep(fields, label, tableAncestors, matches) {
|
|
2170
|
-
const result = matches || [];
|
|
2171
|
-
const ancestors = Array.isArray(tableAncestors) ? tableAncestors : [];
|
|
2172
|
-
if (!Array.isArray(fields)) {
|
|
2173
|
-
return result;
|
|
2174
|
-
}
|
|
2175
|
-
fields.forEach(function (field, fieldIndex) {
|
|
2176
|
-
const labelText = extractLabelText(field);
|
|
2177
|
-
if (labelText === label) {
|
|
2178
|
-
result.push({
|
|
2179
|
-
field,
|
|
2180
|
-
fields,
|
|
2181
|
-
index: fieldIndex,
|
|
2182
|
-
evidence: buildFieldEvidence(field, ancestors),
|
|
2183
|
-
});
|
|
2184
|
-
}
|
|
2185
|
-
const nextAncestors = field && field.componentName === 'TableField'
|
|
2186
|
-
? ancestors.concat(compactTableAncestorEvidence(field))
|
|
2187
|
-
: ancestors;
|
|
2188
|
-
collectFieldMatchesByLabelDeep(field && field.children, label, nextAncestors, result);
|
|
2189
|
-
});
|
|
2190
|
-
return result;
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
2175
|
function collectFieldCandidates(fields, tableAncestors, query) {
|
|
2194
2176
|
const candidates = [];
|
|
2195
2177
|
const normalizedQuery = query ? String(query).trim() : '';
|
|
@@ -2424,18 +2406,6 @@ function resolveFieldReference(fields, input, options) {
|
|
|
2424
2406
|
};
|
|
2425
2407
|
}
|
|
2426
2408
|
|
|
2427
|
-
function requireResolvedField(fields, input, options) {
|
|
2428
|
-
const resolution = resolveFieldReference(fields, input, options);
|
|
2429
|
-
if (resolution.ok) {
|
|
2430
|
-
return resolution;
|
|
2431
|
-
}
|
|
2432
|
-
const err = createCreateFormError(t('create_form.field_resolution_failed'), 'CREATE_FORM_FIELD_RESOLUTION_FAILED', {
|
|
2433
|
-
diagnostics: [resolution.diagnostic],
|
|
2434
|
-
});
|
|
2435
|
-
err.diagnostics = [resolution.diagnostic];
|
|
2436
|
-
throw err;
|
|
2437
|
-
}
|
|
2438
|
-
|
|
2439
2409
|
function outputFieldResolutionFailure(appType, formUuid, diagnostics) {
|
|
2440
2410
|
const fieldDiagnostics = Array.isArray(diagnostics) ? diagnostics : [];
|
|
2441
2411
|
fail(t('create_form.field_resolution_failed'));
|
|
@@ -4517,6 +4487,7 @@ function applyFieldChanges(component, changes) {
|
|
|
4517
4487
|
props[key] = changes[key];
|
|
4518
4488
|
}
|
|
4519
4489
|
});
|
|
4490
|
+
applyFieldFormula(props, changes, component.componentName);
|
|
4520
4491
|
}
|
|
4521
4492
|
|
|
4522
4493
|
function ensureComponentsMap(schema, componentName) {
|
|
@@ -4565,6 +4536,7 @@ function countDataFieldDefinitions(fields) {
|
|
|
4565
4536
|
// ── 应用修改操作(update 模式) ─────────────────────
|
|
4566
4537
|
|
|
4567
4538
|
function applyChangesToSchema(schema, changes, options) {
|
|
4539
|
+
const previousFormulas = formulaFields(schema);
|
|
4568
4540
|
const verbose = Boolean(options && options.verbose);
|
|
4569
4541
|
const componentsTree = schema.pages[0].componentsTree;
|
|
4570
4542
|
if (!componentsTree || componentsTree.length === 0) {
|
|
@@ -4713,6 +4685,7 @@ function applyChangesToSchema(schema, changes, options) {
|
|
|
4713
4685
|
resolveFieldIdReferences(formFields);
|
|
4714
4686
|
|
|
4715
4687
|
formContainer.children = formFields;
|
|
4688
|
+
validateSchemaFormulas(schema, previousFormulas);
|
|
4716
4689
|
appliedChanges.diagnostics = diagnostics;
|
|
4717
4690
|
return appliedChanges;
|
|
4718
4691
|
}
|