昨日复习

  1. 购物车完整案例-父传子 props- 子传父 自定义事件
  2. 动态组件-多个不同的组件要显示在一个位置 ```html

// 变量- 组件名称-AChild1-AChild2

  1. 2. 组件的缓存
  2. > 组件被缓存了,组件没有被销毁。Vue的内置组件 keep-alive
  3. ```vue
  4. <keep-alive>
  5. // 组件-
  6. </keep-alive>

保留了组件的实例,组件的虚拟dom,真实dom销毁了

  • 组件只会创建一次,created只会执行一次

    提供了两个新的钩子函数-激活事件- 休眠事件

  • 插槽

    占位

<template>
   <slot></slot>
</template>

填补内容

<child>
  // 填补内容

</child>
  • 匿名插槽

    <slot></slot>
    // 没有起name 匿名插槽
    
  • 具名插槽

    <slot name="before"></slot>
    

    填补的时候需要指定名称

    <child>
    // 新的语法
    <template v-slot:before> 123 </template>
    // 旧的语法
    <template slot="before">123</template>
    </child>
    
  • 后备内容

    后备-没有人传入插槽内容的时候,后备内容回显示

<slot>后备内容</slot>
  • 作用域插槽

    父组件传插槽内容的时候想要使用子组件的数据

  1. 先在子组件传出你想让父组件用的数据

    <slot a="1" b="2" c="3" :d="变量"></slot>
    <slot name="after" a="1" b="2" c="3" :d="变量"></slot>
    
  2. 传插槽内容时,获取变量

    <child>
    // obj是传出的所有的属性的集合
    <template v-slot:after="obj">
    <template slot="after" slot-scope="obj">
    
    <div >
      a: {{ obj.a  }}
      b: {{ obj.b }}
    
    </div>
    </template>
    </child>
    

    作用域插槽 - 匿名插槽- 具名插槽

  • 用具名插槽的时候

    <child>
     <div slot="名字" slot-scope="变量名">
     <template v-slot:名字="变量名">
    </child>
    
  • 自定义指令

    开发者也可以封装指令

  • 局部注册和全局注册 ```javascript export default { directives: { “focus”: {

      // 指令描述
     inserted(el, options) {
       // 此时表示此指令作用的dom元素已经被插入到页面上
     }
    

    } }

}

.sync 修饰符
> 子传父- 1. 子组件触发 2.父组件监听

.sync- 1.子组件触发


<a name="DpBBM"></a>
### 3.0 案例-tabbar-初始化项目
目标: 创建项目文件夹, 引入字体图标, 下载bootstrap, less, less-loader@5.0.0 axios, 在App.vue注册组件

- 需求: 从0新建项目, 拆分组件, 创建使用

组件分析:

- 组件拆分:
   - MyHeader.vue – ==复用之前的==
   - MyTabBar.vue – 底部导航
   - MyTable.vue – 封装表格
- 三个页面
   - -MyGoodsList.vue – 商品页
   - MyGoodsSearch.vue – 搜索页
   - -MyUserInfo.vue – 用户信息页

思路分析:<br />    ①: vue create tabbar-demo<br />    ②: yarn add less less-loader@5.0.0 -D<br />    ③: yarn add bootstrap axios 并在main.js 引入和全局属性<br />    ④: 根据需求-创建需要的页面组件<br />    ⑤: 把昨天购物车案例-封装的MyHeader.vue文件复制过来复用<br />    ⑥: 从App.vue – 引入组织相关标签<br />新建工程:<br />vue create tabbar-demo<br />yarn add less less-loader@5.0.0 -D<br />yarn add bootstrap axios<br />在main.js中引入bootStrap.css和字体图标样式<br />import "bootstrap/dist/css/bootstrap.css"<br />import "./assets/fonts/iconfont.css"<br />创建/复制如下文件<br />从昨天案例中-直接复制过来components/MyHeader.vue<br />components/MyTabBar.vue<br />views/MyGoodsList.vue<br />views/MyGoodsSearch.vue<br />views/MyUserInfo.vue<br />components/MyTable.vue
<a name="Tlrz9"></a>
### 3.1 案例-tabbar-底部封装
目标: 实现MyTabBar.vue组件

- 需求: 把底部导航也灵活封装起来

分析:<br />    ①: 基本标签+样式(md里复制)<br />    ②: 为tabbar组件指定数据源<br />    ③: 数据源最少2个, 最多5个(validator)<br />    ④: 从App.vue给MyTabBar.vue传入底部导航的数据<br />    ⑤: MyTabBar.vue中循环展示<br />App.vue-数组准备
```json
tabList: [
    {
        iconText: "icon-shangpinliebiao",
        text: "商品列表",
        componentName: "MyGoodsList"
    },
    {
        iconText: "icon-sousuo",
        text: "商品搜索",
        componentName: "MyGoodsSearch"
    },
    {
        iconText: "icon-user",
        text: "我的信息",
        componentName: "MyUserInfo"
    }
]

