对于src\pages\stats\index.vue和src\components\v-tabs\v-tabs.vue两个文件中代码的理解

src\pages\stats\index.vue

 <template>

首先,在大模块的类wrap中包含了view和其他引进组件的小模块,第一个是引入的v-tabs组件

<v-tabs v-model="activeTab" :scroll="false" :value=activeTab :tabs=tabs @change="changeTab"></v-tabs>
  1. v-model="activeTab"v-model是Vue.js中的一个特殊指令,用于在表单输入和应用状态之间创建双向数据绑定。在这里,它绑定了一个名为activeTab的数据属性到<v-tabs>组件,表示当前激活的选项卡索引或标识符。这意味着当用户切换选项卡时,activeTab的值会自动更新以反映当前选中的选项卡。

  2. :scroll="false": 这是一个prop(属性)绑定,使用:(或v-bind:的简写)来动态绑定值。这里,它告诉<v-tabs>组件不要使用滚动条来显示超出容器宽度的选项卡。"false"表示禁用滚动。

  3. :value=activeTab: 这里的:value绑定似乎是多余的,因为v-model已经提供了双向绑定的功能。在标准的Vue和大多数UI库中,<v-tabs>组件不会直接接受一个valueprop来设置当前活动选项卡的索引,因为v-model已经承担了这一职责。这可能是对组件用法的一个误解或误用。

  4. :tabs=tabs: 这是另一个prop绑定,它传递了一个名为tabs的数据属性到<v-tabs>组件。tabs属性通常是一个数组,包含了所有选项卡项的配置或数据。每个选项卡项可能包括标签文本、图标、是否禁用等信息。

  5. @change="changeTab": 这是一个事件监听器,它监听<v-tabs>组件的change事件。当选项卡切换时,change事件会被触发,并执行名为changeTab的方法。changeTab方法可以用来处理选项卡切换后的逻辑,比如加载与当前选项卡相关联的数据。

<swiper
      :touchable="true"
      :current="activeTab"
      class="stat-content"
	  easing-function="easeOutCubic"
      :style="{ height: '550px' }"
	  @change="changeTab2"
    >
  1. :touchable="true": 这是一个prop绑定,表示轮播图是否支持触摸滑动。: 前缀是Vue.js中的v-bind:指令的简写,用于动态绑定属性值。这里设置为true,意味着用户可以通过触摸屏幕来滑动轮播图。

  2. :current="activeTab": 这也是一个prop绑定,用于指定当前显示的轮播项索引或标识符。activeTab是一个在Vue组件的data函数中定义的数据属性,它的值会决定轮播图当前显示的内容。

  3. class="stat-content": 这里给<swiper>组件添加了一个CSS类stat-content,用于应用样式。这个类应该在组件的样式表或全局样式表中定义。

  4. easing-function="easeOutCubic": 这个属性看起来是自定义的,用于指定轮播图滑动时的缓动函数。easeOutCubic是一种缓动效果,表示滑动开始时速度较慢,然后逐渐加快,在结束时再次减慢。不过,请注意,不是所有的Vue UI库都直接支持这种属性的写法,它可能是特定库特有的。

  5. :style="{ height: '550px' }": 使用:style绑定来直接应用内联样式。这里将轮播图的高度设置为550px

  6. @change="changeTab2": 这是一个事件监听器,用于监听轮播图内容变化(或滑动)的事件。当事件发生时,会调用组件中的changeTab2方法。这个方法可以用来处理轮播图内容变化后的逻辑,比如更新某些数据或状态。

      <swiper-item class="swiper_item">
			<view class="charts-box">
				<statMonth></statMonth>
			</view>
      </swiper-item>
      <swiper-item class="swiper_item">
        <view class="charts-box">
			<statYear></statYear>
        </view>
      </swiper-item>
      <swiper-item class="swiper_item">
		  <view class="charts-box">
			<statRanking></statRanking>
		  </view>
      </swiper-item>

 这是三个类似的对于<swiper-item>的调用,只不过内部调用的组件不同。

<script>

首先是对组件引用的声明

	import demodata from '@/mockdata/demodata.json';
	import statMonth from "@/components/stat/stat-month";
	import statYear from "@/components/stat/stat-year";	
	import statRanking from "@/components/stat/stat-ranking";

对export default部分解读

