openyida 2026.7.18 → 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 (200) hide show
  1. package/README.md +37 -2
  2. package/bin/yida.js +113 -18
  3. package/docs/capabilities.md +202 -0
  4. package/lib/agent-center/agent-center.js +2 -3
  5. package/lib/app/app-list.js +3 -4
  6. package/lib/app/build-page.js +13 -6
  7. package/lib/app/check-page.js +16 -6
  8. package/lib/app/compile.js +17 -7
  9. package/lib/app/create-app.js +14 -2
  10. package/lib/app/create-form/api-path.js +25 -0
  11. package/lib/app/create-form/args.js +13 -0
  12. package/lib/app/create-form/commands.js +4 -0
  13. package/lib/app/create-form/definition-reader.js +23 -23
  14. package/lib/app/create-form/field-normalizers.js +26 -7
  15. package/lib/app/create-form/schema-patch.js +4 -1
  16. package/lib/app/create-form.js +155 -68
  17. package/lib/app/create-page.js +47 -10
  18. package/lib/app/generate-page.js +20 -1
  19. package/lib/app/get-form-config.js +2 -1
  20. package/lib/app/get-schema.js +67 -15
  21. package/lib/app/import-app.js +94 -42
  22. package/lib/app/list-forms.js +4 -8
  23. package/lib/app/page-ir.js +140 -0
  24. package/lib/app/publish.js +289 -556
  25. package/lib/app/schema-field-resolution.js +370 -0
  26. package/lib/app/services/app-reader.js +85 -0
  27. package/lib/app/services/app-service.js +235 -0
  28. package/lib/app/services/canvas-page-compiler.js +91 -0
  29. package/lib/app/services/canvas-page-schema-builder.js +149 -0
  30. package/lib/app/services/field-bindings.js +68 -0
  31. package/lib/app/services/form-compiler.js +1922 -0
  32. package/lib/app/services/form-mode-service.js +182 -0
  33. package/lib/app/services/form-schema-patcher.js +333 -0
  34. package/lib/app/services/form-schema-reader.js +59 -0
  35. package/lib/app/services/form-service.js +379 -0
  36. package/lib/app/services/form-validation.js +562 -0
  37. package/lib/app/services/native-page-compiler.js +92 -0
  38. package/lib/app/services/native-page-schema-builder.js +428 -0
  39. package/lib/app/services/page-resource-service.js +185 -0
  40. package/lib/app/update-app.js +22 -9
  41. package/lib/app/update-form-config.js +4 -2
  42. package/lib/asset/asset-cmd.js +19 -6
  43. package/lib/auth/oauth-loopback.js +376 -15
  44. package/lib/auth/token-auth.js +34 -3
  45. package/lib/auth/token-store.js +22 -4
  46. package/lib/basic-info/basic-info.js +2 -1
  47. package/lib/bridge/bridge.js +43 -18
  48. package/lib/core/agent-capabilities.js +5 -3
  49. package/lib/core/command-errors.js +38 -0
  50. package/lib/core/command-manifest.js +59 -1
  51. package/lib/core/copy.js +12 -0
  52. package/lib/core/doctor.js +15 -4
  53. package/lib/core/env-cmd.js +1 -1
  54. package/lib/core/env.js +7 -6
  55. package/lib/core/i18n.js +66 -8
  56. package/lib/core/legacy-schema-guard.js +408 -0
  57. package/lib/core/locales/en.js +38 -46
  58. package/lib/core/locales/zh.js +30 -46
  59. package/lib/core/sample.js +12 -2
  60. package/lib/core/utils.js +334 -66
  61. package/lib/core/yida-client.js +108 -5
  62. package/lib/corp-efficiency/corp-efficiency.js +2 -2
  63. package/lib/corp-manager/corp-manager.js +3 -3
  64. package/lib/formula/evaluate.js +11 -68
  65. package/lib/formula/field-refs.js +83 -0
  66. package/lib/i18n-management/i18n-management.js +2 -3
  67. package/lib/process/configure-process.js +44 -1992
  68. package/lib/process/create-process.js +54 -87
  69. package/lib/process/preview-process.js +20 -8
  70. package/lib/process/services/process-compiler.js +2056 -0
  71. package/lib/process/services/process-reader.js +232 -0
  72. package/lib/process/services/process-resource-service.js +571 -0
  73. package/lib/process/services/process-service.js +122 -0
  74. package/lib/process/services/process-stage-checkpoint.js +246 -0
  75. package/lib/report/append.js +7 -26
  76. package/lib/report/http.js +31 -5
  77. package/lib/report/index.js +10 -2
  78. package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
  79. package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
  80. package/lib/samples/yida-canvas-custom-page/dashboard-starter.canvas.jsx +5 -2
  81. package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
  82. package/lib/samples/yida-canvas-custom-page/portal-shell-home.canvas.jsx +2 -2
  83. package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +206 -10
  84. package/lib/samples/yida-custom-page/data-management.oyd.jsx +14 -3
  85. package/lib/samples/yida-custom-page/design-tokens.js +2 -2
  86. package/lib/samples/yida-custom-page/detail-profile.oyd.jsx +1 -1
  87. package/lib/samples/yida-custom-page/portal-shell-home.oyd.jsx +7 -2
  88. package/lib/samples/yida-custom-page/split-pane-detail.oyd.jsx +2 -1
  89. package/lib/schema/adapters/app-adapter.js +210 -0
  90. package/lib/schema/adapters/form-adapter.js +1362 -0
  91. package/lib/schema/adapters/keys.js +19 -0
  92. package/lib/schema/adapters/page-adapter.js +566 -0
  93. package/lib/schema/adapters/process-adapter.js +519 -0
  94. package/lib/schema/applier.js +1878 -0
  95. package/lib/schema/apply-store.js +1083 -0
  96. package/lib/schema/command.js +645 -0
  97. package/lib/schema/dependency-graph.js +83 -0
  98. package/lib/schema/errors.js +481 -0
  99. package/lib/schema/hash.js +9 -0
  100. package/lib/schema/manifest-limits.js +177 -0
  101. package/lib/schema/manifest-loader.js +309 -0
  102. package/lib/schema/manifest-schema-v1.json +193 -0
  103. package/lib/schema/normalize-manifest.js +281 -0
  104. package/lib/schema/page-canvas-foundation.js +447 -0
  105. package/lib/schema/page-data-source-builder.js +561 -0
  106. package/lib/schema/page-foundation.js +556 -0
  107. package/lib/schema/page-source-loader.js +314 -0
  108. package/lib/schema/planner.js +691 -0
  109. package/lib/schema/remote-dispatch-boundary.js +39 -0
  110. package/lib/schema/remote-missing.js +21 -0
  111. package/lib/schema/remote-reader.js +189 -0
  112. package/lib/schema/resource-registry.js +157 -0
  113. package/lib/schema/server-revision.js +71 -0
  114. package/lib/schema/sort.js +28 -0
  115. package/lib/schema/state-store.js +589 -0
  116. package/package.json +10 -8
  117. package/scripts/check-release-risks.js +254 -0
  118. package/scripts/generate-command-docs.js +6 -1
  119. package/scripts/i18n-baseline.json +11 -11
  120. package/scripts/postinstall.js +8 -12
  121. package/scripts/validate-ci.sh +15 -11
  122. package/scripts/validate-i18n.js +30 -9
  123. package/scripts/validate-package-size.js +5 -2
  124. package/scripts/validate-skills.js +4 -0
  125. package/scripts/validate-structure.js +15 -0
  126. package/yida-skills/SKILL.md +117 -30
  127. package/yida-skills/references/schema-as-code-phase1.md +99 -0
  128. package/yida-skills/references/setup-and-env.md +59 -59
  129. package/yida-skills/references/task-retrospective.md +6 -6
  130. package/yida-skills/skills/yida-app/SKILL.md +78 -17
  131. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
  132. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
  133. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +15 -0
  134. package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
  135. package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
  136. package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
  137. package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
  138. package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
  139. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  140. package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
  141. package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
  142. package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
  143. package/yida-skills/skills/yida-integration/SKILL.md +7 -1
  144. package/yida-skills/skills/yida-login/SKILL.md +50 -99
  145. package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
  146. package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
  147. package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
  148. package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
  149. package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
  150. package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
  151. package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
  152. package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
  153. package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
  154. package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
  155. package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
  156. package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
  157. package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
  158. package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
  159. package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
  160. package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
  161. package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
  162. package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
  163. package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
  164. package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
  165. package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
  166. package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
  167. package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
  168. package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
  169. package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
  170. package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
  171. package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
  172. package/yida-skills/skills/yida-report/SKILL.md +9 -235
  173. package/yida-skills/skills/yida-report/references/schema-builder-details.md +239 -0
  174. package/yida-skills/skills/yida-skill-evaluator/SKILL.md +170 -0
  175. package/yida-skills/skills-index.json +238 -54
  176. package/lib/core/locales/ar.js +0 -834
  177. package/lib/core/locales/de.js +0 -834
  178. package/lib/core/locales/es.js +0 -834
  179. package/lib/core/locales/fr.js +0 -834
  180. package/lib/core/locales/hi.js +0 -834
  181. package/lib/core/locales/ja.js +0 -1227
  182. package/lib/core/locales/ko.js +0 -836
  183. package/lib/core/locales/pt.js +0 -834
  184. package/lib/core/locales/vi.js +0 -834
  185. package/lib/core/locales/zh-HK.js +0 -1263
  186. package/project/pages/src/demo-birthday-game.oyd.jsx +0 -832
  187. package/project/pages/src/demo-chip-insight.oyd.jsx +0 -983
  188. package/project/pages/src/demo-compat-smoke.oyd.jsx +0 -58
  189. package/project/pages/src/demo-crm-batch-entry.oyd.jsx +0 -805
  190. package/project/pages/src/demo-crm-dashboard.oyd.jsx +0 -677
  191. package/project/pages/src/demo-future-vision-2026.oyd.jsx +0 -1102
  192. package/project/pages/src/demo-ppt.oyd.jsx +0 -1192
  193. package/project/pages/src/demo-salary-calculator.oyd.jsx +0 -904
  194. package/project/pages/src/openyida-knowledge-doc.oyd.jsx +0 -1714
  195. package/yida-skills/skills/large-file-write/SKILL.md +0 -93
  196. package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
  197. package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
  198. package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
  199. package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
  200. package/yida-skills/skills/sls-log-workbench/sls-query.js +0 -295
