@yorha2b-lab/autodev 7.7.8 → 7.8.0

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.
Files changed (31) hide show
  1. package/bin/bunker.js +2 -0
  2. package/config.js +2 -0
  3. package/package.json +1 -1
  4. package/src/constitution/inspector.js +20 -0
  5. package/src/constitution/system.js +2 -8
  6. package/src/dialogues/EN-US.js +8 -0
  7. package/src/dialogues/FR-FR.js +8 -0
  8. package/src/dialogues/JA-JP.js +8 -0
  9. package/src/dialogues/RU-RU.js +8 -0
  10. package/src/dialogues/ZH-CN.js +8 -0
  11. package/src/gateway/client.js +1 -1
  12. package/src/gateway/index.js +17 -7
  13. package/src/labs/index.js +5 -1
  14. package/src/labs/inspector.js +183 -0
  15. package/src/labs/scout.js +0 -2
  16. package/src/logistics/formatter.js +32 -0
  17. package/src/workshop/react/builder/index.js +1 -1
  18. package/src/workshop/react/extensions/useTableQuery.js +0 -51
  19. package/src/workshop/react/kit/utils.js +51 -102
  20. package/src/workshop/react/standardParts/AliyunOSSUpload.js +0 -45
  21. package/src/workshop/react/standardParts/DraggableContainer.js +1 -1
  22. package/src/workshop/react/standardParts/EditableCell.js +31 -73
  23. package/src/workshop/react/standardParts/MyBaseForm.js +1 -48
  24. package/src/workshop/react/standardParts/MyDescription.js +2 -2
  25. package/src/workshop/react/standardParts/MyForm.js +41 -15
  26. package/src/workshop/react/standardParts/MyModalForm.js +3 -84
  27. package/src/workshop/react/standardParts/MyModalTable.js +0 -53
  28. package/src/workshop/react/standardParts/MyPopoverTable.js +23 -0
  29. package/src/workshop/react/standardParts/MySearchForm.js +2 -60
  30. package/src/workshop/react/standardParts/MyTable.js +38 -122
  31. package/src/workshop/react/standardParts/index.js +5 -18
@@ -1,19 +1,6 @@
1
1
  import dayjs from 'dayjs'
2
2
  import { request } from './request'
3
3
 
4
- /**
5
- * @namespace BunkerUtils
6
- * @description 地堡核心工具集:包含文件处理、数据脱水、视觉渲染及后勤传输协议;使用前确保已安装对应模块(如:xlsx、ali-oss、crypto-js)
7
- * */
8
-
9
-
10
- /**
11
- * @async
12
- * @function parseExcel
13
- * @description [后勤部] 本地 Excel 解析协议:将二进制表格物资转化为地堡可读的 JSON 序列。
14
- * @param {File|Blob} file - 待解析的原始文件对象
15
- * @returns {Promise<Array[]>} 解析后的二维数组数据
16
- */
17
4
  export const parseExcel = async file => {
18
5
  /* const arrayBuffer = await file.arrayBuffer()
19
6
  const XLSX = require('xlsx')
@@ -24,14 +11,16 @@ export const parseExcel = async file => {
24
11
  return excelData.filter(item => item.length > 0) */
25
12
  }
26
13
 
27
- /**
28
- * @async
29
- * @function initOSS
30
- * @description [出口协议] 阿里云 OSS 凭证初始化:从司令部申请 STS 令牌并计算物理传输签名。
31
- * @param {string} url - 令牌申请地址
32
- * @param {Object} options - 请求配置参数
33
- * @returns {Promise<Object>} 包含 OSS 客户端实例、上传策略及 Host 节点的配置包
34
- */
14
+ export const flattenFormItems = items => {
15
+ if (!Array.isArray(items)) return []
16
+ return items.flatMap(item => {
17
+ if (item.childItems) {
18
+ return flattenFormItems(item.childItems)
19
+ }
20
+ return item
21
+ })
22
+ }
23
+
35
24
  export const initOSS = async (url, options) => {
36
25
  /* const OSS = require('ali-oss')
37
26
  const crypto = require('crypto-js')
@@ -48,13 +37,6 @@ export const initOSS = async (url, options) => {
48
37
  return { client, policy, signature,host: `https://${bucket}.${endpoint.split('//')[1]}`, ...response?.data } */
49
38
  }
