一、需求

在后台管理项目中,可以显而易见的见到表格组件,为了方便我们会把表格组件进行二次封装,即方便了开发,也方便了维护。

2023年04月01日更新单元格编辑功能

注意事项

2023年4月25号

// LTable.vue组件中
// 在修改某一行数据的时候切记删除添加的 index 字段
// 否则会报这样的一个错误:caught (in promise) ER_BAD_FIELD_ERROR: Unknown column 'index' in 'field list'
// 就是这里这个index导致的,在你去请求接口把这个字段删除掉就可以了,
// 例子:delete form.index	下面接着去请求接口就行了
// 为每一行返回固定的className
function tableRowClassName({ row, rowIndex }) {
    row.index = rowIndex;
}
// 为所有单元格设置一个固定的 className
function tableCellClassName({ column, columnIndex }) {
    column.index = columnIndex;
}

二、组件功能

1、基础表格
2、表格分页
3、单条数据操作
4、表格中图片展示
5、根据需要格式化时间
6、单条数据操作 启用/禁用
7、根据数据状态展示不同样式

三、准备工作

components 下创建 LTable.vue 文件
接下来是我们的页面
分别需要一个 index.vuetable.ts 文件
接下来我们直接上代码

四、LTable.vue 完整代码

这里需要吧 LTable.vue 该组件全局组册 或者使用时单独引入(根据自己需求)

<template>
    <div class="l-table">
        <!-- 表格 -->
        <el-table :data="props.tableModule.dataList" border height="100%" style="width: 100%; overflow-y: scroll"
            v-loading="props.tableModule.loading" @selection-change="props.tableModule.selectChange"
            :row-class-name="tableRowClassName" :cell-class-name="tableCellClassName" @cell-dblclick="cellDblClick">
            <el-table-column type="selection" width="50" align="center" />
            <template v-if="tableChildren == '1'">
                <el-table-column v-for="(item, index) in props.tableModule.columns" :key="item.prop" :prop="item.prop"
                    :label="item.label" :align="item.align || 'left'" :width="item.width" :min-width="item.min_width"
                    :fixed="item.fixed">
                    <template slot-scope="scope" #default="scope">
                        <div v-if="item.type == 'switch'">
                            <el-switch v-model="scope.row[item.prop]" :active-value="item.activeValue"
                                :inactive-value="item.inactiveValue" @change="props.tableModule.switchChange(scope.row)">
                            </el-switch>
                        </div>
                        <div v-else-if="item.type == 'status'">
                            <el-tag :type="item.color ? item.color[scope.row[item.prop]] : ''">{{
                                props.tableModule.fieldChange(scope.row[item.prop], item.option) }}</el-tag>
                        </div>
                        <div v-else-if="item.type == 'image'">
                            <el-image style="width: 60px; height: 60px" :src="scope.row[item.prop]"
                                :preview-src-list="[scope.row[item.prop]]">
                            </el-image>
                        </div>
                        <div v-else-if="item.type == 'time'">{{ formatDate(scope.row[item.prop]) }}</div>
                        <div v-else-if="item.isEdit">
                            <el-input v-model="scope.row[item.prop]" :placeholder="'请输入' + item.label"
                                @blur="inputBlur(scope.row)" autofocus ref="inputRef"
                                v-if="scope.row['index'] == rowIndex && scope.column['index'] == columnIndex" />
                            <div v-else>{{ scope.row[item.prop] }}</div>
                        </div>
                        <div v-else>{{ scope.row[item.prop] }}</div>
                    </template>
                </el-table-column>
            </template>
            <template v-else-if="tableChildren == '2'">
                <el-table-column v-for="(one, index) in props.tableModule.columns" :key="index" :label="one.label">
                    <el-table-column v-for="item in props.tableModule.columns[index].children" :key="item.prop"
                        :prop="item.prop" :label="item.label" :align="item.align || 'left'" :width="item.width"
                        :min-width="item.min_width" :fixed="item.fixed">
                        <template slot-scope="scope" #default="scope">
                            <div v-if="item.type == 'switch'">
                                <el-switch v-model="scope.row[item.prop]" :active-value="item.activeValue"
                                    :inactive-value="item.inactiveValue"
                                    @change="props.tableModule.switchChange(scope.row)">
                                </el-switch>
                            </div>
                            <div v-else-if="item.type == 'status'">
                                <el-tag :type="item.color ? item.color[scope.row[item.prop]] : ''">{{
                                    props.tableModule.fieldChange(scope.row[item.prop], item.option) }}</el-tag>
                            </div>
                            <div v-else-if="item.type == 'image'">
                                <el-image style="width: 60px; height: 60px" :src="scope.row[item.prop]"
                                    :preview-src-list="[scope.row[item.prop]]">
                                </el-image>
                            </div>
                            <div v-else-if="item.type == 'time'">{{ formatDate(scope.row[item.prop]) }}</div>
                            <div v-else-if="item.isEdit">
                                <el-input v-model="scope.row[item.prop]" :placeholder="'请输入' + item.label"
                                    @blur="inputBlur(scope.row)" autofocus ref="inputRef"
                                    v-if="scope.row['index'] == rowIndex && scope.column['index'] == columnIndex" />
                                <div v-else>{{ scope.row[item.prop] }}</div>
                            </div>
                            <div v-else>{{ scope.row[item.prop] }}</div>
                        </template>
                    </el-table-column>
                </el-table-column>
            </template>
            <slot name="event"></slot>
        </el-table>
        <div class="l-pages">
            <!-- 分页 -->
            <el-pagination :current-page="props.tableModule.pages.page" :page-size.sync="props.tableModule.pages.limit"
                :page-sizes="pageSizes" :layout="layout" :total="props.tableModule.pages.total"
                @size-change="props.tableModule.sizeChange" @current-change="props.tableModule.currentChange" />
        </div>
    </div>
