1. 子组件修改父组件传递过来的数据
1.1. 单向数据流
违反了单向数据流原则。 所有的 prop都使得其父子prop之间形成了一个单向下行绑定:父级prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。另外,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。一个父组件下不只有你一个子组件。 同样,使用这份 prop 数据的也不只有你一个子组件。 如果每个子组件都能修改 prop 的话,将会导致修改数据的源头不止一处。所以我们需要将修改数据的源头统一为父组件,子组件像要改 prop 只能委托父组件帮它,从而保证数据修改源唯一。易于监测数据的流动,出现了错误可以更加迅速的定位到错误发生的位置。
1.2. 传的是基本类型
parent.vue:
<template>
<div>
<h1>我是父组件</h1>
{{ num }}
<button @click="num++">改变数据</button>
<hr>
<child :count="num"/>
</div>
</template>
child.vue:
<template>
<div>
<h2>我是子组件</h2>
{{ count }}
<button @click="changeData">改变数据</button>
</div>
</template>
<script>
export default {
props: ['count'],
methods: {
changeData() {
this.count++
}
}
}
</script>

结论:
- 父组件改变数据子组件会同步更新而且不会报错及警告
- 子组件改变数据只有子组件中的数据变化了父组件并没有被修改而且还报错了
但是如果父传子传递的是对象子组件是可以修改父组件传递过来的数据的
1.3. 传的是引用类型
parent.vue:
<template>
<div>
<h1>我是父组件</h1>
{{ obj.num }}
<button @click="obj.num++">改变数据</button>
<hr>
<child :count="obj"/>
</div>
</template>
<script>
import Child from './child'
export default {
components: {
Child
},
data() {
return {
obj: {
num: 0
}
}
}
}
</script>
child.vue:
<template>
<div>
<h2>我是子组件</h2>
{{ count.num }}
<button @click="changeData">改变数据</button>
</div>
</template>
<script>
export default {
props: ['count'],
methods: {
changeData() {
this.count.num++
}
}
}
</script>

结论:
- 父组件中的值改变子组件中的会同步更新
- 在子组件中修改父组件传递过来的值父组件也会同步更新
原因:
在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态
1.4. 解决方案父子组件联动
1.4.1. 子通知父修改
子传父,通知父组件配合完成修改
child.vue:
<template>
<div>
<h2>我是子组件</h2>
{{ count }}
<button @click="sendChild">改变数据</button>
</div>
</template>
<script>
export default {
props: ['count'],
methods: {
sendChild() {
this.$emit('receive')
}
}
}
</script>
parent.vue:
<template>
<div>
<h1>我是父组件</h1>
{{ num }}
<button @click="num++">改变数据</button>
<hr>
<!-- 第三步:使用组件 -->
<child :count="num" @receive="changeNum"/>
</div>
</template>
<script>
// 第一步:导入组件
import Child from './child'
export default {
// 第二步:注册组件
components: {
Child
},
data() {
return {
num: 0
}
},
methods: {
changeNum() {
this.num++
}
}
}
</script>

然而这有着显著的缺点:我们在父组件中难以一眼看出此props 是被双向绑定的
1.4.2. sync
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件两者都没有明显的变更来源。
修改步骤:
- 组件内触发的事件名称以
update:my Prop Name命名(你要改变哪个值?),相应的上述child组件改为update:count - 父组件
v-bind:value加上.sync修饰符,即v-bind:count.sync
parent.vue:
<template>
<div>
<h1>我是父组件</h1>
{{ num }}
<button @click="num++">改变数据</button>
<hr>
<child :count.sync="num"/>
</div>
</template>
<script>
import Child from './child'
export default {
components: { Child },
data() {
return {
num: 0
}
}
}
</script>
child.vue:
<template>
<div>
<h2>我是子组件</h2>
{{ count }}
<button @click="changeData">改变数据</button>
</div>
</template>
<script>
export default {
props: ['count'],
data() {
return {
newNum: 5
}
},
methods: {
changeData() {
console.log(this.newNum)
// update:count你要更新父组件传递过来的哪个值? this.newNum++变成什么样
this.$emit('update:count', this.newNum++)
}
}
}
</script>
当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定
注意带有
.sync修饰符的v-bind不能和表达式一起使用 (例如v-bind:title.sync=”doc.title + ‘!’”是无效的)。取而代之的是,你只能提供你想要绑定的property名,类似 v-model。
1.4.3. 把基本类型改为引用类型
参照“传的是引用类型”的例子
1.5. 父子组件Prop值断开
1.5.1. 把父组件的数据赋值给子组件中的新值
这个prop用来传递一个初始值;这个子组件接下来希望将其作为一个本地的prop数据来使用
子组件通过data修改父组件传递过来的数据,把传递过来的数据作为data中局部数据的初始值使用
child.vue:
<template>
<div>
<h2>我是子组件</h2>
{{ increment }}
<button @click="changeData">改变数据</button>
</div>
</template>
<script>
export default {
props: ['count'],
data() {
return {
increment: this.count
}
},
methods: {
changeData() {
this.increment++
}
}
}
</script>
parent.vue:
<template>
<div>
<h1>我是父组件</h1>
{{ num }}
<button @click="num++">改变数据</button>
<hr>
<child :count="num"/>
</div>
</template>
<script>
import Child from './child'
export default {
components: { Child },
data() {
return {
num: 0
}
}
}
</script>

