Vue3.2 + Element 您所在的位置:网站首页 vue3element Vue3.2 + Element

Vue3.2 + Element

#Vue3.2 + Element| 来源: 网络整理| 查看: 265

前言 📖

ProTable 组件目前已是 2.0版本🌈,在 1.0版本 中大家提出的问题与功能优化,目前已经得到优化和解决。

😀 欢迎大家在使用过程中发现任何问题或更好的想法,都可以在下方评论区留言,或者我的开源项目 issues 中提出。如果你觉得还不错,请帮我点个小小的 Star 🧡

一、在线预览 👀 Link:admin.spicyboy.cn 二、Git 仓库地址 (欢迎 Star⭐⭐⭐) Gitee:gitee.com/HalseySpicy… GitHub:github.com/HalseySpicy… 三、ProTable 功能 🚀🚀🚀

ProTable 组件目前使用属性透传进行重构,支持 el-table && el-table-column 所有属性、事件、方法的调用,不会有任何心智负担。

表格内容自适应屏幕宽高,溢出内容表格内部滚动(flex 布局) 表格搜索、重置、分页查询 Hooks 封装 (页面使用不会存在任何搜索、重置、分页查询逻辑) 表格数据操作 Hooks 封装 (单条数据删除、批量删除、重置密码、状态切换等操作) 表格数据多选 Hooks 封装 (支持现跨页勾选数据) 表格数据导入组件、导出 Hooks 封装 表格搜索区域使用 Grid 布局重构,支持自定义响应式配置 表格分页组件封装(Pagination) 表格数据刷新、列显隐、列排序、搜索区域显隐设置 表格数据打印功能(可勾选行数据、隐藏列打印) 表格配置支持多级 prop(示例 ==> prop: user.detail.name) 单元格内容格式化、tag 标签显示(有字典 enum 会根据字典 enum 自动格式化) 支持多级表头、表头内容自定义渲染(支持作用域插槽、tsx 语法、h 函数) 支持单元格内容自定义渲染(支持作用域插槽、tsx 语法、h 函数) 配合 TreeFilter、SelectFilter 组件使用更佳(项目中有使用示例) 四、ProTable 功能需求分析 📑 首先我们来看效果图(总共可以分为五个模块):

image.png

1、表格搜索区域 2、表格数据操作按钮区域 3、表格功能按钮区域 4、表格主体内容展示区域 5、表格分页区域 1、表格搜索区域需求分析:

可以看到搜索区域的字段都是存在于表格当中的,并且每个页面的搜索、重置方法都是一样的逻辑,只是不同的查询参数而已。我们完全可以在传表格配置项 columns 时,直接指定某个 column 的 search 配置,就能把该项变为搜索项,然后使用 el 字段可以指定搜索框的类型,最后把表格的搜索方法都封装成 Hooks 钩子函数。页面上完全就不会存在任何搜索、重置逻辑了。

在 1.0 版本中使用 v-if 判断太麻烦,为了更方便用户传递参数,搜索组件在 2.0 版本中通过 component :is 动态组件 && v-bind 属性透传实现,将用户传递的参数全部透传到组件上,所以大家可以直接根据 element 官方文档在 props 中传递参数了。以下代码还结合了自己逻辑上的一些处理:

{{ data[fieldNames.label] }} import { computed, inject, ref } from "vue"; import { handleProp } from "@/utils"; import { ColumnProps } from "@/components/ProTable/interface"; interface SearchFormItem { column: ColumnProps; searchParam: { [key: string]: any }; } const props = defineProps(); // 判断 fieldNames 设置 label && value && children 的 key 值 const fieldNames = computed(() => { return { label: props.column.fieldNames?.label ?? "label", value: props.column.fieldNames?.value ?? "value", children: props.column.fieldNames?.children ?? "children" }; }); // 接收 enumMap (el 为 select-v2 需单独处理 enumData) const enumMap = inject("enumMap", ref(new Map())); const columnEnum = computed(() => { let enumData = enumMap.value.get(props.column.prop); if (!enumData) return []; if (props.column.search?.el === "select-v2" && props.column.fieldNames) { enumData = enumData.map((item: { [key: string]: any }) => { return { ...item, label: item[fieldNames.value.label], value: item[fieldNames.value.value] }; }); } return enumData; }); // 处理透传的 searchProps (el 为 tree-select、cascader 的时候需要给下默认 label && value && children) const handleSearchProps = computed(() => { const label = fieldNames.value.label; const value = fieldNames.value.value; const children = fieldNames.value.children; const searchEl = props.column.search?.el; let searchProps = props.column.search?.props ?? {}; if (searchEl === "tree-select") { searchProps = { ...searchProps, props: { ...searchProps.props, label, children }, nodeKey: value }; } if (searchEl === "cascader") { searchProps = { ...searchProps, props: { ...searchProps.props, label, value, children } }; } return searchProps; }); // 处理默认 placeholder const placeholder = computed(() => { const search = props.column.search; if (["datetimerange", "daterange", "monthrange"].includes(search?.props?.type) || search?.props?.isRange) { return { rangeSeparator: "至", startPlaceholder: "开始时间", endPlaceholder: "结束时间" }; } const placeholder = search?.props?.placeholder ?? (search?.el.includes("input") ? "请输入" : "请选择"); return { placeholder }; }); // 是否有清除按钮 (当搜索项有默认值时,清除按钮不显示) const clearable = computed(() => { const search = props.column.search; return search?.props?.clearable ?? (search?.defaultValue == null || search?.defaultValue == undefined); }); 复制代码

