Skip to content

Commit 893d044

Browse files
committed
feat: new hook package: @utopia-utils/vueuse
1 parent 35a1752 commit 893d044

File tree

12 files changed

+226
-23
lines changed

12 files changed

+226
-23
lines changed

.vscode/settings.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"cSpell.words": [
3-
"deepmerge"
3+
"deepmerge",
4+
"typecheck"
45
]
5-
}
6+
}

example/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
},
1414
"dependencies": {
1515
"@utopia-utils/core": "workspace:*",
16+
"@utopia-utils/vueuse": "workspace:^",
1617
"vue": "^3.2.45",
1718
"vue-router": "^4.1.6"
1819
},

example/src/App.vue

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,26 @@
11
<script setup lang="ts">
2+
import { ref } from 'vue'
23
import { RouterLink, RouterView } from 'vue-router'
34
import HelloWorld from './components/HelloWorld.vue'
45
import { loadScript } from '@utopia-utils/core'
6+
import { useSmsCountdown } from '@utopia-utils/vueuse'
57
6-
let unload_
8+
let able = ref(false)
79
8-
const foo = () => {
9-
console.log('[9]-App.vue', unload_)
10-
unload_()
11-
}
12-
13-
const loadScript_ = () => {
14-
const { unload } = loadScript('https://unpkg.com/browse/axios@1.3.224/index.js', {
15-
appendPosition: 'body',
16-
onStatusChange: (status) => {
17-
console.log(status)
18-
},
19-
})
20-
unload_ = unload
21-
}
10+
const { counts, startCountdown, text, canSend, stopCountdown } = useSmsCountdown({
11+
sendAble: able,
12+
startText: 'send',
13+
durationText: 'x秒重取'
14+
})
2215
</script>
2316

2417
<template>
2518
<header>
26-
<button @click="loadScript_">load</button>
27-
<button @click="foo">unload</button>
28-
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
29-
19+
<button @click="able = !able">Toggle {{ able }}</button>
20+
<h1>{{ counts }}{{ canSend }}</h1>
21+
<button @click="stopCountdown">stop</button>
22+
<button @click="() => startCountdown()">{{ text }}</button>
3023
<div class="wrapper">
31-
<HelloWorld msg="You did it!" />
32-
3324
<nav>
3425
<RouterLink to="/">Home</RouterLink>
3526
</nav>

packages/vueuse/package.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"name": "@utopia-utils/vueuse",
3+
"type": "module",
4+
"version": "0.3.23",
5+
"description": "Collection of common vue hooks",
6+
"author": "Utopia <https://github.com/GreatAuk>",
7+
"license": "MIT",
8+
"homepage": "https://github.com/GreatAuk/utopia-utils#readme",
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/GreatAuk/utopia-utils.git",
12+
"directory": "packages/vueuse"
13+
},
14+
"bugs": {
15+
"url": "https://github.com/GreatAuk/utopia-utils/issues"
16+
},
17+
"keywords": [
18+
"utils",
19+
"vue hooks",
20+
"vueuse"
21+
],
22+
"sideEffects": false,
23+
"exports": {
24+
".": {
25+
"types": "./dist/index.d.ts",
26+
"require": "./dist/index.cjs",
27+
"import": "./dist/index.js"
28+
}
29+
},
30+
"main": "./dist/index.cjs",
31+
"module": "./dist/index.js",
32+
"types": "./dist/index.d.ts",
33+
"scripts": {
34+
"build": "tsup",
35+
"dev": "tsup --watch",
36+
"lint": "eslint .",
37+
"start": "tsx src/index.ts",
38+
"test": "vitest",
39+
"typecheck": "tsc --noEmit"
40+
},
41+
"peerDependencies": {
42+
"vue": "^3.2.47"
43+
},
44+
"dependencies": {},
45+
"devDependencies": {
46+
"vue": "^3.2.47"
47+
}
48+
}

packages/vueuse/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useSmsCountdown'

