当前位置:   article > 正文

【通用表格组件】vue3 + element-ui + template 实现通用表格组件_vue3封装表格

vue3封装表格

介绍: 这是基于 vue3 + el-table 封装的通用表格组件 的 模板写法,想要参考tsx写法的可以到我另一篇博客喔~

【通用表格组件】vue3 + element-ui + tsx 实现通用表格组件

 这里通用表格,和上一篇通用表单一样的(表格组件都在我博客里),配置完全可控,然后每个el-table-column 都是通过传入的数组来循环便利渲染,大部分常用实现也写在了下面,无法具体实现或需要你自己自定义开发的,都可以通过render来开发。

1、父组件调用方式

  1. <CommonTable
  2. show-index
  3. show-check-box
  4. key-id="id"
  5. :loading="loading"
  6. :max-height="390"
  7. :table-label="tableHeaderData"
  8. :row-class-name="tableSortRowClassName"
  9. :data="tableData"
  10. :option="tableOptionsData"
  11. @operation="operationHandler"
  12. @handle-selection-change="handleSelectionChange"
  13. />
  1. export const tableHeaderData = [
  2. {
  3. label: '部门名称/用户名称',
  4. prop: 'name',
  5. },
  6. {
  7. label: '部门负责人',
  8. prop: 'contactPerson',
  9. },
  10. ]
  11. export const tableOptionsData = {
  12. label: '操作',
  13. width: '300',
  14. fixed: 'right',
  15. children: [
  16. {
  17. label: '查看制作详情',
  18. icon: 'el-icon-view',
  19. methods: 'view',
  20. permission: 'xxx',
  21. render(row) {
  22. return row.status !== 0
  23. }
  24. }
  25. ]
  26. }

