openyida 1.0.0-beta.2 → 1.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/bin/yida.js +60 -1
- package/lib/copy.js +112 -29
- package/lib/create-form.js +240 -23
- package/lib/doctor.js +1503 -0
- package/lib/env.js +2 -0
- package/lib/locales/en.js +6 -0
- package/lib/locales/ja.js +6 -0
- package/lib/locales/zh.js +6 -0
- package/lib/publish.js +1 -1
- package/lib/utils.js +2 -1
- package/package.json +1 -1
- package/yida-skills/SKILL.md +1 -1
- package/yida-skills/skills/yida-create-form-page/SKILL.md +17 -4
package/README.md
CHANGED
|
@@ -156,9 +156,13 @@ npx clawhub@latest install nicky1108/yida-app
|
|
|
156
156
|
感谢所有为 OpenYida 做出贡献的开发者!欢迎阅读 [贡献指南](./CONTRIBUTING.md) 参与共建。
|
|
157
157
|
|
|
158
158
|
<p align="left">
|
|
159
|
-
<a href="https://github.com/yize"><img src="https://avatars.githubusercontent.com/u/1578814?v=4&s=48" width="48" height="48" alt="
|
|
160
|
-
<a href="https://github.com/alex-mm"><img src="https://avatars.githubusercontent.com/u/3302053?v=4&s=48" width="48" height="48" alt="
|
|
159
|
+
<a href="https://github.com/yize"><img src="https://avatars.githubusercontent.com/u/1578814?v=4&s=48" width="48" height="48" alt="九神" title="九神"/></a>
|
|
160
|
+
<a href="https://github.com/alex-mm"><img src="https://avatars.githubusercontent.com/u/3302053?v=4&s=48" width="48" height="48" alt="天晟" title="天晟"/></a>
|
|
161
161
|
<a href="https://github.com/nicky1108"><img src="https://avatars.githubusercontent.com/u/4279283?v=4&s=48" width="48" height="48" alt="nicky1108" title="nicky1108"/></a>
|
|
162
|
+
<a href="https://github.com/angelinheys"><img src="https://avatars.githubusercontent.com/u/49426983?v=4&s=48" width="48" height="48" alt="angelinheys" title="angelinheys"/></a>
|
|
163
|
+
<a href="https://github.com/yipengmu"><img src="https://avatars.githubusercontent.com/u/3232735?v=4&s=48" width="48" height="48" alt="yipengmu" title="yipengmu"/></a>
|
|
164
|
+
<a href="https://github.com/Waawww"><img src="https://avatars.githubusercontent.com/u/31886449?v=4&s=48" width="48" height="48" alt="Waawww" title="Waawww"/></a>
|
|
165
|
+
<a href="https://github.com/kangjiano"><img src="https://avatars.githubusercontent.com/u/54129385?v=4&s=48" width="48" height="48" alt="kangjiano" title="kangjiano"/></a>
|
|
162
166
|
</p>
|
|
163
167
|
|
|
164
168
|
---
|
package/bin/yida.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* openyida org switch --corp-id <corpId> 切换组织(无需重新登录)
|
|
19
19
|
* openyida create-app "<名称>" [desc] [icon] [color] 创建应用
|
|
20
20
|
* openyida create-page <appType> "<页面名>" 创建自定义页面
|
|
21
|
-
* openyida create-form create <appType> "<表单名>" <字段JSON> 创建表单页面
|
|
21
|
+
* openyida create-form create <appType> "<表单名>" <字段JSON> [--layout <布局>] [--theme <主题>] [--label-align <对齐>] 创建表单页面
|
|
22
22
|
* openyida create-form update <appType> <formUuid> <修改JSON> 更新表单页面
|
|
23
23
|
* openyida get-schema <appType> <formUuid> 获取表单 Schema
|
|
24
24
|
* openyida publish <源文件路径> <appType> <formUuid> 编译并发布自定义页面
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
* openyida save-share-config <appType> <formUuid> <url> <isOpen> [openAuth] 保存公开访问/分享配置
|
|
27
27
|
* openyida get-page-config <appType> <formUuid> 查询页面公开访问/分享配置
|
|
28
28
|
* openyida update-form-config <appType> <formUuid> <isRenderNav> <title> 更新表单配置
|
|
29
|
+
* openyida doctor [选项] 检查环境依赖,诊断应用问题
|
|
29
30
|
* openyida export <appType> [output] 导出应用所有表单 Schema(生成迁移包)
|
|
30
31
|
* openyida import <file> [name] 导入迁移包,在目标环境重建应用
|
|
31
32
|
*/
|
|
@@ -43,6 +44,58 @@ const command = process.argv[2];
|
|
|
43
44
|
const args = process.argv.slice(3);
|
|
44
45
|
|
|
45
46
|
function printHelp() {
|
|
47
|
+
console.log(`
|
|
48
|
+
openyida - 宜搭命令行工具
|
|
49
|
+
|
|
50
|
+
用法:
|
|
51
|
+
openyida <命令> [参数...](别名:yida)
|
|
52
|
+
|
|
53
|
+
命令:
|
|
54
|
+
env 检测当前 AI 工具环境和登录态
|
|
55
|
+
copy [--force] 复制 project 工作目录到当前 AI 工具环境
|
|
56
|
+
login 登录态管理(优先缓存,否则扫码)
|
|
57
|
+
logout 退出登录 / 切换账号
|
|
58
|
+
create-app "<名称>" [描述] [图标] [颜色] 创建应用,输出 appType
|
|
59
|
+
create-page <appType> "<页面名>" 创建自定义页面,输出 pageId
|
|
60
|
+
create-form create <appType> "<表单名>" <字段JSON> [--layout <布局>] [--theme <主题>] [--label-align <对齐>] 创建表单页面
|
|
61
|
+
create-form update <appType> <formUuid> <修改JSON> 更新表单页面
|
|
62
|
+
get-schema <appType> <formUuid> 获取表单 Schema
|
|
63
|
+
publish <源文件路径> <appType> <formUuid> 编译并发布自定义页面
|
|
64
|
+
verify-short-url <appType> <formUuid> <url> 验证短链接 URL 是否可用
|
|
65
|
+
save-share-config <appType> <formUuid> <url> <isOpen> [auth] 保存公开访问/分享配置
|
|
66
|
+
get-page-config <appType> <formUuid> 查询页面公开访问/分享配置
|
|
67
|
+
update-form-config <appType> <formUuid> <isRenderNav> <title> 更新表单配置
|
|
68
|
+
doctor [选项] 检查环境依赖,诊断应用问题
|
|
69
|
+
--fix / --repair 诊断并自动修复
|
|
70
|
+
--production --app <appId> 线上应用诊断
|
|
71
|
+
--monitor 启动实时健康度监控
|
|
72
|
+
--report <format> 生成诊断报告(json | markdown | html)
|
|
73
|
+
--create-ticket 根据诊断结果创建工单
|
|
74
|
+
--create-voc 创建 VOC(需求反馈)
|
|
75
|
+
--auto-submit 自动判断并提交工单或 VOC
|
|
76
|
+
|
|
77
|
+
示例:
|
|
78
|
+
openyida login
|
|
79
|
+
openyida logout
|
|
80
|
+
openyida create-app "考勤管理"
|
|
81
|
+
openyida create-page APP_XXX "游戏主页"
|
|
82
|
+
openyida create-form create APP_XXX "员工信息" fields.json
|
|
83
|
+
openyida create-form update APP_XXX FORM-XXX '[{"action":"add","field":{"type":"TextField","label":"备注"}}]'
|
|
84
|
+
openyida get-schema APP_XXX FORM-XXX
|
|
85
|
+
openyida publish pages/src/home.jsx APP_XXX FORM-XXX
|
|
86
|
+
openyida verify-short-url APP_XXX FORM-XXX /o/myapp
|
|
87
|
+
openyida save-share-config APP_XXX FORM-XXX /o/myapp y n
|
|
88
|
+
openyida get-page-config APP_XXX FORM-XXX
|
|
89
|
+
openyida update-form-config APP_XXX FORM-XXX false "页面标题"
|
|
90
|
+
openyida doctor 完整诊断
|
|
91
|
+
openyida doctor --fix 诊断并自动修复
|
|
92
|
+
openyida doctor --production --app APP_XXX 线上应用诊断
|
|
93
|
+
openyida doctor --monitor 实时监控
|
|
94
|
+
openyida doctor --report markdown 生成 Markdown 报告
|
|
95
|
+
openyida doctor --create-ticket 创建工单
|
|
96
|
+
openyida doctor --create-voc 创建 VOC
|
|
97
|
+
openyida doctor --auto-submit 自动判断并提交
|
|
98
|
+
`);
|
|
46
99
|
console.log(t('cli.help'));
|
|
47
100
|
}
|
|
48
101
|
|
|
@@ -304,6 +357,12 @@ async function main() {
|
|
|
304
357
|
break;
|
|
305
358
|
}
|
|
306
359
|
|
|
360
|
+
case 'doctor': {
|
|
361
|
+
const { run } = require('../lib/doctor');
|
|
362
|
+
await run(args);
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
|
|
307
366
|
case 'export': {
|
|
308
367
|
if (args.length < 1) {
|
|
309
368
|
console.error(t('cli.export_usage'));
|
package/lib/copy.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* openyida copy → 复制 project/ 目录模板(默认,合并模式)
|
|
6
6
|
* openyida copy --force → 复制 project/ 目录模板(强制覆盖,先清空目标目录)
|
|
7
7
|
* openyida copy -skills → 创建 yida-skills/ 软链接(如果存在实际目录则先删除)
|
|
8
|
+
* 悟空环境下:删除已有软链(悟空通过手动上传技能,不需要软链)
|
|
8
9
|
* openyida copy -project → 复制 project/ 目录模板(与默认行为相同,显式指定)
|
|
9
10
|
* openyida copy -project --force → 复制 project/ 目录模板(强制覆盖)
|
|
10
11
|
*
|
|
@@ -16,7 +17,12 @@
|
|
|
16
17
|
*
|
|
17
18
|
* project/ 合并模式(默认):已存在的文件强制覆盖,目标目录中多余的文件保留不动
|
|
18
19
|
* project/ 强制模式(--force):先清空目标目录,再完整复制
|
|
19
|
-
* yida-skills
|
|
20
|
+
* yida-skills/(非悟空):始终创建软链接,如目标存在实际目录则先删除
|
|
21
|
+
* yida-skills/(悟空):删除已有软链或目录(悟空通过手动上传技能,不需要软链)
|
|
22
|
+
*
|
|
23
|
+
* Windows 兼容说明:
|
|
24
|
+
* - 软链接在 Windows 上需要管理员权限或开发者模式,失败时自动降级为目录复制
|
|
25
|
+
* - 路径分隔符统一使用 path.join 处理
|
|
20
26
|
*/
|
|
21
27
|
|
|
22
28
|
"use strict";
|
|
@@ -94,15 +100,22 @@ function forceCopyDir(sourceDir, destDir) {
|
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
103
|
+
* 删除已有的 yida-skills 软链接或目录(悟空环境专用)。
|
|
104
|
+
* 悟空通过手动上传技能,不需要软链,执行 -skills 时只做清理。
|
|
105
|
+
* 使用 lstatSync 而非 existsSync,可以检测到悬空软链(目标不存在但链接本身存在)。
|
|
106
|
+
* @returns {boolean} 是否执行了删除操作
|
|
99
107
|
*/
|
|
100
|
-
function
|
|
101
|
-
|
|
108
|
+
function removeSkillsLink(destLink) {
|
|
109
|
+
let stats;
|
|
110
|
+
try {
|
|
111
|
+
stats = fs.lstatSync(destLink);
|
|
112
|
+
} catch {
|
|
113
|
+
// 路径不存在(包括悬空软链也不存在的情况)
|
|
114
|
+
console.log(t("copy.wukong_skills_not_found", destLink));
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
102
117
|
|
|
103
|
-
|
|
104
|
-
if (fs.existsSync(destLink)) {
|
|
105
|
-
const stats = fs.lstatSync(destLink);
|
|
118
|
+
try {
|
|
106
119
|
if (stats.isSymbolicLink()) {
|
|
107
120
|
fs.unlinkSync(destLink);
|
|
108
121
|
console.log(t("copy.symlink_removed", destLink));
|
|
@@ -113,20 +126,69 @@ function createSymlink(sourceDir, destLink) {
|
|
|
113
126
|
fs.unlinkSync(destLink);
|
|
114
127
|
console.log(t("copy.removed", destLink));
|
|
115
128
|
}
|
|
129
|
+
return true;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error(t("copy.remove_failed", destLink, error.message));
|
|
132
|
+
return false;
|
|
116
133
|
}
|
|
134
|
+
}
|
|
117
135
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
/**
|
|
137
|
+
* 创建软链接:如果目标存在实际目录则先删除,再创建软链接。
|
|
138
|
+
* Windows 上软链需要管理员权限或开发者模式,失败时自动降级为目录复制。
|
|
139
|
+
* @returns {boolean} 是否成功创建
|
|
140
|
+
*/
|
|
141
|
+
function createSymlink(sourceDir, destLink) {
|
|
142
|
+
if (!fs.existsSync(sourceDir)) return false;
|
|
143
|
+
|
|
144
|
+
// 如果目标已存在,判断是目录还是软链接
|
|
145
|
+
if (fs.existsSync(destLink)) {
|
|
146
|
+
try {
|
|
147
|
+
const stats = fs.lstatSync(destLink);
|
|
148
|
+
if (stats.isSymbolicLink()) {
|
|
149
|
+
fs.unlinkSync(destLink);
|
|
150
|
+
console.log(t("copy.symlink_removed", destLink));
|
|
151
|
+
} else if (stats.isDirectory()) {
|
|
152
|
+
fs.rmSync(destLink, { recursive: true, force: true });
|
|
153
|
+
console.log(t("copy.dir_deleted", destLink));
|
|
154
|
+
} else {
|
|
155
|
+
fs.unlinkSync(destLink);
|
|
156
|
+
console.log(t("copy.removed", destLink));
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error(t("copy.remove_failed", destLink, error.message));
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Windows 上 junction 只支持目录,且需要管理员权限或开发者模式
|
|
165
|
+
// 失败时降级为目录复制
|
|
166
|
+
const symlinkType = process.platform === "win32" ? "junction" : "dir";
|
|
167
|
+
try {
|
|
168
|
+
fs.symlinkSync(sourceDir, destLink, symlinkType);
|
|
169
|
+
console.log(t("copy.symlink_created", destLink, sourceDir));
|
|
170
|
+
return true;
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (process.platform === "win32" && error.code === "EPERM") {
|
|
173
|
+
console.log(t("copy.symlink_fallback_copy", destLink));
|
|
174
|
+
const count = mergeCopyDir(sourceDir, destLink);
|
|
175
|
+
console.log(t("copy.files_copied", count));
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
console.error(t("copy.symlink_failed", destLink, error.message));
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
121
181
|
}
|
|
122
182
|
|
|
123
183
|
/**
|
|
124
|
-
*
|
|
184
|
+
* 根据已检测的环境信息返回目标根目录,避免重复调用 detectEnvironment()。
|
|
185
|
+
* @param {string|null} activeToolName
|
|
186
|
+
* @param {string|null} activeProjectRoot
|
|
187
|
+
* @param {Array} envResults
|
|
125
188
|
* @returns {string} 目标根目录路径
|
|
126
189
|
*/
|
|
127
|
-
function
|
|
128
|
-
const
|
|
129
|
-
const activeResult = results.find((r) => r.displayName === activeToolName);
|
|
190
|
+
function resolveDestBaseFromEnv(activeToolName, activeProjectRoot, envResults) {
|
|
191
|
+
const activeResult = envResults.find((r) => r.displayName === activeToolName);
|
|
130
192
|
const isWukong = activeResult && activeResult.dirName === ".real";
|
|
131
193
|
|
|
132
194
|
if (isWukong) {
|
|
@@ -141,7 +203,7 @@ function resolveDestBase() {
|
|
|
141
203
|
|
|
142
204
|
// 未检测到活跃工具
|
|
143
205
|
console.error(t("copy.no_ai_tool"));
|
|
144
|
-
|
|
206
|
+
envResults.forEach((r) => {
|
|
145
207
|
console.error(` ${r.isActive ? "✅" : "⬜"} ${r.displayName}`);
|
|
146
208
|
});
|
|
147
209
|
console.error(t("copy.force_hint"));
|
|
@@ -188,17 +250,24 @@ function run() {
|
|
|
188
250
|
console.log(t("copy.package_root", packageRoot));
|
|
189
251
|
|
|
190
252
|
// 2. 确定目标根目录(检测 AI 工具环境)
|
|
191
|
-
|
|
253
|
+
// 同时获取 isWukong 标志,避免后续重复调用 detectEnvironment()
|
|
254
|
+
const { activeToolName, activeProjectRoot, results: envResults } = detectEnvironment();
|
|
255
|
+
const activeEnvResult = envResults.find((r) => r.isActive);
|
|
256
|
+
const isWukong = activeEnvResult && activeEnvResult.dirName === ".real";
|
|
257
|
+
const destBase = resolveDestBaseFromEnv(activeToolName, activeProjectRoot, envResults);
|
|
192
258
|
console.log(t("copy.dest_base", destBase));
|
|
193
259
|
if (isForce) {
|
|
194
260
|
console.log(t("copy.force_mode"));
|
|
195
261
|
}
|
|
196
262
|
|
|
197
263
|
// 3. 确定要复制/链接的内容
|
|
198
|
-
// - 指定了 -skills
|
|
264
|
+
// - 指定了 -skills:
|
|
265
|
+
// 悟空环境:删除已有的 yida-skills/ 软链(悟空手动上传技能,不需要软链)
|
|
266
|
+
// 其他环境:创建 yida-skills/ 软链接(如果存在实际目录则先删除)
|
|
199
267
|
// - 指定了 -project:只复制 project/
|
|
200
268
|
// - 两者都没指定(默认):只复制 project/
|
|
201
269
|
// - 两者都指定:同时处理两项
|
|
270
|
+
|
|
202
271
|
const shouldCopyProject = wantsProject || (!wantsSkills);
|
|
203
272
|
const shouldLinkSkills = wantsSkills;
|
|
204
273
|
|
|
@@ -215,17 +284,28 @@ function run() {
|
|
|
215
284
|
}
|
|
216
285
|
|
|
217
286
|
if (shouldLinkSkills) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
287
|
+
const destSkillsLink = path.join(destBase, "yida-skills");
|
|
288
|
+
if (isWukong) {
|
|
289
|
+
// 悟空环境:删除已有软链,不创建新软链
|
|
290
|
+
console.log(t("copy.wukong_skills_cleanup"));
|
|
291
|
+
const removed = removeSkillsLink(destSkillsLink);
|
|
292
|
+
results.push({
|
|
293
|
+
label: "yida-skills/",
|
|
294
|
+
dest: destSkillsLink,
|
|
295
|
+
count: removed ? 1 : 0,
|
|
296
|
+
type: "wukong-cleanup"
|
|
297
|
+
});
|
|
298
|
+
} else {
|
|
299
|
+
// 其他环境:创建软链接
|
|
300
|
+
console.log(t("copy.creating_symlink"));
|
|
301
|
+
const success = createSymlink(packageYidaSkillsDir, destSkillsLink);
|
|
302
|
+
results.push({
|
|
303
|
+
label: "yida-skills/",
|
|
304
|
+
dest: destSkillsLink,
|
|
305
|
+
count: success ? 1 : 0,
|
|
306
|
+
type: "symlink"
|
|
307
|
+
});
|
|
308
|
+
}
|
|
229
309
|
}
|
|
230
310
|
|
|
231
311
|
// 4. 打印汇总
|
|
@@ -242,6 +322,9 @@ function run() {
|
|
|
242
322
|
results.forEach((r) => {
|
|
243
323
|
if (r.type === "symlink") {
|
|
244
324
|
console.log(` ${r.label.padEnd(14)} → ${r.dest} (${t("copy.symlink_label")})`);
|
|
325
|
+
} else if (r.type === "wukong-cleanup") {
|
|
326
|
+
const statusText = r.count > 0 ? t("copy.wukong_skills_cleaned") : t("copy.wukong_skills_not_found", r.dest);
|
|
327
|
+
console.log(` ${r.label.padEnd(14)} → ${r.dest} (${statusText})`);
|
|
245
328
|
} else {
|
|
246
329
|
console.log(` ${r.label.padEnd(14)} → ${r.dest} (${t("copy.files_count", r.count)})`);
|
|
247
330
|
}
|
package/lib/create-form.js
CHANGED
|
@@ -95,7 +95,35 @@ function buildApiPath(appType, apiName, options = {}) {
|
|
|
95
95
|
// ── 参数解析 ─────────────────────────────────────────
|
|
96
96
|
|
|
97
97
|
function parseArgs() {
|
|
98
|
-
const
|
|
98
|
+
const rawArgs = process.argv.slice(2);
|
|
99
|
+
|
|
100
|
+
// 解析可选参数
|
|
101
|
+
const options = {
|
|
102
|
+
layout: "single", // 布局:single/double/card/section
|
|
103
|
+
theme: "default", // 主题:default/compact/comfortable
|
|
104
|
+
labelAlign: "top", // 标签对齐:top/left/right
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// 复制一份 args 用于解析(避免修改原始数组影响后续处理)
|
|
108
|
+
const args = [...rawArgs];
|
|
109
|
+
|
|
110
|
+
// 解析 --layout, --theme, --label-align 参数
|
|
111
|
+
for (let i = 0; i < args.length; i++) {
|
|
112
|
+
if (args[i] === "--layout" && i + 1 < args.length) {
|
|
113
|
+
options.layout = args[i + 1];
|
|
114
|
+
args.splice(i, 2);
|
|
115
|
+
i--;
|
|
116
|
+
} else if (args[i] === "--theme" && i + 1 < args.length) {
|
|
117
|
+
options.theme = args[i + 1];
|
|
118
|
+
args.splice(i, 2);
|
|
119
|
+
i--;
|
|
120
|
+
} else if (args[i] === "--label-align" && i + 1 < args.length) {
|
|
121
|
+
options.labelAlign = args[i + 1];
|
|
122
|
+
args.splice(i, 2);
|
|
123
|
+
i--;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
99
127
|
const mode = args[0];
|
|
100
128
|
|
|
101
129
|
if (mode === "create") {
|
|
@@ -104,7 +132,13 @@ function parseArgs() {
|
|
|
104
132
|
console.error(t("create_form.example_create"));
|
|
105
133
|
process.exit(1);
|
|
106
134
|
}
|
|
107
|
-
return {
|
|
135
|
+
return {
|
|
136
|
+
mode: "create",
|
|
137
|
+
appType: args[1],
|
|
138
|
+
formTitle: args[2],
|
|
139
|
+
fieldsJsonOrFile: args[3],
|
|
140
|
+
...options
|
|
141
|
+
};
|
|
108
142
|
}
|
|
109
143
|
|
|
110
144
|
if (mode === "update") {
|
|
@@ -113,12 +147,24 @@ function parseArgs() {
|
|
|
113
147
|
console.error(t("create_form.example_update"));
|
|
114
148
|
process.exit(1);
|
|
115
149
|
}
|
|
116
|
-
return {
|
|
150
|
+
return {
|
|
151
|
+
mode: "update",
|
|
152
|
+
appType: args[1],
|
|
153
|
+
formUuid: args[2],
|
|
154
|
+
changesJsonOrFile: args[3],
|
|
155
|
+
...options
|
|
156
|
+
};
|
|
117
157
|
}
|
|
118
158
|
|
|
119
159
|
// 兼容旧用法(无 mode 参数,默认 create 模式)
|
|
120
160
|
if (args.length >= 3 && mode !== "create" && mode !== "update") {
|
|
121
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
mode: "create",
|
|
163
|
+
appType: args[0],
|
|
164
|
+
formTitle: args[1],
|
|
165
|
+
fieldsJsonOrFile: args[2],
|
|
166
|
+
...options
|
|
167
|
+
};
|
|
122
168
|
}
|
|
123
169
|
|
|
124
170
|
console.error(t("create_form.usage_label"));
|
|
@@ -1007,10 +1053,160 @@ function resolveFieldIdReferences(fieldComponents) {
|
|
|
1007
1053
|
});
|
|
1008
1054
|
}
|
|
1009
1055
|
|
|
1056
|
+
// ── 布局配置映射 ─────────────────────────────────────
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* 获取布局配置
|
|
1060
|
+
* @param {string} layout - 布局类型:single/double/card/section
|
|
1061
|
+
* @returns {object} 布局配置对象 { columns, formLayout, groupFields }
|
|
1062
|
+
*/
|
|
1063
|
+
function getLayoutConfig(layout) {
|
|
1064
|
+
const layoutMap = {
|
|
1065
|
+
single: { columns: 1, formLayout: "default", groupFields: false },
|
|
1066
|
+
"1": { columns: 1, formLayout: "default", groupFields: false },
|
|
1067
|
+
double: { columns: 2, formLayout: "default", groupFields: false },
|
|
1068
|
+
"2": { columns: 2, formLayout: "default", groupFields: false },
|
|
1069
|
+
card: { columns: 1, formLayout: "card", groupFields: true },
|
|
1070
|
+
section: { columns: 1, formLayout: "section", groupFields: true },
|
|
1071
|
+
};
|
|
1072
|
+
return layoutMap[layout] || layoutMap.single;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* 获取主题样式配置
|
|
1077
|
+
* @param {string} theme - 主题类型:default/compact/comfortable
|
|
1078
|
+
* @param {string} labelAlign - 标签对齐:top/left/right
|
|
1079
|
+
* @returns {object} 样式配置对象
|
|
1080
|
+
*/
|
|
1081
|
+
function getThemeConfig(theme, labelAlign) {
|
|
1082
|
+
const baseConfig = {
|
|
1083
|
+
labelAlignPc: labelAlign || "top",
|
|
1084
|
+
labelWidthPc: labelAlign === "left" || labelAlign === "right" ? "130px" : "auto",
|
|
1085
|
+
labelWeightPc: "normal",
|
|
1086
|
+
contentMargin: "20",
|
|
1087
|
+
contentPadding: "20",
|
|
1088
|
+
fieldSpacing: "medium",
|
|
1089
|
+
};
|
|
1090
|
+
|
|
1091
|
+
const themeMap = {
|
|
1092
|
+
default: {
|
|
1093
|
+
...baseConfig,
|
|
1094
|
+
contentMargin: "20",
|
|
1095
|
+
contentPadding: "20",
|
|
1096
|
+
},
|
|
1097
|
+
compact: {
|
|
1098
|
+
...baseConfig,
|
|
1099
|
+
contentMargin: "12",
|
|
1100
|
+
contentPadding: "12",
|
|
1101
|
+
fieldSpacing: "small",
|
|
1102
|
+
},
|
|
1103
|
+
comfortable: {
|
|
1104
|
+
...baseConfig,
|
|
1105
|
+
contentMargin: "32",
|
|
1106
|
+
contentPadding: "32",
|
|
1107
|
+
fieldSpacing: "large",
|
|
1108
|
+
},
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
return themeMap[theme] || themeMap.default;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// ── 按 group 分组字段 ─────────────────────────────────
|
|
1115
|
+
|
|
1116
|
+
/**
|
|
1117
|
+
* 将字段按 group 属性分组
|
|
1118
|
+
* @param {Array} fields - 字段定义数组
|
|
1119
|
+
* @returns {Array} 分组后的字段数组,每个元素是 { groupName, fields }
|
|
1120
|
+
*/
|
|
1121
|
+
function groupFieldsByGroup(fields) {
|
|
1122
|
+
const groups = [];
|
|
1123
|
+
const groupMap = new Map();
|
|
1124
|
+
|
|
1125
|
+
fields.forEach((field) => {
|
|
1126
|
+
const groupName = field.group || "基本信息";
|
|
1127
|
+
if (!groupMap.has(groupName)) {
|
|
1128
|
+
groupMap.set(groupName, []);
|
|
1129
|
+
}
|
|
1130
|
+
groupMap.get(groupName).push(field);
|
|
1131
|
+
});
|
|
1132
|
+
|
|
1133
|
+
groupMap.forEach((groupFields, groupName) => {
|
|
1134
|
+
groups.push({ groupName, fields: groupFields });
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
return groups;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// ── 构建分组字段组件 ─────────────────────────────────
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* 构建分组/卡片布局的字段组件
|
|
1144
|
+
* @param {Array} fields - 字段定义数组
|
|
1145
|
+
* @param {string} formLayout - 布局类型:card/section
|
|
1146
|
+
* @returns {Array} 分组后的组件数组
|
|
1147
|
+
*/
|
|
1148
|
+
function buildGroupedFieldComponents(fields, formLayout) {
|
|
1149
|
+
const groups = groupFieldsByGroup(fields);
|
|
1150
|
+
|
|
1151
|
+
return groups.map((group, groupIndex) => {
|
|
1152
|
+
// 构建分组内的字段组件
|
|
1153
|
+
const groupFieldComponents = group.fields.map((field) => buildFieldComponent(field));
|
|
1154
|
+
|
|
1155
|
+
if (formLayout === "card") {
|
|
1156
|
+
// 卡片式布局:每个分组是一个卡片容器
|
|
1157
|
+
return {
|
|
1158
|
+
componentName: "CardContainer",
|
|
1159
|
+
id: nextNodeId(),
|
|
1160
|
+
props: {
|
|
1161
|
+
title: i18n(group.groupName, group.groupName),
|
|
1162
|
+
collapsible: true,
|
|
1163
|
+
defaultCollapsed: false,
|
|
1164
|
+
showTitle: true,
|
|
1165
|
+
cardStyle: "default",
|
|
1166
|
+
headerStyle: "default",
|
|
1167
|
+
__gridSpan: 1,
|
|
1168
|
+
},
|
|
1169
|
+
condition: true,
|
|
1170
|
+
hidden: false,
|
|
1171
|
+
title: "",
|
|
1172
|
+
isLocked: false,
|
|
1173
|
+
conditionGroup: "",
|
|
1174
|
+
children: groupFieldComponents,
|
|
1175
|
+
};
|
|
1176
|
+
} else {
|
|
1177
|
+
// 分组式布局:每个分组是一个区块
|
|
1178
|
+
return {
|
|
1179
|
+
componentName: "SectionContainer",
|
|
1180
|
+
id: nextNodeId(),
|
|
1181
|
+
props: {
|
|
1182
|
+
title: i18n(group.groupName, group.groupName),
|
|
1183
|
+
collapsible: true,
|
|
1184
|
+
defaultCollapsed: false,
|
|
1185
|
+
showTitle: true,
|
|
1186
|
+
sectionStyle: "default",
|
|
1187
|
+
divider: groupIndex > 0, // 第一个分组不显示分隔线
|
|
1188
|
+
__gridSpan: 1,
|
|
1189
|
+
},
|
|
1190
|
+
condition: true,
|
|
1191
|
+
hidden: false,
|
|
1192
|
+
title: "",
|
|
1193
|
+
isLocked: false,
|
|
1194
|
+
conditionGroup: "",
|
|
1195
|
+
children: groupFieldComponents,
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1010
1201
|
// ── 生成表单 Schema ──────────────────────────────────
|
|
1011
1202
|
|
|
1012
|
-
function buildFormSchema(formTitle, fields, formUuid, corpId, appType,
|
|
1013
|
-
|
|
1203
|
+
function buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, theme, labelAlign) {
|
|
1204
|
+
// 解析布局配置
|
|
1205
|
+
const layoutConfig = getLayoutConfig(layout || "single");
|
|
1206
|
+
const columns = layoutConfig.columns;
|
|
1207
|
+
|
|
1208
|
+
// 解析主题配置
|
|
1209
|
+
const themeConfig = getThemeConfig(theme || "default", labelAlign || "top");
|
|
1014
1210
|
const fieldComponents = fields.map(function (field) {
|
|
1015
1211
|
return buildFieldComponent(field);
|
|
1016
1212
|
});
|
|
@@ -1070,12 +1266,12 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, columns)
|
|
|
1070
1266
|
titleColor: "light",
|
|
1071
1267
|
titleBg: "https://img.alicdn.com/imgextra/i2/O1CN0143ATPP1wIa9TrVvzN_!!6000000006285-2-tps-3360-400.png_.webp",
|
|
1072
1268
|
backgroundColorCustom: "#f1f2f3",
|
|
1073
|
-
sizePc: "medium",
|
|
1074
|
-
labelAlignPc:
|
|
1075
|
-
labelWidthPc:
|
|
1076
|
-
labelWeightPc:
|
|
1077
|
-
labelAlignMobile: "top",
|
|
1078
|
-
labelWidthMobile: "80px",
|
|
1269
|
+
sizePc: themeConfig.fieldSpacing === "small" ? "small" : themeConfig.fieldSpacing === "large" ? "large" : "medium",
|
|
1270
|
+
labelAlignPc: themeConfig.labelAlignPc,
|
|
1271
|
+
labelWidthPc: themeConfig.labelWidthPc,
|
|
1272
|
+
labelWeightPc: themeConfig.labelWeightPc,
|
|
1273
|
+
labelAlignMobile: labelAlign || "top",
|
|
1274
|
+
labelWidthMobile: labelAlign === "left" || labelAlign === "right" ? "80px" : "auto",
|
|
1079
1275
|
labelWeightMobile: "normal",
|
|
1080
1276
|
},
|
|
1081
1277
|
condition: true,
|
|
@@ -1150,15 +1346,36 @@ function buildFormSchema(formTitle, fields, formUuid, corpId, appType, columns)
|
|
|
1150
1346
|
afterSubmit: false,
|
|
1151
1347
|
onProcessActionValidate: false,
|
|
1152
1348
|
afterFormDataInit: false,
|
|
1153
|
-
},
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1349
|
+
}, children: [
|
|
1350
|
+
{
|
|
1351
|
+
componentName: "FormContainer",
|
|
1352
|
+
id: nextNodeId(),
|
|
1353
|
+
props: {
|
|
1354
|
+
formLabel: i18n(formTitle, formTitle),
|
|
1355
|
+
formLabelVisible: true,
|
|
1356
|
+
columns: columns,
|
|
1357
|
+
labelAlign: labelAlign || "top",
|
|
1358
|
+
submitText: i18n("提交", "Submit"),
|
|
1359
|
+
stageText: i18n("暂存", "Stage"),
|
|
1360
|
+
submitAndNewText: i18n("提交并继续", "Submit and New"),
|
|
1361
|
+
fieldId: "formContainer_" + Date.now().toString(36),
|
|
1362
|
+
aiFormConfig: { systemPrompt: "", model: "qwen" },
|
|
1363
|
+
beforeSubmit: false,
|
|
1364
|
+
afterSubmit: false,
|
|
1365
|
+
onProcessActionValidate: false,
|
|
1366
|
+
afterFormDataInit: false,
|
|
1367
|
+
},
|
|
1368
|
+
condition: true,
|
|
1369
|
+
hidden: false,
|
|
1370
|
+
title: "",
|
|
1371
|
+
isLocked: false,
|
|
1372
|
+
conditionGroup: "",
|
|
1373
|
+
// ★ 核心:FormContainer 内层的字段组件(支持分组布局)
|
|
1374
|
+
children: layoutConfig.groupFields
|
|
1375
|
+
? buildGroupedFieldComponents(fields, layoutConfig.formLayout)
|
|
1376
|
+
: fieldComponents,
|
|
1377
|
+
},
|
|
1378
|
+
], },
|
|
1162
1379
|
],
|
|
1163
1380
|
},
|
|
1164
1381
|
{
|
|
@@ -1974,7 +2191,7 @@ async function saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, ver
|
|
|
1974
2191
|
// ── create 模式主流程 ─────────────────────────────────
|
|
1975
2192
|
|
|
1976
2193
|
async function mainCreate(parsedArgs, csrfToken, cookies, baseUrl, cookieData) {
|
|
1977
|
-
const { appType, formTitle, fieldsJsonOrFile } = parsedArgs;
|
|
2194
|
+
const { appType, formTitle, fieldsJsonOrFile, layout, theme, labelAlign } = parsedArgs;
|
|
1978
2195
|
|
|
1979
2196
|
const SEP = "=".repeat(50);
|
|
1980
2197
|
console.error(SEP);
|
|
@@ -2025,7 +2242,7 @@ async function mainCreate(parsedArgs, csrfToken, cookies, baseUrl, cookieData) {
|
|
|
2025
2242
|
console.error(t("create_form.corp_id_ok", corpId));
|
|
2026
2243
|
}
|
|
2027
2244
|
|
|
2028
|
-
const schema = buildFormSchema(formTitle, fields, formUuid, corpId, appType,
|
|
2245
|
+
const schema = buildFormSchema(formTitle, fields, formUuid, corpId, appType, layout, theme, labelAlign);
|
|
2029
2246
|
var { configResult } = await saveSchemaAndUpdateConfig(authRef, appType, formUuid, schema, 1, 4);
|
|
2030
2247
|
|
|
2031
2248
|
// 输出结果
|