packages/vueuse/src/types.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { Ref } from 'vue'
2+
3+
/**
4+
* Maybe it's a ref, or a plain value
5+
*
6+
* ```ts
7+
* type MaybeRef<T> = T | Ref<T>
8+
* ```
9+
*/
10+
export type MaybeRef<T> = T | Ref<T>
11+
12+
/**
13+
* Maybe it's a ref, or a plain value, or a getter function
14+
*
15+
* ```ts
16+
* type MaybeRefOrGetter<T> = (() => T) | T | Ref<T> | ComputedRef<T>
17+
* ```
18+
*/
19+
export type MaybeRefOrGetter<T> = MaybeRef<T> | (() => T)
20+
21+
/**
22+
* Void function
23+
*/
24+
export type Fn = () => void
25+
26+
/**
27+
* Any function
28+
*/
29+
export type AnyFn = (...args: any[]) => any
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { computed, ref } from 'vue'
2+
3+
import type { MaybeRef } from '../types'
4+
import { toValue, tryOnScopeDispose } from '../utils'
5+
6+
export interface UseSmsCountdownOptions {
7+
/**
8+
* 倒计时总共的时间(s)
9+
* @default 60s
10+
*/
11+
totalSecond?: number
12+
/** 是否可发送
13+
* @default true
14+
*/
15+
sendAble?: MaybeRef<boolean>
16+
/**
17+
* 开始倒计时的文本
18+
* @default '获取验证码'
19+
*/
20+
startText?: string
21+
/**
22+
* 倒计时期间的提示语,必须带有字符 "%s",这里的 "%s",将会被倒计的秒数替代
23+
* @default '%s秒后重发'
24+
*/
25+
durationText?: string
26+
}
27+
28+
export function useSmsCountdown(options?: UseSmsCountdownOptions) {
29+
const { totalSecond = 60, sendAble = true, startText = '获取验证码', durationText = 'x秒后重发' } = options || {}
30+
31+
if (totalSecond <= 0 && totalSecond % 1 !== 0)
32+
throw new Error('倒计时的时间应该为一个正整数!')
33+
34+
const counts = ref(totalSecond)
35+
36+
/** 是否可以发送验证码,由外部转入的 sendAble 和当前的秒数共同确定 */
37+
const canSend = computed(() => {
38+
return toValue(sendAble) && counts.value === totalSecond
39+
})
40+
41+
const text = computed(() => {
42+
if (counts.value === totalSecond)
43+
return startText
44+
45+
return durationText.replace(/%s/i, counts.value.toString())
46+
})
47+
48+
let intervalId: ReturnType<typeof setInterval> | null = null
49+
50+
function startCountdown() {
51+
if (!canSend.value)
52+
return
53+
54+
counts.value--
55+
intervalId = setInterval(() => {
56+
counts.value--
57+
if (counts.value <= 0) {
58+
counts.value = totalSecond
59+
clearInterval(intervalId!)
60+
}
61+
}, 1000)
62+
}
63+
64+
/**
65+
* 停止计时
66+
*/
67+
function stopCountdown() {
68+
intervalId && clearInterval(intervalId)
69+
intervalId = null
70+
counts.value = totalSecond
71+
}
72+
tryOnScopeDispose(stopCountdown)
73+
74+
return {
75+
counts,
76+
canSend,
77+
text,
78+
startCountdown,
79+
stopCountdown,
80+
}
81+
}

packages/vueuse/src/utils/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './tryOnScopeDispose'
2+
export * from './toValue'
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { unref } from 'vue'
2+
import type { AnyFn, MaybeRefOrGetter } from '../../types'
3+
4+
/**
5+
* Get the value of value/ref/getter.
6+
*/
7+
export function toValue<T>(r: MaybeRefOrGetter<T>): T {
8+
return typeof r === 'function'
9+
? (r as AnyFn)()
10+
: unref(r)
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { getCurrentScope, onScopeDispose } from 'vue'
2+
import type { Fn } from '../../types'
3+
4+
/**
5+
* Call onScopeDispose() if it's inside an effect scope lifecycle, if not, do nothing
6+
*
7+
* @param fn
8+
*/
9+
export function tryOnScopeDispose(fn: Fn) {
10+
if (getCurrentScope()) {
11+
onScopeDispose(fn)
12+
return true
13+
}
14+
return false
15+
}

0 commit comments

Comments
 (0)