通用表格组件改造记录

通用表格组件

场景说明

参照 ruoyi-vue 完成权限管理系统后台时,在大部分页面上都需要使用到表格。ruoyi-vue 做法是每个表格都单独完成,观察每个表格样式很接近,遂想封装一个通用表格组件来取代之前的代码。

以下是 ruoyi-vue 的页面:

在这里插入图片描述

通用表格组件

极其糟糕的思路一:将通用表格组件仅抽取出一个 vue 文件,传入表格列表数据通过 v-for 循环生成 el-table-column,针对不同的列如状态,操作列需要实现不一样的样式和功能,采用 v-if 来判断。当多复用几个表格场景后就发现此种方式纯纯屎山,复用场景一多,v-if,v-else-if 看着都头皮发麻,在通用组件中还参杂业务代码,各种坑是一个不差。以下是屎山代码,谨以为鉴:

<template>
    <div>
        <el-row :gutter="10" class="mb8">
            <el-col :span="1.5">
                <el-button type="primary" plain @click="handleAdd">
                    <icon icon="svg-icon:add" />
                    {{ $t('common.add') }}
                </el-button>
            </el-col>
            <el-col :span="1.5">
                <el-button type="warning" plain @click="handleExport">
                    <icon icon="svg-icon:export" />
                    {{ $t('common.export') }}
                </el-button>
            </el-col>
            <el-col :span="1.5">
                <el-button type="warning" plain @click="handleExport('template')">
                    <icon icon="svg-icon:export" />
                    {{ $t('common.importTemplate') }}
                </el-button>
            </el-col>

            <el-col :span="1.5">
                <el-upload v-model:file-list="fileList" class="upload-demo" method="post" :on-success="handleUploadSuccess"
                    :on-error="handleUploadError" :show-file-list="false" :action="uploadRequestConfig.uploadUrl"
                    :headers="uploadRequestConfig.headers">
                    <el-button type="success" plain>
                        <icon icon="svg-icon:import" />
                        {{ $t('common.import') }}
                    </el-button>
                </el-upload>
            </el-col>
        </el-row>
        <el-table v-loading="props.isLoading" :data="props.tableList">
            <el-table-column type="selection" width="55" align="center" />
            <template v-for="rows in props.tableHeaderConfig" :key="rows.label">

                <el-table-column :label="rows.label" align="center" :width="rows.width" v-if="rows.label == $t('common.status')">
                    <template #default="scope">
                        <el-switch active-value="1" inactive-value="0" v-model="scope.row.status"
                            @change="handleEdit(scope.row, true)"></el-switch>
                    </template>
                </el-table-column>
                <el-table-column :label="rows.label" align="center" :width="rows.width" v-else-if="rows.label === $t('permission.functionList')">
                    <template #default="scope">
                        <span>{{ getFunctionListString(scope.row) }}</span>
                    </template>
                </el-table-column>
                <el-table-column :label="rows.label" align="center" prop="createTime" :width="rows.width"
                    v-else-if="rows.label === $t('common.createTime')">
                    <template #default="scope">
                        <span>{{ dataFormat(scope.row.createTime, "YYYY/MM/DD HH:mm:ss") }}</span>
                    </template>
                </el-table-column>
                <el-table-column :label="rows.label" :prop="rows.prop" :width="rows.width" v-else />
            </template>
            <el-table-column :label="$t('common.operation')" align="center" class-name="small-padding fixed-width" fixed="right" min-width="120">
                <template #default="scope">
                    <el-button size="small" link type="primary" @click="handleEdit(scope.row)">
                        <icon icon="svg-icon:edit" />{{ $t('common.edit') }}
                    </el-button>
                    <el-button size="small" link type="primary">
                        <icon icon="svg-icon:delete" />{{ $t('common.delete') }}
                    </el-button>
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import type { UploadUserFile, FormInstance } from 'element-plus'
import { ElMessage } from 'element-plus';

import { dataFormat } from "@/utils/index";
import Icon from "@/components/Icon.vue";
import { FunctionList, RoleList } from "@/api/types";
import { TableOperation } from "@/components/CommonTable";
import { $t } from "@/utils/i18n";

const props = defineProps({
    isLoading: {
        type: Boolean,
        default: false
    },
    tableList: {
        type: Array,
        default: [],
    },
    tableHeaderConfig: {
        default: () => ([{
            label: '',
            prop: '',
            width: 120,
        }])
    },
    // 提供给upload组件的请求配置
    uploadRequestConfig: {
        type: Object,
        default: () => ({
            uploadUrl: '',
            headers: {
                Authorization: ''
            }
        })
    }
})
const fileList = ref<UploadUserFile[]>();
const emit = defineEmits(["handleEvent"])
const handleAdd = () => {
    emit("handleEvent", { mode: TableOperation.Add })
}
const handleExport = (exportType: 'template' | undefined) => {
    emit("handleEvent", {
        mode: TableOperation.Export,
        option: {
            exportType
        }
    })
}
const getFunctionListString = (role: RoleList) => {
    if (!!role.functionList) {
        return role.functionList.reduce((prev: string, next: FunctionList, index: number) => index == 0 ? prev + next.functionKey : prev + ',' + next.functionKey, "")
    } else {
        return ''
    }
}
const handleUploadSuccess = (response: any) => {
    if (response.code === 200) {
        ElMessage({
            type: 'success',
            message: response.msg
        })
    } else {
        ElMessage({
            type: 'error',
            message: response.msg
        })
    }
}
const handleUploadError = (error: Error) => {
    ElMessage({
        type: 'error',
        message: $t('common.operationFail')
    })
}

