openyida 2026.9.2 → 2026.9.3

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 (80) hide show
  1. package/README.md +9 -7
  2. package/lib/app/create-form/api-path.js +16 -2
  3. package/lib/app/create-form/args.js +14 -0
  4. package/lib/app/create-form/nav-icon-service.js +202 -0
  5. package/lib/app/create-form/nav-icon.js +267 -0
  6. package/lib/app/create-form.js +77 -3
  7. package/lib/app/nav-group.js +221 -53
  8. package/lib/app/page-template-guard.js +64 -0
  9. package/lib/app/publish.js +15 -1
  10. package/lib/app/services/canvas-page-schema-builder.js +1 -1
  11. package/lib/asset/ai-image.js +8 -9
  12. package/lib/asset/asset-cmd.js +3 -49
  13. package/lib/asset/asset-resolve.js +8 -10
  14. package/lib/asset/asset-status.js +7 -7
  15. package/lib/core/agent-capabilities.js +43 -1
  16. package/lib/core/cli-error.js +6 -1
  17. package/lib/core/command-contract.js +42 -1
  18. package/lib/core/command-manifest.js +73 -11
  19. package/lib/core/locales/en.js +5 -1
  20. package/lib/core/locales/zh.js +7 -3
  21. package/lib/core/query-data.js +126 -8
  22. package/lib/core/sample.js +51 -10
  23. package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +7 -7
  24. package/package.json +1 -1
  25. package/scripts/postinstall.js +5 -5
  26. package/yida-skills/SKILL.md +13 -13
  27. package/yida-skills/references/task-retrospective.md +4 -4
  28. package/yida-skills/skills/yida-app/SKILL.md +9 -8
  29. package/yida-skills/skills/yida-app/references/common-issues.md +1 -1
  30. package/yida-skills/skills/yida-app/workflow/step-2-design.md +42 -15
  31. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +8 -1
  32. package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +13 -15
  33. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +4 -2
  34. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +26 -11
  35. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +22 -11
  36. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +8 -8
  37. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +1 -1
  38. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +16 -16
  39. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +1 -1
  40. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +1 -1
  41. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +6 -7
  42. package/yida-skills/skills/yida-canvas-table-form/SKILL.md +2 -2
  43. package/yida-skills/skills/yida-create-app/SKILL.md +13 -7
  44. package/yida-skills/skills/yida-create-form-page/SKILL.md +15 -17
  45. package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +1 -4
  46. package/yida-skills/skills/yida-custom-page/SKILL.md +5 -5
  47. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +2 -6
  48. package/yida-skills/skills/yida-custom-page/references/design-system.md +4 -4
  49. package/yida-skills/skills/yida-data-management/SKILL.md +6 -4
  50. package/yida-skills/skills/yida-data-management/references/data-format-guide.md +13 -5
  51. package/yida-skills/skills/yida-design/SKILL.md +36 -39
  52. package/yida-skills/skills/yida-design/references/asset-workflow.md +32 -85
  53. package/yida-skills/skills/yida-design/references/page-quality-gates.md +3 -3
  54. package/yida-skills/skills/yida-design/references/style-design-selection.md +4 -4
  55. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +15 -24
  56. package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +8 -8
  57. package/yida-skills/skills/yida-design/references/visual-decision-engine.md +1 -1
  58. package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +2 -2
  59. package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +12 -13
  60. package/yida-skills/skills/yida-design/workflow/output-design.md +40 -26
  61. package/yida-skills/skills/yida-design/workflow/output-prd.md +8 -8
  62. package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +14 -0
  63. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +23 -17
  64. package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +3 -3
  65. package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +5 -5
  66. package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +22 -55
  67. package/yida-skills/skills/yida-nav-group/SKILL.md +3 -1
  68. package/yida-skills/skills/yida-page-config/SKILL.md +3 -1
  69. package/yida-skills/skills/yida-prd/SKILL.md +48 -0
  70. package/yida-skills/skills/yida-prd/references/app/blueprint.md +73 -0
  71. package/yida-skills/skills/yida-prd/references/app/navigation-patterns.md +43 -0
  72. package/yida-skills/skills/yida-prd/references/app/role-journey.md +30 -0
  73. package/yida-skills/skills/yida-prd/workflow/output-prd.md +188 -0
  74. package/yida-skills/skills/yida-prd/workflow/step-1-read-brief.md +15 -0
  75. package/yida-skills/skills/{yida-design/workflow/step-3-information-architecture.md → yida-prd/workflow/step-2-information-architecture.md} +2 -2
  76. package/yida-skills/skills/yida-publish-page/SKILL.md +1 -1
  77. package/yida-skills/skills/yida-requirement-analysis/SKILL.md +49 -0
  78. package/yida-skills/skills-index.json +36 -35
  79. package/yida-skills/skills/yida-design/workflow/step-1-positioning.md +0 -58
  80. package/yida-skills/skills/yida-form-detail/SKILL.md +0 -68
