@antsoo-lib/core 0.0.0 → 1.0.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.
- package/CHANGELOG.md +13 -0
- package/dist/core.css +1 -0
- package/dist/index.cjs +61 -0
- package/dist/index.js +50858 -0
- package/dist/types/core/index.d.ts +6 -0
- package/dist/types/core/src/BaseTable/helpers.d.ts +9 -0
- package/dist/types/core/src/BaseTable/index.d.ts +190 -0
- package/dist/types/core/src/BaseTable/registry.d.ts +9 -0
- package/dist/types/core/src/BaseTable/renderers/AreaCascader.d.ts +5 -0
- package/dist/types/core/src/BaseTable/renderers/AutoComplete.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/Button.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/Cascader.d.ts +5 -0
- package/dist/types/core/src/BaseTable/renderers/Checkbox.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/CheckboxGroup.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/DatePicker.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/Input.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/InputGroup.d.ts +5 -0
- package/dist/types/core/src/BaseTable/renderers/InputNumber.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/InputPassword.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/RadioGroup.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/Select.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/SselectPage.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/Switch.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/TreeSelect.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/Upload.d.ts +4 -0
- package/dist/types/core/src/BaseTable/renderers/index.d.ts +24 -0
- package/dist/types/core/src/BaseTable/state.d.ts +19 -0
- package/dist/types/core/src/BaseTable/types.d.ts +391 -0
- package/dist/types/core/src/BaseTable/utils.d.ts +8 -0
- package/dist/types/core/src/index.d.ts +2 -0
- package/index.css +2 -0
- package/index.ts +21 -0
- package/package.json +39 -3
- package/src/BaseTable/helpers.tsx +91 -0
- package/src/BaseTable/index.vue +881 -0
- package/src/BaseTable/registry.ts +20 -0
- package/src/BaseTable/renderers/AreaCascader.tsx +64 -0
- package/src/BaseTable/renderers/AutoComplete.tsx +101 -0
- package/src/BaseTable/renderers/Button.tsx +62 -0
- package/src/BaseTable/renderers/Cascader.tsx +45 -0
- package/src/BaseTable/renderers/Checkbox.tsx +65 -0
- package/src/BaseTable/renderers/CheckboxGroup.tsx +57 -0
- package/src/BaseTable/renderers/DatePicker.tsx +83 -0
- package/src/BaseTable/renderers/Input.tsx +140 -0
- package/src/BaseTable/renderers/InputGroup.tsx +115 -0
- package/src/BaseTable/renderers/InputNumber.tsx +205 -0
- package/src/BaseTable/renderers/InputPassword.tsx +81 -0
- package/src/BaseTable/renderers/RadioGroup.tsx +63 -0
- package/src/BaseTable/renderers/Select.tsx +96 -0
- package/src/BaseTable/renderers/SselectPage.tsx +107 -0
- package/src/BaseTable/renderers/Switch.tsx +60 -0
- package/src/BaseTable/renderers/TreeSelect.tsx +81 -0
- package/src/BaseTable/renderers/Upload.tsx +92 -0
- package/src/BaseTable/renderers/index.ts +67 -0
- package/src/BaseTable/state.ts +37 -0
- package/src/BaseTable/types.ts +507 -0
- package/src/BaseTable/utils.tsx +144 -0
- package/src/index.ts +3 -0
- package/vite.config.ts +48 -0
|
@@ -0,0 +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 '../helpers'
|
|
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
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { InputNumber } from '@antsoo-lib/components'
|
|
2
|
+
import { PRECISION } from '@antsoo-lib/shared'
|
|
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 { getInputNumberFocusState, renderSlotContent } from '../helpers'
|
|
10
|
+
import type { ToolbarState } from '../state'
|
|
11
|
+
import type { InputNumberToolbarItem, ToolbarItem } from '../types'
|
|
12
|
+
|
|
13
|
+
// 数字输入框渲染函数
|
|
14
|
+
export function renderInputNumber(item: ToolbarItem, toolbarState: ToolbarState): VNode {
|
|
15
|
+
const inputNumberItem = item as InputNumberToolbarItem
|
|
16
|
+
const { key, events = {}, slots } = inputNumberItem
|
|
17
|
+
const { props = {}, disabled = false } = inputNumberItem
|
|
18
|
+
|
|
19
|
+
const isDisabled = isFunction(disabled)
|
|
20
|
+
? disabled(toolbarState.getAllValues(), toolbarState)
|
|
21
|
+
: disabled
|
|
22
|
+
|
|
23
|
+
// Clone props to avoid mutation
|
|
24
|
+
const finalProps = { ...props, disabled: isDisabled }
|
|
25
|
+
|
|
26
|
+
// 自动处理精度与格式化 (xmoney/xprice/xweight/xtaxrate)
|
|
27
|
+
const { xmoney, xprice, xweight, xtaxrate } = finalProps
|
|
28
|
+
|
|
29
|
+
// 1. 确定精度
|
|
30
|
+
let precision = finalProps.precision
|
|
31
|
+
if (precision === undefined) {
|
|
32
|
+
if (xmoney) precision = PRECISION.amount
|
|
33
|
+
else if (xprice) precision = PRECISION.price
|
|
34
|
+
else if (xweight) precision = PRECISION.weight
|
|
35
|
+
else if (xtaxrate) precision = PRECISION.taxRate
|
|
36
|
+
else if (finalProps.xnumber) precision = PRECISION.number
|
|
37
|
+
|
|
38
|
+
if (precision !== undefined) {
|
|
39
|
+
finalProps.precision = precision
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 2. 确定步长 (step)
|
|
44
|
+
if (finalProps.step === undefined && precision !== undefined) {
|
|
45
|
+
finalProps.step = 1 / 10 ** precision
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 以字段 key 维度记录 InputNumber 是否处于聚焦中
|
|
49
|
+
const focusState = key ? getInputNumberFocusState(toolbarState) : undefined
|
|
50
|
+
|
|
51
|
+
const formatValueWithPrecisionOnBlur = (value: any) => {
|
|
52
|
+
if (value == null || value === '') return ''
|
|
53
|
+
const rawText = `${value}`
|
|
54
|
+
if (precision === undefined) return rawText
|
|
55
|
+
// 聚焦中保持“用户输入原样”,避免输入中间态(如 2. / 2.0)被 toFixed 强制改写
|
|
56
|
+
if (key && focusState?.get(key)) return rawText
|
|
57
|
+
const num = Number(rawText)
|
|
58
|
+
if (!Number.isFinite(num)) return rawText
|
|
59
|
+
// 失焦时按 precision 补齐尾数 0(同时会发生四舍五入)
|
|
60
|
+
return num.toFixed(precision)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 3. 默认格式化 (千分位 或 百分比)
|
|
64
|
+
if (!finalProps.formatter) {
|
|
65
|
+
if (xtaxrate) {
|
|
66
|
+
// 税率:先补精度再加 %
|
|
67
|
+
finalProps.formatter = (value: any) => {
|
|
68
|
+
const text = formatValueWithPrecisionOnBlur(value)
|
|
69
|
+
return text === '' ? '' : `${text}%`
|
|
70
|
+
}
|
|
71
|
+
finalProps.parser = (value: string | undefined) => (value ? value.replace('%', '') : '')
|
|
72
|
+
} else {
|
|
73
|
+
// 默认千分位
|
|
74
|
+
// 千分位:先补精度再加逗号(确保 2 -> 2.00 之后再变成 2.00 的千分位格式)
|
|
75
|
+
finalProps.formatter = (value: any) => {
|
|
76
|
+
const text = formatValueWithPrecisionOnBlur(value)
|
|
77
|
+
if (text === '') return ''
|
|
78
|
+
return `${text}`.replace(/\B(?=(?:\d{3})+(?!\d))/g, ',')
|
|
79
|
+
}
|
|
80
|
+
finalProps.parser = (value: string | undefined) => (value ? value.replace(/,/g, '') : '')
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const processedEvents: Record<string, VoidFunction> = {}
|
|
85
|
+
Object.keys(events).forEach((eventName) => {
|
|
86
|
+
processedEvents[eventName] = (...args: any[]) => {
|
|
87
|
+
// 对于数字输入框,自动更新状态
|
|
88
|
+
if (eventName === 'onUpdate:value' || eventName === 'onChange') {
|
|
89
|
+
const value = args[0]
|
|
90
|
+
toolbarState.setValue(key, value)
|
|
91
|
+
}
|
|
92
|
+
// 调用原始事件处理函数
|
|
93
|
+
events[eventName]?.(...args, toolbarState.getAllValues(), toolbarState)
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
// 如果没有设置onChange事件,添加默认的状态更新
|
|
98
|
+
if (!processedEvents['onUpdate:value'] && !processedEvents.onChange) {
|
|
99
|
+
processedEvents['onUpdate:value'] = (value: any) => {
|
|
100
|
+
toolbarState.setValue(key, value)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 自动全选逻辑:合并 onFocus 事件
|
|
105
|
+
const userOnFocus = processedEvents.onFocus || events.onFocus
|
|
106
|
+
processedEvents.onFocus = (...args: any[]) => {
|
|
107
|
+
const e = args[0]
|
|
108
|
+
if (key) focusState?.set(key, true)
|
|
109
|
+
try {
|
|
110
|
+
if (e && e.target && typeof (e.target as HTMLInputElement).select === 'function') {
|
|
111
|
+
const target = e.target as HTMLInputElement
|
|
112
|
+
target.select()
|
|
113
|
+
|
|
114
|
+
// 阻止鼠标松开时的默认光标定位行为(仅一次),防止全选被取消
|
|
115
|
+
const preventMouseUp = (ev: Event) => {
|
|
116
|
+
ev.preventDefault()
|
|
117
|
+
target.removeEventListener('mouseup', preventMouseUp)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
target.addEventListener('mouseup', preventMouseUp, { once: true })
|
|
121
|
+
|
|
122
|
+
const cleanup = () => {
|
|
123
|
+
target.removeEventListener('mouseup', preventMouseUp)
|
|
124
|
+
target.removeEventListener('blur', cleanup)
|
|
125
|
+
}
|
|
126
|
+
target.addEventListener('blur', cleanup)
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
// ignore
|
|
130
|
+
}
|
|
131
|
+
// 执行用户原有逻辑
|
|
132
|
+
userOnFocus?.(...args, toolbarState.getAllValues(), toolbarState)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (key) {
|
|
136
|
+
const userOnFocusCapture = processedEvents.onFocusCapture
|
|
137
|
+
processedEvents.onFocusCapture = (...args: any[]) => {
|
|
138
|
+
// capture 阶段优先标记“聚焦态”,确保 formatter 在同一事件周期内可读到最新状态
|
|
139
|
+
focusState?.set(key, true)
|
|
140
|
+
userOnFocusCapture?.(...args)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const userOnBlurCapture = processedEvents.onBlurCapture
|
|
144
|
+
processedEvents.onBlurCapture = (...args: any[]) => {
|
|
145
|
+
// capture 阶段优先标记“失焦态”,避免 InputNumber 内部 onBlur flush 时仍被认为是聚焦态
|
|
146
|
+
focusState?.set(key, false)
|
|
147
|
+
userOnBlurCapture?.(...args)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const userOnBlur = processedEvents.onBlur
|
|
151
|
+
processedEvents.onBlur = (...args: any[]) => {
|
|
152
|
+
focusState?.set(key, false)
|
|
153
|
+
userOnBlur?.(...args)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 如果需要自动聚焦,添加ref处理
|
|
158
|
+
if (finalProps.autoFocus) {
|
|
159
|
+
processedEvents.ref = (el: Element | ComponentPublicInstance | null) => {
|
|
160
|
+
const element = el as HTMLElement
|
|
161
|
+
if (element) {
|
|
162
|
+
// 使用nextTick确保DOM已经渲染完成
|
|
163
|
+
nextTick(() => {
|
|
164
|
+
try {
|
|
165
|
+
element?.focus?.()
|
|
166
|
+
} catch {
|
|
167
|
+
// 忽略焦点设置失败的错误
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 构建插槽对象
|
|
175
|
+
const slotsObject: Record<string, () => VNode | undefined> = {}
|
|
176
|
+
if (slots) {
|
|
177
|
+
if (slots.addonBefore) {
|
|
178
|
+
slotsObject.addonBefore = () => renderSlotContent(slots.addonBefore!, toolbarState)
|
|
179
|
+
}
|
|
180
|
+
if (slots.addonAfter) {
|
|
181
|
+
slotsObject.addonAfter = () => renderSlotContent(slots.addonAfter!, toolbarState)
|
|
182
|
+
}
|
|
183
|
+
if (slots.prefix) {
|
|
184
|
+
slotsObject.prefix = () => renderSlotContent(slots.prefix!, toolbarState)
|
|
185
|
+
}
|
|
186
|
+
if (slots.suffix) {
|
|
187
|
+
slotsObject.suffix = () => renderSlotContent(slots.suffix!, toolbarState)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const Comp = markRaw(InputNumber)
|
|
192
|
+
return (
|
|
193
|
+
<Comp
|
|
194
|
+
key={key}
|
|
195
|
+
value={
|
|
196
|
+
toolbarState.getValue(key) !== undefined
|
|
197
|
+
? toolbarState.getValue(key)
|
|
198
|
+
: (finalProps.value ?? null)
|
|
199
|
+
}
|
|
200
|
+
{...finalProps}
|
|
201
|
+
{...processedEvents}
|
|
202
|
+
v-slots={slotsObject}
|
|
203
|
+
/>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { InputPassword } 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 } from 'vue'
|
|
7
|
+
import type { VNode } from 'vue'
|
|
8
|
+
|
|
9
|
+
import { renderSlotContent } from '../helpers'
|
|
10
|
+
import type { ToolbarState } from '../state'
|
|
11
|
+
import type { InputPasswordToolbarItem, ToolbarItem } from '../types'
|
|
12
|
+
|
|
13
|
+
export function renderInputPassword(item: ToolbarItem, toolbarState: ToolbarState): VNode {
|
|
14
|
+
const inputItem = item as InputPasswordToolbarItem
|
|
15
|
+
const { key, events = {}, slots } = inputItem
|
|
16
|
+
const { props = {}, disabled = false } = item
|
|
17
|
+
|
|
18
|
+
const isDisabled = isFunction(disabled)
|
|
19
|
+
? disabled(toolbarState.getAllValues(), toolbarState)
|
|
20
|
+
: disabled
|
|
21
|
+
const finalProps: Partial<InputProps> & { disabled?: boolean; value?: string } = {
|
|
22
|
+
...props,
|
|
23
|
+
disabled: isDisabled,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 处理事件,注入工具栏状态
|
|
27
|
+
const processedEvents: Record<string, VoidFunction> = {}
|
|
28
|
+
Object.keys(events).forEach((eventName) => {
|
|
29
|
+
processedEvents[eventName] = (...args: any[]) => {
|
|
30
|
+
if (eventName === 'onUpdate:value') {
|
|
31
|
+
toolbarState.setValue(key, args[0])
|
|
32
|
+
} else if (eventName === 'onChange') {
|
|
33
|
+
const firstArg = args[0]
|
|
34
|
+
const hasTarget = firstArg && typeof firstArg === 'object' && 'target' in firstArg
|
|
35
|
+
const nextVal = hasTarget ? firstArg.target?.value : undefined
|
|
36
|
+
if (nextVal !== undefined) toolbarState.setValue(key, nextVal)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 调用原始事件处理函数
|
|
40
|
+
events[eventName]?.(...args, toolbarState.getAllValues(), toolbarState)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// 如果没有设置onChange事件,添加默认的状态更新
|
|
45
|
+
if (!processedEvents['onUpdate:value'] && !processedEvents.onChange) {
|
|
46
|
+
processedEvents['onUpdate:value'] = (value: string) => {
|
|
47
|
+
toolbarState.setValue(key, value)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 构建插槽对象
|
|
52
|
+
const slotsObject: Record<string, () => VNode | undefined> = {}
|
|
53
|
+
if (slots) {
|
|
54
|
+
if (slots.addonBefore) {
|
|
55
|
+
slotsObject.addonBefore = () => renderSlotContent(slots.addonBefore!, toolbarState)
|
|
56
|
+
}
|
|
57
|
+
if (slots.addonAfter) {
|
|
58
|
+
slotsObject.addonAfter = () => renderSlotContent(slots.addonAfter!, toolbarState)
|
|
59
|
+
}
|
|
60
|
+
if (slots.prefix) {
|
|
61
|
+
slotsObject.prefix = () => renderSlotContent(slots.prefix!, toolbarState)
|
|
62
|
+
}
|
|
63
|
+
if (slots.suffix) {
|
|
64
|
+
slotsObject.suffix = () => renderSlotContent(slots.suffix!, toolbarState)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const Comp = markRaw(InputPassword)
|
|
69
|
+
return (
|
|
70
|
+
<Comp
|
|
71
|
+
key={key}
|
|
72
|
+
value={toolbarState.getValue(key) || finalProps.value || ''}
|
|
73
|
+
allowClear
|
|
74
|
+
autocomplete="new-password"
|
|
75
|
+
maxlength={50}
|
|
76
|
+
{...finalProps}
|
|
77
|
+
{...processedEvents}
|
|
78
|
+
v-slots={slotsObject}
|
|
79
|
+
/>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { RadioGroup } from '@antsoo-lib/components'
|
|
2
|
+
import type { RadioGroupProps } from '@antsoo-lib/components'
|
|
3
|
+
import type { VoidFunction } from '@antsoo-lib/shared'
|
|
4
|
+
import { isFunction } from '@antsoo-lib/utils'
|
|
5
|
+
|
|
6
|
+
import { markRaw } from 'vue'
|
|
7
|
+
import type { VNode } from 'vue'
|
|
8
|
+
|
|
9
|
+
import type { ToolbarState } from '../state'
|
|
10
|
+
import type { RadioGroupToolbarItem, ToolbarItem } from '../types'
|
|
11
|
+
|
|
12
|
+
// 单选框渲染函数
|
|
13
|
+
export function renderRadioGroup(item: ToolbarItem, toolbarState: ToolbarState): VNode {
|
|
14
|
+
const radioItem = item as RadioGroupToolbarItem
|
|
15
|
+
const { key, events = {} } = radioItem
|
|
16
|
+
const { props = {}, disabled = false } = radioItem
|
|
17
|
+
|
|
18
|
+
const isDisabled = isFunction(disabled)
|
|
19
|
+
? disabled(toolbarState.getAllValues(), toolbarState)
|
|
20
|
+
: disabled
|
|
21
|
+
const finalProps: Partial<RadioGroupProps> & { disabled?: boolean; value?: any } = {
|
|
22
|
+
...props,
|
|
23
|
+
disabled: isDisabled,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 处理事件,注入工具栏状态
|
|
27
|
+
const processedEvents: Record<string, VoidFunction> = {}
|
|
28
|
+
Object.keys(events).forEach((eventName) => {
|
|
29
|
+
processedEvents[eventName] = (...args: any[]) => {
|
|
30
|
+
// 对于单选框:
|
|
31
|
+
// - v-model 使用 onUpdate:value 直接写入值
|
|
32
|
+
// - onChange 仅从 event.target.value 写入,避免事件对象被写入
|
|
33
|
+
if (eventName === 'onUpdate:value') {
|
|
34
|
+
toolbarState.setValue(key, args[0])
|
|
35
|
+
} else if (eventName === 'onChange') {
|
|
36
|
+
const ev = args[0]
|
|
37
|
+
const nextVal =
|
|
38
|
+
ev && typeof ev === 'object' && 'target' in ev ? ev.target?.value : undefined
|
|
39
|
+
if (nextVal !== undefined) toolbarState.setValue(key, nextVal)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 调用原始事件处理函数
|
|
43
|
+
events[eventName]?.(...args, toolbarState.getAllValues(), toolbarState)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
// 如果没有设置onChange事件,添加默认的状态更新
|
|
48
|
+
if (!processedEvents['onUpdate:value'] && !processedEvents.onChange) {
|
|
49
|
+
processedEvents['onUpdate:value'] = (value: any) => {
|
|
50
|
+
toolbarState.setValue(key, value)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const Comp = markRaw(RadioGroup)
|
|
55
|
+
return (
|
|
56
|
+
<Comp
|
|
57
|
+
key={key}
|
|
58
|
+
value={toolbarState.getValue(key) || finalProps.value}
|
|
59
|
+
{...finalProps}
|
|
60
|
+
{...processedEvents}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Select } from '@antsoo-lib/components'
|
|
2
|
+
import type { SelectProps } from '@antsoo-lib/components'
|
|
3
|
+
import type { VoidFunction } from '@antsoo-lib/shared'
|
|
4
|
+
import { isFunction } from '@antsoo-lib/utils'
|
|
5
|
+
|
|
6
|
+
import { computed, markRaw } from 'vue'
|
|
7
|
+
import type { VNode } from 'vue'
|
|
8
|
+
|
|
9
|
+
import type { ToolbarState } from '../state'
|
|
10
|
+
import type { SelectToolbarItem, ToolbarItem } from '../types'
|
|
11
|
+
|
|
12
|
+
// 选择框渲染函数
|
|
13
|
+
export function renderSelect(item: ToolbarItem, toolbarState: ToolbarState): VNode {
|
|
14
|
+
const selectItem = item as SelectToolbarItem
|
|
15
|
+
const { key, events = {} } = selectItem
|
|
16
|
+
const { props = {}, disabled = false } = selectItem
|
|
17
|
+
|
|
18
|
+
const isDisabled = isFunction(disabled)
|
|
19
|
+
? disabled(toolbarState.getAllValues(), toolbarState)
|
|
20
|
+
: disabled
|
|
21
|
+
|
|
22
|
+
// Resolve options
|
|
23
|
+
const resolvedOptions = isFunction(props.options)
|
|
24
|
+
? props.options(toolbarState.getAllValues())
|
|
25
|
+
: props.options
|
|
26
|
+
|
|
27
|
+
// 从props中提取options,避免在展开props时覆盖处理后的options
|
|
28
|
+
const { options: _originalOptions, ...otherProps } = props
|
|
29
|
+
|
|
30
|
+
// 响应式计算 options,增加对ref对象的处理
|
|
31
|
+
const computedOptions = computed(() => {
|
|
32
|
+
if (isFunction(resolvedOptions)) {
|
|
33
|
+
// Should not happen if props.options was already resolved above, but handle ref() case
|
|
34
|
+
return resolvedOptions(toolbarState.getAllValues())
|
|
35
|
+
}
|
|
36
|
+
// 处理ref对象
|
|
37
|
+
if (resolvedOptions && typeof resolvedOptions === 'object' && 'value' in resolvedOptions) {
|
|
38
|
+
return resolvedOptions.value || []
|
|
39
|
+
}
|
|
40
|
+
return resolvedOptions || []
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const finalProps: Partial<SelectProps> & { disabled?: boolean } = {
|
|
44
|
+
...otherProps,
|
|
45
|
+
disabled: isDisabled,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 定义processedEvents对象
|
|
49
|
+
const processedEvents: Record<string, VoidFunction> = {}
|
|
50
|
+
|
|
51
|
+
Object.keys(events).forEach((eventName) => {
|
|
52
|
+
processedEvents[eventName] = (...args: any[]) => {
|
|
53
|
+
// 对于选择框,自动更新状态
|
|
54
|
+
if (eventName === 'onUpdate:value' || eventName === 'onChange') {
|
|
55
|
+
const value = args[0]
|
|
56
|
+
toolbarState.setValue(key, value)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 调用原始事件处理函数
|
|
60
|
+
events[eventName]?.(...args, toolbarState.getAllValues(), toolbarState)
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// 如果没有设置onChange事件,添加默认的状态更新
|
|
65
|
+
if (!processedEvents['onUpdate:value'] && !processedEvents.onChange) {
|
|
66
|
+
processedEvents['onUpdate:value'] = (value: any) => {
|
|
67
|
+
toolbarState.setValue(key, value)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 获取当前值,确保正确显示
|
|
72
|
+
const rawVal = toolbarState.getValue(key)
|
|
73
|
+
const propVal = props.value
|
|
74
|
+
const isMulti = otherProps.mode === 'multiple' || otherProps.mode === 'tags'
|
|
75
|
+
|
|
76
|
+
const currentValue =
|
|
77
|
+
rawVal !== undefined && rawVal !== null
|
|
78
|
+
? rawVal
|
|
79
|
+
: propVal !== undefined && propVal !== null
|
|
80
|
+
? propVal
|
|
81
|
+
: isMulti
|
|
82
|
+
? []
|
|
83
|
+
: undefined
|
|
84
|
+
|
|
85
|
+
const Comp = markRaw(Select)
|
|
86
|
+
return (
|
|
87
|
+
<Comp
|
|
88
|
+
key={key}
|
|
89
|
+
value={currentValue}
|
|
90
|
+
allowClear
|
|
91
|
+
{...finalProps}
|
|
92
|
+
options={computedOptions.value}
|
|
93
|
+
{...processedEvents}
|
|
94
|
+
/>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { VoidFunction } from '@antsoo-lib/shared'
|
|
2
|
+
import { isFunction } from '@antsoo-lib/utils'
|
|
3
|
+
|
|
4
|
+
import { computed, markRaw, nextTick } from 'vue'
|
|
5
|
+
import type { ComponentPublicInstance, VNode } from 'vue'
|
|
6
|
+
|
|
7
|
+
import { getRegisteredComponent } from '../registry'
|
|
8
|
+
import type { ToolbarState } from '../state'
|
|
9
|
+
import type { SselectPageProps, SselectPageToolbarItem, ToolbarItem } from '../types'
|
|
10
|
+
|
|
11
|
+
export function renderSselectPage(item: ToolbarItem, toolbarState: ToolbarState): VNode {
|
|
12
|
+
const sselectPageItem = item as SselectPageToolbarItem
|
|
13
|
+
const { key, events = {} } = sselectPageItem
|
|
14
|
+
const { props = {} as SselectPageProps, disabled = false } = sselectPageItem
|
|
15
|
+
|
|
16
|
+
// 处理动态禁用逻辑
|
|
17
|
+
const isDisabled = isFunction(disabled)
|
|
18
|
+
? disabled(toolbarState.getAllValues(), toolbarState)
|
|
19
|
+
: disabled
|
|
20
|
+
|
|
21
|
+
// 处理事件,注入工具栏状态
|
|
22
|
+
const processedEvents: Record<string, VoidFunction> = {}
|
|
23
|
+
Object.keys(events).forEach((eventName) => {
|
|
24
|
+
processedEvents[eventName] = (...args: any[]) => {
|
|
25
|
+
// 对于 SselectPage,先更新状态,再调用外部事件处理函数
|
|
26
|
+
if (eventName === 'onUpdate:value' || eventName === 'onChange') {
|
|
27
|
+
const value = args[0] // SselectPage 的第一个参数是选中的值
|
|
28
|
+
// 立即更新工具栏状态,确保外部事件处理函数能获取到最新值
|
|
29
|
+
toolbarState.setValue(key, value)
|
|
30
|
+
|
|
31
|
+
// 对于 onChange 事件,使用 nextTick 确保在下一个 tick 中执行外部处理函数
|
|
32
|
+
if (eventName === 'onChange') {
|
|
33
|
+
nextTick(() => {
|
|
34
|
+
;(events[eventName] as VoidFunction)?.(
|
|
35
|
+
...args,
|
|
36
|
+
toolbarState.getAllValues(),
|
|
37
|
+
toolbarState,
|
|
38
|
+
)
|
|
39
|
+
})
|
|
40
|
+
return // 提前返回,避免重复执行
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 调用原始事件处理函数,传入完整的上下文
|
|
45
|
+
events[eventName]?.(...args, toolbarState.getAllValues(), toolbarState)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// 如果没有设置 onChange 事件,添加默认的状态更新
|
|
50
|
+
if (!processedEvents['onUpdate:value'] && !processedEvents.onChange) {
|
|
51
|
+
processedEvents['onUpdate:value'] = (value: any) => {
|
|
52
|
+
toolbarState.setValue(key, value)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 处理 extraParams 的响应式更新
|
|
57
|
+
// 如果 props.extraParams 是函数,则动态计算
|
|
58
|
+
const computedProps = computed(() => {
|
|
59
|
+
const allValues = toolbarState.getAllValues()
|
|
60
|
+
const dynamicProps: any = { ...props, disabled: isDisabled }
|
|
61
|
+
|
|
62
|
+
// 动态计算 extraParams
|
|
63
|
+
if (isFunction(props.extraParams)) {
|
|
64
|
+
dynamicProps.extraParams = props.extraParams(allValues, toolbarState)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return dynamicProps
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
// 如果需要自动聚焦,添加 ref 处理
|
|
71
|
+
if (props.autoFocus) {
|
|
72
|
+
processedEvents.ref = (el: ComponentPublicInstance | null) => {
|
|
73
|
+
const component = el
|
|
74
|
+
if (component) {
|
|
75
|
+
// 使用 nextTick 确保 DOM 已经渲染完成
|
|
76
|
+
nextTick(() => {
|
|
77
|
+
try {
|
|
78
|
+
;(component as any)?.$refs?.selectRef?.focus?.()
|
|
79
|
+
} catch {
|
|
80
|
+
// 忽略焦点设置失败的错误
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 计算安全的默认值
|
|
88
|
+
const rawVal = toolbarState.getValue(key)
|
|
89
|
+
const propVal = props.value
|
|
90
|
+
const safeValue =
|
|
91
|
+
rawVal !== undefined && rawVal !== null
|
|
92
|
+
? rawVal
|
|
93
|
+
: propVal !== undefined && propVal !== null
|
|
94
|
+
? propVal
|
|
95
|
+
: undefined
|
|
96
|
+
|
|
97
|
+
const SselectPageComponent = getRegisteredComponent('SselectPage')
|
|
98
|
+
if (!SselectPageComponent) {
|
|
99
|
+
console.warn('BaseTable: SselectPage component is not registered.')
|
|
100
|
+
return <span style={{ color: 'red' }}>SselectPage component missing</span>
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const Comp = markRaw(SselectPageComponent) as any
|
|
104
|
+
return (
|
|
105
|
+
<Comp key={key} value={safeValue} allowClear {...computedProps.value} {...processedEvents} />
|
|
106
|
+
)
|
|
107
|
+
}
|