Vue组件---进阶

  • 能够掌握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 less-loader@5.0.0 -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>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值