package/README.md CHANGED
@@ -153,7 +153,7 @@ openyida/
153
153
  │ ├── core/ # Environment detection, i18n, diagnostics, data commands
154
154
  │ ├── process/ # Process form creation, configuration, preview
155
155
  │ ├── report/ # Yida report and chart generation
156
- │ └── samples/ # Sample skeletons emitted by openyida sample
156
+ │ └── samples/ # Reusable code templates emitted by openyida sample
157
157
  ├── project/ # Default workspace template for generated Yida projects
158
158
  ├── yida-skills/ # Source skill docs and Yida API references
159
159
  └── scripts/ # CI, packaging, and installation helpers
@@ -167,13 +167,14 @@ 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-file ./app-theme.css --nav-theme light --logo-source appIcon --layout l_shape
170
+ openyida sample yida-design app-theme --output .cache/openyida/crm/app-theme.css
171
+ openyida create-app --name "CRM" --desc "Customer management" --theme-file .cache/openyida/crm/app-theme.css --nav-theme light --logo-source appIcon --layout l_shape
171
172
  openyida app-list --size 20
172
173
  openyida corp-efficiency
173
174
  openyida create-form create APP_XXX "Customer" .cache/openyida/forms/customer-fields.json
174
175
  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
176
+ openyida sample openyida-page-template form-fields --output .cache/openyida/forms/customer-fields.json
177
+ openyida sample openyida-page-template canvas-form-drawer --output project/pages/src/customer-entry.canvas.jsx --var APP_TYPE=APP_XXX --var FORM_UUID=FORM_XXX
177
178
  openyida get-schema APP_XXX FORM_XXX
178
179
  openyida get-schema APP_XXX FORM_XXX --compact --resolve-fields "Customer Name,Status"
179
180
  openyida get-schema APP_XXX --all --output-dir .cache/schemas
@@ -403,7 +404,8 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
403
404
 
404
405
  | Command | Description |
405
406
  |---------|-------------|
