diff --git a/cli/create.mjs b/cli/create.mjs index c386488..600010f 100644 --- a/cli/create.mjs +++ b/cli/create.mjs @@ -43,11 +43,11 @@ export default async () => { const hooksType = await select({ message: '请选择Hooks用途分类:', choices: [ - { name: '通用', value: 'common', description: '通用' }, - { name: '业务', value: 'business', description: '业务相关' }, - { name: '状态', value: 'business', description: '操作状态相关' }, + { name: '通用', value: 'Common', description: '通用' }, + { name: '业务', value: 'Worker', description: '业务相关' }, + { name: '状态', value: 'State', description: '操作状态相关' }, { name: '文档对象', value: 'Dom', description: '操作Dom相关' }, - { name: '其他', value: 'other', description: '其他杂项' }, + { name: '其他', value: 'Other', description: '其他杂项' }, ], }); diff --git a/docs/.vitepress/router.mjs b/docs/.vitepress/router.mjs index 00cc24b..81a5ad5 100644 --- a/docs/.vitepress/router.mjs +++ b/docs/.vitepress/router.mjs @@ -19,7 +19,7 @@ const Router = { 'useDebounce', ], Data: ['useTable'], - Worker: ['useWorkerFunction'], + Worker: ['useCountDown','useWorkerFunction'], }; function getRouterConfig(langPrefix = '/') { diff --git a/docs/useCountDown/demo/index.vue b/docs/useCountDown/demo/index.vue new file mode 100644 index 0000000..5f149cd --- /dev/null +++ b/docs/useCountDown/demo/index.vue @@ -0,0 +1,19 @@ + + + diff --git a/docs/useCountDown/demo/index2.vue b/docs/useCountDown/demo/index2.vue new file mode 100644 index 0000000..0e4bf00 --- /dev/null +++ b/docs/useCountDown/demo/index2.vue @@ -0,0 +1,22 @@ + + + diff --git a/docs/useCountDown/demo/millisecond.vue b/docs/useCountDown/demo/millisecond.vue new file mode 100644 index 0000000..71e5a63 --- /dev/null +++ b/docs/useCountDown/demo/millisecond.vue @@ -0,0 +1,20 @@ + + + diff --git a/docs/useCountDown/index.md b/docs/useCountDown/index.md new file mode 100644 index 0000000..2de7976 --- /dev/null +++ b/docs/useCountDown/index.md @@ -0,0 +1,46 @@ +# useCountDown + +提供倒计时管理能力。 + +## 基本用法 + + + +## 毫秒级渲染 + + + +## 开始 / 暂停 + + + +## API + +### CurrentTime 格式 + +| 名称 | 说明 | 类型 | +| ------------ | ---------------------- | -------- | +| total | 剩余总时间(单位毫秒) | _number_ | +| days | 剩余天数 | _number_ | +| hours | 剩余小时 | _number_ | +| minutes | 剩余分钟 | _number_ | +| seconds | 剩余秒数 | _number_ | +| milliseconds | 剩余毫秒 | _number_ | + +### 参数 + +| 参数 | 说明 | 类型 | 默认值 | +| ----------- | -------------------------- | -------------------------------- | ------- | +| time | 倒计时时长,单位毫秒 | _number_ | - | +| millisecond | 是否开启毫秒级渲染 | _boolean_ | `false` | +| onChange | 倒计时改变时触发的回调函数 | _(current: CurrentTime) => void_ | - | +| onFinish | 倒计时结束时触发的回调函数 | _() => void_ | - | + +### 返回值 + +| 参数 | 说明 | 类型 | +| ------- | ---------------------------------- | ----------------------- | +| current | 当前剩余的时间 | _CurrentTime_ | +| start | 开始倒计时 | _() => void_ | +| pause | 暂停倒计时 | _() => void_ | +| reset | 重置倒计时,支持传入新的倒计时时长 | _(time?: number): void_ | diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts index 34b9e6f..023dc98 100644 --- a/packages/hooks/src/index.ts +++ b/packages/hooks/src/index.ts @@ -1,8 +1,10 @@ import useBoolean from './useBoolean'; import useToggle from './useToggle'; import useRect from "./useRect"; +import useCountDown from "./useCountDown"; export { useBoolean, useToggle, - useRect + useRect, + useCountDown }; \ No newline at end of file diff --git a/packages/hooks/src/router.js b/packages/hooks/src/router.js index 72a1cd6..c167939 100644 --- a/packages/hooks/src/router.js +++ b/packages/hooks/src/router.js @@ -1,5 +1,6 @@ // 路由映射表 const Router = { - Dom: ["useRect"] + Dom: ['useRect'], + Worker: ['useCountDown'], }; -export default Router; \ No newline at end of file +export default Router; diff --git a/packages/hooks/src/useCountDown/demo/index.vue b/packages/hooks/src/useCountDown/demo/index.vue new file mode 100644 index 0000000..5f149cd --- /dev/null +++ b/packages/hooks/src/useCountDown/demo/index.vue @@ -0,0 +1,19 @@ + + + diff --git a/packages/hooks/src/useCountDown/demo/index2.vue b/packages/hooks/src/useCountDown/demo/index2.vue new file mode 100644 index 0000000..0e4bf00 --- /dev/null +++ b/packages/hooks/src/useCountDown/demo/index2.vue @@ -0,0 +1,22 @@ + + + diff --git a/packages/hooks/src/useCountDown/demo/millisecond.vue b/packages/hooks/src/useCountDown/demo/millisecond.vue new file mode 100644 index 0000000..71e5a63 --- /dev/null +++ b/packages/hooks/src/useCountDown/demo/millisecond.vue @@ -0,0 +1,20 @@ + + + diff --git a/packages/hooks/src/useCountDown/index.md b/packages/hooks/src/useCountDown/index.md new file mode 100644 index 0000000..2de7976 --- /dev/null +++ b/packages/hooks/src/useCountDown/index.md @@ -0,0 +1,46 @@ +# useCountDown + +提供倒计时管理能力。 + +## 基本用法 + + + +## 毫秒级渲染 + + + +## 开始 / 暂停 + + + +## API + +### CurrentTime 格式 + +| 名称 | 说明 | 类型 | +| ------------ | ---------------------- | -------- | +| total | 剩余总时间(单位毫秒) | _number_ | +| days | 剩余天数 | _number_ | +| hours | 剩余小时 | _number_ | +| minutes | 剩余分钟 | _number_ | +| seconds | 剩余秒数 | _number_ | +| milliseconds | 剩余毫秒 | _number_ | + +### 参数 + +| 参数 | 说明 | 类型 | 默认值 | +| ----------- | -------------------------- | -------------------------------- | ------- | +| time | 倒计时时长,单位毫秒 | _number_ | - | +| millisecond | 是否开启毫秒级渲染 | _boolean_ | `false` | +| onChange | 倒计时改变时触发的回调函数 | _(current: CurrentTime) => void_ | - | +| onFinish | 倒计时结束时触发的回调函数 | _() => void_ | - | + +### 返回值 + +| 参数 | 说明 | 类型 | +| ------- | ---------------------------------- | ----------------------- | +| current | 当前剩余的时间 | _CurrentTime_ | +| start | 开始倒计时 | _() => void_ | +| pause | 暂停倒计时 | _() => void_ | +| reset | 重置倒计时,支持传入新的倒计时时长 | _(time?: number): void_ | diff --git a/packages/hooks/src/useCountDown/index.ts b/packages/hooks/src/useCountDown/index.ts new file mode 100644 index 0000000..3f20c38 --- /dev/null +++ b/packages/hooks/src/useCountDown/index.ts @@ -0,0 +1,175 @@ +import { computed, onActivated, onBeforeUnmount, onDeactivated, ref } from 'vue'; +import { cancelRaf, inBrowser, raf } from '../utils'; + +export type CurrentTime = { + days: number; + hours: number; + total: number; + minutes: number; + seconds: number; + milliseconds: number; +}; + +export type UseCountDownOptions = { + time: number; + millisecond?: boolean; + onChange?: (current: CurrentTime) => void; + onFinish?: () => void; +}; + +// 单位: 毫秒 +const SECOND = 1000; // 一秒 +const MINUTE = 60 * SECOND; // 一分钟 +const HOUR = 60 * MINUTE; // 一个小时 +const DAY = 24 * HOUR; // 一天 + +/** + * 将时间戳转换为更易读的当前时间对象。 + * @param time 输入的时间戳,单位为毫秒。 + * @returns 返回一个对象,包含总时间、天、小时、分钟、秒和毫秒。 + */ +function parseTime(time: number): CurrentTime { + // 计算总天数 + const days = Math.floor(time / DAY); + // 计算剩余时间的小时数 + const hours = Math.floor((time % DAY) / HOUR); + // 计算剩余时间的分钟数 + const minutes = Math.floor((time % HOUR) / MINUTE); + // 计算剩余时间的秒数 + const seconds = Math.floor((time % MINUTE) / SECOND); + // 计算剩余时间的毫秒数 + const milliseconds = Math.floor(time % SECOND); + + return { + total: time, + days, + hours, + minutes, + seconds, + milliseconds, + }; +} + +// 判断两个时间是否相等 +function isSameSecond(time1: number, time2: number): boolean { + return Math.floor(time1 / 1000) === Math.floor(time2 / 1000); +} + +function useCountDown(options: UseCountDownOptions) { + let rafId: number; // 帧动画id + let endTime: number; // 结束时间 + let counting: boolean; // 是否正在倒计时 + let deactivated: boolean; // 是否暂停 + + const remain = ref(options.time); + const current = computed(() => parseTime(remain.value)); + + // 暂停 + const pause = () => { + counting = false; + cancelRaf(rafId); + }; + + // 开始 + const start = () => { + if (!counting) { + endTime = Date.now() + remain.value; + counting = true; + tick(); + } + }; + + // 重置 + const reset = (totalTime: number = options.time) => { + pause(); + remain.value = totalTime; + }; + + // 获取剩余时间, 默认为毫秒 超过结束时间 返回 0 + const getCurrentRemain = () => Math.max(endTime - Date.now(), 0); + + // 设置剩余时间 + const setRemain = (value: number) => { + remain.value = value; + // 有 onChange 触发 + options.onChange?.(current.value); + + // 如果为0 暂停 并触发onFinish 告知结束了 + if (value === 0) { + pause(); + options.onFinish?.(); + } + }; + + const microTick = () => { + rafId = raf(() => { + if (counting) { + setRemain(getCurrentRemain()); + + if (remain.value > 0) { + microTick(); + } + } + }); + }; + + const macroTick = () => { + rafId = raf(() => { + if (counting) { + const remainRemain = getCurrentRemain(); + + // 检查当前剩余时间是否与上一次记录的不同,或者已经归零。 + if (!isSameSecond(remainRemain, remain.value) || remainRemain === 0) { + setRemain(remainRemain); + } + + // 如果剩余时间仍大于0,则继续调度 + if (remain.value > 0) { + macroTick(); + } + } + }); + }; + + const tick = () => { + // 检查是否在浏览器环境中, + if (!inBrowser) { + return; + } + + // 根据配置选项决定执行毫秒的启动 + if (options.millisecond) { + microTick(); + } else { + macroTick(); + } + }; + + onBeforeUnmount(pause); + + // 组件被激活时,重新开始倒计时 + onActivated(() => { + if (deactivated) { + counting = true; + deactivated = false; + tick(); + } + }); + + // 当组件被暂停或停止活动时调用的函数。 + onDeactivated(() => { + if (counting) { + pause(); + deactivated = true; + } + }); + + return { + start, + pause, + reset, + current, + }; +} + +export default useCountDown;