深圳厉害的室内设计公司seo资料网
零、文章目录
Vue2基础八、插槽
1、插槽
(1)默认插槽
- 作用:让组件内部的一些 结构 支持 自定义
- 需求: 将需要多次显示的对话框, 封装成一个组件
- 问题:组件的内容部分,不希望写死,希望能使用的时候自定义。怎么办?
- 插槽基本语法:
- 组件内需要定制的结构部分,改用
<slot></slot>
占位 - 使用组件时,
<MyDialog></MyDialog>
标签内部, 传入结构替换slot
- 组件内需要定制的结构部分,改用
-
代码实现
- 父组件
App.vue
<template><div><!-- 2. 在使用组件时,组件标签内填入内容 --><MyDialog><div>你确认要删除么</div></MyDialog><MyDialog><p>你确认要退出么</p></MyDialog></div> </template><script> import MyDialog from './components/MyDialog.vue' export default {data () {return {}},components: {MyDialog} } </script><style> body {background-color: #b3b3b3; } </style>
- 子组件
MyDialog.vue
<template><div class="dialog"><div class="dialog-header"><h3>友情提示</h3><span class="close">✖️</span></div><div class="dialog-content"><!-- 1. 在需要定制的位置,使用slot占位 --><slot></slot></div><div class="dialog-footer"><button>取消</button><button>确认</button></div></div> </template><script> export default {data () {return {}} } </script><style scoped> * {margin: 0;padding: 0; } .dialog {width: 470px;height: 230px;padding: 0 25px;background-color: #ffffff;margin: 40px auto;border-radius: 5px; } .dialog-header {height: 70px;line-height: 70px;font-size: 20px;border-bottom: 1px solid #ccc;position: relative; } .dialog-header .close {position: absolute;right: 0px;top: 0px;cursor: pointer; } .dialog-content {height: 80px;font-size: 18px;padding: 15px 0; } .dialog-footer {display: flex;justify-content: flex-end; } .dialog-footer button {width: 65px;height: 35px;background-color: #ffffff;border: 1px solid #e1e3e9;cursor: pointer;outline: none;margin-left: 10px;border-radius: 3px; } .dialog-footer button:last-child {background-color: #007acc;color: #fff; } </style>
- 父组件
(2)后备内容(默认值)
- 通过插槽完成了内容的定制,传什么显示什么, 但是如果不传,则是空白
- 能否给插槽设置
默认显示内容
呢?
-
插槽后备内容:封装组件时,可以为预留的
<slot>
插槽提供后备内容(默认内容)。 -
语法: 在
<slot>
标签内,放置内容作为默认内容,<slot>后备内容</slot>
-
外部使用组件时,不传东西,则slot会显示后备内容
<MyDialog></MyDialog>
-
外部使用组件时,传东西了,则slot整体会被换掉
<MyDialog>我是内容</MyDialog>
-
-
代码实现
- 父组件
App.vue
<template><div><MyDialog></MyDialog><MyDialog>你确认要退出么</MyDialog></div> </template><script> import MyDialog from './components/MyDialog.vue' export default {data () {return {}},components: {MyDialog} } </script><style> body {background-color: #b3b3b3; } </style>
- 子组件
MyDialog.vue
<template><div class="dialog"><div class="dialog-header"><h3>友情提示</h3><span class="close">✖️</span></div><div class="dialog-content"><!-- 往slot标签内部,编写内容,可以作为后备内容(默认值) --><slot>我是默认的文本内容</slot></div><div class="dialog-footer"><button>取消</button><button>确认</button></div></div> </template><script> export default {data () {return {}} } </script><style scoped> * {margin: 0;padding: 0; } .dialog {width: 470px;height: 230px;padding: 0 25px;background-color: #ffffff;margin: 40px auto;border-radius: 5px; } .dialog-header {height: 70px;line-height: 70px;font-size: 20px;border-bottom: 1px solid #ccc;position: relative; } .dialog-header .close {position: absolute;right: 0px;top: 0px;cursor: pointer; } .dialog-content {height: 80px;font-size: 18px;padding: 15px 0; } .dialog-footer {display: flex;justify-content: flex-end; } .dialog-footer button {width: 65px;height: 35px;background-color: #ffffff;border: 1px solid #e1e3e9;cursor: pointer;outline: none;margin-left: 10px;border-radius: 3px; } .dialog-footer button:last-child {background-color: #007acc;color: #fff; } </style>
- 父组件
(3)具名插槽
- 需求:一个组件内有多处结构,需要外部传入标签,进行定制
- 默认插槽:一个的定制位置
- 具名插槽语法:
- 多个slot使用name属性区分名字
- template配合
v-slot:名字
来分发对应标签 v-slot:插槽名
可以简化成#插槽名
-
代码实现:
- 父组件
App.vue
<template><div><MyDialog><!-- 需要通过template标签包裹需要分发的结构,包成一个整体 --><template v-slot:head><div>我是大标题</div></template><template v-slot:content><div>我是内容</div></template><template #footer><button>取消</button><button>确认</button></template></MyDialog></div> </template><script> import MyDialog from './components/MyDialog.vue' export default {data () {return {}},components: {MyDialog} } </script><style> body {background-color: #b3b3b3; } </style>
- 子组件
MyDialog.vue
<template><div class="dialog"><div class="dialog-header"><!-- 一旦插槽起了名字,就是具名插槽,只支持定向分发 --><slot name="head"></slot></div><div class="dialog-content"><slot name="content"></slot></div><div class="dialog-footer"><slot name="footer"></slot></div></div> </template><script> export default {data () {return {}} } </script><style scoped> * {margin: 0;padding: 0; } .dialog {width: 470px;height: 230px;padding: 0 25px;background-color: #ffffff;margin: 40px auto;border-radius: 5px; } .dialog-header {height: 70px;line-height: 70px;font-size: 20px;border-bottom: 1px solid #ccc;position: relative; } .dialog-header .close {position: absolute;right: 0px;top: 0px;cursor: pointer; } .dialog-content {height: 80px;font-size: 18px;padding: 15px 0; } .dialog-footer {display: flex;justify-content: flex-end; } .dialog-footer button {width: 65px;height: 35px;background-color: #ffffff;border: 1px solid #e1e3e9;cursor: pointer;outline: none;margin-left: 10px;border-radius: 3px; } .dialog-footer button:last-child {background-color: #007acc;color: #fff; } </style>
- 父组件
(4)作用域插槽
-
作用域插槽: 定义 slot 插槽的同时, 是可以
传值
的。给插槽
上可以 绑定数据,将来使用组件时可以用
。 -
场景:封装表格组件
- 父传子,动态渲染表格内容
- 利用默认插槽,定制操作列
- 删除或查看都需要用到
当前项的 id
,属于组件内部的数据
通过作用域插槽
传值绑定,进而使用
-
基本使用步骤:
- 给 slot 标签, 以 添加属性的方式传值
<slot :id="item.id" msg="测试文本"></slot>
- 所有添加的属性, 都会被收集到一个对象中
{ id: 3, msg: '测试文本' }
- 在template中, 通过
#插槽名= "obj"
接收,默认插槽名为default
<MyTable :list="list"><template #default="obj"><button @click="del(obj.id)">删除</button></template> </MyTable>
-
代码实现
- 父组件
App.vue
<template><div><MyTable :data="list"><!-- 3. 通过template #插槽名="变量名" 接收 --><template #default="obj"><button @click="del(obj.row.id)">删除</button></template></MyTable><MyTable :data="list2"><template #default="{ row }"><button @click="show(row)">查看</button></template></MyTable></div> </template><script> import MyTable from './components/MyTable.vue' export default {data () {return {list: [{ id: 1, name: '张小花', age: 18 },{ id: 2, name: '孙大明', age: 19 },{ id: 3, name: '刘德忠', age: 17 },],list2: [{ id: 1, name: '赵小云', age: 18 },{ id: 2, name: '刘蓓蓓', age: 19 },{ id: 3, name: '姜肖泰', age: 17 },]}},methods: {del (id) {this.list = this.list.filter(item => item.id !== id)},show (row) {// console.log(row);alert(`姓名:${row.name}; 年纪:${row.age}`)}},components: {MyTable} } </script>
- 子组件
MyTable.vue
<template><table class="my-table"><thead><tr><th>序号</th><th>姓名</th><th>年纪</th><th>操作</th></tr></thead><tbody><tr v-for="(item, index) in data" :key="item.id"><td>{{ index + 1 }}</td><td>{{ item.name }}</td><td>{{ item.age }}</td><td><!-- 1. 给slot标签,添加属性的方式传值 --><slot :row="item" msg="测试文本"></slot><!-- 2. 将所有的属性,添加到一个对象中 --><!-- {row: { id: 2, name: '孙大明', age: 19 },msg: '测试文本'}--></td></tr></tbody></table> </template><script> export default {props: {data: Array} } </script><style scoped> .my-table {width: 450px;text-align: center;border: 1px solid #ccc;font-size: 24px;margin: 30px auto; } .my-table thead {background-color: #1f74ff;color: #fff; } .my-table thead th {font-weight: normal; } .my-table thead tr {line-height: 40px; } .my-table th, .my-table td {border-bottom: 1px solid #ccc;border-right: 1px solid #ccc; } .my-table td:last-child {border-right: none; } .my-table tr:last-child td {border-bottom: none; } .my-table button {width: 65px;height: 35px;font-size: 18px;border: 1px solid #ccc;outline: none;border-radius: 3px;cursor: pointer;background-color: #ffffff;margin-left: 5px; } </style>
- 父组件
2、综合案例-商品列表
(1)功能需求
- my-tag 标签组件封装
- (1) 双击显示输入框,输入框获取焦点
- (2) 失去焦点,隐藏输入框
- (3) 回显标签信息
- (4) 内容修改,回车 → 修改标签信息
- my-table 表格组件封装
- (1) 动态传递表格数据渲染
- (2) 表头支持用户自定义
- (3) 主体支持用户自定义
(2)代码实现
- 父组件
App.vue
<template><div class="table-case"><MyTable :data="goods"><template #head><th>编号</th><th>名称</th><th>图片</th><th width="100px">标签</th></template><template #body="{ item, index }"><td>{{ index + 1 }}</td><td>{{ item.name }}</td><td><img:src="item.picture"/></td><td><MyTag v-model="item.tag"></MyTag></td></template></MyTable></div>
</template><script>
// my-tag 标签组件的封装
// 1. 创建组件 - 初始化
// 2. 实现功能
// (1) 双击显示,并且自动聚焦
// v-if v-else @dbclick 操作 isEdit
// 自动聚焦:
// 1. $nextTick => $refs 获取到dom,进行focus获取焦点
// 2. 封装v-focus指令// (2) 失去焦点,隐藏输入框
// @blur 操作 isEdit 即可// (3) 回显标签信息
// 回显的标签信息是父组件传递过来的
// v-model实现功能 (简化代码) v-model => :value 和 @input
// 组件内部通过props接收, :value设置给输入框// (4) 内容修改了,回车 => 修改标签信息
// @keyup.enter, 触发事件 $emit('input', e.target.value)// ---------------------------------------------------------------------// my-table 表格组件的封装
// 1. 数据不能写死,动态传递表格渲染的数据 props
// 2. 结构不能写死 - 多处结构自定义 【具名插槽】
// (1) 表头支持自定义
// (2) 主体支持自定义import MyTag from './components/MyTag.vue'
import MyTable from './components/MyTable.vue'
export default {name: 'TableCase',components: {MyTag,MyTable},data () {return {// 测试组件功能的临时数据tempText: '水杯',tempText2: '钢笔',goods: [{ id: 101, picture: './assets/img/news.png', name: '梨皮朱泥三绝清代小品壶经典款紫砂壶', tag: '茶具' },{ id: 102, picture: './assets/img/news.png', name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌', tag: '男鞋' },{ id: 103, picture: './assets/img/news.png', name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm', tag: '儿童服饰' },{ id: 104, picture: './assets/img/news.png', name: '基础百搭,儿童套头针织毛衣1-9岁', tag: '儿童服饰' },]}}
}
</script><style lang="less" scoped>
.table-case {width: 1000px;margin: 50px auto;img {width: 100px;height: 100px;object-fit: contain;vertical-align: middle;}
}</style>
- 子组件
MyTable.vue
<template><table class="my-table"><thead><tr><slot name="head"></slot></tr></thead><tbody><tr v-for="(item, index) in data" :key="item.id"><slot name="body" :item="item" :index="index" ></slot></tr></tbody></table>
</template><script>
export default {props: {data: {type: Array,required: true}}
};
</script><style lang="less" scoped>
.my-table {width: 100%;border-spacing: 0;img {width: 100px;height: 100px;object-fit: contain;vertical-align: middle;}th {background: #f5f5f5;border-bottom: 2px solid #069;}td {border-bottom: 1px dashed #ccc;}td,th {text-align: center;padding: 10px;transition: all .5s;&.red {color: red;}}.none {height: 100px;line-height: 100px;color: #999;}
}
</style>
- 子组件
MyTag.vue
<template><div class="my-tag"><inputv-if="isEdit"v-focusref="inp"class="input"type="text"placeholder="输入标签":value="value"@blur="isEdit = false"@keyup.enter="handleEnter"/><div v-else@dblclick="handleClick"class="text">{{ value }}</div></div>
</template><script>
export default {props: {value: String},data () {return {isEdit: false}},methods: {handleClick () {// 双击后,切换到显示状态 (Vue是异步dom更新)this.isEdit = true// // 等dom更新完了,再获取焦点// this.$nextTick(() => {// // 立刻获取焦点// this.$refs.inp.focus()// })},handleEnter (e) {// 非空处理if (e.target.value.trim() === '') return alert('标签内容不能为空')// 子传父,将回车时,[输入框的内容] 提交给父组件更新// 由于父组件是v-model,触发事件,需要触发 input 事件this.$emit('input', e.target.value)// 提交完成,关闭输入状态this.isEdit = false}}
}
</script><style lang="less" scoped>
.my-tag {cursor: pointer;.input {appearance: none;outline: none;border: 1px solid #ccc;width: 100px;height: 40px;box-sizing: border-box;padding: 10px;color: #666;&::placeholder {color: #666;}}
}
</style>
- 入口
main.js
注册自定义指令
import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = false// 封装全局指令 focus
Vue.directive('focus', {// 指令所在的dom元素,被插入到页面中时触发inserted (el) {el.focus()}
})new Vue({render: h => h(App),
}).$mount('#app')