@yorha2b-lab/autodev 2.2.1 → 3.1.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/bin/autodev.js CHANGED
@@ -2,9 +2,10 @@
2
2
  const fs = require('fs')
3
3
  const path = require('path')
4
4
  const chalk = require('chalk')
5
+ const pkg = require('../package.json')
5
6
  const { program } = require('commander')
6
- const pkg = require(path.join(__dirname, '../package.json'))
7
- const { language, matrixEffect, bootSequence } = require(path.join(__dirname, '../src/utils/utils.js'))
7
+ const bunker = require('../src/core/context')
8
+ const { language, matrixEffect, bootSequence } = require('../src/utils/utils.js')
8
9
 
9
10
  program
10
11
  .version(pkg.version)
@@ -52,8 +53,12 @@ program
52
53
  .description(language('开启全频道联动监控:支持 Page/Part/API 协同构筑', 'Start full-channel linked monitoring: Coordinated Page/Part/API construction'))
53
54
  .action(() => {
54
55
  bootSequence(pkg.version)
55
- const watch = require('../src/commands/watch')
56
- watch(program.opts())
56
+ bunker.init(program.opts())
57
+ require('../src/commands/watch.js')()
58
+ const { config } = bunker.get()
59
+ if (config.proxyTarget && !config.needMock && config.enableAutoAlignment) {
60
+ require('../src/services/proxy-tower.js')()
61
+ }
57
62
  process.on('SIGINT', () => {
58
63
  console.log('\n')
59
64
  console.log(chalk.gray('--------------------------------------------------'))
package/config.js CHANGED
@@ -7,6 +7,8 @@ module.exports = {
7
7
 
8
8
  useDemo: true,
9
9
  needMock: false,
10
+ // 是否开启自动联调对齐协议 (42153 拦截塔)
11
+ enableAutoAlignment: false,
10
12
 
11
13
  textModel: 'qwen-turbo',
12
14
  visionModel: 'qwen3.5-plus',
@@ -17,5 +19,6 @@ module.exports = {
17
19
  componentsDir: 'src/components',
18
20
 
19
21
  responseSuccess: `response?.code === 200`,
22
+ proxyTarget: 'http://id:port',// 代理目标,例如 http://localhost:3000
20
23
 
21
24
  }
@@ -1,75 +1,75 @@
1
1
  {
2
2
  "tabs": [
3
3
  {
4
- "tab": "鬼剑士",
4
+ "label": "鬼剑士",
5
5
  "key": "ghostSwordsman"
6
6
  },
7
7
  {
8
- "tab": "格斗家",
8
+ "label": "格斗家",
9
9
  "key": "fighter"
10
10
  },
11
11
  {
12
- "tab": "神枪手",
12
+ "label": "神枪手",
13
13
  "key": "gunner"
14
14
  },
15
15
  {
16
- "tab": "魔法师",
16
+ "label": "魔法师",
17
17
  "key": "mage"
18
18
  }
19
19
  ],
20
- "staticInfo": {
21
- "has": false,
22
- "text": ""
23
- },
24
20
  "table": {
25
- "pagination": true,
26
- "expandable": true,
27
- "rowSelection": true,
28
- "operation": [
29
- {
30
- "label": "查看",
31
- "action": "viewByRecord"
32
- },
33
- {
34
- "label": "编辑",
35
- "action": "editByRecord"
36
- },
37
- {
38
- "label": "删除",
39
- "action": "deleteByRecord"
40
- }
41
- ],
42
21
  "columns": [
43
22
  {
44
23
  "title": "职业",
45
- "dataIndex": "job"
24
+ "dataIndex": "jobName",
25
+ "type": "text"
46
26
  },
47
27
  {
48
28
  "title": "一觉",
49
- "dataIndex": "firstAwakening"
29
+ "dataIndex": "firstAwakening",
30
+ "type": "text"
50
31
  },
51
32
  {
52
33
  "title": "二觉",
53
- "dataIndex": "secondAwakening"
34
+ "dataIndex": "secondAwakening",
35
+ "type": "text"
54
36
  },
55
37
  {
56
38
  "title": "三觉",
57
- "dataIndex": "thirdAwakening"
39
+ "dataIndex": "thirdAwakening",
40
+ "type": "text"
58
41
  },
59
42
  {
60
43
  "title": "武器类型",
61
44
  "dataIndex": "weaponType",
62
- "render": "_CODE_text=>weaponTypeOptions.find(item=>item.value===text)?.label_CODE_"
45
+ "type": "enum"
63
46
  },
64
47
  {
65
48
  "title": "职业类型",
66
49
  "dataIndex": "jobType",
67
- "render": "_CODE_text=>jobTypeOptions.find(item=>item.value===text)?.label_CODE_"
50
+ "type": "enum"
68
51
  },
69
52
  {
70
53
  "title": "登场时间",
71
- "dataIndex": "releaseDate",
72
- "render": "_CODE_text=>timeRender({time:text})_CODE_"
54
+ "dataIndex": "launchDate",
55
+ "type": "date"
56
+ }
57
+ ],
58
+ "pagination": true,
59
+ "expandable": true,
60
+ "rowSelection": true,
61
+ "operation": [
62
+ {
63
+ "label": "查看",
64
+ "action": "viewByRecord"
65
+ },
66
+ {
67
+ "label": "编辑",
68
+ "action": "editByRecord"
69
+ },
70
+ {
71
+ "label": "删除",
72
+ "action": "deleteByRecord"
73
73
  }
74
74
  ]
75
75
  },
@@ -77,34 +77,44 @@
77
77
  {
78
78
  "label": "转职职业",
79
79
  "name": "transferJob",
80
- "type": "select",
81
- "options": "_CODE_transferJobOptions_CODE_"
80
+ "type": "select"
82
81
  },
83
82
  {
84
83
  "label": "武器类型",
85
84
  "name": "weaponType",
86
- "type": "select",
87
- "options": "_CODE_weaponTypeOptions_CODE_"
85
+ "type": "select"
88
86
  },
89
87
  {
90
88
  "label": "职业类型",
91
89
  "name": "jobType",
92
- "type": "select",
93
- "options": "_CODE_jobTypeOptions_CODE_"
90
+ "type": "select"
94
91
  },
95
92
  {
96
93
  "label": "觉醒名称关键词",
97
- "name": "awakeningKeyword"
94
+ "name": "awakeningNameKeyword"
98
95
  },
99
96
  {
100
97
  "label": "登场时间",
101
- "name": "releaseDateStart,releaseDateEnd",
98
+ "name": "launchDateStart,launchDateEnd",
102
99
  "type": "daterange"
103
100
  }
104
101
  ],
102
+ "staticInfo": {
103
+ "has": false,
104
+ "text": ""
105
+ },
105
106
  "optionDict": {
106
- "_CODE_transferJobOptions_CODE_": [],
107
- "_CODE_weaponTypeOptions_CODE_": [
107
+ "transferJobOptions": [
108
+ {
109
+ "label": "剑魂",
110
+ "value": "soulBender"
111
+ },
112
+ {
113
+ "label": "狂战士",
114
+ "value": "berserker"
115
+ }
116
+ ],
117
+ "weaponTypeOptions": [
108
118
  {
109
119
  "label": "光剑",
110
120
  "value": "lightSword"
@@ -118,33 +128,25 @@
118
128
  "value": "greatSword"
119
129
  }
120
130
  ],
121
- "_CODE_jobTypeOptions_CODE_": [
131
+ "jobTypeOptions": [
122
132
  {
123
133
  "label": "物理",
124
134
  "value": "physical"
125
135
  },
126
136
  {
127
137
  "label": "魔法",
128
- "value": "magical"
138
+ "value": "magic"
129
139
  }
130
140
  ]
131
141
  },
132
142
  "functionButton": [
133
143
  {
134
144
  "btn": "批量删除",
135
- "action": "batchDeleteBySelected"
145
+ "action": "deleteBySelected"
136
146
  },
137
147
  {
138
148
  "btn": "批量导出",
139
- "action": "exportData"
140
- },
141
- {
142
- "btn": "查询",
143
- "action": "search"
144
- },
145
- {
146
- "btn": "重置",
147
- "action": "reset"
149
+ "action": "exportJobData"
148
150
  }
149
151
  ],
150
152
  "pageStruct": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yorha2b-lab/autodev",
3
- "version": "2.2.1",
3
+ "version": "3.1.0",
4
4
  "description": "基于视觉大模型的前端(react+Antd)全自动 CRUD 代码生成器",
5
5
  "bin": {
6
6
  "autodev": "bin/autodev.js"
@@ -46,5 +46,8 @@
46
46
  "openai": "^6.24.0",
47
47
  "ora": "^5.4.1",
48
48
  "sharp": "^0.34.5"
49
+ },
50
+ "devDependencies": {
51
+ "http-proxy": "^1.18.1"
49
52
  }
50
53
  }
@@ -3,18 +3,19 @@ const ora = require('ora')
3
3
  const path = require('path')
4
4
  const chalk = require('chalk')
5
5
 
6
- module.exports = async (filePath, context) => {
6
+ module.exports = async (filePath, liveResponse = null) => {
7
7
 
8
- const { config, options, language, alignResponseFields } = context
8
+ const { get } = require('../../core/context')
9
+ const { config, options, language, unwrapSignal, alignResponseFields } = get()
9
10
 
10
11
  const startTime = Date.now()
11
- const fileName = path.basename(filePath, path.extname(filePath))
12
+ const fileName = liveResponse?.fileName ?? path.basename(filePath, path.extname(filePath))
12
13
  const resourcePath = path.join(process.cwd(), config.pagesDir, fileName, 'resource.js')
13
14
 
14
15
  const spinner = ora({
15
16
  text: chalk.yellow(language(
16
- `🤖 Pod 153: [侦察] 发现加密数据源 [${fileName}]。正在尝试执行语义对齐协议...`,
17
- `🤖 Pod 153: [Recon] Encrypted data source [${fileName}] detected. Initiating semantic alignment...`
17
+ `🤖 Pod 153: [侦察] 发现加密数据源 [${fileName}]。正在尝试执行语义对齐协议...\n`,
18
+ `🤖 Pod 153: [Recon] Encrypted data source [${fileName}] detected. Initiating semantic alignment...\n`
18
19
  )),
19
20
  color: 'yellow'
20
21
  }).start()
@@ -27,28 +28,36 @@ module.exports = async (filePath, context) => {
27
28
  ))
28
29
  }
29
30
 
30
- const responseRaw = fs.readFileSync(filePath, 'utf8')
31
- let responseStr = responseRaw
31
+ let rawJson = liveResponse ? liveResponse.data : JSON.parse(fs.readFileSync(filePath, 'utf8'))
32
32
  let resourceStr = fs.readFileSync(resourcePath, 'utf8')
33
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) {
34
+ const coreArray = unwrapSignal(rawJson)
35
+
36
+ if (!coreArray || coreArray.length === 0) {
39
37
  console.log(chalk.gray(language(
40
38
  ` [System] 数据格式非标准 JSON,将尝试原始字符对齐。`,
41
39
  ` [System] Data format is not standard JSON, will try to align by character.`)
42
40
  ))
43
41
  }
44
42
 
43
+ const sampleData = coreArray && coreArray.length > 0 ? [coreArray[0]] : rawJson
44
+
45
45
  spinner.text = chalk.yellow(language(
46
46
  `🧑‍💻 9S: 正在扫描前后端字段差异... 执行语义桥接任务。`,
47
47
  `🧑‍💻 9S: Scanning field differences... Bridging semantic gaps.`
48
48
  ))
49
49
 
50
- const result = await alignResponseFields(options, responseStr, resourceStr)
50
+ const extractKeys = str => {
51
+ const keys = []
52
+ const regex = /(dataIndex|name)\s*:\s*['"]([^'"]+)['"]/g
53
+ let match
54
+ while ((match = regex.exec(str)) !== null) {
55
+ keys.push(match[2])
56
+ }
57
+ return Array.from(new Set(keys))
58
+ }
51
59
 
60
+ const result = await alignResponseFields(options, JSON.stringify(sampleData), extractKeys(resourceStr).join(','))
52
61
  let changeCount = 0
53
62
  const resultMapping = {}
54
63
  Object.entries(result).forEach(([oldField, newField]) => {
@@ -63,10 +72,27 @@ module.exports = async (filePath, context) => {
63
72
  const endTime = Date.now()
64
73
 
65
74
  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`
75
+ `🤖 Pod 153: [肯定] 语义桥接协议执行完毕。已物理修正 ${changeCount} 处字段偏差。耗时: ${(endTime - startTime) / 1000}s`,
76
+ `🤖 Pod 153: [Affirmative] Semantic bridge protocol complete. Corrected ${changeCount} field deviations. Elapsed: ${(endTime - startTime) / 1000}s`
68
77
  )))
69
78
 
79
+ if (changeCount > 0) {
80
+ console.log(chalk.magenta(`\n┌────── [ YoRHa Autonomous Backend Alignment ] ──────┐`))
81
+ Object.entries(resultMapping).forEach(([oldField, newField]) => {
82
+ // 💡 物理校准:计算空格数量,让箭头对齐在第 15 个字符位
83
+ // 中文字符长度 * 2 是为了抵消它在终端占的双倍宽度
84
+ const padding = " ".repeat(Math.max(1, 15 - oldField.length * 2))
85
+ console.log(
86
+ chalk.gray(` │ `) +
87
+ chalk.yellow(oldField) +
88
+ padding +
89
+ chalk.cyan(` -> `) +
90
+ chalk.white(newField)
91
+ )
92
+ })
93
+ console.log(chalk.magenta(`└───────────────────────────────────────────────────────┘\n`))
94
+ }
95
+
70
96
  if (fs.existsSync(filePath)) {
71
97
  fs.unlinkSync(filePath)
72
98
  }
@@ -4,13 +4,15 @@ const path = require('path')
4
4
  const chalk = require('chalk')
5
5
  const stringify = require('json-stringify-pretty-compact')
6
6
 
7
- module.exports = async (filePath, context) => {
7
+ module.exports = async filePath => {
8
+
9
+ const { get } = require('../../core/context')
8
10
 
9
11
  const {
10
12
  config, language, menus,
11
13
  pagePrompt, resourceTpl, indexTpl,
12
14
  recognizePage, generateMock, resource, index
13
- } = context
15
+ } = get()
14
16
 
15
17
  const startTime = Date.now()
16
18
  const fileName = path.basename(filePath, path.extname(filePath))
@@ -44,11 +46,11 @@ module.exports = async (filePath, context) => {
44
46
  `\n🤖 Pod 042: [报告] 拦截到实弹请求。正在空投标准模拟包: example.json\n`,
45
47
  `\n🤖 Pod 042: [Report] Real-fire request intercepted. Dropping simulation package: example.json\n`
46
48
  )))
47
- pageConfig = require(path.join(__dirname, '../../../example/example.json'))
49
+ pageConfig = require('../../../example/example.json')
48
50
  } else {
49
51
  spinner.text = chalk.cyan(language(
50
- `🤖 Pod 042: 正在上传视觉元数据至司令部进行语义分析...`,
51
- `🤖 Pod 042: Uploading visual metadata to Command for semantic analysis...`
52
+ `🤖 Pod 042: 正在上传视觉元数据至司令部进行语义分析...\n`,
53
+ `🤖 Pod 042: Uploading visual metadata to Command for semantic analysis...\n`
52
54
  ))
53
55
  pageConfig = await recognizePage(pagePrompt, filePath)
54
56
  }
@@ -2,10 +2,12 @@ const fs = require('fs')
2
2
  const ora = require('ora')
3
3
  const chalk = require('chalk')
4
4
  const stringify = require('json-stringify-pretty-compact')
5
+ const { cleanCode, formatFormItemAndColumns } = require('../../utils/utils')
5
6
 
6
- module.exports = async (filePath, context) => {
7
+ module.exports = async filePath => {
7
8
 
8
- const { language, partPrompt, recognizePage } = context
9
+ const { get } = require('../../core/context')
10
+ const { language, partPrompt, recognizePage } = get()
9
11
 
10
12
  const startTime = Date.now()
11
13
 
@@ -19,27 +21,23 @@ module.exports = async (filePath, context) => {
19
21
 
20
22
  try {
21
23
  spinner.text = chalk.cyan(language(
22
- `🤖 Pod 042: 正在从神经云网络提取 UI 元数据...`,
23
- `🤖 Pod 042: Extracting UI metadata from neural cloud network...`
24
+ `🤖 Pod 042: 正在从神经云网络提取 UI 元数据...\n`,
25
+ `🤖 Pod 042: Extracting UI metadata from neural cloud network...\n`
24
26
  ))
25
27
 
26
28
  const pageConfig = await recognizePage(partPrompt, filePath)
27
29
 
28
- const optionDict = pageConfig.optionDict || {}
29
- delete pageConfig.optionDict
30
+ const { formItems, dictBlocks, processedColumns } = formatFormItemAndColumns({ pageConfig })
30
31
 
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, '')
32
+ const result = Object.fromEntries(Object.entries({ formItems, processedColumns }).filter(([key, value]) => value?.length > 0))
33
+
34
+ let mainConfigStr = cleanCode(stringify.default(result, { indent: 4, maxLength: 200 }))
36
35
 
37
36
  let optionsCodeStr = ''
38
- Object.keys(optionDict).forEach(key => {
39
- const varName = key.replaceAll('_CODE_', '')
40
- const optionsArray = optionDict[key]
37
+ dictBlocks.forEach(key => {
38
+ const optionsArray = pageConfig.optionDict?.[key] ?? []
41
39
  const arrayItemsStr = optionsArray.map(opt => ` { label: '${opt.label}', value: '${opt.value}' }`).join(',\n')
42
- optionsCodeStr += `\nexport const ${varName} = [\n${arrayItemsStr}\n]\n`
40
+ optionsCodeStr += `\nexport const ${key} = [\n${arrayItemsStr}\n]\n`
43
41
  })
44
42
 
45
43
  const finalResult = `${mainConfigStr}\n${optionsCodeStr}`
@@ -61,7 +59,7 @@ module.exports = async (filePath, context) => {
61
59
  `│ 命令:请手动将上述代码块物理装配至您的目标文件中。`,
62
60
  `│ Command: Please manually assemble the above code block into your target file.`
63
61
  )))
64
- console.log(chalk.magenta(`└────────────────────────────────────────────────────────────────────┘\n`))
62
+ console.log(chalk.magenta(`└───────────────────────────────────────────────────────────────────┘\n`))
65
63
 
66
64
  if (fs.existsSync(filePath)) {
67
65
  fs.unlinkSync(filePath)
@@ -2,19 +2,15 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
  const chalk = require('chalk')
4
4
  const chokidar = require('chokidar')
5
- const Handlebars = require('handlebars')
6
5
  const stringify = require('json-stringify-pretty-compact')
7
6
 
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')
7
+ const { get } = require('../core/context')
8
+ const { copyTemplateDir } = require('../utils/utils.js')
9
+ const { createTaskQueue } = require('../core/task-queue.js')
11
10
 
12
- const watch = options => {
11
+ const watch = () => {
13
12
 
14
- const config = getConfig()
15
- const template = options.template
16
- const queue = createTaskQueue(2)
17
- const menus = getExistingMenus()
13
+ const { menus, config, options, template, language, apiHandler, pageHandler, partHandler } = get()
18
14
 
19
15
  const compilerPath = path.join(__dirname, `../core/${template}-compiler.js`)
20
16
  if (!fs.existsSync(compilerPath)) {
@@ -22,8 +18,6 @@ const watch = options => {
22
18
  return
23
19
  }
24
20
 
25
- const { index, resource } = require(compilerPath)
26
-
27
21
  try {
28
22
  if (config.hbsDir === '') {
29
23
  copyTemplateDir(options, 'hooks', config.hooksDir)
@@ -34,23 +28,7 @@ const watch = options => {
34
28
  console.error(language('❌ 模板构筑失败:', '❌ Template construction failed:'), error)
35
29
  }
36
30
 
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')
31
+ const queue = createTaskQueue(2)
54
32
 
55
33
  queue.onIdle(() => {
56
34
  console.log(chalk.green(language(
@@ -73,8 +51,8 @@ const watch = options => {
73
51
  })
74
52
 
75
53
  console.log(chalk.magenta(language(
76
- '📡 Operator 6O: 呼叫 2B,地堡全频道联动监控已就绪!\n',
77
- '📡 Operator 6O: Calling 2B, all-channel linked monitoring is ready!\n'
54
+ '📡 Operator 6O: 呼叫 2B,地堡全频道联动监控已就绪!',
55
+ '📡 Operator 6O: Calling 2B, all-channel linked monitoring is ready!'
78
56
  )))
79
57
 
80
58
  watcher.on('add', filePath => {
@@ -82,13 +60,13 @@ const watch = options => {
82
60
  const absolutePath = path.resolve(filePath)
83
61
 
84
62
  if (absolutePath.includes('screenShot')) {
85
- queue.add(() => pageHandler(filePath, context))
63
+ queue.add(() => pageHandler(filePath))
86
64
  }
87
65
  else if (absolutePath.includes('screenPart')) {
88
- queue.add(() => partHandler(filePath, context))
66
+ queue.add(() => partHandler(filePath))
89
67
  }
90
68
  else if (absolutePath.includes('response')) {
91
- queue.add(() => apiHandler(filePath, context))
69
+ queue.add(() => apiHandler(filePath))
92
70
  }
93
71
  })
94
72
  }
@@ -0,0 +1,51 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const Handlebars = require('handlebars')
4
+ const apiHandler = require('../commands/handlers/api-handler')
5
+ const pageHandler = require('../commands/handlers/page-handler')
6
+ const partHandler = require('../commands/handlers/part-handler')
7
+ const { recognizePage, generateMock, alignResponseFields } = require('../services/llm')
8
+ const { language, getConfig, unwrapSignal, isQuerySignal, getExistingMenus } = require('../utils/utils')
9
+
10
+ let instance = null // 💡 物理封存的全局实例
11
+
12
+ module.exports = {
13
+ /**
14
+ * @description 执行中枢神经元初始化
15
+ */
16
+ init: options => {
17
+
18
+ const config = getConfig()
19
+ const menus = getExistingMenus()
20
+ const template = options.template
21
+
22
+ const compilerPath = path.join(__dirname, `./${template}-compiler.js`)
23
+ const { index, resource } = require(compilerPath)
24
+
25
+ const tplDir = config.hbsDir !== ''
26
+ ? path.join(process.cwd(), config.hbsDir)
27
+ : path.join(__dirname, `../../templates/${template}`)
28
+
29
+ instance = {
30
+ resource, index, template,
31
+ apiHandler, pageHandler, partHandler,
32
+ recognizePage, generateMock, alignResponseFields,
33
+ pagePrompt: require(`../prompts/${template}/watch-page.js`),
34
+ partPrompt: require(`../prompts/${template}/watch-part.js`),
35
+ menus, config, options, language, unwrapSignal, isQuerySignal,
36
+ indexTpl: Handlebars.compile(fs.readFileSync(path.join(tplDir, 'index.hbs'), 'utf-8')),
37
+ resourceTpl: Handlebars.compile(fs.readFileSync(path.join(tplDir, 'resource.hbs'), 'utf-8')),
38
+ }
39
+ return instance
40
+ },
41
+
42
+ /**
43
+ * @description 全频道信号接入:获取全局上下文
44
+ */
45
+ get: () => {
46
+ if (!instance) {
47
+ throw new Error(language('🤖 Pod 042 报警:中枢神经元尚未初始化,无法建立信号连接。', '🤖 Pod 042 Warnning:Central neuron not initialized, cannot establish signal connection.'))
48
+ }
49
+ return instance
50
+ }
51
+ }
@@ -2,7 +2,7 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
  const Handlebars = require('handlebars')
4
4
  const stringify = require('json-stringify-pretty-compact')
5
- const { cleanCode, generateSmartImports } = require('../utils/utils.js')
5
+ const { cleanCode, generateSmartImports, formatFormItemAndColumns } = require('../utils/utils.js')
6
6
 
7
7
  Handlebars.registerHelper('raw', options => options.fn())
8
8
  Handlebars.registerHelper('stringify', (context, maxLength = 200) => context ? new Handlebars.SafeString(stringify.default(context, { indent: 4, maxLength })) : '[]')
@@ -18,15 +18,15 @@ const resource = ({ pageConfig, resourceTpl }) => {
18
18
 
19
19
  const hasTabs = pageConfig.tabs?.length > 0
20
20
 
21
+ const { formItems, processedColumns, dictBlocks } = formatFormItemAndColumns({ pageConfig })
22
+
21
23
  const viewData = {
22
24
  hasTabs,
25
+ formItems,
23
26
  tabs: pageConfig.tabs,
24
- formItems: pageConfig.formItems,
25
- formItemsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, pageConfig.formItems])) : pageConfig.formItems,
26
- columnsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, pageConfig.table.columns])) : pageConfig.table.columns,
27
- dictBlocks: pageConfig.formItems
28
- ?.filter(item => item.type === 'select')
29
- ?.map(item => ({ name: item.options.replaceAll('_CODE_', ''), data: pageConfig.optionDict[item.options] ?? [] }))
27
+ dictBlocks: dictBlocks.map(item => ({ name: item, data: pageConfig.optionDict[item] ?? [] })),
28
+ formItemsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, formItems])) : formItems,
29
+ columnsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, processedColumns])) : processedColumns,
30
30
  }
31
31
 
32
32
  const rawCode = resourceTpl(viewData)
@@ -68,7 +68,7 @@ const index = ({ config, fileName, indexTpl, pageConfig }) => {
68
68
  hasPagination: pageConfig.table.pagination,
69
69
  operations: pageConfig.table.operation || [],
70
70
  hasRowSelection: pageConfig.table.rowSelection,
71
- formItems: hasTabs ? 'formItems[activeKey]' : 'formItems',
71
+ formItems: hasFormItems ? (hasTabs ? 'formItems[activeKey]' : 'formItems') : '[]',
72
72
  pageStruct: hasFunctionButtons ? pageConfig.pageStruct : pageConfig.pageStruct?.filter(item => item !== 'FunctionButtonsBlock'),
73
73
  }
74
74
 
@@ -94,7 +94,7 @@ const index = ({ config, fileName, indexTpl, pageConfig }) => {
94
94
  Handlebars.registerPartial('handleBlock', `${fs.readFileSync(path.join(__dirname, '../../templates/react/handlebars/handleBlock.hbs'), 'utf-8')}\n`)
95
95
 
96
96
  const bodyCode = indexTpl(viewData)
97
- const importsStr = generateSmartImports(bodyCode, hasTabs)
97
+ const importsStr = generateSmartImports({ bodyCode, hasTabs, hasFormItems })
98
98
  return cleanCode(`${importsStr}\n\n${bodyCode}`)
99
99
  }
100
100
 
@@ -1,12 +1,32 @@
1
1
  module.exports = (responseStr, resourceStr) => `
2
- responseStr: ${responseStr}
3
- resourceStr: ${resourceStr}
4
- resourceStr是前端目前猜测的字段名(请从resourceStr中提取dataIndex和name)
5
- responseStr是后端的真实响应。
6
- 请比对两者,找出现有前端字段名应该被替换为哪个真实的后端字段名。
7
- 匹配规则: 1.完全相同 2.下划线/驼峰转换 3.语义相似。
8
- 请只输出一个JSON对象, Key为前端猜测的旧名字, Value为Response里的真实新名字。
9
- 例如:{"key_1": "key1"}
10
- 如果没有找到对应的,请不要包含在结果中。
11
- 注意:⚠️⚠️如果有语义相近的两个字段优先取中文字段,比如"createByName"和"createBy",则取"createByName"
12
- 。`.trim()
2
+ 目标:执行【前端虚拟字段】与【后端真实响应】的物理重组与对账。
3
+
4
+ ## 1. 核心输入 (Signal Capture)
5
+ - **前端现状 (Source Keys)**:
6
+ ${resourceStr}
7
+
8
+ - **后端样本 (Target Data)**:
9
+ ${responseStr}
10
+
11
+ ## 2. 匹配算法优先级 (Alignment Protocol)
12
+ 请严格按以下优先级执行逻辑映射,直到找到唯一目标:
13
+ 1. **L1 - 物理全等**: 字符完全一致 (例: userId == userId)。
14
+ 2. **L2 - 格式重组**: 下划线与驼峰转换 (例: user_id == userId)。
15
+ 3. **L3 - 语义穿透**: 业务含义高度一致 (例: amount == price, phone == mobile)。
16
+
17
+ ## 3. ⚠️ 强制冲突修正 (Conflict Resolution)
18
+ 若存在多个潜在目标,执行以下强制判定:
19
+ - **中文字义优先**: 优先匹配能解释前端视觉标签含义的字段。
20
+ - **长度优先**: 若 "createByName" 与 "createBy" 同时存在,强制选择 "createByName"。
21
+
22
+ ## 4. 输出约束 (Output Restrictions)
23
+ - **严禁输出解释性文字、严禁 Markdown 标签**。
24
+ - **仅输出合法的 JSON 对象**。
25
+ - **过滤机制**: 若某字段在后端响应中完全找不到对应映射,物理拦截(不输出)。
26
+
27
+ ## 5. 示例参考 (Example)
28
+ Input:
29
+ Source: { dataIndex: 'userName' }, Response: { name: '2B' }
30
+ Output:
31
+ {"userName": "name"}
32
+ `.trim()
@@ -1,45 +1,60 @@
1
1
  module.exports = `
2
- 识别图片内容(搜索项,表格列,是否有勾选框,查询下拉选项,统计条等页面组件,并按照从上到下的结构把识别出来的组件按顺序组织成型)
3
- ⚠️⚠️⚠️注意:
4
- 1.变量使用驼峰命名法
5
- 【严禁中文】绝对不允许使用中文作为变量名或函数名
6
- 【严禁js关键字】绝对不允许使用js关键字作为变量名或函数名(export/delete/const...)
7
- 2.标签页:
8
- tabs: [{tab:'tab1',key:'tab1'}]
9
- 表格上方的Button请务必归类为functionButton,只有当一组水平排列的项下方有明显的长横线(Ink Bar),或者呈现明显的'卡片包裹感'(Card style)时才识别为tabs
10
- 3.搜索项:
11
- formItems: [{ label: '文本', name: '对应的英文名词' }],
12
- ⚠️⚠️⚠️注意:
13
- 如果是下拉框,请加入type:'select',options:_CODE_对应的英文名词Options_CODE_
14
- 如果是时间范围查询,请加入type:'daterange',name:'对应的英文名词start,对应的英文名词end'
15
- 4.统计条:
16
- staticInfo:{has:true/false,text:'具体统计内容'},
17
- 5.表格:
18
- {
19
- pagination: true/false,
20
- expandable: true/false,
21
- rowSelection: true/false,
22
- operation: [{label:'操作',action:'操作动词+ByRecord'}],
23
- columns: [{ title: '普通列', dataIndex:'对应的英文名词' }],
24
- }
25
- ⚠️⚠️⚠️注意:
26
- 请不要在columns里加上操作列
27
- 如果列需要排序请加上sorter:true
28
- 如果是时间列,请加入render:_CODE_text=>timeRender({time:text})_CODE_
29
- 如果是序号列直接渲染为{ title: '序号',render: _CODE_(_, record, index) => index + 1_CODE_ }
30
- 如果是下拉框列(所对应的查询项明确是下拉框),请加入render:_CODE_text=>对应的options.find(item=>item.value===text)?.label_CODE_
31
- 如果列需要过滤请加上filters:[],onFilter: _CODE_(value, record) => record[对应的英文名词].includes(value)_CODE_
32
- 6.功能按钮
33
- functionButton:[{btn:'btn',action:'操作动词+模块英文名词'}]
34
- 如果和表格行操作重复请在action加上BySelected前缀
35
- 如果是导出功能,对应的动词为export+对应的英文名词,没有名词则为exportData
36
- 7.下拉选项字典
37
- optionDict:{_CODE_对应的英文名词Options_CODE_:[]]}
38
- ⚠️⚠️⚠️注意:数组值为对应列展示的内容组成的类似{label:'',value:''}的数组
39
- 8.pageStruct
40
- 从上至下的页面结构组成
41
- ⚠️⚠️⚠️注意:这里只能从AlertInfo,MySearchForm,FunctionButtonsBlock,MyTable中选择,并且只有真的存在才加入,绝对禁止加入其他组件
42
- ⚠️⚠️⚠️另外这里不要加入tabs组件
43
- 最后输出一个JSON对象,不要包含任何Markdown标签,格式如下
44
- { tabs: [], table: {}, formItems: [],staticInfo:{}, optionDict: {}, functionButton: [],pageStruct:[]}
45
- `
2
+ # Task: Full-Page UI Analysis & Structural Assembly
3
+ 执行全页面视觉特征提取,并按照物理布局顺序输出标准的构筑协议。
4
+
5
+ ## 1. 核心命名协议 (Naming Convention)
6
+ - **驼峰命名**: 所有变量及函数名必须遵循 camelCase。
7
+ - **⚠️ 严禁中文**: 绝对禁止在变量名、键名中使用中文字符。
8
+ - **物理屏蔽关键字**: 禁止使用 JS 保留字 (export, delete, const, let, class, default 等)。
9
+
10
+ ## 2. 零部件构筑规范 (Component Specifications)
11
+
12
+ ### 标签页 (Tabs)
13
+ - **识别条件**: 仅当下部有显著长横线 (Ink Bar) 或呈现包裹感 (Card style) 时判定。
14
+ - **排除项**: 表格上方的独立按钮必须归类为 [functionButton]。
15
+
16
+ ### 搜索项 (FormItems)
17
+ - **基础格式**: [{ label: '文本', name: 'englishName',type:'' }]
18
+ - ⚠️ 属性精简规则:
19
+ - 默认输入框: 仅保留 [label, name] 属性。**严禁出现 type 属性**。
20
+ - 非默认组件: 仅当类型为 [auto, date, radio, select, upload, checkbox, textarea, daterange] 时才允许添加 type 属性。
21
+ **标准定义 (Columns)**:
22
+ - date: 单日期
23
+ - daterange: 日期范围
24
+ - enum: 枚举类型
25
+ - text: 普通文本(默认)
26
+ - 命名规范:
27
+ - daterange: name 必须设为 '字段英文名start,字段英文名end'。
28
+
29
+ ### 统计条 (StaticInfo)
30
+ - **独立存在**: 位于表格外周的汇总信息。输出格式: { has: true, text: '具体内容' }。
31
+
32
+ ### 表格主体 (Table)
33
+ - **标准项**: {title:'列名', dataIndex:'列名英文名词',type:''}
34
+ - **配置项**: [pagination, expandable, rowSelection] 均为布尔值。
35
+ - **标准定义 (Columns)**:
36
+ - ⚠️ **操作锁定**: 严禁在 columns 数组中包含“操作”列。
37
+ - date: 时间/日期列
38
+ - money: 金额列
39
+ - index: 序号列
40
+ - enum: 枚举列
41
+ - text: 普通文本(默认)
42
+ - **行操作 (Operation)**: [{label:'操作名', action:'动词ByRecord'}]。
43
+
44
+ ### 全局功能按钮 (FunctionButton)
45
+ - **格式**: [{btn:'显示文本', action:'动作名'}]。
46
+ - **联动命名**: 若与行操作重复,action 必须加上 'BySelected' 前缀。
47
+ - **导出协议**: action 设为 'exportData' 或 'export+模块名'。
48
+
49
+ ## 3. 布局逻辑编排 (Page Structure)
50
+ - **字段**: [pageStruct] 定义从上至下的物理堆叠顺序。
51
+ - **白名单**: 只能从 [AlertInfo, MySearchForm, FunctionButtonsBlock, MyTable] 中选择。
52
+ - **⚠️ 约束**: 严禁在 pageStruct 中包含 Tabs 或其他未定义组件;并且只有真实存在的组件才能被包含。
53
+
54
+ ## 4. 输出约束 (Output Format)
55
+ - **格式**: 纯 JSON 对象,严禁 Markdown 标签包围。
56
+ - **字典映射 (OptionDict)**: 必须包含所有 select 类型所需的 Options 数组,键名格式为 字段英文名+Options ,元素格式为 {label:'', value:''}。
57
+
58
+ **JSON 骨架要求**:
59
+ { "tabs": [], "table": {}, "formItems": [], "staticInfo": {}, "optionDict": {}, "functionButton": [], "pageStruct": [] }
60
+ `.trim();
@@ -8,31 +8,39 @@ module.exports = `
8
8
  3. 必须严格遵守以下字段规范。
9
9
 
10
10
  ## 1. 表格 (Table)
11
- - 结构: { columns: [{ title: '', dataIndex: '' }] }
11
+ - 结构: { columns: [{ title: '列名', dataIndex: '列名英文名词', type: '' }] }
12
12
  - ⚠️ 字段白名单: [title, dataIndex, sorter, render, filters, onFilter]
13
13
  - 禁止输出:
14
14
  - 严禁在 columns 中包含“操作”列。
15
15
  - ***⚠️⚠️⚠️严禁输出formItems,只允许输出coloumns⚠️⚠️⚠️***
16
- - 特殊逻辑:
17
- - 时间列: render 必须为 _CODE_text=>timeRender({time:text})_CODE_
18
- - 序号列: render 必须为 _CODE_(_, record, index) => index + 1_CODE_
19
- - 下拉映射列: render 必须为 _CODE_text=>字段英文名Options.find(item=>item.value===text)?.label_CODE_
16
+ - **标准定义 (Columns)**:
17
+ - ⚠️ **操作锁定**: 严禁在 columns 数组中包含“操作”列。
18
+ - date: 时间/日期列
19
+ - money: 金额列
20
+ - index: 序号列
21
+ - enum: 枚举列
22
+ - text: 普通文本(默认)
23
+ - **行操作 (Operation)**: [{label:'操作名', action:'动词ByRecord'}]
20
24
 
21
25
  ## 2. 表单 (Form)
22
- - 结构: formItems: [{label:'',name:'',type:'',options:[]}]
26
+ - **基础格式**: [{ label: '文本', name: 'englishName',type:'' }]
23
27
  - 禁止输出:
24
28
  - ***⚠️⚠️⚠️严禁输出columns,只允许输出formItems⚠️⚠️⚠️***
25
29
  - ⚠️ 属性精简规则:
26
30
  - 默认输入框: 仅保留 [label, name] 属性。**严禁出现 type 属性**。
27
31
  - 非默认组件: 仅当类型为 [auto, date, radio, select, upload, checkbox, textarea, daterange] 时才允许添加 type 属性。
32
+ **标准定义 (Columns)**:
33
+ - date: 单日期
34
+ - daterange: 日期范围
35
+ - enum: 枚举类型
36
+ - text: 普通文本(默认)
28
37
  - 命名规范:
29
38
  - daterange: name 必须设为 '字段英文名start,字段英文名end'。
30
- - 选择类 (radio/select/checkbox): options 必须设为 _CODE_字段英文名Options_CODE_。
31
39
  - 增强属性:
32
40
  - 必填校验: 若图片中 label 前有红色星号,必须加入 rules:[{required:true,message:'xxx不能为空'}]。
33
41
  - 文字单位: 若项末尾有单位(如 元/kg),必须存入 unit 属性。
34
42
 
35
43
  ## 3. 下拉选项字典 (OptionDict)
36
- - 结构: optionDict: { _CODE_字段英文名Options_CODE_: [] }
44
+ - 结构: optionDict: { 字段英文名Options: [] }
37
45
  - 内容: 数组元素必须为 {label: '', value: ''} 格式。
38
46
  `
@@ -19,6 +19,7 @@ const getOpenAI = () => {
19
19
  }
20
20
 
21
21
  const askAI = async (model, messages, retryCount = 0) => {
22
+
22
23
  if (retryCount > 3) {
23
24
  throw new Error(language(
24
25
  'YoRHa 司令部连接中断:请检查网络状态或黑盒共鸣情况。',
@@ -31,6 +32,8 @@ const askAI = async (model, messages, retryCount = 0) => {
31
32
  const response = await openai.chat.completions.create({
32
33
  model,
33
34
  messages,
35
+ top_p: 0.1,
36
+ temperature: 0.01,
34
37
  response_format: { type: 'json_object' }
35
38
  })
36
39
  let raw = response.choices[0].message.content.trim()
@@ -38,7 +41,6 @@ const askAI = async (model, messages, retryCount = 0) => {
38
41
  const match = raw.match(/[\{\[][\s\S]*[\}\]]/)
39
42
  const JSON5 = require('json5')
40
43
  return JSON5.parse(match ? match[0] : raw)
41
-
42
44
  } catch (err) {
43
45
  const statusCode = err.status || err.response?.status
44
46
  const isAuthError = err.message.includes('401') || err.message.includes('402') || [401, 402].includes(statusCode)
@@ -0,0 +1,92 @@
1
+ const http = require('http')
2
+ const zlib = require('zlib')
3
+ const chalk = require('chalk')
4
+ const httpProxy = require('http-proxy')
5
+ const { get } = require('../core/context')
6
+
7
+ /**
8
+ * @function startProxyTower
9
+ * @description [地堡 42153 联合波段] 启动流量拦截塔。
10
+ * 独立运行于后台,监听 42153 端口,物理截获联调过程中的 JSON 信号并触发语义对齐。
11
+ */
12
+ module.exports = () => {
13
+
14
+ const { config, language, apiHandler, unwrapSignal, isQuerySignal } = get()
15
+
16
+ const TOWER_PORT = 42153
17
+ const hackedRegistry = new Map()
18
+ const proxy = httpProxy.createProxyServer({})
19
+
20
+
21
+ // 简单的辅助函数:提取 JSON 的所有 Key 作为“指纹”
22
+ const getJsonFingerprint = obj => {
23
+ if (!obj || typeof obj !== 'object') return ''
24
+ return Object.keys(Array.isArray(obj) ? (obj[0] || {}) : obj).sort().join(',')
25
+ }
26
+
27
+ const server = http.createServer((req, res) => {
28
+
29
+ const target = config.proxyTarget
30
+
31
+ if (!target) {
32
+ // 静默模式:如果不配置 proxyTarget,拦截塔只转发不处理(或报错提示)
33
+ return
34
+ }
35
+
36
+ // 💡 物理监控:劫持响应流
37
+ proxy.on('proxyRes', function (proxyRes, req, res) {
38
+ let body = []
39
+ proxyRes.on('data', chunk => body.push(chunk))
40
+ proxyRes.on('end', async () => {
41
+ const buffer = Buffer.concat(body)
42
+ const encoding = proxyRes.headers['content-encoding']
43
+ try {
44
+ const rawBody = encoding === 'gzip' ? zlib.gunzipSync(buffer) : buffer
45
+ const json = JSON.parse(rawBody.toString())
46
+
47
+ const coreData = unwrapSignal(json) // 先脱水
48
+
49
+ // 💡 这一步就是地堡的“火控系统”
50
+ if (!isQuerySignal(req, json, coreData)) {
51
+ return // 增删改信号,直接丢弃,保持静默
52
+ }
53
+
54
+ const referer = req.headers.referer || ''
55
+ const fileName = referer.split('?')[0].split('/').filter(Boolean).at(-1)
56
+
57
+ const fingerprint = getJsonFingerprint(unwrapSignal(json)) // 获取数据指纹
58
+ const lastFingerprint = hackedRegistry.get(fileName)
59
+
60
+ // 如果指纹没变,说明字段结构是一样的,无需再次骇入
61
+ if (lastFingerprint === fingerprint) {
62
+ return
63
+ }
64
+
65
+ console.log(chalk.cyan(language(
66
+ `\n📡 Pod 153: 截获运行时信号 [${fileName}]。执行自动对齐协议...`,
67
+ `\n📡 Pod 153: Captured runtime signal [${fileName}]. Executing semantic alignment protocol...`
68
+ )))
69
+ // 💡 直接调用 api-handler 物理更新 resource.js
70
+ await apiHandler(null, { fileName, data: json })
71
+ hackedRegistry.set(fileName, fingerprint)
72
+ } catch (e) {
73
+ console.log(e)
74
+ // 非 JSON 信号,保持静默
75
+ }
76
+ })
77
+ })
78
+ // 转发至真实后端
79
+ proxy.web(req, res, { target, changeOrigin: true })
80
+ })
81
+
82
+ server.listen(TOWER_PORT, () => {
83
+ console.log(chalk.magenta(language(
84
+ `✨ YoRHa 联合基站:信号拦截塔已在线 [波段: ${TOWER_PORT}]`,
85
+ `✨ YoRHa Joint Station: Signal Intercept Tower Online [Band: ${TOWER_PORT}]`
86
+ )))
87
+ console.log(chalk.gray(language(
88
+ `💡 指令:请将您的代理目标指向 http://localhost:${TOWER_PORT}`,
89
+ `💡 Command: Please point your local proxy target to http://localhost:${TOWER_PORT}`
90
+ )))
91
+ })
92
+ }
@@ -46,16 +46,45 @@ const getConfig = () => {
46
46
  */
47
47
  const cleanCode = str => {
48
48
  return str
49
+ // 物理超度 Markdown 糖衣(解决```json 报错)
50
+ .replace(/```[a-z]*\n?/gi, '')
51
+ .replace(/```/g, '')
52
+ // 逻辑自愈 处理 AI 错误转义的引号(把 \" 还原回 ")
53
+ .replace(/\\"/g, '"')
54
+ .replace(/['"]?_CODE_([\s\S]*?)_CODE_['"]?/g, '$1') // 去掉 _CODE_ 包裹的代码
55
+ .replace(/_CODE_/g, '') // 兜底清理
49
56
  .replace(/"(\w+)":/g, '$1:') // 去掉 key 的双引号
50
57
  .replace(/"/g, "'") // 双引号全部转单引号
51
- .replace(/['"]_CODE_([\s\S]*?)_CODE_['"]/g, '$1') // 去掉 _CODE_ 包裹的代码
52
- .replace(/_CODE_/g, '') // 兜底清理
53
58
  .replace(/[ \t]+$/gm, '') // 去除每一行行尾的多余空格
54
59
  .replace(/\n{3,}/g, '\n\n') // 将3个或以上的换行符压缩成2个换行符
55
60
  .replace(/^\s+/, '') // 去掉文件头部的空行
56
61
  .trim() + '\n'
57
62
  }
58
63
 
64
+ /**
65
+ * @function unwrapSignal
66
+ * @description [地堡数据脱水机] 物理扫描 JSON 结构,剥离业务外壳(code/msg/total 等)。
67
+ * 目标:精准定位到核心的 Array。
68
+ */
69
+ const unwrapSignal = json => {
70
+ if (Array.isArray(json)) return json
71
+
72
+ // 💡 扫描常见的数据仓库 Key
73
+ const dataKeys = ['data', 'list', 'items', 'datas', 'rows', 'result', 'payload', 'results', 'dataList']
74
+ for (const key of dataKeys) {
75
+ if (json[key] && Array.isArray(json[key])) return json[key]
76
+ }
77
+
78
+ // 💡 递归侦察:处理 data: { list: [...] } 这种二级套娃
79
+ for (const key in json) {
80
+ if (json[key] && typeof json[key] === 'object') {
81
+ const nested = unwrapSignal(json[key])
82
+ if (Array.isArray(nested)) return nested
83
+ }
84
+ }
85
+ return null
86
+ }
87
+
59
88
  /**
60
89
  * 引导序列
61
90
  * 模拟系统引导过程中的动画效果
@@ -79,6 +108,31 @@ const bootSequence = async version => {
79
108
  }
80
109
  }
81
110
 
111
+ /**
112
+ * @function isQuerySignal
113
+ * @description [语义雷达]
114
+ * 核心逻辑:不纠结请求是怎么发的,只看带回来的货(Response)长什么样。
115
+ */
116
+ const isQuerySignal = (req, json, coreData) => {
117
+
118
+ const url = req.url.toLowerCase()
119
+ // 💡 1. 物理红区:只要 URL 包含这些动作,无论返回什么都视为“非列表”
120
+ const actionKeywords = ['add', 'delete', 'update', 'save', 'remove', 'edit', 'insert', 'create', 'export', 'upload']
121
+ if (actionKeywords.some(key => url.includes(key))) return false
122
+ // 💡 2. 物理金标准:脱壳后的核心物资是一个【非空数组】
123
+ // 只要带回了一堆长得一样的对象,那它 99.9% 就是列表页
124
+ const hasListData = Array.isArray(coreData) && coreData.length > 0
125
+ // 💡 3. 语义辅助:如果响应里包含“分页指纹”
126
+ // 有时候第一页刚好没数据(coreData 是空数组),但 JSON 里带有 total, page 等字段
127
+ const hasPaginationFingerprint = ['total', 'records', 'page', 'size', 'count'].some(key => {
128
+ const k = key.toLowerCase()
129
+ // 在原始 JSON 的第一层寻找分页相关的 key
130
+ return Object.keys(json).some(rawKey => rawKey.toLowerCase().includes(k))
131
+ })
132
+ // 结论:具备列表特征或者是分页指纹的,判定为查询信号
133
+ return hasListData || hasPaginationFingerprint
134
+ }
135
+
82
136
  /**
83
137
  * 矩阵效果
84
138
  * 模拟数据物理封存过程中的矩阵效果
@@ -113,12 +167,15 @@ const matrixEffect = async (duration = 1500) => {
113
167
  '5f 43 4f 44 45 5f' // _CODE_
114
168
  ]
115
169
 
170
+ const threshold = 3000
116
171
  const endTime = Date.now() + duration
117
- const isLegendary = currentTotal >= 3000
118
172
  const width = process.stdout.columns || 80
173
+ const isLegendary = currentTotal >= threshold
119
174
 
120
175
  if (isLegendary) {
121
- coreFragments.push(chalk.yellow.bold('33 30 30 30 2b')) // "3000+" 的十六进制
176
+ const achievement = `${threshold}+`
177
+ const hex = achievement.split('').map(char => char.charCodeAt(0).toString(16)).join(' ')
178
+ coreFragments.push(chalk.yellow.bold(hex))
122
179
  coreFragments.push(chalk.yellow.bold('4c 45 47 45 4e 44')) // "LEGEND"
123
180
  }
124
181
 
@@ -128,10 +185,10 @@ const matrixEffect = async (duration = 1500) => {
128
185
  console.log(chalk.white(' [System] ') + chalk.green(language('所有构筑数据已同步至 Bunker 存储节点。', 'All data synced to Bunker storage nodes.')))
129
186
  if (currentTotal !== 0) {
130
187
  if (isLegendary) {
131
- console.log(chalk.yellow.bold(language(` [Achievement] 物理克隆总数已超越 3000 战略阈值!当前战力:${currentTotal}`, ` [Achievement] Physical clone count has exceeded 3000 strategic threshold! Current power: ${currentTotal}`)))
188
+ console.log(chalk.yellow.bold(language(` [Achievement] 物理克隆总数已超越 ${threshold} 战略阈值!当前战力:${currentTotal}`, ` [Achievement] Physical clone count has exceeded ${threshold} strategic threshold! Current power: ${currentTotal}`)))
132
189
  console.log(chalk.yellow(language(' [Bunker] 恭喜指挥官,您的构筑协议已成为人类荣光的一部分。', ' [Bunker] Congratulations, your construction protocol is now part of humanity.')))
133
190
  } else {
134
- console.log(chalk.cyan(language(` [System] 当前构筑总数:${currentTotal}。距离 3000 勋章还剩 ${3000 - currentTotal} 次。`, ` [System] Current clones: ${currentTotal}. ${3000 - currentTotal} to Achievement.`)))
191
+ console.log(chalk.cyan(language(` [System] 当前构筑总数:${currentTotal}。距离 ${threshold} 勋章还剩 ${threshold - currentTotal} 次。`, ` [System] Current clones: ${currentTotal}. ${threshold - currentTotal} to Achievement.`)))
135
192
  }
136
193
  }
137
194
  console.log(chalk.cyan(language(' [System] 如果它能帮您节省时间,请在 GitHub 上给它点个赞 ⭐。', ' [System] If it saves you time, feel free to give it a ⭐ on GitHub.')))
@@ -168,34 +225,54 @@ const getExistingMenus = (dir = 'src/pages') => {
168
225
  }
169
226
 
170
227
  /**
171
- * 生成智能导入语句
172
- * 根据代码中实际使用的依赖,自动生成对应的 import 语句
173
- * @param {string} codeStr - 生成的代码字符串
174
- * @param {boolean} hasTabs - 是否包含标签页
175
- * @returns {string} 拼接后的 import 语句
228
+ * @function formatFormItemAndColumns
229
+ * @description [地堡逻辑转录引擎] 执行核心的语义对齐与代码物理注入协议。
230
+ * 该函数负责将 AI 识别出的视觉类型标签(money/date/enum等)转化为标准的 React 渲染逻辑,
231
+ * 并自动提取全频道所需的字典引用(Dictionary Blocks)。
232
+ *
233
+ * @param {Object} params - 构筑参数包
234
+ * @param {Object} params.pageConfig - 由 Pod 042 扫描出的原始页面配置对象
235
+ *
236
+ * @returns {Object} 返回物理装配完成的数据包:
237
+ * @returns {Array} .formItems - 已注入 Options 指令的表单零部件清单
238
+ * @returns {Array} .processedColumns - 已执行逻辑注入(Render 补丁)的表格列清单
239
+ * @returns {Array} .dictBlocks - 全频道去重后的字典(Options)变量名清单
176
240
  */
177
- const generateSmartImports = (codeStr, hasTabs) => {
178
- const hooksLib = ['useTableQuery']
179
- const reactLib = ['useState', 'useEffect', 'useRef', 'useMemo']
180
- const componentsLib = ['MyTable', 'MyModalForm', 'MySearchForm']
181
- const antdLib = ['Card', 'Space', 'Modal', 'Button', 'Alert', 'Table', 'Input', 'Select']
241
+ const formatFormItemAndColumns = ({ pageConfig }) => {
182
242
 
183
- const usedAntd = antdLib.filter(name => new RegExp(`\\b${name}\\b`).test(codeStr))
184
- const usedHooks = hooksLib.filter(name => new RegExp(`\\b${name}\\b`).test(codeStr))
185
- const usedReact = reactLib.filter(name => new RegExp(`\\b${name}\\b`).test(codeStr))
186
- const usedComps = componentsLib.filter(name => new RegExp(`\\b${name}\\b`).test(codeStr))
243
+ const codePresets = {
244
+ money: 'text => moneyRender(text)',
245
+ date: 'text => timeRender({time: text})',
246
+ index: '(_, record, index) => index + 1',
247
+ enum: dataIndex => `text => ${dataIndex}Options.find(item => item.value === text)?.label??text`
248
+ }
187
249
 
188
- const imports = [
189
- usedReact.length && `import { ${usedReact.join(', ')} } from 'react'`,
190
- `import { request } from '../../utils/request'`,
191
- `import { formatQuery } from '../../utils/utils'`,
192
- `import { ${hasTabs ? 'tabs, ' : ''}formItems, modalItems, tableColumns} from './resource'`,
193
- ...usedHooks.map(hook => `import { ${hook} } from '../../hooks/${hook}'`),
194
- ...usedComps.map(comp => `import { ${comp} } from '../../components/${comp}'`),
195
- usedAntd.length && `import { Form, ${usedAntd.join(', ')} } from 'antd'`
196
- ].sort((a, b) => a.length - b.length)
250
+ const columns = pageConfig.table?.columns ?? pageConfig?.columns ?? []
197
251
 
198
- return imports.filter(Boolean).join('\n')
252
+ const tableDicts = columns?.filter(item => item.type === 'enum')?.map(item => `${item.dataIndex}Options`) ?? []
253
+ const formDicts = pageConfig.formItems?.filter(item => item.type === 'select')?.map(item => `${item.name}Options`) ?? []
254
+ const dictBlocks = Array.from(new Set([...formDicts, ...tableDicts]))
255
+
256
+ const formItems = pageConfig.formItems?.map(item => ({
257
+ ...item,
258
+ ...(item.type === 'select' ? { options: `_CODE_${item.name}Options_CODE_` } : {})
259
+ }))
260
+
261
+ const processedColumns = columns?.map(col => {
262
+ if (col.type && codePresets[col.type]) {
263
+ // 💡 物理注入:根据标签,强行塞入标准化的 JS 代码字符串
264
+ const renderCode = typeof codePresets[col.type] === 'function' ? codePresets[col.type](col.dataIndex) : codePresets[col.type]
265
+ delete col.type
266
+ return {
267
+ ...col,
268
+ render: `_CODE_${renderCode}_CODE_` // 重新打标,交给 cleanCode 处理
269
+ }
270
+ }
271
+ delete col.type
272
+ return col
273
+ })
274
+
275
+ return { formItems, dictBlocks, processedColumns }
199
276
  }
200
277
 
201
278
  /**
@@ -218,4 +295,35 @@ const copyTemplateDir = (options, templateSubDir, targetSubDir) => {
218
295
  })
219
296
  }
220
297
 
221
- module.exports = { language, getConfig, cleanCode, matrixEffect, bootSequence, getExistingMenus, copyTemplateDir, generateSmartImports }
298
+ /**
299
+ * 生成智能导入语句
300
+ * 根据代码中实际使用的依赖,自动生成对应的 import 语句
301
+ * @param {string} codeStr - 生成的代码字符串
302
+ * @param {boolean} hasTabs - 是否包含标签页
303
+ * @returns {string} 拼接后的 import 语句
304
+ */
305
+ const generateSmartImports = ({ bodyCode, hasTabs, hasFormItems }) => {
306
+ const hooksLib = ['useTableQuery']
307
+ const reactLib = ['useState', 'useEffect', 'useRef', 'useMemo']
308
+ const componentsLib = ['MyTable', 'MyModalForm', 'MySearchForm']
309
+ const antdLib = ['Card', 'Space', 'Modal', 'Button', 'Alert', 'Table', 'Input', 'Select']
310
+
311
+ const usedAntd = antdLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
312
+ const usedHooks = hooksLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
313
+ const usedReact = reactLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
314
+ const usedComps = componentsLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
315
+
316
+ const imports = [
317
+ usedReact.length && `import { ${usedReact.join(', ')} } from 'react'`,
318
+ `import { request } from '../../utils/request'`,
319
+ `import { formatQuery } from '../../utils/utils'`,
320
+ ...usedHooks.map(hook => `import { ${hook} } from '../../hooks/${hook}'`),
321
+ ...usedComps.map(comp => `import { ${comp} } from '../../components/${comp}'`),
322
+ usedAntd.length && `import { ${hasFormItems ? 'Form, ' : ''}${usedAntd.join(', ')} } from 'antd'`,
323
+ `import { ${hasTabs ? 'tabs, ' : ''}${hasFormItems ? 'formItems, ' : ''}modalItems, tableColumns} from './resource'`,
324
+ ].sort((a, b) => a.length - b.length)
325
+
326
+ return imports.filter(Boolean).join('\n')
327
+ }
328
+
329
+ module.exports = { language, getConfig, cleanCode, unwrapSignal, matrixEffect, bootSequence, isQuerySignal, getExistingMenus, copyTemplateDir, generateSmartImports, formatFormItemAndColumns }
@@ -40,6 +40,7 @@ const AliyunOSSUpload = ({ value, onChange, ...restProps }) => {
40
40
  try {
41
41
  const result = await initOSS(url, options)
42
42
  setOSSData(result)
43
+ return result
43
44
  } catch (err) {
44
45
  console.log(err)
45
46
  }
@@ -79,12 +80,13 @@ const AliyunOSSUpload = ({ value, onChange, ...restProps }) => {
79
80
  * 若令牌已失效(过期),则强行拦截并执行同步刷新协议,同时执行物理路径归一化。
80
81
  */
81
82
  const beforeUpload = async file => {
83
+ let currentOSS = OSSData
82
84
  const expire = Number(OSSData.expire) * 1000
83
85
  if (expire < Date.now()) {
84
- await init()
86
+ currentOSS = await init()
85
87
  }
86
88
  // 💡 物理路径重组:通过正则清除多余的路径分隔符,确保存储节点坐标唯一
87
- file.url = `${OSSData?.dir ?? path}/${file.uid}_${file.name}`.replace(/\/\//g, '/')
89
+ file.url = `${currentOSS?.dir ?? path}/${file.uid}_${file.name}`.replace(/\/\//g, '/')
88
90
  return file
89
91
  }
90
92
 
@@ -1,7 +1,3 @@
1
- {{!--
2
- 📡 [地堡战术响应模组]
3
- 负责物理封存所有交互事件:包含行级操作、全局按钮、页签切换及弹窗构筑提交。
4
- --}}
5
1
  {{#each operations}}
6
2
  const {{this.action}} = async record => {
7
3
  const response = await request('/api')
@@ -1,9 +1,3 @@
1
- {{!--
2
- 📡 [地堡核心驱动引擎]:逻辑链路全自动构筑
3
- 1. 执行“传感器对齐”:从 URL 中提取历史信号,实现状态持久化
4
- 2. 激活“useTableQuery”:物理封存分页、搜索、及动态列加载逻辑
5
- --}}
6
-
7
1
  const initParams = {{#if hasTabs}}{ type: tabs[0].key }{{else}}{}{{/if}}
8
2
 
9
3
  const query = formatQuery(Object.fromEntries(new URLSearchParams(location.search).entries()), {{formItems}})
@@ -1,8 +1,8 @@
1
1
 
2
- {{!-- 📡 [地堡状态模组] 物理信号初始化:驱动弹窗、页签及数据勾选等核心交互单元 --}}
3
2
  const rowKey = 'id'
3
+ {{#if hasFormItems}}
4
4
  const [form] = Form.useForm()
5
- const [modal, setModal] = useState({ visible:false, title:'', formItems:modalItems })
5
+ {{/if}}
6
6
  {{#if hasRowSelection}}
7
7
  const [selectedRows, setSelectedRows] = useState([])
8
8
  {{/if}}
@@ -11,4 +11,5 @@ const [activeKey, setActiveKey] = useState(tabs[0].key)
11
11
  {{/if}}
12
12
  {{#if hasExpandable}}
13
13
  const [expandedRowKeys, setExpandedRowKeys] = useState([])
14
- {{/if}}
14
+ {{/if}}
15
+ const [modal, setModal] = useState({ visible:false, title:'', formItems:modalItems })