|
|
|
@ -0,0 +1,397 @@
|
|
|
|
|
<!-- 📚📚📚 Pro-Table 文档: https://juejin.cn/post/7166068828202336263 -->
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<!-- 查询表单 card -->
|
|
|
|
|
<!-- SearchForm 是自定义的搜索表单组件 -->
|
|
|
|
|
<!-- :search="search" 绑定搜索方法 -->
|
|
|
|
|
<!-- :reset="reset" 绑定重置方法 -->
|
|
|
|
|
<!-- :searchParam="searchParam" 绑定搜索参数 -->
|
|
|
|
|
<!-- :columns="searchColumns" 绑定搜索列配置 -->
|
|
|
|
|
<!-- :searchCol="searchCol" 绑定搜索项每列占比配置 -->
|
|
|
|
|
<!-- v-show="isShowSearch" 根据 isShowSearch 的值显示或隐藏搜索表单 -->
|
|
|
|
|
<!-- ref="searchForm" 为搜索表单组件添加引用 -->
|
|
|
|
|
<SearchForm
|
|
|
|
|
:search="search"
|
|
|
|
|
:reset="reset"
|
|
|
|
|
:searchParam="searchParam"
|
|
|
|
|
:columns="searchColumns"
|
|
|
|
|
:searchCol="searchCol"
|
|
|
|
|
v-show="isShowSearch"
|
|
|
|
|
ref="searchForm"
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<!-- 表格内容 card -->
|
|
|
|
|
<MyCard class="mt-2">
|
|
|
|
|
<div class="card table-main">
|
|
|
|
|
<div class="table-header">
|
|
|
|
|
<div class="flex justify-between header-button-lf mb-2">
|
|
|
|
|
<div>
|
|
|
|
|
<!-- 具名插槽 tableHeader,传递了 selectedListIds、selectedList 和 isSelected 数据 -->
|
|
|
|
|
<slot
|
|
|
|
|
name="tableHeader"
|
|
|
|
|
:selectedListIds="selectedListIds"
|
|
|
|
|
:selectedList="selectedList"
|
|
|
|
|
:isSelected="isSelected"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="header-button-ri">
|
|
|
|
|
<!-- 具名插槽 toolButton,包含了一些功能按钮 -->
|
|
|
|
|
<slot name="toolButton">
|
|
|
|
|
<!-- 刷新按钮,点击时调用 getTableList 方法获取表格数据 -->
|
|
|
|
|
<el-button :icon="Refresh" circle @click="getTableList" />
|
|
|
|
|
<!-- <el-button :icon="Printer" circle v-if="columns.length" @click="handlePrint" /> -->
|
|
|
|
|
<!-- 列设置按钮,当 columns 存在时显示,点击时调用 openColSetting 方法打开列设置 -->
|
|
|
|
|
<el-button
|
|
|
|
|
:icon="Operation"
|
|
|
|
|
circle
|
|
|
|
|
v-if="columns.length"
|
|
|
|
|
@click="openColSetting"
|
|
|
|
|
/>
|
|
|
|
|
<!-- 搜索按钮,当 searchColumns 存在时显示,点击时切换 isShowSearch 的值显示或隐藏搜索表单 -->
|
|
|
|
|
<el-button
|
|
|
|
|
:icon="Search"
|
|
|
|
|
circle
|
|
|
|
|
v-if="searchColumns.length"
|
|
|
|
|
@click="isShowSearch = !isShowSearch"
|
|
|
|
|
/>
|
|
|
|
|
</slot>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 表格主体 -->
|
|
|
|
|
<!-- el-table 是 Element Plus 的表格组件 -->
|
|
|
|
|
<!-- ref="tableRef" 为表格组件添加引用 -->
|
|
|
|
|
<!-- v-bind="$attrs" 绑定父组件传递的所有属性 -->
|
|
|
|
|
<!-- :data="tableData" 绑定表格数据 -->
|
|
|
|
|
<!-- :border="border" 绑定表格是否有纵向边框 -->
|
|
|
|
|
<!-- :row-key="rowKey" 绑定行数据的唯一标识 -->
|
|
|
|
|
<!-- @selection-change="selectionChange" 监听表格行选择变化事件 -->
|
|
|
|
|
<el-table
|
|
|
|
|
ref="tableRef"
|
|
|
|
|
v-bind="$attrs"
|
|
|
|
|
:data="tableData"
|
|
|
|
|
:border="border"
|
|
|
|
|
:row-key="rowKey"
|
|
|
|
|
@selection-change="selectionChange"
|
|
|
|
|
>
|
|
|
|
|
<!-- 默认插槽 -->
|
|
|
|
|
<slot></slot>
|
|
|
|
|
<!-- 循环渲染表格列 -->
|
|
|
|
|
<template v-for="item in tableColumns" :key="item">
|
|
|
|
|
<!-- selection || index 类型的列 -->
|
|
|
|
|
<el-table-column
|
|
|
|
|
v-bind="item"
|
|
|
|
|
:align="item.align?? 'center'"
|
|
|
|
|
:reserve-selection="item.type =='selection'"
|
|
|
|
|
v-if="item.type =='selection' || item.type == 'index'"
|
|
|
|
|
>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<!-- expand 类型的列,支持 tsx 语法和作用域插槽 -->
|
|
|
|
|
<el-table-column
|
|
|
|
|
v-bind="item"
|
|
|
|
|
:align="item.align?? 'center'"
|
|
|
|
|
v-if="item.type == 'expand'"
|
|
|
|
|
v-slot="scope"
|
|
|
|
|
>
|
|
|
|
|
<!-- 如果有自定义渲染函数,则使用该函数渲染 -->
|
|
|
|
|
<component :is="item.render" :row="scope.row" v-if="item.render">
|
|
|
|
|
</component>
|
|
|
|
|
<!-- 否则使用插槽渲染 -->
|
|
|
|
|
<slot :name="item.type" :row="scope.row" v-else></slot>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<!-- 其他类型的列,通过 TableColumn 组件递归渲染 -->
|
|
|
|
|
<TableColumn
|
|
|
|
|
v-if="!item.type && item.prop && item.isShow"
|
|
|
|
|
:column="item"
|
|
|
|
|
>
|
|
|
|
|
<!-- 循环渲染插槽内容 -->
|
|
|
|
|
<template v-for="slot in Object.keys($slots)" #[slot]="scope">
|
|
|
|
|
<slot :name="slot" :row="scope.row"></slot>
|
|
|
|
|
</template>
|
|
|
|
|
</TableColumn>
|
|
|
|
|
</template>
|
|
|
|
|
<!-- 插入表格最后一行之后的插槽 -->
|
|
|
|
|
<template #append>
|
|
|
|
|
<slot name="append"> </slot>
|
|
|
|
|
</template>
|
|
|
|
|
<!-- 表格无数据情况 -->
|
|
|
|
|
<template #empty>
|
|
|
|
|
<div class="table-empty">
|
|
|
|
|
<slot name="empty">
|
|
|
|
|
<div>暂无数据</div>
|
|
|
|
|
</slot>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table>
|
|
|
|
|
<!-- 分页组件插槽 -->
|
|
|
|
|
<slot name="pagination">
|
|
|
|
|
<div class="mt-2 flex flex-row-reverse">
|
|
|
|
|
<!-- Pagination 是自定义的分页组件,当 pagination 为 true 时显示 -->
|
|
|
|
|
<Pagination
|
|
|
|
|
v-if="pagination"
|
|
|
|
|
:pageable="pageable"
|
|
|
|
|
:handleSizeChange="handleSizeChange"
|
|
|
|
|
:handleCurrentChange="handleCurrentChange"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</slot>
|
|
|
|
|
</div>
|
|
|
|
|
</MyCard>
|
|
|
|
|
|
|
|
|
|
<!-- 列设置 -->
|
|
|
|
|
<!-- ColSetting 是自定义的列设置组件,当 toolButton 为 true 时显示 -->
|
|
|
|
|
<!-- v-model:colSetting="colSetting" 双向绑定列设置数据 -->
|
|
|
|
|
<ColSetting v-if="toolButton" ref="colRef" v-model:colSetting="colSetting" />
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts" name="ProTable">
|
|
|
|
|
// 导入 Vue 相关的函数
|
|
|
|
|
import { ref, watch, computed, provide, onMounted } from 'vue'
|
|
|
|
|
// 导入自定义的表格相关 Hooks
|
|
|
|
|
import { useTable } from '@/hooks/useTable'
|
|
|
|
|
// 导入响应式断点类型
|
|
|
|
|
import { BreakPoint } from '@/components/Grid/interface'
|
|
|
|
|
// 导入表格列配置项接口
|
|
|
|
|
import { ColumnProps } from '@/components/ProTable/interface'
|
|
|
|
|
// 导入 Element Plus 的表格组件类型和属性接口
|
|
|
|
|
import { ElTable, TableProps } from 'element-plus'
|
|
|
|
|
// 导入 Element Plus 的图标组件
|
|
|
|
|
import { Refresh, Printer, Operation, Search } from '@element-plus/icons-vue'
|
|
|
|
|
// 导入一些工具函数
|
|
|
|
|
import {
|
|
|
|
|
filterEnum,
|
|
|
|
|
formatValue,
|
|
|
|
|
handleProp,
|
|
|
|
|
handleRowAccordingToProp
|
|
|
|
|
} from '@/utils/util'
|
|
|
|
|
// 导入自定义的搜索表单组件
|
|
|
|
|
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'
|
|
|
|
|
// 导入自定义的表格多选 Hooks
|
|
|
|
|
import { useSelection } from '@/hooks/useSelection'
|
|
|
|
|
// import printJS from "print-js";
|
|
|
|
|
|
|
|
|
|
// 定义搜索表单组件的引用
|
|
|
|
|
const searchForm = ref()
|
|
|
|
|
|
|
|
|
|
// 定义 ProTable 组件的属性接口,继承自 TableProps 并去除 data 属性
|
|
|
|
|
interface ProTableProps extends Partial<Omit<TableProps<any>, 'data'>> {
|
|
|
|
|
// 列配置项数组
|
|
|
|
|
columns: ColumnProps[]
|
|
|
|
|
// 请求表格数据的 API 函数,必传
|
|
|
|
|
requestApi: (params: any) => Promise<any>
|
|
|
|
|
// 是否自动请求数据,可选
|
|
|
|
|
requestAuto?: boolean
|
|
|
|
|
// 返回数据的回调函数,可对数据进行处理,非必传
|
|
|
|
|
dataCallback?: (data: any) => any
|
|
|
|
|
// 表格标题,目前只在打印时用到,非必传
|
|
|
|
|
title?: string
|
|
|
|
|
// 是否需要分页组件,非必传,默认为 true
|
|
|
|
|
pagination?: boolean
|
|
|
|
|
// 初始化请求参数,非必传,默认为 {}
|
|
|
|
|
initParam?: any
|
|
|
|
|
// 是否带有纵向边框,非必传,默认为 true
|
|
|
|
|
border?: boolean
|
|
|
|
|
// 是否显示表格功能按钮,非必传,默认为 true
|
|
|
|
|
toolButton?: boolean
|
|
|
|
|
// 行数据的 Key,用来优化 Table 的渲染,当表格数据多选时,所指定的 id,非必传,默认为 id
|
|
|
|
|
rowKey?: string
|
|
|
|
|
// 表格搜索项每列占比配置,非必传,如 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }
|
|
|
|
|
searchCol?: number | Record<BreakPoint, number>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 接受父组件参数并设置默认值
|
|
|
|
|
const props = withDefaults(defineProps<ProTableProps>(), {
|
|
|
|
|
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<InstanceType<typeof ElTable>>()
|
|
|
|
|
|
|
|
|
|
// 使用表格多选 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
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 定义清空选中数据列表的函数
|
|
|
|
|
const clearSelection = () => tableRef.value!.clearSelection()
|
|
|
|
|
|
|
|
|
|
// 初始化请求,当 requestAuto 为 true 时调用 getTableList 方法
|
|
|
|
|
onMounted(() => props.requestAuto && getTableList())
|
|
|
|
|
|
|
|
|
|
// 监听 initParam 的变化,当变化时重新获取表格数据
|
|
|
|
|
watch(() => props.initParam, getTableList, { deep: true })
|
|
|
|
|
|
|
|
|
|
// 定义表格列配置的响应式变量
|
|
|
|
|
const tableColumns = ref<ColumnProps[]>(props.columns)
|
|
|
|
|
|
|
|
|
|
// 定义 enumMap 用于存储枚举值,通过 provide 提供给子组件
|
|
|
|
|
const enumMap = ref(new Map<string, { [key: string]: any }[]>())
|
|
|
|
|
provide('enumMap', 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!)
|
|
|
|
|
// 如果 enum 是异步请求函数,则请求数据后设置到 enumMap
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 定义扁平化后的 columns 响应式变量
|
|
|
|
|
const flatColumns = ref<ColumnProps[]>()
|
|
|
|
|
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'
|
|
|
|
|
)
|
|
|
|
|
// 定义打开列设置的函数
|
|
|
|
|
const openColSetting = () => colRef.value.openColSetting()
|
|
|
|
|
|
|
|
|
|
//不需要打印可以把以下方法删除(目前数据处理比较复杂 201-238)
|
|
|
|
|
// 定义计算属性,处理打印数据(根据 enum 转换后台返回的值)
|
|
|
|
|
const printData = computed(() => {
|
|
|
|
|
const printDataList = JSON.parse(
|
|
|
|
|
JSON.stringify(
|
|
|
|
|
selectedList.value.length? selectedList.value : tableData.value
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
// 找出需要转换数据的列
|
|
|
|
|
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 = `<div style="text-align: center"><h2>${props.title}</h2></div>`;
|
|
|
|
|
// 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
|
|
|
|
|
})
|
|
|
|
|
</script>
|