当前位置:   article > 正文

Vue3和ElementPlus封装table组件_vue3 + elementplus组件

vue3 + elementplus组件

最近学习vue3.2并自己在写一个项目,然后发现好几个页面都是列表页,重复写table和column也是觉得累,学习的项目列表页不算多,要是公司项目就不一样了,所以就想着自己封装一个table组件,免去大量重复工作和copy改改改。

本文也是仅仅封装了一个相对简单的table组件,主要是table + 分页,要是不想要分页,也是可以通过使用table组件穿参数控制是否展示分页。基于查询表单,后续再安排~

封装一个table组件并不难,主要是搞懂插槽、作用域插槽的写法和用法,下面先复习一下插槽,再进行封装。

一、slot插槽

定义

Vue 实现了一套内容分发的 API,将 元素作为承载分发内容的出口。

简单理解:就是对子组件的扩展,通过 插槽向子组件内部指定位置传递内容。

插槽是组件的一块HTML模板,这块模板显示不显示,怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制。

分类

  • 匿名插槽
  • 具名插槽
  • 作用域插槽

匿名插槽

它不用设置名称属性,可以放置在组件的任意位置;可以理解为父传入样式及内容,子负责展示

  1. <!--index.vue-->
  2. <Child >
  3.     <ul>
  4.         <li>1</li>
  5.         <li>2</li>
  6.     </ul>
  7.     <ul slot>
  8.         <li v-for="(v,i) in arr" :key="i">{{v}}</li>
  9.     </ul>
  10. </Child>
  11. <!--child.vue-->
  12. <!--匿名插槽-->
  13. <template>
  14. <div>
  15. <slot></slot>
  16. </div>
  17. </template>

具名插槽

插槽加了名称属性,就变成了具名插槽。 在 v-slot 之后添加冒号 (:) ,然后写出要传递内容的 slot 的名称(v-slot:slotname);简写:#slotname

  1. <!--index.vue-->
  2. <Child>
  3. <template #default>
  4. <!--指定了 default 的名称,表示不需要为默认插槽指定名称;-->
  5.     <p>我是main的内容</p>
  6.     </template>
  7.    
  8.     <template #header>
  9.     <h2>艾梯哎教育</h2>
  10.     </template>
  11.     <template #list>
  12.     <h2>列表展示</h2>
  13.     <ul>
  14.         <li v-for="(v,i) in arr" :key="i">{{v}}</li>
  15.     </ul>
  16.     </template>
  17. </Child>
  18. <!--child.vue-->
  19. <!--具名插槽-->
  20. <slot></slot>
  21. <slot name="header"></slot>
  22. <slot name="list"></slot>

作用域插槽

好理解的叫法:带数据的插槽。

作用域插槽跟单个插槽和具名插槽的区别,因为单个插槽和具名插槽不绑定数据,所以父组件提供的模板一般要既包括样式又包括内容,而作用域插槽,相当于父组件提供一套样式,数据都是子组件的。

  1. <!--index.vue-->
  2. <Child >
  3. <template #user="obj">
  4.     <h2>老师:{{ obj.user.username }}</h2>
  5.     <h2>所授课程:{{ obj.user.course }}</h2>
  6. </template>
  7. <!--解构写法-->
  8. <template #user="{user}">
  9.     <h2>老师:{{ user.username }}</h2>
  10.     <h2>所授课程:{{ user.course }}</h2>
  11. </template>
  12. <!--解构别名-->
  13. <template #user="{user:person}">
  14.     <h2>老师:{{ person.username }}</h2>
  15.     <h2>所授课程:{{ person.course }}</h2>
  16. </template>
  17. </Child>
  18. <!--child.vue-->
  19. <template>
  20. <div>
  21. <slot name="user" :user="user"></slot>
  22. </div>
  23. </template>
  24. <script setup>
  25. import {ref,reactive} from 'vue'
  26. const user = ref({id:1,username:'张老师',course:'vue'});
  27. </script>

二、封装table

理解了具名作用域插槽 之后,相信大家脑海里已经有思路如何封装了。我这里的思路大概如下:

  • 接收tableData数据和columns列以及其他杂七杂八的配置项;
  • 然后在el-table-column上循环columns列;
  • 然后定义以列名为名称的插槽,并将数据row传递出去;
  • 并提供默认插槽内容,当没特殊数据展示,可不传父的内容,直接使用 定义的默认内容;
  • 如果 columns传入fommater,则会根据传入的function进行转换数据。

