> 文章列表 > 第6章 封装组件高级篇(下) - table

第6章 封装组件高级篇(下) - table

第6章 封装组件高级篇(下) - table

--components/table--src--index.vue--index.ts--index.ts

src/index.ts

export interface TableOptions {// 字段名称prop?: string,// 表头label: string,// 对应列的宽度width?: string | number,// 对齐方式align?: 'left' | 'center' | 'right',// 自定义列模板的插槽名slot?: string,// 是否是操作项action?: boolean,// 是否可以编辑editable?: boolean
}

src/index.vue

<template><el-table:data="tableData"v-loading="isLoading":element-loading-text="elementLoadingText":element-loading-spinner="elementLoadingSpinner":element-loading-background="elementLoadingBackground":element-loading-svg="elementLoadingSvg":element-loading-svg-view-box="elementLoadingSvgViewBox"@row-click="rowClick"v-bind="$attrs"><template v-for="(item, index) in tableOption" :key="index"><el-table-columnv-if="item.prop && !item.action":label="item.label":prop="item.prop":width="item.width":align="item.align"><!-- 显示自定义列 --><template #default="scope"><template v-if="scope.row.rowEdit"><el-input size="small" v-model="scope.row[item.prop!]"></el-input></template><template v-else><template v-if="(scope.$index + scope.column.id) === currentEdit"><div style="display: flex"><el-input size="small" v-model="scope.row[item.prop!]"></el-input><div><slot name="cellEdit" v-if="$slots.cellEdit" :scope="scope"></slot><div class="action-icon" v-else><el-icon-check class="check" @click.stop="check(scope)"></el-icon-check><el-icon-close class="close" @click.stop="close(scope)"></el-icon-close></div></div></div></template><template v-else><slot v-if="item.slot" :name="item.slot" :scope="scope"></slot><span v-else>{{ scope.row[item.prop!] }}</span><component:is="`el-icon-${toLine(editIcon)}`"class="edit"v-if="item.editable"@click.stop="clickEditIcon(scope)"></component></template></template></template></el-table-column></template><el-table-column:label="actionOption!.label":width="actionOption!.width":align="actionOption!.align"><template #default="scope"><slot name="editRow" :scope="scope" v-if="scope.row.rowEdit"></slot><slot name="action" :scope="scope" v-else></slot></template></el-table-column></el-table><div v-if="pagination && !isLoading" class="pagination" :style="{ justifyContent }"><el-paginationv-model:currentPage="currentPage":page-sizes="pageSizes":page-size="pageSize"layout="total, sizes, prev, pager, next, jumper":total="total"@size-change="handleSizeChange"@current-change="handleCurrentChange"></el-pagination></div>
</template><script lang='ts' setup>
import { PropType, computed, ref, watch, onMounted } from 'vue'
import { TableOptions } from './types'
import { toLine } from '../../../utils'
import cloneDeep from 'lodash/cloneDeep'let props = defineProps({// 表格配置选项options: {type: Array as PropType<TableOptions[]>,required: true},// 表格数据data: {type: Array,required: true},// 加载文案elementLoadingText: {type: String,},// 加载图标名elementLoadingSpinner: {type: String,},// 加载背景颜色elementLoadingBackground: {type: String,},// 加载图标是svgelementLoadingSvg: {type: String},// 加载团是svg的配置elementLoadingSvgViewBox: {type: String,},// 编辑显示的图标editIcon: {type: String,default: 'Edit'},// 是否可以编辑行isEditRow: {type: Boolean,default: false},// 编辑行按钮的标识editRowIndex: {type: String,default: ''},// 是否显示分页pagination: {type: Boolean,default: false},// 显示分页的对齐方式paginationAlign: {type: String as PropType<'left' | 'center' | 'right'>,default: 'left'},// 当前是第几页currentPage: {type: Number,default: 1},// 当前一页多少条数据pageSize: {type: Number,default: 10},// 显示分页数据多少条的选项pageSizes: {type: Array,default: () => [10, 20, 30, 40]},// 数据总条数total: {type: Number,default: 0}
})let emits = defineEmits(['confirm', 'cancel', 'update:editRowIndex', 'size-change', 'current-change'])// 分页的每一页数据变化
let handleSizeChange = (val: number) => {emits('size-change', val)// console.log(val)
}
// 分页页数改变
let handleCurrentChange = (val: number) => {emits('current-change', val)// console.log(val)
}// 当前被点击的单元格的标识
let currentEdit = ref<string>('')// 拷贝一份表格的数据
let tableData = ref<any[]>(cloneDeep(props.data))
// 拷贝一份按钮的标识
let cloneEditRowIndex = ref<string>(props.editRowIndex)
// 监听的标识
let watchData = ref<boolean>(false)// 如果data的数据变了 要重新给tableData赋值
// 只需要监听一次就可以了
let stopWatchData =  watch(() => props.data, val => {watchData.value = truetableData.value = valtableData.value.map(item => {item.rowEdit = false})if (watchData.value) stopWatchData()
}, { deep: true })// 监听
watch(() => props.editRowIndex, val => {if (val) cloneEditRowIndex.value = val
})onMounted(() => {tableData.value.map(item => {item.rowEdit = false})
})// 过滤操作项之后的配置
let tableOption = computed(() => props.options.filter(item => !item.action))
// 操作项
let actionOption = computed(() => props.options.find(item => item.action))// 是否在加载中
let isLoading = computed(() => !props.data || !props.data.length)// 表格分页的排列方式
let justifyContent = computed(() => {if (props.paginationAlign === 'left') return 'flex-start'else if (props.paginationAlign === 'right') return 'flex-end'else return 'center'
})// 点击编辑图标
let clickEditIcon = (scope: any) => {// 会做一个判断 判断是否当前单元格被点击了// 拼接$index和column的idcurrentEdit.value = scope.$index + scope.column.id// console.log(currentEdit.value)
}// 点击确认
let check = (scope: any) => {emits('confirm', scope)currentEdit.value = ''
}
// 点击取消
let close = (scope: any) => {emits('cancel', scope)currentEdit.value = ''
}// 点击行的事件
let rowClick = (row: any, column: any) => {// 判断是否是点击的操作项if (column.label === actionOption.value!.label) {if (props.isEditRow && cloneEditRowIndex.value === props.editRowIndex) {// 编辑行的操作row.rowEdit = !row.rowEdit// 重置其他数据的rowEdittableData.value.map(item => {if (item !== row) item.rowEdit = false})// 重置按钮的标识if (!row.rowEdit) emits('update:editRowIndex', '')}}
}
</script><style lang='scss' scoped>
.edit {width: 1em;height: 1em;position: relative;top: 2px;left: 12px;cursor: pointer;
}
.action-icon {display: flex;svg {width: 1em;height: 1em;margin-left: 8px;position: relative;top: 8px;cursor: pointer;}.check {color: red;}.close {color: green;}
}
.pagination {margin-top: 16px;display: flex;
}
</style>