export default {
		components: {
			statMonth,
			statYear,
			statRanking
		},		
		data() {
			return {
				activeTab: 0,
				tabs: ['月数据', '年数据', '排行榜']
			}
		},
		onShow() { },
		watch: {
			activeTab (newVal) {
			  // console.log('activeTab newVal:', newVal);
			  this.activeTab = newVal;
			}
		},
		methods: {
			changeTab(index) {
				// console.log('当前选中的项:' + index)
				this.activeTab = index;
			},	
			changeTab2(e) {
				//  // console.log(e.detail)
				if(e.detail.source === 'touch')
				    //this.changeTab(e.detail.current);
					this.activeTab = e.detail.current;
				// console.log('changeTab2:', this.activeTab)
			}			
		}
	}

组件定义

  • components: 定义了该组件内部使用的子组件,包括statMonthstatYearstatRanking。这些子组件需要在父组件或当前组件中单独定义或导入。

数据对象

  • data(): 返回一个对象,包含组件的响应式数据。这里定义了activeTab(用于控制活动标签页的索引)和tabs(标签页的标题数组)。

观察者

  • watch: 定义了一个观察者activeTab,用于响应activeTab数据的变化。然而,这里的实现是多余的,因为当activeTab被更新时(比如通过changeTabchangeTab2方法),它又会被自己更新的值再次触发,这可能导致无限循环或不必要的重复操作。通常,watch用于执行依赖于数据变化的副作用操作,而不是直接更新数据本身。

方法

  • changeTab(index): 接收一个索引值index,用于更新activeTab的值,从而改变活动标签页。
  • changeTab2(e): 类似于changeTab,但它从事件对象e中提取当前索引(e.detail.current),并检查事件来源是否为触摸(e.detail.source === 'touch')。如果是,则更新activeTab的值。这种方法可能用于处理来自特定UI组件(如可滑动标签页)的事件。

<style>

每个代码的<style>部分都是对样式的美化,这里不做过多介绍,详情可以自行去网页上搜素,都有详细解答,下面也是如此

<style>
	/* #ifndef APP-NVUE */
	page {
		display: flex;
		flex-direction: column;
		box-sizing: border-box;
		background-color: #fff;
		min-height: 100%;
		height: auto;
	}
	view {
		font-size: 14px;
		line-height: inherit;
	}
	.content {
	  display: flex;
	  flex-direction: column;
	  flex: 1;
	}

	.charts-box {
		margin: 20rpx;
		display: flex;
		align-items: center;
		padding-top: 12rpx;
		width: 95%;
		height: 360px;
	}	
	.example-body {
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		flex-direction: row;
		flex-wrap: wrap;
		justify-content: center;
		padding: 0;
		font-size: 14px;
		background-color: #ffffff;
	}
	/* #endif */
	
	.section-box{
		display: flex;
		flex-direction: row;
		align-items: center;
		padding: 20rpx;
	}
	.decoration{
		width: 4px;
		height: 12px;
		border-radius: 10px;
		background-color: #2979ff;
	}
	.section-text{
		color: #333;
		margin-left: 15rpx;
	}

	/* #ifdef APP-NVUE */
	.warp {
		background-color: #fff;
	}
	/* #endif */

	.example-body {
		flex-direction: column;
		padding: 15px;
		background-color: #ffffff;
	}

	.image {
		width: 50rpx;
		height: 50rpx;
	}
	
	.big-number{
		font-size: 50rpx;
		font-weight: 700;
		font-stretch: condensed;
		font-style:oblique;
	}
	
	.text {
		text-align: center;
		font-size: 26rpx;
		margin-top: 10rpx;
	}

	.example-body {
		/* #ifndef APP-NVUE */
		display: block;
		/* #endif */
	}

	.grid-item-box {
		flex: 1;
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		flex-direction: column;
		align-items: center;
		justify-content: center;
		padding: 15px 0;
	}

	.banner-image {
		width: 750rpx;
		height: 400rpx;
	}

	.swiper-box {
		height: 400rpx;
	}

	.search-icons {
		padding: 16rpx;
	}

	.search-container-bar {
		/* #ifndef APP-NVUE */
		display: flex;
		/* #endif */
		flex-direction: row;
		justify-content: center;
		align-items: center;
		position: fixed;
		left: 0;
		right: 0;
		z-index: 10;
		background-color: #fff;
	}

	/* #ifndef APP-NVUE || VUE3*/
	::v-deep
	/* #endif */
	.uni-searchbar__box {
		border-width: 0;
	}

	/* #ifndef APP-NVUE || VUE3 */
	::v-deep
	/* #endif */
	.uni-input-placeholder {
		font-size: 28rpx;
	}
