@yorha2b-lab/autodev 1.0.11 → 1.0.13

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
@@ -6,9 +6,11 @@ const figlet = require('figlet')
6
6
  const { program } = require('commander')
7
7
 
8
8
  console.log(chalk.cyan(figlet.textSync('AutoDev', { horizontalLayout: 'full' })))
9
- console.log(chalk.gray('--------------------------------------------------'))
9
+ console.log(chalk.gray('Booting System...'))
10
10
  console.log(chalk.white(' [System] ') + chalk.green('YoRHa No.2 Type B Unit: ') + chalk.cyan('Online'))
11
- console.log(chalk.white(' [Mission] ') + chalk.yellow('Generate Frontend CRUD: ') + chalk.cyan('Awaiting Command'))
11
+ console.log(chalk.white(' [System] ') + chalk.green('Scanner Type 9S Unit: ') + chalk.cyan('Standby'))
12
+ console.log(chalk.white(' [Mission] ') + chalk.yellow('Frontend Architecture Construction: ') + chalk.cyan('Awaiting Command'))
13
+ console.log(chalk.white(' [Bunker] ') + chalk.magenta('Glory to mankind. (人类荣光永存)'))
12
14
  console.log(chalk.gray('--------------------------------------------------\n'))
13
15
 
14
16
  program.version('1.0.0').description('AI驱动的前端CRUD代码生成器')
@@ -20,13 +22,13 @@ program
20
22
  .action(() => {
21
23
  const tplConfig = path.resolve(__dirname, '../config.js')
22
24
  const tplEnv = path.resolve(__dirname, '../.env.example')
23
- fs.copyFileSync(tplConfig, path.join(process.cwd(), 'config.js'))
24
25
  fs.copyFileSync(tplEnv, path.join(process.cwd(), '.env'))
26
+ fs.copyFileSync(tplConfig, path.join(process.cwd(), 'config.js'))
27
+ fs.mkdirSync(path.join(process.cwd(), 'swagger'), { recursive: true })
25
28
  fs.mkdirSync(path.join(process.cwd(), 'screenShot'), { recursive: true })
26
29
  fs.mkdirSync(path.join(process.cwd(), 'screenPart'), { recursive: true })
27
- fs.mkdirSync(path.join(process.cwd(), 'swagger'), { recursive: true })
28
- console.log('✅ 已生成 config.js')
29
- console.log('✅ 已生成 .env')
30
+ console.log(chalk.cyan('📡 Operator 6O: 呼叫 2B,环境配置文件已下发至本地终端!'))
31
+ console.log(chalk.green('🤖 Pod 042: [报告] config.js 与 .env 存储完毕。'))
30
32
  })
31
33
 
