Vue3利用ElementPlus制作数字递增效果

176 阅读1分钟

效果图如下

录制_2024_03_01_17_47_42_160.gif

父组件

<template>
  <div class="mainBox">
    <div v-for="(item, index) in titleList" :key="index" class="contentBox">
      <div class="title">{{ item }}</div>
      <div class="childTotalBox">
        <childData
          v-if="item === '项目人员情况' && personValue"
          :item="item"
          :contentList="contentList"
          @dataClick="dataClick"
          :personValue="personValue"
        />
        <childData
          v-else-if="item === '劳务人员情况' && personValue"
          :item="item"
          :contentList="contentListTwo"
          @dataClick="dataClick"
          :personValue="personValue"
        />
        <childData
          v-else
          :item="item"
          :contentList="contentListThree"
          @dataClick="dataClick"
          :personValue="personValue"
        />
      </div>
    </div>
  </div>
  <DXDialog
    :title="titles"
    :visible="visible"
    :showBoxShadow="false"
    :destroyOnClose="false"
    :maskClosed="false"
    @close="close"
    width="60vw"
    height="75vh"
    top="12vh"
  >
    <el-col h-68.8vh pos="relative" class="tableBox">
      <Table :columnData="columnData" :list="personList" :person="false" />
    </el-col>
  </DXDialog>
</template>
<script setup lang="ts">
  import { ref } from 'vue';
  import Table from '@daxiedongk/views/LodarManagement/compontents/Table.vue';
  import DXDialog from '@daxiedongk/components/DXDialog/DXDialog.vue';
  import childData from '@daxiedongk/views/LodarManagement/compontents/ProPerson/componemts/childData.vue';
  import {
    columnData,
    contentList,
    contentListThree,
    contentListTwo,
    titleList,
  } from '@daxiedongk/views/LodarManagement/hook/mockDataShowData.ts';
  import { getContractWorkerList } from '@daxiedongk/api/loaderManagement';

  const visible = ref(false);
  const titles = ref('');
  const personList = reactive({
    type: '',
    peopleDetailType: '',
  });
  const dataClick = (item, data) => {
    if (data.label === '进场登记') {
      titles.value = '登记项目人员数';
    } else {
      titles.value = `${data.label}人员数`;
    }
    if (item === '劳务人员情况') {
      personList.type = 'service';
    } else if (item === '管理人员情况') {
      personList.type = 'manage';
    } else {
      personList.type = 'project';
    }
    if (data.label === '进场登记') {
      personList.peopleDetailType = 'registeredNumber';
    } else if (data.label === '今日出勤') {
      personList.peopleDetailType = 'attendanceCounts';
    } else {
      personList.peopleDetailType = 'CurrentNumber';
    }
    visible.value = true;
  };
  const personValue = ref({
    projectRegisteredNumber: 0,
    projectAttendanceCounts: 0,
    projectCurrentNumber: 0,
    projectPercent: 0,
    lwRegisteredNumber: 0,
    lwAttendanceCounts: 0,
    lwCurrentNumber: 0,
    lwPercent: 0,
    glRegisteredNumber: 0,
    glAttendanceCounts: 0,
    glCurrentNumber: 0,
    glPercent: 0,
  });
  const getPersonList = () => {
    getContractWorkerList({}).then((res) => {
      personValue.value = res.data;
    });
  };
  getPersonList();
  const close = () => {
    visible.value = false;
  };