MyTabBar.vue - 标签模板

<template>
  <div class="my-tab-bar">
    <div class="tab-item">
      <!-- 图标 -->
      <span class="iconfont"></span>
      <!-- 文字 -->
      <span></span>
    </div>
  </div>
</template>
<script>
export default {

}
</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>

MyTabBar.vue正确代码

<template>
  <div class="my-tab-bar">
    <div class="tab-item" v-for="(item, index) in tabList" :key="index">
      <!-- 图标 -->
      <span :class="`iconfont ${item.iconText}`"></span>
      <!-- 文字 -->
      <span>{{ item.text }}</span>
    </div>
  </div>
</template>
<script>
export default {
  // props: ["tabList"], // 最基本的接收 但是不能校验
  props: {
    tabList: {
      type: Array,
      required: true, // 要求必填
      // 校验的意思
      validator(value) {
        // 最低的长度 2 最大的长度是 5
        if (value.length >= 2 && value.length <= 5) {
          return true; // 表示通过了校验
        } else {
          return false; // 表示没有通过校验 就会报错
        }
      },
    },
  },
};
</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.2 案例-tabbar-底部高亮

目标: 点击底部导航实现高亮效果

  • 需求: 点击底部实现高亮效果

分析:
①: 绑定点击事件, 获取点击的索引
②: 循环的标签设置动态class, 遍历的索引, 和点击保存的索引比较, 相同则高亮
效果演示:
MyTabBar.vue(==正确代码==)

<template>
  <div class="my-tab-bar">
    <div
      class="tab-item"
      v-for="(item, index) in tabList"
      :class="{ current: index === activeIndex }"
      @click="changeIndex(index)"
      :key="index"
    >
      <!-- 图标 -->
      <span :class="`iconfont ${item.iconText}`"></span>
      <!-- 文字 -->
      <span>{{ item.text }}</span>
    </div>
  </div>
</template>
<script>
export default {
  // props: ["tabList"], // 最基本的接收 但是不能校验
  props: {
    tabList: {
      type: Array,
      required: true, // 要求必填
      // 校验的意思
      validator(value) {
        // 最低的长度 2 最大的长度是 5
        if (value.length >= 2 && value.length <= 5) {
          return true; // 表示通过了校验
        } else {
          return false; // 表示没有通过校验 就会报错
        }
      },
    },
  },
  data() {
    return {
      activeIndex: 0, // 默认第一个激活
    };
  },
  methods: {
    changeIndex(index) {
      this.activeIndex = index;
    },
  },
};
</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.3 案例-tabbar-组件切换

目的: 点击底部导航, 切换页面组件显示
需求: 点击底部切换组件
分析:
①: 底部导航传出动态组件名字符串到App.vue
②: 切换动态组件is属性的值为要显示的组件名
效果演示:
补充: 给内容div.app- 设置上下内边距
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";
import MyTabBar from './components/MyTabBar'
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>

MyTabBar.vue - 点击传递过来组件名
methods: {
btn(index, theObj) {
this.selIndex = index; // 点谁, 就把谁的索引值保存起来
this.$emit(“changeCom”, theObj.componentName); // 要切换的组件名传App.vue
},
},

3.4 案例-tabbar-商品列表

目标: 为MyGoodsList页面, 准备表格组件MyTable.vue-铺设展示数据

  • 需求: 商品列表铺设页面

分析:
①: 封装MyTable.vue – 准备标签和样式
②: axios在MyGoodsList.vue请求数据回来
③: 请求地址: http://localhost:3000/goodsList
json-server启动json数据