@@ -91,7 +91,13 @@ const DEFAULT_THEME_PROFILE = { followRuntimeTheme: false, name: 'harbor-command
91
91
  const DEFAULT_APP_BLUEPRINT = { shell: 'dashboard' };
92
92
  const DEFAULT_INTERACTION_PROFILE = { primaryAction: '查看详情' };
93
93
  const DEFAULT_INSIGHTS = [{ conclusion: '本周业务整体稳定,建议优先处理高价值待跟进事项。' }];
94
- const DEFAULT_DATA_BINDING = { enabled: false, mode: 'seed' };
94
+ const DEFAULT_DATA_BINDING = { enabled: false, mode: 'seed', seedStrategy: 'sample-only' };
95
+ const EMPTY_METRICS = [
96
+ { label: '真实记录', value: '0', delta: '未接入表单数据' },
97
+ { label: '数据来源', value: '未接入', delta: '请配置 dataBinding' },
98
+ { label: '演示指标', value: '0', delta: '演示记录需先写入表单' },
99
+ { label: '图表状态', value: '空态', delta: '等待真实数据' },
100
+ ];
95
101
  const BACKGROUND_IMAGES = {
96
102
  hero: 'https://images.unsplash.com/photo-1566346654781-14e3ef6ee988?auto=format&fit=crop&w=1400&q=80',
97
103
  };
@@ -215,6 +221,19 @@ function ChartPanel({ title, subtitle, children }) {
215
221
  );
216
222
  }
217
223
 
