@yorha2b-lab/autodev 1.0.46 → 2.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/README.md +28 -85
- package/README_EN.md +46 -100
- package/bin/autodev.js +29 -44
- package/config.js +7 -25
- package/example/example.json +46 -50
- package/package.json +1 -1
- package/src/commands/handlers/api-handler.js +80 -0
- package/src/commands/handlers/page-handler.js +92 -0
- package/src/commands/handlers/part-handler.js +76 -0
- package/src/commands/watch.js +96 -0
- package/src/core/react-compiler.js +45 -32
- package/src/prompts/mock.js +2 -2
- package/src/prompts/react/watch-page.js +6 -2
- package/src/prompts/react/watch-part.js +30 -25
- package/src/services/llm.js +2 -24
- package/src/utils/utils.js +7 -28
- package/templates/react/handlebars/MySearchForm.hbs +1 -0
- package/templates/react/handlebars/MyTable.hbs +26 -0
- package/templates/react/handlebars/functionButtonsBlock.hbs +16 -0
- package/templates/react/handlebars/handleBlock.hbs +52 -0
- package/templates/react/handlebars/hookBlock.hbs +9 -0
- package/templates/react/handlebars/stateBlock.hbs +11 -0
- package/templates/react/handlebars/tableOperate.hbs +10 -0
- package/templates/react/index.hbs +6 -121
- package/src/commands/watch-api.js +0 -68
- package/src/commands/watch-page.js +0 -155
- package/src/commands/watch-part.js +0 -77
package/example/example.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"tabs": [
|
|
3
3
|
{
|
|
4
4
|
"tab": "鬼剑士",
|
|
5
|
-
"key": "
|
|
5
|
+
"key": "ghostSwordsman"
|
|
6
6
|
},
|
|
7
7
|
{
|
|
8
8
|
"tab": "格斗家",
|
|
@@ -17,35 +17,6 @@
|
|
|
17
17
|
"key": "mage"
|
|
18
18
|
}
|
|
19
19
|
],
|
|
20
|
-
"formItems": [
|
|
21
|
-
{
|
|
22
|
-
"label": "转职职业",
|
|
23
|
-
"name": "transferJob",
|
|
24
|
-
"type": "select",
|
|
25
|
-
"options": "_CODE_transferJobOptions_CODE_"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"label": "武器类型",
|
|
29
|
-
"name": "weaponType",
|
|
30
|
-
"type": "select",
|
|
31
|
-
"options": "_CODE_weaponTypeOptions_CODE_"
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"label": "职业类型",
|
|
35
|
-
"name": "jobType",
|
|
36
|
-
"type": "select",
|
|
37
|
-
"options": "_CODE_jobTypeOptions_CODE_"
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"label": "觉醒名称关键词",
|
|
41
|
-
"name": "awakeningNameKeyword"
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"label": "登场时间",
|
|
45
|
-
"name": "releaseDateStart,releaseDateEnd",
|
|
46
|
-
"type": "daterange"
|
|
47
|
-
}
|
|
48
|
-
],
|
|
49
20
|
"table": {
|
|
50
21
|
"pagination": true,
|
|
51
22
|
"expandable": true,
|
|
@@ -71,7 +42,7 @@
|
|
|
71
42
|
"columns": [
|
|
72
43
|
{
|
|
73
44
|
"title": "职业",
|
|
74
|
-
"dataIndex": "
|
|
45
|
+
"dataIndex": "job"
|
|
75
46
|
},
|
|
76
47
|
{
|
|
77
48
|
"title": "一觉",
|
|
@@ -102,17 +73,37 @@
|
|
|
102
73
|
}
|
|
103
74
|
]
|
|
104
75
|
},
|
|
76
|
+
"formItems": [
|
|
77
|
+
{
|
|
78
|
+
"label": "转职职业",
|
|
79
|
+
"name": "transferJob",
|
|
80
|
+
"type": "select",
|
|
81
|
+
"options": "_CODE_transferJobOptions_CODE_"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"label": "武器类型",
|
|
85
|
+
"name": "weaponType",
|
|
86
|
+
"type": "select",
|
|
87
|
+
"options": "_CODE_weaponTypeOptions_CODE_"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"label": "职业类型",
|
|
91
|
+
"name": "jobType",
|
|
92
|
+
"type": "select",
|
|
93
|
+
"options": "_CODE_jobTypeOptions_CODE_"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"label": "觉醒名称关键词",
|
|
97
|
+
"name": "awakeningKeyword"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"label": "登场时间",
|
|
101
|
+
"name": "releaseDateStart,releaseDateEnd",
|
|
102
|
+
"type": "daterange"
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
105
|
"optionDict": {
|
|
106
|
-
"_CODE_transferJobOptions_CODE_": [
|
|
107
|
-
{
|
|
108
|
-
"label": "剑魂",
|
|
109
|
-
"value": "swordSoul"
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
"label": "狂战士",
|
|
113
|
-
"value": "berserker"
|
|
114
|
-
}
|
|
115
|
-
],
|
|
106
|
+
"_CODE_transferJobOptions_CODE_": [],
|
|
116
107
|
"_CODE_weaponTypeOptions_CODE_": [
|
|
117
108
|
{
|
|
118
109
|
"label": "光剑",
|
|
@@ -134,26 +125,31 @@
|
|
|
134
125
|
},
|
|
135
126
|
{
|
|
136
127
|
"label": "魔法",
|
|
137
|
-
"value": "
|
|
128
|
+
"value": "magical"
|
|
138
129
|
}
|
|
139
130
|
]
|
|
140
131
|
},
|
|
141
132
|
"functionButton": [
|
|
142
133
|
{
|
|
143
|
-
"btn": "
|
|
144
|
-
"action": "
|
|
134
|
+
"btn": "批量删除",
|
|
135
|
+
"action": "batchDeleteBySelected"
|
|
145
136
|
},
|
|
146
137
|
{
|
|
147
|
-
"btn": "
|
|
148
|
-
"action": "
|
|
138
|
+
"btn": "批量导出",
|
|
139
|
+
"action": "exportData"
|
|
149
140
|
},
|
|
150
141
|
{
|
|
151
|
-
"btn": "
|
|
152
|
-
"action": "
|
|
142
|
+
"btn": "查询",
|
|
143
|
+
"action": "search"
|
|
153
144
|
},
|
|
154
145
|
{
|
|
155
|
-
"btn": "
|
|
156
|
-
"action": "
|
|
146
|
+
"btn": "重置",
|
|
147
|
+
"action": "reset"
|
|
157
148
|
}
|
|
149
|
+
],
|
|
150
|
+
"pageStruct": [
|
|
151
|
+
"MySearchForm",
|
|
152
|
+
"FunctionButtonsBlock",
|
|
153
|
+
"MyTable"
|
|
158
154
|
]
|
|
159
155
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const ora = require('ora')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const chalk = require('chalk')
|
|
5
|
+
|
|
6
|
+
module.exports = async (filePath, context) => {
|
|
7
|
+
|
|
8
|
+
const { config, options, language, alignResponseFields } = context
|
|
9
|
+
|
|
10
|
+
const startTime = Date.now()
|
|
11
|
+
const fileName = path.basename(filePath, path.extname(filePath))
|
|
12
|
+
const resourcePath = path.join(process.cwd(), config.pagesDir, fileName, 'resource.js')
|
|
13
|
+
|
|
14
|
+
const spinner = ora({
|
|
15
|
+
text: chalk.yellow(language(
|
|
16
|
+
`🤖 Pod 153: [侦察] 发现加密数据源 [${fileName}]。正在尝试执行语义对齐协议...`,
|
|
17
|
+
`🤖 Pod 153: [Recon] Encrypted data source [${fileName}] detected. Initiating semantic alignment...`
|
|
18
|
+
)),
|
|
19
|
+
color: 'yellow'
|
|
20
|
+
}).start()
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
if (!fs.existsSync(resourcePath)) {
|
|
24
|
+
throw new Error(language(
|
|
25
|
+
`地堡数据库中未检索到模块 [${fileName}] 的资源文件。请先执行视觉构筑。`,
|
|
26
|
+
`Module [${fileName}] resource not found in Bunker database. Please execute visual construction first.`
|
|
27
|
+
))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const responseRaw = fs.readFileSync(filePath, 'utf8')
|
|
31
|
+
let responseStr = responseRaw
|
|
32
|
+
let resourceStr = fs.readFileSync(resourcePath, 'utf8')
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(responseRaw)
|
|
36
|
+
const normalizedData = Array.isArray(parsed) ? parsed : [parsed]
|
|
37
|
+
responseStr = JSON.stringify(normalizedData.slice(0, 1))
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.log(chalk.gray(language(
|
|
40
|
+
` [System] 数据格式非标准 JSON,将尝试原始字符对齐。`,
|
|
41
|
+
` [System] Data format is not standard JSON, will try to align by character.`)
|
|
42
|
+
))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
spinner.text = chalk.yellow(language(
|
|
46
|
+
`🧑💻 9S: 正在扫描前后端字段差异... 执行语义桥接任务。`,
|
|
47
|
+
`🧑💻 9S: Scanning field differences... Bridging semantic gaps.`
|
|
48
|
+
))
|
|
49
|
+
|
|
50
|
+
const result = await alignResponseFields(options, responseStr, resourceStr)
|
|
51
|
+
|
|
52
|
+
let changeCount = 0
|
|
53
|
+
const resultMapping = {}
|
|
54
|
+
Object.entries(result).forEach(([oldField, newField]) => {
|
|
55
|
+
if (oldField === newField) return
|
|
56
|
+
const regex = new RegExp(`(dataIndex|name)\\s*:\\s*['"]${oldField}['"]`, 'g')
|
|
57
|
+
resourceStr = resourceStr.replace(regex, `$1: '${newField}'`)
|
|
58
|
+
changeCount++
|
|
59
|
+
resultMapping[oldField] = newField
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(resourcePath, resourceStr)
|
|
63
|
+
const endTime = Date.now()
|
|
64
|
+
|
|
65
|
+
spinner.succeed(chalk.green(language(
|
|
66
|
+
`🤖 Pod 153: [肯定] 结果映射完成${JSON.stringify(resultMapping)}。已更正 ${changeCount} 处语义偏差。耗时: ${(endTime - startTime) / 1000}s`,
|
|
67
|
+
`🤖 Pod 153: [Affirmative] Result mapping completed${JSON.stringify(resultMapping)}. Corrected ${changeCount} semantic deviations. Elapsed: ${(endTime - startTime) / 1000}s`
|
|
68
|
+
)))
|
|
69
|
+
|
|
70
|
+
if (fs.existsSync(filePath)) {
|
|
71
|
+
fs.unlinkSync(filePath)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
} catch (error) {
|
|
75
|
+
spinner.fail(chalk.red(language(
|
|
76
|
+
`🤖 Pod 153: [严重警告] 信号对齐失败。接口数据遭遇强力防火墙:${error}`,
|
|
77
|
+
`🤖 Pod 153: [CRITICAL] Alignment failed. Encountered powerful API firewall: ${error}`
|
|
78
|
+
)))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const ora = require('ora')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const chalk = require('chalk')
|
|
5
|
+
const stringify = require('json-stringify-pretty-compact')
|
|
6
|
+
|
|
7
|
+
module.exports = async (filePath, context) => {
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
config, language, menus,
|
|
11
|
+
pagePrompt, resourceTpl, indexTpl,
|
|
12
|
+
recognizePage, generateMock, resource, index
|
|
13
|
+
} = context
|
|
14
|
+
|
|
15
|
+
const startTime = Date.now()
|
|
16
|
+
const fileName = path.basename(filePath, path.extname(filePath))
|
|
17
|
+
const mockDir = path.join(process.cwd(), 'mock')
|
|
18
|
+
const targetDir = path.join(process.cwd(), config.pagesDir, fileName)
|
|
19
|
+
|
|
20
|
+
const spinner = ora({
|
|
21
|
+
text: chalk.cyan(language(
|
|
22
|
+
`[System] 视觉传感器捕捉完成: [${fileName}]。正在调配地堡资源进行物理构筑...`,
|
|
23
|
+
`[System] Visual sensor captured: [${fileName}]. Allocating Bunker resources for physical construction...`
|
|
24
|
+
)),
|
|
25
|
+
color: 'cyan'
|
|
26
|
+
}).start()
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
|
|
30
|
+
if (fs.existsSync(targetDir)) {
|
|
31
|
+
spinner.warn(chalk.yellow(language(
|
|
32
|
+
`🤖 Pod 042: [拦截] 模块 [${fileName}] 已存在于地堡数据库中。为了防止数据覆盖,构筑协议已跳过。`,
|
|
33
|
+
`🤖 Pod 042: [Intercept] Module [${fileName}] already exists. Construction skipped to prevent overwrite.`
|
|
34
|
+
)))
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(mockDir)) fs.mkdirSync(mockDir, { recursive: true })
|
|
39
|
+
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true })
|
|
40
|
+
|
|
41
|
+
let pageConfig
|
|
42
|
+
if (config.useDemo) {
|
|
43
|
+
spinner.text = chalk.yellow(language(
|
|
44
|
+
`🤖 Pod 042: [警告] 拦截到实弹请求。正在空投标准模拟包: example.json`,
|
|
45
|
+
`🤖 Pod 042: [Warning] Real-fire request intercepted. Dropping simulation package: example.json`
|
|
46
|
+
))
|
|
47
|
+
pageConfig = require(path.join(__dirname, '../../../example/example.json'))
|
|
48
|
+
} else {
|
|
49
|
+
spinner.text = chalk.cyan(language(
|
|
50
|
+
`🤖 Pod 042: 正在上传视觉元数据至司令部进行语义分析...`,
|
|
51
|
+
`🤖 Pod 042: Uploading visual metadata to Command for semantic analysis...`
|
|
52
|
+
))
|
|
53
|
+
pageConfig = await recognizePage(pagePrompt, filePath)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fs.writeFileSync(path.join(targetDir, 'resource.js'), resource({ pageConfig, resourceTpl }))
|
|
57
|
+
fs.writeFileSync(path.join(targetDir, 'index.js'), index({ config, fileName, indexTpl, pageConfig }))
|
|
58
|
+
|
|
59
|
+
if (!menus.find(m => m.key === fileName)) {
|
|
60
|
+
menus.push({ label: fileName, key: fileName })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (config.needMock) {
|
|
64
|
+
spinner.text = chalk.yellow(language(
|
|
65
|
+
`🧑💻 9S: 正在突破目标防火墙... 执行数据伪装程序 [${fileName}]`,
|
|
66
|
+
`🧑💻 9S: Piercing target firewall... Executing data camouflage [${fileName}]`
|
|
67
|
+
))
|
|
68
|
+
const rawContent = await generateMock(pageConfig.table.columns, fileName)
|
|
69
|
+
fs.writeFileSync(path.join(mockDir, `${fileName}.js`), `export default ${stringify.default(rawContent, { indent: 4, maxLength: 200 })}`)
|
|
70
|
+
console.log(chalk.green(language(
|
|
71
|
+
`\n[Success] 9S 报告: Mock 数据生成成功`,
|
|
72
|
+
`\n[Success] 9S Report: Mock data generation successful.`
|
|
73
|
+
)))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const endTime = Date.now()
|
|
77
|
+
spinner.succeed(chalk.green(language(
|
|
78
|
+
`🤖 Pod 042: [报告] 构筑协议执行完毕。模块 [${fileName}] 物理装配达成。耗时: ${(endTime - startTime) / 1000}s`,
|
|
79
|
+
`🤖 Pod 042: [Report] Protocol complete. Physical assembly of [${fileName}] achieved. Elapsed: ${(endTime - startTime) / 1000}s`
|
|
80
|
+
)))
|
|
81
|
+
|
|
82
|
+
if (fs.existsSync(filePath)) {
|
|
83
|
+
fs.unlinkSync(filePath)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
} catch (error) {
|
|
87
|
+
spinner.fail(chalk.red(language(
|
|
88
|
+
`🤖 Pod 042: [警告] 构筑中断。检测到逻辑病毒或连接超时:${error}`,
|
|
89
|
+
`🤖 Pod 042: [Warning] Construction aborted. Logic virus or timeout detected: ${error}`
|
|
90
|
+
)))
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const ora = require('ora')
|
|
3
|
+
const chalk = require('chalk')
|
|
4
|
+
const stringify = require('json-stringify-pretty-compact')
|
|
5
|
+
|
|
6
|
+
module.exports = async (filePath, context) => {
|
|
7
|
+
|
|
8
|
+
const { language, partPrompt, recognizePage } = context
|
|
9
|
+
|
|
10
|
+
const startTime = Date.now()
|
|
11
|
+
|
|
12
|
+
const spinner = ora({
|
|
13
|
+
text: chalk.cyan(language(
|
|
14
|
+
`[System] 侦测到局部视觉样本。正在执行“碎片构筑协议”...`,
|
|
15
|
+
`[System] Partial visual sample detected. Executing "Fragment Construction Protocol"...`
|
|
16
|
+
)),
|
|
17
|
+
color: 'cyan'
|
|
18
|
+
}).start()
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
spinner.text = chalk.cyan(language(
|
|
22
|
+
`🤖 Pod 042: 正在从神经云网络提取 UI 元数据...`,
|
|
23
|
+
`🤖 Pod 042: Extracting UI metadata from neural cloud network...`
|
|
24
|
+
))
|
|
25
|
+
|
|
26
|
+
const pageConfig = await recognizePage(partPrompt, filePath)
|
|
27
|
+
|
|
28
|
+
const optionDict = pageConfig.optionDict || {}
|
|
29
|
+
delete pageConfig.optionDict
|
|
30
|
+
|
|
31
|
+
let mainConfigStr = stringify.default(pageConfig, { indent: 4, maxLength: 200 })
|
|
32
|
+
.replace(/"(\w+)":/g, '$1:') // 去掉 key 的双引号
|
|
33
|
+
.replace(/"/g, "'") // 全量替换为单引号
|
|
34
|
+
.replace(/['"]_CODE_([\s\S]*?)_CODE_['"]/g, '$1') // 还原代码片段
|
|
35
|
+
.replace(/_CODE_/g, '')
|
|
36
|
+
|
|
37
|
+
let optionsCodeStr = ''
|
|
38
|
+
Object.keys(optionDict).forEach(key => {
|
|
39
|
+
const varName = key.replaceAll('_CODE_', '')
|
|
40
|
+
const optionsArray = optionDict[key]
|
|
41
|
+
const arrayItemsStr = optionsArray.map(opt => ` { label: '${opt.label}', value: '${opt.value}' }`).join(',\n')
|
|
42
|
+
optionsCodeStr += `\nexport const ${varName} = [\n${arrayItemsStr}\n]\n`
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const finalResult = `${mainConfigStr}\n${optionsCodeStr}`
|
|
46
|
+
const endTime = Date.now()
|
|
47
|
+
|
|
48
|
+
spinner.stop()
|
|
49
|
+
|
|
50
|
+
console.log(chalk.magenta(`\n┌────────────────── [ YoRHa Construction Output ] ──────────────────┐`))
|
|
51
|
+
console.log(chalk.gray(`│ Source: ${filePath}`))
|
|
52
|
+
console.log(chalk.gray(`│ Protocol: Partial UI Fragment | Status: SUCCESS`))
|
|
53
|
+
console.log(chalk.magenta(`├────────────────────────────────────────────────────────────────────┘`))
|
|
54
|
+
console.log(chalk.white(finalResult))
|
|
55
|
+
console.log(chalk.magenta(`┌────────────────────────────────────────────────────────────────────┐`))
|
|
56
|
+
console.log(chalk.green(language(
|
|
57
|
+
`│ 🤖 Pod 042 报告:局部构筑达成。耗时: ${(endTime - startTime) / 1000}s`,
|
|
58
|
+
`│ 🤖 Pod 042 Report: Partial construction achieved. Elapsed: ${(endTime - startTime) / 1000}s`
|
|
59
|
+
)))
|
|
60
|
+
console.log(chalk.green(language(
|
|
61
|
+
`│ 命令:请手动将上述代码块物理装配至您的目标文件中。`,
|
|
62
|
+
`│ Command: Please manually assemble the above code block into your target file.`
|
|
63
|
+
)))
|
|
64
|
+
console.log(chalk.magenta(`└────────────────────────────────────────────────────────────────────┘\n`))
|
|
65
|
+
|
|
66
|
+
if (fs.existsSync(filePath)) {
|
|
67
|
+
fs.unlinkSync(filePath)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
} catch (error) {
|
|
71
|
+
spinner.fail(chalk.red(language(
|
|
72
|
+
`🤖 Pod 042: [警告] 碎片构筑失败。系统检测到逻辑干扰:${error}`,
|
|
73
|
+
`🤖 Pod 042: [Warning] Fragment construction failed. Logic interference detected: ${error}`
|
|
74
|
+
)))
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const chalk = require('chalk')
|
|
4
|
+
const chokidar = require('chokidar')
|
|
5
|
+
const Handlebars = require('handlebars')
|
|
6
|
+
const stringify = require('json-stringify-pretty-compact')
|
|
7
|
+
|
|
8
|
+
const { createTaskQueue } = require('../core/task-queue')
|
|
9
|
+
const { recognizePage, generateMock, alignResponseFields } = require('../services/llm.js')
|
|
10
|
+
const { language, getConfig, getExistingMenus, copyTemplateDir } = require('../utils/utils.js')
|
|
11
|
+
|
|
12
|
+
const watch = options => {
|
|
13
|
+
|
|
14
|
+
const config = getConfig()
|
|
15
|
+
const template = options.template
|
|
16
|
+
const queue = createTaskQueue(2)
|
|
17
|
+
const menus = getExistingMenus()
|
|
18
|
+
|
|
19
|
+
const compilerPath = path.join(__dirname, `../core/${template}-compiler.js`)
|
|
20
|
+
if (!fs.existsSync(compilerPath)) {
|
|
21
|
+
console.error(chalk.red(language(`❌ 暂不支持 [${template}] 框架。`, `❌ [${template}] framework not supported.`)))
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { index, resource } = require(compilerPath)
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (config.hbsDir === '') {
|
|
29
|
+
copyTemplateDir(options, 'hooks', config.hooksDir)
|
|
30
|
+
copyTemplateDir(options, 'utils', config.utilsDir)
|
|
31
|
+
copyTemplateDir(options, 'components', config.componentsDir)
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.error(language('❌ 模板构筑失败:', '❌ Template construction failed:'), error)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const tplDir = config.hbsDir !== '' ? path.join(process.cwd(), config.hbsDir) : path.join(__dirname, `../../templates/${template}`)
|
|
38
|
+
const indexTpl = Handlebars.compile(fs.readFileSync(path.join(tplDir, 'index.hbs'), 'utf-8'))
|
|
39
|
+
const resourceTpl = Handlebars.compile(fs.readFileSync(path.join(tplDir, 'resource.hbs'), 'utf-8'))
|
|
40
|
+
|
|
41
|
+
const context = {
|
|
42
|
+
menus, language,
|
|
43
|
+
config, options,
|
|
44
|
+
resource, index,
|
|
45
|
+
resourceTpl, indexTpl,
|
|
46
|
+
recognizePage, generateMock, alignResponseFields,
|
|
47
|
+
pagePrompt: require(path.join(__dirname, `../prompts/${template}/watch-page.js`)),
|
|
48
|
+
partPrompt: require(path.join(__dirname, `../prompts/${template}/watch-part.js`))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const apiHandler = require('./handlers/api-handler')
|
|
52
|
+
const pageHandler = require('./handlers/page-handler')
|
|
53
|
+
const partHandler = require('./handlers/part-handler')
|
|
54
|
+
|
|
55
|
+
queue.onIdle(() => {
|
|
56
|
+
console.log(chalk.green(language(
|
|
57
|
+
'\n🤖 Pod 042: [报告] 队列已空,正在向 Bunker 同步 Menu 路由配置...',
|
|
58
|
+
'\n🤖 Pod 042: [Report] Queue empty. Syncing Menu configuration to Bunker...'
|
|
59
|
+
)))
|
|
60
|
+
const utilsDir = path.join(process.cwd(), config.utilsDir)
|
|
61
|
+
if (!fs.existsSync(utilsDir)) fs.mkdirSync(utilsDir, { recursive: true })
|
|
62
|
+
fs.writeFileSync(path.join(utilsDir, 'menus.js'), `export const menus = ${stringify.default(menus, { indent: 4, maxLength: 50 })}`)
|
|
63
|
+
console.log(chalk.gray(language(
|
|
64
|
+
'\n🤖 [System] 未检测到运行中的任务,系统待机中...\n',
|
|
65
|
+
'\n🤖 [System] System is STANDBY.\n'
|
|
66
|
+
)))
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const watcher = chokidar.watch(['./screenShot', './screenPart', './response'], {
|
|
70
|
+
persistent: true,
|
|
71
|
+
ignored: /(^|[\/\\])\../,
|
|
72
|
+
awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
console.log(chalk.magenta(language(
|
|
76
|
+
'📡 Operator 6O: 呼叫 2B,地堡全频道联动监控已就绪!\n',
|
|
77
|
+
'📡 Operator 6O: Calling 2B, all-channel linked monitoring is ready!\n'
|
|
78
|
+
)))
|
|
79
|
+
|
|
80
|
+
watcher.on('add', filePath => {
|
|
81
|
+
|
|
82
|
+
const absolutePath = path.resolve(filePath)
|
|
83
|
+
|
|
84
|
+
if (absolutePath.includes('screenShot')) {
|
|
85
|
+
queue.add(() => pageHandler(filePath, context))
|
|
86
|
+
}
|
|
87
|
+
else if (absolutePath.includes('screenPart')) {
|
|
88
|
+
queue.add(() => partHandler(filePath, context))
|
|
89
|
+
}
|
|
90
|
+
else if (absolutePath.includes('response')) {
|
|
91
|
+
queue.add(() => apiHandler(filePath, context))
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = watch
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
1
3
|
const Handlebars = require('handlebars')
|
|
2
4
|
const stringify = require('json-stringify-pretty-compact')
|
|
3
5
|
const { cleanCode, generateSmartImports } = require('../utils/utils.js')
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Handlebars.registerHelper('raw', options => options.fn())
|
|
6
8
|
Handlebars.registerHelper('stringify', (context, maxLength = 200) => context ? new Handlebars.SafeString(stringify.default(context, { indent: 4, maxLength })) : '[]')
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -13,25 +15,20 @@ Handlebars.registerHelper('stringify', (context, maxLength = 200) => context ? n
|
|
|
13
15
|
* @returns {string} 生成的 resource.js 代码
|
|
14
16
|
*/
|
|
15
17
|
const resource = ({ pageConfig, resourceTpl }) => {
|
|
16
|
-
|
|
18
|
+
|
|
17
19
|
const hasTabs = pageConfig.tabs?.length > 0
|
|
18
20
|
|
|
19
|
-
// 构建模板数据
|
|
20
21
|
const viewData = {
|
|
21
|
-
hasTabs,
|
|
22
|
-
tabs: pageConfig.tabs,
|
|
23
|
-
formItems: pageConfig.formItems,
|
|
24
|
-
// 如果有标签页,将 formItems 按 tab 分组;否则直接使用数组
|
|
22
|
+
hasTabs,
|
|
23
|
+
tabs: pageConfig.tabs,
|
|
24
|
+
formItems: pageConfig.formItems,
|
|
25
25
|
formItemsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, pageConfig.formItems])) : pageConfig.formItems,
|
|
26
|
-
// 如果有标签页,将 columns 按 tab 分组;否则直接使用数组
|
|
27
26
|
columnsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, pageConfig.table.columns])) : pageConfig.table.columns,
|
|
28
|
-
// 提取所有下拉框字典配置
|
|
29
27
|
dictBlocks: pageConfig.formItems
|
|
30
|
-
?.filter(item => item.type === 'select')
|
|
31
|
-
?.map(item => ({ name: item.options.replaceAll('_CODE_', ''), data: pageConfig.optionDict[item.options] ?? [] }))
|
|
28
|
+
?.filter(item => item.type === 'select')
|
|
29
|
+
?.map(item => ({ name: item.options.replaceAll('_CODE_', ''), data: pageConfig.optionDict[item.options] ?? [] }))
|
|
32
30
|
}
|
|
33
31
|
|
|
34
|
-
// 渲染模板并清理代码
|
|
35
32
|
const rawCode = resourceTpl(viewData)
|
|
36
33
|
return cleanCode(rawCode)
|
|
37
34
|
}
|
|
@@ -45,40 +42,56 @@ const resource = ({ pageConfig, resourceTpl }) => {
|
|
|
45
42
|
* @returns {string} 生成的 index.js 代码
|
|
46
43
|
*/
|
|
47
44
|
const index = ({ config, fileName, indexTpl, pageConfig }) => {
|
|
45
|
+
|
|
48
46
|
const hasTabs = pageConfig.tabs?.length > 0
|
|
47
|
+
const hasFormItems = pageConfig.formItems?.length > 0
|
|
49
48
|
const hasOperate = pageConfig.table.operation?.length > 0
|
|
50
49
|
|
|
51
|
-
// 动态确定 columns 引用方式:有标签页时使用 activeKey 索引,否则直接使用 columns
|
|
52
50
|
let columnsValue = hasTabs ? 'columns[activeKey]' : 'columns'
|
|
53
|
-
// 如果存在操作列,拼接到 columns 后面
|
|
54
51
|
if (hasOperate) {
|
|
55
52
|
columnsValue = `${columnsValue}.concat(operate)`
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
// 构建模板数据
|
|
59
55
|
const viewData = {
|
|
60
|
-
hasTabs,
|
|
61
|
-
fileName,
|
|
62
|
-
hasOperate,
|
|
63
|
-
columnsValue,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
56
|
+
hasTabs,
|
|
57
|
+
fileName,
|
|
58
|
+
hasOperate,
|
|
59
|
+
columnsValue,
|
|
60
|
+
hasFormItems,
|
|
61
|
+
tabs: pageConfig.tabs,
|
|
62
|
+
pageStruct: pageConfig.pageStruct,
|
|
63
|
+
responseSuccess: config.responseSuccess,
|
|
64
|
+
hasExpandable: pageConfig.table.expandable,
|
|
65
|
+
hasPagination: pageConfig.table.pagination,
|
|
66
|
+
operations: pageConfig.table.operation || [],
|
|
67
|
+
hasRowSelection: pageConfig.table.rowSelection,
|
|
68
|
+
hasStaticInfo: pageConfig.table.staticInfo?.has,
|
|
69
|
+
staticInfoText: pageConfig.table.staticInfo?.text,
|
|
70
|
+
formItems: hasTabs ? 'formItems[activeKey]' : 'formItems',
|
|
71
|
+
functionButtons: pageConfig.functionButton?.filter(item => !['查询', '重置'].includes(item.btn)) || []
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
|
|
74
|
+
if (viewData.hasOperate) {
|
|
75
|
+
Handlebars.registerPartial('tableOperate', `${fs.readFileSync(path.join(__dirname, '../../templates/react/handlebars/tableOperate.hbs'), 'utf-8')}\n`)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (viewData.hasFormItems) {
|
|
79
|
+
Handlebars.registerPartial('MySearchForm', `${fs.readFileSync(path.join(__dirname, '../../templates/react/handlebars/MySearchForm.hbs'), 'utf-8')}\n`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (viewData.hasStaticInfo) {
|
|
83
|
+
Handlebars.registerPartial('AlertInfo', `{{{{raw}}}}<Alert showIcon title='${pageConfig.table.staticInfo?.text}' type='info' style={{ marginBottom: 16 }} />\n{{{{/raw}}}}`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
Handlebars.registerPartial('MyTable', `${fs.readFileSync(path.join(__dirname, '../../templates/react/handlebars/MyTable.hbs'), 'utf-8')}\n`)
|
|
87
|
+
Handlebars.registerPartial('hookBlock', `${fs.readFileSync(path.join(__dirname, '../../templates/react/handlebars/hookBlock.hbs'), 'utf-8')}\n`)
|
|
88
|
+
Handlebars.registerPartial('stateBlock', `${fs.readFileSync(path.join(__dirname, '../../templates/react/handlebars/stateBlock.hbs'), 'utf-8')}\n`)
|
|
89
|
+
Handlebars.registerPartial('handleBlock', `${fs.readFileSync(path.join(__dirname, '../../templates/react/handlebars/handleBlock.hbs'), 'utf-8')}\n`)
|
|
90
|
+
Handlebars.registerPartial('FunctionButtonsBlock', `${fs.readFileSync(path.join(__dirname, '../../templates/react/handlebars/functionButtonsBlock.hbs'), 'utf-8')}\n`)
|
|
91
|
+
|
|
76
92
|
const bodyCode = indexTpl(viewData)
|
|
77
|
-
// 生成智能导入语句
|
|
78
93
|
const importsStr = generateSmartImports(bodyCode, hasTabs)
|
|
79
|
-
// 清理代码并返回
|
|
80
94
|
return cleanCode(`${importsStr}\n\n${bodyCode}`)
|
|
81
95
|
}
|
|
82
96
|
|
|
83
|
-
// 导出 React 编译器的两个核心函数
|
|
84
97
|
module.exports = { index, resource }
|
package/src/prompts/mock.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
module.exports = `
|
|
2
|
-
|
|
2
|
+
识别图片内容(搜索项,表格列,是否有勾选框,查询下拉选项,统计条等页面组件,并按照从上到下的结构把识别出来的组件按顺序组织成型)
|
|
3
3
|
⚠️⚠️⚠️注意:
|
|
4
4
|
1.变量使用驼峰命名法
|
|
5
5
|
【严禁中文】绝对不允许使用中文作为变量名或函数名
|
|
@@ -35,6 +35,10 @@ module.exports = `
|
|
|
35
35
|
6.下拉选项字典
|
|
36
36
|
optionDict:{_CODE_对应的英文名词Options_CODE_:[]]}
|
|
37
37
|
⚠️⚠️⚠️注意:数组值为对应列展示的内容组成的类似{label:'',value:''}的数组
|
|
38
|
+
7.pageStruct
|
|
39
|
+
从上至下的页面结构组成
|
|
40
|
+
⚠️⚠️⚠️注意:这里只能从AlertInfo,MySearchForm,FunctionButtonsBlock,MyTable中选择,并且只有真的存在才加入,绝对禁止加入其他组件
|
|
41
|
+
⚠️⚠️⚠️另外这里不要加入tabs组件
|
|
38
42
|
最后输出一个JSON对象,不要包含任何Markdown标签,格式如下
|
|
39
|
-
{ tabs: [], table: {}, formItems: [], optionDict: {}, functionButton: []}
|
|
43
|
+
{ tabs: [], table: {}, formItems: [], optionDict: {}, functionButton: [],pageStruct:[]}
|
|
40
44
|
`
|