</style>

src\components\v-tabs\v-tabs.vue

 <template>

<scroll-view
      id="scrollContainer"
      :scroll-x="scroll"
      :scroll-left="scroll ? scrollLeft : 0"
      :scroll-with-animation="scroll"
      :style="{ position: fixed ? 'fixed' : 'relative', zIndex: 1993 }"
    >
  1. :scroll-x="scroll":
    • 使用Vue的v-bind(简写为:)指令绑定scroll-x属性到组件的scroll数据属性上。scroll-x属性可能用于控制滚动视图是否允许横向滚动。如果scroll是一个布尔值,当它为true时,滚动视图允许横向滚动;当它为false时,则不允许。
  2. :scroll-left="scroll ? scrollLeft : 0":
    • 同样使用v-bind指令绑定scroll-left属性。这个属性的值取决于scroll的值。如果scrolltrue,则scroll-left的值为scrollLeft(可能是另一个数据属性,表示滚动的起始位置);如果scrollfalse,则scroll-left的值为0,即不滚动到任何位置。
  3. :scroll-with-animation="scroll":
    • 使用v-bind指令绑定scroll-with-animation属性到scroll数据属性。这个属性可能控制滚动时是否带有动画效果。当scrolltrue时,滚动将带有动画;为false时,滚动则无动画。
  4. :style="{ position: fixed ? 'fixed' : 'relative', zIndex: 1993 }":
    • 使用v-bind:style(简写为:style)指令动态绑定样式到scroll-view组件。这里根据fixed(可能是一个数据属性)的值来决定position属性的值。如果fixedtrue,则positionfixed,表示滚动视图的位置是固定的;如果fixedfalse,则positionrelative,表示滚动视图的位置是相对于其正常位置定位的。同时,无论fixed的值如何,zIndex都被设置为1993,这有助于控制滚动视图在层叠上下文中的堆叠顺序。
    <view
        class="v-tabs__container"
        :style="{
          display: scroll ? 'inline-flex' : 'flex',
          whiteSpace: scroll ? 'nowrap' : 'normal',
          background: bgColor,
          height,
          padding
        }"
      >
        <view
          :class="['v-tabs__container-item', { disabled: !!v.disabled }]"
          v-for="(v, i) in tabs"
          :key="i"
          :style="{
            color: current == i ? activeColor : color,
            fontSize: current == i ? fontSize : fontSize,
            fontWeight: bold && current == i ? 'bold' : '',
            justifyContent: !scroll ? 'center' : '',
            flex: scroll ? '' : 1,
            padding: paddingItem
          }"
          @click="change(i)"
        >
          {{ field ? v[field] : v }}
        </view>
        <view
          v-if="!pills"
          :class="['v-tabs__container-line', { animation: lineAnimation }]"
          :style="{
            background: lineColor,
            width: lineWidth + 'px',
            height: lineHeight,
            borderRadius: lineRadius,
            left: lineLeft + 'px',
            transform: `translateX(-${lineWidth / 2}px)`
          }"
        ></view>
        <view
          v-else
          :class="['v-tabs__container-pills', { animation: lineAnimation }]"
          :style="{
            background: pillsColor,
            borderRadius: pillsBorderRadius,
            left: pillsLeft + 'px',
            width: currentWidth + 'px',
            height
          }"
        ></view>
      </view>
外层容器 (<view class="v-tabs__container">)
  • 类名v-tabs__container,用于CSS样式和JavaScript操作中的引用。
  • 样式绑定:style 属性动态绑定了一个对象,这个对象根据 scroll 变量的值来决定显示方式(inline-flex 或 flex)、空白处理方式(nowrap 或 normal)、背景色(bgColor)、高度(height)、内边距(padding)。
标签项 (<view :class="..."> 循环部分)
  • 类名:每个标签项都绑定了 'v-tabs__container-item' 类,并根据 v.disabled 的值决定是否添加 'disabled' 类。
  • 循环:使用 v-for="(v, i) in tabs" 遍历 tabs 数组,为每个标签项生成一个视图。
  • 样式绑定:根据当前激活的标签(current == i)和其他条件(如 bold)动态设置颜色、字体大小、字体加粗、内容对齐方式、弹性布局(flex)和内边距。
  • 点击事件:点击标签项时会触发 change(i) 方法,通常用于更新 current 的值以表示当前激活的标签。
  • 内容显示:使用 {{ field ? v[field] : v }} 来决定显示标签项的内容。如果 field 存在,则显示 v[field] 的值;否则直接显示 v
