一、具体效果
二、组件使用
<ZwTable :columns="columns" :data="data" :total="total" :pageNum="pageNum" :height="height"
:pageSize="pageSize" @onChangeSize="onChangeSize" @onChangeNum="onChangeNum">
<!-- 作用域插槽 -->
<template #columns="scope">
<!-- scope.prop: 属性名 -->
<!-- scope.value:属性值 -->
<!-- scope.row:所有属性 -->
<template v-if="scope.prop === 'a3'">
<el-image style="width: 100px; height: 100px" :src="scope.value" fit="fit"></el-image>
</template>
<template v-if="scope.prop === 'carNumber'">
<LicensePlate :key="Date.now()" v-if="scope.value" :text="scope.value"
:type="scope.row.licensePlateColor" :scale="0.8"></LicensePlate>
</template>
</template>
</ZwTable>
三、定义数据
columnsList: Object.freeze([
{
label: "数据1",
prop: "a1",
width: '260px',
children: [{ // children 多级表头
fixed: 'left', // 固定列
label: "数据2",
prop: "a2",
width: '240px', // 表格宽度
align: 'center' // 对齐方式
}, {
label: "自定义数据",
prop: "a3",
useSlot: true, // 开启插槽。自定义数据
width: '180px',
},
{
label: "数据4",
prop: "a4",
width: '260px',
},]
},
{
label: "数据5",
prop: "a5",
width: '160px',
},
{
label: "数据6",
prop: "a6",
width: '160px',
}, {
label: "数据7",
prop: "a7",
width: '80px',
}, {
label: "时间排序",
prop: "a8",
sortable: true, // 开启排序
sortOrders: ['ascending', 'descending', null] //ascending 表示升序,descending 表示降序,null 表示还原为原始顺序
},
{
label: "数据9",
prop: "a9",
width: '160px'
}, {
width: '120px',
label: "操作",
actions: [
{
mold: "view",
text: "详情",
type: "text",
click: this.onView, // 传入方法
}, {
mold: "edit",
text: "编辑",
type: "text",
click: this.onEdit,
url: require('@/assets/operate_icons/on_edit.png')
}, {
mold: "delete",
text: "删除",
title: "确定要删除吗?",
type: "text",
click: this.onDelete,
url: require('@/assets/operate_icons/on_delete.png')
}],
},
]),
四、mixin 抽离可复用
export default {
data() {
return {
loading: false,
showSearch: true, // 是否显示
pageNum: 1, // 页码
pageSize: 10, // 页数
total: 0, //总数
columns: [], // 后台显示
columnsHide: [], // 后台不显示
};
},
watch: {
columnsHide: {
handler(newV, oldV) {
this.columns = this.columnsList.filter((item) =>
newV.includes(item.prop)
);
},
immediate: true,
deep: true,
},
},
methods: {
// 改变列表位置
changeListIdx({ oldIndex, newIndex }) {
let obj = this.columns.splice(oldIndex, 1);
this.$nextTick(() => {
this.columns.splice(newIndex, 0, obj[0]);
});
},
// 切换页
onChangeNum(val) {
this.pageNum = val;
this.getList();
},
// 切换行
onChangeSize(val) {
this.pageSize = val;
this.pageNum = 1;
this.getList();
},
},
};
五、组件封装
<template>
<div ref="table-box" class="table-content-box">
<div>
<el-table :data="data" :stripe="stripe" :size="size" :empty-text="emptyText"
:height="newHeight || height" :max-height="maxHeight" v-bind="tableConfig"
@selection-change="selectionLineChangeHandle" ref="zwTable" :tree-props="treeProps"
:expand-row-keys="expandRowKeys" :row-key="rowKey" :default-expand-all="defaultExpandAll">
<el-table-column v-if="expand" type="expand" fixed="left">
<template slot-scope="{ row, $index }">
<slot name="expand" :row="row" :index="$index"></slot>
</template>
</el-table-column>
<el-table-column align="center" fixed="left" v-if="selectIsShow" type="selection"
:index="(index) => index + 1" key="selection" width="60px" />
<el-table-column type="index" label="序号" align="center" fixed="left" v-if="idxIsShow"
key="index" width="60px" />
<template v-for="item in columns">
<!-- 渲染 使用插槽 -->
<template v-if="item.useSlot">
<el-table-column :row-key="item.prop" :key="item.prop" :align="item.align || 'center'"
show-overflow-tooltip v-bind="item">
<!-- 匿名插槽 -->
<!-- <template>
<slot>我是本来的内容</slot>
</template> -->
<!-- 具名插槽 -->
<!-- <template>
<slot :name="item.soltName"></slot>
</template> -->
<!-- 作用域 插槽 -->
<template slot-scope="{ row, column, index }">
<!-- {{row[item.prop]}} -->
<slot name="columns" :row="{ ...row, ...item }" :column="column" :index="index"
:value="row[item.prop]" :prop="item.prop">
</slot>
</template>
</el-table-column>
</template>
<!-- 渲染 多级表头 -->
<template v-else-if="item.children && item.children.length > 0">
<TableColumn :key="item.prop" :itemObj="item" :columns="item.children">
<template v-for="slot in Object.keys($scopedSlots)" #[slot]="scope">
<!-- 以之前的名字命名插槽,同时把数据原样绑定 -->
<slot :name="slot" v-bind="scope" />
</template>
</TableColumn>
</template>
<!-- 渲染 操作 -->
<template v-else-if="item.actions && item.actions.length > 0">
<el-table-column :row-key="item.prop" :key="item.prop" :align="item.align || 'center'"
:fixed="item.fixed || 'right'" show-overflow-tooltip v-bind="item">
<template slot-scope="{ row }">
<template v-for="newItem in item.actions">
<template v-if="newItem.mold === 'delete' || newItem.mold === 'twice'">
<el-popconfirm :title="newItem.title || '这一段内容确定删除吗?'" :key="newItem.text"
@confirm="newItem.click(row)" @cancel="() => {}">
<template slot="reference">
<el-tooltip class="item" effect="dark" :content="newItem.text"
placement="top">
<el-image style="
width: 24px;
height: 24px;
vertical-align: sub;
" :src="newItem.url || on_delete" fit="fit"
v-bind="newItem"></el-image>
<!-- <el-button style="margin-left: 8px" :size="newItem.size || 'small '"
:icon=" newItem.icon|| 'el-icon-delete'" v-bind="newItem"></el-button> -->
</el-tooltip>
</template>
</el-popconfirm>
</template>
<template v-else>
<el-tooltip class="item" effect="dark" :content="newItem.text" placement="top"
:key="newItem.text">
<el-image v-if="newItem.text == '详情'"
style="width: 24px; height: 24px; vertical-align: sub"
:src="newItem.url || on_view" fit="fit" @click="newItem.click(row)"
v-bind="newItem"></el-image>
<el-image v-else-if="newItem.text == '编辑'"
style="width: 24px; height: 24px; vertical-align: sub"
:src="newItem.url || on_edit" fit="fit" @click="newItem.click(row)"
v-bind="newItem"></el-image>
<el-image v-else style="width: 24px; height: 24px; vertical-align: sub"
:src="newItem.url || on_other" fit="fit" @click="newItem.click(row)"
v-bind="newItem"></el-image>
<!-- <el-button v-if="newItem.text=='详情'" @click="newItem.click(row)"
:type="newItem.type || primary" :size="newItem.size || 'small '"
:key="newItem.text" :icon=" newItem.icon|| 'el-icon-view'"
style="font-size:24px" v-bind="newItem"></el-button> -->
<!-- <el-button v-else-if="newItem.text=='编辑'" @click="newItem.click(row)"
:type="newItem.type || primary" :size="newItem.size || 'small '"
:key="newItem.text" :icon=" newItem.icon|| 'el-icon-edit-outline'"
v-bind="newItem"></el-button> -->
<!-- <el-button v-else @click="newItem.click(row)" :type="newItem.type || primary"
:size="newItem.size || 'small '" :key="newItem.text"
:icon="newItem.icon || 'el-icon-cherry'" v-bind="newItem"></el-button> -->
</el-tooltip>
</template>
</template>
</template>
</el-table-column>
</template>
<!-- 渲染 其他 -->
<template v-else>
<el-table-column :row-key="item.prop" :key="item.prop" :align="item.align || 'center'"
show-overflow-tooltip v-bind="item"></el-table-column>
</template>
</template>
</el-table>
</div>
<!--分页区域-->
<div class="table-content-foot" v-if="paging">
<div></div>
<div class="pagination">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="pageNum" :page-sizes="pageSizes" :page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>
</div>
</div>
</div>
</template>
<script>
import TableColumn from "./components/table-column/table-column.vue";
export default {
name: "TableConfig",
components: {
TableColumn,
},
props: {
columns: {
type: Array,
default: () => [],
},
data: {
// 表格数据
type: Array,
default: () => [],
},
pageNum: {
type: Number,
default: 1,
},
pageSize: {
type: Number,
default: 10,
},
total: {
type: Number,
default: 0,
},
pageSizes: {
type: Array,
default: () => [10, 20, 30, 40],
},
selectIsShow: {
// 选择框是否显示
type: Boolean,
default: false,
},
idxIsShow: {
// 序列号是否显示
type: Boolean,
default: true,
},
height: {
type: [Number, String],
default: "calc(90vh - 200px)",
},
maxHeight: {
type: [Number, String],
default: "100%",
},
emptyText: {
// 无数据时显示文字
type: String,
default: "暂无数据",
},
stripe: {
// 是否为斑马纹 table
type: Boolean,
default: false,
},
size: {
// Table 的尺寸
type: String,
default: "medium",
},
tableConfig: {
// 更多table 组件的配置
type: Object,
default: () => {
return {
headerCellStyle: {
height: "48px",
background: "#f8f7f8",
color: "#333",
fontWeight: 500,
fontSize: "15px",
fontFamily: "font-family: Source Han Sans CN, Source Han Sans CN",
padding: "0",
},
rowStyle: {
height: "48px ",
fontFamily: "font-family: Source Han Sans CN, Source Han Sans CN",
fontWeight: 400,
fontSize: "15px",
color: "#666",
},
cellStyle: {
padding: "0",
},
};
},
},
paging: {
// 分页
type: Boolean,
default: true,
},
treeProps: {
// 树形表格配置
type: Object,
default: () => {
return {};
},
},
expandRowKeys: {
// 展开行
type: Array,
default: () => [],
},
rowKey: {
// 行key
type: String,
default: "id",
},
defaultExpandAll: {
// 默认展开全部
type: Boolean,
default: false,
},
expand: {
// 是否可展开
type: Boolean,
default: false,
},
},
data() {
return {
// 复选框选中的行
selectRows: [],
// 点击行选中的当前行
currentRow: undefined,
// 内部高度,动态计算
innerHeight: 0,
// 行编辑开启的行索引集合
editRowsKey: [],
// 行编辑的编辑数据,key值为行索引
editRowsData: {},
// 分页条的高度 TODO: 暂时写死,
paginationHeight: 52,
// 按钮区域的高度 TODO: 暂时写死
buttonHeight: 42,
newHeight: 0,
rowBackground: {
background: "#ffff66",
},
on_view: require("@/assets/operate_icons/on_view.png"),
on_edit: require("@/assets/operate_icons/on_edit.png"),
on_delete: require("@/assets/operate_icons/on_delete.png"),
on_other: require("@/assets/operate_icons/on_other.png"),
};
},
created() {
// const father = this.$parent.$el ? this.$parent.$el.parentNode.offsetHeight : 999
// // const father = this.$parent.$el ? this.$parent.$el.parentNode.offsetHeight
// console.log('father: ', father);
// const brother = this.$parent.$el && this.$parent.$el.querySelector(".content-header") ? this.$parent.$el.querySelector(".content-header").offsetHeight : 0
// console.log('brother: ', brother);
// const brother1 = this.$parent.$el && this.$parent.$el.querySelector(".content-toolbar") ? this.$parent.$el.querySelector(".content-toolbar").offsetHeight : 0
// console.log('brother1: ', brother1);
// this.newHeight = father - brother - brother1 - 444
// console.log(' this.newHeight: ', this.newHeight);
},
mounted() {
// const father = this.$parent.$el ? this.$parent.$el.parentNode.offsetHeight : 999
// console.log('father: ', father);
// const brother = this.$parent.$el && this.$parent.$el.querySelector(".content-header") ? this.$parent.$el.querySelector(".content-header").offsetHeight : 0
// console.log('brother: ', brother);
// const brother1 = this.$parent.$el && this.$parent.$el.querySelector(".content-toolbar") ? this.$parent.$el.querySelector(".content-toolbar").offsetHeight : 0
// console.log('brother1: ', brother1);
// this.newHeight = father - brother - brother1 - 44
// console.log(' this.newHeight: ', this.newHeight);
// // this.$refs.zwTable.$el.querySelector('.zj-table__body')
// console.log(' this.$refs.zwTable.$el.querySelector ', this.$refs.zwTable.$el);
// // this.$refs.zwTable.$el.querySelector('.zj-table__body').style.height = (10) + 'px'
// this.$refs.zwTable.$el.style.height = (10) + 'px'
// this.$refs.zwTable.doLayout()
},
updated() {
// 解决表格抖动
this.$refs.zwTable.doLayout();
setTimeout(() => {
this.$forceUpdate();
// this.$refs.zwTable.doLayout();
}, 5);
},
methods: {
// 当选择项发生变化时会触发该事件
selectionLineChangeHandle(val) {
this.$emit("selectionLineChangeHandle", val);
},
// 对外暴露table实列
createTable() {
this.$emit("createTable", this.$refs.zwTable);
},
// 设置某个选中
toggleRowSelection(val) {
this.$refs.zwTable.toggleRowSelection();
},
// 设置全选
toggleAllSelection() {
this.$refs.zwTable.toggleAllSelection();
},
// 行改变
handleSizeChange(val) {
this.$emit("onChangeSize", val);
},
// 页改变
handleCurrentChange(val) {
this.$emit("onChangeNum", val);
},
},
};
</script>
<style lang="scss" scoped>
.table-content-box {
.table-content-foot {
margin-top: 10px;
display: flex;
justify-content: space-between;
.pagination {
::v-deep .el-pagination__total {
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
font-size: 16px;
color: #666666;
}
::v-deep .el-pagination__sizes {
.el-input__inner {
height: 32px;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
font-size: 14px;
color: #666666;
}
}
::v-deep .el-pagination__sizes {
.el-input__inner {
height: 32px;
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
font-size: 14px;
color: #666666;
}
}
::v-deep .btn-prev {
height: 32px;
.el-icon-arrow-left {
font-size: 16px;
}
}
::v-deep .el-pager {
height: 32px;
.number {
font-size: 16px;
height: 32px;
line-height: 32px;
}
}
::v-deep .btn-next {
height: 32px;
.el-icon-arrow-right {
font-size: 16px;
}
}
::v-deep .el-pagination__jump {
font-family: Source Han Sans CN, Source Han Sans CN;
font-weight: 400;
font-size: 14px;
color: #666666;
.el-input__inner {
height: 32px;
}
}
}
}
}
</style>
六、拖拽排序
<template>
<div class="top-right-btn" :style="style">
<el-row>
<!-- <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top"
v-if="search">
<el-button size="mini" circle icon="el-icon-search" @click="toggleSearch()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="刷新" placement="top">
<el-button size="mini" circle icon="el-icon-refresh" @click="refresh()" />
</el-tooltip> -->
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columnsList">
<el-button size="mini" circle icon="el-icon-setting" @click="open= !open" />
</el-tooltip>
</el-row>
<div class="table-list-show" v-show="open">
<template>
<el-checkbox-group v-model="value" @change="dataChange" ref="el-checkbox">
<el-checkbox v-for="(item,idx) in columnsList" :label="item.prop"
:key="idx">{{item.label}}</el-checkbox>
</el-checkbox-group>
</template>
</div>
</div>
</template>
<script>
import Sortable from 'sortablejs'
export default {
name: "RightToolbar",
data() {
return {
// 显隐数据
value: [],
// 弹出层标题
title: "显示/隐藏",
// 是否显示弹出层
open: false,
draggingKey: '', // 当前拖拽项
};
},
props: {
showSearch: {
type: Boolean,
default: true,
},
columnsList: {
type: Array,
default: () => []
},
search: {
type: Boolean,
default: true,
},
gutter: {
type: Number,
default: 10,
},
},
computed: {
style() {
const ret = {};
if (this.gutter) {
ret.marginRight = `${this.gutter / 2}px`;
}
return ret;
}
},
created() {
// 显隐列初始默认隐藏列
this.columnsList.forEach((item) => {
this.value.push(item.prop);
})
this.dataChange()
},
mounted() {
this.Sortable()
},
methods: {
// 显隐搜索
toggleSearch() {
this.$emit("update:showSearch", !this.showSearch);
},
// 刷新
refresh() {
this.$emit("queryTable");
},
// 穿梭
dataChange() {
this.$emit('updateColumnsHide', this.value)
},
Sortable() {
const transfer = document.querySelector('.table-list-show')
const aa = transfer.querySelector('.el-checkbox-group')
new Sortable(aa, {
group: {
name: 'shared',
pull: [false],
},
handle: ".el-checkbox",
draggable: ".el-checkbox",
animation: 150,
onEnd: evt => {
const { oldIndex, newIndex } = evt
this.$emit('changeListIdx', { oldIndex, newIndex })
}
})
}
},
};
</script>
<style lang="scss" scoped>
.top-right-btn {
position: relative;
// top: 44px;
// z-index: 999;
// right: 8px;
.table-list-show {
box-shadow: 0px 0px 10px #a7a1a1;
padding: 10px 5px;
border-radius: 4px;
top: 36px;
right: 0px;
position: absolute;
z-index: 9999;
background-color: #fff;
.el-checkbox {
padding: 3px 10px;
width: 100%;
margin-right: 0;
}
}
}
</style>