vue+element后台管理,table,form,pagination等二次分装,实现5到20行完成一个主页面列表,一天轻轻松松写20个页面,再也不用大量的Ctrl+c Ctrl+v了
Posted 郭顺杰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue+element后台管理,table,form,pagination等二次分装,实现5到20行完成一个主页面列表,一天轻轻松松写20个页面,再也不用大量的Ctrl+c Ctrl+v了相关的知识,希望对你有一定的参考价值。
废话
最近看有小伙伴说后台管理系统,表格特别多,一个页面上百行代码,每天上班,大量的粘贴复制,一个个去改字段,特别累,问有没有简单高效的方法,答案是当然有,把一个页面封装成一个组件,需要什么传什么不就好了,所以本篇文章,只分享思路和简单的组件封装组合,不喜勿喷,先看效果,只需要传不同的值,就能定制自己想要的表格页面,像下面这样,短短几行
就能实现这样的列表
这样的
正文
最终的目录结构,crud/index.vue是我们的主体组件,search/index.vue是我们顶部条件筛选组件,table/index.vue是我们表格分页组件,page1.vue,page2.vue是demo页面,
思路
以第一张列表为例,有一个页面名称,有普通的条件筛选,input,select,date,后面有查询,重置,导出下载等想要的操作,表格内容,有插槽固定列,操作列等,下面有分页等,下面我们一个个写(最后附上完整代码)
第一步
我们也先建一个公共组件,src/components/crud/index.vue,因为我们整个项目都要用,所以我们全局挂载,在main.js 里
import curd from '@/components/crud/index'
Vue.component('h-crud', curd)
第二步
要有个页面名称,XX管理,XX统计等,所以我们要传一个title过去
page1.vue
<h-crud
:title="title"
</h-crud>
data()
return
// 页面标题
title: "复杂列表"
crud.vue
<div class="_title"> title </div>
props:
title:
type: String,
default: () =>
return "";
第三步
检索条件,有的页面有,有的页面没有,所以不需要必填
由图分析,里面有input,select,date,不同的筛选组件,所以我们用type字段区分一下,我们点击查询要知道input,select,date字段名和输入值,所以要有个value值,prop字段名。外面还要有一个表单的size组件的尺寸,labelWidth表单域标签的宽度,后面还有,查询,重置,必须的两个操作按钮,和其他操作按钮(导出,下载等),所以
page1.vue
我们传过去一个对象searchOption,组件emit回来,search查询方法,reset重置方法,otherBtnsClick其他按钮方法
<h-crud
:search-option="searchOption"
@search="search"
@reset="reset"
@otherBtnsClick="otherBtnsClick"
>
</h-crud>
data()
return
// 顶部表单检索内容,非必填
searchOption:
// 表单内组件的尺寸
size: "small",
// 表单域标签的宽度
labelWidth: "80px",
// 检索列表
searchList: [
/*
label 标签名,必填
type 表单类型,必填
value 绑定值,必填
prop 字段名,必填
placeholder 提示语,非必填,默认请输入/请选择
clearable 是否可清空,非必填,默认清空
optionData 下拉菜单数据,type为select时填
type为date时
format 显示在输入框中的格式
valueFormat 返回格式
*/
label: "审批人:", type: "input", value: "", prop: "user" ,
label: "活动区域:",
type: "select",
value: "",
prop: "region",
optionData: [
value: "shanghai", label: "区域一" ,
value: "beijing", label: "区域二", disabled: true
]
,
label: "日期:",
type: "date",
value: "",
prop: "date",
placeholder: "选择日期",
format: "yyyy 年 MM 月 dd 日",
valueFormat: "yyyy-MM-dd"
],
// 其它操作按钮,默认已有查询重置,不必填,可不要
otherBtns: [
title: "导出", prop: "export", type: "danger" ,
title: "下载", prop: "downLoad", type: "success"
]
,
crud.vue
为了便于扩展,筛选内容比较多,我们在抽出一个Search组件出来,把对象searchOption传过去,组件emit回去,search查询方法,reset重置方法,otherBtnsClick其他按钮方法
<div class="_search_content">
<Search
:search-option="searchOption"
@search="
Obj =>
$emit('search', Obj);
"
@reset="handldReset"
@otherBtnsClick="
prop =>
$emit('otherBtnsClick', prop);
"
></Search>
</div>
//引入
import Search from "../Search/index";
//注册
components: Search,
//接收
props:
title:
type: String,
default: () =>
return "";
,
searchOption:
type: Object,
default: () =>
return ;
,
search/index.vue
//一个行内表单
<template>
<div class="_search">
<!-- 先判断searchOption存不存在,因为有的表格没有,
然后把表单的size,label-wdith,写上 -->
<el-form
v-if="searchOption && Object.keys(searchOption).length"
:inline="true"
:size="searchOption.size"
:label-width="searchOption.labelWidth"
>
<el-form-item
v-for="item in searchOption.searchList"
:key="item.label"
:label="item.label"
>
<!-- 判断type类型,输入框时 -->
<template v-if="item.type === 'input'">
<el-input
:clearable="item.clearable || true"
v-model="item.value"
:placeholder="item.placeholder || '请输入'"
></el-input>
</template>
<!-- 判断type类型,选择框时 -->
<template v-else-if="item.type === 'select'">
<el-select
v-model="item.value"
:placeholder="item.placeholder || '请选择'"
:clearable="item.clearable || true"
>
<el-option
v-for="option in item.optionData || []"
:key="option.value"
:label="option.label"
:value="option.value"
:disabled="option.disabled"
/>
</el-select>
</template>
<!-- 判断type类型,日期时 -->
<template v-else-if="item.type === 'date'">
<el-date-picker
v-model="item.value"
type="date"
:placeholder="item.placeholder || '请选择'"
:format="item.format || 'yyyy 年 MM 月 dd 日'"
:value-format="item.valueFormat || 'yyyy-MM-dd'"
>
</el-date-picker>
</template>
<!--
...
后续可根据自己的项目添加其他筛选组件,demo只写了input selece date
-->
</el-form-item>
<!-- 查询重置 -->
<el-form-item>
<el-button type="primary" :size="searchOption.size" @click="searchClick"
>查询</el-button
>
<el-button :size="searchOption.size" @click="resetClick"
>重置</el-button
>
<!-- 其它操作按钮,由于有的不需要,所以先判断有没有 -->
<template
v-if="searchOption.otherBtns && searchOption.otherBtns.length > 0"
>
<el-button
v-for="(btn, index) in searchOption.otherBtns"
:key="index"
:size="btn.size"
:type="btn.type"
@click="otherBtnsClick(btn)"
> btn.title </el-button
>
</template>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default
name: "search",
props:
searchOption:
type: Object,
default: () =>
return ;
,
data()
return ;
,
methods:
//遍历检索条件把值返回出去
searchClick()
let Obj = ;
this.searchOption.searchList.forEach(item =>
if (item.value !== "")
Obj[item.prop] = item.value;
);
this.$emit("search", Obj);
,
resetClick()
this.$emit("reset");
,
//其他操作按钮
otherBtnsClick(btn)
this.$emit("otherBtnsClick", btn.prop);
,
created()
// console.log(this.searchOption);
;
</script>
<style lang="less" scoped></style>
第四步
表格主体,我们给拆成表格列,表格数据,分页数据,三个即可,由于我们有时需要操作处理列里面的数据,所以我们要有个具名插槽,哪个列需要操作写哪个,不需要不写,如下图,状态,数据是1,2,我们改成我们想要的内容,操作列也可以像普通列一样写成插槽,本demo,写成数据传过去在组件里写的
page1.vue
传过去一个tableOption列表对象,接收columnClick操作列方法,handleSizeChange,handleCurrentChange,分页方法,不要操作列可不写,
分页数据也传过去
<h-crud
:table-option="tableOption"
@columnClick="columnClick"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<!-- 普通列插槽 -->
<template slot="status" slot-scope=" row ">
<span v-if="row.status && row.status === 1" style="color: #F53F3F;"
>错误,红的</span
>
<span v-else style="color:green">进行中,绿的</span>
</template>
</h-crud>
// 列表数据
tableOption:
// 表格列,必填
tableColumn: [
prop: "name",
label: "姓名"
,
prop: "status",
label: "状态"
,
prop: "address",
label: "描述",
minWidth: "150"
,
prop: "date",
label: "日期",
minWidth: "200"
],
// 表格数据,必填
tableData: [
id: 1,
name: "张三",
status: 1,
address: "我是描述",
date: "2022-01-01"
,
id: 2,
name: "李四",
status: 2,
address: "我是描述2",
date: "2022-01-02"
],
// 表格操作按钮,非必填
operationColumn:
// 是否fixed
fixed: "right",
// 最小宽度
minWidth: "250",
btns: [
title: "编辑", prop: "edit", type: "primary", size: "small" ,
title: "删除", prop: "delete", type: "danger", size: "small" ,
title: "查看", prop: "dtl", type: "info", size: "small"
]
,
//分页数据
page:
currentPage: 1,
pageSize: 10,
total: 20
crud.vue
//为了便于扩展,表格内容比较多,我们在抽出一个table组件出来
<div class="_table_content">
<Table
:table-option="tableOption"
@columnClick="
(prop, row) =>
$emit('columnClick', prop, row);
"
@size-change="
val =>
$emit('size-change', val);
"
@current-change="
val =>
$emit('current-change', val);
"
>
<template
v-for="item in tableOption.tableColumn"
:slot="item.prop"
slot-scope=" row "
>
<slot :name="item.prop" :row="row">
<template>
row[item.prop]
</template>
</slot>
</template>
</Table>
</div>
Table/index.vue
<template>
<div class="_table">
<!-- <div class="_title">共 tableOption.page.total 条记录</div> -->
<el-table
:data="tableOption.tableData"
stripe
border
:header-cell-style=" backgroundColor: 'rgb(244, 244, 245)' "
style="width: 100%"
>
<el-table-column
v-for="(item, index) in tableOption.tableColumn"
:key="index"
:label="item.label"
:min-width="item.minWidth"
>
<!-- 普通列插槽 -->
<template slot-scope=" row ">
<slot :name="item.prop" :row="row">
<template>
row[item.prop]
</template>
</slot>
</template>
</el-table-column>
<el-table-column
v-if="
tableOption.operationColumn &&
Object.keys(tableOption.operationColumn).length
"
:fixed="tableOption.operationColumn.fixed"
label="操作"
:min-width="tableOption.operationColumn.minWidth"
>
<template slot-scope="scope">
<el-button
v-for="(btn, index) in tableOption.operationColumn.btns"
:key="index"
:type="btn.type"
:size="btn.size || 'small'"
@click="
() =>
$emit('columnClick', btn.prop, scope.row);
"
> btn.title </el-button
>
</template>
</el-table-column>
</el-table>
<!-- :hide-on-single-page="tableOption.page.total >= 0" -->
<el-pagination
background
style="padding:20px 0;text-align:right"
:current-page="tableOption.page.currentPage"
:page-sizes="[10, 20, 50]"
:page-size="tableOption.page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="tableOption.page.total"
@size-change="
val =>
$emit('size-change', val);
"
@current-change="
val =>
$emit('current-change', val);
"
>
</el-pagination>
</div>
</template>
<script>
export default
name: "Table",
props:
tableOption:
type: Object,
default: () =>
return ;
,
data()
return ;
,
methods: ,
created()
;
</script>
<style lang="less" scoped>
._table
border-top: 5px solid #f6f8fc;
padding: 16px;
._title
font-weight: 600;
margin-bottom: 20px;
</style>
到此,封装结束
最终代码,
page1.vue
<template>
<h-crud
:title="title"
:search-option="searchOption"
:table-option="tableOption"
@search="search"
@reset="reset"
@otherBtnsClick="otherBtnsClick"
@columnClick="columnClick"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<!-- 普通列插槽 -->
<template slot="status" slot-scope=" row ">
<span v-if="row.status && row.status === 1" style="color: #F53F3F;"
>错误,红的</span
>
<span v-else style="color:green">进行中,绿的</span>
</template>
</h-crud>
</template>
<script>
export default
data()
return
// 页面标题
title: "复杂列表",
// 顶部表单检索内容,非必填
searchOption:
// 表单内组件的尺寸
size: "small",
// 表单域标签的宽度
labelWidth: "80px",
// 检索列表
searchList: [
/*
label 标签名,必填
type 表单类型,必填
value 绑定值,必填
prop 字段名,必填
placeholder 提示语,非必填,默认请输入/请选择
clearable 是否可清空,非必填,默认清空
optionData 下拉菜单数据,type为select时填
type为date时
format 显示在输入框中的格式
valueFormat 返回格式
*/
label: "审批人:", type: "input", value: "", prop: "user" ,
label: "活动区域:",
type: "select",
value: "",
prop: "region",
optionData: [
value: "shanghai", label: "区域一" ,
value: "beijing", label: "区域二", disabled: true
]
,
label: "日期:",
type: "date",
value: "",
prop: "date",
placeholder: "选择日期",
format: "yyyy 年 MM 月 dd 日",
valueFormat: "yyyy-MM-dd"
],
// 其它操作按钮,默认已有查询重置,不必填,可不要
otherBtns: [
title: "导出", prop: "export", type: "danger" ,
title: "下载", prop: "downLoad", type: "success"
]
,
// 列表数据
tableOption:
// 表格列,必填
tableColumn: [
prop: "name",
label: "姓名"
,
prop: "status",
label: "状态"
,
prop: "address",
label: "描述",
minWidth: "150"
,
prop: "date",
label: "日期",
minWidth: "200"
],
// 表格数据,必填
tableData: [
id: 1,
name: "张三",
status: 1,
address: "我是描述",
date: "2022-01-01"
,
id: 2,
name: "李四",
status: 2,
address: "我是描述2",
date: "2022-01-02"
],
// 表格操作按钮,非必填
operationColumn:
// 是否fixed
fixed: "right",
// 最小宽度
minWidth: "250",
btns: [
title: "编辑", prop: "edit", type: "primary", size: "small" ,
title: "删除", prop: "delete", type: "danger", size: "small" ,
title: "查看", prop: "dtl", type: "info", size: "small"
]
,
page:
currentPage: 1,
pageSize: 10,
total: 20
;
,
methods:
// 条件检索
search(obj)
console.log(obj);
,
// 重置
reset()
console.log("重置");
,
// 其他操作按钮
otherBtnsClick(prop)
switch (prop)
case "export":
console.log("我是导出");
break;
case "downLoad":
console.log("我是下载");
break;
default:
break;
,
// 表格列操作
columnClick(prop, row)
switch (prop)
case "edit":
console.log("编辑", row);
break;
case "delete":
this.$confirm("此操作将删除该数据,是否确认删除?", "删除",
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
)
.then(() =>
this.$message(
type: "success",
message: "删除成功!"
);
)
.catch(() =>
this.$message(
type: "info",
message: "已取消删除"
);
);
break;
case "dtl":
console.log("查看")
break;
default:
break;
,
// 分页
handleSizeChange(val)
console.log(`每页 $val 条`);
,
handleCurrentChange(val)
console.log(`当前页: $val`);
,
created()
;
</script>
<style lang="less" scoped></style>
crud/index.vue
<template>
<div class="_crud">
<div class="_title"> title </div>
<div class="_search_content">
<Search
:search-option="searchOption"
@search="
Obj =>
$emit('search', Obj);
"
@reset="handldReset"
@otherBtnsClick="
prop =>
$emit('otherBtnsClick', prop);
"
></Search>
</div>
<div class="_table_content">
<Table
:table-option="tableOption"
@columnClick="
(prop, row) =>
$emit('columnClick', prop, row);
"
@size-change="
val =>
$emit('size-change', val);
"
@current-change="
val =>
$emit('current-change', val);
"
>
<template
v-for="item in tableOption.tableColumn"
:slot="item.prop"
slot-scope=" row "
>
<slot :name="item.prop" :row="row">
<template>
row[item.prop]
</template>
</slot>
</template>
</Table>
</div>
</div>
</template>
<script>
import Search from "../Search/index";
import Table from "../Table/index";
export default
name: "crud",
components: Search, Table ,
props:
title:
type: String,
default: () =>
return "";
,
searchOption:
type: Object,
default: () =>
return ;
,
tableOption:
type: Object,
default: () =>
return ;
,
data()
return ;
,
methods:
// 重置
handldReset()
this.searchOption.searchList.forEach(item =>
const value = item.value;
if (value instanceof Array)
this.$set(item, "value", []);
else if (value instanceof Object)
this.$set(item, "value", );
else
this.$set(item, "value", "");
);
this.$emit("reset");
,
created()
;
</script>
<style lang="less" scoped>
._crud
._title
font-size: 16px;
font-weight: 600;
padding: 16px;
._search_content
padding: 0 16px;
border-radius: 10px;
</style>
Search/index.vue
<template>
<div class="_search">
<el-form
v-if="searchOption && Object.keys(searchOption).length"
:inline="true"
:size="searchOption.size"
:label-width="searchOption.labelWidth"
>
<el-form-item
v-for="item in searchOption.searchList"
:key="item.label"
:label="item.label"
>
<template v-if="item.type === 'input'">
<el-input
:clearable="item.clearable || true"
v-model="item.value"
:placeholder="item.placeholder || '请输入'"
></el-input>
</template>
<template v-else-if="item.type === 'select'">
<el-select
v-model="item.value"
:placeholder="item.placeholder || '请选择'"
:clearable="item.clearable || true"
>
<el-option
v-for="option in item.optionData || []"
:key="option.value"
:label="option.label"
:value="option.value"
:disabled="option.disabled"
/>
</el-select>
</template>
<template v-else-if="item.type === 'date'">
<el-date-picker
v-model="item.value"
type="date"
:placeholder="item.placeholder || '请选择'"
:format="item.format || 'yyyy 年 MM 月 dd 日'"
:value-format="item.valueFormat || 'yyyy-MM-dd'"
>
</el-date-picker>
</template>
<!--
...
后续可根据自己的项目添加其他筛选组件,demo只写了input selece date
-->
</el-form-item>
<el-form-item>
<el-button type="primary" :size="searchOption.size" @click="searchClick"
>查询</el-button
>
<el-button :size="searchOption.size" @click="resetClick"
>重置</el-button
>
<!-- 其它操作按钮 -->
<template
v-if="searchOption.otherBtns && searchOption.otherBtns.length > 0"
>
<el-button
v-for="(btn, index) in searchOption.otherBtns"
:key="index"
:size="btn.size"
:type="btn.type"
@click="otherBtnsClick(btn)"
> btn.title </el-button
>
</template>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default
name: "search",
props:
searchOption:
type: Object,
default: () =>
return ;
,
data()
return ;
,
methods:
searchClick()
let Obj = ;
this.searchOption.searchList.forEach(item =>
if (item.value !== "")
Obj[item.prop] = item.value;
);
this.$emit("search", Obj);
,
resetClick()
this.$emit("reset");
,
otherBtnsClick(btn)
this.$emit("otherBtnsClick", btn.prop);
,
created()
// console.log(this.searchOption);
;
</script>
<style lang="less" scoped></style>
Table/index.vue
<template>
<div class="_table">
<!-- <div class="_title">共 tableOption.page.total 条记录</div> -->
<el-table
:data="tableOption.tableData"
stripe
border
:header-cell-style=" backgroundColor: 'rgb(244, 244, 245)' "
style="width: 100%"
>
<el-table-column
v-for="(item, index) in tableOption.tableColumn"
:key="index"
:label="item.label"
:min-width="item.minWidth"
>
<!-- 普通列插槽 -->
<template slot-scope=" row ">
<slot :name="item.prop" :row="row">
<template>
row[item.prop]
</template>
</slot>
</template>
</el-table-column>
<el-table-column
v-if="
tableOption.operationColumn &&
Object.keys(tableOption.operationColumn).length
"
:fixed="tableOption.operationColumn.fixed"
label="操作"
:min-width="tableOption.operationColumn.minWidth"
>
<template slot-scope="scope">
<el-button
v-for="(btn, index) in tableOption.operationColumn.btns"
:key="index"
:type="btn.type"
:size="btn.size || 'small'"
@click="
() =>
$emit('columnClick', btn.prop, scope.row);
"
> btn.title </el-button
>
</template>
</el-table-column>
</el-table>
<!-- :hide-on-single-page="tableOption.page.total >= 0" -->
<el-pagination
background
style="padding:20px 0;text-align:right"
:current-page="tableOption.page.currentPage"
:page-sizes="[10, 20, 50]"
:page-size="tableOption.page.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="tableOption.page.total"
@size-change="
val =>
$emit('size-change', val);
"
@current-change="
val =>
$emit('current-change', val);
"
>
</el-pagination>
</div>
</template>
<script>
export default
name: "Table",
props:
tableOption:
type: Object,
default: () =>
return ;
,
data()
return ;
,
methods: ,
created()
;
</script>
<style lang="less" scoped>
._table
border-top: 5px solid #f6f8fc;
padding: 16px;
._title
font-weight: 600;
margin-bottom: 20px;
</style>
配置简单的列表
page2.vue
<template>
<h-crud
:title="title"
:table-option="tableOption"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
</h-crud>
</template>
<script>
export default
data()
return
// 页面标题
title: "简单的列表",
// 列表数据
tableOption:
// 表格列,必填
tableColumn: [
prop: "name",
label: "姓名"
,
prop: "status",
label: "状态"
,
prop: "address",
label: "描述",
minWidth: "150"
,
prop: "date",
label: "日期",
minWidth: "200"
],
// 表格数据,必填
tableData: [
id: 1,
name: "张三",
status: 1,
address: "我是描述",
date: "2022-01-01"
,
id: 2,
name: "李四",
status: 2,
address: "我是描述2",
date: "2022-01-02"
],
page:
currentPage: 1,
pageSize: 10,
total: 20
;
,
methods:
// 分页
handleSizeChange(val)
console.log(`每页 $val 条`);
,
handleCurrentChange(val)
console.log(`当前页: $val`);
,
created()
;
</script>
<style lang="less" scoped></style>
到此结束,后面在写页面,只需要在下面data里传自己想要的操作,想要的数据即可,一天写二十个页面,轻轻松松,具体效果,操作按钮可看demo页面,console打印值
然后,在对应方法里写自己的方法即可
笔记
1、按照自己文件路径去写,粘贴复制时看着里面路径,
2、注释复制完了可以删了,或者看好别注释错了,出现低级的页面报错
3、本片文章,只分享封装思路,你可以在此基础上根据自己需求,继续扩展,封装详情页,各种其他组件
4、喜欢的话,收藏点个赞,哈哈哈,感谢
以上是关于vue+element后台管理,table,form,pagination等二次分装,实现5到20行完成一个主页面列表,一天轻轻松松写20个页面,再也不用大量的Ctrl+c Ctrl+v了的主要内容,如果未能解决你的问题,请参考以下文章
前端Vue+Element UI案例:通用后台管理系统-用户管理:Table表格增删查改Pagination分页搜索框
前端Vue+Element UI案例:通用后台管理系统-Home组件:卡片表格
一个后台管理平台,用vue+elementUI写的,有个附件上传之后,下载不下来
vue+element后台管理,table,form,pagination等二次分装,实现5到20行完成一个主页面列表,一天轻轻松松写20个页面,再也不用大量的Ctrl+c Ctrl+v了