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.
- package/README.md +14 -0
- package/bin/yida.js +107 -18
- package/lib/app/create-app.js +12 -1
- package/lib/app/create-form/args.js +13 -0
- package/lib/app/create-form.js +154 -67
- package/lib/app/create-page.js +45 -9
- package/lib/app/get-schema.js +65 -14
- package/lib/app/import-app.js +92 -41
- 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 +18 -6
- package/lib/auth/token-auth.js +34 -3
- package/lib/auth/token-store.js +22 -4
- package/lib/bridge/bridge.js +37 -5
- package/lib/core/agent-capabilities.js +5 -3
- package/lib/core/command-manifest.js +40 -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/legacy-schema-guard.js +408 -0
- package/lib/core/locales/en.js +8 -46
- package/lib/core/locales/zh.js +8 -46
- package/lib/core/sample.js +1 -1
- package/lib/core/utils.js +334 -66
- package/lib/core/yida-client.js +104 -3
- package/lib/formula/evaluate.js +8 -66
- package/lib/formula/field-refs.js +83 -0
- package/lib/process/configure-process.js +44 -1992
- package/lib/process/create-process.js +54 -87
- 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/data-management.canvas.jsx +228 -14
- package/lib/samples/yida-canvas-custom-page/split-pane-detail.canvas.jsx +204 -8
- 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 +2 -2
- package/scripts/postinstall.js +8 -12
- package/scripts/validate-package-size.js +1 -1
- package/yida-skills/SKILL.md +114 -29
- 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 +60 -14
- 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 +7 -1
- 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 +7 -1
- package/yida-skills/skills-index.json +14 -46
- 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
|
@@ -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
|
|
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
|
-
{
|
|
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
|
|
250
|
-
const
|
|
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) ||
|
|
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
|
+
};
|