目录
- 组件封装
- 搜索组件
- table列表组件
- content组件自定义插槽定制
- modal组件
- 动态获取options数据
组件封装
搜索组件
给页面写一个配置文件,将配置文件传入组件,可直接生成页面,以下面页面为例,
新建src/views/main/system/department/config/search.config.ts:
const searchConfig = {
formItems: [
{
type: 'input',// el组件类型
prop: 'name',
label: '部门名称',
placeholder: '请输入查询的部门名称',
initialValue: 'bbb'// 默认值
},
{
type: 'input',
prop: 'leader',
label: '部门领导',
placeholder: '请输入查询的领导名称'
},
{
type: 'date-picker',
prop: 'createAt',
label: '创建时间'
}
]
}
export default searchConfig
在department.vue文件中引入配置文件并传给search子组件:
<page-search
:search-config="searchConfig"
@query-click="handleQueryClick"
@reset-click="handleResetClick"
/>
<script>
import contentConfig from './config/content.config'
</script>
search子组件使用defineProps接收配置文件中的数据,并遍历展示
<template>
<div class="search">
<!-- 1.输入搜索关键字的表单 -->
<el-form
:model="searchForm"
ref="formRef"
:label-width="searchConfig.labelWidth ?? '80px'"
size="large"
>
<el-row :gutter="20">
<template v-for="item in searchConfig.formItems" :key="item.prop">
<!-- span的值也可以配置 -->
<el-col :span="8">
<!-- :label="item.label" :prop="item.prop"可以写成v-bind="item"来绑定所有属性 -->
<el-form-item :label="item.label" :prop="item.prop">
<!-- 也可以用动态组件 -->
<!-- <component :is="‘el-${item.type}’" xxx></component>-->
<template v-if="item.type === 'input'">
<el-input
v-model="searchForm[item.prop]"
:placeholder="item.placeholder"
/>
</template>
<template v-if="item.type === 'date-picker'">
<el-date-picker
v-model="searchForm[item.prop]"
type="daterange"
range-separator="-"
start-placeholder="开始时间"
end-placeholder="结束时间"
/>
</template>
<template v-if="item.type === 'select'">
<el-select
v-model="searchForm[item.prop]"
:placeholder="item.placeholder"
style="width: 100%"
>
<template v-for="option in item.options" :key="option.value">
<el-option :label="option.label" :value="option.value" />
</template>
</el-select>
</template>
</el-form-item>
</el-col>
</template>
</el-row>
</el-form>
<!-- 2.重置和搜索的按钮 -->
<div class="btns">
<el-button icon="Refresh" @click="handleResetClick">重置</el-button>
<el-button icon="Search" type="primary" @click="handleQueryClick"
>查询</el-button
>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import type { ElForm } from 'element-plus'
// 定义自定义事件/接收的属性,从伏组件
interface IProps {
searchConfig: {
labelWidth?: string
formItems: any[]
}
}
const emit = defineEmits(['queryClick', 'resetClick'])
const props = defineProps<IProps>()
// 定义form的数据
const initialForm: any = {}
for (const item of props.searchConfig.formItems) {
initialForm[item.prop] = item.initialValue ?? ''
}
const searchForm = reactive(initialForm)
// 重置操作
const formRef = ref<InstanceType<typeof ElForm>>()
function handleResetClick() {
// 1.form中的数据全部重置
formRef.value?.resetFields()
// 2.将事件出去, content内部重新发送网络请求
emit('resetClick')
}
function handleQueryClick() {
emit('queryClick', searchForm)
}
</script>
<style lang="less" scoped>
.search {
background-color: #fff;
padding: 20px;
.el-form-item {
padding: 20px 30px;
margin-bottom: 0;
}
.btns {
text-align: right;
padding: 0 50px 10px 0;
.el-button {
height: 36px;
}
}
}
</style>
table列表组件
创建src/views/main/system/department/config/content.config.ts文件,将列表标题和按钮标题写在header里,将table列表展示的列信息写在propsList中
const contentConfig = {
pageName: 'department',
header: {
title: '部门列表',
btnTitle: '新建部门'
},
propsList: [
// 1.selection 2.index
{ type: 'selection', label: '选择', width: '80px' },
{ type: 'index', label: '序号', width: '80px' },
{ type: 'normal', label: '部门名称', prop: 'name', width: '150px' },
{ type: 'normal', label: '部门领导', prop: 'leader', width: '150px' },
{ type: 'normal', label: '上级部门', prop: 'parentId', width: '150px' },
{ type: 'timer', label: '创建时间', prop: 'createAt' },
{ type: 'timer', label: '更新时间', prop: 'updateAt' },
{ type: 'handler', label: '操作', width: '150px' }
]
}
export default contentConfig
在department.vue文件中引入配置文件并传给page-content子组件(代码参考前面的,此处不在赘述),page-content子组件使用defineProps接收并遍历展示
<template>
<div class="content">
<div class="header">
<h3 class="title">{{ contentConfig?.header?.title ?? '数据列表' }}</h3>
<el-button type="primary" @click="handleNewUserClick">
{{ contentConfig?.header?.btnTitle ?? '新建数据' }}
</el-button>
</div>
<div class="table">
<el-table
:data="pageList"
border
style="width: 100%"
v-bind="contentConfig.childrenTree"
>
<template v-for="item in contentConfig.propsList" :key="item.prop">
<template v-if="item.type === 'timer'">
<el-table-column align="center" v-bind="item">
<template #default="scope">
{{ formatUTC(scope.row[item.prop]) }}
</template>
</el-table-column>
</template>
<template v-else-if="item.type === 'handler'">
<el-table-column align="center" v-bind="item">
<template #default="scope">
<el-button
size="small"
icon="Edit"
type="primary"
text
@click="handleEditBtnClick(scope.row)"
>
编辑
</el-button>
<el-button
size="small"
icon="Delete"
type="danger"
text
@click="handleDeleteBtnClick(scope.row.id)"
>
删除
</el-button>
</template>
</el-table-column>
</template>
<template v-else-if="item.type === 'custom'">
<el-table-column align="center" v-bind="item">
<template #default="scope">
<slot
:name="item.slotName"
v-bind="scope"
:prop="item.prop"
hName="why"
></slot>
</template>
</el-table-column>
</template>
<template v-else>
<el-table-column align="center" v-bind="item" />
</template>
</template>
</el-table>
</div>
<div class="pagination">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 30]"
layout="total, sizes, prev, pager, next, jumper"
:total="pageTotalCount"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import useSystemStore from '@/store/main/system/system'
import { formatUTC } from '@/utils/format'
import { ref } from 'vue'
interface IProps {
contentConfig: {
pageName: string
header?: {
title?: string
btnTitle?: string
}
propsList: any[]
childrenTree?: any
}
}
const props = defineProps<IProps>()
// 定义事件
const emit = defineEmits(['newClick', 'editClick'])
// 1.发起action,请求usersList的数据
const systemStore = useSystemStore()
const currentPage = ref(1)
const pageSize = ref(10)
fetchPageListData()
// 2.获取usersList数据,进行展示
const { pageList, pageTotalCount } = storeToRefs(systemStore)
// 3.页码相关的逻辑
function handleSizeChange() {
fetchPageListData()
}
function handleCurrentChange() {
fetchPageListData()
}
// 4.定义函数, 用于发送网络请求
function fetchPageListData(formData: any = {}) {
// 1.获取offset/size
const size = pageSize.value
const offset = (currentPage.value - 1) * size
const pageInfo = { size, offset }
// 2.发起网络请求
const queryInfo = { ...pageInfo, ...formData }
systemStore.postPageListAction(props.contentConfig.pageName, queryInfo)
}
// 5.删除/新建/编辑的操作
function handleDeleteBtnClick(id: number) {
systemStore.deletePageByIdAction(props.contentConfig.pageName, id)
}
function handleNewUserClick() {
emit('newClick')
}
function handleEditBtnClick(itemData: any) {
emit('editClick', itemData)
}
defineExpose({ fetchPageListData })
</script>
<style lang="less" scoped>
.content {
margin-top: 20px;
padding: 20px;
background-color: #fff;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 10px;
.title {
font-size: 22px;
}
}
.table {
:deep(.el-table__cell) {
padding: 12px 0;
}
.el-button {
margin-left: 0;
padding: 5px 8px;
}
}
.pagination {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
</style>
content组件自定义插槽定制
目前的设计不支持定制化,就是在子组件中,对propsList的类型判断不够全,确实也无法判断全,因为就算把所有的el组件都判断一遍,也会有用户自定义组件,所以本节讨论如何对table列表的某一列进行定制化。在content.config.ts中添加自定义的内容:
const contentConfig = {
pageName: 'department',
header: {
title: '部门列表',
btnTitle: '新建部门'
},
propsList: [
// 1.selection 2.index
{ type: 'selection', label: '选择', width: '80px' },
{ type: 'index', label: '序号', width: '80px' },
{ type: 'normal', label: '部门名称', prop: 'name', width: '150px' },
{ type: 'normal', label: '部门领导', prop: 'leader', width: '150px' },
{ type: 'normal', label: '上级部门', prop: 'parentId', width: '150px' },
{
type: 'custom',
label: '部门领导',
prop: 'leader',
width: '150px',
slotName: 'leader'
},
{
type: 'custom',
label: '上级部门',
prop: 'parentId',
width: '150px',
slotName: 'parent'
},
{ type: 'timer', label: '创建时间', prop: 'createAt' },
{ type: 'timer', label: '更新时间', prop: 'updateAt' },
{ type: 'handler', label: '操作', width: '150px' }
]
}
export default contentConfig
在page-content子组件遍历propsList的时候,增加对custom类型的判断,既然要自定义,那肯定得使用el-table-column的默认插槽插入自定义的内容,但是这个自定义的东西是不确定的(不固定的),所以在el-table-column的默认插槽中再插入一个插槽,可能表格中有很多可以自定义的列,就会有多个插槽,所以这个插槽得是具名插槽,不然在使用的时候根本不知道数据要插入哪个插槽,且具名插槽的名字由配置来决定:
<template v-else-if="item.type === 'custom'">
<el-table-column align="center" v-bind="item">
<template #default="scope">
<slot
:name="item.slotName"
></slot>
</template>
</el-table-column>
</template>
父组件使用:
- contentConfig是引入的content.config.ts配置文件
- 通过
<template #leader>
制定插入的内容
<page-content
:content-config="contentConfig"
ref="contentRef"
@new-click="handleNewClick"
@edit-click="handleEditClick"
>
<template #leader>哈哈哈</template>
<template #parent>呵呵呵</span></template>
</page-content>
但是现在父组件传入的东西只能是写死的,父组件如果想根据这行数据来插入内容应该怎么办?在page-content子组件中将scope传给插槽,父组件使用插槽时接收数据。page-content子组件代码:
<template v-else-if="item.type === 'custom'">
<el-table-column align="center" v-bind="item">
<template #default="scope">
<slot
:name="item.slotName"
v-bind="scope"
></slot>
</template>
</el-table-column>
</template>
父组件接收数据:#leader="scope"
,并且可以通过添加class的方式,给这一列添加样式。
<page-content
:content-config="contentConfig"
ref="contentRef"
@new-click="handleNewClick"
@edit-click="handleEditClick"
>
<template #leader="scope">
<span class="leader">哈哈哈: {{ scope.row.leader }}</span>
</template>
<template #parent="scope">
<span class="parent">呵呵呵: {{ scope.row.parent }}</span>
</template>
</page-content>
<style scoped>
.leader {
color: red;
}
.parent {
color: blue;
}
</style>
如果直接写scope.row.leader
,这个leader也是写死的,如果希望从配置中读取,取prop的值,那么page-content子组件需要将prop数据传给插槽(类似于父组件给子组件传值),代码如下:
<template v-else-if="item.type === 'custom'">
<el-table-column align="center" v-bind="item">
<template #default="scope">
<slot
:name="item.slotName"
v-bind="scope"
:prop="item.prop"
></slot>
</template>
</el-table-column>
</template>
父组件在使用插槽时,直接从传过来的scope.prop中获取,因为传过来的所有东西都在scope上面,而item.prop是传到了prop上,所以通过scope.prop获取
<page-content
:content-config="contentConfig"
ref="contentRef"
@new-click="handleNewClick"
@edit-click="handleEditClick"
>
<template #leader="scope">
<span class="leader">哈哈哈: {{ scope.row[scope.prop] }}</span>
</template>
<template #parent="scope">
<span class="parent">呵呵呵: {{ scope.row[scope.prop] }}</span>
</template>
</page-content>
modal组件
const initialData: any = {}
for (const item of props.modalConfig.formItems) {
initialData[item.prop] = item.initialValue ?? ''
}
const formData = reactive<any>(initialData)
如果像以前一样,在定义formData的时候就给他设置默认值,默认值是设置不上去的,这是因为一开始modal组件是不展示的,所以在modal弹出来之后再通过遍历modalConfig.formItems来设置默认值
function setModalVisible(isNew: boolean = true, itemData?: any) {
dialogVisible.value = true
isNewRef.value = isNew
if (!isNew && itemData) {
// 编辑数据
for (const key in formData) {
formData[key] = itemData[key]
}
editData.value = itemData
} else {
// 新建数据
for (const key in formData) {
const item = props.modalConfig.formItems.find((item) => item.prop === key)
formData[key] = item ? item.initialValue : ''
}
editData.value = null
}
}