224
+ function DashboardEmptyState({ title, text }) {
225
+ return (
226
+ <div className="oy-dashboard-empty">
227
+ <Title level={4}>{title}</Title>
228
+ <Paragraph type="secondary">{text}</Paragraph>
229
+ <div className="oy-dashboard-empty-actions">
230
+ <Button type="primary">{PAGE.primaryCta || '登记记录'}</Button>
231
+ <Button>刷新数据</Button>
232
+ </div>
233
+ </div>
234
+ );
235
+ }
236
+
218
237
  function RankList({ data, color }) {
219
238
  return (
220
239
  <div className="oy-rank-list">
@@ -248,6 +267,11 @@ function isDataBindingEnabled(binding) {
248
267
  return Boolean(binding && binding.enabled && binding.mode && binding.mode !== 'seed');
249
268
  }
250
269
 
270
+ function isSampleSeedPreview(binding) {
271
+ const mode = binding && binding.mode ? binding.mode : 'seed';
272
+ return RESEARCH_LEVEL === 'sample' && !isDataBindingEnabled(binding) && mode === 'seed';
273
+ }
274
+
251
275
  function getCsrfToken() {
252
276
  try {
253
277
  return (window.g_config && (window.g_config._csrf_token || window.g_config.csrfToken)) || '';
@@ -341,8 +365,46 @@ function getTotalCount(payload) {
341
365
  return null;
342
366
  }
343
367
 
368
+ function pickField(row, fieldId, fallbackKeys) {
369
+ const data = row.formData || row.data || row;
370
+ if (fieldId && data[fieldId] !== undefined) {
371
+ return data[fieldId];
372
+ }
373
+ for (let i = 0; i < fallbackKeys.length; i++) {
374
+ if (data[fallbackKeys[i]] !== undefined) {
375
+ return data[fallbackKeys[i]];
376
+ }
377
+ }
378
+ return undefined;
379
+ }
380
+
381
+ function parseNumericMetric(value) {
382
+ if (value === null || value === undefined) {
383
+ return null;
384
+ }
385
+ if (typeof value !== 'number' && typeof value !== 'string') {
386
+ return null;
387
+ }
388
+ if (typeof value === 'string' && value.trim() === '') {
389
+ return null;
390
+ }
391
+ const numberValue = Number(value);
392
+ return Number.isFinite(numberValue) ? numberValue : null;
393
+ }
394
+
395
+ function normalizeDashboardRows(rows, binding) {
396
+ const fields = binding.fields || {};
397
+ return rows.map((row, index) => {
398
+ const rawValue = pickField(row, fields.value || fields.amount || fields.count, ['value', 'amount', 'count']);
399
+ return {
400
+ name: pickField(row, fields.name || fields.title || fields.code, ['name', 'title', 'code']) || ('记录 ' + (index + 1)),
401
+ value: parseNumericMetric(rawValue),
402
+ };
403
+ });
404
+ }
405
+
344
406
  function useYidaData(binding) {
345
- const [state, setState] = React.useState({ loading: false, error: '', rows: [], totalCount: null });
407
+ const [state, setState] = React.useState(() => ({ loading: isDataBindingEnabled(binding), error: '', rows: [], totalCount: null }));
346
408
  React.useEffect(() => {
347
409
  if (!isDataBindingEnabled(binding)) { return undefined; }
348
410
  const controller = new AbortController();
@@ -358,7 +420,7 @@ function useYidaData(binding) {
358
420
  if (binding.emptyAsError !== false && totalCount > 0 && rows.length === 0) {
359
421
  throw new Error('接口返回结构未识别:totalCount=' + totalCount + ' 但 rows=0');
360
422
  }
361
- setState({ loading: false, error: '', rows, totalCount });
423
+ setState({ loading: false, error: '', rows: normalizeDashboardRows(rows, binding), totalCount });
362
424
  }).catch((err) => {
363
425
  if (err.name === 'AbortError') { return; }
364
426
  setState({ loading: false, error: err.message || String(err), rows: [], totalCount: null });
@@ -378,7 +440,7 @@ function YidaComp() {
378
440
  const brandSoft = getThemeColor(THEME_PROFILE, 'themeColorSoft', readBrandColor(2, '#F3F5FB'));
379
441
  const palette = parseColorGroup(THEME_PROFILE.palette || [brand, '#14B8A6', '#F97316', '#22C55E', '#A855F7']);
380
442
  const themeVars = buildScopedThemeVars(THEME_SCOPE, THEME_PROFILE);
381
- const trendData = useMemo(() => [
443
+ const seedTrendData = useMemo(() => [
382
444
  { name: '周一', value: 42, target: 36 },
383
445
  { name: '周二', value: 48, target: 39 },
384
446
  { name: '周三', value: 51, target: 44 },
@@ -386,18 +448,73 @@ function YidaComp() {
386
448
  { name: '周五', value: 58, target: 49 },
387
449
  { name: '周六', value: 63, target: 55 },
388
450
  ], []);
389
- const rankData = useMemo(() => FEATURES.slice(0, 5).map((item, index) => ({
451
+ const seedRankData = useMemo(() => FEATURES.slice(0, 5).map((item, index) => ({
390
452
  name: item.title,
391
453
  value: 88 - index * 11,
392
454
  })), []);
393
- const mainMetric = METRICS[0] || { value: '-', label: '核心指标' };
394
455
  const insight = INSIGHTS[0] || null;
395
456
  const primaryAction = INTERACTION_PROFILE.primaryAction || PAGE.primaryCta;
396
457
  const shellLabel = APP_BLUEPRINT.shell || 'single_page';
397
458
  const dataState = useYidaData(DATA_BINDING);
398
- const dataMetric = isDataBindingEnabled(DATA_BINDING) && dataState.totalCount !== null
399
- ? { value: String(dataState.totalCount), label: DATA_BINDING.sourceName || '真实记录' }
400
- : null;
459
+ const usesSeedRows = isSampleSeedPreview(DATA_BINDING);
460
+ const chartRows = isDataBindingEnabled(DATA_BINDING)
461
+ ? dataState.rows.filter((row) => Number.isFinite(row.value))
462
+ : [];
463
+ const hasRowsWithoutChartValue = isDataBindingEnabled(DATA_BINDING)
464
+ && !dataState.loading
465
+ && !dataState.error
466
+ && dataState.rows.length > 0
467
+ && chartRows.length === 0;
468
+ const trendData = usesSeedRows
469
+ ? seedTrendData
470
+ : chartRows.length
471
+ ? chartRows.slice(0, 6).map((row, index) => ({ name: row.name || '记录 ' + (index + 1), value: row.value, target: row.value }))
472
+ : [];
473
+ const rankData = usesSeedRows
474
+ ? seedRankData
475
+ : chartRows.length
476
+ ? chartRows.slice(0, 5)
477
+ : [];
478
+ const metricItems = usesSeedRows
479
+ ? METRICS
480
+ : isDataBindingEnabled(DATA_BINDING)
481
+ ? [
482
+ { value: dataState.loading ? '--' : String(dataState.totalCount === null ? dataState.rows.length : dataState.totalCount), label: DATA_BINDING.sourceName || '真实记录', delta: dataState.loading ? '正在读取真实表单' : '来自宜搭表单' },
483
+ { value: dataState.error ? '异常' : '已接入', label: '数据状态', delta: dataState.error || 'DataBridge' },
484
+ { value: Object.keys(DATA_BINDING.fields || {}).length + ' 项', label: '字段映射', delta: 'page-spec.json' },
485
+ { value: '0', label: '演示指标', delta: '演示记录需先写入表单' },
486
+ ]
487
+ : EMPTY_METRICS;
488
+ const mainMetric = metricItems[0] || { value: '-', label: '核心指标' };
489
+ const hasChartData = trendData.length > 0 && rankData.length > 0;
490
+ const trendEmptyTitle = isDataBindingEnabled(DATA_BINDING)
491
+ ? dataState.error
492
+ ? '真实数据读取异常'
493
+ : dataState.loading
494
+ ? '正在读取真实表单数据'
495
+ : hasRowsWithoutChartValue
496
+ ? '缺少数值字段映射'
497
+ : '暂无真实趋势数据'
498
+ : '未接入真实表单数据';
499
+ const trendEmptyText = isDataBindingEnabled(DATA_BINDING)
500
+ ? hasRowsWithoutChartValue
501
+ ? '已读取真实表单记录,但字段映射中没有可解析的数值字段,趋势图暂不绘制。'
502
+ : '当前数据源已接入宜搭表单。若需要演示内容,请先通过表单数据写入链路创建 demo records,再刷新本页读取。'
503
+ : '完整应用交付页不会用前端静态趋势冒充业务数据,请在 page-spec.json 写入 dataBinding.mode=form。';
504
+ const rankEmptyTitle = isDataBindingEnabled(DATA_BINDING)
505
+ ? dataState.error
506
+ ? '真实数据读取异常'
507
+ : dataState.loading
508
+ ? '正在读取真实排行数据'
509
+ : hasRowsWithoutChartValue
510
+ ? '暂无可绘图数据'
511
+ : '暂无真实排行记录'
512
+ : '未接入真实排行数据';
513
+ const rankEmptyText = isDataBindingEnabled(DATA_BINDING)
514
+ ? hasRowsWithoutChartValue
515
+ ? 'DataBridge 已读取真实记录,但缺少可用于排行的数值字段映射,页面不会合成前端指标。'
516
+ : 'DataBridge 已启用,页面不会回退到 sample 排行。'
517
+ : '请配置真实表单 dataBinding,或先写入 demo records 后再让 Canvas 读取。';
401
518
 
402
519
  return (
403
520
  <ConfigProvider
@@ -478,6 +595,10 @@ function YidaComp() {
478
595
  .oy-rank-bar { height: 8px; border-radius: 999px; background: color-mix(in srgb, var(--oy-brand) 12%, #fff); overflow: hidden; }
479
596
  .oy-rank-bar span { display: block; height: 100%; border-radius: inherit; }
480
597
  .oy-data-status { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 12px; }
598
+ .oy-dashboard-empty { min-height: 220px; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 8px; text-align: center; padding: 28px 18px; border: 1px dashed color-mix(in srgb, var(--oy-brand) 24%, #D7E4EA); border-radius: 14px; background: color-mix(in srgb, var(--oy-brand-soft), #fff 54%); }
599
+ .oy-dashboard-empty h4 { margin: 0; color: #102A2F; }
600
+ .oy-dashboard-empty .ant-typography { max-width: 560px; }
601
+ .oy-dashboard-empty-actions { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; margin-top: 8px; }
481
602
  @media (max-width: 960px) {
482
603
  .oy-dashboard-overview { padding: 20px; }
483
604
  .oy-dashboard-grid, .oy-dashboard-chart-row, .oy-dashboard-kpi { grid-template-columns: 1fr; }
@@ -511,12 +632,19 @@ function YidaComp() {
511
632
  {dataState.error || (dataState.loading ? '正在读取真实数据' : '真实数据已接入' + (dataState.totalCount === null ? '' : ':' + dataState.totalCount + ' 条'))}
512
633
  </Text>
513
634
  </div>
514
- ) : null}
635
+ ) : (
636
+ <div className="oy-data-status">
637
+ <Tag color={usesSeedRows ? 'warning' : 'default'}>{usesSeedRows ? 'Sample seed' : '未接数据'}</Tag>
638
+ <Text type="secondary">
639
+ {usesSeedRows ? '当前为 sample/seed 预览指标,未接真实表单。' : '未配置真实表单 dataBinding,当前不显示前端静态业务指标。'}
640
+ </Text>
641
+ </div>
642
+ )}
515
643
  </div>
516
- <DataFreshnessBadge researchLevel={RESEARCH_LEVEL} />
644
+ <DataFreshnessBadge researchLevel={usesSeedRows ? 'sample seed' : isDataBindingEnabled(DATA_BINDING) ? 'DataBridge' : 'empty'} />
517
645
  </div>
518
646
  <div className="oy-dashboard-kpi">
519
- {(dataMetric ? [dataMetric].concat(METRICS) : METRICS).slice(0, 4).map((item, index) => (
647
+ {metricItems.slice(0, 4).map((item, index) => (
520
648
  <KpiPrimitive item={item} index={index} brandDeep={brandDeep} key={item.label} />
521
649
  ))}
522
650
  </div>
@@ -537,29 +665,45 @@ function YidaComp() {
537
665
 
538
666
  <div className="oy-dashboard-chart-row">
539
667
  <ChartPanel title="趋势分析" subtitle={`${mainMetric.label}: ${mainMetric.value}`}>
540
- <ResponsiveContainer width="100%" height={220}>
541
- <AreaChart data={trendData}>
542
- <CartesianGrid strokeDasharray="3 3" stroke="color-mix(in srgb, var(--oy-brand) 12%, #E8F0F8)" />
543
- <XAxis dataKey="name" tickLine={false} axisLine={false} />
544
- <YAxis tickLine={false} axisLine={false} />
545
- <Tooltip />
546
- <Area type="monotone" dataKey="target" name="目标" stroke={palette[2]} fill={palette[2]} fillOpacity={0.12} />
547
- <Area type="monotone" dataKey="value" name="实际" stroke={brand} fill={brand} fillOpacity={0.18} />
548
- </AreaChart>
549
- </ResponsiveContainer>
668
+ {hasChartData ? (
669
+ <ResponsiveContainer width="100%" height={220}>
670
+ <AreaChart data={trendData}>
671
+ <CartesianGrid strokeDasharray="3 3" stroke="color-mix(in srgb, var(--oy-brand) 12%, #E8F0F8)" />
672
+ <XAxis dataKey="name" tickLine={false} axisLine={false} />
673
+ <YAxis tickLine={false} axisLine={false} />
674
+ <Tooltip />
675
+ <Area type="monotone" dataKey="target" name="目标" stroke={palette[2]} fill={palette[2]} fillOpacity={0.12} />
676
+ <Area type="monotone" dataKey="value" name="实际" stroke={brand} fill={brand} fillOpacity={0.18} />
677
+ </AreaChart>
678
+ </ResponsiveContainer>
679
+ ) : (
680
+ <DashboardEmptyState
681
+ title={trendEmptyTitle}
682
+ text={trendEmptyText}
683
+ />
684
+ )}
550
685
  </ChartPanel>
551
686
 
552
687
  <ChartPanel title={PAGE.featuresTitle} subtitle={`TOP ${rankData.length}`}>
553
- <ResponsiveContainer width="100%" height={220}>
554
- <BarChart data={rankData} layout="vertical" margin={{ left: 10, right: 16 }}>
555
- <CartesianGrid strokeDasharray="3 3" horizontal={false} stroke="color-mix(in srgb, var(--oy-brand) 12%, #E8F0F8)" />
556
- <XAxis type="number" hide />
557
- <YAxis dataKey="name" type="category" tickLine={false} axisLine={false} width={96} />
558
- <Tooltip />
559
- <Bar dataKey="value" name="贡献度" fill={palette[1] || brand} radius={[0, 6, 6, 0]} />
560
- </BarChart>
561
- </ResponsiveContainer>
562
- <RankList data={rankData} color={palette[1] || brand} />
688
+ {hasChartData ? (
689
+ <>
690
+ <ResponsiveContainer width="100%" height={220}>
691
+ <BarChart data={rankData} layout="vertical" margin={{ left: 10, right: 16 }}>
692
+ <CartesianGrid strokeDasharray="3 3" horizontal={false} stroke="color-mix(in srgb, var(--oy-brand) 12%, #E8F0F8)" />
693
+ <XAxis type="number" hide />
694
+ <YAxis dataKey="name" type="category" tickLine={false} axisLine={false} width={96} />
695
+ <Tooltip />
696
+ <Bar dataKey="value" name="贡献度" fill={palette[1] || brand} radius={[0, 6, 6, 0]} />
697
+ </BarChart>
698
+ </ResponsiveContainer>
699
+ <RankList data={rankData} color={palette[1] || brand} />
700
+ </>
701
+ ) : (
702
+ <DashboardEmptyState
703
+ title={rankEmptyTitle}
704
+ text={rankEmptyText}
705
+ />
706
+ )}
563
707
  </ChartPanel>
564
708
  </div>
565
709
  </div>
@@ -141,6 +141,8 @@ function MetricCard({ item, index }) {
141
141
  );
142
142
  }
143
143
 
144
+ const RISK_LEVEL_TAG_COLOR = { '高': 'error', '中': 'warning', '低': 'processing' };
145
+
144
146
  function YidaComp() {
145
147
  const [range, setRange] = useState('今日');
146
148
  const [updatedAt, setUpdatedAt] = useState('09:30');
@@ -157,7 +159,8 @@ function YidaComp() {
157
159
  { label: '人效指数', value: '86.2', delta: 8, color: SAMPLE_THEME.violet },
158
160
  ], []);
159
161
 
160
- const healthScore = range === '今日' ? '87.4' : range === '本周' ? '91.2' : '88.6';
162
+ const HEALTH_SCORE_MAP = { '今日': '87.4', '本周': '91.2', '本月': '88.6' };
163
+ const healthScore = HEALTH_SCORE_MAP[range] || '87.4';
161
164
 
162
165
  return (
163
166
  <ConfigProvider
@@ -354,7 +357,7 @@ function YidaComp() {
354
357
  <div className="oy-risk-card" style={{ '--risk-color': item.color }} key={item.label}>
355
358
  <div className="oy-risk-top">
356
359
  <span className="oy-risk-title">{item.label}</span>
357
- <Tag color={item.level === '高' ? 'error' : item.level === '中' ? 'warning' : 'processing'}>{item.level}</Tag>
360
+ <Tag color={RISK_LEVEL_TAG_COLOR[item.level] || 'processing'}>{item.level}</Tag>
358
361
  </div>
359
362
  <div className="oy-risk-value">{item.value}</div>
360
363
  </div>
@@ -120,7 +120,13 @@ const DEFAULT_INSIGHTS = [
120
120
  suggestion: '首屏保留字段工具条、横向滚动表格、分组行和批量操作,用户能立刻理解这是数据管理场景。',
121
121
  },
122
122
  ];
123
- const DEFAULT_DATA_BINDING = { enabled: false, mode: 'seed' };
123
+ const DEFAULT_DATA_BINDING = { enabled: false, mode: 'seed', seedStrategy: 'sample-only' };
124
+ const EMPTY_METRICS = [
125
+ { label: '真实记录', value: '0', hint: '未接入表单数据' },
126
+ { label: '数据来源', value: '未接入', hint: '请在 page-spec.json 配置 dataBinding' },
127
+ { label: '字段映射', value: '待配置', hint: '写入 appType/formUuid/fields' },
128
+ { label: '演示记录', value: '0', hint: '演示记录需先写入真实表单' },
129
+ ];
124
130
 
125
131
  const FEATURES = parseTemplateJson('{{FEATURES_JSON}}', DEFAULT_FEATURES);
126
132
  const METRICS = parseTemplateJson('{{METRICS_JSON}}', DEFAULT_METRICS);
@@ -221,6 +227,179 @@ function createRowsFromFeatures(features) {
221
227
  });
222
228
  }
223
229
 
230
+ function isDataBindingEnabled(binding) {
231
+ return Boolean(binding && binding.enabled && binding.mode && binding.mode !== 'seed');
232
+ }
233
+
234
+ function isSampleSeedPreview(binding) {
235
+ const mode = binding && binding.mode ? binding.mode : 'seed';
236
+ return RESEARCH_LEVEL === 'sample' && !isDataBindingEnabled(binding) && mode === 'seed';
237
+ }
238
+
239
+ function getCsrfToken() {
240
+ try {
241
+ return (window.g_config && (window.g_config._csrf_token || window.g_config.csrfToken)) || '';
242
+ } catch (err) {
243
+ return '';
244
+ }
245
+ }
246
+
247
+ function buildDataRequest(binding) {
248
+ if (binding.mode === 'form' && binding.appType && binding.formUuid) {
249
+ const qs = new URLSearchParams({
250
+ formUuid: binding.formUuid,
251
+ appType: binding.appType,
252
+ currentPage: String(binding.pageNumber || 1),
253
+ pageSize: String(binding.pageSize || 50),
254
+ searchFieldJson: JSON.stringify(binding.query || {}),
255
+ }).toString();
256
+ return {
257
+ url: '/dingtalk/web/' + binding.appType + '/v1/form/searchFormDatas.json?' + qs,
258
+ method: 'GET',
259
+ body: {},
260
+ };
261
+ }
262
+ return {
263
+ url: binding.endpoint,
264
+ method: binding.method || 'GET',
265
+ body: binding.body || {},
266
+ };
267
+ }
268
+
269
+ function requestJson(req, signal) {
270
+ const csrfToken = getCsrfToken();
271
+ const headers = { 'Content-Type': 'application/json' };
272
+ if (csrfToken) {
273
+ headers.global_csrf_token = csrfToken;
274
+ }
275
+ return fetch(req.url, {
276
+ method: req.method || 'GET',
277
+ credentials: 'include',
278
+ headers,
279
+ body: req.method === 'GET' ? undefined : JSON.stringify(req.body || {}),
280
+ signal,
281
+ }).then((resp) => {
282
+ if (!resp.ok) {
283
+ throw new Error('HTTP ' + resp.status);
284
+ }
285
+ return resp.json();
286
+ }).then((json) => {
287
+ if (json && json.success === false) {
288
+ throw new Error(json.errorMsg || json.message || 'request failed');
289
+ }
290
+ return json;
291
+ });
292
+ }
293
+
294
+ function unwrapRows(payload) {
295
+ const queue = [payload];
296
+ const seen = [];
297
+ while (queue.length) {
298
+ const item = queue.shift();
299
+ if (!item || seen.indexOf(item) >= 0) { continue; }
300
+ seen.push(item);
301
+ if (Array.isArray(item)) { return item; }
302
+ ['data', 'list', 'values', 'records'].forEach((key) => {
303
+ if (Array.isArray(item[key])) {
304
+ queue.unshift(item[key]);
305
+ }
306
+ });
307
+ ['result', 'content', 'value'].forEach((key) => {
308
+ if (item[key] && typeof item[key] === 'object') {
309
+ queue.push(item[key]);
310
+ }
311
+ });
312
+ }
313
+ return [];
314
+ }
315
+
316
+ function getTotalCount(payload) {
317
+ const queue = [payload];
318
+ const seen = [];
319
+ while (queue.length) {
320
+ const item = queue.shift();
321
+ if (!item || seen.indexOf(item) >= 0) { continue; }
322
+ seen.push(item);
323
+ if (typeof item.totalCount === 'number') { return item.totalCount; }
324
+ if (typeof item.total === 'number') { return item.total; }
325
+ if (typeof item.count === 'number') { return item.count; }
326
+ ['result', 'content', 'data', 'value'].forEach((key) => {
327
+ if (item[key] && typeof item[key] === 'object') {
328
+ queue.push(item[key]);
329
+ }
330
+ });
331
+ }
332
+ return null;
333
+ }
334
+
335
+ function pickField(row, fieldId, fallbackKeys) {
336
+ const data = row.formData || row.data || row;
337
+ if (fieldId && data[fieldId] !== undefined) {
338
+ return data[fieldId];
339
+ }
340
+ for (let i = 0; i < fallbackKeys.length; i++) {
341
+ if (data[fallbackKeys[i]] !== undefined) {
342
+ return data[fallbackKeys[i]];
343
+ }
344
+ }
345
+ return '';
346
+ }
347
+
348
+ function normalizeTags(value) {
349
+ if (Array.isArray(value)) {
350
+ return value.map((item) => String(item)).filter(Boolean).slice(0, 4);
351
+ }
352
+ if (value === undefined || value === null || value === '') {
353
+ return ['真实表单'];
354
+ }
355
+ return String(value).split(/[,,、\s]+/).filter(Boolean).slice(0, 4);
356
+ }
357
+
358
+ function normalizeBoundRows(rows, binding) {
359
+ const fields = binding.fields || {};
360
+ return rows.map((row, index) => {
361
+ const status = pickField(row, fields.progress || fields.status, ['progress', 'status', 'state']) || '待确认';
362
+ return {
363
+ id: row.formInstanceId || row.instanceId || row.id || index + 1,
364
+ group: pickField(row, fields.group || fields.owner, ['group', 'owner', 'creator']) || '真实表单记录',
365
+ task: pickField(row, fields.task || fields.title || fields.code, ['task', 'title', 'name', 'code']) || ('记录 ' + (index + 1)),
366
+ progress: status,
367
+ date: pickField(row, fields.date, ['date', 'gmtCreate', 'createTime']) || '-',
368
+ tags: normalizeTags(pickField(row, fields.tags || fields.tag, ['tags', 'tag', 'category'])),
369
+ priority: pickField(row, fields.priority, ['priority', 'level']) || '-',
370
+ note: pickField(row, fields.note || fields.summary, ['note', 'summary', 'description']) || '',
371
+ rich: pickField(row, fields.rich || fields.content, ['rich', 'content', 'remark']) || '',
372
+ };
373
+ });
374
+ }
375
+
376
+ function useYidaData(binding) {
377
+ const [state, setState] = React.useState(() => ({ loading: isDataBindingEnabled(binding), error: '', rows: [], totalCount: null }));
378
+ React.useEffect(() => {
379
+ if (!isDataBindingEnabled(binding)) { return undefined; }
380
+ const controller = new AbortController();
381
+ const req = buildDataRequest(binding);
382
+ if (!req.url) {
383
+ setState({ loading: false, error: '数据绑定缺少 endpoint 或 appType/formUuid', rows: [], totalCount: null });
384
+ return undefined;
385
+ }
386
+ setState({ loading: true, error: '', rows: [], totalCount: null });
387
+ requestJson(req, controller.signal).then((json) => {
388
+ const rows = unwrapRows(json);
389
+ const totalCount = getTotalCount(json);
390
+ if (binding.emptyAsError !== false && totalCount > 0 && rows.length === 0) {
391
+ throw new Error('接口返回结构未识别:totalCount=' + totalCount + ' 但 rows=0');
392
+ }
393
+ setState({ loading: false, error: '', rows: normalizeBoundRows(rows, binding), totalCount });
394
+ }).catch((err) => {
395
+ if (err.name === 'AbortError') { return; }
396
+ setState({ loading: false, error: err.message || String(err), rows: [], totalCount: null });
397
+ });
398
+ return () => controller.abort();
399
+ }, []);
400
+ return state;
401
+ }
402
+
224
403
  function Icon({ name }) {
225
404
  const paths = {
226
405
  table: 'M3 5h18v14H3z M3 9h18 M8 5v14',
@@ -390,7 +569,12 @@ function MetricStrip({ metrics }) {
390
569
  );
391
570
  }
392
571
 
393
- function Sidebar({ roadmap, insight, dataBinding }) {
572
+ function Sidebar({ roadmap, insight, dataBinding, usesSeedRows }) {
573
+ const dataText = dataBinding && dataBinding.enabled
574
+ ? '已配置真实数据源,页面通过 DataBridge 读取宜搭表单。'
575
+ : usesSeedRows
576
+ ? '当前为 sample/seed 预览数据,未接真实表单。'
577
+ : '未接入真实表单 dataBinding,交付态不显示前端 seed 记录。';
394
578
  return (
395
579
  <aside className="oy-data-sidebar">
396
580
  <div className="oy-side-block">
@@ -418,7 +602,7 @@ function Sidebar({ roadmap, insight, dataBinding }) {
418
602
  <div className="oy-side-block is-insight">
419
603
  <Tag color="cyan">{ARCHETYPE}</Tag>
420
604
  <p>{insight.suggestion || PAGE.ctaText}</p>
421
- {dataBinding && dataBinding.enabled ? <Text type="secondary">已配置真实数据源</Text> : <Text type="secondary">当前使用演示数据,可通过 dataBinding 接入宜搭表单。</Text>}
605
+ <Text type="secondary">{dataText}</Text>
422
606
  </div>
423
607
  </aside>
424
608
  );
@@ -430,8 +614,21 @@ const DIMENSION_LABEL = { group: '单选', progress: '状态', date: '日期', p
430
614
  function YidaComp() {
431
615
  const [activeView, setActiveView] = useState((APP_BLUEPRINT.views && APP_BLUEPRINT.views[0]) || '全部数据');
432
616
  const seedRows = useMemo(() => createRowsFromFeatures(FEATURES), []);
433
- const [rows, setRows] = useState(seedRows);
434
- const [selected, setSelected] = useState([1, 2]);
617
+ const usesSeedRows = isSampleSeedPreview(DATA_BINDING);
618
+ const dataState = useYidaData(DATA_BINDING);
619
+ const [localRows, setLocalRows] = useState(() => usesSeedRows ? seedRows : []);
620
+ const rows = isDataBindingEnabled(DATA_BINDING) ? dataState.rows : localRows;
621
+ const metricItems = usesSeedRows
622
+ ? METRICS
623
+ : isDataBindingEnabled(DATA_BINDING)
624
+ ? [
625
+ { label: DATA_BINDING.sourceName || '真实记录', value: dataState.loading ? '--' : String(dataState.totalCount === null ? rows.length : dataState.totalCount), hint: dataState.loading ? '正在读取真实表单' : '来自宜搭表单' },
626
+ { label: '数据状态', value: dataState.error ? '异常' : '已接入', hint: dataState.error || 'DataBridge' },
627
+ { label: '字段映射', value: Object.keys(DATA_BINDING.fields || {}).length + ' 项', hint: 'page-spec.json' },
628
+ { label: '演示记录', value: '0', hint: '演示记录需先写入表单' },
629
+ ]
630
+ : EMPTY_METRICS;
631
+ const [selected, setSelected] = useState(() => usesSeedRows ? [1, 2] : []);
435
632
  const [keyword, setKeyword] = useState('');
436
633
  const [statusFilter, setStatusFilter] = useState('all');
437
634
  const [sortDir, setSortDir] = useState('none');
@@ -509,10 +706,14 @@ function YidaComp() {
509
706
  setCollapsed((cur) => cur.indexOf(key) >= 0 ? cur.filter((item) => item !== key) : cur.concat(key));
510
707
  }
511
708
  function addRecord() {
512
- const nextId = rows.reduce((max, row) => Math.max(max, row.id), 0) + 1;
513
- const groupName = (rows[0] && rows[0].group) || '20260717-研发治理';
709
+ if (!usesSeedRows) {
710
+ notify(isDataBindingEnabled(DATA_BINDING) ? '请通过真实表单登记记录后刷新读取' : '请先在 page-spec.json 接入 dataBinding.mode=form');
711
+ return;
712
+ }
713
+ const nextId = localRows.reduce((max, row) => Math.max(max, Number(row.id) || 0), 0) + 1;
714
+ const groupName = (localRows[0] && localRows[0].group) || '20260717-研发治理';
514
715
  const record = { id: nextId, group: groupName, task: '新任务 ' + nextId, progress: '待确认', date: '2026-07-17', tags: ['新建'], priority: 'P2', note: '待补充', rich: '' };
515
- setRows([record].concat(rows));
716
+ setLocalRows([record].concat(localRows));
516
717
  setSelected(selected.concat(nextId));
517
718
  if (message && message.success) { message.success('已新增一条记录,已自动选中'); }
518
719
  }
@@ -1059,12 +1260,19 @@ function YidaComp() {
1059
1260
  <Tag color="cyan">{activeView}</Tag>
1060
1261
  <Tag>{VISUAL_PROFILE.name}</Tag>
1061
1262
  <Tag>{RESEARCH_LEVEL}</Tag>
1062
- {DATA_BINDING.enabled ? <Tag color="green">真实数据</Tag> : <Tag>演示数据</Tag>}
1263
+ {isDataBindingEnabled(DATA_BINDING) ? <Tag color={dataState.error ? 'error' : dataState.loading ? 'processing' : 'green'}>DataBridge</Tag> : usesSeedRows ? <Tag color="warning">Sample seed</Tag> : <Tag>未接数据</Tag>}
1264
+ <Text type={dataState.error ? 'danger' : 'secondary'}>
1265
+ {isDataBindingEnabled(DATA_BINDING)
1266
+ ? dataState.error || (dataState.loading ? '正在读取真实数据' : '真实数据已接入' + (dataState.totalCount === null ? '' : ':' + dataState.totalCount + ' 条'))
1267
+ : usesSeedRows
1268
+ ? '当前为 sample/seed 预览数据,未接真实表单。'
1269
+ : '未配置真实表单 dataBinding,当前不显示前端 seed 记录。'}
1270
+ </Text>
1063
1271
  </div>
1064
1272
  <Title level={2}>{PAGE.brandName}</Title>
1065
1273
  <p>{PAGE.heroText}</p>
1066
1274
  </div>
1067
- <MetricStrip metrics={METRICS} />
1275
+ <MetricStrip metrics={metricItems} />
1068
1276
  </section>
1069
1277
 
1070
1278
  <section className={sidebarOpen ? 'oy-data-workspace' : 'oy-data-workspace is-full'}>
@@ -1096,16 +1304,22 @@ function YidaComp() {
1096
1304
  </React.Fragment>
1097
1305
  ))}
1098
1306
  {viewRows.length === 0 ? (
1099
- <div className="oy-data-empty">没有符合条件的记录,试试清空搜索或切换筛选</div>
1307
+ <div className="oy-data-empty">
1308
+ {isDataBindingEnabled(DATA_BINDING)
1309
+ ? dataState.loading ? '正在读取真实表单数据' : '暂无真实表单记录。若需要演示内容,请先通过表单数据写入链路创建 demo records,再刷新本页读取。'
1310
+ : usesSeedRows
1311
+ ? '没有符合条件的 sample 记录,试试清空搜索或切换筛选'
1312
+ : '未接入真实表单数据。完整应用交付页不会用前端 seedRows 冒充业务记录,请在 page-spec.json 写入 dataBinding.mode=form。'}
1313
+ </div>
1100
1314
  ) : null}
1101
1315
  <div className="oy-add-row">
1102
- <button type="button" onClick={addRecord}>+ 新增一条记录</button>
1103
- <Text type="secondary">字段、分组和筛选会保持在当前视图中</Text>
1316
+ <button type="button" onClick={addRecord}>+ {usesSeedRows ? '新增一条 sample 记录' : '登记真实表单记录'}</button>
1317
+ <Text type="secondary">{usesSeedRows ? '字段、分组和筛选会保持在当前 sample 视图中' : '请通过表单写入真实记录后刷新读取'}</Text>
1104
1318
  </div>
1105
1319
  </div>
1106
1320
  </div>
1107
1321
 
1108
- {sidebarOpen ? <Sidebar roadmap={ROADMAP} insight={insight} dataBinding={DATA_BINDING} /> : null}
1322
+ {sidebarOpen ? <Sidebar roadmap={ROADMAP} insight={insight} dataBinding={DATA_BINDING} usesSeedRows={usesSeedRows} /> : null}
1109
1323
  </section>
1110
1324
  </div>
1111
1325
  </main>
@@ -8,7 +8,7 @@
8
8
  * @openyida-theme-scope {{OPENYIDA_THEME_SCOPE}}
9
9
  * @openyida-blocks {{OPENYIDA_BLOCKS}}
10
10
  */
11
- import React, { useMemo, useState } from 'react';
11
+ import React, { useMemo, useState, useEffect } from 'react';
12
12
  import { ConfigProvider, Button, Input, Tag, Typography } from 'antd';
13
13
 
14
14
  const { Title, Text, Paragraph } = Typography;
@@ -245,7 +245,7 @@ function RoleBoard({ features }) {
245
245
  }
246
246
 
247
247
  function YidaComp() {
248
- updateShellTheme();
248
+ useEffect(() => { updateShellTheme(); }, []);
249
249
  const navItems = useMemo(() => getNavItems(), []);
250
250
  const [active, setActive] = useState(navItems[0] || PAGE.brandName);
251
251
  const insight = INSIGHTS[0] || { conclusion: PAGE.ctaText, evidence: '', suggestion: '' };