2、组件源码 

  1. <template>
  2. <div v-loading="loading">
  3. <el-table
  4. ref="table"
  5. v-bind="{ ...props }"
  6. :data="data"
  7. style="width: 100%"
  8. :row-class-name="tabRowClassName"
  9. @select="handleSelectionChange"
  10. :row-style="rowStyle"
  11. @select-all="handleSelectionChange"
  12. :cell-class-name="cellClassName"
  13. header-row-class-name="custom-table-header"
  14. :default-expand-all="expendAll"
  15. row-key="id"
  16. :max-height="maxHeight"
  17. >
  18. <!-- 单选框 -->
  19. <el-table-column
  20. v-if="showCheckBoX"
  21. width="55"
  22. type="selection"
  23. :reserve-selection="keep"
  24. :class-name="turnRadio ? `checkBoxRadio` : ``"
  25. align="center"
  26. ></el-table-column>
  27. <!-- 序号 -->
  28. <el-table-column v-if="showTypeIndex" align="center" label="序号" width="50">
  29. <template #default="{ $index }">{{ $index + 1 }}</template>
  30. </el-table-column>
  31. <!-- 表格 -->
  32. <el-table-column
  33. v-for="item in tableLabel.filter((item) => item.label)"
  34. :width="item.width ? item.width : ''"
  35. :key="item[keyId]"
  36. :align="!!item.align ? item.align : 'center'"
  37. :label="item.label"
  38. :show-overflow-tooltip="overflowText"
  39. :fixed="item.fixed"
  40. :prop="item.prop"
  41. >
  42. <template #default="{ row }">
  43. <template v-if="item.Image">
  44. <div>
  45. <el-image class="table-img" :src="row.attachment" :preview-src-list="[row.attachment]">
  46. <template #error>
  47. <span></span>
  48. </template>
  49. </el-image>
  50. </div>
  51. </template>
  52. <template v-else-if="item.render">
  53. <div style="cursor: pointer" @click="!!item.methods && handleClickon(item.methods, row)" v-html="item.render(row)"></div>
  54. </template>
  55. <template v-else-if="item.format">
  56. <div v-if="typeof item.format === 'function'">{{ item.format(row[item.prop], row, item) }}</div>
  57. </template>
  58. <template v-else>
  59. <div class="text-no-wrap" @click="!!item.methods && handleClickon(item.methods, row)">
  60. {{ Object.prototype.toString.call(item.prop) == '[object Array]' ? propFilter(item.prop, row) : row[item.prop] }}
  61. </div>
  62. </template>
  63. </template>
  64. </el-table-column>
  65. <el-table-column v-if="!!option" :width="option.width" :label="option.label" :fixed="option.fixed" align="center">
  66. <template #default="scope" v-if="!!option.children">
  67. <!-- 常规正常情况 -->
  68. <el-button
  69. type="text"
  70. v-for="(item, index) in option.children"
  71. :key="index"
  72. v-show="!buttonHidden(item, scope.row)"
  73. v-bind="{ ...item.props }"
  74. :disabled="buttonDisabled(item, scope.row)"
  75. @click="handleTableButton(item.methods, scope.row, index, scope.$index)"
  76. :class="['btn-' + item.methods, 'btn-right']"
  77. size="mini"
  78. >
  79. <template v-if="item.render">
  80. <div v-html="item.render(scope.row)"></div>
  81. </template>
  82. {{ !!item.label ? item.label : '' }}
  83. </el-button>
  84. </template>
  85. </el-table-column>
  86. </el-table>
  87. </div>
  88. </template>
  89. <script lang="ts">
  90. import { defineComponent, computed, PropType, ref, onMounted, nextTick, reactive, watch } from 'vue'
  91. import { useStore } from 'store/index'
  92. import { Utils } from '@/utils'
  93. import { isNumber } from '@/utils/is'
  94. interface PageInfo {
  95. pageIndex: number
  96. totalCount: number
  97. pageSize: number
  98. pageArr?: []
  99. [ket: string]: any
  100. }
  101. interface ColumnI {
  102. label: string
  103. prop: string
  104. width?: string | number
  105. }
  106. interface TableOption {
  107. label: string
  108. methods: string
  109. props: object
  110. style: object
  111. render: Function
  112. }
  113. interface TableOptions {
  114. label: string
  115. width: string | number
  116. fixed: boolean
  117. popoverWidth?: number | string
  118. children: Array<TableOption>
  119. }
  120. interface Props {
  121. maxHeight: string | number
  122. stateArr: Array<any>
  123. tableLabel: Array<ColumnI>
  124. option: TableOption
  125. showCheckBoX: boolean
  126. showTypeIndex: boolean
  127. turnRadio: boolean
  128. selectedIdArr: Array<any>
  129. pageInfo: PageInfo
  130. showPagination: boolean
  131. overflowText: boolean
  132. loading: boolean
  133. keep: boolean
  134. keyId: string
  135. data: Array<any>
  136. expendAll: boolean
  137. }
  138. export default defineComponent({
  139. name: 'CommonTable',
  140. props: {
  141. maxHeight: {
  142. type: [String, Number],
  143. default: 600
  144. },
  145. stateArr: {
  146. type: Array,
  147. default: () => {
  148. return []
  149. }
  150. },
  151. tableLabel: {
  152. // 表格展示
  153. type: Array as PropType<Array<ColumnI>>,
  154. default: () => {
  155. return []
  156. }
  157. },
  158. data: {
  159. // 数据源
  160. type: Array as PropType<Array<any>>,
  161. default: () => {
  162. return []
  163. }
  164. },
  165. option: {
  166. // 配置需要显示的操作菜单
  167. type: Object as PropType<TableOption>
  168. },
  169. showCheckBoX: {
  170. // 配置是否显示全选(复选框)
  171. type: Boolean,
  172. default: false
  173. },
  174. showTypeIndex: {
  175. type: Boolean,
  176. default: false
  177. },
  178. turnRadio: {
  179. type: Boolean,
  180. default: false
  181. },
  182. selectedIdArr: {
  183. type: Array,
  184. default: []
  185. },
  186. pageInfo: {
  187. // 配置分页
  188. type: Object,
  189. default: () => {
  190. return {
  191. pageIndex: 1,
  192. totalCount: 0,
  193. pageSize: 10,
  194. pageArr: []
  195. }
  196. }
  197. },
  198. showPagination: {
  199. // 是否隐藏 分页显示
  200. type: Boolean,
  201. default: true
  202. },
  203. overflowText: {
  204. // 是否 隐藏文字过长
  205. type: Boolean,
  206. default: false
  207. },
  208. loading: {
  209. // loading 配置
  210. type: Boolean,
  211. default: false
  212. },
  213. keep: {
  214. type: Boolean,
  215. default: false
  216. },
  217. keyId: {
  218. // 动态绑定 key 值
  219. type: String,
  220. default: 'id'
  221. },
  222. props: {
  223. // 表格参数配置
  224. type: Object,
  225. default: function () {
  226. return {
  227. 'show-header': true, // 显示表头e
  228. 'highlight-current-row': false, // 是否要高亮当前行
  229. 'tooltip-effect': 'dark', //
  230. 'max-height': 'auto', // Table 的最大高度。合法的值为数字或者单位为 px 的高度。
  231. 'empty-text': '没有数据', // 空数据显示状态
  232. 'element-loading-text': '加载中', // loading 加载
  233. 'header-cell-style': {
  234. background: '#F2F4F7',
  235. color: '#333',
  236. fontSize: '13px'
  237. }, // 表头样式
  238. border: false, // 是否带有纵向边框
  239. fit: true, // 列的宽度是否自撑开
  240. stripe: false // 是否显示斑马纹
  241. }
  242. }
  243. },
  244. rowStyle: {
  245. type: Object,
  246. default: () => {
  247. return {
  248. height: '40px'
  249. }
  250. }
  251. },
  252. expendAll: {
  253. type: Boolean,
  254. default: false
  255. },
  256. /**
  257. * 行内自定义class
  258. */
  259. rowClassName: {
  260. type: Function,
  261. default: () => {
  262. return () => {}
  263. }
  264. }
  265. },
  266. setup(props: any, { emit }) {
  267. watch(
  268. () => props.data,
  269. () => {
  270. if (props.showCheckBoX || props.turnRadio) {
  271. nextTick(() => {
  272. table.value.clearSelection()
  273. curPageCheck.value = []
  274. if (props.showCheckBoX && props.turnRadio) {
  275. props.data.filter((item: any) => {
  276. if (item.id == props.selectedIdArr[0]) {
  277. table.value.toggleRowSelection(item, true)
  278. }
  279. })
  280. } else if (props.showCheckBoX) {
  281. props.data.filter((item: any) => {
  282. if (props.selectedIdArr.includes(item.id)) {
  283. table.value.toggleRowSelection(item, true)
  284. curPageCheck.value.push(item.id)
  285. }
  286. })
  287. }
  288. })
  289. }
  290. },
  291. {
  292. deep: true,
  293. immediate: true
  294. }
  295. )
  296. watch(
  297. () => props.selectedIdArr,
  298. (val) => {
  299. if (props.showCheckBoX || props.turnRadio) {
  300. nextTick(() => {
  301. table.value.clearSelection()
  302. curPageCheck.value = []
  303. if (props.showCheckBoX && props.turnRadio) {
  304. props.data.filter((item: any) => {
  305. if (item.id == val[0]) {
  306. table.value.toggleRowSelection(item, true)
  307. }
  308. })
  309. } else if (props.showCheckBoX) {
  310. props.data.filter((item: any) => {
  311. if (val.includes(item.id)) {
  312. table.value.toggleRowSelection(item, true)
  313. curPageCheck.value.push(item.id)
  314. }
  315. })
  316. }
  317. })
  318. }
  319. },
  320. {
  321. deep: true
  322. }
  323. )
  324. /**
  325. * prop 单值 或者 数组过滤(此处为针对时间组,不作为通用处理)
  326. */
  327. const propFilter = (prop: [Array<any> | object], row: any) => {
  328. let res = prop.reduce((total: string, cur: any) => {
  329. if (row[cur]) {
  330. return (total += row[cur] + '~')
  331. } else {
  332. return ''
  333. }
  334. }, '')
  335. // console.log(res)
  336. return res ? res.replace(/~$/, '') : ''
  337. }
  338. const handleTableButton = (methods: any, row: object, index: number, rowIndex: number) => {
  339. // 按钮事件
  340. emit('handleTableButton', { methods, row, index, rowIndex })
  341. }
  342. const handleClickon = (methods: any, row: object) => {
  343. if (typeof methods !== 'string') throw '方法名错误'
  344. // 数据操作
  345. emit(methods, { methods, row })
  346. }
  347. const curPageCheck = ref<Array<any>>([])
  348. const handleSelectionChange = (val: Array<any>) => {
  349. let arr = val.map((item) => parseInt(item.id))
  350. let compare = Utils.compareArray(curPageCheck.value, arr)
  351. if (props.showCheckBoX && props.turnRadio) {
  352. // 选择项大于1时
  353. if (val.length > 1) {
  354. let del_row = val.shift()
  355. table.value.toggleRowSelection(del_row, false)
  356. }
  357. }
  358. // 全选
  359. if (props.showCheckBoX && props.selectedIdArr) {
  360. if (props.turnRadio) {
  361. emit('handleSelectionChange', val)
  362. } else {
  363. emit('handleSelectionChange', val, compare)
  364. }
  365. } else {
  366. emit('handleSelectionChange', val)
  367. }
  368. }
  369. const getList = (pages: any) => {
  370. let { page, limit } = pages
  371. if (!isNumber(page)) {
  372. page = page.value
  373. }
  374. if (!isNumber(limit)) {
  375. limit = limit.value
  376. }
  377. const pageInfo = {
  378. page,
  379. limit
  380. }
  381. emit('handleGetList', pageInfo)
  382. }
  383. const getRowKeys = (row: any) => {
  384. return row.id
  385. }
  386. const table = ref<any>(null)
  387. const selectAll = (val: any) => {
  388. if (props.showCheckBoX && props.turnRadio) {
  389. // 选择项大于1时
  390. if (val.length > 1) {
  391. val.length = 1
  392. }
  393. }
  394. emit('handleSelectionChange', val)
  395. }
  396. //斑马纹表格背景色
  397. const tabRowClassName = ({ row, rowIndex }: any) => {
  398. let index = rowIndex + 1
  399. if (index % 2 == 0) {
  400. return 'even-row'
  401. } else {
  402. return 'odd-row'
  403. }
  404. return ''
  405. }
  406. const cellClassName = ({ row, column, rowIndex, columnIndex }: any) => {
  407. if (row.confirmTag === 2 && columnIndex < (props as any).tableLabel.length) {
  408. return 'height_light_cell'
  409. } else {
  410. return ''
  411. }
  412. }
  413. const buttonHidden = (item: any, row?: any) => {
  414. if (typeof item.hidden === 'function') return item.hidden(row) || false
  415. if (!item.hidden) return item.hidden
  416. }
  417. const buttonDisabled = (item: any, row?: any) => {
  418. if (typeof item.disabled === 'function') return item.disabled(row) || false
  419. if (!item.disabled) return item.disabled
  420. }
  421. const store = useStore()
  422. const buttonType = computed(() => store.state.app.buttonType)
  423. const showVertical = ref<boolean>(false)
  424. /**
  425. * 单选框选中事件
  426. */
  427. const rowClick = (row: any): void => {
  428. emit('rowClick', row)
  429. }
  430. const radioId = ref<number | string>(-1)
  431. return {
  432. propFilter,
  433. handleTableButton,
  434. handleClickon,
  435. handleSelectionChange,
  436. getList,
  437. getRowKeys,
  438. tabRowClassName,
  439. cellClassName,
  440. buttonHidden,
  441. buttonDisabled,
  442. buttonType,
  443. showVertical,
  444. table,
  445. radioId,
  446. rowClick,
  447. selectAll
  448. }
  449. }
  450. })
  451. </script>
  452. <style lang="scss" scoped>
  453. ::v-deep .el-table__header,
  454. ::v-deep .el-table__body {
  455. margin: 0;
  456. }
  457. .scrollBar {
  458. @include scrollBar;
  459. }
  460. ::v-deep .el-table::before {
  461. height: 0;
  462. }
  463. ::v-deep .el-button {
  464. padding: 0;
  465. border: none;
  466. margin: 0 4px;
  467. padding: 0 4px 0 8px;
  468. border-left: 1px solid #e2e2e2;
  469. font-size: 14px;
  470. min-height: 14px;
  471. &:first-child {
  472. border-left: none;
  473. }
  474. }
  475. ::v-deep .el-button + .el-button {
  476. margin-left: 0;
  477. }
  478. .btn-see {
  479. color: #39a6ff;
  480. }
  481. .btn-handel {
  482. color: #41a4bd;
  483. }
  484. .btn-edit {
  485. color: #fcb0fb;
  486. }
  487. .btn-delete,
  488. .btn-del {
  489. color: #fd9090;
  490. }
  491. .btn-revoke {
  492. color: #ffa913;
  493. }
  494. ::v-deep .btn-right div {
  495. margin-right: 5px;
  496. }
  497. .btn-right div:empty {
  498. margin-right: 0px;
  499. }
  500. //斑马纹表格背景色
  501. ::v-deep(.el-table) .even-row {
  502. --el-table-tr-background-color: #f5fafb;
  503. }
  504. ::v-deep(.el-table) .odd-row {
  505. --el-table-tr-background-color: #ffffff;
  506. }
  507. .el-table--border::after,
  508. .el-table--group::after {
  509. width: 0;
  510. }
  511. ::v-deep .el-table td,
  512. th.is-leaf {
  513. border: none;
  514. }
  515. ::v-deep .el-table__fixed-right::before,
  516. .el-table__fixed::before {
  517. background-color: transparent;
  518. }
  519. ::v-deep .custom-table-header {
  520. th {
  521. background-color: #62c4ee !important;
  522. color: #fff !important;
  523. }
  524. }
  525. .progress-line {
  526. .el-progress-bar__outer {
  527. height: 16px !important;
  528. }
  529. .el-progress-bar__outer,
  530. .el-progress-bar__inner {
  531. border-radius: 0 !important;
  532. }
  533. }
  534. .text-no-wrap {
  535. @include text-no-wrap;
  536. cursor: pointer;
  537. display: inline;
  538. }
  539. ::v-deep(.el-table) {
  540. td.el-table__cell div,
  541. th.el-table__cell > .cell {
  542. font-size: 14px;
  543. }
  544. th.el-table__cell > .cell {
  545. font-weight: normal;
  546. }
  547. .cell {
  548. padding: 0 10px;
  549. line-height: 39px;
  550. }
  551. .el-table__header-wrapper .checkBoxRadio .el-checkbox {
  552. display: none;
  553. }
  554. .el-checkbox {
  555. display: flex;
  556. align-items: center;
  557. justify-content: center;
  558. }
  559. .table-img {
  560. width: 60px;
  561. height: 60px;
  562. object-fit: cover;
  563. padding: 6px 0;
  564. display: flex;
  565. align-items: center;
  566. margin: 0 auto;
  567. justify-content: center;
  568. }
  569. }
  570. ::v-deep(.el-table--small .el-table__cell) {
  571. padding: 0;
  572. }
  573. ::v-deep(.el-dropdown-menu__item) {
  574. padding: 0 !important;
  575. .el-button {
  576. width: 100%;
  577. text-align: center;
  578. padding: 0 8px;
  579. margin: 0;
  580. }
  581. }
  582. </style>

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/684251
推荐阅读
相关标签
  

闽ICP备14008679号