@yorha2b-lab/autodev 3.4.0 → 3.4.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
@@ -52,8 +52,11 @@ program
52
52
  .alias('start')
53
53
  .description(language('开启全频道联动监控:支持 Page/Part/API 协同构筑', 'Start full-channel linked monitoring: Coordinated Page/Part/API construction'))
54
54
  .action(() => {
55
+ const result = bunker.init(program.opts())
56
+ if (!result) {
57
+ return
58
+ }
55
59
  bootSequence(pkg.version)
56
- bunker.init(program.opts())
57
60
  require('../src/commands/watch.js')()
58
61
  const { config } = bunker.get()
59
62
  if (config.proxyTarget && !config.needMock && config.enableAutoAlignment) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yorha2b-lab/autodev",
3
- "version": "3.4.0",
3
+ "version": "3.4.1",
4
4
  "description": "基于视觉大模型的前端(react+Antd)全自动 CRUD 代码生成器",
5
5
  "bin": {
6
6
  "autodev": "bin/autodev.js"
@@ -10,13 +10,7 @@ const watch = () => {
10
10
  const { copyTemplateDir } = require('../utils/utils.js')
11
11
  const { createTaskQueue } = require('../core/task-queue.js')
12
12
 
13
- const { menus, config, options, template, language, apiHandler, pageHandler, partHandler } = get()
14
-
15
- const compilerPath = path.join(__dirname, `../core/${template}-compiler.js`)
16
- if (!fs.existsSync(compilerPath)) {
17
- console.error(chalk.red(language(`❌ 暂不支持 [${template}] 框架。`, `❌ [${template}] framework not supported.`)))
18
- return
19
- }
13
+ const { menus, config, options, language, apiHandler, pageHandler, partHandler } = get()
20
14
 
21
15
  try {
22
16
  if (config.hbsDir === '') {
@@ -46,6 +40,7 @@ const watch = () => {
46
40
 
47
41
  const watcher = chokidar.watch(['./screenShot', './screenPart', './response'], {
48
42
  persistent: true,
43
+ ignoreInitial: true,
49
44
  ignored: /(^|[\/\\])\../,
50
45
  awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
51
46
  })
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
+ const chalk = require('chalk')
3
4
  const Handlebars = require('handlebars')
4
5
  const stringify = require('json-stringify-pretty-compact')
5
6
 
@@ -26,10 +27,16 @@ module.exports = {
26
27
  const tplDir = config.hbsDir !== '' ? path.join(process.cwd(), config.hbsDir) : path.join(__dirname, `../../templates/${template}`)
27
28
 
28
29
  const compilerPath = path.join(__dirname, `./${template}-compiler.js`)
30
+ if (!fs.existsSync(compilerPath)) {
31
+ console.error(chalk.red(language(`❌ 暂不支持 [${template}] 框架。欢迎提交 Pull Request 贡献。`, `❌ [${template}] framework not supported. Welcome to contribute a Pull Request to help.`)))
32
+ return
33
+ }
29
34
  const { index, resource } = require(compilerPath)
30
35
 
31
36
  // 💡 物理扫描:把目录下所有的 .hbs 文件全部识别并自动注册
32
- const partialsDir = path.join(__dirname, `../../templates/${template}/handlebars`)
37
+ const partialsDir = config.hbsDir !== ''
38
+ ? path.join(process.cwd(), config.hbsDir, 'handlebars')
39
+ : path.join(__dirname, `../../templates/${template}/handlebars`)
33
40
  fs.readdirSync(partialsDir).forEach(file => {
34
41
  if (file.endsWith('.hbs')) {
35
42
  const name = path.basename(file, '.hbs') // 去掉后缀拿名字
@@ -130,8 +130,9 @@ export const MyModalForm = ({ width, title, submit, record, visible, setModal, l
130
130
  const [selectedTableRows, setSelectedTableRows] = useState([])
131
131
 
132
132
  const flattenFormItems = items => {
133
+ if (!Array.isArray(items)) return []
133
134
  return items.flatMap(item => {
134
- if (item.layoutType && item.childItems) {
135
+ if (item.childItems) {
135
136
  return flattenFormItems(item.childItems)
136
137
  }
137
138
  return item
@@ -181,7 +182,7 @@ export const MyModalForm = ({ width, title, submit, record, visible, setModal, l
181
182
  try {
182
183
  const values = await form.validateFields()
183
184
  const formattedValues = { ...values }
184
- formItems.forEach(item => {
185
+ flattenFormItems(formItems).forEach(item => {
185
186
  const val = formattedValues[item.name]
186
187
  if (item.type?.includes('date') && val) {
187
188
  if (Array.isArray(val)) {
@@ -1,7 +1,7 @@
1
1
  import { MyTable } from './MyTable'
2
2
  import { useMemo, useState } from 'react'
3
- import { Modal, Space, Button } from 'antd'
4
3
  import { MySearchForm } from './MySearchForm'
4
+ import { Form, Modal, Space, Button } from 'antd'
5
5
  import { useTableQuery } from '../hooks/useTableQuery'
6
6
 
7
7
  /**
@@ -38,6 +38,7 @@ import { useTableQuery } from '../hooks/useTableQuery'
38
38
  */
39
39
  export const MyModalTable = ({ api, onOk, title, width, footer, visible, operate, setModal, formItems, setModalForm, rowSelection, formatResponse, functionButtons, columns: modalColumns, rowKey = 'id', initialParams = {}, modalPagination = true }) => {
40
40
 
41
+ const [form] = Form.useForm()
41
42
  const [pending, setPending] = useState(false)
42
43
 
43
44
  /**
@@ -82,7 +83,7 @@ export const MyModalTable = ({ api, onOk, title, width, footer, visible, operate
82
83
  💡 [地堡战术细节]
83
84
  syncUrlParams={false}:确保弹窗内的搜索行为不会干扰全局 URL 信号。
84
85
  */}
85
- {formItems?.length > 0 && <MySearchForm search={search} formItems={formItems} setSearch={handleSearch} syncUrlParams={false} />}
86
+ {formItems?.length > 0 && <MySearchForm form={form} search={search} formItems={formItems} setSearch={handleSearch} syncUrlParams={false} />}
86
87
  {/*
87
88
  💡 [高阶赋能逻辑]
88
89
  functionButtons 能够获得对当前表格状态 (refresh, setLoading...) 的完全控制权。
@@ -40,6 +40,7 @@ export const MySearchForm = ({ form, search, loading, labelCol, setSearch, formI
40
40
  * @description 执行“归零协议”:清除当前所有搜索信号,并可选择性擦除 URL 中的物理记录。
41
41
  */
42
42
  const handleReset = () => {
43
+ form.resetFields()
43
44
  if (syncUrlParams) {
44
45
  // 物理清除:重置浏览器历史记录,保持 URL 纯净
45
46
  window.history.replaceState(null, '', window.location.pathname)
@@ -59,24 +60,24 @@ export const MySearchForm = ({ form, search, loading, labelCol, setSearch, formI
59
60
  */
60
61
  const handleFinish = values => {
61
62
  const formattedValues = { ...values }
62
- if (syncUrlParams) {
63
- Object.keys(formattedValues).forEach(key => {
64
- const val = formattedValues[key]
65
- const isDateType = formItems.find(item => item.name === key)?.type?.includes('date')
66
- // 💡 [黑科技 1] 日期自动转换与拆解
67
- if (isDateType && val) {
68
- if (Array.isArray(val) && val.length > 0) {
69
- // 物理拆分:当 name 为 'start,end' 格式时,自动拆解为两个独立的后端参数
70
- const [startKey, endKey] = key.split(',')
71
- formattedValues[startKey] = val[0].startOf('day').valueOf()// 修正为当日 00:00:00
72
- formattedValues[endKey] = val[1].endOf('day').valueOf()// 修正为当日 23:59:59
73
- delete formattedValues[key]
74
- } else {
75
- // 单日转换:转化为毫秒级数字
76
- formattedValues[key] = val.valueOf()
77
- }
63
+ Object.keys(formattedValues).forEach(key => {
64
+ const val = formattedValues[key]
65
+ const isDateType = formItems.find(item => item.name === key)?.type?.includes('date')
66
+ // 💡 [黑科技 1] 日期自动转换与拆解
67
+ if (isDateType && val) {
68
+ if (Array.isArray(val) && val.length > 0) {
69
+ // 物理拆分:当 name 为 'start,end' 格式时,自动拆解为两个独立的后端参数
70
+ const [startKey, endKey] = key.split(',')
71
+ formattedValues[startKey] = val[0].startOf('day').valueOf()// 修正为当日 00:00:00
72
+ formattedValues[endKey] = val[1].endOf('day').valueOf()// 修正为当日 23:59:59
73
+ delete formattedValues[key]
74
+ } else {
75
+ // 单日转换:转化为毫秒级数字
76
+ formattedValues[key] = val.valueOf()
78
77
  }
79
- })
78
+ }
79
+ })
80
+ if (syncUrlParams) {
80
81
  // 💡 [黑科技 2] 镜像至 URL
81
82
  const params = new URLSearchParams(Object.fromEntries(Object.entries(formattedValues).filter(([key, value]) => !['', null, undefined].includes(value))))
82
83
  window.history.replaceState(null, '', `${window.location.pathname}?${params.toString()}`)
@@ -27,7 +27,10 @@
27
27
  window.history.replaceState(null, '', `${window.location.pathname}?${params.toString()}`)
28
28
  setSearch(newSearch)
29
29
  } else {
30
- setSearch({ ...search,pageNo: pagination.current, pageSize: pagination.pageSize, orderBy: undefined })
30
+ const newSearch = { ...search, pageNo: pagination.current, pageSize: pagination.pageSize, orderBy: undefined }
31
+ const params = new URLSearchParams(Object.fromEntries(Object.entries({ ...query, ...newSearch }).filter(([key, value]) => !['', null, undefined].includes(value))))
32
+ window.history.replaceState(null, '', `${window.location.pathname}?${params.toString()}`)
33
+ setSearch(newSearch)
31
34
  }
32
35
  }}
33
36
  {{{{/raw}}}}
@@ -28,6 +28,7 @@ const {{this.action}} = () => {
28
28
  {{#if hasTabs}}
29
29
  const onTabChange = key => {
30
30
  setActiveKey(key)
31
+ {{#if hasFormItems}}form.resetFields(){{/if}}
31
32
  {{#if hasRowSelection}}setSelectedRows([]){{/if}}
32
33
  setSearch({ pageNo: 1, pageSize: 10, type: key })
33
34
  }
@@ -41,6 +41,8 @@ export const useTableQuery = ({ api, cols, initialParams = {}, formatResponse })
41
41
  const apiRef = useRef(api)
42
42
  // 💡 [核心防御] 信号 ID 计数器:用于物理拦截由于网络时延导致的“竞态病毒(Race Condition)”
43
43
  const fetchIdRef = useRef(0)
44
+ // 💡 [核心防御] 数据解码协议引用:确保异步回调中永远能指向最新的指令地址
45
+ const formatResponseRef = useRef(formatResponse)
44
46
 
45
47
  useEffect(() => {
46
48
  apiRef.current = api
@@ -70,10 +72,16 @@ export const useTableQuery = ({ api, cols, initialParams = {}, formatResponse })
70
72
  const response = await apiRef.current(search)
71
73
  // 💡 [物理拦截] 信号校准:若当前 ID 不是最新 ID,说明该信号已过期,执行丢弃协议
72
74
  if (currentFetchId !== fetchIdRef.current) return
73
- const { data, total, columns: newCols } = formatResponse(response ?? {})
75
+ const { data, total, columns: newCols } = formatResponseRef.current?.(response ?? {}) || {}
74
76
  // 💡 [性能装甲] 只有当列指纹发生变化时,才触发 React 的重绘逻辑
75
- if (newCols && getColumnSchema(newCols) !== getColumnSchema(columns)) {
76
- setColumns(newCols)
77
+ if (newCols) {
78
+ // 使用函数式状态更新,既能拿到最新的 prevCols,又无需将 columns 作为 useCallback 的依赖!
79
+ setColumns(prevCols => {
80
+ if (getColumnSchema(newCols) !== getColumnSchema(prevCols)) {
81
+ return newCols
82
+ }
83
+ return prevCols
84
+ })
77
85
  }
78
86
  setDataSource(data)
79
87
  setTotal(total ?? 0)
@@ -58,7 +58,7 @@ export const initOSS = async (url, options) => {
58
58
  export const formatQuery = (params, formItems) => {
59
59
  const cleanParams = {}
60
60
  Object.keys(params).forEach(key => {
61
- if (formItems.some(item => item.name.includes(key) && item.type?.includes('date'))) {
61
+ if (formItems.some(item => item.name.includes(key) && item.type?.includes('date')) && params[key]) {
62
62
  cleanParams[key] = Number(params[key])
63
63
  } else {
64
64
  cleanParams[key] = params[key] ?? null
@@ -225,7 +225,9 @@ export const formatUnit = (value, { k = 1024, units = ['B', 'KB', 'MB', 'GB', 'T
225
225
  if (value === null || value === undefined || isNaN(value) || value === 0) {
226
226
  return `0 ${units[0] ?? ''}`
227
227
  }
228
- const i = Math.floor(Math.log(value) / Math.log(k))
228
+ const absValue = Math.abs(value)
229
+ // 💡 安全屏障:小于 1 的数值直接归入初始级单位,不进行对数拆解
230
+ const i = absValue < 1 ? 0 : Math.floor(Math.log(absValue) / Math.log(k))
229
231
  const index = Math.min(i, units.length - 1)
230
232
  const result = (value / Math.pow(k, index)).toFixed(decimals)
231
233
  return `${parseFloat(result)} ${units[index]}`