工作接了个需求,需要实现表格的动态列,但是后端又不参与,全权交给前端,百度搜了一下,大多都是el-table-column
的for循环,我觉得用起来不爽,还得改变el-table-column
的书写方式,用对象保存列的相关信息,所以搞了一个这玩应
效果就是不改变书写习惯而且还能达到前端控制列的显示与隐藏
话不多讲,上代码
哦,不对,先上效果
动态图没做过,见谅吧
项目结构
简介:
- ColumnControl为列的控制器
- ProjectTable为二次封装的el-table表格
- store中用于存放列的信息以及显示信息
- Home是使用上述三个东西的页面
ColumnControl组件内容
多说一句,由于我的项目使用了自动引入,所以你复制完之后可能有的方法并没有引入,如ref,computed等属于vue,useStore属于vuex,需要自行引入
具体这些个东西都是干什么的见注释吧
<script setup lang="ts">
import { Grid } from '@element-plus/icons-vue'
import { showColumn, allColumn } from '@/store/getters'
import { CheckboxValueType } from 'element-plus'
const store = useStore()
// #region 全选
const checkAll = ref<boolean>(true)
// 选中与半选的状态控制, 条件就是 当前选中的数据个数大于0 且 小于所有列的总数
const isIndeterminate = computed<boolean>(() => {
return checkList.value.length > 0 && checkList.value.length < allColumn.value.length
})
// 全选与否的事件控制器
const handleCheckAllChange = (boolean: CheckboxValueType) => {
if (boolean) { // 全选
checkList.value = allColumn.value.map(item => item.value)
}else { // 全不选
checkList.value = []
}
}
// #endregion 全选
// #region 多选框
// 当前选中的个数 用了可写的computed属性
const checkList = computed<string[]>({
get: () => showColumn.value, // showColumn是存在store中的属性
set: (val: string[]) => {
store.dispatch('setShowColumn', val)
}
})
// #endregion 多选框
</script>
<template>
<el-popover placement="bottom" popper-class="column-popover" :width="200" trigger="click">
<template #reference>
<el-button circle :icon="Grid"></el-button>
</template>
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" @change="handleCheckAllChange">全选</el-checkbox>
<el-divider />
<el-checkbox-group class="column-checkgroup" v-model="checkList">
<el-checkbox class="column-checkgroup-item" v-for="item in allColumn" :key="item.value" :label="item.value">{{ item.label
}}</el-checkbox>
</el-checkbox-group>
</el-popover>
</template>
<style lang="scss">
.column-popover {
max-height: 330px;
overflow-y: auto;
.el-divider--horizontal {
margin: 10px 0;
}
}
.column-checkgroup {
&-item {
display: flex;
}
}
</style>
ProjectTable组件内容
<script setup lang="ts">
import { showColumn } from '@/store/getters'
import { RendererElement } from 'vue'
import { IColumn } from '@/store/modules/table'
// #region ts接口
interface IPage {
currentPage: number
pageSize: number
total: number
}
interface Props {
data: any[]
height?: string | number
pagination?: IPage
hiddenCheckbox?: boolean
hiddenIndex?: boolean
}
interface Emits {
(e: 'selection-change', value: any[]): void
}
// #endregion ts接口
withDefaults(defineProps<Props>(), {
data: () => [], // 表格数据
height: '100%', // 表格高度
hiddenCheckbox: false, // 隐藏表格多选框?
hiddenIndex: false, // 隐藏表格序号?
pagination: () => ({ // 翻页,看项目需求,如果翻到第二页需要从11开始,那么就需要这个
currentPage: 1,
pageSize: 10,
total: 0
})
})
const emits = defineEmits<Emits>()
const store = useStore()
onMounted(() => {
initSlotList() // 关键, 初始化插槽
})
// #region 插槽
const slots = useSlots()
const slotList = ref<RendererElement[]>([])
const initSlotList = () => {
if (slots.default) { // el-table-column 使用时不传name 所以属于默认插槽
slotList.value = slots.default() || [] // 语法
initDynamicColumn() // 初始化动态列
}
}
// 初始化动态列
const initDynamicColumn = () => {
const checkboxList: IColumn[] = [] // 所有列
slotList.value.map(item => {
const props = item.props
// 存在prop属性 label为表头名称
if (props && typeof props === 'object' && props.prop) {
checkboxList.push({ value: props.prop, label: props.label })
}
})
store.dispatch('setAllColumn', checkboxList)
}
// #endregion 插槽
// 表格多选事件
const selectionChange = (list: any[]) => {
emits('selection-change', list)
}
</script>
<template>
<el-table :data="data" border stripe :height="height" @selection-change="selectionChange"
header-cell-class-name="header-cell">
<el-table-column v-if="!hiddenCheckbox" type="selection" align="center" width="55" />
<el-table-column v-if="!hiddenIndex" type="index" align="center" label="#" width="60">
<template #default="{ $index }">
<div>
{{ $index + (pagination.currentPage - 1) * pagination.pageSize + 1 }}
</div>
</template>
</el-table-column>
<!-- 这个template 属于核心代码了 -->
<template v-for="(item, index) in slotList" :key="index">
<component v-if="showColumn.includes(item.props.prop)" :is="item"></component>
</template>
</el-table>
</template>
<style lang="scss" scoped>
</style>
store仓库
别问为什么用vuex 不用pinia 问就是不会
index.ts文件
import table from './modules/table'
const store = createStore({
modules: {
table
}
})
export default store
getters.ts文件
import { IColumn } from './modules/table'
import store from './index'
// vue3 组合api 没法使用mapGetters 弄了个这玩应凑合用
export const allColumn = computed<IColumn[]>(() => {
return store.getters.allColumn
})
export const showColumn = computed<string[]>(() => {
return store.getters.showColumn
})
table.ts文件
import { Module } from 'vuex'
// #region ts接口
export interface IColumn {
value: string
label: string
}
interface IState {
allColumn: IColumn[]
showColumn: string[]
}
// #endregion ts接口
// Module<S, R> S表示咱们这个页面(table.ts)中的state类型
// R: 由于咱们是模块, 在外面的store使用 store.modules = {table} 挂载的咱们
// 而外面的store也会有 state 属性, 这个R就是外面state属性的类型
// 由于我的store/index.ts没写state,所以这里给个any
const table: Module<IState, any> = {
state() {
return {
allColumn: [], // 表格全部的列 格式 IColumn
showColumn: [] // 当前展示的列
}
},
getters: {
allColumn(state) {
return state.allColumn
},
showColumn(state) {
return state.showColumn
}
},
mutations: {
SET_ALL_COLUMN(state, data) {
state.allColumn = data
},
SET_SHOW_COLUMN(state, data) {
state.showColumn = data
}
},
// 使用时 用actions进行数据更改, 尽量不要使用mutations, 没原因, 建议而已
actions: {
setAllColumn({ commit }, data: IColumn[]) {
commit('SET_ALL_COLUMN', data)
// 设置全部列时 默认展示所有的列
const showColumn = data.map(item => item.value)
commit('SET_SHOW_COLUMN', showColumn)
},
setShowColumn({ commit }, data: string[]) {
commit('SET_SHOW_COLUMN', data)
}
}
}
export default table
Home页面
<script setup lang="ts">
// #region ts接口
interface ITableRow {
name: string,
age: number,
random: number
}
interface IPage {
currentPage: number
pageSize: number
total: number
}
// #endregion ts接口
onMounted(() => {
getList()
})
// #region 表格
// 分页数据, 本例由于动态表格封装所以弄了一个
const pagination = ref<IPage>({
currentPage: 1,
pageSize: 10,
total: 0
})
const tableData = ref<ITableRow[]>([])
// 获取列表
const getList = () => {
const list: ITableRow[] = []
for (let i = 0; i < 5; i++) {
list.push({
name: '姓名' + Math.floor(Math.random() * 10),
age: Math.floor(Math.random() * 100),
random: Math.random()
})
}
tableData.value = list
}
// #endregion 表格
</script>
<template>
<div class="home">
<div class="home-buttons">
<div class="home-buttons-opra">
<el-button type="primary">没用,占位</el-button>
</div>
<div class="home-buttons-tools">
<ColumnControl />
</div>
</div>
<div class="home-list">
<!-- 如果要隐藏多选或者序号或者你扩展了什么 <ProjectTable hiddenIndex> 你懂得! -->
<ProjectTable :pagination="pagination" :data="tableData" >
<!-- 使用时就正常使用,不需要写for循环 -->
<el-table-column prop="name" label="姓名" />
<el-table-column prop="age" label="年龄" />
<el-table-column prop="random" label="随机数" />
</ProjectTable>
</div>
</div>
</template>
<style lang="scss" scoped>
.home {
width: 700px;
display: flex;
flex-direction: column;
&-buttons {
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
}
}
</style>
总结
这玩应在使用时除了引用ColumnControl和ProjectTable两个组件以外,与你普通书写el-table是一样的,你也不用重新弄个数组保存所有的列,实现这东西的时候是真的难受,用的时候是爽的不要不要的
注意!!!!!!
我写的这个玩应只是一个最最基本的,没有缓存功能,也没跟用户挂钩,甚至你都不需要关浏览器或者重新登陆,你只要切换一个页面再切回来,这时你刚刚隐藏的列就又会被显示出来,当然要解决这些问题也是完全可以的,不过现在是周五下午五点了,我要下班了,伟大的扩展任务就交给你们了!