chore: init project
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div class="mb-7 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<h2
|
||||
class="text-foreground mb-3 text-3xl font-bold leading-9 tracking-tight lg:text-4xl"
|
||||
>
|
||||
<slot></slot>
|
||||
</h2>
|
||||
|
||||
<p class="text-muted-foreground lg:text-md text-sm">
|
||||
<slot name="desc"></slot>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
156
packages/business/common-ui/src/authentication/code-login.vue
Normal file
156
packages/business/common-ui/src/authentication/code-login.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<script setup lang="ts">
|
||||
import { VbenButton, VbenInput, VbenPinInput } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { computed, onBeforeUnmount, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Title from './auth-title.vue';
|
||||
|
||||
import type { LoginCodeEmits } from './typings';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* @zh_CN 是否处于加载处理状态
|
||||
*/
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AuthenticationCodeLogin',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
loading: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: LoginCodeEmits['submit'];
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const formState = reactive({
|
||||
code: '',
|
||||
phoneNumber: '',
|
||||
requirePhoneNumber: false,
|
||||
submitted: false,
|
||||
});
|
||||
|
||||
const countdown = ref(0);
|
||||
const timer = ref<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const isValidPhoneNumber = computed(() => {
|
||||
return /^1[3-9]\d{9}$/.test(formState.phoneNumber);
|
||||
});
|
||||
|
||||
const btnText = computed(() => {
|
||||
return countdown.value > 0
|
||||
? $t('authentication.send-text', [countdown.value])
|
||||
: $t('authentication.send-code');
|
||||
});
|
||||
const btnLoading = computed(() => {
|
||||
return countdown.value > 0;
|
||||
});
|
||||
|
||||
const phoneNumberStatus = computed(() => {
|
||||
return (formState.submitted || formState.requirePhoneNumber) &&
|
||||
!isValidPhoneNumber.value
|
||||
? 'error'
|
||||
: 'default';
|
||||
});
|
||||
|
||||
const codeStatus = computed(() => {
|
||||
return formState.submitted && !formState.code ? 'error' : 'default';
|
||||
});
|
||||
|
||||
function handleSubmit() {
|
||||
formState.submitted = true;
|
||||
if (phoneNumberStatus.value !== 'default' || codeStatus.value !== 'default') {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('submit', {
|
||||
code: formState.code,
|
||||
phoneNumber: formState.phoneNumber,
|
||||
});
|
||||
}
|
||||
|
||||
function handleGo(path: string) {
|
||||
router.push(path);
|
||||
}
|
||||
|
||||
async function handleSendCode() {
|
||||
if (btnLoading.value) {
|
||||
return;
|
||||
}
|
||||
if (!isValidPhoneNumber.value) {
|
||||
formState.requirePhoneNumber = true;
|
||||
return;
|
||||
}
|
||||
countdown.value = 60;
|
||||
// TODO: 调用发送验证码接口
|
||||
startCountdown();
|
||||
}
|
||||
|
||||
function startCountdown() {
|
||||
if (countdown.value > 0) {
|
||||
timer.value = setTimeout(() => {
|
||||
countdown.value--;
|
||||
startCountdown();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
countdown.value = 0;
|
||||
clearTimeout(timer.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Title>
|
||||
{{ $t('authentication.welcome-back') }} 📲
|
||||
<template #desc>
|
||||
<span class="text-muted-foreground">
|
||||
{{ $t('authentication.code-subtitle') }}
|
||||
</span>
|
||||
</template>
|
||||
</Title>
|
||||
<VbenInput
|
||||
v-model="formState.phoneNumber"
|
||||
:status="phoneNumberStatus"
|
||||
:error-tip="$t('authentication.mobile-tip')"
|
||||
:label="$t('authentication.mobile')"
|
||||
name="phoneNumber"
|
||||
type="number"
|
||||
:placeholder="$t('authentication.mobile')"
|
||||
:autofocus="true"
|
||||
@keyup.enter="handleSubmit"
|
||||
/>
|
||||
<VbenPinInput
|
||||
v-model="formState.code"
|
||||
:handle-send-code="handleSendCode"
|
||||
:status="codeStatus"
|
||||
:code-length="4"
|
||||
:error-tip="$t('authentication.code-tip')"
|
||||
:label="$t('authentication.code')"
|
||||
name="password"
|
||||
:placeholder="$t('authentication.code')"
|
||||
:btn-text="btnText"
|
||||
:btn-loading="btnLoading"
|
||||
@keyup.enter="handleSubmit"
|
||||
/>
|
||||
<VbenButton :loading="loading" class="mt-2 w-full" @click="handleSubmit">
|
||||
{{ $t('common.login') }}
|
||||
</VbenButton>
|
||||
<VbenButton
|
||||
class="mt-4 w-full"
|
||||
variant="outline"
|
||||
@click="handleGo('/auth/login')"
|
||||
>
|
||||
{{ $t('common.back') }}
|
||||
</VbenButton>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { IcRoundColorLens } from '@vben-core/iconify';
|
||||
import { VbenIconButton } from '@vben-core/shadcn-ui';
|
||||
|
||||
import {
|
||||
preference,
|
||||
staticPreference,
|
||||
updatePreference,
|
||||
} from '@vben/preference';
|
||||
|
||||
defineOptions({
|
||||
name: 'AuthenticationColorToggle',
|
||||
});
|
||||
|
||||
function handleUpdate(value: string) {
|
||||
updatePreference({
|
||||
colorPrimary: value,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="group relative flex items-center overflow-hidden">
|
||||
<div
|
||||
class="ease-ou flex w-0 overflow-hidden transition-all duration-500 group-hover:w-48"
|
||||
>
|
||||
<template
|
||||
v-for="color in staticPreference.colorPrimaryPresets"
|
||||
:key="color"
|
||||
>
|
||||
<VbenIconButton
|
||||
class="flex-center flex-shrink-0"
|
||||
@click="handleUpdate(color)"
|
||||
>
|
||||
<div
|
||||
class="relative h-3.5 w-3.5 rounded-[2px] before:absolute before:left-0.5 before:top-0.5 before:h-2.5 before:w-2.5 before:rounded-[2px] before:border before:border-gray-900 before:opacity-0 before:transition-all before:duration-150 before:content-[''] hover:scale-110"
|
||||
:class="[
|
||||
preference.colorPrimary === color ? `before:opacity-100` : '',
|
||||
]"
|
||||
:style="{ backgroundColor: color }"
|
||||
></div>
|
||||
</VbenIconButton>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<VbenIconButton>
|
||||
<IcRoundColorLens class="text-primary size-5" />
|
||||
</VbenIconButton>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import { VbenButton, VbenInput } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { computed, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Title from './auth-title.vue';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* @zh_CN 是否处于加载处理状态
|
||||
*/
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AuthenticationForgetPassword',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
loading: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: [string];
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
const formState = reactive({
|
||||
email: '',
|
||||
submitted: false,
|
||||
});
|
||||
|
||||
const emailStatus = computed(() => {
|
||||
return formState.submitted && !formState.email ? 'error' : 'default';
|
||||
});
|
||||
|
||||
function handleSubmut() {
|
||||
formState.submitted = true;
|
||||
if (emailStatus.value !== 'default') {
|
||||
return;
|
||||
}
|
||||
emit('submit', formState.email);
|
||||
}
|
||||
|
||||
function handleGo(path: string) {
|
||||
router.push(path);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Title>
|
||||
{{ $t('authentication.forget-password') }} 🤦🏻♂️
|
||||
<template #desc>
|
||||
{{ $t('authentication.forget-password-subtitle') }}
|
||||
</template>
|
||||
</Title>
|
||||
<div class="mb-6">
|
||||
<VbenInput
|
||||
v-model="formState.email"
|
||||
:status="emailStatus"
|
||||
:error-tip="$t('authentication.email-tip')"
|
||||
:label="$t('authentication.email')"
|
||||
name="email"
|
||||
autofocus
|
||||
placeholder="example@example.com"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<VbenButton class="mt-2 w-full" @click="handleSubmut">
|
||||
{{ $t('authentication.send-reset-link') }}
|
||||
</VbenButton>
|
||||
<VbenButton
|
||||
class="mt-4 w-full"
|
||||
variant="outline"
|
||||
@click="handleGo('/auth/login')"
|
||||
>
|
||||
{{ $t('common.back') }}
|
||||
</VbenButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
8
packages/business/common-ui/src/authentication/index.ts
Normal file
8
packages/business/common-ui/src/authentication/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export { default as AuthenticationCodeLogin } from './code-login.vue';
|
||||
export { default as AuthenticationColorToggle } from './color-toggle.vue';
|
||||
export { default as AuthenticationForgetPassword } from './forget-password.vue';
|
||||
export { default as AuthenticationLayoutToggle } from './layout-toggle.vue';
|
||||
export { default as AuthenticationLogin } from './login.vue';
|
||||
export { default as AuthenticationQrCodeLogin } from './qrcode-login.vue';
|
||||
export { default as AuthenticationRegister } from './register.vue';
|
||||
export type { LoginAndRegisterParams, LoginCodeParams } from './typings';
|
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import type { VbenDropdownMenuItem } from '@vben-core/shadcn-ui';
|
||||
import type { AuthPageLayout } from '@vben-core/typings';
|
||||
|
||||
import { MdiDockBottom, MdiDockLeft, MdiDockRight } from '@vben-core/iconify';
|
||||
import { VbenDropdownRadioMenu, VbenIconButton } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { preference, updatePreference, usePreference } from '@vben/preference';
|
||||
import { computed } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'AuthenticationLayoutToggle',
|
||||
// inheritAttrs: false,
|
||||
});
|
||||
|
||||
const menus = computed((): VbenDropdownMenuItem[] => [
|
||||
{
|
||||
icon: MdiDockLeft,
|
||||
key: 'panel-left',
|
||||
text: $t('layout.align-left'),
|
||||
},
|
||||
{
|
||||
icon: MdiDockBottom,
|
||||
key: 'panel-center',
|
||||
text: $t('layout.center'),
|
||||
},
|
||||
{
|
||||
icon: MdiDockRight,
|
||||
key: 'panel-right',
|
||||
text: $t('layout.align-right'),
|
||||
},
|
||||
]);
|
||||
|
||||
function handleUpdate(value: string) {
|
||||
updatePreference({
|
||||
authPageLayout: value as AuthPageLayout,
|
||||
});
|
||||
}
|
||||
|
||||
const { authPanelCenter, authPanelLeft, authPanelRight } = usePreference();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VbenDropdownRadioMenu
|
||||
:menus="menus"
|
||||
:model-value="preference.authPageLayout"
|
||||
@update:model-value="handleUpdate"
|
||||
>
|
||||
<VbenIconButton>
|
||||
<MdiDockRight v-if="authPanelRight" class="size-5" />
|
||||
<MdiDockLeft v-if="authPanelLeft" class="size-5" />
|
||||
<MdiDockBottom v-if="authPanelCenter" class="size-5" />
|
||||
</VbenIconButton>
|
||||
</VbenDropdownRadioMenu>
|
||||
</template>
|
175
packages/business/common-ui/src/authentication/login.vue
Normal file
175
packages/business/common-ui/src/authentication/login.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
VbenButton,
|
||||
VbenCheckbox,
|
||||
VbenInput,
|
||||
VbenInputPassword,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { computed, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Title from './auth-title.vue';
|
||||
import ThirdPartyLogin from './third-party-login.vue';
|
||||
|
||||
import type { LoginEmits } from './typings';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* @zh_CN 是否处于加载处理状态
|
||||
*/
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AuthenticationLogin',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
loading: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: LoginEmits['submit'];
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const REMEMBER_ME_KEY = 'REMEMBER_ME_USERNAME';
|
||||
|
||||
const localUsername = localStorage.getItem(REMEMBER_ME_KEY) || '';
|
||||
const formState = reactive({
|
||||
password: '',
|
||||
rememberMe: !!localUsername,
|
||||
submitted: false,
|
||||
username: localUsername,
|
||||
});
|
||||
|
||||
const usernameStatus = computed(() => {
|
||||
return formState.submitted && !formState.username ? 'error' : 'default';
|
||||
});
|
||||
|
||||
const passwordStatus = computed(() => {
|
||||
return formState.submitted && !formState.password ? 'error' : 'default';
|
||||
});
|
||||
|
||||
function handleSubmit() {
|
||||
formState.submitted = true;
|
||||
if (
|
||||
usernameStatus.value !== 'default' ||
|
||||
passwordStatus.value !== 'default'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(
|
||||
REMEMBER_ME_KEY,
|
||||
formState.rememberMe ? formState.username : '',
|
||||
);
|
||||
|
||||
emit('submit', {
|
||||
password: formState.password,
|
||||
username: formState.username,
|
||||
});
|
||||
}
|
||||
|
||||
function handleGo(path: string) {
|
||||
router.push(path);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Title>
|
||||
{{ $t('authentication.welcome-back') }} 👋🏻
|
||||
<template #desc>
|
||||
<span class="text-muted-foreground">
|
||||
{{ $t('authentication.login-subtitle') }}
|
||||
</span>
|
||||
</template>
|
||||
</Title>
|
||||
|
||||
<VbenInput
|
||||
v-model="formState.username"
|
||||
:status="usernameStatus"
|
||||
:error-tip="$t('authentication.username-tip')"
|
||||
:label="$t('authentication.username')"
|
||||
name="username"
|
||||
:placeholder="$t('authentication.username')"
|
||||
type="text"
|
||||
:autofocus="false"
|
||||
@keyup.enter="handleSubmit"
|
||||
/>
|
||||
<VbenInputPassword
|
||||
v-model="formState.password"
|
||||
:status="passwordStatus"
|
||||
:error-tip="$t('authentication.password-tip')"
|
||||
:label="$t('authentication.password')"
|
||||
name="password"
|
||||
:placeholder="$t('authentication.password')"
|
||||
required
|
||||
type="password"
|
||||
@keyup.enter="handleSubmit"
|
||||
/>
|
||||
|
||||
<div class="mb-6 mt-4 flex justify-between">
|
||||
<div class="flex-center flex">
|
||||
<VbenCheckbox v-model:checked="formState.rememberMe" name="rememberMe">
|
||||
{{ $t('authentication.remember-me') }}
|
||||
</VbenCheckbox>
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="text-primary hover:text-primary/80 cursor-pointer text-sm font-normal"
|
||||
@click="handleGo('/auth/forget-password')"
|
||||
>
|
||||
{{ $t('authentication.forget-password') }}
|
||||
</span>
|
||||
<!-- <VbenButton variant="ghost" @click="handleGo('/auth/forget-password')">
|
||||
忘记密码?
|
||||
</VbenButton> -->
|
||||
</div>
|
||||
<VbenButton :loading="loading" class="w-full" @click="handleSubmit">
|
||||
{{ $t('common.login') }}
|
||||
</VbenButton>
|
||||
|
||||
<div class="mb-2 mt-4 flex items-center justify-between">
|
||||
<VbenButton
|
||||
variant="outline"
|
||||
class="w-1/2"
|
||||
@click="handleGo('/auth/code-login')"
|
||||
>
|
||||
{{ $t('authentication.mobile-login') }}
|
||||
</VbenButton>
|
||||
<VbenButton
|
||||
variant="outline"
|
||||
class="ml-4 w-1/2"
|
||||
@click="handleGo('/auth/qrcode-login')"
|
||||
>
|
||||
{{ $t('authentication.qrcode-login') }}
|
||||
</VbenButton>
|
||||
<!-- <VbenButton
|
||||
:loading="loading"
|
||||
variant="outline"
|
||||
class="w-1/3"
|
||||
@click="handleGo('/auth/register')"
|
||||
>
|
||||
创建账号
|
||||
</VbenButton> -->
|
||||
</div>
|
||||
|
||||
<!-- 第三方登录 -->
|
||||
<ThirdPartyLogin />
|
||||
|
||||
<div class="text-center text-sm">
|
||||
{{ $t('authentication.account-tip') }}
|
||||
<span
|
||||
class="text-primary hover:text-primary/80 cursor-pointer text-sm font-normal"
|
||||
@click="handleGo('/auth/register')"
|
||||
>
|
||||
{{ $t('authentication.create-account') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import { VbenButton } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { useQRCode } from '@vueuse/integrations/useQRCode';
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Title from './auth-title.vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'AuthenticationQrCodeLogin',
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const text = ref('https://vben.vvbin.cn');
|
||||
|
||||
const qrcode = useQRCode(text, {
|
||||
errorCorrectionLevel: 'H',
|
||||
margin: 4,
|
||||
});
|
||||
|
||||
function handleGo(path: string) {
|
||||
router.push(path);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Title>
|
||||
{{ $t('authentication.welcome-back') }} 📱
|
||||
<template #desc>
|
||||
<span class="text-muted-foreground">
|
||||
{{ $t('authentication.qrcode-subtitle') }}
|
||||
</span>
|
||||
</template>
|
||||
</Title>
|
||||
|
||||
<div class="mt-6 flex flex-col items-center justify-center">
|
||||
<img :src="qrcode" alt="qrcode" class="w-1/2" />
|
||||
<p class="text-muted-foreground mt-4 text-sm">
|
||||
{{ $t('authentication.qrcode-prompt') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<VbenButton
|
||||
class="mt-4 w-full"
|
||||
variant="outline"
|
||||
@click="handleGo('/auth/login')"
|
||||
>
|
||||
{{ $t('common.back') }}
|
||||
</VbenButton>
|
||||
</div>
|
||||
</template>
|
163
packages/business/common-ui/src/authentication/register.vue
Normal file
163
packages/business/common-ui/src/authentication/register.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
VbenButton,
|
||||
VbenCheckbox,
|
||||
VbenInput,
|
||||
VbenInputPassword,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { computed, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Title from './auth-title.vue';
|
||||
|
||||
import type { RegisterEmits } from './typings';
|
||||
|
||||
interface Props {
|
||||
/**
|
||||
* @zh_CN 是否处于加载处理状态
|
||||
*/
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'RegisterForm',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
loading: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
submit: RegisterEmits['submit'];
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const formState = reactive({
|
||||
agreePolicy: false,
|
||||
comfirmPassword: '',
|
||||
password: '',
|
||||
submitted: false,
|
||||
username: '',
|
||||
});
|
||||
|
||||
const usernameStatus = computed(() => {
|
||||
return formState.submitted && !formState.username ? 'error' : 'default';
|
||||
});
|
||||
|
||||
const passwordStatus = computed(() => {
|
||||
return formState.submitted && !formState.password ? 'error' : 'default';
|
||||
});
|
||||
|
||||
const comfirmPasswordStatus = computed(() => {
|
||||
return formState.submitted && formState.password !== formState.comfirmPassword
|
||||
? 'error'
|
||||
: 'default';
|
||||
});
|
||||
|
||||
function handleSubmit() {
|
||||
formState.submitted = true;
|
||||
if (
|
||||
usernameStatus.value !== 'default' ||
|
||||
passwordStatus.value !== 'default'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit('submit', {
|
||||
password: formState.password,
|
||||
username: formState.username,
|
||||
});
|
||||
}
|
||||
|
||||
function handleGo(path: string) {
|
||||
router.push(path);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Title>
|
||||
{{ $t('authentication.create-an-account') }} 🚀
|
||||
<template #desc> {{ $t('authentication.sign-up-subtitle') }} </template>
|
||||
</Title>
|
||||
<VbenInput
|
||||
v-model="formState.username"
|
||||
:status="usernameStatus"
|
||||
:error-tip="$t('authentication.username-tip')"
|
||||
:label="$t('authentication.username')"
|
||||
name="username"
|
||||
:placeholder="$t('authentication.username')"
|
||||
type="text"
|
||||
/>
|
||||
<!-- Use 8 or more characters with a mix of letters, numbers & symbols. -->
|
||||
<VbenInputPassword
|
||||
v-model="formState.password"
|
||||
:status="passwordStatus"
|
||||
:error-tip="$t('authentication.password-tip')"
|
||||
:label="$t('authentication.password')"
|
||||
name="password"
|
||||
:placeholder="$t('authentication.password')"
|
||||
required
|
||||
type="password"
|
||||
:password-strength="true"
|
||||
>
|
||||
<template #strengthText>
|
||||
{{ $t('authentication.password-strength') }}
|
||||
</template>
|
||||
</VbenInputPassword>
|
||||
|
||||
<VbenInputPassword
|
||||
v-model="formState.comfirmPassword"
|
||||
:status="comfirmPasswordStatus"
|
||||
:error-tip="$t('authentication.comfirm-password-tip')"
|
||||
:label="$t('authentication.comfirm-password')"
|
||||
name="comfirmPassword"
|
||||
:placeholder="$t('authentication.comfirm-password')"
|
||||
required
|
||||
type="password"
|
||||
/>
|
||||
|
||||
<div class="relative mt-4 flex pb-6">
|
||||
<div class="flex-center">
|
||||
<VbenCheckbox
|
||||
v-model:checked="formState.agreePolicy"
|
||||
name="agreePolicy"
|
||||
>
|
||||
{{ $t('authentication.sign-up-agree') }}
|
||||
<span class="text-primary hover:text-primary/80">{{
|
||||
$t('authentication.sign-up-privacy-policy')
|
||||
}}</span>
|
||||
&
|
||||
<span class="text-primary hover:text-primary/80">
|
||||
{{ $t('authentication.sign-up-terms') }}
|
||||
</span>
|
||||
</VbenCheckbox>
|
||||
</div>
|
||||
<Transition name="slide-up">
|
||||
<p
|
||||
v-show="formState.submitted && !formState.agreePolicy"
|
||||
class="text-destructive absolute bottom-1 left-0 text-xs"
|
||||
>
|
||||
{{ $t('authentication.sign-up-agree-tip') }}
|
||||
</p>
|
||||
</Transition>
|
||||
</div>
|
||||
<div>
|
||||
<VbenButton :loading="loading" class="w-full" @click="handleSubmit">
|
||||
{{ $t('authentication.sign-up') }}
|
||||
</VbenButton>
|
||||
</div>
|
||||
<div class="mt-4 text-center text-sm">
|
||||
{{ $t('authentication.already-account') }}
|
||||
<span
|
||||
class="text-primary hover:text-primary/80 cursor-pointer text-sm font-normal"
|
||||
@click="handleGo('/auth/login')"
|
||||
>
|
||||
{{ $t('authentication.go-login') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { MdiGithub, MdiGoogle, MdiQqchat, MdiWechat } from '@vben-core/iconify';
|
||||
import { VbenIconButton } from '@vben-core/shadcn-ui';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({
|
||||
name: 'ThirdPartyLogin',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full sm:mx-auto md:max-w-md">
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<span class="border-input w-[35%] border-b dark:border-gray-600"></span>
|
||||
<span class="text-muted-foreground text-center text-xs uppercase">
|
||||
{{ $t('authentication.third-party-login') }}
|
||||
</span>
|
||||
<span class="border-input w-[35%] border-b dark:border-gray-600"></span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-wrap justify-center">
|
||||
<VbenIconButton class="mb-3">
|
||||
<MdiWechat />
|
||||
</VbenIconButton>
|
||||
<VbenIconButton class="mb-3">
|
||||
<MdiQqchat />
|
||||
</VbenIconButton>
|
||||
<VbenIconButton class="mb-3">
|
||||
<MdiGithub />
|
||||
</VbenIconButton>
|
||||
<VbenIconButton class="mb-3">
|
||||
<MdiGoogle />
|
||||
</VbenIconButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
29
packages/business/common-ui/src/authentication/typings.ts
Normal file
29
packages/business/common-ui/src/authentication/typings.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
interface LoginAndRegisterParams {
|
||||
password: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
interface LoginCodeParams {
|
||||
code: string;
|
||||
phoneNumber: string;
|
||||
}
|
||||
|
||||
interface LoginEmits {
|
||||
submit: [LoginAndRegisterParams];
|
||||
}
|
||||
|
||||
interface LoginCodeEmits {
|
||||
submit: [LoginCodeParams];
|
||||
}
|
||||
|
||||
interface RegisterEmits {
|
||||
submit: [LoginAndRegisterParams];
|
||||
}
|
||||
|
||||
export type {
|
||||
LoginAndRegisterParams,
|
||||
LoginCodeEmits,
|
||||
LoginCodeParams,
|
||||
LoginEmits,
|
||||
RegisterEmits,
|
||||
};
|
Reference in New Issue
Block a user