@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 +4 -1
- package/package.json +1 -1
- package/src/commands/watch.js +2 -7
- package/src/core/context.js +8 -1
- package/templates/react/components/MyModalForm.js +3 -2
- package/templates/react/components/MyModalTable.js +3 -2
- package/templates/react/components/MySearchForm.js +18 -17
- package/templates/react/handlebars/MyTable.hbs +4 -1
- package/templates/react/handlebars/handleBlock.hbs +1 -0
- package/templates/react/hooks/useTableQuery.js +11 -3
- package/templates/react/utils/utils.js +4 -2
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
package/src/commands/watch.js
CHANGED
|
@@ -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,
|
|
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
|
})
|
package/src/core/context.js
CHANGED
|
@@ -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 =
|
|
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.
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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 } =
|
|
75
|
+
const { data, total, columns: newCols } = formatResponseRef.current?.(response ?? {}) || {}
|
|
74
76
|
// 💡 [性能装甲] 只有当列指纹发生变化时,才触发 React 的重绘逻辑
|
|
75
|
-
if (newCols
|
|
76
|
-
|
|
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
|
|
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]}`
|