| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- <script setup lang="ts">
- import type { VbenFormSchema } from '@vben/common-ui';
- import { computed, reactive, ref, watch } from 'vue';
- import { useVbenForm, useVbenModal, VbenButton, z } from '@vben/common-ui';
- import { useAccessStore, useUserStore } from '@vben/stores';
- import { message } from 'antdv-next';
- import MD5 from 'crypto-js/md5';
- import {
- createAccountApi,
- getUserInfoApi,
- loginApi,
- sendSmsCodeApi,
- } from '#/api';
- import CaptchaInput from './CaptchaInput.vue';
- defineOptions({
- name: 'LoginComponent',
- });
- const loading = ref(false);
- const [Modal, modalApi] = useVbenModal();
- const isRegister = ref(false);
- const accessStore = useAccessStore();
- const userStore = useUserStore();
- const countdown = ref(0);
- const isSending = ref(false);
- const REMEMBER_ME_KEY = `REMEMBER_ME_USERNAME_${location.hostname}`;
- const localUsername = localStorage.getItem(REMEMBER_ME_KEY) || '';
- const rememberMe = ref(!!localUsername);
- const open = defineModel<boolean>('open', { default: false });
- const captchaRandomKey = ref('');
- const loginFormSchema = computed((): VbenFormSchema[] => {
- return [
- {
- component: 'VbenInput',
- componentProps: {
- placeholder: '',
- },
- fieldName: 'username',
- label: 'Login Name',
- rules: z.string().min(1, { message: '请输入用户名' }),
- defaultValue: localUsername,
- },
- {
- component: 'VbenInputPassword',
- componentProps: {
- placeholder: '',
- },
- fieldName: 'password',
- label: 'Password',
- rules: z.string().min(1, { message: '请输入密码' }),
- },
- {
- component: 'VbenInput',
- componentProps: {
- placeholder: '',
- },
- fieldName: 'captcha',
- label: 'Verification Code',
- rules: z.string().min(1, { message: '请输入验证码' }),
- formItemClass: 'captcha-form-item',
- },
- ];
- });
- const registerFormSchema = computed((): VbenFormSchema[] => {
- return [
- {
- component: 'VbenInput',
- componentProps: {
- placeholder: '',
- },
- fieldName: 'mobile',
- label: 'Mobile Number',
- rules: z
- .string()
- .min(1, { message: '请输入手机号' })
- .regex(/^1[3-9]\d{9}$/, { message: '请输入正确的手机号' }),
- },
- {
- component: 'VbenInput',
- componentProps: {
- placeholder: '',
- },
- fieldName: 'code',
- label: 'Verification Code',
- rules: z.string().min(1, { message: '请输入验证码' }),
- formItemClass: 'verification-form-item',
- },
- {
- component: 'VbenInput',
- componentProps: {
- placeholder: '',
- },
- fieldName: 'accountName',
- label: 'Account Name',
- rules: z.string().min(1, { message: '请输入账号名称' }),
- },
- {
- component: 'VbenInputPassword',
- componentProps: {
- placeholder: '',
- },
- fieldName: 'password',
- label: 'Password',
- rules: z.string().min(1, { message: '请输入密码' }),
- },
- {
- component: 'VbenCheckbox',
- componentProps: {
- placeholder: '',
- },
- fieldName: 'agreeTerms',
- label: '',
- rules: z
- .boolean()
- .refine((val) => val === true, { message: '请同意用户条款' }),
- formItemClass: 'agree-terms-form-item',
- },
- ];
- });
- const [LoginForm, loginFormApi] = useVbenForm(
- reactive({
- commonConfig: {
- hideLabel: false,
- hideRequiredMark: true,
- formItemClass: 'pb-[20px]',
- },
- layout: 'vertical',
- schema: loginFormSchema,
- showDefaultActions: false,
- wrapperClass: 'text-[12px]',
- }),
- );
- const [RegisterForm, registerFormApi] = useVbenForm(
- reactive({
- commonConfig: {
- hideLabel: false,
- hideRequiredMark: true,
- formItemClass: 'pb-[20px]',
- },
- layout: 'vertical',
- schema: registerFormSchema,
- showDefaultActions: false,
- wrapperClass: 'text-[12px]',
- }),
- );
- watch(
- () => open.value,
- (val) => {
- modalApi.setState({ isOpen: val });
- isRegister.value = false;
- },
- { immediate: true },
- );
- function handleClose() {
- open.value = false;
- }
- async function handleLoginSubmit() {
- const { valid } = await loginFormApi.validate();
- const values = await loginFormApi.getValues();
- if (valid) {
- localStorage.setItem(
- REMEMBER_ME_KEY,
- rememberMe.value ? values?.username : '',
- );
- try {
- loading.value = true;
- const encryptedPassword = MD5(values?.password || '').toString();
- const loginRes = await loginApi({
- account: values?.username,
- pwd: encryptedPassword,
- check_code: values?.captcha,
- check_key: captchaRandomKey.value,
- });
- if (loginRes && loginRes.isSuccess) {
- if (loginRes.token) {
- accessStore.setAccessToken(loginRes.token);
- }
- open.value = false;
- message.success('登录成功');
- const userInfoRes = await getUserInfoApi();
- if (userInfoRes && userInfoRes.isSuccess) {
- const userInfo = {
- account:
- userInfoRes.result?.account ||
- userInfoRes.result?.englishName ||
- '',
- avatar: userInfoRes.result?.avatarFileId || '',
- cellPhone: userInfoRes.result?.cellPhone || '',
- realName:
- userInfoRes.result?.chineseName || userInfoRes.result?.name || '',
- email: userInfoRes.result?.emailAddress || '',
- roles: [],
- userId: userInfoRes.result?.id || '',
- username: userInfoRes.result?.name || '',
- };
- userStore.setUserInfo(userInfo);
- }
- } else {
- message.error(loginRes?.error || '登录失败');
- }
- } finally {
- loading.value = false;
- }
- }
- }
- async function handleSendCode() {
- const values = await registerFormApi.getValues();
- const mobile = values?.mobile;
- if (!mobile) {
- message.error('请先输入手机号');
- return;
- }
- const mobileRegex = /^1[3-9]\d{9}$/;
- if (!mobileRegex.test(mobile)) {
- message.error('请输入正确的手机号');
- return;
- }
- try {
- isSending.value = true;
- await sendSmsCodeApi({
- sms_mobile: mobile,
- sms_scene: 'sms_reg',
- });
- countdown.value = 60;
- const timer = setInterval(() => {
- countdown.value--;
- if (countdown.value <= 0) {
- clearInterval(timer);
- isSending.value = false;
- }
- }, 1000);
- message.success('验证码已发送');
- } catch {
- isSending.value = false;
- message.error('发送验证码失败');
- }
- }
- async function handleRegisterSubmit() {
- const { valid } = await registerFormApi.validate();
- const values = await registerFormApi.getValues();
- if (valid) {
- try {
- loading.value = true;
- const encryptedPassword = MD5(values?.password || '').toString();
- const registerRes = await createAccountApi({
- sms_scene: 'sms_reg',
- sms_mobile: values?.mobile,
- sms_check_code: values?.code,
- user: {
- langNameList: [
- {
- name: 'zh-CN',
- value: values?.accountName || '',
- },
- {
- name: 'en',
- value: '',
- },
- ],
- password: encryptedPassword,
- },
- });
- if (registerRes && registerRes.isSuccess) {
- message.success('注册成功,请登录后使用');
- isRegister.value = false;
- } else {
- message.error(registerRes?.error || '注册失败');
- }
- } finally {
- loading.value = false;
- }
- }
- }
- function handleGoToRegister() {
- isRegister.value = true;
- }
- function handleGoToLogin() {
- isRegister.value = false;
- }
- defineExpose({
- getFormApi: () => loginFormApi,
- });
- </script>
- <template>
- <div>
- <Modal
- id="loginModal"
- :bordered="false"
- :closable="false"
- :close-on-click-modal="false"
- :close-on-press-escape="false"
- :footer="false"
- :fullscreen-button="false"
- :header="false"
- class="relative sm:w-[940px]"
- content-class="p-0"
- @update:open="handleClose"
- >
- <div class="relative flex">
- <div class="hidden w-[470px] sm:flex">
- <img
- alt="login"
- class="w-[470px] object-contain"
- src="@/assets/image/login-banner.png"
- />
- </div>
- <div class="w-full px-[86px] py-[30px] sm:w-1/2">
- <div v-if="!isRegister" class="mb-6">
- <h2 class="text-[21px] font-bold">Login</h2>
- <p class="text-muted-foreground mt-3 text-sm">
- New user?
- <span
- class="cursor-pointer text-[#8B0046]"
- @click="handleGoToRegister"
- >
- Create an account
- </span>
- </p>
- </div>
- <div v-if="isRegister" class="mb-6">
- <h2 class="text-[21px] font-bold">Create an account</h2>
- <p class="text-muted-foreground mt-3 text-sm">
- Already have an account?
- <span
- class="cursor-pointer text-[#8B0046]"
- @click="handleGoToLogin"
- >
- Login
- </span>
- </p>
- </div>
- <div class="my-[20px] h-[1px] w-auto bg-[#E5E5E5]"></div>
- <LoginForm v-if="!isRegister">
- <template #captcha="{ field }">
- <CaptchaInput
- :model-value="field.value"
- @update:model-value="
- (val) => {
- loginFormApi.setFieldValue('captcha', val);
- }
- "
- @update:random-key="
- (val) => {
- captchaRandomKey = val;
- }
- "
- />
- </template>
- </LoginForm>
- <RegisterForm v-if="isRegister">
- <template #code="{ field }">
- <div class="flex w-full items-center justify-around gap-2">
- <input
- v-model="field.value"
- class="h-[38px] flex-1 rounded-md border border-gray-300 px-3 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-[#8B0046]"
- placeholder=""
- type="text"
- @input="
- () => {
- registerFormApi.setFieldValue('code', field.value);
- }
- "
- />
- <VbenButton
- :disabled="countdown > 0"
- :loading="isSending"
- class="h-[38px] whitespace-nowrap rounded-md bg-gradient-to-b from-[#8B0046] to-[#460023] px-4 text-white"
- @click="handleSendCode"
- >
- {{ countdown > 0 ? `${countdown}s` : 'Send Code' }}
- </VbenButton>
- </div>
- </template>
- <template #agreeTerms="{ field }">
- <label class="flex cursor-pointer items-center gap-2">
- <input
- v-model="field.value"
- class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-600"
- type="checkbox"
- @change="
- () => {
- registerFormApi.setFieldValue('agreeTerms', field.value);
- }
- "
- />
- <span class="text-sm">I agree with user's terms</span>
- </label>
- </template>
- </RegisterForm>
- <VbenButton
- v-if="!isRegister"
- :class="{ 'cursor-wait': loading }"
- :loading="loading"
- aria-label="login"
- class="m-auto mt-[20px] flex h-[50px] w-full cursor-pointer items-center justify-center rounded-[25px] bg-gradient-to-b from-[#8B0046] to-[#460023] text-[16px] text-white"
- @click="handleLoginSubmit"
- >
- Login
- </VbenButton>
- <VbenButton
- v-if="isRegister"
- :class="{ 'cursor-wait': loading }"
- :loading="loading"
- aria-label="register"
- class="m-auto flex h-[50px] w-full cursor-pointer items-center justify-center rounded-[25px] bg-gradient-to-b from-[#8B0046] to-[#460023] text-[16px] text-white"
- @click="handleRegisterSubmit"
- >
- Create an account
- </VbenButton>
- </div>
- <div class="absolute right-[20px] top-[24px] h-[44px] w-[59.12px]">
- <img alt="" class="" src="@/assets/image/system-logo.png" />
- </div>
- </div>
- <div
- class="fixed right-[-80px] top-[0] flex h-[34px] w-[34px] cursor-pointer items-center justify-center rounded-[50%] bg-[#fff] text-[#000] transition-opacity hover:opacity-80"
- @click="handleClose"
- >
- X
- </div>
- </Modal>
- </div>
- </template>
|