<template>
<div class="base-table-warp" v-bind="$attrs">
<el-table
v-bind="props"
v-loading="tableLoading"
element-loading-text="加载中..."
element-loading-background="rgba(255, 255, 255, 0.5)"
ref="tableRef"
border
stripe
:header-cell-class-name="headerCellClassName"
@sortChange="sortChange"
@filterChange="filterChange"
>
<CustomTableColumn v-for="column in columns" :key="column.field" :column="column">
<template #header="scope">
<div class="el-table__header__cell__custom">
<slot name="header" :scope="scope">
<el-checkbox v-if="getColumnCheckbox(column)" v-model="checkboxAllCheckd" :indeterminate="indeterminate"> </el-checkbox>
<BaseToolTip :rows="1" placement="top" v-else>
{{ getHeaderRender(scope, column) }}
</BaseToolTip>
</slot>
</div>
</template>
<template #default="scope">
<slot name="default" :scope="scope">
<div class="el-table__body__cell__custom">
<el-radio
v-if="[column.field, column.type].includes('radio')"
:label="getRowLabel(scope)"
v-model="radioCheckd"
:disabled="getSelectDisabled(scope, column)"
@change="val => radioChange(val, scope, column)"
>
</el-radio>
<el-checkbox
v-else-if="getColumnCheckbox(column)"
:label="getRowLabel(scope)"
v-model="checkboxCheckd"
:disabled="getSelectDisabled(scope, column)"
@change="val => checkboxChange(val, scope, column)"
>
</el-checkbox>
<component
v-else-if="column.componentIs"
:is="column.componentIs"
v-bind="column.getComponentPorpsCallback ? column.getComponentPorpsCallback(scope, column) : column.componentPorps"
></component>
<BaseToolTip v-else :rows="1" placement="top"> {{ getDefaultRender(scope, column) }}</BaseToolTip>
</div>
</slot>
</template>
</CustomTableColumn>
<template #empty><slot name="empty"></slot></template>
<template #append><slot name="append"></slot></template>
</el-table>
<BasePagination
v-if="isPagination"
v-bind="props"
:disabled="tableLoading"
v-model:currentPage="modelCurrentPage"
v-model:pageSize="modelPageSize"
@paginationChange="paginationChange"
></BasePagination>
</div>
</template>
<script lang="jsx" setup>
import {ElTable} from 'element-plus';
import BasePagination from './BasePagination';
import BaseToolTip from './BaseToolTip.vue';
import {ref, toRefs, computed, useSlots, nextTick, onMounted} from 'vue';
import useGlobalProperties from '_@/hooks/useGlobalProperties.js';
const tableSlots = useSlots();
const {$utils} = useGlobalProperties();
const CustomTableColumn = {
name: 'CustomTableColumn',
props: ['column'],
inheritAttrs: false,
setup: (props, {attrs, slots}) => {
const columnSlots = {
header: scope => {
const headerSlotsName = [props.column.field, 'header'].join('-');
if (tableSlots?.[headerSlotsName]) {
return tableSlots[headerSlotsName](scope);
}
return slots?.header ? slots.header(scope) : '';
},
default: scope => {
if (tableSlots?.[props.column.field]) {
return tableSlots[props.column.field](scope);
}
if (!props.column.field && props.column?.type === 'expand' && tableSlots?.expand) {
return tableSlots.expand(scope);
}
if (tableSlots?.default) {
return tableSlots.default(scope);
}
return slots?.default ? slots.default(scope) : '';
}
};
if (props?.column?.children?.length) {
return () => (
<el-table-column {...props.column}>
{props.column.children.map(item => {
return (
<CustomTableColumn column={item} key={item.field}>
{columnSlots}
</CustomTableColumn>
);
})}
</el-table-column>
);
}
return () => (
<el-table-column {...props.column} prop={props.column.field}>
{columnSlots}
</el-table-column>
);
}
};
defineOptions({
inheritAttrs: false
});
const props = defineProps({
...ElTable.props,
...BasePagination.props,
columns: {
type: Array,
required: true,
validator(value) {
return value.every(item => {
return ['field', 'label'].every(key => Object.keys(item).includes(key));
});
}
},
radio: {
type: String
},
checkbox: {
type: Array
},
rowKey: {
type: [Function, String],
default: 'id'
},
defaultPageSize: {
type: Number,
default: 10
},
isPagination: {
type: Boolean,
default: true
},
paramsPropKey: {
type: Object,
default: () => ({
order: 'sortType',
prop: 'sortField',
descending: 'desc',
ascending: 'asc',
currentPage: 'pageIndex',
pageSize: 'pageSize'
})
}
});
const {isPagination = true, defaultSort, currentPage, pageSize, defaultPageSize, paramsPropKey, columns, radio, rowKey, checkbox, data} = toRefs(props);
const tableRef = ref(null);
const tableLoading = ref(true);
const propSort = ref(null);
const propFilter = ref(null);
const emits = defineEmits([
...Object.keys(ElTable.emits),
...Object.keys(BasePagination.emits),
'update:radio',
'update:checkbox',
'sortChange',
'selectAll',
'selectionChange',
'radioChange'
]);
const getHeaderRender = (scope, column) => scope.column.label;
const indexSlot = (scope, column) => {
const no = scope.$index + 1;
if ('index' in column) {
return typeof column.index === 'function' ? column.index(no) : column.index;
} else {
return no + modelPageSize.value * (modelCurrentPage.value - 1);
}
};
const getSelectDisabled = (scope, column) => (typeof column?.disabled === 'function' ? column.disabled(scope, column) : false);
const radioChange = (val, scope, column) => {
if (!getSelectDisabled(scope, column)) {
emits('radioChange', {value: val, ...scope});
}
};
const getColumnCheckbox = column => ['checkbox', 'selection'].some(key => [column.field, column.type].includes(key));
const checkboxChange = (val, scope, column) => {
if (!getSelectDisabled(scope, column)) {
emits('checkboxChange', {value: val, ...scope});
emits('selectionChange', {value: val, ...scope});
}
};
const getRowLabel = ({row}) => (typeof rowKey.value === 'function' ? rowKey.value(row) : row[rowKey.value]);
const getDefaultRender = (scope, column) => {
if ([column.field, column.type].includes('index')) {
return indexSlot(scope, column);
}
if (column?.formatter) {
const {$index, cellIndex, column, row} = scope;
return column?.formatter(row, column, cellIndex, $index);
}
if (scope?.column?.property) {
return $utils.getTextPlaceholder(scope.row[scope.column.property], column.empty);
}
return '';
};
const getListRowLabel = list => list.map(row => getRowLabel({row}));
const getAllRowLabel = computed(() => getListRowLabel(data.value));
const getAllEnableValue = computed(() => {
return getListRowLabel(data.value.filter(row => !getSelectDisabled({row}, {...getCheckboxColumn.value, row})));
});
const getAllDisabledCheckboxValue = computed(() => {
return getListRowLabel(
data.value.filter(row => getSelectDisabled({row}, {...getCheckboxColumn.value, row}) && checkboxCheckd.value.includes(getRowLabel({row})))
);
});
const getAllCheckboxValue = computed(() => getAllEnableValue.value.concat(...getAllDisabledCheckboxValue.value));
const getCheckboxColumn = computed(() => columns.value.find(({type}) => ['selection', 'checkbox'].includes(type)) ?? {});
const checkboxAllCheckd = computed({
get() {
return checkboxCheckd.value?.length && getAllRowLabel.value.every(item => checkboxCheckd.value.includes(item));
},
set(newVal) {
if (getAllCheckboxValue.value.every(val => checkboxCheckd.value.includes(val))) {
checkboxCheckd.value = getAllDisabledCheckboxValue.value;
} else {
checkboxCheckd.value = getAllCheckboxValue.value;
}
checkboxCheckd.value = result;
nextTick(() => {
emits('select-all', result);
});
}
});
const indeterminate = computed(() => Boolean(checkboxCheckd.value?.length && getAllRowLabel.value.some(item => !checkboxCheckd.value.includes(item))));
const getParamsPropKey = computed(() => ({
...paramsPropKey.value
}));
const modelCurrentPage = computed({
get() {
return currentPage.value;
},
set(newVal) {
emits('update:currentPage', newVal);
emits('currentPage', newVal);
}
});
const modelPageSize = computed({
get() {
return pageSize.value || defaultPageSize.value;
},
set(newVal) {
emits('update:pageSize', newVal);
emits('pageSizePage', newVal);
}
});
const radioCheckd = computed({
get() {
return radio.value;
},
set(newVal) {
emits('update:radio', newVal);
}
});
const checkboxCheckd = computed({
get() {
return checkbox.value.filter(item => getAllRowLabel.value.includes(item));
},
set(newVal) {
emits('update:checkbox', newVal);
}
});
const getSort = computed(() => updatePropSort(propSort.value || defaultSort.value));
const getPagination = computed(() => ({
[getParamsPropKey.value.currentPage]: modelCurrentPage.value,
[getParamsPropKey.value.pageSize]: modelPageSize.value
}));
const getFilter = computed(() => propFilter.value);
const updatePropSort = val => {
const {order, prop} = val ?? {};
if (!prop) {
return {};
}
return {
[getParamsPropKey.value.order]: getParamsPropKey.value[order] || order || null,
[getParamsPropKey.value.prop]: prop
};
};
const sortChange = val => {
propSort.value = val;
nextTick(() => {
emits('sortChange', val);
});
};
const filterChange = val => {
const [[key, value]] = Object.entries(val);
const {filterMultiple = true} = columns.value.find(({columnKey}) => columnKey === key);
if (!propFilter.value) {
propFilter.value = {};
}
if (value.length) {
propFilter.value[key] = filterMultiple ? value : value[0];
} else {
delete propFilter.value[key];
}
emits('filterChange', propFilter.value);
};
const headerCellClassName = ({row, column}) => {
let str = '';
if (column.columnKey && column.filterable) {
str += ' is-filter';
}
return str;
};
const paginationChange = val => {
emits('paginationChange', val);
};
const updateCurrentPage = (current = 1) => {
modelCurrentPage.value = current;
};
const updatePageSize = (size = defaultPageSize.value) => {
modelPageSize.value = size;
};
const updatePagination = (current = 1, size = defaultPageSize.value) => {
updateCurrentPage(current);
updatePageSize(size);
};
const updateRadio = (val = null) => {
radioCheckd.value = val;
};
const updateCheckbox = (val = []) => {
checkboxCheckd.value = val;
};
const clearSort = () => {
propSort.value = null;
tableRef.value.clearSort();
};
const clearFilter = () => {
propFilter.value = null;
tableRef.value.clearFilter();
};
const doLayout = () => {
tableRef.value.doLayout();
};
defineExpose({
tableRef,
tableLoading,
isPagination,
updateCurrentPage,
updatePageSize,
updatePagination,
updateRadio,
updateCheckbox,
getPagination,
getSort,
getFilter,
clearSort,
clearFilter,
doLayout
});
</script>
<style lang="scss">
.el-table .caret-wrapper {
display: inline-flex;
flex-direction: column;
align-items: center;
height: 14px;
width: 24px;
vertical-align: middle;
cursor: pointer;
overflow: initial;
position: relative;
}
.el-table col {
min-width: 50px !important;
}
.el-table td.el-table__cell div {
.el-checkbox__label,
.el-radio__label {
display: none;
}
& > .el-checkbox,
& > .el-radio {
display: flex;
}
}
.el-table th {
.cell {
display: flex;
align-items: center;
}
vertical-align: middle;
.el-table__header__cell__custom {
display: inline-flex;
}
&.is-right > .cell {
justify-content: flex-end;
}
&.is-center > .cell {
justify-content: center;
}
&.is-filter .el-table__header__cell__custom {
max-width: calc(100% - 14px);
}
&.is-sortable .el-table__header__cell__custom {
max-width: calc(100% - 24px);
}
&.is-sortable.is-filter .el-table__header__cell__custom {
max-width: calc(100% - 24px - 14px);
}
}
.el-table .cell {
display: flex;
align-items: center;
}
.el-table td {
.el-table__body__cell__custom {
flex: 1;
}
}
.el-table--border th.el-table__cell::after {
top: 50% !important;
transform: translateY(-50%);
}
.el-table.el-loading-parent--relative {
.el-table__empty-text {
opacity: 0;
pointer-events: none;
}
}
</style>
console