@yorha2b-lab/autodev 3.0.0 → 3.1.1

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,10 +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 bunker = require(path.join(__dirname, '../src/core/context.js'))
8
- 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')
9
9
 
10
10
  program
11
11
  .version(pkg.version)
@@ -54,9 +54,10 @@ program
54
54
  .action(() => {
55
55
  bootSequence(pkg.version)
56
56
  bunker.init(program.opts())
57
- require(path.join(__dirname, '../src/commands/watch'))()
58
- if (bunker.get().config.proxyTarget) {
59
- require(path.join(__dirname, '../src/services/proxy-tower'))()
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')()
60
61
  }
61
62
  process.on('SIGINT', () => {
62
63
  console.log('\n')
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',
@@ -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": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "description": "基于视觉大模型的前端(react+Antd)全自动 CRUD 代码生成器",
5
5
  "bin": {
6
6
  "autodev": "bin/autodev.js"
@@ -5,7 +5,7 @@ const chalk = require('chalk')
5
5
 
6
6
  module.exports = async (filePath, liveResponse = null) => {
7
7
 
8
- const { get } = require(path.join(__dirname, '../../core/context'))
8
+ const { get } = require('../../core/context')
9
9
  const { config, options, language, unwrapSignal, alignResponseFields } = get()
10
10
 
11
11
  const startTime = Date.now()
@@ -14,8 +14,8 @@ module.exports = async (filePath, liveResponse = null) => {
14
14
 
15
15
  const spinner = ora({
16
16
  text: chalk.yellow(language(
17
- `🤖 Pod 153: [侦察] 发现加密数据源 [${fileName}]。正在尝试执行语义对齐协议...`,
18
- `🤖 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`
19
19
  )),
20
20
  color: 'yellow'
21
21
  }).start()
@@ -47,7 +47,17 @@ module.exports = async (filePath, liveResponse = null) => {
47
47
  `🧑‍💻 9S: Scanning field differences... Bridging semantic gaps.`
48
48
  ))
49
49
 
50
- const result = await alignResponseFields(options, JSON.stringify(sampleData), 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
+ }
59
+
60
+ const result = await alignResponseFields(options, JSON.stringify(sampleData), extractKeys(resourceStr).join(','))
51
61
  let changeCount = 0
52
62
  const resultMapping = {}
53
63
  Object.entries(result).forEach(([oldField, newField]) => {
@@ -67,7 +77,7 @@ module.exports = async (filePath, liveResponse = null) => {
67
77
  )))
68
78
 
69
79
  if (changeCount > 0) {
70
- console.log(chalk.gray(`\n┌────────────────── [ 9S 语义映射表 ] ──────────────────┐`))
80
+ console.log(chalk.magenta(`\n┌────── [ YoRHa Autonomous Backend Alignment ] ──────┐`))
71
81
  Object.entries(resultMapping).forEach(([oldField, newField]) => {
72
82
  // 💡 物理校准:计算空格数量,让箭头对齐在第 15 个字符位
73
83
  // 中文字符长度 * 2 是为了抵消它在终端占的双倍宽度
@@ -80,7 +90,7 @@ module.exports = async (filePath, liveResponse = null) => {
80
90
  chalk.white(newField)
81
91
  )
82
92
  })
83
- console.log(chalk.gray(`└───────────────────────────────────────────────────────┘\n`))
93
+ console.log(chalk.magenta(`└───────────────────────────────────────────────────────┘\n`))
84
94
  }
85
95
 
86
96
  if (fs.existsSync(filePath)) {
@@ -6,7 +6,7 @@ const stringify = require('json-stringify-pretty-compact')
6
6
 
7
7
  module.exports = async filePath => {
8
8
 
9
- const { get } = require(path.join(__dirname, '../../core/context'))
9
+ const { get } = require('../../core/context')
10
10
 
11
11
  const {
12
12
  config, language, menus,
@@ -46,11 +46,11 @@ module.exports = async filePath => {
46
46
  `\n🤖 Pod 042: [报告] 拦截到实弹请求。正在空投标准模拟包: example.json\n`,
47
47
  `\n🤖 Pod 042: [Report] Real-fire request intercepted. Dropping simulation package: example.json\n`
48
48
  )))
49
- pageConfig = require(path.join(__dirname, '../../../example/example.json'))
49
+ pageConfig = require('../../../example/example.json')
50
50
  } else {
51
51
  spinner.text = chalk.cyan(language(
52
- `🤖 Pod 042: 正在上传视觉元数据至司令部进行语义分析...`,
53
- `🤖 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`
54
54
  ))
55
55
  pageConfig = await recognizePage(pagePrompt, filePath)
56
56
  }
@@ -1,12 +1,12 @@
1
1
  const fs = require('fs')
2
2
  const ora = require('ora')
3
- const path = require('path')
4
3
  const chalk = require('chalk')
5
4
  const stringify = require('json-stringify-pretty-compact')
5
+ const { cleanCode, formatFormItemAndColumns } = require('../../utils/utils')
6
6
 
7
7
  module.exports = async filePath => {
8
8
 
9
- const { get } = require(path.join(__dirname, '../../core/context'))
9
+ const { get } = require('../../core/context')
10
10
  const { language, partPrompt, recognizePage } = get()
11
11
 
12
12
  const startTime = Date.now()
@@ -21,27 +21,23 @@ module.exports = async filePath => {
21
21
 
22
22
  try {
23
23
  spinner.text = chalk.cyan(language(
24
- `🤖 Pod 042: 正在从神经云网络提取 UI 元数据...`,
25
- `🤖 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`
26
26
  ))
27
27
 
28
28
  const pageConfig = await recognizePage(partPrompt, filePath)
29
29
 
30
- const optionDict = pageConfig.optionDict || {}
31
- delete pageConfig.optionDict
30
+ const { formItems, dictBlocks, processedColumns } = formatFormItemAndColumns({ pageConfig })
32
31
 
33
- let mainConfigStr = stringify.default(pageConfig, { indent: 4, maxLength: 200 })
34
- .replace(/"(\w+)":/g, '$1:') // 去掉 key 的双引号
35
- .replace(/"/g, "'") // 全量替换为单引号
36
- .replace(/['"]_CODE_([\s\S]*?)_CODE_['"]/g, '$1') // 还原代码片段
37
- .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 }))
38
35
 
39
36
  let optionsCodeStr = ''
40
- Object.keys(optionDict).forEach(key => {
41
- const varName = key.replaceAll('_CODE_', '')
42
- const optionsArray = optionDict[key]
37
+ dictBlocks.forEach(key => {
38
+ const optionsArray = pageConfig.optionDict?.[key] ?? []
43
39
  const arrayItemsStr = optionsArray.map(opt => ` { label: '${opt.label}', value: '${opt.value}' }`).join(',\n')
44
- optionsCodeStr += `\nexport const ${varName} = [\n${arrayItemsStr}\n]\n`
40
+ optionsCodeStr += `\nexport const ${key} = [\n${arrayItemsStr}\n]\n`
45
41
  })
46
42
 
47
43
  const finalResult = `${mainConfigStr}\n${optionsCodeStr}`
@@ -63,7 +59,7 @@ module.exports = async filePath => {
63
59
  `│ 命令:请手动将上述代码块物理装配至您的目标文件中。`,
64
60
  `│ Command: Please manually assemble the above code block into your target file.`
65
61
  )))
66
- console.log(chalk.magenta(`└────────────────────────────────────────────────────────────────────┘\n`))
62
+ console.log(chalk.magenta(`└───────────────────────────────────────────────────────────────────┘\n`))
67
63
 
68
64
  if (fs.existsSync(filePath)) {
69
65
  fs.unlinkSync(filePath)
@@ -4,9 +4,9 @@ const chalk = require('chalk')
4
4
  const chokidar = require('chokidar')
5
5
  const stringify = require('json-stringify-pretty-compact')
6
6
 
7
- const { get } = require(path.join(__dirname, '../core/context'))
8
- const { copyTemplateDir } = require(path.join(__dirname, '../utils/utils.js'))
9
- const { createTaskQueue } = require(path.join(__dirname, '../core/task-queue.js'))
7
+ const { get } = require('../core/context')
8
+ const { copyTemplateDir } = require('../utils/utils.js')
9
+ const { createTaskQueue } = require('../core/task-queue.js')
10
10
 
11
11
  const watch = () => {
12
12
 
@@ -1,11 +1,11 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const Handlebars = require('handlebars')
4
- const apiHandler = require(path.join(__dirname, '../commands/handlers/api-handler'))
5
- const pageHandler = require(path.join(__dirname, '../commands/handlers/page-handler'))
6
- const partHandler = require(path.join(__dirname, '../commands/handlers/part-handler'))
7
- const { recognizePage, generateMock, alignResponseFields } = require(path.join(__dirname, '../services/llm'))
8
- const { language, getConfig, unwrapSignal, getExistingMenus } = require(path.join(__dirname, '../utils/utils'))
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
9
 
10
10
  let instance = null // 💡 物理封存的全局实例
11
11
 
@@ -27,14 +27,14 @@ module.exports = {
27
27
  : path.join(__dirname, `../../templates/${template}`)
28
28
 
29
29
  instance = {
30
- menus, config, options, language, unwrapSignal,
31
30
  resource, index, template,
32
31
  apiHandler, pageHandler, partHandler,
33
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,
34
36
  indexTpl: Handlebars.compile(fs.readFileSync(path.join(tplDir, 'index.hbs'), 'utf-8')),
35
37
  resourceTpl: Handlebars.compile(fs.readFileSync(path.join(tplDir, 'resource.hbs'), 'utf-8')),
36
- pagePrompt: require(path.join(__dirname, `../prompts/${template}/watch-page.js`)),
37
- partPrompt: require(path.join(__dirname, `../prompts/${template}/watch-part.js`))
38
38
  }
39
39
  return instance
40
40
  },
@@ -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(path.join(__dirname, '../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)
@@ -43,6 +43,8 @@ const resource = ({ pageConfig, resourceTpl }) => {
43
43
  */
44
44
  const index = ({ config, fileName, indexTpl, pageConfig }) => {
45
45
 
46
+ console.log(stringify.default(pageConfig, { indent: 4, maxLength: 200 }))
47
+
46
48
  const hasTabs = pageConfig.tabs?.length > 0
47
49
  const hasFormItems = pageConfig.formItems?.length > 0
48
50
  const hasOperate = pageConfig.table.operation?.length > 0
@@ -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为前端猜测的字段名dataIndex, 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] 均为布尔值false,只有页面明确有对应功能时才为true。
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
  `
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const chalk = require('chalk')
4
- const { language, getConfig } = require(path.join(__dirname, '../utils/utils.js'))
4
+ const { language, getConfig } = require('../utils/utils.js')
5
5
 
6
6
  let client = null
7
7
 
@@ -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)
@@ -1,9 +1,8 @@
1
1
  const http = require('http')
2
- const path = require('path')
3
2
  const zlib = require('zlib')
4
3
  const chalk = require('chalk')
5
4
  const httpProxy = require('http-proxy')
6
- const { get } = require(path.join(__dirname, '../core/context'))
5
+ const { get } = require('../core/context')
7
6
 
8
7
  /**
9
8
  * @function startProxyTower
@@ -12,7 +11,7 @@ const { get } = require(path.join(__dirname, '../core/context'))
12
11
  */
13
12
  module.exports = () => {
14
13
 
15
- const { config, language, apiHandler, unwrapSignal } = get()
14
+ const { config, language, apiHandler, unwrapSignal, isQuerySignal } = get()
16
15
 
17
16
  const TOWER_PORT = 42153
18
17
  const hackedRegistry = new Map()
@@ -44,6 +43,14 @@ module.exports = () => {
44
43
  try {
45
44
  const rawBody = encoding === 'gzip' ? zlib.gunzipSync(buffer) : buffer
46
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
+
47
54
  const referer = req.headers.referer || ''
48
55
  const fileName = referer.split('?')[0].split('/').filter(Boolean).at(-1)
49
56
 
@@ -46,10 +46,15 @@ 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+/, '') // 去掉文件头部的空行
@@ -61,7 +66,7 @@ const cleanCode = str => {
61
66
  * @description [地堡数据脱水机] 物理扫描 JSON 结构,剥离业务外壳(code/msg/total 等)。
62
67
  * 目标:精准定位到核心的 Array。
63
68
  */
64
- const unwrapSignal = (json) => {
69
+ const unwrapSignal = json => {
65
70
  if (Array.isArray(json)) return json
66
71
 
67
72
  // 💡 扫描常见的数据仓库 Key
@@ -103,6 +108,31 @@ const bootSequence = async version => {
103
108
  }
104
109
  }
105
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
+
106
136
  /**
107
137
  * 矩阵效果
108
138
  * 模拟数据物理封存过程中的矩阵效果
@@ -137,12 +167,15 @@ const matrixEffect = async (duration = 1500) => {
137
167
  '5f 43 4f 44 45 5f' // _CODE_
138
168
  ]
139
169
 
170
+ const threshold = 3000
140
171
  const endTime = Date.now() + duration
141
- const isLegendary = currentTotal >= 3000
142
172
  const width = process.stdout.columns || 80
173
+ const isLegendary = currentTotal >= threshold
143
174
 
144
175
  if (isLegendary) {
145
- 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))
146
179
  coreFragments.push(chalk.yellow.bold('4c 45 47 45 4e 44')) // "LEGEND"
147
180
  }
148
181
 
@@ -152,10 +185,10 @@ const matrixEffect = async (duration = 1500) => {
152
185
  console.log(chalk.white(' [System] ') + chalk.green(language('所有构筑数据已同步至 Bunker 存储节点。', 'All data synced to Bunker storage nodes.')))
153
186
  if (currentTotal !== 0) {
154
187
  if (isLegendary) {
155
- 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}`)))
156
189
  console.log(chalk.yellow(language(' [Bunker] 恭喜指挥官,您的构筑协议已成为人类荣光的一部分。', ' [Bunker] Congratulations, your construction protocol is now part of humanity.')))
157
190
  } else {
158
- 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.`)))
159
192
  }
160
193
  }
161
194
  console.log(chalk.cyan(language(' [System] 如果它能帮您节省时间,请在 GitHub 上给它点个赞 ⭐。', ' [System] If it saves you time, feel free to give it a ⭐ on GitHub.')))
@@ -191,6 +224,77 @@ const getExistingMenus = (dir = 'src/pages') => {
191
224
  .map(file => ({ label: file, key: file }))
192
225
  }
193
226
 
227
+ /**
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)变量名清单
240
+ */
241
+ const formatFormItemAndColumns = ({ pageConfig }) => {
242
+
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
+ }
249
+
250
+ const columns = pageConfig.table?.columns ?? pageConfig?.columns ?? []
251
+
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 }
276
+ }
277
+
278
+ /**
279
+ * 复制模板目录到目标项目
280
+ * @param {Object} options - 命令行选项
281
+ * @param {string} templateSubDir - 模板子目录(如 'hooks'、'components')
282
+ * @param {string} targetSubDir - 目标子目录(如 'src/hooks'、'src/components')
283
+ */
284
+ const copyTemplateDir = (options, templateSubDir, targetSubDir) => {
285
+ const targetDir = path.join(process.cwd(), targetSubDir)
286
+ const sourceDir = path.join(__dirname, `../../templates/${options.template}/${templateSubDir}`)
287
+ if (!fs.existsSync(sourceDir)) return
288
+ fs.mkdirSync(targetDir, { recursive: true })
289
+ fs.readdirSync(sourceDir).forEach(file => {
290
+ const src = path.join(sourceDir, file)
291
+ const dest = path.join(targetDir, file)
292
+ if (!fs.existsSync(dest)) {
293
+ fs.cpSync(src, dest, { recursive: true })
294
+ }
295
+ })
296
+ }
297
+
194
298
  /**
195
299
  * 生成智能导入语句
196
300
  * 根据代码中实际使用的依赖,自动生成对应的 import 语句
@@ -213,33 +317,13 @@ const generateSmartImports = ({ bodyCode, hasTabs, hasFormItems }) => {
213
317
  usedReact.length && `import { ${usedReact.join(', ')} } from 'react'`,
214
318
  `import { request } from '../../utils/request'`,
215
319
  `import { formatQuery } from '../../utils/utils'`,
216
- usedAntd.length && `import { Form, ${usedAntd.join(', ')} } from 'antd'`,
217
320
  ...usedHooks.map(hook => `import { ${hook} } from '../../hooks/${hook}'`),
218
321
  ...usedComps.map(comp => `import { ${comp} } from '../../components/${comp}'`),
322
+ usedAntd.length && `import { ${hasFormItems ? 'Form, ' : ''}${usedAntd.join(', ')} } from 'antd'`,
219
323
  `import { ${hasTabs ? 'tabs, ' : ''}${hasFormItems ? 'formItems, ' : ''}modalItems, tableColumns} from './resource'`,
220
324
  ].sort((a, b) => a.length - b.length)
221
325
 
222
326
  return imports.filter(Boolean).join('\n')
223
327
  }
224
328
 
225
- /**
226
- * 复制模板目录到目标项目
227
- * @param {Object} options - 命令行选项
228
- * @param {string} templateSubDir - 模板子目录(如 'hooks'、'components')
229
- * @param {string} targetSubDir - 目标子目录(如 'src/hooks'、'src/components')
230
- */
231
- const copyTemplateDir = (options, templateSubDir, targetSubDir) => {
232
- const targetDir = path.join(process.cwd(), targetSubDir)
233
- const sourceDir = path.join(__dirname, `../../templates/${options.template}/${templateSubDir}`)
234
- if (!fs.existsSync(sourceDir)) return
235
- fs.mkdirSync(targetDir, { recursive: true })
236
- fs.readdirSync(sourceDir).forEach(file => {
237
- const src = path.join(sourceDir, file)
238
- const dest = path.join(targetDir, file)
239
- if (!fs.existsSync(dest)) {
240
- fs.cpSync(src, dest, { recursive: true })
241
- }
242
- })
243
- }
244
-
245
- module.exports = { language, getConfig, cleanCode, unwrapSignal, matrixEffect, bootSequence, getExistingMenus, copyTemplateDir, generateSmartImports }
329
+ module.exports = { language, getConfig, cleanCode, unwrapSignal, matrixEffect, bootSequence, isQuerySignal, getExistingMenus, copyTemplateDir, generateSmartImports, formatFormItemAndColumns }
@@ -3,7 +3,6 @@ const rowKey = 'id'
3
3
  {{#if hasFormItems}}
4
4
  const [form] = Form.useForm()
5
5
  {{/if}}
6
- const [modal, setModal] = useState({ visible:false, title:'', formItems:modalItems })
7
6
  {{#if hasRowSelection}}
8
7
  const [selectedRows, setSelectedRows] = useState([])
9
8
  {{/if}}
@@ -12,4 +11,5 @@ const [activeKey, setActiveKey] = useState(tabs[0].key)
12
11
  {{/if}}
13
12
  {{#if hasExpandable}}
14
13
  const [expandedRowKeys, setExpandedRowKeys] = useState([])
15
- {{/if}}
14
+ {{/if}}
15
+ const [modal, setModal] = useState({ visible:false, title:'', formItems:modalItems })