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