{
    "goodsList": [
        {
            "id": 1,
            "goods_name": "Teenmix/天美意夏季专柜同款金色布女鞋6YF18BT6",
            "goods_price": 298,
            "tags": [
                "舒适",
                "透气"
            ],
            "inputVisible": false,
            "inputValue": ""
        },
        {
            "id": 2,
            "goods_name": "奥休斯(all shoes) 冬季保暖女士休闲雪地靴 舒适加绒防水短靴 防滑棉鞋子",
            "goods_price": 89,
            "tags": [
                "保暖",
                "防滑"
            ],
            "inputVisible": false,
            "inputValue": ""
        },
        {
            "id": 3,
            "goods_name": "初语秋冬新款毛衣女 套头宽松针织衫简约插肩袖上衣",
            "goods_price": 199,
            "tags": [
                "秋冬",
                "毛衣"
            ],
            "inputVisible": false,
            "inputValue": ""
        },
        {
            "id": 4,
            "goods_name": "佐露絲蕾丝衫女2020春秋装新款大码女装衬衫上衣雪纺衫韩版打底衫长袖",
            "goods_price": 19,
            "tags": [
                "雪纺衫",
                "打底"
            ],
            "inputVisible": false,
            "inputValue": ""
        },
        {
            "id": 5,
            "goods_name": "熙世界中长款长袖圆领毛衣女2022秋装新款假两件连衣裙女107SL170",
            "goods_price": 178,
            "tags": [
                "圆领",
                "连衣裙"
            ],
            "inputVisible": false,
            "inputValue": ""
        },
        {
            "id": 6,
            "goods_name": "烟花烫2021秋季装新品女装简约修身显瘦七分袖欧根纱连衣裙 花央",
            "goods_price": 282,
            "tags": [
                "秋季新品",
                "显瘦"
            ],
            "inputVisible": false,
            "inputValue": ""
        },
        {
            "id": 7,
            "goods_name": "韩都衣舍2021韩版女装秋装新宽松显瘦纯色系带长袖衬衫NG8201",
            "goods_price": 128,
            "tags": [
                "韩都衣舍",
                "长袖衬衫"
            ],
            "inputVisible": false,
            "inputValue": ""
        },
        {
            "id": 8,
            "goods_name": "预售纤莉秀大码女装胖妹妹秋装2020新款圆领百搭绣花胖mm休闲套头卫衣",
            "goods_price": 128,
            "tags": [
                "预售",
                "卫衣"
            ],
            "inputVisible": false,
            "inputValue": ""
        },
        {
            "id": 9,
            "goods_name": "莎密2022夏改良旗袍裙连衣裙修身复古时尚日常短款礼服旗袍",
            "goods_price": 128,
            "tags": [
                "莎密",
                "礼服"
            ],
            "inputVisible": false,
            "inputValue": ""
        },
        {
            "id": 10,
            "goods_name": "南极人秋冬韩版七彩棉加绒加厚一体保暖打底裤p7011",
            "goods_price": 128,
            "tags": [
                "南极人",
                "打底裤"
            ],
            "inputVisible": false,
            "inputValue": ""
        }
    ]
}
④: 传入MyTable.vue中循环数据显示<br />    ⑤: 给删除按钮添加bootstrap的样式: btn btn-danger btn-sm<br />效果演示:<br />MyTable.vue - 准备table整个表格标签和样式(可复制)
<template>
  <table class="table table-bordered table-stripped">
    <!-- 表格标题区域 -->
    <thead>
      <tr>
        <th>#</th>
        <th>商品名称</th>
        <th>价格</th>
        <th>标签</th>
        <th>操作</th>
      </tr>
    </thead>
    <!-- 表格主体区域 -->
    <tbody>
      <tr >
        <td>1</td>
        <td>商品</td>
        <td>998</td>
        <td>xxx</td>
        <td>xxx</td>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  name: 'MyTable'
}
</script>


<style scoped lang="less">
.my-goods-list {
  .badge {
    margin-right: 5px;
  }
}
</style>

使用axios请求数据, 把表格页面铺设出来
main.js - 注册axios配置默认地址
import axios from “axios”;
axios.defaults.baseURL = “http://localhost:3000“;
MyGoodsList.vue - 使用axios请求数据, 把数据传入给MyTable.vue里循环铺设

MyTable.vue里正确代码

<template>
  <table class="table table-bordered table-stripped">
    <!-- 表格标题区域 -->
    <thead>
      <tr>
        <th>#</th>
        <th>商品名称</th>
        <th>价格</th>
        <th>标签</th>
        <th>操作</th>
      </tr>
    </thead>
    <!-- 表格主体区域 -->
    <tbody>
      <tr v-for="item in list" :key="item.id">
        <td>{{ item.id }}</td>
        <td>{{ item.goods_name }}</td>
        <td>{{ item.goods_price }}</td>
        <td>{{ item.tags }}</td>
        <td>
          <button class="btn btn-danger btn-sm">删除</button>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  name: "MyTable",
  props: {
    list: {
      type: Array,
      required: true,
    },
  },
};
</script>