标签线 (<view v-if="!pills">)
  • 条件渲染:仅当 pills 为 false 时渲染。
  • 类名:根据是否应用动画(lineAnimation)来添加 'animation' 类。
  • 样式绑定:定义了背景色、宽度、高度、边框半径、左偏移量和变换(用于居中标签线)。
另一标签 (<view v-else>)
  • 条件渲染:当 pills 为 true 时渲染。
  • 类名:与标签线类似,但使用的是 'v-tabs__container-pills' 类,并根据是否应用动画来添加 'animation' 类。
  • 样式绑定:定义了背景色、边框半径、左偏移量、宽度和高度。宽度 currentWidth 可能是根据当前激活标签项的宽度动态计算的。
    <view
      class="v-tabs__placeholder"
      :style="{
        height: fixed ? height : '0',
        padding
      }"
    ></view>
  • height: fixed ? height : '0': 这是一个条件表达式,它根据fixed这个数据属性的值来决定height属性的值。如果fixedtrue,则height属性的值将设置为组件的height数据属性的值(这里假设height是已经定义好的数据属性,其值可以是具体的像素值或百分比等)。如果fixedfalse,则height属性的值将被设置为字符串'0',即占位符的高度将为0,使得它在视觉上不可见或仅占据最小的空间。

<script>

   @property {Number} value 选中的下标
 * @property {Array} tabs tabs 列表
 * @property {String} bgColor = '#fff' 背景颜色
 * @property {String} color = '#333' 默认颜色
 * @property {String} activeColor = '#2979ff' 选中文字颜色
 * @property {String} fontSize = '28rpx' 默认文字大小
 * @property {String} activeFontSize = '28rpx' 选中文字大小
 * @property {Boolean} bold = [true | false] 选中文字是否加粗
 * @property {Boolean} scroll = [true | false] 是否滚动
 * @property {String} height = '60rpx' tab 的高度
 * @property {String} lineHeight = '10rpx' 下划线的高度
 * @property {String} lineColor = '#2979ff' 下划线的颜色
 * @property {Number} lineScale = 0.5 下划线的宽度缩放比例
 * @property {String} lineRadius = '10rpx' 下划线圆角
 * @property {Boolean} pills = [true | false] 是否胶囊样式
 * @property {String} pillsColor = '#2979ff' 胶囊背景色
 * @property {String} pillsBorderRadius = '10rpx' 胶囊圆角大小
 * @property {String} field 如果是对象,显示的键名
 * @property {Boolean} fixed = [true | false] 是否固定
 * @property {String} paddingItem = '0 22rpx' 选项的边距
 * @property {Boolean} lineAnimation = [true | false] 下划线是否有动画
 * @event {Function(current)} change 改变标签触发

 描述了一个标签页(Tabs)组件的属性和事件,该组件允许用户在不同的标签之间切换,并且提供了丰富的自定义选项来适应不同的设计需求。尤其注意:

  • change{Function(current)} - 改变标签触发的事件。当用户切换标签时,会触发这个事件,并传递当前激活的标签的索引(current)作为参数。这个事件允许开发者在标签切换时执行自定义的逻辑,如加载对应标签的内容。
