openyida 2026.9.1 → 2026.9.2

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 (85) hide show
  1. package/README.md +6 -7
  2. package/bin/yida.js +1 -7
  3. package/lib/app/canvas-compile.js +7 -0
  4. package/lib/app/create-app.js +142 -9
  5. package/lib/app/create-form.js +6 -22
  6. package/lib/app/custom-theme.js +300 -0
  7. package/lib/app/get-schema.js +10 -9
  8. package/lib/app/services/canvas-page-schema-builder.js +226 -21
  9. package/lib/app/services/form-compiler.js +6 -18
  10. package/lib/app/services/native-page-schema-builder.js +5 -5
  11. package/lib/app/theme-presets.js +1 -1
  12. package/lib/app/update-app.js +225 -9
  13. package/lib/auth/token-store.js +13 -1
  14. package/lib/core/command-manifest.js +6 -16
  15. package/lib/core/locales/en.js +16 -13
  16. package/lib/core/locales/zh.js +16 -14
  17. package/lib/core/query-data.js +215 -23
  18. package/lib/core/sample.js +4 -0
  19. package/lib/report/append.js +3 -1
  20. package/lib/report/contract.js +7 -0
  21. package/lib/report/index.js +94 -5
  22. package/lib/report/inspect.js +32 -3
  23. package/lib/report/runtime-probe.js +248 -0
  24. package/lib/report/url.js +13 -0
  25. package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +411 -0
  26. package/lib/samples/openyida-scaffold/form-fields.json +74 -0
  27. package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +16 -12
  28. package/lib/samples/yida-density/density-switch-page.js +1 -1
  29. package/lib/samples/yida-rechart/trend-combo.canvas.jsx +3 -3
  30. package/package.json +1 -1
  31. package/scripts/postinstall.js +76 -1
  32. package/yida-skills/SKILL.md +14 -12
  33. package/yida-skills/references/official-example-schema-patterns.md +2 -0
  34. package/yida-skills/references/report-field-config-guide.md +10 -28
  35. package/yida-skills/references/task-retrospective.md +5 -5
  36. package/yida-skills/references/yida-api.md +10 -10
  37. package/yida-skills/skills/yida-app/SKILL.md +8 -5
  38. package/yida-skills/skills/yida-app/workflow/step-2-design.md +1 -3
  39. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +5 -4
  40. package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +5 -6
  41. package/yida-skills/skills/yida-app/workflow/step-5-seed-records.md +1 -1
  42. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +14 -7
  43. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +7 -4
  44. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +10 -9
  45. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +1 -1
  46. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +28 -71
  47. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +7 -6
  48. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +28 -13
  49. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +8 -24
  50. package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +6 -6
  51. package/yida-skills/skills/yida-canvas-table-form/SKILL.md +8 -5
  52. package/yida-skills/skills/yida-chart/references/echarts-bindding-guide.md +1 -1
  53. package/yida-skills/skills/yida-connector/examples/operations-search-formdata-v2.json +3 -3
  54. package/yida-skills/skills/yida-connector/examples/operations-search-formdata.json +2 -2
  55. package/yida-skills/skills/yida-create-app/SKILL.md +21 -12
  56. package/yida-skills/skills/yida-create-form-page/SKILL.md +8 -8
  57. package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +2 -3
  58. package/yida-skills/skills/yida-custom-page/SKILL.md +2 -4
  59. package/yida-skills/skills/yida-custom-page/references/coding-guide.md +6 -6
  60. package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
  61. package/yida-skills/skills/yida-dashboard/references/interaction-patterns.md +1 -1
  62. package/yida-skills/skills/yida-data-management/SKILL.md +18 -17
  63. package/yida-skills/skills/yida-data-management/references/data-format-guide.md +2 -2
  64. package/yida-skills/skills/yida-design/SKILL.md +5 -4
  65. package/yida-skills/skills/yida-design/references/page-quality-gates.md +3 -3
  66. package/yida-skills/skills/yida-design/references/style-design-selection.md +5 -5
  67. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +672 -0
  68. package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +3 -3
  69. package/yida-skills/skills/yida-design/references/visual-decision-engine.md +1 -1
  70. package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +14 -14
  71. package/yida-skills/skills/yida-design/workflow/output-design.md +31 -44
  72. package/yida-skills/skills/yida-design/workflow/output-prd.md +9 -9
  73. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +21 -21
  74. package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +1 -1
  75. package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +3 -3
  76. package/yida-skills/skills/yida-form-detail/SKILL.md +26 -69
  77. package/yida-skills/skills/yida-publish-page/SKILL.md +4 -3
  78. package/yida-skills/skills/yida-report/SKILL.md +6 -11
  79. package/yida-skills/skills/yida-report/references/examples.md +11 -18
  80. package/yida-skills/skills-index.json +18 -15
  81. package/lib/app/form-detail-style.js +0 -441
  82. package/lib/app/form-theme-action.js +0 -439
  83. package/yida-skills/skills/yida-canvas-custom-page/references/theme-runtime-helpers.md +0 -177
  84. package/yida-skills/skills/yida-form-detail/references/form-detail-css.md +0 -194
  85. package/yida-skills/skills/yida-form-detail/references/injection-guide.md +0 -94
