@antsoo-lib/core 2.0.5 → 3.0.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/CHANGELOG.md +16 -0
- package/dist/core.css +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +6 -5
- package/dist/types/BaseSearch/index.d.ts +1 -1
- package/dist/types/BaseTable/index.d.ts +4 -480
- package/dist/types/Form/CoreForm.d.ts +82 -0
- package/dist/types/SSelectPage/index.d.ts +102 -0
- package/package.json +5 -5
- package/src/BaseSearch/index.vue +371 -371
- package/src/BaseTable/index.vue +910 -910
- package/src/Form/CoreForm.vue +782 -782
- package/src/Form/types.ts +86 -86
- package/src/SSelectPage/index.vue +607 -607
- package/src/index.ts +17 -17
- package/src/render/AreaCascader.tsx +64 -64
- package/src/render/AutoComplete.tsx +101 -101
- package/src/render/Button.tsx +62 -62
- package/src/render/Cascader.tsx +45 -45
- package/src/render/Checkbox.tsx +65 -65
- package/src/render/CheckboxGroup.tsx +57 -57
- package/src/render/Custom.tsx +19 -19
- package/src/render/DatePicker.tsx +83 -83
- package/src/render/Input.tsx +140 -140
- package/src/render/InputGroup.tsx +115 -115
- package/src/render/InputNumber.tsx +205 -205
- package/src/render/InputPassword.tsx +81 -81
- package/src/render/InputRange.tsx +154 -154
- package/src/render/RadioGroup.tsx +63 -63
- package/src/render/Select.tsx +96 -96
- package/src/render/SselectPage.tsx +107 -107
- package/src/render/Switch.tsx +60 -60
- package/src/render/Tree.tsx +136 -136
- package/src/render/TreeSelect.tsx +81 -81
- package/src/render/Upload.tsx +91 -91
- package/src/render/helper.tsx +221 -221
- package/src/render/index.ts +108 -108
- package/src/render/registry.ts +20 -20
- package/src/render/state.ts +37 -37
- package/src/render/types.ts +567 -567
- package/src/utils/attrMapping.ts +106 -106
- package/vite.config.ts +61 -61
- package/.turbo/turbo-build.log +0 -40
package/src/render/Input.tsx
CHANGED
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
import { Input, Textarea } from '@antsoo-lib/components'
|
|
2
|
-
import type { InputProps } from '@antsoo-lib/components'
|
|
3
|
-
import type { VoidFunction } from '@antsoo-lib/shared'
|
|
4
|
-
import { isFunction } from '@antsoo-lib/utils'
|
|
5
|
-
|
|
6
|
-
import { markRaw, nextTick } from 'vue'
|
|
7
|
-
import type { ComponentPublicInstance, VNode } from 'vue'
|
|
8
|
-
|
|
9
|
-
import { renderSlotContent } from './helper'
|
|
10
|
-
import type { ToolbarState } from './state'
|
|
11
|
-
import type { InputToolbarItem, ToolbarItem } from './types'
|
|
12
|
-
|
|
13
|
-
// 输入框渲染函数
|
|
14
|
-
export function renderInput(item: ToolbarItem, toolbarState: ToolbarState): VNode {
|
|
15
|
-
const inputItem = item as InputToolbarItem
|
|
16
|
-
const { key, events = {}, slots } = inputItem
|
|
17
|
-
const { props = {}, disabled = false } = inputItem
|
|
18
|
-
|
|
19
|
-
const isDisabled = isFunction(disabled)
|
|
20
|
-
? disabled(toolbarState.getAllValues(), toolbarState)
|
|
21
|
-
: disabled
|
|
22
|
-
|
|
23
|
-
// Create a new props object to avoid mutating the original
|
|
24
|
-
const finalProps: Partial<InputProps> & {
|
|
25
|
-
disabled?: boolean
|
|
26
|
-
autoFocus?: boolean
|
|
27
|
-
value?: string
|
|
28
|
-
} = {
|
|
29
|
-
...props,
|
|
30
|
-
disabled: isDisabled,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 处理事件,注入工具栏状态
|
|
34
|
-
const processedEvents: Record<string, VoidFunction> = {}
|
|
35
|
-
Object.keys(events).forEach((eventName) => {
|
|
36
|
-
processedEvents[eventName] = (...args: any[]) => {
|
|
37
|
-
// 对于输入框:
|
|
38
|
-
// - v-model 使用 onUpdate:value 直接写入值
|
|
39
|
-
// - onChange 只在存在 event.target.value 时写入,避免将原始事件对象误写入状态
|
|
40
|
-
if (eventName === 'onUpdate:value') {
|
|
41
|
-
toolbarState.setValue(key, args[0])
|
|
42
|
-
} else if (eventName === 'onChange') {
|
|
43
|
-
const firstArg = args[0]
|
|
44
|
-
const hasTarget = firstArg && typeof firstArg === 'object' && 'target' in firstArg
|
|
45
|
-
const nextVal = hasTarget ? firstArg.target?.value : undefined
|
|
46
|
-
if (nextVal !== undefined) toolbarState.setValue(key, nextVal)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// 调用原始事件处理函数
|
|
50
|
-
events[eventName]?.(...args, toolbarState.getAllValues(), toolbarState)
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
// 如果没有设置onChange事件,添加默认的状态更新
|
|
55
|
-
if (!processedEvents['onUpdate:value'] && !processedEvents.onChange) {
|
|
56
|
-
processedEvents['onUpdate:value'] = (value: string) => {
|
|
57
|
-
toolbarState.setValue(key, value)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 自动全选逻辑:合并 onFocus 事件
|
|
62
|
-
const userOnFocus = processedEvents.onFocus || events.onFocus
|
|
63
|
-
processedEvents.onFocus = (...args: any[]) => {
|
|
64
|
-
const e = args[0]
|
|
65
|
-
try {
|
|
66
|
-
if (e && e.target && typeof (e.target as HTMLInputElement).select === 'function') {
|
|
67
|
-
const target = e.target as HTMLInputElement
|
|
68
|
-
target.select()
|
|
69
|
-
|
|
70
|
-
// 阻止鼠标松开时的默认光标定位行为(仅一次),防止全选被取消
|
|
71
|
-
const preventMouseUp = (ev: Event) => {
|
|
72
|
-
ev.preventDefault()
|
|
73
|
-
target.removeEventListener('mouseup', preventMouseUp)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// 监听 mouseup,确保在点击聚焦时能阻止光标重置
|
|
77
|
-
target.addEventListener('mouseup', preventMouseUp, { once: true })
|
|
78
|
-
|
|
79
|
-
// 清理机制:如果用户是键盘聚焦或其他方式,未触发 mouseup 就失焦了,需要移除监听器
|
|
80
|
-
const cleanup = () => {
|
|
81
|
-
target.removeEventListener('mouseup', preventMouseUp)
|
|
82
|
-
target.removeEventListener('blur', cleanup)
|
|
83
|
-
}
|
|
84
|
-
target.addEventListener('blur', cleanup)
|
|
85
|
-
}
|
|
86
|
-
} catch {
|
|
87
|
-
// ignore
|
|
88
|
-
}
|
|
89
|
-
// 执行用户原有逻辑
|
|
90
|
-
userOnFocus?.(...args, toolbarState.getAllValues(), toolbarState)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 如果需要自动聚焦,添加ref处理
|
|
94
|
-
if (finalProps.autoFocus) {
|
|
95
|
-
processedEvents.ref = (el: Element | ComponentPublicInstance | null) => {
|
|
96
|
-
const element = el as HTMLElement
|
|
97
|
-
if (element) {
|
|
98
|
-
// 使用nextTick确保DOM已经渲染完成
|
|
99
|
-
nextTick(() => {
|
|
100
|
-
try {
|
|
101
|
-
element?.focus?.()
|
|
102
|
-
} catch {
|
|
103
|
-
// 忽略焦点设置失败的错误
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// 构建插槽对象
|
|
111
|
-
const slotsObject: Record<string, () => VNode | undefined> = {}
|
|
112
|
-
if (slots) {
|
|
113
|
-
if (slots.addonBefore) {
|
|
114
|
-
slotsObject.addonBefore = () => renderSlotContent(slots.addonBefore!, toolbarState)
|
|
115
|
-
}
|
|
116
|
-
if (slots.addonAfter) {
|
|
117
|
-
slotsObject.addonAfter = () => renderSlotContent(slots.addonAfter!, toolbarState)
|
|
118
|
-
}
|
|
119
|
-
if (slots.prefix) {
|
|
120
|
-
slotsObject.prefix = () => renderSlotContent(slots.prefix!, toolbarState)
|
|
121
|
-
}
|
|
122
|
-
if (slots.suffix) {
|
|
123
|
-
slotsObject.suffix = () => renderSlotContent(slots.suffix!, toolbarState)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const Comp = markRaw(inputItem.textarea ? Textarea : Input)
|
|
128
|
-
return (
|
|
129
|
-
<Comp
|
|
130
|
-
key={key}
|
|
131
|
-
value={toolbarState.getValue(key) || finalProps.value || ''}
|
|
132
|
-
allowClear
|
|
133
|
-
autocomplete="off"
|
|
134
|
-
maxlength={50}
|
|
135
|
-
{...finalProps}
|
|
136
|
-
{...processedEvents}
|
|
137
|
-
v-slots={slotsObject}
|
|
138
|
-
/>
|
|
139
|
-
)
|
|
140
|
-
}
|
|
1
|
+
import { Input, Textarea } from '@antsoo-lib/components'
|
|
2
|
+
import type { InputProps } from '@antsoo-lib/components'
|
|
3
|
+
import type { VoidFunction } from '@antsoo-lib/shared'
|
|
4
|
+
import { isFunction } from '@antsoo-lib/utils'
|
|
5
|
+
|
|
6
|
+
import { markRaw, nextTick } from 'vue'
|
|
7
|
+
import type { ComponentPublicInstance, VNode } from 'vue'
|
|
8
|
+
|
|
9
|
+
import { renderSlotContent } from './helper'
|
|
10
|
+
import type { ToolbarState } from './state'
|
|
11
|
+
import type { InputToolbarItem, ToolbarItem } from './types'
|
|
12
|
+
|
|
13
|
+
// 输入框渲染函数
|
|
14
|
+
export function renderInput(item: ToolbarItem, toolbarState: ToolbarState): VNode {
|
|
15
|
+
const inputItem = item as InputToolbarItem
|
|
16
|
+
const { key, events = {}, slots } = inputItem
|
|
17
|
+
const { props = {}, disabled = false } = inputItem
|
|
18
|
+
|
|
19
|
+
const isDisabled = isFunction(disabled)
|
|
20
|
+
? disabled(toolbarState.getAllValues(), toolbarState)
|
|
21
|
+
: disabled
|
|
22
|
+
|
|
23
|
+
// Create a new props object to avoid mutating the original
|
|
24
|
+
const finalProps: Partial<InputProps> & {
|
|
25
|
+
disabled?: boolean
|
|
26
|
+
autoFocus?: boolean
|
|
27
|
+
value?: string
|
|
28
|
+
} = {
|
|
29
|
+
...props,
|
|
30
|
+
disabled: isDisabled,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 处理事件,注入工具栏状态
|
|
34
|
+
const processedEvents: Record<string, VoidFunction> = {}
|
|
35
|
+
Object.keys(events).forEach((eventName) => {
|
|
36
|
+
processedEvents[eventName] = (...args: any[]) => {
|
|
37
|
+
// 对于输入框:
|
|
38
|
+
// - v-model 使用 onUpdate:value 直接写入值
|
|
39
|
+
// - onChange 只在存在 event.target.value 时写入,避免将原始事件对象误写入状态
|
|
40
|
+
if (eventName === 'onUpdate:value') {
|
|
41
|
+
toolbarState.setValue(key, args[0])
|
|
42
|
+
} else if (eventName === 'onChange') {
|
|
43
|
+
const firstArg = args[0]
|
|
44
|
+
const hasTarget = firstArg && typeof firstArg === 'object' && 'target' in firstArg
|
|
45
|
+
const nextVal = hasTarget ? firstArg.target?.value : undefined
|
|
46
|
+
if (nextVal !== undefined) toolbarState.setValue(key, nextVal)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 调用原始事件处理函数
|
|
50
|
+
events[eventName]?.(...args, toolbarState.getAllValues(), toolbarState)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// 如果没有设置onChange事件,添加默认的状态更新
|
|
55
|
+
if (!processedEvents['onUpdate:value'] && !processedEvents.onChange) {
|
|
56
|
+
processedEvents['onUpdate:value'] = (value: string) => {
|
|
57
|
+
toolbarState.setValue(key, value)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 自动全选逻辑:合并 onFocus 事件
|
|
62
|
+
const userOnFocus = processedEvents.onFocus || events.onFocus
|
|
63
|
+
processedEvents.onFocus = (...args: any[]) => {
|
|
64
|
+
const e = args[0]
|
|
65
|
+
try {
|
|
66
|
+
if (e && e.target && typeof (e.target as HTMLInputElement).select === 'function') {
|
|
67
|
+
const target = e.target as HTMLInputElement
|
|
68
|
+
target.select()
|
|
69
|
+
|
|
70
|
+
// 阻止鼠标松开时的默认光标定位行为(仅一次),防止全选被取消
|
|
71
|
+
const preventMouseUp = (ev: Event) => {
|
|
72
|
+
ev.preventDefault()
|
|
73
|
+
target.removeEventListener('mouseup', preventMouseUp)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 监听 mouseup,确保在点击聚焦时能阻止光标重置
|
|
77
|
+
target.addEventListener('mouseup', preventMouseUp, { once: true })
|
|
78
|
+
|
|
79
|
+
// 清理机制:如果用户是键盘聚焦或其他方式,未触发 mouseup 就失焦了,需要移除监听器
|
|
80
|
+
const cleanup = () => {
|
|
81
|
+
target.removeEventListener('mouseup', preventMouseUp)
|
|
82
|
+
target.removeEventListener('blur', cleanup)
|
|
83
|
+
}
|
|
84
|
+
target.addEventListener('blur', cleanup)
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
// ignore
|
|
88
|
+
}
|
|
89
|
+
// 执行用户原有逻辑
|
|
90
|
+
userOnFocus?.(...args, toolbarState.getAllValues(), toolbarState)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 如果需要自动聚焦,添加ref处理
|
|
94
|
+
if (finalProps.autoFocus) {
|
|
95
|
+
processedEvents.ref = (el: Element | ComponentPublicInstance | null) => {
|
|
96
|
+
const element = el as HTMLElement
|
|
97
|
+
if (element) {
|
|
98
|
+
// 使用nextTick确保DOM已经渲染完成
|
|
99
|
+
nextTick(() => {
|
|
100
|
+
try {
|
|
101
|
+
element?.focus?.()
|
|
102
|
+
} catch {
|
|
103
|
+
// 忽略焦点设置失败的错误
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 构建插槽对象
|
|
111
|
+
const slotsObject: Record<string, () => VNode | undefined> = {}
|
|
112
|
+
if (slots) {
|
|
113
|
+
if (slots.addonBefore) {
|
|
114
|
+
slotsObject.addonBefore = () => renderSlotContent(slots.addonBefore!, toolbarState)
|
|
115
|
+
}
|
|
116
|
+
if (slots.addonAfter) {
|
|
117
|
+
slotsObject.addonAfter = () => renderSlotContent(slots.addonAfter!, toolbarState)
|
|
118
|
+
}
|
|
119
|
+
if (slots.prefix) {
|
|
120
|
+
slotsObject.prefix = () => renderSlotContent(slots.prefix!, toolbarState)
|
|
121
|
+
}
|
|
122
|
+
if (slots.suffix) {
|
|
123
|
+
slotsObject.suffix = () => renderSlotContent(slots.suffix!, toolbarState)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const Comp = markRaw(inputItem.textarea ? Textarea : Input)
|
|
128
|
+
return (
|
|
129
|
+
<Comp
|
|
130
|
+
key={key}
|
|
131
|
+
value={toolbarState.getValue(key) || finalProps.value || ''}
|
|
132
|
+
allowClear
|
|
133
|
+
autocomplete="off"
|
|
134
|
+
maxlength={50}
|
|
135
|
+
{...finalProps}
|
|
136
|
+
{...processedEvents}
|
|
137
|
+
v-slots={slotsObject}
|
|
138
|
+
/>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
import { Col, InputGroup, Row } from '@antsoo-lib/components'
|
|
2
|
-
import type { AnyObject } from '@antsoo-lib/shared'
|
|
3
|
-
|
|
4
|
-
import { markRaw } from 'vue'
|
|
5
|
-
import type { ExtractPropTypes, VNode } from 'vue'
|
|
6
|
-
|
|
7
|
-
import { rendererMap } from '.'
|
|
8
|
-
import { validateInputGroupConfig } from './helper'
|
|
9
|
-
import type { ToolbarState } from './state'
|
|
10
|
-
import type { InputGroupPropsLike, InputGroupToolbarItem, ToolbarItem } from './types'
|
|
11
|
-
|
|
12
|
-
// Input.Group 渲染函数
|
|
13
|
-
export function renderInputGroup(
|
|
14
|
-
config: InputGroupToolbarItem,
|
|
15
|
-
toolbarState: ToolbarState,
|
|
16
|
-
formValues?: AnyObject,
|
|
17
|
-
): VNode {
|
|
18
|
-
// 默认 compact 布局
|
|
19
|
-
const props = { compact: true, ...(config.props || {}) } as InputGroupPropsLike
|
|
20
|
-
|
|
21
|
-
const { valid, errors } = validateInputGroupConfig(config)
|
|
22
|
-
if (!valid) {
|
|
23
|
-
console.warn('Invalid inputGroup config:', errors)
|
|
24
|
-
return (
|
|
25
|
-
<span key={config.key} class="input-group-error">
|
|
26
|
-
Invalid inputGroup config
|
|
27
|
-
</span>
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// 组装子节点(为每个子项注入事件桥,确保更新到 formValues)
|
|
32
|
-
const childNodes = config.children.map((child, index) => {
|
|
33
|
-
const renderer = rendererMap[child.type]
|
|
34
|
-
if (!renderer) {
|
|
35
|
-
console.warn(`Unknown child type in InputGroup: ${child.type}`)
|
|
36
|
-
return <span key={child.key || `unknown-${index}`}>Unknown type: {child.type}</span>
|
|
37
|
-
}
|
|
38
|
-
// 为子项注入通用的值更新事件,使其除更新 toolbarState 外,还能同步到父级表单的 formValues
|
|
39
|
-
const originalEvents = child.events || {}
|
|
40
|
-
const childItem = {
|
|
41
|
-
...child,
|
|
42
|
-
events: {
|
|
43
|
-
...originalEvents,
|
|
44
|
-
// 统一拦截 v-model 更新
|
|
45
|
-
'onUpdate:value': (val: any, ..._args: any[]) => {
|
|
46
|
-
if (child.key && formValues) {
|
|
47
|
-
formValues[child.key] = val
|
|
48
|
-
}
|
|
49
|
-
// 委托原事件
|
|
50
|
-
if (originalEvents['onUpdate:value']) {
|
|
51
|
-
try {
|
|
52
|
-
originalEvents['onUpdate:value'](val, toolbarState.getAllValues(), toolbarState)
|
|
53
|
-
} catch {
|
|
54
|
-
// 忽略用户事件错误
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
// 兜底拦截 onChange(部分组件可能使用 onChange 作为主事件)
|
|
59
|
-
onChange: (evtOrVal: any, ..._args: any[]) => {
|
|
60
|
-
const val = evtOrVal?.target?.value ?? evtOrVal
|
|
61
|
-
if (child.key && formValues) {
|
|
62
|
-
formValues[child.key] = val
|
|
63
|
-
}
|
|
64
|
-
try {
|
|
65
|
-
originalEvents.onChange?.(val, toolbarState.getAllValues(), toolbarState)
|
|
66
|
-
} catch {
|
|
67
|
-
// 忽略用户事件错误
|
|
68
|
-
}
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
} as ToolbarItem
|
|
72
|
-
|
|
73
|
-
// 复用已有渲染器,确保配置一致性与联动
|
|
74
|
-
return renderer(childItem as ToolbarItem, toolbarState, formValues)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
const layoutType = config.layout?.type ?? (props.compact ? 'compact' : 'none')
|
|
78
|
-
const Content = (() => {
|
|
79
|
-
if (layoutType === 'row') {
|
|
80
|
-
const RowComp = markRaw(Row)
|
|
81
|
-
const ColComp = markRaw(Col)
|
|
82
|
-
return (
|
|
83
|
-
<RowComp {...(config.layout?.rowProps || {})}>
|
|
84
|
-
{childNodes.map((node, idx) => {
|
|
85
|
-
const colCfg = config.layout?.cols?.[idx]
|
|
86
|
-
return (
|
|
87
|
-
<ColComp
|
|
88
|
-
key={config.children[idx]?.key || `col-${idx}`}
|
|
89
|
-
span={colCfg?.span ?? config.children[idx]?.span ?? undefined}
|
|
90
|
-
flex={colCfg?.flex}
|
|
91
|
-
class={(colCfg as Partial<ExtractPropTypes<typeof ColComp>>)?.class}
|
|
92
|
-
style={(colCfg as Partial<ExtractPropTypes<typeof ColComp>>)?.style}
|
|
93
|
-
>
|
|
94
|
-
{node}
|
|
95
|
-
</ColComp>
|
|
96
|
-
)
|
|
97
|
-
})}
|
|
98
|
-
</RowComp>
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
// compact / none:直接返回子节点数组,保证 Input.Group 能与其直接子元素进行无缝紧凑样式合并
|
|
102
|
-
return childNodes
|
|
103
|
-
})()
|
|
104
|
-
|
|
105
|
-
const Comp = markRaw(InputGroup) as any
|
|
106
|
-
return (
|
|
107
|
-
<Comp
|
|
108
|
-
key={config.key}
|
|
109
|
-
{...props}
|
|
110
|
-
{...((config.attr as Partial<ExtractPropTypes<typeof Comp>>) || {})}
|
|
111
|
-
>
|
|
112
|
-
{Content}
|
|
113
|
-
</Comp>
|
|
114
|
-
)
|
|
115
|
-
}
|
|
1
|
+
import { Col, InputGroup, Row } from '@antsoo-lib/components'
|
|
2
|
+
import type { AnyObject } from '@antsoo-lib/shared'
|
|
3
|
+
|
|
4
|
+
import { markRaw } from 'vue'
|
|
5
|
+
import type { ExtractPropTypes, VNode } from 'vue'
|
|
6
|
+
|
|
7
|
+
import { rendererMap } from '.'
|
|
8
|
+
import { validateInputGroupConfig } from './helper'
|
|
9
|
+
import type { ToolbarState } from './state'
|
|
10
|
+
import type { InputGroupPropsLike, InputGroupToolbarItem, ToolbarItem } from './types'
|
|
11
|
+
|
|
12
|
+
// Input.Group 渲染函数
|
|
13
|
+
export function renderInputGroup(
|
|
14
|
+
config: InputGroupToolbarItem,
|
|
15
|
+
toolbarState: ToolbarState,
|
|
16
|
+
formValues?: AnyObject,
|
|
17
|
+
): VNode {
|
|
18
|
+
// 默认 compact 布局
|
|
19
|
+
const props = { compact: true, ...(config.props || {}) } as InputGroupPropsLike
|
|
20
|
+
|
|
21
|
+
const { valid, errors } = validateInputGroupConfig(config)
|
|
22
|
+
if (!valid) {
|
|
23
|
+
console.warn('Invalid inputGroup config:', errors)
|
|
24
|
+
return (
|
|
25
|
+
<span key={config.key} class="input-group-error">
|
|
26
|
+
Invalid inputGroup config
|
|
27
|
+
</span>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 组装子节点(为每个子项注入事件桥,确保更新到 formValues)
|
|
32
|
+
const childNodes = config.children.map((child, index) => {
|
|
33
|
+
const renderer = rendererMap[child.type]
|
|
34
|
+
if (!renderer) {
|
|
35
|
+
console.warn(`Unknown child type in InputGroup: ${child.type}`)
|
|
36
|
+
return <span key={child.key || `unknown-${index}`}>Unknown type: {child.type}</span>
|
|
37
|
+
}
|
|
38
|
+
// 为子项注入通用的值更新事件,使其除更新 toolbarState 外,还能同步到父级表单的 formValues
|
|
39
|
+
const originalEvents = child.events || {}
|
|
40
|
+
const childItem = {
|
|
41
|
+
...child,
|
|
42
|
+
events: {
|
|
43
|
+
...originalEvents,
|
|
44
|
+
// 统一拦截 v-model 更新
|
|
45
|
+
'onUpdate:value': (val: any, ..._args: any[]) => {
|
|
46
|
+
if (child.key && formValues) {
|
|
47
|
+
formValues[child.key] = val
|
|
48
|
+
}
|
|
49
|
+
// 委托原事件
|
|
50
|
+
if (originalEvents['onUpdate:value']) {
|
|
51
|
+
try {
|
|
52
|
+
originalEvents['onUpdate:value'](val, toolbarState.getAllValues(), toolbarState)
|
|
53
|
+
} catch {
|
|
54
|
+
// 忽略用户事件错误
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
// 兜底拦截 onChange(部分组件可能使用 onChange 作为主事件)
|
|
59
|
+
onChange: (evtOrVal: any, ..._args: any[]) => {
|
|
60
|
+
const val = evtOrVal?.target?.value ?? evtOrVal
|
|
61
|
+
if (child.key && formValues) {
|
|
62
|
+
formValues[child.key] = val
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
originalEvents.onChange?.(val, toolbarState.getAllValues(), toolbarState)
|
|
66
|
+
} catch {
|
|
67
|
+
// 忽略用户事件错误
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
} as ToolbarItem
|
|
72
|
+
|
|
73
|
+
// 复用已有渲染器,确保配置一致性与联动
|
|
74
|
+
return renderer(childItem as ToolbarItem, toolbarState, formValues)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const layoutType = config.layout?.type ?? (props.compact ? 'compact' : 'none')
|
|
78
|
+
const Content = (() => {
|
|
79
|
+
if (layoutType === 'row') {
|
|
80
|
+
const RowComp = markRaw(Row)
|
|
81
|
+
const ColComp = markRaw(Col)
|
|
82
|
+
return (
|
|
83
|
+
<RowComp {...(config.layout?.rowProps || {})}>
|
|
84
|
+
{childNodes.map((node, idx) => {
|
|
85
|
+
const colCfg = config.layout?.cols?.[idx]
|
|
86
|
+
return (
|
|
87
|
+
<ColComp
|
|
88
|
+
key={config.children[idx]?.key || `col-${idx}`}
|
|
89
|
+
span={colCfg?.span ?? config.children[idx]?.span ?? undefined}
|
|
90
|
+
flex={colCfg?.flex}
|
|
91
|
+
class={(colCfg as Partial<ExtractPropTypes<typeof ColComp>>)?.class}
|
|
92
|
+
style={(colCfg as Partial<ExtractPropTypes<typeof ColComp>>)?.style}
|
|
93
|
+
>
|
|
94
|
+
{node}
|
|
95
|
+
</ColComp>
|
|
96
|
+
)
|
|
97
|
+
})}
|
|
98
|
+
</RowComp>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
// compact / none:直接返回子节点数组,保证 Input.Group 能与其直接子元素进行无缝紧凑样式合并
|
|
102
|
+
return childNodes
|
|
103
|
+
})()
|
|
104
|
+
|
|
105
|
+
const Comp = markRaw(InputGroup) as any
|
|
106
|
+
return (
|
|
107
|
+
<Comp
|
|
108
|
+
key={config.key}
|
|
109
|
+
{...props}
|
|
110
|
+
{...((config.attr as Partial<ExtractPropTypes<typeof Comp>>) || {})}
|
|
111
|
+
>
|
|
112
|
+
{Content}
|
|
113
|
+
</Comp>
|
|
114
|
+
)
|
|
115
|
+
}
|