在做后台管理系统时,通常都会有很多列表页面,所以就寻思着是否封装一下。
如上所示是一个简单的列表页面,有搜索模块,表格模块,分页模块组成。
那么接下来我们就来把它们封装一下
搜索模块
- 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>