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
@@ -84,7 +84,7 @@ const DEFAULT_ROADMAP = [
84
84
  { stage: '03', title: '推进处理', text: '完成分派、备注或状态更新。' },
85
85
  ];
86
86
  const DEFAULT_THEME_PROFILE = { followRuntimeTheme: false, name: 'amber-client-file', themeColor: '#B7791F', themeColorDeep: '#7C4A03', themeColorSoft: '#FFF8E7', themeColorTint: 'rgba(183, 121, 31, 0.18)', palette: ['#B7791F', '#0F9F8E', '#E11D48', '#2563EB', '#F97316'] };
87
- const DEFAULT_DATA_BINDING = { enabled: false, mode: 'seed' };
87
+ const DEFAULT_DATA_BINDING = { enabled: false, mode: 'seed', seedStrategy: 'sample-only' };
88
88
  const DEFAULT_INSIGHTS = [{ conclusion: '双栏详情页适合高频查阅和逐条处理。' }];
89
89
  const BACKGROUND_IMAGES = {
90
90
  detail: 'https://images.unsplash.com/photo-1771147372627-7fffe86cf00b?auto=format&fit=crop&w=1400&q=80',
@@ -98,6 +98,7 @@ const THEME_PROFILE = parseTemplateJson('{{OPENYIDA_THEME_PROFILE_JSON}}', DEFAU
98
98
  const THEME_SCOPE = withFallback('{{OPENYIDA_THEME_SCOPE}}', 'page');
99
99
  const DATA_BINDING = parseTemplateJson('{{OPENYIDA_DATA_BINDING_JSON}}', DEFAULT_DATA_BINDING);
100
100
  const INSIGHTS = parseTemplateJson('{{OPENYIDA_INSIGHTS_JSON}}', DEFAULT_INSIGHTS);
101
+ const RESEARCH_LEVEL = withFallback('{{OPENYIDA_RESEARCH_LEVEL}}', 'sample');
101
102
 
102
103
  function getThemeColor(fallback) {
103
104
  if (typeof window === 'undefined' || !window.getComputedStyle) {
@@ -152,6 +153,163 @@ function getSeedRows() {
152
153
  ];
153
154
  }
154
155
 
156
+ function isDataBindingEnabled(binding) {
157
+ return Boolean(binding && binding.enabled && binding.mode && binding.mode !== 'seed');
158
+ }
159
+
160
+ function isSampleSeedPreview(binding) {
161
+ const mode = binding && binding.mode ? binding.mode : 'seed';
162
+ return RESEARCH_LEVEL === 'sample' && !isDataBindingEnabled(binding) && mode === 'seed';
163
+ }
164
+
165
+ function getCsrfToken() {
166
+ try {
167
+ return (window.g_config && (window.g_config._csrf_token || window.g_config.csrfToken)) || '';
168
+ } catch (err) {
169
+ return '';
170
+ }
171
+ }
172
+
173
+ function buildDataRequest(binding) {
174
+ if (binding.mode === 'form' && binding.appType && binding.formUuid) {
175
+ const qs = new URLSearchParams({
176
+ formUuid: binding.formUuid,
177
+ appType: binding.appType,
178
+ currentPage: String(binding.pageNumber || 1),
179
+ pageSize: String(binding.pageSize || 20),
180
+ searchFieldJson: JSON.stringify(binding.query || {}),
181
+ }).toString();
182
+ return {
183
+ url: '/dingtalk/web/' + binding.appType + '/v1/form/searchFormDatas.json?' + qs,
184
+ method: 'GET',
185
+ body: {},
186
+ };
187
+ }
188
+ return {
189
+ url: binding.endpoint,
190
+ method: binding.method || 'GET',
191
+ body: binding.body || {},
192
+ };
193
+ }
194
+
195
+ function requestJson(req, signal) {
196
+ const csrfToken = getCsrfToken();
197
+ const headers = { 'Content-Type': 'application/json' };
198
+ if (csrfToken) {
199
+ headers.global_csrf_token = csrfToken;
200
+ }
201
+ return fetch(req.url, {
202
+ method: req.method || 'GET',
203
+ credentials: 'include',
204
+ headers,
205
+ body: req.method === 'GET' ? undefined : JSON.stringify(req.body || {}),
206
+ signal,
207
+ }).then((resp) => {
208
+ if (!resp.ok) {
209
+ throw new Error('HTTP ' + resp.status);
210
+ }
211
+ return resp.json();
212
+ }).then((json) => {
213
+ if (json && json.success === false) {
214
+ throw new Error(json.errorMsg || json.message || 'request failed');
215
+ }
216
+ return json;
217
+ });
218
+ }
219
+
220
+ function unwrapRows(payload) {
221
+ const queue = [payload];
222
+ const seen = [];
223
+ while (queue.length) {
224
+ const item = queue.shift();
225
+ if (!item || seen.indexOf(item) >= 0) { continue; }
226
+ seen.push(item);
227
+ if (Array.isArray(item)) { return item; }
228
+ ['data', 'list', 'values', 'records'].forEach((key) => {
229
+ if (Array.isArray(item[key])) {
230
+ queue.unshift(item[key]);
231
+ }
232
+ });
233
+ ['result', 'content', 'value'].forEach((key) => {
234
+ if (item[key] && typeof item[key] === 'object') {
235
+ queue.push(item[key]);
236
+ }
237
+ });
238
+ }
239
+ return [];
240
+ }
241
+
242
+ function getTotalCount(payload) {
243
+ const queue = [payload];
244
+ const seen = [];
245
+ while (queue.length) {
246
+ const item = queue.shift();
247
+ if (!item || seen.indexOf(item) >= 0) { continue; }
248
+ seen.push(item);
249
+ if (typeof item.totalCount === 'number') { return item.totalCount; }
250
+ if (typeof item.total === 'number') { return item.total; }
251
+ if (typeof item.count === 'number') { return item.count; }
252
+ ['result', 'content', 'data', 'value'].forEach((key) => {
253
+ if (item[key] && typeof item[key] === 'object') {
254
+ queue.push(item[key]);
255
+ }
256
+ });
257
+ }
258
+ return null;
259
+ }
260
+
261
+ function pickField(row, fieldId, fallbackKeys) {
262
+ const data = row.formData || row.data || row;
263
+ if (fieldId && data[fieldId] !== undefined) {
264
+ return data[fieldId];
265
+ }
266
+ for (let i = 0; i < fallbackKeys.length; i++) {
267
+ if (data[fallbackKeys[i]] !== undefined) {
268
+ return data[fallbackKeys[i]];
269
+ }
270
+ }
271
+ return '';
272
+ }
273
+
274
+ function normalizeBoundRows(rows, binding) {
275
+ const fields = binding.fields || {};
276
+ return rows.map((row, index) => ({
277
+ id: row.formInstanceId || row.instanceId || row.id || ('ROW-' + (index + 1)),
278
+ title: pickField(row, fields.title || fields.code, ['title', 'name', 'code']) || ('记录 ' + (index + 1)),
279
+ desc: pickField(row, fields.desc || fields.summary || fields.description, ['desc', 'summary', 'description', 'content']) || '',
280
+ status: pickField(row, fields.status, ['status', 'state', 'progress']) || '待处理',
281
+ owner: pickField(row, fields.owner, ['owner', 'creator', 'dept']) || '未分配',
282
+ amount: pickField(row, fields.amount || fields.value, ['amount', 'value', 'count']) || '-',
283
+ }));
284
+ }
285
+
286
+ function useYidaData(binding) {
287
+ const [state, setState] = React.useState(() => ({ loading: isDataBindingEnabled(binding), error: '', rows: [], totalCount: null }));
288
+ React.useEffect(() => {
289
+ if (!isDataBindingEnabled(binding)) { return undefined; }
290
+ const controller = new AbortController();
291
+ const req = buildDataRequest(binding);
292
+ if (!req.url) {
293
+ setState({ loading: false, error: '数据绑定缺少 endpoint 或 appType/formUuid', rows: [], totalCount: null });
294
+ return undefined;
295
+ }
296
+ setState({ loading: true, error: '', rows: [], totalCount: null });
297
+ requestJson(req, controller.signal).then((json) => {
298
+ const rows = unwrapRows(json);
299
+ const totalCount = getTotalCount(json);
300
+ if (binding.emptyAsError !== false && totalCount > 0 && rows.length === 0) {
301
+ throw new Error('接口返回结构未识别:totalCount=' + totalCount + ' 但 rows=0');
302
+ }
303
+ setState({ loading: false, error: '', rows: normalizeBoundRows(rows, binding), totalCount });
304
+ }).catch((err) => {
305
+ if (err.name === 'AbortError') { return; }
306
+ setState({ loading: false, error: err.message || String(err), rows: [], totalCount: null });
307
+ });
308
+ return () => controller.abort();
309
+ }, []);
310
+ return state;
311
+ }
312
+
155
313
  function getStatusColor(status) {
156
314
  if (/高|异常|风险|超时/.test(status)) {
157
315
  return 'error';
@@ -164,7 +322,7 @@ function getStatusColor(status) {
164
322
 
165
323
  const QUEUE_STATUS = { high: '高优先级', pending: '待跟进' };
166
324
 
167
- function SplitQueue({ rows, selectedId, onSelect, filters, onFilterChange }) {
325
+ function SplitQueue({ rows, selectedId, onSelect, filters, onFilterChange, emptyText }) {
168
326
  return (
169
327
  <aside className="oy-split-queue">
170
328
  <div className="oy-filter-bar">
@@ -181,7 +339,7 @@ function SplitQueue({ rows, selectedId, onSelect, filters, onFilterChange }) {
181
339
  </div>
182
340
  <div className="oy-queue-list">
183
341
  {rows.length === 0 ? (
184
- <div className="oy-queue-empty"><Text>无匹配工单,试试清空搜索或切换状态</Text></div>
342
+ <div className="oy-queue-empty"><Text>{emptyText || '无匹配工单,试试清空搜索或切换状态'}</Text></div>
185
343
  ) : rows.map((row) => (
186
344
  <button
187
345
  type="button"
@@ -203,6 +361,28 @@ function SplitQueue({ rows, selectedId, onSelect, filters, onFilterChange }) {
203
361
 
204
362
  function DetailPane({ item }) {
205
363
  const insight = INSIGHTS[0] || { conclusion: PAGE.ctaText, evidence: '', suggestion: '' };
364
+ if (!item) {
365
+ return (
366
+ <section className="oy-detail-pane oy-empty-detail">
367
+ <div className="oy-detail-hero">
368
+ <Tag color="default">空态入口</Tag>
369
+ <Title level={2}>等待真实记录</Title>
370
+ <Paragraph>未接入真实表单数据时,交付态不会展示前端 seed 详情。请在 page-spec.json 写入 dataBinding.mode=form,或先登记真实表单记录。</Paragraph>
371
+ <div className="oy-detail-actions">
372
+ <Button type="primary">{PAGE.primaryCta}</Button>
373
+ <Button>刷新数据</Button>
374
+ </div>
375
+ </div>
376
+ </section>
377
+ );
378
+ }
379
+ const metricItems = isSampleSeedPreview(DATA_BINDING)
380
+ ? METRICS
381
+ : [
382
+ { label: '当前状态', value: item.status || '-' },
383
+ { label: '负责人', value: item.owner || '-' },
384
+ { label: '金额/数值', value: item.amount || '-' },
385
+ ];
206
386
  return (
207
387
  <section className="oy-detail-pane">
208
388
  <div className="oy-detail-hero">
@@ -215,7 +395,7 @@ function DetailPane({ item }) {
215
395
  </div>
216
396
  </div>
217
397
  <div className="oy-detail-grid">
218
- {METRICS.map((metric) => (
398
+ {metricItems.map((metric) => (
219
399
  <div className="oy-metric-card" key={metric.label}>
220
400
  <Text>{metric.label}</Text>
221
401
  <strong>{metric.value}</strong>
@@ -246,8 +426,11 @@ function DetailPane({ item }) {
246
426
 
247
427
  function YidaComp() {
248
428
  useEffect(() => { updateShellTheme(); }, []);
249
- const rows = useMemo(() => getSeedRows(), []);
250
- const [selectedId, setSelectedId] = useState(rows[0] && rows[0].id);
429
+ const seedRows = useMemo(() => getSeedRows(), []);
430
+ const usesSeedRows = isSampleSeedPreview(DATA_BINDING);
431
+ const dataState = useYidaData(DATA_BINDING);
432
+ const rows = dataState.rows.length ? dataState.rows : (usesSeedRows ? seedRows : []);
433
+ const [selectedId, setSelectedId] = useState(() => usesSeedRows && seedRows[0] ? seedRows[0].id : null);
251
434
  const [queueFilters, setQueueFilters] = useState({ keyword: '', status: 'all' });
252
435
  const handleQueueFilter = (key, value) => setQueueFilters((prev) => Object.assign({}, prev, { [key]: value }));
253
436
  const filteredRows = useMemo(() => {
@@ -262,8 +445,13 @@ function YidaComp() {
262
445
  });
263
446
  }, [rows, queueFilters]);
264
447
  const visibleSelectedId = filteredRows.some((row) => row.id === selectedId) ? selectedId : (filteredRows[0] && filteredRows[0].id);
265
- const selected = rows.find((row) => row.id === visibleSelectedId) || rows[0];
448
+ const selected = rows.find((row) => row.id === visibleSelectedId) || null;
266
449
  const themeVars = buildScopedThemeVars();
450
+ const queueEmptyText = isDataBindingEnabled(DATA_BINDING)
451
+ ? dataState.loading ? '正在读取真实表单数据' : '暂无真实表单记录。若需要演示内容,请先通过表单数据写入链路创建 demo records。'
452
+ : usesSeedRows
453
+ ? '无匹配 sample 记录,试试清空搜索或切换状态'
454
+ : '未接入真实表单数据。完整应用交付页不会用前端 seedRows 冒充业务队列。';
267
455
 
268
456
  return (
269
457
  <ConfigProvider getPopupContainer={(triggerNode) => (triggerNode && triggerNode.parentElement) || document.body}>
@@ -334,14 +522,22 @@ function YidaComp() {
334
522
  <div className="oy-brand-mark">{PAGE.brandInitials}</div>
335
523
  <div>
336
524
  <Tag color="warning">{PAGE.tagline}</Tag>
525
+ {isDataBindingEnabled(DATA_BINDING) ? <Tag color={dataState.error ? 'error' : dataState.loading ? 'processing' : 'green'}>DataBridge</Tag> : usesSeedRows ? <Tag color="warning">Sample seed</Tag> : <Tag>未接数据</Tag>}
337
526
  <h1>{PAGE.brandName}</h1>
338
527
  <p>{PAGE.heroText}</p>
528
+ <p className="oy-data-status">
529
+ {isDataBindingEnabled(DATA_BINDING)
530
+ ? dataState.error || (dataState.loading ? '正在读取真实数据' : '真实数据已接入' + (dataState.totalCount === null ? '' : ':' + dataState.totalCount + ' 条'))
531
+ : usesSeedRows
532
+ ? '当前为 sample/seed 预览数据,未接真实表单。'
533
+ : '未配置真实表单 dataBinding,当前不显示前端 seed 队列。'}
534
+ </p>
339
535
  </div>
340
536
  </div>
341
537
  <Button type="primary">{PAGE.primaryCta}</Button>
342
538
  </header>
343
539
  <section className="oy-split-layout">
344
- <SplitQueue rows={filteredRows} selectedId={visibleSelectedId} onSelect={setSelectedId} filters={queueFilters} onFilterChange={handleQueueFilter} />
540
+ <SplitQueue rows={filteredRows} selectedId={visibleSelectedId} onSelect={setSelectedId} filters={queueFilters} onFilterChange={handleQueueFilter} emptyText={queueEmptyText} />
345
541
  <DetailPane item={selected} />
346
542
  </section>
347
543
  </div>
@@ -0,0 +1,210 @@
1
+ 'use strict';
2
+
3
+ const { validateSemanticKey } = require('./keys');
4
+ const { schemaError } = require('../errors');
5
+ const { createRemoteMissingError } = require('../remote-missing');
6
+ const { readApp } = require('../../app/services/app-reader');
7
+ const { createAppResource, updateAppResource } = require('../../app/services/app-service');
8
+ const { isDeepStrictEqual } = require('util');
9
+
10
+ const appAdapter = {
11
+ resourceType: 'app',
12
+ adapterVersion: 1,
13
+ validate(entry) {
14
+ const app = entry.definition;
15
+ validateSemanticKey(app.key, '/app/key');
16
+ },
17
+
18
+ normalize(entry) {
19
+ this.validate(entry);
20
+ const app = entry.definition;
21
+
22
+ const desired = {
23
+ key: app.key,
24
+ name: app.name,
25
+ };
26
+
27
+ return {
28
+ resourceType: 'app',
29
+ key: app.key,
30
+ desired,
31
+ dependsOn: [],
32
+ dependencySources: {},
33
+ };
34
+ },
35
+
36
+ async readObserved(binding, context = {}) {
37
+ const normalized = normalizeAppBindings(binding);
38
+ if (!normalized.appType) {
39
+ throw schemaError('SCHEMA_OBSERVED_BINDING_MISSING', 'App observed read requires appType binding.', {
40
+ details: { resourceType: 'app' },
41
+ });
42
+ }
43
+ const reader = context.services && context.services.readApp || readApp;
44
+ try {
45
+ return await reader(context, { appType: normalized.appType });
46
+ } catch (error) {
47
+ if (error && error.code === 'APP_READ_NOT_FOUND') {
48
+ throw createRemoteMissingError();
49
+ }
50
+ throw error;
51
+ }
52
+ },
53
+
54
+ projectObserved(observed, binding, context = {}) {
55
+ const normalized = normalizeAppBindings(binding);
56
+ const resource = context.resource || {};
57
+ const desired = resource.desired || {};
58
+ const managed = {
59
+ key: desired.key || resource.key,
60
+ };
61
+ const name = normalizeObservedAppName(observed && (observed.appName || observed.name));
62
+ if (name) {
63
+ managed.name = name;
64
+ }
65
+ return {
66
+ managed,
67
+ bindings: {
68
+ appType: normalized.appType,
69
+ },
70
+ };
71
+ },
72
+
73
+ async create(desired, context = {}) {
74
+ const creator = context.services && context.services.createAppResource || createAppResource;
75
+ const result = await creator(context, {
76
+ appName: desired.name,
77
+ description: desired.name,
78
+ contentLocale: context.contentLocale,
79
+ });
80
+ if (typeof context.checkpointCreateIdentity === 'function') {
81
+ await context.checkpointCreateIdentity(result);
82
+ }
83
+ return result;
84
+ },
85
+
86
+ async resumeCreate(desired, createIdentity, context = {}) {
87
+ const bindings = normalizeAppBindings(createIdentity);
88
+ return { appType: bindings.appType };
89
+ },
90
+
91
+ async update(desired, observed, binding, context = {}) {
92
+ const normalized = normalizeAppBindings(binding);
93
+ const updater = context.services && context.services.updateAppResource || updateAppResource;
94
+ return updater(context, {
95
+ appType: normalized.appType,
96
+ name: desired.name,
97
+ });
98
+ },
99
+
100
+ buildBindings(result, context = {}) {
101
+ const existing = normalizeAppBindings(context.stateResource);
102
+ const appType = result && result.appType || existing.appType;
103
+ if (!appType) {
104
+ throw schemaError('SCHEMA_APPLY_VERIFY_FAILED', 'App operation did not produce app bindings.', {
105
+ details: { resourceType: 'app' },
106
+ });
107
+ }
108
+ return { appType };
109
+ },
110
+
111
+ projectOperationResult(_result, stateResource, context = {}) {
112
+ if (context.operation !== 'create') {
113
+ return null;
114
+ }
115
+ const bindings = normalizeAppBindings(stateResource);
116
+ if (!bindings.appType) {
117
+ throw schemaError('SCHEMA_APPLY_VERIFY_FAILED', 'App operation did not produce app bindings.', {
118
+ details: { resourceType: 'app' },
119
+ });
120
+ }
121
+ return {
122
+ managed: cloneManagedApp(context.resource && context.resource.desired),
123
+ bindings: {
124
+ appType: bindings.appType,
125
+ },
126
+ };
127
+ },
128
+
129
+ verify(projection, context = {}) {
130
+ if (!projection || !isDeepStrictEqual(projection.managed, context.resource && context.resource.desired)) {
131
+ throw schemaError('SCHEMA_APPLY_VERIFY_FAILED', 'App observed projection does not match desired managed state.', {
132
+ details: {
133
+ resourceType: 'app',
134
+ key: context.resource && context.resource.key,
135
+ },
136
+ });
137
+ }
138
+ return projection;
139
+ },
140
+
141
+ validateStateResource(entry) {
142
+ const state = entry.state || {};
143
+ assertAllowedKeys(state.bindings, ['appType'], `${entry.path}/bindings`);
144
+ if (!state.bindings.appType || typeof state.bindings.appType !== 'string') {
145
+ throw schemaError('SCHEMA_STATE_INVALID', 'App state bindings must include appType.', {
146
+ path: `${entry.path}/bindings/appType`,
147
+ });
148
+ }
149
+ if (state.lastApplied !== undefined) {
150
+ validateManagedApp(state.lastApplied, `${entry.path}/lastApplied`);
151
+ }
152
+ },
153
+ };
154
+
155
+ function normalizeAppBindings(binding) {
156
+ const source = binding && binding.bindings ? binding.bindings : binding || {};
157
+ return {
158
+ appType: source.appType || '',
159
+ };
160
+ }
161
+
162
+ function normalizeObservedAppName(value) {
163
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
164
+ return value.zh_CN || value.en_US || Object.keys(value).sort().map(key => value[key]).find(Boolean) || '';
165
+ }
166
+ return value ? String(value) : '';
167
+ }
168
+
169
+ function cloneManagedApp(value) {
170
+ return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
171
+ }
172
+
173
+ function validateManagedApp(value, pointer) {
174
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
175
+ throw schemaError('SCHEMA_STATE_INVALID', 'App lastApplied must be a compact managed object.', {
176
+ path: pointer,
177
+ });
178
+ }
179
+ assertAllowedKeys(value, ['key', 'name'], pointer);
180
+ if (typeof value.key !== 'string' || !value.key) {
181
+ throw schemaError('SCHEMA_STATE_INVALID', 'App lastApplied must include key.', {
182
+ path: `${pointer}/key`,
183
+ });
184
+ }
185
+ if (typeof value.name !== 'string' || !value.name) {
186
+ throw schemaError('SCHEMA_STATE_INVALID', 'App lastApplied must include name.', {
187
+ path: `${pointer}/name`,
188
+ });
189
+ }
190
+ }
191
+
192
+ function assertAllowedKeys(value, allowedKeys, pointer) {
193
+ const allowed = new Set(allowedKeys);
194
+ Object.keys(value || {}).forEach((key) => {
195
+ if (!allowed.has(key)) {
196
+ throw schemaError('SCHEMA_STATE_INVALID', 'State contains an unknown property.', {
197
+ path: `${pointer}/${escapePointer(key)}`,
198
+ details: { property: key },
199
+ });
200
+ }
201
+ });
202
+ }
203
+
204
+ function escapePointer(value) {
205
+ return String(value).replace(/~/g, '~0').replace(/\//g, '~1');
206
+ }
207
+
208
+ module.exports = {
209
+ appAdapter,
210
+ };