feat: Add mermaid-fixer skill for automatic Mermaid syntax error correction
- Add skill that uses check-mermaid.js to validate Mermaid diagrams - Skill guides Claude to parse error reports and apply intelligent fixes - Fixes common Mermaid parser bugs (parentheses, brackets, braces in labels) - Include demo scripts with test Markdown file for validation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
128
demo_scripts/check-mermaid.js
Normal file
128
demo_scripts/check-mermaid.js
Normal file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
// 获取命令行传入的 Markdown 文件路径
|
||||
const fileArg = process.argv[2];
|
||||
if (!fileArg) {
|
||||
console.error('❌ 请提供 Markdown 文件路径。用法: node check-mermaid.js <file.md>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const filePath = path.resolve(fileArg);
|
||||
const fileName = path.basename(filePath);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`❌ 文件不存在: ${filePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
// 正则匹配 ```mermaid ... ``` 代码块,兼容 Windows(\r\n) 和 Linux(\n)
|
||||
const mermaidRegex = /```mermaid\r?\n([\s\S]*?)```/g;
|
||||
let match;
|
||||
let blockCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
// 用于收集所有格式化后的错误报告块
|
||||
const errorReports = [];
|
||||
|
||||
while ((match = mermaidRegex.exec(content)) !== null) {
|
||||
blockCount++;
|
||||
|
||||
const fullMatch = match[0];
|
||||
const code = match[1];
|
||||
|
||||
// 1. 计算在 Markdown 文件中的绝对行号
|
||||
const textBeforeMatch = content.substring(0, match.index);
|
||||
const startLine = textBeforeMatch.split(/\r?\n/).length;
|
||||
const endLine = startLine + fullMatch.split(/\r?\n/).length - 1;
|
||||
|
||||
// 创建临时文件存放单个 Mermaid 代码
|
||||
const tmpFile = path.join(os.tmpdir(), `mermaid-check-${Date.now()}-${blockCount}.mmd`);
|
||||
fs.writeFileSync(tmpFile, code.trim());
|
||||
|
||||
try {
|
||||
// 调用 mmdc 进行静默渲染检查
|
||||
execSync(`npx mmdc -i "${tmpFile}" -o "${tmpFile}.svg" -q`, { stdio: 'pipe' });
|
||||
} catch (error) {
|
||||
errorCount++;
|
||||
|
||||
// 2. 净化报错信息:剔除底层执行堆栈
|
||||
const stderr = error.stderr ? error.stderr.toString() : error.message;
|
||||
const errorLines = stderr.split(/\r?\n/);
|
||||
const cleanErrorLines = [];
|
||||
|
||||
for (const line of errorLines) {
|
||||
if (line.trim().startsWith('at ') ||
|
||||
line.includes('Parser3.parseError') ||
|
||||
line.includes('fromText')) {
|
||||
break;
|
||||
}
|
||||
cleanErrorLines.push(line);
|
||||
}
|
||||
const cleanErrorOutput = cleanErrorLines.join('\n').trim();
|
||||
|
||||
// 3. 截断代码内容:最少3行,最多6行
|
||||
const codeLines = code.split(/\r?\n/).filter(l => l.trim() !== '');
|
||||
let displayCode = '';
|
||||
|
||||
if (codeLines.length <= 6) {
|
||||
displayCode = codeLines.join('\n');
|
||||
} else {
|
||||
const head = codeLines.slice(0, 3).join('\n');
|
||||
const tail = codeLines.slice(-3).join('\n');
|
||||
displayCode = `${head}\n ...\n ... (中间省略 ${codeLines.length - 6} 行) ...\n ...\n${tail}`;
|
||||
}
|
||||
|
||||
// 4. 组装单个错误的 Markdown 块
|
||||
errorReports.push(`### ❌ 错误 #${errorCount} (代码块 #${blockCount})
|
||||
|
||||
- **文档位置:** 第 \`${startLine}\` 行至第 \`${endLine}\` 行
|
||||
|
||||
#### 核心错误详情
|
||||
\`\`\`text
|
||||
${cleanErrorOutput}
|
||||
\`\`\`
|
||||
|
||||
#### 代码内容片段
|
||||
\`\`\`text
|
||||
${displayCode}
|
||||
\`\`\``);
|
||||
|
||||
} finally {
|
||||
// 清理临时文件
|
||||
if (fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile);
|
||||
if (fs.existsSync(`${tmpFile}.svg`)) fs.unlinkSync(`${tmpFile}.svg`);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 生成最终的 Markdown 报告输出
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
if (errorCount > 0) {
|
||||
// 存在错误的情况
|
||||
console.log(`## 🚨 Mermaid 语法检查报告\n`);
|
||||
console.log(`**检查文件:** \`${fileName}\``);
|
||||
console.log(`**检查结果:** ❌ 发现 ${errorCount} 处语法错误 (共检测到 ${blockCount} 个代码块)\n`);
|
||||
console.log(`---\n`);
|
||||
console.log(errorReports.join('\n\n---\n\n'));
|
||||
|
||||
// 返回非零状态码,确保在 CI/CD 或 Git Hook 中能够阻断流程
|
||||
process.exit(1);
|
||||
} else {
|
||||
// 全部正确或没有代码块的情况
|
||||
console.log(`## 🎉 Mermaid 语法检查报告\n`);
|
||||
console.log(`**检查文件:** \`${fileName}\``);
|
||||
if (blockCount === 0) {
|
||||
console.log(`**检查结果:** ⚠️ 未检测到 Mermaid 代码块`);
|
||||
} else {
|
||||
console.log(`**检查结果:** ✅ 全部通过 (共检测到 ${blockCount} 个代码块)`);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
482
demo_scripts/demo.md
Normal file
482
demo_scripts/demo.md
Normal file
@@ -0,0 +1,482 @@
|
||||
# Admin 用户物料清理数据流说明文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档详细说明 ERPAuto 系统中,Admin 用户执行物料清理操作时,被清理物料的完整获取流程、数据来源和处理链路。
|
||||
|
||||
## 核心结论
|
||||
|
||||
**Admin 用户清理的物料来源**:被清理的物料代码从数据库表 `dbo.MaterialsToBeDeleted` 中获取,根据 Admin 用户在 UI 界面选择的负责人(Manager)进行过滤。
|
||||
|
||||
---
|
||||
|
||||
## 数据流总览
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant UI as CleanerPage (UI)
|
||||
participant Hook as useCleaner Hook
|
||||
participant API as Renderer API
|
||||
participant IPC as IPC Channel
|
||||
participant Service as ValidationService
|
||||
participant DB as Database
|
||||
|
||||
UI->>Hook: handleExecuteDeletion()
|
||||
Hook->>API: runCleanerExecution()
|
||||
API->>IPC: validation.getCleanerData({selectedManagers})
|
||||
|
||||
IPC->>Service: getCleanerData(userInfo, selectedManagers)
|
||||
|
||||
alt Admin User
|
||||
Service->>Service: loadMaterialCodesForCleaner()
|
||||
Service->>DB: SELECT MaterialCode FROM dbo.MaterialsToBeDeleted<br/>WHERE ManagerName IN (selectedManagers)
|
||||
DB-->>Service: materialCodes[]
|
||||
Service-->>IPC: {orderNumbers[], materialCodes[]}
|
||||
else Regular User
|
||||
Service->>DB: SELECT MaterialCode FROM dbo.MaterialsToBeDeleted<br/>WHERE ManagerName = username
|
||||
DB-->>Service: materialCodes[]
|
||||
Service-->>IPC: {orderNumbers[], materialCodes[]}
|
||||
end
|
||||
|
||||
IPC-->>API: cleanerData
|
||||
API->>IPC: cleaner.runCleaner({orderNumbers, materialCodes})
|
||||
IPC->>Service: CleanerApplicationService.runCleaner()
|
||||
Service->>UI: 执行清理 (ERP 删除)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 架构分层
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "渲染进程 (Renderer)"
|
||||
UI[CleanerPage.tsx]
|
||||
HOOK[useCleaner.ts]
|
||||
RAPI[renderer/api.ts]
|
||||
end
|
||||
|
||||
subgraph "主进程 (Main)"
|
||||
IPC_VAL[IPC: validation.getCleanerData]
|
||||
IPC_CLEAN[IPC: cleaner.runCleaner]
|
||||
VAS[ValidationApplicationService]
|
||||
CAS[CleanerApplicationService]
|
||||
ERP["CleanerService (ERP)"]
|
||||
end
|
||||
|
||||
subgraph "数据库 (Database)"
|
||||
MTBD[(dbo.MaterialsToBeDeleted)]
|
||||
DMPD[(dbo.DiscreteMaterialPlanData)]
|
||||
MTTD[(dbo.MaterialsTypeToBeDeleted)]
|
||||
end
|
||||
|
||||
UI --> HOOK
|
||||
HOOK --> RAPI
|
||||
RAPI --> IPC_VAL
|
||||
RAPI --> IPC_CLEAN
|
||||
IPC_VAL --> VAS
|
||||
IPC_CLEAN --> CAS
|
||||
VAS --> MTBD
|
||||
VAS --> DMPD
|
||||
VAS --> MTTD
|
||||
CAS --> ERP
|
||||
|
||||
style MTBD fill:#f9f,stroke:#333
|
||||
style MTTD fill:#f9f,stroke:#333
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 关键数据表
|
||||
|
||||
### 1. `dbo.MaterialsToBeDeleted` (核心来源表)
|
||||
|
||||
**作用**:存储所有被标记为待删除的物料代码及其负责人。
|
||||
|
||||
**表结构**:
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `ID` | int | 主键 |
|
||||
| `MaterialCode` | varchar | **物料代码 (被清理的目标)** |
|
||||
| `ManagerName` | varchar | 负责人姓名 |
|
||||
|
||||
**Admin 获取物料的 SQL 查询**:
|
||||
|
||||
```sql
|
||||
SELECT MaterialCode
|
||||
FROM dbo.MaterialsToBeDeleted
|
||||
WHERE ManagerName IN (@manager0, @manager1, ...)
|
||||
AND MaterialCode IS NOT NULL
|
||||
```
|
||||
|
||||
### 2. `dbo.MaterialsTypeToBeDeleted` (物料类型配置表)
|
||||
|
||||
**作用**:定义物料名称关键词与负责人的映射关系,用于自动分配负责人。
|
||||
|
||||
**表结构**:
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `MaterialName` | varchar | 物料名称关键词 |
|
||||
| `ManagerName` | varchar | 对应的负责人 |
|
||||
|
||||
**示例**:
|
||||
| MaterialName | ManagerName |
|
||||
|-------------|-------------|
|
||||
| "电阻" | "张三" |
|
||||
| "电容" | "李四" |
|
||||
|
||||
### 3. `dbo.DiscreteMaterialPlanData` (物料计划数据表)
|
||||
|
||||
**作用**:存储从 ERP 提取的完整物料计划数据,用于校验和展示物料详情。
|
||||
|
||||
**关键字段**:
|
||||
|
||||
- `MaterialCode` - 物料代码
|
||||
- `MaterialName` - 物料名称
|
||||
- `Specification` - 规格
|
||||
- `Model` - 型号
|
||||
- `SourceNo` - 订单号
|
||||
|
||||
---
|
||||
|
||||
## Admin 用户完整数据流
|
||||
|
||||
### 阶段 1: 校验 (Validation)
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph "步骤 1: 用户触发的数据来源"
|
||||
A1[Admin 点击'数据校验']
|
||||
end
|
||||
|
||||
subgraph "步骤 2: 数据获取模式"
|
||||
A2{校验模式?}
|
||||
A21[Full Mode<br/>全量数据]
|
||||
A22[Filtered Mode<br/>筛选数据]
|
||||
end
|
||||
|
||||
subgraph "步骤 3: 数据库查询"
|
||||
A3[DiscreteMaterialPlanDAO]
|
||||
A31["queryAllDistinctByMaterialCode()"]
|
||||
A32["queryBySourceNumbersDistinct(orderNumbers)"]
|
||||
end
|
||||
|
||||
subgraph "步骤 4: 数据增强"
|
||||
A4[加载负责人信息]
|
||||
A41[加载 MaterialsTypeToBeDeleted<br/>关键词匹配]
|
||||
A42[加载 MaterialsToBeDeleted<br/>已标记记录]
|
||||
end
|
||||
|
||||
subgraph "结果"
|
||||
A5["ValidationResult[]<br/>包含 isMarkedForDeletion 标志"]
|
||||
end
|
||||
|
||||
A1 --> A2
|
||||
A2 -->|Full| A21
|
||||
A2 -->|Filtered| A22
|
||||
A21 --> A31
|
||||
A22 --> A32
|
||||
A31 --> A4
|
||||
A32 --> A4
|
||||
A4 --> A41
|
||||
A4 --> A42
|
||||
A4 --> A5
|
||||
|
||||
style A5 fill:#9f9,stroke:#333
|
||||
```
|
||||
|
||||
**Admin 特殊逻辑**:
|
||||
|
||||
- Admin 可以看到**所有负责人**的物料
|
||||
- UI 会显示 Manager 列下拉筛选器
|
||||
- Admin 默认选中所有 Manager
|
||||
|
||||
### 阶段 2: 保存删除计划 (可选)
|
||||
|
||||
用户在 UI 上勾选物料 → 点击"确认删除" → 数据写入 `MaterialsToBeDeleted` 表:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant UI as 用户界面
|
||||
participant DAO as MaterialsToBeDeletedDAO
|
||||
participant DB as Database
|
||||
|
||||
UI->>DAO: upsertBatch(materials[])
|
||||
|
||||
loop 对每个物料
|
||||
DAO->>DB: MERGE INTO dbo.MaterialsToBeDeleted<br/>ON MaterialCode<br/>WHEN MATCHED UPDATE<br/>WHEN NOT MATCHED INSERT
|
||||
end
|
||||
|
||||
DB-->>DAO: 成功/失败统计
|
||||
DAO-->>UI: { total, success, failed }
|
||||
```
|
||||
|
||||
**SQL 逻辑**(MERGE UPSERT):
|
||||
|
||||
```sql
|
||||
MERGE INTO dbo.MaterialsToBeDeleted AS target
|
||||
USING (VALUES (@MaterialCode, @ManagerName)) AS source (MaterialCode, ManagerName)
|
||||
ON target.MaterialCode = source.MaterialCode
|
||||
WHEN MATCHED THEN
|
||||
UPDATE SET ManagerName = source.ManagerName
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (MaterialCode, ManagerName) VALUES (source.MaterialCode, source.ManagerName);
|
||||
```
|
||||
|
||||
### 阶段 3: 执行清理 (关键步骤)
|
||||
|
||||
这是 Admin 用户执行实际删除操作的核心流程:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph "Renderer 层"
|
||||
S1["handleExecuteDeletion()"]
|
||||
S1-->S2["runCleanerExecution {selectedManagers}"]
|
||||
end
|
||||
|
||||
subgraph "获取清理数据 (主进程)"
|
||||
S2-->S3[ValidationApplicationService.getCleanerData]
|
||||
S3-->S4{用户类型?}
|
||||
|
||||
S4-->|Admin + selectedManagers| S5[loadMaterialCodesForCleaner<br/>Admin With Managers]
|
||||
S5-->S6[SELECT FROM MaterialsToBeDeleted<br/>WHERE ManagerName IN selectedManagers]
|
||||
|
||||
S4-->|Admin no managers| S7[Admin Without Managers]
|
||||
S7-->S8[SELECT FROM DiscreteMaterialPlanData<br/>WHERE SourceNo IN orderNumbers]
|
||||
|
||||
S4-->|普通用户 | S9[SELECT FROM MaterialsToBeDeleted<br/>WHERE ManagerName = username]
|
||||
end
|
||||
|
||||
subgraph "执行清理"
|
||||
S6-->S10["返回 materialCodes[]"]
|
||||
S8-->S10
|
||||
S9-->S10
|
||||
S10-->S11[CleanerApplicationService.runCleaner]
|
||||
S11-->S12[CleanerService.clean<br/>连接 ERP 执行删除]
|
||||
end
|
||||
|
||||
S12-->S13[清理完成报告]
|
||||
|
||||
style S5 fill:#ff9,stroke:#333
|
||||
style S6 fill:#f96,stroke:#333
|
||||
style S12 fill:#f99,stroke:#333
|
||||
```
|
||||
|
||||
**关键代码路径**:
|
||||
|
||||
```typescript
|
||||
// src/main/services/validation/validation-application-service.ts
|
||||
// loadMaterialCodesForCleaner() 方法
|
||||
|
||||
// Admin with selected managers:
|
||||
if (isAdmin && selectedManagers && selectedManagers.length > 0) {
|
||||
const materialCodes = await this.queryMaterialCodesByManagers(
|
||||
dbService,
|
||||
markedTableName, // dbo.MaterialsToBeDeleted
|
||||
selectedManagers
|
||||
)
|
||||
return materialCodes
|
||||
}
|
||||
|
||||
// Admin without selected managers (fallback):
|
||||
if (isAdmin) {
|
||||
if (orderNumbers.length === 0) {
|
||||
return [] // 无数据可处理
|
||||
}
|
||||
const materialDao = new DiscreteMaterialPlanDAO()
|
||||
const records = await materialDao.queryBySourceNumbersDistinct(orderNumbers)
|
||||
const materialCodes = [...new Set(records.map((r) => r.MaterialCode as string).filter(Boolean))]
|
||||
return materialCodes
|
||||
}
|
||||
```
|
||||
|
||||
### 阶段 4: ERP 删除执行
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant CAS as CleanerApplicationService
|
||||
participant Cleaner as CleanerService
|
||||
participant ERP as ERP System
|
||||
|
||||
CAS->>Cleaner: clean({orderNumbers, materialCodes})
|
||||
|
||||
loop 每个订单号
|
||||
Cleaner->>ERP: 登录 (一次)
|
||||
loop 每个物料代码
|
||||
Cleaner->>ERP: 查询物料计划
|
||||
alt 物料存在
|
||||
Cleaner->>ERP: 执行删除
|
||||
ERP-->>Cleaner: 删除结果
|
||||
else 物料不存在
|
||||
Cleaner-->>Cleaner: 跳过
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Cleaner-->>CAS: CleanerResult
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Admin 用户的数据来源路径
|
||||
|
||||
整理出 Admin 用户获取物料的完整路径:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Start[Admin 点击执行清理] --> Check{有 selectedManagers?}
|
||||
|
||||
Check -->|Yes| Path1[路径 1: MaterialsToBeDeleted 表]
|
||||
Path1 --> Q1["SELECT MaterialCode FROM MaterialsToBeDeleted<br/>WHERE ManagerName IN (selectedManagers)"]
|
||||
Q1 --> Merge[合并所有物料代码]
|
||||
|
||||
Check -->|No| Path2[路径 2: DiscreteMaterialPlanData 表]
|
||||
Path2 --> NeedProd{有共享 Production ID?}
|
||||
NeedProd -->|Yes| GetOrder[从共享 ID 解析订单号]
|
||||
GetOrder --> Q2["SELECT DISTINCT MaterialCode FROM DiscreteMaterialPlanData<br/>WHERE SourceNo IN (orderNumbers)"]
|
||||
Q2 --> Merge
|
||||
|
||||
NeedProd -->|No| Empty[返回空数组<br/>无法执行清理]
|
||||
|
||||
Merge --> Execute[传递给 CleanerService<br/>执行 ERP 删除]
|
||||
|
||||
style Path1 fill:#9f9,stroke:#333,stroke-width:2px
|
||||
style Path2 fill:#ff9,stroke:#333,stroke-width:2px
|
||||
style Empty fill:#f99,stroke:#333
|
||||
style Execute fill:#f96,stroke:#333,stroke-width:3px
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 关键代码位置参考
|
||||
|
||||
### 渲染层 (Renderer)
|
||||
|
||||
| 文件 | 函数 | 说明 |
|
||||
| ---------------------------------------- | ----------------------- | -------------------------- |
|
||||
| `src/renderer/src/pages/CleanerPage.tsx` | `handleExecuteDeletion` | 清理入口 |
|
||||
| `src/renderer/src/hooks/useCleaner.ts` | `handleExecuteDeletion` | 调用 `runCleanerExecution` |
|
||||
| `src/renderer/src/hooks/cleaner/api.ts` | `runCleanerExecution` | 先获取数据再执行清理 |
|
||||
|
||||
### 主进程层 (Main)
|
||||
|
||||
| 文件 | 类/函数 | 说明 |
|
||||
| ---------------------------------------------------------------- | ----------------------------------------------- | ---------------- |
|
||||
| `src/main/ipc/validation-handler.ts` | `VALIDATION_GET_CLEANER_DATA` | IPC 入口 |
|
||||
| `src/main/services/validation/validation-application-service.ts` | `getCleanerData`, `loadMaterialCodesForCleaner` | **核心逻辑** |
|
||||
| `src/main/services/cleaner/cleaner-application-service.ts` | `runCleaner` | 执行 ERP 清理 |
|
||||
| `src/main/services/erp/cleaner.ts` | `CleanerService.clean` | ERP 浏览器自动化 |
|
||||
|
||||
### 数据库层 (DAO)
|
||||
|
||||
| 文件 | 类 | 说明 |
|
||||
| ----------------------------------------------------------- | ------------------------- | ---------------------------- |
|
||||
| `src/main/services/database/materials-to-be-deleted-dao.ts` | `MaterialsToBeDeletedDAO` | `MaterialsToBeDeleted`表操作 |
|
||||
| `src/main/services/database/discrete-material-plan-dao.ts` | `DiscreteMaterialPlanDAO` | 物料计划数据查询 |
|
||||
|
||||
---
|
||||
|
||||
## 数据流状态图
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> 物料录入:用户在 UI 输入物料
|
||||
物料录入 --> 待校验:保存到 MaterialsToBeDeleted 表
|
||||
|
||||
待校验 --> 校验中:点击"数据校验"
|
||||
校验中 --> 已标记:通过关键词匹配负责人
|
||||
已标记 --> 待清理:用户勾选物料
|
||||
|
||||
待清理 --> 清理执行中:点击"执行清理"
|
||||
清理执行中 --> 清理完成:ERP 删除成功
|
||||
清理执行中 --> 部分失败:部分物料删除失败
|
||||
|
||||
清理完成 --> [*]
|
||||
部分失败 --> [*]
|
||||
|
||||
note right of 待清理
|
||||
Admin 可以查看和选择
|
||||
所有负责人的物料
|
||||
end note
|
||||
|
||||
note right of 清理执行中
|
||||
从 MaterialsToBeDeleted 表
|
||||
根据 selectedManagers 过滤
|
||||
获取要删除的物料代码
|
||||
end note
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据流对比:Admin vs 普通用户
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "Admin 用户"
|
||||
A1[勾选多个 Manager]
|
||||
A2[SELECT FROM MaterialsToBeDeleted<br/>WHERE ManagerName IN selectedManagers]
|
||||
A3[获取所有选中负责人的物料]
|
||||
end
|
||||
|
||||
subgraph "普通用户"
|
||||
B1[只能看到自己的物料]
|
||||
B2[SELECT FROM MaterialsToBeDeleted<br/>WHERE ManagerName = username]
|
||||
B3[只能删除自己的物料]
|
||||
end
|
||||
|
||||
A1 --> A2 --> A3 --> Exec[执行清理]
|
||||
B1 --> B2 --> B3 --> Exec
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题解答
|
||||
|
||||
### Q1: **物料代码是如何被录入到 `MaterialsToBeDeleted` 表的?**
|
||||
|
||||
**A**: 有三种方式:
|
||||
|
||||
1. **用户手动勾选** → 在 CleanerPage 勾选物料 → 点击"确认删除" → `upsertBatch`
|
||||
2. **关键词自动匹配** → 校验时根据 `MaterialsTypeToBeDeleted` 配置自动分配负责人和标记
|
||||
3. **API 直接写入** → 其他服务调用 `materials.upsertBatch` IPC
|
||||
|
||||
### Q2: **Admin 如果不选 Manager 会怎样?**
|
||||
|
||||
**A**: Admin 可以不选 Manager,此时:
|
||||
|
||||
- 系统会尝试从"共享 Production ID"解析订单号
|
||||
- 然后从 `DiscreteMaterialPlanData` 表查询所有物料代码(不经过 `MaterialsToBeDeleted` 过滤)
|
||||
- 如果没有共享 Production ID,则返回空数组,无法执行清理
|
||||
|
||||
### Q3: **物料代码会在清理后被自动删除吗?**
|
||||
|
||||
**A**:
|
||||
|
||||
- ✅ **干运行模式**:不删除 ERP 数据,但会保留 UI 状态
|
||||
- ✅ **正式执行**:
|
||||
- ERP 中的物料计划被删除
|
||||
- `MaterialsToBeDeleted` 表中记录**不会被自动删除**(需要手动清理)
|
||||
|
||||
### Q4: **如何清理已删除的物料记录?**
|
||||
|
||||
**A**: Admin 可以在物料管理界面:
|
||||
|
||||
- 按 Manager 筛选
|
||||
- 批量删除已处理的物料记录
|
||||
- 调用 `materials.delete` IPC 接口
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
Admin 用户执行清理时,被清理物料的来源路径:
|
||||
|
||||
1. **主要来源**: `dbo.MaterialsToBeDeleted` 表
|
||||
2. **过滤条件**: `ManagerName IN (selectedManagers)`
|
||||
3. **执行流程**:
|
||||
- 从数据库查询物料代码
|
||||
- 结合订单号列表
|
||||
- 通过 Playwright 连接 ERP 系统
|
||||
- 逐个物料执行删除操作
|
||||
|
||||
**关键点**:被清理的物料**必须**先在 `MaterialsToBeDeleted` 表中存在记录,并且其 `ManagerName` 与 Admin 选择的负责人匹配。
|
||||
150
skills/mermaid-fixer/SKILL.md
Normal file
150
skills/mermaid-fixer/SKILL.md
Normal file
@@ -0,0 +1,150 @@
|
||||
---
|
||||
name: mermaid-fixer
|
||||
description: Fix Mermaid diagram syntax errors in Markdown files. Use when: (1) Mermaid diagrams aren't rendering or show parse errors, (2) User mentions "mermaid syntax error", "mermaid not working", "diagram broken", (3) CI/CD fails on Mermaid validation, (4) Documentation contains Mermaid code blocks that need fixing.
|
||||
---
|
||||
|
||||
# Mermaid Diagram Fixer
|
||||
|
||||
Automatically detects and fixes Mermaid diagram syntax errors in Markdown files using the `check-mermaid.js` validation script.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Run `check-mermaid.js`** to get detailed error reports
|
||||
2. **Parse error information** (line numbers, error types, code snippets)
|
||||
3. **Apply intelligent fixes** based on the specific error
|
||||
4. **Verify** by running the check again
|
||||
|
||||
## Core Script
|
||||
|
||||
The `scripts/check-mermaid.js` script is the heart of this skill. It:
|
||||
- Scans Markdown files for ```` ```mermaid ```` code blocks
|
||||
- Validates each block using the Mermaid CLI (`mmdc`)
|
||||
- Returns detailed error reports with line numbers and error messages
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Run the Check Script
|
||||
|
||||
```bash
|
||||
node scripts/check-mermaid.js <path/to/file.md>
|
||||
```
|
||||
|
||||
**Expected output format:**
|
||||
|
||||
- **No errors**: Returns exit code 0 with success message
|
||||
- **Has errors**: Returns exit code 1 with structured error report:
|
||||
|
||||
```markdown
|
||||
## 🚨 Mermaid 语法检查报告
|
||||
|
||||
**检查文件:** `example.md`
|
||||
**检查结果:** ❌ 发现 2 处语法错误 (共检测到 5 个代码块)
|
||||
|
||||
---
|
||||
|
||||
### ❌ 错误 #1 (代码块 #2)
|
||||
|
||||
- **文档位置:** 第 `51` 行至第 `86` 行
|
||||
|
||||
#### 核心错误详情
|
||||
\`\`\`text
|
||||
Error: Parse error on line 13: ...
|
||||
Expecting 'SQE', 'DOUBLECIRCLEEND', ... got 'PS'
|
||||
\`\`\`
|
||||
|
||||
#### 代码内容片段
|
||||
\`\`\`text
|
||||
[shows the problematic code]
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### Step 2: Parse and Understand Errors
|
||||
|
||||
Key information from each error:
|
||||
- **文档位置**: Which line numbers contain the error
|
||||
- **核心错误详情**: The parser error message
|
||||
- **代码内容片段**: The actual Mermaid code causing the issue
|
||||
|
||||
### Step 3: Apply Fixes Based on Error Type
|
||||
|
||||
#### Error Type: "got 'PS'" (Parentheses Issue)
|
||||
|
||||
**Cause**: Parentheses `()` in node labels or arrow labels within subgraphs
|
||||
|
||||
**Fix**: Quote the label
|
||||
```mermaid
|
||||
# Before (causes error)
|
||||
NodeA[Label (with parens)]
|
||||
-->|Label (parens)| NodeB
|
||||
|
||||
# After (fixed)
|
||||
NodeA["Label (with parens)"]
|
||||
-->"|Label (parens)|" NodeB
|
||||
```
|
||||
|
||||
#### Error Type: "got 'SQS'" (Square Brackets Issue)
|
||||
|
||||
**Cause**: Square brackets `[]` in node labels
|
||||
|
||||
**Fix**: Quote the label
|
||||
```mermaid
|
||||
# Before
|
||||
NodeA[Result[]<br/>text]
|
||||
|
||||
# After
|
||||
NodeA["Result[]<br/>text"]
|
||||
```
|
||||
|
||||
#### Error Type: "got 'DIAMOND_START'" or similar
|
||||
|
||||
**Cause**: Curly braces `{}` or other special characters in labels
|
||||
|
||||
**Fix**: Quote the label
|
||||
```mermaid
|
||||
# Before
|
||||
NodeA[Label {variable}]
|
||||
|
||||
# After
|
||||
NodeA["Label {variable}"]
|
||||
```
|
||||
|
||||
### Step 4: Apply the Fix to the File
|
||||
|
||||
1. Read the Markdown file
|
||||
2. Locate the problematic code block using the reported line numbers
|
||||
3. Apply the appropriate fix (quote labels with special characters)
|
||||
4. Save the file
|
||||
|
||||
### Step 5: Verify
|
||||
|
||||
Run `check-mermaid.js` again to confirm all errors are resolved.
|
||||
|
||||
## Common Fix Patterns
|
||||
|
||||
| Error Pattern | Fix Strategy |
|
||||
|---------------|--------------|
|
||||
| `NodeID[label (text)]` | Change to `NodeID["label (text)"]` |
|
||||
| `-->|label (text)|` | Change to `-->|"label (text)"\|` |
|
||||
| `NodeID[label[]text]` | Change to `NodeID["label[]text"]` |
|
||||
| `NodeID[label{text}]` | Change to `NodeID["label{text}"]` |
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Always quote labels** containing: `()`, `[]`, `{}`, `<`, `>`, `#`, or Chinese characters
|
||||
2. **Preserve the original structure** - only modify the problematic labels
|
||||
3. **Check all reported errors** - don't stop after fixing just one
|
||||
4. **Re-verify** after each fix round
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
User: "My Mermaid diagrams in docs/architecture.md aren't rendering"
|
||||
|
||||
Claude:
|
||||
1. Run: node scripts/check-mermaid.js docs/architecture.md
|
||||
2. Parse error output
|
||||
3. Identify errors (e.g., "got 'PS'" on line 45)
|
||||
4. Read file, locate line 45, find the Mermaid block
|
||||
5. Apply fix: quote labels with parentheses
|
||||
6. Save and verify
|
||||
```
|
||||
23
skills/mermaid-fixer/evals/evals.json
Normal file
23
skills/mermaid-fixer/evals/evals.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"skill_name": "mermaid-fixer",
|
||||
"evals": [
|
||||
{
|
||||
"id": 1,
|
||||
"prompt": "The Mermaid diagrams in demo_scripts/demo.md have syntax errors. Please fix them.",
|
||||
"expected_output": "The skill should run check-mermaid.js, identify the 3 syntax errors, and apply fixes by quoting labels with special characters. After fixes, all 9 Mermaid blocks should pass validation.",
|
||||
"files": ["demo_scripts/demo.md"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"prompt": "My documentation file README.md has broken Mermaid diagrams. Can you check and fix them?",
|
||||
"expected_output": "The skill runs check-mermaid.js on README.md, parses any error reports, applies appropriate fixes based on error types, and verifies the fixes by running the check again.",
|
||||
"files": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"prompt": "CI/CD is failing with Mermaid validation errors on docs/api.md. Please fix the diagrams.",
|
||||
"expected_output": "The skill identifies all Mermaid syntax errors using check-mermaid.js, applies fixes for each error type (parentheses, brackets, braces in labels), and ensures the file passes validation.",
|
||||
"files": []
|
||||
}
|
||||
]
|
||||
}
|
||||
128
skills/mermaid-fixer/scripts/check-mermaid.js
Normal file
128
skills/mermaid-fixer/scripts/check-mermaid.js
Normal file
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
// 获取命令行传入的 Markdown 文件路径
|
||||
const fileArg = process.argv[2];
|
||||
if (!fileArg) {
|
||||
console.error('❌ 请提供 Markdown 文件路径。用法: node check-mermaid.js <file.md>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const filePath = path.resolve(fileArg);
|
||||
const fileName = path.basename(filePath);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`❌ 文件不存在: ${filePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
// 正则匹配 ```mermaid ... ``` 代码块,兼容 Windows(\r\n) 和 Linux(\n)
|
||||
const mermaidRegex = /```mermaid\r?\n([\s\S]*?)```/g;
|
||||
let match;
|
||||
let blockCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
// 用于收集所有格式化后的错误报告块
|
||||
const errorReports = [];
|
||||
|
||||
while ((match = mermaidRegex.exec(content)) !== null) {
|
||||
blockCount++;
|
||||
|
||||
const fullMatch = match[0];
|
||||
const code = match[1];
|
||||
|
||||
// 1. 计算在 Markdown 文件中的绝对行号
|
||||
const textBeforeMatch = content.substring(0, match.index);
|
||||
const startLine = textBeforeMatch.split(/\r?\n/).length;
|
||||
const endLine = startLine + fullMatch.split(/\r?\n/).length - 1;
|
||||
|
||||
// 创建临时文件存放单个 Mermaid 代码
|
||||
const tmpFile = path.join(os.tmpdir(), `mermaid-check-${Date.now()}-${blockCount}.mmd`);
|
||||
fs.writeFileSync(tmpFile, code.trim());
|
||||
|
||||
try {
|
||||
// 调用 mmdc 进行静默渲染检查
|
||||
execSync(`npx mmdc -i "${tmpFile}" -o "${tmpFile}.svg" -q`, { stdio: 'pipe' });
|
||||
} catch (error) {
|
||||
errorCount++;
|
||||
|
||||
// 2. 净化报错信息:剔除底层执行堆栈
|
||||
const stderr = error.stderr ? error.stderr.toString() : error.message;
|
||||
const errorLines = stderr.split(/\r?\n/);
|
||||
const cleanErrorLines = [];
|
||||
|
||||
for (const line of errorLines) {
|
||||
if (line.trim().startsWith('at ') ||
|
||||
line.includes('Parser3.parseError') ||
|
||||
line.includes('fromText')) {
|
||||
break;
|
||||
}
|
||||
cleanErrorLines.push(line);
|
||||
}
|
||||
const cleanErrorOutput = cleanErrorLines.join('\n').trim();
|
||||
|
||||
// 3. 截断代码内容:最少3行,最多6行
|
||||
const codeLines = code.split(/\r?\n/).filter(l => l.trim() !== '');
|
||||
let displayCode = '';
|
||||
|
||||
if (codeLines.length <= 6) {
|
||||
displayCode = codeLines.join('\n');
|
||||
} else {
|
||||
const head = codeLines.slice(0, 3).join('\n');
|
||||
const tail = codeLines.slice(-3).join('\n');
|
||||
displayCode = `${head}\n ...\n ... (中间省略 ${codeLines.length - 6} 行) ...\n ...\n${tail}`;
|
||||
}
|
||||
|
||||
// 4. 组装单个错误的 Markdown 块
|
||||
errorReports.push(`### ❌ 错误 #${errorCount} (代码块 #${blockCount})
|
||||
|
||||
- **文档位置:** 第 \`${startLine}\` 行至第 \`${endLine}\` 行
|
||||
|
||||
#### 核心错误详情
|
||||
\`\`\`text
|
||||
${cleanErrorOutput}
|
||||
\`\`\`
|
||||
|
||||
#### 代码内容片段
|
||||
\`\`\`text
|
||||
${displayCode}
|
||||
\`\`\``);
|
||||
|
||||
} finally {
|
||||
// 清理临时文件
|
||||
if (fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile);
|
||||
if (fs.existsSync(`${tmpFile}.svg`)) fs.unlinkSync(`${tmpFile}.svg`);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 生成最终的 Markdown 报告输出
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
if (errorCount > 0) {
|
||||
// 存在错误的情况
|
||||
console.log(`## 🚨 Mermaid 语法检查报告\n`);
|
||||
console.log(`**检查文件:** \`${fileName}\``);
|
||||
console.log(`**检查结果:** ❌ 发现 ${errorCount} 处语法错误 (共检测到 ${blockCount} 个代码块)\n`);
|
||||
console.log(`---\n`);
|
||||
console.log(errorReports.join('\n\n---\n\n'));
|
||||
|
||||
// 返回非零状态码,确保在 CI/CD 或 Git Hook 中能够阻断流程
|
||||
process.exit(1);
|
||||
} else {
|
||||
// 全部正确或没有代码块的情况
|
||||
console.log(`## 🎉 Mermaid 语法检查报告\n`);
|
||||
console.log(`**检查文件:** \`${fileName}\``);
|
||||
if (blockCount === 0) {
|
||||
console.log(`**检查结果:** ⚠️ 未检测到 Mermaid 代码块`);
|
||||
} else {
|
||||
console.log(`**检查结果:** ✅ 全部通过 (共检测到 ${blockCount} 个代码块)`);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
Reference in New Issue
Block a user