openyida 2026.9.1 → 2026.9.2-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 +8 -8
- package/bin/yida.js +1 -7
- package/lib/app/canvas-compile.js +7 -0
- package/lib/app/create-app.js +142 -9
- package/lib/app/create-form/api-path.js +16 -2
- package/lib/app/create-form/args.js +14 -0
- package/lib/app/create-form/nav-icon-service.js +202 -0
- package/lib/app/create-form/nav-icon.js +267 -0
- package/lib/app/create-form.js +83 -25
- package/lib/app/custom-theme.js +300 -0
- package/lib/app/get-schema.js +10 -9
- package/lib/app/nav-group.js +221 -53
- package/lib/app/publish.js +13 -1
- package/lib/app/services/canvas-page-schema-builder.js +226 -21
- package/lib/app/services/form-compiler.js +6 -18
- package/lib/app/services/native-page-schema-builder.js +5 -5
- package/lib/app/theme-presets.js +1 -1
- package/lib/app/update-app.js +225 -9
- package/lib/auth/token-store.js +13 -1
- package/lib/core/agent-capabilities.js +43 -1
- package/lib/core/cli-error.js +6 -1
- package/lib/core/command-contract.js +42 -1
- package/lib/core/command-manifest.js +75 -22
- package/lib/core/locales/en.js +20 -13
- package/lib/core/locales/zh.js +20 -14
- package/lib/core/query-data.js +341 -31
- package/lib/core/sample.js +4 -0
- package/lib/report/append.js +3 -1
- package/lib/report/contract.js +7 -0
- package/lib/report/index.js +94 -5
- package/lib/report/inspect.js +32 -3
- package/lib/report/runtime-probe.js +248 -0
- package/lib/report/url.js +13 -0
- package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +411 -0
- package/lib/samples/openyida-scaffold/form-fields.json +74 -0
- package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +16 -12
- package/lib/samples/yida-density/density-switch-page.js +1 -1
- package/lib/samples/yida-rechart/trend-combo.canvas.jsx +3 -3
- package/package.json +1 -1
- package/scripts/postinstall.js +79 -4
- package/yida-skills/SKILL.md +18 -16
- package/yida-skills/references/official-example-schema-patterns.md +2 -0
- package/yida-skills/references/report-field-config-guide.md +10 -28
- package/yida-skills/references/task-retrospective.md +5 -5
- package/yida-skills/references/yida-api.md +10 -10
- package/yida-skills/skills/yida-app/SKILL.md +13 -9
- package/yida-skills/skills/yida-app/references/common-issues.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +31 -16
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +6 -4
- package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +5 -6
- package/yida-skills/skills/yida-app/workflow/step-5-seed-records.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +14 -7
- package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +26 -11
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +27 -13
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +13 -12
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +1 -1
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +28 -71
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +7 -6
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +28 -13
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +10 -26
- package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +6 -6
- package/yida-skills/skills/yida-canvas-table-form/SKILL.md +8 -5
- package/yida-skills/skills/yida-chart/references/echarts-bindding-guide.md +1 -1
- package/yida-skills/skills/yida-connector/examples/operations-search-formdata-v2.json +3 -3
- package/yida-skills/skills/yida-connector/examples/operations-search-formdata.json +2 -2
- package/yida-skills/skills/yida-create-app/SKILL.md +23 -14
- package/yida-skills/skills/yida-create-form-page/SKILL.md +13 -9
- package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +2 -3
- package/yida-skills/skills/yida-custom-page/SKILL.md +5 -7
- package/yida-skills/skills/yida-custom-page/references/coding-guide.md +6 -6
- package/yida-skills/skills/yida-custom-page/references/design-system.md +1 -1
- package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
- package/yida-skills/skills/yida-dashboard/references/interaction-patterns.md +1 -1
- package/yida-skills/skills/yida-data-management/SKILL.md +24 -21
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +15 -7
- package/yida-skills/skills/yida-design/SKILL.md +36 -38
- package/yida-skills/skills/yida-design/references/page-quality-gates.md +4 -4
- package/yida-skills/skills/yida-design/references/style-design-selection.md +7 -7
- package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +672 -0
- package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +3 -3
- package/yida-skills/skills/yida-design/references/visual-decision-engine.md +1 -1
- package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +18 -19
- package/yida-skills/skills/yida-design/workflow/output-design.md +52 -45
- package/yida-skills/skills/yida-design/workflow/output-prd.md +12 -10
- package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +14 -0
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +25 -25
- package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +3 -3
- package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +5 -5
- package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +22 -55
- package/yida-skills/skills/yida-form-detail/SKILL.md +26 -69
- package/yida-skills/skills/yida-nav-group/SKILL.md +3 -1
- package/yida-skills/skills/yida-page-config/SKILL.md +3 -1
- package/yida-skills/skills/yida-prd/SKILL.md +48 -0
- package/yida-skills/skills/yida-prd/references/app/blueprint.md +73 -0
- package/yida-skills/skills/yida-prd/references/app/navigation-patterns.md +43 -0
- package/yida-skills/skills/yida-prd/references/app/role-journey.md +30 -0
- package/yida-skills/skills/yida-prd/workflow/output-prd.md +190 -0
- package/yida-skills/skills/yida-prd/workflow/step-1-read-brief.md +15 -0
- package/yida-skills/skills/{yida-design/workflow/step-3-information-architecture.md → yida-prd/workflow/step-2-information-architecture.md} +2 -2
- package/yida-skills/skills/yida-publish-page/SKILL.md +5 -4
- package/yida-skills/skills/yida-report/SKILL.md +6 -11
- package/yida-skills/skills/yida-report/references/examples.md +11 -18
- package/yida-skills/skills/yida-requirement-analysis/SKILL.md +49 -0
- package/yida-skills/skills-index.json +49 -23
- package/lib/app/form-detail-style.js +0 -441
- package/lib/app/form-theme-action.js +0 -439
- package/yida-skills/skills/yida-canvas-custom-page/references/theme-runtime-helpers.md +0 -177
- package/yida-skills/skills/yida-design/workflow/step-1-positioning.md +0 -58
- package/yida-skills/skills/yida-form-detail/references/form-detail-css.md +0 -194
- package/yida-skills/skills/yida-form-detail/references/injection-guide.md +0 -94
package/README.md
CHANGED
|
@@ -167,11 +167,13 @@ For a user-facing list of supported features and matching CLI commands, see [Ope
|
|
|
167
167
|
|
|
168
168
|
```bash
|
|
169
169
|
openyida create-app "CRM"
|
|
170
|
-
openyida create-app --name "CRM" --desc "Customer management" --theme
|
|
170
|
+
openyida create-app --name "CRM" --desc "Customer management" --theme-file ./app-theme.css --nav-theme light --logo-source appIcon --layout l_shape
|
|
171
171
|
openyida app-list --size 20
|
|
172
172
|
openyida corp-efficiency
|
|
173
173
|
openyida create-form create APP_XXX "Customer" .cache/openyida/forms/customer-fields.json
|
|
174
174
|
openyida create-form update APP_XXX FORM_XXX .cache/openyida/forms/customer-changes.json
|
|
175
|
+
openyida sample openyida-scaffold form-fields --output .cache/openyida/forms/customer-fields.json
|
|
176
|
+
openyida sample openyida-scaffold canvas-form-drawer --output project/pages/src/customer-entry.canvas.jsx --var APP_TYPE=APP_XXX --var FORM_UUID=FORM_XXX
|
|
175
177
|
openyida get-schema APP_XXX FORM_XXX
|
|
176
178
|
openyida get-schema APP_XXX FORM_XXX --compact --resolve-fields "Customer Name,Status"
|
|
177
179
|
openyida get-schema APP_XXX --all --output-dir .cache/schemas
|
|
@@ -200,7 +202,7 @@ openyida configure-process APP_XXX FORM_XXX .cache/openyida/process/process.json
|
|
|
200
202
|
openyida process preview APP_XXX PROC_INST_XXX --output .cache/openyida/process/process.html
|
|
201
203
|
openyida data query form APP_XXX FORM_XXX --page 1 --size 20
|
|
202
204
|
openyida data query form APP_XXX FORM_XXX --dynamic-order '{"dateField_xxx":"-"}'
|
|
203
|
-
openyida data create form APP_XXX FORM_XXX --data-file .cache/openyida/data-import/record.json
|
|
205
|
+
openyida data create form APP_XXX FORM_XXX --expect-form-name 客户 --expect-form-type receipt --data-file .cache/openyida/data-import/record.json
|
|
204
206
|
openyida get-permission APP_XXX FORM_XXX
|
|
205
207
|
```
|
|
206
208
|
|
|
@@ -388,7 +390,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
388
390
|
| `openyida app-list [--size N]` | List my Yida apps |
|
|
389
391
|
| `openyida corp-efficiency [overview\|details\|detail\|groups\|notify] [options] [--open\|--no-open]` | Query enterprise efficiency overview and detail reports |
|
|
390
392
|
| `openyida create-app "<name>"\|--name <name> [options] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a Yida app |
|
|
391
|
-
| `openyida update-app <appType> [--name "..."] [--
|
|
393
|
+
| `openyida update-app <appType> [--name "..."] [--theme-file <css>] [--nav-theme light\|dark\|white\|gray] [--logo-source appIcon\|customImage] [--layout side\|top\|l_shape] [--hide-app-nav\|--show-app-nav]` | Update app info |
|
|
392
394
|
| `openyida app-online <appType> [--to-ding-app-center] [--show-app-center]` | Enable a Yida app |
|
|
393
395
|
| `openyida app-offline <appType> [--to-ding-app-center] [--show-app-center]` | Disable a Yida app |
|
|
394
396
|
| `openyida nav-group <list\|create\|rename\|delete\|move\|order\|auto-order\|hide\|show> <appType> ...` | Manage app sidebar navigation groups |
|
|
@@ -401,7 +403,8 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
401
403
|
|
|
402
404
|
| Command | Description |
|
|
403
405
|
|---------|-------------|
|
|
404
|
-
| `openyida create-form create <appType> "<formTitle>" <fieldsJsonFile> [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a form page |
|
|
406
|
+
| `openyida create-form create <appType> "<formTitle>" <fieldsJsonFile> [--icon auto\|<iconName>] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a form page |
|
|
407
|
+
| `openyida create-form icons [--json]` | List available form navigation icons |
|
|
405
408
|
| `openyida create-form validate-fields <fieldsJsonOrFile> [--json]` | Validate form field JSON locally |
|
|
406
409
|
| `openyida create-form update <appType> ... [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Update a form page |
|
|
407
410
|
| `openyida create-form patch <appType> <formUuid> <patchJsonOrFile> [--open\|--no-open]` | Update a form page |
|
|
@@ -422,15 +425,12 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
422
425
|
| `openyida publish <src> <appType> <formUuid> [--health-check] [--force] [--canvas] [--auto-nav-order] [--open\|--no-open]` | Compile and publish custom page |
|
|
423
426
|
| `openyida update-form-config <appType> ...` | Update form configuration |
|
|
424
427
|
| `openyida get-form-config <appType> <formUuid> [--json]` | Query form configuration |
|
|
425
|
-
| `openyida form-detail-style apply <appType> <formUuid> [--css file\|--preset clean-card] [--json]` | Manage form detail page style |
|
|
426
|
-
| `openyida form-detail-style remove <appType> <formUuid> [--json]` | Manage form detail page style |
|
|
427
|
-
| `openyida form-detail-style check <appType> <formUuid> [--json]` | Manage form detail page style |
|
|
428
428
|
|
|
429
429
|
### Data & Permissions
|
|
430
430
|
|
|
431
431
|
| Command | Description |
|
|
432
432
|
|---------|-------------|
|
|
433
|
-
| `openyida data <query\|get\|create\|update> <resource> ... \| delete form <appType> <formUuid> --inst-id <id> --confirm [--json]` | Unified data management (form/process/task/subform) |
|
|
433
|
+
| `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) |
|
|
434
434
|
| `openyida task-center <type> [options]` | Global task center (todo/processed/cc etc.) |
|
|
435
435
|
| `openyida basic-info <overview\|commodity\|grant\|capacity\|quota\|abs-path\|dataflow\|i18n\|domain>` | Query organization basic info, capacity, quotas, and domain settings |
|
|
436
436
|
| `openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]` | Fetch Markdown content from a DingTalk document |
|
package/bin/yida.js
CHANGED
|
@@ -882,14 +882,8 @@ async function main() {
|
|
|
882
882
|
break;
|
|
883
883
|
}
|
|
884
884
|
|
|
885
|
-
case 'form-detail-style': {
|
|
886
|
-
const { run: runFormDetailStyle } = require('../lib/app/form-detail-style');
|
|
887
|
-
await runFormDetailStyle(args);
|
|
888
|
-
break;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
885
|
case 'update-app': {
|
|
892
|
-
if (args.length < 2) {
|
|
886
|
+
if (args.length < 2 && !hasHelpFlag(args)) {
|
|
893
887
|
throwCliUsage(t('cli.update_app_usage'), t('cli.update_app_example'));
|
|
894
888
|
}
|
|
895
889
|
const { run: runUpdateApp } = require('../lib/app/update-app');
|
|
@@ -139,6 +139,13 @@ const BARE_DEPENDENCY_GLOBALS = Object.freeze({
|
|
|
139
139
|
const CANVAS_RUNTIME_GLOBAL_IDENTIFIERS = new Set([
|
|
140
140
|
...Object.keys(globals.builtin),
|
|
141
141
|
...Object.keys(globals.browser),
|
|
142
|
+
// globals follows its package release cadence; keep modern standards that
|
|
143
|
+
// are already available in supported browsers explicit until that list catches up.
|
|
144
|
+
'URLPattern',
|
|
145
|
+
'navigation',
|
|
146
|
+
'cookieStore',
|
|
147
|
+
'scheduler',
|
|
148
|
+
'Temporal',
|
|
142
149
|
'iframeWindow',
|
|
143
150
|
'parentWindow',
|
|
144
151
|
]);
|
package/lib/app/create-app.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* create-app.js - 宜搭应用创建命令
|
|
3
3
|
*
|
|
4
4
|
* 用法:openyida create-app "<appName>" [description] [icon] [iconColor] [colour] [navTheme] [layoutDirection]
|
|
5
|
+
* openyida create-app --name "<appName>" --theme-file ./app-theme.css [--nav-theme light] [--logo-source appIcon] [--layout l_shape]
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
'use strict';
|
|
@@ -20,13 +21,65 @@ const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff
|
|
|
20
21
|
const { throwCommandError } = require('../core/command-errors');
|
|
21
22
|
const { assertPresetThemeKey } = require('./theme-presets');
|
|
22
23
|
const { resolveBuilderAiSource } = require('./builder-ai-source');
|
|
24
|
+
const { normalizeCssColorToHex, readThemeCssFile } = require('./custom-theme');
|
|
25
|
+
const {
|
|
26
|
+
applyCustomThemeUpdate,
|
|
27
|
+
assertLogoSource,
|
|
28
|
+
assertNavTheme,
|
|
29
|
+
normalizeLayoutDirection,
|
|
30
|
+
} = require('./update-app');
|
|
23
31
|
|
|
24
32
|
const DEFAULT_APP_OPTIONS = {
|
|
25
|
-
icon: 'xian-yingyong',
|
|
26
33
|
iconColor: '#0089FF',
|
|
27
34
|
colour: 'podBlue',
|
|
28
35
|
};
|
|
29
36
|
|
|
37
|
+
// 与 yida-next/src/components/IconSelect/Icon.js 保持一致。
|
|
38
|
+
const APP_ICON_NAMES = Object.freeze([
|
|
39
|
+
'xian-xinwen',
|
|
40
|
+
'xian-diqiu',
|
|
41
|
+
'xian-zhengfu',
|
|
42
|
+
'xian-qiche',
|
|
43
|
+
'xian-yingyong',
|
|
44
|
+
'xian-feiji',
|
|
45
|
+
'xian-xueshimao',
|
|
46
|
+
'xian-diannao',
|
|
47
|
+
'xian-qiye',
|
|
48
|
+
'xian-gongzuozheng',
|
|
49
|
+
'xian-danju',
|
|
50
|
+
'xian-gouwuche',
|
|
51
|
+
'xian-shichang',
|
|
52
|
+
'xian-xinyongka',
|
|
53
|
+
'xian-jingli',
|
|
54
|
+
'xian-huodong',
|
|
55
|
+
'xian-falv',
|
|
56
|
+
'xian-jiangbei',
|
|
57
|
+
'xian-baogao',
|
|
58
|
+
'xian-liucheng',
|
|
59
|
+
'huoche',
|
|
60
|
+
'chaxun',
|
|
61
|
+
'shenbao',
|
|
62
|
+
'daka',
|
|
63
|
+
]);
|
|
64
|
+
const DEFAULT_CREATE_LAYOUT_DIRECTION = 'l_shape';
|
|
65
|
+
|
|
66
|
+
function selectRandomAppIcon(random = Math.random) {
|
|
67
|
+
const index = Math.min(APP_ICON_NAMES.length - 1, Math.floor(random() * APP_ICON_NAMES.length));
|
|
68
|
+
return APP_ICON_NAMES[Math.max(0, index)];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function applyThemeFileCreateDefaults(params, themeFileInfo) {
|
|
72
|
+
if (!params || !params.themeFile || !themeFileInfo) {return params;}
|
|
73
|
+
const iconColor = normalizeCssColorToHex(themeFileInfo.themeColor);
|
|
74
|
+
if (!iconColor) {
|
|
75
|
+
throw new Error(`--color-brand1-6 无法转换为应用图标色: ${themeFileInfo.themeColor}`);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
...params,
|
|
79
|
+
iconColor,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
30
83
|
const INDUSTRY_APP_DEFAULTS = [
|
|
31
84
|
{
|
|
32
85
|
name: 'legal',
|
|
@@ -147,7 +200,7 @@ function readOptionValue(args, index, optionName) {
|
|
|
147
200
|
return value;
|
|
148
201
|
}
|
|
149
202
|
|
|
150
|
-
function parseCreateAppArgs(args) {
|
|
203
|
+
function parseCreateAppArgs(args, random = Math.random) {
|
|
151
204
|
const params = {
|
|
152
205
|
appName: null,
|
|
153
206
|
description: null,
|
|
@@ -155,7 +208,9 @@ function parseCreateAppArgs(args) {
|
|
|
155
208
|
iconColor: null,
|
|
156
209
|
colour: null,
|
|
157
210
|
navTheme: null,
|
|
211
|
+
logoSource: null,
|
|
158
212
|
layoutDirection: null,
|
|
213
|
+
themeFile: null,
|
|
159
214
|
locale: null,
|
|
160
215
|
};
|
|
161
216
|
const positional = [];
|
|
@@ -194,6 +249,16 @@ function parseCreateAppArgs(args) {
|
|
|
194
249
|
params.navTheme = readOptionValue(args, index, arg);
|
|
195
250
|
index++;
|
|
196
251
|
break;
|
|
252
|
+
case '--logo-source':
|
|
253
|
+
case '--logoSource':
|
|
254
|
+
params.logoSource = readOptionValue(args, index, arg);
|
|
255
|
+
index++;
|
|
256
|
+
break;
|
|
257
|
+
case '--theme-file':
|
|
258
|
+
case '--custom-theme-file':
|
|
259
|
+
params.themeFile = readOptionValue(args, index, arg);
|
|
260
|
+
index++;
|
|
261
|
+
break;
|
|
197
262
|
case '--layout':
|
|
198
263
|
case '--layout-direction':
|
|
199
264
|
case '--layoutDirection':
|
|
@@ -224,13 +289,33 @@ function parseCreateAppArgs(args) {
|
|
|
224
289
|
params.appName = params.appName || positional[0] || null;
|
|
225
290
|
params.description = params.description || positional[1] || params.appName;
|
|
226
291
|
|
|
292
|
+
const explicitIcon = params.icon || positional[2] || null;
|
|
293
|
+
params.iconWasExplicit = !!explicitIcon;
|
|
227
294
|
const inferredDefaults = inferAppDefaults(params.appName, params.description);
|
|
228
|
-
params.icon =
|
|
295
|
+
params.icon = explicitIcon || (
|
|
296
|
+
inferredDefaults.industry === 'default'
|
|
297
|
+
? selectRandomAppIcon(random)
|
|
298
|
+
: inferredDefaults.icon
|
|
299
|
+
);
|
|
229
300
|
params.iconColor = params.iconColor || positional[3] || inferredDefaults.iconColor;
|
|
230
301
|
params.colour = params.colour || positional[4] || inferredDefaults.colour;
|
|
231
302
|
assertPresetThemeKey(params.colour);
|
|
232
303
|
params.navTheme = params.navTheme || positional[5] || null;
|
|
233
|
-
params.layoutDirection =
|
|
304
|
+
params.layoutDirection = normalizeLayoutDirection(
|
|
305
|
+
params.layoutDirection || positional[6] || DEFAULT_CREATE_LAYOUT_DIRECTION
|
|
306
|
+
);
|
|
307
|
+
assertNavTheme(params.navTheme);
|
|
308
|
+
assertLogoSource(params.logoSource);
|
|
309
|
+
if (params.logoSource && !params.themeFile) {
|
|
310
|
+
throw new Error('--logo-source must be used with --theme-file so the navigation theme is saved atomically');
|
|
311
|
+
}
|
|
312
|
+
if (params.themeFile) {
|
|
313
|
+
params.navTheme = params.navTheme || 'light';
|
|
314
|
+
params.logoSource = params.logoSource || 'appIcon';
|
|
315
|
+
if (params.logoSource !== 'appIcon') {
|
|
316
|
+
throw new Error('create-app only supports --logo-source appIcon because a new app has no homepageLogo image yet');
|
|
317
|
+
}
|
|
318
|
+
}
|
|
234
319
|
params.industry = inferredDefaults.industry;
|
|
235
320
|
|
|
236
321
|
return params;
|
|
@@ -269,10 +354,10 @@ function buildCreateAppPayload(params, auth, contentLocale, openExclusive, openP
|
|
|
269
354
|
openPhysicColumn,
|
|
270
355
|
openIsolationDatabase: 'n',
|
|
271
356
|
openExclusiveUnit: 'n',
|
|
272
|
-
createWithModernTheme: 'y',
|
|
273
357
|
group: 'ALL',
|
|
274
358
|
fromBuilderAi: 'y',
|
|
275
359
|
builderAiSource: resolveBuilderAiSource(),
|
|
360
|
+
createWithModernTheme: 'y',
|
|
276
361
|
};
|
|
277
362
|
if (navTheme) {
|
|
278
363
|
payload.navTheme = navTheme;
|
|
@@ -305,7 +390,13 @@ async function run(args) {
|
|
|
305
390
|
chalkError(t('create_app.usage'), { hint: t('create_app.example') });
|
|
306
391
|
}
|
|
307
392
|
|
|
308
|
-
|
|
393
|
+
// 在远端创建应用前先完成本地主题校验,避免 CSS 缺失或 token 非法时留下半成品应用。
|
|
394
|
+
if (params.themeFile) {
|
|
395
|
+
const themeFileInfo = readThemeCssFile(params.themeFile);
|
|
396
|
+
Object.assign(params, applyThemeFileCreateDefaults(params, themeFileInfo));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const { appName, description, icon, iconColor, colour, navTheme, logoSource, layoutDirection, themeFile } = params;
|
|
309
400
|
|
|
310
401
|
const { c, banner, step, label, success: chalkSuccess, result: chalkResult } = require('../core/chalk');
|
|
311
402
|
|
|
@@ -313,7 +404,7 @@ async function run(args) {
|
|
|
313
404
|
label('Name', appName);
|
|
314
405
|
label('Desc', description);
|
|
315
406
|
label('Icon', `${icon} ${c.dim}(${iconColor})${c.reset}`);
|
|
316
|
-
label('Theme', `${colour} / ${navTheme || 'default'} / ${layoutDirection || 'default'}`);
|
|
407
|
+
label('Theme', `${themeFile || colour} / ${navTheme || 'default'} / ${logoSource || 'default'} / ${layoutDirection || 'default'}`);
|
|
317
408
|
|
|
318
409
|
// Step 1: 读取登录态
|
|
319
410
|
step(1, t('common.step_login', 1));
|
|
@@ -356,6 +447,29 @@ async function run(args) {
|
|
|
356
447
|
const appUrl = `${authRef.baseUrl}/${appType}/workbench`;
|
|
357
448
|
const corpId = authRef.corpId || (authRef.authData && authRef.authData.corp_id) || '';
|
|
358
449
|
|
|
450
|
+
if (themeFile) {
|
|
451
|
+
step(3, t('update_app.step_update'));
|
|
452
|
+
const themeResponse = await applyCustomThemeUpdate(appType, {
|
|
453
|
+
themeFile,
|
|
454
|
+
navTheme,
|
|
455
|
+
logoSource,
|
|
456
|
+
layoutDirection,
|
|
457
|
+
}, authRef);
|
|
458
|
+
if (!themeResponse || !themeResponse.success) {
|
|
459
|
+
const errorMsg = themeResponse
|
|
460
|
+
? themeResponse.errorMsg || t('common.unknown_error')
|
|
461
|
+
: t('common.request_failed');
|
|
462
|
+
console.log(JSON.stringify({
|
|
463
|
+
success: false,
|
|
464
|
+
appCreated: true,
|
|
465
|
+
appType,
|
|
466
|
+
themeApplied: false,
|
|
467
|
+
error: errorMsg,
|
|
468
|
+
}));
|
|
469
|
+
throwCommandError(t('create_app.failed', errorMsg));
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
359
473
|
chalkResult(true, t('create_app.success'), [
|
|
360
474
|
['appType', appType],
|
|
361
475
|
['Corp ID', corpId || t('common.unknown_error')],
|
|
@@ -366,7 +480,17 @@ async function run(args) {
|
|
|
366
480
|
if (prdFile) {updatePrdCorpId(prdFile, corpId, appType, authRef.baseUrl);}
|
|
367
481
|
|
|
368
482
|
console.log(JSON.stringify(withBrowserHandoff(
|
|
369
|
-
{
|
|
483
|
+
{
|
|
484
|
+
success: true,
|
|
485
|
+
appType,
|
|
486
|
+
appName,
|
|
487
|
+
corpId,
|
|
488
|
+
url: appUrl,
|
|
489
|
+
themeFile: themeFile || undefined,
|
|
490
|
+
navTheme: themeFile ? navTheme : undefined,
|
|
491
|
+
logoSource: themeFile ? logoSource : undefined,
|
|
492
|
+
layoutDirection,
|
|
493
|
+
},
|
|
370
494
|
appUrl,
|
|
371
495
|
{ stage: 'create_app_success', title: appName },
|
|
372
496
|
openOption.mode
|
|
@@ -379,4 +503,13 @@ async function run(args) {
|
|
|
379
503
|
}
|
|
380
504
|
}
|
|
381
505
|
|
|
382
|
-
module.exports = {
|
|
506
|
+
module.exports = {
|
|
507
|
+
APP_ICON_NAMES,
|
|
508
|
+
DEFAULT_CREATE_LAYOUT_DIRECTION,
|
|
509
|
+
run,
|
|
510
|
+
parseCreateAppArgs,
|
|
511
|
+
inferAppDefaults,
|
|
512
|
+
selectRandomAppIcon,
|
|
513
|
+
applyThemeFileCreateDefaults,
|
|
514
|
+
buildCreateAppPayload,
|
|
515
|
+
};
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* @param {string} [options.prefix] 额外的路径前缀(会以 / 拼接)
|
|
12
12
|
* @param {string} [options.namespace] 命名空间,默认 dingtalk
|
|
13
13
|
* @param {'formdesign'|'formnav'} [options.queryModule] 查询模块,默认 formdesign
|
|
14
|
+
* @param {string} [options.api] 页面请求标识,例如 Nav.update
|
|
15
|
+
* @param {boolean} [options.mock] 是否使用 mock 接口
|
|
14
16
|
* @param {boolean} [options.addTimestamp] 是否追加 _stamp 时间戳查询参数
|
|
15
17
|
* @returns {string} 拼接完成的 API 请求路径
|
|
16
18
|
*/
|
|
@@ -19,14 +21,26 @@ function buildApiPath(appType, apiName, options = {}) {
|
|
|
19
21
|
prefix = '',
|
|
20
22
|
namespace = 'dingtalk',
|
|
21
23
|
queryModule = 'formdesign',
|
|
24
|
+
api,
|
|
25
|
+
mock,
|
|
22
26
|
addTimestamp = false,
|
|
23
27
|
} = options;
|
|
24
28
|
if (queryModule !== 'formdesign' && queryModule !== 'formnav') {
|
|
25
29
|
throw new Error(`Unsupported query module: ${queryModule}`);
|
|
26
30
|
}
|
|
27
31
|
const prefixPath = prefix ? `/${prefix}` : '';
|
|
28
|
-
const
|
|
29
|
-
|
|
32
|
+
const queryParams = [];
|
|
33
|
+
if (api) {
|
|
34
|
+
queryParams.push(`_api=${encodeURIComponent(api)}`);
|
|
35
|
+
}
|
|
36
|
+
if (mock !== undefined) {
|
|
37
|
+
queryParams.push(`_mock=${String(Boolean(mock))}`);
|
|
38
|
+
}
|
|
39
|
+
if (addTimestamp) {
|
|
40
|
+
queryParams.push(`_stamp=${Date.now()}`);
|
|
41
|
+
}
|
|
42
|
+
const query = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
|
43
|
+
return `/${namespace}/web/${appType}${prefixPath}/query/${queryModule}/${apiName}.json${query}`;
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
module.exports = {
|
|
@@ -85,6 +85,7 @@ function createParseArgs(dependencies) {
|
|
|
85
85
|
layout: 'single',
|
|
86
86
|
theme: 'default',
|
|
87
87
|
labelAlign: 'top',
|
|
88
|
+
icon: 'auto',
|
|
88
89
|
contentLocale: null,
|
|
89
90
|
browserOpenMode: openOption.mode,
|
|
90
91
|
};
|
|
@@ -104,6 +105,12 @@ function createParseArgs(dependencies) {
|
|
|
104
105
|
options.labelAlign = args[i + 1];
|
|
105
106
|
args.splice(i, 2);
|
|
106
107
|
i--;
|
|
108
|
+
} else if (args[i] === '--icon' && i + 1 < args.length) {
|
|
109
|
+
options.icon = args[i + 1];
|
|
110
|
+
args.splice(i, 2);
|
|
111
|
+
i--;
|
|
112
|
+
} else if (args[i] === '--icon') {
|
|
113
|
+
throwCreateFormError('--icon requires a value', 'CREATE_FORM_INVALID_ARGUMENTS');
|
|
107
114
|
} else if ((args[i] === '--locale' || args[i] === '--content-locale' || args[i] === '--lang') && i + 1 < args.length) {
|
|
108
115
|
options.contentLocale = args[i + 1];
|
|
109
116
|
if (!normalizeYidaLocale(options.contentLocale)) {
|
|
@@ -138,6 +145,13 @@ function createParseArgs(dependencies) {
|
|
|
138
145
|
|
|
139
146
|
const mode = args[0];
|
|
140
147
|
|
|
148
|
+
if (mode === 'icons' || mode === 'list-icons') {
|
|
149
|
+
return {
|
|
150
|
+
mode: 'icons',
|
|
151
|
+
...options
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
141
155
|
if (mode === 'validate-fields') {
|
|
142
156
|
if (args.length === 2) {
|
|
143
157
|
return {
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const FORM_NAVIGATION_UPDATE_KEYS = [
|
|
4
|
+
'gmtModified',
|
|
5
|
+
'parentNavUuid',
|
|
6
|
+
'hidden',
|
|
7
|
+
'mobileHidden',
|
|
8
|
+
'i18nTitle',
|
|
9
|
+
'isNewReport',
|
|
10
|
+
'id',
|
|
11
|
+
'isNewForm',
|
|
12
|
+
'slug',
|
|
13
|
+
'formType',
|
|
14
|
+
'formUuid',
|
|
15
|
+
'navUuid',
|
|
16
|
+
'navType',
|
|
17
|
+
'isNew',
|
|
18
|
+
'gmtCreate',
|
|
19
|
+
'parentId',
|
|
20
|
+
'url',
|
|
21
|
+
'topicId',
|
|
22
|
+
'displayType',
|
|
23
|
+
'relateFormUuid',
|
|
24
|
+
'processCode',
|
|
25
|
+
'listOrder',
|
|
26
|
+
'formStatus',
|
|
27
|
+
'relateFormType',
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function buildFormNavigationIconUpdatePayload(navigationNode, icon) {
|
|
31
|
+
const updatePayload = {
|
|
32
|
+
_locale_time_zone_offset: 28800000,
|
|
33
|
+
};
|
|
34
|
+
FORM_NAVIGATION_UPDATE_KEYS.forEach(function (key) {
|
|
35
|
+
if (navigationNode[key] !== undefined) {
|
|
36
|
+
updatePayload[key] = key === 'i18nTitle' && navigationNode[key] !== null && typeof navigationNode[key] === 'object'
|
|
37
|
+
? String(navigationNode[key])
|
|
38
|
+
: navigationNode[key];
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const navigationTitle = navigationNode.title || navigationNode.i18nTitle || {};
|
|
43
|
+
updatePayload.title = typeof navigationTitle === 'string'
|
|
44
|
+
? navigationTitle
|
|
45
|
+
: JSON.stringify(navigationTitle);
|
|
46
|
+
updatePayload.formUuid = navigationNode.formUuid || 'NAV-SYSTEM-FROM-ME-UUID';
|
|
47
|
+
updatePayload.icon = icon;
|
|
48
|
+
return updatePayload;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function createFormNavIconService(dependencies) {
|
|
52
|
+
const {
|
|
53
|
+
buildApiPath,
|
|
54
|
+
requestWithAutoLogin,
|
|
55
|
+
sendGetRequest,
|
|
56
|
+
sendPostRequest,
|
|
57
|
+
throwCreateFormError,
|
|
58
|
+
sanitizeFailureResult,
|
|
59
|
+
candidateLimit = 8,
|
|
60
|
+
} = dependencies;
|
|
61
|
+
|
|
62
|
+
function flattenNavigationNodes(nodes, output) {
|
|
63
|
+
const resultNodes = output || [];
|
|
64
|
+
(nodes || []).forEach(function (node) {
|
|
65
|
+
resultNodes.push(node);
|
|
66
|
+
if (Array.isArray(node.children)) {
|
|
67
|
+
flattenNavigationNodes(node.children, resultNodes);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
return resultNodes;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function fetchFormNavigationList(authRef, appType) {
|
|
74
|
+
const navigationResult = await requestWithAutoLogin(function (auth) {
|
|
75
|
+
return sendGetRequest(
|
|
76
|
+
auth.baseUrl,
|
|
77
|
+
buildApiPath(appType, 'getFormNavigationListByOrder', {
|
|
78
|
+
queryModule: 'formnav',
|
|
79
|
+
addTimestamp: true,
|
|
80
|
+
}),
|
|
81
|
+
{
|
|
82
|
+
_api: 'Nav.queryList',
|
|
83
|
+
_mock: false,
|
|
84
|
+
_locale_time_zone_offset: 28800000,
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}, authRef);
|
|
88
|
+
|
|
89
|
+
if (!navigationResult || navigationResult.success === false || !Array.isArray(navigationResult.content)) {
|
|
90
|
+
const message = navigationResult
|
|
91
|
+
? navigationResult.errorMsg || navigationResult.message || 'Failed to fetch form navigation list'
|
|
92
|
+
: 'Failed to fetch form navigation list';
|
|
93
|
+
throwCreateFormError(message, 'CREATE_FORM_NAV_ICON_LIST_FAILED', {
|
|
94
|
+
appType,
|
|
95
|
+
result: sanitizeFailureResult(navigationResult),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return flattenNavigationNodes(navigationResult.content);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function resolveFormNavigationNode(nodes, formUuid) {
|
|
103
|
+
const exactMatches = (nodes || []).filter(function (node) {
|
|
104
|
+
return node && (node.formUuid === formUuid || node.navUuid === formUuid);
|
|
105
|
+
});
|
|
106
|
+
const relatedMatches = (nodes || []).filter(function (node) {
|
|
107
|
+
return node && node.relateFormUuid === formUuid;
|
|
108
|
+
});
|
|
109
|
+
const matches = exactMatches.length > 0 ? exactMatches : relatedMatches;
|
|
110
|
+
if (matches.length !== 1) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return matches[0];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function updateCreatedFormNavigationIcon(authRef, appType, formUuid, icon) {
|
|
117
|
+
const navigationNodes = await fetchFormNavigationList(authRef, appType);
|
|
118
|
+
const navigationNode = resolveFormNavigationNode(navigationNodes, formUuid);
|
|
119
|
+
if (!navigationNode) {
|
|
120
|
+
throwCreateFormError('Created form navigation node could not be resolved uniquely.', 'CREATE_FORM_NAV_ICON_NODE_NOT_FOUND', {
|
|
121
|
+
appType,
|
|
122
|
+
formUuid,
|
|
123
|
+
candidates: navigationNodes.filter(function (node) {
|
|
124
|
+
return node && node.navType !== 'SYSTEM';
|
|
125
|
+
}).slice(0, candidateLimit).map(function (node) {
|
|
126
|
+
return {
|
|
127
|
+
id: node.id,
|
|
128
|
+
navUuid: node.navUuid,
|
|
129
|
+
formUuid: node.formUuid,
|
|
130
|
+
relateFormUuid: node.relateFormUuid,
|
|
131
|
+
formType: node.formType,
|
|
132
|
+
};
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const updatePayload = buildFormNavigationIconUpdatePayload(navigationNode, icon);
|
|
138
|
+
|
|
139
|
+
const updateResult = await requestWithAutoLogin(function (auth) {
|
|
140
|
+
return sendPostRequest(
|
|
141
|
+
auth.baseUrl,
|
|
142
|
+
buildApiPath(appType, 'updateFormNavigation', {
|
|
143
|
+
queryModule: 'formnav',
|
|
144
|
+
api: 'Nav.update',
|
|
145
|
+
mock: false,
|
|
146
|
+
addTimestamp: true,
|
|
147
|
+
}),
|
|
148
|
+
updatePayload,
|
|
149
|
+
formUuid,
|
|
150
|
+
auth,
|
|
151
|
+
{
|
|
152
|
+
appType,
|
|
153
|
+
referer: `${auth.baseUrl}/${appType}/admin/${formUuid}`,
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
}, authRef);
|
|
157
|
+
|
|
158
|
+
if (!updateResult || updateResult.success === false) {
|
|
159
|
+
const message = updateResult
|
|
160
|
+
? updateResult.errorMsg || updateResult.message || 'Failed to update form navigation icon'
|
|
161
|
+
: 'Failed to update form navigation icon';
|
|
162
|
+
throwCreateFormError(message, 'CREATE_FORM_NAV_ICON_UPDATE_FAILED', {
|
|
163
|
+
appType,
|
|
164
|
+
formUuid,
|
|
165
|
+
navUuid: navigationNode.navUuid,
|
|
166
|
+
icon,
|
|
167
|
+
result: sanitizeFailureResult(updateResult),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const updatedNavigationNodes = await fetchFormNavigationList(authRef, appType);
|
|
172
|
+
const updatedNavigationNode = resolveFormNavigationNode(updatedNavigationNodes, formUuid);
|
|
173
|
+
if (!updatedNavigationNode || updatedNavigationNode.icon !== icon) {
|
|
174
|
+
throwCreateFormError('Form navigation icon readback did not match the requested icon.', 'CREATE_FORM_NAV_ICON_READBACK_MISMATCH', {
|
|
175
|
+
appType,
|
|
176
|
+
formUuid,
|
|
177
|
+
navUuid: navigationNode.navUuid,
|
|
178
|
+
expectedIcon: icon,
|
|
179
|
+
actualIcon: updatedNavigationNode && updatedNavigationNode.icon,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
id: updatedNavigationNode.id,
|
|
185
|
+
navUuid: updatedNavigationNode.navUuid,
|
|
186
|
+
icon: updatedNavigationNode.icon,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
fetchFormNavigationList,
|
|
192
|
+
flattenNavigationNodes,
|
|
193
|
+
resolveFormNavigationNode,
|
|
194
|
+
updateCreatedFormNavigationIcon,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = {
|
|
199
|
+
FORM_NAVIGATION_UPDATE_KEYS,
|
|
200
|
+
buildFormNavigationIconUpdatePayload,
|
|
201
|
+
createFormNavIconService,
|
|
202
|
+
};
|