openyida 2026.7.19 → 2026.7.21

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 (139) hide show
  1. package/README.md +14 -0
  2. package/bin/yida.js +107 -18
  3. package/lib/app/create-app.js +12 -1
  4. package/lib/app/create-form/args.js +13 -0
  5. package/lib/app/create-form.js +154 -67
  6. package/lib/app/create-page.js +45 -9
  7. package/lib/app/get-schema.js +65 -14
  8. package/lib/app/import-app.js +92 -41
  9. package/lib/app/publish.js +289 -556
  10. package/lib/app/schema-field-resolution.js +370 -0
  11. package/lib/app/services/app-reader.js +85 -0
  12. package/lib/app/services/app-service.js +235 -0
  13. package/lib/app/services/canvas-page-compiler.js +91 -0
  14. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  15. package/lib/app/services/field-bindings.js +68 -0
  16. package/lib/app/services/form-compiler.js +1922 -0
  17. package/lib/app/services/form-mode-service.js +182 -0
  18. package/lib/app/services/form-schema-patcher.js +333 -0
  19. package/lib/app/services/form-schema-reader.js +59 -0
  20. package/lib/app/services/form-service.js +379 -0
  21. package/lib/app/services/form-validation.js +562 -0
  22. package/lib/app/services/native-page-compiler.js +92 -0
  23. package/lib/app/services/native-page-schema-builder.js +428 -0
  24. package/lib/app/services/page-resource-service.js +185 -0
  25. package/lib/app/update-app.js +18 -6
  26. package/lib/auth/token-auth.js +34 -3
  27. package/lib/auth/token-store.js +22 -4
  28. package/lib/bridge/bridge.js +37 -5
  29. package/lib/core/agent-capabilities.js +5 -3
  30. package/lib/core/command-manifest.js +40 -0
  31. package/lib/core/doctor.js +15 -4
  32. package/lib/core/env-cmd.js +1 -1
  33. package/lib/core/env.js +7 -6
  34. package/lib/core/legacy-schema-guard.js +408 -0
  35. package/lib/core/locales/en.js +8 -46
  36. package/lib/core/locales/zh.js +8 -46
  37. package/lib/core/sample.js +1 -1
  38. package/lib/core/utils.js +334 -66
  39. package/lib/core/yida-client.js +104 -3
  40. package/lib/formula/evaluate.js +8 -66
  41. package/lib/formula/field-refs.js +83 -0
  42. package/lib/process/configure-process.js +44 -1992
  43. package/lib/process/create-process.js +54 -87
  44. package/lib/process/services/process-compiler.js +2056 -0
  45. package/lib/process/services/process-reader.js +232 -0
  46. package/lib/process/services/process-resource-service.js +571 -0
  47. package/lib/process/services/process-service.js +122 -0
  48. package/lib/process/services/process-stage-checkpoint.js +246 -0
  49. package/lib/report/append.js +7 -26
  50. package/lib/report/http.js +31 -5
  51. package/lib/report/index.js +10 -2
  52. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  53. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  54. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  55. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
  56. package/lib/schema/adapters/app-adapter.js +210 -0
  57. package/lib/schema/adapters/form-adapter.js +1362 -0
  58. package/lib/schema/adapters/keys.js +19 -0
  59. package/lib/schema/adapters/page-adapter.js +566 -0
  60. package/lib/schema/adapters/process-adapter.js +519 -0
  61. package/lib/schema/applier.js +1878 -0
  62. package/lib/schema/apply-store.js +1083 -0
  63. package/lib/schema/command.js +645 -0
  64. package/lib/schema/dependency-graph.js +83 -0
  65. package/lib/schema/errors.js +481 -0
  66. package/lib/schema/hash.js +9 -0
  67. package/lib/schema/manifest-limits.js +177 -0
  68. package/lib/schema/manifest-loader.js +309 -0
  69. package/lib/schema/manifest-schema-v1.json +193 -0
  70. package/lib/schema/normalize-manifest.js +281 -0
  71. package/lib/schema/page-canvas-foundation.js +447 -0
  72. package/lib/schema/page-data-source-builder.js +561 -0
  73. package/lib/schema/page-foundation.js +556 -0
  74. package/lib/schema/page-source-loader.js +314 -0
  75. package/lib/schema/planner.js +691 -0
  76. package/lib/schema/remote-dispatch-boundary.js +39 -0
  77. package/lib/schema/remote-missing.js +21 -0
  78. package/lib/schema/remote-reader.js +189 -0
  79. package/lib/schema/resource-registry.js +157 -0
  80. package/lib/schema/server-revision.js +71 -0
  81. package/lib/schema/sort.js +28 -0
  82. package/lib/schema/state-store.js +589 -0
  83. package/package.json +2 -2
  84. package/scripts/postinstall.js +8 -12
  85. package/scripts/validate-package-size.js +1 -1
  86. package/yida-skills/SKILL.md +114 -29
  87. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  88. package/yida-skills/references/setup-and-env.md +59 -59
  89. package/yida-skills/references/task-retrospective.md +6 -6
  90. package/yida-skills/skills/yida-app/SKILL.md +60 -14
  91. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  92. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  93. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +7 -1
  94. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  95. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  96. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  97. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  98. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  99. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  100. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  101. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  102. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  103. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  104. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  105. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  106. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  107. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  108. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  109. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  110. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  111. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  112. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  113. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  114. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  115. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  116. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  117. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  118. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  119. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  120. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  121. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  122. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  123. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  124. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  125. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  126. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  127. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  128. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  129. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  130. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  131. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  132. package/yida-skills/skills/yida-report/SKILL.md +7 -1
  133. package/yida-skills/skills-index.json +14 -46
  134. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  135. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  136. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  137. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  138. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  139. package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