<style scoped lang="less">
.my-goods-list {
  .badge {
    margin-right: 5px;
  }
}
</style>

MyGoodsList.vue代码

<template>
  <div>
    <my-table :list="list"></my-table>
  </div>
</template>

<script>
import MyTable from "../components/MyTable.vue";
import axios from "axios";
axios.defaults.baseURL = "http://localhost:3000";
export default {
  components: {
    MyTable,
  },
  data() {
    return {
      list: [], // 用来接收数据
    };
  },
  created() {
    this.getGoodsList();
  },
  methods: {
    getGoodsList() {
      axios.get("/goodsList").then(({ data }) => {
        this.list = data; // 获取返回的数据
      });
    },
  },
};
</script>

<style>
</style>

3.5_案例-tabbar-商品表格-插槽

目标: 使用插槽技术, 和作用域插槽技术, 给MyTable.vue组件, 自定义列标题, 自定义表格内容

  • 需求: 允许用户自定义表格头和表格单元格内容

分析:
①: 把MyTable.vue里准备slot
②: 使用MyTable组件时传入具体标签
步骤:

  1. 提高组件==复用性和灵活性==, 把表格列标题thead部分预留标签, 设置name属性
  2. 使用MyTable.vue时, 传入列标题标签
  3. 表格内容td部分也可以让组件使用者自定义, 也给tbody下tr内留好标签和name属性名
  4. 使用插槽需要用到插槽内的obj对象上的数据, 使用作用域插槽技术

MyTable.vue - 留好具名插槽

<template>
  <table class="table table-bordered table-stripped">
    <!-- 表格标题区域 -->
    <thead>
      <tr>
        <slot name="head"></slot>
      </tr>
    </thead>
    <!-- 表格主体区域 -->
    <tbody>
      <tr v-for="item in list" :key="item.id">
        <slot :row="item" name="body"></slot>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  name: "MyTable",
  props: {
    list: {
      type: Array,
      required: true,
    },
  },
};
</script>


<style scoped lang="less">
.my-goods-list {
  .badge {
    margin-right: 5px;
  }
}
</style>

MyGoodsList.vue

<template>
  <div>
    <my-table :list="list">
      <template #head>
        <!-- <template slot="head"> -->
        <!-- <template v-slot:head> -->
        <th>#</th>
        <th>商品名称</th>
        <th>价格</th>
        <th>标签</th>
        <th>操作</th>
      </template>
      <template #body="{ row }">
        <td>{{ row.id }}</td>
        <td>{{ row.goods_name }}</td>
        <td>{{ row.goods_price }}</td>
        <td>{{ row.tags }}</td>
        <td>
          <button class="btn btn-danger btn-sm">删除</button>
        </td>
      </template>
    </my-table>
  </div>
</template>

<script>
import MyTable from "../components/MyTable.vue";
import axios from "axios";
axios.defaults.baseURL = "http://localhost:3000";
export default {
  components: {
    MyTable,
  },
  data() {
    return {
      list: [], // 用来接收数据
    };
  },
  created() {
    this.getGoodsList();
  },
  methods: {
    getGoodsList() {
      axios.get("/goodsList").then(({ data }) => {
        this.list = data; // 获取返回的数据
      });
    },
  },
};
</script>

<style>
</style>

3.6 案例-tabbar-商品表格-tags微标

目标: 把单元格里的标签, tags徽章铺设下

  • 需求: 标签列自定义显示

分析:
①: 插槽里传入的td单元格
②: 自定义span标签的循环展示-给予样式
效果演示:
bootstrap徽章: https://v4.bootcss.com/docs/components/badge/
MyGoodsList.vue - 插槽
class=”badge badge-warning”
>
{{ str }}

下面额外添加样式

3.7 案例-tabbar-商品表格-删除功能

目标: 点击删除对应这条数据

  • 需求: 点击删除按钮删除数据

分析:
①: 删除按钮绑定点击事件
②: 作用域插槽绑定id值出来
③: 传给删除方法, 删除MyGoodsList.vue里数组里数据
效果演示
提示: id在MyTable.vue里, 但是MyGoodsList.vue里要使用, 而且在插槽位置, 使用作用域插槽已经把整个obj对象(包含id)带出来了
MyGoodsList.vue组件