406
- | `openyida create-form create <appType> "<formTitle>" <fieldsJsonFile> [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a form page |
407
+ | `openyida create-form create <appType> "<formTitle>" <fieldsJsonFile> [--icon auto\|<iconName>] [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Create a form page |
408
+ | `openyida create-form icons [--json]` | List available form navigation icons |
407
409
  | `openyida create-form validate-fields <fieldsJsonOrFile> [--json]` | Validate form field JSON locally |
408
410
  | `openyida create-form update <appType> ... [--locale zh_CN\|en_US\|ja_JP] [--open\|--no-open]` | Update a form page |
409
411
  | `openyida create-form patch <appType> <formUuid> <patchJsonOrFile> [--open\|--no-open]` | Update a form page |
@@ -518,7 +520,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
518
520
  | `openyida batch <file>\|--commands "cmd1 ; cmd2" [--stop-on-error] [--json]` | Run OpenYida commands in batch |
519
521
  | `openyida flash-to-prd --file <path> --name "<project>"` | Convert flash notes or meeting notes to a PRD prompt |
520
522
  | `openyida ai <text\|image> [options]` | Call Yida AI text and image recognition APIs |
521
- | `openyida asset <status\|verify-url\|resolve\|generate> [options]` | Detect asset capability / verify image URLs / resolve materials |
523
+ | `openyida asset <status\|resolve\|generate> [options]` | Detect asset capability / resolve materials |
522
524
  | `openyida cdn-config [options]` | Configure CDN / OSS upload |
523
525
  | `openyida cdn-upload <image-path>` | Upload image to CDN |
524
526
  | `openyida cdn-refresh [options]` | Refresh CDN cache |
@@ -549,7 +551,7 @@ The runner rejects public generic echo services such as httpbin and example.com.
549
551
 
550
552
  The opt-in `node scripts/e2e-real/connector/action-update-runner.js` regression uses the owner-confirmed login-free `www.aliwork.com` fixture and a single owned NONE-auth connector containing a target action plus one preservation sentinel. Enable it with `OPENYIDA_E2E=1 OPENYIDA_E2E_CONNECTOR_ACTION_UPDATE=1`. It verifies isolated `currentPage`, `pageSize`, `userLanguage`, `searchFieldJson`, and dynamic `_stamp` edits, restores each field and the final baseline, and persists only response structure, data count, and SHA-256. It never stores response row values or auth/profile/corp identifiers, never retries unknown writes, and intentionally leaves the owned connector as a `cleanup_blocked` residual because no proven delete API exists.
551
553
 
552
- `openyida asset resolve --hero <path-or-url> --product <path-or-url> --require-hero --upload-assets --json` is the preferred preflight for homepage visuals. It verifies public image URLs, uploads local images when CDN is configured, mirrors verified external images to CDN when `--upload-assets` is passed, and returns `materialStatus: final|draft|none` so agents do not claim an unfinished visual page is final.
554
+ `openyida asset resolve --hero <path-or-url> --product <path-or-url> --require-hero --upload-assets --json` is the preferred preflight for homepage visuals. It handles local files and external image references in one flow, uploads or mirrors them when CDN is configured, and returns `materialStatus: final|draft|none` so agents do not claim an unfinished visual page is final.
553
555
 
554
556
  #### Environment and Localization
555
557
 
@@ -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
+ };
@@ -0,0 +1,267 @@
1
+ 'use strict';
2
+
3
+ // Keep this catalog aligned with yida-next's page navigation icon picker:
4
+ // src/components/AppSubNav/SortMenuTree/Theme/nav-icon-config.json.
5
+ const FORM_NAV_ICON_GROUPS = [
6
+ {
7
+ key: 'defaultIcons',
8
+ label: '常用图标',
9
+ items: [
10
+ ['zhuye', '首页'],
11
+ ['clock', '时钟'],
12
+ ['CalendarSeventeen', '日历'],
13
+ ['set', '设置'],
14
+ ['account-settings', '账号设置'],
15
+ ['account', '人'],
16
+ ['lock', '锁'],
17
+ ['Addresslist', '通讯录'],
18
+ ['App', '应用'],
19
+ ['application', '应用2'],
20
+ ['dashboard', '看板'],
21
+ ['biaodan', '表单'],
22
+ ['baobiao', '报表'],
23
+ ['juhebiao', '聚合表'],
24
+ ['liucheng', '流程'],
25
+ ['biaoge', '表格'],
26
+ ['list', '列表'],
27
+ ['gerenwendang', '文档'],
28
+ ['Project', '项目'],
29
+ ['Filemanage', '文件管理'],
30
+ ['add-doc', '添加文件'],
31
+ ['bumen', '部门'],
32
+ ['gongzuo', '工作'],
33
+ ['book', '书'],
34
+ ['bonus', '礼物'],
35
+ ['briefcase', '公文包'],
36
+ ['building', '建筑'],
37
+ ['dingdingka', '钉钉卡'],
38
+ ['desktop', '电脑'],
39
+ ['document', '文件'],
40
+ ['folder', '文件夹'],
41
+ ],
42
+ },
43
+ {
44
+ key: 'systemIcons',
45
+ label: '更多图标',
46
+ items: [
47
+ ['smile', '笑脸'],
48
+ ['rengongfuwu', '人工服务'],
49
+ ['shop', '商城'],
50
+ ['calendar', '日历'],
51
+ ['Documents', '文件包'],
52
+ ['DingDrive', '云'],
53
+ ['appointment', '职务'],
54
+ ['Mail', '邮件'],
55
+ ['Product_management', '产品管理'],
56
+ ['Todo', '任务'],
57
+ ['admin', '账号'],
58
+ ['airplane', '飞机'],
59
+ ['bianqian', '标签'],
60
+ ['bus', '汽车'],
61
+ ['caidan1', '菜单'],
62
+ ['calendar-span', '日历'],
63
+ ['cascade', '连接'],
64
+ ['certificate-type', '印章管理'],
65
+ ['chengyuan', '成员'],
66
+ ['chuangjiantuandui', '创建团队'],
67
+ ['command', '快捷键'],
68
+ ['custom-column', '列表'],
69
+ ['data-settings', '数据设置'],
70
+ ['dagangshu', '大纲树'],
71
+ ['diannao', '电脑'],
72
+ ['diqu', '地区'],
73
+ ['dizhi', '地址'],
74
+ ['fulishe', '福利社'],
75
+ ['keshihuadaping', '可视化大屏'],
76
+ ['employee-add', '新增成员'],
77
+ ['fujian', '附件'],
78
+ ['fuwushichang', '市场'],
79
+ ['gouwudai', '购物袋'],
80
+ ['guanlianzuzhi', '关联组织'],
81
+ ['huangguan', '皇冠'],
82
+ ['huiyishi', '会议室'],
83
+ ['huiyuan', '会员'],
84
+ ['image', '图片'],
85
+ ['list-container', '列表2'],
86
+ ['name-card', '名片'],
87
+ ['printer', '打印机'],
88
+ ['renwu', '任务2'],
89
+ ['riqiziduan', '日期'],
90
+ ['scan', '扫码'],
91
+ ['shezhi', '设置'],
92
+ ['shoucang', '收藏'],
93
+ ['wenjian', '文件2'],
94
+ ['shujuguanliye1', '数据管理页'],
95
+ ['shujuyuanshezhi', '数据源'],
96
+ ['suo', '锁'],
97
+ ['tianjia', '添加'],
98
+ ['tongxunlu', '通讯录'],
99
+ ['wode', '我的'],
100
+ ['zhuguanliyuan', '主管理员'],
101
+ ['ziguanliyuan', '子管理员'],
102
+ ],
103
+ },
104
+ ];
105
+
106
+ const FORM_NAV_ICONS = FORM_NAV_ICON_GROUPS.flatMap(function (group) {
107
+ return group.items.map(function (item) {
108
+ return {
109
+ name: item[0],
110
+ label: item[1],
111
+ group: group.key,
112
+ };
113
+ });
114
+ });
115
+
116
+ const ICON_BY_LOWER_NAME = new Map(FORM_NAV_ICONS.map(function (icon) {
117
+ return [icon.name.toLowerCase(), icon.name];
118
+ }));
119
+
120
+ const SEMANTIC_ICON_RULES = [
121
+ { icon: 'clock', keywords: ['考勤', '打卡', '工时', '时钟', 'attendance', 'timesheet'] },
122
+ { icon: 'huiyishi', keywords: ['会议室', '会议预约', '会议', 'meeting'] },
123
+ { icon: 'CalendarSeventeen', keywords: ['日程', '日历', '排期', '计划', '预约', 'calendar', 'schedule'] },
124
+ { icon: 'Project', keywords: ['项目', 'project'] },
125
+ { icon: 'Todo', keywords: ['任务', '待办', '工单', 'task', 'todo', 'ticket'] },
126
+ { icon: 'name-card', keywords: ['客户', '联系人', '访客', '名片', 'crm', 'customer', 'contact', 'visitor'] },
127
+ { icon: 'chengyuan', keywords: ['员工', '人员', '成员', '人才', '招聘', 'employee', 'member', 'recruit'] },
128
+ { icon: 'bumen', keywords: ['部门', '组织', '团队', 'department', 'organization', 'team'] },
129
+ { icon: 'Product_management', keywords: ['产品', '需求', 'product', 'requirement'] },
130
+ { icon: 'shop', keywords: ['商品', '商城', '店铺', 'product catalog', 'store', 'shop'] },
131
+ { icon: 'gouwudai', keywords: ['订单', '采购', '销售', '购物', 'order', 'purchase', 'sales'] },
132
+ { icon: 'dingdingka', keywords: ['财务', '报销', '费用', '付款', '收款', '预算', '发票', 'finance', 'expense', 'payment', 'invoice'] },
133
+ { icon: 'document', keywords: ['合同', '协议', '公文', '文书', 'contract', 'agreement'] },
134
+ { icon: 'Filemanage', keywords: ['文件', '档案', '资料', 'file', 'archive'] },
135
+ { icon: 'book', keywords: ['知识', '图书', '课程', '培训', 'knowledge', 'book', 'course', 'training'] },
136
+ { icon: 'baobiao', keywords: ['报表', '统计', '分析', 'report', 'analytics'] },
137
+ { icon: 'dashboard', keywords: ['看板', '指标', '驾驶舱', 'dashboard', 'metric'] },
138
+ { icon: 'liucheng', keywords: ['审批', '流程', 'approval', 'workflow'] },
139
+ { icon: 'dizhi', keywords: ['地址', '地点', '场地', 'address', 'location'] },
140
+ { icon: 'building', keywords: ['仓库', '库房', '楼宇', '场馆', 'warehouse', 'building'] },
141
+ { icon: 'desktop', keywords: ['设备', '电脑', '终端', 'device', 'computer'] },
142
+ { icon: 'fuwushichang', keywords: ['市场', '营销', '活动', '推广', 'marketing', 'campaign'] },
143
+ { icon: 'bonus', keywords: ['礼品', '奖励', '福利', 'gift', 'reward', 'benefit'] },
144
+ { icon: 'rengongfuwu', keywords: ['客服', '服务', '咨询', 'service', 'support'] },
145
+ { icon: 'Mail', keywords: ['邮件', '消息', '通知', 'mail', 'message', 'notification'] },
146
+ { icon: 'image', keywords: ['图片', '照片', '素材', 'image', 'photo', 'media'] },
147
+ { icon: 'fujian', keywords: ['附件', 'attachment'] },
148
+ { icon: 'certificate-type', keywords: ['印章', '用印', '证书', 'seal', 'certificate'] },
149
+ { icon: 'admin', keywords: ['账号', '管理员', 'account', 'admin'] },
150
+ { icon: 'lock', keywords: ['权限', '安全', '密码', 'permission', 'security', 'password'] },
151
+ { icon: 'bus', keywords: ['车辆', '用车', '交通', 'vehicle', 'car', 'transport'] },
152
+ { icon: 'airplane', keywords: ['差旅', '航班', '机票', 'travel', 'flight'] },
153
+ { icon: 'diqu', keywords: ['地区', '区域', 'region', 'area'] },
154
+ { icon: 'scan', keywords: ['扫码', '二维码', '条码', 'scan', 'qrcode', 'barcode'] },
155
+ { icon: 'shezhi', keywords: ['配置', '设置', 'config', 'setting'] },
156
+ { icon: 'biaoge', keywords: ['台账', '清单', '表格', '明细', 'ledger', 'table', 'list'] },
157
+ { icon: 'liucheng', keywords: ['申请', 'request'] },
158
+ ];
159
+
160
+ const GENERIC_FORM_ICONS = ['biaodan', 'list', 'document', 'briefcase', 'gongzuo'];
161
+
162
+ function normalizeText(value) {
163
+ if (value === undefined || value === null) {
164
+ return '';
165
+ }
166
+ if (typeof value === 'string' || typeof value === 'number') {
167
+ return String(value).toLowerCase();
168
+ }
169
+ if (Array.isArray(value)) {
170
+ return value.map(normalizeText).filter(Boolean).join(' ');
171
+ }
172
+ if (typeof value === 'object') {
173
+ return Object.values(value).map(normalizeText).filter(Boolean).join(' ');
174
+ }
175
+ return '';
176
+ }
177
+
178
+ function collectFieldSemanticText(fields) {
179
+ const values = [];
180
+ const visit = function (fieldList) {
181
+ (fieldList || []).forEach(function (field) {
182
+ if (!field || typeof field !== 'object') {
183
+ return;
184
+ }
185
+ values.push(field.label, field.title, field.name, field.key);
186
+ if (field.props && typeof field.props === 'object') {
187
+ values.push(field.props.label, field.props.title, field.props.name);
188
+ }
189
+ if (Array.isArray(field.children)) {
190
+ field.children.forEach(function (children) {
191
+ visit(Array.isArray(children) ? children : [children]);
192
+ });
193
+ }
194
+ });
195
+ };
196
+ visit(fields);
197
+ return normalizeText(values);
198
+ }
199
+
200
+ function findSemanticIcon(text) {
201
+ return SEMANTIC_ICON_RULES.find(function (rule) {
202
+ return rule.keywords.some(function (keyword) {
203
+ return text.includes(keyword.toLowerCase());
204
+ });
205
+ });
206
+ }
207
+
208
+ function stableGenericIcon(text) {
209
+ let hash = 0;
210
+ for (let index = 0; index < text.length; index++) {
211
+ hash = ((hash * 31) + text.charCodeAt(index)) >>> 0;
212
+ }
213
+ return GENERIC_FORM_ICONS[hash % GENERIC_FORM_ICONS.length];
214
+ }
215
+
216
+ function inferFormNavIcon(formTitle, fields) {
217
+ const titleText = normalizeText(formTitle);
218
+ const titleMatch = findSemanticIcon(titleText);
219
+ if (titleMatch) {
220
+ return titleMatch.icon;
221
+ }
222
+
223
+ const fieldText = collectFieldSemanticText(fields);
224
+ const fieldMatch = findSemanticIcon(fieldText);
225
+ if (fieldMatch) {
226
+ return fieldMatch.icon;
227
+ }
228
+
229
+ return stableGenericIcon(titleText || fieldText || 'form');
230
+ }
231
+
232
+ function normalizeFormNavIcon(value) {
233
+ const raw = String(value || '').trim();
234
+ if (!raw || raw.toLowerCase() === 'auto') {
235
+ return null;
236
+ }
237
+ return ICON_BY_LOWER_NAME.get(raw.toLowerCase()) || '';
238
+ }
239
+
240
+ function resolveFormNavIcon(value, formTitle, fields) {
241
+ const normalized = normalizeFormNavIcon(value);
242
+ if (normalized) {
243
+ return {
244
+ icon: normalized,
245
+ source: 'explicit',
246
+ };
247
+ }
248
+ if (String(value || '').trim() && String(value).trim().toLowerCase() !== 'auto') {
249
+ return {
250
+ icon: '',
251
+ source: 'invalid',
252
+ };
253
+ }
254
+ return {
255
+ icon: inferFormNavIcon(formTitle, fields),
256
+ source: 'auto',
257
+ };
258
+ }
259
+
260
+ module.exports = {
261
+ FORM_NAV_ICON_GROUPS,
262
+ FORM_NAV_ICONS,
263
+ collectFieldSemanticText,
264
+ inferFormNavIcon,
265
+ normalizeFormNavIcon,
266
+ resolveFormNavIcon,
267
+ };