@@ -0,0 +1,246 @@
1
+ 'use strict';
2
+
3
+ const { schemaError } = require('../../schema/errors');
4
+
5
+ const PROCESS_STAGES = Object.freeze([
6
+ 'draft_created',
7
+ 'saved',
8
+ 'published',
9
+ 'readback_verified',
10
+ 'completed',
11
+ ]);
12
+ const ADAPTER_CHECKPOINT_STAGES = new Set(PROCESS_STAGES.slice(0, 3));
13
+ const STAGE_CHECKPOINT_KEYS = new Set([
14
+ 'stage',
15
+ 'processCode',
16
+ 'processId',
17
+ 'processVersion',
18
+ 'nodeBindings',
19
+ 'desiredManagedHash',
20
+ 'observedManagedHash',
21
+ ]);
22
+ const ADAPTER_PARTIAL_KEYS = new Set([
23
+ 'processCode',
24
+ 'processId',
25
+ 'processVersion',
26
+ 'nodeBindings',
27
+ ]);
28
+ const NODE_BINDING_KEYS = new Set(['nodeId', 'componentName']);
29
+ const HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
30
+ const SEMANTIC_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
31
+
32
+ function validateProcessStageCheckpoint(checkpoint, options = {}) {
33
+ assertObject(checkpoint);
34
+ assertAllowedKeys(checkpoint, STAGE_CHECKPOINT_KEYS);
35
+ if (!PROCESS_STAGES.includes(checkpoint.stage)) {
36
+ invalidCheckpoint();
37
+ }
38
+ assertHash(checkpoint.desiredManagedHash);
39
+ if (options.desiredManagedHash !== undefined && checkpoint.desiredManagedHash !== options.desiredManagedHash) {
40
+ invalidCheckpoint();
41
+ }
42
+ if (stageIndex(checkpoint.stage) >= stageIndex('readback_verified')) {
43
+ assertHash(checkpoint.observedManagedHash);
44
+ } else if (checkpoint.observedManagedHash !== undefined) {
45
+ invalidCheckpoint();
46
+ }
47
+ for (const property of ['processCode', 'processId']) {
48
+ if (checkpoint[property] !== undefined && !isNonEmptyString(checkpoint[property])) {
49
+ invalidCheckpoint();
50
+ }
51
+ }
52
+ if (
53
+ checkpoint.processVersion !== undefined &&
54
+ (!Number.isInteger(checkpoint.processVersion) || checkpoint.processVersion < 0)
55
+ ) {
56
+ invalidCheckpoint();
57
+ }
58
+ if (checkpoint.nodeBindings !== undefined) {
59
+ validateNodeBindings(checkpoint.nodeBindings);
60
+ }
61
+ if (options.operation === 'stateRepair') {
62
+ invalidCheckpoint();
63
+ }
64
+ if (checkpoint.stage === 'completed' && options.operationStatus !== undefined && options.operationStatus !== 'completed') {
65
+ invalidCheckpoint();
66
+ }
67
+ if (options.operationStatus === 'completed' && checkpoint.stage !== 'completed') {
68
+ invalidCheckpoint();
69
+ }
70
+ return checkpoint;
71
+ }
72
+
73
+ function advanceProcessStagedCheckpoint(previous, event, input = {}) {
74
+ assertObject(input);
75
+ if (event === 'adapterCheckpoint') {
76
+ assertAllowedKeys(input, new Set(['desiredManagedHash', 'partial', 'stage']));
77
+ assertAdapterCheckpointStage(input.stage);
78
+ return advanceProcessStageCheckpoint(previous, input.stage, input.partial || {}, {
79
+ desiredManagedHash: input.desiredManagedHash,
80
+ });
81
+ }
82
+ if (event === 'reconciledRemoteComplete') {
83
+ assertAllowedKeys(input, new Set(['desiredManagedHash']));
84
+ let checkpoint = previous;
85
+ let index = checkpoint ? stageIndex(checkpoint.stage) : -1;
86
+ const publishedIndex = stageIndex('published');
87
+ while (index < publishedIndex) {
88
+ index += 1;
89
+ checkpoint = advanceProcessStageCheckpoint(checkpoint, PROCESS_STAGES[index], {}, {
90
+ desiredManagedHash: input.desiredManagedHash,
91
+ });
92
+ }
93
+ return checkpoint;
94
+ }
95
+ if (event === 'readbackVerified') {
96
+ assertAllowedKeys(input, new Set(['desiredManagedHash', 'observedManagedHash']));
97
+ if (previous && previous.stage === 'readback_verified') {
98
+ validateProcessStageCheckpoint(previous, { desiredManagedHash: input.desiredManagedHash });
99
+ if (previous.observedManagedHash !== input.observedManagedHash) {
100
+ invalidCheckpoint();
101
+ }
102
+ return clonePlain(previous);
103
+ }
104
+ return advanceProcessStageCheckpoint(previous, 'readback_verified', {}, input);
105
+ }
106
+ if (event === 'completed') {
107
+ assertAllowedKeys(input, new Set(['desiredManagedHash']));
108
+ return advanceProcessStageCheckpoint(previous, 'completed', {}, {
109
+ desiredManagedHash: input.desiredManagedHash,
110
+ });
111
+ }
112
+ invalidCheckpoint();
113
+ }
114
+
115
+ function isProcessStageCheckpointRemoteComplete(checkpoint, options = {}) {
116
+ if (!checkpoint) {
117
+ return false;
118
+ }
119
+ validateProcessStageCheckpoint(checkpoint, options);
120
+ return stageIndex(checkpoint.stage) >= stageIndex('published');
121
+ }
122
+
123
+ function advanceProcessStageCheckpoint(previous, stage, partial = {}, options = {}) {
124
+ if (previous !== undefined && previous !== null) {
125
+ validateProcessStageCheckpoint(previous, {
126
+ desiredManagedHash: options.desiredManagedHash,
127
+ });
128
+ }
129
+ assertObject(partial);
130
+ assertAllowedKeys(partial, ADAPTER_PARTIAL_KEYS);
131
+ const expectedIndex = previous ? stageIndex(previous.stage) + 1 : 0;
132
+ if (PROCESS_STAGES[expectedIndex] !== stage) {
133
+ invalidCheckpoint();
134
+ }
135
+
136
+ const next = clonePlain(previous) || {};
137
+ next.stage = stage;
138
+ mergeIdentityProperty(next, partial, 'processCode');
139
+ mergeIdentityProperty(next, partial, 'processId');
140
+ mergeIdentityProperty(next, partial, 'processVersion');
141
+ mergeNodeBindings(next, partial.nodeBindings);
142
+ next.desiredManagedHash = options.desiredManagedHash;
143
+ if (options.observedManagedHash !== undefined) {
144
+ next.observedManagedHash = options.observedManagedHash;
145
+ }
146
+ return validateProcessStageCheckpoint(next, {
147
+ desiredManagedHash: options.desiredManagedHash,
148
+ });
149
+ }
150
+
151
+ function assertAdapterCheckpointStage(stage) {
152
+ if (!ADAPTER_CHECKPOINT_STAGES.has(stage)) {
153
+ invalidCheckpoint();
154
+ }
155
+ }
156
+
157
+ function mergeIdentityProperty(target, source, property) {
158
+ if (source[property] === undefined) {
159
+ return;
160
+ }
161
+ if (target[property] !== undefined && target[property] !== source[property]) {
162
+ invalidCheckpoint();
163
+ }
164
+ target[property] = source[property];
165
+ }
166
+
167
+ function mergeNodeBindings(target, incoming) {
168
+ if (incoming === undefined) {
169
+ return;
170
+ }
171
+ validateNodeBindings(incoming);
172
+ const merged = Object.create(null);
173
+ for (const [key, binding] of Object.entries(target.nodeBindings || {})) {
174
+ merged[key] = clonePlain(binding);
175
+ }
176
+ for (const [key, binding] of Object.entries(incoming)) {
177
+ if (Object.prototype.hasOwnProperty.call(merged, key)) {
178
+ if (
179
+ merged[key].nodeId !== binding.nodeId ||
180
+ merged[key].componentName !== binding.componentName
181
+ ) {
182
+ invalidCheckpoint();
183
+ }
184
+ continue;
185
+ }
186
+ merged[key] = clonePlain(binding);
187
+ }
188
+ target.nodeBindings = merged;
189
+ }
190
+
191
+ function validateNodeBindings(value) {
192
+ assertObject(value);
193
+ for (const [key, binding] of Object.entries(value)) {
194
+ if (!SEMANTIC_KEY_PATTERN.test(key)) {
195
+ invalidCheckpoint();
196
+ }
197
+ assertObject(binding);
198
+ assertAllowedKeys(binding, NODE_BINDING_KEYS);
199
+ if (!isNonEmptyString(binding.nodeId) || !isNonEmptyString(binding.componentName)) {
200
+ invalidCheckpoint();
201
+ }
202
+ }
203
+ }
204
+
205
+ function assertObject(value) {
206
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
207
+ invalidCheckpoint();
208
+ }
209
+ }
210
+
211
+ function assertAllowedKeys(value, allowed) {
212
+ for (const key of Object.keys(value)) {
213
+ if (!allowed.has(key)) {
214
+ invalidCheckpoint();
215
+ }
216
+ }
217
+ }
218
+
219
+ function assertHash(value) {
220
+ if (typeof value !== 'string' || !HASH_PATTERN.test(value)) {
221
+ invalidCheckpoint();
222
+ }
223
+ }
224
+
225
+ function stageIndex(stage) {
226
+ return PROCESS_STAGES.indexOf(stage);
227
+ }
228
+
229
+ function isNonEmptyString(value) {
230
+ return typeof value === 'string' && value.trim().length > 0;
231
+ }
232
+
233
+ function clonePlain(value) {
234
+ return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
235
+ }
236
+
237
+ function invalidCheckpoint() {
238
+ throw schemaError('SCHEMA_APPLY_JOURNAL_INVALID', 'Schema staged apply checkpoint is invalid.');
239
+ }
240
+
241
+ module.exports = {
242
+ advanceProcessStageCheckpoint,
243
+ advanceProcessStagedCheckpoint,
244
+ isProcessStageCheckpointRemoteComplete,
245
+ validateProcessStageCheckpoint,
246
+ };
@@ -16,7 +16,7 @@
16
16
  const fs = require('fs');
