vue3 + ts element Plus table组件二次封装

一.关于table组件二次封装的设想与实现

1.常规化模块
    1.1 分页组件
        1.1.1 常规化模块
            (1) 分页组件 是否展示  总页数  当前页数  页数跳转  以及刷新列表方法
    1.2 按钮组
        1.2.1 常规化按钮组
            (1) 顶部status状态  是否展示  状态名称 状态索引 点击事件后方法的回调
            (2) 特殊按钮组 是否展示  自定义按钮组样式模块 按钮组种是否含有其他展示数据
        1.2.2 按钮组合并为统一组件
            (1) 合并的兼容效果
            (2) 合并后的展示优先级
2. 表格组件
    2.1 表头模块
        2.1.1 常规化表头
            (1) 表头背景色 表格高度 表格宽度 表格统一样式 是否可合并表头 表格是否展示斑马纹  表格斑马纹的颜色  表格是否展示分割线 表格鼠标悬浮色 
    2.2 表格模块
        2.2.1 多形态表格
            (1) 表格是否支持多选 单选
            (2) 表格是否展示索引列
            (3) 表格种多状态判断问题
            (4) 表格种多类型事件
            (5) 表格多选状态下是否支持保留选中数据 以及数据的反显
            (6) 表格多类型判断以及多场景应用
    2.3 表格组件参数认
        2.3.1 必传参数
            (1) 表格的数据
            (2) 表格高度
            (3) 表格展示数据与相关的资源
            (4) 当前数据的总条数 以及刷新列表的方法
        2.3.2 非必传参数 以及默认值
            (1) loading 默认值为false
            (2) 是否展示斑马纹 true
            (3) 是否展示表格分割线  true
            (4) 表格基础属性 动态 基础表格
            (5) 表格是否展示多选单选框 false
            (6) 表格展示多选单选框是否支持 数据保留以及反显  true
            (7) 父组件方法调用 单一传 emit 方式 仅传一次 通过父组件的表格不通参数与索引在父组件种做区分以及方法调用
            (8) 表格宽度 100%
            (9) 表格在展示的自定义模块不同状态的展示
            (10) 是否展示分页 true
            (11) 是否展示按钮组  false
            (12) 按钮组展示数据

二.组件封装的实现

1.html部分
<template>
    <el-table ref="multipleTableRef" v-loading="loading" :border="true" :data="tableData" stripe @row-click="changeClickRow"
        :row-key="getRowKeys" @selection-change="selectionChange" :height="tableHeight + 'vh'">
        <el-table-column v-if="selectVisible" :reserve-selection="true" align="left" type="selection" width="45" />
        <template v-for="(column, index) in tableColumn" :key="index">
            <!-- 单图展示模块 -->
            <el-table-column v-if="column.type === 'image'" v-bind="setAttrs(column)">
                <template #default="scope">
                    <el-image v-if="scope.row.image != '' && scope.row.image != null" :src="getImage(scope.row.image)"
                        style="width: 70px;height: 70px;"></el-image>
                    <el-image v-else style="width: 70px;height: 70px;" :src="defaultImage"></el-image>
                </template>
            </el-table-column>
            <!-- 多图展示模块 -->
            <el-table-column v-if="column.type === 'images'" v-bind="setAttrs(column)">
                <template #default="scope">
                    <div v-if="scope.row.image != '' && scope.row.image != null">
                        <el-image v-for="item in scope.row.image.split(',')" :src="getImage(item.image)"
                            style="width: 70px;height: 70px;"></el-image>
                    </div>
                    <el-image v-else style="width: 70px;height: 70px;" :src="defaultImage"></el-image>
                </template>
            </el-table-column>
            <!-- 自定义展示模块 -->
            <el-table-column v-if="column.render" v-bind="setAttrs(column)" show-overflow-tooltip>
                <template #default="scope">
                    <Render :column="column" :index="scope.$index" :render="column.render" :row="scope.row"
                        v-bind="$attrs" />
                </template>
            </el-table-column>
            <!-- 带索引展示模块 -->
            <el-table-column v-else-if="column.type === 'index'" type="index" v-bind="setAttrs(column)" width="50" />
            <!-- 多文字展示模块 -->
            <el-table-column v-else :key="column.prop" show-overflow-tooltip v-bind="setAttrs(column)" />
        </template>
    </el-table>
    <v-paging v-if="isVPaging" :pagesize="querys.limit" :total="dataCount" :options="querys" :render="getList"></v-paging>
</template>
2.TS部分
<script setup lang="ts">

/// 依赖组件引用模块
// 默认图片
import defaultImage from '@/assets/logo.png'

