openyida 2026.7.26-beta.3 → 2026.7.27

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 CHANGED
@@ -414,6 +414,8 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
414
414
  | `openyida data <action> <resource> [args]` | Unified data management (form/process/task/subform) |
415
415
  | `openyida task-center <type> [options]` | Global task center (todo/processed/cc etc.) |
416
416
  | `openyida basic-info <overview\|commodity\|grant\|capacity\|quota\|abs-path\|dataflow\|i18n\|domain>` | Query organization basic info, capacity, quotas, and domain settings |
417
+ | `openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]` | Fetch Markdown content from a DingTalk document |
418
+ | `openyida read-dingtalk-tingji <taskUuid> [--json]` | Fetch DingTalk Tingji details by task UUID |
417
419
  | `openyida get-permission <appType> <formUuid>` | Query form permission config |
418
420
  | `openyida save-permission <appType> <formUuid> ...` | Save form permission config |
419
421
  | `openyida corp-manager <search-user\|list\|add\|remove\|address-book> ...` | Manage platform admins and address book permissions |
package/bin/yida.js CHANGED
@@ -869,6 +869,18 @@ async function main() {
869
869
  break;
870
870
  }
871
871
 
872
+ case 'read-dingtalk-doc': {
873
+ const { run } = require('../lib/document/document-markdown');
874
+ await run(args);
875
+ break;
876
+ }
877
+
878
+ case 'read-dingtalk-tingji': {
879
+ const { run } = require('../lib/document/tingji');
880
+ await run(args);
881
+ break;
882
+ }
883
+
872
884
  case 'doctor': {
873
885
  const { run } = require('../lib/core/doctor');
874
886
  await run(args);
@@ -402,9 +402,17 @@ const COMMAND_SIDE_EFFECTS = new Map([
402
402
  'integration.list',
403
403
  'list-forms',
404
404
  'process.preview',
405
+ 'read-dingtalk-tingji',
405
406
  'verify-short-url',
406
407
  ], sideEffect('remote_read')),
407
408
 
409
+ ['read-dingtalk-doc', sideEffect('mixed', {
410
+ mutates_yida: false,
411
+ mutates_local: true,
412
+ read_actions: ['<docUrl>', '<docUrl> --json'],
413
+ mutating_actions: ['<docUrl> --output <file>', '<docUrl> -o <file>'],
414
+ })],
415
+
408
416
  ['asset', sideEffect('mixed', {
409
417
  mutates_yida: false,
410
418
  mutates_local: true,
@@ -620,11 +628,24 @@ const COMMAND_PERMISSIONS = new Map([
620
628
  'integration.list',
621
629
  'list-forms',
622
630
  'process.preview',
631
+ 'read-dingtalk-tingji',
623
632
  'verify-short-url',
624
633
  ], permission('allow', 'read', {
625
634
  reason: 'Read-only, diagnostic, validation, or manifest discovery command.',
626
635
  })),
627
636
 
637
+ ['read-dingtalk-doc', actionDependentPermission({
638
+ preauthorized_actions: [
639
+ '<docUrl> --output <file>',
640
+ '<docUrl> -o <file>',
641
+ ],
642
+ preauthorized_patterns: [{
643
+ type: 'argv_contains_any',
644
+ values: ['http://', 'https://'],
645
+ description: 'A valid document URL is present; optional output writes are non-destructive local operations.',
646
+ }],
647
+ })],
648
+
628
649
  ...permissionEntries([
629
650
  'eval',
630
651
  ], actionDependentPermission({
@@ -1038,6 +1059,12 @@ const COMMAND_GROUPS = [
1038
1059
  command('basic-info', ['basic-info'], 'basic-info <overview|commodity|grant|capacity|quota|abs-path|dataflow|i18n|domain>', 'help.cmd_basic_info', {
1039
1060
  output: 'json',
1040
1061
  }),
1062
+ command('read-dingtalk-doc', ['read-dingtalk-doc'], 'read-dingtalk-doc <docUrl> [--output <file>] [--json]', 'help.cmd_read_dingtalk_doc', {
1063
+ output: 'text|json',
1064
+ }),
1065
+ command('read-dingtalk-tingji', ['read-dingtalk-tingji'], 'read-dingtalk-tingji <taskUuid> [--json]', 'help.cmd_read_dingtalk_tingji', {
1066
+ output: 'json',
1067
+ }),
1041
1068
  command('get-permission', ['get-permission'], 'get-permission <appType> <formUuid>', 'help.cmd_get_permission'),
1042
1069
  command('save-permission', ['save-permission'], 'save-permission <appType> <formUuid> ...', 'help.cmd_save_permission'),
1043
1070
  command('corp-manager', ['corp-manager'], 'corp-manager <search-user|list|add|remove|address-book> ...', 'help.cmd_corp_manager', { output: 'json' }),
@@ -47,6 +47,8 @@ module.exports = {
47
47
  cmd_data: 'Unified data management (form/process/task/subform)',
48
48
  cmd_task_center: 'Global task center (todo/processed/cc etc.)',
49
49
  cmd_basic_info: 'Query organization basic info, capacity, quotas, and domain settings',
50
+ cmd_read_dingtalk_doc: 'Fetch Markdown content from a DingTalk document',
51
+ cmd_read_dingtalk_tingji: 'Fetch DingTalk Tingji details by task UUID',
50
52
  cmd_get_permission: 'Query form permission config',
51
53
  cmd_save_permission: 'Save form permission config',
52
54
  cmd_corp_manager: 'Manage platform admins and address book permissions',
@@ -47,6 +47,8 @@ module.exports = {
47
47
  cmd_data: '统一数据管理(表单/流程/任务/子表单)',
48
48
  cmd_task_center: '全局任务中心(待办/已处理/抄送等)',
49
49
  cmd_basic_info: '查询组织基本信息、容量、额度和域名设置',
50
+ cmd_read_dingtalk_doc: '获取钉钉文档的 Markdown 内容',
51
+ cmd_read_dingtalk_tingji: '按任务 UUID 获取钉钉听记详情',
50
52
  cmd_get_permission: '查询表单权限配置',
51
53
  cmd_save_permission: '保存表单权限配置',
52
54
  cmd_corp_manager: '管理平台管理员与通讯录权限',
package/lib/core/utils.js CHANGED
@@ -916,6 +916,18 @@ async function httpGet(baseUrl, requestPath, queryParams, cookiesOrOptions, mayb
916
916
  });
917
917
  return;
918
918
  }
919
+ if (optionsOverride.responseType === 'text') {
920
+ if (Number(res.statusCode) < 200 || Number(res.statusCode) >= 300) {
921
+ resolve({
922
+ success: false,
923
+ errorMsg: `HTTP ${res.statusCode}`,
924
+ __httpStatus: res.statusCode,
925
+ });
926
+ return;
927
+ }
928
+ resolve(data);
929
+ return;
930
+ }
919
931
  try {
920
932
  const parsed = JSON.parse(data);
921
933
  if (isLoginExpired(parsed)) {
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { createAuthRef, createYidaClient } = require('../core/yida-client');
6
+ const { throwUsage } = require('../core/command-errors');
7
+
8
+ const API_PATH = '/query/document/markdown.json';
9
+
10
+ function parseArgs(args) {
11
+ const parsed = { docUrl: '', output: '', json: false, help: false };
12
+ for (let index = 0; index < args.length; index++) {
13
+ const arg = args[index];
14
+ if (arg === '--help' || arg === '-h') {
15
+ parsed.help = true;
16
+ } else if (arg === '--json') {
17
+ parsed.json = true;
18
+ } else if ((arg === '--output' || arg === '-o') && args[index + 1]) {
19
+ parsed.output = args[++index];
20
+ } else if (!arg.startsWith('--') && !parsed.docUrl) {
21
+ parsed.docUrl = arg;
22
+ } else {
23
+ throwUsage('Usage: openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]');
24
+ }
25
+ }
26
+ return parsed;
27
+ }
28
+
29
+ function validateDocUrl(docUrl) {
30
+ if (!docUrl) {
31
+ throwUsage('Usage: openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]');
32
+ }
33
+ let parsed;
34
+ try {
35
+ parsed = new URL(docUrl);
36
+ } catch {
37
+ throw new Error('docUrl must be a valid HTTP(S) URL');
38
+ }
39
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
40
+ throw new Error('docUrl must be a valid HTTP(S) URL');
41
+ }
42
+ return parsed.toString();
43
+ }
44
+
45
+ function unwrapMarkdownResponse(response) {
46
+ if (typeof response !== 'string') {
47
+ const detail = response && (response.errorMsg || response.message);
48
+ throw new Error(detail || 'Failed to fetch document Markdown');
49
+ }
50
+
51
+ const trimmed = response.trim();
52
+ if (!trimmed.startsWith('{')) {
53
+ return response;
54
+ }
55
+
56
+ let parsed;
57
+ try {
58
+ parsed = JSON.parse(trimmed);
59
+ } catch {
60
+ return response;
61
+ }
62
+
63
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
64
+ return response;
65
+ }
66
+ if (parsed.success === false) {
67
+ throw new Error(parsed.errorMsg || parsed.message || 'Failed to fetch document Markdown');
68
+ }
69
+ if (typeof parsed.content === 'string') {
70
+ return parsed.content;
71
+ }
72
+ return response;
73
+ }
74
+
75
+ async function fetchDocumentMarkdown(docUrl, options = {}) {
76
+ const authRef = options.authRef || createAuthRef();
77
+ const client = options.client || createYidaClient({ authRef });
78
+ const response = await client.get(API_PATH, { docUrl: validateDocUrl(docUrl) }, {
79
+ responseType: 'text',
80
+ timeout: options.timeout || 300000,
81
+ });
82
+ return unwrapMarkdownResponse(response);
83
+ }
84
+
85
+ async function run(args) {
86
+ const parsed = parseArgs(args);
87
+ if (parsed.help) {
88
+ console.log('Usage: openyida read-dingtalk-doc <docUrl> [--output <file>] [--json]');
89
+ return { help: true };
90
+ }
91
+ const docUrl = validateDocUrl(parsed.docUrl);
92
+ const content = await fetchDocumentMarkdown(docUrl);
93
+ if (parsed.output) {
94
+ const outputPath = path.resolve(parsed.output);
95
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
96
+ fs.writeFileSync(outputPath, content, 'utf8');
97
+ const result = { success: true, docUrl, output: outputPath, length: content.length };
98
+ console.log(JSON.stringify(result, null, 2));
99
+ return result;
100
+ }
101
+ const result = { success: true, docUrl, content };
102
+ console.log(parsed.json ? JSON.stringify(result, null, 2) : content);
103
+ return result;
104
+ }
105
+
106
+ module.exports = {
107
+ API_PATH,
108
+ parseArgs,
109
+ validateDocUrl,
110
+ unwrapMarkdownResponse,
111
+ fetchDocumentMarkdown,
112
+ run,
113
+ };
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ const { createAuthRef, createYidaClient } = require('../core/yida-client');
4
+ const { throwUsage } = require('../core/command-errors');
5
+
6
+ const API_PATH = '/query/document/tingji.json';
7
+
8
+ function parseArgs(args) {
9
+ const parsed = { taskUuid: '', help: false };
10
+ for (const arg of args) {
11
+ if (arg === '--help' || arg === '-h') {
12
+ parsed.help = true;
13
+ } else if (arg === '--json') {
14
+ continue;
15
+ } else if (!arg.startsWith('--') && !parsed.taskUuid) {
16
+ parsed.taskUuid = arg;
17
+ } else {
18
+ throwUsage('Usage: openyida read-dingtalk-tingji <taskUuid> [--json]');
19
+ }
20
+ }
21
+ return parsed;
22
+ }
23
+
24
+ function validateTaskUuid(taskUuid) {
25
+ const normalized = String(taskUuid || '').trim();
26
+ if (!normalized) {
27
+ throwUsage('Usage: openyida read-dingtalk-tingji <taskUuid> [--json]');
28
+ }
29
+ if (normalized.length > 256 || /[\r\n]/.test(normalized)) {
30
+ throw new Error('taskUuid is invalid');
31
+ }
32
+ return normalized;
33
+ }
34
+
35
+ async function fetchTingjiDetail(taskUuid, options = {}) {
36
+ const authRef = options.authRef || createAuthRef();
37
+ const client = options.client || createYidaClient({ authRef });
38
+ const response = await client.get(API_PATH, { taskUuid: validateTaskUuid(taskUuid) }, {
39
+ timeout: options.timeout || 300000,
40
+ });
41
+ if (!response || response.success === false || response.__needLogin || response.__csrfExpired) {
42
+ const detail = response && (response.errorMsg || response.message);
43
+ throw new Error(detail || 'Failed to fetch Tingji detail');
44
+ }
45
+ return response.content !== undefined ? response.content : response;
46
+ }
47
+
48
+ async function run(args) {
49
+ const parsed = parseArgs(args);
50
+ if (parsed.help) {
51
+ console.log('Usage: openyida read-dingtalk-tingji <taskUuid> [--json]');
52
+ return { help: true };
53
+ }
54
+ const taskUuid = validateTaskUuid(parsed.taskUuid);
55
+ const detail = await fetchTingjiDetail(taskUuid);
56
+ const result = { success: true, taskUuid, detail };
57
+ console.log(JSON.stringify(result, null, 2));
58
+ return result;
59
+ }
60
+
61
+ module.exports = { API_PATH, parseArgs, validateTaskUuid, fetchTingjiDetail, run };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openyida",
3
- "version": "2026.7.26-beta.3",
3
+ "version": "2026.7.27",
4
4
  "description": "OpenYida CLI - 宜搭低代码 AI 开发工具(安装即用,零配置)",
5
5
  "bin": {
6
6
  "openyida": "bin/yida.js",
@@ -167,13 +167,15 @@ OpenYida builder 默认使用 `create-app / create-form / create-page / generate
167
167
  | `yida-skills/integration` | 连接器、外部 API、执行动作、设计器数据源、集成自动化、逻辑流 | `yida-integration`、`yida-connector`、`yida-connector-safe-actions`、`yida-data-source-connectors` |
168
168
  | `yida-skills/access` | 平台/应用/表单/页面权限、公开访问、分享 | `yida-corp-manager`、`yida-app-permission`、`yida-form-permission`、`yida-page-config` |
169
169
  | `yida-skills/ops` | Sequence、主键冲突、VOC 反馈 | `yida-db-seq-fix`、`yida-voc` |
170
- | `yida-skills/agent` | 导出对话、会议纪要/闪记转 PRD | `yida-export-conversation`、`yida-flash-note-to-prd` |
170
+ | `yida-skills/agent` | 导出对话、读取钉钉文档/听记、会议纪要/闪记转 PRD | `yida-export-conversation`、`yida-document-markdown`、`yida-tingji`、`yida-flash-note-to-prd` |
171
171
 
172
172
  ### 高频分歧
173
173
 
174
174
  | 用户意图 | 选哪个 |
175
175
  |------|------|
176
176
  | 从零搭一个完整应用/系统 | `yida-app`;默认 `fast_build`,不要自动升级到深度设计 |
177
+ | 读取钉钉在线文档正文 | `yida-document-markdown`,使用登录态接口获取 Markdown |
178
+ | 按 taskUuid 读取钉钉听记 | `yida-tingji`,将听记任务 ID 原样传入命令 |
177
179
  | 只创建应用壳并拿 appType | `yida-create-app` |
178
180
  | 创建自定义展示页资源 | `yida-create-page`,之后默认接 `yida-canvas-custom-page` 和 `yida-publish-page` |
179
181
  | 开发表单字段结构 / 增删改字段 | `yida-create-form-page` |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: yida-canvas-custom-page
3
- description: 宜搭 Code Canvas / 代码画布自定义页面开发规范,是自定义页面的默认链路。用于现代 React 交互、hooks 状态、可视化、AI 生成、官网、看板、工作台、列表、详情或需要崩溃隔离的页面(真 React18 + runtimeCode + importedModules)。也覆盖用户明确提到 code canvas、代码画布、YidaCodeCanvas、runtimeCode、importedModules,或在 Code Canvas 中使用门户、数据管理视图、成员、部门、上传等宜搭运行态组件的场景。
3
+ description: 宜搭 Code Canvas / 代码画布自定义页面开发规范,是现代 React18 自定义页面的默认链路。用于官网、看板、工作台、列表、详情、门户壳、可视化、hooks 交互,以及用户明确提到 code canvas、代码画布、YidaCodeCanvas、runtimeCode、importedModules、门户组件、数据管理视图、成员、部门或上传组件的场景。
4
4
  ---
5
5
 
6
6
  # 宜搭 Code Canvas 自定义页面开发
@@ -12,22 +12,22 @@ Code Canvas 是宜搭的代码画布自定义页面链路:以 `YidaCodeCanvas`
12
12
  相较普通 `.oyd.jsx` 自定义页,Code Canvas 更适合:
13
13
 
14
14
  - 现代 React hooks 交互、图表、动效、复杂状态。
15
- - AI 首次生成页面:官网、看板、工作台、列表、详情、门户壳。
16
- - 需要组件级崩溃隔离和更现代前端体验的页面。
15
+ - 首版页面生成:官网、看板、工作台、列表、详情、门户壳。
16
+ - 需要 React18 函数组件、状态隔离和现代前端体验的页面。
17
17
  - 只需要通过 HTTP / 连接器读写数据的页面。
18
18
  - 需要在 Canvas 内受控接入门户、成员、部门、上传等宜搭运行态组件的页面。
19
19
 
20
- 如果用户明确要求普通自定义页面 JSX/Jsx 组件链路,或页面强依赖普通自定义页实例桥,则选择 `yida-custom-page`:例如 `this.$(fieldId)` 双向绑定、`this.utils.yida.*`、`this.dataSourceMap`、提交流程深度耦合等。
20
+ 普通自定义页面使用 `yida-custom-page`:适用于用户明确要求 JSX/Jsx 普通页面,或页面需要 `this.$(fieldId)` 双向绑定、`this.utils.yida.*`、`this.dataSourceMap`、提交流程深度耦合等普通页面实例桥能力。
21
21
 
22
22
  ## 运行时事实
23
23
 
24
24
  - Canvas 源码写成 `.canvas.jsx` / `.canvas.tsx`,`openyida publish` 会自动走 Canvas 链路。
25
- - 页面源码路径按 Bash cwd 选择:从仓库根执行命令时用 `project/pages/src/...`;如果 cwd 已是 `<workspace>/project`,用 `pages/src/...`,不要写成 `project/pages/src/...`。
25
+ - 页面源码路径按 Bash cwd 选择:从仓库根执行命令时用 `project/pages/src/...`;cwd 已是 `<workspace>/project` 时用 `pages/src/...`。
26
26
  - `runtimeCode` 在宿主页真实 `window` 中执行,入口必须返回 `YidaComp` / `YidaComp.default` / 组件函数。
27
27
  - 推荐入口写法是 `function YidaComp(props) { ... }`,或 `const App = ...; export default App;`。CLI 已兼容 `const/let/class YidaComp; export default YidaComp`,但生成新代码时优先避开同名默认导出,减少不同 Canvas 运行态装配器下的重复声明风险。
28
28
  - Canvas 组件没有普通页面实例上下文;数据读写通过 fetch、开放 API、连接器代理或显式 props 数据桥完成。
29
29
  - 第三方依赖走白名单;React、antd、ahooks、d3、recharts、Radix、framer-motion 等可按规则 import。
30
- - 宜搭运行态组件通过“原生组件桥 + fallback + 值归一化”接入;不改 `vc-deep-yida` 时,以宿主已存在的 `window.Deep` / `window.DeepYida` 探测为主,`window.YidaNativeComponents` 仅作为兼容入口。需要嵌入门户数据管理视图时探测 `DataManageViews`,并显式传入目标表单 `form.value/formUuid`。
30
+ - 宜搭运行态组件通过“先探测、可用再增强、不可用就 fallback、值统一归一化”接入;以 `window.Deep` / `window.DeepYida` 探测为主,`window.YidaNativeComponents` 作为兼容入口。嵌入门户数据管理视图时使用 `DataManageViews`,并显式传入目标表单 `form.value/formUuid`。
31
31
 
32
32
  > 依赖白名单和运行时细节见 [dependencies-and-cdn.md](references/dependencies-and-cdn.md) 与 [employeefield-verification.md](references/employeefield-verification.md)。
33
33
 
@@ -40,8 +40,8 @@ Code Canvas 是宜搭的代码画布自定义页面链路:以 `YidaCodeCanvas`
40
40
  | 需要门户 topBanner / quickEntry / 数据卡片 | 使用本技能,按“门户组件桥”接入,必要时 fallback 自绘 |
41
41
  | 需要成员、部门、附件上传、图片上传 | 使用本技能,按“宜搭组件桥”接入并归一化值 |
42
42
  | 需要字段结构、公式、联动、权限、报表、流程 | 使用对应配置型技能承载,Canvas 只做展示和事件分发 |
43
- | 深度依赖普通页 `this` 实例桥 | 选择 `yida-custom-page` |
44
- | 强依赖普通自定义页实例数据桥:表单内字段双向绑定 `this.$(fieldId)`、`this.utils.yida.*`、`dataSourceMap`、提交流程深度耦合 | 使用 `yida-custom-page`(该实例桥由普通自定义页面提供) |
43
+ | 深度依赖普通页 `this` 实例桥 | 使用 `yida-custom-page` |
44
+ | 表单内字段双向绑定 `this.$(fieldId)`、`this.utils.yida.*`、`dataSourceMap`、提交流程深度耦合 | 使用 `yida-custom-page`(该实例桥由普通自定义页面提供) |
45
45
 
46
46
  ## 两类特殊组件场景
47
47
 
@@ -59,9 +59,9 @@ openyida sample yida-canvas-custom-page portal-native-components --output projec
59
59
 
60
60
  - `PortalTopBanner`、`PortalQuickEntry`:优先接入,适合门户首页的 Banner 和快捷入口。
61
61
  - `QuickAccessCard`、`RecentlyUsedCard`:先做运行态验证,再用于动态门户卡片。
62
- - `DataCard`、`PortalContainer`:依赖门户上下文、数据卡片配置和样式变量,作为增强能力谨慎启用。
62
+ - `DataCard`、`PortalContainer`:仅在目标门户上下文、数据卡片配置和样式变量都验证通过后启用。
63
63
 
64
- 做法:从 `window.Deep`、`window.DeepYida` 探测组件;若环境已有 `window.YidaNativeComponents` 也可兼容读取。探测失败时使用 Canvas 自绘卡片,页面仍保持可用。
64
+ 做法:从 `window.Deep`、`window.DeepYida` 探测组件;若环境已有 `window.YidaNativeComponents` 也可兼容读取。探测到组件时渲染原生组件;未探测到时渲染 Canvas 自绘卡片,页面保持可用。
65
65
 
66
66
  ### 2. 成员、部门、上传组件
67
67
 
@@ -78,7 +78,7 @@ openyida sample yida-canvas-custom-page portal-native-components --output projec
78
78
  - `DepartmentSelectField`:验证部门搜索、弹层、权限提示、单选/多选后启用。
79
79
  - `AttachmentField` / `ImageField`:验证 OSS 签名、上传权限、预览、删除、失败提示后启用。
80
80
 
81
- 做法:原生组件只负责交互输入;页面业务状态保存归一化后的成员、部门、文件结构;提交仍通过 fetch / 连接器 / 开放 API 完成。组件条件未满足时 fallback 到 Canvas 自绘输入、搜索或链接录入。
81
+ 做法:原生组件负责交互输入;页面业务状态保存归一化后的成员、部门、文件结构;提交通过 fetch / 连接器 / 开放 API 完成。组件验证通过时使用原生组件;验证未通过时使用 Canvas 自绘输入、搜索或链接录入。
82
82
 
83
83
  > 详细桥接规则、值结构和验收清单见 [native-components-bridge.md](references/native-components-bridge.md)。
84
84
 
@@ -88,35 +88,35 @@ openyida sample yida-canvas-custom-page portal-native-components --output projec
88
88
 
89
89
  1. **Canvas 入口明确**:源码必须导出或返回 `YidaComp`,并把主组件作为默认导出或 `YidaComp` 暴露。
90
90
  2. **发布链路正确**:Canvas 源码使用 `.canvas.jsx` / `.canvas.tsx`,或发布时显式加 `--canvas`。
91
- 3. **源码修改发布闭环**:只要本轮 Write/Edit/Create 了 `project/pages/src/*.canvas.jsx` 或 `project/pages/src/*.canvas.tsx`,本技能的本地校验只证明源码可发布,不等于远端页面已更新;final 前必须看到成功的 `openyida publish <source> <appType> <displayPageFormUuid>`。没有 publish 成功证据时,只能说“Canvas 源码已修改,尚未发布”,不能说“页面已更新 / 已重新发布”。
91
+ 3. **源码修改发布闭环**:本轮 Write/Edit/Create 了 `project/pages/src/*.canvas.jsx` 或 `project/pages/src/*.canvas.tsx` 后,final 前需要成功执行 `openyida publish <source> <appType> <displayPageFormUuid>`。有 publish 成功证据时表述为“页面已发布”;只有本地校验证据时表述为“Canvas 源码已修改,尚未发布”。
92
92
  4. **依赖可加载**:普通 import 只使用 Code Canvas 白名单依赖;宜搭运行态组件走原生组件桥。
93
- 5. **不使用普通页实例契约**:Canvas 中不写 `renderJsx()`、`didMount()`、`this.forceUpdate()`、`this.utils.yida.*`、`this.dataSourceMap`。
93
+ 5. **使用 Canvas 函数组件契约**:Canvas 代码写 `YidaComp` React 函数组件;数据、生命周期和渲染都通过 hooks、props、fetch/连接器完成。需要 `renderJsx()`、`didMount()`、`this.forceUpdate()`、`this.utils.yida.*`、`this.dataSourceMap` 时切到 `yida-custom-page`。
94
94
  6. **副作用清理**:`useEffect` 注册事件、定时器、图表实例时必须返回 cleanup。
95
- 7. **交互控件必须受控且真正驱动数据**:凡是暗示“会改变下方数据/视图”的控件(筛选 `Select`、搜索 `Input`/`Input.Search`、周期切换、`Tabs`/`Segmented`、批量/重置 `Button` 等),禁止只写 `defaultValue` 当装饰。必须用 `useState` 建立受控状态、绑定 `onChange`/`onClick`,并让 `Table`/列表/卡片的数据源经过基于该状态的 `useMemo` 派生过滤后再渲染。**反例(会导致“点了筛选下面数据不变”)**:把固定 `seedRows` 直接喂给 `<Table dataSource={rows} />`,同时摆一排 `defaultValue` 且无 `onChange` 的筛选控件。切换筛选后若可能让当前选中项失效,需回退选中态(如 `selected < filteredRows.length ? selected : 0`)。
95
+ 7. **交互控件必须受控且真正驱动数据**:筛选 `Select`、搜索 `Input`/`Input.Search`、周期切换、`Tabs`/`Segmented`、批量/重置 `Button` 等控件都用 `useState` 建立受控状态,绑定 `onChange`/`onClick`,并让 `Table`/列表/卡片的数据源通过 `useMemo` 按状态派生后渲染。切换筛选后若当前选中项失效,回退选中态(如 `selected < filteredRows.length ? selected : 0`)。
96
96
 
97
97
  ### 重要规则(IMPORTANT)
98
98
 
99
- 1. **数据桥显式化**:通过 fetch、连接器或开放 API 读写数据,避免在页面里硬编码 Cookie、CSRF 或密钥。
100
- 2. **组件增强可降级**:门户、成员、部门、上传组件都要 feature detect 和 fallback,组件缺失时页面不白屏。
101
- 3. **值先归一化**:成员、部门、文件的原始返回值只作为 `raw` 调试,业务 payload 使用统一结构。
102
- 4. **业务页主色跟随应用主题,sample 例外**:真实业务页默认读取 `--color-brand1-*` 与 `--color-group`,避免硬编码蓝色;但 `lib/samples/**` 和官方 sample 展示应用必须自带页面级固定主题(`followRuntimeTheme: false` 或等价 CSS 变量),每个 sample 使用不同色相,不允许被宿主应用主题统一染成黑灰。
99
+ 1. **数据桥显式化**:通过 fetch、连接器或开放 API 读写数据;Cookie、CSRF、密钥和签名留在平台、连接器或后端服务侧。
100
+ 2. **组件增强可降级**:门户、成员、部门、上传组件都做 feature detect 和 fallback;组件缺失时页面仍展示 Canvas 自绘基线。
101
+ 3. **值先归一化**:成员、部门、文件的原始返回值只作为 `raw` 检查信息,业务 payload 使用统一结构。
102
+ 4. **业务页主色跟随应用主题,sample 例外**:真实业务页默认读取 `--color-brand1-*` 与 `--color-group`;`lib/samples/**` 和官方 sample 展示应用自带页面级固定主题(`followRuntimeTheme: false` 或等价 CSS 变量),每个 sample 使用不同色相。
103
103
  5. **先验证再扩展业务**:原生组件、上传、组织搜索、弹层类能力先做 smoke 页面,确认 PC/移动端都可用后再承载复杂业务。
104
- 6. **模板占位符必须可直发**:Canvas sample / generate-page 模板要同时支持“生成器替换变量”和“sample 原样发布”。禁止写 `JSON.parse('{{FEATURES_JSON}}')` 这类裸解析;JSON 占位符必须用 `parseTemplateJson(raw, fallback)`,展示文案占位符必须用 `withFallback` / `applyPageFallbacks` 兜底,确保未替换时不会运行期崩溃,也不会把 `{{BRAND_NAME}}`、`{{HERO_TEXT}}`、`{{OPENYIDA_RESEARCH_LEVEL}}` 等直接显示到页面。
105
- 7. **light 页面禁灰黑主题**:业务列表、协同表、数据管理页、工作台和门户默认不要用 `#111827` 这类近黑色做按钮、描边、选中态或大阴影;主操作、选中态、筛选焦点和批量操作使用品牌色或 sample 自带主题色,边框用浅色品牌混合。只有用户明确要求暗色大屏/夜间模式/高对比风格时才使用深色主视觉。
106
- 8. **门户运行态组件要补必需 props 和局部降级**:`QuickAccessCard` / `RecentlyUsedCard` 必须传 `theme="row-white"` 等必需 props,避免运行态读取 `theme.includes(...)` 报错;所有门户/字段/上传增强组件外层加局部 ErrorBoundary,单个组件不兼容时只降级该块,不让整页进入 Canvas 错误态。
107
- 9. **自定义主题必须页面内注入**:`--theme` 只接受平台预置 key;如果页面设计使用非预置主题(例如活力橙、深玫红、自定义暗黑金),Canvas 页面必须在自身源码中注入 `style#yida-global-theme` 或等价 scoped CSS vars,并在根节点设置 `data-theme-scope="page"`。官方 sample 每个页面都要做,避免宿主应用 `black` 主题把页面染成黑灰。
108
- 10. **真实交付不使用前端 seed 冒充业务数据**:`openyida sample` 原样发布可以保留 sample/seed 数据,但必须在页面上标注为 sample/seed。完整应用或真实交付页只要需要列表、看板、详情记录,并且本轮已经创建/解析业务表单,就必须在 `page-spec.json` 写入 `dataBinding.mode=form`、真实 `appType/formUuid` 和字段映射,让页面从表单读取。若需要演示数据,先通过表单数据写入链路创建 demo/mock records,再由 Canvas 读取这些真实表单记录;未写入 demo records 且没有真实数据时展示空态、表单入口、刷新/登记按钮。
109
- 11. **页面生成二选一**:选择模板路径时,`openyida generate-page ... --spec ... --compile` 之后只读取 CLI 摘要或 `.openyida-page.json` 判断 `domainFidelity` / dataBinding,并对生成源码做小范围 Edit/patch;禁止立刻 Read 大段源码后全量 Write 覆盖同一路径。选择手写路径时,直接 Write 最终 `.canvas.jsx` 并快检/发布,不要先跑 `generate-page` 再完全覆盖。
110
- 12. **Canvas 产物硬禁 emoji**:`.canvas.jsx` 源码、模板 spec 会渲染到页面的文案、JS 注释、数据常量和产物文件路径都不能包含 emoji。`generate-page --compile`、`compileCanvasLocal` 或 `publish` 报 emoji 错误时,先改 spec/源码/路径;不要尝试跳过 lint 或重复发布。
104
+ 6. **模板占位符必须可直发**:Canvas sample / generate-page 模板同时支持“生成器替换变量”和“sample 原样发布”。JSON 占位符用 `parseTemplateJson(raw, fallback)`,展示文案占位符用 `withFallback` / `applyPageFallbacks` 兜底,未替换时页面继续可运行,并显示业务化 fallback 文案。
105
+ 7. **light 页面使用清爽业务色**:业务列表、协同表、数据管理页、工作台和门户默认使用 light 模式;主操作、选中态、筛选焦点和批量操作使用品牌色或 sample 自带主题色,边框用浅色品牌混合。用户明确要求暗色大屏/夜间模式/高对比风格时使用深色主视觉。
106
+ 8. **门户运行态组件要补必需 props 和局部降级**:`QuickAccessCard` / `RecentlyUsedCard` `theme="row-white"` 等必需 props;所有门户/字段/上传增强组件外层加局部 ErrorBoundary,单个组件不兼容时只降级该块,整页保持可用。
107
+ 9. **自定义主题必须页面内注入**:`--theme` 只接受平台预置 key;页面设计使用非预置主题(例如活力橙、深玫红、自定义暗黑金)时,在 Canvas 源码中注入 `style#yida-global-theme` 或等价 scoped CSS vars,并在根节点设置 `data-theme-scope="page"`。官方 sample 每个页面都做页面级主题注入。
108
+ 10. **真实交付使用真实数据源**:`openyida sample` 原样发布可以保留 sample/seed 数据,并在页面上标注为 sample/seed。完整应用或真实交付页只要需要列表、看板、详情记录,并且本轮已经创建/解析业务表单,就在 `page-spec.json` 写入 `dataBinding.mode=form`、真实 `appType/formUuid` 和字段映射,让页面从表单读取。需要演示数据时,先通过表单数据写入链路创建 demo/mock records,再由 Canvas 读取这些真实表单记录;没有真实数据时展示空态、表单入口、刷新/登记按钮。
109
+ 11. **页面生成二选一**:模板路径先写 `page-spec.json`,执行 `openyida generate-page ... --spec ... --compile`,之后读取 CLI 摘要或 `.openyida-page.json` 判断 `domainFidelity` / dataBinding,并对生成源码做小范围 Edit/patch。手写路径直接 Write 最终 `.canvas.jsx` 并快检/发布。
110
+ 12. **Canvas 产物使用纯文本业务文案**:`.canvas.jsx` 源码、模板 spec 会渲染到页面的文案、JS 注释、数据常量和产物文件路径都使用无 emoji 文本。`generate-page --compile`、`compileCanvasLocal` 或 `publish` 报 emoji 错误时,先改 spec/源码/路径,再重新校验发布。
111
111
 
112
112
  ## 数据真实性边界
113
113
 
114
114
  Canvas 模板有两种允许状态:
115
115
 
116
116
  - **Sample / 离线预览**:`openyida sample` 或模板原样发布可以显示内置 seedRows,页面必须标注 `sample/seed`,final 也要说明“当前为演示数据/未接真实表单数据”。
117
- - **完整应用 / 真实交付**:先解析真实 `appType/formUuid/fieldId`,写入 `page-spec.json` 的 `dataBinding.mode=form` 后再 `openyida generate-page ... --spec <page-spec.json>`。需要 demo/mock 记录时,先用数据写入链路把记录写入表单并抽查,再让页面读取;不能把前端 seedRows、静态 DEFAULT_FEATURES 或固定指标说成真实业务数据。
117
+ - **完整应用 / 真实交付**:先解析真实 `appType/formUuid/fieldId`,写入 `page-spec.json` 的 `dataBinding.mode=form` 后再 `openyida generate-page ... --spec <page-spec.json>`。需要 demo/mock 记录时,先用数据写入链路把记录写入表单并抽查,再让页面读取;前端 seedRows、静态 DEFAULT_FEATURES 或固定指标只作为 sample 数据标注。
118
118
 
119
- 生成后如果 `.openyida-page.json` 的 `dataBinding.enabled !== true`,且页面仍展示列表/看板/详情业务记录,只能标为 sample/draft;完整应用 final 不得说“已接真实数据”。未接数据的交付页应保留真实空态、登记入口、刷新按钮和数据接入提示。
119
+ 生成后如果 `.openyida-page.json` 的 `dataBinding.enabled !== true`,且页面仍展示列表/看板/详情业务记录,交付状态标为 sample/draft;完整应用 final 只有在真实数据绑定已启用并验证后表述为“已接真实数据”。未接数据的交付页保留真实空态、登记入口、刷新按钮和数据接入提示。
120
120
 
121
121
  ## 模板占位符防回归
122
122
 
@@ -128,9 +128,9 @@ Canvas 模板有两条真实使用链路:
128
128
  因此模板源码必须满足:
129
129
 
130
130
  - 原始 sample 经过 `compileCanvasLocal` 能通过。
131
- - 原始 sample 执行 `YidaComp()` 不应抛错。
132
- - 可见渲染内容不得泄漏 `{{...}}` 占位符。
133
- - 源码不得包含 `JSON.parse('{{...}}')`;用安全解析函数接默认数据。
131
+ - 原始 sample 执行 `YidaComp()` 能正常返回组件。
132
+ - 可见渲染内容显示业务化文案或 fallback 文案。
133
+ - JSON 占位符使用安全解析函数接默认数据。
134
134
 
135
135
  改 Canvas sample 后运行:
136
136
 
@@ -138,27 +138,26 @@ Canvas 模板有两条真实使用链路:
138
138
  npx jest tests/canvas-compile.test.js tests/generate-page.test.js --runInBand
139
139
  ```
140
140
 
141
- ## Sample 改造沉淀纪律
141
+ ## Sample 质量规则
142
142
 
143
- 批量优化 `lib/samples/**` 或官方 sample 展示应用时,不能只按当前模板微调颜色。每个 sample 都要形成可复用经验:
143
+ 批量优化 `lib/samples/**` 或官方 sample 展示应用时,按以下质量规则执行:
144
144
 
145
- - **先读复盘规范**:如果用户要求总结上轮经验、继续优化官方 sample、同步到官方 Samples 应用,或多次基于截图纠正页面质量,先读全局 [任务复盘与沉淀规范](../../references/task-retrospective.md),再判断要补模板、测试、CLI 还是 skill。
146
- - **先看参考再动手**:用户要求“高级、Dribbble、好看、像产品/官网/详情页/数据表”时,先参考 Dribbble 的同类构图和免费可商用素材站的真实图片,再抽象成布局、层次、色彩和数据密度原则;不要照搬单个作品。
147
- - **说清参考转译**:交付 sample 改造时要用 1-2 句话说明参考被转译成了什么,例如“详情页采用对象 hero + sticky 元信息 + 时间线结构”、“数据管理页采用多维表工具栏 + 分组行 + 彩色标签密集表格”。不要只说“已参考 Dribbble”。
145
+ - **先看参考再动手**:用户要求“高级、Dribbble、好看、像产品/官网/详情页/数据表”时,先参考 Dribbble 的同类构图和免费可商用素材站的真实图片,再抽象成布局、层次、色彩和数据密度原则,并转译为当前业务页面。
146
+ - **说清参考转译**:交付 sample 改造时要用 1-2 句话说明参考被转译成了什么,例如“详情页采用对象 hero + sticky 元信息 + 时间线结构”、“数据管理页采用多维表工具栏 + 分组行 + 彩色标签密集表格”。
148
147
  - **每页独立主题**:sample 页默认 `themeScope=page` 或等价固定 CSS 变量;业务列表、详情、门户、工作台、官网、数据管理、大屏要有不同色相和不同信息节奏,不被宿主应用主题统一染色。
149
148
  - **非预置主题不走 `--theme`**:`deepBlue/podBlue/.../black` 这些平台 key 才能传给 `--theme`;自己设计的主题色要写到页面 `style#yida-global-theme` / scoped token 中,并确保每个 sample 页面都有这段注入。
150
- - **Sample 数据要像真实业务,但不能冒充真实交付数据**:列表、详情、数据管理、工作台、大屏 sample 必须模拟足够丰富的数据、状态、筛选、趋势、分组、时间线或指标,不要只放 3 个卡片和空泛文案;完整应用/真实交付页必须优先接 `dataBinding.mode=form` 或展示真实空态,不把 sample seed 当业务主列表。
151
- - **工作台不是 demo 壳**:工作台页面要铺满应用内容区,侧栏/导航/主面板形成真实产品首页;禁止用 `max-width + margin: 0 auto + 外层 padding` 做居中展示框,也不要把 `dribbble research`、`sample`、`workbench + operation` 这类设计过程词露给用户。
152
- - **数据大屏地图要稳定**:大屏中心态势图如果是地图,不能只依赖外部 ECharts CDN / GeoJSON 成功后才显示;优先探测宜搭宿主地图组件(如 `YoushuMap` / `ChinaMap` / `MapChart` 等),并提供内置区域地图组件兜底。禁止把“地图组件暂不可用”作为正常展示态暴露给用户。
153
- - **截图问题要反哺模板**:用户指出导航未覆盖、地图抽象、颜色不好、内容不丰富、产品首页不像首页等问题时,不只修当前 JSX;如果属于模板共性,补到 sample 模板、测试或本技能规则。
154
- - **官网实景化不是只换 Hero**:强视觉官网至少形成“场景 Hero + 产品/服务 + 过程/空间”的摄影故事,品牌色从真实材质提取,section 要覆盖真实产品、制作/服务过程与到店/使用情境。具体按 `yida-page-uiux/references/landing/realistic-brand-homepage.md` 执行。
155
- - **交互要真的联动数据**:改完带筛选/搜索/切换的 sample,必须实际验证“改筛选下方列表/表格/卡片数据发生变化”,不能只看 `compileCanvasLocal` 编译通过。装饰性 `defaultValue` 控件(无 `onChange`、数据源恒为固定 `seedRows`)视为缺陷,等同“按钮点了没反应”。
149
+ - **Sample 数据要像真实业务,真实交付要接真实数据**:列表、详情、数据管理、工作台、大屏 sample 模拟足够丰富的数据、状态、筛选、趋势、分组、时间线或指标;完整应用/真实交付页优先接 `dataBinding.mode=form`,未接入时展示真实空态。
150
+ - **工作台使用真实产品首页结构**:工作台页面铺满应用内容区,侧栏/导航/主面板形成真实产品首页;设计过程词不出现在可见页面。
151
+ - **数据大屏地图要稳定**:大屏中心态势图如果是地图,优先探测宜搭宿主地图组件(如 `YoushuMap` / `ChinaMap` / `MapChart` 等),并提供内置区域地图组件兜底;正常展示态呈现地图、区域态势或业务空态。
152
+ - **截图验收要覆盖模板共性**:导航覆盖、地图表现、配色、内容丰富度、产品首页结构等属于模板共性时,同步补 sample 模板、测试或本技能规则。
153
+ - **官网实景化覆盖完整品牌旅程**:强视觉官网至少形成“场景 Hero + 产品/服务 + 过程/空间”的摄影故事,品牌色从真实材质提取,section 覆盖真实产品、制作/服务过程与到店/使用情境。具体按 `yida-page-uiux/references/landing/realistic-brand-homepage.md` 执行。
154
+ - **交互要真的联动数据**:改完带筛选/搜索/切换的 sample,实际验证“改筛选下方列表/表格/卡片数据发生变化”。控件使用受控状态、`onChange`/`onClick` 和派生数据源。
156
155
  - **线上发布后回读**:发布到官方 sample 应用后,用 `get-schema` 回读确认 `YidaCodeCanvas/runtimeCode` 已更新,必要时检查页面 class/关键文案/关键区块存在。
157
- - **CLI 缺口要落盘**:如果改造过程中发现 CLI 行为“回包成功但未生效”、缺少 sample 注册、缺少模板类型或测试覆盖,应优先补 CLI/测试,而不是只用一次性脚本绕过。
156
+ - **CLI 能力缺口要补齐**:sample 注册、模板类型、发布生效、测试覆盖等 CLI 共性缺口优先补 CLI/测试。
158
157
 
159
158
  ## 开发流程
160
159
 
161
- 下面命令以仓库根为视角;如果当前 cwd 已经是 `<workspace>/project`,把 `project/pages/src/...` 改成 `pages/src/...`。读取生成文件、Schema 或校验产物时优先用宿主 Read / Glob / Grep,不要在 CLI 成功后 Bash `cat`/`ls` 复核。
160
+ 下面命令以仓库根为视角;如果当前 cwd 已经是 `<workspace>/project`,把 `project/pages/src/...` 改成 `pages/src/...`。读取生成文件、Schema 或校验产物时优先用宿主 Read / Glob / Grep
162
161
 
163
162
  ```bash
164
163
  # 1. 只读检查环境和登录态;真实创建资源前必须通过
@@ -187,9 +186,9 @@ openyida publish project/pages/src/<页面名>.canvas.jsx <appType> <formUuid>
187
186
  openyida get-schema <appType> <formUuid> --field-map-json
188
187
  ```
189
188
 
190
- `openyida check-page` / `openyida compile` 当前面向普通自定义页面 `.oyd.jsx` / `.jsx`;Canvas 以 `compileCanvasLocal` 和 `openyida publish .canvas.jsx` 的 Canvas 编译阶段为准。`compileCanvasLocal` 是发布前快检,不能替代 `openyida publish` 的远端写入证据。
189
+ `openyida check-page` / `openyida compile` 当前面向普通自定义页面 `.oyd.jsx` / `.jsx`;Canvas 以 `compileCanvasLocal` 和 `openyida publish .canvas.jsx` 的 Canvas 编译阶段为准。`compileCanvasLocal` 是发布前快检,`openyida publish` 是远端写入证据。
191
190
 
192
- 如需保存完整 Schema,使用 create_file / Write / file edit tool 创建 `<projectRoot>/.cache/openyida/<页面名或任务名>/<页面名>-schema.json`;从 workspace 根执行后续命令时路径加 `project/` 前缀。不要把 `openyida` stdout 通过 shell 重定向保存成 JSON。
191
+ 如需保存完整 Schema,使用 create_file / Write / file edit tool 创建 `<projectRoot>/.cache/openyida/<页面名或任务名>/<页面名>-schema.json`;从 workspace 根执行后续命令时路径加 `project/` 前缀。
193
192
 
194
193
  ## 模板速查
195
194
 
@@ -211,11 +210,10 @@ openyida get-schema <appType> <formUuid> --field-map-json
211
210
  | --- | --- | --- |
212
211
  | [page-generation-guide.md](references/page-generation-guide.md) | 模板路由、官网素材、themeScope、Page Spec、primitives | 生成页面前必读 |
213
212
  | [native-components-bridge.md](references/native-components-bridge.md) | 门户、成员、部门、上传组件桥接和值归一化 | 需要宜搭运行态组件时必读 |
214
- | [dependencies-and-cdn.md](references/dependencies-and-cdn.md) | 依赖白名单、windowAlias、CDN、antd/dayjs 问题 | 新增依赖或排查依赖加载时必读 |
213
+ | [dependencies-and-cdn.md](references/dependencies-and-cdn.md) | 依赖白名单、windowAlias、CDN 加载契约 | 新增依赖或验证依赖加载时必读 |
215
214
  | [employeefield-verification.md](references/employeefield-verification.md) | 运行时事实、原生组件验证、EmployeeField 验收 | 验证成员/字段组件时阅读 |
216
215
  | [data-bridge-guide.md](references/data-bridge-guide.md) | Canvas 内自建 HTTP 数据桥 | 接入真实数据时阅读 |
217
216
  | [canvas-design-system.md](references/canvas-design-system.md) | App 主题色、antd token、控件焦点/下拉 reset、图表配色 | 写样式和主题时阅读 |
218
217
  | [component-library-guide.md](references/component-library-guide.md) | 开源组件库推荐组合和禁用清单 | 选择 UI/图表依赖时阅读 |
219
218
  | [canvas-authoring-examples.md](references/canvas-authoring-examples.md) | 最小组件、hooks、副作用、图表示例 | 手写 Canvas 代码时阅读 |
220
219
  | [真实品牌官网 Playbook](../yida-page-uiux/references/landing/realistic-brand-homepage.md) | 实景素材组、材质配色、品牌旅程、Sample 无 CDN 兜底和视觉验收 | 生成或改造强视觉官网时必读 |
221
- | [任务复盘与沉淀规范](../../references/task-retrospective.md) | 官方 sample 改造经验、Dribbble 转译、页面级主题、发布回读、CLI/skill 反哺 | sample 批量优化、截图纠错、用户要求总结经验时阅读 |
@@ -2,7 +2,7 @@
2
2
 
3
3
  本文件是从零写 Code Canvas 页面的 vetted 模板集。所有示例都遵守运行时事实:`YidaComp` 是普通 React18 函数组件,必须**导出或返回** `YidaComp` / `YidaComp.default`;只 `import` 依赖白名单内的包;副作用在 `useEffect` 里注册并返回 cleanup。
4
4
 
5
- > 白名单、windowAlias、CDNantd/dayjs 陷阱见 [dependencies-and-cdn.md](dependencies-and-cdn.md);读写宜搭数据见 [data-bridge-guide.md](data-bridge-guide.md)。
5
+ > 白名单、windowAlias 与 CDN 加载契约见 [dependencies-and-cdn.md](dependencies-and-cdn.md);读写宜搭数据见 [data-bridge-guide.md](data-bridge-guide.md)。
6
6
 
7
7
  ## 1. 最小可运行组件(入口 + 本地状态)
8
8
 
@@ -60,7 +60,7 @@ export default YidaComp;
60
60
 
61
61
  ## 3. 可视化:recharts 图表
62
62
 
63
- `recharts` 在白名单内(windowAlias `Recharts`)。标准 `import` 即可,CLI 本地编译会把它计入 `importedModules`。图表容器给定高度,避免 0 高度不渲染。图表颜色是 JS 传给库的字符串,**不能硬编码蓝**——用 `readBrandColor` 读平台品牌色,让线条跟随 App 主题(见 [canvas-design-system.md](canvas-design-system.md))。
63
+ `recharts` 在白名单内(windowAlias `Recharts`)。标准 `import` 即可,CLI 本地编译会把它计入 `importedModules`。图表容器给定高度,保证首屏可渲染。图表颜色是 JS 传给库的字符串,用 `readBrandColor` 读平台品牌色,让线条跟随 App 主题(见 [canvas-design-system.md](canvas-design-system.md))。
64
64
 
65
65
  ```jsx
66
66
  import React from 'react';