vue3-smart-table 1.0.2 → 1.0.3

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.
@@ -0,0 +1,307 @@
1
+ /**
2
+ * 内置渲染器集合
3
+ * 可以按需引入或批量注册
4
+ */
5
+ import { h } from 'vue'
6
+ import { ElButton, ElTag, ElImage, ElMessage } from 'element-plus'
7
+ import { DocumentCopy, CopyDocument } from '@element-plus/icons-vue'
8
+ import type { ColumnConfig } from '../types'
9
+ import { getValueByPath } from '../utils/path'
10
+ import { wrapSFCComponent, createFunctionalRenderer } from '../renderer'
11
+ import EditableInput from './input.vue'
12
+ import EditableNumber from './inputNumber.vue'
13
+ import EditableSelect from './select.vue'
14
+
15
+ /**
16
+ * 包装 SFC 组件
17
+ */
18
+ const input = wrapSFCComponent(EditableInput)
19
+ const inputNumber = wrapSFCComponent(EditableNumber)
20
+ const select = wrapSFCComponent(EditableSelect)
21
+
22
+ /**
23
+ * button 渲染器
24
+ */
25
+ const button = createFunctionalRenderer((props) => {
26
+ const rp = props.col.renderProps || {}
27
+ const val = getValueByPath(props.row, props.col.key)
28
+ return h(ElButton as any, {
29
+ type: rp.type || 'primary',
30
+ ...rp,
31
+ onClick: () => props.onClick?.(props.row, props.col)
32
+ }, () => rp.label || val)
33
+ })
34
+
35
+ /**
36
+ * link 渲染器
37
+ */
38
+ const link = createFunctionalRenderer((props) => {
39
+ const rp = props.col.renderProps || {}
40
+ const val = getValueByPath(props.row, props.col.key)
41
+ return h('a', {
42
+ href: rp.href || '#',
43
+ target: rp.blank ? '_blank' : '_self',
44
+ style: rp.style || 'color:#409EFF;cursor:pointer;',
45
+ }, rp.label || val)
46
+ })
47
+
48
+ /**
49
+ * html 渲染器
50
+ */
51
+ const html = createFunctionalRenderer((props) => {
52
+ const val = getValueByPath(props.row, props.col.key)
53
+ return h('div', {
54
+ class: 'line-clamp-2',
55
+ innerHTML: val ?? '',
56
+ ...(props.col?.renderProps || {})
57
+ })
58
+ })
59
+
60
+ /**
61
+ * copy 渲染器
62
+ */
63
+ const copy = createFunctionalRenderer((props) => {
64
+ const val = getValueByPath(props.row, props.col.key) ?? ''
65
+ const rp = props.col.renderProps ?? {}
66
+ const butStyle = {
67
+ 'position': 'absolute',
68
+ 'right': '-5px',
69
+ 'top': '50%',
70
+ 'transform': 'translateY(-50%)',
71
+ 'cursor': 'pointer',
72
+ 'display': 'none',
73
+ 'font-size': '12px',
74
+ 'color': rp.iconColor || '#409EFF',
75
+ 'user-select': 'none'
76
+ }
77
+ return h('div', {
78
+ class: 'st_copy_wrapper',
79
+ style: 'width: 100%; position: relative; display: inline-block;'
80
+ },
81
+ [
82
+ h('span', {
83
+ class: 'st_copy_text line-clamp-1',
84
+ style: 'padding-right: 10px; display: block;',
85
+ }, val),
86
+ val && h('span', {
87
+ class: 'st_copy_btn',
88
+ style: butStyle,
89
+ title: rp.copyTitle || '复制',
90
+ onClick: () => {
91
+ if (!val) return
92
+ try {
93
+ if (navigator.clipboard && navigator.clipboard.writeText) {
94
+ navigator.clipboard.writeText(val).then(() => {
95
+ ElMessage.success(rp.successText ?? '复制成功')
96
+ }).catch(() => {
97
+ ElMessage.error(rp.errorText ?? '复制失败')
98
+ })
99
+ } else {
100
+ const textarea = document.createElement('textarea')
101
+ textarea.value = val
102
+ textarea.style.position = 'fixed'
103
+ textarea.style.opacity = '0'
104
+ document.body.appendChild(textarea)
105
+ textarea.select()
106
+ const successful = document.execCommand('copy')
107
+ document.body.removeChild(textarea)
108
+
109
+ if (successful) {
110
+ ElMessage.success(rp.successText ?? '复制成功')
111
+ } else {
112
+ ElMessage.error(rp.errorText ?? '复制失败')
113
+ }
114
+ }
115
+ } catch (err) {
116
+ ElMessage.error(rp.errorText ?? '复制失败')
117
+ }
118
+ }
119
+ }, [h(DocumentCopy, {
120
+ style: 'width: 1em; height: 1em;'
121
+ })])
122
+ ].filter(Boolean)
123
+ )
124
+ })
125
+
126
+ /**
127
+ * img 渲染器
128
+ */
129
+ const img = createFunctionalRenderer((props) => {
130
+ const val = getValueByPath(props.row, props.col.key) ?? ''
131
+ const rp = props.col?.renderProps || {}
132
+
133
+ const getImageList = () => {
134
+ if (!val) return []
135
+ if (Array.isArray(val)) {
136
+ return val.filter(item => item && typeof item === 'string')
137
+ }
138
+ return [val]
139
+ }
140
+
141
+ const imageList = getImageList()
142
+
143
+ if (imageList.length === 0) {
144
+ return rp.placeholder || ''
145
+ }
146
+
147
+ const defaultStyle = {
148
+ width: rp.width || '80px',
149
+ height: rp.height || '80px',
150
+ marginRight: imageList.length > 1 ? '4px' : '0',
151
+ ...(rp.style || {})
152
+ }
153
+
154
+ if (imageList.length === 1) {
155
+ return h(ElImage, {
156
+ src: imageList[0],
157
+ previewSrcList: rp.previewSrcList || imageList,
158
+ fit: rp.fit || 'contain',
159
+ style: defaultStyle,
160
+ ...rp
161
+ })
162
+ }
163
+
164
+ return h('div',
165
+ {
166
+ class: 'st_img_wrapper',
167
+ style: 'display: flex; align-items: center; position: relative'
168
+ },
169
+ [
170
+ h(ElImage, {
171
+ src: imageList[0],
172
+ previewSrcList: rp.previewSrcList || imageList,
173
+ fit: rp.fit || 'contain',
174
+ style: defaultStyle,
175
+ ...rp
176
+ }),
177
+ imageList.length > 1 && h('span', {
178
+ class: 'st_img_total',
179
+ style: `position: absolute; top: 0; right: 0; `,
180
+ title: `${imageList.length}`
181
+ }, [h(CopyDocument, { style: `width: 1em; height: 1em; ` })])
182
+ ]
183
+ )
184
+ })
185
+
186
+ /**
187
+ * dict 渲染器
188
+ */
189
+ const dict = createFunctionalRenderer((props) => {
190
+ const val = getValueByPath(props.row, props.col.key) ?? ''
191
+ const rp = props.col.renderProps || {}
192
+ const options = rp.options ?? []
193
+ const showValue = rp.showValue ?? false
194
+
195
+ if (val === null || val === undefined || val === '') return ''
196
+
197
+ const values = Array.isArray(val) ? val.map(String) : [String(val)]
198
+ const matchedOptions = options.filter((opt: any) => values.includes(String(opt.value)))
199
+ const unmatched = values.filter(v => !options.some((opt: any) => String(opt.value) === v))
200
+
201
+ const children = matchedOptions.map((item: any, _index: number) => {
202
+ return h(
203
+ ElTag,
204
+ { key: item.value, type: item.listClass, class: item.cssClass, disableTransitions: true },
205
+ { default: () => item.label + ' ' }
206
+ )
207
+ })
208
+
209
+ if (showValue && unmatched.length > 0) {
210
+ children.push(h('span', {}, unmatched.join(' ')))
211
+ }
212
+
213
+ return h('div', {}, children)
214
+ })
215
+
216
+ /**
217
+ * map 渲染器
218
+ */
219
+ const map = createFunctionalRenderer((props) => {
220
+ const val = getValueByPath(props.row, props.col.key) ?? ''
221
+ const options = (props.col.renderProps?.options ?? {}) as Record<string, any>
222
+ return val != null ? options[val] ?? '' : ''
223
+ })
224
+
225
+ /**
226
+ * formatter 渲染器
227
+ */
228
+ export function isDataColumn(
229
+ col: ColumnConfig
230
+ ): col is any {
231
+ return typeof (col as any).formatter === 'function'
232
+ }
233
+
234
+ const formatter = createFunctionalRenderer((props) => {
235
+ const { col, row } = props
236
+ const val = getValueByPath(props.row, props.col.key) ?? ''
237
+ if (isDataColumn(col)) {
238
+ return col.formatter?.(val, row)
239
+ }
240
+ return val ?? ''
241
+ })
242
+
243
+ /**
244
+ * icon 渲染器
245
+ */
246
+ const icon = createFunctionalRenderer((props) => {
247
+ const val = getValueByPath(props.row, props.col.key) ?? ''
248
+ const rp = props.col.renderProps || {}
249
+ if (!val) return ''
250
+ // 判断网络图片
251
+ if (/^https?:\/\//.test(val)) {
252
+ return h(ElImage, {
253
+ src: val,
254
+ previewSrcList: [val],
255
+ fit: 'contain',
256
+ style: 'width:40px;height:40px',
257
+ ...rp
258
+ })
259
+ }
260
+ // 判断 svg 源码
261
+ if (/^\s*<svg[\s\S]*<\/svg>\s*$/.test(val)) {
262
+ return h('div', {
263
+ innerHTML: val,
264
+ style: `width:40px;height:40px;display:inline-block;${rp.style || ''}`,
265
+ ...rp
266
+ })
267
+ }
268
+ // 默认当作 iconfont
269
+ return h('i', {
270
+ class: val,
271
+ style: `font-size:20px;${rp.style || ''}`,
272
+ ...rp
273
+ })
274
+ })
275
+
276
+ /**
277
+ * 所有内置渲染器
278
+ */
279
+ export const builtInRenderers = {
280
+ input,
281
+ 'input-number': inputNumber,
282
+ select,
283
+ button,
284
+ link,
285
+ html,
286
+ copy,
287
+ img,
288
+ dict,
289
+ map,
290
+ formatter,
291
+ icon,
292
+ }
293
+
294
+ /**
295
+ * 安装所有内置渲染器
296
+ */
297
+ export function registerBuiltInRenderers(registry: { registerMultiple: (renderers: Record<string, any>) => void }) {
298
+ registry.registerMultiple(builtInRenderers)
299
+ }
300
+
301
+ /**
302
+ * 创建默认渲染器集合(兼容旧 API)
303
+ * @deprecated 建议使用插件化架构
304
+ */
305
+ export function createRenderer() {
306
+ return builtInRenderers
307
+ }
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <el-input
3
+ v-model="value"
4
+ v-bind="{ placeholder: '', size: 'small', clearable: true, ...col.renderProps }"
5
+ @blur="onBlur"
6
+ @keyup.enter="onEnter"
7
+ />
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { ref, watch } from 'vue'
12
+ import type { ColumnConfig } from '../types'
13
+ import { getValueByPath, setValueByPath } from '../utils/path'
14
+
15
+ interface Props {
16
+ readonly row: any
17
+ readonly col: ColumnConfig
18
+ onCellBlur?: (row: any, col: ColumnConfig) => void
19
+ onCellEnter?: (row: any, col: ColumnConfig) => void
20
+ }
21
+
22
+ const props = defineProps<Props>()
23
+ const value = ref(getValueByPath(props.row, props.col.key))
24
+
25
+ watch(value, (v) => {
26
+ setValueByPath(props.row, props.col.key, v)
27
+ })
28
+
29
+ const onBlur = () => props.onCellBlur?.(props.row, props.col)
30
+ const onEnter = () => props.onCellEnter?.(props.row, props.col)
31
+ </script>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <el-input-number
3
+ v-model="value"
4
+ v-bind="{ min: 0, max: 99999, controls: false, size: 'small', ...col.renderProps }"
5
+ @blur="onBlur"
6
+ @keyup.enter="onEnter"
7
+ />
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { ref, watch } from 'vue'
12
+ import type { ColumnConfig } from '../types'
13
+ import { getValueByPath, setValueByPath } from '../utils/path'
14
+
15
+ interface Props {
16
+ readonly row: any
17
+ readonly col: ColumnConfig
18
+ onCellChange?: (row: any, col: ColumnConfig) => void
19
+ onCellBlur?: (row: any, col: ColumnConfig) => void
20
+ onCellEnter?: (row: any, col: ColumnConfig) => void
21
+ }
22
+
23
+ const props = defineProps<Props>()
24
+ const value = ref(getValueByPath(props.row, props.col.key))
25
+
26
+ watch(value, (v) => {
27
+ setValueByPath(props.row, props.col.key, v)
28
+ props.onCellChange?.(props.row, props.col)
29
+ })
30
+
31
+ const onBlur = () => props.onCellBlur?.(props.row, props.col)
32
+ const onEnter = () => props.onCellEnter?.(props.row, props.col)
33
+ </script>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <el-select
3
+ v-model="value"
4
+ v-bind="{ placeholder: '请选择', size: 'small', clearable: true, ...col.renderProps }"
5
+ @change="onChange"
6
+ @blur="onBlur"
7
+ @keyup.enter="onEnter"
8
+ >
9
+ <el-option
10
+ v-for="opt in col.renderProps?.options || []"
11
+ :key="opt.value"
12
+ :label="opt.label"
13
+ :value="opt.value"
14
+ />
15
+ </el-select>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { ref, watch } from 'vue'
20
+ import type { ColumnConfig } from '../types'
21
+ import { getValueByPath, setValueByPath } from '../utils/path'
22
+
23
+ interface Props {
24
+ readonly row: any
25
+ readonly col: ColumnConfig
26
+ onCellChange?: (row: any, col: ColumnConfig) => void
27
+ onCellBlur?: (row: any, col: ColumnConfig) => void
28
+ onCellEnter?: (row: any, col: ColumnConfig) => void
29
+ }
30
+
31
+ const props = defineProps<Props>()
32
+ const value = ref(getValueByPath(props.row, props.col.key))
33
+
34
+ watch(value, (v) => {
35
+ setValueByPath(props.row, props.col.key, v)
36
+ })
37
+
38
+ const onChange = () => props.onCellChange?.(props.row, props.col)
39
+ const onBlur = () => props.onCellBlur?.(props.row, props.col)
40
+ const onEnter = () => props.onCellEnter?.(props.row, props.col)
41
+ </script>
@@ -0,0 +1,229 @@
1
+ import type { ButtonProps, TableColumnCtx } from 'element-plus'
2
+ import { DefaultRow } from 'element-plus/es/components/table/src/table/defaults'
3
+ import type { Component } from 'vue'
4
+
5
+ // 导出验证函数类型
6
+ export type { validateRendererProps } from './renderer'
7
+
8
+ /* ======================= 基础工具类型 ======================= */
9
+
10
+ /** 支持额外参数(Element Plus 透传 props) */
11
+ export type WithRestProps<T> = T & {
12
+ [key: string]: any
13
+ }
14
+
15
+ /* ======================= 渲染器系统 ======================= */
16
+
17
+ /** Renderer 组件类型 */
18
+ export type Renderer = Component
19
+
20
+ /** 渲染器注册表接口 */
21
+ export interface RendererRegistry {
22
+ register(name: string, renderer: Renderer): void
23
+ registerMultiple(renderers: Record<string, Renderer>): void
24
+ get(name: string): Renderer | undefined
25
+ has(name: string): boolean
26
+ unregister(name: string): boolean
27
+ clear(): void
28
+ names(): string[]
29
+ }
30
+
31
+ /** 全局配置接口 */
32
+ export interface SmartTableConfig {
33
+ /** 自定义渲染器 */
34
+ renderers?: Record<string, Renderer>
35
+ /** 默认分页配置 */
36
+ defaultPagination?: {
37
+ page?: number
38
+ size?: number
39
+ total?: number
40
+ }
41
+ /** 默认表格属性 */
42
+ defaultTableProps?: Record<string, any>
43
+ /** 默认列属性 */
44
+ defaultColumnProps?: Record<string, any>
45
+ }
46
+
47
+ /* ======================= 操作列按钮 ======================= */
48
+
49
+ export interface ButtonConfig<R = any> {
50
+ permission?: string | string[]
51
+ label: string
52
+ type?: 'primary' | 'success' | 'warning' | 'danger' | 'info'
53
+ action: (row: R) => void
54
+ visible?: (row: R) => boolean
55
+ width?: number
56
+ }
57
+
58
+ /* ======================= Renderer ======================= */
59
+
60
+ export type RendererName =
61
+ | 'html'
62
+ | 'copy'
63
+ | 'img'
64
+ | 'dict'
65
+ | 'map'
66
+ | 'formatter'
67
+ | 'icon'
68
+ | 'input'
69
+ | 'input-number'
70
+ | 'select'
71
+ | 'button'
72
+ | 'link'
73
+ | 'slot'
74
+
75
+ /** renderer 对应的 renderProps */
76
+ export interface RendererPropsMap {
77
+ html: WithRestProps<{
78
+ style?: string
79
+ class?: string
80
+ }>
81
+
82
+ copy: WithRestProps<{
83
+ /** 复制按钮图标颜色 */
84
+ iconColor?: string
85
+ /** 复制按钮提示文本 */
86
+ copyTitle?: string
87
+ /** 复制成功提示 */
88
+ successText?: string
89
+ /** 复制失败提示 */
90
+ errorText?: string
91
+ }>
92
+
93
+ img: WithRestProps<{
94
+ /** 图片宽度 */
95
+ width?: string | number
96
+ /** 图片高度 */
97
+ height?: string | number
98
+ /** 图片适应方式 */
99
+ fit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down'
100
+ /** 预览图片列表 */
101
+ previewSrcList?: string[]
102
+ /** 无图片时的占位文本 */
103
+ placeholder?: string
104
+ /** 自定义样式 */
105
+ style?: string
106
+ }>
107
+
108
+ dict: WithRestProps<{
109
+ /** 字典配置 */
110
+ options: Array<{
111
+ label: string
112
+ value: string | number
113
+ listClass?: string
114
+ cssClass?: string
115
+ }>
116
+ /** 是否显示未匹配的值 */
117
+ showValue?: boolean
118
+ }>
119
+
120
+ map: WithRestProps<{
121
+ /** key-value 映射 */
122
+ options: Record<string | number, any>
123
+ }>
124
+
125
+ formatter: never // formatter 使用 ColumnConfig.formatter 函数
126
+
127
+ icon: WithRestProps<{
128
+ /** 自定义样式 */
129
+ style?: string
130
+ /** 图标大小(像素) */
131
+ size?: number
132
+ /** 自定义类名 */
133
+ class?: string
134
+ }>
135
+
136
+ input: WithRestProps<{
137
+ /** 占位文本 */
138
+ placeholder?: string
139
+ /** 输入框尺寸 */
140
+ size?: 'small' | 'default' | 'large'
141
+ /** 是否可清空 */
142
+ clearable?: boolean
143
+ }>
144
+
145
+ 'input-number': WithRestProps<{
146
+ /** 最小值 */
147
+ min?: number
148
+ /** 最大值 */
149
+ max?: number
150
+ /** 步长 */
151
+ step?: number
152
+ /** 精度 */
153
+ precision?: number
154
+ /** 输入框尺寸 */
155
+ size?: 'small' | 'default' | 'large'
156
+ /** 是否显示增减按钮 */
157
+ controls?: boolean
158
+ }>
159
+
160
+ select: WithRestProps<{
161
+ /** 选项配置 */
162
+ options: Array<{
163
+ label: string
164
+ value: string | number
165
+ }>
166
+ /** 占位文本 */
167
+ placeholder?: string
168
+ /** 选择器尺寸 */
169
+ size?: 'small' | 'default' | 'large'
170
+ /** 是否可清空 */
171
+ clearable?: boolean
172
+ }>
173
+
174
+ button: WithRestProps<ButtonProps & {
175
+ /** 按钮文本 */
176
+ label?: string
177
+ /** 自定义样式 */
178
+ style?: string
179
+ /** 自定义类名 */
180
+ class?: string
181
+ }>
182
+
183
+ link: WithRestProps<{
184
+ /** 链接文本 */
185
+ label?: string
186
+ /** 链接地址 */
187
+ href: string
188
+ /** 是否新窗口打开 */
189
+ blank?: boolean
190
+ /** 自定义样式 */
191
+ style?: string
192
+ /** 自定义类名 */
193
+ class?: string
194
+ }>
195
+ }
196
+
197
+ /* ======================= 列类型 ======================= */
198
+
199
+ export type ColumnType =
200
+ | 'default'
201
+ | 'selection'
202
+ | 'index'
203
+ | 'operation'
204
+
205
+ /* ======================= ColumnConfig ======================= */
206
+ export interface BaseColumn<R extends DefaultRow> {
207
+ key: keyof R & string
208
+ label?: string
209
+ visible?: boolean
210
+ inControl?: boolean
211
+ columnProps?: Partial<TableColumnCtx<R>>
212
+ render?: RendererName
213
+ slot?: string // 插槽名称,默认用 key
214
+ renderProps?: Partial<RendererPropsMap[keyof RendererPropsMap]>
215
+ buttons?: ButtonConfig<R>[] // operation 列专用
216
+ maxbtn?: number // operation 列专用
217
+ __rows?: R[] // operation 列内部使用
218
+ }
219
+
220
+ export interface SelectionColumn<R extends DefaultRow> extends BaseColumn<R> { type: 'selection' }
221
+ export interface IndexColumn<R extends DefaultRow> extends BaseColumn<R> { type: 'index' }
222
+ export interface OperationColumn<R extends DefaultRow> extends BaseColumn<R> { type: 'operation'; buttons: ButtonConfig<R>[] }
223
+ export interface DataColumn<R extends DefaultRow> extends BaseColumn<R> { type?: 'default'; formatter?: (value: any, row: R) => any }
224
+
225
+ export type ColumnConfig<R extends DefaultRow = any> =
226
+ | SelectionColumn<R>
227
+ | IndexColumn<R>
228
+ | OperationColumn<R>
229
+ | DataColumn<R>
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 安全获取对象深层属性
3
+ * 支持 a.b.c / a.0.b
4
+ */
5
+ export function getValueByPath(obj: any, path?: string) {
6
+ if (!obj || !path) return undefined
7
+ return path.split('.').reduce((acc, key) => acc?.[key], obj)
8
+ }
9
+
10
+ /**
11
+ * 安全设置对象深层属性(用于可编辑表格)
12
+ */
13
+ export function setValueByPath(
14
+ obj: any,
15
+ path: string,
16
+ value: any
17
+ ) {
18
+ if (!obj || !path) return
19
+ const keys = path.split('.')
20
+ const lastKey = keys.pop()!
21
+
22
+ const target = keys.reduce((acc, key) => {
23
+ if (!acc[key]) acc[key] = {}
24
+ return acc[key]
25
+ }, obj)
26
+
27
+ target[lastKey] = value
28
+ }
29
+
package/src/index.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Vue3 Smart Table - 主入口文件
3
+ *
4
+ * 基于 Vue 3 + Element Plus 的高可复用表格组件库
5
+ */
6
+ import SmartTable from './components/SmartTable/index.vue'
7
+
8
+ // 导出类型
9
+ export * from './components/SmartTable/types'
10
+
11
+ // 导出核心功能(从 SmartTable 内部导出)
12
+ export {
13
+ getRendererManager,
14
+ wrapSFCComponent,
15
+ createFunctionalRenderer,
16
+ validateRendererProps
17
+ } from './components/SmartTable/renderer'
18
+
19
+ export {
20
+ setSmartTableConfig,
21
+ getSmartTableConfig
22
+ } from './components/SmartTable/config'
23
+
24
+ // 导出内置渲染器
25
+ export {
26
+ builtInRenderers,
27
+ registerBuiltInRenderers,
28
+ createRenderer
29
+ } from './components/SmartTable/renderers'
30
+
31
+ // 导出类型工具
32
+ export { defineColumn } from './types/enhanced'
33
+
34
+ // 导出主组件
35
+ export { SmartTable }
36
+
37
+ // 默认导出
38
+ export default SmartTable