具体实现如下:

  1. <!--MyTable-->
  2. <template>
  3. <div>
  4. <el-table
  5. :style="{ width: '100%' }"
  6. ref="elTableRef"
  7. :data="tableData"
  8. :height="height"
  9. :max-height="maxHeight"
  10. :stripe="stripe"
  11. :row-key="rowKey"
  12. :highlight-current-row="highlightCurrentRow"
  13. @row-click="handleRowClick"
  14. @selection-change="handleSelectionChange">
  15. <el-table-column
  16. v-for="item in columns"
  17. :key="item.prop"
  18. :prop="item.prop"
  19. :label="item.label"
  20. :show-overflow-tooltip="item.showOverflowTooltip"
  21. :width="item.width"
  22. :fixed="item.fixed"
  23. :type="item.type"
  24. :sortable="item.sortable"
  25. :selectable="item.selectableFn">
  26. <!-- type 对应列的类型。 如果设置了selection则显示多选框; -->
  27. <!-- 如果设置了 index 则显示该行的索引(从 1 开始计算); -->
  28. <!-- 如果设置了 expand 则显示为一个可展开的按钮-->
  29. <!-- selection / index / expand -->
  30. <template #default="{row, column, $index}" v-if="item.type==='index'">
  31. {{getIndex($index)}}
  32. </template>
  33. <template #default="{row, column, $index}" v-if="!item.type">
  34. <!-- 具名作用域插槽 -->
  35. <slot
  36. :name="item.prop"
  37. :slotProps="row"
  38. :index="$index">
  39. <!-- 默认内容,当父组件不传内容时默认显示该内容 -->
  40. <span v-if="item.formatter">
  41. {{ item.formatter(row[item.prop]) }}
  42. </span>
  43. <span v-else>
  44. {{ row[item.prop] }}
  45. </span>
  46. </slot>
  47. </template>
  48. </el-table-column>
  49. </el-table>
  50. <div class="pagination-wrap">
  51. <el-pagination
  52. v-if="hasPagination"
  53. v-model:current-page="currentPage"
  54. v-model:page-size="pageSize"
  55. :page-sizes="pageSizes"
  56. :small="small"
  57. :background="true"
  58. :layout="layout"
  59. :total="total"
  60. @size-change="handleSizeChange"
  61. @current-change="handleCurrentChange"
  62. />
  63. </div>
  64. </div>
  65. </template>
  66. <script setup>
  67. import {toRefs} from 'vue'
  68. const props = defineProps({
  69. // 表格相关
  70. tableData: {
  71. type: Array,
  72. default: []
  73. },
  74. columns:{
  75. type: Array,
  76. default: []
  77. },
  78. height: {
  79. type: String,
  80. default: '500px'
  81. },
  82. maxHeight: {
  83. type: String,
  84. default: '500px'
  85. },
  86. stripe: {
  87. type: Boolean,
  88. default: true
  89. },
  90. rowKey: {
  91. type: String,
  92. default: 'id'
  93. },
  94. highlightCurrentRow: {
  95. type: Boolean,
  96. default: true
  97. },
  98. // 分页相关
  99. hasPagination: {
  100. type:Boolean,
  101. default: true
  102. },
  103. total: {
  104. type: Number,
  105. default: 0
  106. },
  107. currentPage: {
  108. type: Number,
  109. default: 1
  110. },
  111. pageSize: {
  112. type:Number,
  113. default: 10
  114. },
  115. pageSizes: {
  116. type: Array,
  117. default: [10, 20, 50, 100, 200]
  118. },
  119. layout: {
  120. type: String,
  121. default: 'total, sizes, prev, pager, next, jumper'
  122. },
  123. small: {
  124. type: String,
  125. default: 'small'
  126. }
  127. })
  128. let {
  129. tableData,
  130. columns,
  131. height,
  132. maxHeight,
  133. stripe,
  134. rowKey,
  135. highlightCurrentRow,
  136. hasPagination,
  137. total,
  138. currentPage,
  139. pageSize,
  140. pageSizes,
  141. layout,
  142. small
  143. } = toRefs(props)
  144. const emit = defineEmits(['rowClick','selectChange','changeTableData','update:currentPage','update:pageSize'])
  145. // 当某一行被点击时会触发该事件
  146. const handleRowClick = (row, column, event) => {
  147. emit('rowClick', { row, column, event })
  148. }
  149. // 当选择项发生变化时会触发该事件
  150. const handleSelectionChange = (selection) => {
  151. emit('selectChange', selection)
  152. }
  153. // 每页条数变化的事件
  154. const handleSizeChange = (val) => {
  155. emit('update:pageSize',val)
  156. emit('changeTableData', currentPage.value, val)
  157. }
  158. // 当前页码变化的事件
  159. const handleCurrentChange = (val) => {
  160. emit('update:currentPage',val)
  161. emit('changeTableData', val, pageSize.value)
  162. }
  163. const getIndex = (index)=> {
  164. return index + 1 + (currentPage.value - 1) * pageSize.value
  165. }
  166. </script>
  167. <style lang="scss" scoped>
  168. </style>

