@yorha2b-lab/autodev 7.7.5 → 7.7.7

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": "7.7.5",
3
+ "version": "7.7.7",
4
4
  "description": "基于 AI 视觉与语义协议的前端 (React+Antd) 代码构筑与旧系统重构引擎",
5
5
  "bin": {
6
6
  "bunker": "bin/bunker.js"
@@ -1 +1 @@
1
- <MySearchForm form={form} search={search} initialValues={query} loading={loading} setSearch={setSearch} formItems={ {{formItems}} } initialParams={initialParams} />
1
+ <MySearchForm form={form} search={search} loading={loading} formItems={ {{formItems}} } initialParams={initialParams} initialValues={query} setSearch={setSearch} />
@@ -86,4 +86,8 @@ const modalSubmit = async params => {
86
86
  setModal({ visible: false })
87
87
  refresh()
88
88
  }
89
- }
89
+ }
90
+
91
+ useEffect(() => {
92
+ //初始化事件
93
+ }, [])
@@ -1,4 +1,6 @@
1
1
 
2
+ const { } = useContext(GlobalContext)
3
+
2
4
  const rowKey = 'id'
3
5
  {{#if hasFormItems}}
4
6
  const [form] = Form.useForm()
@@ -53,7 +53,7 @@ module.exports = `
53
53
  - 必填校验: 若图片中 label 前有红色星号,必须加入 rules:[{required:true,message:'xxx不能为空'}]。
54
54
 
55
55
  ## 3. 页签 (Tabs)
56
- - 结构: tabs: [{label:'页签名',key:'页签英文'}]
56
+ - 结构: tabs: [{tab:'页签名',key:'页签英文'}]
57
57
 
58
58
  ## 4. 下拉选项字典 (OptionDict)
59
59
  - 结构: optionDict: { 字段英文名Options: [] }
@@ -18,7 +18,7 @@ module.exports = `
18
18
  ## 零部件构筑规范 (Component Specifications)
19
19
 
20
20
  ## 标签页 (tabs)
21
- - **基础格式**: [{ label: '文本', key: 'englishName' }]
21
+ - **基础格式**: [{ tab: '文本', key: 'englishName' }]
22
22
  - **识别条件**: 底部有显著长横线 (Ink Bar) 或呈现包裹感 (Card style) 时判定。
23
23
  - **排除项**: 表格上方的独立按钮必须归类为 [functionButton]。
24
24
 
@@ -50,8 +50,8 @@ module.exports = ({ template, logistics, headquarters }) => {
50
50
 
51
51
  const hooksLib = ['useTableQuery']
52
52
  const utilsLib = ['timeRender', 'moneyRender']
53
- const reactLib = ['useState', 'useEffect', 'useRef', 'useMemo']
54
53
  const componentsLib = ['MyTable', 'MyImage', 'MyModalForm', 'MySearchForm']
54
+ const reactLib = ['useRef', 'useMemo', 'useState', 'useEffect', 'useContext']
55
55
  const antdLib = ['Tag', 'Card', 'Badge', 'Space', 'Modal', 'Alert', 'Image', 'Table', 'Input', 'Select', 'Button']
56
56
 
57
57
  const usedAntd = antdLib.filter(name => new RegExp(`\\b${name}\\b`).test(bodyCode))
@@ -67,6 +67,7 @@ module.exports = ({ template, logistics, headquarters }) => {
67
67
  ...usedComps.map(comp => `import { ${comp} } from '../../components/${comp}'`),
68
68
  ...(module === 'index' ? [
69
69
  `import { request } from '../../utils/request'`,
70
+ `import { GlobalContext } from '../../layouts'`,
70
71
  `import { formatQuery } from '../../utils/utils'`,
71
72
  `import { ${hasTabs ? 'tabs, ' : ''}${hasFormItems ? 'formItems, ' : ''}modalItems, tableColumns} from './resource'`
72
73
  ] : [
@@ -20,12 +20,12 @@
20
20
  "items": {
21
21
  "type": "object",
22
22
  "required": [
23
- "label",
23
+ "tab",
24
24
  "key"
25
25
  ],
26
26
  "additionalProperties": false,
27
27
  "properties": {
28
- "label": {
28
+ "tab": {
29
29
  "type": "string"
30
30
  },
31
31
  "key": {
@@ -0,0 +1,114 @@
1
+ import { Button, Upload } from 'antd'
2
+ import { initOSS } from '../utils/utils'
3
+ import { useState, useEffect } from 'react'
4
+ import { UploadOutlined } from '@ant-design/icons'
5
+
6
+ /**
7
+ * @component AliyunOSSUpload
8
+ * @description [地堡核心传输单元] 阿里云 OSS 高级上传组件。
9
+ * 集成了“物理路径自动重组”、“STS 令牌自动续期”以及“ Antd 属性无损透传”三大核心协议。
10
+ *
11
+ * @param {Object} props - 构筑参数
12
+ * @param {string} [props.path] - 默认存储扇区:定义文件在云端的物理根路径
13
+ * @param {string} [props.url] - 司令部通信地址:用于申请最新的物理传输令牌 (STS)
14
+ * @param {Array} props.value - 传感器当前封存的文件列表 (符合 Antd fileList 规范)
15
+ * @param {Function} props.onChange - 状态反馈协议:当物理文件状态变更时同步至地堡中枢
16
+ * @param {Object} [props.oss] - 外部空投的初始凭证包。若缺失,组件将启动自主初始化流程。
17
+ * @param {Object} [props.options] - 通信加密/配置参数:在申请令牌时透传给 request 的额外负载
18
+ * @param {Object} [props.originUploadProps] - [重要] 无损透传:支持 Antd Upload 的所有原生属性(如 multiple, accept 等)
19
+ *
20
+ * @example
21
+ * // 基本构筑
22
+ * <AliyunOSSUpload path='project/data' url='/api/oss/token' />
23
+ *
24
+ * // 高级装配(限制文件类型并开启多选)
25
+ * <AliyunOSSUpload accept='.pdf' multiple={true} path='docs' url='/api/oss/token' />
26
+ */
27
+ export const AliyunOSSUpload = ({ value, onChange, ...restProps }) => {
28
+
29
+ // 💡 物理剥离:将地堡专用参数与 Antd 原生属性分离
30
+ const { oss, url, path, options, expireField = 'expire', ...originUploadProps } = restProps
31
+
32
+ const [OSSData, setOSSData] = useState(oss)
33
+
34
+ /**
35
+ * @async
36
+ * @function init
37
+ * @description [信号握手] 执行令牌获取协议,从司令部申请最新的物理上传授权
38
+ */
39
+ const init = async () => {
40
+ try {
41
+ const result = await initOSS(url, options)
42
+ setOSSData(result)
43
+ return result
44
+ } catch (err) {
45
+ console.log(err)
46
+ }
47
+ }
48
+
49
+ useEffect(() => {
50
+ // 💡 自愈逻辑:如果发现能源包 (OSSData) 为空,则自动触发初始化
51
+ if (!OSSData) {
52
+ init()
53
+ }
54
+ }, [])
55
+
56
+ const handleChange = ({ fileList }) => onChange?.([...fileList])
57
+
58
+ const onRemove = file => {
59
+ const files = (value || []).filter(v => v.uid !== file.uid)
60
+ onChange?.(files)
61
+ }
62
+
63
+ /**
64
+ * @function getExtraData
65
+ * @description [数据封装] 将动态生成的物理凭证与目标文件路径压入传输负载
66
+ * @param {Object} file - 待传输的文件对象
67
+ */
68
+ const getExtraData = file => ({
69
+ key: file.url,
70
+ policy: OSSData?.policy,
71
+ Signature: OSSData?.signature,
72
+ OSSAccessKeyId: OSSData?.accessKeyId,
73
+ 'x-oss-security-token': OSSData?.stsToken,
74
+ })
75
+
76
+ /**
77
+ * @async
78
+ * @function beforeUpload
79
+ * @description [生存期侦察] 在传输启动前执行 TTL 检测。
80
+ * 若令牌已失效(过期),则强行拦截并执行同步刷新协议,同时执行物理路径归一化。
81
+ */
82
+ const beforeUpload = async file => {
83
+ let currentOSS = OSSData
84
+ const expire = Number(OSSData?.[expireField] ?? 0) * 1000
85
+ if (expire < Date.now()) {
86
+ currentOSS = await init()
87
+ }
88
+ // 💡 物理路径重组:通过正则清除多余的路径分隔符,确保存储节点坐标唯一
89
+ file.url = `${currentOSS?.dir ?? path}/${file.uid}_${file.name}`.replace(/\/\//g, '/')
90
+ return file
91
+ }
92
+
93
+ /**
94
+ * @constant uploadProps
95
+ * @description [物理合并] 最终生成的 Antd Upload 指令集:
96
+ * 自动优先应用 originUploadProps 中的自定义设置。
97
+ */
98
+ const uploadProps = {
99
+ onRemove,
100
+ name: 'file',
101
+ beforeUpload,
102
+ fileList: value,
103
+ data: getExtraData,
104
+ action: OSSData?.host,
105
+ onChange: handleChange,
106
+ ...originUploadProps
107
+ }
108
+
109
+ return (
110
+ <Upload {...uploadProps}>
111
+ <Button icon={<UploadOutlined />}>选择文件</Button>
112
+ </Upload>
113
+ )
114
+ }
@@ -60,7 +60,7 @@ export const EditableRow = ({ index, record, ...props }) => {
60
60
  * @param {boolean} [props.defaultEdit] - 持久化模式:是否默认始终开启编辑状态
61
61
  * @param {string} [props.editType='text'] - 零部件型号(text/number/select/checkbox)
62
62
  */
63
- export const EditableCell = ({ title, record, editable, disabled, children, dataIndex, placeholder, handleSave, defaultEdit, rules = [], options = [], editType = 'text', ...restProps }) => {
63
+ export const EditableCell = ({ title, record, editable, disabled, children, dataIndex, placeholder, handleSave, defaultEdit, editRender, rules = [], options = [], editType = 'text', ...restProps }) => {
64
64
 
65
65
  const inputRef = useRef(null)
66
66
  const form = useContext(EditableContext)
@@ -125,6 +125,9 @@ export const EditableCell = ({ title, record, editable, disabled, children, data
125
125
  case 'number':
126
126
  inputNode = <InputNumber ref={inputRef} onPressEnter={save} {...commonProps} />
127
127
  break
128
+ case 'custom':
129
+ inputNode = editRender({ form, record, save, dataIndex, disabled, toggleEdit, commonProps })
130
+ break
128
131
  case 'checkbox':
129
132
  inputNode = (
130
133
  <div tabIndex={-1} onBlur={e => {
@@ -151,7 +154,7 @@ export const EditableCell = ({ title, record, editable, disabled, children, data
151
154
  {editable ? (
152
155
  editing || defaultEdit ? (
153
156
  // 骇入模式:渲染输入零件
154
- <Form.Item {...(['checkbox'].includes(editType) ? { style: formProps.style } : formProps)}>
157
+ <Form.Item {...(['custom', 'checkbox'].includes(editType) ? { style: formProps.style } : formProps)}>
155
158
  {inputNode}
156
159
  </Form.Item>
157
160
  ) : (
@@ -43,10 +43,10 @@ const ACTION_PROCESSORS = {
43
43
  // prefix: (item) => ({ props: { prefix: <Icon type={item.prefix} /> } }),
44
44
  }
45
45
 
46
- const FormRenderer = ({ form, formItems, tableProps = {} }) => {
46
+ const FormRenderer = ({ form, options, formItems, renderAction, tableProps = {} }) => {
47
47
 
48
48
  // 定义核心渲染逻辑,供递归使用
49
- const renderCore = items => <FormRenderer form={form} formItems={items} tableProps={tableProps} />
49
+ const renderCore = items => <FormRenderer form={form} options={options} formItems={items} renderAction={renderAction} tableProps={tableProps} />
50
50
 
51
51
  // 定义单项动作注入逻辑
52
52
  const getRenderItemProps = item => {
@@ -61,28 +61,35 @@ const FormRenderer = ({ form, formItems, tableProps = {} }) => {
61
61
  }
62
62
 
63
63
  return formItems.map((item, index) => {
64
+ // 💡 保持纯粹不可变:通过解构浅拷贝,绝不污染原始 formItems 对象!
65
+ const processedItem = {
66
+ ...item,
67
+ ...(item.renderAction && renderAction?.[item.name] ? { render: renderAction[item.name] } : {}),
68
+ ...(['select', 'radio', 'checkbox'].includes(item.type) && !item.options ? { options: options?.[item.name] || [] } : {})
69
+ }
70
+
64
71
  // 情况 A:发现容器型零件 (含有 layoutType)
65
- if (item.layoutType && LAYOUT_PROCESSORS[item.layoutType]) {
66
- const Layout = LAYOUT_PROCESSORS[item.layoutType] ?? LAYOUT_PROCESSORS.default
72
+ if (processedItem.layoutType && LAYOUT_PROCESSORS[processedItem.layoutType]) {
73
+ const Layout = LAYOUT_PROCESSORS[processedItem.layoutType] ?? LAYOUT_PROCESSORS.default
67
74
  return (
68
75
  <Layout
69
- item={item}
70
- key={item.name || index}
71
- renderCore={renderCore} // 注入渲染能力,让 Layout 自己去 Map
76
+ item={processedItem}
77
+ key={processedItem.name || index}
78
+ renderCore={renderCore}
72
79
  />
73
80
  )
74
81
  }
75
82
 
76
83
  // 情况 B:标准作战零件
77
84
  return (
78
- <Col span={item.span ?? 6} key={item.name || index}>
79
- <MyBaseForm item={{ ...item, ...getRenderItemProps(item) }} form={form} />
85
+ <Col span={processedItem.span ?? 6} key={processedItem.name || index}>
86
+ <MyBaseForm item={{ ...processedItem, ...getRenderItemProps(processedItem) }} form={form} />
80
87
  </Col>
81
88
  )
82
89
  })
83
90
  }
84
91
 
85
- export const MyForm = ({ formItems, externalForm, labelCol, wrapperCol, tableProps, layout = 'horizontal', onValuesChange }) => {
92
+ export const MyForm = ({ formItems = [], externalForm, labelCol, wrapperCol, tableProps, onValuesChange, layout = 'horizontal', options = {}, renderAction = {} }) => {
86
93
 
87
94
  const [internalForm] = Form.useForm()
88
95
  const form = externalForm || internalForm
@@ -90,8 +97,8 @@ export const MyForm = ({ formItems, externalForm, labelCol, wrapperCol, tablePro
90
97
  return (
91
98
  <Form form={form} layout={layout} preserve={false} labelCol={labelCol} wrapperCol={wrapperCol} onValuesChange={onValuesChange} scrollToFirstError={{ behavior: 'smooth', block: 'center' }}>
92
99
  <Row gutter={[24, 0]}>
93
- <FormRenderer form={form} formItems={formItems} tableProps={tableProps} />
100
+ <FormRenderer form={form} options={options} formItems={formItems} renderAction={renderAction} tableProps={tableProps} />
94
101
  </Row>
95
102
  </Form>
96
103
  )
97
- }
104
+ }
@@ -36,17 +36,12 @@ const flattenFormItems = items => {
36
36
  * title='构筑新模块'
37
37
  * visible={visible}
38
38
  * submit={async (vals) => await save(vals)}
39
- * formItems={[{ label: '截止日期', name: 'deadline', type: 'date' }]}
39
+ * modalItems={[{ label: '截止日期', name: 'deadline', type: 'date' }]}
40
40
  * />
41
41
  */
42
42
  export const MyModalForm = ({ extra, width, title, layout, okText, options, footer, submit, record, visible, setModal, labelCol, modalItems, wrapperCol, cancelText, tableConfig, okButtonProps, renderAction, onValuesChange, handleModalTableOk }) => {
43
43
 
44
44
  const rowKey = tableConfig?.rowKey || 'id'
45
- const formItems = useMemo(() => modalItems.map(item => ({
46
- ...item,
47
- ...(item.renderAction ? { render: renderAction?.[item.name] } : {}),
48
- ...(['select'].includes(item.type) ? { options: item.options ?? options?.[item.name] ?? [] } : {})
49
- })), [options, modalItems, renderAction])
50
45
 
51
46
  const [form] = Form.useForm()
52
47
  const [pending, setPending] = useState(false)
@@ -62,7 +57,7 @@ export const MyModalForm = ({ extra, width, title, layout, okText, options, foot
62
57
  useEffect(() => {
63
58
  if (visible && !prevVisibleRef.current) {
64
59
  if (record && Object.keys(record).length > 0) {
65
- const itemMap = new Map(flattenFormItems(formItems).map(i => [i.name, i]))
60
+ const itemMap = new Map(flattenFormItems(modalItems).map(i => [i.name, i]))
66
61
  const initialData = {}
67
62
  Object.entries(record).forEach(([key, value]) => {
68
63
  const config = itemMap.get(key)
@@ -86,7 +81,7 @@ export const MyModalForm = ({ extra, width, title, layout, okText, options, foot
86
81
  }
87
82
  }
88
83
  prevVisibleRef.current = visible
89
- }, [visible, record, form, formItems])
84
+ }, [visible, record, form, modalItems])
90
85
 
91
86
  /**
92
87
  * @async
@@ -98,7 +93,7 @@ export const MyModalForm = ({ extra, width, title, layout, okText, options, foot
98
93
  try {
99
94
  const values = await form.validateFields()
100
95
  const formattedValues = { ...values }
101
- flattenFormItems(formItems).forEach(item => {
96
+ flattenFormItems(modalItems).forEach(item => {
102
97
  const val = formattedValues[item.name]
103
98
  if (item.type?.includes('date') && val) {
104
99
  if (Array.isArray(val)) {
@@ -159,10 +154,12 @@ export const MyModalForm = ({ extra, width, title, layout, okText, options, foot
159
154
  {extra?.header?.(ctx)}
160
155
  <MyForm
161
156
  layout={layout}
157
+ options={options}
162
158
  externalForm={form}
163
159
  labelCol={labelCol}
164
- formItems={formItems}
160
+ formItems={modalItems}
165
161
  wrapperCol={wrapperCol}
162
+ renderAction={renderAction}
166
163
  tableProps={{ tableConfig, setModalTable, setSelectedTableRows }}
167
164
  onValuesChange={(changed, all) => onValuesChange?.({ changed, all, form, record })}
168
165
  />
@@ -32,7 +32,7 @@ import { SearchOutlined, RedoOutlined } from '@ant-design/icons'
32
32
  * formItems={[{ label: '状态', name: 'status', type: 'select', options: [...] }]}
33
33
  * />
34
34
  */
35
- export const MySearchForm = ({ form, extra, search, options, loading, labelCol, setSearch, formItems, showLimit = 7, initialValues, initialParams, customReset, customFinish, renderAction, onValuesChange, syncUrlParams = true, defaultPageSize = 10 }) => {
35
+ export const MySearchForm = ({ form, extra, search, options, loading, labelCol, setSearch, formItems, showLimit = 7, initialValues = {}, initialParams = {}, customReset, customFinish, renderAction, onValuesChange, syncUrlParams = true, defaultPageSize = 10 }) => {
36
36
 
37
37
  const [limit, setLimit] = useState(showLimit)
38
38
 
@@ -55,7 +55,7 @@ import { useRef, useMemo, useEffect, useCallback } from 'react'
55
55
  * }}
56
56
  * />
57
57
  */
58
- export const MyTable = ({ size, query, total, search, autoScroll, onChange, pagination, renderAction, rowClassName, customSave, setDataSource, lineFormChange, columns = [], rowSelection, rowKey = 'id', loading = false, dataSource = [], draggable = false, scroll = { x: 'max-content' }, isLocalPaging = false, ...restProps }) => {
58
+ export const MyTable = ({ size, query, total, search, options, autoScroll, onChange, setSearch, pagination, editRender = {}, renderAction, rowClassName, customSave, setDataSource, lineFormChange, columns = [], rowSelection, rowKey = 'id', loading = false, dataSource = [], draggable = false, scroll = { x: 'max-content' }, isLocalPaging = false, ...restProps }) => {
59
59
 
60
60
  const tableRef = useRef(null)
61
61
  const hasScrolledRef = useRef(false)
@@ -115,30 +115,53 @@ export const MyTable = ({ size, query, total, search, autoScroll, onChange, pagi
115
115
  const mergedColumns = useMemo(() => {
116
116
  return columns.map(col => {
117
117
  if (!col.editType) {
118
+ let currentCol = { ...col }
118
119
  if (col.renderAction && renderAction?.[col.dataIndex]) {
119
- return { ...col, render: renderAction[col.dataIndex] }
120
+ currentCol.render = renderAction[col.dataIndex]
120
121
  }
121
- return col
122
+ if (col.search) {
123
+ currentCol = {
124
+ ...currentCol,
125
+ filterSearch: true,
126
+ onFilter: (value, record) => col.remote ? undefined : col.customFilter({ value, record, dataIndex: col.dataIndex }),
127
+ filterDropdown: ({ close, confirm, selectedKeys, clearFilters, setSelectedKeys }) => (
128
+ <Space direction='vertical' style={{ padding: 12 }}>
129
+ {col.customFilterDropdown({ dataIndex: col.dataIndex, selectedKeys, setSelectedKeys })}
130
+ <div style={{ display: 'flex' }}>
131
+ <a style={{ marginLeft: 'auto', marginRight: 8 }} onClick={() => {
132
+ if (col.remote) {
133
+ setSearch(prev => ({ ...prev, pageNo: 1, [col.dataIndex]: col.formatFilter?.(selectedKeys) ?? selectedKeys?.toString() }))
134
+ }
135
+ confirm()
136
+ }}>确定</a>
137
+ <a onClick={() => {
138
+ if (col.remote) {
139
+ setSearch(prev => ({ ...prev, pageNo: 1, [col.dataIndex]: undefined }))
140
+ }
141
+ clearFilters()
142
+ }}>重置</a>
143
+ </div>
144
+ </Space>
145
+ ),
146
+ }
147
+ }
148
+ return currentCol
122
149
  }
123
150
  return {
124
151
  ...col,
125
152
  onCell: (record) => ({
153
+ ...col,
126
154
  record,
127
155
  handleSave,
128
- title: col.title,
129
- rules: col.rules,
130
- options: col.options,
131
- editType: col.editType,
132
- dataIndex: col.dataIndex,
133
- placeholder: col.placeholder,
134
- defaultEdit: col.defaultEdit,
156
+ editRender: editRender?.[col.dataIndex],
157
+ options: col.options ?? options?.[col.dataIndex],
135
158
  // 💡 条件防御逻辑:支持针对单行的物理锁定
136
159
  disabled: col.specialDisabled ? record.disabled : col.disabled,
137
160
  editable: col.specialEditType ? record.needEdit && !!col.editType : !!col.editType,
138
161
  }),
139
162
  }
140
163
  })
141
- }, [columns, handleSave, renderAction])
164
+ }, [columns, options, handleSave, editRender, renderAction])
142
165
 
143
166
  /**
144
167
  * @constant paginationConfig
@@ -1,117 +1,5 @@
1
- import { initOSS } from '../utils/utils'
2
- import { useState, useEffect } from 'react'
3
- import { UploadOutlined } from '@ant-design/icons'
4
- import { Button, Tree, Radio, Input, Upload, Select, Cascader, Checkbox, DatePicker, InputNumber, TreeSelect, AutoComplete } from 'antd'
5
-
6
- /**
7
- * @component AliyunOSSUpload
8
- * @description [地堡核心传输单元] 阿里云 OSS 高级上传组件。
9
- * 集成了“物理路径自动重组”、“STS 令牌自动续期”以及“ Antd 属性无损透传”三大核心协议。
10
- *
11
- * @param {Object} props - 构筑参数
12
- * @param {string} [props.path] - 默认存储扇区:定义文件在云端的物理根路径
13
- * @param {string} [props.url] - 司令部通信地址:用于申请最新的物理传输令牌 (STS)
14
- * @param {Array} props.value - 传感器当前封存的文件列表 (符合 Antd fileList 规范)
15
- * @param {Function} props.onChange - 状态反馈协议:当物理文件状态变更时同步至地堡中枢
16
- * @param {Object} [props.oss] - 外部空投的初始凭证包。若缺失,组件将启动自主初始化流程。
17
- * @param {Object} [props.options] - 通信加密/配置参数:在申请令牌时透传给 request 的额外负载
18
- * @param {Object} [props.originUploadProps] - [重要] 无损透传:支持 Antd Upload 的所有原生属性(如 multiple, accept 等)
19
- *
20
- * @example
21
- * // 基本构筑
22
- * <AliyunOSSUpload path='project/data' url='/api/oss/token' />
23
- *
24
- * // 高级装配(限制文件类型并开启多选)
25
- * <AliyunOSSUpload accept='.pdf' multiple={true} path='docs' url='/api/oss/token' />
26
- */
27
- const AliyunOSSUpload = ({ value, onChange, ...restProps }) => {
28
-
29
- // 💡 物理剥离:将地堡专用参数与 Antd 原生属性分离
30
- const { oss, url, path, options, expireField = 'expire', ...originUploadProps } = restProps
31
-
32
- const [OSSData, setOSSData] = useState(oss)
33
-
34
- /**
35
- * @async
36
- * @function init
37
- * @description [信号握手] 执行令牌获取协议,从司令部申请最新的物理上传授权
38
- */
39
- const init = async () => {
40
- try {
41
- const result = await initOSS(url, options)
42
- setOSSData(result)
43
- return result
44
- } catch (err) {
45
- console.log(err)
46
- }
47
- }
48
-
49
- useEffect(() => {
50
- // 💡 自愈逻辑:如果发现能源包 (OSSData) 为空,则自动触发初始化
51
- if (!OSSData) {
52
- init()
53
- }
54
- }, [])
55
-
56
- const handleChange = ({ fileList }) => onChange?.([...fileList])
57
-
58
- const onRemove = file => {
59
- const files = (value || []).filter(v => v.uid !== file.uid)
60
- onChange?.(files)
61
- }
62
-
63
- /**
64
- * @function getExtraData
65
- * @description [数据封装] 将动态生成的物理凭证与目标文件路径压入传输负载
66
- * @param {Object} file - 待传输的文件对象
67
- */
68
- const getExtraData = file => ({
69
- key: file.url,
70
- policy: OSSData?.policy,
71
- Signature: OSSData?.signature,
72
- OSSAccessKeyId: OSSData?.accessKeyId,
73
- 'x-oss-security-token': OSSData?.stsToken,
74
- })
75
-
76
- /**
77
- * @async
78
- * @function beforeUpload
79
- * @description [生存期侦察] 在传输启动前执行 TTL 检测。
80
- * 若令牌已失效(过期),则强行拦截并执行同步刷新协议,同时执行物理路径归一化。
81
- */
82
- const beforeUpload = async file => {
83
- let currentOSS = OSSData
84
- const expire = Number(OSSData?.[expireField] ?? 0) * 1000
85
- if (expire < Date.now()) {
86
- currentOSS = await init()
87
- }
88
- // 💡 物理路径重组:通过正则清除多余的路径分隔符,确保存储节点坐标唯一
89
- file.url = `${currentOSS?.dir ?? path}/${file.uid}_${file.name}`.replace(/\/\//g, '/')
90
- return file
91
- }
92
-
93
- /**
94
- * @constant uploadProps
95
- * @description [物理合并] 最终生成的 Antd Upload 指令集:
96
- * 自动优先应用 originUploadProps 中的自定义设置。
97
- */
98
- const uploadProps = {
99
- onRemove,
100
- name: 'file',
101
- beforeUpload,
102
- fileList: value,
103
- data: getExtraData,
104
- action: OSSData?.host,
105
- onChange: handleChange,
106
- ...originUploadProps
107
- }
108
-
109
- return (
110
- <Upload {...uploadProps}>
111
- <Button icon={<UploadOutlined />}>选择文件</Button>
112
- </Upload>
113
- )
114
- }
1
+ import { AliyunOSSUpload } from './AliyunOSSUpload'
2
+ import { Tree, Form, Radio, Input, Upload, Select, Cascader, Checkbox, DatePicker, InputNumber, TreeSelect, AutoComplete } from 'antd'
115
3
 
116
4
  /**
117
5
  * @function formNode
@@ -130,6 +18,18 @@ const AliyunOSSUpload = ({ value, onChange, ...restProps }) => {
130
18
  */
131
19
  export const formNode = ({ item }) => {
132
20
 
21
+ if (item.type === 'group' && Array.isArray(item.groups)) {
22
+ return (
23
+ <Input.Group compact style={{ width: item.width ?? '100%', display: 'flex', ...item.style }}>
24
+ {item.groups.map(subItem => (
25
+ <Form.Item noStyle key={subItem.name} name={subItem.name} rules={subItem.rules} initialValue={subItem.value}>
26
+ {formNode({ item: subItem })}
27
+ </Form.Item>
28
+ ))}
29
+ </Input.Group>
30
+ )
31
+ }
32
+
133
33
  const commonProps = {
134
34
  disabled: item.readOnly,
135
35
  style: { width: item.width ?? '100%' },
@@ -137,34 +37,22 @@ export const formNode = ({ item }) => {
137
37
  ...item.props
138
38
  }
139
39
 
140
- switch (item.type) {
141
- case 'date':
142
- return <DatePicker {...commonProps} />
143
- case 'number':
144
- return <InputNumber {...commonProps} />
145
- case 'ossUpload':
146
- return <AliyunOSSUpload {...commonProps} />
147
- case 'radio':
148
- return <Radio.Group options={item.options} {...commonProps} />
149
- case 'treeSelect':
150
- return <TreeSelect treeData={item.options} {...commonProps} />
151
- case 'auto':
152
- return <AutoComplete options={item.options} {...commonProps} />
153
- case 'checkbox':
154
- return <Checkbox.Group options={item.options} {...commonProps} />
155
- case 'tree':
156
- return <Tree checkable treeData={item.options} {...commonProps} />
157
- case 'textarea':
158
- return <Input.TextArea autoSize={{ minRows: 4 }} {...commonProps} />
159
- case 'daterange':
160
- return <DatePicker.RangePicker {...commonProps} placeholder={undefined} />
161
- case 'cascader':
162
- return <Cascader showSearch allowClear options={item.options} {...commonProps} />
163
- case 'upload':
164
- return <Upload {...item.uploadProps} {...commonProps}>{item.content ?? '上传文件'}</Upload>
165
- case 'select':
166
- return <Select allowClear showSearch mode={item.mode} options={item.options} optionFilterProp='label' {...commonProps} />
167
- default:
168
- return <Input allowClear {...commonProps} />
40
+ const componentDict = {
41
+ date: <DatePicker {...commonProps} />,
42
+ number: <InputNumber {...commonProps} />,
43
+ default: <Input allowClear {...commonProps} />,
44
+ ossUpload: <AliyunOSSUpload {...commonProps} />,
45
+ auto: <AutoComplete options={item.options} {...commonProps} />,
46
+ radio: <Radio.Group options={item.options} {...commonProps} />,
47
+ tree: <Tree checkable treeData={item.options} {...commonProps} />,
48
+ treeSelect: <TreeSelect treeData={item.options} {...commonProps} />,
49
+ checkbox: <Checkbox.Group options={item.options} {...commonProps} />,
50
+ textarea: <Input.TextArea autoSize={{ minRows: 4 }} {...commonProps} />,
51
+ daterange: <DatePicker.RangePicker {...commonProps} placeholder={undefined} />,
52
+ cascader: <Cascader showSearch allowClear options={item.options} {...commonProps} />,
53
+ upload: <Upload {...item.uploadProps} {...commonProps}>{item.content ?? '上传文件'}</Upload>,
54
+ select: <Select allowClear showSearch mode={item.mode} options={item.options} optionFilterProp='label' {...commonProps} />,
169
55
  }
56
+
57
+ return componentDict[item.type] ?? componentDict.default
170
58
  }