</script>
<style scoped lang="less">
  .mainBox {
    background: rgba(79, 190, 249, 0.1);
    clip-path: polygon(0 5%, 3% 0, 97% 0, 100% 4.8%, 100% 93%, 96% 98%, 4% 98%, 0% 93%);
    padding: 10% 4%;
    height: 100%;
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    .contentBox {
      width: 100%;
      margin-bottom: 1.8rem;
      padding-left: 2.5rem;
      .title {
        font-size: 1.2rem;
        font-weight: 600;
        margin-bottom: 1%;
        color: #ffffff;
      }
      .childTotalBox {
        height: 70%;
        width: 100%;
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
    }
  }
  .tableBox {
    :deep(.el-table) {
      .el-table__cell {
        padding: 0;
      }
      .el-table__body {
        .el-table__cell {
          padding: 0.5rem 0;
        }
      }
    }
  }
  .el-pagination {
    --el-pagination-bg-color: black !important;
    --el-pagination-button-disabled-bg-color: black !important;
    --el-pagination-hover-color: #ffffff !important;
  }
</style>

子组件

<template>
  <div class="childDiv">
    <div v-for="(it, indexs) in contentList" :key="indexs" class="childBox">
      <div class="iconBox">
        <img :src="it.iconBox" style="width: 100%; height: 100%" alt="" />
      </div>
      <div class="childTextBox">
        <div
          @click="it.label !== '出勤率' ? dataClick(item, it) : ''"
          cursor-pointer
          class="data-show"
          >{{ it.label !== '出勤率' ? `${it.label}人数` : it.label }}</div
        >
        <div v-if="personValue && it.label === '出勤率'">
          <NumericalIncrement
            :duration="2"
            :is-decimal="true"
            :value="`${personValue[it.value]}`"
            class="num"
            style="min-width: 0"
          ></NumericalIncrement>
          <span style="font-size: 0.75rem" ml="0.2">%</span>
        </div>
        <NumericalIncrement
          v-if="personValue && it.label !== '出勤率'"
          :duration="2"
          :is-decimal="true"
          :value="personValue[it.value]"
          class="num"
        ></NumericalIncrement>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import NumericalIncrement from '@daxiedongk/views/LodarManagement/compontents/ProPerson/componemts/NumericalIncrement.vue';
  import { onMounted, ref } from 'vue';

  defineProps<{
    contentList: {
      iconBox: string;
      label: string;
      value: number;
    }[];
    item: string;
    personValue: Record<string, any>;
  }>();

  const emit = defineEmits(['dataClick']);
  const val = ref(0);
  onMounted(() => {
    setTimeout(() => {
      val.value = 200;
    }, 3000);
  });
  const dataClick = (item, it) => {
    emit('dataClick', item, it);
  };
</script>

<style scoped>
  .num {
    min-width: 40px;
    font-size: 1.2rem;
    color: #fff;
  }
  .childDiv {
    height: 70%;
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .childBox {
      width: 25%;
      height: 100%;
      display: flex;
      align-items: center;
      .iconBox {
        width: 2rem;
        height: 2rem;
        background-color: rgb(59 130 246 / 0.5);
        border-radius: 50%;
        margin-right: 5%;
      }
      .childTextBox {
        height: 100%;
        display: flex;
        flex-direction: column;
        justify-content: center;
        .data-show {
          font-size: 1rem;
          color: #7699b4;
        }
        .data-show:hover {
          color: #ffffff;
        }
      }
    }
  }
</style>

孙子组件

第一种写法

<template>
  <div>
    <span ref="numberDom">0</span>
  </div>
</template>

<script setup lang="ts">
  import { defineProps, onBeforeUnmount, onBeforeUpdate, onMounted, ref, withDefaults } from 'vue';

  /**
   * @param value 数值大小
   * @param duration 递增动画持续时间;
   * @param isDecimal 是否显示为小数
   */
  const props = withDefaults(
    defineProps<{
      value: number | string;
      duration: number;
      isDecimal: boolean;
    }>(),
    {
      duration: 2,
      isDecimal: false,
    }
  );

  let timer: number | null = null;
  const timerDelay = 5;
  const numberDom = ref<any>(null);

  onMounted(() => {
    numericalIncrement(numberDom.value);
  });
  onBeforeUpdate(() => {
    if (timer) {
      clearInterval(timer!);
      timer = null;
    }
    numericalIncrement(numberDom.value);
  });
  onBeforeUnmount(() => {
    if (timer) {
      clearInterval(timer!);
      timer = null;
    }
  });

  /**
   * @method
   * @param ele 数值对应的dom元素
   * @desc 数值递增动画
   */
  const numericalIncrement = (ele: Element) => {
    const step = (props.value * timerDelay) / (props.duration * 1000);
    let current: number = 0;
    let start: number = 0;
    let flag: boolean = false;
    timer = setInterval(() => {
      start += step;
      if (start >= props.value) {
        flag = props.isDecimal;
        clearInterval(timer!);
        start = props.value;
        timer = null;
      }
      current = start;
      if (flag) {
        ele.innerHTML = current.toString().replace(/(\d)(?=(?:\d{3}[+]?)+$)/g, '$1,');
      } else {
        ele.innerHTML = current
          .toFixed(0)
          .toString()
          .replace(/(\d)(?=(?:\d{3}[+]?)+$)/g, '$1,');
      }
    }, timerDelay);
  };
</script>

<style scoped>
  div {
    display: inline-block;
  }
</style>

第二种写法

<script setup lang="ts">
  const props = defineProps<{}>();

  const dataValue = computed(() => props.value ?? 0);
  const counter = useTransition(dataValue, {
    duration: 1500,
  });
</script>

<template>
  <el-statistic :value="counter" :value-style="{ color: '#ffffff' }" />
</template>

<style scoped lang="less"></style>
注意:将子组件中的内容替换成以下格式

image.png