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.
- package/README.md +37 -2
- package/bin/yida.js +113 -18
- package/docs/capabilities.md +202 -0
- package/lib/agent-center/agent-center.js +2 -3
- package/lib/app/app-list.js +3 -4
- package/lib/app/build-page.js +13 -6
- package/lib/app/check-page.js +16 -6
- package/lib/app/compile.js +17 -7
- package/lib/app/create-app.js +14 -2
- package/lib/app/create-form/api-path.js +25 -0
- package/lib/app/create-form/args.js +13 -0
- package/lib/app/create-form/commands.js +4 -0
- package/lib/app/create-form/definition-reader.js +23 -23
- package/lib/app/create-form/field-normalizers.js +26 -7
- package/lib/app/create-form/schema-patch.js +4 -1
- package/lib/app/create-form.js +155 -68
- package/lib/app/create-page.js +47 -10
- package/lib/app/generate-page.js +20 -1
- package/lib/app/get-form-config.js +2 -1
- package/lib/app/get-schema.js +67 -15
- package/lib/app/import-app.js +94 -42
- package/lib/app/list-forms.js +4 -8
- package/lib/app/page-ir.js +140 -0
- package/lib/app/publish.js +289 -556
- package/lib/app/schema-field-resolution.js +370 -0
- package/lib/app/services/app-reader.js +85 -0
- package/lib/app/services/app-service.js +235 -0
- package/lib/app/services/canvas-page-compiler.js +91 -0
- package/lib/app/services/canvas-page-schema-builder.js +149 -0
- package/lib/app/services/field-bindings.js +68 -0
- package/lib/app/services/form-compiler.js +1922 -0
- package/lib/app/services/form-mode-service.js +182 -0
- package/lib/app/services/form-schema-patcher.js +333 -0
- package/lib/app/services/form-schema-reader.js +59 -0
- package/lib/app/services/form-service.js +379 -0
- package/lib/app/services/form-validation.js +562 -0
- package/lib/app/services/native-page-compiler.js +92 -0
- package/lib/app/services/native-page-schema-builder.js +428 -0
- package/lib/app/services/page-resource-service.js +185 -0
- package/lib/app/update-app.js +22 -9
- package/lib/app/update-form-config.js +4 -2
- package/lib/asset/asset-cmd.js +19 -6
- package/lib/auth/oauth-loopback.js +376 -15
- package/lib/auth/token-auth.js +34 -3
- package/lib/auth/token-store.js +22 -4
- package/lib/basic-info/basic-info.js +2 -1
- package/lib/bridge/bridge.js +43 -18
- package/lib/core/agent-capabilities.js +5 -3
- package/lib/core/command-errors.js +38 -0
- package/lib/core/command-manifest.js +59 -1
- package/lib/core/copy.js +12 -0
- package/lib/core/doctor.js +15 -4
- package/lib/core/env-cmd.js +1 -1
- package/lib/core/env.js +7 -6
- package/lib/core/i18n.js +66 -8
- package/lib/core/legacy-schema-guard.js +408 -0
- package/lib/core/locales/en.js +38 -46
- package/lib/core/locales/zh.js +30 -46
- package/lib/core/sample.js +12 -2
- package/lib/core/utils.js +334 -66
- package/lib/core/yida-client.js +108 -5
- package/lib/corp-efficiency/corp-efficiency.js +2 -2
- package/lib/corp-manager/corp-manager.js +3 -3
- package/lib/formula/evaluate.js +11 -68
- package/lib/formula/field-refs.js +83 -0
- package/lib/i18n-management/i18n-management.js +2 -3
- package/lib/process/configure-process.js +44 -1992
- package/lib/process/create-process.js +54 -87
- package/lib/process/preview-process.js +20 -8
- package/lib/process/services/process-compiler.js +2056 -0
- package/lib/process/services/process-reader.js +232 -0
- package/lib/process/services/process-resource-service.js +571 -0
- package/lib/process/services/process-service.js +122 -0
- package/lib/process/services/process-stage-checkpoint.js +246 -0
- package/lib/report/append.js +7 -26
- package/lib/report/http.js +31 -5
- package/lib/report/index.js +10 -2
- package/lib/samples/yida-canvas-custom-page/business-list.canvas.jsx +63 -14
- package/lib/samples/yida-canvas-custom-page/dashboard-overview.canvas.jsx +176 -32
- package/lib/samples/yida-canvas-custom-page/dashboard-starter.canvas.jsx +5 -2
- package/lib/samples/yida-canvas-custom-page/data-management.canvas.jsx +228 -14
- package/lib/samples/yida-canvas-custom-page/portal-shell-home.canvas.jsx +2 -2
- package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +206 -10
- package/lib/samples/yida-custom-page/data-management.oyd.jsx +14 -3
- package/lib/samples/yida-custom-page/design-tokens.js +2 -2
- package/lib/samples/yida-custom-page/detail-profile.oyd.jsx +1 -1
- package/lib/samples/yida-custom-page/portal-shell-home.oyd.jsx +7 -2
- package/lib/samples/yida-custom-page/split-pane-detail.oyd.jsx +2 -1
- package/lib/schema/adapters/app-adapter.js +210 -0
- package/lib/schema/adapters/form-adapter.js +1362 -0
- package/lib/schema/adapters/keys.js +19 -0
- package/lib/schema/adapters/page-adapter.js +566 -0
- package/lib/schema/adapters/process-adapter.js +519 -0
- package/lib/schema/applier.js +1878 -0
- package/lib/schema/apply-store.js +1083 -0
- package/lib/schema/command.js +645 -0
- package/lib/schema/dependency-graph.js +83 -0
- package/lib/schema/errors.js +481 -0
- package/lib/schema/hash.js +9 -0
- package/lib/schema/manifest-limits.js +177 -0
- package/lib/schema/manifest-loader.js +309 -0
- package/lib/schema/manifest-schema-v1.json +193 -0
- package/lib/schema/normalize-manifest.js +281 -0
- package/lib/schema/page-canvas-foundation.js +447 -0
- package/lib/schema/page-data-source-builder.js +561 -0
- package/lib/schema/page-foundation.js +556 -0
- package/lib/schema/page-source-loader.js +314 -0
- package/lib/schema/planner.js +691 -0
- package/lib/schema/remote-dispatch-boundary.js +39 -0
- package/lib/schema/remote-missing.js +21 -0
- package/lib/schema/remote-reader.js +189 -0
- package/lib/schema/resource-registry.js +157 -0
- package/lib/schema/server-revision.js +71 -0
- package/lib/schema/sort.js +28 -0
- package/lib/schema/state-store.js +589 -0
- package/package.json +10 -8
- package/scripts/check-release-risks.js +254 -0
- package/scripts/generate-command-docs.js +6 -1
- package/scripts/i18n-baseline.json +11 -11
- package/scripts/postinstall.js +8 -12
- package/scripts/validate-ci.sh +15 -11
- package/scripts/validate-i18n.js +30 -9
- package/scripts/validate-package-size.js +5 -2
- package/scripts/validate-skills.js +4 -0
- package/scripts/validate-structure.js +15 -0
- package/yida-skills/SKILL.md +117 -30
- package/yida-skills/references/schema-as-code-phase1.md +99 -0
- package/yida-skills/references/setup-and-env.md +59 -59
- package/yida-skills/references/task-retrospective.md +6 -6
- package/yida-skills/skills/yida-app/SKILL.md +78 -17
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +18 -7
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +3 -3
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +15 -0
- package/yida-skills/skills/yida-create-app/SKILL.md +21 -3
- package/yida-skills/skills/yida-create-form-page/SKILL.md +19 -1
- package/yida-skills/skills/yida-create-page/SKILL.md +16 -3
- package/yida-skills/skills/yida-create-process/SKILL.md +100 -4
- package/yida-skills/skills/yida-custom-page/SKILL.md +19 -4
- package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
- package/yida-skills/skills/yida-dashboard/references/pitfalls.md +1 -1
- package/yida-skills/skills/yida-get-schema/SKILL.md +49 -5
- package/yida-skills/skills/yida-i18n/SKILL.md +1 -1
- package/yida-skills/skills/yida-integration/SKILL.md +7 -1
- package/yida-skills/skills/yida-login/SKILL.md +50 -99
- package/yida-skills/skills/yida-page-config/SKILL.md +7 -1
- package/yida-skills/skills/yida-page-uiux/references/app/blueprint.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/app/navigation-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/app/role-journey.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/app-blueprint.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/chart-contracts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/component-contracts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/layout-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/dashboard/theme-recipes.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/object-narrative.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/section-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/detail/timeline-and-related.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/assets-workflow.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/industry-playbooks.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/research-levels.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/landing/section-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/drawer-detail.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/empty-loading-error.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/filter-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/list/table-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/scenes/screen.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/entry-patterns.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/portal-layouts.md +0 -1
- package/yida-skills/skills/yida-page-uiux/references/workbench/task-feed.md +0 -1
- package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +1 -1
- package/yida-skills/skills/yida-process-rule/SKILL.md +35 -1
- package/yida-skills/skills/yida-publish-page/SKILL.md +46 -4
- package/yida-skills/skills/yida-report/SKILL.md +9 -235
- package/yida-skills/skills/yida-report/references/schema-builder-details.md +239 -0
- package/yida-skills/skills/yida-skill-evaluator/SKILL.md +170 -0
- package/yida-skills/skills-index.json +238 -54
- package/lib/core/locales/ar.js +0 -834
- package/lib/core/locales/de.js +0 -834
- package/lib/core/locales/es.js +0 -834
- package/lib/core/locales/fr.js +0 -834
- package/lib/core/locales/hi.js +0 -834
- package/lib/core/locales/ja.js +0 -1227
- package/lib/core/locales/ko.js +0 -836
- package/lib/core/locales/pt.js +0 -834
- package/lib/core/locales/vi.js +0 -834
- package/lib/core/locales/zh-HK.js +0 -1263
- package/project/pages/src/demo-birthday-game.oyd.jsx +0 -832
- package/project/pages/src/demo-chip-insight.oyd.jsx +0 -983
- package/project/pages/src/demo-compat-smoke.oyd.jsx +0 -58
- package/project/pages/src/demo-crm-batch-entry.oyd.jsx +0 -805
- package/project/pages/src/demo-crm-dashboard.oyd.jsx +0 -677
- package/project/pages/src/demo-future-vision-2026.oyd.jsx +0 -1102
- package/project/pages/src/demo-ppt.oyd.jsx +0 -1192
- package/project/pages/src/demo-salary-calculator.oyd.jsx +0 -904
- package/project/pages/src/openyida-knowledge-doc.oyd.jsx +0 -1714
- package/yida-skills/skills/large-file-write/SKILL.md +0 -93
- package/yida-skills/skills/large-file-write/references/write-patterns.md +0 -147
- package/yida-skills/skills/large-file-write/scripts/write.js +0 -157
- package/yida-skills/skills/sls-log-workbench/SKILL.md +0 -134
- package/yida-skills/skills/sls-log-workbench/references/workbench-reference.md +0 -130
- 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:
|
|
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
|
|
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
|
|
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
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
) :
|
|
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={
|
|
644
|
+
<DataFreshnessBadge researchLevel={usesSeedRows ? 'sample seed' : isDataBindingEnabled(DATA_BINDING) ? 'DataBridge' : 'empty'} />
|
|
517
645
|
</div>
|
|
518
646
|
<div className="oy-dashboard-kpi">
|
|
519
|
-
{
|
|
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
|
-
|
|
541
|
-
<
|
|
542
|
-
<
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
<
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
434
|
-
const
|
|
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
|
-
|
|
513
|
-
|
|
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
|
-
|
|
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.
|
|
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={
|
|
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"
|
|
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}>+
|
|
1103
|
-
<Text type="secondary"
|
|
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: '' };
|