@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,881 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { Tooltip } from '@antsoo-lib/components'
|
|
3
|
+
import { QuestionCircleOutlined } from '@antsoo-lib/icons'
|
|
4
|
+
import type { AnyObject } from '@antsoo-lib/shared'
|
|
5
|
+
import { PRECISION } from '@antsoo-lib/shared'
|
|
6
|
+
import { env, generateUniqueId, isNaN } from '@antsoo-lib/utils'
|
|
7
|
+
import { cloneDeep } from 'lodash-es'
|
|
8
|
+
import type { VxeComponentSizeType } from 'vxe-pc-ui'
|
|
9
|
+
import type { VxeGridPropTypes, VxeGridProps } from 'vxe-table'
|
|
10
|
+
|
|
11
|
+
import { computed, ref } from 'vue'
|
|
12
|
+
|
|
13
|
+
import { renderToolbar, useToolbarState } from './utils'
|
|
14
|
+
import type { ToolbarConfig } from './utils'
|
|
15
|
+
|
|
16
|
+
// Props默认值
|
|
17
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
18
|
+
checkbox: true,
|
|
19
|
+
radio: false,
|
|
20
|
+
drag: false,
|
|
21
|
+
height: '100%',
|
|
22
|
+
loading: false,
|
|
23
|
+
pager: true,
|
|
24
|
+
seq: false,
|
|
25
|
+
showFooter: true,
|
|
26
|
+
total: 0,
|
|
27
|
+
tree: false,
|
|
28
|
+
lToolBarCount: 4,
|
|
29
|
+
rToolBarCount: 4,
|
|
30
|
+
permissions: () => [],
|
|
31
|
+
getCommonCodeOptions: undefined,
|
|
32
|
+
})
|
|
33
|
+
// 抛出Emit方法(类型化)
|
|
34
|
+
const emit = defineEmits<{
|
|
35
|
+
(e: 'pageChange', page: number, size: number): void
|
|
36
|
+
(e: 'pageShowSizeChange', current: number, size: number): void
|
|
37
|
+
(e: 'selectAllChangeEvent', checked: boolean, records: any[]): void
|
|
38
|
+
(
|
|
39
|
+
e: 'selectChangeEvent',
|
|
40
|
+
params: { checked: boolean; row: any; rowIndex: number; records: any[] },
|
|
41
|
+
): void
|
|
42
|
+
(e: 'radioChangeEvent', params: { row: any; rowIndex: number; records: any }): void
|
|
43
|
+
(e: 'buttonClick', params: { button: Button; row: any; grid: any }): void
|
|
44
|
+
}>()
|
|
45
|
+
const { isDev, isTest } = env()
|
|
46
|
+
const showHelpText = isDev || isTest
|
|
47
|
+
|
|
48
|
+
// Interface
|
|
49
|
+
interface Button {
|
|
50
|
+
name?: (row: any) => string
|
|
51
|
+
permission?: string
|
|
52
|
+
event?: (row: any, controls: any, grid: any) => void
|
|
53
|
+
disabled?: (row: any, grid: any) => boolean
|
|
54
|
+
beforeCreate?: (row: any) => boolean
|
|
55
|
+
props?: AnyObject
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface Column {
|
|
59
|
+
type?: 'checkbox' | 'radio' | 'seq' | 'html' | 'expand' | null
|
|
60
|
+
buttons?: Button[]
|
|
61
|
+
[key: string]: any
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface Props {
|
|
65
|
+
// 增加一个 scope 属性,用于区分同构表格
|
|
66
|
+
scope?: string
|
|
67
|
+
// 是否开启选择器
|
|
68
|
+
checkbox?: boolean
|
|
69
|
+
// 复选框配置项
|
|
70
|
+
checkboxConfig?: AnyObject
|
|
71
|
+
// 是否开启单选框
|
|
72
|
+
radio?: boolean
|
|
73
|
+
// 单选框配置项
|
|
74
|
+
radioConfig?: AnyObject
|
|
75
|
+
// 列配置项
|
|
76
|
+
columns?: Column[]
|
|
77
|
+
// 个性化信息配置项
|
|
78
|
+
customConfig?: AnyObject
|
|
79
|
+
// 列配置信息
|
|
80
|
+
columnConfig?: AnyObject
|
|
81
|
+
// 渲染数据
|
|
82
|
+
data?: any[]
|
|
83
|
+
// 是否开启拖拽
|
|
84
|
+
drag?: boolean
|
|
85
|
+
// 表尾数据,数组-支持多行
|
|
86
|
+
footerData?: any[]
|
|
87
|
+
// 表格高度
|
|
88
|
+
height?: string
|
|
89
|
+
// 左侧工具栏
|
|
90
|
+
lToolBar?: ToolbarConfig
|
|
91
|
+
// 是否开启加载
|
|
92
|
+
loading?: boolean
|
|
93
|
+
// 个性化定制额外的列配置项,注意:若自定义配置项与默认配置项冲突,以自定义配置项为准
|
|
94
|
+
options?: Partial<VxeGridProps>
|
|
95
|
+
// 是否开启分页
|
|
96
|
+
pager?: boolean
|
|
97
|
+
// 右侧工具栏
|
|
98
|
+
rToolBar?: ToolbarConfig
|
|
99
|
+
// 行配置信息
|
|
100
|
+
rowConfig?: AnyObject
|
|
101
|
+
// 行拖拽配置项
|
|
102
|
+
rowDragConfig?: AnyObject
|
|
103
|
+
// 是否开启序号
|
|
104
|
+
seq?: boolean
|
|
105
|
+
// 是否展示表尾数据
|
|
106
|
+
showFooter?: boolean
|
|
107
|
+
// 工具栏配置
|
|
108
|
+
toolbarConfig?: AnyObject
|
|
109
|
+
// tooltip 配置项
|
|
110
|
+
tooltipConfig?: AnyObject
|
|
111
|
+
// 页码:当前加载总数据条数
|
|
112
|
+
total?: number
|
|
113
|
+
// 是否开启树状结构
|
|
114
|
+
tree?: boolean
|
|
115
|
+
// 树状结构配置项
|
|
116
|
+
treeConfig?: AnyObject
|
|
117
|
+
// 左侧工具栏显示个数
|
|
118
|
+
lToolBarCount?: number | (() => number)
|
|
119
|
+
// 右侧工具栏显示个数
|
|
120
|
+
rToolBarCount?: number | (() => number)
|
|
121
|
+
// 分页当前页(受控)
|
|
122
|
+
currentPage?: number
|
|
123
|
+
// 分页每页条数(受控)
|
|
124
|
+
pageSize?: number
|
|
125
|
+
|
|
126
|
+
// Decoupled: Permissions array
|
|
127
|
+
permissions?: string[]
|
|
128
|
+
// Decoupled: Function to get common code options
|
|
129
|
+
getCommonCodeOptions?: (type: string) => Array<{ code: string; name: string }>
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// table 实例
|
|
133
|
+
const basetable = ref()
|
|
134
|
+
// 分页器实例
|
|
135
|
+
const paginationRef = ref()
|
|
136
|
+
// 当前每页数量,默认10条/页
|
|
137
|
+
const currentPageSize = ref(10)
|
|
138
|
+
|
|
139
|
+
// 受控分页:优先使用外部传入,否则回退默认
|
|
140
|
+
const paginationCurrent = computed(() => props.currentPage ?? 1)
|
|
141
|
+
const paginationPageSize = computed(() => props.pageSize ?? currentPageSize.value)
|
|
142
|
+
|
|
143
|
+
// 格式化数字
|
|
144
|
+
const formatNumber = (value: any, precision: number) => {
|
|
145
|
+
if (value === null || value === undefined || value === '') return ''
|
|
146
|
+
const num = Number(value)
|
|
147
|
+
if (isNaN(num)) return value
|
|
148
|
+
return num.toFixed(precision)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 顶部左右侧工具栏状态实例
|
|
152
|
+
const lToolBarState = useToolbarState()
|
|
153
|
+
const rToolBarState = useToolbarState()
|
|
154
|
+
|
|
155
|
+
// 抽出操作栏,根据是否存在buttons来判断
|
|
156
|
+
const actionColumn = computed(() => {
|
|
157
|
+
return props.columns?.find((column) => column.buttons)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// 预过滤拥有权限的按钮,减少每行的计算量
|
|
161
|
+
const permittedButtons = computed(() => {
|
|
162
|
+
const all = actionColumn.value?.buttons || []
|
|
163
|
+
// Decoupled: Use props.permissions
|
|
164
|
+
return all.filter((b) => !b.permission || props.permissions.includes(b.permission))
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
// 左侧工具栏
|
|
168
|
+
const lToolBarItems = computed(() => {
|
|
169
|
+
if (!props.lToolBar) return []
|
|
170
|
+
// Decoupled: Pass props.permissions
|
|
171
|
+
return renderToolbar(props.lToolBar, lToolBarState, props.lToolBarCount, props.permissions)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
// 右侧工具栏
|
|
175
|
+
const rToolBarItems = computed(() => {
|
|
176
|
+
if (!props.rToolBar) return []
|
|
177
|
+
// Decoupled: Pass props.permissions
|
|
178
|
+
return renderToolbar(props.rToolBar, rToolBarState, props.rToolBarCount, props.permissions)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
// 树状配置
|
|
182
|
+
const treeConfig = computed(() => {
|
|
183
|
+
return props.tree
|
|
184
|
+
? {
|
|
185
|
+
treeConfig: {
|
|
186
|
+
rowField: 'id',
|
|
187
|
+
parentField: 'pid',
|
|
188
|
+
transform: true,
|
|
189
|
+
expandAll: true,
|
|
190
|
+
reserve: true,
|
|
191
|
+
...props.treeConfig,
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
: {}
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
const tableId = computed(() => {
|
|
198
|
+
if (props.options?.id) {
|
|
199
|
+
return props.options.id
|
|
200
|
+
}
|
|
201
|
+
// 获取当前路径
|
|
202
|
+
const path = location.pathname
|
|
203
|
+
// 结合唯一标识(若有),生成唯一码
|
|
204
|
+
const rawId = `${path}::${props.scope || 'default'}`
|
|
205
|
+
return generateUniqueId(rawId)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const gridColumns = computed(() => handleGridColumns())
|
|
209
|
+
const options = computed(() => {
|
|
210
|
+
const gridColumnsVal = gridColumns.value
|
|
211
|
+
return {
|
|
212
|
+
id: tableId.value,
|
|
213
|
+
// 表格基础尺寸
|
|
214
|
+
size: 'mini' as VxeComponentSizeType,
|
|
215
|
+
// 高度自适应
|
|
216
|
+
height: props.height,
|
|
217
|
+
// 给表头的单元格附加 className
|
|
218
|
+
headerCellClassName: 'base-columns-cell',
|
|
219
|
+
// 单元格class
|
|
220
|
+
cellClassName: 'base-columns-cell',
|
|
221
|
+
// 斑马纹
|
|
222
|
+
stripe: true,
|
|
223
|
+
// 边框
|
|
224
|
+
border: true,
|
|
225
|
+
// 内容过长时显示为省略号
|
|
226
|
+
showOverflow: true,
|
|
227
|
+
// 自动监听父元素的变化去重新调用 recalculate 方法计算样式
|
|
228
|
+
autoResize: true,
|
|
229
|
+
// 列配置
|
|
230
|
+
columns: gridColumnsVal as VxeGridPropTypes.Column[],
|
|
231
|
+
// 表格数据
|
|
232
|
+
data: props.data,
|
|
233
|
+
// 表格是否显示加载中
|
|
234
|
+
loading: props.loading,
|
|
235
|
+
// 是否显示表尾
|
|
236
|
+
showFooter: props.showFooter,
|
|
237
|
+
// 列配置信息
|
|
238
|
+
columnConfig: {
|
|
239
|
+
// 每一列启用列宽调整
|
|
240
|
+
resizable: true,
|
|
241
|
+
useKey: true,
|
|
242
|
+
...props.columnConfig,
|
|
243
|
+
},
|
|
244
|
+
// 个性化信息配置项
|
|
245
|
+
customConfig: {
|
|
246
|
+
// 操作模式
|
|
247
|
+
mode: 'modal' as const,
|
|
248
|
+
storage: true,
|
|
249
|
+
...props.customConfig,
|
|
250
|
+
},
|
|
251
|
+
// 复选框配置项
|
|
252
|
+
checkboxConfig: {
|
|
253
|
+
// 整行触发选择
|
|
254
|
+
trigger: 'row' as const,
|
|
255
|
+
...props.checkboxConfig,
|
|
256
|
+
},
|
|
257
|
+
// 单选框配置项
|
|
258
|
+
radioConfig: {
|
|
259
|
+
// 整行触发选择
|
|
260
|
+
trigger: 'row' as const,
|
|
261
|
+
...props.radioConfig,
|
|
262
|
+
},
|
|
263
|
+
// 行配置信息
|
|
264
|
+
rowConfig: {
|
|
265
|
+
keyField: 'id',
|
|
266
|
+
// 当鼠标移到行时,是否要高亮当前行
|
|
267
|
+
isHover: true,
|
|
268
|
+
// 启用列拖拽调整顺序
|
|
269
|
+
drag: props.drag,
|
|
270
|
+
useKey: true,
|
|
271
|
+
...props.rowConfig,
|
|
272
|
+
},
|
|
273
|
+
// 行拖拽配置项
|
|
274
|
+
rowDragConfig: {
|
|
275
|
+
// 只对 tree-config 启用有效,是否允许同级行拖拽,用于树结构,启用后允许同层级之间进行拖拽
|
|
276
|
+
isPeerDrag: true,
|
|
277
|
+
...props.rowDragConfig,
|
|
278
|
+
},
|
|
279
|
+
// 工具栏配置
|
|
280
|
+
toolbarConfig: {
|
|
281
|
+
slots: {
|
|
282
|
+
buttons: 'lToolBar',
|
|
283
|
+
tools: 'rToolBar',
|
|
284
|
+
},
|
|
285
|
+
// 自定义容器类名
|
|
286
|
+
className: 'base-table-toolbar',
|
|
287
|
+
// 最大化表格
|
|
288
|
+
zoom: true,
|
|
289
|
+
// 表格设置
|
|
290
|
+
custom: true,
|
|
291
|
+
...props.toolbarConfig,
|
|
292
|
+
},
|
|
293
|
+
// tooltip 配置项
|
|
294
|
+
tooltipConfig: {
|
|
295
|
+
maxWidth: 200,
|
|
296
|
+
...props.tooltipConfig,
|
|
297
|
+
},
|
|
298
|
+
// 树形结构配置项
|
|
299
|
+
...treeConfig.value,
|
|
300
|
+
// 其他个性化定制
|
|
301
|
+
...props.options,
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 控制按钮是否展示
|
|
307
|
+
* @param {object} row - 当前按钮数据
|
|
308
|
+
*/
|
|
309
|
+
function getVisibleButtons(row: any) {
|
|
310
|
+
if (!permittedButtons.value.length) return []
|
|
311
|
+
return permittedButtons.value.filter((button) => !button.beforeCreate || button.beforeCreate(row))
|
|
312
|
+
}
|
|
313
|
+
const rowButtonLoading = ref<Record<string, boolean>>({})
|
|
314
|
+
function getButtonKey(row: any, button: any) {
|
|
315
|
+
const n = typeof button.name === 'function' ? button.name(row) : button.name
|
|
316
|
+
const id = row?.id ?? ''
|
|
317
|
+
return `${id}::${String(n)}`
|
|
318
|
+
}
|
|
319
|
+
function isButtonLoading(row: any, button: any) {
|
|
320
|
+
const k = getButtonKey(row, button)
|
|
321
|
+
return !!rowButtonLoading.value[k]
|
|
322
|
+
}
|
|
323
|
+
function handleRowButtonClick(button: any, row: any) {
|
|
324
|
+
const k = getButtonKey(row, button)
|
|
325
|
+
const controls = {
|
|
326
|
+
setLoading: (v: boolean) => {
|
|
327
|
+
rowButtonLoading.value[k] = v
|
|
328
|
+
},
|
|
329
|
+
getLoading: () => !!rowButtonLoading.value[k],
|
|
330
|
+
}
|
|
331
|
+
const fn = button.event
|
|
332
|
+
if (typeof fn === 'function') fn(row, controls, basetable.value)
|
|
333
|
+
emit('buttonClick', { button, row, grid: basetable.value })
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 跳转指定页码
|
|
338
|
+
* @param {number} page - 页码
|
|
339
|
+
*/
|
|
340
|
+
function goToPage(page: number) {
|
|
341
|
+
if (paginationRef.value && page >= 1) {
|
|
342
|
+
onChange(page, currentPageSize.value)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* 分页改变时触发
|
|
348
|
+
* @param {number} page - 页码
|
|
349
|
+
* @param {number} size - 每页条数
|
|
350
|
+
*/
|
|
351
|
+
function onChange(page: number, size: number) {
|
|
352
|
+
currentPageSize.value = size
|
|
353
|
+
emit('pageChange', page, size)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* 分页每页条数改变时触发
|
|
358
|
+
* @param {number} current - 页码
|
|
359
|
+
* @param {number} size - 每页条数
|
|
360
|
+
*/
|
|
361
|
+
function onShowSizeChange(current: number, size: number) {
|
|
362
|
+
currentPageSize.value = size
|
|
363
|
+
emit('pageShowSizeChange', current, size)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 用于 type=checkbox,触发全选
|
|
368
|
+
* @param {object} params - 事件参数对象
|
|
369
|
+
* @param {boolean} params.checked - 复选框是否被选中
|
|
370
|
+
*/
|
|
371
|
+
function selectAllChangeEvent({ checked }: { checked: boolean }) {
|
|
372
|
+
const $grid = basetable.value
|
|
373
|
+
if ($grid) {
|
|
374
|
+
const records = $grid.getCheckboxRecords()
|
|
375
|
+
emit('selectAllChangeEvent', checked, records)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* 用于 type=checkbox,触发行选择
|
|
381
|
+
* @param {object} params - 事件参数对象
|
|
382
|
+
* @param {boolean} params.checked - 复选框是否被选中
|
|
383
|
+
* @param {object} params.row - 当前选中的行数据
|
|
384
|
+
* @param {number} params.rowIndex - 当前选中的行下标
|
|
385
|
+
*/
|
|
386
|
+
function selectChangeEvent({
|
|
387
|
+
checked,
|
|
388
|
+
row,
|
|
389
|
+
rowIndex,
|
|
390
|
+
}: {
|
|
391
|
+
checked: boolean
|
|
392
|
+
row: any
|
|
393
|
+
rowIndex: number
|
|
394
|
+
}) {
|
|
395
|
+
const $grid = basetable.value
|
|
396
|
+
if ($grid) {
|
|
397
|
+
const records = $grid.getCheckboxRecords()
|
|
398
|
+
emit('selectChangeEvent', {
|
|
399
|
+
checked,
|
|
400
|
+
row,
|
|
401
|
+
rowIndex,
|
|
402
|
+
records,
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* 用于 type=radio,触发行选择
|
|
409
|
+
* @param {object} params - 事件参数对象
|
|
410
|
+
* @param {boolean} params.checked - 单选框是否被选中
|
|
411
|
+
* @param {object} params.row - 当前选中的行数据
|
|
412
|
+
* @param {number} params.rowIndex - 当前选中的行下标
|
|
413
|
+
*/
|
|
414
|
+
function radioChangeEvent({ row, rowIndex }: { row: any; rowIndex: number }) {
|
|
415
|
+
const $grid = basetable.value
|
|
416
|
+
if ($grid) {
|
|
417
|
+
const records = $grid.getRadioRecord()
|
|
418
|
+
emit('radioChangeEvent', {
|
|
419
|
+
row,
|
|
420
|
+
rowIndex,
|
|
421
|
+
records,
|
|
422
|
+
})
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// 处理表格列配置
|
|
427
|
+
function handleGridColumns() {
|
|
428
|
+
// 找到第一个没有type属性的列索引
|
|
429
|
+
const firstNonTypeIndex = props.columns?.findIndex((c) => !c.type) ?? -1
|
|
430
|
+
// 拷贝列配置
|
|
431
|
+
const columns = cloneDeep(props.columns) || []
|
|
432
|
+
// 是否支持序号列,默认不支持
|
|
433
|
+
if (props.seq) {
|
|
434
|
+
columns.unshift({
|
|
435
|
+
type: 'seq',
|
|
436
|
+
field: 'BaseTableSeq',
|
|
437
|
+
width: 70,
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
// 是否支持复选框,默认支持。给予field字段是为了给表尾数据的【总计】文案做关联匹配。
|
|
441
|
+
if (props.checkbox) {
|
|
442
|
+
columns.unshift({
|
|
443
|
+
type: 'checkbox',
|
|
444
|
+
field: 'BaseTablePlaceholder',
|
|
445
|
+
})
|
|
446
|
+
}
|
|
447
|
+
// 是否支持单选框,默认不支持。给予field字段是为了给表尾数据的【总计】文案做关联匹配。
|
|
448
|
+
if (props.radio) {
|
|
449
|
+
columns.unshift({
|
|
450
|
+
type: 'radio',
|
|
451
|
+
field: 'BaseTablePlaceholder',
|
|
452
|
+
})
|
|
453
|
+
}
|
|
454
|
+
// 递归处理列配置
|
|
455
|
+
const processColumn = (column: Column): any => {
|
|
456
|
+
const { buttons, ...col } = column
|
|
457
|
+
// 选择器居中时,若启用了溢出选项,会引起3px的误差
|
|
458
|
+
if (col.type === 'checkbox' || col.type === 'radio') {
|
|
459
|
+
return {
|
|
460
|
+
...col,
|
|
461
|
+
width: 'auto',
|
|
462
|
+
align: 'center',
|
|
463
|
+
fixed: 'left',
|
|
464
|
+
showOverflow: false,
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// 处理按钮插槽
|
|
468
|
+
if (buttons) {
|
|
469
|
+
return {
|
|
470
|
+
...col,
|
|
471
|
+
field: 'BaseTableEditColumns',
|
|
472
|
+
slots: { default: 'buttons' },
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// 处理 helpText
|
|
476
|
+
if (showHelpText || (col.helpText && col.helpText !== false)) {
|
|
477
|
+
if (col.type !== 'seq') {
|
|
478
|
+
col.slots = { ...col.slots, header: col.slots?.header ?? 'headerHelp' }
|
|
479
|
+
col.params = { ...col.params, helpText: col.helpText || col.field }
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// 为第一个没有type属性的列添加树节点和拖拽排序功能
|
|
484
|
+
const baseColumn: any = {
|
|
485
|
+
width: 'auto',
|
|
486
|
+
minWidth: 80,
|
|
487
|
+
footerAlign: 'right',
|
|
488
|
+
footerClassName: 'base-table-footer-cell',
|
|
489
|
+
...col,
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// 处理 commonCode 格式化
|
|
493
|
+
if (col.commonCode) {
|
|
494
|
+
const originalFormatter = col.formatter
|
|
495
|
+
// 缓存 getCommonCodeOptions 的结果,避免每次 formatter 执行都重新获取
|
|
496
|
+
let cachedCodes: Array<{ code: string; name: string }> | null = null
|
|
497
|
+
const commonCodeType = col.commonCode
|
|
498
|
+
|
|
499
|
+
baseColumn.formatter = (value: any) => {
|
|
500
|
+
// 注意:不能用 `||`,否则当 cellValue 为 null 时会回退到整个参数对象,导致出现 [object Object]
|
|
501
|
+
const hasCellValue = value && Object.prototype.hasOwnProperty.call(value, 'cellValue')
|
|
502
|
+
const v = hasCellValue ? value.cellValue : value
|
|
503
|
+
|
|
504
|
+
// 优化:直接在 formatter 内部处理 commonCode 逻辑,避免频繁调用 getCommonCodeName
|
|
505
|
+
let name = v
|
|
506
|
+
if (v !== null && v !== undefined && commonCodeType && props.getCommonCodeOptions) {
|
|
507
|
+
if (!cachedCodes) {
|
|
508
|
+
cachedCodes = props.getCommonCodeOptions(commonCodeType)
|
|
509
|
+
}
|
|
510
|
+
if (cachedCodes) {
|
|
511
|
+
const codeItem = cachedCodes.find((item) => item.code === String(v))
|
|
512
|
+
if (codeItem) {
|
|
513
|
+
name = codeItem.name
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
name = name ?? ''
|
|
518
|
+
|
|
519
|
+
if (originalFormatter) {
|
|
520
|
+
try {
|
|
521
|
+
return originalFormatter(name)
|
|
522
|
+
} catch (e) {
|
|
523
|
+
console.warn('commonCode formatter 执行异常', e)
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return name
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// 处理 xweight/xmoney/xprice/xtaxrate/xnumber 格式化
|
|
531
|
+
// 优先级:precision > xweight > xmoney ...
|
|
532
|
+
let precision: number | undefined = col.precision
|
|
533
|
+
if (precision === undefined) {
|
|
534
|
+
if (col.xweight) precision = PRECISION.weight
|
|
535
|
+
else if (col.xmoney) precision = PRECISION.amount
|
|
536
|
+
else if (col.xprice) precision = PRECISION.price
|
|
537
|
+
else if (col.xtaxrate) precision = PRECISION.taxRate
|
|
538
|
+
else if (col.xnumber) precision = PRECISION.number
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (precision !== undefined) {
|
|
542
|
+
const originalFormatter = baseColumn.formatter || col.formatter
|
|
543
|
+
baseColumn.align = baseColumn.align || 'right' // 数字默认右对齐
|
|
544
|
+
baseColumn.formatter = (value: any) => {
|
|
545
|
+
const hasCellValue = value && Object.prototype.hasOwnProperty.call(value, 'cellValue')
|
|
546
|
+
let v = hasCellValue ? value.cellValue : value
|
|
547
|
+
|
|
548
|
+
// 如果已经有 formatter (比如 commonCode 处理过的),先执行它
|
|
549
|
+
if (originalFormatter) {
|
|
550
|
+
try {
|
|
551
|
+
v = originalFormatter(value)
|
|
552
|
+
} catch (e) {
|
|
553
|
+
console.warn('formatter 执行异常', e)
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return formatNumber(v, precision!)
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (col?.event) {
|
|
562
|
+
const oldClass = baseColumn.className || ''
|
|
563
|
+
baseColumn.className = `${oldClass} cell-events`.trim()
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// 递归处理子列
|
|
567
|
+
if (baseColumn.children && baseColumn.children.length > 0) {
|
|
568
|
+
baseColumn.children = baseColumn.children.map(processColumn)
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
return baseColumn
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// 返回处理数据
|
|
575
|
+
return columns.map((column: Column, idx: number) => {
|
|
576
|
+
const baseColumn = processColumn(column)
|
|
577
|
+
|
|
578
|
+
if (idx === firstNonTypeIndex && props.tree) {
|
|
579
|
+
baseColumn.treeNode = true
|
|
580
|
+
}
|
|
581
|
+
if (idx === firstNonTypeIndex && props.drag) {
|
|
582
|
+
baseColumn.dragSort = true
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return baseColumn
|
|
586
|
+
})
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// 处理表尾数据,注意:footerData是个数组,支持多行表尾
|
|
590
|
+
function handleFooterData() {
|
|
591
|
+
// 是否展示表尾
|
|
592
|
+
if (props.showFooter) {
|
|
593
|
+
// 因为表尾需要有一列占位,用于展示【总计】这个文案,默认使用复选框
|
|
594
|
+
const footerData = cloneDeep(props?.footerData ?? [])
|
|
595
|
+
return footerData.map((item) => ({
|
|
596
|
+
...item,
|
|
597
|
+
BaseTablePlaceholder: '总计',
|
|
598
|
+
}))
|
|
599
|
+
}
|
|
600
|
+
return []
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const footerRenderData = computed(() => handleFooterData())
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* 获取当前选中的复选框数据
|
|
607
|
+
* @returns {Array} 选中的行数据数组
|
|
608
|
+
*/
|
|
609
|
+
function getCheckboxRecords() {
|
|
610
|
+
const $grid = basetable.value
|
|
611
|
+
if ($grid) {
|
|
612
|
+
return $grid.getCheckboxRecords()
|
|
613
|
+
}
|
|
614
|
+
return []
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* 获取当前选中的单选框数据
|
|
619
|
+
* @returns {object | null} 选中的行数据对象,如果没有选中则返回 null
|
|
620
|
+
*/
|
|
621
|
+
function getRadioRecord() {
|
|
622
|
+
const $grid = basetable.value
|
|
623
|
+
if ($grid) {
|
|
624
|
+
const records = $grid.getRadioRecord()
|
|
625
|
+
return records ?? null
|
|
626
|
+
}
|
|
627
|
+
return null
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* 获取当前选中的数据(兼容复选框和单选框)
|
|
632
|
+
* @returns {Array | object | null}
|
|
633
|
+
* - 复选框模式:返回选中的行数据数组
|
|
634
|
+
* - 单选框模式:返回选中的行数据对象,如果没有选中则返回 null
|
|
635
|
+
*/
|
|
636
|
+
function getSelectedRecords() {
|
|
637
|
+
if (props.radio) {
|
|
638
|
+
return getRadioRecord()
|
|
639
|
+
}
|
|
640
|
+
if (props.checkbox) {
|
|
641
|
+
return getCheckboxRecords()
|
|
642
|
+
}
|
|
643
|
+
return null
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* 递归查找列配置
|
|
648
|
+
* @param columns 列数组
|
|
649
|
+
* @param field 目标字段名
|
|
650
|
+
* @returns 找到的列配置或 undefined
|
|
651
|
+
*/
|
|
652
|
+
function findColumnByField(columns: Column[], field: string): Column | undefined {
|
|
653
|
+
for (const col of columns) {
|
|
654
|
+
if (col.field === field) {
|
|
655
|
+
return col
|
|
656
|
+
}
|
|
657
|
+
if (col.children && col.children.length > 0) {
|
|
658
|
+
const found = findColumnByField(col.children, field)
|
|
659
|
+
return found
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return undefined
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* 处理单元格点击事件
|
|
667
|
+
* @param {object} params - 事件参数对象
|
|
668
|
+
* @param {object} params.row - 当前点击的行
|
|
669
|
+
* @param {string} params.column - 当前点击的列
|
|
670
|
+
*/
|
|
671
|
+
function handleCellClick({ row, column }: { row: any; column: any }) {
|
|
672
|
+
// 当用户进行文本选择时,不触发点击事件
|
|
673
|
+
const selection = window.getSelection()
|
|
674
|
+
if (selection && selection.toString().length > 0) return
|
|
675
|
+
|
|
676
|
+
if (column?.className?.includes('cell-events')) {
|
|
677
|
+
const findColumn = findColumnByField(options.value.columns, column.field)
|
|
678
|
+
;(findColumn as any)?.event?.(row, column)
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Expose
|
|
683
|
+
defineExpose({
|
|
684
|
+
grid: basetable,
|
|
685
|
+
getAllToolBarValues: () => ({
|
|
686
|
+
lToolBar: lToolBarState.getAllValues(),
|
|
687
|
+
rToolBar: rToolBarState.getAllValues(),
|
|
688
|
+
}),
|
|
689
|
+
getLToolBarValues: () => lToolBarState.getAllValues(),
|
|
690
|
+
getRToolBarValues: () => rToolBarState.getAllValues(),
|
|
691
|
+
goToPage,
|
|
692
|
+
lToolBarState,
|
|
693
|
+
rToolBarState,
|
|
694
|
+
getSelectedRecords,
|
|
695
|
+
})
|
|
696
|
+
</script>
|
|
697
|
+
|
|
698
|
+
<template>
|
|
699
|
+
<vxe-grid
|
|
700
|
+
ref="basetable"
|
|
701
|
+
v-bind="options"
|
|
702
|
+
:footer-data="footerRenderData"
|
|
703
|
+
:style="{
|
|
704
|
+
'--vxe-ui-table-row-hover-background-color': '#E3FCF7',
|
|
705
|
+
'--vxe-ui-table-column-hover-background-color': '#E3FCF7',
|
|
706
|
+
'--vxe-ui-table-row-hover-striped-background-color': '#E3FCF7',
|
|
707
|
+
}"
|
|
708
|
+
@cell-click="handleCellClick"
|
|
709
|
+
@checkbox-all="selectAllChangeEvent"
|
|
710
|
+
@checkbox-change="selectChangeEvent"
|
|
711
|
+
@radio-change="radioChangeEvent"
|
|
712
|
+
>
|
|
713
|
+
<!-- 顶部左侧工具栏配置 -->
|
|
714
|
+
<template #lToolBar>
|
|
715
|
+
<div v-if="lToolBarItems.length > 0" class="base-table-toolbar-container">
|
|
716
|
+
<component :is="item" v-for="item in lToolBarItems" :key="item.key" />
|
|
717
|
+
</div>
|
|
718
|
+
</template>
|
|
719
|
+
<!-- 顶部右侧工具栏配置 -->
|
|
720
|
+
<template #rToolBar>
|
|
721
|
+
<div v-if="rToolBarItems.length > 0" class="base-table-toolbar-container">
|
|
722
|
+
<component :is="item" v-for="item in rToolBarItems" :key="item.key" />
|
|
723
|
+
</div>
|
|
724
|
+
</template>
|
|
725
|
+
<!-- 通用表头提示插槽 -->
|
|
726
|
+
<template #headerHelp="{ column }">
|
|
727
|
+
<span>{{ column.title }}</span>
|
|
728
|
+
<Tooltip v-if="column.params?.helpText" :title="column.params.helpText">
|
|
729
|
+
<QuestionCircleOutlined class="base-table-header-help-icon" />
|
|
730
|
+
</Tooltip>
|
|
731
|
+
</template>
|
|
732
|
+
<template #buttons="{ row }">
|
|
733
|
+
<div class="base-table-buttons-container">
|
|
734
|
+
<template v-for="(button, index) in getVisibleButtons(row)" :key="button.name">
|
|
735
|
+
<AButton
|
|
736
|
+
v-if="index < 2 || getVisibleButtons(row).length <= 3"
|
|
737
|
+
type="link"
|
|
738
|
+
size="small"
|
|
739
|
+
:disabled="button.disabled?.(row, basetable)"
|
|
740
|
+
:loading="isButtonLoading(row, button)"
|
|
741
|
+
v-bind="button.props"
|
|
742
|
+
@click="handleRowButtonClick(button, row)"
|
|
743
|
+
>
|
|
744
|
+
{{ typeof button.name === 'function' ? button.name(row) : button.name }}
|
|
745
|
+
</AButton>
|
|
746
|
+
</template>
|
|
747
|
+
<APopover v-if="getVisibleButtons(row).length > 3" placement="bottomRight">
|
|
748
|
+
<template #content>
|
|
749
|
+
<template v-for="button in getVisibleButtons(row).slice(2)" :key="button.name">
|
|
750
|
+
<AButton
|
|
751
|
+
style="display: block"
|
|
752
|
+
type="link"
|
|
753
|
+
size="small"
|
|
754
|
+
:disabled="button.disabled?.(row, basetable)"
|
|
755
|
+
:loading="isButtonLoading(row, button)"
|
|
756
|
+
v-bind="button.props"
|
|
757
|
+
@click="handleRowButtonClick(button, row)"
|
|
758
|
+
>
|
|
759
|
+
{{ typeof button.name === 'function' ? button.name(row) : button.name }}
|
|
760
|
+
</AButton>
|
|
761
|
+
</template>
|
|
762
|
+
</template>
|
|
763
|
+
<AButton type="link" size="small"> 更多 </AButton>
|
|
764
|
+
</APopover>
|
|
765
|
+
</div>
|
|
766
|
+
</template>
|
|
767
|
+
<!-- 分页器 -->
|
|
768
|
+
<template v-if="pager" #pager>
|
|
769
|
+
<div class="base-table-pager">
|
|
770
|
+
<APagination
|
|
771
|
+
ref="paginationRef"
|
|
772
|
+
size="small"
|
|
773
|
+
:total="total"
|
|
774
|
+
:current="paginationCurrent"
|
|
775
|
+
:page-size="paginationPageSize"
|
|
776
|
+
show-size-changer
|
|
777
|
+
show-quick-jumper
|
|
778
|
+
@change="onChange"
|
|
779
|
+
@show-size-change="onShowSizeChange"
|
|
780
|
+
/>
|
|
781
|
+
<p class="base-table-pager-total">总计 {{ total }} 条数据</p>
|
|
782
|
+
</div>
|
|
783
|
+
</template>
|
|
784
|
+
<!-- 透传所有其他插槽到vxe-grid -->
|
|
785
|
+
<template v-for="(_, slotName) in $slots" #[slotName]="slotProps">
|
|
786
|
+
<template v-if="!['lToolBar', 'rToolBar', 'buttons', 'pager'].includes(String(slotName))">
|
|
787
|
+
<slot :name="String(slotName)" v-bind="slotProps" />
|
|
788
|
+
</template>
|
|
789
|
+
</template>
|
|
790
|
+
</vxe-grid>
|
|
791
|
+
</template>
|
|
792
|
+
|
|
793
|
+
<style lang="scss" scoped>
|
|
794
|
+
.base-table-header-help-icon {
|
|
795
|
+
margin-left: 4px;
|
|
796
|
+
color: #999;
|
|
797
|
+
cursor: help;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
:deep(.base-table-toolbar) {
|
|
801
|
+
padding-top: 0;
|
|
802
|
+
padding-bottom: 0;
|
|
803
|
+
|
|
804
|
+
.base-table-toolbar-container {
|
|
805
|
+
display: flex;
|
|
806
|
+
align-items: center;
|
|
807
|
+
gap: 12px;
|
|
808
|
+
margin-bottom: 12px;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
.vxe-tools--operate {
|
|
812
|
+
margin-bottom: 12px;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
.vxe-button--dropdown.size--mini,
|
|
816
|
+
.vxe-button.type--button.size--mini {
|
|
817
|
+
margin: 0 0 0 12px;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.base-table-buttons-container {
|
|
822
|
+
display: flex;
|
|
823
|
+
align-items: center;
|
|
824
|
+
gap: 8px;
|
|
825
|
+
|
|
826
|
+
.ant-btn {
|
|
827
|
+
padding: 0;
|
|
828
|
+
font-size: 12px;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
:deep(.ant-popover) {
|
|
833
|
+
padding-top: 0;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
:deep(.ant-popover-inner) {
|
|
837
|
+
padding: 0 !important;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
:deep(.base-columns-cell) {
|
|
841
|
+
.vxe-cell {
|
|
842
|
+
padding: 8px 12px !important;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
:deep(.base-table-footer-cell) {
|
|
847
|
+
.vxe-cell {
|
|
848
|
+
height: 40px !important;
|
|
849
|
+
padding: 8px 12px !important;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
.base-table-pager {
|
|
854
|
+
display: flex;
|
|
855
|
+
align-items: end;
|
|
856
|
+
justify-content: space-between;
|
|
857
|
+
height: fit-content;
|
|
858
|
+
min-height: 36px;
|
|
859
|
+
|
|
860
|
+
:deep(.ant-pagination-options) {
|
|
861
|
+
.ant-select {
|
|
862
|
+
width: 100px;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
.base-table-pager-total {
|
|
867
|
+
color: #262626;
|
|
868
|
+
line-height: 24px;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
:deep(.cell-events) {
|
|
873
|
+
color: #1288fc;
|
|
874
|
+
cursor: pointer;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/* 斑马纹行悬浮背景颜色 */
|
|
878
|
+
:deep(.vxe-table--body .vxe-body--row.row--stripe.row--hover) {
|
|
879
|
+
background-color: red !important;
|
|
880
|
+
}
|
|
881
|
+
</style>
|