<template>
  <div>
    <my-table :list="list">
      <template #head>
        <!-- <template slot="head"> -->
        <!-- <template v-slot:head> -->
        <th>#</th>
        <th>商品名称</th>
        <th>价格</th>
        <th>标签</th>
        <th>操作</th>
      </template>
      <template #body="{ row }">
        <td>{{ row.id }}</td>
        <td>{{ row.goods_name }}</td>
        <td>{{ row.goods_price }}</td>
        <!-- 循环row.tags -->
        <td>
          <span
            class="badge badge-warning"
            v-for="(item, index) in row.tags"
            :key="index"
            >{{ item }}</span
          >
        </td>
        <td>
          <button @click="delGoods(row.id)" class="btn btn-danger btn-sm">
            删除
          </button>
        </td>
      </template>
    </my-table>
  </div>
</template>

<script>
import MyTable from "../components/MyTable.vue";
import axios from "axios";
axios.defaults.baseURL = "http://localhost:3000";
export default {
  components: {
    MyTable,
  },
  data() {
    return {
      list: [], // 用来接收数据
    };
  },
  created() {
    this.getGoodsList();
  },
  methods: {
    getGoodsList() {
      axios.get("/goodsList").then(({ data }) => {
        this.list = data; // 获取返回的数据
      });
    },
    delGoods(id) {
      // 正常的方式
      if (confirm("确定要删除该商品吗")) {
        // axios.delete("/goodsList/" + id); // 正常应该调用删除接口
        this.list = this.list.filter((item) => item.id !== id); // 测试开发
        // this.getGoodsList();
      }
    },
  },
};
</script>

<style>
</style>

3.8 案例-tabbar-添加tab

目标: 实现点击tab按钮, 出现输入框自动获取焦点, 失去焦点关闭input, 回车新增tag, esc清空内容

  • 需求1: 点击Tab, 按钮消失, 输入框出现
  • 需求2: 输入框自动聚焦
  • 需求3: 失去焦点, 输入框消失, 按钮出
  • 需求4: 监测input回车, 无数据拦截
  • 需求5: 监测input取消, 清空数据
  • 需求6: 监测input回车, 有数据添加

效果目标:

3.8.0 点击按钮消失, 输入框出现

MyGoodsList.vue - 标签位置添加
注意: 每个tab按钮和input都是独立变量控制, 那么直接在row身上的属性控制即可

<input
          class="tag-input form-control"
          style="width: 100px;"
          type="text"
          v-if="row.inputVisible"
          />
          <button 
          v-else 
          style="display: block;" 
          class="btn btn-primary btn-sm add-tag"
          @click="row.inputVisible = true"
          >+Tag</button>

3.8.1 input自动获取焦点

main.js - 定义全局自定义指令

 directives: {
    focus: {
      // 指令作用的标签插入元素之后 生效
      inserted(el) {
        el.focus(); // 聚焦
      },
    },
  },

MyGoodsList.vue - 使用 v-focus指令

3.8.2 input失去焦点关闭input

监听input失去焦点事件, 让input消失

<input
            class="tag-input form-control"
            style="width: 100px"
            type="text"
            v-if="row.inputVisible"
            v-focus
            @blur="row.inputVisible = false"
            @keyup.enter="enterInput($event, row)"
            @keyup.esc="$event.target.value = ''"
          />

3.8.3 input回车新增tag

监听input的回车事件, 如果无数据拦截代码

<input
            class="tag-input form-control"
            style="width: 100px"
            type="text"
            v-if="row.inputVisible"
            v-focus
            @blur="row.inputVisible = false"
            @keyup.enter="enterInput($event, row)"
            @keyup.esc="$event.target.value = ''"
          />

事件处理函数

enterInput(event, row) {
      if (!event.target.value) {
        alert("输入内容不能为空");
        return;
      }
      row.tags.push(event.target.value);
      event.target.value = ""; // 清空原来的值
    },

3.8.4 input框esc清空内容

 <input
            class="tag-input form-control"
            style="width: 100px"
            type="text"
            v-if="row.inputVisible"
            v-focus
            @blur="row.inputVisible = false"
            @keyup.enter="enterInput($event, row)"
            @keyup.esc="$event.target.value = ''"
          />

MyGoodsList.vue完整代码