export default {
  props: {
    value: {
      type: Number,
      default: 0
    },
    tabs: {
      type: Array,
      default () {
        return []
      }
    },
    bgColor: {
      type: String,
      default: '#fff'
    },
    padding: {
      type: String,
      default: '0'
    },
    color: {
      type: String,
      default: '#333'
    },
    activeColor: {
      type: String,
      default: '#7d787a'
    },
    fontSize: {
      type: String,
      default: '28rpx'
    },
    activeFontSize: {
      type: String,
      default: '32rpx'
    },
    bold: {
      type: Boolean,
      default: true
    },
    scroll: {
      type: Boolean,
      default: true
    },
    height: {
      type: String,
      default: '70rpx'
    },
    lineColor: {
      type: String,
      default: '#7d787a'
    },
    lineHeight: {
      type: String,
      default: '10rpx'
    },
    lineScale: {
      type: Number,
      default: 0.5
    },
    lineRadius: {
      type: String,
      default: '10rpx'
    },
    pills: {
      type: Boolean,
      default: false
    },
    pillsColor: {
      type: String,
      default: '#7d787a'
    },
    pillsBorderRadius: {
      type: String,
      default: '10rpx'
    },
    field: {
      type: String,
      default: ''
    },
    fixed: {
      type: Boolean,
      default: false
    },
    paddingItem: {
      type: String,
      default: '0 22rpx'
    },
    lineAnimation: {
      type: Boolean,
      default: true
    }
  },
  data () {
    return {
      elId: '',
      lineWidth: 30,
      currentWidth: 0, // 当前选项的宽度
      lineLeft: 0, // 滑块距离左侧的位置
      pillsLeft: 0, // 胶囊距离左侧的位置
      scrollLeft: 0, // 距离左边的位置
      containerWidth: 0, // 容器的宽度
      current: 0 // 当前选中项
    }
  },

 这部分代码是对props属性和data数据的一些初始化加定义,方便后续若对页面前端进行动态展示的时候,与用户端进行更好的交互,便于数据的处理。

watch: {
    value (newVal) {
      // console.log('newVal:', newVal);
	  this.current = newVal
      this.$nextTick(() => {
        this.getTabItemWidth()
      })
    },
    // current (newVal) {
    //   this.$emit('input', newVal)
    // },
	current (newVal) {
	    // console.log('current newVal:', newVal);
		this.$nextTick(() => {
			this.getTabItemWidth()
		})
		this.$emit('input', newVal) 
		// this.$emit('update:modelVal', newVal)
	},
    tabs (newVal) {
      this.$nextTick(() => {
        this.getTabItemWidth()
      })
    }
  },

value (newVal) { ... }

这个观察器用于观察value prop的变化。当父组件改变传递给这个子组件的value值时,这个观察器会被触发。

  • newVal参数表示value prop的新值。
  • 在观察器内部,首先将组件的current数据属性设置为新的value值,以保持内部状态与外部输入的同步。
  • 然后,使用this.$nextTick来确保DOM更新完成后再执行getTabItemWidth方法。这是因为getTabItemWidth方法可能需要基于更新后的DOM来计算某些尺寸,而Vue的DOM更新是异步的。

current (newVal) { ... }(第二个定义)

这个观察器也用于观察current数据属性的变化。但是,由于value观察器已经负责将value prop的值同步到current,这里通常不需要再单独观察current的变化(除非你有其他特定的逻辑需求)。

  • current变化时,同样使用this.$nextTick来确保DOM更新完成后再执行getTabItemWidth方法。
  • 使用this.$emit('input', newVal)来通知父组件current的值已经改变。这是Vue自定义事件的一个常见用法,用于实现父子组件之间的双向绑定(尽管在Vue 2中,对于.sync修饰符的prop,推荐使用update:propName事件名,但在Vue 3中,.sync修饰符的工作方式已经改变,且更推荐使用v-model的变体来实现类似的功能)

注意:如果你使用的是Vue 2,并且value prop是通过.sync修饰符传递给子组件的,那么你可能想要使用this.$emit('update:value', newVal)来代替this.$emit('input', newVal),以保持与.sync修饰符的约定一致。但是,在Vue 3中,.sync修饰符的行为已经改变,并且更推荐使用v-model的自定义版本来实现类似的双向绑定。

tabs (newVal) { ... }

这个观察器用于观察tabs prop的变化。当父组件改变传递给这个子组件的tabs数组时,这个观察器会被触发。

  • newVal参数表示tabs prop的新值。
  • 在观察器内部,使用this.$nextTick来确保DOM更新完成后再执行getTabItemWidth方法。这可能是因为tabs的变化会影响到DOM中标签项的数量或内容,从而需要重新计算它们的宽度。
methods: {
    // 产生随机字符串
    randomString (len) {
      len = len || 32
      let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
      let maxPos = $chars.length
      let pwd = ''
      for (let i = 0; i < len; i++) {
        pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
      }
      return pwd
    },
    // 切换事件
    change (index) {
      const isDisabled = !!this.tabs[index].disabled
      if (this.current !== index && !isDisabled) {
        this.current = index
        this.$emit('change', index)
      }
    },
    // 获取左移动位置
    getTabItemWidth () {
	  // console.log('123');
      let query = uni
        .createSelectorQuery()
        // #ifndef MP-ALIPAY
        .in(this)
      // #endif
      // 获取容器的宽度
      query
        .select(`#scrollContainer`)
        .boundingClientRect((data) => {
          if (!this.containerWidth && data) {
            this.containerWidth = data.width
          }
        })
        .exec()
      // 获取所有的 tab-item 的宽度
	  // uni.createSelectorQuery().in(this) .selectAll('.v-tabs__container-item')
      query.selectAll('.v-tabs__container-item')
        .boundingClientRect((data) => {
          if (!data) {
            return
          }
          let lineLeft = 0
          let currentWidth = 0
          if (data) {
			  // console.log(data);
            for (let i = 0; i < data.length; i++) {
              if (i < this.current) {
                lineLeft += data[i].width
              } else if (i == this.current) {
                currentWidth = data[i].width
              } else {
                break
              }
            }
          }
          // 当前滑块的宽度
          this.currentWidth = currentWidth
          // 缩放后的滑块宽度
          this.lineWidth = currentWidth * this.lineScale * 1
          // 滑块作移动的位置
          this.lineLeft = lineLeft + currentWidth / 2
          // 胶囊距离左侧的位置
          this.pillsLeft = lineLeft
          // 计算滚动的距离左侧的位置
          if (this.scroll) {
            this.scrollLeft = this.lineLeft - this.containerWidth / 2
          }
        })
        .exec()
    }
  }
randomString(len)
  • 目的:生成一个指定长度的随机字符串。
  • 参数len(可选),字符串的长度,默认为32。
  • 实现:通过定义一个包含可用字符的字符串$chars(去除了容易混淆的字符),然后在一个循环中随机选择这些字符来构建最终的字符串。
change(index)
  • 目的:切换当前激活的标签页。
  • 参数index,要切换到的标签页的索引。
  • 实现:首先检查目标标签页是否被禁用(disabled属性)。如果没有被禁用且当前索引不等于目标索引,则更新current为新的索引,并通过$emit触发一个change事件,将新的索引作为参数传递给父组件。
getTabItemWidth()
  • 目的:计算标签项(tab-item)的宽度,以及滑块(通常用于指示当前激活的标签页)的位置和宽度。
  • 实现
    • 使用uni.createSelectorQuery()来查询DOM元素。在条件编译中,.in(this)可能用于确保查询在正确的上下文中执行(尽管在非MP-ALIPAY环境下这个调用是可选的)。
    • 查询#scrollContainer元素的宽度,并将其存储在containerWidth中(如果尚未设置)。
    • 接着,查询所有.v-tabs__container-item元素的宽度。对于每个元素,如果其索引小于当前激活的标签页索引,则累加其宽度到lineLeft(滑块应该开始的位置)。当达到当前激活的标签页时,记录其宽度为currentWidth
    • 根据currentWidth和缩放比例lineScale计算缩放后的滑块宽度,并设置滑块的位置(lineLeft加上currentWidth的一半)。
    • 如果存在滚动(this.scroll为真),则计算滚动的距离(使滑块保持在容器中心)。
mounted () {
    this.elId = 'xfjpeter_' + this.randomString()
    this.current = this.value
    this.$nextTick(() => {
      this.getTabItemWidth()
    })
  }
}

生命周期钩子(mounted

  • mounted():当组件被挂载到DOM上后执行。
  • 实现
    • 生成一个唯一的elId,可能用于某些特定的DOM操作或样式绑定(尽管在这段代码中未直接使用)。
    • current初始化为value prop的值,确保组件初始化时显示正确的标签页。
    • 使用$nextTick确保DOM更新完成后执行getTabItemWidth(),以获取并设置标签项和滑块的相关尺寸和位置。

<style>

<style lang="scss" scoped>
.v-tabs {
  width: 100%;
  box-sizing: border-box;
  overflow: hidden;

  ::-webkit-scrollbar {
    display: none;
  }

  &__container {
    min-width: 100%;
    position: relative;
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
    overflow: hidden;

    &-item {
      display: flex;
      align-items: center;
      height: 100%;
      position: relative;
      z-index: 10;
      // padding: 0 11px;
      transition: all 0.3s;
      white-space: nowrap;
      &.disabled {
        opacity: 0.5;
        color: #999;
      }
    }

    &-line {
      position: absolute;
      bottom: 0;
    }

    &-pills {
      position: absolute;
      z-index: 9;
    }
    &-line,
    &-pills {
      &.animation {
        transition: all 0.3s linear;
      }
    }
  }
}
</style>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值