@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/helper.tsx
CHANGED
|
@@ -1,221 +1,221 @@
|
|
|
1
|
-
import { Button, Dropdown, Menu, MenuItem, Select } from '@antsoo-lib/components'
|
|
2
|
-
import { DownOutlined, UpOutlined } from '@antsoo-lib/icons'
|
|
3
|
-
import { isEmpty, isFunction } from '@antsoo-lib/utils'
|
|
4
|
-
|
|
5
|
-
import { markRaw, nextTick, ref } from 'vue'
|
|
6
|
-
import type { VNode } from 'vue'
|
|
7
|
-
|
|
8
|
-
import { rendererMap } from '.'
|
|
9
|
-
import type { ToolbarState } from './state'
|
|
10
|
-
import type { InputGroupToolbarItem, SlotConfig, ToolbarConfig, ToolbarItem } from './types'
|
|
11
|
-
|
|
12
|
-
// 工具栏渲染函数
|
|
13
|
-
export function renderToolbar(
|
|
14
|
-
config: ToolbarConfig,
|
|
15
|
-
toolbarState: ToolbarState,
|
|
16
|
-
count: number | (() => number),
|
|
17
|
-
permissions: string[] = [],
|
|
18
|
-
): VNode[] {
|
|
19
|
-
if (!config || !config.items || !Array.isArray(config.items)) {
|
|
20
|
-
return []
|
|
21
|
-
}
|
|
22
|
-
const allShowBtn = config.items.filter((item) => {
|
|
23
|
-
// 预设xtype项由上层组件转换,这里直接过滤掉
|
|
24
|
-
if ((item as any).xtype) {
|
|
25
|
-
return false
|
|
26
|
-
}
|
|
27
|
-
// Decoupled: Check permission against the passed array
|
|
28
|
-
if (item.permission && !permissions.includes(item.permission)) {
|
|
29
|
-
return false
|
|
30
|
-
}
|
|
31
|
-
// 检查 beforeCreate 属性
|
|
32
|
-
if (item.beforeCreate === undefined) {
|
|
33
|
-
return true // 默认显示
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (typeof item.beforeCreate === 'boolean') {
|
|
37
|
-
return item.beforeCreate
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (typeof item.beforeCreate === 'function') {
|
|
41
|
-
try {
|
|
42
|
-
return item.beforeCreate(toolbarState)
|
|
43
|
-
} catch (error) {
|
|
44
|
-
console.warn(`beforeCreate function error for item ${item.key}:`, error)
|
|
45
|
-
return false
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return true
|
|
50
|
-
})
|
|
51
|
-
const extendCount = isFunction(count) ? count() : count
|
|
52
|
-
|
|
53
|
-
// 优化按钮展示逻辑
|
|
54
|
-
let extendShow: any[]
|
|
55
|
-
let foldShow: any[]
|
|
56
|
-
|
|
57
|
-
if (extendCount <= 0 || allShowBtn.length === 0) {
|
|
58
|
-
// 边界条件:extendCount 无效或没有按钮
|
|
59
|
-
extendShow = []
|
|
60
|
-
foldShow = []
|
|
61
|
-
} else if (allShowBtn.length <= extendCount) {
|
|
62
|
-
// 按钮数量不超过限制,全部展示
|
|
63
|
-
extendShow = allShowBtn
|
|
64
|
-
foldShow = []
|
|
65
|
-
} else {
|
|
66
|
-
// 按钮数量超过限制,需要收起部分按钮
|
|
67
|
-
if (extendCount === 1) {
|
|
68
|
-
// 特殊情况:只能展示1个按钮,直接显示"更多"按钮
|
|
69
|
-
extendShow = []
|
|
70
|
-
foldShow = allShowBtn
|
|
71
|
-
} else {
|
|
72
|
-
// 实际展示的普通按钮数量为 extendCount - 1,为"更多"按钮预留位置
|
|
73
|
-
extendShow = allShowBtn.slice(0, extendCount - 1)
|
|
74
|
-
foldShow = allShowBtn.slice(extendCount - 1)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// 渲染显示的按钮
|
|
79
|
-
const renderedExtendShow = extendShow.map((item: ToolbarItem) => {
|
|
80
|
-
// 对于 PresetToolbarItem,它没有 type 属性来匹配 rendererMap,但应该已被 filter 掉。
|
|
81
|
-
if ('xtype' in item) return null
|
|
82
|
-
|
|
83
|
-
const renderer = rendererMap[item.type]
|
|
84
|
-
if (!renderer) {
|
|
85
|
-
console.warn(`Unknown toolbar item type: ${item.type}`)
|
|
86
|
-
return <div key={item.key}>Unknown type: {item.type}</div>
|
|
87
|
-
}
|
|
88
|
-
return renderer(item, toolbarState, toolbarState.getAllValues())
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
// 如果有折叠的按钮,添加下拉菜单
|
|
92
|
-
if (!isEmpty(foldShow)) {
|
|
93
|
-
const menuItems = foldShow.map((item: ToolbarItem) => {
|
|
94
|
-
// 同样的类型守卫
|
|
95
|
-
if ('xtype' in item) return null
|
|
96
|
-
|
|
97
|
-
return (
|
|
98
|
-
<MenuItem
|
|
99
|
-
key={item.key || `fold-${Math.random()}`}
|
|
100
|
-
onClick={() => {
|
|
101
|
-
// 处理按钮点击事件
|
|
102
|
-
if (item.events?.onClick) {
|
|
103
|
-
const loadingControl = {
|
|
104
|
-
setLoading: (_loading: boolean) => {
|
|
105
|
-
// 这里可以根据需要实现加载状态控制
|
|
106
|
-
},
|
|
107
|
-
getLoading: () => false,
|
|
108
|
-
}
|
|
109
|
-
item.events.onClick(loadingControl, toolbarState.getAllValues(), toolbarState)
|
|
110
|
-
}
|
|
111
|
-
}}
|
|
112
|
-
>
|
|
113
|
-
{item.label || item.key || '未命名'}
|
|
114
|
-
</MenuItem>
|
|
115
|
-
)
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
const dropdownMenu = <Menu>{menuItems}</Menu>
|
|
119
|
-
const dropdownVisible = ref(false)
|
|
120
|
-
const MoreButton = markRaw(Dropdown)
|
|
121
|
-
const moreButton = (
|
|
122
|
-
<MoreButton
|
|
123
|
-
overlay={dropdownMenu}
|
|
124
|
-
trigger={['hover']}
|
|
125
|
-
onOpenChange={(visible: boolean) => {
|
|
126
|
-
dropdownVisible.value = visible
|
|
127
|
-
}}
|
|
128
|
-
>
|
|
129
|
-
<Button type="default">
|
|
130
|
-
更多
|
|
131
|
-
{dropdownVisible.value ? <UpOutlined /> : <DownOutlined />}
|
|
132
|
-
</Button>
|
|
133
|
-
</MoreButton>
|
|
134
|
-
)
|
|
135
|
-
renderedExtendShow.push(moreButton)
|
|
136
|
-
}
|
|
137
|
-
return renderedExtendShow.filter(Boolean) as VNode[]
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 渲染插槽内容的辅助函数
|
|
141
|
-
export function renderSlotContent(
|
|
142
|
-
slotConfig: SlotConfig,
|
|
143
|
-
toolbarState: ToolbarState,
|
|
144
|
-
): VNode | undefined {
|
|
145
|
-
if (!slotConfig?.content) return undefined
|
|
146
|
-
|
|
147
|
-
// 如果是函数,直接调用
|
|
148
|
-
if (typeof slotConfig.content === 'function') {
|
|
149
|
-
return slotConfig.content(toolbarState)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// 如果是组件配置对象
|
|
153
|
-
const { component, key, props = {}, events = {}, options = [] } = slotConfig.content
|
|
154
|
-
const allValues = toolbarState.getAllValues()
|
|
155
|
-
|
|
156
|
-
// 处理事件,注入工具栏状态
|
|
157
|
-
const processedEvents: Record<string, VoidFunction> = {}
|
|
158
|
-
Object.entries(events).forEach(([eventName, handler]) => {
|
|
159
|
-
if (eventName === 'onChange') {
|
|
160
|
-
// 对于 onChange 事件,使用 nextTick 确保在下一个 tick 中执行外部处理函数
|
|
161
|
-
processedEvents[eventName] = (...args: any[]) => {
|
|
162
|
-
nextTick(() => {
|
|
163
|
-
handler(...args, allValues, toolbarState)
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
|
-
} else {
|
|
167
|
-
// 其他事件保持原有逻辑
|
|
168
|
-
processedEvents[eventName] = (...args: any[]) => {
|
|
169
|
-
handler(...args, allValues, toolbarState)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
const value = allValues[key]
|
|
175
|
-
|
|
176
|
-
// 如果是Select组件且有options,添加选项子节点
|
|
177
|
-
if (component === Select && options.length > 0) {
|
|
178
|
-
const Comp = markRaw(component)
|
|
179
|
-
return (
|
|
180
|
-
<Comp value={value ?? ''} {...props} {...processedEvents}>
|
|
181
|
-
{options.map((option: any) => (
|
|
182
|
-
<Select.Option key={option.value} value={option.value}>
|
|
183
|
-
{option.label}
|
|
184
|
-
</Select.Option>
|
|
185
|
-
))}
|
|
186
|
-
</Comp>
|
|
187
|
-
)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// 普通组件渲染
|
|
191
|
-
const Comp = markRaw(component)
|
|
192
|
-
return <Comp value={value ?? ''} {...props} {...processedEvents} />
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function validateInputGroupConfig(cfg: InputGroupToolbarItem): {
|
|
196
|
-
valid: boolean
|
|
197
|
-
errors?: string[]
|
|
198
|
-
} {
|
|
199
|
-
const errors: string[] = []
|
|
200
|
-
if (!cfg) errors.push('inputGroup config is required')
|
|
201
|
-
if (cfg.type !== 'inputGroup') errors.push("type must be 'inputGroup'")
|
|
202
|
-
if (!Array.isArray(cfg.children) || cfg.children.length === 0)
|
|
203
|
-
errors.push('children must be a non-empty array')
|
|
204
|
-
// 校验子项 key 唯一性
|
|
205
|
-
const keys = (cfg.children || []).map((c) => c.key).filter(Boolean) as string[]
|
|
206
|
-
const dup = keys.filter((k, i) => keys.indexOf(k) !== i)
|
|
207
|
-
if (dup.length) errors.push(`duplicate child keys: ${Array.from(new Set(dup)).join(',')}`)
|
|
208
|
-
return { valid: errors.length === 0, errors: errors.length ? errors : undefined }
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// InputNumber 的焦点态只用于“显示逻辑(失焦补零)”,不属于业务值:
|
|
212
|
-
// - 不写入 ToolbarState,避免污染 getAllValues() 及其下游(按钮事件、联动禁用、外部 expose、deep watch 等)
|
|
213
|
-
// - 采用 WeakMap:当 ToolbarState 实例被销毁且无引用时,对应缓存可被 GC 自动回收
|
|
214
|
-
const inputNumberFocusState = new WeakMap<ToolbarState, Map<string, boolean>>()
|
|
215
|
-
export function getInputNumberFocusState(toolbarState: ToolbarState) {
|
|
216
|
-
const existing = inputNumberFocusState.get(toolbarState)
|
|
217
|
-
if (existing) return existing
|
|
218
|
-
const next = new Map<string, boolean>()
|
|
219
|
-
inputNumberFocusState.set(toolbarState, next)
|
|
220
|
-
return next
|
|
221
|
-
}
|
|
1
|
+
import { Button, Dropdown, Menu, MenuItem, Select } from '@antsoo-lib/components'
|
|
2
|
+
import { DownOutlined, UpOutlined } from '@antsoo-lib/icons'
|
|
3
|
+
import { isEmpty, isFunction } from '@antsoo-lib/utils'
|
|
4
|
+
|
|
5
|
+
import { markRaw, nextTick, ref } from 'vue'
|
|
6
|
+
import type { VNode } from 'vue'
|
|
7
|
+
|
|
8
|
+
import { rendererMap } from '.'
|
|
9
|
+
import type { ToolbarState } from './state'
|
|
10
|
+
import type { InputGroupToolbarItem, SlotConfig, ToolbarConfig, ToolbarItem } from './types'
|
|
11
|
+
|
|
12
|
+
// 工具栏渲染函数
|
|
13
|
+
export function renderToolbar(
|
|
14
|
+
config: ToolbarConfig,
|
|
15
|
+
toolbarState: ToolbarState,
|
|
16
|
+
count: number | (() => number),
|
|
17
|
+
permissions: string[] = [],
|
|
18
|
+
): VNode[] {
|
|
19
|
+
if (!config || !config.items || !Array.isArray(config.items)) {
|
|
20
|
+
return []
|
|
21
|
+
}
|
|
22
|
+
const allShowBtn = config.items.filter((item) => {
|
|
23
|
+
// 预设xtype项由上层组件转换,这里直接过滤掉
|
|
24
|
+
if ((item as any).xtype) {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
// Decoupled: Check permission against the passed array
|
|
28
|
+
if (item.permission && !permissions.includes(item.permission)) {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
// 检查 beforeCreate 属性
|
|
32
|
+
if (item.beforeCreate === undefined) {
|
|
33
|
+
return true // 默认显示
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (typeof item.beforeCreate === 'boolean') {
|
|
37
|
+
return item.beforeCreate
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (typeof item.beforeCreate === 'function') {
|
|
41
|
+
try {
|
|
42
|
+
return item.beforeCreate(toolbarState)
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.warn(`beforeCreate function error for item ${item.key}:`, error)
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return true
|
|
50
|
+
})
|
|
51
|
+
const extendCount = isFunction(count) ? count() : count
|
|
52
|
+
|
|
53
|
+
// 优化按钮展示逻辑
|
|
54
|
+
let extendShow: any[]
|
|
55
|
+
let foldShow: any[]
|
|
56
|
+
|
|
57
|
+
if (extendCount <= 0 || allShowBtn.length === 0) {
|
|
58
|
+
// 边界条件:extendCount 无效或没有按钮
|
|
59
|
+
extendShow = []
|
|
60
|
+
foldShow = []
|
|
61
|
+
} else if (allShowBtn.length <= extendCount) {
|
|
62
|
+
// 按钮数量不超过限制,全部展示
|
|
63
|
+
extendShow = allShowBtn
|
|
64
|
+
foldShow = []
|
|
65
|
+
} else {
|
|
66
|
+
// 按钮数量超过限制,需要收起部分按钮
|
|
67
|
+
if (extendCount === 1) {
|
|
68
|
+
// 特殊情况:只能展示1个按钮,直接显示"更多"按钮
|
|
69
|
+
extendShow = []
|
|
70
|
+
foldShow = allShowBtn
|
|
71
|
+
} else {
|
|
72
|
+
// 实际展示的普通按钮数量为 extendCount - 1,为"更多"按钮预留位置
|
|
73
|
+
extendShow = allShowBtn.slice(0, extendCount - 1)
|
|
74
|
+
foldShow = allShowBtn.slice(extendCount - 1)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 渲染显示的按钮
|
|
79
|
+
const renderedExtendShow = extendShow.map((item: ToolbarItem) => {
|
|
80
|
+
// 对于 PresetToolbarItem,它没有 type 属性来匹配 rendererMap,但应该已被 filter 掉。
|
|
81
|
+
if ('xtype' in item) return null
|
|
82
|
+
|
|
83
|
+
const renderer = rendererMap[item.type]
|
|
84
|
+
if (!renderer) {
|
|
85
|
+
console.warn(`Unknown toolbar item type: ${item.type}`)
|
|
86
|
+
return <div key={item.key}>Unknown type: {item.type}</div>
|
|
87
|
+
}
|
|
88
|
+
return renderer(item, toolbarState, toolbarState.getAllValues())
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
// 如果有折叠的按钮,添加下拉菜单
|
|
92
|
+
if (!isEmpty(foldShow)) {
|
|
93
|
+
const menuItems = foldShow.map((item: ToolbarItem) => {
|
|
94
|
+
// 同样的类型守卫
|
|
95
|
+
if ('xtype' in item) return null
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<MenuItem
|
|
99
|
+
key={item.key || `fold-${Math.random()}`}
|
|
100
|
+
onClick={() => {
|
|
101
|
+
// 处理按钮点击事件
|
|
102
|
+
if (item.events?.onClick) {
|
|
103
|
+
const loadingControl = {
|
|
104
|
+
setLoading: (_loading: boolean) => {
|
|
105
|
+
// 这里可以根据需要实现加载状态控制
|
|
106
|
+
},
|
|
107
|
+
getLoading: () => false,
|
|
108
|
+
}
|
|
109
|
+
item.events.onClick(loadingControl, toolbarState.getAllValues(), toolbarState)
|
|
110
|
+
}
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
{item.label || item.key || '未命名'}
|
|
114
|
+
</MenuItem>
|
|
115
|
+
)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const dropdownMenu = <Menu>{menuItems}</Menu>
|
|
119
|
+
const dropdownVisible = ref(false)
|
|
120
|
+
const MoreButton = markRaw(Dropdown)
|
|
121
|
+
const moreButton = (
|
|
122
|
+
<MoreButton
|
|
123
|
+
overlay={dropdownMenu}
|
|
124
|
+
trigger={['hover']}
|
|
125
|
+
onOpenChange={(visible: boolean) => {
|
|
126
|
+
dropdownVisible.value = visible
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
<Button type="default">
|
|
130
|
+
更多
|
|
131
|
+
{dropdownVisible.value ? <UpOutlined /> : <DownOutlined />}
|
|
132
|
+
</Button>
|
|
133
|
+
</MoreButton>
|
|
134
|
+
)
|
|
135
|
+
renderedExtendShow.push(moreButton)
|
|
136
|
+
}
|
|
137
|
+
return renderedExtendShow.filter(Boolean) as VNode[]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 渲染插槽内容的辅助函数
|
|
141
|
+
export function renderSlotContent(
|
|
142
|
+
slotConfig: SlotConfig,
|
|
143
|
+
toolbarState: ToolbarState,
|
|
144
|
+
): VNode | undefined {
|
|
145
|
+
if (!slotConfig?.content) return undefined
|
|
146
|
+
|
|
147
|
+
// 如果是函数,直接调用
|
|
148
|
+
if (typeof slotConfig.content === 'function') {
|
|
149
|
+
return slotConfig.content(toolbarState)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 如果是组件配置对象
|
|
153
|
+
const { component, key, props = {}, events = {}, options = [] } = slotConfig.content
|
|
154
|
+
const allValues = toolbarState.getAllValues()
|
|
155
|
+
|
|
156
|
+
// 处理事件,注入工具栏状态
|
|
157
|
+
const processedEvents: Record<string, VoidFunction> = {}
|
|
158
|
+
Object.entries(events).forEach(([eventName, handler]) => {
|
|
159
|
+
if (eventName === 'onChange') {
|
|
160
|
+
// 对于 onChange 事件,使用 nextTick 确保在下一个 tick 中执行外部处理函数
|
|
161
|
+
processedEvents[eventName] = (...args: any[]) => {
|
|
162
|
+
nextTick(() => {
|
|
163
|
+
handler(...args, allValues, toolbarState)
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
// 其他事件保持原有逻辑
|
|
168
|
+
processedEvents[eventName] = (...args: any[]) => {
|
|
169
|
+
handler(...args, allValues, toolbarState)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const value = allValues[key]
|
|
175
|
+
|
|
176
|
+
// 如果是Select组件且有options,添加选项子节点
|
|
177
|
+
if (component === Select && options.length > 0) {
|
|
178
|
+
const Comp = markRaw(component)
|
|
179
|
+
return (
|
|
180
|
+
<Comp value={value ?? ''} {...props} {...processedEvents}>
|
|
181
|
+
{options.map((option: any) => (
|
|
182
|
+
<Select.Option key={option.value} value={option.value}>
|
|
183
|
+
{option.label}
|
|
184
|
+
</Select.Option>
|
|
185
|
+
))}
|
|
186
|
+
</Comp>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 普通组件渲染
|
|
191
|
+
const Comp = markRaw(component)
|
|
192
|
+
return <Comp value={value ?? ''} {...props} {...processedEvents} />
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function validateInputGroupConfig(cfg: InputGroupToolbarItem): {
|
|
196
|
+
valid: boolean
|
|
197
|
+
errors?: string[]
|
|
198
|
+
} {
|
|
199
|
+
const errors: string[] = []
|
|
200
|
+
if (!cfg) errors.push('inputGroup config is required')
|
|
201
|
+
if (cfg.type !== 'inputGroup') errors.push("type must be 'inputGroup'")
|
|
202
|
+
if (!Array.isArray(cfg.children) || cfg.children.length === 0)
|
|
203
|
+
errors.push('children must be a non-empty array')
|
|
204
|
+
// 校验子项 key 唯一性
|
|
205
|
+
const keys = (cfg.children || []).map((c) => c.key).filter(Boolean) as string[]
|
|
206
|
+
const dup = keys.filter((k, i) => keys.indexOf(k) !== i)
|
|
207
|
+
if (dup.length) errors.push(`duplicate child keys: ${Array.from(new Set(dup)).join(',')}`)
|
|
208
|
+
return { valid: errors.length === 0, errors: errors.length ? errors : undefined }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// InputNumber 的焦点态只用于“显示逻辑(失焦补零)”,不属于业务值:
|
|
212
|
+
// - 不写入 ToolbarState,避免污染 getAllValues() 及其下游(按钮事件、联动禁用、外部 expose、deep watch 等)
|
|
213
|
+
// - 采用 WeakMap:当 ToolbarState 实例被销毁且无引用时,对应缓存可被 GC 自动回收
|
|
214
|
+
const inputNumberFocusState = new WeakMap<ToolbarState, Map<string, boolean>>()
|
|
215
|
+
export function getInputNumberFocusState(toolbarState: ToolbarState) {
|
|
216
|
+
const existing = inputNumberFocusState.get(toolbarState)
|
|
217
|
+
if (existing) return existing
|
|
218
|
+
const next = new Map<string, boolean>()
|
|
219
|
+
inputNumberFocusState.set(toolbarState, next)
|
|
220
|
+
return next
|
|
221
|
+
}
|
package/src/render/index.ts
CHANGED
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
import type { AnyObject } from '@antsoo-lib/shared'
|
|
2
|
-
|
|
3
|
-
import type { VNode } from 'vue'
|
|
4
|
-
|
|
5
|
-
import { renderAreaCascader } from './AreaCascader'
|
|
6
|
-
import { renderAutoComplete } from './AutoComplete'
|
|
7
|
-
import { renderButton } from './Button'
|
|
8
|
-
import { renderCascader } from './Cascader'
|
|
9
|
-
import { renderCheckbox } from './Checkbox'
|
|
10
|
-
import { renderCheckboxGroup } from './CheckboxGroup'
|
|
11
|
-
import { renderCustom } from './Custom'
|
|
12
|
-
import { renderDatePicker } from './DatePicker'
|
|
13
|
-
import { renderInput } from './Input'
|
|
14
|
-
import { renderInputGroup } from './InputGroup'
|
|
15
|
-
import { renderInputNumber } from './InputNumber'
|
|
16
|
-
import { renderInputPassword } from './InputPassword'
|
|
17
|
-
import { renderInputRange } from './InputRange'
|
|
18
|
-
import { renderRadioGroup } from './RadioGroup'
|
|
19
|
-
import { renderSelect } from './Select'
|
|
20
|
-
import { renderSselectPage } from './SselectPage'
|
|
21
|
-
import { renderSwitch } from './Switch'
|
|
22
|
-
import { renderTree } from './Tree'
|
|
23
|
-
import { renderTreeSelect } from './TreeSelect'
|
|
24
|
-
import { renderUpload } from './Upload'
|
|
25
|
-
import type { ToolbarState } from './state'
|
|
26
|
-
import type { ToolbarItem } from './types'
|
|
27
|
-
|
|
28
|
-
// 渲染器映射类型定义
|
|
29
|
-
export type Renderer = (
|
|
30
|
-
item: ToolbarItem,
|
|
31
|
-
toolbarState: ToolbarState,
|
|
32
|
-
formValues?: AnyObject,
|
|
33
|
-
) => VNode
|
|
34
|
-
|
|
35
|
-
// 渲染器映射
|
|
36
|
-
const defaultRendererMap: Record<string, Renderer> = {
|
|
37
|
-
button: renderButton,
|
|
38
|
-
input: renderInput,
|
|
39
|
-
inputRange: renderInputRange as Renderer,
|
|
40
|
-
inputGroup: renderInputGroup as Renderer,
|
|
41
|
-
radioGroup: renderRadioGroup,
|
|
42
|
-
checkbox: renderCheckbox,
|
|
43
|
-
switch: renderSwitch,
|
|
44
|
-
checkboxGroup: renderCheckboxGroup,
|
|
45
|
-
datePicker: renderDatePicker,
|
|
46
|
-
select: renderSelect,
|
|
47
|
-
inputNumber: renderInputNumber,
|
|
48
|
-
inputPassword: renderInputPassword,
|
|
49
|
-
upload: renderUpload,
|
|
50
|
-
tree: renderTree as Renderer,
|
|
51
|
-
treeSelect: renderTreeSelect,
|
|
52
|
-
sselectPage: renderSselectPage,
|
|
53
|
-
autoComplete: renderAutoComplete as Renderer,
|
|
54
|
-
cascader: renderCascader as Renderer,
|
|
55
|
-
areaCascader: renderAreaCascader as Renderer,
|
|
56
|
-
custom: renderCustom as Renderer,
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// 导出的渲染器映射,支持动态修改
|
|
60
|
-
export const rendererMap: Record<string, Renderer> = { ...defaultRendererMap }
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 注册自定义渲染器
|
|
64
|
-
* @param name 渲染器名称
|
|
65
|
-
* @param renderer 渲染函数
|
|
66
|
-
*/
|
|
67
|
-
export function registerRenderer(name: string, renderer: Renderer) {
|
|
68
|
-
rendererMap[name] = renderer
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 批量注册自定义渲染器
|
|
73
|
-
* @param renderers 渲染器映射对象
|
|
74
|
-
*/
|
|
75
|
-
export function registerRenderers(renderers: Record<string, Renderer>) {
|
|
76
|
-
Object.assign(rendererMap, renderers)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* 获取渲染器
|
|
81
|
-
* @param name 渲染器名称
|
|
82
|
-
* @returns 渲染函数
|
|
83
|
-
*/
|
|
84
|
-
export function getRenderer(name: string): Renderer | undefined {
|
|
85
|
-
return rendererMap[name]
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export {
|
|
89
|
-
renderAreaCascader,
|
|
90
|
-
renderAutoComplete,
|
|
91
|
-
renderButton,
|
|
92
|
-
renderCascader,
|
|
93
|
-
renderCheckbox,
|
|
94
|
-
renderCheckboxGroup,
|
|
95
|
-
renderDatePicker,
|
|
96
|
-
renderInput,
|
|
97
|
-
renderInputGroup,
|
|
98
|
-
renderInputNumber,
|
|
99
|
-
renderInputPassword,
|
|
100
|
-
renderInputRange,
|
|
101
|
-
renderRadioGroup,
|
|
102
|
-
renderSelect,
|
|
103
|
-
renderSselectPage,
|
|
104
|
-
renderSwitch,
|
|
105
|
-
renderTree,
|
|
106
|
-
renderTreeSelect,
|
|
107
|
-
renderUpload,
|
|
108
|
-
}
|
|
1
|
+
import type { AnyObject } from '@antsoo-lib/shared'
|
|
2
|
+
|
|
3
|
+
import type { VNode } from 'vue'
|
|
4
|
+
|
|
5
|
+
import { renderAreaCascader } from './AreaCascader'
|
|
6
|
+
import { renderAutoComplete } from './AutoComplete'
|
|
7
|
+
import { renderButton } from './Button'
|
|
8
|
+
import { renderCascader } from './Cascader'
|
|
9
|
+
import { renderCheckbox } from './Checkbox'
|
|
10
|
+
import { renderCheckboxGroup } from './CheckboxGroup'
|
|
11
|
+
import { renderCustom } from './Custom'
|
|
12
|
+
import { renderDatePicker } from './DatePicker'
|
|
13
|
+
import { renderInput } from './Input'
|
|
14
|
+
import { renderInputGroup } from './InputGroup'
|
|
15
|
+
import { renderInputNumber } from './InputNumber'
|
|
16
|
+
import { renderInputPassword } from './InputPassword'
|
|
17
|
+
import { renderInputRange } from './InputRange'
|
|
18
|
+
import { renderRadioGroup } from './RadioGroup'
|
|
19
|
+
import { renderSelect } from './Select'
|
|
20
|
+
import { renderSselectPage } from './SselectPage'
|
|
21
|
+
import { renderSwitch } from './Switch'
|
|
22
|
+
import { renderTree } from './Tree'
|
|
23
|
+
import { renderTreeSelect } from './TreeSelect'
|
|
24
|
+
import { renderUpload } from './Upload'
|
|
25
|
+
import type { ToolbarState } from './state'
|
|
26
|
+
import type { ToolbarItem } from './types'
|
|
27
|
+
|
|
28
|
+
// 渲染器映射类型定义
|
|
29
|
+
export type Renderer = (
|
|
30
|
+
item: ToolbarItem,
|
|
31
|
+
toolbarState: ToolbarState,
|
|
32
|
+
formValues?: AnyObject,
|
|
33
|
+
) => VNode
|
|
34
|
+
|
|
35
|
+
// 渲染器映射
|
|
36
|
+
const defaultRendererMap: Record<string, Renderer> = {
|
|
37
|
+
button: renderButton,
|
|
38
|
+
input: renderInput,
|
|
39
|
+
inputRange: renderInputRange as Renderer,
|
|
40
|
+
inputGroup: renderInputGroup as Renderer,
|
|
41
|
+
radioGroup: renderRadioGroup,
|
|
42
|
+
checkbox: renderCheckbox,
|
|
43
|
+
switch: renderSwitch,
|
|
44
|
+
checkboxGroup: renderCheckboxGroup,
|
|
45
|
+
datePicker: renderDatePicker,
|
|
46
|
+
select: renderSelect,
|
|
47
|
+
inputNumber: renderInputNumber,
|
|
48
|
+
inputPassword: renderInputPassword,
|
|
49
|
+
upload: renderUpload,
|
|
50
|
+
tree: renderTree as Renderer,
|
|
51
|
+
treeSelect: renderTreeSelect,
|
|
52
|
+
sselectPage: renderSselectPage,
|
|
53
|
+
autoComplete: renderAutoComplete as Renderer,
|
|
54
|
+
cascader: renderCascader as Renderer,
|
|
55
|
+
areaCascader: renderAreaCascader as Renderer,
|
|
56
|
+
custom: renderCustom as Renderer,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 导出的渲染器映射,支持动态修改
|
|
60
|
+
export const rendererMap: Record<string, Renderer> = { ...defaultRendererMap }
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 注册自定义渲染器
|
|
64
|
+
* @param name 渲染器名称
|
|
65
|
+
* @param renderer 渲染函数
|
|
66
|
+
*/
|
|
67
|
+
export function registerRenderer(name: string, renderer: Renderer) {
|
|
68
|
+
rendererMap[name] = renderer
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 批量注册自定义渲染器
|
|
73
|
+
* @param renderers 渲染器映射对象
|
|
74
|
+
*/
|
|
75
|
+
export function registerRenderers(renderers: Record<string, Renderer>) {
|
|
76
|
+
Object.assign(rendererMap, renderers)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 获取渲染器
|
|
81
|
+
* @param name 渲染器名称
|
|
82
|
+
* @returns 渲染函数
|
|
83
|
+
*/
|
|
84
|
+
export function getRenderer(name: string): Renderer | undefined {
|
|
85
|
+
return rendererMap[name]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
renderAreaCascader,
|
|
90
|
+
renderAutoComplete,
|
|
91
|
+
renderButton,
|
|
92
|
+
renderCascader,
|
|
93
|
+
renderCheckbox,
|
|
94
|
+
renderCheckboxGroup,
|
|
95
|
+
renderDatePicker,
|
|
96
|
+
renderInput,
|
|
97
|
+
renderInputGroup,
|
|
98
|
+
renderInputNumber,
|
|
99
|
+
renderInputPassword,
|
|
100
|
+
renderInputRange,
|
|
101
|
+
renderRadioGroup,
|
|
102
|
+
renderSelect,
|
|
103
|
+
renderSselectPage,
|
|
104
|
+
renderSwitch,
|
|
105
|
+
renderTree,
|
|
106
|
+
renderTreeSelect,
|
|
107
|
+
renderUpload,
|
|
108
|
+
}
|