三、使用MyTable组件

因为check需要格式化内容并且用el-tag来进行展示内容,所以此处向table组件传入了需要展示的内容,此时,向table的name=check的插槽传入内容,那么table组件的默认展示内容则失效。

同理,由于每个taable组件的操作项也不一样,所以此处向name=operator的slot插入内容。

  1. <MyTable
  2. :tableData="tableData"
  3. :columns="columns"
  4. :total="total"
  5. :currentPage="listQuery.pageNo"
  6. :pageSize="listQuery.pageSize"
  7. @changeTableData="changeTableData">
  8. <template #check="{ slotProps }">
  9. <el-tag class="ml-2" :type="slotProps.check?'success':'danger'">
  10. {{ checkFilter(slotProps.check) }}
  11. </el-tag>
  12. </template>
  13. <template #operator="{slotProps}">
  14. <el-button type="primary" @click="setData('edit',slotProps)">编辑</el-button>
  15. <el-button type="danger" @click="handleDel(slotProps)">删除</el-button>
  16. </template>
  17. </MyTable>
  18. <script setup>
  19. import { ref,onMounted } from 'vue'
  20. import MyTable from '@/components/table/index.vue'
  21. import useHooks from '@/hooks'
  22. const { checkFilter, dateFormate } = useHooks()
  23. let listQuery = ref({
  24. pageNo:1,
  25. pageSize: 10
  26. })
  27. const tableData = ref([])
  28. let total = ref(0)
  29. /**
  30. * prop:数据项列名
  31. * label:列名展示名
  32. * fixed:固定列 true/right/left
  33. * width:列宽
  34. * show-overflow-tooltip
  35. * type:对应列的类型 selection / index / expand
  36. * sortable:true/false
  37. * selectable:Function
  38. * formatter:格式化内容 function(row, column, cellValue, index)
  39. **/
  40. let columns = ref([
  41. {prop: 'number',label: '车牌自编号'},
  42. {prop: 'numberplate',label: '车牌号'},
  43. {prop: 'date',label: '出厂日期',formatter: dateFormate},
  44. {prop: 'check',label: '车辆状态'},
  45. {prop: 'operator',label: '操作',fixed: "right"},
  46. ])
  47. onMounted(() => {
  48. getCarList()
  49. })
  50. const changeTableData = (pageNum,pageSize) => {
  51. listQuery.value.pageNo = pageNum
  52. listQuery.value.pageSize = pageSize
  53. getCarList()
  54. }
  55. // 列表查询
  56. async function getCarList() {
  57. const {data: {data}} = await carList(listQuery.value)
  58. tableData.value = data.list
  59. total.value = data.rows
  60. }
  61. </script>

效果如下:

 

cef83f1ca276458da478452ec7772e20.png如果需要加上索引或者复选框,需要在columns上添加上

 

  1. {type: 'selection',label:'',width: '50px'},
  2. {type: 'index',label:'序号',width: '60px'},

若是列项超长不需要tooltip,则配置showOverflowTooltip为false(默认是true)

    {prop: 'number',label: '车牌自编号',showOverflowTooltip: true, width: '100px'},

 

113407b2c8dd4584a369f61df475b6f0.png到此一个talbe组件就封装完成,如有不当的地方还请大家多多包含和赐教!如大家有不一样的封装思想也多多留言交流,互相学习互相进步。

 

 

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

闽ICP备14008679号