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.
- package/README.md +6 -7
- package/bin/yida.js +1 -7
- package/lib/app/canvas-compile.js +7 -0
- package/lib/app/create-app.js +142 -9
- package/lib/app/create-form.js +6 -22
- package/lib/app/custom-theme.js +300 -0
- package/lib/app/get-schema.js +10 -9
- package/lib/app/services/canvas-page-schema-builder.js +226 -21
- package/lib/app/services/form-compiler.js +6 -18
- package/lib/app/services/native-page-schema-builder.js +5 -5
- package/lib/app/theme-presets.js +1 -1
- package/lib/app/update-app.js +225 -9
- package/lib/auth/token-store.js +13 -1
- package/lib/core/command-manifest.js +6 -16
- package/lib/core/locales/en.js +16 -13
- package/lib/core/locales/zh.js +16 -14
- package/lib/core/query-data.js +215 -23
- package/lib/core/sample.js +4 -0
- package/lib/report/append.js +3 -1
- package/lib/report/contract.js +7 -0
- package/lib/report/index.js +94 -5
- package/lib/report/inspect.js +32 -3
- package/lib/report/runtime-probe.js +248 -0
- package/lib/report/url.js +13 -0
- package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +411 -0
- package/lib/samples/openyida-scaffold/form-fields.json +74 -0
- package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +16 -12
- package/lib/samples/yida-density/density-switch-page.js +1 -1
- package/lib/samples/yida-rechart/trend-combo.canvas.jsx +3 -3
- package/package.json +1 -1
- package/scripts/postinstall.js +76 -1
- package/yida-skills/SKILL.md +14 -12
- package/yida-skills/references/official-example-schema-patterns.md +2 -0
- package/yida-skills/references/report-field-config-guide.md +10 -28
- package/yida-skills/references/task-retrospective.md +5 -5
- package/yida-skills/references/yida-api.md +10 -10
- package/yida-skills/skills/yida-app/SKILL.md +8 -5
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +1 -3
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +5 -4
- package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +5 -6
- package/yida-skills/skills/yida-app/workflow/step-5-seed-records.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +14 -7
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +7 -4
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +10 -9
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +1 -1
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +28 -71
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +7 -6
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +28 -13
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +8 -24
- package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +6 -6
- package/yida-skills/skills/yida-canvas-table-form/SKILL.md +8 -5
- package/yida-skills/skills/yida-chart/references/echarts-bindding-guide.md +1 -1
- package/yida-skills/skills/yida-connector/examples/operations-search-formdata-v2.json +3 -3
- package/yida-skills/skills/yida-connector/examples/operations-search-formdata.json +2 -2
- package/yida-skills/skills/yida-create-app/SKILL.md +21 -12
- package/yida-skills/skills/yida-create-form-page/SKILL.md +8 -8
- package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +2 -3
- package/yida-skills/skills/yida-custom-page/SKILL.md +2 -4
- package/yida-skills/skills/yida-custom-page/references/coding-guide.md +6 -6
- package/yida-skills/skills/yida-dashboard/SKILL.md +1 -1
- package/yida-skills/skills/yida-dashboard/references/interaction-patterns.md +1 -1
- package/yida-skills/skills/yida-data-management/SKILL.md +18 -17
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +2 -2
- package/yida-skills/skills/yida-design/SKILL.md +5 -4
- package/yida-skills/skills/yida-design/references/page-quality-gates.md +3 -3
- package/yida-skills/skills/yida-design/references/style-design-selection.md +5 -5
- package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +672 -0
- package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +3 -3
- package/yida-skills/skills/yida-design/references/visual-decision-engine.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +14 -14
- package/yida-skills/skills/yida-design/workflow/output-design.md +31 -44
- package/yida-skills/skills/yida-design/workflow/output-prd.md +9 -9
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +21 -21
- package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +1 -1
- package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +3 -3
- package/yida-skills/skills/yida-form-detail/SKILL.md +26 -69
- package/yida-skills/skills/yida-publish-page/SKILL.md +4 -3
- package/yida-skills/skills/yida-report/SKILL.md +6 -11
- package/yida-skills/skills/yida-report/references/examples.md +11 -18
- package/yida-skills/skills-index.json +18 -15
- package/lib/app/form-detail-style.js +0 -441
- package/lib/app/form-theme-action.js +0 -439
- package/yida-skills/skills/yida-canvas-custom-page/references/theme-runtime-helpers.md +0 -177
- package/yida-skills/skills/yida-form-detail/references/form-detail-css.md +0 -194
- 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
|
+
};
|
package/lib/app/get-schema.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
|
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
|
|
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}
|
|
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}注:
|
|
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 = `
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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',
|
|
137
|
-
{ package: '@ali/vc-deep-yida',
|
|
138
|
-
{ package: '@ali/vc-deep-yida',
|
|
139
|
-
{ package: '@ali/vc-deep-yida',
|
|
140
|
-
{ package: '@ali/vc-deep-yida',
|
|
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: '#
|
|
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
|
|
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',
|