17
17
 
18
18
  const { CliError } = require('../core/cli-error');
19
- const { createAuthRef, createYidaClient, isAuthRefReady } = require('../core/yida-client');
19
+ const { createAuthRef, isAuthRefReady } = require('../core/yida-client');
20
20
  const { CHART_COMPONENT_MAP } = require('./constants');
21
21
  const {
22
22
  buildDataSetModelMap,
@@ -32,32 +32,12 @@ const {
32
32
  const { genNodeId, genFieldId, randomId } = require('./constants');
33
33
  const { warn } = require('../core/chalk');
34
34
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
35
+ const { getReportSchema, saveReportSchema } = require('./http');
36
+ const { requireSchemaServerRevision } = require('../schema/server-revision');
35
37
 
36
38
  // ── HTTP:获取已有报表 Schema ─────────────────────────
37
39
  // GET 用 /alibaba/web/ 路径
38
40
 
39
- async function getReportSchema(authRef, appType, reportId) {
40
- return createYidaClient({ authRef }).get(
41
- `/alibaba/web/${appType}/_view/query/formdesign/getFormSchema.json`,
42
- { formUuid: reportId, schemaVersion: 'V5' }
43
- );
44
- }
45
-
46
- // ── HTTP:保存报表 Schema ─────────────────────────────
47
- // SAVE 用 /dingtalk/web/ 路径
48
-
49
- async function saveSchema(authRef, appType, reportId, schema) {
50
- return createYidaClient({ authRef }).postForm(
51
- `/dingtalk/web/${appType}/_view/query/formdesign/saveFormSchema.json`,
52
- {
53
- formUuid: reportId,
54
- content: JSON.stringify(schema),
55
- schemaVersion: 'V5',
56
- importSchema: 'true',
57
- }
58
- );
59
- }
60
-
61
41
  function ensureSession() {
62
42
  const authRef = createAuthRef();
63
43
  if (!isAuthRefReady(authRef)) {
@@ -189,7 +169,7 @@ async function main(args) {
189
169
  // Step 1: 读取登录态
190
170
  warn('\n[Step 1] 读取登录态...');
191
171
  const authRef = ensureSession();
192
- const corpId = authRef.corpId || (authRef.authData && authRef.authData.corp_id) || '';
172
+ const corpId = authRef.corpId || authRef.cookieData.corp_id || '';
193
173
  warn('登录态就绪,域名:', authRef.baseUrl);
194
174
 
195
175
  // Step 2: 读取图表定义
@@ -228,6 +208,7 @@ async function main(args) {
228
208
  const schema = typeof getResult.content === 'string'
229
209
  ? JSON.parse(getResult.content)
230
210
  : getResult.content;
211
+ const serverRevision = requireSchemaServerRevision(getResult);
231
212
 
232
213
  const page = schema.pages[0];
233
214
  const rootContent = page.componentsTree[0].children.find(
@@ -305,7 +286,7 @@ async function main(args) {
305
286
 
306
287
  // Step 6: 保存 Schema
307
288
  warn('\n[Step 5] 保存 Schema...');
308
- const saveResult = await saveSchema(authRef, appType, reportId, schema);
289
+ const saveResult = await saveReportSchema(authRef, appType, reportId, schema, serverRevision);
309
290
 
310
291
  if (!saveResult || !saveResult.success) {
311
292
  const errorMsg = saveResult ? saveResult.errorMsg || '未知错误' : '请求失败';
@@ -352,5 +333,5 @@ module.exports = {
352
333
  parseArgs,
353
334
  readChartsDefinition,
354
335
  getReportSchema,
355
- saveSchema,
336
+ saveSchema: saveReportSchema,
356
337
  };
@@ -2,30 +2,56 @@
2
2
 
3
3
  const { createYidaClient } = require('../core/yida-client');
4
4
  const { buildYidaI18n } = require('../core/yida-i18n');
5
+ const { requireSchemaServerRevision } = require('../schema/server-revision');
5
6
 
6
7
  /**
7
8
  * 调用 saveFormSchemaInfo 创建空白报表
8
9
  */
9
10
  async function createBlankReport(authRef, appType, reportTitle) {
10
- return createYidaClient({ authRef }).postForm(`/dingtalk/web/${appType}/query/formdesign/saveFormSchemaInfo.json`, {
11
+ return createYidaClient({ authRef }).postForm(`/dingtalk/web/${appType}/query/formdesign/saveFormSchemaInfo.json`, auth => ({
12
+ _csrf_token: auth.csrfToken,
11
13
  formType: 'report',
12
14
  title: JSON.stringify(buildYidaI18n(reportTitle, { en_US: reportTitle, ja_JP: reportTitle })),
13
- });
15
+ }));
16
+ }
17
+
18
+ async function getReportSchema(authRef, appType, reportId) {
19
+ const result = await createYidaClient({ authRef }).get(
20
+ `/alibaba/web/${appType}/_view/query/formdesign/getFormSchema.json`,
21
+ { formUuid: reportId, schemaVersion: 'V5' }
22
+ );
23
+ if (result && result.success !== false) {
24
+ requireSchemaServerRevision(result, createRevisionError);
25
+ }
26
+ return result;
14
27
  }
15
28
 
16
29
  /**
17
30
  * 调用 saveFormSchema 保存报表 Schema
18
31
  */
19
- async function saveReportSchema(authRef, appType, reportId, schema) {
20
- return createYidaClient({ authRef }).postForm(`/dingtalk/web/${appType}/_view/query/formdesign/saveFormSchema.json`, {
32
+ async function saveReportSchema(authRef, appType, reportId, schema, serverRevision) {
33
+ const gmtModified = requireSchemaServerRevision(
34
+ { gmtModified: serverRevision },
35
+ createRevisionError
36
+ );
37
+ return createYidaClient({ authRef }).postFormOnce(`/dingtalk/web/${appType}/_view/query/formdesign/saveFormSchema.json`, auth => ({
38
+ _csrf_token: auth.csrfToken,
21
39
  formUuid: reportId,
22
40
  content: JSON.stringify(schema),
23
41
  schemaVersion: 'V5',
24
42
  importSchema: 'true',
25
- });
43
+ gmtModified,
44
+ }));
45
+ }
46
+
47
+ function createRevisionError() {
48
+ const error = new Error('Report Schema revision is missing or invalid.');
49
+ error.code = 'REPORT_SCHEMA_REVISION_INVALID';
50
+ return error;
26
51
  }
27
52
 
28
53
  module.exports = {
29
54
  createBlankReport,
55
+ getReportSchema,
30
56
  saveReportSchema,
31
57
  };
@@ -16,7 +16,8 @@ const {
16
16
  const { genFieldId } = require('./constants');
17
17
 
18
18
  const { warn } = require('../core/chalk');
19
- const { createBlankReport, saveReportSchema } = require('./http');
19
+ const { createBlankReport, getReportSchema, saveReportSchema } = require('./http');
20
+ const { requireSchemaServerRevision } = require('../schema/server-revision');
20
21
  const { parseOpenOption, withBrowserHandoff } = require('../core/browser-handoff');
21
22
 
22
23
  // ── 参数解析 ──────────────────────────────────────────
@@ -284,6 +285,13 @@ async function main(args) {
284
285
  const reportId = createResult.content.formUuid || createResult.content;
285
286
  warn('报表创建成功,ID:', reportId);
286
287
 
288
+ const shellResult = await getReportSchema(authRef, appType, reportId);
289
+ if (!shellResult || shellResult.success === false) {
290
+ const errorMsg = shellResult ? shellResult.errorMsg || '未知错误' : '请求失败';
291
+ throw new CliError(errorMsg, { code: 'CREATE_REPORT_GET_SCHEMA_FAILED' });
292
+ }
293
+ const serverRevision = requireSchemaServerRevision(shellResult);
294
+
287
295
  // Step 4: 构建报表 Schema
288
296
  warn('\n[Step 4] 构建报表 Schema...');
289
297
  const schema = buildReportSchema(reportTitle, charts, reportId, corpId);
@@ -416,7 +424,7 @@ async function main(args) {
416
424
 
417
425
  // Step 5: 保存报表 Schema
418
426
  warn('\n[Step 5] 保存报表 Schema...');
419
- const saveResult = await saveReportSchema(authRef, appType, reportId, schema);
427
+ const saveResult = await saveReportSchema(authRef, appType, reportId, schema, serverRevision);
420
428
 
421
429
  if (!saveResult || !saveResult.success) {
422
430
  const errorMsg = saveResult
@@ -136,7 +136,12 @@ const DEFAULT_INSIGHTS = [
136
136
  suggestion: '高频批量动作固定在列表底部,选中记录后右侧展示下一步。',
137
137
  },
138
138
  ];
139
- const DEFAULT_DATA_BINDING = { enabled: false, mode: 'seed' };
139
+ const DEFAULT_DATA_BINDING = { enabled: false, mode: 'seed', seedStrategy: 'sample-only' };
140
+ const EMPTY_METRICS = [
141
+ { label: '真实记录', value: '0', hint: '未接入表单数据' },
142
+ { label: '数据来源', value: '未接入', hint: '请在 page-spec.json 配置 dataBinding' },
143
+ { label: '演示记录', value: '0', hint: '演示记录需先写入真实表单' },
144
+ ];
140
145
  const BACKGROUND_IMAGES = {
141
146
  hero: 'https://images.unsplash.com/photo-1566346654781-14e3ef6ee988?auto=format&fit=crop&w=1400&q=80',
142
147
  card: 'https://images.unsplash.com/photo-1759884247160-27b8465544b6?auto=format&fit=crop&w=1200&q=80',
@@ -268,6 +273,11 @@ function isDataBindingEnabled(binding) {
268
273
  return Boolean(binding && binding.enabled && binding.mode && binding.mode !== 'seed');
269
274
  }
270
275
 
276
+ function isSampleSeedPreview(binding) {
277
+ const mode = binding && binding.mode ? binding.mode : 'seed';
278
+ return RESEARCH_LEVEL === 'sample' && !isDataBindingEnabled(binding) && mode === 'seed';
279
+ }
280
+
271
281
  function getCsrfToken() {
272
282
  try {
273
283
  return (window.g_config && (window.g_config._csrf_token || window.g_config.csrfToken)) || '';
@@ -393,7 +403,7 @@ function normalizeBoundRows(rows, binding) {
393
403
  }
394
404
 
395
405
  function useYidaData(binding) {
396
- const [state, setState] = React.useState({ loading: false, error: '', rows: [], totalCount: null });
406
+ const [state, setState] = React.useState(() => ({ loading: isDataBindingEnabled(binding), error: '', rows: [], totalCount: null }));
397
407
  React.useEffect(() => {
398
408
  if (!isDataBindingEnabled(binding)) { return undefined; }
399
409
  const controller = new AbortController();
@@ -446,7 +456,9 @@ function YidaComp() {
446
456
  period: index % 2 === 0 ? 'week' : 'month',
447
457
  })), []);
448
458
  const dataState = useYidaData(DATA_BINDING);
449
- const rows = dataState.rows.length ? dataState.rows : seedRows;
459
+ const usesSeedRows = isSampleSeedPreview(DATA_BINDING);
460
+ const metricItems = usesSeedRows || isDataBindingEnabled(DATA_BINDING) ? METRICS : EMPTY_METRICS;
461
+ const rows = dataState.rows.length ? dataState.rows : (usesSeedRows ? seedRows : []);
450
462
  const filteredRows = useMemo(() => {
451
463
  const kw = (filters.keyword || '').trim().toLowerCase();
452
464
  return rows.filter((row) => {
@@ -547,6 +559,11 @@ function YidaComp() {
547
559
  .oy-list-insight { padding: 12px 14px; margin: 0 0 16px; border-radius: 14px; border: 1px solid color-mix(in srgb, var(--oy-brand), transparent 70%); background: #EFF6FF; }
548
560
  .oy-list-brief { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 10px; }
549
561
  .oy-data-status { margin: 0 0 12px; padding: 10px 12px; border-radius: 8px; border: 1px solid var(--oy-line); background: var(--oy-surface-soft); display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
562
+ .oy-list-empty-state { min-height: 278px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 32px 22px; text-align: center; border: 1px dashed var(--oy-line); border-radius: 14px; background: color-mix(in srgb, var(--oy-brand-soft), #fff 58%); }
563
+ .oy-list-empty-state h4 { margin: 0; color: var(--oy-text); }
564
+ .oy-list-empty-state .ant-typography { max-width: 540px; }
565
+ .oy-list-empty-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; margin-top: 8px; }
566
+ .oy-list-empty-detail { min-height: 360px; display: flex; flex-direction: column; justify-content: center; padding: 8px; }
550
567
  @media (max-width: 960px) {
551
568
  .oy-business-list { padding: 20px; }
552
569
  .oy-list-grid, .oy-list-metrics, .oy-list-filter, .oy-list-hero { grid-template-columns: 1fr; }
@@ -573,7 +590,7 @@ function YidaComp() {
573
590
  </header>
574
591
 
575
592
  <section className="oy-list-metrics">
576
- {METRICS.slice(0, 3).map((item) => (
593
+ {metricItems.slice(0, 3).map((item) => (
577
594
  <div className="oy-list-metric" key={item.label}>
578
595
  <Text>{item.label}</Text>
579
596
  <div className="oy-list-number">{item.value}</div>
@@ -594,15 +611,39 @@ function YidaComp() {
594
611
  {dataState.error || (dataState.loading ? '正在读取真实数据' : '真实数据已接入' + (dataState.totalCount === null ? '' : ':' + dataState.totalCount + ' 条'))}
595
612
  </Text>
596
613
  </div>
597
- ) : null}
598
- <Table
599
- columns={columns}
600
- dataSource={filteredRows}
601
- pagination={false}
602
- size="middle"
603
- rowClassName={(_, index) => index === activeIndex ? 'is-selected-row' : ''}
604
- onRow={(_, index) => ({ onClick: () => setSelected(index || 0) })}
605
- />
614
+ ) : (
615
+ <div className="oy-data-status">
616
+ <Tag color={usesSeedRows ? 'warning' : 'default'}>{usesSeedRows ? 'Sample seed' : '未接数据'}</Tag>
617
+ <Text type="secondary">
618
+ {usesSeedRows ? '当前为 sample/seed 预览数据,未接真实表单。' : '未配置真实表单 dataBinding,当前不显示前端 seed 列表。'}
619
+ </Text>
620
+ </div>
621
+ )}
622
+ {filteredRows.length ? (
623
+ <Table
624
+ columns={columns}
625
+ dataSource={filteredRows}
626
+ pagination={false}
627
+ size="middle"
628
+ rowClassName={(_, index) => index === activeIndex ? 'is-selected-row' : ''}
629
+ onRow={(_, index) => ({ onClick: () => setSelected(index || 0) })}
630
+ />
631
+ ) : (
632
+ <div className="oy-list-empty-state">
633
+ <Title level={4}>{dataState.loading ? '正在读取真实表单数据' : isDataBindingEnabled(DATA_BINDING) ? '暂无真实表单记录' : usesSeedRows ? '暂无匹配的 sample 记录' : '未接入真实表单数据'}</Title>
634
+ <Paragraph type="secondary">
635
+ {isDataBindingEnabled(DATA_BINDING)
636
+ ? '当前数据源已接入宜搭表单。若需要演示内容,请先通过表单数据写入链路创建 demo records,再刷新本页读取。'
637
+ : usesSeedRows
638
+ ? '这是模板原样发布的 sample/seed 数据,仅用于离线预览模板结构。'
639
+ : '完整应用交付页不会用前端 seedRows 冒充业务记录。请在 page-spec.json 写入 dataBinding.mode=form,或先登记真实表单记录。'}
640
+ </Paragraph>
641
+ <div className="oy-list-empty-actions">
642
+ <Button type="primary">{PAGE.primaryCta || '登记记录'}</Button>
643
+ <Button>刷新数据</Button>
644
+ </div>
645
+ </div>
646
+ )}
606
647
  </div>
607
648
  <BulkActionBar actions={INTERACTION_PROFILE.bulkActions} count={filteredRows.length} />
608
649
  </section>
@@ -613,7 +654,15 @@ function YidaComp() {
613
654
  <Text strong>{insight.suggestion}</Text>
614
655
  </div>
615
656
  ) : null}
616
- <DetailPreview active={active} page={PAGE} roadmap={ROADMAP} />
657
+ {filteredRows.length ? (
658
+ <DetailPreview active={active} page={PAGE} roadmap={ROADMAP} />
659
+ ) : (
660
+ <div className="oy-list-empty-detail">
661
+ <Tag color="default">空态入口</Tag>
662
+ <Title level={4} style={{ marginTop: 14 }}>等待真实记录</Title>
663
+ <Paragraph type="secondary">接入表单后,选中列表记录将在这里展示摘要、负责人、状态和处理路径。</Paragraph>
664
+ </div>
665
+ )}
617
666
  </aside>
618
667
  </div>
619
668
  </div>