基于element-plus封装列表页

297 阅读3分钟

在做后台管理系统时,通常都会有很多列表页面,所以就寻思着是否封装一下。

image.png

如上所示是一个简单的列表页面,有搜索模块,表格模块,分页模块组成。

那么接下来我们就来把它们封装一下

搜索模块

  • hx-search.vue
<template>
  <el-form ref="ksFormRef" :model="model" label-width="60px" :inline="false" size="small">
    <el-row :gutter="20">
      <slot />
      <el-col :span="isinline ? 8 : 24" :offset="offset">
        <ks-header-btn v-bind="$attrs" @on-reset-init="reset_form()"></ks-header-btn>
      </el-col>
    </el-row>
  </el-form>
</template>

<script setup lang="ts">
import { ref, defineExpose } from 'vue'
import type { FormInstance } from 'element-plus'
import hxHeaderBtn from './hx-header-btn.vue'

defineProps({
  model: Object,
  //   按钮和框是否同一行
  isinline: {
    type: Boolean,
    default: false
  },
  offset: {
    type: Number,
    default: 0
  }
})

const emits = defineEmits(['onReset'])
const ksFormRef = ref(null)
function reset_form_func() {
  ksFormRef.value.resetFields()
}
function reset_form() {
  reset_form_func()
  emits('onReset')
}

defineExpose({
  reset_form_func
})

</script>
  • hx-search-item.vue
<template>
    <el-col :span="span" :offset="0">
         <el-form-item :label="label" prop="prop">
             <slot />
         </el-form-item>
    </el-col>
</template>
<script setup lang="ts">
defineProps({
    span: {
        type: Number,
        default: 8
    },
    label: String,
    prop: String
})
</script>
  • hx-header-btn.vue
<template>
  <section class="flex items-center" :class="isinline ? 'justify-end' : 'justify-between'">
    <div class="flex items-center">
      <el-button type="primary" class="pl-0" @click="$emit('onSearch')" v-if="showBtn.includes('search')">
        <el-icon class="ml-6"><Search /></el-icon>
        <span>查询</span>
      </el-button>
      <el-button class="pl-0" @click="$emit('onResetInit')" v-if="showBtn.includes('reset')">
        <el-icon class="ml-6"><Refresh /></el-icon>
        <span>重置</span>
      </el-button>
      <el-button class="pl-0" @click="$emit('onRefresh')" v-if="showBtn.includes('refresh')">
        <el-icon class="ml-6"><ZoomIn /></el-icon>
        <span>刷新</span>
      </el-button>
    </div>
    <div class="flex items-center">
      <el-button class="pl-0" @click="$emit('onImport')" v-if="showBtn.includes('import')">
        <el-icon class="ml-6"><DocumentImport /></el-icon>
        <span>导入</span>
      </el-button>
      <el-button class="pl-0" @click="$emit('onExport')" v-if="showBtn.includes('export')">
        <el-icon class="ml-6"><DocumentExport /></el-icon>
        <span>导出</span>
      </el-button>
      <el-button
        class="pl-0"
        style="color: #ffffff; background: #c8a06c"
        @click="$emit('onAdd')"
        v-if="showBtn.includes('add')"
      >
        <el-icon class="ml-6"><Plus /></el-icon>
        <span>新增</span>
      </el-button>
    </div>
  </section>
</template>
<script setup lang="ts">
defineProps({
  showBtn: {
    type: Array,
    // 'search', 'reset', 'refresh', 'import', 'export', 'add'
    default: () => ['search', 'reset', 'refresh']
  },
  //   按钮和框是否同一行
  isinline: {
    type: Boolean,
    default: false
  }
 
})

defineEmits(['onSearch', 'onResetInit', 'onRefresh', 'onImport', 'onExport', 'onAdd'])
</script>

现在搜索框的功能就封装好了,调用的时候如下:

<template>
    <hx-search
        :model="selectParams"
        :showBtn="['search', 'reset']"
        @on-search="search_func"
        @on-reset="reset_func"
        >
        <hx-search-item label="姓名" prop="title">
            <el-input v-model="selectParams.title" placeholder="请输入标题搜索" clearable></el-input>
        </hx-search-item>
        <hx-search-item label="分类" prop="category">
            <el-input v-model="selectParams.category" placeholder="请输入分类搜索" clearable></el-input>
        </hx-search-item>
            
    </hx-search>
</template>
<script setup lang="ts">
    import { ref } from 'vue'
    const selectParams = ref({
        title: '',
        category: ''
    })
</script>

table列表封装

