You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nuxt.js 2.x动态适配多扇区的甜甜圈组件实现咨询

Nuxt.js 2.x动态适配多扇区的甜甜圈组件实现咨询

嗨,我来帮你搞定这个动态甜甜圈的需求!咱们得把原来固定3扇区的逻辑改成支持3-10个动态大扇区,每个大扇区仍保留4个子扇区,同时还得维持原来的间隙效果。一步步来改造:

1. 调整组件Props,新增动态扇区配置

首先给组件添加一个控制大扇区数量的prop,同时计算总子扇区数:

<script>
import { msToLapTime } from '@/utils'

export default {
  props: {
    title: {
      type: String,
      default: 'Lap',
    },
    value: {
      type: Number,
      default: 0,
    },
    currentSector: {
      type: Number,
      default: 0,
    },
    sectors: {
      type: Object,
      default: () => {},
    },
    // 新增:动态大扇区数量(限制3-10)
    sectorCount: {
      type: Number,
      default: 3,
      validator: val => val >=3 && val <=10
    },
  },
  computed: {
    speedValue() {
      return this.value > 0
        ? msToLapTime(this.value, this.$dateFns).slice(0, 7)
        : '00:00.0'
    },
    // 计算总子扇区数:大扇区数 * 4
    totalSegments() {
      return this.sectorCount * 4
    }
  },
  // ... 其他代码
}
</script>

2. 修改模板,动态渲染子扇区

把原来固定的count替换成计算属性totalSegments,同时给每个子扇区绑定动态样式:

<template>
<div class="lap is-flex is-flex-direction-column">
  <div class="live-label is-flex is-justify-content-center" v-text="title" />
  <div class="lap-content">
    <div class="circle">
      <ul class="circle__wrapper">
        <li 
          v-for="index in totalSegments" 
          :key="index" 
          class="circle__section"
          :style="getSegmentStyle(index)"
        />
      </ul>
      <div class="circle__center is-flex is-align-items-center is-justify-content-center">
        <div class="live-label--large" v-text="speedValue" />
      </div>
    </div>
  </div>
</div>
</template>

3. 新增动态样式计算方法,替代固定Sass Mixin

原来的Sass mixin是编译时固定12份,现在咱们用JS计算每个子扇区的旋转、倾斜角度,实现动态布局,同时保留间隙:

<script>
// ... 其他代码
export default {
  // ... props、computed
  methods: {
    // 计算每个子扇区的动态样式
    getSegmentStyle(index) {
      const total = this.totalSegments
      // 单个子扇区的角度:360 / 总份数
      const segmentAngle = 360 / total
      // 旋转角度:(索引-1)*单个扇区角度
      const rotateAngle = (index - 1) * segmentAngle
      // 倾斜角度:和原来mixin逻辑一致,保持扇形形状
      const skewAngle = -360 / (total / 2)
      // 间隙调整:数值越小间隙越窄,可根据需求修改
      return {
        transform: `rotate(${rotateAngle}deg) skewY(${skewAngle}deg) translate(3px, -3px)`
      }
    },
    // ... 其他方法
  }
}
</script>

同时修改Sass样式,去掉固定的circle-fraction mixin调用,调整基础样式:

$grid-size: #{'(100vh - 185px) / 8'}

@mixin grid-elem-size($property, $size){
  #{$property}: calc(#{$grid-size} * #{$size})!important;
}

.circle {
  position: relative;
  &__wrapper {
    border-radius: 50%;
    position: relative;
    padding: 0;
    margin: 1em auto;
    list-style: none;
    overflow: hidden;
    &::before {
      content: '';
      position: absolute;
      z-index: 2;
      width: calc(100% + 2px);
      height: calc(100% + 2px);
      top: -1px;
      left: -1px;
      border-radius: 50%;
      mask-image: radial-gradient(farthest-side, transparent calc(100% - 2px), #fff 100%);
      transform: rotate(1deg);
    }
    @include grid-elem-size('height', 2.5);
    @include grid-elem-size('width', 2.5);
  }
  &__section {
    position: absolute;
    top: 0;
    right: 0;
    width: 50%;
    height: 50%;
    background-color: #656565;
    transform-origin: 0% 100%;
    // 去掉原来固定的mixin调用,改用动态样式绑定
    &.filled {
      background-color: #d0cfcf;
      animation-name: color;
      animation-duration: 2s;
      animation-iteration-count: infinite;
    }
  }
  &__center {
    position: absolute;
    top: calc(50% - #{$grid-size} * 1.1);
    left: calc(50% - #{$grid-size} * 1.1);
    border-radius: 50%;
    background-color: var(--background-color);
    @include grid-elem-size('height', 2.2);
    @include grid-elem-size('width', 2.2);
    &::before {
      content: '';
      position: absolute;
      width: 100%;
      height: 100%;
      border-radius: 50%;
      mask-image: radial-gradient(farthest-side, transparent calc(100% - 1px), #fff 100%);
      transform: rotate(1.5deg);
    }
  }
}

// 保留原来的闪烁动画
@keyframes color {
  0% { background-color: #d0cfcf; }
  50% { background-color: #e8e8e8; }
  100% { background-color: #d0cfcf; }
}

4. 重写setSegmentColour方法,适配动态扇区

原来的固定分界(4、8)要改成根据大扇区数动态计算每个大扇区对应的子扇区范围:

<script>
export default {
  // ... 其他代码
  methods: {
    // ... getSegmentStyle方法
    setSegmentColour() {
      const sectionArr = document.querySelectorAll('.circle__section')
      const segmentsPerSector = 4 // 每个大扇区固定4个子扇区
      
      // 先重置所有子扇区的样式
      sectionArr.forEach(el => {
        el.className = 'circle__section'
      })

      // 给已完成的大扇区设置对应颜色,当前扇区设置filled
      for (let sector = 1; sector <= this.currentSector; sector++) {
        const startIndex = (sector - 1) * segmentsPerSector
        const endIndex = sector * segmentsPerSector
        
        for (let i = startIndex; i < endIndex; i++) {
          if (sector < this.currentSector) {
            // 已完成的扇区,取对应颜色
            const colorClass = this.sectors[`s${sector}Colour`]
            if (colorClass) {
              sectionArr[i].classList.add(colorClass)
            }
          } else {
            // 当前扇区,设置filled样式
            sectionArr[i].classList.add('filled')
          }
        }
      }
    },
  },
  watch: {
    currentSector() {
      this.setSegmentColour()
    },
    sectorCount() {
      // 扇区数变化时,DOM更新后重新渲染样式
      this.$nextTick(() => {
        this.setSegmentColour()
      })
    }
  },
  mounted() {
    this.setSegmentColour()
  },
}
</script>

额外提示

  • 间隙大小可以通过getSegmentStyle里的translate(3px, -3px)调整,数值越小间隙越窄
  • 如果需要更灵活的样式控制,可以把间隙大小做成prop传入
  • 测试时可以给sectorCount传不同的值(比如5、7),验证甜甜圈的布局是否正确

备注:内容来源于stack exchange,提问作者Nina Herasymova

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.21 10:23:04