创建分析my-tabs

my-tabs是一个通用轮子,具备哪些能力
数据tab 不能写死,样式不能固定
1.可以在父组件中定制样式、

  1. // 配置对象
  2. config: {
  3. type: Object,
  4. //default如果是复杂数据类型,那么需要指定value为一个函数,通过返回值的形式指定默认值
  5. default: () => {
  6. return {};
  7. }

2.可以在父组件中指定数据

  1. // 父组件传入的 tabs 数据
  2. tabData: {
  3. type: Array,
  4. //默认空数组
  5. default: () => []
  6. },

3.可以在父组件中选中项

  1. // 默认激活项
  2. defaultIndex: {
  3. type: Number,
  4. default: 0
  5. },

封装网络请求

调用服务端接口获得数据

微信开发者工具
wx.request

uni-app

创建utils 封装请求对象 request.js

  1. const BASE_URL = 'https://api.imooc-blog.lgdsunday.club/api';
  2. function request({ url, data, method }) {
  3. return new Promise((resolve, reject) => {
  4. //uni.request 发起网络请求
  5. uni.request({
  6. url: BASE_URL + url,
  7. data,
  8. method,
  9. success: ({ data }) => {
  10. if (data.success) {
  11. //请求成功
  12. resolve(data);
  13. } else {
  14. //请求失败
  15. //给用户一个提示
  16. uni.showToast({
  17. title: data.message,
  18. icon: 'none',
  19. mask: true,
  20. duration: 3000
  21. });
  22. reject(data.message);
  23. }
  24. },
  25. fail: (error) => {
  26. reject(error);
  27. },
  28. complete: () => {
  29. // 关闭加载
  30. uni.hideLoading();
  31. }
  32. });
  33. });
  34. }
  35. export default request;

新建/api/hot.js

将放置所有和热搜相关的接口
如果不指定methods ,默认get请求
不指定data,默认不传递任何参数

  1. import request from '../utils/request';
  2. export function getHotTabs() {
  3. return request({
  4. url: '/hot/tabs'
  5. });
  6. }

在hot.vue里面使用hot.js

使用hot.js里面暴露出来的方法

  1. import { getHotTabs, getHotListFromTabType } from 'api/hot';

在methods里面定于获取数据的方法

  1. methods: {
  2. //获取热搜文章类型
  3. async loadHotTabs(){
  4. const res = await getHotTabs()
  5. console.log(res)
  6. }
  7. }

created时候调用

  1. //created: 组件实现配置完成,但DOM未渲染,进行网络请求,配置响应式数据
  2. created(){
  3. this.loadHotTabs()
  4. }

基本的数据展示

image.png

在hot.vue里面声明数据

tab数据源

  1. data() {
  2. return {
  3. // tabs 数据源
  4. tabData: [],
  5. }

把服务端数据赋值给tabData

  1. async loadHotTabs() {
  2. // uniapp 支持 async await
  3. const { data: res } = await loadHotTabs();
  4. this.tabData = res.list;

这样打印出的数据就没有多余的data节点
image.png

在my-tabs里面进行数据接收

image.png

在hot.vue渲染tabs组件

/uniapp中进行父向子传递 ,遵循vue v-bind 指令 >
:tabData=”tabData” === bind:tabData =”{{ tabData}}”

  1. <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"

  1. <!-- 处理my-tabs的html结构 -->
  2. <view class="tab-container">
  3. <view class="tab-box">
  4. <scroll-view
  5. id="_scroll"
  6. scroll-x
  7. class="scroll-view"
  8. scroll-with-animation //对应动画
  9. :scroll-left="scrollLeft"
  10. >
  11. <view class="scroll-content">
  12. <view class="tab-item-box">
  13. <block v-for="(item, index) in tabList" :key="index">

tabs样式

  1. .tab-container {
  2. font-size: $uni-font-size-base;
  3. height: 45px;
  4. line-height: 45px;
  5. background-color: $uni-bg-color;
  6. .tab-box {
  7. width: 100%;
  8. height: 45px;
  9. display: flex;
  10. position: relative;
  11. .scroll-view {
  12. white-space: nowrap;
  13. width: 100%;
  14. height: 100%;
  15. box-sizing: border-box;
  16. .scroll-content {
  17. width: 100%;
  18. height: 100%;
  19. position: relative;
  20. .tab-item-box {
  21. height: 100%;
  22. .tab-item {
  23. height: 100%;
  24. display: inline-block;
  25. text-align: center;
  26. padding: 0 15px;
  27. position: relative;
  28. text-align: center;
  29. color: $uni-text-color;
  30. &-active {
  31. color: $uni-text-color-hot;
  32. }
  33. }
  34. }
  35. .underLine {
  36. height: 2px;
  37. width: 25px;
  38. background-color: #f01414;
  39. border-radius: 3px;
  40. transition: 0.5s;
  41. position: absolute;
  42. bottom: 0;
  43. }
  44. }
  45. }
  46. }

设置激活项

当我们defaultIndex 等于当前index 添加class tab-item-active

  1. <block v-for="(item, index) in tabList" :key="index">
  2. <view
  3. class="tab-item"
  4. :id="'_tab_' + index"
  5. :class="{ 'tab-item-active': activeIndex === index }"
  1. &-active {
  2. color: $uni-text-color-hot;
  3. }

image.png
会遇到其他问题,父组件传递的数据,不应该在子组件进行修改

在data里面声明激活项activeIndex 代替defaultIndex
通过watch监听器,监听defaultIndex的变化,相当于小程序里的observes

通过handler,让activeIndex跟随defaultIndex发生变化

  1. data: () => {
  2. return { //
  3. //当前激活项的 index
  4. activeIndex: -1,
  5. watch: {
  6. //监听激活项目的变化
  7. defaultIndex:{
  8. //当defaultIndex发生变化时,回调的方法
  9. handler(val){
  10. this.activeIndex = val;
  11. },
  12. //该回调将会在侦听开始之后被立即调用
  13. immediatetrue
  14. }
  15. }

添加点击事件,
@click 等于 bind:tap

@click="tabClick(index)"

  1. methods:{
  2. tabClick(index) {
  3. this.activeIndex = index;
  4. // 发送通知,通知表示激活项发生变化了
  5. this.$emit('tabClick', index);
  6. },

总结

tab激活功能
后台传递一个0
image.png
my-tabs接收到了一个defaultIndex之后,通过activeIndex修改激活项

实现滑块

滑块和tab平级
横向移动
绑定一个transform:'translateX()'

  1. <view
  2. class="underLine"
  3. :style="{
  4. transform: 'translateX(' + slider.left + 'px)',
  5. width: defaultConfig.underLineWidth + 'px',
  6. height: defaultConfig.underLineHeight + 'px',
  7. backgroundColor: defaultConfig.underLineColor
  8. }"
  9. />

在data之中声明滑块的对象

  1. data(){
  2. return {
  3. slider: {
  4. // 距离左侧的距离
  5. left: 0
  6. },
  7. }
  8. }
  1. .underLine {
  2. height: 2px;
  3. width: 25px;
  4. background-color: #f01414;
  5. border-radius: 3px;
  6. transition: 0.5s;
  7. position: absolute;
  8. bottom: 0;
  9. }

实现滑块的滚动

想要实现滑块的滚动
1,确定滚动的时机 ==>点击tab
2.计算滑块滚动的距离

1.计算滑块的滚动位置,根据当前的activeIndex
当前点击项item的位置,在点击项item的宽度,计算出当前item滚动的宽度
需要拿到tabItem的宽度,tabItem的left,s’lider的width
left = tabItem.left + (tabItem.width -slider.width)/2

  1. methods:{
  2. tabToIndex() {
  3. if (this.tabList.length === 0) return;
  4. // 获取当前的 activeIndex
  5. const activeIndex = this.activeIndex;
  6. // 配置 滚动条 的数据
  7. this.slider = {
  8. // TODO:left 如何定义呢?
  9. // 1. 维护一个单独的数据对象 `tabList`
  10. // 2. 在 `tabList` 的 `item` 中为一个 `_slider` 属性
  11. // 3. 该属性保存了 【当前 `item` 下 的滑块位置】
  12. // 3.1. 计算公式:`滑块左侧位置 = item.left + (item.width - slider.width) / 2`
  13. left: this.tabList[activeIndex]._slider.left
  14. }

定义默认配置,

  1. data(){
  2. return {
  3. defaultConfig: {
  4. // 默认的字体颜色
  5. textColor: '#333333',
  6. // 高亮字体颜色
  7. activeTextColor: '#f94d2a',
  8. // 下划线宽度 px
  9. underLineWidth: 24,
  10. // 下划线高度 px
  11. underLineHeight: 2,
  12. // 下划线颜色
  13. underLineColor: '#f94d2a'
  14. }
  15. }
  16. }
  17. <view
  18. class="underLine"
  19. :style="{
  20. transform: 'translateX(' + slider.left + 'px)',
  21. width: defaultConfig.underLineWidth + 'px',
  22. height: defaultConfig.underLineHeight + 'px',
  23. backgroundColor: defaultConfig.underLineColor
  24. }

拿到tabitem的宽,要定义新的数据项tablist

image.png

tablist如何定值
在watch里面监听tabData变化,
维护tablist的每一个item,时机为DOM渲染完成之后
this.$nextTick() 兼容有问题
setTimeout

  1. watch: {
  2. // 侦听数据的变化
  3. tabData: {
  4. handler(val) {
  5. this.tabList = val;
  6. //tablist数据和tabData同步
  7. setTimeout(() => {
  8. //计算宽度
  9. this.updateTabWidth();
  10. }, 0);
  11. },
  12. // 该回调将会在侦听开始之后被立即调用
  13. immediate: true
  14. },
  1. methods: {
  2. /**
  3. * 更新 tab item 的宽度
  4. */
  5. updateTabWidth() {
  6. // tabItem的宽度,tabItem的left ,为tabList中的每一个item维护一个slide
  7. let data = this.tabList;
  8. if (data.length == 0) return false;
  9. //uniapp中拿到渲染后的dom
  10. //获取dom的固定写法
  11. 创建去selectorQuery的实例,从当前的vue组件里里面in(this)
  12. const query = uni.createSelectorQuery().in(this);
  13. // 循环数据源
  14. data.forEach((item, index) => {
  15. // 获取 dom 的固定写法
  16. query
  17. //传入内容,在view里面加上:id="'_tab_'+ index"
  18. .select('#_tab_' + index)
  19. .boundingClientRect((res) => {
  20. // 为数据对象中每一个 item 都维护一个 _slider(滑动条) 对象
  21. item._slider = {
  22. // 当前的 tab 距离左侧的距离
  23. //left = tabItem.left + (tabItem.width -slider.width)/2
  24. left: res.left + (res.width - this.defaultConfig.underLineWidth) / 2
  25. };
  26. // 运算完成之后,执行一次 【滑块】位置运算
  27. if (data.length - 1 === index) {
  28. this.tabToIndex();
  29. }
  30. })
  31. .exec();

image.png

dom固定写法,res就是拿到的dom

image.pngimage.png

计算tabtoindex
left: this.tabList[activeIndex]._slider.left

在用户点击时候调用tabToIndex()
image.png

scrollView的点击位移

当选中项发生变化时,希望scrollView进行对应的位移
提供了scroll-left属性

  1. <scroll-view
  2. :scroll-left="scrollLeft"
  3. >

在data里scrollView的横向滚动条位置
scrollLeft: 0

  1. // 为 scrollView 设置滚动位置
  2. this.scrollLeft = this.activeIndex * this.defaultConfig.underLineWidth;

增加可配置项

  1. defaultConfig: {
  2. // 默认的字体颜色
  3. textColor: '#333333',
  4. // 高亮字体颜色
  5. activeTextColor: '#f94d2a',
  6. // 下划线宽度 px
  7. underLineWidth: 24,
  8. // 下划线高度 px
  9. underLineHeight: 2,
  10. // 下划线颜色
  11. underLineColor: '#f94d2a'
  12. }

绑定行内样式

  1. :style="{
  2. color:
  3. activeIndex === index ? defaultConfig.activeTextColor : defaultConfig.textColor
  4. }"

props传过来的config和defaultconfig没发生关联

watch去监听config的变化,用传过来的config覆盖defaultconfig里的相关属性,得到最新

  1. // 监听 config
  2. config: {
  3. handler(val) {
  4. this.defaultConfig = { ...this.defaultConfig, ...val };
  5. },
  6. // 该回调将会在侦听开始之后被立即调用
  7. immediate: true
  8. }