Skip to content

Commit

Permalink
feat(projects): login page: code-login
Browse files Browse the repository at this point in the history
  • Loading branch information
honghuangdc committed Apr 13, 2024
1 parent 81f08f6 commit f4e7c92
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 10 deletions.
3 changes: 2 additions & 1 deletion packages/hooks/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import useBoolean from './use-boolean';
import useLoading from './use-loading';
import useCountDown from './use-count-down';
import useContext from './use-context';
import useSvgIconRender from './use-svg-icon-render';

export { useBoolean, useLoading, useContext, useSvgIconRender };
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender };
49 changes: 49 additions & 0 deletions packages/hooks/src/use-count-down.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { computed, onScopeDispose, ref } from 'vue';
import { useRafFn } from '@vueuse/core';

/**
* count down
*
* @param seconds - count down seconds
*/
export default function useCountDown(seconds: number) {
const FPS_PER_SECOND = 60;

const fps = ref(0);

const count = computed(() => Math.ceil(fps.value / FPS_PER_SECOND));

const isCounting = computed(() => fps.value > 0);

const { pause, resume } = useRafFn(
() => {
if (fps.value > 0) {
fps.value -= 1;
} else {
pause();
}
},
{ immediate: false }
);

function start(updateSeconds: number = seconds) {
fps.value = FPS_PER_SECOND * updateSeconds;
resume();
}

function stop() {
fps.value = 0;
pause();
}

onScopeDispose(() => {
pause();
});

return {
count,
isCounting,
start,
stop
};
}
71 changes: 71 additions & 0 deletions src/hooks/business/captcha.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { computed } from 'vue';
import { useCountDown, useLoading } from '@sa/hooks';
import { $t } from '@/locales';
import { REG_PHONE } from '@/constants/reg';

export function useCaptcha() {
const { loading, startLoading, endLoading } = useLoading();
const { count, start, stop, isCounting } = useCountDown(10);

const label = computed(() => {
let text = $t('page.login.codeLogin.getCode');

const countingLabel = $t('page.login.codeLogin.reGetCode', { time: count.value });

if (loading.value) {
text = '';
}

if (isCounting.value) {
text = countingLabel;
}

return text;
});

function isPhoneValid(phone: string) {
if (phone.trim() === '') {
window.$message?.error?.($t('form.phone.required'));

return false;
}

if (!REG_PHONE.test(phone)) {
window.$message?.error?.($t('form.phone.invalid'));

return false;
}

return true;
}

async function getCaptcha(phone: string) {
const valid = isPhoneValid(phone);

if (!valid || loading.value) {
return;
}

startLoading();

// request
await new Promise(resolve => {
setTimeout(resolve, 500);
});

window.$message?.success?.($t('page.login.codeLogin.sendCodeSuccess'));

start();

endLoading();
}

return {
label,
start,
stop,
isCounting,
loading,
getCaptcha
};
}
2 changes: 2 additions & 0 deletions src/locales/langs/en-us.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ const local: App.I18n.Schema = {
codeLogin: {
title: 'Verification Code Login',
getCode: 'Get verification code',
reGetCode: 'Reacquire after {time}s',
sendCodeSuccess: 'Verification code sent successfully',
imageCodePlaceholder: 'Please enter image verification code'
},
register: {
Expand Down
2 changes: 2 additions & 0 deletions src/locales/langs/zh-cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ const local: App.I18n.Schema = {
codeLogin: {
title: '验证码登录',
getCode: '获取验证码',
reGetCode: '{time}秒后重新获取',
sendCodeSuccess: '验证码发送成功',
imageCodePlaceholder: '请输入图片验证码'
},
register: {
Expand Down
2 changes: 2 additions & 0 deletions src/typings/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,8 @@ declare namespace App {
codeLogin: {
title: string;
getCode: string;
reGetCode: string;
sendCodeSuccess: string;
imageCodePlaceholder: string;
};
register: {
Expand Down
47 changes: 41 additions & 6 deletions src/views/_builtin/login/modules/code-login.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,59 @@
<script setup lang="ts">
import { computed, reactive } from 'vue';
import { $t } from '@/locales';
import { useRouterPush } from '@/hooks/common/router';
import { useAntdForm, useFormRules } from '@/hooks/common/form';
import { useCaptcha } from '@/hooks/business/captcha';
defineOptions({
name: 'CodeLogin'
});
const { toggleLoginModule } = useRouterPush();
const { formRef, validate } = useAntdForm();
const { label, isCounting, loading, getCaptcha } = useCaptcha();
interface FormModel {
phone: string;
code: string;
}
const model: FormModel = reactive({
phone: '',
code: ''
});
const rules = computed<Record<keyof FormModel, App.Global.FormRule[]>>(() => {
const { formRules } = useFormRules();
return {
phone: formRules.phone,
code: formRules.code
};
});
async function handleSubmit() {
await validate();
// request
window.$message?.success($t('page.login.common.validateSuccess'));
}
</script>

<template>
<AForm>
<AFormItem>
<AInput size="large" :placeholder="$t('page.login.common.phonePlaceholder')" />
<AForm ref="formRef" :model="model" :rules="rules">
<AFormItem name="phone">
<AInput v-model:value="model.phone" size="large" :placeholder="$t('page.login.common.phonePlaceholder')" />
</AFormItem>
<AFormItem>
<AInput size="large" :placeholder="$t('page.login.common.codePlaceholder')" />
<AFormItem name="code">
<div class="w-full flex-y-center gap-16px">
<AInput v-model:value="model.code" size="large" :placeholder="$t('page.login.common.codePlaceholder')" />
<AButton size="large" :disabled="isCounting" :loading="loading" @click="getCaptcha(model.phone)">
{{ label }}
</AButton>
</div>
</AFormItem>
<ASpace direction="vertical" size="large" class="w-full">
<AButton type="primary" block size="large" shape="round">
<AButton type="primary" block size="large" shape="round" @click="handleSubmit">
{{ $t('common.confirm') }}
</AButton>
<AButton block size="large" shape="round" @click="toggleLoginModule('pwd-login')">
Expand Down
12 changes: 9 additions & 3 deletions src/views/_builtin/login/modules/reset-pwd.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,29 @@ const rules = computed<RuleRecord>(() => {
async function handleSubmit() {
await validate();
// request to reset password
window.$message?.success($t('page.login.common.validateSuccess'));
}
</script>

<template>
<AForm ref="formRef" :model="model" :rules="rules">
<AFormItem name="phone">
<AInput v-model:value="model.phone" :placeholder="$t('page.login.common.phonePlaceholder')" />
<AInput v-model:value="model.phone" size="large" :placeholder="$t('page.login.common.phonePlaceholder')" />
</AFormItem>
<AFormItem name="code">
<AInput v-model:value="model.code" :placeholder="$t('page.login.common.codePlaceholder')" />
<AInput v-model:value="model.code" size="large" :placeholder="$t('page.login.common.codePlaceholder')" />
</AFormItem>
<AFormItem name="password">
<AInputPassword v-model:value="model.password" :placeholder="$t('page.login.common.passwordPlaceholder')" />
<AInputPassword
v-model:value="model.password"
size="large"
:placeholder="$t('page.login.common.passwordPlaceholder')"
/>
</AFormItem>
<AFormItem name="confirmPassword">
<AInputPassword
v-model:value="model.confirmPassword"
size="large"
:placeholder="$t('page.login.common.confirmPasswordPlaceholder')"
/>
</AFormItem>
Expand Down

0 comments on commit f4e7c92

Please sign in to comment.