<template>
    <section class="hx-table-wrapper">
        <el-table 
            ref="hxTableRef" 
            class="hx-table" 
            :data="tableData.list" 
            :size="size" 
            :stripe="stripe"
            style="width: 100%"
            :header-cell-style="headerBg ? { 'background-color': '#f4f4f5' } : ''"
            v-loading="isLoading"
        >
            <el-table-column v-if="!!isCheckBox" label="全选" type="selection" width="50" />
            <el-table-column v-if="!!isSerial" label="序号" type="index" width="50" />
            <template v-for="item in column" :key="item.prop">
                <el-table-column :prop="item.prop" :label="item.label" :width="item.width">
                    <template #default="scope" v-if="!!item.isScope">
                        <slot :name="item.prop + 'Scope'" :row="scope.row" ></slot>
                    </template>
                </el-table-column>
            </template>
            // 操作列
            <el-table-column 
                fixed="right"
                :label="operation.label || '操作'"
                :width="operation?.width || 180"
                v-if="operation.columns"
            >
                <template #default="scope">
                    <slot name="operations" :row="scope.row">
                        <template v-for="item in operation?.columns" :key="item.text || item.icon">
                            <el-popconfirm
                                v-if="item.popconfirm"
                                :title="item.title"
                                :confirm-button-text="item.confirmText || '确定'"
                                :cancel-button-text="item.cancelText || '取消'"
                                :width="item.width || 160"
                                @confirm="item.click(scope.row)"
                            >
                                <template #reference>
                                    <el-button
                                        :type="item.type || 'danger'"
                                        :link="item.link || false"
                                        size="small"
                                        :plain="item.plain || false"
                                        :icon="item.icon ? item.icon : ''"
                                    >{{item.text}}</el-button>
                                </template>
                            </el-popconfirm>
                            <el-button 
                                v-else
                                :type="item.type || 'danger'"
                                :link="item.link || false"
                                size="small"
                                :plain="item.plain || false"
                                :icon="item.icon ? item.icon : ''"
                                @click="item.click(scope.row)"
                            >{{ item.text }}</el-button>
                        </template>
                    </slot>
                </template>
            </el-table-column>
        </el-table>
        
        // 分页
        <hx-pagination ref="hxPaginationRef" :total="tableData.total" v-bind="$attrs"></hx-pagination>
    </section>
</template>
<script setup lang="ts">
import { ref, defineProps, reactive, nextTick } from 'vue'
import hxPagination from '../hx-pagination/hx-pagination.vue'
/**
 * ======= defineProps: ==========
 * column: Array  -- 自定义表格列 => 示例数据 hxTableColumn
 * requestApi: Function -- 表格查询函数
 * operation: Object  -- 操作列 => 示例数据 hxTableOperation
 * border: Boolean -- 边框
 * isCheckbox:Boolean -- 是否有复选框列
 * isSerial:Boolean -- 是否有序号列
 * selectData: Array  -- 已勾选的列
 * headerBg: Boolean  -- 表头是否有背景
 * size: String  -- 表格尺寸
 *
 *
 * ======== defineExpose 导出方法 =========
 * getTableData: 获取表格数据方法,传入参数
 * getSelectionRowsFunc: 获取选中的数据
 *
 */

type operationColumn = {
    text: string
    type?: string
    link?: boolean
    plain?: boolean
    icon?: string
    click?: () => void
}
// 操作列示例数据
type hxTableOperation = {
    label?: string
    width?: number
    column?: operationColumn[]
}

type hxTableColumn = {
    label: string
    prop: string
    width?: string
    isScope?: boolean
}[]

const props = defineProps({
    column: {
        type: Array,
        default: (): hxTableColumn => []
    },
    requestApi: {
        type: Object,
        default: () => {}
    },
    operation: {
        type: Object,
        default: () => ({})
    },
    border: {
        type: Boolean,
        default: false
    },
    stripe: {
        type: Boolean,
        default: false
    },
    isCheckbox: {
        type: Boolean,
        default: true
    },
    isSerial: {
        type: Boolean,
        default: true
    },
    selectData: {
        type: Array,
        default: () => []
    },
    size: {
        type: String,
        default: 'default'
    },
    headerBg: {
        type: Boolean,
        default: true
    }
})

const isLoading = ref(false)
let tableData = reactive<any>({})
const hxPaginationRef = ref(null)

// 获取表格数据
const getTableData = (params) => {
    return new Promise((resolve, reject) => {
        isLoading.value = true
        prams.limit = params.limit || hxPaginationRef.value.defaultLimit
        params.page = params.page || hxPaginationRef.value.defaultPage
        props.requestApi(params).then((res) => {
            tableData.list = res.data.list
            tableData.total = res.data.total
            resolve(res.data)
            setTimeout(() => {
                toggleSelection()
            }, 100)
        })
        .catch(err => {
            reject(err)
        })
        .finally(() => {
            isLoading.value = false
        })
    })
}

// 设置已勾选的列
const hxTableRef = ref(null)
const toggleSelection = () => {
    // 它可以是[id, id] 也可以是 [{id: 0, name: 'xxx'}]
    props.selectData.forEach((payload: any) => {
        let id = 0
        if (typeof payload == 'object') {
            id = payload.id || 0
        } else {
            id = payload
        }
        nextTick(() => {
            tableData.list.find((item) => {
                if (id == item.id) {
                    hxTableRef.value!.toggleRowSelection(item, true)
                }
            })
        })
    })
}