结论: 各自改变各自的互不影响
1.5.2. 计算属性
这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
child.vue:
<template>
<div>
<h2>子组件</h2>
<p>
{{ formatProp }}
</p>
</div>
</template>
<script>
export default {
props: ['childTitle'],
computed: {
formatProp() {
return this.childTitle.split('')
}
}
}
</script>
parent.vue:
<template>
<div>
<h1>父组件</h1>
{{ title }}
<hr>
<child :child-title="title" />
</div>
</template>
<script>
import Child from './child.vue'
export default {
components: {
Child
},
data() {
return {
title: '北昌教育'
}
}
}
</script>

2. 组件封装
一段代码在项目中出现两次就必须封装,大到一个页面,一个组件,小到一个function和一个css样式
- 数据从父组件(使用的组件)传入
子组件(公共组件)本身不要生成数据,如果需要生成数据,只能在组件内部进行使用,不要传递出去
对于通过props传入的参数,不建议对其进行操作,因为会同时修改父组件里面的数据(如果修改的话,控制台中也会报错的),如果需要修改数据,可以传递给父组件,让父组件去修改(在父组件中处理事件)。
- 子组件不要放数据
- 子组件不能修改父组件传递进来的数据
- 父组件中处理事件
父组件中处理的事件是和后端交互的事件,比如发起的axios请求,但并不是所有的事件都放到父组件中处理,比如组件内部的一些交互行为,或者处理的数据只在组件内部传递,就可以在子组件中处理
- 父组件一般处理的是和接口相关的事件
- 子组件放的一般是子组件中私有的交互事件处理
-
留一个slot
一个通用的组件,往往不能完美的适应所有的应用场景,所以在封装组件的时候,只需要完成组件的80%的功能,剩下的20%让父组件通过slot解决 -
不要依赖Vuex(切忌不要依赖)
封装的组件依赖的Vuex那么意味着你使用公共组件的时候需要再下载vuex
- 合理使用scoped
样式中添加scoped可以让样式只对当前组件生效,但是一味使用scoped,会产生重复代码,所以需要有一个全局的样式,组件内只写针对于组件的样式,避免重复的样式代码
scoped隔离样式
- 组件的职责是单一的
封装业务组件或者基础组件,如果不能给这个组件起一个有意义的名字,证明这个组件承担的职责可能不够单一,需要继续抽离出来新的组件,直到它可以是一个独立的组件即可
2.1. 分页组件
基于elementUI组件的二次封装
2.1.1. 用到分页组件的地方
- 用户列表
- 订单列表
- 商品列表
- 商品分类
2.1.2. 封装实现
page.vue:
<template>
<!-- 分页
@size-change:size长度、尺码; change改变 => 分页的数量发生变化的时候执行该事件
@current-change:current当前 change改变 => 当前页码改变的时候执行该事件
:current-page="currentPage4" 当前页码
:page-sizes:下拉菜单的选项
:page-size:每页显示的条数
layout:分页的布局
-->
<el-pagination class="wyf-pagination"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="num"
:page-sizes="sizes"
:page-size="size"
:layout="layout"
:total="totalNum">
</el-pagination>
</template>
<script>
export default {
props: {
// 当前第几页
num: {
type: Number,
required: true
},
// 每页多少条
size: {
type: Number,
default: 2
},
// 总条数
totalNum: {
type: Number,
required: true
},
// 下拉选项
sizes: {
type: Array,
required: true
},
// 分页的布局
layout: {
type: String,
required: true
}
},
methods: {
// 就是size-change事件触发的时候的回调函数;val每页条数
handleSizeChange(val) {
// 通知父组件修改pagesize为val,然后再次请求用户列表的接口
this.$emit('sendSizeChange', val)
},
// 就是@current-change事件触发的时候的回调函数;val当前页
handleCurrentChange(val) {
// 通知父组件修改pagenum为val,然后再次请求用户列表的接口
this.$emit('sendCurrentChange', val)
},
}
}
</script>
<style></style>
users.vue:
<template>
<Page
:num="page.pagenum"
:size="page.pagesize"
:total-num="total"
:sizes="arrPageSize"
:layout="strLayout"
@sendCurrentChange="receiveCurrentChange"
@sendSizeChange="receiveSizeChange"
/>
</template>
<script>
// 导入分页组件
import Page from './common/page.vue'
export default {
components: {
Page
},
methods: {
// 子组件每页数量发生变化时通知父组件重新传递请求参数及重新请求数据
receiveSizeChange(val) {
this.queryInfo.pagesize = val
this.initUsersData();
},
// 子组件当前页码发生变化时通知父组件重新传递请求参数及重新请求数据
receiveCurrentChange(val) {
this.queryInfo.pagenum = val;
this.initUsersData();
}
}
}
</script>
2.1.3. 接口定义(组件的用法)
2.1.3.1. Page Attributes
| 属性名 | 说明 | 默认值 | 数据类型 |
|---|---|---|---|
| num | 当前是第几页 | 1 | Number |
| sizes | 每页显示多少条下拉菜单的选项 | 无 | Array |
| total-num | 总数 | 无 | Number |
| layout | 分页布局 | 无 | String |
| size | 每页显示多少条数据 | 2 | Number |
2.1.3.2. Page Events
| 事件名 | 说明 | 参数 |
|---|---|---|
| sendSizeChange | 当每页多少条数据发生变化的时候会触发 | 一个参数 当前选中的每页多少条数据 |
| sendCurrentChange | 切换页码的时候触发 | 一个参数 当前切换到了第几页 |
2.2. 搜索组件
2.2.1. 用到搜索组件的模块
- 用户列表
- 商品列表
- 订单列表
2.2.2. 步骤
- 保证样式没有问题
- 再实现逻辑
2.2.3. 封装实现
search.vue:
<template>
<el-input placeholder="请输入内容" v-model="searchVal">
<template slot="append">
<i class="el-icon-search" @click="search"></i>
</template>
</el-input>
</template>
<script>
export default {
data() {
return {
searchVal: ''
}
},
methods: {
search() {
this.$emit('sendSearch', this.searchVal)
}
}
}
</script>
users.vue:
<template>
<div slot="header" class="clearfix">
<span>
<div style="margin-top: 15px;">
<!-- 搜索组件 -->
<search @sendSearch="receiveSearch" />
<el-button type="primary" @click="dialogFormVisible = true">添加用户</el-button>
</div>
</span>
</div>
</template>
<script>
import Search from './common/search.vue'
export default {
components: {
Search
},
methods: {
// 搜索
receiveSearch(searchVal) {
this.$http.get('users', {
params: {
query: searchVal,
pagenum: 1,
pagesize: 10
}
}).then(res => {
console.log(res)
let { users, total } = res.data.data
this.usersData = users
this.total = total
})
},
}
}
</script>
goods.vue:
<template>
<div slot="header" class="clearfix">
<span>
<div style="margin-top: 15px;">
<search @sendSearch="receiveSearch" />
<el-button type="primary">
<router-link to="/add">添加商品</router-link>
</el-button>
</div>
</span>
</div>
</template>
<script>
export default {
// 导入组件
import Search from './common/search.vue'
export default {
components: {
Search
},
methods: {
// 搜索
receiveSearch(val) {
this.$http.get('goods', {
params: {
query: val,
pagenum: 1,
pagesize: 10
}
}).then(res => {
let { goods, total } = res.data.data
this.tableData = goods
this.total = total
})
}
}
}
</script>
2.2.4. 接口定义(组件的用法)
2.2.4.1. Search Attributes
无
2.2.4.2. Search Events
| 事件名 | 说明 | 参数 |
|---|---|---|
| sendSearch | 当点击搜索icon的时候会出发该事件 | 就是一个形参,符合命名规范即可 |
封装组件可以参照函数封装:
比如:页面有两个地方用到了轮播图,但是两个地方的轮播图有细微的差别,这个时候我们就得考虑函数封装有参数来处理细微的差别。假如这个差别就是一个是左右轮播,另一个是上下轮播
- 先实现一个轮播图的功能,比如你先实现左右轮播
- 在左右轮播的基础上修改为上下轮播
- 把他们差异的变量提取出来作为函数的参数
2.3. 模态框
- 作为公共组件先实现添加用户的功能
- 再让他兼容添加又支持编辑
- 分配角色也要用这个那么你就继续让该公共组件兼容分配角色模态框
2.3.1. 添加
我们先从添加模态框入手,开始一步步完成可复用的组件
先保证我们抽离出去的公共组件可以完成添加用户的功能(界面、逻辑)
2.3.1.1. 模态框的显示
- 触发模态框打开的按钮在父组件
user.vue中,控制子组件显示隐藏的变量在子组件中,因此父定义一个布尔值传递给子组件,作为控制该组件显示隐藏:visible.sync的属性值
user.vue:
<!-- 添加用户模态框 -->
<Modal :sendModalShow="addDialogFormVisible" />
<script>
export default {
data() {
return {
addDialogFormVisible: false
}
}
}
</script>
- 在子组件中接收父组件传递过来的数据
modal.vue:
<template>
<el-dialog title="添加用户" :visible.sync="sendModalShow">
<div slot="footer" class="dialog-footer">
<el-button @click="sendModalShow = false">取 消</el-button>
<el-button type="primary">确 定</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
data() {
return {
formLabelWidth: '120px'
}
},
props: {
sendModalShow: Boolean
}
}
</script>
2.3.1.2. 模态框的隐藏
2.3.1.2.1. 子直接修改父的数据
很简单再点击modal组件的时候只需要把sendModalShow改变为false就能实现模态框的隐藏了!像这样:
modal.vue:
<template>
<div slot="footer" class="dialog-footer">
<el-button @click="sendModalShow = false">取 消</el-button>
<el-button type="primary">确 定</el-button>
</div>
</template>
![]() |
|---|
| 意思是不能直接修改父组件传递过来的数据 |
之前讲过子组件不能直接修改父组件传递过来的数据,要想修改三种办法:
- 子通知父修改
- sync
基本类型转为引用
2.3.1.1.2. sync
modal.vue:
<script>
export default {
methods: {
changeModalHide() {
// update:sendModalShow你要更新父组件传递过来的哪个值? false意思是变成什么样
this.$emit('update:sendModalShow', false)
}
}
}
</script>
users.vue“
<Modal :sendModalShow.sync="addDialogFormVisible" />
2.3.1.3. 收集modal中的数据
收集modal表单数据发送请求的时候作为参数传递给接口
users.vue:
<template>
<Modal :sendModalShow.sync="addDialogFormVisible" :addFormAttr="addForm" />
</template>
<script>
export default {
data() {
// 传递给后端的参数 这里的参数要传递给后端,所以要和接口中要求的请求参数一致,否则会导致后端收不到前端传递的参数
addForm: {
username: '', // 用户名
password: '', // 密码
email: '', // 邮箱
mobile: '' // 手机号码
},
}
}
</script>
modal.vue:
<template>
<el-dialog title="添加用户" :visible.sync="sendModalShow">
<el-form :model="addFormAttr">
<el-form-item label="用户名" :label-width="formLabelWidth">
<el-input v-model="addFormAttr.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" :label-width="formLabelWidth">
<el-input v-model="addFormAttr.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱" :label-width="formLabelWidth">
<el-input v-model="addFormAttr.email" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="手机号" :label-width="formLabelWidth">
<el-input v-model="addFormAttr.mobile" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="changeModalHide">取 消</el-button>
<el-button type="primary" @click="addUser">确 定</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
addFormAttr: {
type: Object
}
},
}
</script>
2.3.1.4. 请求添加用户的接口
modal.vue:
<template>
<el-button type="primary" @click="addUser">确 定</el-button>
</template>
<script>
export default {
methods: {
addUser() {
this.$emit('addFormHandle')
}
}
}
</script>
users.vue:
<template>
<Modal :sendModalShow.sync="addDialogFormVisible" :addFormAttr="addForm" @addFormHandle="add" />
</template>
<script>
export default {
methods: {
// 添加用户
add() {
// 处理接口的请求
console.log('收集到的表单的数据', this.addForm)
this.$http.post('users', this.addForm).then(res => {
console.log(res)
// 把我们需要的字段结构出来
let { status, msg } = res.data.meta
// 创建失败的提示
if (status!== 201) return this.$message.error(msg)
// 添加成功的提示
this.$message.success(msg)
// 再次请求用户列表数据
this.initUsers()
})
// 关闭模态框
this.addDialogFormVisible = false
},
}
}
</script>
2.3.2. 接口定义(组件的用法)v1.0
2.3.2.1. Modal Attributes
| 属性名 | 说明 | 默认值 | 数据类型 |
|---|---|---|---|
| sendModalShow.sync | 控制模态框显示隐藏的,可以接收一个变量 | false | Boolean |
| labelWidth? | label的宽度 | 100px | String |
| formAttr | 收集表单数据的 | {} | Object |
| title | 模态框的标题 | 无 | String |
2.3.2.2. Modal Events
| 事件名 | 说明 | 参数 |
|---|---|---|
| sendAddUser | 点击确定按钮发起http请求的 | 无 |
你现在封装公共组件Modal只能给添加用户使用,你能给编辑使用吗?不能!为什么不能?
- Modal里面表单4个字段input,编辑的是3个
- Modal里面的input都是启用状态,编辑里面的username是禁用状态
- Modal里面的确定按钮发送的是添加用户的请求,不能再一个按钮上添加两个click事件
- Modal标题不一样
我们现在面临的问题就是:标题不同的问题、字段多少的问题、禁用的问题、同一个按钮发送不同请求的问题
2.3.3. 编辑
复用modal组件试试看
2.3.3.1. 标题和隐藏显示
要看标题是否差异化了得打开和关闭模态框因此一起做了
users.vue:
<template>
<!-- 添加模态框 -->
<Modal title="添加用户" :sendModalShow.sync="addDialogFormVisible" :addFormAttr="addForm" @addFormHandle="add" />
<!-- 编辑模态框 -->
<Modal title="编辑用户" :sendModalShow.sync="editDialogFormVisible" />
</template>
- 标题修改title:从父组件接收一个字符串“添加用户”“编辑用户”…
- 控制模态框显示隐藏变量传递sendModalShow,从父组件接收一个控制不同模态框的变量默认值为false
modal.vue:
<template>
<el-dialog :title="title" :visible.sync="sendModalShow">
<el-form >
<el-form-item label="用户名" :label-width="formLabelWidth">
<el-input autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" :label-width="formLabelWidth">
<el-input autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱" :label-width="formLabelWidth">
<el-input autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="手机号" :label-width="formLabelWidth">
<el-input autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="changeModalHide">取 消</el-button>
<el-button type="primary" >确 定</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
sendModalShow: Boolean, // 显示还是隐藏
title: String // 标题
},
methods: {
// 模态框隐藏
changeModalHide() {
// update:count你要更新父组件传递过来的哪个值? this.newNum++变成什么样
this.$emit('update:sendModalShow', false)
},
}
}
</script>
组件还要求传递方法等等,我们还没有传递因此会报错,所以我们把组件中没有传递的方法属性等先删掉,先测试标题和隐藏的功能
2.3.3.2. 添加和编辑表单显示字段不同
思路:在组件内部通过v-if控制显示哪个字段,v-if的值就是我们从父组件(子组件标签)传递进去的,如果我们传递进去的是false就是没有这个字段,传进去的是true就有这个字段
user.vue:
<template>
<!-- 添加用户模态框 -->
<Modal
title="添加用户"
:sendModalShow.sync="addDialogFormVisible"
:addFormAttr="addForm"
@addFormHandle="add" />
<!-- 编辑模态框 -->
<Modal
title="编辑用户"
:sendModalShow.sync="editDialogFormVisible"
:hasPwd="false"
/>
</template>
modal.vue:
<template>
<el-dialog :title="title" :visible.sync="sendModalShow">
<el-form >
<el-form-item label="用户名" :label-width="formLabelWidth" v-if="hasName">
<el-input autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" :label-width="formLabelWidth" v-if="hasPwd">
<el-input autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱" :label-width="formLabelWidth" v-if="hasEmail">
<el-input autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="手机号" :label-width="formLabelWidth" v-if="hasMobile">
<el-input autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="changeModalHide">取 消</el-button>
<el-button type="primary" >确 定</el-button>
</div>
</el-dialog>
</template>
<script>
export default {
data() {
return {
formLabelWidth: '120px'
}
},
props: {
sendModalShow: Boolean, // 显示隐藏
// 是否有用户名
hasName: {
type: Boolean,
default: true
},
// 是否有密码
hasPwd: {
type: Boolean,
default: true
},
// 是否有邮箱
hasEmail: {
type: Boolean,
default: true
},
// 是否有手机号码
hasMobile: {
type: Boolean,
default: true
},
title: String // 模态框标题
},
methods: {
// 模态框隐藏
changeModalHide() {
// update:count你要更新父组件传递过来的哪个值? this.newNum++变成什么样
this.$emit('update:sendModalShow', false)
}
}
}
</script>
执行结果:
- 点开添加用户模态框四个字段都有
- 点开编辑用户模态框没有密码字段
2.3.3.3. 收集数据
users.vue:
<template>
<Modal
title="编辑用户"
:sendModalShow.sync="editDialogFormVisible"
:formAttr="editShowUserInfo"
:hasPwd="false"
/>
</template>
<script>
export default {
data() {
return {
editShowUserInfo: {
username: '',
mobile: '',
email: ''
},
}
}
}
</script>
把addFormAttr修改为了formAttr
modal.vue:
无变化
2.3.3.4. 数据回填
把原有的信息在编辑的时候显示在模态框中对应的input中
users.vue:
<template slot-scope="scope">
<el-button type="primary" size="mini" icon="el-icon-edit" @click="showEditDialog(scope.row.id)"></el-button>
</template>
<script>
export default {
data() {
return {
// 编辑用户数据回填的对象
editShowUserInfo: {
username: '',
mobile: '',
email: ''
},
}
},
methods: {
// 打开编辑模态框
showEditDialog(userId) {
// 打开模态框
this.editDialogFormVisible = true
// // 请求查询单个用户的信息接口
this.$http.get(`users/${userId}`).then(res => {
console.log('当前点击用户的信息', res)
// 解构出服务端返回的数据
let { data } = res.data
// 吧服务端返回的数据赋值给data中的变量
this.editShowUserInfo = data
})
},
}
}
</script>
无变化(已经绑定过了)
2.3.3.5. 请求编辑用户的接口
当我们调用编辑用户接口的时候发现一个问题,模态框组件的确定按钮只有一个,该怎么办呢?我们可以使用slot来解决!
modal.vue:
<template>
<div slot="footer" class="dialog-footer">
<el-button @click="changeModalHide">取 消</el-button>
<!-- 确定按钮插槽 -->
<slot name="confirm"></slot>
</div>
</template>
users.vue:
<template>
<Modal
title="添加用户"
:sendModalShow.sync="addDialogFormVisible"
:formAttr="addForm"
>
<el-button slot="confirm" type="primary" @click="add">确定</el-button>
</Modal>
<Modal
title="编辑用户"
:sendModalShow.sync="editDialogFormVisible"
:formAttr="editShowUserInfo"
@editFormHandle="edit"
:hasPwd="false"
>
<el-button slot="confirm" type="primary" @click="edit">确定</el-button>
</Modal>
</template>
2.3.4. 生成API
2.3.4.1. Attributes
| 属性名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| title | 模态框的标题 | String | - |
| sendModalShow.sync | 控制模态框的显示或隐藏 | Boolean | - |
| formAttr | 模态框数据绑定的对象 | Object | - |
| hasName | 是否显示username文本框 | Boolean | true |
| hasPwd | 是否显示password密码框 | Boolean | true |
| hasEmail | 是否显示email文本框 | Boolean | true |
| hasMobile | 是否显示mobile文本框 | Boolean | true |
| disabledName | 是否禁用username输入框 | Boolean | false |
| disabledPwd | 是否禁用disabledPwd输入框 | Boolean | false |
| disabledEmail | 是否禁用disabledEmail输入框 | Boolean | false |
| disabledMobile | 是否禁用disabledMobile输入框 | Boolean | false |
2.3.4.2. Slots
| 插槽名 | 说明 |
|---|---|
| confirm | 自定义请求接口 |
2.3.5. 完整代码
modal.vue:
<template>
<el-dialog :title="title" :visible.sync="sendModalShow">
<el-form :model="formAttr">
<el-form-item label="用户名" :label-width="formLabelWidth" v-if="hasName">
<el-input v-model="formAttr.username" :disabled="disabledName" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" :label-width="formLabelWidth" v-if="hasPwd">
<el-input v-model="formAttr.password" :disabled="disabledPwd" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱" :label-width="formLabelWidth" v-if="hasEmail">
<el-input v-model="formAttr.email" :disabled="disabledEmail" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="手机号" :label-width="formLabelWidth" v-if="hasMobile">
<el-input v-model="formAttr.mobile" :disabled="disabledMobile" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="changeModalHide">取 消</el-button>
<!-- 确定按钮插槽 -->
<slot name="confirm"></slot>
</div>
</el-dialog>
</template>
<script>
export default {
data() {
return {
formLabelWidth: '120px'
}
},
props: {
// 显示隐藏
sendModalShow: Boolean,
// 是否开启禁用
disabledName: {
type: Boolean,
default: false
},
// 是否开启禁用
disabledPwd: {
type: Boolean,
default: false
},
// 是否开启禁用
disabledEmail: {
type: Boolean,
default: false
},
// 是否开启禁用
disabledMobile: {
type: Boolean,
default: false
},
// 收集数据
formAttr: {
type: Object
},
// 是否显示用户名
hasName: {
type: Boolean,
default: true
},
// 是否显示密码
hasPwd: {
type: Boolean,
default: true
},
// 是否显示邮箱
hasEmail: {
type: Boolean,
default: true
},
// 是否显示手机号
hasMobile: {
type: Boolean,
default: true
},
// 模态框标题
title: String
},
methods: {
// 模态框隐藏
changeModalHide() {
// update:count你要更新父组件传递过来的哪个值? this.newNum++变成什么样
this.$emit('update:sendModalShow', false)
}
}
}
</script>
users.vue:
<template>
<div>
<Modal
title="添加用户"
:sendModalShow.sync="addDialogFormVisible"
:formAttr="addForm"
>
<el-button slot="confirm" type="primary" @click="add">确定</el-button>
</Modal>
<Modal
title="编辑用户"
:sendModalShow.sync="editDialogFormVisible"
:formAttr="editShowUserInfo"
:hasPwd="false"
:disabledName="true"
>
<el-button slot="confirm" type="primary" @click="edit">确定</el-button>
</Modal>
</div>
</template>
<script>
import Modal from "@/components/common/modal.vue";
export default {
components: {
Modal
},
data() {
return {
addDialogFormVisible: false, // 用来控制模态框的隐藏显示的
editDialogFormVisible: false,
// 传递给后端的参数 这里的参数要传递给后端,所以要和接口中要求的请求参数一致,否则会导致后端收不到前端传递的参数
addForm: {
username: '', // 用户名
password: '', // 密码
email: '', // 邮箱
mobile: '' // 手机号码
},
// 编辑用户数据回填的对象
editShowUserInfo: {
username: '',
mobile: '',
email: ''
},
};
},
methods: {
// 打开编辑模态框
showEditDialog(userId) {
// 打开模态框
this.editDialogFormVisible = true
// // 请求查询单个用户的信息接口
this.$http.get(`users/${userId}`).then(res => {
console.log('当前点击用户的信息', res)
// 解构出服务端返回的数据
let { data } = res.data
// 吧服务端返回的数据赋值给data中的变量
this.editShowUserInfo = data
})
},
// 编辑用户提交
edit() {
console.log('edit执行了')
this.$http.put(`users/${this.editShowUserInfo.id}`, {
email: this.editShowUserInfo.email,
mobile: this.editShowUserInfo.mobile
}).then(res => {
console.log(res)
// 关闭模态框
this.editDialogFormVisible = false
// 重新请求用户列表的数据
this.initUsers()
})
},
// 添加用户
add() {
// 处理接口的请求
console.log('收集到的表单的数据', this.addForm)
this.$http.post('users', this.addForm).then(res => {
console.log(res)
// 把我们需要的字段结构出来
let { status, msg } = res.data.meta
// 创建失败的提示
if (status!== 201) return this.$message.error(msg)
// 添加成功的提示
this.$message.success(msg)
// 再次请求用户列表数据
this.initUsers()
})
// 关闭模态框
this.addDialogFormVisible = false
},
}
};
</script>
2.3.6. 优化
是否有某个文本框组件,我们在子组件使用的是v-if结合父组件传递给子组件的布尔值来动态的创建或销毁,这一步其实有些多余,完全可以判断父组件传递给子组件的对象是否有某个字段,如果有那么就对应的创建文本框组件没有那么就不创建
modal.vue:
<template>
<el-dialog :title="title" :visible.sync="sendModalShow">
<el-form :model="formAttr">
<el-form-item label="用户名" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('username')">
<el-input :disabled="disabledName" v-model="formAttr.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('password')">
<el-input :disabled="disabledPwd" v-model="formAttr.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('email')">
<el-input :disabled="disabledEmail" v-model="formAttr.email" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="手机号" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('mobile')">
<el-input :disabled="disabledMobile" v-model="formAttr.mobile" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="changeModalHide">取 消</el-button>
<slot name="confirm"></slot>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
// 是否开启禁用
disabledName: {
type: Boolean,
default: false
},
// 是否开启禁用
disabledPwd: {
type: Boolean,
default: false
},
// 是否开启禁用
disabledEmail: {
type: Boolean,
default: false
},
// 是否开启禁用
disabledMobile: {
type: Boolean,
default: false
},
sendModalShow: {
type: Boolean
},
formAttr: {
type: Object
},
labelWidth: {
type: String
},
title: {
type: String
}
},
methods: {
changeModalHide() {
this.$emit('update:sendModalShow', false)
}
}
}
</script>
users.vue:
<!-- 编辑模态框 -->
<Modal
title="编辑用户"
labelWidth="100px"
:sendModalShow.sync="editDialogFormVisible"
:formAttr="editUserForm"
:disabledName="true"
>
<el-button slot="confirm" type="primary" @click="edit">确定</el-button>
</Modal>
2.3.7. bug修改
点击模态框的“取消”可以正常关闭模态框,但是当点击“遮罩层”、“关闭icon”、“esc”的时候会报错,意思就是子组件不能直接修改父组件传递进来的数据。在这里我们使用的是update和sync,点击“取消”是可以的,解决思路也就明确了,点击“遮罩层”、“关闭icon”、“esc”的时候,和点击“取消”执行相同的方法就可以了
modal.vue:
<template>
<el-dialog :title="title" :visible.sync="sendModalShow" :before-close="changeModalHide">
<el-form :model="formAttr">
<el-form-item label="用户名" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('username')">
<el-input :disabled="disabledName" v-model="formAttr.username" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('password')">
<el-input :disabled="disabledPwd" v-model="formAttr.password" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="邮箱" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('email')">
<el-input :disabled="disabledEmail" v-model="formAttr.email" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="手机号" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('mobile')">
<el-input :disabled="disabledMobile" v-model="formAttr.mobile" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="changeModalHide">取 消</el-button>
<slot name="confirm"></slot>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
// 是否开启禁用
disabledName: {
type: Boolean,
default: false
},
// 是否开启禁用
disabledPwd: {
type: Boolean,
default: false
},
// 是否开启禁用
disabledEmail: {
type: Boolean,
default: false
},
// 是否开启禁用
disabledMobile: {
type: Boolean,
default: false
},
sendModalShow: {
type: Boolean
},
formAttr: {
type: Object
},
labelWidth: {
type: String
},
title: {
type: String
}
},
methods: {
changeModalHide() {
this.$emit('update:sendModalShow', false)
}
}
}
</script>
2.3.8. 最终版完整代码
modal.vue:
<template>
<el-dialog :title="title" :visible.sync="sendModalShow" :before-close="changeModalHide">
<el-form>
<el-form-item label="用户名" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('username')">
<el-input autocomplete="off" :disabled="isDisabledName" v-model="formAttr.username"></el-input>
</el-form-item>
<el-form-item label="密码" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('password')">
<el-input autocomplete="off" :disabled="isDisabledPwd" v-model="formAttr.password"></el-input>
</el-form-item>
<el-form-item label="邮箱" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('email')">
<el-input autocomplete="off" :disabled="isDisabledEmail" v-model="formAttr.email"></el-input>
</el-form-item>
<el-form-item label="手机" :label-width="labelWidth" v-if="formAttr.hasOwnProperty('mobile')">
<el-input autocomplete="off" :disabled="isDisabledMobile" v-model="formAttr.mobile"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="changeModalHide">取 消</el-button>
<!-- 放确定按钮的插槽 -->
<slot></slot>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
// 模态框的title
title: {
type: String
},
// 控制模态框显示隐藏的属性值
sendModalShow: {
type: Boolean,
default: false
},
// label的宽度
labelWidth: {
type: String,
default: '100px'
},
// 收集表单数据
formAttr: {
type: Object,
default: {}
},
// 是否禁用input
isDisabledName: {
type: Boolean,
default: false
},
isDisabledPwd: {
type: Boolean,
default: false
},
isDisabledEmail: {
type: Boolean,
default: false
},
isDisabledMobile: {
type: Boolean,
default: false
}
},
methods: {
// 模态框隐藏
changeModalHide() {
this.$emit('update:sendModalShow', false)
}
}
}
</script>
user.vue:
<template>
<div>
<!-- 面包屑 -->
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>用户管理</el-breadcrumb-item>
<el-breadcrumb-item>用户列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 面包屑 -->
<!-- 表格 -->
<el-card class="box-card">
<div slot="header" class="clearfix">
<span>
<Search @sendSearch="search" />
<el-button type="primary" @click="dialogFormVisible = true">添加用户</el-button>
</span>
</div>
<div class="text item">
<el-table :data="usersData" border style="width: 100%">
<el-table-column type="index" label="id" width="50">
</el-table-column>
<el-table-column prop="username" label="姓名" width="180">
</el-table-column>
<el-table-column prop="email" label="邮箱">
</el-table-column>
<el-table-column prop="mobile" label="电话">
</el-table-column>
<el-table-column prop="role_name" label="角色">
</el-table-column>
<el-table-column prop="mg_state" label="状态">
<template slot-scope="scope">
<el-switch v-model="scope.row.mg_state" @change="updateState(scope.row)">
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button type="primary" icon="el-icon-edit" size="mini" @click="editOpen(scope.row)"></el-button>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="del(scope.row.id)"></el-button>
<el-button type="warning" icon="el-icon-setting" size="mini" @click="openSetRole(scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页
@size-change -> 每页条数发生了变化我就执行handleSizeChange方法
@current-change -> 当前页码发生变化就会执行handleCurrentChange方法
:page-sizes -> 配置每页多少条数据下拉菜单的数组
:current-page -> 当前是第几页
:total => 总条数
-->
<Page :num="usersParams.pagenum" :size="[2, 4, 6, 8]" :total="usersListTotal" @sendSizeChange="handleSizeChange" @sendCurrentChange="handleCurrentChange" />
</el-card>
<!-- 表格 -->
<!-- 添加用户的模态框 -->
<Modal title="添加用户" :sendModalShow.sync="dialogFormVisible" :labelWidth="formLabelWidth" :formAttr="addUserForm">
<el-button type="primary" @click="add">确 定</el-button>
</Modal>
<!-- 添加用户的模态框 -->
<!-- 编辑用户的模态框 -->
<Modal title="编辑用户" :sendModalShow.sync="editDialogFormVisible" :formAttr="editUserForm" :isDisabledName="true">
<el-button type="primary" @click="edit">确 定</el-button>
</Modal>
<!-- 编辑用户的模态框 -->
<!-- 分配角色的模态框 -->
<el-dialog title="分配角色" :visible.sync="setRoleDialogFormVisible">
<el-form>
<el-form-item :label-width="formLabelWidth">
当前用户名:{{ roleObj.username }}
</el-form-item>
<el-form-item :label-width="formLabelWidth">
当前的角色:{{ roleObj.role_name }}
</el-form-item>
<el-form-item label="新角色" :label-width="formLabelWidth">
<p>value的值是:{{ roleId }}</p>
<el-select v-model="roleId" placeholder="请选择">
<el-option v-for="item in rolesList" :key="item.id" :label="item.roleName" :value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="setRoleDialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="setNewRole">确 定</el-button>
</div>
</el-dialog>
<!-- 分配角色的模态框 -->
</div>
</template>
<script>
// 导入search组件
import Search from '../components/search.vue'
import Page from '../components/page.vue'
import Modal from '../components/modal.vue'
export default {
components: {
Search,
Page,
Modal
},
data() {
return {
// 分配角色下拉菜单选中的值
roleId: '',
// 分配角色模态框
setRoleDialogFormVisible: false,
// 收集编辑用户邮箱和手机号码
editUserForm: {},
// 当前被编辑的用户id
editingUserId: '',
// 编辑用户模态框的显示或隐藏
editDialogFormVisible: false,
usersData: [],
/* 添加用户模态框 */
// 添加用户模态框显示还是隐藏
dialogFormVisible: false,
// label的宽度 - 添加用户
formLabelWidth: '100px',
// 收集用户输入的数据(数据的数量大于一条就用对象或数组)
addUserForm: {
username: '',
password: '',
email: '',
mobile: ''
},
/* 添加用户模态框 */
// 请求用户列表的参数
usersParams: {
query: '',
pagenum: 1,
pagesize: 2
},
// 用户列表总条数
usersListTotal: 0,
// 角色列表
rolesList: [],
// 分配角色数据回填
roleObj: {}
}
},
created() {
this.getUserData()
},
methods: {
// 每页显示的条数发生变化就会执行这个方法
handleSizeChange(val) {
console.log(`每页 ${val} 条`);
// 每页显示多少条数据把分页组件下拉菜单中每页多少条的值取到然后赋值给我们请求用户列表中的pagesize字段这样分页组件的改变就会和用户列表数据发生了关联
this.usersParams.pagesize = val
//重新请求用户列表的数据
this.getUserData()
},
handleCurrentChange(val) {
// 分页组件的页码发生变化后的值就是当前是第几页,然后把这个值赋值给请求用户列表中的参数pagenum
this.usersParams.pagenum = val
console.log(`页码的改变触发了这个方法的执行;当前页: ${val}`);
//重新请求用户列表的数据
this.getUserData()
},
// 更新用户的状态
/*
@params uId 用户的id -> 点击的时候把当前用户的对象或用户的id传递过来
@params type 布尔值true或false -> 从传递进来的对象中获取状态的字段
没回答出来:
小黑
剑辉
*/
updateState(userObj) {
this.$http.put(`users/${userObj.id}/state/${userObj.mg_state}`).then(res => {
console.log('更新状态后的返回值:', res)
})
},
// 分配新角色的方法
/*
@params id 用户的id => roleObj.id
@params rid 角色id
*/
setNewRole() {
this.$http.put(`users/${this.roleObj.id}/role`, {
rid: this.roleId
}).then(res => {
console.log(res)
// 关闭模态框
this.setRoleDialogFormVisible = false
})
},
// 打开分配新角色的模态框
openSetRole(obj) {
// 把当前选中的角色信息赋值给roleObj
this.roleObj = obj
// 打开模态框
this.setRoleDialogFormVisible = true
this.$http.get('roles').then(res => {
console.log('角色列表:', res)
this.rolesList = res.data.data
})
},
// 编辑用户 - 打开模态框
editOpen(user) {
console.log('当前用户的信息:', user)
// 把当前编辑的用户的对象赋值给editUserForm
this.editUserForm = user
// 打开编辑用户的模态框
this.editDialogFormVisible = true
// 用户数据的回填
},
// 编辑用户 - 信息提交
edit() {
// 请求编辑用户的接口
/*
@params id 请求路径中的参数path表示
@params email 邮箱
@params mobile 手机号
*/
this.$http.put(`users/${this.editUserForm.id}`,
this.editUserForm).then(res => {
console.log(res)
// 关闭模态框
this.editDialogFormVisible = false
// 编辑成功
if (res.data.meta.status === 200) {
// statement
this.$message.success(res.data.meta.msg)
// 再次调用用户列表的方法
this.getUserData()
}
})
},
// 搜索用户
search(searchKey) {
this.usersParams.query = searchKey
this.$http.get('users', {
params: this.usersParams,
}).then(res => {
console.log('搜索结果:', res)
let { users } = res.data.data
this.usersData = users
})
},
// 请求用户列表
getUserData() {
// 请求用户列表的接口
this.$http.get('users', {
params: this.usersParams
}, ).then(res => {
console.log(res) // -> 数据
let { users, total } = res.data.data
this.usersListTotal = total
this.usersData = users
})
},
// 添加用户
add() {
// 关闭添加用户的模态框
this.dialogFormVisible = false
// 发送添加用户的http请求
this.$http.post(`users`,
this.addUserForm).then(res => {
console.log('添加用户返回的数据:', res)
// 成功的判断
if (res) this.getUserData()
})
},
// 删除用户
del(userId) {
this.$confirm('此操作将永久删除该用户, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
/*
* @params id(你要删除的谁)?
* */
this.$http.delete(`users/${userId}`).then(res => {
// 删除成功
if (res) this.getUserData()
})
}).catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
});
});
console.log(userId)
}
}
}
</script>
2.3.9. 接口定义(组件的用法)v最终版
2.3.9.1. Modal Attributes
| 属性名 | 说明 | 默认值 | 数据类型 |
|---|---|---|---|
| sendModalShow.sync | 控制模态框显示隐藏的,可以接收一个变量 | false | Boolean |
| labelWidth? | label的宽度 | 100px | String |
| formAttr | 收集表单数据的 | {} | Object |
| title | 模态框的标题 | 无 | String |
2.3.9.2. Modal Events
无
2.3.9.3. Modal Slot
| slot名字 | 说明 | 参数 |
|---|---|---|
| 具名插槽(confirm) | 这里放置的是点击确定按钮发送http请求的 | 无 |
3. 作业
3.1. 封装组件
3.1.1. 搜索
3.1.2. 分页
3.1.3. 级联选择器
3.1.4. 删除确认框
3.1.5. 面包屑
3.1.6. 分类参数
动态参数和静态属性


被折叠的 条评论
为什么被折叠?



