openyida 2026.4.2-beta.1 → 2026.4.2-beta.12
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 +9 -0
- package/lib/auth/login.js +13 -1
- package/lib/core/copy.js +19 -8
- package/lib/core/doctor.js +33 -10
- package/lib/core/env.js +1 -1
- package/lib/core/locales/ar.js +1 -0
- package/lib/core/locales/de.js +1 -0
- package/lib/core/locales/en.js +1 -0
- package/lib/core/locales/es.js +1 -0
- package/lib/core/locales/fr.js +1 -0
- package/lib/core/locales/hi.js +1 -0
- package/lib/core/locales/ja.js +1 -0
- package/lib/core/locales/ko.js +1 -0
- package/lib/core/locales/pt.js +1 -0
- package/lib/core/locales/vi.js +1 -0
- package/lib/core/locales/zh-TW.js +1 -0
- package/lib/core/locales/zh.js +1 -0
- package/lib/core/utils.js +2 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +3 -40
- package/yida-skills/SKILL.md +1 -1
- package/yida-skills/skills/yida-custom-page/SKILL.md +37 -0
- package/yida-skills/skills/yida-login/SKILL.md +0 -2
package/README.md
CHANGED
|
@@ -31,6 +31,15 @@ npm install -g openyida
|
|
|
31
31
|
|
|
32
32
|
**Zero config, works out of the box.** After installation, just chat in Claude Code / OpenCode / Aone Copilot:
|
|
33
33
|
|
|
34
|
+
### Wukong Installation
|
|
35
|
+
|
|
36
|
+
Wukong uses manual skill package installation instead of npm:
|
|
37
|
+
|
|
38
|
+
1. Download the latest skill package (`.zip`) from [GitHub Releases](https://github.com/openyida/openyida/releases)
|
|
39
|
+
2. Open Wukong → **Skill Center** → **Upload Skill**, then select the downloaded package
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
34
43
|
```
|
|
35
44
|
Build me an IPD system on Yida to manage the full chip production workflow
|
|
36
45
|
Help me set up a CRM
|
package/lib/auth/login.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* login.js - 宜搭登录态管理(Playwright 扫码登录)
|
|
3
3
|
*
|
|
4
|
+
* 登录策略(按优先级):
|
|
5
|
+
* 1. 本地 Cookie 缓存(最快)
|
|
6
|
+
* 2. 本地 Chrome(channel: 'chrome')
|
|
7
|
+
* 3. Playwright 内置 Chromium(兜底)
|
|
8
|
+
*
|
|
4
9
|
* 导出函数:
|
|
5
10
|
* ensureLogin() - 确保有效登录态(优先缓存,否则扫码)
|
|
6
11
|
* checkLoginOnly() - 仅检查登录态,不触发登录
|
|
@@ -228,7 +233,14 @@ const { chromium } = playwright;
|
|
|
228
233
|
const { URL } = require('url');
|
|
229
234
|
|
|
230
235
|
(async () => {
|
|
231
|
-
|
|
236
|
+
// 优先使用本地已安装的 Chrome,避免下载 Playwright 内置 Chromium
|
|
237
|
+
let browser;
|
|
238
|
+
try {
|
|
239
|
+
browser = await chromium.launch({ channel: 'chrome', headless: false });
|
|
240
|
+
} catch {
|
|
241
|
+
// 本地未安装 Chrome,降级为 Playwright 内置 Chromium
|
|
242
|
+
browser = await chromium.launch({ headless: false });
|
|
243
|
+
}
|
|
232
244
|
const context = await browser.newContext();
|
|
233
245
|
const page = await context.newPage();
|
|
234
246
|
await page.goto(${JSON.stringify(loginUrl)}, { timeout: 120000 });
|
package/lib/core/copy.js
CHANGED
|
@@ -194,7 +194,7 @@ function resolveDestBaseFromEnv(activeToolName, activeProjectRoot, envResults) {
|
|
|
194
194
|
if (isWukong) {
|
|
195
195
|
return activeProjectRoot
|
|
196
196
|
? path.dirname(activeProjectRoot)
|
|
197
|
-
: path.join(os.homedir(), '.real', 'workspace');
|
|
197
|
+
: path.join(process.env.AGENT_WORK_ROOT || path.join(os.homedir(), '.real', 'workspace'));
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
if (activeToolName) {
|
|
@@ -274,13 +274,24 @@ function run() {
|
|
|
274
274
|
const results = [];
|
|
275
275
|
|
|
276
276
|
if (shouldCopyProject) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
277
|
+
// 检查 destBase 是否为空目录:
|
|
278
|
+
// - 空目录(如悟空新工作区)→ 直接把 project/ 内容铺进 destBase,不创建 project/ 这层
|
|
279
|
+
// - 非空目录(已有其他文件)→ 复制整个 project/ 目录(含目录本身)
|
|
280
|
+
const destBaseEntries = fs.existsSync(destBase)
|
|
281
|
+
? fs.readdirSync(destBase).filter((name) => name !== '.DS_Store')
|
|
282
|
+
: [];
|
|
283
|
+
const isDestBaseEmpty = destBaseEntries.length === 0;
|
|
284
|
+
|
|
285
|
+
const projectDestDir = isDestBaseEmpty
|
|
286
|
+
? destBase
|
|
287
|
+
: path.join(destBase, 'project');
|
|
288
|
+
|
|
289
|
+
if (isDestBaseEmpty) {
|
|
290
|
+
console.log(t('copy.dest_empty_flatten'));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const count = copyItem('project/', packageProjectDir, projectDestDir, isForce);
|
|
294
|
+
results.push({ label: 'project/', dest: projectDestDir, count, type: 'copy' });
|
|
284
295
|
}
|
|
285
296
|
|
|
286
297
|
if (shouldLinkSkills) {
|
package/lib/core/doctor.js
CHANGED
|
@@ -212,7 +212,7 @@ class EnvironmentChecker {
|
|
|
212
212
|
severity: Severity.ERROR,
|
|
213
213
|
message: 'Playwright 未安装',
|
|
214
214
|
fixType: FixType.COMMAND,
|
|
215
|
-
fixCommand: 'npm install playwright
|
|
215
|
+
fixCommand: 'npm install playwright',
|
|
216
216
|
};
|
|
217
217
|
}
|
|
218
218
|
}
|
|
@@ -426,18 +426,23 @@ class ProjectInitChecker {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
async check() {
|
|
429
|
-
|
|
429
|
+
// 若 projectRoot 本身就是 project 目录(AI 工具将工作区根识别为 project/ 子目录时),
|
|
430
|
+
// 则直接检测 projectRoot/config.json,避免拼出 project/project 的错误路径。
|
|
431
|
+
const rootBasename = path.basename(this.projectRoot);
|
|
432
|
+
const projectDir = rootBasename === 'project'
|
|
433
|
+
? this.projectRoot
|
|
434
|
+
: path.join(this.projectRoot, 'project');
|
|
430
435
|
const configPath = path.join(projectDir, 'config.json');
|
|
431
436
|
const passed = fs.existsSync(projectDir) && fs.existsSync(configPath);
|
|
432
437
|
|
|
433
438
|
return [{
|
|
434
439
|
id: 'project-init',
|
|
435
|
-
label: passed ? 'project/ 工作目录已初始化' :
|
|
440
|
+
label: passed ? 'project/ 工作目录已初始化' : 'project/ 工作目录检测',
|
|
436
441
|
passed,
|
|
437
|
-
severity: passed ? Severity.INFO : Severity.
|
|
438
|
-
message: passed ? null : 'project/
|
|
439
|
-
fixType: passed ? null : FixType.
|
|
440
|
-
|
|
442
|
+
severity: passed ? Severity.INFO : Severity.ERROR,
|
|
443
|
+
message: passed ? null : 'project/ 目录未初始化,将自动初始化',
|
|
444
|
+
fixType: passed ? null : FixType.AUTO,
|
|
445
|
+
fixAction: passed ? null : 'init-project',
|
|
441
446
|
}];
|
|
442
447
|
}
|
|
443
448
|
}
|
|
@@ -657,7 +662,7 @@ class FixEngine {
|
|
|
657
662
|
|
|
658
663
|
for (const issue of issues) {
|
|
659
664
|
if (issue.fixType === FixType.AUTO) {
|
|
660
|
-
const result = this.applyAutoFix(issue);
|
|
665
|
+
const result = await this.applyAutoFix(issue);
|
|
661
666
|
this.fixResults.push(result);
|
|
662
667
|
} else if (issue.fixType === FixType.COMMAND) {
|
|
663
668
|
this.fixResults.push({
|
|
@@ -680,9 +685,9 @@ class FixEngine {
|
|
|
680
685
|
/**
|
|
681
686
|
* 执行自动修复动作。
|
|
682
687
|
* @param {object} issue
|
|
683
|
-
* @returns {object}
|
|
688
|
+
* @returns {Promise<object>}
|
|
684
689
|
*/
|
|
685
|
-
applyAutoFix(issue) {
|
|
690
|
+
async applyAutoFix(issue) {
|
|
686
691
|
switch (issue.fixAction) {
|
|
687
692
|
case 'create-config': {
|
|
688
693
|
const configPath = path.join(this.projectRoot, 'config.json');
|
|
@@ -710,6 +715,24 @@ class FixEngine {
|
|
|
710
715
|
};
|
|
711
716
|
}
|
|
712
717
|
|
|
718
|
+
case 'init-project': {
|
|
719
|
+
try {
|
|
720
|
+
const copy = require('./copy');
|
|
721
|
+
await copy([]);
|
|
722
|
+
return {
|
|
723
|
+
id: issue.id,
|
|
724
|
+
fixed: true,
|
|
725
|
+
message: '已自动初始化 project/ 工作目录',
|
|
726
|
+
};
|
|
727
|
+
} catch (error) {
|
|
728
|
+
return {
|
|
729
|
+
id: issue.id,
|
|
730
|
+
fixed: false,
|
|
731
|
+
message: `project/ 初始化失败,请手动运行:openyida copy(${error.message})`,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
713
736
|
default:
|
|
714
737
|
return {
|
|
715
738
|
id: issue.id,
|
package/lib/core/env.js
CHANGED
|
@@ -55,7 +55,7 @@ function detectEnvironment() {
|
|
|
55
55
|
const isActive = activeTool && activeTool.dirName === dirName;
|
|
56
56
|
// path.join 在 Windows 上自动使用反斜杠,兼容所有平台
|
|
57
57
|
const workspaceRoot = isWukong
|
|
58
|
-
? path.join(home, '.real', 'workspace', 'project')
|
|
58
|
+
? path.join(process.env.AGENT_WORK_ROOT || path.join(home, '.real', 'workspace'), 'project')
|
|
59
59
|
: cwdProject;
|
|
60
60
|
const hasProject = fs.existsSync(workspaceRoot);
|
|
61
61
|
|
package/lib/core/locales/ar.js
CHANGED
|
@@ -203,6 +203,7 @@ module.exports = {
|
|
|
203
203
|
title: ' openyida copy - تهيئة دليل عمل Yida',
|
|
204
204
|
package_root: '\n📦 جذر الحزمة: {0}',
|
|
205
205
|
dest_root: '🤖 الدليل الهدف: {0}',
|
|
206
|
+
dest_empty_flatten: '📂 الدليل الهدف فارغ، سيتم نسخ محتويات project/ مباشرةً (بدون إنشاء مجلد project/ فرعي)',
|
|
206
207
|
force_mode: '⚠️ وضع --force: سيتم مسح الدليل الهدف قبل النسخ',
|
|
207
208
|
no_package: '\n❌ لم يتم العثور على دليل حزمة openyida',
|
|
208
209
|
no_package_hint1: ' تأكد من تثبيت openyida بشكل عام:',
|
package/lib/core/locales/de.js
CHANGED
|
@@ -203,6 +203,7 @@ module.exports = {
|
|
|
203
203
|
title: ' openyida copy - Yida-Arbeitsverzeichnis initialisieren',
|
|
204
204
|
package_root: '\n📦 Paketstamm: {0}',
|
|
205
205
|
dest_root: '🤖 Zielverzeichnis: {0}',
|
|
206
|
+
dest_empty_flatten: '📂 Zielverzeichnis ist leer, project/-Inhalte werden direkt hineinkopiert (kein project/-Unterverzeichnis)',
|
|
206
207
|
force_mode: '⚠️ --force-Modus: Zielverzeichnis wird vor dem Kopieren geleert',
|
|
207
208
|
no_package: '\n❌ openyida-Paketverzeichnis nicht gefunden',
|
|
208
209
|
no_package_hint1: ' Stellen Sie sicher, dass openyida global installiert ist:',
|
package/lib/core/locales/en.js
CHANGED
|
@@ -575,6 +575,7 @@ Examples:
|
|
|
575
575
|
package_root: '\n📦 Package root: {0}',
|
|
576
576
|
dest_base: '🤖 Target root: {0}',
|
|
577
577
|
dest_root: '🤖 Target root: {0}',
|
|
578
|
+
dest_empty_flatten: '📂 Target directory is empty, flattening project/ contents directly into it (no project/ subdirectory)',
|
|
578
579
|
force_mode: '⚠️ --force mode: target directory will be cleared before copying',
|
|
579
580
|
no_package: '\n❌ openyida package directory not found',
|
|
580
581
|
no_package_hint1: ' Please ensure openyida is installed globally:',
|
package/lib/core/locales/es.js
CHANGED
|
@@ -203,6 +203,7 @@ module.exports = {
|
|
|
203
203
|
title: ' openyida copy - Inicializar directorio de trabajo Yida',
|
|
204
204
|
package_root: '\n📦 Raíz del paquete: {0}',
|
|
205
205
|
dest_root: '🤖 Directorio destino: {0}',
|
|
206
|
+
dest_empty_flatten: '📂 El directorio destino está vacío, los contenidos de project/ se copiarán directamente (sin crear subdirectorio project/)',
|
|
206
207
|
force_mode: '⚠️ Modo --force: el directorio destino será vaciado antes de copiar',
|
|
207
208
|
no_package: '\n❌ Directorio del paquete openyida no encontrado',
|
|
208
209
|
no_package_hint1: ' Asegúrese de que openyida esté instalado globalmente:',
|
package/lib/core/locales/fr.js
CHANGED
|
@@ -203,6 +203,7 @@ module.exports = {
|
|
|
203
203
|
title: ' openyida copy - Initialisation du répertoire de travail Yida',
|
|
204
204
|
package_root: '\n📦 Racine du package : {0}',
|
|
205
205
|
dest_root: '🤖 Répertoire cible : {0}',
|
|
206
|
+
dest_empty_flatten: '📂 Le répertoire cible est vide, les contenus de project/ seront copiés directement (sans créer de sous-répertoire project/)',
|
|
206
207
|
force_mode: '⚠️ Mode --force : le répertoire cible sera vidé avant la copie',
|
|
207
208
|
no_package: '\n❌ Répertoire du package openyida introuvable',
|
|
208
209
|
no_package_hint1: " Vérifiez qu'openyida est installé globalement :",
|
package/lib/core/locales/hi.js
CHANGED
|
@@ -203,6 +203,7 @@ module.exports = {
|
|
|
203
203
|
title: ' openyida copy - Yida कार्य निर्देशिका प्रारंभ करें',
|
|
204
204
|
package_root: '\n📦 पैकेज रूट: {0}',
|
|
205
205
|
dest_root: '🤖 लक्ष्य निर्देशिका: {0}',
|
|
206
|
+
dest_empty_flatten: '📂 लक्ष्य निर्देशिका खाली है, project/ की सामग्री सीधे कॉपी की जाएगी (project/ उपनिर्देशिका नहीं बनेगी)',
|
|
206
207
|
force_mode: '⚠️ --force मोड: कॉपी करने से पहले लक्ष्य निर्देशिका साफ की जाएगी',
|
|
207
208
|
no_package: '\n❌ openyida पैकेज निर्देशिका नहीं मिली',
|
|
208
209
|
no_package_hint1: ' सुनिश्चित करें कि openyida वैश्विक रूप से स्थापित है:',
|
package/lib/core/locales/ja.js
CHANGED
|
@@ -560,6 +560,7 @@ openyida - Yida CLI ツール
|
|
|
560
560
|
package_root: '\n📦 パッケージルート: {0}',
|
|
561
561
|
dest_base: '🤖 ターゲットルート: {0}',
|
|
562
562
|
dest_root: '🤖 ターゲットルート: {0}',
|
|
563
|
+
dest_empty_flatten: '📂 ターゲットディレクトリが空のため、project/ の内容を直接コピーします(project/ サブディレクトリは作成しません)',
|
|
563
564
|
force_mode: '⚠️ --force モード:ターゲットディレクトリをクリアしてからコピーします',
|
|
564
565
|
no_package: '\n❌ openyida パッケージディレクトリが見つかりません',
|
|
565
566
|
no_package_hint1: ' openyida がグローバルにインストールされていることを確認してください:',
|
package/lib/core/locales/ko.js
CHANGED
|
@@ -206,6 +206,7 @@ module.exports = {
|
|
|
206
206
|
title: ' openyida copy - Yida 작업 디렉토리 초기화',
|
|
207
207
|
package_root: '\n📦 패키지 루트: {0}',
|
|
208
208
|
dest_root: '🤖 대상 루트: {0}',
|
|
209
|
+
dest_empty_flatten: '📂 대상 디렉토리가 비어 있어 project/ 내용을 직접 복사합니다 (project/ 하위 디렉토리 생성 안 함)',
|
|
209
210
|
force_mode: '⚠️ --force 모드: 대상 디렉토리를 초기화 후 복사합니다',
|
|
210
211
|
no_package: '\n❌ openyida 패키지 디렉토리를 찾을 수 없습니다',
|
|
211
212
|
no_package_hint1: ' openyida가 전역으로 설치되어 있는지 확인하세요:',
|
package/lib/core/locales/pt.js
CHANGED
|
@@ -203,6 +203,7 @@ module.exports = {
|
|
|
203
203
|
title: ' openyida copy - Inicializar diretório de trabalho Yida',
|
|
204
204
|
package_root: '\n📦 Raiz do pacote: {0}',
|
|
205
205
|
dest_root: '🤖 Diretório destino: {0}',
|
|
206
|
+
dest_empty_flatten: '📂 O diretório destino está vazio, os conteúdos de project/ serão copiados diretamente (sem criar subdiretório project/)',
|
|
206
207
|
force_mode: '⚠️ Modo --force: o diretório destino será esvaziado antes de copiar',
|
|
207
208
|
no_package: '\n❌ Diretório do pacote openyida não encontrado',
|
|
208
209
|
no_package_hint1: ' Certifique-se de que o openyida está instalado globalmente:',
|
package/lib/core/locales/vi.js
CHANGED
|
@@ -203,6 +203,7 @@ module.exports = {
|
|
|
203
203
|
title: ' openyida copy - Khởi tạo thư mục làm việc Yida',
|
|
204
204
|
package_root: '\n📦 Thư mục gốc gói: {0}',
|
|
205
205
|
dest_root: '🤖 Thư mục đích: {0}',
|
|
206
|
+
dest_empty_flatten: '📂 Thư mục đích trống, nội dung project/ sẽ được sao chép trực tiếp (không tạo thư mục con project/)',
|
|
206
207
|
force_mode: '⚠️ Chế độ --force: thư mục đích sẽ được xóa trước khi sao chép',
|
|
207
208
|
no_package: '\n❌ Không tìm thấy thư mục gói openyida',
|
|
208
209
|
no_package_hint1: ' Hãy đảm bảo openyida đã được cài đặt toàn cục:',
|
|
@@ -469,6 +469,7 @@ openyida - 宜搭命令列工具
|
|
|
469
469
|
package_root: '\n📦 套件根目錄:{0}',
|
|
470
470
|
dest_base: '🤖 目標根目錄:{0}',
|
|
471
471
|
dest_root: '🤖 目標根目錄:{0}',
|
|
472
|
+
dest_empty_flatten: '📂 目標目錄為空,將 project/ 內容直接鋪入(不建立 project/ 子目錄)',
|
|
472
473
|
force_mode: '⚠️ --force 模式:目標目錄將被清空後重新複製',
|
|
473
474
|
no_package: '\n❌ 未找到 openyida 安裝套件目錄',
|
|
474
475
|
no_package_hint1: ' 請確認 openyida 已正確全域安裝:',
|
package/lib/core/locales/zh.js
CHANGED
|
@@ -531,6 +531,7 @@ openyida - 宜搭命令行工具
|
|
|
531
531
|
package_root: '\n📦 包根目录: {0}',
|
|
532
532
|
dest_base: '🤖 目标根目录: {0}',
|
|
533
533
|
dest_root: '🤖 目标根目录: {0}',
|
|
534
|
+
dest_empty_flatten: '📂 目标目录为空,将 project/ 内容直接铺入(不创建 project/ 子目录)',
|
|
534
535
|
force_mode: '⚠️ --force 模式:目标目录将被清空后重新复制',
|
|
535
536
|
no_package: '\n❌ 未找到 openyida 安装包目录',
|
|
536
537
|
no_package_hint1: ' 请确认 openyida 已正确全局安装:',
|
package/lib/core/utils.js
CHANGED
|
@@ -87,7 +87,8 @@ function detectActiveTool() {
|
|
|
87
87
|
tool: 'wukong',
|
|
88
88
|
displayName: '悟空(Wukong)',
|
|
89
89
|
dirName: '.real',
|
|
90
|
-
|
|
90
|
+
// 直接使用 AGENT_WORK_ROOT,悟空工作区路径是动态的(含 uuid),不能硬编码
|
|
91
|
+
workspaceRoot: path.join(env.AGENT_WORK_ROOT, 'project'),
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -132,47 +132,10 @@ safeExec(() => {
|
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
// ── 2. Wukong
|
|
135
|
+
// ── 2. Wukong ────────────────────────────────────────────────────────
|
|
136
|
+
// 悟空通过手动上传技能,无需 postinstall 安装。
|
|
136
137
|
|
|
137
|
-
|
|
138
|
-
if (fs.existsSync(path.join(HOME_DIR, '.real'))) {
|
|
139
|
-
installSkills(path.join(HOME_DIR, '.real'));
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// ── 3. Chromium 按需安装 ─────────────────────────────────────────────
|
|
144
|
-
|
|
145
|
-
safeExec(() => {
|
|
146
|
-
const { execSync } = require('child_process');
|
|
147
|
-
|
|
148
|
-
let chromiumPath = null;
|
|
149
|
-
try {
|
|
150
|
-
// 通过 playwright 内置 API 获取 Chromium 可执行文件路径
|
|
151
|
-
const { chromium } = require('playwright');
|
|
152
|
-
chromiumPath = chromium.executablePath();
|
|
153
|
-
} catch {
|
|
154
|
-
// playwright 未安装或 API 不可用,跳过
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (chromiumPath && fs.existsSync(chromiumPath)) {
|
|
159
|
-
// Chromium 已存在,无需重复安装
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
console.log('\n 🌐 正在安装 Chromium 浏览器(首次安装需要下载,请稍候)...');
|
|
164
|
-
try {
|
|
165
|
-
execSync('npx playwright install chromium', {
|
|
166
|
-
stdio: 'inherit',
|
|
167
|
-
timeout: 300_000, // 5 分钟超时
|
|
168
|
-
});
|
|
169
|
-
console.log(' ✅ Chromium 安装完成!\n');
|
|
170
|
-
} catch {
|
|
171
|
-
console.warn(' ⚠️ Chromium 安装失败,可手动执行:npx playwright install chromium\n');
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// ── 4. 首次安装欢迎引导 ──────────────────────────────────────────────
|
|
138
|
+
// ── 3. 首次安装欢迎引导 ──────────────────────────────────────────────
|
|
176
139
|
|
|
177
140
|
safeExec(() => {
|
|
178
141
|
const FIRST_INSTALL_FLAG = path.join(HOME_DIR, '.openyida', 'installed');
|
package/yida-skills/SKILL.md
CHANGED
|
@@ -301,6 +301,43 @@ this.forceUpdate();
|
|
|
301
301
|
|
|
302
302
|
> **生成代码时的自检清单**:检查 `renderJsx` 中所有 `onClick`、`onChange`、`onSubmit` 等事件属性,确保每一个都是 `(e) => { this.xxx(e) }` 形式,不存在任何 `onClick={this.xxx}` 的写法。
|
|
303
303
|
|
|
304
|
+
// ❌ 错误③:在 .map(function(){}) 普通函数回调中使用箭头函数事件处理器,this 已在 function 回调里丢失,箭头函数捕获的 this 是 undefined!
|
|
305
|
+
export function renderJsx() {
|
|
306
|
+
return (
|
|
307
|
+
<div>
|
|
308
|
+
{quickBtns.map(function(btn, idx) {
|
|
309
|
+
return (
|
|
310
|
+
<button
|
|
311
|
+
key={idx}
|
|
312
|
+
onClick={(e) => { this.goToForm(btn.form); }} // ❌ this 是 undefined,运行时报错
|
|
313
|
+
>
|
|
314
|
+
{btn.label}
|
|
315
|
+
</button>
|
|
316
|
+
);
|
|
317
|
+
})}
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ✅ 正确:.map() 回调必须使用箭头函数,确保 this 正确捕获
|
|
323
|
+
export function renderJsx() {
|
|
324
|
+
return (
|
|
325
|
+
<div>
|
|
326
|
+
{quickBtns.map((btn, idx) => (
|
|
327
|
+
<button
|
|
328
|
+
key={idx}
|
|
329
|
+
onClick={(e) => { this.goToForm(btn.form); }} // ✅ 箭头函数回调 + 箭头函数事件处理器,this 正确
|
|
330
|
+
>
|
|
331
|
+
{btn.label}
|
|
332
|
+
</button>
|
|
333
|
+
))}
|
|
334
|
+
</div>
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
> **补充自检项**:检查 `renderJsx` 中所有 `.map()`、`.filter()`、`.forEach()` 等数组方法的回调,确保全部使用**箭头函数**形式 `(item) => ...`,不存在任何 `.map(function(item) {...})` 的写法,否则回调内部的 `this` 会丢失。
|
|
340
|
+
|
|
304
341
|
3. **输入法组合输入处理**:使用 `_isComposing` 标记配合 `compositionstart` / `compositionend` 事件,正确处理中文输入法的组合输入状态,避免输入过程中触发提交
|
|
305
342
|
4. **定时器清理**:在 `didUnmount` 中必须清理所有通过 `setInterval` / `setTimeout` 创建的定时器,防止内存泄漏
|
|
306
343
|
5. **错误处理**:所有 API 调用(`this.utils.yida.*`、`fetch`)必须使用 `.catch()` 处理异常,并通过 `this.utils.toast({ title: message, type: 'error' })` 向用户展示错误提示
|