index.ts

import { App } from 'vue'
import table from './src/index.vue'// 让这个组件可以通过use的形式使用
export default {install(app: App) {app.component('m-table', table)}
}

使用view/table/index.vue

<template><m-table:options="options":data="tableData"elementLoadingText="加载中..."elementLoadingBackground="rgba(0,0,0,.8)":element-loading-svg="svg"element-loading-svg-view-box="-10, -10, 50, 50"isEditRowpaginationstripeborder:total="total":currentPage="current":pageSize="pageSize"v-model:editRowIndex="editRowIndex"@confirm="confirm"@size-change="handleSizeChange"@current-change="handleCurrentChange"><template #date="{ scope }"><el-icon-timer></el-icon-timer><span style="margin-left: 10px">{{ scope.row.date }}</span></template><template #name="{ scope }"><el-popover effect="light" trigger="hover" placement="top"><template #default><p>姓名: {{ scope.row.name }}</p><p>住址: {{ scope.row.address }}</p></template><template #reference><div class="name-wrapper"><el-tag size="medium">{{ scope.row.name }}</el-tag></div></template></el-popover></template><template #editRow="scope"><el-button size="small" type="primary" @click="sure(scope.scope)">确认</el-button><el-button size="small" type="danger">取消</el-button></template><template #action="scope"><el-button size="small" type="primary" @click="edit(scope.scope)">编辑</el-button><el-button size="small" type="danger">删除</el-button></template></m-table>
</template><script lang='ts' setup>
import { TableOptions } from '../../components/table/src/types'
import { ref, onMounted } from 'vue'
import axios from 'axios'let options: TableOptions[] = [{prop: 'date',label: '日期',// width: '180',align: 'center',slot: 'date',editable: true},{prop: 'name',label: '姓名',// width: '180',align: 'center',slot: 'name'},{prop: 'address',label: '地址',align: 'center',editable: true},{label: '操作',action: true,align: 'center'}
]
let tableData = ref<any[]>([])
let editRowIndex = ref<string>('')
let svg = `<path class="path" d="M 30 15L 28 17M 25.61 25.61A 15 15, 0, 0, 1, 15 30A 15 15, 0, 1, 1, 27.99 7.5L 15 15" style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/>`
// setTimeout(() => {
// tableData.value = [
//   {
//     date: '2016-05-03',
//     name: 'Tom1',
//     address: 'No. 189, Grove St, Los Angeles',
//   },
//   {
//     date: '2016-05-02',
//     name: 'Tom2',
//     address: 'No. 189, Grove St, Los Angeles',
//   },
//   {
//     date: '2016-05-04',
//     name: 'Tom3',
//     address: 'No. 189, Grove St, Los Angeles',
//   },
//   {
//     date: '2016-05-01',
//     name: 'Tom4',
//     address: 'No. 189, Grove St, Los Angeles',
//   },
// ]
// }, 3000)let current = ref<number>(1)
let pageSize = ref<number>(10)
let total = ref<number>(0)
let getData = () => {axios.post('/api/list', {current: current.value,pageSize: pageSize.value,}).then((res: any) => {if (res.data.code === '200') {tableData.value = res.data.data.rowstotal.value = res.data.data.totalconsole.log(res.data.data)}})
}
let handleSizeChange = (val: number) => {pageSize.value = valgetData()
}
let handleCurrentChange = (val: number) => {current.value = valgetData()
}
onMounted(() => {getData()
})let edit = (scope: any) => {// console.log(scope)editRowIndex.value = 'edit'
}
let sure = (scope: any) => {console.log(scope)
}
let confirm = (scope: any) => {// console.log(scope)
}
</script><style lang='scss' scoped>
svg {width: 1em;height: 1em;
}
</style>

总结
支持 element-plus 表格所有组件
支持 element-plus 表格所有属性
支持 element-plus 表格所有方法
所有表格项完全可配置
支持单元格可编辑
支持行编辑
支持表格分页

难点:
表格配置项设计
表格逻辑梳理
如何支持所有组件、属性和方法
如何支持单元格可编辑
如何支持行编辑
如何支持表格分页
如何与实际业务结合