const handleEdit = (row: any, isEditStatus = false) => {
    emit("handleEvent", {
        mode: TableOperation.Edit,
        option: {
            rowData: row,
            isEditStatus
        }
    })
}
</script>

差强人意的思路二:将组件拆分成两个组件,一是按钮栏为 CoTableOperation.vue,一是表格CoTable.vue,拆分的组件如下:

在这里插入图片描述

CoTableOperation.vue 的思路如下:

按钮栏功能主要包括最大交集的功能是新增,修改,删除,导入,导出,每个页面的颜色和样式又是相同的,通过传入组件的功能数组列表自动生成按钮,如果需要自定义的按钮则通过插槽导入。代码部分如下:

export type TableOperation = 'Add' | 'Edit' | 'Delete' | 'Export' | 'Import' ;
<script lang="ts">
import { h, defineComponent } from "vue";
import { ElRow, ElCol, ElButton } from 'element-plus'

import { TableOperation } from "./table";
import Icon from "@/components/Icon.vue";
import { $t } from "@/utils/i18n";

export default defineComponent({
    props: {
        tableOperation: {
            type: Array as () => TableOperation[],
            default: () => []
        }
    },
    emits: ["tableOperationHandler"],
    setup(props, ctx) {
        const { slots } = ctx;
        const tableOperationMap = {
            Add: {
                icon: "svg-icon:add",
                label: "common.add",
                type: "primary",
                plain: true,
                onClick: () => {
                    ctx.emit("tableOperationHandler", "Add");
                }
            },
            Edit: {
                icon: "svg-icon:edit",
                label: "common.edit",
                type: "success",
                plain: true,
                onClick: () => {
                    ctx.emit("tableOperationHandler", "Edit");
                }
            },
            Delete: {
                icon: "svg-icon:delete",
                label: "common.delete",
                type: "danger",
                plain: true,
                onClick: () => {
                    ctx.emit("tableOperationHandler", "Delete");
                }
            },
            Export: {
                icon: "svg-icon:export",
                label: "common.export",
                type: "info",
                plain: true,
                onClick: () => {
                    ctx.emit("tableOperationHandler", "Export");
                }
            },
            Import: {
                icon: "svg-icon:import",
                label: "common.import",
                type: "warning",
                plain: true,
                onClick: () => {
                    ctx.emit("tableOperationHandler", "Import");
                }
            }
        }
        const children = props.tableOperation.map(item => {
            const { icon, label, type, plain, onClick } = tableOperationMap[item];
            return h(ElCol, { span: 1.5 }, [
                h(
                    ElButton,
                    {
                        type,
                        plain,
                        onClick
                    },
                    () => {
                        return [h(Icon, { icon }), $t(label)]
                    }
                )
            ])
        })
        return () => {
            return h(ElRow, { gutter: 10 }, [children, h(ElCol, { span: 1.5 }, slots.default && slots.default())])
        };
    },
})
</script>

代码的思想是通过传入组件功能列表属性 tableOperation 匹配 tableOperationMap 映射表,通过 h 函数生成组件。每个按钮的事件都是使用 tableOperationHandler 来传递,触发的按钮功能作为参数进行传递。

CoTable.vue 的思路如下:

表格中不需要自定义的内容通过 tableHeaderConfig 列表,使用 v-for 循环生成 el-table-column。需要自定义的内容通过 customizeTableHeaderConfig 列表,使用 v-for 循环生成 slot 插槽,插槽 name 是表头的 prop 属性。

<script setup lang="ts">
import { PropType } from "vue";

import { TableHeaderOption } from "./table";
import Icon from "@/components/Icon.vue";
import { $t } from "@/utils/i18n";