// 分页组件
import vPaging from '@/components/paging/index.vue'
// vue
import { reactive } from 'vue';
// render
import Render from './render'
import { getImage } from '@/api/index/api';
/// 父组件参数传递模块

const props = defineProps({
    // 表格数据
    tableData: {
        type: Array<any>,
        default: true,
    },
    // 表格高度
    tableHeight: {
        type: Number,
        default: true
    },
    // 表格渲染资源
    tableColumn: {
        type: Array<any>,
        default: () => [],
    },
    // 列表总条数
    dataCount: {
        type: Number,
        default: true,
    },
    // 列表每页多少条
    dataPage: {
        type: Number,
        default: 20,
    },
    // loading状态
    loading: {
        type: Boolean,
        default: false,
    },
    // 是否展示分页
    isVPaging: {
        type: Boolean,
        default: true,
    },
    // table组件的属性设置
    tableAttr: {
        type: Object,
        default: () => ({
            border: true,
        }),
    },
    // 是否可选择行
    selectVisible: {
        type: Boolean,
        default: false,
    },

    // 多选  单选
    // 参数值为true 为多选 false 为单选
    multipleChange: {
        type: Boolean,
        default: true
    }

});


const querys = reactive({//配置对应的查询参数
    appTimeStart: '',
    appTimeEnd: '',
    page: 1,
    limit: useVModel(props, 'dataPage').value
});


/// 父组件方法调用模块

const emit = defineEmits(['selectionChange', 'getList'])

const multipleChange = useVModel(props, 'multipleChange')

const selectVisible = useVModel(props, 'selectVisible')

// 给元素设置属性
const setAttrs = (params) => {
    const { ...options } = params
    if (!options.align) {
        options.align = 'center'
    }
    return { ...options }
}
// 复选框
const selectionChange = (selections: any) => {
    /**
     * 这里需要注意我们在复选框中本来是不支持单选的我们选择使用了手动更新的办法去将复选变为单选
     * 不过状态的更新并不影响我们组件的使用以及参数的回调
     */
    multipleSelect.value = selections
    if (multipleChange.value == true) {
        emit('selectionChange', multipleSelect.value)
    } else {
        if (multipleSelect.value.length > 1) {
            multipleTableRef.value.toggleRowSelection(multipleSelect.value[0], false)
        }
        emit('selectionChange', multipleSelect.value[multipleSelect.value.length - 1])
    }
}

// 行点击事件
const changeClickRow = (row: any) => {
    /**
     * 这里需要注意  我们的行单点事件并不能直接触发我们的select选择事件 所以需要增减判断去手动更新表格中的select状态
     * 更加注意的是我们事件触发机制是建立在selectVisible为true的前提下的
    */
    if (selectVisible.value == false) return false;
    if (multipleSelect.value != undefined && multipleSelect.value.length != 0) {
        multipleSelect.value.map((item: any, index: number) => {
            if (item.id == row.id) {
                multipleTableRef.value.toggleRowSelection(multipleSelect.value[index], false)
            } else {
                multipleTableRef.value.toggleRowSelection(row, true)
            }
        })
    } else {
        multipleTableRef.value.toggleRowSelection(row, true)
    }
    if (multipleChange.value == true) {
        emit('selectionChange', multipleSelect.value)
    } else {
        if (multipleSelect.value.length > 1) {
            multipleTableRef.value.toggleRowSelection(multipleSelect.value[0], false)
        }
        emit('selectionChange', multipleSelect.value[multipleSelect.value.length - 1])
    }
}

// 表格切换来回数据反显
const getRowKeys = (row: any) => {
    return row.id
}

// 展示分页  数据刷新模块
const getList = () => {
    let params = {
        page: querys.page,
        rows: querys.limit
    }
    emit('getList', params)
}


/// 变量定义模块
const multipleTableRef = ref()

// 行选中的数据
const multipleSelect = ref<Array<any>>()

</script>
3.引用页面部分
(1)引用html部分
<tempTable ref="tableRef" :tableColumn="columns" :loading="loading" @get-list="getList"             
            :tableHeight="63" dataCount="pageTotal" :tableData="tableData">
</tempTable>
(2)逻辑模块
<script lang="tsx" setup>
import tempTable from "@/components/table/tempTable.vue"
import { ElButton, ElTag } from 'element-plus';

const tableRef = ref()

