- 能够掌握vue组件进阶知识使用
- 能够掌握自定义指令
- 能够完成tabbar案例
一.Vue 组件进阶
1.动态组件
目标:多个组件使用同一个挂载点,并动态切换
- 1. 准备被切换的 - UserName.vue / UserInfo.vue 2个组件
-
UserName.vue <template> <div> <div> <span>用户名</span> <input type="text"> </div> <div> <span>密码</span> <input type="password"> </div> </div> </template> <script> export default { data(){ return { } }, methods: { } } </script> <style> </style> /UserInfo.vue <template> <div> <div> <span>人生格言:</span> <input type="text"> </div> <div> <span>个人简介:</span> <textarea></textarea> </div> </div> </template> <script> export default { data(){ return { } }, methods: { } } </script> <style> </style>
- 2. 引入到UseDynamic.vue注册
-
UseDynamic.vue <template> <div> <button @click="comName = 'UserName'">账号密码填写</button> <button @click="comName = 'UserInfo'">个人信息填写</button> <p>下面显示注册组件-动态切换:</p> <div style="border: 1px solid red;"> <component :is="comName"></component> </div> </div> </template> <script> // 目标: 动态组件 - 切换组件显示 // 场景: 同一个挂载点要切换 不同组件 显示 // 1. 创建要被切换的组件 - 标签+样式 // 2. 引入到要展示的vue文件内, 注册 // 3. 变量-承载要显示的组件名 // 4. 设置挂载点<component :is="变量"></component> // 5. 点击按钮-切换comName的值为要显示的组件名 import UserName from '../components/01/UserName' import UserInfo from '../components/01/UserInfo' export default { data() { return { comName: "UserName" } }, components: { UserName, UserInfo } } </script> <style></style>
- 3. 准备变量来承载要显示的"组件名"
- 4. 设置挂载点, 使用is属性来设置要显示哪个组件
- 5. 点击按钮 – 修改comName变量里的"组件名"
运行此项目
什么是动态组件?
在同一个挂载点, 可以切换显示不同组件
如何使用动态组件?
vue内置的component组件, 配合is属性
如何切换?
改变is属性的值, 为要显示的组件名即可
2.组件缓存
创建02项目
userInfo.vue文件内容与上一项目的文件内容一致,02_UserDynamic.vue文件内容也是跟上一项目内容一致,但是不一样的是需要引入02项目中的组件
userName.vue
<template>
<div>
<div>
<span>用户名</span>
<input type="text" v-model="username">
</div>
<div>
<span>密码</span>
<input type="password" v-model="password">
</div>
</div>
</template>
<script>
export default {
data() {
return {
username: '',
password: ''
};
},
created() {
console.log("02-UserName-创建");
},
destroyed() {
console.log("02-UserName-销毁");
},
};
</script>
<style>
</style>
语法:
⚫ Vue内置的keep-alive组件 包起来要频繁切换的组件
使用keep-alive组件之后就不在触发创建销毁等命令
3.组件激活和非激活
目标:扩展2个新的生命周期方法
⚫ 方法名:
- activated – 激活时触发
- deactivated – 失去激活状态触发
基于上一内容
UserName.vue
<template>
<div>
<div>
<span>用户名</span>
<input type="text" v-model="username">
</div>
<div>
<span>密码</span>
<input type="password" v-model="password">
</div>
</div>
</template>
<script>
export default {
data() {
return {
username: '',
password: ''
};
},
created() {
console.log("02-UserName-创建");
},
destroyed() {
console.log("02-UserName-销毁");
},
// 组件缓存下 - 多了2个钩子函数
activated(){
console.log("02-UserName-激活");
},
deactivated(){
console.log("02-UserName-失去激活");
}
};
</script>
<style>
</style>
4.组件插槽
目标:通过 slot 标签, 让组件内可以接收不同的标签结构显示
⚫ 给组件插入什么标签, 组件就显示什么标签
1. 组件内用占位 2. 使用组件时夹着的地方, 传入标签替换slot
创建项目3
PanelComponent.vue
<template>
<div>
<!-- 按钮标题 -->
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="isShow = !isShow">
{
{ isShow ? "收起" : "展开" }}
</span>
</div>
<!-- 下拉内容 -->
<div class="container" v-show="isShow">
<slot></slot>
</div>
</div>
</template>
<script>
// 目标:组件插槽使用 - 为了让封装的组件显示不同的标签结构(灵活)
// 1.组件内<slot></solt>占位
// 2.使用组件,传入具体的标签替换 到slot位置
export default {
data() {
return {
isShow: false,
};
},
};
</script>
<style scoped>
h3 {
text-align: center;
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 1em;
}
.title h4 {
line-height: 2;
margin: 0;
}
.container {
border: 1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
img {
width: 50%;
}
</style>
03_UseSlot.vue
<template>
<div id="container">
<div id="app">
<h3>案例:折叠面板</h3>
<PanelComponent>
<img src="../assets/mm.gif" alt="">
<span>我是内容</span>
</PanelComponent>
<PanelComponent>
<p>寒雨连江夜入吴,</p>
<p>平明送客楚山孤。</p>
<p>洛阳亲友如相问,</p>
<p>一片冰心在玉壶。</p>
</PanelComponent>
</div>
</div>
</template>
<script>
import PanelComponent from "../components/03/PanelComponent.vue"
export default {
components: {
PanelComponent
}
};
</script>
<style>
#app {
width: 400px;
margin: 20px auto;
background-color: #fff;
border: 4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
padding: 1em 2em 2em;
}
</style>
在运行此项目之前,需要注意组件有没有添加到App.vue文件中
5.组件进阶 - 插槽默认内容
目标:如果外面不给传, 想给个默认显示内容
口诀: 内放置内容, 作为默认显示内容
效果:
- 不给组件传标签. slot内容原地显示
- 给组件内传标签, 则slot整体被换掉
6.组件进阶 – 具名插槽
目标:一个组件内有2处以上需要外部传入标签的地方
语法:
- 1. slot使用name属性区分名字
- 2. template配合v-slot:名字来分发对应标签
⚫ v-slot:可以简化成#
views/05_UseSlot.vue
<template>
<div id="container">
<div id="app">
<h3>案例:折叠面板</h3>
<PannelComment>
<!-- 需求: 插槽时, 使用组件内变量 -->
<!-- scope变量: {row: defaultObj} -->
<template v-slot="scope">
<p>{
{ scope.row.defaultTwo }}</p>
</template>
</PannelComment>
</div>
</div>
</template>
<script>
import PannelComment from "../components/05/PannelComment.vue";
export default {
components: {
PannelComment,
},
};
</script>
<style>
#app {
width: 400px;
margin: 20px auto;
background-color: #fff;
border: 4px solid blueviolet;
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
padding: 1em 2em 2em;
}
</style>
05/PannelComment.vue
<template>
<div>
<!-- 按钮标题 -->
<div class="title">
<h4>芙蓉楼送辛渐</h4>
<span class="btn" @click="isShow = !isShow">
{
{ isShow ? "收起" : "展开" }}
</span>
</div>
<!-- 下拉内容 -->
<div class="container" v-show="isShow">
<slot :row="defaultObj">{
{ defaultObj.defaultOne }}</slot>
</div>
</div>
</template>
<script>
// 目标: 作用域插槽
// 场景: 使用插槽, 使用组件内的变量
// 1. slot标签, 自定义属性和内变量关联
// 2. 使用组件, template配合v-slot="变量名"
// 变量名会收集slot身上属性和值形成对象
export default {
data() {
return {
isShow: false,
defaultObj: {
defaultOne: "无名氏",
defaultTwo: "小传同学"
}
};
},
};
</script>
<style scoped>
h3 {
text-align: center;
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ccc;
padding: 0 1em;
}
.title h4 {
line-height: 2;
margin: 0;
}
.container {
border: 1px solid #ccc;
padding: 0 1em;
}
.btn {
/* 鼠标改成手的形状 */
cursor: pointer;
}
img {
width: 50%;
}
</style>
作用域插槽什么时候使用? 使用组件插槽技术时, 需要用到子组件内变量 作用域插槽使用口诀? 子组件在slot身上添加属性和子组件的值 使用组件处template配合v-slot=“变量名”
收集slot身上的所有属性和值
7.作用域插槽
准备工作: 插槽显示默认内容
需求: 在使用此组件时, 不改右侧源码, 是否使用defaultTwo值替换默认内容
目标:使用插槽时, 想使用子组件内变量
⚫ 口诀:
- 1. 子组件, 在slot上绑定属性和子组件内的值
- 2. 使用组件, 传入自定义标签, 用template和v-slot="自定义变量名"
- 3. scope变量名自动绑定slot上所有属性和值
scope = {row: defaultObj}
8.组件进阶 – 作用域插槽使用场景
目标:自定义组件内标签+内容
- 准备MyTable.vue组件 – 内置表格, 传入数组循环铺设页面, 把对象每个内容显示在单元格里
- 准备UseTable.vue – 准备数据传入给MyTable.vue使用
例子: 我想要给td内显示图片, 需要传入自定义的img标签
- 在MyTable.vue的td中准备占位, 但是外面需要把图片地址赋予给src属性,所以在slot上把obj数据绑定
- 在UseTable使用MyTable的时候, template上v-slot绑定变量, 传入img组件设置图片地址
总组件App.vue
<template>
<div>
<h1>1.动态dynamic组件使用</h1>
<UseDynamic></UseDynamic>
<hr>
<h1>2.组件的缓存知识</h1>
<UseDynamic2></UseDynamic2>
<hr>
<h1>3.组件-插槽</h1>
<UseSlot></UseSlot>
<hr>
<h1>4.组件-具名插槽</h1>
<UseSlot2></UseSlot2>
<hr>
<h1>5. 组件-作用域插槽</h1>
<UseSlot3></UseSlot3>
<hr>
<h1>6.作用域插槽--使用场景</h1>
<p>组件内标签可以随意定义和数据使用</p>
<UseTable></UseTable>
</div>
</template>
<script>
import UseDynamic from './views/01_UseDynamic.vue';
import UseDynamic2 from './views/02_UseDynamic.vue';
import UseSlot from './views/03_UseSlot.vue';
import UseSlot2 from './views/04_UseSlot.vue';
import UseSlot3 from './views/05_UseSlot.vue';
import UseTable from './views/06_UseTable.vue'
export default {
data(){
return {
}
},
methods: {
},
components:{
UseDynamic,
UseDynamic2,
UseSlot,
UseSlot2,
UseSlot3,
UseTable
}
}
</script>
<style>
</style>
跳转组件06/MyTable.vue
<template>
<div>
<table border="1">
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
<th>头像</th>
</tr>
</thead>
<tbody>
<tr v-for="(obj, index) in arr" :key="index">
<td>{
{ index + 1 }}</td>
<td>{
{ obj.name }}</td>
<td>{
{ obj.age }}</td>
<td>
<slot :row="obj">
<!-- 默认值给上,如果使用组件不自定义标签显示默认文字 -->
{
{ obj.headImgUrl }}
</slot>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: {
arr: Array
}
}
</script>
跳转组件06_UseTable.vue
<template>
<div>
<MyTable :arr="list"></MyTable>
<MyTable :arr="list">
<!-- scope: {row: obj} -->
<template v-slot="scope">
<a :href="scope.row.headImgUrl">{
{ scope.row.headImgUrl }}</a>
</template>
</MyTable>
<MyTable :arr="list">
<template v-slot="scope">
<img style="width: 100px;" :src="scope.row.headImgUrl" alt="">
</template>
</MyTable>
</div>
</template>
<script>
import MyTable from "../components/06/MyTable";
export default {
components: {
MyTable,
},
data() {
return {
list: [
{
name: "小传同学",
age: 18,
headImgUrl:
"http://yun.itheima.com/Upload/./Images/20210303/603f2d2153241.jpg",
},
{
name: "小黑同学",
age: 25,
headImgUrl:
"http://yun.itheima.com/Upload/./Images/20210304/6040b101a18ef.jpg",
},
{
name: "智慧同学",
age: 21,
headImgUrl:
"http://yun.itheima.com/Upload/./Images/20210302/603e0142e535f.jpg",
},
],
};
},
};
</script>
<style></style>
我们为什么要使用作用域插槽? 可以让组件更加灵活的适用于不同的场景和项目
二.自定义指令
1.自定义指令_注册
目标:获取标签, 扩展额外的功能
全局注册 - 语法
在全局文件中设置全局变量main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 全局指令 - 到处"直接"使用
Vue.directive("gfocus", {
inserted(el) {
el.focus() // 触发标签的事件方法
}
})
new Vue({
render: h => h(App),
}).$mount('#app')
在组件中进行调用07_UseDirective.vue
<template>
<div>
<input type="text" v-gfocus>
</div>
</template>
<script>
// 目标:创建"自定义指令",让输入框自动聚焦
// 1.创建自定义指令
// 全局 / 局部
// 2.在标签上使用自定义指令 v-指令名
// 注意:
// inserted方法-- 指令所在的标签被插入到所在的网页上触发
</script>
<style>
</style>
局部注册 – 语法
直接在需要调用的组件中进行设置07_UseDirective.vue
<template>
<div>
<!-- <input type="text" v-gfocus> -->
<input type="text" v-focus>
</div>
</template>
<script>
// 目标:创建"自定义指令",让输入框自动聚焦
// 1.创建自定义指令
// 全局 / 局部
// 2.在标签上使用自定义指令 v-指令名
// 注意:
// inserted方法-- 指令所在的标签被插入到所在的网页上触发
export default {
directives:{
focus:{
inserted(el){
el.focus()
}
}
}
}
</script>
<style>
</style>
我们为什么要自定义指令? 在Vue内置指令满足不了需求时, 可以自己定义使用
2.自定义指令_传值
目标:定义color指令-传入一个颜色, 给标签设置文字颜色
语法:
使用:在标签上使用 v-color="'red'"
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
// 全局指令 - 到处"直接"使用
Vue.directive("gfocus", {
inserted(el) {
el.focus() // 触发标签的事件方法
}
})
// 目标: 自定义指令传值
Vue.directive('color', {
inserted(el, binding) {
el.style.color = binding.value
},
update(el, binding) {
el.style.color = binding.value
}
})
new Vue({
render: h => h(App),
}).$mount('#app')
07_UseDirective.vue
<template>
<div>
<!-- <input type="text" v-gfocus> -->
<input type="text" v-focus>
<!-- 全局设置 -->
<!-- <p v-color="'red'">修改文字颜色</p> -->
<!-- 局部设置 -->
<p v-color="colorStr">修改文字颜色</p>
</div>
</template>
<script>
// 目标: 创建 "自定义指令", 让输入框自动聚焦
// 1. 创建自定义指令
// 全局 / 局部
// 2. 在标签上使用自定义指令 v-指令名
// 注意:
// inserted方法 - 指令所在标签, 被插入到网页上触发(一次)
// update方法 - 指令对应数据/标签更新时, 此方法执行
export default {
data() {
return {
colorStr: 'red'
}
},
directives: {
focus: {
inserted(el) {
el.focus()
}
}
}
}
</script>
<style></style>
指令如何传值?
v-指令名="值"
指令值变化触发什么方法?
自定义指令的update方法而非inserted方法
三.tabbar 案例
1.tabbar案例_项目初始化
项目初始化
需求: 从0新建项目, 拆分组件, 创建使用
项目初始化
组件拆分: MyHeader.vue – 复用之前的
MyTabBar.vue – 底部导航
MyTable.vue – 封装表格 三个页面
- MyGoodsList.vue – 商品页
- MyGoodsSearch.vue – 搜索页
- MyUserInfo.vue – 用户信息页
分析:
- ①: 创建项目:vue create tabbar-demo
- ②: npm insatll less [email protected] -D
- ③: npm insatll bootstrap axios 并在main.js 引入和全局属性
- ④: 根据需求-创建需要的页面组件
- ⑤: 把昨天购物车案例-封装的MyHeader.vue文件复制过来复用
- ⑥: 从App.vue – 引入组织相关标签
-
<template> <div> <MyHeader :background="'blue'" :fontColor="'white'" title="Tabbar案例"></MyHeader> </div> </template> <script> import MyHeader from './components/MyHeader.vue'; export default { MyHeader, components: { MyHeader } } </script> <style> </style>
2.tabbar案例_底部封装
需求: 把底部导航也灵活封装起来
- ①: 基本标签+样式(md里复制)
- ②: 为tabbar组件指定数据源
- ③: 数据源最少2个, 最多5个(validator)
- ④: 从App.vue给MyTabBar.vue传入底部导航的数据
- ⑤: MyTabBar.vue中循环展示
- App.vue
-
<template> <div> <MyHeader :background="'blue'" :fontColor="'white'" title="Tabbar案例"></MyHeader> <MyTabBar :arr="tabList"></MyTabBar> </div> </template> <script> // 目标:完成底部封装 // 1.MyTarBar.vue组件标签+样式 准备 // 2.字体图标引入 // 3.准备底部数据 // 4.使用MyTabBar组件,传入数据(父传子),循环产生底部导航 // 5.子组件内props自定义检验规则(2-5项) import MyHeader from './components/MyHeader.vue'; import MyTabBar from './components/MyTabBar.vue'; export default { data(){ return{ tabList: [ { iconText: "icon-shangpinliebiao", text: "商品列表", componentName: "MyGoodsList" }, { iconText: "icon-sousuo", text: "商品搜索", componentName: "MyGoodsSearch" }, { iconText: "icon-user", text: "我的信息", componentName: "MyUserInfo" } ] }; }, components: { MyHeader, MyTabBar } } </script> <style> </style>
MyTabBar.vue
-
<template> <div class="my-tab-bar"> <div class="tab-item" v-for="(obj, index) in arr" :key="index" @click="btn(index, obj)" :class="{ current: index === selIndex }" > <!-- 图标 --> <span class="iconfont" :class="obj.iconText"></span> <!-- 文字 --> <span>{ { obj.text }}</span> </div> </div> </template> <script> // 目标: 点谁谁亮 // 1. 绑定点击事件 - 传入索引值 // 2. 循环索引 - 保存索引 对比 // 3. 点击把索引值同步给selIndex变量上, 引发上面判断的更新 export default { props: { arr: { type: Array, required: true, // 自定义校验规则 validator(value) { // value就是接到数组 if (value.length >= 2 && value.length <= 5) { return true; // 符合条件就return true } else { console.error("数据源必须在2-5项"); return false; } }, }, }, data() { return { selIndex: 0, // 默认第一个高亮 }; }, methods: { btn(index, theObj) { this.selIndex = index; // 点谁, 就把谁的索引值保存起来 this.$emit("changeCom", theObj.componentName); // 要切换的组件名传App.vue }, }, }; </script> <style lang="less" scoped> .my-tab-bar { position: fixed; left: 0; bottom: 0; width: 100%; height: 50px; border-top: 1px solid #ccc; display: flex; justify-content: space-around; align-items: center; background-color: white; .tab-item { display: flex; flex-direction: column; align-items: center; } } .current { color: #1d7bff; } </style>
3.tabbar案例_底部高亮
需求: 点击底部实现高亮效果
- ①: 绑定点击事件, 获取点击的索引
- ②: 循环的标签设置动态class, 遍历的索引, 和点击保存的索引比较, 相同则高亮
<template>
<div class="my-tab-bar">
<div
class="tab-item"
v-for="(obj, index) in arr"
:key="index"
@click="btn(index, obj)"
:class="{ current: index === selIndex }"
>
<!-- 图标 -->
<span class="iconfont" :class="obj.iconText"></span>
<!-- 文字 -->
<span>{
{ obj.text }}</span>
</div>
</div>
</template>
<script>
// 目标: 点谁谁亮
// 1. 绑定点击事件 - 传入索引值
// 2. 循环索引 - 保存索引 对比
// 3. 点击把索引值同步给selIndex变量上, 引发上面判断的更新
export default {
props: {
arr: {
type: Array,
required: true,
// 自定义校验规则
validator(value) {
// value就是接到数组
if (value.length >= 2 && value.length <= 5) {
return true; // 符合条件就return true
} else {
console.error("数据源必须在2-5项");
return false;
}
},
},
},
data() {
return {
selIndex: 0, // 默认第一个高亮
};
},
methods: {
btn(index, theObj) {
this.selIndex = index; // 点谁, 就把谁的索引值保存起来
this.$emit("changeCom", theObj.componentName); // 要切换的组件名传App.vue
},
},
};
</script>
<style lang="less" scoped>
.my-tab-bar {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 50px;
border-top: 1px solid #ccc;
display: flex;
justify-content: space-around;
align-items: center;
background-color: white;
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
}
}
.current {
color: #1d7bff;
}
</style>
4.tabbar案例_组件切换
需求: 点击底部切换组件
- ①: 底部导航传出动态组件名字符串到App.vue
- ②: 切换动态组件is属性的值为要显示的组件名
点击底部导航栏会出现页面进行跳转
MyGoodsList.vue
<template>
<div>
商品的列表页面
</div>
</template>
<script>
export default {
data(){
return {
}
},
methods: {
}
}
</script>
<style>
</style>
MyGoodsSearch.vue
<template>
<div>
商品的搜索页面
</div>
</template>
<script>
export default {
data(){
return {
}
},
methods: {
}
}
</script>
<style>
</style>
MyUserInfo.vue
<template>
<div>
个人中心页面
</div>
</template>
<script>
export default {
data(){
return {
}
},
methods: {
}
}
</script>
<style>
</style>
App.vue
<template>
<div>
<MyHeader :background="'blue'" :fontColor="'white'" title="TabBar案例"></MyHeader>
<div class="main">
<component :is="comName"></component>
</div>
<MyTabBar :arr="tabList" @changeCom="changeComFn"></MyTabBar>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader";
// 目标: 完成底部封装
// 1. MyTabBar.vue 组件标签+样式 准备
// 2. 字体图标引入
// 3. 准备底部数据
// 4. 使用MyTabBar组件, 传入数据(父->子), 循环产生底部导航
// 5. 子组件内props自定义检验规则(2-5项)
// 6. 子组件内循环产生底部导航
import MyTabBar from './components/MyTabBar'
// 目标: 切换组件显示
// 1. 创建组件 - 编写内容
// 2. 引入App.vue注册
// 3. 挂载点设置is
// 4. 切换comName的值(重要)
// 5. 底部导航点击- MyTabBar.vue里
// 子 -> 父技术 (传要切换的组件名出来)
import MyGoodsList from './views/MyGoodsList'
import MyGoodsSearch from './views/MyGoodsSearch'
import MyUserInfo from './views/MyUserInfo'
export default {
data() {
return {
comName: "MyGoodsList", // 默认显示的组件
tabList: [ // 底部导航的数据
{
iconText: "icon-shangpinliebiao",
text: "商品列表",
componentName: "MyGoodsList",
},
{
iconText: "icon-sousuo",
text: "商品搜索",
componentName: "MyGoodsSearch",
},
{
iconText: "icon-user",
text: "我的信息",
componentName: "MyUserInfo",
},
],
};
},
components: {
MyHeader,
MyTabBar,
MyGoodsList,
MyGoodsSearch,
MyUserInfo
},
methods: {
changeComFn(cName) {
this.comName = cName; // MyTabBar里选出来的组件名赋予给is属性的comName
// 导致组件的切换
}
}
};
</script>
<style scoped>
.main {
padding-top: 45px;
padding-bottom: 51px;
}
</style>
MyTarBar.vue
<template>
<div class="my-tab-bar">
<div
class="tab-item"
v-for="(obj, index) in arr"
:key="index"
@click="btn(index, obj)"
:class="{ current: index === selIndex }"
>
<!-- 图标 -->
<span class="iconfont" :class="obj.iconText"></span>
<!-- 文字 -->
<span>{
{ obj.text }}</span>
</div>
</div>
</template>
<script>
// 目标: 点谁谁亮
// 1. 绑定点击事件 - 传入索引值
// 2. 循环索引 - 保存索引 对比
// 3. 点击把索引值同步给selIndex变量上, 引发上面判断的更新
export default {
props: {
arr: {
type: Array,
required: true,
// 自定义校验规则
validator(value) {
// value就是接到数组
if (value.length >= 2 && value.length <= 5) {
return true; // 符合条件就return true
} else {
console.error("数据源必须在2-5项");
return false;
}
},
},
},
data() {
return {
selIndex: 0, // 默认第一个高亮
};
},
methods: {
btn(index, theObj) {
this.selIndex = index; // 点谁, 就把谁的索引值保存起来
this.$emit("changeCom", theObj.componentName); // 要切换的组件名传App.vue
},
},
};
</script>
<style lang="less" scoped>
.my-tab-bar {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 50px;
border-top: 1px solid #ccc;
display: flex;
justify-content: space-around;
align-items: center;
background-color: white;
.tab-item {
display: flex;
flex-direction: column;
align-items: center;
}
}
.current {
color: #1d7bff;
}
</style>
5.tabbar案例_商品列表
需求: 商品列表铺设页面
-
①: 封装MyTable.vue – 准备标签和样式
-
②: axios在MyGoodsList.vue请求数据回来
-
③: 请求地址: https://www.escook.cn/api/goods
-
④: 传入MyTable.vue中循环数据显示
-
⑤: 给删除按钮添加bootstrap的样式: btn btn-danger btn-sm
6.tabbar案例_商品表格_插槽使用
需求: 允许用户自定义表格头和表格单元格内容
- ①: 把MyTable.vue里准备slot
- ②: 使用MyTable组件时传入具体标签
- ①: 插槽里传入的td单元格
- ②: 自定义span标签的循环展示-给予样式
7.tabbar案例_商品表格_删除数据
- ①: 删除按钮绑定点击事件
- ②: 作用域插槽绑定id值出来
- ③: 传给删除方法, 删除MyGoodsList.vue里数组里数据
8.tabbar案例_商品表格_添加tab
- 需求1: 点击Tab, 按钮消失, 输入框出现 需求2: 输入框自动聚焦
- 需求3: 失去焦点, 输入框消失, 按钮出
- 需求4: 监测input回车, 无数据拦截
- 需求5: 监测input取消, 清空数据
- 需求6: 监测input回车, 有数据添加
main.js引入第三方库
import Vue from 'vue'
import App from './App.vue'
import "./assets/fonts/iconfont.css" //引入字体图标的·css文件
import "bootstrap/dist/css/bootstrap.css"
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
MyGoodsList.vue
<template>
<div>
<MyTable :arr="list">
<template #header>
<th>#</th>
<th>商品名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th>
</template>
<!-- scope的值: {row: obj} -->
<template #body="scope">
<td>{
{ scope.row.id }}</td>
<td>{
{ scope.row.goods_name }}</td>
<td>{
{ scope.row.goods_price }}</td>
<td>
<input
class="tag-input form-control"
style="width: 100px;"
type="text"
v-if="scope.row.inputVisible"
v-focus
@blur="scope.row.inputVisible = false"
@keydown.enter="enterFn(scope.row)"
v-model="scope.row.inputValue"
@keydown.esc="scope.row.inputValue = ''"
/>
<button
v-else
style="display: block;"
class="btn btn-primary btn-sm add-tag"
@click="scope.row.inputVisible = true"
>+Tag</button>
<span v-for="(str, ind) in scope.row.tags" :key="ind"
class="badge badge-warning"
>
{
{ str }}
</span>
</td>
<td>
<button class="btn btn-danger btn-sm"
@click="removeBtn(scope.row.id)"
>删除</button>
</td>
</template>
</MyTable>
</div>
</template>
<script>
import MyTable from "../components/MyTable";
import axios from "axios";
axios.defaults.baseURL = "https://www.escook.cn";
// 目标: 循环商品列表表格
// 1. 封装MyTable.vue 整体表格组件-一套标签和样式
// 2. axios请求数据
// 3. 传入MyTable组件里循环tr显示数据
// 目标: 展示tags标签
// 1. tags数组 - 某个td循环span使用文字
// 2. span设置bs的样式
// 目标: 删除数据
// 1. 删除按钮 - 点击事件
// 2. 通过作用域插槽拿到id, 实现点击事件方法
// 3. 通过id查找数组里数据, 找到索引
export default {
components: {
MyTable,
},
data() {
return {
list: [] // 所有数据
};
},
created() {
axios({
url: "/api/goods",
}).then((res) => {
console.log(res);
this.list = res.data.data;
});
},
methods: {
removeBtn(id){
let index = this.list.findIndex(obj => obj.id === id)
this.list.splice(index, 1)
},
enterFn(obj){ // 回车
// console.log(obj.inputValue);
if (obj.inputValue.trim().length === 0) {
alert('请输入数据')
return
}
obj.tags.push(obj.inputValue) // 表单里的字符串状态tags数组
obj.inputValue = ""
}
}
};
</script>
<style>
</style>
MyTable.vue
<template>
<table class="table table-bordered table-stripped">
<!-- 表格标题区域 -->
<thead>
<tr>
<!-- <th>#</th>
<th>商品名称</th>
<th>价格</th>
<th>标签</th>
<th>操作</th> -->
<slot name="header"></slot>
</tr>
</thead>
<!-- 表格主体区域 -->
<tbody>
<tr v-for="obj in arr"
:key="obj.id"
>
<!-- <td>{
{ obj.id }}</td>
<td>{
{ obj.goods_name }}</td>
<td>{
{ obj.goods_price }}</td>
<td>{
{ obj.tags }}</td>
<td>
<button class="btn btn-danger btn-sm">删除</button>
</td> -->
<slot name="body" :row="obj"></slot>
</tr>
</tbody>
</table>
</template>
<script>
export default {
name: 'MyTable',
props: {
arr: Array
}
}
</script>
<style scoped lang="less">
.my-goods-list {
.badge {
margin-right: 5px;
}
}
</style>
App.vue
<template>
<div>
<MyHeader
:background="'blue'"
:fontColor="'white'"
title="TabBar案例"
></MyHeader>
<div class="main">
<component :is="comName"></component>
</div>
<MyTabBar :arr="tabList"
@changeCom="changeComFn"
></MyTabBar>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader";
// 目标: 完成底部封装
// 1. MyTabBar.vue 组件标签+样式 准备
// 2. 字体图标引入
// 3. 准备底部数据
// 4. 使用MyTabBar组件, 传入数据(父->子), 循环产生底部导航
// 5. 子组件内props自定义检验规则(2-5项)
// 6. 子组件内循环产生底部导航
import MyTabBar from './components/MyTabBar'
// 目标: 切换组件显示
// 1. 创建组件 - 编写内容
// 2. 引入App.vue注册
// 3. 挂载点设置is
// 4. 切换comName的值(重要)
// 5. 底部导航点击- MyTabBar.vue里
// 子 -> 父技术 (传要切换的组件名出来)
import MyGoodsList from './views/MyGoodsList'
import MyGoodsSearch from './views/MyGoodsSearch'
import MyUserInfo from './views/MyUserInfo'
export default {
data() {
return {
comName: "MyGoodsList", // 默认显示的组件
tabList: [ // 底部导航的数据
{
iconText: "icon-shangpinliebiao",
text: "商品列表",
componentName: "MyGoodsList",
},
{
iconText: "icon-sousuo",
text: "商品搜索",
componentName: "MyGoodsSearch",
},
{
iconText: "icon-user",
text: "我的信息",
componentName: "MyUserInfo",
},
],
};
},
components: {
MyHeader,
MyTabBar,
MyGoodsList,
MyGoodsSearch,
MyUserInfo
},
methods: {
changeComFn(cName){
this.comName = cName; // MyTabBar里选出来的组件名赋予给is属性的comName
// 导致组件的切换
}
}
};
</script>
<style scoped>
.main{
padding-top: 45px;
padding-bottom: 51px;
}
</style>