mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-12-03 11:47:48 +08:00
refactor(components): [statistic] (#10939)
* refactor(components): [statistic] * fix: type error * docs: fix style * chore: use rAF * chore: update docs
This commit is contained in:
parent
a4b73eccda
commit
04f7ea8105
@ -11,13 +11,13 @@ Display statistics.
|
||||
|
||||
:::demo To highlight a number or a group of numbers, such as statistical value, amount, and ranking, you can add elements such as icon and unit before and after the number and title.
|
||||
|
||||
statistic/num
|
||||
statistic/basic
|
||||
|
||||
:::
|
||||
|
||||
## Countdown
|
||||
|
||||
:::demo Set `timeIndices`Start the countdown. Countdown component, support English and Chinese countdown, support to add other components control countdown.
|
||||
:::demo Countdown component, support to add other components control countdown.
|
||||
|
||||
statistic/countdown
|
||||
:::
|
||||
@ -40,10 +40,10 @@ statistic/card
|
||||
### Statistic Attributes
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|-------------------|--------------------------------|---------------------------------------------------------------------|---------|
|
||||
| value | Numerical content | ^[string] / ^[number] | 0 |
|
||||
| ----------------- | ------------------------------ | ------------------------------------------------------------------- | ------- |
|
||||
| value | Numerical content | ^[number] | 0 |
|
||||
| decimal-separator | Setting the decimal point | ^[string] | . |
|
||||
| formatter | Custom numerical presentation | ^[Function]`(value: string \| number) => string \| number` | — |
|
||||
| formatter | Custom numerical presentation | ^[Function]`(value: number) => string \| number` | — |
|
||||
| group-separator | Sets the thousandth identifier | ^[string] | , |
|
||||
| precision | numerical precision | ^[number] | 0 |
|
||||
| prefix | Sets the prefix of a number | ^[string] | — |
|
||||
@ -54,7 +54,7 @@ statistic/card
|
||||
### Statistic Slots
|
||||
|
||||
| Name | Description |
|
||||
|--------|-----------------------------|
|
||||
| ------ | --------------------------- |
|
||||
| prefix | Numeric prefix |
|
||||
| suffix | Suffixes for numeric values |
|
||||
| title | Numeric titles |
|
||||
@ -62,15 +62,15 @@ statistic/card
|
||||
### Statistic Exposes
|
||||
|
||||
| Name | Description | Type |
|
||||
|--------------|-----------------------|----------------------------------|
|
||||
| displayValue | Current display value | ^[object]`Ref<string \| number>` |
|
||||
| ------------ | --------------------- | -------------------------------- |
|
||||
| displayValue | current display value | ^[object]`Ref<string \| number>` |
|
||||
|
||||
## Countdown API
|
||||
|
||||
### Countdown Attributes
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|-------------|----------------------------------|---------------------------------------------------------------------|----------|
|
||||
| ----------- | -------------------------------- | ------------------------------------------------------------------- | -------- |
|
||||
| value | target time | ^[number] / ^[Dayjs] | — |
|
||||
| format | Formatting the countdown display | ^[string] | HH:mm:ss |
|
||||
| prefix | Sets the prefix of a countdown | ^[string] | — |
|
||||
@ -81,14 +81,14 @@ statistic/card
|
||||
### Countdown Events
|
||||
|
||||
| Method | Description | Type |
|
||||
|--------|------------------------------|--------------------------------------|
|
||||
| ------ | ---------------------------- | ------------------------------------ |
|
||||
| change | Time difference change event | ^[Function]`(value: number) => void` |
|
||||
| finish | countdown end event | ^[Function]`() => void` |
|
||||
|
||||
### Countdown Slots
|
||||
|
||||
| Name | Description |
|
||||
|--------|------------------------|
|
||||
| ------ | ---------------------- |
|
||||
| prefix | countdown value prefix |
|
||||
| suffix | countdown value suffix |
|
||||
| title | countdown title |
|
||||
@ -96,9 +96,5 @@ statistic/card
|
||||
### Countdown Exposes
|
||||
|
||||
| Name | Description | Type |
|
||||
|--------------|-----------------------|------------------------|
|
||||
| displayValue | Current display value | ^[object]`Ref<string>` |
|
||||
|
||||
<style lang="scss">
|
||||
@use '../../examples/statistic/index.scss';
|
||||
</style>
|
||||
| ------------ | --------------------- | ---------------------- |
|
||||
| displayValue | current display value | ^[object]`Ref<string>` |
|
||||
|
42
docs/examples/statistic/basic.vue
Normal file
42
docs/examples/statistic/basic.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="Daily active users" :value="268500" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic :value="138">
|
||||
<template #title>
|
||||
<div style="display: inline-flex; align-items: center">
|
||||
Ratio of men to women
|
||||
<el-icon style="margin-left: 4px" :size="12">
|
||||
<Male />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
<template #suffix>/100</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="Total Transactions" :value="172000" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="Feedback number" :value="562">
|
||||
<template #suffix>
|
||||
<el-icon style="vertical-align: -0.125em">
|
||||
<ChatLineRound />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ChatLineRound, Male } from '@element-plus/icons-vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-col {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
@ -1,233 +1,166 @@
|
||||
<template>
|
||||
<div class="play-container3">
|
||||
<div class="s-card s-bg">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<el-card class="itemCard">
|
||||
<ElStatistic :value="98500" :value-style="{ fontSize: '28px' }">
|
||||
<template #title>
|
||||
<div class="titleLeft">
|
||||
<span> Daily active users </span>
|
||||
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="Number of users who logged into the product in one day"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon :size="12">
|
||||
<Warning />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</ElStatistic>
|
||||
<div class="s-Bottom">
|
||||
<el-row>
|
||||
<el-col :span="12" :xs="24" class="item">
|
||||
<div>than yesterday</div>
|
||||
<div class="green">
|
||||
24%
|
||||
<el-icon>
|
||||
<CaretTop />
|
||||
</el-icon>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
:xs="24"
|
||||
class="item"
|
||||
style="text-align: right"
|
||||
>
|
||||
<div>two days ago</div>
|
||||
<div class="red">
|
||||
17%
|
||||
<el-icon>
|
||||
<CaretBottom />
|
||||
</el-icon>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="8">
|
||||
<div class="statistic-card">
|
||||
<el-statistic :value="98500">
|
||||
<template #title>
|
||||
<div style="display: inline-flex; align-items: center">
|
||||
Daily active users
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="Number of users who logged into the product in one day"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon style="margin-left: 4px" :size="12">
|
||||
<Warning />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card class="itemCard">
|
||||
<ElStatistic :value="693700" :value-style="{ fontSize: '28px' }">
|
||||
<template #title>
|
||||
<div class="titleLeft">
|
||||
Monthly Active Users
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="Number of users who logged into the product in one day"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon :size="12">
|
||||
<Warning />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</ElStatistic>
|
||||
<div class="s-Bottom">
|
||||
<el-row>
|
||||
<el-col :span="12" :xs="24" class="item">
|
||||
<div>year on year</div>
|
||||
<div class="green">
|
||||
24%
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="dark"
|
||||
content="Top Center prompts info"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon :size="12">
|
||||
<Warning />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col
|
||||
:span="12"
|
||||
:xs="24"
|
||||
class="item"
|
||||
style="text-align: right"
|
||||
>
|
||||
<div>month on month</div>
|
||||
<div class="red">
|
||||
12%
|
||||
<el-icon>
|
||||
<CaretBottom />
|
||||
</el-icon>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-statistic>
|
||||
<div class="statistic-footer">
|
||||
<div class="footer-item">
|
||||
<span>than yesterday</span>
|
||||
<span class="green">
|
||||
24%
|
||||
<el-icon>
|
||||
<CaretTop />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
<div class="footer-item">
|
||||
<span>two days ago</span>
|
||||
<span class="red">
|
||||
17%
|
||||
<el-icon>
|
||||
<CaretBottom />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="statistic-card">
|
||||
<el-statistic :value="693700">
|
||||
<template #title>
|
||||
<div style="display: inline-flex; align-items: center">
|
||||
Monthly Active Users
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="Number of users who logged into the product in one month"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon style="margin-left: 4px" :size="12">
|
||||
<Warning />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-card class="itemCard">
|
||||
<ElStatistic :value-style="{ fontSize: '28px' }" :value="72000">
|
||||
<template #title>
|
||||
<div class="titleLeft">
|
||||
New transactions today
|
||||
<!-- <el-icon>
|
||||
<Warning />
|
||||
</el-icon> -->
|
||||
</div>
|
||||
</template>
|
||||
</ElStatistic>
|
||||
<div class="s-Bottom">
|
||||
<el-row>
|
||||
<el-col :span="22">
|
||||
<span class="item">
|
||||
than yesterday
|
||||
<span class="red">
|
||||
16%
|
||||
<el-icon>
|
||||
<CaretBottom />
|
||||
</el-icon>
|
||||
</span>
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
<el-icon :size="14" @click="onClick">
|
||||
<ArrowRight />
|
||||
</el-icon>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-statistic>
|
||||
<div class="statistic-footer">
|
||||
<div class="footer-item">
|
||||
<span>year on year</span>
|
||||
<span class="green">
|
||||
24%
|
||||
<el-icon>
|
||||
<CaretTop />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
<div class="footer-item">
|
||||
<span>month on month</span>
|
||||
<span class="red">
|
||||
12%
|
||||
<el-icon>
|
||||
<CaretBottom />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="statistic-card">
|
||||
<el-statistic :value="72000" title="New transactions today">
|
||||
<template #title>
|
||||
<div style="display: inline-flex; align-items: center">
|
||||
New transactions today
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-statistic>
|
||||
<div class="statistic-footer">
|
||||
<div class="footer-item">
|
||||
<span>than yesterday</span>
|
||||
<span class="green">
|
||||
16%
|
||||
<el-icon>
|
||||
<CaretTop />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
<div class="footer-item">
|
||||
<el-icon :size="14">
|
||||
<ArrowRight />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from 'element-plus'
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ArrowRight,
|
||||
CaretBottom,
|
||||
CaretTop,
|
||||
Warning,
|
||||
} from '@element-plus/icons-vue'
|
||||
|
||||
function onClick() {
|
||||
ElMessage({
|
||||
message: 'Are you going far away?',
|
||||
type: 'success',
|
||||
})
|
||||
}
|
||||
|
||||
// code here
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
<style scoped>
|
||||
:global(h2#card-usage ~ .example .example-showcase) {
|
||||
background-color: var(--el-fill-color) !important;
|
||||
}
|
||||
|
||||
.el-statistic {
|
||||
--el-statistic-head-justify-content: flex-start;
|
||||
--el-statistic-content-justify-content: flex-start;
|
||||
--el-statistic-content-font-size: 28px;
|
||||
}
|
||||
.play-container3 {
|
||||
|
||||
.statistic-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.f-center {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.s-card {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.s-bg {
|
||||
padding: 1rem;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
background: var(--el-fill-color);
|
||||
}
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--el-bg-color-overlay);
|
||||
}
|
||||
|
||||
.itemCard {
|
||||
width: 100%;
|
||||
.statistic-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-regular);
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.titleLeft {
|
||||
.statistic-footer .footer-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.statistic-footer .footer-item span:last-child {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
i {
|
||||
margin-left: 4px;
|
||||
}
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.s-Bottom {
|
||||
margin-top: 16px;
|
||||
font-size: 12px;
|
||||
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
.item {
|
||||
height: 20px;
|
||||
padding-left: 4px;
|
||||
padding-bottom: 4px;
|
||||
display: inline-block;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
|
||||
.red {
|
||||
color: var(--el-color-error);
|
||||
}
|
||||
.green {
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
.red {
|
||||
color: var(--el-color-error);
|
||||
}
|
||||
</style>
|
||||
|
@ -1,75 +1,54 @@
|
||||
<template>
|
||||
<div class="play-container2">
|
||||
<div class="s-card">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<ElCountdown title="Start to grab" countdown :value="value" />
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<ElCountdown
|
||||
title="Remaining VIP time"
|
||||
format="HH:mm:ss"
|
||||
:value="value1"
|
||||
/>
|
||||
<div class="f-center">
|
||||
<el-button type="primary" @click="reset">Reset</el-button>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<el-countdown title="Start to grab" :value="value" />
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-countdown
|
||||
title="Remaining VIP time"
|
||||
format="HH:mm:ss"
|
||||
:value="value1"
|
||||
/>
|
||||
<el-button class="countdown-footer" type="primary" @click="reset"
|
||||
>Reset
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-countdown format="DD [days] HH [hours] mm:ss" :value="value2">
|
||||
<template #title>
|
||||
<div style="display: inline-flex; align-items: center">
|
||||
<el-icon style="margin-right: 4px" :size="12">
|
||||
<Calendar />
|
||||
</el-icon>
|
||||
New Year's Day is still to come
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<ElCountdown :value="value2" format="DD day HH:mm:ss">
|
||||
<template #title>
|
||||
<el-icon style="margin-right: 4px" :size="12"
|
||||
><Calendar
|
||||
/></el-icon>
|
||||
<span> New Year's Day is still to come </span>
|
||||
</template>
|
||||
</ElCountdown>
|
||||
<div class="f-center">2023-01-01 00:00:00</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-countdown>
|
||||
<div class="countdown-footer">2023-01-01 00:00:00</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { Calendar } from '@element-plus/icons-vue'
|
||||
|
||||
const value = ref(Date.now() + 1000 * 60 * 60 * 7)
|
||||
const value1 = ref(Date.now() + 1000 * 60 * 60 * 24 * 2)
|
||||
const value2 = ref(dayjs('2023-01-01 00:00:00').valueOf())
|
||||
const value2 = ref(dayjs('2023-01-01 00:00:00'))
|
||||
|
||||
function reset() {
|
||||
value1.value = Date.now() + 1000 * 60 * 60 * 24 * 2
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.play-container2 {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
.f-center {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.s-card {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.s-bg {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background: #f0f2f5;
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
.item {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
text-align: left;
|
||||
}
|
||||
<style scoped>
|
||||
.el-col {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.countdown-footer {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,3 +0,0 @@
|
||||
h2#card-usage ~ .example .example-showcase {
|
||||
background-color: var(--el-fill-color) !important;
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<div class="play-container1">
|
||||
<div class="s-card">
|
||||
<el-row>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="Daily active users" :value="268500" />
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic :value="138">
|
||||
<template #title>
|
||||
Ratio of men to women
|
||||
<el-icon style="" :size="12">
|
||||
<Male />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #suffix> /100 </template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="Total Transactions" :value="172000">
|
||||
<template #suffix>
|
||||
<span style="font-size: 12px" />
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<el-statistic title="Feedback number" :value="562">
|
||||
<template #suffix>
|
||||
<el-icon>
|
||||
<ChatLineRound />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-statistic>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ChatLineRound, Male } from '@element-plus/icons-vue'
|
||||
const value: any = ref(Date.now() + 1000 * 60 * 60 * 24 * 2)
|
||||
function add() {
|
||||
value.value = value.value + 1000 * 60 * 10
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.play-container1 {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.f-center {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.s-card {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.s-bg {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background: var(--el-fill-color);
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
73
packages/components/countdown/__tests__/countdown.test.tsx
Normal file
73
packages/components/countdown/__tests__/countdown.test.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import dayjs from 'dayjs'
|
||||
import Countdown from '../src/countdown.vue'
|
||||
|
||||
import type { VueWrapper } from '@vue/test-utils'
|
||||
|
||||
const TITLE_CLASS = '.el-statistic__head'
|
||||
const CONTENT_CLASS = '.el-statistic__content'
|
||||
|
||||
describe('Countdown.vue', () => {
|
||||
let wrapper: VueWrapper<any>
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('render test', () => {
|
||||
wrapper = mount(() => (
|
||||
<Countdown title="test" value={Date.now() + 1000 * 60} />
|
||||
))
|
||||
|
||||
expect(wrapper.find(TITLE_CLASS).text()).toBe('test')
|
||||
expect(wrapper.find(CONTENT_CLASS).text()).toBe('00:01:00')
|
||||
})
|
||||
|
||||
describe('format', () => {
|
||||
it.each([
|
||||
['DD HH:mm:ss', '02 02:02:02'],
|
||||
['DD [days] HH [hours] mm:ss', '02 days 02 hours 02:02'],
|
||||
['HH:mm:ss', '50:02:02'],
|
||||
['HH:mm:ss:SSS', '50:02:02:002'],
|
||||
])('should work with %s', (format, expected) => {
|
||||
const value = dayjs()
|
||||
.add(2, 'd')
|
||||
.add(2, 'h')
|
||||
.add(2, 'm')
|
||||
.add(2, 's')
|
||||
.add(2, 'ms')
|
||||
|
||||
wrapper = mount(() => <Countdown value={value} format={format} />)
|
||||
|
||||
expect(wrapper.find(CONTENT_CLASS).text()).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
it('change event', () => {
|
||||
const onChange = vi.fn()
|
||||
wrapper = mount(() => (
|
||||
<Countdown onChange={onChange} value={Date.now() + 1000 * 60} />
|
||||
))
|
||||
|
||||
vi.advanceTimersByTime(16)
|
||||
expect(onChange).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('finish event', () => {
|
||||
const onFinish = vi.fn()
|
||||
wrapper = mount(() => (
|
||||
<Countdown onFinish={onFinish} value={Date.now() + 1000 * 60} />
|
||||
))
|
||||
|
||||
vi.advanceTimersByTime(1000 * 30)
|
||||
expect(onFinish).not.toHaveBeenCalled()
|
||||
vi.runAllTimers()
|
||||
expect(onFinish).toHaveBeenCalled()
|
||||
})
|
||||
})
|
7
packages/components/countdown/index.ts
Normal file
7
packages/components/countdown/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { withInstall } from '@element-plus/utils'
|
||||
import Countdown from './src/countdown.vue'
|
||||
|
||||
export const ElCountdown = withInstall(Countdown)
|
||||
export default ElCountdown
|
||||
|
||||
export * from './src/countdown'
|
50
packages/components/countdown/src/countdown.ts
Normal file
50
packages/components/countdown/src/countdown.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { buildProps, definePropType, isNumber } from '@element-plus/utils'
|
||||
import { CHANGE_EVENT } from '@element-plus/constants'
|
||||
|
||||
import type { ExtractPropTypes, StyleValue } from 'vue'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import type Countdown from './countdown.vue'
|
||||
|
||||
export const countdownProps = buildProps({
|
||||
/**
|
||||
* @description Formatting the countdown display
|
||||
*/
|
||||
format: {
|
||||
type: String,
|
||||
default: 'HH:mm:ss',
|
||||
},
|
||||
/**
|
||||
* @description Sets the prefix of a countdown
|
||||
*/
|
||||
prefix: String,
|
||||
/**
|
||||
* @description Sets the suffix of a countdown
|
||||
*/
|
||||
suffix: String,
|
||||
/**
|
||||
* @description countdown titles
|
||||
*/
|
||||
title: String,
|
||||
/**
|
||||
* @description target time
|
||||
*/
|
||||
value: {
|
||||
type: definePropType<number | Dayjs>([Number, Object]),
|
||||
default: 0,
|
||||
},
|
||||
/**
|
||||
* @description Styles countdown values
|
||||
*/
|
||||
valueStyle: {
|
||||
type: definePropType<StyleValue>([String, Object, Array]),
|
||||
},
|
||||
} as const)
|
||||
export type CountdownProps = ExtractPropTypes<typeof countdownProps>
|
||||
|
||||
export const countdownEmits = {
|
||||
finish: () => true,
|
||||
[CHANGE_EVENT]: (value: number) => isNumber(value),
|
||||
}
|
||||
export type CountdownEmits = typeof countdownEmits
|
||||
|
||||
export type CountdownInstance = InstanceType<typeof Countdown>
|
80
packages/components/countdown/src/countdown.vue
Normal file
80
packages/components/countdown/src/countdown.vue
Normal file
@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<el-statistic
|
||||
:value="rawValue"
|
||||
:title="title"
|
||||
:prefix="prefix"
|
||||
:suffix="suffix"
|
||||
:value-style="valueStyle"
|
||||
:formatter="formatter"
|
||||
>
|
||||
<template v-for="(_, name) in $slots" #[name]>
|
||||
<slot :name="name" />
|
||||
</template>
|
||||
</el-statistic>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||
import { ElStatistic } from '@element-plus/components/statistic'
|
||||
import { cAF, rAF } from '@element-plus/utils'
|
||||
import { countdownEmits, countdownProps } from './countdown'
|
||||
import { formatTime, getTime } from './utils'
|
||||
|
||||
defineOptions({
|
||||
name: 'ElCountdown',
|
||||
})
|
||||
|
||||
const props = defineProps(countdownProps)
|
||||
const emit = defineEmits(countdownEmits)
|
||||
|
||||
let timer: ReturnType<typeof rAF> | undefined
|
||||
const rawValue = ref(getTime(props.value) - Date.now())
|
||||
const displayValue = computed(() => formatTime(rawValue.value, props.format))
|
||||
|
||||
const formatter = (val: number) => formatTime(val, props.format)
|
||||
|
||||
const stopTimer = () => {
|
||||
if (timer) {
|
||||
cAF(timer)
|
||||
timer = undefined
|
||||
}
|
||||
}
|
||||
|
||||
const startTimer = () => {
|
||||
const timestamp = getTime(props.value)
|
||||
const frameFunc = () => {
|
||||
let diff = timestamp - Date.now()
|
||||
emit('change', diff)
|
||||
if (diff <= 0) {
|
||||
diff = 0
|
||||
stopTimer()
|
||||
emit('finish')
|
||||
} else {
|
||||
timer = rAF(frameFunc)
|
||||
}
|
||||
rawValue.value = diff
|
||||
}
|
||||
timer = rAF(frameFunc)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.value, props.format],
|
||||
() => {
|
||||
stopTimer()
|
||||
startTimer()
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopTimer()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
/**
|
||||
* @description current display value
|
||||
*/
|
||||
displayValue,
|
||||
})
|
||||
</script>
|
36
packages/components/countdown/src/utils.ts
Normal file
36
packages/components/countdown/src/utils.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { isNumber } from '@element-plus/utils'
|
||||
|
||||
import type { Dayjs } from 'dayjs'
|
||||
|
||||
const timeUnits = [
|
||||
['Y', 1000 * 60 * 60 * 24 * 365], // years
|
||||
['M', 1000 * 60 * 60 * 24 * 30], // months
|
||||
['D', 1000 * 60 * 60 * 24], // days
|
||||
['H', 1000 * 60 * 60], // hours
|
||||
['m', 1000 * 60], // minutes
|
||||
['s', 1000], // seconds
|
||||
['S', 1], // million seconds
|
||||
] as const
|
||||
|
||||
export const getTime = (value: number | Dayjs) => {
|
||||
return isNumber(value) ? new Date(value).getTime() : value.valueOf()
|
||||
}
|
||||
|
||||
export const formatTime = (timestamp: number, format: string) => {
|
||||
let timeLeft = timestamp
|
||||
const escapeRegex = /\[([^\]]*)]/g
|
||||
|
||||
const replacedText = timeUnits.reduce((current, [name, unit]) => {
|
||||
const replaceRegex = new RegExp(`${name}+(?![^\\[\\]]*\\])`, 'g')
|
||||
if (replaceRegex.test(current)) {
|
||||
const value = Math.floor(timeLeft / unit)
|
||||
timeLeft -= value * unit
|
||||
return current.replace(replaceRegex, (match) =>
|
||||
String(value).padStart(match.length, '0')
|
||||
)
|
||||
}
|
||||
return current
|
||||
}, format)
|
||||
|
||||
return replacedText.replace(escapeRegex, '$1')
|
||||
}
|
2
packages/components/countdown/style/css.ts
Normal file
2
packages/components/countdown/style/css.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import '@element-plus/components/base/style/css'
|
||||
import '@element-plus/theme-chalk/el-statistic.css'
|
2
packages/components/countdown/style/index.ts
Normal file
2
packages/components/countdown/style/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import '@element-plus/components/base/style'
|
||||
import '@element-plus/theme-chalk/src/statistic.scss'
|
@ -19,6 +19,7 @@ export * from './collapse-transition'
|
||||
export * from './color-picker'
|
||||
export * from './config-provider'
|
||||
export * from './container'
|
||||
export * from './countdown'
|
||||
export * from './date-picker'
|
||||
export * from './descriptions'
|
||||
export * from './dialog'
|
||||
@ -50,6 +51,7 @@ export * from './select-v2'
|
||||
export * from './skeleton'
|
||||
export * from './slider'
|
||||
export * from './space'
|
||||
export * from './statistic'
|
||||
export * from './steps'
|
||||
export * from './switch'
|
||||
export * from './table'
|
||||
@ -66,7 +68,6 @@ export * from './tree-select'
|
||||
export * from './tree-v2'
|
||||
export * from './upload'
|
||||
export * from './virtual-list'
|
||||
export * from './statistic'
|
||||
|
||||
// plugins
|
||||
export * from './infinite-scroll'
|
||||
|
@ -1,63 +0,0 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import dayjs from 'dayjs'
|
||||
import Countdown from '../src/countdown.vue'
|
||||
|
||||
const TITLE_CLASS = '.el-statistic__head'
|
||||
const CONTENT_CLASS = '.el-statistic__content'
|
||||
|
||||
describe('Countdown.vue', () => {
|
||||
it('render test', async () => {
|
||||
const wrapper = mount(() => (
|
||||
<Countdown title="test" value={Date.now() + 1000 * 60} />
|
||||
))
|
||||
|
||||
expect(wrapper.find(TITLE_CLASS).text()).toBe('test')
|
||||
expect(wrapper.find(CONTENT_CLASS).text()).toBe('00:00:59')
|
||||
})
|
||||
|
||||
describe('format', () => {
|
||||
const value = dayjs()
|
||||
.add(2, 'd')
|
||||
.add(2, 'h')
|
||||
.add(2, 'm')
|
||||
.add(2, 's')
|
||||
.add(2, 'ms')
|
||||
|
||||
it.each([
|
||||
['DD HH:mm:ss', '02 02:02:01'],
|
||||
['HH:mm:ss', '50:02:01'],
|
||||
['H:m:s', '50:2:1'],
|
||||
])('should work with %s', async (format, expected) => {
|
||||
const wrapper = mount(() => <Countdown value={value} format={format} />)
|
||||
|
||||
expect(wrapper.find(CONTENT_CLASS).text()).toBe(expected)
|
||||
})
|
||||
})
|
||||
|
||||
it('change event', async () => {
|
||||
vi.useFakeTimers()
|
||||
const onChange = vi.fn()
|
||||
mount(() => (
|
||||
<Countdown onChange={onChange} value={Date.now() + 1000 * 60} />
|
||||
))
|
||||
|
||||
vi.advanceTimersByTime((1000 / 30) * 2)
|
||||
expect(onChange).toHaveBeenCalledTimes(2)
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('finish event', async () => {
|
||||
vi.useFakeTimers()
|
||||
const onFinish = vi.fn()
|
||||
mount(() => (
|
||||
<Countdown onFinish={onFinish} value={Date.now() + 1000 * 60} />
|
||||
))
|
||||
|
||||
vi.advanceTimersByTime(1000 * 30)
|
||||
expect(onFinish).not.toHaveBeenCalled()
|
||||
vi.runAllTimers()
|
||||
expect(onFinish).toHaveBeenCalled()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
})
|
@ -1,11 +1,7 @@
|
||||
import { withInstall } from '@element-plus/utils'
|
||||
import Statistic from './src/statistic.vue'
|
||||
import Countdown from './src/countdown.vue'
|
||||
|
||||
export * from './src/statistic'
|
||||
export * from './src/countdown'
|
||||
|
||||
export const ElStatistic = withInstall(Statistic)
|
||||
export const ElCountdown = withInstall(Countdown)
|
||||
|
||||
export default ElStatistic
|
||||
export * from './src/statistic'
|
||||
|
@ -1,86 +0,0 @@
|
||||
import { padStart } from 'lodash-unified'
|
||||
import { buildProps, definePropType, isNumber } from '@element-plus/utils'
|
||||
import { CHANGE_EVENT } from '@element-plus/constants'
|
||||
import type { StyleValue } from 'vue'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import type Countdown from './countdown.vue'
|
||||
export const countdownProps = buildProps({
|
||||
/**
|
||||
* @description Formatting the countdown display
|
||||
*/
|
||||
format: {
|
||||
type: String,
|
||||
default: 'HH:mm:ss',
|
||||
},
|
||||
/**
|
||||
* @description Sets the prefix of a countdown
|
||||
*/
|
||||
prefix: String,
|
||||
/**
|
||||
* @description Sets the suffix of a countdown
|
||||
*/
|
||||
suffix: String,
|
||||
/**
|
||||
* @description countdown titles
|
||||
*/
|
||||
title: String,
|
||||
/**
|
||||
* @description target time
|
||||
*/
|
||||
value: {
|
||||
type: definePropType<number | Dayjs>([Number, Object]),
|
||||
default: 0,
|
||||
},
|
||||
/**
|
||||
* @description Styles countdown values
|
||||
*/
|
||||
valueStyle: {
|
||||
type: definePropType<StyleValue>([String, Object, Array]),
|
||||
},
|
||||
} as const)
|
||||
|
||||
export const countdownEmits = {
|
||||
finish: () => true,
|
||||
[CHANGE_EVENT]: (value: number) => isNumber(value),
|
||||
}
|
||||
|
||||
export const formatTimeStr = (format: string, time: number) => {
|
||||
const timeUnits: [string, number][] = [
|
||||
['Y', 1000 * 60 * 60 * 24 * 365], // years
|
||||
['M', 1000 * 60 * 60 * 24 * 30], // months
|
||||
['D', 1000 * 60 * 60 * 24], // days
|
||||
['H', 1000 * 60 * 60], // hours
|
||||
['m', 1000 * 60], // minutes
|
||||
['s', 1000], // seconds
|
||||
['S', 1], // million seconds
|
||||
]
|
||||
// The unformatted value of the previous tick
|
||||
let pre = 0
|
||||
// previous tick
|
||||
let preTick = 0
|
||||
// time left
|
||||
let timeLeft = time
|
||||
return timeUnits.reduce((con: string, item: [string, number]) => {
|
||||
const name = item[0]
|
||||
return con.replace(new RegExp(`${name}+`, 'g'), (match) => {
|
||||
let sum = 0
|
||||
if (!format.includes(name)) {
|
||||
pre = Math.floor(timeLeft / item[1])
|
||||
timeLeft = timeLeft - pre * item[1]
|
||||
preTick = item[1]
|
||||
} else {
|
||||
pre = 0
|
||||
preTick = 0
|
||||
sum = Math.floor(timeLeft / item[1])
|
||||
timeLeft = timeLeft - sum * item[1]
|
||||
if (pre > 0) {
|
||||
sum = sum + pre * (preTick / item[1])
|
||||
pre = 0
|
||||
}
|
||||
}
|
||||
return padStart(String(sum), match.length, '0') // autoCompletion
|
||||
})
|
||||
}, format)
|
||||
}
|
||||
|
||||
export type CountdownInstance = InstanceType<typeof Countdown>
|
@ -1,93 +0,0 @@
|
||||
<template>
|
||||
<div :class="ns.b()">
|
||||
<div v-if="!!$slots.title || title" ref="title" :class="ns.e('head')">
|
||||
<span :class="ns.e('title')">
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
<div :class="ns.e('content')">
|
||||
<span v-if="!!$slots.title || prefix" :class="ns.e('prefix')">
|
||||
<slot name="prefix">
|
||||
{{ prefix }}
|
||||
</slot>
|
||||
</span>
|
||||
<span :class="ns.e('number')" :style="valueStyle">
|
||||
{{ displayValue }}
|
||||
</span>
|
||||
<span v-if="!!$slots.title || suffix" :class="ns.e('suffix')">
|
||||
<slot name="suffix">
|
||||
{{ suffix }}
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, ref, watch } from 'vue'
|
||||
import { useNamespace } from '@element-plus/hooks'
|
||||
import { isNumber } from '@element-plus/utils'
|
||||
import { countdownEmits, countdownProps, formatTimeStr } from './countdown'
|
||||
|
||||
const REFRESH_INTERVAL = 1000 / 30
|
||||
|
||||
defineOptions({
|
||||
name: 'ElCountdown',
|
||||
})
|
||||
|
||||
const props = defineProps(countdownProps)
|
||||
const emit = defineEmits(countdownEmits)
|
||||
const ns = useNamespace('statistic')
|
||||
const displayValue = ref('')
|
||||
let timer: ReturnType<typeof setInterval> | undefined
|
||||
|
||||
const getTime = (val: number) => {
|
||||
return new Date(val).getTime()
|
||||
}
|
||||
|
||||
const stopTimer = () => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
timer = undefined
|
||||
}
|
||||
}
|
||||
|
||||
const startTimer = () => {
|
||||
const { value, format } = props
|
||||
const timestamp = getTime(isNumber(value) ? value : value.valueOf())
|
||||
displayValue.value = formatTimeStr(format, timestamp - Date.now())
|
||||
timer = setInterval(() => {
|
||||
let diff = timestamp - Date.now()
|
||||
emit('change', diff)
|
||||
if (diff <= 0) {
|
||||
diff = 0
|
||||
stopTimer()
|
||||
emit('finish')
|
||||
}
|
||||
displayValue.value = formatTimeStr(format, diff)
|
||||
}, REFRESH_INTERVAL)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.value, props.format],
|
||||
() => {
|
||||
stopTimer()
|
||||
startTimer()
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
stopTimer()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
/**
|
||||
* @description Current display value
|
||||
*/
|
||||
displayValue,
|
||||
})
|
||||
</script>
|
@ -1,6 +1,9 @@
|
||||
import { buildProps, definePropType } from '@element-plus/utils'
|
||||
import type { StyleValue } from 'vue'
|
||||
|
||||
import type { ExtractPropTypes, StyleValue } from 'vue'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import type Statistic from './statistic.vue'
|
||||
|
||||
export const statisticProps = buildProps({
|
||||
/**
|
||||
* @description Setting the decimal point
|
||||
@ -26,14 +29,12 @@ export const statisticProps = buildProps({
|
||||
/**
|
||||
* @description Custom numerical presentation
|
||||
*/
|
||||
formatter: {
|
||||
type: definePropType<(val: string | number) => string | number>(Function),
|
||||
},
|
||||
formatter: Function,
|
||||
/**
|
||||
* @description Numerical content
|
||||
*/
|
||||
value: {
|
||||
type: [String, Number],
|
||||
type: definePropType<number | Dayjs>([Number, Object]),
|
||||
default: 0,
|
||||
},
|
||||
/**
|
||||
@ -55,17 +56,7 @@ export const statisticProps = buildProps({
|
||||
valueStyle: {
|
||||
type: definePropType<StyleValue>([String, Object, Array]),
|
||||
},
|
||||
// rate: {
|
||||
// type: Number,
|
||||
// default: 3,
|
||||
// },
|
||||
} as const)
|
||||
export const groupFormat = function (
|
||||
target: string,
|
||||
step = 3,
|
||||
groupSeparator = ','
|
||||
): string {
|
||||
const reg = new RegExp(`\\B(?=(\\d{${step}})+(?!\\d))`, 'g')
|
||||
return target.replace(reg, groupSeparator)
|
||||
}
|
||||
export type StatisticProps = ExtractPropTypes<typeof statisticProps>
|
||||
|
||||
export type StatisticInstance = InstanceType<typeof Statistic>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div :class="ns.b()">
|
||||
<div v-if="$slots.title || title" ref="title" :class="ns.e('head')">
|
||||
<div v-if="$slots.title || title" :class="ns.e('head')">
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
@ -24,45 +24,36 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { isNil } from 'lodash-unified'
|
||||
import { useNamespace } from '@element-plus/hooks'
|
||||
import { isFunction } from '@element-plus/utils'
|
||||
import { groupFormat, statisticProps } from './statistic'
|
||||
import { isFunction, isNumber } from '@element-plus/utils'
|
||||
import { statisticProps } from './statistic'
|
||||
|
||||
defineOptions({
|
||||
name: 'ElStatistic',
|
||||
})
|
||||
|
||||
const THOUSANDTH = 3
|
||||
|
||||
const props = defineProps(statisticProps)
|
||||
const ns = useNamespace('statistic')
|
||||
|
||||
const displayValue = computed(() => {
|
||||
if (isFunction(props.formatter)) {
|
||||
return props.formatter(props.value)
|
||||
} else if (
|
||||
isNil(props.value) ||
|
||||
!/^(-|\+)?\d+(\.\d+)?$/.test(`${props.value}`)
|
||||
) {
|
||||
return props.value
|
||||
} else {
|
||||
let [integer, decimal] = `${props.value}`.split('.')
|
||||
if (!isNil(props.precision)) {
|
||||
decimal = `${decimal || ''}${(1)
|
||||
.toFixed(props.precision)
|
||||
.replace('.', '')
|
||||
.slice(1)}`
|
||||
decimal = decimal.slice(0, props.precision)
|
||||
}
|
||||
integer = groupFormat(integer, THOUSANDTH, props.groupSeparator)
|
||||
return [integer, decimal].join(decimal ? props.decimalSeparator || '.' : '')
|
||||
}
|
||||
const { value, formatter, precision, decimalSeparator, groupSeparator } =
|
||||
props
|
||||
|
||||
if (isFunction(formatter)) return formatter(value)
|
||||
|
||||
if (!isNumber(value)) return value
|
||||
|
||||
let [integer, decimal = ''] = String(value).split('.')
|
||||
decimal = decimal
|
||||
.padEnd(precision, '0')
|
||||
.slice(0, precision > 0 ? precision : 0)
|
||||
integer = integer.replace(/\B(?=(\d{3})+(?!\d))/g, groupSeparator)
|
||||
return [integer, decimal].join(decimal ? decimalSeparator : '')
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
/**
|
||||
* @description Current display value
|
||||
* @description current display value
|
||||
*/
|
||||
displayValue,
|
||||
})
|
||||
|
@ -82,6 +82,8 @@ import { ElSelectV2 } from '@element-plus/components/select-v2'
|
||||
import { ElSkeleton, ElSkeletonItem } from '@element-plus/components/skeleton'
|
||||
import { ElSlider } from '@element-plus/components/slider'
|
||||
import { ElSpace } from '@element-plus/components/space'
|
||||
import { ElStatistic } from '@element-plus/components/statistic'
|
||||
import { ElCountdown } from '@element-plus/components/countdown'
|
||||
import { ElStep, ElSteps } from '@element-plus/components/steps'
|
||||
import { ElSwitch } from '@element-plus/components/switch'
|
||||
import { ElTable, ElTableColumn } from '@element-plus/components/table'
|
||||
@ -95,15 +97,13 @@ import { ElTooltip } from '@element-plus/components/tooltip'
|
||||
import { ElTooltipV2 } from '@element-plus/components/tooltip-v2'
|
||||
import { ElTransfer } from '@element-plus/components/transfer'
|
||||
import { ElTree } from '@element-plus/components/tree'
|
||||
import { ElCountdown, ElStatistic } from '@element-plus/components/statistic'
|
||||
|
||||
import { ElTreeSelect } from '@element-plus/components/tree-select'
|
||||
import { ElTreeV2 } from '@element-plus/components/tree-v2'
|
||||
import { ElUpload } from '@element-plus/components/upload'
|
||||
|
||||
import type { Plugin } from 'vue'
|
||||
|
||||
export default [
|
||||
ElCountdown,
|
||||
ElStatistic,
|
||||
ElAffix,
|
||||
ElAlert,
|
||||
ElAutocomplete,
|
||||
@ -178,6 +178,8 @@ export default [
|
||||
ElSkeletonItem,
|
||||
ElSlider,
|
||||
ElSpace,
|
||||
ElStatistic,
|
||||
ElCountdown,
|
||||
ElSteps,
|
||||
ElStep,
|
||||
ElSwitch,
|
||||
|
@ -627,12 +627,12 @@ $cascader: map.merge(
|
||||
$statistic: () !default;
|
||||
$statistic: map.merge(
|
||||
(
|
||||
'head-justify-content': center,
|
||||
'content-justify-content': center,
|
||||
'padding': 0 5px,
|
||||
'content-margin-top': 4px,
|
||||
'content-text-color': getCssVar('text-color', 'primary'),
|
||||
'head-text-color': getCssVar('text-color', 'regular'),
|
||||
'title-font-weight': 400,
|
||||
'title-font-size': getCssVar('font-size', 'extra-small'),
|
||||
'title-color': getCssVar('text-color', 'regular'),
|
||||
'content-font-weight': 400,
|
||||
'content-font-size': getCssVar('font-size', 'extra-large'),
|
||||
'content-color': getCssVar('text-color', 'primary'),
|
||||
),
|
||||
$statistic
|
||||
);
|
||||
|
@ -4,43 +4,32 @@
|
||||
|
||||
@include b(statistic) {
|
||||
@include set-component-css-var('statistic', $statistic);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-variant: tabular-nums;
|
||||
// line-height: 1.5715;
|
||||
list-style: none;
|
||||
font-feature-settings: 'tnum';
|
||||
@include e(head) {
|
||||
padding: getCssVar('statistic-padding');
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: getCssVar('font-size', 'extra-small');
|
||||
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: getCssVar('statistic-head-justify-content');
|
||||
color: getCssVar('statistic-head-text-color');
|
||||
@include e(head) {
|
||||
font-weight: getCssVar('statistic-title-font-weight');
|
||||
font-size: getCssVar('statistic-title-font-size');
|
||||
color: getCssVar('statistic-title-color');
|
||||
line-height: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
@include e(content) {
|
||||
display: flex;
|
||||
justify-content: getCssVar('statistic-content-justify-content');
|
||||
align-items: center;
|
||||
padding-top: getCssVar('statistic-content-margin-top');
|
||||
@include e(number) {
|
||||
font-style: normal;
|
||||
padding: getCssVar('statistic-padding');
|
||||
font-family: sans-serif;
|
||||
}
|
||||
span {
|
||||
font-weight: 400;
|
||||
color: getCssVar('statistic-content-text-color');
|
||||
font-size: getCssVar('font-size', 'extra-large');
|
||||
font-weight: getCssVar('statistic-content-font-weight');
|
||||
font-size: getCssVar('statistic-content-font-size');
|
||||
color: getCssVar('statistic-content-color');
|
||||
|
||||
@include e(value) {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@include e(prefix) {
|
||||
margin-right: 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@include e(suffix) {
|
||||
margin-left: 4px;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user