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
@@ -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, Select, Tag, Typography } from 'antd';
13
13
 
14
14
  const { Title, Text, Paragraph } = Typography;
@@ -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>
@@ -245,9 +425,12 @@ function DetailPane({ item }) {
245
425
  }
246
426
 
247
427
  function YidaComp() {
248
- updateShellTheme();
249
- const rows = useMemo(() => getSeedRows(), []);
250
- const [selectedId, setSelectedId] = useState(rows[0] && rows[0].id);
428
+ useEffect(() => { updateShellTheme(); }, []);
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>
@@ -75,14 +75,19 @@ export function injectControlReset() {
75
75
  style.id = id;
76
76
  document.head.appendChild(style);
77
77
  }
78
- style.innerHTML = CONTROL_RESET_CSS;
78
+ style.textContent = CONTROL_RESET_CSS;
79
79
  }
80
80
 
81
81
  export function didMount() {
82
82
  this.injectControlReset();
83
83
  }
84
84
 
85
- export function didUnmount() {}
85
+ export function didUnmount() {
86
+ var style = document.getElementById('openyida-asset-control-reset');
87
+ if (style && style.parentNode) {
88
+ style.parentNode.removeChild(style);
89
+ }
90
+ }
86
91
 
87
92
  export function chooseCategory(value) {
88
93
  _customState.category = value;
@@ -166,6 +171,12 @@ export function renderJsx() {
166
171
  return { text: due + ' 天后保养', color: colors.inkSoft, bg: colors.lineSoft };
167
172
  };
168
173
 
174
+ var healthColorOf = function (health) {
175
+ if (health >= 80) { return colors.success; }
176
+ if (health >= 60) { return colors.warning; }
177
+ return colors.danger;
178
+ };
179
+
169
180
  return (
170
181
  <div className="oyd-page oyd-asset-page" style={styles.page}>
171
182
  <div style={{ display: 'none' }}>{this.state && this.state.timestamp}</div>
@@ -229,7 +240,7 @@ export function renderJsx() {
229
240
  {filtered.map((d) => {
230
241
  var st = statusMap[d.status] || statusMap.stopped;
231
242
  var dt = dueTag(d.due);
232
- var healthColor = d.health >= 80 ? colors.success : d.health >= 60 ? colors.warning : colors.danger;
243
+ var healthColor = healthColorOf(d.health);
233
244
  return (
234
245
  <div key={d.id} onClick={(e) => { self.openDevice(d.id); }} style={{ display: isMobile ? 'block' : 'grid', gridTemplateColumns: '2.2fr 1.4fr 1fr 1.1fr 0.9fr', alignItems: 'center', padding: isMobile ? '14px 16px' : '14px 18px', borderTop: '1px solid ' + colors.lineSoft, cursor: 'pointer' }}>
235
246
  <div>
@@ -10,8 +10,8 @@ var colors = {
10
10
  active: '#EDE9FE', // 通用浅色激活/按下底
11
11
  disabled: '#DDD6FE', // 禁用态:浅、去饱和
12
12
  primaryLight: '#EDE9FE', // 主色浅背景:选中行底、标签高亮底
13
- controlSelectedBg: 'var(--oyd-control-selected-bg, rgba(47,111,237,.08))', // native 控件选中浅底
14
- controlInfoBg: 'var(--oyd-control-info-bg, rgba(47,111,237,.08))', // native 提示块浅底
13
+ controlSelectedBg: 'var(--oyd-control-selected-bg, rgba(124,58,237,.08))', // native 控件选中浅底
14
+ controlInfoBg: 'var(--oyd-control-info-bg, rgba(124,58,237,.08))', // native 提示块浅底
15
15
 
16
16
  // 语义色(固定,不随主题变——保证成功/警告/错误语义稳定)
17
17
  success: '#52C41A',
@@ -221,7 +221,7 @@ export function renderJsx() {
221
221
  {HISTORY.map((h, idx) => {
222
222
  var dot = h.tone === 'warn' ? colors.warning : colors.primary;
223
223
  return (
224
- <div key={idx} style={{ position: 'relative', paddingBottom: idx === HISTORY.length - 1 ? 0 : 18 }}>
224
+ <div key={h.date + '-' + h.title} style={{ position: 'relative', paddingBottom: idx === HISTORY.length - 1 ? 0 : 18 }}>
225
225
  <span style={{ position: 'absolute', left: -19, top: 3, width: 10, height: 10, borderRadius: 999, background: '#fff', border: '2.5px solid ' + dot }}></span>
226
226
  <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' }}>
227
227
  <span style={{ fontSize: 14, fontWeight: 700, color: colors.ink }}>{h.title}</span>
@@ -96,14 +96,19 @@ export function injectControlReset() {
96
96
  style.id = id;
97
97
  document.head.appendChild(style);
98
98
  }
99
- style.innerHTML = CONTROL_RESET_CSS;
99
+ style.textContent = CONTROL_RESET_CSS;
100
100
  }
101
101
 
102
102
  export function didMount() {
103
103
  this.injectControlReset();
104
104
  }
105
105
 
106
- export function didUnmount() {}
106
+ export function didUnmount() {
107
+ var style = document.getElementById('openyida-portal-control-reset');
108
+ if (style && style.parentNode) {
109
+ style.parentNode.removeChild(style);
110
+ }
111
+ }
107
112
 
108
113
  export function handleSearchInput(e) {
109
114
  if (_customState._isComposing) { return; }
@@ -139,6 +139,7 @@ export function renderJsx() {
139
139
  normal: { label: '常规', color: colors.inkSoft, bg: colors.chipBg },
140
140
  };
141
141
  var slaColor = { danger: colors.danger, warn: colors.warning, ok: colors.success };
142
+ var slaBg = { danger: colors.dangerBg, warn: colors.warningBg, ok: colors.successBg };
142
143
 
143
144
  var list = self.getVisibleTickets();
144
145
  var current = self.getSelected();
@@ -202,7 +203,7 @@ export function renderJsx() {
202
203
  </div>
203
204
  <div style={{ marginTop: 8, fontSize: 13, color: colors.inkSoft }}>{current.id} · {current.requester} · {current.channel} · 最近更新 {current.updated}</div>
204
205
  </div>
205
- <span style={{ height: 30, padding: '0 12px', borderRadius: 8, background: colors.dangerBg, color: slaColor[current.slaLevel], fontSize: 13, fontWeight: 700, display: 'inline-flex', alignItems: 'center', gap: 6 }}>
206
+ <span style={{ height: 30, padding: '0 12px', borderRadius: 8, background: slaBg[current.slaLevel] || colors.dangerBg, color: slaColor[current.slaLevel], fontSize: 13, fontWeight: 700, display: 'inline-flex', alignItems: 'center', gap: 6 }}>
206
207
  <svg width="14" height="14" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8.5" r="5.5" fill="none" stroke="currentColor" strokeWidth="1.4" /><path d="M8 5.5v3l2 1.2M8 2h0" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" /></svg>
207
208
  SLA {current.sla}
208
209
  </span>
@@ -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
+ };