index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <!-- 📚📚📚 Pro-Table 文档: https://juejin.cn/post/7166068828202336263 -->
  2. <template>
  3. <!-- 表格主体 -->
  4. <div class="card table-main">
  5. <!-- 表格头部 操作按钮 -->
  6. <div class="table-header">
  7. <div class="header-button-lf">
  8. <slot name="tableHeader" :selected-list="selectedList" :selected-list-ids="selectedListIds" :is-selected="isSelected" />
  9. </div>
  10. <div v-if="toolButton" class="header-button-ri">
  11. <slot name="toolButton">
  12. <el-button v-if="showToolButton('refresh')" circle @click="onRefresh" title="刷新表格">
  13. <SvgIcon name="ele-Refresh" />
  14. </el-button>
  15. <el-button v-if="showToolButton('setting') && columns.length" circle @click="openColSetting" title="列设置">
  16. <SvgIcon name="ele-Setting" />
  17. </el-button>
  18. <el-button v-if="showToolButton('exportCurrent') && columns.length" circle @click="exportCurrent" title="导出当前页">
  19. <SvgIcon name="iconfont icon-daochu" />
  20. </el-button>
  21. <el-button v-if="showToolButton('exportAll') && columns.length" circle @click="exportAll" title="导出全部">
  22. <SvgIcon name="iconfont icon-export" />
  23. </el-button>
  24. </slot>
  25. </div>
  26. </div>
  27. <!-- 表格主体 -->
  28. <!-- :scrollbar-always-on="true" 滚动条一直展示-->
  29. <el-table
  30. ref="tableRef"
  31. v-bind="$attrs"
  32. :data="processTableData"
  33. :border="border"
  34. :row-key="rowKey"
  35. @selection-change="selectionChange"
  36. v-loading="loading"
  37. :scrollbar-always-on="true"
  38. >
  39. <!-- 默认插槽 -->
  40. <slot />
  41. <template v-for="item in tableColumns" :key="item">
  42. <!-- selection || radio || index || expand || sort -->
  43. <el-table-column
  44. v-if="item.type && columnTypes.includes(item.type)"
  45. v-bind="item"
  46. :align="item.align ?? 'left'"
  47. :reserve-selection="item.type == 'selection'"
  48. >
  49. <template #default="scope">
  50. <!-- expand -->
  51. <template v-if="item.type == 'expand'">
  52. <component :is="item.render" v-bind="scope" v-if="item.render" />
  53. <slot v-else :name="item.type" v-bind="scope" />
  54. </template>
  55. <!-- radio -->
  56. <el-radio v-if="item.type == 'radio'" v-model="radio" :label="scope.row[rowKey]">
  57. <i></i>
  58. </el-radio>
  59. </template>
  60. </el-table-column>
  61. <!-- other -->
  62. <TableColumn v-if="!item.type && item.prop && item.isShow" :column="item" col-setting="">
  63. <template v-for="slot in Object.keys($slots)" #[slot]="scope">
  64. <slot :name="slot" v-bind="scope" />
  65. </template>
  66. </TableColumn>
  67. </template>
  68. <!-- 插入表格最后一行之后的插槽 -->
  69. <template #append>
  70. <slot name="append" />
  71. </template>
  72. <!-- 无数据 -->
  73. <template #empty>
  74. <Empty />
  75. </template>
  76. </el-table>
  77. <!-- 分页组件 -->
  78. <slot name="pagination">
  79. <PaginationEl v-if="pagination" @pagination="onRefresh" :total="total" v-model:current-page="pageIndex" v-model:page-size="pageSize" />
  80. </slot>
  81. </div>
  82. <!-- 列设置 -->
  83. <ColSetting v-if="toolButton" ref="colRef" v-model:col-setting="colSetting" @update:colSetting="updateColSetting" />
  84. </template>
  85. <script setup lang="ts" name="ProTable">
  86. import { ref, provide, onMounted, unref, computed, reactive, PropType, watch, shallowRef } from "vue";
  87. import { ElTable } from 'element-plus';
  88. import { useSelection } from '@/hooks/useSelection';
  89. import { ColumnProps, TypeProps } from '@/components/ProTable/interface';
  90. import PaginationEl from './components/Pagination.vue';
  91. import ColSetting from './components/ColSetting.vue';
  92. import TableColumn from './components/TableColumn.vue';
  93. import { handleProp } from '@/utils/tools';
  94. // 接受父组件参数,配置默认值
  95. const props = defineProps({
  96. columns: {
  97. // 列配置项 ==> 必传
  98. type: Array as PropType<ColumnProps[]>,
  99. required: true,
  100. },
  101. data: {
  102. // 静态 table data 数据
  103. type: Array,
  104. default: () => [],
  105. required: true,
  106. },
  107. pagination: {
  108. // 是否需要分页组件 ==> 非必传(默认为true)
  109. type: Boolean,
  110. default: true,
  111. },
  112. total: {
  113. type: Number,
  114. default: 0,
  115. },
  116. pageIndex: {
  117. type: Number,
  118. default: 1,
  119. },
  120. pageSize: {
  121. type: Number,
  122. default: 10,
  123. },
  124. border: {
  125. // 是否带有纵向边框 ==> 非必传(默认为false)
  126. type: Boolean,
  127. default: false,
  128. },
  129. toolButton: {
  130. // 是否显示表格功能按钮 ==> 非必传(默认为true)
  131. type: [Array, Boolean],
  132. // default: true,
  133. default: ['refresh', 'setting'],
  134. },
  135. rowKey: {
  136. // 行数据的 Key,用来优化 Table 的渲染,当表格数据多选时,所指定的 id ==> 非必传(默认为 id)
  137. type: String,
  138. default: 'id',
  139. },
  140. loading: {
  141. // 是否显示加载中
  142. type: Boolean,
  143. default: false,
  144. },
  145. radio: {
  146. type: String,
  147. default: '',
  148. },
  149. });
  150. const emit = defineEmits([
  151. 'dargSort',
  152. 'updateColSetting',
  153. 'selectChange',
  154. 'updateTable',
  155. 'update:pagination',
  156. 'exportCurrent',
  157. 'exportAll',
  158. 'update:pageSize',
  159. 'update:pageIndex',
  160. 'update:radio',
  161. ]);
  162. const pageSize = computed({
  163. get() {
  164. return props.pageSize;
  165. },
  166. set(val) {
  167. emit('update:pageSize', val);
  168. },
  169. });
  170. const pageIndex = computed({
  171. get() {
  172. return props.pageIndex;
  173. },
  174. set(val) {
  175. emit('update:pageIndex', val);
  176. },
  177. });
  178. const radio = computed({
  179. get() {
  180. return props.radio;
  181. },
  182. set(val) {
  183. emit('update:radio', val);
  184. },
  185. });
  186. // table 实例
  187. const tableRef = ref<InstanceType<typeof ElTable>>();
  188. // column 列类型
  189. const columnTypes: TypeProps[] = ['selection', 'radio', 'index', 'expand', 'sort'];
  190. // 控制 ToolButton 显示
  191. const showToolButton = (key: 'refresh' | 'setting' | 'exportCurrent' | 'exportAll') => {
  192. return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton;
  193. };
  194. // 表格多选 Hooks
  195. const { selectionChange, selectedList, selectedListIds, isSelected } = useSelection(props.rowKey);
  196. // 清空选中数据列表
  197. const clearSelection = () => tableRef.value!.clearSelection();
  198. // 初始化表格数据 && 拖拽排序
  199. onMounted(() => {
  200. getSelectColumns();
  201. });
  202. // 处理表格数据
  203. const processTableData = computed(() => {
  204. if (!props.pagination) return props.data;
  205. return props.data;
  206. });
  207. // 接收 columns 并设置为响应式
  208. const tableColumns = reactive<ColumnProps[]>(props.columns);
  209. // 扁平化 columns
  210. const flatColumns = computed(() => flatColumnsFunc(tableColumns));
  211. // 定义 enumMap 存储 enum 值(避免异步请求无法格式化单元格内容 || 无法填充搜索下拉选择)
  212. const enumMap = ref(new Map<string, { [key: string]: any }[]>());
  213. const setEnumMap = async ({ prop, enum: enumValue }: ColumnProps) => {
  214. if (!enumValue) return;
  215. // 如果当前 enumMap 存在相同的值 return
  216. if (enumMap.value.has(prop!) && (typeof enumValue === 'function' || enumMap.value.get(prop!) === enumValue)) return;
  217. // 当前 enum 为静态数据,则直接存储到 enumMap
  218. if (typeof enumValue !== 'function') return enumMap.value.set(prop!, unref(enumValue!));
  219. // 为了防止接口执行慢,而存储慢,导致重复请求,所以预先存储为[],接口返回后再二次存储
  220. enumMap.value.set(prop!, []);
  221. // 当前 enum 为后台数据需要请求数据,则调用该请求接口,并存储到 enumMap
  222. const { data } = await enumValue();
  223. enumMap.value.set(prop!, data);
  224. };
  225. // 注入 enumMap
  226. provide('enumMap', enumMap);
  227. // 扁平化 columns 的方法
  228. const flatColumnsFunc = (columns: ColumnProps[], flatArr: ColumnProps[] = []) => {
  229. columns.forEach(async (col) => {
  230. if (col._children?.length) flatArr.push(...flatColumnsFunc(col._children));
  231. flatArr.push(col);
  232. // column 添加默认 isShow
  233. col.isShow = col.isShow ?? true;
  234. // 设置 enumMap
  235. await setEnumMap(col);
  236. });
  237. return flatArr.filter((item) => !item._children?.length);
  238. };
  239. // 过滤需要搜索的配置项 && 排序
  240. const searchColumns = computed(() => {
  241. return flatColumns.value?.filter((item) => item.search?.el || item.search?.render).sort((a, b) => a.search!.order! - b.search!.order!);
  242. });
  243. // 设置 搜索表单默认排序 && 搜索表单项的默认值
  244. searchColumns.value?.forEach((column, index) => {
  245. column.search!.order = column.search?.order ?? index + 2;
  246. const key = column.search?.key ?? handleProp(column.prop!);
  247. const defaultValue = column.search?.defaultValue;
  248. });
  249. // 列设置 ==> 需要过滤掉不需要设置的列
  250. const colRef = ref();
  251. const colSetting = tableColumns!.filter((item) => {
  252. const { type, prop, isShow } = item;
  253. return !columnTypes.includes(type!) && prop !== 'operation' && isShow;
  254. });
  255. const openColSetting = () => colRef.value.openColSetting();
  256. // 刷新事件
  257. const onRefresh = () => {
  258. clearSelection();
  259. emit('updateTable');
  260. };
  261. const filterColumns = ref([]);
  262. const filterColumnsProp = ref([]);
  263. // 获取需要显示的列
  264. const getSelectColumns = () => {
  265. const flatColumns = flatColumnsFunc(tableColumns);
  266. filterColumns.value = flatColumns.filter((item) => item.isShow && item.prop !== 'operation' && item.prop);
  267. filterColumnsProp.value = filterColumns.value.map((item) => item.prop);
  268. };
  269. // 更新列设置
  270. const exportNewColumns = ref([]);
  271. const exportNewColumnsProp = ref([]);
  272. const updateColSetting = (test: ColumnProps[]) => {
  273. // 平铺数组后获取isShow为true的对象
  274. const flatColumns = flatColumnsFunc(test);
  275. exportNewColumns.value = flatColumns.filter((item) => item.isShow);
  276. // 获取prop
  277. const newColumnsProp = exportNewColumns.value.map((item) => item.prop);
  278. emit('updateColSetting', exportNewColumns.value, newColumnsProp);
  279. };
  280. watch(
  281. () => exportNewColumns.value,
  282. (val: any) => {
  283. console.log(val,'2121')
  284. if(val.length){
  285. exportNewColumnsProp.value = val.map((item: any) => item.prop);
  286. }else{
  287. exportNewColumns.value = flatColumnsFunc(colSetting);
  288. exportNewColumnsProp.value = exportNewColumns.value.map((item: any) => item.prop);
  289. }
  290. console.log(exportNewColumns.value, 'test');
  291. },
  292. { immediate: true,deep:true }
  293. );
  294. // 导出当前页
  295. const exportCurrent = () => {
  296. emit('exportCurrent');
  297. };
  298. // 导出全部
  299. const exportAll = () => {
  300. emit('exportAll');
  301. };
  302. watch(
  303. // 监听table数据改变后重置选择
  304. () => props.data,
  305. () => {
  306. clearSelection();
  307. }
  308. );
  309. // 暴露给父组件的参数和方法 (外部需要什么,都可以从这里暴露出去)
  310. defineExpose({
  311. element: tableRef,
  312. tableData: processTableData,
  313. radio,
  314. clearSelection,
  315. enumMap,
  316. isSelected,
  317. selectedList,
  318. selectedListIds,
  319. filterColumns,
  320. filterColumnsProp,
  321. exportNewColumns:shallowRef(exportNewColumns.value)
  322. });
  323. </script>
  324. <style scoped lang="scss">
  325. .table-header {
  326. margin-bottom: 15px;
  327. display: flex;
  328. justify-content: space-between;
  329. }
  330. .header-button-lf {
  331. flex: 1;
  332. }
  333. .header-button-ri {
  334. }
  335. </style>