32
34
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yorha2b-lab/autodev",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "基于视觉大模型的前端(Umi+Antd)全自动 CRUD 代码生成器",
5
5
  "bin": {
6
6
  "autodev": "bin/autodev.js"
@@ -1,5 +1,7 @@
1
1
  const fs = require('fs')
2
+ const ora = require('ora')
2
3
  const path = require('path')
4
+ const chalk = require('chalk')
3
5
  const chokidar = require('chokidar')
4
6
 
5
7
  const watchApi = () => {
@@ -21,7 +23,8 @@ const watchApi = () => {
21
23
  watcher.on('add', async filePath => {
22
24
  const startTime = Date.now()
23
25
  const fileName = path.basename(filePath, path.extname(filePath))
24
- console.log(`🚀 检测到新文件: ${fileName}, 开始识别...`)
26
+ const spinner = ora(chalk.cyan(`🤖 Pod 153: [报告] 侦测到未知的 Swagger 数据源 [${fileName}],开始进行语义对齐...`))
27
+ spinner.start()
25
28
 
26
29
  try {
27
30
  const swaggerStr = fs.readFileSync(filePath, 'utf8')
@@ -35,9 +38,9 @@ const watchApi = () => {
35
38
  fs.writeFileSync(path.join(`./${config.pagesDir}/${fileName}/resource.js`), resourceStr.replace(/"(\w+)":/g, '$1:').replace(/"/g, "'"))
36
39
  const endTime = Date.now()
37
40
  //fs.unlinkSync(filePath)
38
- console.log(`识别完成:耗时 ${(endTime - startTime) / 1000} 秒`)
41
+ spinner.succeed(chalk.green(`🤖 Pod 153: [肯定] 字段对齐协议执行完毕:耗时 ${(endTime - startTime) / 1000} 秒。9S 的骇客任务很顺利。`))
39
42
  } catch (error) {
40
- console.error(`识别 ${fileName} 失败:`, error)
43
+ console.log(chalk.red(`🤖 Pod 153: [警告] 骇入 [${fileName}] 失败。接口数据遭遇强力防火墙:${error}`))
41
44
  }
42
45
  })
43
46
  }
@@ -1,20 +1,30 @@
1
1
  const fs = require('fs')
2
- const ora = require('ora')
2
+ const ora = require('ora') // 命令行加载动画
3
3
  const path = require('path')
4
+ const chalk = require('chalk')
4
5
 
6
+
7
+ /**
8
+ * 监听截图目录,自动生成 CRUD 页面
9
+ * @param {Object} options - 命令行选项
10
+ * @param {string} options.template - 前端框架模板(react/vue/angular)
11
+ */
5
12
  const watchPage = options => {
6
13
 
7
- const chokidar = require('chokidar')
8
- const Handlebars = require('handlebars')
9
- const pagePrompt = require('../prompts/watch-page.js')
10
- const { createTaskQueue } = require('../core/task-queue')
11
- const stringify = require('json-stringify-pretty-compact')
12
- const { recognizePage, generateMock } = require('../services/llm.js')
13
- const { getConfig, copyHooks, copyComponents, getExistingMenus } = require('../utils/utils.js')
14
+ const chokidar = require('chokidar') // 文件监听库
15
+ const Handlebars = require('handlebars') // 模板引擎
16
+ const pagePrompt = require('../prompts/watch-page.js') // 页面识别提示词
17
+ const { createTaskQueue } = require('../core/task-queue') // 任务队列,控制并发
18
+ const stringify = require('json-stringify-pretty-compact') // 格式化 JSON
19
+ const { recognizePage, generateMock } = require('../services/llm.js') // AI 服务
20
+ const { getConfig, copyHooks, copyComponents, getExistingMenus } = require('../utils/utils.js') // 工具函数
14
21
 
15
22
  const config = getConfig()
23
+
24
+ // 根据框架加载对应的编译器(react-compiler.js / vue-compiler.js 等)
16
25
  const compilerPath = path.join(__dirname, `../core/${options.template}-compiler.js`)
17
26
 
27
+ // 检查是否支持该框架
18
28
  if (!fs.existsSync(compilerPath)) {
19
29
  console.error(`\n❌ 糟糕!暂不支持 [${options.template}] 框架的自动生成。`)
20
30
  console.log(`💡 提示:目前仅内置了 react 编译器。`)
@@ -22,14 +32,17 @@ const watchPage = options => {
22
32
  return
23
33
  }
24
34
 
35
+ // 加载编译器的 index 和 resource 函数
25
36
  const { index, resource } = require(compilerPath)
26
37
 
27
38
  try {
39
+ // 首次运行时复制 hooks 和 components 模板到目标项目
28
40
  copyHooks(options)
29
41
  copyComponents(options)
30
42
  } catch (error) {
31
43
  console.error('❌ 程序运行出错:', error)
32
44
  }
45
+
33
46
  /**
34
47
  * 创建任务队列,限制并发数为 2。
35
48
  *
@@ -39,25 +52,36 @@ const watchPage = options => {
39
52
  * 3. 并发设为 2 是实测下来速度与稳定性的最佳平衡点。
40
53
  */
41
54
  const queue = createTaskQueue(2)
55
+
56
+ // 获取已有的菜单配置
42
57
  const menus = getExistingMenus()
58
+
59
+ // 加载模板文件
43
60
  const tplDir = path.join(__dirname, `../../templates/${options.template}`)
44
61
  const indexTpl = Handlebars.compile(fs.readFileSync(path.join(tplDir, 'index.hbs'), 'utf-8'))
45
62
  const resourceTpl = Handlebars.compile(fs.readFileSync(path.join(tplDir, 'resource.hbs'), 'utf-8'))
63
+
64
+ // 所有任务完成后统一更新菜单配置
46
65
  queue.onIdle(() => {
47
- console.log('💾 所有排队的截图已处理完成,正在统一更新 menu 配置...')
66
+ console.log(chalk.green('🤖 Pod 042: [报告] 所有排队的视觉数据已解析完毕,正在向 Bunker 同步 Menu 路由配置...'))
48
67
  const constantDir = path.join(process.cwd(), config.utilsDir)
49
68
  if (!fs.existsSync(constantDir)) fs.mkdirSync(constantDir, { recursive: true })
69
+ // 格式化菜单配置为 JS 代码
50
70
  fs.writeFileSync(path.join(constantDir, 'constant.js'), `export const menus = ${stringify.default(menus, { indent: 4, maxLength: 50 })}`)
51
- console.log('✅ Menu 配置更新成功!')
71
+ console.log(chalk.green('🤖 Pod 042: [肯定] Menu 配置同步成功。'))
52
72
  })
73
+
74
+ // 监听 screenShot 目录
53
75
  const watcher = chokidar.watch('./screenShot', {
54
- persistent: true,
55
- ignored: /(^|[\/\\])\../,
56
- awaitWriteFinish: {
57
- stabilityThreshold: 500,
58
- pollInterval: 100
76
+ persistent: true, // 持续监听
77
+ ignored: /(^|[\/\\])\../, // 忽略隐藏文件
78
+ awaitWriteFinish: { // 等待文件写入完成
79
+ stabilityThreshold: 500, // 稳定性阈值(毫秒)
80
+ pollInterval: 100 // 轮询间隔
59
81
  }
60
82
  })
83
+
84
+ // 文件添加事件
61
85
  watcher.on('add', filePath => {
62
86
  queue.add(async () => {
63
87
  const startTime = Date.now()
@@ -66,27 +90,41 @@ const watchPage = options => {
66
90
  const targetDir = path.join(process.cwd(), config.pagesDir, fileName)
67
91
 
68
92
  try {
93
+ // 创建必要的目录
69
94
  if (!fs.existsSync(mockDir)) fs.mkdirSync(mockDir, { recursive: true })
70
95
  if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true })
71
- const spinner = ora(`🚀 Pod 042: 开始处理排队任务: ${fileName}...`)
96
+
97
+ const spinner = ora(chalk.cyan(`🤖 Pod 042: [报告] 捕获到新的视觉图像 [${fileName}],开始执行构筑程序...`))
72
98
  spinner.start()
99
+
100
+ // 1. 识别页面结构(调用视觉大模型)
73
101
  const pageConfig = await recognizePage(pagePrompt, filePath)
102
+
103
+ // 2. 生成 resource.js(表格列、表单配置、字典等)
74
104
  fs.writeFileSync(path.join(targetDir, 'resource.js'), resource({ pageConfig, resourceTpl }))
105
+
106
+ // 3. 生成 index.js(页面组件)
75
107
  fs.writeFileSync(path.join(targetDir, 'index.js'), index({ fileName, indexTpl, pageConfig }))
108
+
109
+ // 4. 如果是新页面,添加到菜单配置
76
110
  if (!menus.find(m => m.key === fileName)) {
77
111
  menus.push({ label: fileName, key: fileName })
78
112
  }
113
+
114
+ // 5. 生成 Mock 数据(可选)
79
115
  if (config.needMock) {
80
- console.log(`🚀 开始生成 ${fileName} 的 Mock 数据...`)
116
+ console.log(chalk.yellow(`🧑‍💻 9S: 交给我吧 2B!正在骇入并伪造 [${fileName}] 的 Mock 数据...`))
81
117
  const rawContent = await generateMock(pageConfig.table.columns, fileName)
118
+ // 格式化 Mock 数据
82
119
  fs.writeFileSync(path.join(mockDir, `${fileName}.js`), `export default ${stringify.default(rawContent, { indent: 4, maxLength: 200 })}`.replaceAll(`"'`, `"`).replaceAll(`'"`, `"`))
83
- console.log(`生成 ${fileName} 的 Mock 数据耗时: ${(Date.now() - startTime) / 1000} 秒`)
120
+ console.log(chalk.green(`🤖 Pod 042: [报告] Mock 数据注入完成,耗时: ${(Date.now() - startTime) / 1000} 秒`))
84
121
  }
122
+
85
123
  const endTime = Date.now()
86
- //fs.unlinkSync(filePath)
87
- spinner.succeed(`✅ Pod 042: 模块 [${fileName}] 装配完成!耗时 ${(endTime - startTime) / 1000} 秒`)
124
+ //fs.unlinkSync(filePath) // 可选:处理完成后删除截图
125
+ spinner.succeed(chalk.green(`🤖 Pod 042: [报告] 模块 [${fileName}] 物理装配完成!耗时 ${(endTime - startTime) / 1000} 秒`))
88
126
  } catch (error) {
89
- console.error(`❌ 处理失败: ${filePath}`, error)
127
+ console.log(chalk.red(`🤖 Pod 042: [警告] 模块 [${fileName}] 构筑失败。原因:${error}`))
90
128
  }
91
129
  })
92
130
  })
@@ -1,4 +1,6 @@
1
1
  const fs = require('fs')
2
+ const ora = require('ora')
3
+ const chalk = require('chalk')
2
4
  const chokidar = require('chokidar')
3
5
  const stringify = require('json-stringify-pretty-compact')
4
6
 
@@ -18,7 +20,8 @@ const watchPart = () => {
18
20
  watcher.on('add', async filePath => {
19
21
  try {
20
22
  const startTime = Date.now()
21
- console.log(`🚀 检测到新图片: 开始识别...`)
23
+ const spinner = ora(chalk.cyan(`🤖 Pod 042: [报告] 捕获到新的视觉图像,开始执行构筑程序...`))
24
+ spinner.start()
22
25
  const partPrompt = require('../prompts/watch-part.js')
23
26
  const pageConfig = await recognizePage(partPrompt, filePath)
24
27
  const optionDict = pageConfig.optionDict || {}
@@ -37,9 +40,9 @@ const watchPart = () => {
37
40
  })
38
41
  const finalResult = `${mainConfigStr}\n${optionsCodeStr}`
39
42
  const endTime = Date.now()
40
- console.log(`🎉 识别完成!耗时 ${(endTime - startTime) / 1000} 秒\n================\n\n${finalResult}\n\n================`)
43
+ spinner.succeed(`🤖 Pod 042: [报告] 识别程序结束!耗时 ${(endTime - startTime) / 1000} 秒\n================\n\n${finalResult}\n\n================`)
41
44
  } catch (error) {
42
- console.error('识别图片失败', error)
45
+ console.log(chalk.red(`🤖 Pod 042: [警告] 识别程序失败。原因:${error}`))
43
46
  } finally {
44
47
  fs.unlinkSync(filePath)
45
48
  }
@@ -2,53 +2,82 @@ const Handlebars = require('handlebars')
2
2
  const stringify = require('json-stringify-pretty-compact')
3
3
  const { cleanCode, generateSmartImports } = require('../utils/utils.js')
4
4
 
5
+ // 注册 Handlebars 辅助函数,用于将对象格式化为 JSON 字符串
5
6
  Handlebars.registerHelper('stringify', (context, maxLength = 200) => context ? new Handlebars.SafeString(stringify.default(context, { indent: 4, maxLength })) : '[]')
6
7
 
8
+ /**
9
+ * 生成 resource.js 文件(表格列配置、表单配置、字典等)
10
+ * @param {Object} param0 - 函数参数
11
+ * @param {Object} param0.pageConfig - 页面配置对象(包含 tabs、formItems、table 等)
12
+ * @param {Object} param0.resourceTpl - Handlebars 模板函数
13
+ * @returns {string} 生成的 resource.js 代码
14
+ */
7
15
  const resource = ({ pageConfig, resourceTpl }) => {
16
+ // 判断是否存在标签页配置
8
17
  const hasTabs = pageConfig.tabs?.length > 0
9
18
 
19
+ // 构建模板数据
10
20
  const viewData = {
11
- hasTabs,
12
- tabs: pageConfig.tabs,
13
- formItems: pageConfig.formItems,
21
+ hasTabs, // 是否有标签页
22
+ tabs: pageConfig.tabs, // 标签页配置
23
+ formItems: pageConfig.formItems, // 表单项配置
24
+ // 如果有标签页,将 formItems 按 tab 分组;否则直接使用数组
14
25
  formItemsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, pageConfig.formItems])) : pageConfig.formItems,
26
+ // 如果有标签页,将 columns 按 tab 分组;否则直接使用数组
15
27
  columnsData: hasTabs ? Object.fromEntries(pageConfig.tabs.map(tab => [tab.key, pageConfig.table.columns])) : pageConfig.table.columns,
28
+ // 提取所有下拉框字典配置
16
29
  dictBlocks: pageConfig.formItems
17
- ?.filter(item => item.type === 'select')
18
- ?.map(item => ({ name: item.options.replace('_CODE_', ''), data: pageConfig.optionDict[item.options] ?? [] }))
30
+ ?.filter(item => item.type === 'select') // 过滤出下拉框类型
31
+ ?.map(item => ({ name: item.options.replace('_CODE_', ''), data: pageConfig.optionDict[item.options] ?? [] })) // 转换为字典格式
19
32
  }
20
33
 
34
+ // 渲染模板并清理代码
21
35
  const rawCode = resourceTpl(viewData)
22
36
  return cleanCode(rawCode)
23
37
  }
24
38
 
39
+ /**
40
+ * 生成 index.js 文件(页面组件)
41
+ * @param {Object} param0 - 函数参数
42
+ * @param {string} param0.fileName - 页面文件名
43
+ * @param {Object} param0.indexTpl - Handlebars 模板函数
44
+ * @param {Object} param0.pageConfig - 页面配置对象
45
+ * @returns {string} 生成的 index.js 代码
46
+ */
25
47
  const index = ({ fileName, indexTpl, pageConfig }) => {
26
48
  const hasTabs = pageConfig.tabs?.length > 0
27
49
  const hasOperate = pageConfig.table.operation?.length > 0
28
50
 
51
+ // 动态确定 columns 引用方式:有标签页时使用 activeKey 索引,否则直接使用 columns
29
52
  let columnsValue = hasTabs ? 'columns[activeKey]' : 'columns'
53
+ // 如果存在操作列,拼接到 columns 后面
30
54
  if (hasOperate) {
31
55
  columnsValue = `${columnsValue}.concat(operate)`
32
56
  }
33
57
 
58
+ // 构建模板数据
34
59
  const viewData = {
35
- hasTabs,
36
- fileName,
37
- hasOperate,
38
- columnsValue,
39
- tabs: pageConfig.tabs,
40
- hasExpandable: pageConfig.table.expandable,
41
- hasPagination: pageConfig.table.pagination,
42
- operations: pageConfig.table.operation || [],
43
- hasRowSelection: pageConfig.table.rowSelection,
44
- hasStaticInfo: pageConfig.table.staticInfo?.has,
45
- staticInfoText: pageConfig.table.staticInfo?.text,
46
- functionButtons: pageConfig.functionButton?.filter(item => !['查询', '重置'].includes(item.btn)) || []
60
+ hasTabs, // 是否有标签页
61
+ fileName, // 页面文件名
62
+ hasOperate, // 是否有操作列
63
+ columnsValue, // columns 引用表达式
64
+ tabs: pageConfig.tabs, // 标签页配置
65
+ hasExpandable: pageConfig.table.expandable, // 是否有展开行
66
+ hasPagination: pageConfig.table.pagination, // 是否有分页
67
+ operations: pageConfig.table.operation || [], // 操作列配置
68
+ hasRowSelection: pageConfig.table.rowSelection, // 是否有行选择
69
+ hasStaticInfo: pageConfig.table.staticInfo?.has, // 是否有静态信息
70
+ staticInfoText: pageConfig.table.staticInfo?.text, // 静态信息文本
71
+ functionButtons: pageConfig.functionButton?.filter(item => !['查询', '重置'].includes(item.btn)) || [] // 功能按钮(排除查询和重置)
47
72
  }
48
73
 
74
+ // 渲染模板
49
75
  const bodyCode = indexTpl(viewData)
76
+ // 生成智能导入语句
50
77
  const importsStr = generateSmartImports(bodyCode, hasTabs)
78
+ // 清理代码并返回
51
79
  return cleanCode(`${importsStr}\n\n${bodyCode}`)
52
80
  }
53
81
 
82
+ // 导出 React 编译器的两个核心函数
54
83
  module.exports = { index, resource }
@@ -29,7 +29,7 @@ module.exports = `
29
29
  如果是下拉框列(所对应的查询项明确是下拉框),请加入render:_CODE_text=>对应的options.find(item=>item.value===text)?.label_CODE_
30
30
  如果列需要过滤请加上filters:[],onFilter: _CODE_(value, record) => record[对应的英文名词].includes(value)_CODE_
31
31
  5.功能按钮
32
- functionButtion:[{btn:'btn',action:'操作动词+模块英文名词'}]
32
+ functionButton:[{btn:'btn',action:'操作动词+模块英文名词'}]
33
33
  如果和表格行操作重复请在action加上BySelected前缀
34
34
  如果是导出功能,对应的动词为export+对应的英文名词,没有名词则为exportData
35
35
  6.下拉选项字典
@@ -1,12 +1,17 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const JSON5 = require('json5')
4
- const sharp = require('sharp')
4
+ const sharp = require('sharp') // 图片压缩库,用于处理截图
5
+ const chalk = require('chalk')
5
6
  const OpenAI = require('openai')
6
7
  const { getConfig } = require('../utils/utils.js')
7
8
 
8
9
  let client = null
9
10
 
11
+ /**
12
+ * 获取 OpenAI 客户端实例(单例模式)
13
+ * 优先使用项目根目录的 .env 文件,其次使用包内的 .env
14
+ */
10
15
  const getOpenAI = () => {
11
16
  if (!client) {
12
17
  const localEnv = path.resolve(process.cwd(), '.env')
@@ -19,24 +24,49 @@ const getOpenAI = () => {
19
24
  return client
20
25
  }
21
26
 
27
+ /**
28
+ * 调用 AI 模型并解析响应
29
+ * @param {string} model - 模型名称(如 qwen3.5-plus, qwen-turbo)
30
+ * @param {Array} messages - 对话消息数组
31
+ * @param {number} retryCount - 重试次数
32
+ * @returns {Object} 解析后的 JSON 对象
33
+ */
22
34
  const askAI = async (model, messages, retryCount = 0) => {
23
-
24
- if (retryCount > 3) throw new Error('AI 重试次数耗尽,请检查网络或图片')
35
+ // 最多重试 3 次,避免无限循环
36
+ if (retryCount > 3) throw new Error('YoRHa 司令部连接中断:请检查网络状态或黑盒共鸣情况。')
25
37
 
26
38
  try {
27
39
  const openai = getOpenAI()
28
- const response = await openai.chat.completions.create({ model, messages, response_format: { type: 'json_object' } })
40
+ // 调用 AI 模型,强制返回 JSON 格式
41
+ const response = await openai.chat.completions.create({
42
+ model,
43
+ messages,
44
+ response_format: { type: 'json_object' }
45
+ })
46
+
29
47
  let raw = response.choices[0].message.content.trim()
48
+
49
+ // 清理可能的 Markdown 标记(```json ... ```)
30
50
  raw = raw.replace(/^```json\n?/, '').replace(/\n?```$/, '')
51
+
52
+ // 提取 JSON 内容(支持对象和数组)
31
53
  const match = raw.match(/[\{\[][\s\S]*[\}\]]/)
54
+
55
+ // 使用 JSON5 解析(支持单引号、注释等非标准 JSON)
32
56
  return JSON5.parse(match ? match[0] : raw)
33
57
  } catch (err) {
34
- console.warn(`[解析失败,第 ${retryCount + 1} 次重试...]`, err.message)
58
+ console.warn(chalk.yellow(`[系统警告] 神经云网络连接不稳,正在尝试重新链接... (第 ${retryCount + 1} 次重试)`), err.message)
35
59
  return askAI(model, messages, retryCount + 1)
36
60
  }
37
61
  }
38
62
 
39
63
  module.exports = {
64
+ /**
65
+ * 生成 Mock 数据
66
+ * @param {Array} columns - 表格列配置
67
+ * @param {string} fileName - 文件名
68
+ * @returns {string} 生成的 Mock 数据字符串
69
+ */
40
70
  generateMock: async (columns, fileName) => {
41
71
  const config = getConfig()
42
72
  const mockPrompt = require('../prompts/mock.js')
@@ -49,10 +79,18 @@ module.exports = {
49
79
  ]
50
80
  )
51
81
  },
82
+
83
+ /**
84
+ * 识别页面截图并提取结构信息
85
+ * @param {string} prompt - 提示词
86
+ * @param {string} filePath - 截图文件路径
87
+ * @returns {Object} 识别后的页面配置对象
88
+ */
52
89
  recognizePage: async (prompt, filePath) => {
53
90
  const config = getConfig()
54
91
  const { UI_DESIGNER } = require('../prompts/system.js')
55
92
 
93
+ // 压缩图片到 1280px 宽度(避免超出 token 限制),质量 80%
56
94
  const compressedBuffer = await sharp(filePath)
57
95
  .resize(1280, null, { withoutEnlargement: true })
58
96
  .jpeg({ quality: 80 })
@@ -73,6 +111,13 @@ module.exports = {
73
111
  ]
74
112
  )
75
113
  },
114
+
115
+ /**
116
+ * 对齐 Swagger 接口字段与前端字段
117
+ * @param {string} swaggerStr - 真实接口响应数据
118
+ * @param {string} resourceStr - 前端 resource.js 内容
119
+ * @returns {Object} 字段映射对象 { oldName: newName }
120
+ */
76
121
  alignSwaggerFields: async (swaggerStr, resourceStr) => {
77
122
  const config = getConfig()
78
123
  const apiPrompt = require('../prompts/watch-api.js')
@@ -1,21 +1,30 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
 
4
+ /**
5
+ * 获取项目配置信息
6
+ * 优先读取项目根目录的 config.js,如果不存在则使用默认配置
7
+ * @returns {Object} 合并后的配置对象
8
+ */
4
9
  const getConfig = () => {
5
- const localConfigPath = path.join(process.cwd(), 'config.js')
6
- const defaultConfigPath = path.resolve(__dirname, '../../config.js')
10
+ const localConfigPath = path.join(process.cwd(), 'config.js') // 项目根目录配置
11
+ const defaultConfigPath = path.resolve(__dirname, '../../config.js') // 默认配置
7
12
  let config = require(defaultConfigPath)
8
13
  if (fs.existsSync(localConfigPath)) {
9
14
  const userConfig = require(localConfigPath)
10
- config = { ...config, ...userConfig }
15
+ config = { ...config, ...userConfig } // 合并用户自定义配置
11
16
  }
12
17
  return config
13
18
  }
14
19
 
15
20
  /**
21
+ * 清理大模型生成的代码
16
22
  * 大模型输出的标准 JSON 无法携带 render: (text) => <Tag> 等箭头函数。
17
23
  * 我们在 Prompt 中要求大模型用 _CODE_ 占位符包裹函数字符串,
18
24
  * 在此处用正则剥离外层的双引号和占位符,将其还原为真正的可执行 JS 代码。
25
+ *
26
+ * @param {string} str - 大模型返回的 JSON 字符串
27
+ * @returns {string} 清理后的可执行 JS 代码
19
28
  */
20
29
  const cleanCode = str => {
21
30
  return str
@@ -29,6 +38,11 @@ const cleanCode = str => {
29
38
  .trim() + '\n'
30
39
  }
31
40
 
41
+ /**
42
+ * 获取已有的页面菜单配置
43
+ * @param {string} dir - 页面目录路径(默认 'src/pages')
44
+ * @returns {Array} 菜单数组 [{ label: '页面名', key: '页面名' }]
45
+ */
32
46
  const getExistingMenus = (dir = 'src/pages') => {
33
47
  const pagesDir = path.join(process.cwd(), dir)
34
48
  if (!fs.existsSync(pagesDir)) return []
@@ -37,17 +51,27 @@ const getExistingMenus = (dir = 'src/pages') => {
37
51
  .map(file => ({ label: file, key: file }))
38
52
  }
39
53
 
54
+ /**
55
+ * 生成智能导入语句
56
+ * 根据代码中实际使用的依赖,自动生成对应的 import 语句
57
+ *
58
+ * @param {string} codeStr - 生成的代码字符串
59
+ * @param {boolean} hasTabs - 是否包含标签页
60
+ * @returns {string} 拼接后的 import 语句
61
+ */
40
62
  const generateSmartImports = (codeStr, hasTabs) => {
41
- const hooksLib = ['useTableQuery']
42
- const reactLib = ['useState', 'useEffect', 'useRef', 'useMemo']
43
- const componentsLib = ['MyTable', 'MyModalForm', 'MySearchForm']
44
- const antdLib = ['Card', 'Space', 'Modal', 'Button', 'Alert', 'Table', 'Input', 'Select']
63
+ const hooksLib = ['useTableQuery'] // 自定义 hooks 库
64
+ const reactLib = ['useState', 'useEffect', 'useRef', 'useMemo'] // React 核心库
65
+ const componentsLib = ['MyTable', 'MyModalForm', 'MySearchForm'] // 自定义组件库
66
+ const antdLib = ['Card', 'Space', 'Modal', 'Button', 'Alert', 'Table', 'Input', 'Select'] // Ant Design 组件库
45
67
 
68
+ // 检测代码中实际使用的组件
46
69
  const usedAntd = antdLib.filter(name => new RegExp(`\\b${name}\\b`).test(codeStr))
47
70
  const usedHooks = hooksLib.filter(name => new RegExp(`\\b${name}\\b`).test(codeStr))
48
71
  const usedReact = reactLib.filter(name => new RegExp(`\\b${name}\\b`).test(codeStr))
49
72
  const usedComps = componentsLib.filter(name => new RegExp(`\\b${name}\\b`).test(codeStr))
50
73
 
74
+ // 构建 import 语句数组
51
75
  const imports = [
52
76
  usedReact.length && `import { ${usedReact.join(', ')} } from 'react'`,
53
77
  `import { Link, history } from 'umi'`,
@@ -56,23 +80,34 @@ const generateSmartImports = (codeStr, hasTabs) => {
56
80
  ...usedHooks.map(hook => `import { ${hook} } from '../../hooks/${hook}'`),
57
81
  ...usedComps.map(comp => `import { ${comp} } from '../../components/${comp}'`),
58
82
  usedAntd.length && `import { Form,${usedAntd.join(', ')} } from 'antd'`
59
- ].sort((a, b) => a.length - b.length)
83
+ ].sort((a, b) => a.length - b.length) // 按长度排序(短的在前)
60
84
 
61
- return imports.filter(Boolean).join('\n')
85
+ return imports.filter(Boolean).join('\n') // 过滤空值并拼接
62
86
  }
63
87
 
88
+ /**
89
+ * 复制 hooks 模板到目标项目
90
+ * @param {Object} options - 命令行选项
91
+ * @param {string} hooksDir - hooks 目录路径(默认 'src/hooks')
92
+ */
64
93
  const copyHooks = (options, hooksDir = 'src/hooks') => {
65
94
  const targetDir = path.join(process.cwd(), hooksDir)
66
- if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true })
95
+ if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true }) // 递归创建目录
67
96
  const hookDir = path.join(__dirname, `../../templates/${options.template}/hooks`)
68
97
  fs.readdirSync(hookDir).forEach(file => fs.copyFileSync(path.join(hookDir, file), path.join(targetDir, file)))
69
98
  }
70
99
 
100
+ /**
101
+ * 复制 components 模板到目标项目
102
+ * @param {Object} options - 命令行选项
103
+ * @param {string} componentsDir - components 目录路径(默认 'src/components')
104
+ */
71
105
  const copyComponents = (options, componentsDir = 'src/components') => {
72
106
  const targetDir = path.join(process.cwd(), componentsDir)
73
- if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true })
107
+ if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true }) // 递归创建目录
74
108
  const componentsDirectory = path.join(__dirname, `../../templates/${options.template}/components`)
75
109
  fs.readdirSync(componentsDirectory).forEach(file => fs.copyFileSync(path.join(componentsDirectory, file), path.join(targetDir, file)))
76
110
  }
77
111
 
112
+ // 导出工具函数
78
113
  module.exports = { getConfig, copyHooks, cleanCode, copyComponents, getExistingMenus, generateSmartImports }
@@ -23,7 +23,9 @@ export const EditableCell = ({ title, record, editable, children, dataIndex, han
23
23
 
24
24
  useEffect(() => {
25
25
  if (editing) {
26
- inputRef.current?.focus()
26
+ if (inputRef.current && typeof inputRef.current.focus === 'function') {
27
+ inputRef.current.focus()
28
+ }
27
29
  }
28
30
  }, [editing])
29
31
 
@@ -52,7 +54,16 @@ export const EditableCell = ({ title, record, editable, children, dataIndex, han
52
54
  inputNode = <Select ref={inputRef} options={options} onBlur={save} />
53
55
  break;
54
56
  case 'checkbox':
55
- inputNode = <Checkbox.Group options={options} onChange={save} />
57
+ inputNode = (
58
+ <div tabIndex={-1} onBlur={e => {
59
+ // 确保失去焦点时,新的焦点不在当前 checkbox 组内部
60
+ if (!e.currentTarget.contains(e.relatedTarget)) {
61
+ save()
62
+ }
63
+ }}>
64
+ <Checkbox.Group ref={inputRef} options={options} />
65
+ </div>
66
+ )
56
67
  break;
57
68
  default:
58
69
  inputNode = <Input ref={inputRef} onPressEnter={save} onBlur={save} />
@@ -1,16 +1,17 @@
1
- import { Form } from 'antd'
2
1
  import { formNode } from './index'
2
+ import { Form, Space, Button } from 'antd'
3
+ import { PlusOutlined, PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons'
3
4
 
4
5
  export const MyBaseForm = ({ item, form }) => {
5
6
 
6
7
  const renderFormContent = ({ item }) => {
7
8
 
8
9
  if (!item.name) {
9
- return typeof item.render === 'function' ? item.render(item) : (item.value ?? '-')
10
+ return item.value
10
11
  }
11
12
 
12
13
  const inputNode = (
13
- <Form.Item noStyle name={item.name} rules={item.rules} initialValue={item.value}>
14
+ <Form.Item noStyle name={item.name} rules={item.rules} initialValue={item.value} valuePropName={item.valuePropName}>
14
15
  {formNode({ item })}
15
16
  </Form.Item>
16
17
  )
@@ -28,20 +29,31 @@ export const MyBaseForm = ({ item, form }) => {
28
29
  }
29
30
 
30
31
  return (
31
- <Form.Item
32
- name={item.name}
33
- label={item.label}
34
- rules={item.rules}
35
- extra={item.extra}
36
- tooltip={item.tooltip}
37
- labelCol={item.labelCol}
38
- initialValue={item.value}
39
- wrapperCol={item.wrapperCol}
40
- valuePropName={item.valuePropName}
41
- required={item.required ?? !!item.rules}
42
- style={{ marginBottom: !item.name ? 0 : undefined, ...item.style }}
43
- >
44
- {item.render ? (typeof item.render === 'function' ? item.render(item, form) : item.render) : renderFormContent({ item })}
45
- </Form.Item>
32
+ item.isList ?
33
+ <Form.List name={item.name} initialValue={item.value}>
34
+ {(fields, { add, remove }) => (
35
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
36
+ {fields.map((field, index) => (
37
+ <Space key={field.key} style={{ display: 'flex', marginBottom: 8 }} align='baseline'>
38
+ {item.render(field, index, { add, remove }, form)}
39
+ {index !== 0 && <MinusCircleOutlined onClick={() => remove(field.name)} />}
40
+ {fields.length === index + 1 && <PlusCircleOutlined onClick={() => add()} />}
41
+ </Space>
42
+ ))}
43
+ {fields.length === 0 && <Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>添加{item.label || '项'}</Button>}
44
+ </div>
45
+ )}
46
+ </Form.List> :
47
+ <Form.Item
48
+ label={item.label}
49
+ extra={item.extra}
50
+ tooltip={item.tooltip}
51
+ labelCol={item.labelCol}
52
+ wrapperCol={item.wrapperCol}
53
+ required={item.required ?? !!item.rules}
54
+ style={{ marginBottom: !item.name ? 0 : undefined, ...item.style }}
55
+ >
56
+ {item.render ? (typeof item.render === 'function' ? item.render(item, form) : item.render) : renderFormContent({ item })}
57
+ </Form.Item>
46
58
  )
47
59
  }
@@ -1,9 +1,9 @@
1
- import { formNode } from './index'
1
+ import dayjs from 'dayjs'
2
+ import { MyBaseForm } from './MyBaseForm'
2
3
  import { useState, useEffect } from 'react'
3
4
  import { Row, Col, Form, Modal, } from 'antd'
4
- import { MyBaseForm } from './MyBaseForm'
5
5
 
6
- export const MyModalForm = ({ width, title, submit, record, visible, setModal, labelCol, formItems, wrapperCol, onValuesChange }) => {
6
+ export const MyModalForm = ({ width, title, submit, record, visible, setModal, labelCol, formItems, wrapperCol, dateSeparator = ',', onValuesChange }) => {
7
7
 
8
8
  const [form] = Form.useForm()
9
9
  const [pending, setPending] = useState(false)
@@ -12,7 +12,11 @@ export const MyModalForm = ({ width, title, submit, record, visible, setModal, l
12
12
  if (visible) {
13
13
  form.resetFields()
14
14
  if (record && Object.keys(record).length > 0) {
15
- form.setFieldsValue(record)
15
+ const entries = Object.entries(record).map(([key, value]) => [
16
+ key,
17
+ formItems.find(item => item.name === key)?.type?.includes('date') ? value?.split(dateSeparator)?.map(item => dayjs(item)) : value
18
+ ])
19
+ form.setFieldsValue(Object.fromEntries(entries))
16
20
  }
17
21
  }
18
22
  }, [visible, record, form])
@@ -27,6 +31,8 @@ export const MyModalForm = ({ width, title, submit, record, visible, setModal, l
27
31
  }
28
32
  } catch (error) {
29
33
  console.log('表单校验失败:', error)
34
+ } finally {
35
+ setPending(false)
30
36
  }
31
37
  }
32
38
 
@@ -4,9 +4,9 @@ import { MySearchForm } from './MySearchForm'
4
4
  import { useTableQuery } from '../hooks/useTableQuery'
5
5
 
6
6
 
7
- export const MyModalTable = ({ onOk, title, width, footer, visible, request, columns, setModal, formItems, rowSelection, extraParams = {} }) => {
7
+ export const MyModalTable = ({ api, onOk, title, width, footer, visible, columns, setModal, formItems, rowSelection, formatResponse }) => {
8
8
 
9
- const { total, loading, dataSource, search, setSearch } = useTableQuery(async params => await request({ ...params, ...extraParams }), {})
9
+ const { total, loading, dataSource, search, setSearch } = useTableQuery(api, formatResponse, {})
10
10
 
11
11
  const handleSearch = values => setSearch({ ...search, ...values, pageNo: 1 })
12
12
 
@@ -14,7 +14,7 @@ export const MyModalTable = ({ onOk, title, width, footer, visible, request, col
14
14
 
15
15
  const handleOk = () => {
16
16
  if (onOk) {
17
- onOk({ selectedRows: rowSelection?.selectedRows, dataSource })
17
+ onOk()
18
18
  }
19
19
  setModal({ visible: false })
20
20
  }
@@ -29,12 +29,7 @@ export const MyModalTable = ({ onOk, title, width, footer, visible, request, col
29
29
  dataSource={dataSource}
30
30
  rowSelection={rowSelection}
31
31
  onChange={handleTableChange}
32
- pagination={{
33
- total: total,
34
- showSizeChanger: true,
35
- current: search.pageNo,
36
- pageSize: search.pageSize,
37
- }} />
32
+ pagination={{ total: total, showSizeChanger: true, current: search.pageNo, pageSize: search.pageSize }} />
38
33
  </Modal>
39
34
  )
40
35
  }
@@ -1,12 +1,15 @@
1
- import { useState } from 'react'
1
+ import dayjs from 'dayjs'
2
+ import { history } from 'umi'
2
3
  import { MyBaseForm } from './MyBaseForm'
4
+ import { useState, useEffect } from 'react'
3
5
  import { Row, Col, Form, Button, Space } from 'antd'
4
6
 
5
- export const MySearchForm = ({ form, search, labelCol, setSearch, formItems, showLimit = 7, customReset, extraOperate, customFinish, onValuesChange, defaultPageSize = 10 }) => {
7
+ export const MySearchForm = ({ form, search, loading, labelCol, setSearch, formItems, showLimit = 7, dateSeparator = '\GMT\,', initialValues, customReset, extraOperate, customFinish, onValuesChange, defaultPageSize = 10 }) => {
6
8
 
7
9
  const [limit, setLimit] = useState(showLimit)
8
10
 
9
11
  const handleReset = () => {
12
+ history.push({ search: '' })
10
13
  if (customReset) {
11
14
  customReset()
12
15
  } else {
@@ -15,6 +18,8 @@ export const MySearchForm = ({ form, search, labelCol, setSearch, formItems, sho
15
18
  }
16
19
 
17
20
  const handleFinish = values => {
21
+ const params = new URLSearchParams(Object.fromEntries(Object.entries(values).filter(([key, value]) => !['', null, undefined].includes(value))))
22
+ history.push({ search: params.toString() })
18
23
  if (customFinish) {
19
24
  customFinish(values)
20
25
  } else {
@@ -22,8 +27,18 @@ export const MySearchForm = ({ form, search, labelCol, setSearch, formItems, sho
22
27
  }
23
28
  }
24
29
 
30
+ useEffect(() => {
31
+ if (Object.values(initialValues).length > 0) {
32
+ const entries = Object.entries(initialValues).map(([key, value]) => [
33
+ key,
34
+ formItems.find(item => item.name === key)?.type?.includes('date') ? value?.split(dateSeparator)?.map(item => dayjs(item)) : value
35
+ ])
36
+ form.setFieldsValue(Object.fromEntries(entries))
37
+ }
38
+ }, [])
39
+
25
40
  return (
26
- <Form onFinish={handleFinish} labelCol={labelCol} onValuesChange={onValuesChange}>
41
+ <Form form={form} onFinish={handleFinish} labelCol={labelCol} onValuesChange={onValuesChange}>
27
42
  <Row gutter={24}>
28
43
  {formItems.slice(0, limit)?.map((item, ind) => (
29
44
  <Col key={ind} span={item.span ?? 6}>
@@ -33,10 +48,10 @@ export const MySearchForm = ({ form, search, labelCol, setSearch, formItems, sho
33
48
  <Col flex='auto' style={{ textAlign: 'right' }}>
34
49
  <Form.Item>
35
50
  <Space>
36
- <Button type='primary' htmlType='submit'>查询</Button>
37
- <Button onClick={handleReset} htmlType='reset'>重置</Button>
51
+ <Button type='primary' loading={loading} htmlType='submit'>查询</Button>
52
+ <Button onClick={handleReset} loading={loading} htmlType='reset'>重置</Button>
38
53
  {extraOperate}
39
- {formItems.length > showLimit && (<a onClick={() => setLimit(formItems.length > limit ? formItems.length : 7)}>{formItems.length > limit ? '展开' : '收起'}</a>)}
54
+ {formItems.length > showLimit && (<a onClick={() => setLimit(formItems.length > limit ? formItems.length : showLimit)}>{formItems.length > limit ? '展开' : '收起'}</a>)}
40
55
  </Space>
41
56
  </Form.Item>
42
57
  </Col>
@@ -1,4 +1,5 @@
1
1
  import { Table } from 'antd'
2
+ import { useEffect } from 'react'
2
3
  import { EditableRow, EditableCell } from './EditableCell'
3
4
 
4
5
  const components = {
@@ -8,10 +9,10 @@ const components = {
8
9
  }
9
10
  }
10
11
 
11
- export const MyTable = ({ size, onSave, onChange, pagination, rowClassName, columns = [], rowSelection, rowKey = 'id', loading = false, dataSource = [], editable = false, scroll = { x: 'max-content' }, ...restProps }) => {
12
+ export const MyTable = ({ size, query, onSave, autoScroll, onChange, pagination, rowClassName, columns = [], rowSelection, rowKey = 'id', loading = false, dataSource = [], editable = false, scroll = { x: 'max-content' }, ...restProps }) => {
12
13
 
13
14
  const mergedColumns = columns.map(col => {
14
- if (!col.editable) {
15
+ if (!col.editType) {
15
16
  return col
16
17
  }
17
18
  return {
@@ -29,6 +30,18 @@ export const MyTable = ({ size, onSave, onChange, pagination, rowClassName, colu
29
30
  }
30
31
  })
31
32
 
33
+ useEffect(() => {
34
+ /* if (query && autoScroll) {
35
+ //如果需要可以在这里通过query来处理滚动到目标行的操作
36
+ const trList = document.getElementsByClassName('ant-table-row')
37
+ const index = 0 //根据query来计算索引
38
+ const tr = trList[index]
39
+ if (tr) {
40
+ tr.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' })
41
+ }
42
+ } */
43
+ }, [])
44
+
32
45
  return (
33
46
  <Table
34
47
  {...restProps}
@@ -1,11 +1,7 @@
1
- import { Radio, Input, Upload, Select, Cascader, Checkbox, DatePicker, InputNumber, AutoComplete } from 'antd'
1
+ import { Tree, Radio, Input, Upload, Select, Cascader, Checkbox, DatePicker, InputNumber, TreeSelect, AutoComplete } from 'antd'
2
2
 
3
3
  export const formNode = ({ item }) => {
4
4
 
5
- if (typeof item.render === 'function') {
6
- return item.render(item)
7
- }
8
-
9
5
  const commonProps = {
10
6
  disabled: item.readOnly,
11
7
  style: { width: item.width ?? '100%' },
@@ -22,10 +18,14 @@ export const formNode = ({ item }) => {
22
18
  return <DatePicker.RangePicker {...commonProps} />
23
19
  case 'radio':
24
20
  return <Radio.Group options={item.options} {...commonProps} />
21
+ case 'treeSelect':
22
+ return <TreeSelect treeData={item.options} {...commonProps} />
25
23
  case 'auto':
26
24
  return <AutoComplete options={item.options} {...commonProps} />
27
25
  case 'checkbox':
28
26
  return <Checkbox.Group options={item.options} {...commonProps} />
27
+ case 'tree':
28
+ return <Tree checkable treeData={item.options} {...commonProps} />
29
29
  case 'textarea':
30
30
  return <Input.TextArea autoSize={{ minRows: 4 }} {...commonProps} />
31
31
  case 'cascader':
@@ -1,5 +1,7 @@
1
1
  export default props => {
2
2
 
3
+ const query = Object.fromEntries(new URLSearchParams(location.search).entries())
4
+
3
5
  const [form] = Form.useForm()
4
6
  const [modal, setModal] = useState({})
5
7
  {{#if hasTabs}}
@@ -17,7 +19,7 @@ export default props => {
17
19
  const { total, loading, dataSource, search, setSearch, refresh } = useTableQuery(
18
20
  async params => await request('/api/{{fileName}}', { method: 'POST', body: params }),
19
21
  response => response?.data ?? [],
20
- initParams,
22
+ {...initParams, ...query},
21
23
  )
22
24
 
23
25
  {{#each operations}}
@@ -85,7 +87,7 @@ export default props => {
85
87
  return (
86
88
  <Card {{#if hasTabs}}tabList={tabs} activeTabKey={activeKey} onTabChange={onTabChange}{{/if}}>
87
89
  {modal.visible && <MyModalForm {...modal} formItems={modalItems} submit={modalSubmit} setModal={setModal} />}
88
- <MySearchForm form={form} search={search} setSearch={setSearch} formItems={ {{#if hasTabs}}formItems[activeKey]{{else}}formItems{{/if}} } />
90
+ <MySearchForm form={form} search={search} initialValues={query} loading={loading} setSearch={setSearch} formItems={ {{#if hasTabs}}formItems[activeKey]{{else}}formItems{{/if}} } />
89
91
  {{#if hasTabs}}
90
92
  {{#each tabs}}
91
93
  {activeKey === '{{this.key}}' &&
@@ -107,6 +109,7 @@ export default props => {
107
109
  {{/if}}
108
110
  <MyTable
109
111
  rowKey='id'
112
+ query={query}
110
113
  loading={loading}
111
114
  dataSource={dataSource}
112
115
  columns={ {{columnsValue}} }