创建分析my-tabs
my-tabs是一个通用轮子,具备哪些能力
数据tab 不能写死,样式不能固定
1.可以在父组件中定制样式、
// 配置对象
config: {
type: Object,
//default如果是复杂数据类型,那么需要指定value为一个函数,通过返回值的形式指定默认值
default: () => {
return {};
}
2.可以在父组件中指定数据
// 父组件传入的 tabs 数据
tabData: {
type: Array,
//默认空数组
default: () => []
},
3.可以在父组件中选中项
// 默认激活项
defaultIndex: {
type: Number,
default: 0
},
封装网络请求
调用服务端接口获得数据
微信开发者工具
wx.request
uni-app
创建utils 封装请求对象 request.js
const BASE_URL = 'https://api.imooc-blog.lgdsunday.club/api';
function request({ url, data, method }) {
return new Promise((resolve, reject) => {
//uni.request 发起网络请求
uni.request({
url: BASE_URL + url,
data,
method,
success: ({ data }) => {
if (data.success) {
//请求成功
resolve(data);
} else {
//请求失败
//给用户一个提示
uni.showToast({
title: data.message,
icon: 'none',
mask: true,
duration: 3000
});
reject(data.message);
}
},
fail: (error) => {
reject(error);
},
complete: () => {
// 关闭加载
uni.hideLoading();
}
});
});
}
export default request;
新建/api/hot.js
将放置所有和热搜相关的接口
如果不指定methods ,默认get请求
不指定data,默认不传递任何参数
import request from '../utils/request';
export function getHotTabs() {
return request({
url: '/hot/tabs'
});
}
在hot.vue里面使用hot.js
使用hot.js里面暴露出来的方法
import { getHotTabs, getHotListFromTabType } from 'api/hot';
在methods里面定于获取数据的方法
methods: {
//获取热搜文章类型
async loadHotTabs(){
const res = await getHotTabs()
console.log(res)
}
}
created时候调用
//created: 组件实现配置完成,但DOM未渲染,进行网络请求,配置响应式数据
created(){
this.loadHotTabs()
}
基本的数据展示
在hot.vue里面声明数据
tab数据源
data() {
return {
// tabs 数据源
tabData: [],
}
把服务端数据赋值给tabData
async loadHotTabs() {
// uniapp 支持 async await
const { data: res } = await loadHotTabs();
this.tabData = res.list;
这样打印出的数据就没有多余的data节点
在my-tabs里面进行数据接收
在hot.vue渲染tabs组件
/uniapp中进行父向子传递 ,遵循vue v-bind 指令 >
:tabData=”tabData” === bind:tabData =”{{ tabData}}”
<my-tabs tabData ="tabData"></my-tabs>
处理my-tabs的html结构
tabs需要横向滚动,使用scroll-view scroll-x
小程序里的for循环写法
wx:for ="{{arr}}" default 变量名 item, 下标 index
uniapp写法
v-for="(item,index) in arr" :key="index"
<!-- 处理my-tabs的html结构 -->
<view class="tab-container">
<view class="tab-box">
<scroll-view
id="_scroll"
scroll-x
class="scroll-view"
scroll-with-animation //对应动画
:scroll-left="scrollLeft"
>
<view class="scroll-content">
<view class="tab-item-box">
<block v-for="(item, index) in tabList" :key="index">
tabs样式
.tab-container {
font-size: $uni-font-size-base;
height: 45px;
line-height: 45px;
background-color: $uni-bg-color;
.tab-box {
width: 100%;
height: 45px;
display: flex;
position: relative;
.scroll-view {
white-space: nowrap;
width: 100%;
height: 100%;
box-sizing: border-box;
.scroll-content {
width: 100%;
height: 100%;
position: relative;
.tab-item-box {
height: 100%;
.tab-item {
height: 100%;
display: inline-block;
text-align: center;
padding: 0 15px;
position: relative;
text-align: center;
color: $uni-text-color;
&-active {
color: $uni-text-color-hot;
}
}
}
.underLine {
height: 2px;
width: 25px;
background-color: #f01414;
border-radius: 3px;
transition: 0.5s;
position: absolute;
bottom: 0;
}
}
}
}
设置激活项
当我们defaultIndex 等于当前index 添加class tab-item-active
<block v-for="(item, index) in tabList" :key="index">
<view
class="tab-item"
:id="'_tab_' + index"
:class="{ 'tab-item-active': activeIndex === index }"
&-active {
color: $uni-text-color-hot;
}
会遇到其他问题,父组件传递的数据,不应该在子组件进行修改
在data里面声明激活项activeIndex 代替defaultIndex
通过watch监听器,监听defaultIndex的变化,相当于小程序里的observes
通过handler,让activeIndex跟随defaultIndex发生变化
data: () => {
return { //
//当前激活项的 index
activeIndex: -1,
watch: {
//监听激活项目的变化
defaultIndex:{
//当defaultIndex发生变化时,回调的方法
handler(val){
this.activeIndex = val;
},
//该回调将会在侦听开始之后被立即调用
immediate:true
}
}
添加点击事件,
@click 等于 bind:tap
@click="tabClick(index)"
methods:{
tabClick(index) {
this.activeIndex = index;
// 发送通知,通知表示激活项发生变化了
this.$emit('tabClick', index);
},
总结
tab激活功能
后台传递一个0
my-tabs接收到了一个defaultIndex之后,通过activeIndex修改激活项
实现滑块
滑块和tab平级
横向移动
绑定一个transform:'translateX()'
<view
class="underLine"
:style="{
transform: 'translateX(' + slider.left + 'px)',
width: defaultConfig.underLineWidth + 'px',
height: defaultConfig.underLineHeight + 'px',
backgroundColor: defaultConfig.underLineColor
}"
/>
在data之中声明滑块的对象
data(){
return {
slider: {
// 距离左侧的距离
left: 0
},
}
}
.underLine {
height: 2px;
width: 25px;
background-color: #f01414;
border-radius: 3px;
transition: 0.5s;
position: absolute;
bottom: 0;
}
实现滑块的滚动
想要实现滑块的滚动
1,确定滚动的时机 ==>点击tab
2.计算滑块滚动的距离
1.计算滑块的滚动位置,根据当前的activeIndex
当前点击项item的位置,在点击项item的宽度,计算出当前item滚动的宽度
需要拿到tabItem的宽度,tabItem的left,s’lider的widthleft = tabItem.left + (tabItem.width -slider.width)/2
methods:{
tabToIndex() {
if (this.tabList.length === 0) return;
// 获取当前的 activeIndex
const activeIndex = this.activeIndex;
// 配置 滚动条 的数据
this.slider = {
// TODO:left 如何定义呢?
// 1. 维护一个单独的数据对象 `tabList`
// 2. 在 `tabList` 的 `item` 中为一个 `_slider` 属性
// 3. 该属性保存了 【当前 `item` 下 的滑块位置】
// 3.1. 计算公式:`滑块左侧位置 = item.left + (item.width - slider.width) / 2`
left: this.tabList[activeIndex]._slider.left
}
定义默认配置,
data(){
return {
defaultConfig: {
// 默认的字体颜色
textColor: '#333333',
// 高亮字体颜色
activeTextColor: '#f94d2a',
// 下划线宽度 px
underLineWidth: 24,
// 下划线高度 px
underLineHeight: 2,
// 下划线颜色
underLineColor: '#f94d2a'
}
}
}
<view
class="underLine"
:style="{
transform: 'translateX(' + slider.left + 'px)',
width: defaultConfig.underLineWidth + 'px',
height: defaultConfig.underLineHeight + 'px',
backgroundColor: defaultConfig.underLineColor
}
拿到tabitem的宽,要定义新的数据项tablist
tablist如何定值
在watch里面监听tabData变化,
维护tablist的每一个item,时机为DOM渲染完成之后
this.$nextTick() 兼容有问题
setTimeout
watch: {
// 侦听数据的变化
tabData: {
handler(val) {
this.tabList = val;
//tablist数据和tabData同步
setTimeout(() => {
//计算宽度
this.updateTabWidth();
}, 0);
},
// 该回调将会在侦听开始之后被立即调用
immediate: true
},
methods: {
/**
* 更新 tab item 的宽度
*/
updateTabWidth() {
// tabItem的宽度,tabItem的left ,为tabList中的每一个item维护一个slide
let data = this.tabList;
if (data.length == 0) return false;
//uniapp中拿到渲染后的dom
//获取dom的固定写法
创建去selectorQuery的实例,从当前的vue组件里里面in(this)
const query = uni.createSelectorQuery().in(this);
// 循环数据源
data.forEach((item, index) => {
// 获取 dom 的固定写法
query
//传入内容,在view里面加上:id="'_tab_'+ index"
.select('#_tab_' + index)
.boundingClientRect((res) => {
// 为数据对象中每一个 item 都维护一个 _slider(滑动条) 对象
item._slider = {
// 当前的 tab 距离左侧的距离
//left = tabItem.left + (tabItem.width -slider.width)/2
left: res.left + (res.width - this.defaultConfig.underLineWidth) / 2
};
// 运算完成之后,执行一次 【滑块】位置运算
if (data.length - 1 === index) {
this.tabToIndex();
}
})
.exec();
dom固定写法,res就是拿到的dom
计算tabtoindexleft: this.tabList[activeIndex]._slider.left
在用户点击时候调用tabToIndex()
scrollView的点击位移
当选中项发生变化时,希望scrollView进行对应的位移
提供了scroll-left属性
<scroll-view
:scroll-left="scrollLeft"
>
在data里scrollView的横向滚动条位置
scrollLeft: 0
// 为 scrollView 设置滚动位置
this.scrollLeft = this.activeIndex * this.defaultConfig.underLineWidth;
增加可配置项
defaultConfig: {
// 默认的字体颜色
textColor: '#333333',
// 高亮字体颜色
activeTextColor: '#f94d2a',
// 下划线宽度 px
underLineWidth: 24,
// 下划线高度 px
underLineHeight: 2,
// 下划线颜色
underLineColor: '#f94d2a'
}
绑定行内样式
:style="{
color:
activeIndex === index ? defaultConfig.activeTextColor : defaultConfig.textColor
}"
props传过来的config和defaultconfig没发生关联
watch去监听config的变化,用传过来的config覆盖defaultconfig里的相关属性,得到最新
// 监听 config
config: {
handler(val) {
this.defaultConfig = { ...this.defaultConfig, ...val };
},
// 该回调将会在侦听开始之后被立即调用
immediate: true
}