const props = defineProps({
    isLoading: {
        type: Boolean,
        default: false
    },
    tableList: {
        type: Array,
        default: () => ([]),
    },
    tableHeaderConfig: {
        type: Object as PropType<TableHeaderOption[]>,
        default: () => ([{
            label: '',
            prop: '',
            width: 120,
        }])
    },
    customizeTableHeaderConfig: {
        type: Object as PropType<TableHeaderOption[]>,
        default: () => ([])
    }
})
</script>
<template>
    <el-table v-loading="props.isLoading" :data="props.tableList">
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column :label="rows.label" :prop="rows.prop" :width="rows.width"
            v-for="rows in props.tableHeaderConfig" :key="rows.label" />
        <el-table-column v-for="rows in props.customizeTableHeaderConfig" :key="rows.label" :label="rows.label"
            :prop="rows.prop">
            <template #default="scope">
                <slot :name="rows.prop" :scope="scope.row"></slot>
            </template>
        </el-table-column>
        <el-table-column :label="$t('common.operation')" align="center" class-name="small-padding fixed-width"
            fixed="right" min-width="120">
            <template #default="scope">
                <slot :scope="scope.row">
                    <el-button size="small" link type="primary">
                        <icon icon="svg-icon:edit" />{{ $t('common.edit') }}
                    </el-button>
                    <el-button size="small" link type="primary">
                        <icon icon="svg-icon:delete" />{{ $t('common.delete') }}
                    </el-button>
                </slot>
            </template>
        </el-table-column>
    </el-table>
</template>

使用方式如下:

<co-table-operation :tableOperation='["Add", "Delete"]' @tableOperationHandler="tableOperationHandler">
    <div>1111</div>
</co-table-operation>
<co-table :tableList="tableData.i18nList" :isLoading="tableData.isLoading"
    :tableHeaderConfig="tableData.headerConfig"
    :customizeTableHeaderConfig="tableData.customizeTableHeaderConfig">
    <template #createTime="scope">
        <span>{{  dataFormat(scope.createTime, "YYYY/MM/DD HH:mm:ss") }}</span>
    </template>
    <template #updateBy="scope">
        <span>{{ scope.scope.updateBy }}</span>
    </template>
</co-table>

效果图如下:

在这里插入图片描述

  • 22
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 我可以给你一些建议,你可以使用Vue CLI 3来创建一个新的Vue项目,并使用Vue的组件系统来构建你的通用表格组件。还可以使用Element UI、Ant Design Vue等第三方UI库来创建你的表格组件。 ### 回答2: 生成Vue3通用表格组件的过程需要经过以下几个步骤: 1. 创建一个Vue3的组件文件,命名为"Table.vue",并在该文件中导入Vue3的相关依赖。 2. 在组件文件中定义表格组件的基本结构,可以使用HTML的table标签作为表格的容器,然后在标签内部定义表头和表体。 3. 使用Vue3的props属性来接收父组件传递的数据,例如表头数据和表体数据。 4. 使用Vue3的v-for指令遍历表头数据,将每个表头数据渲染成table的th标签,并显示表头的内容。 5. 使用Vue3的v-for指令遍历表体数据,将每个表体数据渲染成table的tr标签,并显示表体的内容。 6. 如果需要支持表格的排序、分页等功能,则需要在表格组件中定义相关的方法和属性,并在表头中添加相应的事件监听。 7. 当表格的数据发生变化时,通过Vue3的watch函数来监听数据的变化,并在回调函数中更新表格的显示。 8. 最后,将表格组件导出,并在父组件中引入,并传递相应的数据和事件。 通过以上步骤,我们就可以生成一个Vue3通用表格组件,使得在其他组件中可以方便地使用该表格组件,并根据需要进行定制和扩展。生成的组件可以实现基本的表格展示功能,并支持排序、分页等常用功能,提高了开发效率和代码复用性。 ### 回答3: 生成Vue 3通用表格组件的过程可以分为以下几个步骤: 1. 定义组件结构:在Vue 3中,可以使用`defineComponent`方法来定义一个组件。通过引入组件库所需的各种依赖项,包括`reactive`函数用于响应式地管理数据状态,以及`setup`函数用于配置组件。 2. 定义表格配置项:在组件中,我们可以定义一些配置项,例如表格的列数、行数、边框样式、数据源等。通过配置项,可以灵活地扩展表格组件的功能。 3. 数据源管理:Vue 3中引入了`reactive`函数,提供了更强大的响应式特性。我们可以使用`reactive`函数将数据源转化为响应式的对象。这样一来,当数据源发生变化时,表格组件会自动进行更新。 4. 组件交互逻辑:在Vue 3中,`setup`函数可以为我们提供组件的交互逻辑。我们可以在`setup`函数中定义一些方法,例如排序、筛选、分页等功能。通过这些方法,可以让表格组件具备更多的交互能力。 5. 渲染表格:Vue 3提供了更强大的模板编译器,可以让我们更灵活地控制视图的渲染。通过使用`v-for`指令和条件渲染等技术,我们可以根据数据源的变化动态渲染表格的内容。 总的来说,生成Vue 3通用表格组件需要定义组件的结构、配置项和交互逻辑,使用`reactive`函数管理数据源,以及通过模板编译器渲染表格的内容。这样,我们就可以生成一个具备通用特性的表格组件,可以在各种场景中灵活使用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值