const columns = ref<Array<any>>([
    {
        label: '编号',
        width: 50,
        type: 'index',
        align: 'center'
    },
    {
        label: '服务时间', prop: 'reserve_time', render(props) {
            let { row } = props
            let reserve_time = row.reserve_time
            return reserve_time.replace('T', ' ')
        }
    },
    { label: '回访人员', prop: '_cuser' },
    {
        label: '回访状态', prop: 'status',
        render(props) {
            const { row } = props
            let status = row._status
            let srcHtml = null
            if (row.visit_result == 'X3WVP7B2A') {
                if (row.status == 'X3WUCWYUU') {
                    srcHtml = <ElTag style="color: #880DE2;font-size:14px;">{status}</ElTag>
                }
                if (row.status == 'X3WUCWYUT') {
                    srcHtml = <ElTag style="color: #69CFE2;font-size:14px;">{status}</ElTag>
                }
                if (row.status == 'X3WUCWYUS') {
                    srcHtml = <ElTag style="color: #f00;font-size:14px;">{status}</ElTag>
                }
                if (row.status == 'X3WUCWYUR') {
                    srcHtml = <ElTag style="color: #0099ff;font-size:14px;">{status}</ElTag>
                }
            }
            if (row.visit_result != 'X3WVP7B2A') {
                srcHtml = <ElTag style="color: #f00;font-size:14px;">{status}</ElTag>
            }
            return srcHtml
        },
    },
    {
        label: '单据类型', prop: '_order_type',
        render(props) {
            const { row } = props
            let order_type = row._order_type
            let srcHtml = null
            if (row.order_type == 'X3QQN8P5M') {
                srcHtml = <ElTag style="color: #0099ff;font-size:14px;">{order_type}</ElTag>
            }
            if (row.order_type == 'X3QQN8P5N') {
                srcHtml = <ElTag style="color: #FDD143;font-size:14px;">{order_type}</ElTag>
            }
            if (row.order_type == 'X3QQN8P5O') {
                srcHtml = <ElTag style="color: #3CC1D9;font-size:14px;">{order_type}</ElTag>
            }
            if (row.order_type == 'X3QQN8P5P') {
                srcHtml = <ElTag style="color: #FE9023;font-size:14px;">{order_type}</ElTag>
            }
            return srcHtml
        },
    },
    { label: '服务电话', prop: 'customer_phone' },
    { label: '服务地址', prop: 'address' },
    {
        label: '服务技师', prop: '_serve_user_id',
        render(props) {
            const { row } = props
            let orga = row._serve_orga_id
            let user = row._serve_user_id
            return <span style="font-size:14px;">{orga} {user}</span>
        },
    },
    {
        label: '操作',
        render(props) {
            const { row } = props
            return <ElButton link type="primary" onClick={() => visitDetailRef.value.openDialog(row)} >查 看</ElButton>
        },
    }
])
4.render.ts 模块代码
export default {
    props: {
      row: Object,
      render: Function,
      index: Number,
      column: {
        type: Object,
        default: null,
      },
    },
    setup: function (props: any, context: any) {
      return () =>
        props.render({
          ...props,
          ...context.attrs,
        })
    },
  }
 

三.模块详细说明

1.为什么不使用v-model双向绑定进行分页的数据传输

在封装的过程中发现既然封装了为什么不干脆一封到低呢?我们将原有的分页一起丢进table组件中我们在进行请求的时候仅仅只需要获取到当前的页数,条数就可以没有必要将其他多余的参数进行变量声明,我们在切换分页的时候只需要拿参数就可以了,在初始化请求中我们将我们的默认参数进行赋值  这样就不会出现拿到的参数是空的状态

2.如何将JSX与TS在一起使用

我在使用的过程中发现如果<script lang="tsx" setup>  lang="ts"时我们的代码行就会提示报错  所以我新增加了@vitejs/plugin-vue-jsx 这个依赖 安装命令如下

npm install --save-dev @vitejs/plugin-vue-jsx

接着就是配置模块了

在vite.config.ts中导入以下代码

import vueJsx from '@vitejs/plugin-vue-jsx'

然后在plugins中将vueJsx()写入这样我们的JSX与TS配置就好了,还得注意的一点就是  使用模块的script标签中 lang的值为tsx

四.总结

组件的封装不是说为了封装而封装、我们在封装的过程中还是要以实际的项目需求为基础、封装的过程中需要注意思考为什么要这样或者那样封装。后续如果出现新的更改我还是会一点点改过来。

