diff --git a/packages/devui-vue/devui/countdown/__tests__/countdown.spec.ts b/packages/devui-vue/devui/countdown/__tests__/countdown.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..138a2a5c6b379f6e04a51fc6274e528713dd0dc4 --- /dev/null +++ b/packages/devui-vue/devui/countdown/__tests__/countdown.spec.ts @@ -0,0 +1,8 @@ +import { mount } from '@vue/test-utils'; +import { Countdown } from '../index'; + +describe('countdown test', () => { + it('countdown init render', async () => { + // todo + }) +}) diff --git a/packages/devui-vue/devui/countdown/index.ts b/packages/devui-vue/devui/countdown/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..d3cf19a29e030a93b7145526ec33a51194110d8b --- /dev/null +++ b/packages/devui-vue/devui/countdown/index.ts @@ -0,0 +1,18 @@ +import type { App } from 'vue' +import Countdown from './src/countdown' + +Countdown.install = function(app: App): void { + app.component(Countdown.name, Countdown) +} + +export { Countdown } + +export default { + title: 'Countdown 倒计时', + category: '数据展示', + status: '已完成', + install(app: App): void { + app.use(Countdown as any) + } +} + \ No newline at end of file diff --git a/packages/devui-vue/devui/countdown/src/countdown-types.ts b/packages/devui-vue/devui/countdown/src/countdown-types.ts new file mode 100644 index 0000000000000000000000000000000000000000..91a13a1ff0a9e66b31c146bca44f09c8d34fcc84 --- /dev/null +++ b/packages/devui-vue/devui/countdown/src/countdown-types.ts @@ -0,0 +1,37 @@ +import type { ExtractPropTypes } from 'vue' + +export const countdownProps = { + value: { + type: Number, + required: true + }, + format: { + type: String, + default: 'HH:mm:ss' + }, + prefix: { + type: String, + default: '' + }, + suffix: { + type: String, + default: '' + }, + valueStyle: { + type: Object, + default: ()=>{ + return {} + } + } +} as const + +export interface DateFormat{ + Y?: string | number + M?: string | number + D?: string | number + H?: string | number + m?: string | number + s?: string | number + S?: string | number +} +export type CountdownProps = ExtractPropTypes diff --git a/packages/devui-vue/devui/countdown/src/countdown.scss b/packages/devui-vue/devui/countdown/src/countdown.scss new file mode 100644 index 0000000000000000000000000000000000000000..33f1814075d7e782f4878f584393756e549fbacc --- /dev/null +++ b/packages/devui-vue/devui/countdown/src/countdown.scss @@ -0,0 +1,20 @@ +@import '../../style/theme/color'; + +.devui-countdown { + .countdown-content { + font-size: 24px; + + span { + color: $devui-text; + display: inline-block; + } + + .countdown-prefix { + margin-right: 4px; + } + + .countdown-suffix { + margin-left: 4px; + } + } +} diff --git a/packages/devui-vue/devui/countdown/src/countdown.tsx b/packages/devui-vue/devui/countdown/src/countdown.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d5aec728ba7f82304e786aeb01de4f6451800099 --- /dev/null +++ b/packages/devui-vue/devui/countdown/src/countdown.tsx @@ -0,0 +1,73 @@ +import { defineComponent, ref, onUnmounted } from 'vue' +import { countdownProps, CountdownProps } from './countdown-types' +import './countdown.scss' +import { getFormatTime, getLegalTime, getTimeSplit, getDeduplication, numFormat } from './utils' + +export default defineComponent({ + name: 'DCountdown', + props: countdownProps, + emits: ['onChange', 'onFinish'], + setup(props: CountdownProps, ctx) { + const s = getDeduplication(props.format); + const timeFormat = getTimeSplit(props.format); + const timeStr = ref('') + + const getTimeStr = (legalTime: Map) => { + const fomatMap = new Set(['Y', 'M', 'D', 'H', 'm', 's', 'S']); + const t = timeFormat.reduce((pre, cur) => { + if (fomatMap.has(cur.k)) { + return pre + numFormat(legalTime.get(cur.k), cur.n) + } + return pre + cur.k; + }, '') + timeStr.value = t; + } + + const getTime = () => { + const leftTime = props.value > new Date().getTime() ? props.value - new Date().getTime() : 0 + const formatTime = getFormatTime(leftTime); + const legalTime = getLegalTime(s, formatTime); + !ctx.slots.default && getTimeStr(legalTime); + ctx.emit('onChange', { + leftTime, + formatTime, + legalTime + }); + return leftTime; + } + + const countdown = setInterval(() => { + const t = getTime(); + if (t === 0) { + ctx.emit('onFinish'); + clearInterval(countdown) + } + }, s.has('S') ? 100 : 1000) + + getTime(); + onUnmounted(() => { + clearInterval(countdown); + }) + + return () => { + return (
+ { + ctx.slots.default ? ctx.slots.default() : ( +
+ + {props.prefix} + + + {timeStr.value} + + + {props.suffix} + +
+ ) + } +
+ ) + } + } +}) diff --git a/packages/devui-vue/devui/countdown/src/utils.ts b/packages/devui-vue/devui/countdown/src/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..e700f598733d639e5f90e9dcd20a018f9c64d6a1 --- /dev/null +++ b/packages/devui-vue/devui/countdown/src/utils.ts @@ -0,0 +1,81 @@ + +export const getFormatTime = (leftTime: number): Map => { + const timeformat = new Map([['Y', 0], ['M', 0], ['D', 0], ['H', 0], ['m', 0], ['s', 0], ['S', 0]]); + const year = Math.floor(leftTime / (365 * 24 * 60 * 60 * 1000)); + const month = Math.floor(leftTime / (30 * 24 * 60 * 60 * 1000) % 12); + const day = Math.floor(leftTime / (24 * 60 * 60 * 1000) % 30); + const hour = Math.floor(leftTime / (60 * 60 * 1000) % 24); + const minute = Math.floor(leftTime / (60 * 1000) % 60); + const second = Math.floor(leftTime / 1000 % 60); + const millsecond = leftTime % 1000; + timeformat.set('Y', year); + timeformat.set('M', month); + timeformat.set('D', day); + timeformat.set('H', hour); + timeformat.set('m', minute); + timeformat.set('s', second); + timeformat.set('S', millsecond); + return timeformat; +} + +export const getLegalTime = (s: Set, timeformat: Map): Map => { + const dateValue = new Map([['Y', 0], ['M', 0], ['D', 0], ['H', 0], ['m', 0], ['s', 0], ['S', 0]]) + const m = new Map([['Y', 12], ['M', 30], ['D', 24], ['H', 60], ['m', 60], ['s', 1000], ['S', 1]]) + let storage = 0; + for (const k of dateValue.keys()) { + if (s.has(k)) { + dateValue.set(k, timeformat.get(k) + storage) + storage = 0; + } else { + storage += timeformat.get(k) * m.get(k); + } + } + if (!s.has('S') && timeformat.get('S') > 500) { + dateValue.set('s', dateValue.get('s') + 1); + } + return dateValue +} + + +interface ITimeSplit { + k: string + n: number +} +export const getTimeSplit = (format: string): ITimeSplit[] => { + const fomatMap = new Set(['Y', 'M', 'D', 'H', 'm', 's', 'S']); + const m: ITimeSplit[] = []; + for (let i = 0; i < format.length; i++) { + const k = format[i]; + if (m.length === 0 || m[m.length - 1].k !== k || !fomatMap.has(k)) { + m.push({ k, n: 1 }) + } else { + m[m.length - 1].n++ + } + } + return m; +} +export const getDeduplication = (format: string): Set => { + const fomatMap = new Set(['Y', 'M', 'D', 'H', 'm', 's', 'S']); + const s: Set = new Set(); + for (let i = 0; i < format.length; i++) { + const k = format[i]; + if (fomatMap.has(k)) { + s.add(k) + } + } + return s; +} + +export const numFormat = (n: number, len: number): number | string => { + const maxNum = 10 ** len - 1; + if (n >= maxNum) { + return n; + } else { + const carryLen = len - n.toString().length; + let str = '' + for (let i = 0; i < carryLen; i++) { + str += '0' + } + return str + n; + } +} \ No newline at end of file diff --git a/packages/devui-vue/docs/components/countdown/index.md b/packages/devui-vue/docs/components/countdown/index.md new file mode 100644 index 0000000000000000000000000000000000000000..af6b6a68aeaf1d716c8a76f66fdafff2b03fdfe9 --- /dev/null +++ b/packages/devui-vue/docs/components/countdown/index.md @@ -0,0 +1,169 @@ +# Countdown 倒计时 + +倒计时 + +### 何时使用 + +当倒计时时使用 + + +### 基本用法 +默认:时分秒 +:::demo + +```vue + + + + + +``` +::: + + +年月日时分秒 +:::demo + +```vue + + + + + +``` +::: + +### 插槽 +:::demo + +```vue + + + + + +``` +::: + +### d-countdown + +d-countdown 参数 + +| 参数 | 类型 | 默认 | 说明 | +| ---- | ---- | ---- | ---- | +| format | string | HH:mm:ss | 格式化倒计时展示,参考moment | +| value | number | - | 数值内容 | +| prefix | string | - | 设置数值的前缀 | +| suffix | string | - | 设置数值的后缀 | +| valueStyle | CSSProperties | - | 设置数值的样式 | + +### d-countdown 事件 + +d-countdown 事件 + +| 事件 | 类型 | 说明 | +| ---- | ---- | ---- | +| onChange | ({leftTime,formatTime,legalTime}) => void | 倒计时时间变化时触发。leftTime:倒计时剩余得时间戳;formatTime:年月日时分秒毫秒格式倒计时;legalTime:根据format格式化后的值。 | +| onFinish | () => void | 倒计时完成时触发 | +