@@ -0,0 +1,300 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { getAccessToken } = require('../auth/token-auth');
6
+
7
+ const MAX_THEME_FILE_BYTES = 20 * 1024 * 1024;
8
+ const REQUIRED_BRAND_SCALE_TOKENS = Object.freeze([
9
+ '--color-brand1-1',
10
+ '--color-brand1-2',
11
+ '--color-brand1-3',
12
+ '--color-brand1-5',
13
+ '--color-brand1-6',
14
+ '--color-brand1-9',
15
+ '--color-brand1-10',
16
+ ]);
17
+ const UNSAFE_THEME_PATTERNS = [
18
+ { pattern: /<\/?script\b/i, message: '主题 CSS 不能包含 script 标签' },
19
+ { pattern: /<\/?style\b/i, message: '主题 CSS 不能包含 style 标签' },
20
+ { pattern: /@import\b/i, message: '主题 CSS 不能包含 @import' },
21
+ { pattern: /expression\s*\(/i, message: '主题 CSS 不能包含 expression()' },
22
+ { pattern: /behavior\s*:/i, message: '主题 CSS 不能包含 behavior 属性' },
23
+ { pattern: /-moz-binding\s*:/i, message: '主题 CSS 不能包含 -moz-binding' },
24
+ { pattern: /\b(?:javascript|data|vbscript|file|blob)\s*:/i, message: '主题 CSS 不能包含危险资源协议' },
25
+ ];
26
+
27
+ const THEME_COLOR_HEX_PATTERN = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
28
+ const THEME_COLOR_RGB_PATTERN = /^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*(0(?:\.\d+)?|1(?:\.0+)?|\.\d+))?\s*\)$/i;
29
+ const THEME_COLOR_HSL_PATTERN = /^hsla?\(\s*([-+]?(?:\d+(?:\.\d+)?|\.\d+))(?:deg)?\s*,\s*([-+]?(?:\d+(?:\.\d+)?|\.\d+))%\s*,\s*([-+]?(?:\d+(?:\.\d+)?|\.\d+))%(?:\s*,\s*([-+]?(?:\d+(?:\.\d+)?|\.\d+))(%?))?\s*\)$/i;
30
+
31
+ function toHexChannel(value) {
32
+ return Math.max(0, Math.min(255, Math.round(value))).toString(16).padStart(2, '0');
33
+ }
34
+
35
+ function opaqueHex(red, green, blue, alpha = 1) {
36
+ const blend = (channel) => channel * alpha + 255 * (1 - alpha);
37
+ return `#${toHexChannel(blend(red))}${toHexChannel(blend(green))}${toHexChannel(blend(blue))}`.toUpperCase();
38
+ }
39
+
40
+ /**
41
+ * 与 yc-utils normalizeCssColorToHex 保持相同口径:透明色按白色背景合成。
42
+ * appIcon 的 icon%%color 协议只保存不透明 HEX。
43
+ */
44
+ function normalizeCssColorToHex(value) {
45
+ const color = String(value || '').trim();
46
+ const shortHexMatch = color.match(/^#([0-9a-f]{3})$/i);
47
+ if (shortHexMatch) {
48
+ return `#${shortHexMatch[1].split('').map((channel) => channel + channel).join('')}`.toUpperCase();
49
+ }
50
+ if (/^#[0-9a-f]{6}$/i.test(color)) {
51
+ return color.toUpperCase();
52
+ }
53
+
54
+ const rgbMatch = color.match(THEME_COLOR_RGB_PATTERN);
55
+ if (rgbMatch && rgbMatch.slice(1, 4).every((channel) => Number(channel) <= 255)) {
56
+ const alpha = rgbMatch[4] === undefined ? 1 : Number(rgbMatch[4]);
57
+ return opaqueHex(Number(rgbMatch[1]), Number(rgbMatch[2]), Number(rgbMatch[3]), alpha);
58
+ }
59
+
60
+ const hslMatch = color.match(THEME_COLOR_HSL_PATTERN);
61
+ if (!hslMatch) {return '';}
62
+ const saturation = Number(hslMatch[2]);
63
+ const lightness = Number(hslMatch[3]);
64
+ const alpha = hslMatch[4] === undefined
65
+ ? 1
66
+ : Number(hslMatch[4]) / (hslMatch[5] === '%' ? 100 : 1);
67
+ if (saturation < 0 || saturation > 100 || lightness < 0 || lightness > 100 || alpha < 0 || alpha > 1) {
68
+ return '';
69
+ }
70
+
71
+ const hue = ((Number(hslMatch[1]) % 360) + 360) % 360;
72
+ const normalizedSaturation = saturation / 100;
73
+ const normalizedLightness = lightness / 100;
74
+ const chroma = (1 - Math.abs(2 * normalizedLightness - 1)) * normalizedSaturation;
75
+ const hueSection = hue / 60;
76
+ const secondLargest = chroma * (1 - Math.abs((hueSection % 2) - 1));
77
+ const matchValue = normalizedLightness - chroma / 2;
78
+ let red = 0;
79
+ let green = 0;
80
+ let blue = 0;
81
+ if (hueSection < 1) {
82
+ red = chroma;
83
+ green = secondLargest;
84
+ } else if (hueSection < 2) {
85
+ red = secondLargest;
86
+ green = chroma;
87
+ } else if (hueSection < 3) {
88
+ green = chroma;
89
+ blue = secondLargest;
90
+ } else if (hueSection < 4) {
91
+ green = secondLargest;
92
+ blue = chroma;
93
+ } else if (hueSection < 5) {
94
+ red = secondLargest;
95
+ blue = chroma;
96
+ } else {
97
+ red = chroma;
98
+ blue = secondLargest;
99
+ }
100
+ return opaqueHex(
101
+ (red + matchValue) * 255,
102
+ (green + matchValue) * 255,
103
+ (blue + matchValue) * 255,
104
+ alpha
105
+ );
106
+ }
107
+
108
+ function normalizeThemeColor(value) {
109
+ if (!value) {return null;}
110
+ const color = String(value).trim();
111
+ if (THEME_COLOR_HEX_PATTERN.test(color)) {
112
+ return color.toUpperCase();
113
+ }
114
+
115
+ const rgbMatch = color.match(THEME_COLOR_RGB_PATTERN);
116
+ if (rgbMatch && rgbMatch.slice(1, 4).every((channel) => Number(channel) <= 255)) {
117
+ return color;
118
+ }
119
+
120
+ const hslMatch = color.match(THEME_COLOR_HSL_PATTERN);
121
+ if (hslMatch) {
122
+ const saturation = Number(hslMatch[2]);
123
+ const lightness = Number(hslMatch[3]);
124
+ const alpha = hslMatch[4] === undefined
125
+ ? 1
126
+ : Number(hslMatch[4]) / (hslMatch[5] === '%' ? 100 : 1);
127
+ if (saturation >= 0 && saturation <= 100 && lightness >= 0 && lightness <= 100 && alpha >= 0 && alpha <= 1) {
128
+ return color;
129
+ }
130
+ }
131
+
132
+ throw new Error(`Unsupported theme color: ${value}. Use #RGB, #RRGGBB, rgb(a), or hsl(a).`);
133
+ }
134
+
135
+ function extractThemeColor(cssText) {
136
+ const source = String(cssText || '').replace(/\/\*[\s\S]*?\*\//g, '');
137
+ const match = source.match(/(?:^|[;{])\s*--color-brand1-6\s*:\s*([^;}\n]+)/i);
138
+ if (!match) {
139
+ throw new Error('自定义主题文件必须声明 --color-brand1-6,CLI 会用它保存应用 themeColor');
140
+ }
141
+ try {
142
+ return normalizeThemeColor(match[1]);
143
+ } catch (error) {
144
+ throw new Error(`--color-brand1-6 必须是可直接保存的颜色值: ${error.message}`);
145
+ }
146
+ }
147
+
148
+ function validateThemeCssContent(cssText) {
149
+ const source = String(cssText || '');
150
+ const sourceWithoutComments = source.replace(/\/\*[\s\S]*?\*\//g, '');
151
+ for (const rule of UNSAFE_THEME_PATTERNS) {
152
+ if (rule.pattern.test(sourceWithoutComments)) {
153
+ throw new Error(rule.message);
154
+ }
155
+ }
156
+
157
+ const urlPattern = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
158
+ let match = urlPattern.exec(sourceWithoutComments);
159
+ while (match) {
160
+ const value = String(match[2] || '').trim();
161
+ const allowed = /^https:\/\//i.test(value) ||
162
+ (value.startsWith('/') && !value.startsWith('//')) ||
163
+ /^asset\s*\(/i.test(value) ||
164
+ /^var\s*\(/i.test(value);
165
+ if (!allowed) {
166
+ throw new Error(`主题 CSS 包含不安全的 url(): ${value || '(empty)'}`);
167
+ }
168
+ match = urlPattern.exec(sourceWithoutComments);
169
+ }
170
+
171
+ const declaredTokens = new Set();
172
+ const declarationPattern = /(?:^|[;{])\s*(--color-brand1-(?:10|[1-9]))\s*:\s*([^;}\n]+)/gi;
173
+ let declaration = declarationPattern.exec(sourceWithoutComments);
174
+ while (declaration) {
175
+ if (declaration[2].trim()) {
176
+ declaredTokens.add(declaration[1].toLowerCase());
177
+ }
178
+ declaration = declarationPattern.exec(sourceWithoutComments);
179
+ }
180
+ const missingTokens = REQUIRED_BRAND_SCALE_TOKENS.filter((token) => !declaredTokens.has(token));
181
+ if (missingTokens.length > 0) {
182
+ throw new Error(
183
+ `自定义主题文件必须完整声明平台品牌色阶 ${REQUIRED_BRAND_SCALE_TOKENS.join('、')},缺少: ${missingTokens.join(', ')}`
184
+ );
185
+ }
186
+ }
187
+
188
+ function readThemeCssFile(filePath) {
189
+ const resolvedPath = path.resolve(String(filePath || ''));
190
+ if (path.extname(resolvedPath).toLowerCase() !== '.css') {
191
+ throw new Error('自定义主题文件必须是 .css 文件');
192
+ }
193
+ if (/[<>"'`]/.test(path.basename(resolvedPath))) {
194
+ throw new Error('自定义主题文件名包含不安全字符');
195
+ }
196
+ const stat = fs.statSync(resolvedPath);
197
+ if (!stat.isFile()) {
198
+ throw new Error(`自定义主题文件不存在: ${resolvedPath}`);
199
+ }
200
+ if (stat.size <= 0 || stat.size > MAX_THEME_FILE_BYTES) {
201
+ throw new Error('自定义主题文件大小必须在 1 字节到 20 MB 之间');
202
+ }
203
+ const content = fs.readFileSync(resolvedPath);
204
+ const cssText = content.toString('utf8');
205
+ validateThemeCssContent(cssText);
206
+ return {
207
+ path: resolvedPath,
208
+ name: path.basename(resolvedPath),
209
+ size: stat.size,
210
+ content,
211
+ themeColor: extractThemeColor(cssText),
212
+ };
213
+ }
214
+
215
+ function unwrapUploadResponse(response) {
216
+ let current = response;
217
+ for (let depth = 0; depth < 4; depth++) {
218
+ if (!current || typeof current !== 'object') {
219
+ break;
220
+ }
221
+ if (current.success === false) {
222
+ throw new Error(current.errorMsg || current.message || '自定义主题文件上传失败');
223
+ }
224
+ if (current.content && typeof current.content === 'object') {
225
+ if (current.content.url || current.content.downloadUrl) {
226
+ return current.content;
227
+ }
228
+ current = current.content;
229
+ continue;
230
+ }
231
+ break;
232
+ }
233
+ if (current && typeof current === 'object' && (current.url || current.downloadUrl)) {
234
+ return current;
235
+ }
236
+ throw new Error('自定义主题文件上传结果缺少 CSS 地址');
237
+ }
238
+
239
+ function buildCustomThemeStyle(uploadResult) {
240
+ const upload = unwrapUploadResponse(uploadResult);
241
+ return JSON.stringify({
242
+ enabled: true,
243
+ iframePropagation: false,
244
+ cssUrl: upload.downloadUrl || upload.url,
245
+ cssFileName: upload.name || '',
246
+ });
247
+ }
248
+
249
+ async function uploadCustomThemeFile(appType, filePath, authRef, options = {}) {
250
+ const file = readThemeCssFile(filePath);
251
+ const fetchImpl = options.fetchImpl || globalThis.fetch;
252
+ const FormDataImpl = options.FormDataImpl || globalThis.FormData;
253
+ const BlobImpl = options.BlobImpl || globalThis.Blob;
254
+ if (typeof fetchImpl !== 'function' || typeof FormDataImpl !== 'function' || typeof BlobImpl !== 'function') {
255
+ throw new Error('当前 Node.js 环境缺少 fetch/FormData/Blob,请使用 Node.js 18+');
256
+ }
257
+
258
+ const accessToken = await getAccessToken({ projectRoot: authRef && authRef.projectRoot });
259
+ const endpoint = new URL('/query/app/customTheme/upload.json', authRef.baseUrl);
260
+ if (authRef.csrfToken) {
261
+ endpoint.searchParams.set('_csrf_token', authRef.csrfToken);
262
+ }
263
+ const form = new FormDataImpl();
264
+ form.append('appType', appType);
265
+ form.append('uploadInput', new BlobImpl([file.content], { type: 'text/css' }), file.name);
266
+
267
+ const response = await fetchImpl(endpoint, {
268
+ method: 'POST',
269
+ headers: {
270
+ Accept: 'application/json, text/plain, */*',
271
+ Authorization: `Bearer ${accessToken}`,
272
+ Origin: authRef.baseUrl,
273
+ Referer: `${authRef.baseUrl}/`,
274
+ 'x-requested-with': 'XMLHttpRequest',
275
+ },
276
+ body: form,
277
+ });
278
+
279
+ if (response.status === 401 || response.status === 403) {
280
+ return { __needLogin: true, __httpStatus: response.status };
281
+ }
282
+ const payload = await response.json().catch(() => null);
283
+ if (!response.ok) {
284
+ throw new Error((payload && (payload.errorMsg || payload.message)) || `主题文件上传失败: HTTP ${response.status}`);
285
+ }
286
+ return payload;
287
+ }
288
+
289
+ module.exports = {
290
+ MAX_THEME_FILE_BYTES,
291
+ REQUIRED_BRAND_SCALE_TOKENS,
292
+ validateThemeCssContent,
293
+ normalizeThemeColor,
294
+ normalizeCssColorToHex,
295
+ extractThemeColor,
296
+ readThemeCssFile,
297
+ unwrapUploadResponse,
298
+ buildCustomThemeStyle,
299
+ uploadCustomThemeFile,
300
+ };
@@ -24,7 +24,7 @@ const { fetchFormPageList } = require('./form-navigation');
24
24
  const { buildFieldResolution } = require('./schema-field-resolution');
25
25
  const { buildSemanticAnalysis } = require('./schema-semantic-analysis');
26
26
 
27
- // 需要在报表 fieldCode 中加 _value 后缀的字段类型
27
+ // 报表运行时可能暴露 raw fieldId 或 _value;最终值必须由报表查询探针确认。
28
28
  const FIELD_TYPES_NEEDING_VALUE_SUFFIX = new Set([
29
29
  'SelectField',
30
30
  'MultiSelectField',
@@ -44,9 +44,9 @@ const FIELD_COMPONENT_NAMES = new Set([
44
44
  ]);
45
45
 
46
46
  /**
47
- * 从 Schema 中提取字段摘要,列出每个字段的真实 fieldId、组件别名和报表用 reportFieldCode。
47
+ * 从 Schema 中提取字段摘要。表单 Schema 只能提供报表字段候选,不能代替 cube 运行时元数据。
48
48
  * @param {object} schemaResult - getFormSchema API 返回结果
49
- * @returns {Array<{label, componentName, fieldId, alias, reportFieldCode, options}>}
49
+ * @returns {Array<{label, componentName, fieldId, alias, reportFieldCode, reportFieldCodeCandidates, options}>}
50
50
  */
51
51
  function extractFieldSummary(schemaResult) {
52
52
  const fields = [];
@@ -67,9 +67,9 @@ function extractFieldSummary(schemaResult) {
67
67
  ? (typeof labelRaw === 'object' ? (labelRaw.zh_CN || labelRaw.en_US || '') : String(labelRaw))
68
68
  : '';
69
69
  const fieldId = props.fieldId || '';
70
- const reportFieldCode = FIELD_TYPES_NEEDING_VALUE_SUFFIX.has(node.componentName)
71
- ? `${fieldId}_value`
72
- : fieldId;
70
+ const reportFieldCodeCandidates = FIELD_TYPES_NEEDING_VALUE_SUFFIX.has(node.componentName)
71
+ ? [fieldId, `${fieldId}_value`]
72
+ : [fieldId];
73
73
  if (fieldId) {
74
74
  const options = extractOptionSummary(props);
75
75
  const optionSource = getOptionSource(props) || [];
@@ -87,7 +87,8 @@ function extractFieldSummary(schemaResult) {
87
87
  componentName: node.componentName,
88
88
  fieldId,
89
89
  alias: aliasMaps.aliasByFieldId[fieldId] || '',
90
- reportFieldCode,
90
+ reportFieldCode: fieldId,
91
+ reportFieldCodeCandidates,
91
92
  options,
92
93
  optionCount: optionSource.length,
93
94
  optionsTruncated: optionSource.length > options.length,
@@ -512,7 +513,7 @@ function printFieldSummary(result) {
512
513
  return;
513
514
  }
514
515
 
515
- process.stderr.write(`\n ${c.bold}${c.cyan}📋 字段摘要${c.reset} ${c.dim}(报表配置请使用 reportFieldCode)${c.reset}\n`);
516
+ process.stderr.write(`\n ${c.bold}${c.cyan}📋 字段摘要${c.reset} ${c.dim}(报表字段最终以运行时查询探针为准)${c.reset}\n`);
516
517
  process.stderr.write(` ${c.dim}${'─'.repeat(80)}${c.reset}\n`);
517
518
  process.stderr.write(
518
519
  ` ${c.bold}${'label'.padEnd(16)}${'alias'.padEnd(18)}${'componentName'.padEnd(20)}${'fieldId'.padEnd(28)}reportFieldCode${c.reset}\n`
@@ -524,7 +525,7 @@ function printFieldSummary(result) {
524
525
  );
525
526
  }
526
527
  process.stderr.write(` ${c.dim}${'─'.repeat(80)}${c.reset}\n`);
527
- process.stderr.write(` ${c.dim}注:SelectField/EmployeeField 在报表中需加 _value 后缀${c.reset}\n\n`);
528
+ process.stderr.write(` ${c.dim}注:Select/Employee 等字段同时返回 raw 与 _value 候选;create-report 会用真实图表查询验证并在原 reportId 内窄修复${c.reset}\n\n`);
528
529
  }
529
530
 
530
531
  function buildSchemaSummary(appType, formUuid, schemaResult, meta = {}) {
@@ -10,7 +10,61 @@ const {
10
10
  findEmojiInValue,
11
11
  } = require('../../core/no-emoji-detector');
12
12
 
13
- const CANVAS_YIDA_API_BRIDGE_SOURCE = `function openyidaYidaApiBridgeTargets() {
13
+ const CANVAS_YIDA_API_BRIDGE_SOURCE = `var OPENYIDA_YIDA_FORM_API_METHODS = [
14
+ 'saveFormData',
15
+ 'updateFormData',
16
+ 'searchFormDataIds',
17
+ 'getFormComponentDefinationList',
18
+ 'deleteFormData',
19
+ 'getFormDataById',
20
+ 'searchFormDatas'
21
+ ];
22
+ var OPENYIDA_YIDA_PROCESS_API_METHODS = [
23
+ 'startProcessInstance',
24
+ 'updateProcessInstance',
25
+ 'deleteProcessInstance',
26
+ 'getProcessInstances',
27
+ 'getProcessInstanceIds',
28
+ 'getProcessInstanceById'
29
+ ];
30
+ var OPENYIDA_YIDA_FORM_DESIGN_API_METHODS = [
31
+ 'saveFormSchemaInfo',
32
+ 'getFormSchema',
33
+ 'saveFormSchema',
34
+ 'updateFormConfig'
35
+ ];
36
+ var OPENYIDA_YIDA_GENERIC_API_METHODS = [
37
+ 'request',
38
+ 'searchUserList'
39
+ ];
40
+ var OPENYIDA_YIDA_SEARCH_PARAM_METHODS = [
41
+ 'searchFormDatas',
42
+ 'searchFormDataIds',
43
+ 'getProcessInstances',
44
+ 'getProcessInstanceIds'
45
+ ];
46
+ var OPENYIDA_KNOWN_YIDA_API_METHODS = OPENYIDA_YIDA_FORM_API_METHODS
47
+ .concat(OPENYIDA_YIDA_PROCESS_API_METHODS)
48
+ .concat(OPENYIDA_YIDA_FORM_DESIGN_API_METHODS)
49
+ .concat(OPENYIDA_YIDA_GENERIC_API_METHODS);
50
+ var OPENYIDA_UTIL_API_METHODS = [
51
+ 'dialog',
52
+ 'formatter',
53
+ 'getDateTimeRange',
54
+ 'getLocale',
55
+ 'getLoginUserId',
56
+ 'getLoginUserName',
57
+ 'isMobile',
58
+ 'isSubmissionPage',
59
+ 'isViewPage',
60
+ 'loadScript',
61
+ 'loadStyleSheet',
62
+ 'openPage',
63
+ 'previewImage',
64
+ 'toast'
65
+ ];
66
+
67
+ function openyidaYidaApiBridgeTargets() {
14
68
  var targets = [];
15
69
  function pushTarget(target) {
16
70
  if (target && targets.indexOf(target) === -1) {
@@ -23,12 +77,29 @@ const CANVAS_YIDA_API_BRIDGE_SOURCE = `function openyidaYidaApiBridgeTargets() {
23
77
  return targets;
24
78
  }
25
79
 
26
- function openyidaNormalizeYidaApiParams(params) {
80
+ function openyidaShouldNormalizeSearchParams(methodName, params) {
81
+ if (!params) {
82
+ return false;
83
+ }
84
+ if (Object.prototype.hasOwnProperty.call(params, 'searchFieldJson')) {
85
+ return true;
86
+ }
87
+ return OPENYIDA_YIDA_SEARCH_PARAM_METHODS.indexOf(methodName) !== -1;
88
+ }
89
+
90
+ function openyidaNormalizeYidaApiParams(methodName, params) {
27
91
  var next = Object.assign({}, params || {});
92
+ if (!openyidaShouldNormalizeSearchParams(methodName, next)) {
93
+ return next;
94
+ }
28
95
  if (next.searchFieldJson && typeof next.searchFieldJson !== 'string') {
29
96
  next.searchFieldJson = JSON.stringify(next.searchFieldJson);
30
97
  }
31
- if (next.query && !next.searchFieldJson) {
98
+ if (
99
+ next.query &&
100
+ !next.searchFieldJson &&
101
+ OPENYIDA_YIDA_SEARCH_PARAM_METHODS.indexOf(methodName) !== -1
102
+ ) {
32
103
  next.searchFieldJson = JSON.stringify(next.query);
33
104
  delete next.query;
34
105
  }
@@ -38,41 +109,175 @@ function openyidaNormalizeYidaApiParams(params) {
38
109
  return next;
39
110
  }
40
111
 
112
+ function openyidaCollectYidaApiMethodNames(yida) {
113
+ var names = OPENYIDA_KNOWN_YIDA_API_METHODS.slice();
114
+ function addName(name) {
115
+ if (name && names.indexOf(name) === -1) {
116
+ names.push(name);
117
+ }
118
+ }
119
+ var cursor = yida;
120
+ while (cursor && cursor !== Object.prototype) {
121
+ try {
122
+ Object.getOwnPropertyNames(cursor).forEach(function(name) {
123
+ if (name !== 'constructor' && typeof yida[name] === 'function') {
124
+ addName(name);
125
+ }
126
+ });
127
+ } catch (err) {}
128
+ cursor = Object.getPrototypeOf(cursor);
129
+ }
130
+ return names;
131
+ }
132
+
133
+ function openyidaAvailableYidaApiMethods(yida, methodNames) {
134
+ if (!yida) {
135
+ return [];
136
+ }
137
+ return methodNames.filter(function(methodName) {
138
+ return typeof yida[methodName] === 'function';
139
+ });
140
+ }
141
+
41
142
  function openyidaCreateYidaApiBridge(context) {
42
143
  var yida = context && context.utils && context.utils.yida;
144
+ var methodNames = openyidaCollectYidaApiMethodNames(yida);
43
145
  function invoke(methodName, params) {
44
146
  if (!yida || typeof yida[methodName] !== 'function') {
45
147
  return Promise.reject(new Error('this.utils.yida.' + methodName + ' is not available'));
46
148
  }
47
149
  try {
48
- return Promise.resolve(yida[methodName].call(yida, openyidaNormalizeYidaApiParams(params)));
150
+ return Promise.resolve(yida[methodName].call(yida, openyidaNormalizeYidaApiParams(methodName, params)));
49
151
  } catch (err) {
50
152
  return Promise.reject(err);
51
153
  }
52
154
  }
53
- return {
155
+ var bridge = {
54
156
  ready: !!yida,
157
+ apiGroups: {
158
+ form: OPENYIDA_YIDA_FORM_API_METHODS.slice(),
159
+ process: OPENYIDA_YIDA_PROCESS_API_METHODS.slice(),
160
+ formDesign: OPENYIDA_YIDA_FORM_DESIGN_API_METHODS.slice(),
161
+ generic: OPENYIDA_YIDA_GENERIC_API_METHODS.slice()
162
+ },
163
+ knownMethods: OPENYIDA_KNOWN_YIDA_API_METHODS.slice(),
164
+ availableMethods: openyidaAvailableYidaApiMethods(yida, methodNames),
165
+ missingKnownMethods: OPENYIDA_KNOWN_YIDA_API_METHODS.filter(function(methodName) {
166
+ return !yida || typeof yida[methodName] !== 'function';
167
+ }),
168
+ invoke: invoke
169
+ };
170
+ methodNames.forEach(function(methodName) {
171
+ if (!bridge[methodName]) {
172
+ bridge[methodName] = function(params) {
173
+ return invoke(methodName, params);
174
+ };
175
+ }
176
+ });
177
+ return bridge;
178
+ }
179
+
180
+ function openyidaCollectUtilsMethodNames(utils) {
181
+ var names = OPENYIDA_UTIL_API_METHODS.slice();
182
+ function addName(name) {
183
+ if (name && names.indexOf(name) === -1) {
184
+ names.push(name);
185
+ }
186
+ }
187
+ var cursor = utils;
188
+ while (cursor && cursor !== Object.prototype) {
189
+ try {
190
+ Object.getOwnPropertyNames(cursor).forEach(function(name) {
191
+ if (name !== 'constructor' && name !== 'yida' && name !== 'router' && typeof utils[name] === 'function') {
192
+ addName(name);
193
+ }
194
+ });
195
+ } catch (err) {}
196
+ cursor = Object.getPrototypeOf(cursor);
197
+ }
198
+ return names;
199
+ }
200
+
201
+ function openyidaAvailableUtilsMethods(utils, methodNames) {
202
+ if (!utils) {
203
+ return [];
204
+ }
205
+ return methodNames.filter(function(methodName) {
206
+ return typeof utils[methodName] === 'function';
207
+ });
208
+ }
209
+
210
+ function openyidaCreateRouterBridge(utils) {
211
+ var router = utils && utils.router;
212
+ var bridge = {
213
+ ready: !!router,
214
+ availableMethods: []
215
+ };
216
+ if (router && typeof router.push === 'function') {
217
+ bridge.availableMethods.push('push');
218
+ bridge.push = function() {
219
+ return router.push.apply(router, arguments);
220
+ };
221
+ } else {
222
+ bridge.push = function() {
223
+ throw new Error('this.utils.router.push is not available');
224
+ };
225
+ }
226
+ return bridge;
227
+ }
228
+
229
+ function openyidaCreateUtilsBridge(context, yidaBridge) {
230
+ var utils = context && context.utils;
231
+ var methodNames = openyidaCollectUtilsMethodNames(utils);
232
+ function invoke(methodName) {
233
+ if (!utils || typeof utils[methodName] !== 'function') {
234
+ throw new Error('this.utils.' + methodName + ' is not available');
235
+ }
236
+ return utils[methodName].apply(utils, Array.prototype.slice.call(arguments, 1));
237
+ }
238
+ var bridge = {
239
+ ready: !!utils,
240
+ apiGroups: {
241
+ utility: OPENYIDA_UTIL_API_METHODS.slice(),
242
+ router: ['push'],
243
+ yida: OPENYIDA_KNOWN_YIDA_API_METHODS.slice()
244
+ },
245
+ knownMethods: OPENYIDA_UTIL_API_METHODS.slice(),
246
+ availableMethods: openyidaAvailableUtilsMethods(utils, methodNames),
247
+ missingKnownMethods: OPENYIDA_UTIL_API_METHODS.filter(function(methodName) {
248
+ return !utils || typeof utils[methodName] !== 'function';
249
+ }),
55
250
  invoke: invoke,
56
- searchFormDatas: function(params) { return invoke('searchFormDatas', params); },
57
- searchFormDataIds: function(params) { return invoke('searchFormDataIds', params); },
58
- getFormDataById: function(params) { return invoke('getFormDataById', params); },
59
- getFormComponentDefinationList: function(params) { return invoke('getFormComponentDefinationList', params); },
60
- saveFormData: function(params) { return invoke('saveFormData', params); },
61
- updateFormData: function(params) { return invoke('updateFormData', params); },
62
- deleteFormData: function(params) { return invoke('deleteFormData', params); }
251
+ router: openyidaCreateRouterBridge(utils),
252
+ yida: yidaBridge
63
253
  };
254
+ methodNames.forEach(function(methodName) {
255
+ if (!bridge[methodName]) {
256
+ bridge[methodName] = function() {
257
+ var args = Array.prototype.slice.call(arguments);
258
+ args.unshift(methodName);
259
+ return invoke.apply(null, args);
260
+ };
261
+ }
262
+ });
263
+ return bridge;
64
264
  }
65
265
 
66
266
  function openyidaInstallYidaApiBridge() {
67
267
  var bridge = openyidaCreateYidaApiBridge(this);
268
+ var utilsBridge = openyidaCreateUtilsBridge(this, bridge);
68
269
  try {
69
270
  window.__OPENYIDA_YIDA_API__ = bridge;
70
271
  window.openyidaYidaApi = bridge;
272
+ window.__OPENYIDA_UTILS__ = utilsBridge;
273
+ window.openyidaUtils = utilsBridge;
71
274
  } catch (err) {}
72
275
  openyidaYidaApiBridgeTargets().forEach(function(target) {
73
276
  try {
74
277
  target.__OPENYIDA_YIDA_API__ = bridge;
75
278
  target.openyidaYidaApi = bridge;
279
+ target.__OPENYIDA_UTILS__ = utilsBridge;
280
+ target.openyidaUtils = utilsBridge;
76
281
  } catch (err) {}
77
282
  });
78
283
  return bridge;
@@ -133,19 +338,19 @@ function buildCanvasPageSchemaObject(sourceCode, runtimeCode, importedModules, f
133
338
  },
134
339
  ],
135
340
  componentsMap: [
136
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'YidaCodeCanvas' },
137
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootHeader' },
138
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootContent' },
139
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'RootFooter' },
140
- { package: '@ali/vc-deep-yida', version: '1.5.169', componentName: 'Page' },
341
+ { package: '@ali/vc-deep-yida', componentName: 'YidaCodeCanvas' },
342
+ { package: '@ali/vc-deep-yida', componentName: 'RootHeader' },
343
+ { package: '@ali/vc-deep-yida', componentName: 'RootContent' },
344
+ { package: '@ali/vc-deep-yida', componentName: 'RootFooter' },
345
+ { package: '@ali/vc-deep-yida', componentName: 'Page' },
141
346
  ],
142
347
  componentsTree: [
143
348
  {
144
349
  componentName: 'Page',
145
350
  id: nextNodeId(),
146
351
  props: {
147
- contentBgColor: 'white',
148
- pageStyle: { backgroundColor: '#f2f3f5' },
352
+ contentBgColor: 'var(--pod-page-bg-color, var(--color-white, #fff))',
353
+ pageStyle: { backgroundColor: 'var(--pod-page-bg-color, var(--color-white, #fff))' },
149
354
  contentMargin: '0',
150
355
  contentPadding: '0',
151
356
  showTitle: false,
@@ -153,10 +358,10 @@ function buildCanvasPageSchemaObject(sourceCode, runtimeCode, importedModules, f
153
358
  templateVersion: '1.0.0',
154
359
  contentMarginMobile: '0',
155
360
  className: 'page_' + nextSuffix(),
156
- contentBgColorMobile: 'white',
361
+ contentBgColorMobile: 'var(--pod-page-bg-color, var(--color-white, #fff))',
157
362
  },
158
363
  condition: true,
159
- css: 'body{background-color:#f2f3f5}.vc-page-yida-page{--yida-form-content-padding:0;--yida-form-content-margin:0;--yida-layout-padding:0}.vc-deep-container-entry.vc-rootcontent{padding:0!important;margin-top:0!important;margin-right:0!important;margin-bottom:0!important;margin-left:0!important}',
364
+ css: 'body{background-color:var(--pod-page-bg-color,var(--color-white,#fff))}.vc-page-yida-page{--yida-form-content-padding:0;--yida-form-content-margin:0;--yida-layout-padding:0}.vc-deep-container-entry.vc-rootcontent{padding:0!important;margin-top:0!important;margin-right:0!important;margin-bottom:0!important;margin-left:0!important}',
160
365
  methods: {
161
366
  __initMethods__: {
162
367
  type: 'js',