50
39
 
51
- /**
52
- * @function formatQuery
53
- * @description [数据脱水] 搜索参数校准协议:根据零部件类型(如日期)自动执行类型转换(String -> Number)。
54
- * @param {Array} formItems - 对应的零部件配置清单
55
- * @param {Object} params - 原始表单/URL 传感器数据
56
- * @returns {Object} 经过物理清洗、可直接用于 API 请求的参数包
57
- */
58
40
  export const formatQuery = (params, formItems) => {
59
41
  const cleanParams = {}
60
42
  Object.keys(params).forEach(key => {
@@ -67,15 +49,6 @@ export const formatQuery = (params, formItems) => {
67
49
  return cleanParams
68
50
  }
69
51
 
70
- /**
71
- * @function timeRender
72
- * @description [视觉渲染] 时间戳格式化:将冷冰冰的数字转化为人类可读的时间字符串。
73
- * @param {Object} params - 渲染参数
74
- * @param {number|string} params.time - 原始时间戳
75
- * @param {boolean} [params.date] - 是否仅保留日期(YYYY-MM-DD)
76
- * @param {boolean} [params.minute] - 是否精确到分钟(YYYY-MM-DD HH:mm)
77
- * @returns {string} 格式化后的时间字符串
78
- */
79
52
  export const timeRender = ({ time, date, minute }) => {
80
53
  if (!time) {
81
54
  return ''
@@ -90,16 +63,6 @@ export const timeRender = ({ time, date, minute }) => {
90
63
  }
91
64
  }
92
65
 
93
- /**
94
- * @async
95
- * @function exportDataToExcel
96
- * @description [物资外运] 数据表格导出协议:支持复杂的渲染逻辑回溯,将页面数据封存为 .xlsx 格式。
97
- * @param {string} url - 数据源地址
98
- * @param {Object} options - 请求参数
99
- * @param {string} fileName - 导出的文件名
100
- * @param {Function} [formatter] - 数据预处理回调
101
- * @param {Array} columns - 表格列配置,支持 exportRender 优先级
102
- */
103
66
  export const exportDataToExcel = async ({ url, options, columns, fileName, formatter }) => {
104
67
  /* const response = await request(url, options)
105
68
  const result = formatter ? formatter(response) : (response?.data ?? [])
@@ -125,16 +88,47 @@ export const exportDataToExcel = async ({ url, options, columns, fileName, forma
125
88
  } */
126
89
  }
127
90
 
128
- /**
129
- * @async
130
- * @function streamDownload
131
- * @description [后勤传输] 智能流式下载协议:具备“语义侦察”能力,能自动识别被伪装成二进制流的 JSON 报错信息。
132
- * @param {Object} params - 传输配置
133
- * @param {string} params.url - 物资下载地址
134
- * @param {Object} [params.options] - Fetch 配置
135
- * @param {Object} [params.headers] - 自定义请求头
136
- * @param {string} [fileName='下载'] - 预设文件名(支持从 Content-Disposition 自动对齐)
137
- */
91
+ export const arrayToTree = (list, levels, keys = { title: 'title', value: 'value', children: 'children' }) => {
92
+ const tree = []
93
+ const maps = levels.map(() => new Map())
94
+ list.forEach(item => {
95
+ let parent = tree
96
+ levels.forEach(({ code, name }, index) => {
97
+ const map = maps[index]
98
+ const id = item[code]
99
+ if (!map.has(id)) {
100
+ const node = { [keys.title]: item[name], [keys.value]: id, ...(index < levels.length - 1 ? { [keys.children]: [] } : {}) }
101
+ map.set(id, node)
102
+ parent.push(node)
103
+ }
104
+ const node = map.get(id)
105
+ parent = node[keys.children] ?? []
106
+ })
107
+ })
108
+ return tree
109
+ }
110
+
111
+ export const moneyRender = (value, { decimals = 2, currency = 'CNY', language = 'zh-CN', showSymbol = true } = {}) => {
112
+ if (value === null || value === undefined || value === '') return '-'
113
+ const number = Number(value)
114
+ if (isNaN(number)) return '-'
115
+ const options = showSymbol
116
+ ? { style: 'currency', currency, minimumFractionDigits: decimals }
117
+ : { style: 'decimal', minimumFractionDigits: decimals, maximumFractionDigits: decimals }
118
+ return new Intl.NumberFormat(language, options).format(number)
119
+ }
120
+
121
+ export const formatUnit = (value, { k = 1024, units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'], decimals = 2 } = {}) => {
122
+ if (value === null || value === undefined || isNaN(value) || value === 0) {
123
+ return `0 ${units[0] ?? ''}`
124
+ }
125
+ const absValue = Math.abs(value)
126
+ const i = absValue < 1 ? 0 : Math.floor(Math.log(absValue) / Math.log(k))
127
+ const index = Math.min(i, units.length - 1)
128
+ const result = (value / Math.pow(k, index)).toFixed(decimals)
129
+ return `${parseFloat(result)} ${units[index]}`
130
+ }
131
+
138
132
  export const streamDownload = async ({ url, options, headers = { 'Content-Type': 'application/json' } }, fileName = '下载') => {
139
133
 
140
134
  const response = await fetch(url, { ...options, headers })
@@ -151,7 +145,6 @@ export const streamDownload = async ({ url, options, headers = { 'Content-Type':
151
145
  return
152
146
  }
153
147
 
154
- // 💡 语义侦察逻辑:检查前 10 个字节,判断是否为 JSON 报错信息
155
148
  const slice = blob.slice(0, 10)
156
149
  const text = (await slice.text()).trim()
157
150
 
@@ -166,7 +159,6 @@ export const streamDownload = async ({ url, options, headers = { 'Content-Type':
166
159
  return
167
160
  }
168
161
 
169
- // 物理解析 Content-Disposition 头部,获取指挥官预设的文件名
170
162
  const disposition = response.headers.get('content-disposition')
171
163
  let name = fileName
172
164
 
@@ -188,47 +180,4 @@ export const streamDownload = async ({ url, options, headers = { 'Content-Type':
188
180
  link.remove()
189
181
 
190
182
  setTimeout(() => URL.revokeObjectURL(urlObj), 1000)
191
- }
192
-
193
- /**
194
- * @function moneyRender
195
- * @description [视觉渲染] 金额校准渲染:支持多国语境、货币符号及精度控制。
196
- * @param {number|string} value - 原始金额数值
197
- * @param {Object} [options] - 配置参数
198
- * @param {number} [options.decimals=2] - 小数位数
199
- * @param {string} [options.currency='CNY'] - 货币代号
200
- * @param {string} [options.language='zh-CN'] - 语言环境
201
- * @param {boolean} [options.showSymbol=true] - 是否显示货币符号
202
- * @returns {string} 格式化后的金额文本
203
- */
204
- export const moneyRender = (value, { decimals = 2, currency = 'CNY', language = 'zh-CN', showSymbol = true } = {}) => {
205
- if (value === null || value === undefined || value === '') return '-'
206
- const number = Number(value)
207
- if (isNaN(number)) return '-'
208
- const options = showSymbol
209
- ? { style: 'currency', currency, minimumFractionDigits: decimals }
210
- : { style: 'decimal', minimumFractionDigits: decimals, maximumFractionDigits: decimals }
211
- return new Intl.NumberFormat(language, options).format(number)
212
- }
213
-
214
- /**
215
- * @function formatUnit
216
- * @description [视觉渲染] 单位自动进阶协议:实现 B/KB/MB... 等数据的物理量级自动折算。
217
- * @param {number} value - 原始字节数值
218
- * @param {Object} [options] - 配置参数
219
- * @param {Array} [options.units] - 单位阶梯
220
- * @param {number} [options.k=1024] - 进位系数
221
- * @param {number} [options.decimals=2] - 保留小数位
222
- * @returns {string} 带单位的物理量级文本
223
- */
224
- export const formatUnit = (value, { k = 1024, units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'], decimals = 2 } = {}) => {
225
- if (value === null || value === undefined || isNaN(value) || value === 0) {
226
- return `0 ${units[0] ?? ''}`
227
- }
228
- const absValue = Math.abs(value)
229
- // 💡 安全屏障:小于 1 的数值直接归入初始级单位,不进行对数拆解
230
- const i = absValue < 1 ? 0 : Math.floor(Math.log(absValue) / Math.log(k))
231
- const index = Math.min(i, units.length - 1)
232
- const result = (value / Math.pow(k, index)).toFixed(decimals)
233
- return `${parseFloat(result)} ${units[index]}`
234
183
  }
@@ -3,39 +3,12 @@ import { initOSS } from '../utils/utils'
3
3
  import { useState, useEffect } from 'react'
4
4
  import { UploadOutlined } from '@ant-design/icons'
5
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
6
  export const AliyunOSSUpload = ({ value, onChange, ...restProps }) => {
28
7
 
29
- // 💡 物理剥离:将地堡专用参数与 Antd 原生属性分离
30
8
  const { oss, url, path, options, expireField = 'expire', ...originUploadProps } = restProps
31
9
 
32
10
  const [OSSData, setOSSData] = useState(oss)
33
11
 
34
- /**
35
- * @async
36
- * @function init
37
- * @description [信号握手] 执行令牌获取协议,从司令部申请最新的物理上传授权
38
- */
39
12
  const init = async () => {
40
13
  try {
41
14
  const result = await initOSS(url, options)
@@ -47,7 +20,6 @@ export const AliyunOSSUpload = ({ value, onChange, ...restProps }) => {
47
20
  }
48
21
 
49
22
  useEffect(() => {
50
- // 💡 自愈逻辑:如果发现能源包 (OSSData) 为空,则自动触发初始化
51
23
  if (!OSSData) {
52
24
  init()
53
25
  }
@@ -60,11 +32,6 @@ export const AliyunOSSUpload = ({ value, onChange, ...restProps }) => {
60
32
  onChange?.(files)
61
33
  }
62
34
 
63
- /**
64
- * @function getExtraData
65
- * @description [数据封装] 将动态生成的物理凭证与目标文件路径压入传输负载
66
- * @param {Object} file - 待传输的文件对象
67
- */
68
35
  const getExtraData = file => ({
69
36
  key: file.url,
70
37
  policy: OSSData?.policy,
@@ -73,28 +40,16 @@ export const AliyunOSSUpload = ({ value, onChange, ...restProps }) => {
73
40
  'x-oss-security-token': OSSData?.stsToken,
74
41
  })
75
42
 
76
- /**
77
- * @async
78
- * @function beforeUpload
79
- * @description [生存期侦察] 在传输启动前执行 TTL 检测。
80
- * 若令牌已失效(过期),则强行拦截并执行同步刷新协议,同时执行物理路径归一化。
81
- */
82
43
  const beforeUpload = async file => {
83
44
  let currentOSS = OSSData
84
45
  const expire = Number(OSSData?.[expireField] ?? 0) * 1000
85
46
  if (expire < Date.now()) {
86
47
  currentOSS = await init()
87
48
  }
88
- // 💡 物理路径重组:通过正则清除多余的路径分隔符,确保存储节点坐标唯一
89
49
  file.url = `${currentOSS?.dir ?? path}/${file.uid}_${file.name}`.replace(/\/\//g, '/')
90
50
  return file
91
51
  }
92
52
 
93
- /**
94
- * @constant uploadProps
95
- * @description [物理合并] 最终生成的 Antd Upload 指令集:
96
- * 自动优先应用 originUploadProps 中的自定义设置。
97
- */
98
53
  const uploadProps = {
99
54
  onRemove,
100
55
  name: 'file',
@@ -16,7 +16,7 @@ export const DraggableContainer = ({ props, customSave, setDataSource, dataSourc
16
16
  const currentData = dataSourceRef?.current || []
17
17
  const sortedDataSource = arrayMoveImmutable(currentData.slice(), oldIndex, newIndex)
18
18
  setDataSource(sortedDataSource)
19
- customSave?.(sortedDataSource)
19
+ customSave?.({}, sortedDataSource)
20
20
  }
21
21
  }
22
22
 
@@ -1,35 +1,20 @@
1
1
  import { SortableElement } from 'react-sortable-hoc'
2
- import { Form, Input, InputNumber, Select, Checkbox } from 'antd'
3
- import React, { useRef, useState, useEffect, useContext } from 'react'
2
+ import { Form, Input, InputNumber, Select, Checkbox, TreeSelect } from 'antd'
3
+ import { useRef, useState, useEffect, useContext, createContext } from 'react'
4
4
 
5
- /**
6
- * @constant EditableContext
7
- * @description [地堡跨组件通信协议] 用于在行(Row)与单元格(Cell)之间共享 Form 传感器的物理句柄。
8
- */
9
- const EditableContext = React.createContext(null)
5
+ const EditableContext = createContext(null)
10
6
 
11
7
  const SortableItem = SortableElement((props) => <tr {...props} />)
12
8
 
13
- /**
14
- * @component EditableRow
15
- * @description [地堡战术行容器] 每一行都是一个独立的逻辑闭环。
16
- * 负责初始化 Form 实例并执行“行级数据同步”与“变动传感监听”。
17
- *
18
- * @param {Object} props - 组件属性
19
- * @param {number} props.index - 行物理索引
20
- * @param {Object} props.record - 初始物资包(当前行数据)
21
- * @param {Function} [props.onValuesChange] - 联动传感:监听行内任何零部件的值变动
22
- */
23
- export const EditableRow = ({ index, record, draggable, ...props }) => {
9
+ export const EditableRow = ({ index, record, columns, draggable, ...props }) => {
24
10
 
25
11
  const [form] = Form.useForm()
26
12
 
27
- /**
28
- * @description [数据热更协议] 当外部记录发生变化时,物理同步至内部表单传感器。
29
- */
30
13
  useEffect(() => {
31
14
  if (record) {
32
- form.setFieldsValue(record)
15
+ const modeCol = columns.find(col => col.dataIndex in record && col.mode)
16
+ const extra = modeCol ? { [modeCol.dataIndex]: record[modeCol.dataIndex]?.toString()?.split(',') || [] } : {}
17
+ form.setFieldsValue({ ...record, ...extra })
33
18
  }
34
19
  }, [record])
35
20
 
@@ -42,34 +27,14 @@ export const EditableRow = ({ index, record, draggable, ...props }) => {
42
27
  )
43
28
  }
44
29
 
45
- /**
46
- * @component EditableCell
47
- * @description [地堡精密编辑单元] 最小物理作战单位。
48
- * 支持多种装配模式(text/number/select/checkbox),并具备“自动对焦”与“失焦自愈(自动保存)”功能。
49
- *
50
- * @param {Object} props - 构筑参数
51
- * @param {Array} [props.rules=[]] - 逻辑校验准则
52
- * @param {Object} props.record - 当前行原始物资数据
53
- * @param {boolean} props.disabled - 物理锁定:是否禁止修改
54
- * @param {string} props.title - 零部件视觉标题,兼作占位符使用
55
- * @param {React.ReactNode} props.children - 默认展示的视觉节点
56
- * @param {boolean} props.editable - 权限协议:是否允许开启编辑模式
57
- * @param {string} props.dataIndex - 逻辑寻址地址:对应后端数据的字段名
58
- * @param {Array} [props.options=[]] - 数据字典(仅用于 select/checkbox)
59
- * @param {Function} props.handleSave - 物理封存协议:数据校验通过后的保存回调
60
- * @param {boolean} [props.defaultEdit] - 持久化模式:是否默认始终开启编辑状态
61
- * @param {string} [props.editType='text'] - 零部件型号(text/number/select/checkbox)
62
- */
63
- export const EditableCell = ({ title, record, editable, disabled, children, dataIndex, placeholder, handleSave, defaultEdit, editRender, rules = [], options = [], editType = 'text', ...restProps }) => {
30
+ export const EditableCell = ({ mode, title, record, rowKey, editMode, editable, disabled, children, dataIndex, editingKey, dependencies, placeholder, handleSave, defaultEdit, editRender, rules = [], options = [], editType = 'text', ...restProps }) => {
64
31
 
65
32
  const inputRef = useRef(null)
33
+ const editingKeyRef = useRef(editingKey)
66
34
  const form = useContext(EditableContext)
67
35
 
68
36
  const [editing, setEditing] = useState(false)
69
37
 
70
- /**
71
- * @description [自动对焦协议] 当单元格进入“骇入模式”时,光标自动锁定,提升作战效率。
72
- */
73
38
  useEffect(() => {
74
39
  if (editing) {
75
40
  if (inputRef.current && typeof inputRef.current.focus === 'function') {
@@ -78,23 +43,24 @@ export const EditableCell = ({ title, record, editable, disabled, children, data
78
43
  }
79
44
  }, [editing])
80
45
 
81
- /**
82
- * @function toggleEdit
83
- * @description 切换“观察/骇入”模式,并执行初始信号同步。
84
- */
46
+ useEffect(() => {
47
+ if (editingKeyRef.current !== editingKey) {
48
+ editingKeyRef.current = editingKey
49
+ inputRef.current?.blur()
50
+ }
51
+ }, [editingKey])
52
+
85
53
  const toggleEdit = () => {
86
- setEditing(!editing)
87
- form.setFieldsValue({ [dataIndex]: record[dataIndex] })
54
+ setEditing(prev => !prev)
55
+ form.setFieldsValue({ [dataIndex]: mode ? record[dataIndex]?.toString()?.split(',') || [] : record[dataIndex] })
88
56
  }
89
57
 
90
- /**
91
- * @async
92
- * @function save
93
- * @description 执行“物理封存协议”:触发表单校验,通过后自动关闭编辑状态并向中枢发送保存请求。
94
- */
95
58
  const save = async () => {
96
59
  try {
97
60
  const values = await form.validateFields()
61
+ if (mode) {
62
+ values[dataIndex] = values[dataIndex].join(',')
63
+ }
98
64
  if (!defaultEdit) {
99
65
  toggleEdit()
100
66
  }
@@ -108,6 +74,7 @@ export const EditableCell = ({ title, record, editable, disabled, children, data
108
74
 
109
75
  const formProps = {
110
76
  rules,
77
+ dependencies,
111
78
  name: dataIndex,
112
79
  style: { margin: 0 },
113
80
  }
@@ -119,22 +86,21 @@ export const EditableCell = ({ title, record, editable, disabled, children, data
119
86
  }
120
87
 
121
88
  switch (editType) {
122
- case 'select':
123
- inputNode = <Select ref={inputRef} options={options} {...commonProps} />
124
- break;
125
89
  case 'number':
126
90
  inputNode = <InputNumber ref={inputRef} onPressEnter={save} {...commonProps} />
127
91
  break
92
+ case 'select':
93
+ inputNode = <Select ref={inputRef} mode={mode} options={options} {...commonProps} />
94
+ break;
95
+ case 'treeSelect':
96
+ inputNode = <TreeSelect ref={inputRef} treeData={options} {...commonProps} />
97
+ break;
128
98
  case 'custom':
129
99
  inputNode = editRender({ form, record, save, dataIndex, disabled, toggleEdit, commonProps })
130
100
  break
131
101
  case 'checkbox':
132
102
  inputNode = (
133
103
  <div tabIndex={-1} onBlur={e => {
134
- /**
135
- * 💡 [地堡特有防御逻辑]:解决 Checkbox.Group 内部焦点切换导致误触发 save 的问题。
136
- * 通过 contains 判定焦点是否真正离开了当前作战区域。
137
- */
138
104
  if (!e.currentTarget.contains(e.relatedTarget)) {
139
105
  save()
140
106
  }
@@ -152,17 +118,9 @@ export const EditableCell = ({ title, record, editable, disabled, children, data
152
118
  return (
153
119
  <td {...restProps}>
154
120
  {editable ? (
155
- editing || defaultEdit ? (
156
- // 骇入模式:渲染输入零件
157
- <Form.Item {...(['custom', 'checkbox'].includes(editType) ? { style: formProps.style } : formProps)}>
158
- {inputNode}
159
- </Form.Item>
160
- ) : (
161
- // 观察模式:渲染静态内容,点击开启骇入
162
- <div onClick={toggleEdit} className='editable-cell-value-wrap' style={{ paddingRight: 24, minHeight: 32, cursor: 'pointer' }}>
163
- {children}
164
- </div>
165
- )
121
+ editing || defaultEdit || record[rowKey] === editingKey ?
122
+ <Form.Item {...(['custom', 'checkbox'].includes(editType) ? { style: formProps.style } : formProps)}>{inputNode}</Form.Item> :
123
+ <div {...(editMode === 'cellEdit' ? { onClick: toggleEdit, className: 'editable-cell-value-wrap' } : {})} style={{ paddingRight: 24, minHeight: 32, cursor: 'pointer' }}>{children}</div>
166
124
  ) : (children)}
167
125
  </td>
168
126
  )
@@ -3,13 +3,6 @@ import { formNode } from './index'
3
3
  import { Form, Button } from 'antd'
4
4
  import { PlusOutlined } from '@ant-design/icons'
5
5
 
6
- /**
7
- * @function getValueFromEvent
8
- * @description [数据拦截协议] 专门针对上传类组件的物理补丁。
9
- * 修正 Antd 默认将 Event 对象存入 Form 的行为,强制提取物理文件列表 (fileList)。
10
- * @param {Object|Array} e - 原始触发事件或文件数组
11
- * @returns {Array} 归一化后的文件列表
12
- */
13
6
  const getValueFromEvent = e => {
14
7
  if (Array.isArray(e)) {
15
8
  return e
@@ -17,24 +10,12 @@ const getValueFromEvent = e => {
17
10
  return e?.fileList
18
11
  }
19
12
 
20
- /**
21
- * @function renderFormContent
22
- * @description [中枢渲染逻辑] 负责装配 Form.Item 的内核。
23
- * 具备“路径递归”与“单位挂件”两大核心功能。
24
- *
25
- * @param {Object} params - 渲染参数
26
- * @param {Object} params.item - 零部件配置对象
27
- * @param {Array} [params.prefixName=[]] - 物理路径前缀,用于支持 Form.List 的深层嵌套
28
- * @returns {React.ReactNode} 装配完成的输入单元
29
- */
30
13
  const renderFormContent = ({ item, prefixName = [] }) => {
31
14
 
32
- // 💡 物理占位逻辑:若无 name 属性,则直接输出原始 value 文本
33
15
  if (!item.name) {
34
- return item.value
16
+ return <span dangerouslySetInnerHTML={{ __html: item.value }} />
35
17
  }
36
18
 
37
- // 💡 路径自动对齐:支持字符串或数组格式的 name,自动合并前缀实现“逻辑寻址”
38
19
  const namePath = Array.isArray(prefixName) ? [...prefixName, ...(Array.isArray(item.name) ? item.name : [item.name])] : item.name
39
20
 
40
21
  const inputNode = (
@@ -43,7 +24,6 @@ const renderFormContent = ({ item, prefixName = [] }) => {
43
24
  </Form.Item>
44
25
  )
45
26
 
46
- // 💡 视觉单位挂件逻辑:实现类似“金额(元)”的自动横向排版
47
27
  if (item.unit) {
48
28
  return (
49
29
  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
@@ -56,43 +36,21 @@ const renderFormContent = ({ item, prefixName = [] }) => {
56
36
  return inputNode
57
37
  }
58
38
 
59
- /**
60
- * @component MyBaseForm
61
- * @description [地堡通用零部件单元] 表单系统的物理外壳。
62
- * 具备“单兵作战(Item)”与“集群协同(List)”两种模式切换能力。
63
- *
64
- * @param {Object} props - 组件属性
65
- * @param {Object} props.item - 构筑协议对象
66
- * @param {Object} props.form - Antd Form 实例,用于实现复杂的跨组件联动
67
- * @param {boolean} [props.item.isList] - 模式切换:是否开启动态增减列表模式
68
- * @param {Function} [props.item.render] - 逻辑劫持:支持自定义渲染函数,可完全改写渲染行为
69
- *
70
- * @example
71
- * // 模式 A:标准表单项
72
- * <MyBaseForm item={{ label: '姓名', name: 'name' }} />
73
- *
74
- * // 模式 B:动态列表项
75
- * <MyBaseForm item={{ isList: true, name: 'users', render: (field) => <Input {...field} /> }} />
76
- */
77
39
  export const MyBaseForm = ({ item, form }) => {
78
40
  return (
79
41
  item.isList ?
80
- // 💡 集群模式:处理动态数组类型的表单构筑
81
42
  <Form.List name={item.name} initialValue={item.value}>
82
43
  {(fields, { add, remove }) => (
83
44
  <div style={{ display: 'flex', flexDirection: 'column' }}>
84
45
  {fields.map((field, index) => (
85
46
  <Fragment key={field.key}>
86
- {/* 物理映射:将 field 控制器回传给指挥官自定义的 render 逻辑 */}
87
47
  {item.render(field, index, { add, remove }, form, renderFormContent)}
88
48
  </Fragment>
89
49
  ))}
90
- {/* 零信号兜底:若列表为空,显示添加按钮 */}
91
50
  {fields.length === 0 && <Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>添加{item.label || '项'}</Button>}
92
51
  </div>
93
52
  )}
94
53
  </Form.List> :
95
- // 💡 单兵模式:标准 Form.Item 构筑
96
54
  <Form.Item
97
55
  label={item.label}
98
56
  tooltip={item.tooltip}
@@ -102,11 +60,6 @@ export const MyBaseForm = ({ item, form }) => {
102
60
  required={item.required ?? item.rules?.some(v => v.required)}
103
61
  style={{ marginBottom: !item.name ? 0 : undefined, ...item.style }}
104
62
  >
105
- {/*
106
- 逻辑优先级:
107
- 1. 优先使用 item.render 执行语义劫持
108
- 2. 否则使用 renderFormContent 进行标准构筑
109
- */}
110
63
  {item.render ? (typeof item.render === 'function' ? item.render(item, form) : item.render) : renderFormContent({ item })}
111
64
  </Form.Item>
112
65
  )
@@ -1,14 +1,14 @@
1
1
  import { Form, Descriptions } from 'antd'
2
2
  import { MyBaseForm } from './MyBaseForm'
3
3
 
4
- export const MyDescription = ({ title, column, descriptions, form: externalForm, onValuesChange }) => {
4
+ export const MyDescription = ({ title, column, layout = 'vertical', descriptions, form: externalForm, onValuesChange }) => {
5
5
 
6
6
  const [internalForm] = Form.useForm()
7
7
  const form = externalForm || internalForm
8
8
 
9
9
  return (
10
10
  <Form form={form} preserve={false} component={false} onValuesChange={onValuesChange}>
11
- <Descriptions title={title} column={column}>
11
+ <Descriptions title={title} column={column} layout={layout}>
12
12
  {descriptions.map(item => (
13
13
  <Descriptions.Item key={item.label} label={item.name ? undefined : item.label}>
14
14
  {item.name ? <MyBaseForm item={item} form={form} /> : item.value}