表格搜索组件在 2.0 版本中还支持了响应式配置,使用 Grid 方法进行整体重构 😋。

动画.gif

2、表格数据操作按钮区域需求分析:

表格数据操作按钮基本上每个页面都会不一样,所以我们直接使用 作用域插槽 来完成每个页面的数据操作按钮区域,作用域插槽 可以将表格多选数据信息从 ProTable 的 Hooks 多选钩子函数中传到页面上使用。

scope 数据中包含:selectedList(当前选择的数据)、selectedListIds(当前选择的数据id)、isSelected(当前是否选中的数据)

新增用户 批量添加用户 导出用户数据 批量删除用户 复制代码 3、表格功能按钮区域分析:

这块区域没什么特殊功能,只有四个按钮,其功能分别为:表格数据刷新(一直会携带当前查询和分页条件)、表格数据打印、表格列设置(列显隐、列排序)、表格搜索区域显隐(方便展示更多的数据信息)。 可通过 toolButton 属性控制这块区域的显隐。

表格打印功能基于 PrintJs 实现,因 PrintJs 不支持多级表头打印,所以当页面存在多级表头时,只会打印最后一级表头。表格打印功能可根据显示的列和勾选的数据动态打印,默认打印当前显示的所有数据。

image.png

4、表格主体内容展示区域分析:

🍉 该区域是最重要的数据展示区域,对于使用最多的功能就是表头和单元格内容可以自定义渲染,在第 1.0 版本中,自定义表头只支持传入renderHeader方法,自定义单元格内容只支持slot插槽。

💥 目前 2.0 版本中,表头支持headerRender方法(避免与 el-table-column 上的属性重名导致报错)、作用域插槽(column.prop + 'Header')两种方式自定义,单元格内容支持render方法和作用域插槽(column 上的 prop 属性)两种方式自定义。

