@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.
Files changed (59) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/core.css +1 -0
  3. package/dist/index.cjs +61 -0
  4. package/dist/index.js +50858 -0
  5. package/dist/types/core/index.d.ts +6 -0
  6. package/dist/types/core/src/BaseTable/helpers.d.ts +9 -0
  7. package/dist/types/core/src/BaseTable/index.d.ts +190 -0
  8. package/dist/types/core/src/BaseTable/registry.d.ts +9 -0
  9. package/dist/types/core/src/BaseTable/renderers/AreaCascader.d.ts +5 -0
  10. package/dist/types/core/src/BaseTable/renderers/AutoComplete.d.ts +4 -0
  11. package/dist/types/core/src/BaseTable/renderers/Button.d.ts +4 -0
  12. package/dist/types/core/src/BaseTable/renderers/Cascader.d.ts +5 -0
  13. package/dist/types/core/src/BaseTable/renderers/Checkbox.d.ts +4 -0
  14. package/dist/types/core/src/BaseTable/renderers/CheckboxGroup.d.ts +4 -0
  15. package/dist/types/core/src/BaseTable/renderers/DatePicker.d.ts +4 -0
  16. package/dist/types/core/src/BaseTable/renderers/Input.d.ts +4 -0
  17. package/dist/types/core/src/BaseTable/renderers/InputGroup.d.ts +5 -0
  18. package/dist/types/core/src/BaseTable/renderers/InputNumber.d.ts +4 -0
  19. package/dist/types/core/src/BaseTable/renderers/InputPassword.d.ts +4 -0
  20. package/dist/types/core/src/BaseTable/renderers/RadioGroup.d.ts +4 -0
  21. package/dist/types/core/src/BaseTable/renderers/Select.d.ts +4 -0
  22. package/dist/types/core/src/BaseTable/renderers/SselectPage.d.ts +4 -0
  23. package/dist/types/core/src/BaseTable/renderers/Switch.d.ts +4 -0
  24. package/dist/types/core/src/BaseTable/renderers/TreeSelect.d.ts +4 -0
  25. package/dist/types/core/src/BaseTable/renderers/Upload.d.ts +4 -0
  26. package/dist/types/core/src/BaseTable/renderers/index.d.ts +24 -0
  27. package/dist/types/core/src/BaseTable/state.d.ts +19 -0
  28. package/dist/types/core/src/BaseTable/types.d.ts +391 -0
  29. package/dist/types/core/src/BaseTable/utils.d.ts +8 -0
  30. package/dist/types/core/src/index.d.ts +2 -0
  31. package/index.css +2 -0
  32. package/index.ts +21 -0
  33. package/package.json +39 -3
  34. package/src/BaseTable/helpers.tsx +91 -0
  35. package/src/BaseTable/index.vue +881 -0
  36. package/src/BaseTable/registry.ts +20 -0
  37. package/src/BaseTable/renderers/AreaCascader.tsx +64 -0
  38. package/src/BaseTable/renderers/AutoComplete.tsx +101 -0
  39. package/src/BaseTable/renderers/Button.tsx +62 -0
  40. package/src/BaseTable/renderers/Cascader.tsx +45 -0
  41. package/src/BaseTable/renderers/Checkbox.tsx +65 -0
  42. package/src/BaseTable/renderers/CheckboxGroup.tsx +57 -0
  43. package/src/BaseTable/renderers/DatePicker.tsx +83 -0
  44. package/src/BaseTable/renderers/Input.tsx +140 -0
  45. package/src/BaseTable/renderers/InputGroup.tsx +115 -0
  46. package/src/BaseTable/renderers/InputNumber.tsx +205 -0
  47. package/src/BaseTable/renderers/InputPassword.tsx +81 -0
  48. package/src/BaseTable/renderers/RadioGroup.tsx +63 -0
  49. package/src/BaseTable/renderers/Select.tsx +96 -0
  50. package/src/BaseTable/renderers/SselectPage.tsx +107 -0
  51. package/src/BaseTable/renderers/Switch.tsx +60 -0
  52. package/src/BaseTable/renderers/TreeSelect.tsx +81 -0
  53. package/src/BaseTable/renderers/Upload.tsx +92 -0
  54. package/src/BaseTable/renderers/index.ts +67 -0
  55. package/src/BaseTable/state.ts +37 -0
  56. package/src/BaseTable/types.ts +507 -0
  57. package/src/BaseTable/utils.tsx +144 -0
  58. package/src/index.ts +3 -0
  59. 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
+ }