@yorha2b-lab/autodev 1.0.28 → 1.0.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yorha2b-lab/autodev",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "description": "基于视觉大模型的前端(Umi+Antd)全自动 CRUD 代码生成器",
5
5
  "bin": {
6
6
  "autodev": "bin/autodev.js"
@@ -62,6 +62,7 @@ const index = ({ config, fileName, indexTpl, pageConfig }) => {
62
62
  hasOperate, // 是否有操作列
63
63
  columnsValue, // columns 引用表达式
64
64
  tabs: pageConfig.tabs, // 标签页配置
65
+ responseSuccess: config.responseSuccess, // 成功回调函数
65
66
  hasExpandable: pageConfig.table.expandable, // 是否有展开行
66
67
  hasPagination: pageConfig.table.pagination, // 是否有分页
67
68
  operations: pageConfig.table.operation || [], // 操作列配置
@@ -72,7 +73,7 @@ const index = ({ config, fileName, indexTpl, pageConfig }) => {
72
73
  }
73
74
 
74
75
  // 渲染模板
75
- const bodyCode = indexTpl(viewData).replaceAll('_responseSuccess_', config.responseSuccess)
76
+ const bodyCode = indexTpl(viewData)
76
77
  // 生成智能导入语句
77
78
  const importsStr = generateSmartImports(bodyCode, hasTabs)
78
79
  // 清理代码并返回
@@ -1,6 +1,7 @@
1
+ import { Fragment } from 'react'
1
2
  import { formNode } from './index'
2
- import { Form, Space, Button } from 'antd'
3
- import { PlusOutlined, PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons'
3
+ import { Form, Button } from 'antd'
4
+ import { PlusOutlined } from '@ant-design/icons'
4
5
 
5
6
  const getValueFromEvent = e => {
6
7
  if (Array.isArray(e)) {
@@ -9,14 +10,16 @@ const getValueFromEvent = e => {
9
10
  return e?.fileList
10
11
  }
11
12
 
12
- const renderFormContent = ({ item }) => {
13
+ const renderFormContent = ({ item, prefixName = [] }) => {
13
14
 
14
15
  if (!item.name) {
15
16
  return item.value
16
17
  }
17
18
 
19
+ const namePath = Array.isArray(prefixName) ? [...prefixName, ...(Array.isArray(item.name) ? item.name : [item.name])] : item.name
20
+
18
21
  const inputNode = (
19
- <Form.Item noStyle name={item.name} rules={item.rules} initialValue={item.value} {...(item.type === 'upload' ? { getValueFromEvent, valuePropName: 'fileList' } : { valuePropName: item.valuePropName })}>
22
+ <Form.Item noStyle name={namePath} rules={item.rules} initialValue={item.value} {...(['upload', 'ossUpload'].includes(item.type) ? { getValueFromEvent, valuePropName: 'fileList' } : { valuePropName: item.valuePropName })}>
20
23
  {formNode({ item })}
21
24
  </Form.Item>
22
25
  )
@@ -41,11 +44,9 @@ export const MyBaseForm = ({ item, form }) => {
41
44
  {(fields, { add, remove }) => (
42
45
  <div style={{ display: 'flex', flexDirection: 'column' }}>
43
46
  {fields.map((field, index) => (
44
- <Space key={field.key} style={{ display: 'flex', marginBottom: 8 }} align='baseline'>
45
- {item.render(field, index, { add, remove }, form)}
46
- {index !== 0 && <MinusCircleOutlined onClick={() => remove(field.name)} />}
47
- {fields.length === index + 1 && <PlusCircleOutlined onClick={() => add()} />}
48
- </Space>
47
+ <Fragment key={field.key}>
48
+ {item.render(field, index, { add, remove }, form, renderFormContent)}
49
+ </Fragment>
49
50
  ))}
50
51
  {fields.length === 0 && <Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>添加{item.label || '项'}</Button>}
51
52
  </div>
@@ -12,14 +12,26 @@ 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
- const entries = Object.entries(record).map(([key, value]) => [
16
- key,
17
- formItems.find(item => item.name === key)?.type?.includes('date') ? (value?.toString()?.includes(',') ? value?.split(',')?.map(item => dayjs(Number(item))) : dayjs(Number(value))) : value
18
- ])
19
- form.setFieldsValue(Object.fromEntries(entries))
15
+ const itemMap = new Map(formItems.map(i => [i.name, i]))
16
+ const initialData = {}
17
+ Object.entries(record).forEach(([key, value]) => {
18
+ const config = itemMap.get(key)
19
+ if (config?.type?.includes('date') && value) {
20
+ if (Array.isArray(value)) {
21
+ initialData[key] = value.map(v => dayjs(isNaN(v) ? v : Number(v)))
22
+ } else if (typeof value === 'string' && value.includes(',')) {
23
+ initialData[key] = value.split(',').map(v => dayjs(Number(v)))
24
+ } else {
25
+ initialData[key] = dayjs(isNaN(value) ? value : Number(value))
26
+ }
27
+ } else {
28
+ initialData[key] = value
29
+ }
30
+ })
31
+ form.setFieldsValue(initialData)
20
32
  }
21
33
  }
22
- }, [visible, record, form])
34
+ }, [visible, record, form, formItems])
23
35
 
24
36
  const handleOk = async () => {
25
37
  try {
@@ -4,9 +4,9 @@ import { MySearchForm } from './MySearchForm'
4
4
  import { useTableQuery } from '../hooks/useTableQuery'
5
5
 
6
6
 
7
- export const MyModalTable = ({ api, onOk, title, width, footer, visible, columns, setModal, formItems, rowSelection, formatResponse }) => {
7
+ export const MyModalTable = ({ api, onOk, title, width, footer, visible, columns, setModal, formItems, rowSelection, formatResponse, extraParams = {} }) => {
8
8
 
9
- const { total, loading, dataSource, search, setSearch } = useTableQuery(api, formatResponse, {})
9
+ const { total, loading, dataSource, search, setSearch } = useTableQuery(api, formatResponse, extraParams)
10
10
 
11
11
  const handleSearch = values => setSearch({ ...search, ...values, pageNo: 1 })
12
12
 
@@ -21,7 +21,7 @@ export const MyModalTable = ({ api, onOk, title, width, footer, visible, columns
21
21
 
22
22
  return (
23
23
  <Modal centered destroyOnHidden title={title} width={width} open={visible} onOk={handleOk} footer={footer} onCancel={() => setModal({ visible: false })}>
24
- {formItems?.length > 0 && <MySearchForm search={search} formItems={formItems} setSearch={handleSearch} />}
24
+ {formItems?.length > 0 && <MySearchForm search={search} formItems={formItems} setSearch={handleSearch} syncUrlParams={false} />}
25
25
  <MyTable
26
26
  loading={loading}
27
27
  columns={columns}
@@ -4,12 +4,14 @@ import { MyBaseForm } from './MyBaseForm'
4
4
  import { useState, useEffect } from 'react'
5
5
  import { Row, Col, Form, Button, Space } from 'antd'
6
6
 
7
- export const MySearchForm = ({ form, search, loading, labelCol, setSearch, formItems, showLimit = 7, initialValues, customReset, extraOperate, customFinish, onValuesChange, defaultPageSize = 10 }) => {
7
+ export const MySearchForm = ({ form, search, loading, labelCol, setSearch, formItems, showLimit = 7, initialValues, customReset, extraOperate, customFinish, onValuesChange, syncUrlParams = true, defaultPageSize = 10 }) => {
8
8
 
9
9
  const [limit, setLimit] = useState(showLimit)
10
10
 
11
11
  const handleReset = () => {
12
- history.push({ search: '' })
12
+ if (syncUrlParams) {
13
+ history.push({ search: '' })
14
+ }
13
15
  if (customReset) {
14
16
  customReset()
15
17
  } else {
@@ -19,19 +21,24 @@ export const MySearchForm = ({ form, search, loading, labelCol, setSearch, formI
19
21
 
20
22
  const handleFinish = values => {
21
23
  const formattedValues = { ...values }
22
- Object.keys(formattedValues).forEach(key => {
23
- const val = formattedValues[key]
24
- const isDateType = formItems.find(item => item.name === key)?.type?.includes('date')
25
- if (isDateType && val) {
26
- if (Array.isArray(val)) {
27
- formattedValues[key] = val.map((d, i) => i === 0 ? d.startOf('day').valueOf() : d.endOf('day').valueOf()).join(',')
28
- } else {
29
- formattedValues[key] = val.valueOf()
24
+ if (syncUrlParams) {
25
+ Object.keys(formattedValues).forEach(key => {
26
+ const val = formattedValues[key]
27
+ const isDateType = formItems.find(item => item.name === key)?.type?.includes('date')
28
+ if (isDateType && val) {
29
+ if (Array.isArray(val) && val.length > 0) {
30
+ const [startKey, endKey] = key.split(',')
31
+ formattedValues[startKey] = val[0].startOf('day').valueOf()
32
+ formattedValues[endKey] = val[1].endOf('day').valueOf()
33
+ delete formattedValues[key]
34
+ } else {
35
+ formattedValues[key] = val.valueOf()
36
+ }
30
37
  }
31
- }
32
- })
33
- const params = new URLSearchParams(Object.fromEntries(Object.entries(formattedValues).filter(([key, value]) => !['', null, undefined].includes(value))))
34
- history.push({ search: params.toString() })
38
+ })
39
+ const params = new URLSearchParams(Object.fromEntries(Object.entries(formattedValues).filter(([key, value]) => !['', null, undefined].includes(value))))
40
+ history.push({ search: params.toString() })
41
+ }
35
42
  if (customFinish) {
36
43
  customFinish(formattedValues)
37
44
  } else {
@@ -41,11 +48,20 @@ export const MySearchForm = ({ form, search, loading, labelCol, setSearch, formI
41
48
 
42
49
  useEffect(() => {
43
50
  if (Object.values(initialValues).length > 0 && formItems.length > 0) {
44
- const entries = Object.entries(initialValues).filter(([key]) => formItems.some(item => item.name === key)).map(([key, value]) => [
45
- key,
46
- formItems.find(item => item.name === key)?.type?.includes('date') ? (value?.toString()?.includes(',') ? value?.split(',')?.map(item => dayjs(Number(item))) : dayjs(Number(value))) : value
47
- ])
48
- form.setFieldsValue(Object.fromEntries(entries))
51
+ const newValues = {}
52
+ formItems.forEach(item => {
53
+ if (initialValues[item.name] !== undefined) {
54
+ const value = initialValues[item.name]
55
+ newValues[item.name] = item.type?.includes('date') ? dayjs(Number(value)) : value
56
+ }
57
+ if (item.name.includes(',') && item.type?.includes('date')) {
58
+ const [startKey, endKey] = item.name.split(',')
59
+ if (initialValues[startKey] && initialValues[endKey]) {
60
+ newValues[item.name] = [dayjs(Number(initialValues[startKey])), dayjs(Number(initialValues[endKey]))]
61
+ }
62
+ }
63
+ })
64
+ form.setFieldsValue(newValues)
49
65
  }
50
66
  }, [initialValues, form, formItems])
51
67
 
@@ -1,5 +1,5 @@
1
1
  import { Table } from 'antd'
2
- import { useRef, useEffect } from 'react'
2
+ import { useRef, useMemo, useEffect } from 'react'
3
3
  import { EditableRow, EditableCell } from './EditableCell'
4
4
 
5
5
  const components = {
@@ -9,61 +9,81 @@ const components = {
9
9
  }
10
10
  }
11
11
 
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
+ export const MyTable = ({ size, query, onSave, autoScroll, onChange, pagination, rowClassName, columns = [], rowSelection, rowKey = 'id', loading = false, dataSource = [], scroll = { x: 'max-content' }, ...restProps }) => {
13
13
 
14
14
 
15
- const hasScrolled = useRef(false)
15
+ const tableRef = useRef(null)
16
+ const hasScrolledRef = useRef(false)
16
17
 
17
- const mergedColumns = columns.map(col => {
18
- if (!col.editType) {
19
- return col
20
- }
21
- return {
22
- ...col,
23
- onCell: (record) => ({
24
- record,
25
- title: col.title,
26
- rules: col.rules,
27
- handleSave: onSave,
28
- options: col.options,
29
- editType: col.editType,
30
- editable: !!col.editType,
31
- dataIndex: col.dataIndex,
32
- }),
33
- }
34
- })
18
+ const isEditable = useMemo(() => columns.some(col => col.editType), [columns])
19
+
20
+ const mergedColumns = useMemo(() => {
21
+ return columns.map(col => {
22
+ if (!col.editType) {
23
+ return col
24
+ }
25
+ return {
26
+ ...col,
27
+ onCell: (record) => ({
28
+ record,
29
+ title: col.title,
30
+ rules: col.rules,
31
+ handleSave: onSave,
32
+ options: col.options,
33
+ editType: col.editType,
34
+ editable: !!col.editType,
35
+ dataIndex: col.dataIndex,
36
+ }),
37
+ }
38
+ })
39
+ }, [columns, onSave])
35
40
 
36
41
  useEffect(() => {
37
- if (hasScrolled.current || !query || !autoScroll || !dataSource?.length) return
38
- if (query && autoScroll && dataSource?.length > 0) {
39
- const timer = setTimeout(() => {
40
- const trList = document.getElementsByClassName('ant-table-row')
41
- const tr = trList[Number(query.rowIndex)]
42
- if (tr) {
43
- tr.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' })
44
- tr.style.transition = 'background-color 0.8s ease'
45
- tr.style.backgroundColor = '#e6f4ff'
46
- hasScrolled.current = true
47
- }
48
- }, 100)
49
- return () => clearTimeout(timer)
50
- }
51
- }, [dataSource])
42
+ if (!autoScroll || !dataSource?.length || query?.rowIndex === undefined) return
43
+ if (hasScrolledRef.current === query.rowIndex) return
44
+ const timer = setTimeout(() => {
45
+ const tableElement = tableRef.current
46
+ if (!tableElement) return
47
+ const trList = tableElement.getElementsByClassName('ant-table-row')
48
+ const tr = trList[Number(query.rowIndex)]
49
+ if (tr) {
50
+ tr.scrollIntoView({ behavior: 'smooth', block: 'center' })
51
+ tr.classList.add('row-highlight-pulse')
52
+ setTimeout(() => tr.classList.remove('row-highlight-pulse'), 1000)
53
+ hasScrolledRef.current = query.rowIndex
54
+ }
55
+ }, 300)
56
+ return () => clearTimeout(timer)
57
+ }, [dataSource, query?.rowIndex, autoScroll, tableRef])
52
58
 
53
59
  return (
54
- <Table
55
- {...restProps}
56
- rowKey={rowKey}
57
- scroll={scroll}
58
- loading={loading}
59
- onChange={onChange}
60
- columns={mergedColumns}
61
- dataSource={dataSource}
62
- pagination={pagination}
63
- size={size ?? 'middle'}
64
- rowSelection={rowSelection}
65
- components={editable ? components : undefined}
66
- rowClassName={editable ? () => 'editable-row' : rowClassName}
67
- />
60
+ <div ref={tableRef}>
61
+ <Table
62
+ {...restProps}
63
+ rowKey={rowKey}
64
+ scroll={scroll}
65
+ loading={loading}
66
+ onChange={onChange}
67
+ columns={mergedColumns}
68
+ dataSource={dataSource}
69
+ pagination={pagination}
70
+ size={size ?? 'middle'}
71
+ rowSelection={rowSelection}
72
+ components={isEditable ? components : undefined}
73
+ rowClassName={(record, index) => {
74
+ const externalClass = typeof rowClassName === 'function' ? rowClassName(record, index) : rowClassName
75
+ const classes = [externalClass]
76
+ if (isEditable) classes.push('editable-row')
77
+ if (autoScroll && query?.rowIndex === index) classes.push('search-highlight-row')
78
+ return classes.filter(Boolean).join(' ')
79
+ }}
80
+ />
81
+ <style jsx>{`
82
+ :global(.row-highlight-pulse) {
83
+ background-color: #e6f4ff !important;
84
+ transition: background-color 0.5s ease;
85
+ }
86
+ `}</style>
87
+ </div>
68
88
  )
69
89
  }
@@ -3,7 +3,7 @@ import { useState, useEffect } from 'react'
3
3
  import { UploadOutlined } from '@ant-design/icons'
4
4
  import { Button, Tree, Radio, Input, Upload, Select, Cascader, Checkbox, DatePicker, InputNumber, TreeSelect, AutoComplete } from 'antd'
5
5
 
6
- const AliyunOSSUpload = ({ style, value, disabled, onChange }) => {
6
+ const AliyunOSSUpload = ({ path, style, value, disabled, onChange }) => {
7
7
 
8
8
  const [OSSData, setOSSData] = useState()
9
9
 
@@ -23,7 +23,7 @@ const AliyunOSSUpload = ({ style, value, disabled, onChange }) => {
23
23
  const handleChange = ({ fileList }) => onChange?.([...fileList])
24
24
 
25
25
  const onRemove = file => {
26
- const files = (value || []).filter(v => v.url !== file.url)
26
+ const files = (value || []).filter(v => v.uid !== file.uid)
27
27
  onChange?.(files)
28
28
  }
29
29
 
@@ -42,7 +42,7 @@ const AliyunOSSUpload = ({ style, value, disabled, onChange }) => {
42
42
  if (expire < Date.now()) {
43
43
  await init()
44
44
  }
45
- file.url = `${OSSData.dir}/${file.name}`.replace(/\/\//g, '/')
45
+ file.url = `${OSSData.dir ?? path}/${file.uid}_${file.name}`.replace(/\/\//g, '/')
46
46
  return file
47
47
  }
48
48
 
@@ -94,11 +94,11 @@ export const formNode = ({ item }) => {
94
94
  case 'textarea':
95
95
  return <Input.TextArea autoSize={{ minRows: 4 }} {...commonProps} />
96
96
  case 'cascader':
97
- return <Cascader options={item.options} allowClear {...commonProps} />
98
- case 'select':
99
- return <Select allowClear mode={item.mode} options={item.options} {...commonProps} />
97
+ return <Cascader showSearch allowClear options={item.options} {...commonProps} />
100
98
  case 'upload':
101
99
  return <Upload {...item.uploadProps} {...commonProps}>{item.content ?? '上传文件'}</Upload>
100
+ case 'select':
101
+ return <Select allowClear showSearch mode={item.mode} options={item.options} {...commonProps} />
102
102
  default:
103
103
  return <Input allowClear {...commonProps} />
104
104
  }
@@ -25,7 +25,7 @@ export default props => {
25
25
  {{#each operations}}
26
26
  const {{this.action}} = async record => {
27
27
  const response = await request('/api')
28
- if(_responseSuccess_){
28
+ if({{{../responseSuccess}}}){
29
29
  refresh()
30
30
  }
31
31
  }
@@ -40,7 +40,7 @@ export default props => {
40
40
  content: '确认{{this.btn}}吗?',
41
41
  onOk: async () => {
42
42
  const response = await request('/api')
43
- if(_responseSuccess_){
43
+ if({{{../responseSuccess}}}){
44
44
  refresh()
45
45
  }
46
46
  }
@@ -6,16 +6,6 @@ export const paramsFormat = params => {
6
6
  return new URLSearchParams(cleanParams).toString()
7
7
  }
8
8
 
9
- export const formatValue = ({ item, ind }) => {
10
- if (dayjs.isDayjs(item[1])) {
11
- return item[1].startOf('day').valueOf()
12
- }
13
- if (dayjs.isDayjs(item[1]?.[ind])) {
14
- return ind === 0 ? item[1]?.[ind]?.startOf('day').valueOf() : item[1]?.[ind]?.endOf('day').valueOf()
15
- }
16
- return item[1]?.toString()?.trim() ?? undefined
17
- }
18
-
19
9
  export const initOSS = async (url, options) => {
20
10
  const OSS = require('ali-oss')
21
11
  const crypto = require('crypto-js')
@@ -60,7 +50,17 @@ export const exportDataToExcel = async (url, options, columns, fileName, resultK
60
50
  const response = await request(url, options)
61
51
  if (response?.[resultKey]?.length > 0) {
62
52
  const XLSX = require('xlsx')
63
- const datas = response?.[resultKey]?.map(item => Object.fromEntries(columns.map(col => [col.title, col.render ? col.render(item[col.dataIndex])?.props?.children ?? col.render(item[col.dataIndex]) : item[col.dataIndex]])))
53
+ const datas = response?.[resultKey]?.map(item => Object.fromEntries(columns.map(col => {
54
+ let value = item[col.dataIndex]
55
+ if (col.exportRender) {
56
+ value = col.exportRender(value, item, index)
57
+ }
58
+ if (col.render) {
59
+ const rendered = col.render(value, item, index)
60
+ value = typeof rendered === 'object' ? (rendered?.props?.children || value) : rendered
61
+ }
62
+ return [col.title, value]
63
+ })))
64
64
  const workbook = XLSX.utils.book_new()
65
65
  const header = columns.map(item => item.title)
66
66
  const worksheet = XLSX.utils.json_to_sheet(datas, { header })