<template>
  <div>
    <my-table :list="list">
      <template #head>
        <!-- <template slot="head"> -->
        <!-- <template v-slot:head> -->
        <th>#</th>
        <th>商品名称</th>
        <th>价格</th>
        <th>标签</th>
        <th>操作</th>
      </template>
      <template #body="{ row }">
        <td>{{ row.id }}</td>
        <td>{{ row.goods_name }}</td>
        <td>{{ row.goods_price }}</td>
        <!-- 循环row.tags -->
        <td>
          <!-- 需要放入按钮和输入框 -->
          <!-- 互斥 -->
          <input
            class="tag-input form-control"
            style="width: 100px"
            type="text"
            v-if="row.inputVisible"
            v-focus
            @blur="row.inputVisible = false"
            @keyup.enter="enterInput($event, row)"
            @keyup.esc="$event.target.value = ''"
          />
          <button
            v-else
            style="display: block"
            class="btn btn-primary btn-sm add-tag"
            @click="row.inputVisible = true"
          >
            +Tag
          </button>
          <span
            class="badge badge-warning"
            v-for="(item, index) in row.tags"
            :key="index"
            >{{ item }}</span
          >
        </td>
        <td>
          <button @click="delGoods(row.id)" class="btn btn-danger btn-sm">
            删除
          </button>
        </td>
      </template>
    </my-table>
  </div>
</template>

<script>
import MyTable from "../components/MyTable.vue";
import axios from "axios";
axios.defaults.baseURL = "http://localhost:3000";
export default {
  components: {
    MyTable,
  },
  data() {
    return {
      list: [], // 用来接收数据
    };
  },
  directives: {
    focus: {
      // 指令作用的标签插入元素之后 生效
      inserted(el) {
        el.focus(); // 聚焦
      },
    },
  },
  created() {
    this.getGoodsList();
  },
  methods: {
    getGoodsList() {
      axios.get("/goodsList").then(({ data }) => {
        this.list = data; // 获取返回的数据
      });
    },
    delGoods(id) {
      // 正常的方式
      if (confirm("确定要删除该商品吗")) {
        // axios.delete("/goodsList/" + id); // 正常应该调用删除接口
        this.list = this.list.filter((item) => item.id !== id); // 测试开发
        // this.getGoodsList();
      }
    },
    enterInput(event, row) {
      if (!event.target.value) {
        alert("输入内容不能为空");
        return;
      }
      row.tags.push(event.target.value);
      event.target.value = ""; // 清空原来的值
    },
  },
};
</script>

<style>
</style>

4.路由的概念

路由就是一种映射关系

接口和地址的映射- 后端路由

前端路由

地址- 组件的映射关系

SPA-单页应用-多个组件

地址对应组件- 1个地址对应一个组件

  • 任何项目都需要路由- 都需要多个业务的支撑

    5. 为什么要学路由和路由的分类

    前端路由 分为两类 #/ 和不带#

的特点 hash值- #后面的值的变化不会引起页面的刷新

可以监测到hash值的变化

也有不带# 属于history模式

好处-地址简洁-比较符合编程习惯

history正常情况下,地址变化会因为页面刷新,但是后端可以处理-监听到页面请求,不响应。

  • hash模式
  • history模式

    项目中需要场景切换

6. 使用VueRouter导入路由的使用

  1. 手动创建

    安装vue-router

$ npm i vue-router@3.5.3

如果不知道版本,就去npmjs官网,查询包名-查出有多少个版本

  • 新建单独的文件夹 router/index.js ```javascript import Vue from ‘vue’ import VueRouter from ‘vue-router’

Vue.use(VueRouter) // use会调用传入对象的install方法,并且会传入vue

export default new VueRouter({ routes: [{ path: ‘/login’, component: login }, { path: ‘/home’, component: home },{ path: ‘/article’, component: article }]

})

> main.js挂载路由

```javascript
import router from  '@/router'

new Vue({
  router
})

路由要展示-还需要容器
App.vue

<router-view></router-view>
// 路由切换的组件就会在这里展示
  1. 自动选择

    创建项目的时候,选择多选模式-选上Router

7.声明式导航

  • router-link可以提供声明式导航
    <router-link to="/login">登录</router-link>
    
    to-属性表示要跳的地址

    激活样式,可以针对激活样式做一些显示的效果

  1. vue-router提供了一个全局组件 router-link
  2. router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
  3. router-link提供了声明式导航高亮的功能(自带类名)