【Vue3+Vite+Ts+element-plus】
超级详细 最新 vite4+vue3+ts+element-plus+eslint-prettier 项目搭建流程
【Vue3+Vite+Ts+element-plus】使用tsx实现左侧栏菜单无限层级封装
【Ts 系列】
TypeScript 从入门到进阶之基础篇(一) ts类型篇
在我们日常开发中 会经常用到表格 特别是后台管理系统 几乎大部分页面都涉及到表格,使用好的表格封装能为我们节省很多开发时间,也能让代码清晰易懂,接下来我们将从零使用tsx 二次封装一下element-plus的表格。
我们需要创建一个vue3项目 我用的是vite去创建vue3+ts 项目的 ,这里就不多讲项目的创建了 ,如果要详细的项目搭建流程可参考:超级详细 最新 vite4+vue3+ts+element-plus+eslint-prettier 项目搭建流程
在使用tsx之前 我们要安装一些插件 使我们的项目支持tsx
//下面3种安装方式选择一种 推荐pnpm
yarn add @vitejs/plugin-vue-jsx
//or
npm install @vitejs/plugin-vue-jsx -D
//or
pnpm install @vitejs/plugin-vue-jsx -D
在 vite.config.ts 文件中挂载
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [ vueJsx()]
})
tsconfig.json 文件中
{
// include 需要包含tsx
"include": ["src/*", "src/**/*.vue", "src/**/*.tsx", "src/**/*.jsx", "src/**/*.ts", "src/**/*.js"],
"compilerOptions": {
// 在.tsx文件里支持JSX
"jsx": "preserve",
}
}
首先我们在项目的 components 文件夹下 新建 DynamicTable 文件夹, 并在DynamicTable* 文件夹下新建 DynamicTable.vue文件
在封装表格之前 我们要了解 在通常的表格中 有哪些情况会出现 例如: 表格可能涉及到分页 、可能涉及到接口的请求、也可能表格中的每一行 可内容自定义 等情况 。首先我们先将表格和分页写出来先 代码如下
<script lang="tsx">
import { ElTable } from 'element-plus'
export default {
props: {
//表格数据
tableData: {
type: Array,
default: () => [],
},
//表格列数据
tableColumns: {
type: Array,
default: () => [],
},
//表格配置项
configuration: {
type: Object,
default: () => ({}),
},
//分页下拉选择
pageSizes: {
type: Array,
default: () => [10, 20, 50, 100],
},
//分页组件的样式
paginationLayout: {
type: String,
default: () => 'total,sizes,prev, pager, next,jumper',
},
//是否为分页按钮添加背景色
background: {
type: Boolean,
default: true,
},
//请求函数
request: {
type: Function,
default: () => {
return Function
},
},
//是否开启单选
highlightCurrentRow: {
type: Boolean,
default: () => {
return false
},
},
//是否在打开页面时默认加载表格请求 默认为 true
isLoadRequest: {
type: Boolean,
default: () => {
return true
},
},
//表格勾选的数据
selectedList: {
type: Array,
default: () => {
return []
},
},
//获取表格勾选的行数据
getAllSelectedList: {
type: Array,
default: () => {
return []
},
},
//表格行禁用
tableDisable: {
type: Function,
default: () => {
return Function
},
},
//分页是否只有一页时隐藏
hideOnSinglePage: {
type: Boolean,
default: () => {
return false
},
},
//是否自定义转换
isCustomConversion: {
type: Boolean,
default: () => {
return false
},
},
//返回转换好的数据
customConversion: {
type: Function,
default: () => {
return Function
},
},
//是否自定义分页
isCustomPage: {
type: Boolean,
default: () => {
return false
},
},
// 接收自定义分页返回的参数
customPage: {
type: Function,
default: () => {
return Function
},
},
},
emits: [
'handleSelectionChange',
'update:selectedList',
'update:getAllSelectedList',
'update:tableData',
'getResultData',
'selectable',
],
setup(props, { slots, expose, emit }) {
const singleTableRef = ref<InstanceType<typeof ElTable>>()
//表格加载
const loading = ref(false)
const clientWidth = ref(document.documentElement.clientWidth)
onMounted(() => {
window.onresize = () => {
return (() => {
clientWidth.value = document.documentElement.clientWidth
})()
}
})
const getWidth = (width: string) => {
return `${clientWidth.value * (parseInt(width) / 1440.0)}px`
}
//设置表格 Column/
const setTableColumn = () => {
return props.tableColumns.map((item: any) => {
if (!item.slot) {
return (
<el-table-column
{...item}
width={
item.type === 'selection' ? getWidth(item.width) : item.width
}
selectable={selectable}
></el-table-column>
)
} else {
return (
<el-table-column
{...item}
selectable={selectable}
width={
item.slotName === 'operation'
? getWidth(item.width)
: item.width
}
>
{{
default: (scope: any) => slots[item.slotName]?.(scope),
}}
</el-table-column>
)
}
})
}
/**
* 分页相关配置
*/
//表格数据
const tableData = ref(props.tableData as any[])
//第几页
const currentPage = ref(1)
//每页的数量
const pageSize = ref(10)
//总数量
const total = ref(props.tableData.length)
const getData = ref({})
watch(
props.tableData,
(nel) => {
tableData.value = nel
},
{ deep: true },
)
//数据请求
const refreshTable = async (datas: any = {}, reset = true) => {
if (reset) {
currentPage.value = 1
}
loading.value = true
getData.value = datas
try {
const { result }: any = await props.request({
...datas,
pageNo: props.isCustomPage ? 1 : currentPage.value,
pageSize: pageSize.value,
})
const { totalPage }: any = result.pageInfo
//判断要跳转的页是否没有了 是的话回到接口返回的最后一页
if (!reset && totalPage < currentPage.value && currentPage.value > 1) {
currentPage.value = totalPage
refreshTable(datas, reset)
}
//表格数据转换赋值
tableData.value = result
emit('getResultData', result, tableData.value)
//总条数赋值
total.value = props.isCustomPage
? props.customPage()
: result.pageInfo && result.pageInfo.totalSize
? Number(result.pageInfo.totalSize)
: 0
loading.value = false
} catch (err) {
tableData.value = []
loading.value = false
}
}
//是否默认开启请求
props.isLoadRequest ? refreshTable(getData.value) : ''
onMounted(() => {
getData.value = {}
})
//page-size 改变时触发
const handleSizeChange = (val: number) => {
console.log(`${val} items per page`)
refreshTable(getData.value, false)
}
//current-page 改变时触发
const handleCurrentChange = (val: number) => {
console.log(`current page: ${val}`)
refreshTable(getData.value, false)
}
//勾选事件
const selectedList = ref<string[]>([])
const handleSelectionChange = (val: any) => {
selectedList.value = val.map((item: any) => item.id)
emit('update:getAllSelectedList', val)
emit('update:selectedList', selectedList.value)
}
const selectable = (row: any) => {
return props.tableDisable(row)
}
//选中项高亮
const tableRowClassName = ({ row }: { row: any }) => {
return selectedList.value.indexOf(row.id) ? 'warning-row' : 'success-row'
}
/**获取页数 和每页数量 */
const getPageSizeAndCurrentPage = () => {
return {
currentPage: currentPage.value,
pageSize: pageSize.value,
}
}
/**设置页数 和每页数量 */
const setCurrentPage = (current: any) => {
currentPage.value = current
}
const tableRowStype = () => {
return {
borderRadius: '15px',
overflow: 'hidden',
}
}
// 使用 expose 暴露组件内部的方法
expose({
refreshTable,
getPageSizeAndCurrentPage,
setCurrentPage,
})
return () => (
<div class="table-style table">
<el-table
max-height="800px"
ref="singleTableRef"
class="custom-table"
row-key="id"
v-loading={loading.value}
data={tableData.value}
row-class-name={tableRowClassName}
row-style={tableRowStype}
cell-style={{ textAlign: 'center' }}
onSelectionChange={handleSelectionChange}
{...props.configuration}
>
{setTableColumn()}
</el-table>
<el-pagination
small
v-model:current-page={currentPage.value}
v-model:page-size={pageSize.value}
total={total.value}
highlightCurrentRow={props.highlightCurrentRow}
background={props.background}
layout={props.paginationLayout}
onCurrentChange={handleCurrentChange}
onSizeChange={handleSizeChange}
popper-class="popperClass"
hideOnSinglePage={props.hideOnSinglePage}
></el-pagination>
</div>
)
},
}
</script>
在需要使用的引入使用 并 创建一个ts文件用于存放列的配置即可
<dynamic-table
ref="dynamicTableRef"
class="dynamic-table"
:request="request.listPagesBrand"
:tableColumns="tableColumns"
:isLoadRequest="false"
:tableDisable="selectable"
>
<template #internal="scope">
{{ scope.row.internal === 1 ? '是' : '否' }}
</template>
<template #operation="scope">
<el-button
type="primary"
link
@click="editbrand(scope.row.id)"
:disabled="!!Number(scope.row.internal)"
>
编辑
</el-button>
<el-divider direction="vertical" />
<delete-button
:data="[scope.row.id]"
:request="request.delBatchBrand"
@requestCallback="() => submitSearch(false)"
:disabled="!!Number(scope.row.internal)"
type="primary"
link
></delete-button>
</template>
</dynamic-table>
//Table 表头定义
const tableColumns: any = [
{
type: 'selection',
width: '70px',
},
{
prop: 'brandName',
label: '品牌',
showOverflowTooltip: true,
},
{
prop: 'englishLogo',
label: '英文标识',
showOverflowTooltip: true,
},
{
slot: true,
slotName: 'internal',
label: '内置',
showOverflowTooltip: true,
},
{
slot: true,
slotName: 'operation',
width: '200px',
label: '操作',
},
]
export default tableColumns