vue——tabbar案例

1.项目初始化

组件拆分:

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

①: vue create tabbar-demo

②: yarn add less less-loader@5.0.0 -D

③: yarn add bootstrap axios 并在main.js 引入和全局属性
import Vue from 'vue'
import App from './App.vue'
import "./assets/fonts/iconfont.css" // 引入字体图标css文件
import "bootstrap/dist/css/bootstrap.css"
④: 根据需求-创建需要的页面组件
⑤: 把昨天购物车案例-封装的MyHeader.vue文件复制过来复用
⑥: 从App.vue – 引入组织相关标签
<MyHeader
      :background="'blue'"
      :fontColor="'white'"
      title="TabBar案例"
    ></MyHeader>
import MyHeader from "./components/MyHeader";
export default {
  components: {
    MyGoodsList,
    MyGoodsSearch,
    MyUserInfo,
  },
}

1.底部封装 

①: 基本标签+样式(略)

②: 为tabbar组件指定数据源
③: 数据源最少2个, 最多5个(validator)
使用自定义校验规则!!

(放入props中)

// 自定义校验规则
      validator(value) {
        // value就是接到数组
        if (value.length >= 2 && value.length <= 5) {
          return true; // 符合条件就return true
        } else {
          console.error("数据源必须在2-5项");
          return false;
        }
      },
④: 从App.vue给MyTabBar.vue传入底部导航的数据 (父传子)
⑤: MyTabBar.vue中循环展示
App.vue(父):
    <MyTabBar :arr="tabList"></MyTabBar>
import MyTabBar from "./components/MyTabBar";
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,
  },
}

MyTabBar.vue(子):

<div
      class="tab-item"
      v-for="(obj, index) in arr"
      :key="index"
    >
      <!-- 图标 -->
      <span class="iconfont" :class="obj.iconText"></span>
      <!-- 文字 -->
      <span>{{ obj.text }}</span>
    </div>
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;
        }
      },
    },
  },

2. 底部高亮

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

①: 绑定点击事件, 获取点击的索引

<div
      class="tab-item"
      v-for="(obj, index) in arr"
      :key="index"
      @click="btn(index)"
    >
      <!-- 图标 -->
      <span class="iconfont" :class="obj.iconText"></span>
      <!-- 文字 -->
      <span>{{ obj.text }}</span>
    </div>
②: 循环的标签设置动态class, 遍历的索引, 和点击保存的索引比较, 相同则高亮
循环索引(index)与保存的索引(selindex)对比,发生点击事件时把索引值同步给selindex变量上,引发上面判断的更新
<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>
export default {
  data() {
    return {
      selIndex: 0, // 默认第一个高亮
    };
  },
  methods: {
    btn(index, theObj) {
      this.selIndex = index; // 点谁, 就把谁的索引值保存起来
    },
  },
};

 3.组件切换

需求:点击底部切换组件

①: 底部导航传出动态组件名字符串到App.vue

<div class="main">
      <component :is="comName"></component>
    </div>
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,
  },
}

②: 切换动态组件is属性的值为要显示的组件名(子传父)

App.vue(父):
    <MyTabBar :arr="tabList" @changeCom="changeComFn"></MyTabBar>
methods: {
    changeComFn(cName) {
      this.comName = cName; // MyTabBar里选出来的组件名赋予给is属性的comName
      // 导致组件的切换
    },
  },

MyTabbar.vue(子):

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

 4.商品列表-准备

需求:商品列表铺设页面

①: 封装MyTable.vue – 准备标签和样式

②: axios在MyGoodsList.vue请求数据回来
③: 请求地址: https://www.escook.cn/api/goods
④: 传入MyTable.vue中循环数据显示(父传子)

MyGoodsList.vue(父):

