-
-
Notifications
You must be signed in to change notification settings - Fork 633
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
401 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
<template> | ||
<div class="var-countdown"> | ||
<slot v-bind="timeData"> | ||
{{ showTime }} | ||
</slot> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { defineComponent, Ref, ref, watch } from 'vue' | ||
import dayjs, { UnitTypeLongPlural } from 'dayjs' | ||
import duration, { Duration } from 'dayjs/plugin/duration' | ||
import { props } from './props' | ||
import { requestAnimationFrame, cancelAnimationFrame } from '../utils/elements' | ||
import { toNumber } from '../utils/shared' | ||
dayjs.extend(duration) | ||
type Format = { | ||
DD: Duration['asDays'] | ||
HH: Duration['asHours'] | ||
mm: Duration['asMinutes'] | ||
ss: Duration['asSeconds'] | ||
} | ||
type TimeData = Exclude<UnitTypeLongPlural, 'months' | 'years' | 'dates'> | ||
export default defineComponent({ | ||
name: 'VarCountdown', | ||
props, | ||
setup(props) { | ||
const endTime: Ref<number> = ref(0) | ||
const isStart: Ref<boolean> = ref(false) | ||
const showTime: Ref<string> = ref('') | ||
const handle: Ref<number> = ref(0) | ||
const pauseTime: Ref<number> = ref(0) | ||
const timeData: Ref<Record<TimeData, number>> = ref({ | ||
days: 0, | ||
hours: 0, | ||
minutes: 0, | ||
seconds: 0, | ||
milliseconds: 0, | ||
}) | ||
const isSameTime = (durationTime: number, newFormat: string): boolean => { | ||
if (!showTime.value) return true | ||
return showTime.value === dayjs.duration(durationTime).format(newFormat) | ||
} | ||
const replaceValue = (milliseconds: number, fixedLength: number): string => { | ||
const len = `${milliseconds}`.length | ||
if (fixedLength === 3) { | ||
return len === 1 ? `00${milliseconds}` : len === 2 ? `0${milliseconds}` : `${milliseconds}` | ||
} | ||
const sliceMilliseconds = `${milliseconds}`.slice(0, 2) | ||
return len === 1 ? `0${sliceMilliseconds}` : `${sliceMilliseconds}` | ||
} | ||
const formatMilliseconds = (milliseconds: number, newFormat: string): string => { | ||
let time = newFormat | ||
if (time.includes('SSS')) time = time.replaceAll('SSS', replaceValue(milliseconds, 3)) | ||
if (time.includes('SS')) time = (time || time).replaceAll('SS', replaceValue(milliseconds, 2)) | ||
if (time.includes('S')) time = (time || time).replaceAll('S', `${milliseconds}`.slice(0, 1)) | ||
return time | ||
} | ||
const formatTime = (type: keyof Format, durationTime: number) => { | ||
const duration = dayjs.duration(durationTime) | ||
const format: Format = { | ||
DD: duration.asDays, | ||
HH: duration.asHours, | ||
mm: duration.asMinutes, | ||
ss: duration.asSeconds, | ||
} | ||
const millis = duration.milliseconds() | ||
const method = format[type].bind(duration) | ||
let roundTime = `${Math.floor(method())}` | ||
roundTime = +roundTime < 10 ? `0${roundTime}` : roundTime | ||
let newFormat = props.format.replaceAll(type, roundTime) | ||
newFormat = formatMilliseconds(millis, newFormat) | ||
if (!isSameTime(durationTime, newFormat)) props.onChange?.() | ||
timeData.value = { | ||
days: duration.days(), | ||
hours: duration.hours(), | ||
minutes: duration.minutes(), | ||
seconds: duration.seconds(), | ||
milliseconds: duration.milliseconds(), | ||
} | ||
showTime.value = duration.format(newFormat) | ||
} | ||
const countdown = () => { | ||
const { format, time, onEnd, autoStart } = props | ||
const now = Date.now() | ||
if (!endTime.value) endTime.value = now + toNumber(time) | ||
let durationTime = endTime.value - now | ||
if (durationTime < 0) durationTime = 0 | ||
pauseTime.value = durationTime | ||
if (format.includes('DD')) formatTime('DD', durationTime) | ||
else if (format.includes('HH')) formatTime('HH', durationTime) | ||
else if (format.includes('mm')) formatTime('mm', durationTime) | ||
else if (format.includes('ss')) formatTime('ss', durationTime) | ||
else showTime.value = `${durationTime}` | ||
if (durationTime === 0) { | ||
onEnd?.() | ||
return | ||
} | ||
if (autoStart || isStart.value) handle.value = requestAnimationFrame(countdown) | ||
} | ||
// expose | ||
const start = () => { | ||
if (isStart.value) return | ||
isStart.value = true | ||
endTime.value = Date.now() + (pauseTime.value || toNumber(props.time)) | ||
countdown() | ||
} | ||
// expose | ||
const pause = () => { | ||
isStart.value = false | ||
} | ||
// expose | ||
const reset = () => { | ||
endTime.value = 0 | ||
isStart.value = false | ||
cancelAnimationFrame(handle.value) | ||
countdown() | ||
} | ||
watch( | ||
() => props.time, | ||
() => reset(), | ||
{ immediate: true } | ||
) | ||
return { | ||
showTime, | ||
timeData, | ||
start, | ||
pause, | ||
reset, | ||
} | ||
}, | ||
}) | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import example from '../example' | ||
import { render } from '@testing-library/vue' | ||
|
||
test('test countdown example', async () => { | ||
const wrapper = render(example) | ||
console.log(wrapper) | ||
}) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# Countdown 倒计时 | ||
|
||
### 介绍 | ||
|
||
用于实时展示倒计时数值,支持毫秒精度。 | ||
|
||
### 引入 | ||
|
||
```js | ||
import { Countdown } from '@varlet/ui' | ||
|
||
export default defineComponent({ | ||
components: { | ||
[Countdown.name]: Countdown | ||
} | ||
}) | ||
``` | ||
|
||
### 基本使用 | ||
|
||
`time` 属性表示倒计时总时长,单位为毫秒。 | ||
|
||
```html | ||
<var-countdown :time="time" /> | ||
``` | ||
|
||
```js | ||
import { defineComponent, ref } from 'vue' | ||
import { Countdown } from '@varlet/ui' | ||
|
||
export default defineComponent({ | ||
components: { | ||
[Countdown.name]: Countdown | ||
}, | ||
setup() { | ||
const time = ref(30 * 60 * 60 * 1000) | ||
|
||
return { | ||
time | ||
} | ||
} | ||
}) | ||
``` | ||
### 自定义格式 | ||
|
||
通过 `format` 属性设置倒计时文本的内容。 | ||
|
||
```html | ||
<var-countdown :time="time" format="DD 天 HH 时 mm 分 ss 秒" /> | ||
``` | ||
|
||
### 显示毫秒 | ||
|
||
通过 `S` 文本显示毫秒。 | ||
|
||
```html | ||
<var-countdown :time="time" format="HH:mm:ss:SS" /> | ||
``` | ||
|
||
### 自定义样式 | ||
|
||
通过插槽自定义倒计时的样式,`timeData` 对象格式见下方表格。。 | ||
|
||
```html | ||
<van-count-down :time="time"> | ||
<template #default="timeData"> | ||
|
||
</template> | ||
</van-count-down> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
<template> | ||
<div> | ||
<app-type>基本使用</app-type> | ||
<var-countdown time="108000000" /> | ||
</div> | ||
<div> | ||
<app-type>自定义格式</app-type> | ||
<var-countdown time="108000000" format="DD 天 HH 时 mm 分 ss 秒" /> | ||
</div> | ||
<div> | ||
<app-type>显示毫秒</app-type> | ||
<var-countdown time="108000000" format="HH : mm : ss : SS" /> | ||
</div> | ||
<div> | ||
<app-type>自定义样式</app-type> | ||
<var-countdown time="108000000"> | ||
<template #default="timeData"> | ||
<span class="block">{{ timeData.hours }}</span> | ||
<span class="colon">:</span> | ||
<span class="block">{{ timeData.minutes }}</span> | ||
<span class="colon">:</span> | ||
<span class="block">{{ timeData.seconds }}</span> | ||
</template> | ||
</var-countdown> | ||
</div> | ||
<div> | ||
<app-type>手动控制</app-type> | ||
<var-countdown :time="time" ref="countdown" :auto-start="false" format="ss : SSS" @end="end" @change="change" /> | ||
<div style="display: flex; justify-content: space-between; margin-top: 10px"> | ||
<var-button type="primary" @click="$refs.countdown.start()">开始</var-button> | ||
<var-button @click="$refs.countdown.pause()">暂停</var-button> | ||
<var-button @click="$refs.countdown.reset()">重置</var-button> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import { defineComponent, ref } from 'vue' | ||
import Countdown from '..' | ||
import Snackbar from '../../snackbar' | ||
import Button from '../../button' | ||
export default defineComponent({ | ||
name: 'CountdownExample', | ||
components: { | ||
[Countdown.name]: Countdown, | ||
[Button.name]: Button, | ||
}, | ||
setup() { | ||
const countdown = ref(null) | ||
const time = ref(3000) | ||
const end = () => { | ||
Snackbar.info('end!') | ||
} | ||
const change = () => { | ||
console.log('change') | ||
} | ||
return { | ||
time, | ||
end, | ||
countdown, | ||
change, | ||
} | ||
}, | ||
}) | ||
</script> | ||
|
||
<style scoped> | ||
.block { | ||
background: #2e67ba; | ||
color: white; | ||
width: 30px; | ||
height: 30px; | ||
border-radius: 50%; | ||
display: inline-flex; | ||
align-items: center; | ||
justify-content: center; | ||
} | ||
.colon { | ||
margin: 0 5px; | ||
font-size: 18px; | ||
font-weight: 500; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { App } from 'vue' | ||
import Countdown from './Countdown.vue' | ||
|
||
Countdown.install = function (app: App) { | ||
app.component(Countdown.name, Countdown) | ||
} | ||
|
||
export default Countdown |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export const props = { | ||
time: { | ||
type: [String, Number], | ||
default: 0, | ||
}, | ||
format: { | ||
type: String, | ||
default: 'HH:mm:ss', | ||
}, | ||
autoStart: { | ||
type: Boolean, | ||
default: true, | ||
}, | ||
onEnd: { | ||
type: Function, | ||
}, | ||
onChange: { | ||
type: Function, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.