// 获取选中的值
function getSelectionRowsFunc() {
    return hxTableRef.value!.getSelectionRows()
}

defineExpose({
    getTableData,
    getSelectionRowsFunc
})
</script>

<style>
.hx-table-wrapper {
    min-height: 150px;
}
</style>
  • hx-pagination
<template>
    <div class="flex" style="padding-top: 20px;justify-content: flex-end" >
        <el-pagination
            :background="background"
            :total="total"
            :layout="paginationLayout"
            :default-page-size="defaultLimit"
            :page-size="pageSizes"
            :small="small"
            @current-change="$emit('onChangePage', $event)"
            @size-change="$emit('onChangeSize', $event)"
        ></el-pagination>
    </div>
</template>
<script setup lang="ts">
 /**
 * ======= defineProps: ==========
 * small: Boolean  -- 是否使用小型分页样式
 * background: Boolean -- 是否为分页按钮添加背景色
 * total: Number  -- 总条目数
 * paginationLayout: String -- 组件布局,子组件名用逗号分隔
 *
 * ======= defineEmits 自定义事件 ==========
 * onChangePage -- 改变页面时触发
 * onChangeSize -- 改变条数时触发
 *
 * ======== defineExpose 导出方法 =========
 * defaultLimit: 默认条数
 * defaultPage: 默认页面
 *
 */
 
import { defineProps, ref } from 'vue'
 const props = defineProps({
  small: {
    type: Boolean,
    default: true
  },
  total: {
    type: Number,
    default: 50
  },
  background: {
    type: Boolean,
    default: true
  },
  paginationLayout: {
    type: String,
    default: 'total, sizes, prev, pager, next, jumper'
  },
  pageSizes: {
    type: Array,
    default: () => [50, 100, 300, 500, 1000]
  }
})
const emits = defineEmits(['onChangePage', 'onChangeSize'])
const defaultLimit = ref(props.pageSizes[0])
const defaultPage = ref(1)

defineExpose({
  defaultLimit,
  defaultPage
})
</script>

现在搜索模块,表格模块,分页模块组件都创建好了,就可以正式使用啦

  • 正式调用
<template>
 <div>
     <hx-search 
         :model="formParams" 
         :showBtn="['search', 'reset']" 
         @on-search="search_func" 
         @on-reset="reset_func" 
     > 
         <hx-search-item  label="姓名" prop="name" > 
             <el-input v-model="formParams.name" placeholder="请输入姓名搜索" clearable></el-input> 
         </hx-search-item> 
         <hx-search-item  label="分类"  prop="phone"> 
             <el-input v-model="formParams.phone" placeholder="请输入手机号搜索" clearable></el-input> 
         </hx-search-item> 
     </hx-search>
     <hx-table
         ref="hxTableRef"
         stripe
         :requst-api="http://localhost:8000/api/user"
         :column="hxTableColumn"
         :operation="hxTableOperation"
         :selectData="selectData"
         @on-change-page="hxTable_change_page"
         @on-change-size="hxTable_change_size"
     >
         <template #sexScope="{ row }">
             {{ row.sex == '1' ? '男' : row.sex == '0' ? '女' : '-' }}
         </template> 
     </hx-table>
 </div>
</template>
<script setup lang="ts">
import hxSearch from '@/components/hx-search/hx-search.vue'
import hxSearchItem from '@/components/hx-search/hx-search-item.vue'
import hxTable from '@/components/hx-table/hx-table.vue'

// 自定义列
const hxTableColumn = [
    {
        label: '姓名'prop: 'name',
        width: '180'
    },
  {
    label: '性别',
    prop: 'sex',
    width: '130',
    isScope: true
  },
  {
    label: '手机号',
    prop: 'phone'
  },
  {
    label: '证件号码',
    prop: 'certificateNumber'
  }
]

// 操作列
// 操作列
const ksTableOperation = {
  label: '操作列名',
  width: 120,
  columns: [
    {
      text: '编辑',
      type: 'primary',
      link: true,
      plain: false,
      icon: 'Edit',
      click: (row) => {
        console.log('编辑', row)
      }
    },
    {
      popconfirm: true,
      title: '是否删除此条提醒',
      confirmText: '删除',
      width: 180,
      text: '删除',
      type: 'danger',
      link: false,
      plain: false,
      icon: 'Delete',
      click: (row) => {
        console.log('删除', row)
      }
    }
  ]
}

// 表格ref
const hxTableRef = ref(null)
// 表格查询的参数
const formParams = ref({
  'name': '',
  'phone': ''
})

// 获取表格数据
function queryTableData() {
    return new Promise(async (resolve, reject) => {
        const result = await ksTableRef.value.getTableData(formParams.value)
        resolve(result)
    })
}

// 搜索
function search_func() {
    queryTableData()
}
// 重置
function reset_func() {
    queryTableData()
}
 // 改变页数
function hxTable_change_page(val) {
    params.value.page = val
    queryTableData()
}
// 改变条数
function hxTable_change_size(val) {
    params.value.limit = val
    queryTableData()
}
</script>