Files
lingji-work-fe/src/views/login/components/login-form/index.vue
2025-09-08 16:23:35 +08:00

338 lines
9.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- eslint-disable vue/no-duplicate-attributes -->
<template>
<div class="flex items-center w-400 h-100%">
<div class="w-full bg-#fff rounded-16px px-40px py-32px flex flex-col items-center">
<img src="@/assets/img/icon-logo.png" alt="" width="144" height="36" class="mb-24px" />
<Tabs v-model:activeKey="activeKey" class="mb-24px">
<TabPane tab="密码登录" key="1" />
<TabPane tab="短信登录" key="2" />
</Tabs>
<Form ref="formRef" :model="loginForm" :rules="formRules" class="w-320 form-wrap">
<FormItem name="mobile">
<Input v-model:value="loginForm.mobile" placeholder="请输入手机号" allowClear :maxlength="11" />
</FormItem>
<FormItem v-if="isCaptchaLogin" name="captcha" class="captcha-form-item">
<Input v-model:value="loginForm.captcha" placeholder="请输入验证码" :maxlength="6">
<template #suffix>
<div class="w-79px flex justify-center whitespace-nowrap">
<span
class="color-#939499 font-family-regular text-16px font-400 lh-24px cursor-not-allowed"
:class="{
'!color-#6D4CFE': isLegalMobile || countdown > 0,
'!cursor-pointer': canGetCaptcha,
}"
@click="getCode"
>{{ countdown > 0 ? `${countdown}s` : hasGetCode ? '重新发送' : '发送验证码' }}</span
>
</div>
</template>
</Input>
</FormItem>
<FormItem v-else name="password" class="password-form-item">
<Input.Password v-model:value="loginForm.password" placeholder="请输入密码">
<template #iconRender="visible">
<img :src="visible ? icon2 : icon1" width="20" height="20" class="cursor-pointer" />
</template>
</Input.Password>
</FormItem>
<FormItem class="mt-52px">
<div class="text-12px flex justify-center items-center mb-16px">
<Checkbox v-model:checked="hasCheck" class="mr-8px"></Checkbox>
<span class="text-12px color-#737478 font-400 lh-20px font-family-regular"
>登录即代表同意<span class="color-#6D4CFE"> 用户协议 </span><span class="color-#6D4CFE">
隐私政策</span
></span
>
</div>
<Button
type="primary"
class="w-full h-48 mb-8px !text-16px !font-500 !rounded-8px btn-login"
:class="disabledSubmitBtn ? 'cursor-no-drop' : 'cursor-pointer'"
:disabled="disabledSubmitBtn"
@click="handleSubmit"
>
登录
</Button>
<div class="flex justify-between btn-row">
<Button
type="text"
class="!color-#939499 !p-0 !h-22px hover:color-#6D4CFE"
size="small"
@click="onForgetPassword"
>
忘记密码
</Button>
<Button
type="text"
class="!color-#939499 !p-0 !h-22px hover:color-#6D4CFE"
size="small"
@click="onRegister"
>
注册
</Button>
</div>
</FormItem>
</Form>
</div>
</div>
<PuzzleVerification
:show="isVerificationVisible"
@submit="handleVerificationSubmit"
@cancel="isVerificationVisible = false"
/>
<SelectAccountModal ref="selectAccountModalRef" :mobileNumber="mobileNumber" :accounts="accounts" />
</template>
<script setup lang="ts">
import {
Checkbox,
Modal,
Button,
Form,
FormItem,
Input,
Space,
message,
Typography,
Card,
List,
Tabs,
} from 'ant-design-vue';
const { Link } = Typography;
const { TabPane } = Tabs;
import PuzzleVerification from '../PuzzleVerification.vue';
import SelectAccountModal from '../select-account-modal/index.vue';
import { fetchLoginCaptCha, fetchAuthorizationsCaptcha, fetchProfileInfo, postLoginPassword } from '@/api/all/login';
import { joinEnterpriseByInviteCode } from '@/api/all';
import { ref, reactive, onUnmounted, computed } from 'vue';
import { useUserStore } from '@/stores';
import { useEnterpriseStore } from '@/stores/modules/enterprise';
import { handleUserLogin, goUserLogin } from '@/utils/user';
import router from '@/router';
import { useRoute } from 'vue-router';
import icon1 from '@/assets/img/login/icon-close.png';
import icon2 from '@/assets/img/login/icon-open.png';
const setPageType = inject('setPageType');
const formRef = ref();
const route = useRoute();
const userStore = useUserStore();
const enterpriseStore = useEnterpriseStore();
const countdown = ref(0);
let timer = ref();
const isLogin = ref(true);
const isVerificationVisible = ref(false);
const hasGetCode = ref(false);
const submitting = ref(false);
const hasCheck = ref(false);
const mobileNumber = ref('');
const selectAccountModalRef = ref(null);
const accounts = ref([]);
const activeKey = ref('1');
const isLegalMobile = ref(false);
const loginForm = reactive({
mobile: '',
captcha: '',
password: '',
});
// 表单校验规则
const formRules = {
mobile: [
{
required: true,
validator: (_rule: any, value: string) => {
if (!value) {
isLegalMobile.value = false;
return Promise.reject('手机号不能为空');
}
if (!/^1[3-9]\d{9}$/.test(value)) {
isLegalMobile.value = false;
return Promise.reject('手机号格式不正确');
} else {
isLegalMobile.value = true;
return Promise.resolve();
}
},
trigger: ['blur'],
},
],
password: [
{
required: true,
validator: (_rule: any, value: string) => {
if (!value) {
return Promise.reject('请输入密码');
}
if (value.length < 6) {
return Promise.reject('密码长度不能小于6位');
}
return Promise.resolve();
},
trigger: ['blur'],
},
],
captcha: [
{
required: true,
validator: (_rule: any, value: string) => {
if (!value) {
return Promise.reject('请输入验证码');
}
if (!/^\d{6}$/.test(value)) {
return Promise.reject('验证码必须是6位数字');
} else {
return Promise.resolve();
}
},
trigger: ['blur'],
},
],
};
const isCaptchaLogin = computed(() => {
return activeKey.value === '2';
});
const canGetCaptcha = computed(() => {
return isLegalMobile.value && countdown.value === 0;
});
const disabledSubmitBtn = computed(() => {
if (isCaptchaLogin.value) {
return !hasCheck.value || !isLegalMobile.value || !loginForm.captcha.trim() || !/^\d{6}$/.test(loginForm.captcha);
}
// 密码登录时的验证逻辑
return !hasCheck.value || !isLegalMobile.value || !loginForm.password.trim();
});
const validateField = (field: string) => {
formRef.value.validateFields(field);
};
const clearError = (field: string) => {
formRef.value.clearValidate(field);
};
const getCode = async () => {
if (!canGetCaptcha.value) return;
formRef.value.validateFields('mobile').then(() => {
isVerificationVisible.value = true;
});
};
// 验证码验证通过后
const handleVerificationSubmit = async () => {
isVerificationVisible.value = false;
startCountdown();
try {
const { code, message: msg } = await fetchLoginCaptCha({ mobile: loginForm.mobile });
if (code === 200) {
message.success(msg);
}
} catch (error) {
// 重置倒计时
countdown.value = 0;
clearInterval(timer.value);
}
};
// 获取用户信息
const getProfileInfo = async () => {
const { code, data } = await fetchProfileInfo();
if (code === 200) {
let enterprises = data['enterprises'];
mobileNumber.value = data['mobile'];
accounts.value = enterprises;
if (enterprises.length > 0) {
enterpriseStore.setEnterpriseInfo(data.enterprises[0]);
if (enterprises.length === 1) {
handleUserLogin();
} else {
// 多个企业时候需要弹窗让用户选择企业
selectAccountModalRef.value.open();
}
}
}
};
// 提交表单
const handleSubmit = async () => {
console.log('handleSubmit', disabledSubmitBtn.value);
if (disabledSubmitBtn.value) return;
try {
// 校验所有字段
await formRef.value.validate();
if (!hasCheck.value) {
message.error('请先勾选同意用户协议');
return;
}
submitting.value = true;
const _fn = isCaptchaLogin.value ? fetchAuthorizationsCaptcha : postLoginPassword;
const { code, data } = await _fn(loginForm);
if (code === 200) {
// 处理登录成功逻辑
message.success('登录成功');
userStore.setToken(data.access_token);
const { invite_code } = route.query;
if (invite_code) {
const { code } = await joinEnterpriseByInviteCode(invite_code as string);
if (code === 200) {
message.success('加入企业成功');
}
}
getProfileInfo();
}
} catch (error) {
// 错误信息会显示在输入框下方
} finally {
submitting.value = false;
}
};
// 开始倒计时
const startCountdown = () => {
countdown.value = 60;
hasGetCode.value = true;
timer.value = setInterval(() => {
countdown.value--;
if (countdown.value <= 0) {
clearInterval(timer.value as number);
timer.value = null;
}
}, 1000);
};
const onForgetPassword = () => {
setPageType('resetPasswordForm');
};
const onRegister = () => {
console.log('onRegister');
setPageType('registerForm');
};
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value);
}
});
</script>
<style scoped lang="scss">
@import './style.scss';
</style>