最后欢迎各位路过大佬的指正和批评,你们的批评与指正将会是我最大的动力!!!

  • 45
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,我们来一步步实现基于Vue3、TypeScriptElement Plus的表格分页查询配置。 1. 首先我们需要安装Vue3、Vue Router、Element Plus和Axios等依赖: ```bash npm install vue@next vue-router@4.0.0-beta.8 element-plus@next axios ``` 2. 在`main.ts`中引入VueVueRouter、Element Plus和Axios: ```typescript import { createApp } from 'vue' import App from './App.vue' import router from './router' import ElementPlus from 'element-plus' import 'element-plus/lib/theme-chalk/index.css' import axios from 'axios' const app = createApp(App) app.use(router) app.use(ElementPlus) app.config.globalProperties.$axios = axios app.mount('#app') ``` 3. 在`src/components`目录下创建一个`Table.vue`组件,用来展示表格数据: ```html <template> <el-table :data="tableData"> <el-table-column v-for="column in columns" :key="column.prop" :prop="column.prop" :label="column.label"></el-table-column> </el-table> </template> <script lang="ts"> import { defineComponent } from 'vue' export default defineComponent({ props: { columns: { type: Array, default: () => [] }, data: { type: Array, default: () => [] } }, computed: { tableData() { return this.data.map(item => { const row = {} this.columns.forEach(column => { row[column.prop] = item[column.prop] }) return row }) } } }) </script> ``` 4. 在`src/components`目录下创建一个`Pagination.vue`组件,用来展示分页控件: ```html <template> <el-pagination :current-page="currentPage" :page-size="pageSize" :total="total" @current-change="handleCurrentChange" @size-change="handleSizeChange" ></el-pagination> </template> <script lang="ts"> import { defineComponent } from 'vue' export default defineComponent({ props: { currentPage: { type: Number, default: 1 }, pageSize: { type: Number, default: 10 }, total: { type: Number, default: 0 } }, methods: { handleCurrentChange(currentPage: number) { this.$emit('update:currentPage', currentPage) }, handleSizeChange(pageSize: number) { this.$emit('update:pageSize', pageSize) } } }) </script> ``` 5. 在`src/views`目录下创建一个`TableDemo.vue`视图组件,用来展示表格和分页控件: ```html <template> <div> <el-form :inline="true" :model="query" class="table-demo__form"> <el-form-item v-for="field in fields" :key="field.prop" :label="field.label"> <el-input v-model="query[field.prop]" placeholder="请输入"></el-input> </el-form-item> <el-button type="primary" icon="el-icon-search" @click="handleSearch">查询</el-button> </el-form> <Table :columns="columns" :data="data"></Table> <Pagination :currentPage.sync="currentPage" :pageSize.sync="pageSize" :total="total"></Pagination> </div> </template> <script lang="ts"> import { defineComponent, ref } from 'vue' import axios from 'axios' import Table from '@/components/Table.vue' import Pagination from '@/components/Pagination.vue' export default defineComponent({ components: { Table, Pagination }, setup() { const fields = [ { prop: 'name', label: '姓名' }, { prop: 'age', label: '年龄' }, { prop: 'gender', label: '性别' } ] const columns = [ { prop: 'name', label: '姓名' }, { prop: 'age', label: '年龄' }, { prop: 'gender', label: '性别' } ] const currentPage = ref(1) const pageSize = ref(10) const total = ref(0) const data = ref([]) const query = ref({ name: '', age: '', gender: '' }) const fetchData = async () => { const res = await axios.get('/api/users', { params: { ...query.value, page: currentPage.value, size: pageSize.value } }) data.value = res.data.items total.value = res.data.total } const handleSearch = () => { currentPage.value = 1 fetchData() } fetchData() return { fields, columns, currentPage, pageSize, total, data, query, handleSearch } } }) </script> <style> .table-demo__form { margin-bottom: 20px; } </style> ``` 6. 在`src/router/index.ts`中配置路由: ```typescript import { createRouter, createWebHashHistory } from 'vue-router' import TableDemo from '@/views/TableDemo.vue' const routes = [ { path: '/', name: 'TableDemo', component: TableDemo } ] const router = createRouter({ history: createWebHashHistory(), routes }) export default router ``` 7. 在`public`目录下创建一个`api`目录,用来模拟后端接口: ```json // api/users.json { "total": 100, "items": [ { "name": "张三", "age": 18, "gender": "男" }, { "name": "李四", "age": 20, "gender": "女" }, { "name": "王五", "age": 22, "gender": "男" }, { "name": "赵六", "age": 24, "gender": "女" }, { "name": "钱七", "age": 26, "gender": "男" } ] } ``` 8. 在`package.json`中添加一个`dev`命令,用来启动开发服务器: ```json { "scripts": { "dev": "vite" } } ``` 9. 启动开发服务器并访问`http://localhost:3000/`,就可以看到我们的表格和分页控件了。 以上就是基于Vue3、TypeScriptElement Plus的表格分页查询配置的实现步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值