2023-11-11 因升级了Element-plus组件,TSelectTable组件(下拉选择表格组件)显示查询条件功能有所变动(新增了使用示例);效果如下:

一、最终效果

二、代码示例
<t-select-table
:table="table"
:columns="table.columns"
:max-height="400"
:keywords="{ label: 'name', value: 'id' }"
@radioChange="radioChange"
>t-select-table>
三、参数配置
1. 配置参数(Attributes)继承 el-table 及 el-select 属性
| 参数 | 说明 | 类型 | 默认值 |
|---|
| v-model | 绑定值 | boolean / string / number | 仅显示 |
| table | 表格数据对象 | Object | {} |
| —data | 展示下拉数据源 | Array | [] |
| —total | 数据总条数 | Number | - |
| —pageSize | 每页显示条目个数 | Number | - |
| —currentPage | 当前页数 | Number | - |
| columns | 表头信息 | Array | [] |
| ----bind | el-table-column Attributes | Object | - |
| ----noShowTip | 是否换行 (设置:noShowTip:true) | Boolean | false |
| ----fixed | 列是否固定( left, right) | string, boolean | - |
| ----align | 对齐方式(left/center/right) | String | center |
| ----render | 返回三个参数(text:当前值,row:当前整条数据 ,index:当前行) | function | - |
| ----slotName | 插槽显示此列数据(其值是具名作用域插槽) | String | - |
| ------scope | 具名插槽获取此行数据必须用解构接收{scope} | Object | 当前行数据 |
| keywords | 关键字配置(value-key 配置) | Object | 无 |
| ------label | 选项的标签 | String | ‘label’ |
| ------value | 选项的值 | String / number | ‘value’ |
| radioTxt | 单选文案 | String | 单选 |
| multiple | 是否开启多选 | Boolean | false |
| rowClickRadio | 是否开启整行选中(单选) | boolean | true |
| isShowFirstColumn | 是否显示首列(单选) | boolean | true |
| defaultSelectVal | 设置第一页默认选中项–keywords.value 值(单选是 String, Number 类型;多选时是数组) | Number / string / Array | - |
| filterable | 是否开启过滤(根据 keywords 的 label 值进行过滤) | Boolean | true |
| reserveSelection | 是否支持翻页选中 | Boolean | true |
| isShowPagination | 开启分页 | Boolean | false |
| tableWidth | table 宽度 | Number | 550 |
| isKeyup | 单选是否开启键盘事件 | Boolean | false |
| isShowQuery | 是否允许配置查询条件(继承TQueryCondition的所有属性、事件、插槽) | Boolean | false |
| isShowBlurBtn | 条件查询组件是否显示隐藏下拉框按钮 | Boolean | false |
| btnBind | 显示下拉框按钮配置,继承el-button所有属性;默认值{type:'danger',btnTxt:'关闭下拉框'} | Object | - |
2. 事件(events)继承 el-table 及 el-select 属性
| 事件名 | 说明 | 回调参数 |
|---|
| page-change | 页码改变事件 | 返回选中的页码 |
| selectionChange | 多选事件 | 返回选中的项数据及选中项的 keywords.value 集合 |
| radioChange | 单选 | 返回当前项所有数据 |
3.方法(Methods)继承 el-table 及 el-select 属性
| 方法名 | 说明 | 回调参数 |
|---|
| clear | 清空选中项 | |
| focus | 使 input 获取焦点 | |
| blur | 使 input 失去焦点,并隐藏下拉框 | |
四、具体代码
<template>
<el-select
ref="selectRef"
v-model="state.defaultValue"
popper-class="t-select-table"
:multiple="multiple"
v-bind="selectAttr"
:value-key="keywords.value"
:filterable="filterable"
:filter-method="filterMethod || filterMethodHandle"
v-click-outside="closeBox"
@visible-change="visibleChange"
@remove-tag="removeTag"
@clear="clear"
@keyup="selectKeyup"
>
<template #empty>
<div class="t-table-select__table" :style="{ width: `${tableWidth}px` }">
<div class="table_query_condition" v-if="isShowQuery">
<t-query-condition
ref="tQueryConditionRef"
:boolEnter="false"
@handleEvent="handleEvent"
v-bind="$attrs"
>
<template v-for="(index, name) in slots" v-slot:[name]="data">
<slot :name="name" v-bind="data">slot>
template>
<template #querybar v-if="isShowBlurBtn">
<el-button
v-bind="{ type: 'danger', ...btnBind }"
@click="blur"
>{{ btnBind.btnTxt || '关闭下拉框' }}el-button
>
<slot name="querybar">slot>
template>
t-query-condition>
div>
<el-table
ref="selectTable"
:data="state.tableData"
:class="{
radioStyle: !multiple,
highlightCurrentRow: isRadio,
keyUpStyle: isKeyup,
}"
highlight-current-row
border
:row-key="getRowKey"
@row-click="rowClick"
@cell-dblclick="cellDblclick"
@selection-change="handlesSelectionChange"
v-bind="$attrs"
>
<el-table-column
v-if="multiple"
type="selection"
width="55"
align="center"
:reserve-selection="reserveSelection"
fixed
>el-table-column>
<el-table-column
type="radio"
width="55"
:label="radioTxt"
fixed
align="center"
v-if="!multiple && isShowFirstColumn"
>
<template #default="scope">
<el-radio
v-model="radioVal"
:label="scope.$index + 1"
@click.stop="
radioChangeHandle($event, scope.row, scope.$index + 1)
"
>el-radio>
template>
el-table-column>
<el-table-column
v-for="(item, index) in columns"
:key="index + 'i'"
:type="item.type"
:label="item.label"
:prop="item.prop"
:min-width="item['min-width'] || item.minWidth"
:width="item.width"
:align="item.align || 'center'"
:fixed="item.fixed"
:show-overflow-tooltip="item.noShowTip"
v-bind="{ ...item.bind, ...$attrs }"
>
<template #default="scope">
<template v-if="item.render">
<render-col
:column="item"
:row="scope.row"
:render="item.render"
:index="scope.$index"
/>
template>
<template v-if="item.slotName">
<slot :name="item.slotName" :scope="scope">slot>
template>
<div v-if="!item.render && !item.slotName">
<span>{{ scope.row[item.prop] }}span>
div>
template>
el-table-column>
<slot>slot>
el-table>
<div class="t-table-select__page" v-if="isShowPagination">
<el-pagination
v-model:current-page="table.currentPage"
v-model:page-size="table.pageSize"
small
background
@current-change="handlesCurrentChange"
layout="total, prev, pager, next, jumper"
:pager-count="5"
:total="table.total"
v-bind="$attrs"
/>
div>
div>
template>
el-select>
template>
<script setup lang="ts" name="TSelectTable">
import TQueryCondition from '../../query-condition/src/index.vue'
import RenderCol from './renderCol.vue'
import {
computed,
useAttrs,
useSlots,
ref,
watch,
nextTick,
reactive,
onMounted,
} from 'vue'
import { ElMessage } from 'element-plus'
import ClickOutside from '../../utils/directives/click-outside/index'
const props = defineProps({
value: {
type: [String, Number, Array],
},
table: {
type: Object,
default: () => {
return {}
},
},
columns: {
type: Array as unknown as any[],
default: () => [],
},
radioTxt: {
type: String,
default: '单选',
},
isShowQuery: {
type: Boolean,
default: false,
},
isShowBlurBtn: {
type: Boolean,
default: false,
},
btnBind: {
type: Object,
default: () => {
return {
btnTxt: '关闭下拉框',
}
},
},
rowClickRadio: {
type: Boolean,
default: true,
},
isShowFirstColumn: {
type: Boolean,
default: true,
},
filterable: {
type: Boolean,
default: true,
},
reserveSelection: {
type: Boolean,
default: true,
},
isShowPagination: {
type: Boolean,
default: false,
},
filterMethod: {
type: Function,
},
keywords: {
type: Object,
default: () => {
return {
label: 'label',
value: 'value',
}
},
},
isKeyup: {
type: Boolean,
default: false,
},
multiple: {
type: Boolean,
default: false,
},
tableWidth: {
type: Number,
default: 550,
},
defaultSelectVal: {
type: [String, Number, Array],
},
})
const selectAttr = computed(() => {
return {
clearable: true,
...useAttrs(),
}
})
const vClickOutside = ClickOutside
const slots = useSlots()
const isDefaultSelectVal = ref(true)
const forbidden = ref(true)
const isRadio = ref(false)
const isQueryVisible = ref(false)
const isVisible = ref(false)
const radioVal = ref('')
const state: any = reactive({
defaultSelectValue: props.defaultSelectVal,
tableData: props.table.data,
defaultValue: props.value,
ids: [],
tabularMap: {},
})
const selectRef: any = ref<HTMLElement | null>(null)
const selectTable: any = ref<HTMLElement | null>(null)
const tQueryConditionRef: any = ref<HTMLElement | null>(null)
const nowIndex = ref(-1)
watch(
() => props.table.data,
(val) => {
state.tableData = val
nextTick(() => {
state.tableData &&
state.tableData.length > 0 &&
state.tableData.forEach((item) => {
state.tabularMap[item[props.keywords.value]] =
item[props.keywords.label]
})
})
},
{ deep: true }
)
watch(
() => props.value,
(val) => {
state.tableData = val
nextTick(() => {
if (props.multiple) {
state.defaultValue = Array.isArray(props.value)
? props.value
: typeof props.value === 'string'
? props.value.split(',')
: []
state.defaultValue = (state.defaultValue || []).map((item) => {
return item
})
} else {
state.defaultValue = props.value
? { [props.keywords.value]: props.value }
: ''
}
findLabel()
})
},
{ deep: true }
)
watch(
() => props.defaultSelectVal,
(val) => {
state.defaultSelectValue = val
if (val && isDefaultSelectVal.value) {
defaultSelect(val)
}
},
{ deep: true }
)
onMounted(() => {
if (state.defaultSelectValue && isDefaultSelectVal.value) {
defaultSelect(state.defaultSelectValue)
}
})
const visibleChange = (visible) => {
isVisible.value = visible
if (isQueryVisible.value) {
selectRef.value.visible = true
}
if (visible) {
if (props.defaultSelectVal && isDefaultSelectVal.value) {
defaultSelect(props.defaultSelectVal)
}
initTableData()
} else {
findLabel()
filterMethodHandle('')
}
}
const handleEvent = () => {
selectRef.value.visible = true
}
const queryVisibleChange = (val) => {
isQueryVisible.value = val
}
const closeBox = () => {
if (tQueryConditionRef.value && props.isShowQuery) {
Object.values(tQueryConditionRef.value?.props?.opts).map((val: any) => {
if (
val.comp.includes('select') ||
val.comp.includes('picker') ||
val.comp.includes('date')
) {
val.eventHandle = {
'visible-change': ($event) => queryVisibleChange($event),
}
selectRef.value.visible = true
}
})
if (isVisible.value && props.isShowQuery) {
selectRef.value.visible = true
} else {
selectRef.value.visible = false
}
}
}
const selectKeyup = (e) => {
if (!props.multiple) {
if (!props.isKeyup) return
if (state.tableData.length === 0) return
switch (e.keyCode) {
case 40:
if (state.tableData[nowIndex.value * 1 + 1] !== undefined) {
selectTable.value.setCurrentRow(
state.tableData[nowIndex.value * 1 + 1]
)
nowIndex.value = nowIndex.value * 1 + 1
} else {
nowIndex.value = 0
selectTable.value.setCurrentRow(state.tableData[0])
}
break
case 38:
if (
state.tableData[nowIndex.value * 1 - 1] !== undefined &&
nowIndex.value > 0
) {
selectTable.value.setCurrentRow(
state.tableData[nowIndex.value * 1 - 1]
)
nowIndex.value = nowIndex.value * 1 - 1
} else {
nowIndex.value = 0
selectTable.value.setCurrentRow(state.tableData[0])
}
break
case 13:
rowClick(state.tableData[nowIndex.value])
break
}
}
}
const findLabel = () => {
nextTick(() => {
if (props.multiple) {
selectRef.value.selected?.forEach((item) => {
item.currentLabel = item.value
})
} else {
selectRef.value.selectedLabel =
(state.defaultValue && state.defaultValue[props.keywords.label]) || ''
}
})
}
const emits = defineEmits(['page-change', 'selectionChange', 'radioChange'])
const handlesCurrentChange = (val) => {
if (props.multiple) {
if (!props.reserveSelection) {
clear()
}
} else {
clear()
}
emits('page-change', val)
}
const defaultSelect = (defaultSelectVal) => {
if (typeof defaultSelectVal === 'object' && props.multiple) {
let multipleList: any = []
defaultSelectVal.map((val) => {
state.tableData.forEach((row: any) => {
if (val === row[props.keywords.value]) {
multipleList.push(row)
}
})
})
setTimeout(() => {
state.defaultValue = multipleList.map(
(item) => item[props.keywords.label]
)
multipleList.forEach((row) => {
const arr = state.tableData.filter(
(item) => item[props.keywords.value] === row[props.keywords.value]
)
if (arr.length > 0) {
selectTable.value.toggleRowSelection(arr[0], true)
}
})
selectRef.value.selected.forEach((item) => {
item.currentLabel = item.value
})
}, 0)
} else {
let row, index
state.tableData.map((val, i) => {
if (val[props.keywords.value] === defaultSelectVal) {
row = val
index = i
}
})
radioVal.value = index + 1
state.defaultValue = row
setTimeout(() => {
selectRef.value.selectedLabel = row[props.keywords.label]
}, 0)
}
}
const handlesSelectionChange = (val) => {
isDefaultSelectVal.value = false
state.defaultValue = val.map((item) => item[props.keywords.label])
state.ids = val.map((item) => item[props.keywords.value])
emits('selectionChange', val, state.ids)
}
const getRowKey = (row) => {
return row[props.keywords.value]
}
const filterMethodHandle = (val) => {
if (!props.filterable) return
const tableData = JSON.parse(JSON.stringify(props.table?.data))
if (tableData && tableData.length > 0) {
if (!props.multiple) {
if (val) {
radioVal.value = ''
} else {
tableData.map((item, index) => {
if (
item[props.keywords.value] === state.defaultValue &&
state.defaultValue[props.keywords.value]
) {
radioVal.value = index + 1
}
})
}
}
state.tableData = tableData.filter((item) => {
if (item[props.keywords.label].includes(val)) {
return item
}
})
}
}
const initTableData = () => {
nextTick(() => {
if (props.multiple) {
state.defaultValue.forEach((row) => {
const arr = state.tableData.filter(
(item) => item[props.keywords.value] === row[props.keywords.value]
)
if (arr.length > 0) {
selectTable.value.toggleRowSelection(arr[0], true)
}
})
} else {
const arr = state.tableData.filter(
(item) =>
item[props.keywords.value] === state.defaultValue &&
state.defaultValue[props.keywords.value]
)
selectTable.value.setCurrentRow(arr[0])
}
})
}
const copyDomText = (val) => {
const text = val
const input = document.createElement('input')
input.value = text
document.body.appendChild(input)
input.select()
document.execCommand('copy')
document.body.removeChild(input)
}
const cellDblclick = (row, column) => {
try {
copyDomText(row[column.property])
ElMessage.success('复制成功')
} catch (e) {
ElMessage.error('复制失败')
}
}
const radioChangeHandle = (event, row, index) => {
event.preventDefault()
isDefaultSelectVal.value = false
radioClick(row, index)
}
const isForbidden = () => {
forbidden.value = false
setTimeout(() => {
forbidden.value = true
}, 0)
}
const radioClick = (row, index) => {
forbidden.value = !!forbidden.value
if (radioVal.value) {
if (radioVal.value === index) {
radioVal.value = ''
isForbidden()
state.defaultValue = {}
emits('radioChange', {}, null)
blur()
} else {
isForbidden()
radioVal.value = index
state.defaultValue = row
emits('radioChange', row, row[props.keywords.value])
blur()
}
} else {
isForbidden()
radioVal.value = index
state.defaultValue = row
emits('radioChange', row, row[props.keywords.value])
blur()
}
}
const rowClick = async (row) => {
if (!props.rowClickRadio) return
if (!props.multiple) {
let rowIndex
props.table?.data.forEach((item, index) => {
if (item[props.keywords.value] === row[props.keywords.value]) {
rowIndex = index
}
})
isDefaultSelectVal.value = false
await radioClick(row, rowIndex + 1)
if (radioVal.value) {
isRadio.value = true
} else {
isRadio.value = false
}
}
}
const removeTag = (tag) => {
const row = state.tableData.find((item) => item[props.keywords.label] === tag)
selectTable.value.toggleRowSelection(row, false)
}
const clear = () => {
if (props.multiple) {
selectTable.value.clearSelection()
state.defaultValue = []
} else {
selectTable.value.setCurrentRow(-1)
nowIndex.value = -1
radioVal.value = ''
forbidden.value = false
state.defaultValue = null
emits('radioChange', {}, null)
}
}
const blur = () => {
selectRef.value.blur()
}
const focus = () => {
selectRef.value.focus()
}
defineExpose({ focus, blur, clear, tQueryConditionRef })
script>
<style lang="scss">
.t-select-table {
// 单选样式
.radioStyle {
.el-radio {
.el-radio__label {
display: none;
}
&:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner {
box-shadow: none;
}
}
.el-table__row {
cursor: pointer;
}
}
// 键盘事件开启选择高亮
.keyUpStyle {
.el-table__body {
tbody {
.current-row {
color: var(--el-color-primary) !important;
cursor: pointer;
}
}
}
}
// 选中行样式
.highlightCurrentRow {
:deep(.current-row) {
color: var(--el-color-primary);
cursor: pointer;
}
}
.t-table-select__table {
padding: 10px;
.el-table__body,
.el-table__header {
margin: 0;
}
// 条件查询组件样式
.table_query_condition {
width: 100%;
overflow-x: auto;
overflow-y: hidden;
padding: 10px;
}
}
.t-table-select__page {
padding-top: 5px;
padding-right: 10px;
.el-pagination {
display: flex;
justify-content: flex-end;
align-items: center;
margin-right: calc(2% - 20px);
background-color: var(--el-table-tr-bg-color);
}
}
}
style>
./directives
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
- 314
- 315
- 316
- 317
- 318
- 319
- 320
- 321
- 322
- 323
- 324
- 325
- 326
- 327
- 328
- 329
- 330
- 331
- 332
- 333
- 334
- 335
- 336
- 337
- 338
- 339
- 340
- 341
- 342
- 343
- 344
- 345
- 346
- 347
- 348
- 349
- 350
- 351
- 352
- 353
- 354
- 355
- 356
- 357
- 358
- 359
- 360
- 361
- 362
- 363
- 364
- 365
- 366
- 367
- 368
- 369
- 370
- 371
- 372
- 373
- 374
- 375
- 376
- 377
- 378
- 379
- 380
- 381
- 382
- 383
- 384
- 385
- 386
- 387
- 388
- 389
- 390
- 391
- 392
- 393
- 394
- 395
- 396
- 397
- 398
- 399
- 400
- 401
- 402
- 403
- 404
- 405
- 406
- 407
- 408
- 409
- 410
- 411
- 412
- 413
- 414
- 415
- 416
- 417
- 418
- 419
- 420
- 421
- 422
- 423
- 424
- 425
- 426
- 427
- 428
- 429
- 430
- 431
- 432
- 433
- 434
- 435
- 436
- 437
- 438
- 439
- 440
- 441
- 442
- 443
- 444
- 445
- 446
- 447
- 448
- 449
- 450
- 451
- 452
- 453
- 454
- 455
- 456
- 457
- 458
- 459
- 460
- 461
- 462
- 463
- 464
- 465
- 466
- 467
- 468
- 469
- 470
- 471
- 472
- 473
- 474
- 475
- 476
- 477
- 478
- 479
- 480
- 481
- 482
- 483
- 484
- 485
- 486
- 487
- 488
- 489
- 490
- 491
- 492
- 493
- 494
- 495
- 496
- 497
- 498
- 499
- 500
- 501
- 502
- 503
- 504
- 505
- 506
- 507
- 508
- 509
- 510
- 511
- 512
- 513
- 514
- 515
- 516
- 517
- 518
- 519
- 520
- 521
- 522
- 523
- 524
- 525
- 526
- 527
- 528
- 529
- 530
- 531
- 532
- 533
- 534
- 535
- 536
- 537
- 538
- 539
- 540
- 541
- 542
- 543
- 544
- 545
- 546
- 547
- 548
- 549
- 550
- 551
- 552
- 553
- 554
- 555
- 556
- 557
- 558
- 559
- 560
- 561
- 562
- 563
- 564
- 565
- 566
- 567
- 568
- 569
- 570
- 571
- 572
- 573
- 574
- 575
- 576
- 577
- 578
- 579
- 580
- 581
- 582
- 583
- 584
- 585
- 586
- 587
- 588
- 589
- 590
- 591
- 592
- 593
- 594
- 595
- 596
- 597
- 598
- 599
- 600
- 601
- 602
- 603
- 604
- 605
- 606
- 607
- 608
- 609
- 610
- 611
- 612
- 613
- 614
- 615
- 616
- 617
- 618
- 619
- 620
- 621
- 622
- 623
- 624
- 625
- 626
- 627
- 628
- 629
- 630
- 631
- 632
- 633
- 634
- 635
- 636
- 637
- 638
- 639
- 640
- 641
- 642
- 643
- 644
- 645
- 646
- 647
- 648
- 649
- 650
- 651
- 652
- 653
- 654
- 655
- 656
- 657
- 658
- 659
- 660
- 661
- 662
- 663
- 664
- 665
- 666
- 667
- 668
- 669
- 670
- 671
- 672
- 673
- 674
- 675
- 676
- 677
- 678
- 679
- 680
- 681
- 682
- 683
- 684
- 685
- 686
- 687
- 688
- 689
- 690
- 691
- 692
- 693
- 694
- 695
- 696
- 697
- 698
- 699
- 700
- 701
- 702
- 703
- 704
- 705
- 706
- 707
- 708
- 709
- 710
- 711
- 712
- 713
- 714
- 715
- 716
- 717
- 718
- 719
- 720
- 721
- 722
- 723
- 724
- 725
- 726
- 727
- 728
- 729
- 730
- 731
五、组件地址
gitHub组件地址
gitee码云组件地址
六、相关文章
vue3+ts基于Element-plus再次封装基础组件文档
基于ElementUi再次封装基础组件文档
基于ant-design-vue再次封装基础组件文档