@yorha2b-lab/autodev 1.0.0
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/.env.example +12 -0
- package/CONTRIBUTING.md +118 -0
- package/LICENSE +21 -0
- package/README.md +173 -0
- package/bin/autodev.js +40 -0
- package/config.js +28 -0
- package/package.json +50 -0
- package/src/commands/watch-api.js +41 -0
- package/src/commands/watch-page.js +94 -0
- package/src/commands/watch-part.js +46 -0
- package/src/core/react-compiler.js +53 -0
- package/src/core/task-queue.js +38 -0
- package/src/prompts/mock.js +5 -0
- package/src/prompts/system.js +9 -0
- package/src/prompts/watch-api.js +11 -0
- package/src/prompts/watch-page.js +39 -0
- package/src/prompts/watch-part.js +26 -0
- package/src/services/llm.js +66 -0
- package/src/utils/utils.js +69 -0
- package/templates/react/components/EditableCell.js +76 -0
- package/templates/react/components/MyBaseForm.js +47 -0
- package/templates/react/components/MyModalForm.js +51 -0
- package/templates/react/components/MyModalTable.js +40 -0
- package/templates/react/components/MySearchForm.js +46 -0
- package/templates/react/components/MyTable.js +48 -0
- package/templates/react/components/index.js +40 -0
- package/templates/react/hooks/useTableQuery.js +40 -0
- package/templates/react/index.hbs +110 -0
- package/templates/react/resource.hbs +18 -0
package/.env.example
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# AI模型API配置
|
|
2
|
+
# 请替换为你的实际API密钥
|
|
3
|
+
API_KEY=your_api_key_here
|
|
4
|
+
|
|
5
|
+
# API基础URL
|
|
6
|
+
# 如果使用OpenAI官方API,可以设置为: https://api.openai.com/v1
|
|
7
|
+
# 如果使用其他兼容API,请设置对应的基础URL
|
|
8
|
+
BASE_URL=your_api_base_url_here
|
|
9
|
+
|
|
10
|
+
# 可选:自定义模型配置
|
|
11
|
+
# VISION_MODEL=qwen3.5-plus
|
|
12
|
+
# TEXT_MODEL=qwen-turbo
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# 贡献指南
|
|
2
|
+
|
|
3
|
+
感谢您对 Auto CRUD Copilot 项目的关注!我们欢迎任何形式的贡献,包括但不限于:
|
|
4
|
+
|
|
5
|
+
- 🐛 报告Bug
|
|
6
|
+
- 💡 提出新功能建议
|
|
7
|
+
- 📝 改进文档
|
|
8
|
+
- 🔧 提交代码修复
|
|
9
|
+
- 🎨 添加新模板或组件
|
|
10
|
+
|
|
11
|
+
## 🚀 如何贡献
|
|
12
|
+
|
|
13
|
+
### 报告Bug
|
|
14
|
+
|
|
15
|
+
如果您发现了Bug,请通过以下步骤报告:
|
|
16
|
+
|
|
17
|
+
1. 检查是否已有相关的Issue
|
|
18
|
+
2. 如果没有,请创建新的Issue
|
|
19
|
+
3. 在Issue中提供以下信息:
|
|
20
|
+
- 问题描述
|
|
21
|
+
- 复现步骤
|
|
22
|
+
- 期望行为
|
|
23
|
+
- 实际行为
|
|
24
|
+
- 环境信息(操作系统、Node版本等)
|
|
25
|
+
- 相关截图或错误日志
|
|
26
|
+
|
|
27
|
+
### 提出新功能建议
|
|
28
|
+
|
|
29
|
+
1. 检查是否已有相关的Issue或讨论
|
|
30
|
+
2. 如果没有,请创建新的Issue
|
|
31
|
+
3. 在Issue中详细描述:
|
|
32
|
+
- 功能描述
|
|
33
|
+
- 使用场景
|
|
34
|
+
- 预期效果
|
|
35
|
+
- 可能的实现方案
|
|
36
|
+
|
|
37
|
+
特别欢迎 Vue / Angular 开发者提交对应的 Template 和 Compiler 实现! 我们已经为您预留好了插件接口。
|
|
38
|
+
|
|
39
|
+
### 提交代码
|
|
40
|
+
|
|
41
|
+
1. Fork 本仓库
|
|
42
|
+
2. 创建您的特性分支 (`git checkout -b feature/AmazingFeature`)
|
|
43
|
+
3. 提交您的更改 (`git commit -m 'Add some AmazingFeature'`)
|
|
44
|
+
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
|
45
|
+
5. 创建一个 Pull Request
|
|
46
|
+
|
|
47
|
+
## 📋 开发指南
|
|
48
|
+
|
|
49
|
+
### 环境准备
|
|
50
|
+
|
|
51
|
+
1. 克隆仓库
|
|
52
|
+
```bash
|
|
53
|
+
git clone https://github.com/yorha2b-lab/auto-crud-copilot.git
|
|
54
|
+
cd auto-crud-copilot
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
2. 安装依赖
|
|
58
|
+
```bash
|
|
59
|
+
npm install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
3. 创建环境变量文件
|
|
63
|
+
```bash
|
|
64
|
+
cp .env.example .env
|
|
65
|
+
# 编辑 .env 文件,填入您的API密钥
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 项目结构
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
auto-crud-copilot/
|
|
72
|
+
├── bin/ # 可执行文件
|
|
73
|
+
├── src/
|
|
74
|
+
│ ├── commands/ # 命令实现
|
|
75
|
+
│ ├── core/ # 核心逻辑
|
|
76
|
+
│ ├── prompts/ # AI提示词
|
|
77
|
+
│ ├── services/ # 服务层
|
|
78
|
+
│ └── utils/ # 工具函数
|
|
79
|
+
├── templates/ # 代码模板
|
|
80
|
+
│ ├── react/
|
|
81
|
+
│ ├── vue/
|
|
82
|
+
│ └── angular/
|
|
83
|
+
└── config.js # 配置文件
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- 提交信息遵循[Conventional Commits](https://www.conventionalcommits.org/zh-hans/v1.0.0/)规范
|
|
87
|
+
|
|
88
|
+
### 测试
|
|
89
|
+
|
|
90
|
+
在提交代码前,请确保:
|
|
91
|
+
|
|
92
|
+
1. 代码通过所有测试
|
|
93
|
+
2. 代码符合项目的编码规范
|
|
94
|
+
|
|
95
|
+
## 📝 Pull Request 流程
|
|
96
|
+
|
|
97
|
+
1. 确保您的PR描述清晰,说明了更改的目的和实现方式
|
|
98
|
+
2. 确保您的代码没有合并冲突
|
|
99
|
+
3. 确保所有测试通过
|
|
100
|
+
4. 等待维护者审查和合并
|
|
101
|
+
|
|
102
|
+
## 🏷️ 发布流程
|
|
103
|
+
|
|
104
|
+
项目维护者负责发布新版本,遵循[语义化版本](https://semver.org/lang/zh-CN/)规范。
|
|
105
|
+
|
|
106
|
+
## 🤝 行为准则
|
|
107
|
+
|
|
108
|
+
请遵守我们的行为准则,保持友好和尊重的交流环境。
|
|
109
|
+
|
|
110
|
+
## 📞 联系方式
|
|
111
|
+
|
|
112
|
+
如有任何问题,请通过以下方式联系我们:
|
|
113
|
+
|
|
114
|
+
- 创建Issue
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
再次感谢您的贡献!🎉
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yorha2b-Lab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Auto CRUD Copilot
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@yorha2b-lab/autodev)
|
|
4
|
+
[](https://www.npmjs.com/package/@yorha2b-lab/autodev)
|
|
5
|
+
[](https://github.com/yorha2b-lab/auto-crud-copilot/stargazers)
|
|
6
|
+
[](https://github.com/yorha2b-lab/auto-crud-copilot/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
基于视觉大模型的前端(Umi+Antd)全自动 CRUD 代码生成器 🚀
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
拒绝重复劳动!为什么你需要 Auto CRUD Copilot?
|
|
13
|
+
告别手写 Table/Form:截图即代码,提升你的开发效率,告别烦人的复制粘贴。
|
|
14
|
+
拒绝接口联调痛苦:自动读取 Swagger,AI 帮你抹平前后端字段命名差异。
|
|
15
|
+
架构解耦:核心逻辑与 UI 框架分离,React/Vue/Angular 均可适配。
|
|
16
|
+
|
|
17
|
+
## ✨ 特性
|
|
18
|
+
|
|
19
|
+
- 🖼️ **视觉识别**: 通过截图自动识别页面结构,生成对应的CRUD代码
|
|
20
|
+
- 🔧 **智能生成**: 支持React、Vue、Angular等多种前端框架模板
|
|
21
|
+
- 📊 **表格组件**: 自动生成可编辑、可排序、可筛选的数据表格
|
|
22
|
+
- 📝 **表单组件**: 智能生成搜索表单和模态框表单
|
|
23
|
+
- 🔌 **API对齐**: 自动对齐Swagger接口与前端字段映射
|
|
24
|
+
- 🎨 **UI组件**: 支持多种UI组件类型(输入框、选择器、日期等)
|
|
25
|
+
- 📱 **响应式**: 生成的代码支持响应式布局
|
|
26
|
+
|
|
27
|
+
## 🚀 快速开始
|
|
28
|
+
|
|
29
|
+
### 安装
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g auto-crud-copilot
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 环境配置
|
|
36
|
+
|
|
37
|
+
创建 `.env` 文件并配置以下环境变量:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# AI模型API配置
|
|
41
|
+
API_KEY=your_api_key_here
|
|
42
|
+
BASE_URL=your_api_base_url_here
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 基本使用
|
|
46
|
+
|
|
47
|
+
1. **生成完整CRUD页面**
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# 将页面截图放入screenShot目录
|
|
51
|
+
autodev watch:page
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
2. **生成局部UI组件**
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# 将组件截图放入screenPart目录
|
|
58
|
+
autodev watch:part
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
3. **对齐API字段**
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# 将Swagger文档放入swagger目录
|
|
65
|
+
autodev watch:api
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 📖 详细使用指南
|
|
69
|
+
|
|
70
|
+
### watch:page 命令
|
|
71
|
+
|
|
72
|
+
监听 `screenShot` 目录下的截图文件,自动生成完整的增删改查页面。
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
autodev watch:page -t react
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
参数说明:
|
|
79
|
+
- `-t, --template <type>`: 指定前端框架模板,默认为 `react`
|
|
80
|
+
|
|
81
|
+
### watch:part 命令
|
|
82
|
+
|
|
83
|
+
监听 `screenPart` 目录下的截图文件,自动生成局部UI组件。
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
autodev watch:part -t vue
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### watch:api 命令
|
|
90
|
+
|
|
91
|
+
监听 `swagger` 目录下的API文档,自动对齐真实接口字段。
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
autodev watch:api
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 📁 项目结构
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
your-project/
|
|
101
|
+
├── screenShot/ # 页面截图目录
|
|
102
|
+
├── screenPart/ # 组件截图目录
|
|
103
|
+
├── swagger/ # API文档目录
|
|
104
|
+
├── mock/ # 生成的Mock数据
|
|
105
|
+
├── src/
|
|
106
|
+
│ ├── pages/ # 生成的页面代码
|
|
107
|
+
│ ├── components/ # 生成的组件代码
|
|
108
|
+
│ ├── hooks/ # 生成的Hook代码
|
|
109
|
+
└── config.js # 配置文件
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## ⚙️ 配置
|
|
113
|
+
|
|
114
|
+
在 `config.js` 中可以配置以下选项:
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
module.exports = {
|
|
118
|
+
// 是否需要生成 Mock 数据
|
|
119
|
+
needMock: false,
|
|
120
|
+
|
|
121
|
+
// 使用的 AI 模型类型
|
|
122
|
+
visionModel: 'qwen3.5-plus',
|
|
123
|
+
textModel: 'qwen-turbo',
|
|
124
|
+
|
|
125
|
+
// 目标项目的目录路径
|
|
126
|
+
hooksDir: 'src/hooks',
|
|
127
|
+
pagesDir: 'src/pages',
|
|
128
|
+
utilsDir: 'src/utils',
|
|
129
|
+
componentsDir: 'src/components',
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 🎯 支持的UI组件
|
|
134
|
+
|
|
135
|
+
- 输入框 (Input)
|
|
136
|
+
- 数字输入框 (InputNumber)
|
|
137
|
+
- 选择器 (Select)
|
|
138
|
+
- 单选框 (Radio)
|
|
139
|
+
- 复选框 (Checkbox)
|
|
140
|
+
- 日期选择器 (DatePicker)
|
|
141
|
+
- 日期范围选择器 (RangePicker)
|
|
142
|
+
- 级联选择器 (Cascader)
|
|
143
|
+
- 自动完成 (AutoComplete)
|
|
144
|
+
- 文本域 (TextArea)
|
|
145
|
+
- 文件上传 (Upload)
|
|
146
|
+
|
|
147
|
+
## 🤝 贡献
|
|
148
|
+
|
|
149
|
+
欢迎贡献代码!请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 了解如何参与项目开发。
|
|
150
|
+
|
|
151
|
+
## 📄 许可证
|
|
152
|
+
|
|
153
|
+
本项目采用 [MIT](LICENSE) 许可证。
|
|
154
|
+
|
|
155
|
+
## 🙏 致谢
|
|
156
|
+
|
|
157
|
+
感谢以下开源项目:
|
|
158
|
+
|
|
159
|
+
- [OpenAI](https://openai.com/) - 提供强大的AI能力
|
|
160
|
+
- [Ant Design](https://ant.design/) - 优秀的企业级UI设计语言
|
|
161
|
+
- [UmiJS](https://umijs.org/) - 企业级前端应用框架
|
|
162
|
+
|
|
163
|
+
## 📞 联系方式
|
|
164
|
+
|
|
165
|
+
如有问题或建议,欢迎提交 [Issue](https://github.com/yorha2b-lab/auto-crud-copilot/issues)。
|
|
166
|
+
|
|
167
|
+
## 🛠️ 常见问题 (FAQ)
|
|
168
|
+
Q: 这个工具收费吗?
|
|
169
|
+
A: 工具本身开源免费,但调用的 AI 模型(如 GPT-4v, Qwen-VL)可能需要你配置自己的 API Key。建议使用阿里云 Qwen-VL 等高性价比模型。
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
⭐ 如果这个项目对你有帮助,请给个Star支持一下!
|
package/bin/autodev.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const chalk = require('chalk')
|
|
4
|
+
const figlet = require('figlet')
|
|
5
|
+
|
|
6
|
+
console.log(chalk.cyan(figlet.textSync('AutoDev', { horizontalLayout: 'full' })))
|
|
7
|
+
console.log(chalk.gray('--------------------------------------------------'))
|
|
8
|
+
console.log(chalk.white(' [System] ') + chalk.green('YoRHa No.2 Type B Unit: ') + chalk.cyan('Online'))
|
|
9
|
+
console.log(chalk.white(' [Mission] ') + chalk.yellow('Generate Frontend CRUD: ') + chalk.cyan('Awaiting Command'))
|
|
10
|
+
console.log(chalk.gray('--------------------------------------------------\n'))
|
|
11
|
+
|
|
12
|
+
require("dotenv").config({
|
|
13
|
+
path: path.resolve(__dirname, '../.env')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const { program } = require('commander')
|
|
17
|
+
const watchApi = require('../src/commands/watch-api')
|
|
18
|
+
const watchPage = require('../src/commands/watch-page')
|
|
19
|
+
const watchPart = require('../src/commands/watch-part')
|
|
20
|
+
|
|
21
|
+
program.version('1.0.0').description('AI驱动的前端CRUD代码生成器')
|
|
22
|
+
|
|
23
|
+
program.option('-t, --template <type>', '指定前端框架模板', 'react')
|
|
24
|
+
|
|
25
|
+
program
|
|
26
|
+
.command('watch:page')
|
|
27
|
+
.description('监听截图,自动生成完整的增删改查页面')
|
|
28
|
+
.action(() => watchPage(program.opts()))
|
|
29
|
+
|
|
30
|
+
program
|
|
31
|
+
.command('watch:part')
|
|
32
|
+
.description('监听截图,自动生成局部 UI 组件')
|
|
33
|
+
.action(() => watchPart(program.opts()))
|
|
34
|
+
|
|
35
|
+
program
|
|
36
|
+
.command('watch:api')
|
|
37
|
+
.description('监听 Swagger,自动对齐真实接口字段')
|
|
38
|
+
.action(() => watchApi())
|
|
39
|
+
|
|
40
|
+
program.parse(process.argv)
|
package/config.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto CRUD Copilot 配置文件
|
|
3
|
+
*
|
|
4
|
+
* 此文件包含了项目运行所需的所有配置项
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
// 是否需要生成 Mock 数据
|
|
9
|
+
needMock: false,
|
|
10
|
+
|
|
11
|
+
// 使用的 AI 模型类型
|
|
12
|
+
visionModel: 'qwen3.5-plus',
|
|
13
|
+
|
|
14
|
+
textModel: 'qwen-turbo',
|
|
15
|
+
|
|
16
|
+
// 目标项目的 hooks 目录路径
|
|
17
|
+
hooksDir: 'src/hooks',
|
|
18
|
+
|
|
19
|
+
// 目标项目的 pages 目录路径
|
|
20
|
+
pagesDir: 'src/pages',
|
|
21
|
+
|
|
22
|
+
// 目标项目的 utils 目录路径
|
|
23
|
+
utilsDir: 'src/utils',
|
|
24
|
+
|
|
25
|
+
// 目标项目的 components 目录路径
|
|
26
|
+
componentsDir: 'src/components',
|
|
27
|
+
|
|
28
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yorha2b-lab/autodev",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "基于视觉大模型的前端(Umi+Antd)全自动 CRUD 代码生成器",
|
|
5
|
+
"bin": {
|
|
6
|
+
"autodev": "bin/autodev.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"crud",
|
|
13
|
+
"generator",
|
|
14
|
+
"ai",
|
|
15
|
+
"vision",
|
|
16
|
+
"react",
|
|
17
|
+
"vue",
|
|
18
|
+
"angular",
|
|
19
|
+
"antd",
|
|
20
|
+
"umi",
|
|
21
|
+
"code-generation",
|
|
22
|
+
"automation",
|
|
23
|
+
"frontend",
|
|
24
|
+
"scaffold"
|
|
25
|
+
],
|
|
26
|
+
"author": "yorha2b-Lab",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"type": "commonjs",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/yorha2b-lab/auto-crud-copilot.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/yorha2b-lab/auto-crud-copilot/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/yorha2b-lab/auto-crud-copilot#readme",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"chokidar": "^5.0.0",
|
|
39
|
+
"commander": "^14.0.3",
|
|
40
|
+
"dotenv": "^17.3.1",
|
|
41
|
+
"handlebars": "^4.7.8",
|
|
42
|
+
"json-stringify-pretty-compact": "^4.0.0",
|
|
43
|
+
"json5": "^2.2.3",
|
|
44
|
+
"openai": "^6.24.0",
|
|
45
|
+
"sharp": "^0.34.5",
|
|
46
|
+
"chalk": "^4.1.2",
|
|
47
|
+
"figlet": "^1.10.0",
|
|
48
|
+
"ora": "^5.4.1"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const chokidar = require('chokidar')
|
|
4
|
+
const config = require('../../config.js')
|
|
5
|
+
const { alignSwaggerFields } = require('../services/llm.js')
|
|
6
|
+
|
|
7
|
+
const watchApi = () => {
|
|
8
|
+
const watcher = chokidar.watch('./swagger', {
|
|
9
|
+
persistent: true,
|
|
10
|
+
ignored: /(^|[\/\\])\../,
|
|
11
|
+
awaitWriteFinish: {
|
|
12
|
+
stabilityThreshold: 500,
|
|
13
|
+
pollInterval: 100
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
watcher.on('add', async filePath => {
|
|
18
|
+
const startTime = Date.now()
|
|
19
|
+
const fileName = path.basename(filePath, path.extname(filePath))
|
|
20
|
+
console.log(`🚀 检测到新文件: ${fileName}, 开始识别...`)
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const swaggerStr = fs.readFileSync(filePath, 'utf8')
|
|
24
|
+
let resourceStr = fs.readFileSync(`./${config.pagesDir}/${fileName}/resource.js`, 'utf8')
|
|
25
|
+
const result = await alignSwaggerFields(swaggerStr, resourceStr)
|
|
26
|
+
Object.entries(result).forEach(([oldField, newField]) => {
|
|
27
|
+
if (oldField === newField) return
|
|
28
|
+
const regex = new RegExp(`(dataIndex|name)\\s*:\\s*['"]${oldField}['"]`, 'g')
|
|
29
|
+
resourceStr = resourceStr.replace(regex, `$1: '${newField}'`)
|
|
30
|
+
})
|
|
31
|
+
fs.writeFileSync(path.join(`./${config.pagesDir}/${fileName}/resource.js`), resourceStr.replace(/"(\w+)":/g, '$1:').replace(/"/g, "'"))
|
|
32
|
+
const endTime = Date.now()
|
|
33
|
+
//fs.unlinkSync(filePath)
|
|
34
|
+
console.log(`识别完成:耗时 ${(endTime - startTime) / 1000} 秒`)
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(`识别 ${fileName} 失败:`, error)
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = watchApi
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const ora = require('ora')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const chokidar = require('chokidar')
|
|
5
|
+
const Handlebars = require('handlebars')
|
|
6
|
+
const config = require('../../config.js')
|
|
7
|
+
const pagePrompt = require('../prompts/watch-page.js')
|
|
8
|
+
const { createTaskQueue } = require('../core/task-queue')
|
|
9
|
+
const stringify = require('json-stringify-pretty-compact')
|
|
10
|
+
const { recognizePage, generateMock } = require('../services/llm.js')
|
|
11
|
+
const { copyHooks, copyComponents, getExistingMenus } = require('../utils/utils.js')
|
|
12
|
+
|
|
13
|
+
const watchPage = options => {
|
|
14
|
+
|
|
15
|
+
const compilerPath = path.join(__dirname, `../core/${options.template}-compiler.js`)
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(compilerPath)) {
|
|
18
|
+
console.error(`\n❌ 糟糕!暂不支持 [${options.template}] 框架的自动生成。`)
|
|
19
|
+
console.log(`💡 提示:目前仅内置了 react 编译器。`)
|
|
20
|
+
console.log(`🚀 强烈欢迎社区大佬提 PR 补充 ${options.template}-compiler.js !\n`)
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { index, resource } = require(compilerPath)
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
copyHooks(options)
|
|
28
|
+
copyComponents(options)
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error('❌ 程序运行出错:', error)
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 创建任务队列,限制并发数为 2。
|
|
34
|
+
*
|
|
35
|
+
* 为什么限制并发?
|
|
36
|
+
* 1. 视觉大模型的 API 计费昂贵且通常有严格的 TPM/RPM (每分钟请求数) 限流。
|
|
37
|
+
* 2. 如果用户一次性丢入 5 张截图,全量并发极易触发 HTTP 429 Too Many Requests 报错。
|
|
38
|
+
* 3. 并发设为 2 是实测下来速度与稳定性的最佳平衡点。
|
|
39
|
+
*/
|
|
40
|
+
const queue = createTaskQueue(2)
|
|
41
|
+
const menus = getExistingMenus()
|
|
42
|
+
const tplDir = path.join(__dirname, `../../templates/${options.template}`)
|
|
43
|
+
const indexTpl = Handlebars.compile(fs.readFileSync(path.join(tplDir, 'index.hbs'), 'utf-8'))
|
|
44
|
+
const resourceTpl = Handlebars.compile(fs.readFileSync(path.join(tplDir, 'resource.hbs'), 'utf-8'))
|
|
45
|
+
queue.onIdle(() => {
|
|
46
|
+
console.log('💾 所有排队的截图已处理完成,正在统一更新 menu 配置...')
|
|
47
|
+
const constantDir = path.join(process.cwd(), config.utilsDir)
|
|
48
|
+
if (!fs.existsSync(constantDir)) fs.mkdirSync(constantDir, { recursive: true })
|
|
49
|
+
fs.writeFileSync(path.join(constantDir, 'constant.js'), `export const menus = ${stringify.default(menus, { indent: 4, maxLength: 50 })}`)
|
|
50
|
+
console.log('✅ Menu 配置更新成功!')
|
|
51
|
+
})
|
|
52
|
+
const watcher = chokidar.watch('./screenShot', {
|
|
53
|
+
persistent: true,
|
|
54
|
+
ignored: /(^|[\/\\])\../,
|
|
55
|
+
awaitWriteFinish: {
|
|
56
|
+
stabilityThreshold: 500,
|
|
57
|
+
pollInterval: 100
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
watcher.on('add', filePath => {
|
|
61
|
+
queue.add(async () => {
|
|
62
|
+
const startTime = Date.now()
|
|
63
|
+
const mockDir = path.join(process.cwd(), 'mock')
|
|
64
|
+
const fileName = path.basename(filePath, path.extname(filePath))
|
|
65
|
+
const targetDir = path.join(process.cwd(), config.pagesDir, fileName)
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
if (!fs.existsSync(mockDir)) fs.mkdirSync(mockDir, { recursive: true })
|
|
69
|
+
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true })
|
|
70
|
+
const spinner = ora(`🚀 Pod 042: 开始处理排队任务: ${fileName}...`)
|
|
71
|
+
spinner.start()
|
|
72
|
+
const pageConfig = await recognizePage(pagePrompt, filePath)
|
|
73
|
+
fs.writeFileSync(path.join(targetDir, 'resource.js'), resource({ pageConfig, resourceTpl }))
|
|
74
|
+
fs.writeFileSync(path.join(targetDir, 'index.js'), index({ fileName, indexTpl, pageConfig }))
|
|
75
|
+
if (!menus.find(m => m.key === fileName)) {
|
|
76
|
+
menus.push({ label: fileName, key: fileName })
|
|
77
|
+
}
|
|
78
|
+
if (config.needMock) {
|
|
79
|
+
console.log(`🚀 开始生成 ${fileName} 的 Mock 数据...`)
|
|
80
|
+
const rawContent = await generateMock(pageConfig.table.columns, fileName)
|
|
81
|
+
fs.writeFileSync(path.join(mockDir, `${fileName}.js`), `export default ${stringify.default(rawContent, { indent: 4, maxLength: 200 })}`.replaceAll(`"'`, `"`).replaceAll(`'"`, `"`))
|
|
82
|
+
console.log(`生成 ${fileName} 的 Mock 数据耗时: ${(Date.now() - startTime) / 1000} 秒`)
|
|
83
|
+
}
|
|
84
|
+
const endTime = Date.now()
|
|
85
|
+
//fs.unlinkSync(filePath)
|
|
86
|
+
spinner.succeed(`✅ Pod 042: 模块 [${fileName}] 装配完成!耗时 ${(endTime - startTime) / 1000} 秒`)
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`❌ 处理失败: ${filePath}`, error)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = watchPage
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const chokidar = require('chokidar')
|
|
3
|
+
const partPrompt = require('../prompts/watch-part.js')
|
|
4
|
+
const { recognizePage } = require('../services/llm.js')
|
|
5
|
+
const stringify = require('json-stringify-pretty-compact')
|
|
6
|
+
|
|
7
|
+
const watchPart = () => {
|
|
8
|
+
const watcher = chokidar.watch('./screenPart', {
|
|
9
|
+
persistent: true,
|
|
10
|
+
ignored: /(^|[\/\\])\../,
|
|
11
|
+
awaitWriteFinish: {
|
|
12
|
+
stabilityThreshold: 500,
|
|
13
|
+
pollInterval: 100
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
watcher.on('add', async filePath => {
|
|
17
|
+
try {
|
|
18
|
+
const startTime = Date.now()
|
|
19
|
+
console.log(`🚀 检测到新图片: 开始识别...`)
|
|
20
|
+
const pageConfig = await recognizePage(partPrompt, filePath)
|
|
21
|
+
const optionDict = pageConfig.optionDict || {}
|
|
22
|
+
delete pageConfig.optionDict
|
|
23
|
+
let mainConfigStr = stringify.default(pageConfig, { indent: 4, maxLength: 200 })
|
|
24
|
+
.replace(/"(\w+)":/g, '$1:')
|
|
25
|
+
.replace(/"/g, "'")
|
|
26
|
+
.replace(/['"]_CODE_([\s\S]*?)_CODE_['"]/g, '$1')
|
|
27
|
+
.replace(/_CODE_/g, '')
|
|
28
|
+
let optionsCodeStr = ''
|
|
29
|
+
Object.keys(optionDict).forEach(key => {
|
|
30
|
+
const varName = key.replace('_CODE_', '')
|
|
31
|
+
const optionsArray = optionDict[key]
|
|
32
|
+
const arrayItemsStr = optionsArray.map(opt => ` { label: '${opt.label}', value: '${opt.value}' }`).join(',\n')
|
|
33
|
+
optionsCodeStr += `\nexport const ${varName} = [\n${arrayItemsStr}\n]\n`
|
|
34
|
+
})
|
|
35
|
+
const finalResult = `${mainConfigStr}\n${optionsCodeStr}`
|
|
36
|
+
const endTime = Date.now()
|
|
37
|
+
console.log(`🎉 识别完成!耗时 ${(endTime - startTime) / 1000} 秒\n================\n\n${finalResult}\n\n================`)
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('识别图片失败', error)
|
|
40
|
+
} finally {
|
|
41
|
+
fs.unlinkSync(filePath)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = watchPart
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const Handlebars = require('handlebars')
|
|
2
|
+
const stringify = require('json-stringify-pretty-compact')
|
|
3
|
+
const { cleanCode, generateSmartImports } = require('../utils/utils.js')
|
|
4
|
+
|
|
5
|
+
Handlebars.registerHelper('stringify', (context, maxLength = 200) => context ? new Handlebars.SafeString(stringify.default(context, { indent: 4, maxLength })) : '[]')
|
|
6
|
+
|
|
7
|
+
const resource = ({ pageConfig, resourceTpl }) => {
|
|
8
|
+
const hasTabs = pageConfig.tabs?.length > 0
|
|
9
|
+
|
|
10
|
+
const viewData = {
|
|
11
|
+
hasTabs,
|
|
12
|
+
tabs: pageConfig.tabs,
|
|
13
|
+
formItems: pageConfig.formItems,
|
|
14
|
+
formItemsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, pageConfig.formItems])) : pageConfig.formItems,
|
|
15
|
+
columnsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, pageConfig.table.columns])) : pageConfig.table.columns,
|
|
16
|
+
dictBlocks: pageConfig.formItems
|
|
17
|
+
?.filter(item => item.type === 'select')
|
|
18
|
+
?.map(item => ({ name: item.options.replace('_CODE_', ''), data: pageConfig.optionDict[item.options] ?? [] }))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const rawCode = resourceTpl(viewData)
|
|
22
|
+
return cleanCode(rawCode)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const index = ({ fileName, indexTpl, pageConfig }) => {
|
|
26
|
+
const hasTabs = pageConfig.tabs?.length > 0
|
|
27
|
+
const hasOperate = pageConfig.table.operation?.length > 0
|
|
28
|
+
|
|
29
|
+
let columnsValue = hasTabs ? 'columns[activeKey]' : 'columns'
|
|
30
|
+
if (hasOperate) {
|
|
31
|
+
columnsValue = `${columnsValue}.concat(operate)`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const viewData = {
|
|
35
|
+
hasTabs,
|
|
36
|
+
fileName,
|
|
37
|
+
hasOperate,
|
|
38
|
+
columnsValue,
|
|
39
|
+
tabs: pageConfig.tabs,
|
|
40
|
+
hasPagination: pageConfig.table.pagination,
|
|
41
|
+
operations: pageConfig.table.operation || [],
|
|
42
|
+
hasRowSelection: pageConfig.table.rowSelection,
|
|
43
|
+
hasStaticInfo: pageConfig.table.staticInfo?.has,
|
|
44
|
+
staticInfoText: pageConfig.table.staticInfo?.text,
|
|
45
|
+
functionButtons: pageConfig.functionButton?.filter(item => !['查询', '重置'].includes(item.btn)) || []
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const bodyCode = indexTpl(viewData)
|
|
49
|
+
const importsStr = generateSmartImports(bodyCode, hasTabs)
|
|
50
|
+
return cleanCode(`${importsStr}\n\n${bodyCode}`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { index, resource }
|