使用作用域插槽: {{ scope.row.username }} {{ scope.row.label }} 复制代码 使用 tsx 语法: const columns: ColumnProps[] = [ { prop: "username", label: "用户姓名", // 使用 headerRender 自定义表头 headerRender: (row) => { return ( { ElMessage.success("我是通过 tsx 语法渲染的表头"); }} > {row.label} ); } }, { prop: "status", label: "用户状态", // 使用 render 自定义表格内容 render: (scope: { row }) => { return ( changeStatus(scope.row)} /> ) ); } }, ]; 复制代码

💢💢💢 最强大的功能:如果你想使用 el-table 的任何属性、事件,目前通过属性透传都能支持。

如果你还不了解属性透传,请阅读 vue 官方文档:cn.vuejs.org/guide/compo…

ProTable 组件上的绑定的所有属性和事件都会通过 v-bind="$attrs" 透传到 el-table 上。 ProTable 组件内部暴露了 el-table DOM,可通过 proTable.value.element.方法名 调用其方法。 import { ref } from "vue"; import { ElTable } from "element-plus"; const tableRef = ref(); defineExpose({ element: tableRef }); 复制代码 5、表格分页区域分析:

分页区域也没有什么特殊的功能,该支持的都支持了🤣(页面上使用 ProTable 组件完全不存在分页逻辑)

interface Pageable { pageNum: number; pageSize: number; total: number; } interface PaginationProps { pageable: Pageable; handleSizeChange: (size: number) => void; handleCurrentChange: (currentPage: number) => void; } defineProps(); 复制代码 五、ProTable 文档 📚 1、ProTable 属性(ProTableProps):

使用 v-bind="$attrs" 通过属性透传将 ProTable 组件属性全部透传到 el-table 上,所以我们支持 el-table 的所有 Props 属性。在此基础上,还扩展了以下 Props:

属性名类型是否必传默认值属性描述columnsColumnProps✅—ProTable 组件会根据此字段渲染搜索表单与表格列,详情见 ColumnPropsrequestApiFunction✅—获取表格数据的请求 APIrequestAutoBoolean❌true表格初始化是否自动执行请求 APIrequestErrorFunction❌—表格 API 请求错误监听dataCallbackFunction❌—后台返回数据的回调函数,可对后台返回数据进行处理titleString❌—表格标题,目前只在打印的时候用到paginationBoolean❌true是否显示分页组件:pagination 为 false 后台返回数据应该没有分页信息 和 list 字段,data 就是 list 数据initParamObject❌{}表格请求的初始化参数,该值变化会自动请求表格数据toolButtonBoolean❌true是否显示表格功能按钮rowKeyString❌'id'当表格数据多选时,所指定的 idsearchColObject❌{ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }表格搜索项每列占比配置 2、Column 配置(ColumnProps):

使用 v-bind="column" 通过属性透传将每一项 column 属性全部透传到 el-table-column 上,所以我们支持 el-table-column 的所有 Props 属性。在此基础上,还扩展了以下 Props:

属性名类型是否必传默认值属性描述tagBoolean❌false当前单元格值是否为标签展示,可通过 enum 数据中 tagType 字段指定 tag 类型isShowBoolean❌true当前列是否显示在表格内(只对 prop 列生效)searchSearchProps❌—搜索项配置,详情见 SearchPropsenumObject | Function❌—字典,可格式化单元格内容,还可以作为搜索框的下拉选项(字典可以为 API 请求函数,内部会自动执行)isFilterEnumBoolean❌true当前单元格值是否根据 enum 格式化(例如 enum 只作为搜索项数据,不参与内容格式化)fieldNamesObject❌—指定 label && value && children 的 key 值headerRenderFunction❌—自定义表头内容渲染(tsx 语法、h 语法)renderFunction❌—自定义单元格内容渲染(tsx 语法、h 语法)_childrenColumnProps❌—多级表头 3、搜索项 配置(SearchProps):

使用 v-bind="column.search.props“ 通过属性透传将 search.props 属性全部透传到每一项搜索组件上,所以我们支持 input、select、tree-select、date-packer、time-picker、time-select、switch 大部分属性,并在其基础上还扩展了以下 Props:

属性名类型是否必传默认值属性描述elString✅—当前项搜索框的类型,支持:input、input-number、select、select-v2、tree-select、cascader、date-packer、time-picker、time-select、switch、sliderpropsObject❌—根据 element plus 官方文档来传递,该属性所有值会透传到组件defaultValueAny❌—搜索项默认值keyString❌—当搜索项 key 不为 prop 属性时,可通过 key 指定orderNumber❌—搜索项排序(从小到大)spanNumber❌1搜索项所占用的列数,默认为 1 列offsetNumber❌—搜索字段左侧偏移列数 4、ProTable 事件:

根据 ElementPlus Table 文档在 ProTable 组件上绑定事件即可,组件会通过 $attrs 透传给 el-table。

el-table 事件文档链接

5、ProTable 方法:

ProTable 组件暴露了 el-table 实例和一些组件内部的参数和方法:

el-table 方法文档链接

方法名描述elementel-table 实例,可以通过element.方法名来调用 el-table 的所有方法tableData当前页面所展示的数据searchParam所有的搜索参数,不包含分页pageable当前表格的分页数据getTableList获取、刷新表格数据的方法(携带所有参数)reset重置表格查询参数,相当于点击重置搜索按钮clearSelection清空表格所选择的数据,除此方法之外还可使用 element.clearSelection()enumMap当前表格使用的所有字典数据(Map 数据结构)isSelected表格是否选中数据selectedList表格选中的数据列表selectedListIds表格选中的数据列表的 id 6、ProTable 插槽: 插槽名描述—默认插槽,支持直接在 ProTable 中写 el-table-column 标签tableHeader自定义表格头部左侧区域的插槽,一般情况该区域放操作按钮toolButton自定义表格头部左右侧侧功能区域的插槽append插入至表格最后一行之后的内容, 如果需要对表格的内容进行无限滚动操作,可能需要用到这个 slot。 若表格有合计行,该 slot 会位于合计行之上。empty当表格数据为空时自定义的内容pagination分页组件插槽column.prop单元格的作用域插槽column.prop + "Header"表头的作用域插槽 六、代码实现 & 基础使用 💪(代码较多,详情请去项目里查看) 使用一段话总结下我的想法:📚📚

🤔 前提:首先我们在封装 ProTable 组件的时候,在不影响 el-table 原有的属性、事件、方法的前提下,然后在其基础上做二次封装,否则做得再好,也不太完美。

🧐 思路:把一个表格页面所有重复的功能 (表格多选、查询、重置、刷新、分页、数据操作二次确认、文件下载、文件上传) 都封装成 Hooks 函数钩子或组件,然后在 ProTable 组件中使用这些函数钩子或组件。在页面中使用的时,只需传给 ProTable 当前表格数据的请求 API、表格配置项 columns 就行了,数据传输都使用 作用域插槽 或 tsx 语法从 ProTable 传递给父组件就能在页面上获取到了。

1、常用 Hooks 函数 useTable: import { Table } from "./interface"; import { reactive, computed, toRefs } from "vue"; /** * @description table 页面操作方法封装 * @param {Function} api 获取表格数据 api 方法 (必传) * @param {Object} initParam 获取数据初始化参数 (非必传,默认为{}) * @param {Boolean} isPageable 是否有分页 (非必传,默认为true) * @param {Function} dataCallBack 对后台返回的数据进行处理的方法 (非必传) * */ export const useTable = ( api: (params: any) => Promise | any, initParam: object = {}, isPageable: boolean = true, dataCallBack?: (data: any) => any, requestError?: (error: any) => void ) => { const state = reactive({ // 表格数据 tableData: [], // 分页数据 pageable: { // 当前页数 pageNum: 1, // 每页显示条数 pageSize: 10, // 总条数 total: 0 }, // 查询参数(只包括查询) searchParam: {}, // 初始化默认的查询参数 searchInitParam: {}, // 总参数(包含分页和查询参数) totalParam: {} }); /** * @description 分页查询参数(只包括分页和表格字段排序,其他排序方式可自行配置) * */ const pageParam = computed({ get: () => { return { pageNum: state.pageable.pageNum, pageSize: state.pageable.pageSize }; }, set: (newVal: any) => { console.log("我是分页更新之后的值", newVal); } }); /** * @description 获取表格数据 * @return void * */ const getTableList = async () => { try { // 先把初始化参数和分页参数放到总参数里面 Object.assign(state.totalParam, initParam, isPageable ? pageParam.value : {}); let { data } = await api({ ...state.searchInitParam, ...state.totalParam }); dataCallBack && (data = dataCallBack(data)); state.tableData = isPageable ? data.list : data; // 解构后台返回的分页数据 (如果有分页更新分页信息) const { pageNum, pageSize, total } = data; isPageable && updatePageable({ pageNum, pageSize, total }); } catch (error) { requestError && requestError(error); } }; /** * @description 更新查询参数 * @return void * */ const updatedTotalParam = () => { state.totalParam = {}; // 处理查询参数,可以给查询参数加自定义前缀操作 let nowSearchParam: { [key: string]: any } = {}; // 防止手动清空输入框携带参数(这里可以自定义查询参数前缀) for (let key in state.searchParam) { // * 某些情况下参数为 false/0 也应该携带参数 if (state.searchParam[key] || state.searchParam[key] === false || state.searchParam[key] === 0) { nowSearchParam[key] = state.searchParam[key]; } } Object.assign(state.totalParam, nowSearchParam, isPageable ? pageParam.value : {}); }; /** * @description 更新分页信息 * @param {Object} resPageable 后台返回的分页数据 * @return void * */ const updatePageable = (resPageable: Table.Pageable) => { Object.assign(state.pageable, resPageable); }; /** * @description 表格数据查询 * @return void * */ const search = () => { state.pageable.pageNum = 1; updatedTotalParam(); getTableList(); }; /** * @description 表格数据重置 * @return void * */ const reset = () => { state.pageable.pageNum = 1; state.searchParam = {}; // 重置搜索表单的时,如果有默认搜索参数,则重置默认的搜索参数 Object.keys(state.searchInitParam).forEach(key => { state.searchParam[key] = state.searchInitParam[key]; }); updatedTotalParam(); getTableList(); }; /** * @description 每页条数改变 * @param {Number} val 当前条数 * @return void * */ const handleSizeChange = (val: number) => { state.pageable.pageNum = 1; state.pageable.pageSize = val; getTableList(); }; /** * @description 当前页改变 * @param {Number} val 当前页 * @return void * */ const handleCurrentChange = (val: number) => { state.pageable.pageNum = val; getTableList(); }; return { ...toRefs(state), getTableList, search, reset, handleSizeChange, handleCurrentChange, updatedTotalParam }; }; 复制代码 useSelection: import { ref, computed } from "vue"; /** * @description 表格多选数据操作 * @param {String} rowKey 当表格可以多选时,所指定的 id * */ export const useSelection = (rowKey: string = "id") => { const isSelected = ref(false); const selectedList = ref([]); // 当前选中的所有 ids 数组 const selectedListIds = computed((): string[] => { let ids: string[] = []; selectedList.value.forEach(item => ids.push(item[rowKey])); return ids; }); /** * @description 多选操作 * @param {Array} rowArr 当前选择的所有数据 * @return void */ const selectionChange = (rowArr: { [key: string]: any }[]) => { rowArr.length ? (isSelected.value = true) : (isSelected.value = false); selectedList.value = rowArr; }; return { isSelected, selectedList, selectedListIds, selectionChange }; }; 复制代码 useDownload: import { ElNotification } from "element-plus"; /** * @description 接收数据流生成 blob,创建链接,下载文件 * @param {Function} api 导出表格的api方法 (必传) * @param {String} tempName 导出的文件名 (必传) * @param {Object} params 导出的参数 (默认{}) * @param {Boolean} isNotify 是否有导出消息提示 (默认为 true) * @param {String} fileType 导出的文件格式 (默认为.xlsx) * */ export const useDownload = async ( api: (param: any) => Promise | any, tempName: string, params: any = {}, isNotify: boolean = true, fileType: string = ".xlsx" ) => { if (isNotify) { ElNotification({ title: "温馨提示", message: "如果数据庞大会导致下载缓慢哦,请您耐心等待!", type: "info", duration: 3000 }); } try { const res = await api(params); const blob = new Blob([res]); // 兼容 edge 不支持 createObjectURL 方法 if ("msSaveOrOpenBlob" in navigator) return window.navigator.msSaveOrOpenBlob(blob, tempName + fileType); const blobUrl = window.URL.createObjectURL(blob); const exportFile = document.createElement("a"); exportFile.style.display = "none"; exportFile.download = `${tempName}${fileType}`; exportFile.href = blobUrl; document.body.appendChild(exportFile); exportFile.click(); // 去除下载对 url 的影响 document.body.removeChild(exportFile); window.URL.revokeObjectURL(blobUrl); } catch (error) { console.log(error); } }; 复制代码 useHandleData: import { ElMessageBox, ElMessage } from "element-plus"; import { HandleData } from "./interface"; /** * @description 操作单条数据信息 (二次确认【删除、禁用、启用、重置密码】) * @param {Function} api 操作数据接口的api方法 (必传) * @param {Object} params 携带的操作数据参数 {id,params} (必传) * @param {String} message 提示信息 (必传) * @param {String} confirmType icon类型 (不必传,默认为 warning) * @returns {Promise} */ export const useHandleData = ( api: (params: any) => Promise | any, params: any = {}, message: string, confirmType: HandleData.MessageType = "warning" ) => { return new Promise((resolve, reject) => { ElMessageBox.confirm(`是否${message}?`, "温馨提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: confirmType, draggable: true }).then(async () => { const res = await api(params); if (!res) return reject(false); ElMessage({ type: "success", message: `${message}成功!` }); resolve(true); }); }); }; 复制代码 2、Protable 组件: ProTable: 暂无数据 import { ref, watch, computed, provide, onMounted } from "vue"; import { useTable } from "@/hooks/useTable"; import { useSelection } from "@/hooks/useSelection"; import { BreakPoint } from "@/components/Grid/interface"; import { ColumnProps } from "@/components/ProTable/interface"; import { ElTable, TableProps } from "element-plus"; import { Refresh, Printer, Operation, Search } from "@element-plus/icons-vue"; import { filterEnum, formatValue, handleProp, handleRowAccordingToProp } from "@/utils"; import SearchForm from "@/components/SearchForm/index.vue"; import Pagination from "./components/Pagination.vue"; import ColSetting from "./components/ColSetting.vue"; import TableColumn from "./components/TableColumn.vue"; import printJS from "print-js"; interface ProTableProps extends Partial { columns: ColumnProps[]; // 列配置项 requestApi: (params: any) => Promise | any; // 请求表格数据的 api ==> 非必传 requestAuto?: boolean; // 是否自动执行请求 api ==> 非必传(默认为true) requestError?: (params: any) => void; // 表格 api 请求错误监听 ==> 非必传 dataCallback?: (data: any) => any; // 返回数据的回调函数,可以对数据进行处理 ==> 非必传 title?: string; // 表格标题,目前只在打印的时候用到 ==> 非必传 pagination?: boolean; // 是否需要分页组件 ==> 非必传(默认为true) initParam?: any; // 初始化请求参数 ==> 非必传(默认为{}) border?: boolean; // 是否带有纵向边框 ==> 非必传(默认为true) toolButton?: boolean; // 是否显示表格功能按钮 ==> 非必传(默认为true) rowKey?: string; // 行数据的 Key,用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id) searchCol?: number | Record; // 表格搜索项 每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 } } // 接受父组件参数,配置默认值 const props = withDefaults(defineProps(), { requestAuto: true, columns: () => [], pagination: true, initParam: {}, border: true, toolButton: true, rowKey: "id", searchCol: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }) }); // 是否显示搜索模块 const isShowSearch = ref(true); // 表格 DOM 元素 const tableRef = ref(); // 表格多选 Hooks const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey); // 表格操作 Hooks const { tableData, pageable, searchParam, searchInitParam, getTableList, search, reset, handleSizeChange, handleCurrentChange } = useTable(props.requestApi, props.initParam, props.pagination, props.dataCallback, props.requestError); // 清空选中数据列表 const clearSelection = () => tableRef.value!.clearSelection(); // 初始化请求 onMounted(() => props.requestAuto && getTableList()); // 监听页面 initParam 改化,重新获取表格数据 watch(() => props.initParam, getTableList, { deep: true }); // 接收 columns 并设置为响应式 const tableColumns = ref(props.columns); // 定义 enumMap 存储 enum 值(避免异步请求无法格式化单元格内容 || 无法填充搜索下拉选择) const enumMap = ref(new Map()); provide("enumMap", enumMap); const setEnumMap = async (col: ColumnProps) => { if (!col.enum) return; // 如果当前 enum 为后台数据需要请求数据,则调用该请求接口,并存储到 enumMap if (typeof col.enum !== "function") return enumMap.value.set(col.prop!, col.enum!); const { data } = await col.enum(); enumMap.value.set(col.prop!, data); }; // 扁平化 columns const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) => { columns.forEach(async col => { if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children)); flatArr.push(col); // 给每一项 column 添加 isShow && isFilterEnum 默认属性 col.isShow = col.isShow ?? true; col.isFilterEnum = col.isFilterEnum ?? true; // 设置 enumMap setEnumMap(col); }); return flatArr.filter(item => !item._children?.length); }; // flatColumns const flatColumns = ref(); flatColumns.value = flatColumnsFunc(tableColumns.value); // 过滤需要搜索的配置项 const searchColumns = flatColumns.value.filter(item => item.search?.el); // 设置搜索表单排序默认值 && 设置搜索表单项的默认值 searchColumns.forEach((column, index) => { column.search!.order = column.search!.order ?? index + 2; if (column.search?.defaultValue !== undefined && column.search?.defaultValue !== null) { searchInitParam.value[column.search.key ?? handleProp(column.prop!)] = column.search?.defaultValue; searchParam.value[column.search.key ?? handleProp(column.prop!)] = column.search?.defaultValue; } }); // 排序搜索表单项 searchColumns.sort((a, b) => a.search!.order! - b.search!.order!); // 列设置 ==> 过滤掉不需要设置的列 const colRef = ref(); const colSetting = tableColumns.value!.filter( item => !["selection", "index", "expand"].includes(item.type!) && item.prop !== "operation" && item.isShow ); const openColSetting = () => colRef.value.openColSetting(); // 🙅‍♀️ 不需要打印可以把以下方法删除,打印功能目前存在很多 bug(目前数据处理比较复杂 209-246 行) // 处理打印数据(把后台返回的值根据 enum 做转换) const printData = computed(() => { const printDataList = JSON.parse(JSON.stringify(selectedList.value.length ? selectedList.value : tableData.value)); // 找出需要转换数据的列(有 enum || 多级 prop && 需要根据 enum 格式化) const needTransformCol = flatColumns.value!.filter( item => (item.enum || (item.prop && item.prop.split(".").length > 1)) && item.isFilterEnum ); needTransformCol.forEach(colItem => { printDataList.forEach((tableItem: { [key: string]: any }) => { tableItem[handleProp(colItem.prop!)] = colItem.prop!.split(".").length > 1 && !colItem.enum ? formatValue(handleRowAccordingToProp(tableItem, colItem.prop!)) : filterEnum(handleRowAccordingToProp(tableItem, colItem.prop!), enumMap.value.get(colItem.prop!), colItem.fieldNames); for (const key in tableItem) { if (tableItem[key] === null) tableItem[key] = formatValue(tableItem[key]); } }); }); return printDataList; }); // 打印表格数据(💥 多级表头数据打印时,只能扁平化成一维数组,printJs 不支持多级表头打印) const handlePrint = () => { const header = `${props.title}`; const gridHeaderStyle = "border: 1px solid #ebeef5;height: 45px;color: #232425;text-align: center;background-color: #fafafa;"; const gridStyle = "border: 1px solid #ebeef5;height: 40px;color: #494b4e;text-align: center"; printJS({ printable: printData.value, header: props.title && header, properties: flatColumns .value!.filter(item => !["selection", "index", "expand"].includes(item.type!) && item.isShow && item.prop !== "operation") .map((item: ColumnProps) => ({ field: handleProp(item.prop!), displayName: item.label })), type: "json", gridHeaderStyle, gridStyle }); }; // 暴露给父组件的参数和方法(外部需要什么,都可以从这里暴露出去) defineExpose({ element: tableRef, tableData, searchParam, pageable, getTableList, reset, clearSelection, enumMap, isSelected, selectedList, selectedListIds }); 复制代码 TableColumn: import { inject, ref, useSlots } from "vue"; import { ColumnProps } from "@/components/ProTable/interface"; import { filterEnum, formatValue, handleProp, handleRowAccordingToProp } from "@/utils"; defineProps(); const slots = useSlots(); const enumMap = inject("enumMap", ref(new Map())); // 渲染表格数据 const renderCellData = (item: ColumnProps, scope: { [key: string]: any }) => { return enumMap.value.get(item.prop) && item.isFilterEnum ? filterEnum(handleRowAccordingToProp(scope.row, item.prop!), enumMap.value.get(item.prop)!, item.fieldNames) : formatValue(handleRowAccordingToProp(scope.row, item.prop!)); }; // 获取 tag 类型 const getTagType = (item: ColumnProps, scope: { [key: string]: any }) => { return filterEnum(handleRowAccordingToProp(scope.row, item.prop!), enumMap.value.get(item.prop), item.fieldNames, "tag"); }; const RenderTableColumn = (item: ColumnProps) => { return ( {item.isShow && ( {{ default: (scope: any) => { if (item._children) return item._children.map(child => RenderTableColumn(child)); if (item.render) return item.render(scope); if (slots[handleProp(item.prop!)]) return slots[handleProp(item.prop!)]!(scope); if (item.tag) return {renderCellData(item, scope)}; return renderCellData(item, scope); }, header: () => { if (item.headerRender) return item.headerRender(item); if (slots[`${handleProp(item.prop!)}Header`]) return slots[`${handleProp(item.prop!)}Header`]!({ row: item }); return item.label; } }} )} ); }; 复制代码 3、页面使用 ProTable 组件: 新增用户 批量添加用户 导出用户数据 To 子集详情页面 批量删除用户 {{ scope.row }} {{ scope.row.label }} {{ scope.row.createTime }} 查看 编辑 重置密码 删除 import { ref, reactive } from "vue"; import { useRouter } from "vue-router"; import { User } from "@/api/interface"; import { ColumnProps } from "@/components/ProTable/interface"; import { useHandleData } from "@/hooks/useHandleData"; import { useDownload } from "@/hooks/useDownload"; import { useAuthButtons } from "@/hooks/useAuthButtons"; import { ElMessage, ElMessageBox } from "element-plus"; import ProTable from "@/components/ProTable/index.vue"; import ImportExcel from "@/components/ImportExcel/index.vue"; import UserDrawer from "@/views/proTable/components/UserDrawer.vue"; import { CirclePlus, Delete, EditPen, Download, Upload, View, Refresh } from "@element-plus/icons-vue"; import { getUserList, deleteUser, editUser, addUser, changeUserStatus, resetUserPassWord, exportUserInfo, BatchAddUser, getUserStatus, getUserGender } from "@/api/modules/user"; const router = useRouter(); // 跳转详情页 const toDetail = () => { router.push(`/proTable/useProTable/detail/${Math.random().toFixed(3)}?params=detail-page`); }; // 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数) const proTable = ref(); // 如果表格需要初始化请求参数,直接定义传给 ProTable(之后每次请求都会自动带上该参数,此参数更改之后也会一直带上,改变此参数会自动刷新表格数据) const initParam = reactive({ type: 1 }); // dataCallback 是对于返回的表格数据做处理,如果你后台返回的数据不是 list && total && pageNum && pageSize 这些字段,那么你可以在这里进行处理成这些字段 // 或者直接去 hooks/useTable.ts 文件中把字段改为你后端对应的就行 const dataCallback = (data: any) => { return { list: data.list, total: data.total, pageNum: data.pageNum, pageSize: data.pageSize }; }; // 如果你想在请求之前对当前请求参数做一些操作,可以自定义如下函数:params 为当前所有的请求参数(包括分页),最后返回请求列表接口 // 默认不做操作就直接在 ProTable 组件上绑定 :requestApi="getUserList" const getTableList = (params: any) => { let newParams = JSON.parse(JSON.stringify(params)); newParams.username && (newParams.username = "custom-" + newParams.username); return getUserList(newParams); }; // 页面按钮权限(按钮权限既可以使用 hooks,也可以直接使用 v-auth 指令,指令适合直接绑定在按钮上,hooks 适合根据按钮权限显示不同的内容) const { BUTTONS } = useAuthButtons(); // 自定义渲染表头(使用tsx语法) const headerRender = (row: ColumnProps) => { return ( { ElMessage.success("我是通过 tsx 语法渲染的表头"); }} > {row.label} ); }; // 表格配置项 const columns: ColumnProps[] = [ { type: "selection", fixed: "left", width: 80 }, { type: "index", label: "#", width: 80 }, { type: "expand", label: "Expand", width: 100 }, { prop: "username", label: "用户姓名", search: { el: "input" }, render: scope => { return ( ElMessage.success("我是通过 tsx 语法渲染的内容")}> {scope.row.username} ); } }, { prop: "gender", label: "性别", // 字典数据 // enum: genderType, // 字典请求不带参数 enum: getUserGender, // 字典请求携带参数 // enum: () => getUserGender({ id: 1 }), search: { el: "select", props: { filterable: true } }, fieldNames: { label: "genderLabel", value: "genderValue" } }, // 多级 prop { prop: "user.detail.age", label: "年龄", search: { el: "input" } }, { prop: "idCard", label: "身份证号", search: { el: "input" } }, { prop: "email", label: "邮箱" }, { prop: "address", label: "居住地址" }, { prop: "status", label: "用户状态", enum: getUserStatus, search: { el: "tree-select", props: { filterable: true } }, fieldNames: { label: "userLabel", value: "userStatus" }, render: scope => { return ( {BUTTONS.value.status ? ( changeStatus(scope.row)} /> ) : ( {scope.row.status ? "启用" : "禁用"} )} ); } }, { prop: "createTime", label: "创建时间", headerRender, width: 180, search: { el: "date-picker", span: 2, props: { type: "datetimerange", valueFormat: "YYYY-MM-DD HH:mm:ss" }, defaultValue: ["2022-11-12 11:35:00", "2022-12-12 11:35:00"] } }, { prop: "operation", label: "操作", fixed: "right", width: 330 } ]; // 删除用户信息 const deleteAccount = async (params: User.ResUserList) => { await useHandleData(deleteUser, { id: [params.id] }, `删除【${params.username}】用户`); proTable.value.getTableList(); }; // 批量删除用户信息 const batchDelete = async (id: string[]) => { await useHandleData(deleteUser, { id }, "删除所选用户信息"); proTable.value.clearSelection(); proTable.value.getTableList(); }; // 重置用户密码 const resetPass = async (params: User.ResUserList) => { await useHandleData(resetUserPassWord, { id: params.id }, `重置【${params.username}】用户密码`); proTable.value.getTableList(); }; // 切换用户状态 const changeStatus = async (row: User.ResUserList) => { await useHandleData(changeUserStatus, { id: row.id, status: row.status == 1 ? 0 : 1 }, `切换【${row.username}】用户状态`); proTable.value.getTableList(); }; // 导出用户列表 const downloadFile = async () => { ElMessageBox.confirm("确认导出用户数据?", "温馨提示", { type: "warning" }).then(() => useDownload(exportUserInfo, "用户列表", proTable.value.searchParam) ); }; // 批量添加用户 const dialogRef = ref(null); const batchAdd = () => { const params = { title: "用户", tempApi: exportUserInfo, importApi: BatchAddUser, getTableList: proTable.value.getTableList }; dialogRef.value?.acceptParams(params); }; // 打开 drawer(新增、查看、编辑) const drawerRef = ref(null); const openDrawer = (title: string, row: Partial = {}) => { const params = { title, isView: title === "查看", row: { ...row }, api: title === "新增" ? addUser : title === "编辑" ? editUser : undefined, getTableList: proTable.value.getTableList }; drawerRef.value?.acceptParams(params); }; 复制代码 七、贡献者 👨‍👦‍👦 HalseySpicy denganjia


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有