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.
- package/README.md +9 -7
- 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 +77 -3
- package/lib/app/nav-group.js +221 -53
- package/lib/app/page-template-guard.js +64 -0
- package/lib/app/publish.js +15 -1
- package/lib/app/services/canvas-page-schema-builder.js +1 -1
- package/lib/asset/ai-image.js +8 -9
- package/lib/asset/asset-cmd.js +3 -49
- package/lib/asset/asset-resolve.js +8 -10
- package/lib/asset/asset-status.js +7 -7
- 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 +73 -11
- package/lib/core/locales/en.js +5 -1
- package/lib/core/locales/zh.js +7 -3
- package/lib/core/query-data.js +126 -8
- package/lib/core/sample.js +51 -10
- package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +7 -7
- package/package.json +1 -1
- package/scripts/postinstall.js +5 -5
- package/yida-skills/SKILL.md +13 -13
- package/yida-skills/references/task-retrospective.md +4 -4
- package/yida-skills/skills/yida-app/SKILL.md +9 -8
- package/yida-skills/skills/yida-app/references/common-issues.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +42 -15
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +8 -1
- package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +13 -15
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +4 -2
- 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 +22 -11
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +8 -8
- 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 +16 -16
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +1 -1
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +1 -1
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +6 -7
- package/yida-skills/skills/yida-canvas-table-form/SKILL.md +2 -2
- package/yida-skills/skills/yida-create-app/SKILL.md +13 -7
- package/yida-skills/skills/yida-create-form-page/SKILL.md +15 -17
- package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +1 -4
- package/yida-skills/skills/yida-custom-page/SKILL.md +5 -5
- package/yida-skills/skills/yida-custom-page/references/coding-guide.md +2 -6
- package/yida-skills/skills/yida-custom-page/references/design-system.md +4 -4
- package/yida-skills/skills/yida-data-management/SKILL.md +6 -4
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +13 -5
- package/yida-skills/skills/yida-design/SKILL.md +36 -39
- package/yida-skills/skills/yida-design/references/asset-workflow.md +32 -85
- package/yida-skills/skills/yida-design/references/page-quality-gates.md +3 -3
- package/yida-skills/skills/yida-design/references/style-design-selection.md +4 -4
- package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +15 -24
- package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +8 -8
- package/yida-skills/skills/yida-design/references/visual-decision-engine.md +1 -1
- package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +2 -2
- package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +12 -13
- package/yida-skills/skills/yida-design/workflow/output-design.md +40 -26
- package/yida-skills/skills/yida-design/workflow/output-prd.md +8 -8
- 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 +23 -17
- 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-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 +188 -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 +1 -1
- package/yida-skills/skills/yida-requirement-analysis/SKILL.md +49 -0
- package/yida-skills/skills-index.json +36 -35
- package/yida-skills/skills/yida-design/workflow/step-1-positioning.md +0 -58
- 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/ #
|
|
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
|
|
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-
|
|
176
|
-
openyida sample openyida-
|
|
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\|
|
|
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
|
|
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
|
|
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
|
+
};
|
|
@@ -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
|
+
};
|