<MyTable :arr="list"></MyTable>
import axios from "axios";
axios.defaults.baseURL = "https://www.escook.cn";
export default {
  components: {
    MyTable,
  },
  data() {
    return {
      list: [] // 所有数据
    };
  },
  created() {
    axios({
      url: "/api/goods",
    }).then((res) => {
      console.log(res);
      this.list = res.data.data;
    });
  },
}

MyTable.vue(子):

 <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>      
</tr>
props: {
      arr: Array
  }

⑤: 给删除按钮添加bootstrap的样式: btn btn-danger btn-sm


5.商品列表-插槽使用 

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

①: 把MyTable.vue里准备slot

②: 使用MyTable组件时传入具体标签
<thead>
      <tr>
        <slot name="header"></slot>
      </tr>
    </thead>
    <!-- 表格主体区域 -->
    <tbody>
      <tr v-for="obj in arr"
      :key="obj.id"
      >
        <slot name="body" :row="obj"></slot>
      </tr>
    </tbody>

MyGoodsList.vue:

<MyTable :arr="list">
      <template #header>
        <th>#</th>
        <th>商品名称</th>
        <th>价格</th>
        <th>标签</th>
        <th>操作</th>
      </template>
      <template #body="scope">
        <td>{{ scope.row.id }}</td>
        <td>{{ scope.row.goods_name }}</td>
        <td>{{ scope.row.goods_price }}</td>
        <td>{{ scope.tags }}</td>
        <td>
          <button class="btn btn-danger btn-sm"
          >删除</button>
        </td>
      </template>
    </MyTable>

6.商品表格—tags铺设

需求:标签列自定义显示

①: 插槽里传入的td单元格

②: 自定义span标签的循环展示-给予样式
        <td>
          <span v-for="(str, ind) in scope.row.tags" :key="ind"
          class="badge badge-warning"
          >
            {{ str }}
          </span>
        </td>

7.商品表格-删除数据 

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

①: 删除按钮绑定点击事件

②: 作用域插槽绑定id值出来
③: 传给删除方法, 删除MyGoodsList.vue里数组里数据
<td>
          <button class="btn btn-danger btn-sm"
          @click="removeBtn(scope.row.id)"
          >删除</button>
        </td>
methods: {
    removeBtn(id){
      let index = this.list.findIndex(obj => obj.id === id)
      this.list.splice(index, 1)
    },
}

8. 商品表格-创建tags

需求1: 点击Tab, 按钮消失, 输入框出现

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

①: 准备静态Tab标签按钮 – 事件绑定

②: 点击出输入框, 按钮消失
此时v-if(v-else)不能绑定isshow一个变量,因为有多个点击按钮,需要绑定每一行的自带属性scope.row.inputVisible!!!
<td>
          <input
            class="tag-input form-control"
            style="width: 100px"
            type="text"
            v-if="scope.row.inputVisible"
          />
          <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>

③: 自定义指令, 让输入框自动聚焦

main.js:

// 全局指令
Vue.directive("focus", {
    inserted(el) {
        el.focus()
    }
})

mygoodslist.vue:

<input
            class="tag-input form-control"
            style="width: 100px"
            type="text"
            v-if="scope.row.inputVisible"
            v-focus
          />
④: 监测失去焦点事件 – 给关联的对象属性设置 – 影响标签出现/隐藏
<input
            class="tag-input form-control"
            style="width: 100px"
            type="text"
            v-if="scope.row.inputVisible"
            v-focus
            @blur="scope.row.inputVisible = false"
          />

⑤: 监测input的回车, 判断是否有值 – 给出提示 / 添加数据

<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"
          />
   enterFn(obj) {
      // 回车
      // console.log(obj.inputValue);
      if (obj.inputValue.trim().length === 0) {
        alert("请输入数据");
        return;
      }
      obj.tags.push(obj.inputValue); // 表单里的字符串状态tags数组
      obj.inputValue = "";
    },
  },

⑥: 监测input的取消, 清空数据

          <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 = ''"
          />

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值