openyida 2026.9.1 → 2026.9.2-1

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.
Files changed (91) hide show
  1. package/README.md +8 -8
  2. package/bin/yida.js +1 -7
  3. package/lib/app/canvas-compile.js +7 -0
  4. package/lib/app/create-app.js +142 -9
  5. package/lib/app/create-form/api-path.js +16 -2
  6. package/lib/app/create-form/args.js +14 -0
  7. package/lib/app/create-form/nav-icon-service.js +202 -0
  8. package/lib/app/create-form/nav-icon.js +267 -0
  9. package/lib/app/create-form.js +83 -25
  10. package/lib/app/custom-theme.js +300 -0
  11. package/lib/app/get-schema.js +10 -9
  12. package/lib/app/services/canvas-page-schema-builder.js +226 -21
  13. package/lib/app/services/form-compiler.js +6 -18
  14. package/lib/app/services/native-page-schema-builder.js +5 -5
  15. package/lib/app/theme-presets.js +1 -1
  16. package/lib/app/update-app.js +225 -9
  17. package/lib/auth/token-store.js +13 -1
  18. package/lib/core/agent-capabilities.js +5 -0
  19. package/lib/core/command-contract.js +42 -1
  20. package/lib/core/command-manifest.js +43 -18
  21. package/lib/core/locales/en.js +17 -13
  22. package/lib/core/locales/zh.js +17 -14
  23. package/lib/core/query-data.js +215 -23
  24. package/lib/core/sample.js +4 -0
  25. package/lib/report/append.js +3 -1
  26. package/lib/report/contract.js +7 -0
  27. package/lib/report/index.js +94 -5
  28. package/lib/report/inspect.js +32 -3
  29. package/lib/report/runtime-probe.js +248 -0
  30. package/lib/report/url.js +13 -0
  31. package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +411 -0
  32. package/lib/samples/openyida-scaffold/form-fields.json +74 -0
  33. package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +16 -12
  34. package/lib/samples/yida-density/density-switch-page.js +1 -1
  35. package/lib/samples/yida-rechart/trend-combo.canvas.jsx +3 -3
  36. package/package.json +1 -1
  37. package/scripts/postinstall.js +76 -1
  38. package/yida-skills/SKILL.md +14 -12
  39. package/yida-skills/references/official-example-schema-patterns.md +2 -0
  40. package/yida-skills/references/report-field-config-guide.md +10 -28
  41. package/yida-skills/references/task-retrospective.md +5 -5
  42. package/yida-skills/references/yida-api.md +10 -10
  43. package/yida-skills/skills/yida-app/SKILL.md +8 -5
  44. package/yida-skills/skills/yida-app/workflow/step-2-design.md +1 -3
  45. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +5 -4
  46. package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +5 -6
  47. package/yida-skills/skills/yida-app/workflow/step-5-seed-records.md +1 -1
  48. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +14 -7
  49. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +7 -4
  50. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +10 -9
  51. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +1 -1
  52. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +28 -71
  53. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +7 -6
  54. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +28 -13
  55. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +8 -24
  56. package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +6 -6
  57. package/yida-skills/skills/yida-canvas-table-form/SKILL.md +8 -5
  58. package/yida-skills/skills/yida-chart/references/echarts-bindding-guide.md +1 -1
  59. package/yida-skills/skills/yida-connector/examples/operations-search-formdata-v2.json +3 -3
  60. package/yida-skills/skills/yida-connector/examples/operations-search-formdata.json +2 -2
  61. package/yida-skills/skills/yida-create-app/SKILL.md +21 -12
  62. package/yida-skills/skills/yida-create-form-page/SKILL.md +13 -9
  63. package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +2 -3
  64. package/yida-skills/skills/yida-custom-page/SKILL.md +2 -4
  65. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +6 -6
  66. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  67. package/yida-skills/skills/yida-dashboard/references/interaction-patterns.md +1 -1
  68. package/yida-skills/skills/yida-data-management/SKILL.md +18 -17
  69. package/yida-skills/skills/yida-data-management/references/data-format-guide.md +2 -2
  70. package/yida-skills/skills/yida-design/SKILL.md +5 -4
  71. package/yida-skills/skills/yida-design/references/page-quality-gates.md +3 -3
  72. package/yida-skills/skills/yida-design/references/style-design-selection.md +5 -5
  73. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +672 -0
  74. package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +3 -3
  75. package/yida-skills/skills/yida-design/references/visual-decision-engine.md +1 -1
  76. package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +14 -14
  77. package/yida-skills/skills/yida-design/workflow/output-design.md +31 -44
  78. package/yida-skills/skills/yida-design/workflow/output-prd.md +9 -9
  79. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +21 -21
  80. package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +1 -1
  81. package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +3 -3
  82. package/yida-skills/skills/yida-form-detail/SKILL.md +26 -69
  83. package/yida-skills/skills/yida-publish-page/SKILL.md +4 -3
  84. package/yida-skills/skills/yida-report/SKILL.md +6 -11
  85. package/yida-skills/skills/yida-report/references/examples.md +11 -18
  86. package/yida-skills/skills-index.json +18 -15
  87. package/lib/app/form-detail-style.js +0 -441
  88. package/lib/app/form-theme-action.js +0 -439
  89. package/yida-skills/skills/yida-canvas-custom-page/references/theme-runtime-helpers.md +0 -177
  90. package/yida-skills/skills/yida-form-detail/references/form-detail-css.md +0 -194
  91. 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 deepBlue
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 "..."] [--layout slide\|ver] [--theme deepBlue] [--hide-app-nav\|--show-app-nav]` | Update app info |
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
  ]);
@@ -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 = params.icon || positional[2] || inferredDefaults.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 = params.layoutDirection || positional[6] || null;
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
- const { appName, description, icon, iconColor, colour, navTheme, layoutDirection } = params;
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
- { success: true, appType, appName, corpId, url: appUrl },
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 = { run, parseCreateAppArgs, inferAppDefaults, buildCreateAppPayload };
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 timestamp = addTimestamp ? `?_stamp=${Date.now()}` : '';
29
- return `/${namespace}/web/${appType}${prefixPath}/query/${queryModule}/${apiName}.json${timestamp}`;
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
+ };