一.自定义指令
每个指令有着自己各自独立的功能,可以封装一些dom操作,扩展额外功能·v-focus、v-loading、v-lazy
①全局注册语法:
css
Vue.directive('指令名',{
// 指令名:指令的配置项
"inserted "(el) {
el.focus()
}
})
②局部注册语法:
css
```css
directive:{
"指令名":{
// 指令名:指令的配置项
inserted () {
el.focus()
}
}
}
使用:< input v-指令名 type="text" >
(1)指令的值
实现一个color指令,传入不同的颜色,给标签设置文字颜色
①语法:再绑定指令时,可以通过等号的形式为指令绑定具体的参数值
②通过binding.value可以拿到指令值,指令值修改会触发update函数
css
directives: {
color: {
// 1. inserted 提供的是元素被添加到页面中时的逻辑
inserted (el, binding) {
// console.log(el, binding.value);
// binding.value 就是指令的值
el.style.color = binding.value
},
// 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑
update (el, binding) {
console.log('指令的值修改了');
el.style.color = binding.value
}
}
}
(2)v-loading指令封装
场景:实际开发中,发送请求需要时间,在请求的数据未回来时,页面处于空白状态 => 用户体验不好
需求:封装一个v-loading指令,实现加载中的效果
分析:1.本质loading效果就是一个蒙层,盖在盒子上
2.数据请求中,开启loading状态,添加蒙层
3.数据请求完毕,关闭loading状态,移除蒙层
实现:1.准备一个loading类,通过伪元素定位,设置宽高,实现蒙层
2.开启关闭loading状态(添加移除蒙层),本质只需要添加移除类即可
3.结合自定义指令的语法进行封装复用
css
.loading:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #fff url('./loading.gif') no-repeat center;
}
(3)默认插槽
作用:让组件内部的一些结构支持自定义
需求:要在一个页面中显示一个对话框,封装成一个组件
问题:组件的内容部分,不希望写死,希望能够自定义
①基本语法
1.组件内需要定制的结构部分,改用< slot >占位
2.使用组件时,< MyDialog>标签内部,传入结构替换slot
css
<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>
②后备内容
通过插槽完成了内容的定制,传什么显示什么,但是如果不传,则是空白
封装组件时,可以为预留的< slot>插槽提供后备内容
**语法:**在< slot>标签内,放置内容,作为默认显示内容
**效果:**外部使用组件时,不传东西,则slot会显示后备内容;传东西了,则slot整体会被换掉
css
<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>
(4)具名插槽
语法:1.多个slot使用name属性区分内容
css
<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>
2.template配合v-slot名字来分发对应标签
css
<MyDialog>
<!-- 需要通过template标签包裹需要分发的结构,包成一个整体 -->
<template v-slot:head>
<div>我是大标题</div>
</template>
<template v-slot:content>
<div>我是内容</div>
</template>
<template #footer>
<button>取消</button>
<button>确认</button>
</template>
</MyDialog>
(5)作用域插槽
定义slot插槽的同时,是可以传值的,给插槽上可以绑定数据,将来使用组件时可以用
场景 :封装表格组件
1.父传子,动态渲染表格
2.利用默认插槽,定制操作列
3.删除或查看都需要用到当前项的id,属于组件内部的数据通过作用域插槽传值绑定,进而使用
基本使用步骤:
1.给slot标签,已添加属性的方式传值
2.所有添加的属性,都会被收集到一个对象中
3.在template,通过 #插槽名="obj" 接收,默认插槽为default
css
<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>
(6)综合案例 - 商品列表
css
<template>
<div class="my-tag">
<input
v-if="isEdit"
v-focus
ref="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>
css
<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: 'https://yanxuan-item.nosdn.127.net/f8c37ffa41ab1eb84bff499e1f6acfc7.jpg', name: '梨皮朱泥三绝清代小品壶经典款紫砂壶', tag: '茶具' },
{ id: 102, picture: 'https://yanxuan-item.nosdn.127.net/221317c85274a188174352474b859d7b.jpg', name: '全防水HABU旋钮牛皮户外徒步鞋山宁泰抗菌', tag: '男鞋' },
{ id: 103, picture: 'https://yanxuan-item.nosdn.127.net/cd4b840751ef4f7505c85004f0bebcb5.png', name: '毛茸茸小熊出没,儿童羊羔绒背心73-90cm', tag: '儿童服饰' },
{ id: 104, picture: 'https://yanxuan-item.nosdn.127.net/56eb25a38d7a630e76a608a9360eec6b.jpg', 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>
二.单页应用程序:SPA - Single Page Application
所有功能在一个html页面上实现
三.VueRouter
修改地址栏路径时,切换显示匹配的组件
四.组件存放目录问题 . vue文件本质无区别(更易维护)
组件分类:页面组件和复用组件