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 +2 -0
- package/bin/yida.js +12 -0
- package/lib/core/command-manifest.js +27 -0
- package/lib/core/locales/en.js +2 -0
- package/lib/core/locales/zh.js +2 -0
- package/lib/core/utils.js +12 -0
- package/lib/document/document-markdown.js +113 -0
- package/lib/document/tingji.js +61 -0
- package/package.json +1 -1
- package/yida-skills/SKILL.md +3 -1
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +45 -47
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +2 -2
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-design-system.md +11 -11
- package/yida-skills/skills/yida-canvas-custom-page/references/component-library-guide.md +14 -14
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +23 -32
- package/yida-skills/skills/yida-canvas-custom-page/references/dependencies-and-cdn.md +6 -26
- package/yida-skills/skills/yida-canvas-custom-page/references/employeefield-verification.md +52 -23
- package/yida-skills/skills/yida-canvas-custom-page/references/native-components-bridge.md +22 -22
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +10 -10
- package/yida-skills/skills/yida-document-markdown/SKILL.md +46 -0
- package/yida-skills/skills/yida-tingji/SKILL.md +44 -0
- package/yida-skills/skills-index.json +29 -0
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' }),
|
package/lib/core/locales/en.js
CHANGED
|
@@ -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',
|
package/lib/core/locales/zh.js
CHANGED
|
@@ -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
package/yida-skills/SKILL.md
CHANGED
|
@@ -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` |
|
|
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 /
|
|
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
|
-
-
|
|
16
|
-
-
|
|
15
|
+
- 首版页面生成:官网、看板、工作台、列表、详情、门户壳。
|
|
16
|
+
- 需要 React18 函数组件、状态隔离和现代前端体验的页面。
|
|
17
17
|
- 只需要通过 HTTP / 连接器读写数据的页面。
|
|
18
18
|
- 需要在 Canvas 内受控接入门户、成员、部门、上传等宜搭运行态组件的页面。
|
|
19
19
|
|
|
20
|
-
|
|
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
|
|
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
|
-
-
|
|
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` 实例桥 |
|
|
44
|
-
|
|
|
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`
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
100
|
-
2.
|
|
101
|
-
3. **值先归一化**:成员、部门、文件的原始返回值只作为 `raw`
|
|
102
|
-
4. **业务页主色跟随应用主题,sample 例外**:真实业务页默认读取 `--color-brand1-*` 与 `--color-group
|
|
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
|
|
105
|
-
7. **light
|
|
106
|
-
8. **门户运行态组件要补必需 props 和局部降级**:`QuickAccessCard` / `RecentlyUsedCard`
|
|
107
|
-
9. **自定义主题必须页面内注入**:`--theme` 只接受平台预置 key
|
|
108
|
-
10.
|
|
109
|
-
11.
|
|
110
|
-
12. **Canvas
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
143
|
+
批量优化 `lib/samples/**` 或官方 sample 展示应用时,按以下质量规则执行:
|
|
144
144
|
|
|
145
|
-
-
|
|
146
|
-
-
|
|
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
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
- **交互要真的联动数据**:改完带筛选/搜索/切换的 sample
|
|
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
|
|
156
|
+
- **CLI 能力缺口要补齐**:sample 注册、模板类型、发布生效、测试覆盖等 CLI 共性缺口优先补 CLI/测试。
|
|
158
157
|
|
|
159
158
|
## 开发流程
|
|
160
159
|
|
|
161
|
-
下面命令以仓库根为视角;如果当前 cwd 已经是 `<workspace>/project`,把 `project/pages/src/...` 改成 `pages/src/...`。读取生成文件、Schema 或校验产物时优先用宿主 Read / Glob / Grep
|
|
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`
|
|
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/`
|
|
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
|
|
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
|
|
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
|
|
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';
|