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/lib/app/create-form.js
CHANGED
|
@@ -82,6 +82,12 @@ const { createParseArgs } = require('./create-form/args');
|
|
|
82
82
|
const { createDefinitionReaders } = require('./create-form/definition-reader');
|
|
83
83
|
const { dispatchCreateFormCommand } = require('./create-form/commands');
|
|
84
84
|
const { buildApiPath } = require('./create-form/api-path');
|
|
85
|
+
const {
|
|
86
|
+
FORM_NAV_ICON_GROUPS,
|
|
87
|
+
FORM_NAV_ICONS,
|
|
88
|
+
resolveFormNavIcon,
|
|
89
|
+
} = require('./create-form/nav-icon');
|
|
90
|
+
const { createFormNavIconService } = require('./create-form/nav-icon-service');
|
|
85
91
|
const { deepMerge, splitJsonPointer } = require('./create-form/schema-patch');
|
|
86
92
|
const {
|
|
87
93
|
actionRefName,
|
|
@@ -4647,11 +4653,13 @@ function applyChangesToSchema(schema, changes, options) {
|
|
|
4647
4653
|
|
|
4648
4654
|
// ── 发送 POST 请求(支持 302 自动重登录) ────────────
|
|
4649
4655
|
|
|
4650
|
-
function sendPostRequest(baseUrl, requestPath, extraParams, formUuid, authRef) {
|
|
4656
|
+
function sendPostRequest(baseUrl, requestPath, extraParams, formUuid, authRef, requestOptions) {
|
|
4657
|
+
requestOptions = requestOptions || {};
|
|
4651
4658
|
const authParams = authRef && authRef.csrfToken ? { _csrf_token: authRef.csrfToken } : {};
|
|
4652
4659
|
const postData = querystring.stringify(Object.assign(authParams, extraParams));
|
|
4660
|
+
const refererAppType = requestOptions.appType || extraParams.appType || '';
|
|
4653
4661
|
const referer = formUuid
|
|
4654
|
-
? `${baseUrl}/alibaba/web/${
|
|
4662
|
+
? requestOptions.referer || `${baseUrl}/alibaba/web/${refererAppType}/design/pageDesigner?formUuid=${formUuid}`
|
|
4655
4663
|
: baseUrl + '/';
|
|
4656
4664
|
return httpPost(baseUrl, requestPath, postData, { referer });
|
|
4657
4665
|
}
|
|
@@ -4678,6 +4686,19 @@ function sendCreateFormRequest(authRef, appType, formTitle) {
|
|
|
4678
4686
|
);
|
|
4679
4687
|
}
|
|
4680
4688
|
|
|
4689
|
+
const {
|
|
4690
|
+
resolveFormNavigationNode,
|
|
4691
|
+
updateCreatedFormNavigationIcon,
|
|
4692
|
+
} = createFormNavIconService({
|
|
4693
|
+
buildApiPath,
|
|
4694
|
+
requestWithAutoLogin,
|
|
4695
|
+
sendGetRequest,
|
|
4696
|
+
sendPostRequest,
|
|
4697
|
+
throwCreateFormError,
|
|
4698
|
+
sanitizeFailureResult,
|
|
4699
|
+
candidateLimit: FIELD_RESOLUTION_CANDIDATE_LIMIT,
|
|
4700
|
+
});
|
|
4701
|
+
|
|
4681
4702
|
// ── 登录态辅助:从 authData 中提取 corpId ──────────
|
|
4682
4703
|
|
|
4683
4704
|
function resolveCorpId(authData) {
|
|
@@ -4853,6 +4874,25 @@ async function mainValidateFields(parsedArgs) {
|
|
|
4853
4874
|
return output;
|
|
4854
4875
|
}
|
|
4855
4876
|
|
|
4877
|
+
async function mainListIcons(parsedArgs) {
|
|
4878
|
+
const output = {
|
|
4879
|
+
success: true,
|
|
4880
|
+
count: FORM_NAV_ICONS.length,
|
|
4881
|
+
default: 'auto',
|
|
4882
|
+
groups: FORM_NAV_ICON_GROUPS.map(function (group) {
|
|
4883
|
+
return {
|
|
4884
|
+
key: group.key,
|
|
4885
|
+
label: group.label,
|
|
4886
|
+
icons: group.items.map(function (item) {
|
|
4887
|
+
return { name: item[0], label: item[1] };
|
|
4888
|
+
}),
|
|
4889
|
+
};
|
|
4890
|
+
}),
|
|
4891
|
+
};
|
|
4892
|
+
console.log(JSON.stringify(output, null, parsedArgs.json ? 2 : 0));
|
|
4893
|
+
return output;
|
|
4894
|
+
}
|
|
4895
|
+
|
|
4856
4896
|
async function mainCreate(parsedArgs, authRef) {
|
|
4857
4897
|
const { appType, formTitle, fieldsJsonOrFile, layout, theme, labelAlign } = parsedArgs;
|
|
4858
4898
|
|
|
@@ -4866,9 +4906,22 @@ async function mainCreate(parsedArgs, authRef) {
|
|
|
4866
4906
|
const { fields, columns, validations } = readFieldsDefinition(fieldsJsonOrFile);
|
|
4867
4907
|
assertNoEmojiInFormDefinition(formTitle, fields, validations);
|
|
4868
4908
|
validateFormFieldDefinitions(fields);
|
|
4909
|
+
const iconResolution = resolveFormNavIcon(parsedArgs.icon, formTitle, fields);
|
|
4910
|
+
if (!iconResolution.icon) {
|
|
4911
|
+
throwCreateFormError(
|
|
4912
|
+
`Unsupported form navigation icon: ${parsedArgs.icon}. Run openyida create-form icons --json for the supported catalog.`,
|
|
4913
|
+
'CREATE_FORM_NAV_ICON_INVALID',
|
|
4914
|
+
{
|
|
4915
|
+
icon: parsedArgs.icon,
|
|
4916
|
+
availableIconCount: FORM_NAV_ICONS.length,
|
|
4917
|
+
}
|
|
4918
|
+
);
|
|
4919
|
+
}
|
|
4920
|
+
const formIcon = iconResolution.icon;
|
|
4869
4921
|
const fieldCount = countDataFieldDefinitions(fields);
|
|
4870
4922
|
success(t('create_form.fields_loaded', fieldCount));
|
|
4871
4923
|
label('Columns:', String(columns));
|
|
4924
|
+
label('Icon:', `${formIcon} (${iconResolution.source})`);
|
|
4872
4925
|
fields.forEach(function (field, index) {
|
|
4873
4926
|
listItem((index + 1) + '. ' + normalizeFormDefinitionType(field) + ': ' + getDefinitionDisplayName(field));
|
|
4874
4927
|
});
|
|
@@ -4935,6 +4988,8 @@ async function mainCreate(parsedArgs, authRef) {
|
|
|
4935
4988
|
formUuid,
|
|
4936
4989
|
fieldCount,
|
|
4937
4990
|
});
|
|
4991
|
+
postCreateStage = 'updateFormNavigationIcon';
|
|
4992
|
+
await updateCreatedFormNavigationIcon(authRef, appType, formUuid, formIcon);
|
|
4938
4993
|
} catch (err) {
|
|
4939
4994
|
emitCreateFormPostCreateFailure({
|
|
4940
4995
|
appType,
|
|
@@ -4954,7 +5009,7 @@ async function mainCreate(parsedArgs, authRef) {
|
|
|
4954
5009
|
['URL', formUrl],
|
|
4955
5010
|
]);
|
|
4956
5011
|
console.log(JSON.stringify(withBrowserHandoff(
|
|
4957
|
-
{ success: true, formUuid, formTitle, appType, fieldCount, url: formUrl },
|
|
5012
|
+
{ success: true, formUuid, formTitle, appType, fieldCount, icon: formIcon, iconSource: iconResolution.source, url: formUrl },
|
|
4958
5013
|
formUrl,
|
|
4959
5014
|
{ stage: 'create_form_success', title: formTitle },
|
|
4960
5015
|
parsedArgs.browserOpenMode
|
|
@@ -4991,6 +5046,7 @@ async function createFormForLegacyProcessQuiet(context, input) {
|
|
|
4991
5046
|
layout: input.layout || 'single',
|
|
4992
5047
|
theme: input.theme || 'default',
|
|
4993
5048
|
labelAlign: input.labelAlign || 'top',
|
|
5049
|
+
icon: input.icon || 'auto',
|
|
4994
5050
|
contentLocale: input.contentLocale || null,
|
|
4995
5051
|
browserOpenMode: 'off',
|
|
4996
5052
|
};
|
|
@@ -5000,6 +5056,15 @@ async function createFormForLegacyProcessQuiet(context, input) {
|
|
|
5000
5056
|
const { fields, validations } = readFieldsDefinition(fieldsJsonOrFile);
|
|
5001
5057
|
assertNoEmojiInFormDefinition(formTitle, fields, validations, 'legacy create-form input');
|
|
5002
5058
|
validateFormFieldDefinitions(fields);
|
|
5059
|
+
const iconResolution = resolveFormNavIcon(parsedArgs.icon, formTitle, fields);
|
|
5060
|
+
if (!iconResolution.icon) {
|
|
5061
|
+
throwCreateFormError(
|
|
5062
|
+
`Unsupported form navigation icon: ${parsedArgs.icon}. Run openyida create-form icons --json for the supported catalog.`,
|
|
5063
|
+
'CREATE_FORM_NAV_ICON_INVALID',
|
|
5064
|
+
{ icon: parsedArgs.icon, availableIconCount: FORM_NAV_ICONS.length }
|
|
5065
|
+
);
|
|
5066
|
+
}
|
|
5067
|
+
const formIcon = iconResolution.icon;
|
|
5003
5068
|
const fieldCount = countDataFieldDefinitions(fields);
|
|
5004
5069
|
const createResult = await sendCreateFormRequest(authRef, appType, formTitle);
|
|
5005
5070
|
|
|
@@ -5030,6 +5095,7 @@ async function createFormForLegacyProcessQuiet(context, input) {
|
|
|
5030
5095
|
applySmartValidations(schema, createValidationRules);
|
|
5031
5096
|
}
|
|
5032
5097
|
const saveResult = await saveFormSchema(authRef, appType, formUuid, schema, serverRevision, 4);
|
|
5098
|
+
const navIconResult = await updateCreatedFormNavigationIcon(authRef, appType, formUuid, formIcon);
|
|
5033
5099
|
return {
|
|
5034
5100
|
success: true,
|
|
5035
5101
|
appType,
|
|
@@ -5038,6 +5104,9 @@ async function createFormForLegacyProcessQuiet(context, input) {
|
|
|
5038
5104
|
fieldCount,
|
|
5039
5105
|
createResult,
|
|
5040
5106
|
saveResult,
|
|
5107
|
+
navIconResult,
|
|
5108
|
+
icon: formIcon,
|
|
5109
|
+
iconSource: iconResolution.source,
|
|
5041
5110
|
url: authRef.baseUrl + '/' + appType + '/workbench/' + formUuid,
|
|
5042
5111
|
};
|
|
5043
5112
|
}
|
|
@@ -6017,6 +6086,9 @@ async function run(args) {
|
|
|
6017
6086
|
if (parsedArgs.mode === 'validate-fields') {
|
|
6018
6087
|
return mainValidateFields(parsedArgs);
|
|
6019
6088
|
}
|
|
6089
|
+
if (parsedArgs.mode === 'icons') {
|
|
6090
|
+
return mainListIcons(parsedArgs);
|
|
6091
|
+
}
|
|
6020
6092
|
|
|
6021
6093
|
step(1, t('common.step_login', 1));
|
|
6022
6094
|
const authRef = createAuthRef();
|
|
@@ -6058,6 +6130,8 @@ module.exports = {
|
|
|
6058
6130
|
normalizeFormDefinitionType,
|
|
6059
6131
|
validateFormFieldDefinitions,
|
|
6060
6132
|
collectFormFieldValidationDiagnostics,
|
|
6133
|
+
resolveFormNavigationNode,
|
|
6134
|
+
updateCreatedFormNavigationIcon,
|
|
6061
6135
|
applyChangesToSchema,
|
|
6062
6136
|
applyFormRules,
|
|
6063
6137
|
applySchemaPatchOperations,
|
package/lib/app/nav-group.js
CHANGED
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
requestWithAutoLogin,
|
|
8
8
|
} = require('../core/utils');
|
|
9
9
|
const { createAuthRef, isAuthRefReady } = require('../core/yida-client');
|
|
10
|
+
const { CliError } = require('../core/cli-error');
|
|
10
11
|
const { parseYidaI18n } = require('../core/yida-i18n');
|
|
11
12
|
|
|
12
13
|
const ROOT_NAV_UUID = 'NAV-SYSTEM-PARENT-UUID';
|
|
@@ -422,6 +423,92 @@ function autoOrderRootNodes(list) {
|
|
|
422
423
|
};
|
|
423
424
|
}
|
|
424
425
|
|
|
426
|
+
function snapshotNavigationRoots(roots) {
|
|
427
|
+
const snapshot = [];
|
|
428
|
+
|
|
429
|
+
const visit = (nodes, parentNavUuid) => {
|
|
430
|
+
(nodes || []).forEach((node, siblingIndex) => {
|
|
431
|
+
snapshot.push({
|
|
432
|
+
navUuid: node.navUuid,
|
|
433
|
+
parentNavUuid,
|
|
434
|
+
siblingIndex,
|
|
435
|
+
navType: node.navType || '',
|
|
436
|
+
});
|
|
437
|
+
visit(node.children || [], node.navUuid);
|
|
438
|
+
});
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
visit(roots || [], ROOT_NAV_UUID);
|
|
442
|
+
return snapshot;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function buildNavigationSnapshot(list) {
|
|
446
|
+
return snapshotNavigationRoots(buildMutableForest(list).roots);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function navigationSnapshotsEqual(left, right) {
|
|
450
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return left.every((item, index) => {
|
|
455
|
+
const other = right[index];
|
|
456
|
+
return other &&
|
|
457
|
+
item.navUuid === other.navUuid &&
|
|
458
|
+
item.parentNavUuid === other.parentNavUuid &&
|
|
459
|
+
item.siblingIndex === other.siblingIndex &&
|
|
460
|
+
item.navType === other.navType;
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function findFirstNavigationDifference(actual, expected) {
|
|
465
|
+
const actualItems = Array.isArray(actual) ? actual : [];
|
|
466
|
+
const expectedItems = Array.isArray(expected) ? expected : [];
|
|
467
|
+
const length = Math.max(actualItems.length, expectedItems.length);
|
|
468
|
+
|
|
469
|
+
for (let index = 0; index < length; index++) {
|
|
470
|
+
const actualItem = actualItems[index] || null;
|
|
471
|
+
const expectedItem = expectedItems[index] || null;
|
|
472
|
+
if (!actualItem || !expectedItem ||
|
|
473
|
+
actualItem.navUuid !== expectedItem.navUuid ||
|
|
474
|
+
actualItem.parentNavUuid !== expectedItem.parentNavUuid ||
|
|
475
|
+
actualItem.siblingIndex !== expectedItem.siblingIndex ||
|
|
476
|
+
actualItem.navType !== expectedItem.navType) {
|
|
477
|
+
return {
|
|
478
|
+
index,
|
|
479
|
+
actual: actualItem,
|
|
480
|
+
expected: expectedItem,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function summarizeNavigationOrderDiff(code, beforeOrder, expectedOrder, observedOrder) {
|
|
489
|
+
const hasObservedOrder = Array.isArray(observedOrder);
|
|
490
|
+
return {
|
|
491
|
+
beforeCount: Array.isArray(beforeOrder) ? beforeOrder.length : 0,
|
|
492
|
+
expectedCount: Array.isArray(expectedOrder) ? expectedOrder.length : 0,
|
|
493
|
+
observedCount: hasObservedOrder ? observedOrder.length : null,
|
|
494
|
+
firstPlannedChange: findFirstNavigationDifference(beforeOrder, expectedOrder),
|
|
495
|
+
...(code === 'NAV_ORDER_READBACK_MISMATCH' && hasObservedOrder
|
|
496
|
+
? { firstReadbackMismatch: findFirstNavigationDifference(observedOrder, expectedOrder) }
|
|
497
|
+
: {}),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function buildNavigationOrderPlan(list, ordered) {
|
|
502
|
+
const beforeOrder = buildNavigationSnapshot(list);
|
|
503
|
+
const expectedOrder = snapshotNavigationRoots(ordered.roots);
|
|
504
|
+
return {
|
|
505
|
+
beforeOrder,
|
|
506
|
+
expectedOrder,
|
|
507
|
+
targetNavUuids: ordered.orderedNavUuids.slice(),
|
|
508
|
+
changed: !navigationSnapshotsEqual(beforeOrder, expectedOrder),
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
425
512
|
function countChildren(list, navUuid) {
|
|
426
513
|
return (list || []).filter((node) => node.parentNavUuid === navUuid).length;
|
|
427
514
|
}
|
|
@@ -455,7 +542,7 @@ async function fetchNavigationList(appType, authRef) {
|
|
|
455
542
|
return Array.isArray(result.content) ? result.content : [];
|
|
456
543
|
}
|
|
457
544
|
|
|
458
|
-
async function postNavAction(appType, action, payload, authRef) {
|
|
545
|
+
async function postNavAction(appType, action, payload, authRef, options = {}) {
|
|
459
546
|
const endpoints = {
|
|
460
547
|
save: 'saveFormNavigation.json',
|
|
461
548
|
update: 'updateFormNavigation.json',
|
|
@@ -475,21 +562,143 @@ async function postNavAction(appType, action, payload, authRef) {
|
|
|
475
562
|
throw new Error(`Unknown nav action: ${action}`);
|
|
476
563
|
}
|
|
477
564
|
|
|
478
|
-
const
|
|
565
|
+
const requestFn = (auth) => {
|
|
479
566
|
const path = `/dingtalk/web/${appType}/query/formnav/${endpoint}` +
|
|
480
567
|
`?_api=${encodeURIComponent(apiNames[action])}` +
|
|
481
568
|
'&_mock=false' +
|
|
482
569
|
`&_stamp=${Date.now()}`;
|
|
483
570
|
return httpPost(auth.baseUrl, path, querystring.stringify(payload));
|
|
484
|
-
}
|
|
571
|
+
};
|
|
572
|
+
const result = options.oneShot === true
|
|
573
|
+
? await requestFn(authRef)
|
|
574
|
+
: await requestWithAutoLogin(requestFn, authRef);
|
|
485
575
|
|
|
486
|
-
if (!result || result.success === false) {
|
|
576
|
+
if (!result || result.success === false || result.__needLogin || result.__csrfExpired) {
|
|
487
577
|
throw new Error(result ? result.errorMsg || `Nav action failed: ${action}` : `Nav action failed: ${action}`);
|
|
488
578
|
}
|
|
489
579
|
|
|
490
580
|
return result;
|
|
491
581
|
}
|
|
492
582
|
|
|
583
|
+
function buildNavigationOrderSuccess(appType, action, ordered, observedList, plan, recoveredByReadback) {
|
|
584
|
+
const orderedNodes = ordered.orderedNavUuids.map((navUuid) => normalizeNavNode(
|
|
585
|
+
resolveNode(observedList, navUuid, { excludeSystem: true })
|
|
586
|
+
));
|
|
587
|
+
return {
|
|
588
|
+
success: true,
|
|
589
|
+
appType,
|
|
590
|
+
action,
|
|
591
|
+
targetParentNavUuid: ROOT_NAV_UUID,
|
|
592
|
+
changed: plan.changed,
|
|
593
|
+
alreadyApplied: !plan.changed,
|
|
594
|
+
mutationPerformed: plan.changed,
|
|
595
|
+
readbackVerified: true,
|
|
596
|
+
recoveredByReadback: recoveredByReadback === true,
|
|
597
|
+
sideEffectState: plan.changed ? 'committed' : 'none',
|
|
598
|
+
orderedNavUuids: ordered.orderedNavUuids,
|
|
599
|
+
orderedNodes,
|
|
600
|
+
skipped: !plan.changed,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function buildNavigationOrderError(code, appType, action, plan, observedOrder, cause) {
|
|
605
|
+
const sideEffectState = code === 'NAV_ORDER_NOT_APPLIED' ? 'none' : 'unknown';
|
|
606
|
+
const retrySafe = code === 'NAV_ORDER_NOT_APPLIED';
|
|
607
|
+
const readbackVerified = code === 'NAV_ORDER_NOT_APPLIED';
|
|
608
|
+
const status = code === 'NAV_ORDER_NOT_APPLIED'
|
|
609
|
+
? 'NOT_APPLIED'
|
|
610
|
+
: code === 'NAV_ORDER_READBACK_MISMATCH'
|
|
611
|
+
? 'SEMANTIC_FAILURE'
|
|
612
|
+
: 'RESULT_UNKNOWN';
|
|
613
|
+
return new CliError(code, {
|
|
614
|
+
code,
|
|
615
|
+
details: {
|
|
616
|
+
target: {
|
|
617
|
+
type: 'navigationOrder',
|
|
618
|
+
appType,
|
|
619
|
+
action,
|
|
620
|
+
navUuids: plan.targetNavUuids,
|
|
621
|
+
},
|
|
622
|
+
changed: plan.changed,
|
|
623
|
+
...(code === 'NAV_ORDER_NOT_APPLIED' ? { mutationPerformed: false } : {}),
|
|
624
|
+
readbackVerified,
|
|
625
|
+
retryable: retrySafe,
|
|
626
|
+
retrySafe,
|
|
627
|
+
sideEffectState,
|
|
628
|
+
status,
|
|
629
|
+
readbackAllowed: true,
|
|
630
|
+
orderDiff: summarizeNavigationOrderDiff(
|
|
631
|
+
code,
|
|
632
|
+
plan.beforeOrder,
|
|
633
|
+
plan.expectedOrder,
|
|
634
|
+
observedOrder
|
|
635
|
+
),
|
|
636
|
+
cause: cause && cause.message ? cause.message : undefined,
|
|
637
|
+
nextStep: `openyida nav-group list ${appType} --flat`,
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async function applyNavigationOrder(appType, action, list, ordered, authRef, dependencies = {}) {
|
|
643
|
+
const fetchList = dependencies.fetchNavigationList || fetchNavigationList;
|
|
644
|
+
const postAction = dependencies.postNavAction || postNavAction;
|
|
645
|
+
const plan = buildNavigationOrderPlan(list, ordered);
|
|
646
|
+
|
|
647
|
+
if (!plan.changed) {
|
|
648
|
+
return buildNavigationOrderSuccess(appType, action, ordered, list, plan, false);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
const firstNode = resolveNode(list, ordered.orderedNavUuids[0], { excludeSystem: true });
|
|
652
|
+
let mutationError = null;
|
|
653
|
+
try {
|
|
654
|
+
await postAction(appType, 'updateOrderNew', {
|
|
655
|
+
currentId: firstNode.id,
|
|
656
|
+
parentNavUuid: ROOT_NAV_UUID,
|
|
657
|
+
navType: firstNode.navType,
|
|
658
|
+
ids: ordered.ids.join(','),
|
|
659
|
+
}, authRef, { oneShot: true });
|
|
660
|
+
} catch (error) {
|
|
661
|
+
mutationError = error;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
let updatedList;
|
|
665
|
+
try {
|
|
666
|
+
updatedList = await fetchList(appType, authRef);
|
|
667
|
+
} catch (readbackError) {
|
|
668
|
+
throw buildNavigationOrderError(
|
|
669
|
+
'NAV_ORDER_RESULT_UNKNOWN',
|
|
670
|
+
appType,
|
|
671
|
+
action,
|
|
672
|
+
plan,
|
|
673
|
+
null,
|
|
674
|
+
readbackError
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const observedOrder = buildNavigationSnapshot(updatedList);
|
|
679
|
+
if (navigationSnapshotsEqual(observedOrder, plan.expectedOrder)) {
|
|
680
|
+
return buildNavigationOrderSuccess(appType, action, ordered, updatedList, plan, !!mutationError);
|
|
681
|
+
}
|
|
682
|
+
if (navigationSnapshotsEqual(observedOrder, plan.beforeOrder)) {
|
|
683
|
+
throw buildNavigationOrderError(
|
|
684
|
+
'NAV_ORDER_NOT_APPLIED',
|
|
685
|
+
appType,
|
|
686
|
+
action,
|
|
687
|
+
plan,
|
|
688
|
+
observedOrder,
|
|
689
|
+
mutationError
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
throw buildNavigationOrderError(
|
|
693
|
+
'NAV_ORDER_READBACK_MISMATCH',
|
|
694
|
+
appType,
|
|
695
|
+
action,
|
|
696
|
+
plan,
|
|
697
|
+
observedOrder,
|
|
698
|
+
mutationError
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
|
|
493
702
|
function normalizeListOutput(list, options = {}) {
|
|
494
703
|
const includeSystem = options.includeSystem === true;
|
|
495
704
|
return (list || [])
|
|
@@ -647,62 +856,17 @@ async function orderRootNodes(appType, values, authRef) {
|
|
|
647
856
|
|
|
648
857
|
const list = await fetchNavigationList(appType, authRef);
|
|
649
858
|
const ordered = reorderRootNodes(list, values);
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
await postNavAction(appType, 'updateOrderNew', {
|
|
653
|
-
currentId: firstNode.id,
|
|
654
|
-
parentNavUuid: ROOT_NAV_UUID,
|
|
655
|
-
navType: firstNode.navType,
|
|
656
|
-
ids: ordered.ids.join(','),
|
|
657
|
-
}, authRef);
|
|
658
|
-
|
|
659
|
-
const updatedList = await fetchNavigationList(appType, authRef);
|
|
660
|
-
const orderedNodes = ordered.orderedNavUuids.map((navUuid) => normalizeNavNode(
|
|
661
|
-
resolveNode(updatedList, navUuid, { excludeSystem: true })
|
|
662
|
-
));
|
|
663
|
-
return {
|
|
664
|
-
success: true,
|
|
665
|
-
appType,
|
|
666
|
-
action: 'order',
|
|
667
|
-
targetParentNavUuid: ROOT_NAV_UUID,
|
|
668
|
-
orderedNavUuids: ordered.orderedNavUuids,
|
|
669
|
-
orderedNodes,
|
|
670
|
-
};
|
|
859
|
+
return applyNavigationOrder(appType, 'order', list, ordered, authRef);
|
|
671
860
|
}
|
|
672
861
|
|
|
673
862
|
async function autoOrderNavigation(appType, authRef) {
|
|
674
863
|
const list = await fetchNavigationList(appType, authRef);
|
|
675
864
|
const ordered = autoOrderRootNodes(list);
|
|
676
865
|
if (!ordered.orderedNavUuids.length) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
appType,
|
|
680
|
-
action: 'auto-order',
|
|
681
|
-
targetParentNavUuid: ROOT_NAV_UUID,
|
|
682
|
-
orderedNavUuids: [],
|
|
683
|
-
orderedNodes: [],
|
|
684
|
-
skipped: true,
|
|
685
|
-
};
|
|
866
|
+
const plan = buildNavigationOrderPlan(list, ordered);
|
|
867
|
+
return buildNavigationOrderSuccess(appType, 'auto-order', ordered, list, plan, false);
|
|
686
868
|
}
|
|
687
|
-
|
|
688
|
-
const firstNode = resolveNode(list, ordered.orderedNavUuids[0], { excludeSystem: true });
|
|
689
|
-
await postNavAction(appType, 'updateOrderNew', {
|
|
690
|
-
currentId: firstNode.id,
|
|
691
|
-
parentNavUuid: ROOT_NAV_UUID,
|
|
692
|
-
navType: firstNode.navType,
|
|
693
|
-
ids: ordered.ids.join(','),
|
|
694
|
-
}, authRef);
|
|
695
|
-
|
|
696
|
-
const updatedList = await fetchNavigationList(appType, authRef);
|
|
697
|
-
const updatedOrdered = autoOrderRootNodes(updatedList);
|
|
698
|
-
return {
|
|
699
|
-
success: true,
|
|
700
|
-
appType,
|
|
701
|
-
action: 'auto-order',
|
|
702
|
-
targetParentNavUuid: ROOT_NAV_UUID,
|
|
703
|
-
orderedNavUuids: updatedOrdered.orderedNavUuids,
|
|
704
|
-
orderedNodes: updatedOrdered.orderedNodes,
|
|
705
|
-
};
|
|
869
|
+
return applyNavigationOrder(appType, 'auto-order', list, ordered, authRef);
|
|
706
870
|
}
|
|
707
871
|
|
|
708
872
|
async function setVisibility(appType, value, hidden, authRef) {
|
|
@@ -790,8 +954,11 @@ async function run(args) {
|
|
|
790
954
|
|
|
791
955
|
module.exports = {
|
|
792
956
|
ROOT_NAV_UUID,
|
|
957
|
+
applyNavigationOrder,
|
|
793
958
|
autoOrderNavigation,
|
|
794
959
|
autoOrderRootNodes,
|
|
960
|
+
buildNavigationOrderPlan,
|
|
961
|
+
buildNavigationSnapshot,
|
|
795
962
|
buildI18nTitle,
|
|
796
963
|
buildNavigationTree,
|
|
797
964
|
compareNavigationNodes,
|
|
@@ -800,6 +967,7 @@ module.exports = {
|
|
|
800
967
|
getNavigationPriority,
|
|
801
968
|
moveNodeInTree,
|
|
802
969
|
normalizeNavNode,
|
|
970
|
+
navigationSnapshotsEqual,
|
|
803
971
|
orderRootNodes,
|
|
804
972
|
parseArgs,
|
|
805
973
|
reorderRootNodes,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CliError } = require('../core/cli-error');
|
|
4
|
+
|
|
5
|
+
const TEMPLATE_RESIDUE_PATTERNS = [
|
|
6
|
+
{
|
|
7
|
+
pattern: /@openyida-page-template-base/,
|
|
8
|
+
residue: 'template-marker',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
pattern: /\bSAMPLE_ROWS\b/,
|
|
12
|
+
residue: 'sample-data',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
pattern: /\{\{(?:APP_TYPE|FORM_UUID)\}\}/,
|
|
16
|
+
residue: 'template-variable',
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function findPageTemplateResidues(sourceCode) {
|
|
21
|
+
const source = String(sourceCode || '');
|
|
22
|
+
const lines = source.split('\n');
|
|
23
|
+
const issues = [];
|
|
24
|
+
|
|
25
|
+
TEMPLATE_RESIDUE_PATTERNS.forEach(({ pattern, residue }) => {
|
|
26
|
+
lines.forEach((line, index) => {
|
|
27
|
+
if (pattern.test(line)) {
|
|
28
|
+
issues.push({
|
|
29
|
+
line: index + 1,
|
|
30
|
+
residue,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return issues;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function assertPageTemplateCustomized(sourceCode, sourcePath) {
|
|
40
|
+
const issues = findPageTemplateResidues(sourceCode);
|
|
41
|
+
if (issues.length === 0) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
throw new CliError('内置页面模板尚未完成业务化改写:请先替换示例数据、模板变量和占位文案,再发布。', {
|
|
46
|
+
code: 'OPENYIDA_PAGE_TEMPLATE_NOT_CUSTOMIZED',
|
|
47
|
+
details: {
|
|
48
|
+
sourcePath,
|
|
49
|
+
issues,
|
|
50
|
+
retryable: false,
|
|
51
|
+
retrySafe: true,
|
|
52
|
+
sideEffectState: 'none',
|
|
53
|
+
nextAction: {
|
|
54
|
+
type: 'edit_source_then_publish',
|
|
55
|
+
sourcePath,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
assertPageTemplateCustomized,
|
|
63
|
+
findPageTemplateResidues,
|
|
64
|
+
};
|
package/lib/app/publish.js
CHANGED
|
@@ -28,12 +28,13 @@ const { createAuthRef, isTokenAuthRef } = require('../core/yida-client');
|
|
|
28
28
|
const { requireSchemaServerRevision } = require('../core/server-revision');
|
|
29
29
|
const { t } = require('../core/i18n');
|
|
30
30
|
const { banner, step, label, success, fail, warn, info, error, result, usage, hint } = require('../core/chalk');
|
|
31
|
-
const { CliError } = require('../core/cli-error');
|
|
31
|
+
const { CliError, isCliError, toErrorPayload } = require('../core/cli-error');
|
|
32
32
|
const { compileSource } = require('./page-compiler');
|
|
33
33
|
const { compileCanvas } = require('./canvas-compile');
|
|
34
34
|
const { runLintCheck } = require('./page-linter');
|
|
35
35
|
const { buildPageFile, shouldBuildPageSource } = require('./page-compat');
|
|
36
36
|
const { warnLargePageSource } = require('./page-size-guard');
|
|
37
|
+
const { assertPageTemplateCustomized } = require('./page-template-guard');
|
|
37
38
|
const { fetchFormPageList } = require('./form-navigation');
|
|
38
39
|
const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
|
|
39
40
|
const { assertNoEmojiInValue } = require('../core/no-emoji-guard');
|
|
@@ -540,6 +541,7 @@ async function runMain(argv) {
|
|
|
540
541
|
sourcePath = initialSource.absolutePath;
|
|
541
542
|
const initialSourceCode = initialSource.source;
|
|
542
543
|
warnLargePageSource(initialSourceCode, sourcePath);
|
|
544
|
+
assertPageTemplateCustomized(initialSourceCode, sourcePath);
|
|
543
545
|
|
|
544
546
|
let sourceCode = initialSourceCode;
|
|
545
547
|
let compiledCode = '';
|
|
@@ -723,6 +725,18 @@ async function runMain(argv) {
|
|
|
723
725
|
success(t('publish.nav_order_ok', orderedNames || '-'));
|
|
724
726
|
} catch (navOrderError) {
|
|
725
727
|
navOrderWarning = navOrderError && navOrderError.message ? navOrderError.message : t('common.unknown_error');
|
|
728
|
+
navOrderResult = isCliError(navOrderError)
|
|
729
|
+
? toErrorPayload(navOrderError)
|
|
730
|
+
: {
|
|
731
|
+
success: false,
|
|
732
|
+
errorCode: 'NAV_ORDER_RESULT_UNKNOWN',
|
|
733
|
+
errorMsg: navOrderWarning,
|
|
734
|
+
retryable: false,
|
|
735
|
+
retrySafe: false,
|
|
736
|
+
sideEffectState: 'unknown',
|
|
737
|
+
readbackVerified: false,
|
|
738
|
+
nextStep: `openyida nav-group list ${appType} --flat`,
|
|
739
|
+
};
|
|
726
740
|
warn(t('publish.nav_order_failed', navOrderWarning));
|
|
727
741
|
}
|
|
728
742
|
}
|
|
@@ -361,7 +361,7 @@ function buildCanvasPageSchemaObject(sourceCode, runtimeCode, importedModules, f
|
|
|
361
361
|
contentBgColorMobile: 'var(--pod-page-bg-color, var(--color-white, #fff))',
|
|
362
362
|
},
|
|
363
363
|
condition: true,
|
|
364
|
-
css: '
|
|
364
|
+
css: '.vc-page-yida-page{--yida-form-content-padding:0;--yida-form-content-margin:0;--yida-layout-padding:0}.vc-deep-container-entry.vc-rootcontent{padding:0!important;margin-top:0!important;margin-right:0!important;margin-bottom:0!important;margin-left:0!important}',
|
|
365
365
|
methods: {
|
|
366
366
|
__initMethods__: {
|
|
367
367
|
type: 'js',
|
package/lib/asset/ai-image.js
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* 由 skill 文档引导它:能自己生成就生成,否则去免费素材库搜真实图片;
|
|
12
12
|
* 3. 提供推荐的免费/可商用素材库清单与使用纪律,供 skill / agent 参考。
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* 无论走哪条路,候选素材都交给 asset resolve 统一解析;有 CDN 时再转存,
|
|
15
|
+
* 绝不把编造的 URL 直接写进页面。
|
|
16
16
|
*
|
|
17
17
|
* 用法:
|
|
18
18
|
* const { detectImageGenerator, getFreeStockLibraries, getMaterialSourcingGuidance } = require('./ai-image');
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* 推荐的免费 / 可商用图片素材库
|
|
26
26
|
* 说明:这些站点提供免费可商用(多为 CC0 / 自有免费授权)的高质量图片,
|
|
27
27
|
* 适合官网 Hero、产品图、场景配图。agent 应优先从这里取真实图片 URL,
|
|
28
|
-
*
|
|
28
|
+
* 取到后交给 `openyida asset resolve` 统一处理。
|
|
29
29
|
*/
|
|
30
30
|
const FREE_STOCK_LIBRARIES = [
|
|
31
31
|
{
|
|
@@ -34,7 +34,7 @@ const FREE_STOCK_LIBRARIES = [
|
|
|
34
34
|
license: 'Unsplash License(免费可商用,无需署名)',
|
|
35
35
|
bestFor: '高质量摄影级 Hero 大图、生活方式/场景图',
|
|
36
36
|
directImageHint:
|
|
37
|
-
'images.unsplash.com/photo-... 形式的直链可直接用于 <img
|
|
37
|
+
'images.unsplash.com/photo-... 形式的直链可直接用于 <img>;页面链接需先取得图片直链。',
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
name: 'Pexels',
|
|
@@ -82,7 +82,7 @@ function detectImageGenerator(options = {}) {
|
|
|
82
82
|
reason:
|
|
83
83
|
'openyida 无内置文生图能力(`openyida ai` 仅支持文生文/识图)。' +
|
|
84
84
|
'图片素材应由智能体自行生成,或到免费素材库(Unsplash/Pexels 等)检索真实图片,' +
|
|
85
|
-
'
|
|
85
|
+
'再交给 `openyida asset resolve` 解析、(有 CDN 时)转存 CDN 后回填 spec。',
|
|
86
86
|
delegateToAgent: true,
|
|
87
87
|
};
|
|
88
88
|
}
|
|
@@ -105,13 +105,12 @@ function getMaterialSourcingGuidance() {
|
|
|
105
105
|
'1. 判断页面是否需要真实图片(官网 Hero / 产品图 / 场景图通常需要)。',
|
|
106
106
|
'2. 优先:若智能体自身具备图片生成能力,按品牌调性生成图片并保存到本地。',
|
|
107
107
|
'3. 备选:到免费可商用素材库(见 libraries)检索与主题匹配的真实图片,拿到图片直链。',
|
|
108
|
-
'4.
|
|
109
|
-
'5.
|
|
110
|
-
'6. 兜底:若既无生成能力又找不到可校验素材,交付「标注素材缺口」的低保真草稿,绝不编造图片 URL 或声称已上传。',
|
|
108
|
+
'4. 解析:把本地文件或外链交给 `openyida asset resolve` 统一处理并回填 spec.assets;已配置 CDN 时转存为稳定 URL。',
|
|
109
|
+
'5. 兜底:若既无生成能力又找不到可用素材,交付「标注素材缺口」的低保真草稿,绝不编造图片 URL 或声称已上传。',
|
|
111
110
|
],
|
|
112
111
|
libraries: getFreeStockLibraries(),
|
|
113
112
|
rules: [
|
|
114
|
-
'绝不编造图片 URL
|
|
113
|
+
'绝不编造图片 URL;素材经 `openyida asset resolve` 统一处理后再写进页面。',
|
|
115
114
|
'无 CDN 时不得声称「已上传」或「已是最终版」。',
|
|
116
115
|
'素材授权需为免费可商用;如不确定授权,改用文字排版/数据图示/插画(如 unDraw)替代。',
|
|
117
116
|
],
|