</template>
<script setup>
import { defineProps, onMounted, reactive, toRefs } from 'vue'
import { formatDate } from '@/utils/index'
const props = defineProps({
    tableModule: Object,
    layout: {
        type: String,
        default: "total, sizes, prev, pager, next, jumper",
    },
    pageSizes: {
        type: Array,
        default() {
            return [10, 20, 30, 50];
        },
    },
    tableChildren: {
        type: String,
        default: '1'
    }
})
const state = reactive({
    rowIndex: 0,
    columnIndex: 0
})
const { rowIndex, columnIndex } = toRefs(state)
onMounted(() => { })
// 编辑单元格
// 为每一行返回固定的className
function tableRowClassName({ row, rowIndex }) {
    row.index = rowIndex;
}
// 为所有单元格设置一个固定的 className
function tableCellClassName({ column, columnIndex }) {
    column.index = columnIndex;
}
// el-table单元格双击事件
function cellDblClick(row, column, cell, event) {
    state.rowIndex = row.index
    state.columnIndex = column.index
}
// input失去焦点
function inputBlur(row) {
    state.rowIndex = 0
    state.columnIndex = 0
    props.tableModule.editInputBlur()
}
</script>
<style scoped lang="scss">
.l-table {
    height: calc(100% - 32px);
    padding-bottom: 10px;
    .el-table {
        height: calc(100% - 42px) !important;
    }
    .l-pages {
        margin-top: 10px;
    }
}
</style>

index.vue 完整代码

<template>
    <div class="role">
        <!--  :tableChildren="2" 开启二级表头 普通表格不穿 -->
        <LTable :tableModule="tableModule"  :tableChildren="2">
            <template #event>
                <el-table-column align="center" fixed="right" label="操作" width="120">
                    <template slot-scope="scope" #default="scope">
                        <el-button @click="userDelete(scope)">删除</el-button>
                    </template>
                </el-table-column>
            </template>
        </LTable>
    </div>
</template>
<script setup lang="ts">
import { reactive, toRefs, ref, onMounted } from 'vue'
import { columnsData } from './table'
import { ElMessage, ElMessageBox } from 'element-plus'
const state = reactive({
    columns: columnsData,
    selections: [],
    loading: false,
    dataList: [
        {
            username: '张三',
            phone: '16688889999',
            createDate: 1679489616000,
            avatar: 'https://img1.baidu.com/it/u=2427424503,947601508&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500',
            userStatus: 1,
        }
    ],
    pages: {
        total: 0,
        limit: 20,
        page: 1,
    }
})
const { loading, dataList, columns, pages } = toRefs(state)
onMounted(() => {
    getDataList()
})
function getDataList() {
}
function userDelete(row: any) {
    console.log(row)
}
// 表格分页相关
const tableModule = ref<Object>({
    currentChange: currentChange,
    selectChange: selectChange,
    fieldChange: fieldChange,
    sizeChange: sizeChange,
    editInputBlur: editInputBlur,
    columns: columns,
    dataList: dataList,
    loading: loading,
    pages: pages
})
function selectChange(selection: any) {
    state.selections = selection
}
function fieldChange(row: any, option: any) {
    if (option[row]) {
        return option[row]
    }
}
function sizeChange(item: any) {
    state.pages.limit = item
    getDataList()
}
function currentChange(item: any) {
    state.pages.page = item
    getDataList()
}
function editInputBlur() {
    console.log('可编辑单元格失去焦点')
}
</script>
<style scoped lang="scss"></style>

table.ts 完整代码

talbe.ts文件介绍
该文件是控制我们表格的列展示的
const columnsData: any = [
    {
        prop: 'username', // 数据字段对应
        label: '姓名', 	// 列名称
        min_width: 120,	// 最小长度(这里建议每一个表格中最少有一个字段设置最小长度)设置这个就不用设置 widht 了,大家可以自己摸索
        fixed: true // 固定表头(注意:二级表头时不可用)
    },
    {
        prop: 'phone',
        label: '手机号',
        isEdit: true, // isEdit为 true 开启单元格编辑(可编辑的单元格由自己控制)
        align: 'center', // 控制列对其方式(默认左对齐)
        width: 150,
    },
    {
        prop: 'createDate',
        label: ' 创建时间',
        align: 'center',
        type: 'time', // 针对字段需要时间格式化
        width: 180,
    },
    {
        prop: 'avatar',
        label: '头像',
        align: 'center',
        type: 'image', // 设置字段以图片展示
        width: 80
    },
    {
        prop: 'userStatus',
        label: '用户状态',
        align: 'center',
        type: 'switch', // 编辑用户状态
        activeValue: 1, // switch为打开时的值
        inactiveValue: 2,// switch为关闭时的值
        width: 100
    },
    {
        prop: 'userStatus',
        label: '用户状态',
        align: 'center',
        width: 100,
        type: 'status', // 与 type 为 switch 不同(status主要是看的而不是对数据的操作)
        option: {
            '1': '启用',
            '2': '禁用',
        },
        color: {
            '1': 'success',
            '2': 'info',
        }
    },
]
export {
    columnsData
}
// 二级表头写法
const columnsData: any = [
    {
        label: '用户信息', // 表头名称
        children: [ // 子级表头
            {
                prop: 'username',
                label: '姓名',
                min_width: 120
            }
        ]
    },
    // 依次向下添加即可
]
export {
    columnsData
}

这里附上一张效果图
Element Plus二次封装el-table、可编辑表格
二级表头效果图
Element Plus二次封装el-table、可编辑表格
可编辑单元格
Element Plus二次封装el-table、可编辑表格

表格增删改查请移步至 el-table表格查询封装

发表回复