openyida 2026.7.7 → 2026.7.8
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 +4 -2
- package/bin/yida.js +4 -4
- package/lib/app/canvas-compile.js +303 -0
- package/lib/app/publish.js +190 -39
- package/lib/auth/auth.js +11 -3
- package/lib/auth/login.js +34 -15
- package/lib/core/command-manifest.js +1 -1
- package/lib/core/env.js +19 -4
- package/lib/core/locales/ar.js +6 -2
- package/lib/core/locales/de.js +6 -2
- package/lib/core/locales/en.js +7 -3
- package/lib/core/locales/es.js +6 -2
- package/lib/core/locales/fr.js +6 -2
- package/lib/core/locales/hi.js +6 -2
- package/lib/core/locales/ja.js +7 -3
- package/lib/core/locales/ko.js +6 -2
- package/lib/core/locales/pt.js +6 -2
- package/lib/core/locales/vi.js +6 -2
- package/lib/core/locales/zh-HK.js +7 -3
- package/lib/core/locales/zh.js +7 -3
- package/lib/core/utils.js +195 -15
- package/lib/samples/yida-custom-page/custom-page-template.js +6 -6
- package/lib/samples/yida-custom-page/design-tokens.js +14 -12
- package/package.json +1 -1
- package/scripts/e2e-real/skill-coverage.js +4 -0
- package/scripts/validate-ci.sh +6 -0
- package/scripts/validate-package-size.js +12 -1
- package/yida-skills/SKILL.md +19 -5
- package/yida-skills/references/development-rules.md +2 -2
- package/yida-skills/references/field-and-url-reference.md +14 -1
- package/yida-skills/skills/yida-app/SKILL.md +3 -2
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +113 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +163 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-design-system.md +148 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +115 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/dependencies-and-cdn.md +45 -0
- package/yida-skills/skills/yida-canvas-custom-page/references/employeefield-verification.md +68 -0
- package/yida-skills/skills/yida-canvas-upgrade/SKILL.md +99 -0
- package/yida-skills/skills/yida-canvas-upgrade/references/migration-examples.md +69 -0
- package/yida-skills/skills/yida-custom-page/SKILL.md +7 -2
- package/yida-skills/skills/yida-custom-page/examples/attachment-upload.js +1 -1
- package/yida-skills/skills/yida-custom-page/references/coding-guide.md +3 -3
- package/yida-skills/skills/yida-custom-page/references/component-jsx-guide.md +3 -2
- package/yida-skills/skills/yida-custom-page/references/design-system.md +30 -13
- package/yida-skills/skills/yida-nav-shell/SKILL.md +119 -0
- package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +302 -0
- package/yida-skills/skills/yida-page-config/SKILL.md +1 -0
- package/yida-skills/skills/yida-page-uiux/SKILL.md +68 -0
- package/yida-skills/skills/yida-page-uiux/references/scenes/dashboard.md +128 -0
- package/yida-skills/skills/yida-page-uiux/references/scenes/detail.md +79 -0
- package/yida-skills/skills/yida-page-uiux/references/scenes/landing.md +101 -0
- package/yida-skills/skills/yida-page-uiux/references/scenes/list.md +78 -0
- package/yida-skills/skills/yida-page-uiux/references/scenes/workbench.md +74 -0
- package/yida-skills/skills/yida-page-uiux/references/visual-decision-engine.md +151 -0
- package/yida-skills/skills/yida-page-uiux/workflow/output-decision-block.md +36 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-0-nav-shape.md +39 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-1-page-type.md +25 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-2-intent-decode.md +25 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-3-scene-routing.md +29 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-4-visual-decision.md +32 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-5-icon-and-assets.md +36 -0
- package/yida-skills/skills/yida-page-uiux/workflow/step-6-deai-selfcheck.md +37 -0
- package/yida-skills/skills/yida-publish-page/SKILL.md +7 -3
package/lib/core/locales/zh.js
CHANGED
|
@@ -250,7 +250,7 @@ openyida - 宜搭命令行工具
|
|
|
250
250
|
generate_page_example: '示例: openyida generate-page product-homepage --brand-name OpenKuma --brand-initials OK --output pages/src/home.oyd.jsx --compile',
|
|
251
251
|
build_page_usage: '用法: openyida build-page <源文件路径> [--output pages/build/page.yida.jsx|--write] [--json]',
|
|
252
252
|
build_page_example: '示例: openyida build-page pages/src/dashboard.oyd.jsx --output pages/build/dashboard.yida.jsx',
|
|
253
|
-
publish_usage: '用法: openyida publish <源文件路径> <appType> <formUuid> [--health-check]',
|
|
253
|
+
publish_usage: '用法: openyida publish <源文件路径> <appType> <formUuid> [--health-check] [--canvas]',
|
|
254
254
|
publish_example: '示例: openyida publish pages/src/home.oyd.jsx APP_XXX FORM-XXX --health-check',
|
|
255
255
|
formula_usage: '用法: openyida formula evaluate <公式或文件> [--schema schema.json] [--json] [--strict]',
|
|
256
256
|
formula_example: '示例: openyida formula evaluate \'IF(GT(#{numberField_total}, 100), "高", "低")\' --schema .cache/schema.json',
|
|
@@ -1066,7 +1066,7 @@ openyida - 宜搭命令行工具
|
|
|
1066
1066
|
lint_array_callback_function: '.{0}(function ...) 会导致回调内 this 丢失,请改为 .{0}((item) => ...)',
|
|
1067
1067
|
lint_foreach_callback_function: '.forEach(function ...) 可能导致回调内 this 丢失,建议改为 .forEach((item) => ...)',
|
|
1068
1068
|
lint_controlled_input: 'input 使用了 value 受控模式,宜搭页面应使用 defaultValue + onChange 写入 _customState',
|
|
1069
|
-
lint_native_select_ui: '检测到原生 <select
|
|
1069
|
+
lint_native_select_ui: '检测到原生 <select>,样式不一致且移动端兼容性差。普通自定义页请用 div+button+onClick 实现自定义下拉;只有 Code Canvas 场景在确认依赖映射可用后,才使用宜搭字段组件。',
|
|
1070
1070
|
lint_iframe_self_navigation: '宜搭自定义页面运行在 iframe 中,跳转宜搭页面时请使用 target="_top"/target="_blank" 或 window.top.location,避免页面套娃',
|
|
1071
1071
|
lint_page_size_limit: 'pageSize={0} 超过宜搭接口上限 100,请改为 100 或更小',
|
|
1072
1072
|
lint_page_size_recommend: 'pageSize={0} 偏大,推荐使用 50(性能最佳,最大 100),优先选择 10/20/50',
|
|
@@ -1113,6 +1113,10 @@ openyida - 宜搭命令行工具
|
|
|
1113
1113
|
building_schema: '[4/4] 构建 Schema...',
|
|
1114
1114
|
formula_prefix_fixed: ' 🔧 已自动修正 {0} 处子表公式前缀引用',
|
|
1115
1115
|
schema_built: ' ✅ Schema 构建完成!',
|
|
1116
|
+
step_canvas_compile: '\n📦 Step 1: 编译 Code Canvas 源码\n',
|
|
1117
|
+
canvas_compiling: ' 🎨 本地编译 Code Canvas 源码...',
|
|
1118
|
+
canvas_compile_done: ' ✅ Code Canvas 编译完成!',
|
|
1119
|
+
canvas_compile_failed: ' ❌ Code Canvas 编译失败:{0}',
|
|
1116
1120
|
step_login: '\n🔑 Step 2: 读取登录态',
|
|
1117
1121
|
step_publish: '\n📤 Step 3: 发布 Schema\n',
|
|
1118
1122
|
resend_save_csrf: ' 🔄 重新发送 saveFormSchema 请求(csrf_token 已刷新)...',
|
|
@@ -1142,7 +1146,7 @@ openyida - 宜搭命令行工具
|
|
|
1142
1146
|
exception: '\n❌ 发布异常: {0}',
|
|
1143
1147
|
error: '\n❌ 发布异常: {0}',
|
|
1144
1148
|
source_not_found: '❌ 源文件不存在:{0}',
|
|
1145
|
-
usage: '用法: openyida publish <源文件路径> <appType> <formUuid> [--health-check]',
|
|
1149
|
+
usage: '用法: openyida publish <源文件路径> <appType> <formUuid> [--health-check] [--canvas]',
|
|
1146
1150
|
example: '示例:openyida publish pages/src/xxx.js APP_XXX FORM-XXX --health-check',
|
|
1147
1151
|
},
|
|
1148
1152
|
|
package/lib/core/utils.js
CHANGED
|
@@ -264,6 +264,43 @@ function isInjectedAuthMode(env = process.env) {
|
|
|
264
264
|
return ['1', 'true', 'yes', 'on'].includes(authEnabled);
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
function hasEnvCookieAuth(env = process.env) {
|
|
268
|
+
return !!String(env.OPENYIDA_COOKIE_B64 || '').trim();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function isEnvAuthMode(env = process.env) {
|
|
272
|
+
return isInjectedAuthMode(env) || hasEnvCookieAuth(env);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let lastEnvAuthError = null;
|
|
276
|
+
|
|
277
|
+
function getLastEnvAuthError() {
|
|
278
|
+
return lastEnvAuthError;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function setLastEnvAuthError(code, failureReason, message) {
|
|
282
|
+
lastEnvAuthError = {
|
|
283
|
+
code,
|
|
284
|
+
failure_reason: failureReason,
|
|
285
|
+
message,
|
|
286
|
+
authMode: 'env',
|
|
287
|
+
};
|
|
288
|
+
return lastEnvAuthError;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function normalizeEnvBaseUrl(baseUrl, fallbackBaseUrl) {
|
|
292
|
+
const raw = String(baseUrl || fallbackBaseUrl || 'https://www.aliwork.com').trim();
|
|
293
|
+
try {
|
|
294
|
+
const parsed = new URL(raw);
|
|
295
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
return raw.replace(/\/+$/, '');
|
|
299
|
+
} catch {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
267
304
|
/**
|
|
268
305
|
* 查找项目根目录(project 工作区)。
|
|
269
306
|
*
|
|
@@ -354,6 +391,123 @@ function extractInfoFromCookies(cookies) {
|
|
|
354
391
|
return { csrfToken, corpId, userId };
|
|
355
392
|
}
|
|
356
393
|
|
|
394
|
+
function parseCookieHeader(rawCookieHeader, options = {}) {
|
|
395
|
+
const raw = String(rawCookieHeader || '').trim();
|
|
396
|
+
if (!raw) {
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
let domain = '';
|
|
400
|
+
if (options.baseUrl) {
|
|
401
|
+
try {
|
|
402
|
+
domain = new URL(options.baseUrl).hostname;
|
|
403
|
+
} catch {
|
|
404
|
+
domain = '';
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return raw
|
|
408
|
+
.split(';')
|
|
409
|
+
.map((part) => {
|
|
410
|
+
const trimmed = part.trim();
|
|
411
|
+
const equalsIndex = trimmed.indexOf('=');
|
|
412
|
+
if (equalsIndex <= 0) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
const name = trimmed.slice(0, equalsIndex).trim();
|
|
416
|
+
const value = trimmed.slice(equalsIndex + 1).trim();
|
|
417
|
+
if (!name) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
const cookie = { name, value };
|
|
421
|
+
if (domain) {
|
|
422
|
+
cookie.domain = domain;
|
|
423
|
+
}
|
|
424
|
+
return cookie;
|
|
425
|
+
})
|
|
426
|
+
.filter(Boolean);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function decodeCookieHeaderFromBase64(encodedCookie) {
|
|
430
|
+
const normalized = String(encodedCookie || '').trim().replace(/\s+/g, '');
|
|
431
|
+
if (!normalized) {
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(normalized)) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
const decoded = Buffer.from(normalized, 'base64').toString('utf8').trim();
|
|
439
|
+
return decoded && decoded.includes('=') ? decoded : null;
|
|
440
|
+
} catch {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function loadEnvCookieData(defaultBaseUrl, env = process.env) {
|
|
446
|
+
lastEnvAuthError = null;
|
|
447
|
+
const encodedCookie = String(env.OPENYIDA_COOKIE_B64 || '').trim();
|
|
448
|
+
const envAuthEnabled = isInjectedAuthMode(env);
|
|
449
|
+
if (!encodedCookie) {
|
|
450
|
+
if (envAuthEnabled) {
|
|
451
|
+
setLastEnvAuthError(
|
|
452
|
+
'not_logged_in',
|
|
453
|
+
'env_cookie_missing',
|
|
454
|
+
'OPENYIDA_COOKIE_B64 is required when YIDA_AUTH_ENABLED=true'
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const baseUrl = normalizeEnvBaseUrl(env.OPENYIDA_BASE_URL, defaultBaseUrl);
|
|
461
|
+
if (!baseUrl) {
|
|
462
|
+
setLastEnvAuthError(
|
|
463
|
+
'not_logged_in',
|
|
464
|
+
'base_url_invalid',
|
|
465
|
+
'OPENYIDA_BASE_URL is invalid'
|
|
466
|
+
);
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const rawCookieHeader = decodeCookieHeaderFromBase64(encodedCookie);
|
|
471
|
+
if (!rawCookieHeader) {
|
|
472
|
+
setLastEnvAuthError(
|
|
473
|
+
'not_logged_in',
|
|
474
|
+
'env_cookie_decode_failed',
|
|
475
|
+
'OPENYIDA_COOKIE_B64 is not a valid base64 encoded Cookie header'
|
|
476
|
+
);
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const cookies = parseCookieHeader(rawCookieHeader, { baseUrl });
|
|
481
|
+
if (cookies.length === 0) {
|
|
482
|
+
setLastEnvAuthError(
|
|
483
|
+
'not_logged_in',
|
|
484
|
+
'env_cookie_parse_failed',
|
|
485
|
+
'OPENYIDA_COOKIE_B64 decoded to an empty or invalid Cookie header'
|
|
486
|
+
);
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const { csrfToken, corpId, userId } = extractInfoFromCookies(cookies);
|
|
491
|
+
if (!csrfToken) {
|
|
492
|
+
setLastEnvAuthError(
|
|
493
|
+
'csrf_missing',
|
|
494
|
+
'csrf_token_missing',
|
|
495
|
+
'Env Cookie header does not contain tianshu_csrf_token'
|
|
496
|
+
);
|
|
497
|
+
return null;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
cookies,
|
|
502
|
+
csrf_token: csrfToken,
|
|
503
|
+
corp_id: corpId,
|
|
504
|
+
user_id: userId,
|
|
505
|
+
base_url: baseUrl,
|
|
506
|
+
auth_source: 'env',
|
|
507
|
+
auth_mode: 'env',
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
357
511
|
// ── 登录态缓存读取 ────────────────────────────────────
|
|
358
512
|
|
|
359
513
|
/**
|
|
@@ -368,6 +522,14 @@ function loadCookieData(projectRoot, defaultBaseUrl) {
|
|
|
368
522
|
const root = projectRoot || findProjectRoot();
|
|
369
523
|
const fallbackBaseUrl = defaultBaseUrl || 'https://www.aliwork.com';
|
|
370
524
|
|
|
525
|
+
const envCookieData = loadEnvCookieData(fallbackBaseUrl);
|
|
526
|
+
if (envCookieData) {
|
|
527
|
+
return envCookieData;
|
|
528
|
+
}
|
|
529
|
+
if (lastEnvAuthError && isEnvAuthMode()) {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
|
|
371
533
|
// 尝试迁移旧版 cookies.json(仅在首次使用多环境功能时执行一次)
|
|
372
534
|
const { migrateOldCookieFile, getCookieFilePath } = require('./env-manager');
|
|
373
535
|
migrateOldCookieFile(root);
|
|
@@ -428,21 +590,22 @@ function loadCookieData(projectRoot, defaultBaseUrl) {
|
|
|
428
590
|
* @returns {object} loginResult
|
|
429
591
|
*/
|
|
430
592
|
function triggerLogin(options = {}) {
|
|
431
|
-
if (
|
|
593
|
+
if (isEnvAuthMode()) {
|
|
432
594
|
const cookieData = loadCookieData();
|
|
433
595
|
if (cookieData && cookieData.cookies && cookieData.cookies.length > 0 && cookieData.csrf_token) {
|
|
434
596
|
return cookieData;
|
|
435
597
|
}
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
598
|
+
const envAuthError = getLastEnvAuthError();
|
|
599
|
+
const reason = envAuthError
|
|
600
|
+
? envAuthError.failure_reason
|
|
601
|
+
: (cookieData && cookieData.cookies ? 'csrf_token_missing' : 'env_cookie_missing');
|
|
439
602
|
const { CliError } = require('./cli-error');
|
|
440
603
|
throw new CliError(
|
|
441
|
-
`not_logged_in:
|
|
604
|
+
`not_logged_in: env cookie is unavailable (${reason}). Refresh the host page so it can inject a fresh Yida cookie.`,
|
|
442
605
|
{
|
|
443
606
|
code: 'INJECTED_AUTH_REQUIRED',
|
|
444
607
|
details: {
|
|
445
|
-
authMode: '
|
|
608
|
+
authMode: 'env',
|
|
446
609
|
failure_reason: reason,
|
|
447
610
|
},
|
|
448
611
|
}
|
|
@@ -499,10 +662,16 @@ function refreshCsrfToken() {
|
|
|
499
662
|
* @returns {boolean}
|
|
500
663
|
*/
|
|
501
664
|
function isLoginExpired(responseJson) {
|
|
665
|
+
if (!responseJson) {return false;}
|
|
666
|
+
const status = String(responseJson.status || '').toLowerCase();
|
|
667
|
+
const errorCode = String(responseJson.errorCode || responseJson.code || '').toLowerCase();
|
|
668
|
+
const errorMsg = String(responseJson.errorMsg || responseJson.message || '').toLowerCase();
|
|
669
|
+
if (status === 'not_logged_in' || errorCode === 'not_logged_in' || errorMsg.includes('not_logged_in')) {
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
502
672
|
return (
|
|
503
|
-
responseJson &&
|
|
504
673
|
responseJson.success === false &&
|
|
505
|
-
|
|
674
|
+
['307', '302', '401', '403'].includes(errorCode)
|
|
506
675
|
);
|
|
507
676
|
}
|
|
508
677
|
|
|
@@ -523,6 +692,10 @@ function isHttpRedirectStatus(statusCode) {
|
|
|
523
692
|
return [301, 302, 303, 307, 308].includes(Number(statusCode));
|
|
524
693
|
}
|
|
525
694
|
|
|
695
|
+
function isHttpAuthStatus(statusCode) {
|
|
696
|
+
return [401, 403].includes(Number(statusCode));
|
|
697
|
+
}
|
|
698
|
+
|
|
526
699
|
// ── base_url 解析 ─────────────────────────────────────
|
|
527
700
|
|
|
528
701
|
/**
|
|
@@ -612,7 +785,7 @@ function httpPost(baseUrl, requestPath, postData, cookies, optionsOverride = {})
|
|
|
612
785
|
if (!optionsOverride.silentStatus) {
|
|
613
786
|
warn(t('common.http_status', res.statusCode));
|
|
614
787
|
}
|
|
615
|
-
if (isHttpRedirectStatus(res.statusCode)) {
|
|
788
|
+
if (isHttpRedirectStatus(res.statusCode) || isHttpAuthStatus(res.statusCode)) {
|
|
616
789
|
resolve({
|
|
617
790
|
__needLogin: true,
|
|
618
791
|
__httpStatus: res.statusCode,
|
|
@@ -694,7 +867,7 @@ function httpPostJson(baseUrl, requestPath, payload, cookies, optionsOverride =
|
|
|
694
867
|
if (!optionsOverride.silentStatus) {
|
|
695
868
|
warn(t('common.http_status', res.statusCode));
|
|
696
869
|
}
|
|
697
|
-
if (isHttpRedirectStatus(res.statusCode)) {
|
|
870
|
+
if (isHttpRedirectStatus(res.statusCode) || isHttpAuthStatus(res.statusCode)) {
|
|
698
871
|
resolve({
|
|
699
872
|
__needLogin: true,
|
|
700
873
|
__httpStatus: res.statusCode,
|
|
@@ -784,7 +957,7 @@ function httpGet(baseUrl, requestPath, queryParams, cookies, optionsOverride = {
|
|
|
784
957
|
if (!optionsOverride.silentStatus) {
|
|
785
958
|
warn(t('common.http_status', res.statusCode));
|
|
786
959
|
}
|
|
787
|
-
if (isHttpRedirectStatus(res.statusCode)) {
|
|
960
|
+
if (isHttpRedirectStatus(res.statusCode) || isHttpAuthStatus(res.statusCode)) {
|
|
788
961
|
resolve({
|
|
789
962
|
__needLogin: true,
|
|
790
963
|
__httpStatus: res.statusCode,
|
|
@@ -830,14 +1003,16 @@ function httpGet(baseUrl, requestPath, queryParams, cookies, optionsOverride = {
|
|
|
830
1003
|
*/
|
|
831
1004
|
async function requestWithAutoLogin(requestFn, authRef) {
|
|
832
1005
|
let result = await requestFn(authRef);
|
|
1006
|
+
const usingEnvAuth = isEnvAuthMode()
|
|
1007
|
+
|| !!(authRef && authRef.cookieData && authRef.cookieData.auth_source === 'env');
|
|
833
1008
|
|
|
834
1009
|
if (result && result.__csrfExpired) {
|
|
835
|
-
if (
|
|
1010
|
+
if (usingEnvAuth) {
|
|
836
1011
|
return {
|
|
837
1012
|
success: false,
|
|
838
1013
|
__needLogin: true,
|
|
839
1014
|
errorCode: 'INJECTED_AUTH_REQUIRED',
|
|
840
|
-
errorMsg: 'not_logged_in:
|
|
1015
|
+
errorMsg: 'not_logged_in: env cookie csrf_token expired. Refresh the host page so it can inject a fresh Yida cookie.',
|
|
841
1016
|
};
|
|
842
1017
|
}
|
|
843
1018
|
const refreshedData = refreshCsrfToken();
|
|
@@ -854,12 +1029,12 @@ async function requestWithAutoLogin(requestFn, authRef) {
|
|
|
854
1029
|
}
|
|
855
1030
|
|
|
856
1031
|
if (result && result.__needLogin) {
|
|
857
|
-
if (
|
|
1032
|
+
if (usingEnvAuth) {
|
|
858
1033
|
return {
|
|
859
1034
|
success: false,
|
|
860
1035
|
__needLogin: true,
|
|
861
1036
|
errorCode: 'INJECTED_AUTH_REQUIRED',
|
|
862
|
-
errorMsg: 'not_logged_in:
|
|
1037
|
+
errorMsg: 'not_logged_in: env cookie missing or expired. Refresh the host page so it can inject a fresh Yida cookie.',
|
|
863
1038
|
};
|
|
864
1039
|
}
|
|
865
1040
|
const newCookieData = triggerLogin({ force: true });
|
|
@@ -886,6 +1061,9 @@ module.exports = {
|
|
|
886
1061
|
hasDesktopEnvironment,
|
|
887
1062
|
findProjectRoot,
|
|
888
1063
|
extractInfoFromCookies,
|
|
1064
|
+
parseCookieHeader,
|
|
1065
|
+
loadEnvCookieData,
|
|
1066
|
+
getLastEnvAuthError,
|
|
889
1067
|
loadCookieData,
|
|
890
1068
|
triggerLogin,
|
|
891
1069
|
refreshCsrfToken,
|
|
@@ -901,4 +1079,6 @@ module.exports = {
|
|
|
901
1079
|
getNodeExecutable,
|
|
902
1080
|
resolveWukongWorkspaceRoot,
|
|
903
1081
|
isInjectedAuthMode,
|
|
1082
|
+
hasEnvCookieAuth,
|
|
1083
|
+
isEnvAuthMode,
|
|
904
1084
|
};
|
|
@@ -165,7 +165,7 @@ export function injectTailwindSource() {
|
|
|
165
165
|
'@import "tailwindcss/theme";',
|
|
166
166
|
'@import "tailwindcss/preflight";',
|
|
167
167
|
'@import "tailwindcss/utilities";',
|
|
168
|
-
'@theme { --color-brand: #2F6FED; }',
|
|
168
|
+
'@theme { --color-brand: var(--color-brand1-6, #2F6FED); }',
|
|
169
169
|
].join('\n');
|
|
170
170
|
document.head.appendChild(style);
|
|
171
171
|
}
|
|
@@ -183,11 +183,11 @@ export function injectTailwindFallback() {
|
|
|
183
183
|
style.innerHTML = [
|
|
184
184
|
'.oyd-btn,.oyd-select-trigger,.oyd-select-option{appearance:none;-webkit-appearance:none;font-family:inherit;}',
|
|
185
185
|
'.oyd-btn{height:36px;border-radius:6px;border:1px solid #D0D5DD;background:#fff;padding:0 12px;font-size:14px;cursor:pointer;}',
|
|
186
|
-
'.oyd-btn-primary{background
|
|
186
|
+
'.oyd-btn-primary{background:var(--color-brand1-6,#2F6FED);border-color:var(--color-brand1-6,#2F6FED);color:#fff;}',
|
|
187
187
|
'.oyd-select-trigger{height:38px;border-radius:6px;border:1px solid #D0D5DD;background:#fff;padding:0 12px;font-size:14px;text-align:left;box-shadow:0 6px 14px rgba(15,23,42,.06);}',
|
|
188
188
|
'.oyd-select-menu{position:absolute;z-index:30;margin-top:6px;width:100%;padding:6px;border:1px solid #E4E7EC;border-radius:10px;background:#fff;box-shadow:0 16px 32px rgba(16,24,40,.14);}',
|
|
189
189
|
'.oyd-select-option{width:100%;min-height:36px;border:0;border-radius:8px;background:#fff;padding:0 10px;text-align:left;font-size:14px;cursor:pointer;}',
|
|
190
|
-
'.oyd-select-option-active{background
|
|
190
|
+
'.oyd-select-option-active{background:var(--color-brand1-1,#EFF6FF);color:var(--color-brand1-6,#1D4ED8);font-weight:600;}',
|
|
191
191
|
].join('');
|
|
192
192
|
document.head.appendChild(style);
|
|
193
193
|
}
|
|
@@ -351,7 +351,7 @@ var styles = {
|
|
|
351
351
|
marginBottom: '16px'
|
|
352
352
|
},
|
|
353
353
|
button: {
|
|
354
|
-
backgroundColor: '#1677FF',
|
|
354
|
+
backgroundColor: 'var(--color-brand1-6, #1677FF)',
|
|
355
355
|
color: '#fff',
|
|
356
356
|
border: 'none',
|
|
357
357
|
borderRadius: '4px',
|
|
@@ -424,10 +424,10 @@ var styles = {
|
|
|
424
424
|
minHeight: 36,
|
|
425
425
|
padding: '8px 12px',
|
|
426
426
|
textAlign: 'left',
|
|
427
|
-
background: '#EFF6FF',
|
|
427
|
+
background: 'var(--color-brand1-1, #EFF6FF)',
|
|
428
428
|
border: 0,
|
|
429
429
|
borderRadius: 8,
|
|
430
|
-
color: '#1D4ED8',
|
|
430
|
+
color: 'var(--color-brand1-6, #1D4ED8)',
|
|
431
431
|
appearance: 'none',
|
|
432
432
|
WebkitAppearance: 'none',
|
|
433
433
|
fontFamily: 'inherit',
|
|
@@ -2,23 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
// ── 色彩系统 ─────────────────────────────────────────
|
|
4
4
|
var colors = {
|
|
5
|
-
//
|
|
6
|
-
primary:
|
|
7
|
-
primaryHover:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
// 主色(跟随宜搭平台品牌变量,自动适配 App 主题;色阶 1 最浅、6 主色、10 最深)
|
|
6
|
+
primary: 'var(--color-brand1-6)', // 主色:主按钮、链接、选中态高亮
|
|
7
|
+
primaryHover: 'var(--color-brand1-5)', // 主色 hover:填充按钮/链接 hover,比主色亮一档
|
|
8
|
+
primaryActive: 'var(--color-brand1-7)', // 主色按下:填充按钮 active,比主色深一档
|
|
9
|
+
hover: 'var(--color-brand1-1)', // 通用浅色 hover 底:列表行、菜单项 hover
|
|
10
|
+
active: 'var(--color-brand1-2)', // 通用浅色激活/按下底
|
|
11
|
+
disabled: 'var(--color-brand1-3)', // 禁用态:浅、去饱和
|
|
12
|
+
primaryLight: 'var(--color-brand1-2)', // 主色浅背景:选中行底、标签高亮底
|
|
13
|
+
|
|
14
|
+
// 语义色(固定,不随主题变——保证成功/警告/错误语义稳定)
|
|
14
15
|
success: '#52C41A',
|
|
15
16
|
successLight: '#F6FFED',
|
|
16
17
|
warning: '#FAAD14',
|
|
17
18
|
warningLight: '#FFFBE6',
|
|
18
19
|
error: '#FF4D4F',
|
|
19
20
|
errorLight: '#FFF2F0',
|
|
20
|
-
info
|
|
21
|
-
|
|
21
|
+
// info = 品牌信息色,跟随主题(不再固定蓝)
|
|
22
|
+
info: 'var(--color-brand1-6)',
|
|
23
|
+
infoLight: 'var(--color-brand1-1)',
|
|
22
24
|
|
|
23
25
|
// 中性色
|
|
24
26
|
text: '#1D2129',
|
|
@@ -168,7 +170,7 @@ var styles = {
|
|
|
168
170
|
success: { color: '#52C41A', bg: '#F6FFED', border: '#B7EB8F' },
|
|
169
171
|
warning: { color: '#FAAD14', bg: '#FFFBE6', border: '#FFE58F' },
|
|
170
172
|
error: { color: '#FF4D4F', bg: '#FFF2F0', border: '#FFCCC7' },
|
|
171
|
-
info: { color: '
|
|
173
|
+
info: { color: 'var(--color-brand1-6)', bg: 'var(--color-brand1-1)', border: 'var(--color-brand1-3)' },
|
|
172
174
|
default: { color: '#4E5969', bg: '#F2F3F5', border: '#E5E6EB' },
|
|
173
175
|
};
|
|
174
176
|
var c = colorMap[type] || colorMap.default;
|
package/package.json
CHANGED
|
@@ -15,6 +15,8 @@ const SKILL_COVERAGE = {
|
|
|
15
15
|
'yida-app-permission': { level: 'offline-unit', tests: ['tests/app-permission.test.js'], reason: 'app admin mutations affect real application access; shared real E2E only validates safe read paths' },
|
|
16
16
|
'yida-basic-info': { level: 'offline-unit', tests: ['tests/basic-info.test.js'], reason: 'basic-info reads org admin metadata and can update domains; unit coverage avoids mutating shared real org settings' },
|
|
17
17
|
'yida-business-rule': { level: 'opt-in', reason: 'business association rules mutate form event configuration; validate in a dedicated real-form/UI stage before adding to deterministic shared E2E' },
|
|
18
|
+
'yida-canvas-custom-page': { level: 'offline-unit', tests: ['skill metadata and packaging validation'], reason: 'Code Canvas authoring skill has no dedicated CLI command; runtimeCode/importedModules are produced by the platform compile service and a YidaCodeCanvas schema cannot be published via openyida, so shared real E2E validates skill metadata and routing rather than mutating a real page' },
|
|
19
|
+
'yida-canvas-upgrade': { level: 'offline-unit', tests: ['skill metadata and packaging validation'], reason: 'native-to-Code-Canvas migration skill emits source/report artifacts but has no CLI publish path for YidaCodeCanvas; validated via skill metadata and routing until a Canvas publish stage exists' },
|
|
18
20
|
'yida-chart': { level: 'real-e2e', stages: ['report', 'dashboard'], tests: ['report chart config generation'] },
|
|
19
21
|
'yida-connector': { level: 'offline', stages: ['connector-local'], commands: ['connector gen-template', 'connector parse-api'] },
|
|
20
22
|
'yida-connector-safe-actions': { level: 'offline', stages: ['connector-local'], commands: ['connector parse-api', 'connector test --action <operationId>'], reason: 'skill documents conservative HTTP connector action generation and repair workflow; shared E2E should validate local parsing without mutating tenant connectors' },
|
|
@@ -43,8 +45,10 @@ const SKILL_COVERAGE = {
|
|
|
43
45
|
'yida-login': { level: 'real-e2e', stages: ['auth'], commands: ['login --check-only --json'] },
|
|
44
46
|
'yida-logout': { level: 'offline-unit', tests: ['login/auth unit coverage'], reason: 'real logout would destroy the shared E2E session' },
|
|
45
47
|
'yida-nav-group': { level: 'offline-unit', tests: ['tests/nav-group.test.js'], reason: 'navigation grouping mutates app sidebar order; unit coverage validates payloads and tree operations until a dedicated cleanup-safe nav stage exists' },
|
|
48
|
+
'yida-nav-shell': { level: 'offline-unit', tests: ['skill metadata and packaging validation'], reason: 'page-internal JSX navigation shell is an authoring pattern, not a standalone CLI command; validate skill packaging and use custom-page/page-uiux generation fixtures for runtime page coverage' },
|
|
46
49
|
'yida-openyida-publish-guard': { level: 'offline-unit', tests: ['skill metadata and packaging validation'], reason: 'publish guard is an agent workflow safety rule for comparing live schema before publish; real page publish behavior remains covered by yida-publish-page E2E stages' },
|
|
47
50
|
'yida-page-config': { level: 'real-e2e', stages: ['share'], commands: ['get-page-config', 'verify-short-url', 'save-share-config'] },
|
|
51
|
+
'yida-page-uiux': { level: 'offline-unit', tests: ['skill metadata and packaging validation', 'routing eval scenarios'], reason: 'visual direction skill emits a decision block before JSX authoring and should be validated through routing/packaging rather than mutating Yida resources' },
|
|
48
52
|
'yida-ppt': { level: 'deprecated', reason: 'skill is deprecated in favor of yida-ppt-slider' },
|
|
49
53
|
'yida-ppt-slider': { level: 'offline-unit', reason: 'presentation-style custom page skill should be validated by page generation/check-page fixtures' },
|
|
50
54
|
'yida-process-rule': { level: 'opt-in-real-e2e', stages: ['process'], commands: ['configure-process'], reason: 'process stage publishes workflow rules on the disposable E2E form and is excluded from default full E2E unless explicitly requested' },
|
package/scripts/validate-ci.sh
CHANGED
|
@@ -3,6 +3,12 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
cd "$(dirname "$0")/.."
|
|
5
5
|
|
|
6
|
+
if [ -z "${npm_config_cache:-}" ]; then
|
|
7
|
+
npm_config_cache="$(mktemp -d "${TMPDIR:-/tmp}/openyida-npm-cache-XXXXXX")"
|
|
8
|
+
export npm_config_cache
|
|
9
|
+
trap 'rm -rf "$npm_config_cache"' EXIT
|
|
10
|
+
fi
|
|
11
|
+
|
|
6
12
|
echo "=== Step 1: Install dependencies ==="
|
|
7
13
|
npm ci --ignore-scripts
|
|
8
14
|
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
const { spawnSync } = require('child_process');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
6
9
|
|
|
7
10
|
const MAX_TARBALL_BYTES = 1536 * 1024;
|
|
8
11
|
const MAX_UNPACKED_BYTES = 4608 * 1024;
|
|
9
|
-
const MAX_ENTRY_COUNT =
|
|
12
|
+
const MAX_ENTRY_COUNT = 350;
|
|
10
13
|
const MAX_SINGLE_FILE_BYTES = 512 * 1024;
|
|
11
14
|
|
|
12
15
|
function formatBytes(bytes) {
|
|
@@ -26,10 +29,18 @@ function fail(message) {
|
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
function runNpmPackDryRun() {
|
|
32
|
+
const shouldCreateCache = !process.env.OPENYIDA_NPM_CACHE;
|
|
33
|
+
const npmCache = shouldCreateCache
|
|
34
|
+
? fs.mkdtempSync(path.join(os.tmpdir(), 'openyida-npm-cache-'))
|
|
35
|
+
: process.env.OPENYIDA_NPM_CACHE;
|
|
29
36
|
const result = spawnSync('npm', ['pack', '--dry-run', '--json'], {
|
|
30
37
|
encoding: 'utf8',
|
|
38
|
+
env: { ...process.env, npm_config_cache: npmCache, NPM_CONFIG_CACHE: npmCache },
|
|
31
39
|
stdio: 'pipe',
|
|
32
40
|
});
|
|
41
|
+
if (shouldCreateCache) {
|
|
42
|
+
fs.rmSync(npmCache, { recursive: true, force: true });
|
|
43
|
+
}
|
|
33
44
|
|
|
34
45
|
if (result.status !== 0) {
|
|
35
46
|
process.stderr.write(result.stderr || result.stdout);
|
package/yida-skills/SKILL.md
CHANGED
|
@@ -54,7 +54,8 @@ description: >
|
|
|
54
54
|
↓
|
|
55
55
|
[Step 4](按需)创建/更新表单 → openyida create-form → 获得 formUuid(表单)
|
|
56
56
|
↓
|
|
57
|
-
[Step 5] 编写自定义页面代码 → yida-custom-page
|
|
57
|
+
[Step 5] 编写自定义页面代码 → 先按「自定义页面选路」定链路(**默认 Code Canvas yida-canvas-custom-page**,含开放 API 读数据页;仅强依赖原生实例数据桥的页回退 native yida-custom-page)
|
|
58
|
+
↓ (首次生成面向用户的页必做:先用 yida-page-uiux 产出「视觉方向决策块」,避免统一灰白圆角的 AI 味模板脸,再交所选链路落地)
|
|
58
59
|
↓
|
|
59
60
|
[Step 6] 发布页面 → openyida publish
|
|
60
61
|
↓
|
|
@@ -69,6 +70,8 @@ description: >
|
|
|
69
70
|
[Step 10] 输出访问链接,用系统浏览器打开
|
|
70
71
|
```
|
|
71
72
|
|
|
73
|
+
> **Step 5 先定视觉方向(首次生成面向用户页必做)**:写 JSX 前先用 `yida-page-uiux` 锁定视觉方向(页面类型判定 → 意图解码 → 差异化决策 → 去 AI 味自检),产出「视觉方向决策块」,再交所选链路(默认 `yida-canvas-custom-page`,回退 `yida-custom-page`)按 `design-system.md` token 落地。跳过此步会直接套用统一灰白圆角模板,生成有 AI 味的平庸页面。详见 `skills/yida-page-uiux/SKILL.md`。
|
|
74
|
+
>
|
|
72
75
|
> **Step 7 导航整理(含看板/多页面时必做)**:首次生成完整应用后,必须基于业务信息架构重排导航,不能保留创建时的默认顺序。默认原则:面向决策者的**总览/驾驶舱看板作为应用门面靠前**,数据录入/明细表单在其后;同级多个专题看板按业务优先级排,不要把所有页面无脑堆最前。详见 `skills/yida-nav-group/SKILL.md`。
|
|
73
76
|
>
|
|
74
77
|
> **Step 8 灌入示例数据(有表单时默认执行)**:新建应用的表单默认无数据,看板会空。导航整理完成后,默认向核心表单灌入 **2-3 条**覆盖关键维度(如不同活动/渠道/日期)的示例记录,让看板首屏可展示真实聚合效果。`DateField`/`CascadeDateField` 必须用 13 位毫秒时间戳;灌后执行 `openyida data query` 抽查至少 1 条,确认字段值非空。详见 `skills/yida-data-management/SKILL.md`。
|
|
@@ -78,12 +81,19 @@ description: >
|
|
|
78
81
|
## 技能路由(单一 / 增量任务)
|
|
79
82
|
|
|
80
83
|
> 选定 **1 个**最匹配的项执行。表**按业务域分组**,每组内既可能是 skill 也可能是 CLI:
|
|
81
|
-
> - 行名为 `yida-xxx` / `sls-log-workbench` / `large-file-write` 的是 **skill** →
|
|
84
|
+
> - 行名为 `yida-xxx` / `sls-log-workbench` / `large-file-write` 的是 **skill** → 先读对应子技能文档再执行;
|
|
82
85
|
> - 行名为 `openyida xxx` 并标 **`CLI`** 的**无 SKILL.md** → 识别到诉求直接执行命令、**不要当 skill 去 read**。
|
|
83
86
|
>
|
|
84
87
|
> 按分组 +「何时选择」内联区别对号入座即可。
|
|
85
88
|
|
|
86
|
-
> ⚠️ **同类易错先分清**:改字段结构→`create-form-page`|只读 Schema→`get-schema`|改数据记录→`data-management`|详情页美化→`form-detail
|
|
89
|
+
> ⚠️ **同类易错先分清**:改字段结构→`create-form-page`|只读 Schema→`get-schema`|改数据记录→`data-management`|详情页美化→`form-detail`;自定义页视觉方向/去AI味→`page-uiux`(定方向)|token/组件实现→`custom-page`(design-system);加导航先分清→平台左侧菜单分组/排序→`nav-group`|页面隐藏应用导航后页面内自绘导航壳→`nav-shell`(必须隐藏原导航,并让导航项 URL 带 `isRenderNav=false` 等参数);字段实时校验→`formula`|提交后编排→`integration`|跨表高级函数→`business-rule`;从零建流程→`create-process`|改已有流程→`process-rule`;权限按层级:组织→`corp-manager`/应用→`app-permission`/表单→`form-permission`/页面分享→`page-config`;**自定义页面选路见下方专表**。
|
|
90
|
+
|
|
91
|
+
> 🧭 **自定义页面选路(默认 Code Canvas,按顺序命中即停)**:
|
|
92
|
+
> 1. **默认 → Code Canvas** `yida-canvas-custom-page`:现代 React 交互 / hooks 状态 / 可视化 / AI 生成 / 需崩溃隔离,**以及只需通过开放 API(HTTP)读取宜搭数据的页面**(Canvas 自写 fetch 即可拿数据);
|
|
93
|
+
> 2. 仅当页面强依赖**原生实例数据桥**——表单内字段双向绑定 `this.$(fieldId)`、`this.utils.yida.*`、`dataSourceMap`、提交流程 / 设计器数据源深度耦合,且用开放 API 重写代价过高 → 回退 **native** `yida-custom-page`;
|
|
94
|
+
> 3. 已有普通 `.oyd.jsx` 要迁到 Canvas → `yida-canvas-upgrade`。
|
|
95
|
+
>
|
|
96
|
+
> 依据(源码核实):Canvas 代码在宿主页真实 `window` 中 `new Function` 执行,但物料只透传 `code/runtimeCode/importedModules/pageType`,**无 `this` 上下文、无 `dataSourceMap`**,`this.utils.yida.*` 不可用;宿主 window 全局(`__yida_plugin_runtime__` 插件系统、`__VcDeepYidaUtils__`)均非表单数据桥,也无 `window.Deep` 字段组件。Canvas 读宜搭数据 = 在 `YidaComp` 内自写 HTTP 调开放 API。只有需要免费 `this` 实例桥的页才留 native。
|
|
87
97
|
|
|
88
98
|
| 分组 | 技能 | 何时选择(关键区别已内联) |
|
|
89
99
|
|------|------|--------------------------|
|
|
@@ -94,7 +104,11 @@ description: >
|
|
|
94
104
|
| **页面与表单** | `yida-create-page` | 创建空白自定义页面拿 formUuid,后续写 JSX |
|
|
95
105
|
| | `yida-create-form-page` | 创建/更新表单、增删改**字段结构**(普通表单,无审批) |
|
|
96
106
|
| | `yida-create-process` | 从零建**带审批**流程表单(表单还不存在,一步到位) |
|
|
97
|
-
| | `yida-
|
|
107
|
+
| | `yida-page-uiux` | 写自定义页面 UI 前先定视觉方向、去 AI 味(工作台/看板/列表/详情/官网落地页;产出决策块,不写代码;canvas / native 两链路通用) |
|
|
108
|
+
| | `yida-canvas-custom-page` | **自定义页面默认链路**:现代 React 交互 / hooks / 可视化 / AI 页,含只需开放 API HTTP 读数据的页(Code Canvas,`runtimeCode` + `importedModules`,真 React18 + 崩溃隔离) |
|
|
109
|
+
| | `yida-custom-page` | 回退项:仅当强依赖原生实例数据桥(`this.$(fieldId)` 双向绑定 / `this.utils.yida.*` / `dataSourceMap` / 提交流程深度耦合)时才用(native `.oyd.jsx` 链路) |
|
|
110
|
+
| | `yida-canvas-upgrade` | 将已有普通 `.oyd.jsx` / `Jsx` 页面升级迁移到 Code Canvas / `YidaCodeCanvas` 链路 |
|
|
111
|
+
| | `yida-nav-shell` | 自定义页**隐藏应用导航**(`isRenderNav=false`,沉浸/门户/大屏/分享)后,页面内用 JSX 自绘侧边/顶部/浮动/标签导航壳;发布后要配置隐藏原导航,跨页导航项要拼完整 URL 并合并 `isRenderNav=false` / `corpid` / 业务参数(**区别** `yida-nav-group` 平台左侧菜单分组:那是真实导航树,本项是页面内自建导航) |
|
|
98
112
|
| | `yida-publish-page` | JSX 写完后编译并发布 |
|
|
99
113
|
| | `yida-openyida-publish-guard` | 发布已有自定义页面前检查线上设计器状态,避免本地旧源码覆盖用户在线改动 |
|
|
100
114
|
| | `yida-table-form` | Excel 式表格批量录入提交 |
|
|
@@ -119,7 +133,7 @@ description: >
|
|
|
119
133
|
| | `yida-app-permission` | **单应用级**权限(应用管理员/开发成员) |
|
|
120
134
|
| | `yida-form-permission` | **单表单级**权限(权限组/数据范围) |
|
|
121
135
|
| | `yida-page-config` | **页面级**:公开访问 / 组织内分享 |
|
|
122
|
-
| **应用配置与平台** | `yida-nav-group` |
|
|
136
|
+
| **应用配置与平台** | `yida-nav-group` | 应用**左侧菜单**分组/排序(真实导航树;页面内自绘导航壳见 `yida-nav-shell`) |
|
|
123
137
|
| | `yida-form-detail` | 只注 **CSS** 美化详情页,不改字段 |
|
|
124
138
|
| | `yida-density` | 列表/表格信息密度选择 |
|
|
125
139
|
| | `yida-i18n` | 应用多语言 / 国际化 |
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 致命规则(FATAL)
|
|
6
6
|
|
|
7
|
-
**F1
|
|
7
|
+
**F1 先读后执行**:执行任何子技能前,必须先读取对应子技能文档`skills/<技能名>/SKILL.md`,按文档里的参数格式操作,不凭记忆猜。
|
|
8
8
|
|
|
9
9
|
**F2 corpId 一致性检查**:创建页面前对比 prd 文档与 `.cache/cookies.json` 的 corpId——
|
|
10
10
|
- 一致 → 继续;
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
| 方案 | 做法 | 适用 | 落到 |
|
|
74
74
|
|------|------|------|------|
|
|
75
75
|
| A 优化原生报表 | 调整图表类型、布局、筛选器,仍用宜搭原生组件 | 快速优化、无需写代码 | `yida-report`(`openyida create-report`) |
|
|
76
|
-
| B ECharts 高级报表 | ECharts + 自定义页面 JSX,高度定制 | 精美视觉、复杂交互、数据大屏 | 读 `
|
|
76
|
+
| B ECharts 高级报表 | ECharts + 自定义页面 JSX,高度定制 | 精美视觉、复杂交互、数据大屏 | 读 `yida-chart` 子技能文档 |
|
|
77
77
|
|
|
78
78
|
话术示例:
|
|
79
79
|
|
|
@@ -34,9 +34,22 @@
|
|
|
34
34
|
|---------|---------|
|
|
35
35
|
| 应用首页 | `{base_url}/{appType}/workbench` |
|
|
36
36
|
| 表单提交页 | `{base_url}/{appType}/submission/{formUuid}` |
|
|
37
|
+
| 数据管理页(列表) | `{base_url}/{appType}/workbench/{formUuid}` |
|
|
38
|
+
| 数据管理页(iframe 嵌入) | `{base_url}/{appType}/workbench/{formUuid}?iframe=true` |
|
|
37
39
|
| 自定义页面 | `{base_url}/{appType}/custom/{formUuid}` |
|
|
38
40
|
| 自定义页面(隐藏导航) | 上行 + `?isRenderNav=false` |
|
|
39
41
|
| 表单详情页 | `{base_url}/{appType}/formDetail/{formUuid}?formInstId={formInstId}` |
|
|
40
42
|
| 表单详情页(编辑态) | 上行 + `&mode=edit` |
|
|
41
43
|
|
|
42
|
-
>
|
|
44
|
+
> 任意地址可追加 `corpid={corpId}` 自动切到对应组织;无 query 时用 `?corpid=...`,已有 query 时用 `&corpid=...`。
|
|
45
|
+
|
|
46
|
+
## 页面内自定义导航 URL 参数规则
|
|
47
|
+
|
|
48
|
+
当自定义页自己绘制导航壳、并隐藏宜搭原导航时,导航项不能只保存 `formUuid`,必须保存可合并的 URL 参数:
|
|
49
|
+
|
|
50
|
+
- 自定义展示页目标:使用 `{base_url}/{appType}/custom/{formUuid}?isRenderNav=false`,保持目标页也不显示宜搭原导航。
|
|
51
|
+
- 数据表单目标:若在导航壳内容区嵌入,用 `{base_url}/{appType}/workbench/{formUuid}?iframe=true`;若整页跳转到 workbench,明确接受目标页回到宜搭工作台框架。
|
|
52
|
+
- 跨组织访问:在已有 query 后追加 `&corpid={corpId}`;没有 query 时用 `?corpid={corpId}`。
|
|
53
|
+
- 业务深链:导航项可带 `tab`、`view`、`dateRange`、`mode` 等白名单参数;拼 URL 时与公共参数合并,不能被 `router.push(formUuid)` 吞掉。
|
|
54
|
+
|
|
55
|
+
推荐用统一 URL 构造函数处理 `?` / `&`,不要手写多个分支散落在 JSX 里。
|