|
@@ -1,69 +1,290 @@
|
|
|
<template>
|
|
|
- <el-form size="large" class="login-content-form">
|
|
|
- <el-form-item class="login-animation1">
|
|
|
- <el-input type="text" placeholder="请输入手机号" v-model="state.ruleForm.userName" clearable autocomplete="off">
|
|
|
+ <el-form size="large" class="login-content-form" ref="ruleFormRef" :model="state.ruleForm" @submit.native.prevent>
|
|
|
+ <el-form-item
|
|
|
+ prop="userName"
|
|
|
+ class="mb30"
|
|
|
+ :rules="[
|
|
|
+ { required: true, message: '请输入手机号', trigger: 'blur' },
|
|
|
+ { validator: checkPhone, trigger: 'blur' },
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ <el-input placeholder="请输入手机号" v-model="state.ruleForm.userName" clearable autocomplete="off" class="inputDeep">
|
|
|
<template #prefix>
|
|
|
- <i class="iconfont icon-dianhua el-input__icon"></i>
|
|
|
+ <SvgIcon name="ele-Phone" class="el-input__icon" />
|
|
|
</template>
|
|
|
</el-input>
|
|
|
</el-form-item>
|
|
|
- <el-form-item class="login-animation2">
|
|
|
+ <el-form-item prop="password" class="mb30" :rules="[{ required: true, message: '请输入短信验证码', trigger: 'blur' }]">
|
|
|
<el-col :span="15">
|
|
|
- <el-input type="text" maxlength="4" placeholder="请输入验证码" v-model="state.ruleForm.code" clearable autocomplete="off">
|
|
|
+ <el-input
|
|
|
+ type="text"
|
|
|
+ maxlength="4"
|
|
|
+ placeholder="请输入短信验证码"
|
|
|
+ v-model="state.ruleForm.code"
|
|
|
+ clearable
|
|
|
+ autocomplete="off"
|
|
|
+ class="inputDeep"
|
|
|
+ >
|
|
|
<template #prefix>
|
|
|
- <el-icon class="el-input__icon"><ele-Position /></el-icon>
|
|
|
+ <SvgIcon name="ele-ChatDotSquare" class="el-input__icon" />
|
|
|
</template>
|
|
|
</el-input>
|
|
|
</el-col>
|
|
|
<el-col :span="1"></el-col>
|
|
|
<el-col :span="8">
|
|
|
- <el-button class="login-content-code">获取验证码</el-button>
|
|
|
+ <el-button class="login-content-code" :disabled="isDisabled" @click="getIdentifyCodeBtn">{{
|
|
|
+ isDisabled ? count + 's后重新获取' : click
|
|
|
+ }}</el-button>
|
|
|
</el-col>
|
|
|
</el-form-item>
|
|
|
- <el-form-item class="login-animation3">
|
|
|
- <el-button round type="primary" class="login-content-submit">
|
|
|
- <span>登 录</span>
|
|
|
- </el-button>
|
|
|
+ <el-form-item
|
|
|
+ prop="verifyCode"
|
|
|
+ :rules="[
|
|
|
+ { required: true, message: '请输入验证码', trigger: 'blur' },
|
|
|
+ {
|
|
|
+ validator: validatePass,
|
|
|
+ trigger: 'blur',
|
|
|
+ },
|
|
|
+ ]"
|
|
|
+ class="mb30"
|
|
|
+ >
|
|
|
+ <el-col :span="15">
|
|
|
+ <el-input
|
|
|
+ type="text"
|
|
|
+ maxlength="4"
|
|
|
+ class="inputDeep"
|
|
|
+ placeholder="验证码不区分大小写"
|
|
|
+ v-model="state.ruleForm.verifyCode"
|
|
|
+ @keyup.enter="onSignIn(ruleFormRef)"
|
|
|
+ clearable
|
|
|
+ autocomplete="off"
|
|
|
+ >
|
|
|
+ <template #prefix>
|
|
|
+ <SvgIcon name="iconfont icon-quanxian" class="el-input__icon" />
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8" :offset="1" class="flex">
|
|
|
+ <ReImageVerify v-model:code="verifyCode" />
|
|
|
+ </el-col>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" class="login-content-submit" round @click="onSignIn(ruleFormRef)" :loading="state.loading">登录</el-button>
|
|
|
</el-form-item>
|
|
|
- <div class="font12 mt30 login-animation4 login-msg">
|
|
|
- * 温馨提示:建议使用谷歌、Microsoft Edge,版本 79.0.1072.62 及以上浏览器,360浏览器请使用极速模式
|
|
|
- </div>
|
|
|
+ <motion :delay="500">
|
|
|
+ <div>
|
|
|
+ 运营管理系统 <span class="color-danger font-bold">v5.0</span>
|
|
|
+ </div>
|
|
|
+ </motion>
|
|
|
+ <motion :delay="500">
|
|
|
+ <div class="login-msg">
|
|
|
+ <div>联系管理员<b>重置密码</b></div>
|
|
|
+ <!-- <el-button link type="primary" class="font16" @click="forgetPwd">忘记密码</el-button> -->
|
|
|
+ </div>
|
|
|
+ </motion>
|
|
|
</el-form>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts" name="loginMobile">
|
|
|
-import { reactive } from 'vue';
|
|
|
-// 定义接口来定义对象的类型
|
|
|
-interface LoginMobileState {
|
|
|
- userName: any;
|
|
|
- code: string | number | undefined;
|
|
|
-}
|
|
|
+import { reactive, defineAsyncComponent, ref, computed } from 'vue';
|
|
|
+import { ElNotification, FormInstance } from 'element-plus';
|
|
|
+import { throttle } from '@/utils/tools';
|
|
|
+import { JSEncrypt } from 'jsencrypt';
|
|
|
+import { signIn } from '@/api/login';
|
|
|
+import { Cookie, Local, Session } from '@/utils/storage';
|
|
|
+import { storeToRefs } from 'pinia';
|
|
|
+import { useThemeConfig } from '@/stores/themeConfig';
|
|
|
+import { useRoute, useRouter } from 'vue-router';
|
|
|
+import { initFrontEndControlRoutes } from '@/router/frontEnd';
|
|
|
+import { initBackEndControlRoutes } from '@/router/backEnd';
|
|
|
+import { formatAxis } from '@/utils/formatTime';
|
|
|
+import { NextLoading } from '@/utils/loading';
|
|
|
+import { verifyPhone } from '@/utils/toolsValidate';
|
|
|
+import Motion from "@/utils/motion";
|
|
|
|
|
|
-// 定义对象与类型
|
|
|
-const ruleForm: LoginMobileState = {
|
|
|
- userName: '',
|
|
|
- code: '',
|
|
|
-};
|
|
|
+const ReImageVerify = defineAsyncComponent(() => import('@/components/ImgVerify/index.vue'));
|
|
|
|
|
|
// 定义变量内容
|
|
|
const state = reactive({
|
|
|
- ruleForm
|
|
|
+ ruleForm: {
|
|
|
+ userName: '',
|
|
|
+ code: '',
|
|
|
+ verifyCode: '',
|
|
|
+ },
|
|
|
+ loading: false,
|
|
|
});
|
|
|
|
|
|
+// 验证码
|
|
|
+const verifyCode = ref<string>(''); // 验证码
|
|
|
+// 验证手机号
|
|
|
+const validatePass = (rule: any, value: any, callback: any) => {
|
|
|
+ if (value === '') {
|
|
|
+ callback(new Error('请输入验证码'));
|
|
|
+ } else {
|
|
|
+ if (state.ruleForm.verifyCode !== '') {
|
|
|
+ if (state.ruleForm.verifyCode.toUpperCase() !== verifyCode.value.toUpperCase()) {
|
|
|
+ callback(new Error('验证码错误,请重新输入'));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ callback();
|
|
|
+ }
|
|
|
+};
|
|
|
+const count = ref(60);
|
|
|
+const click = ref('获取验证码');
|
|
|
+const isDisabled = ref(false);
|
|
|
+const checkPhone = (rule, value, callback) => {
|
|
|
+ if (!value) {
|
|
|
+ return callback(new Error('手机号不能为空'));
|
|
|
+ } else {
|
|
|
+ if (verifyPhone(value)) {
|
|
|
+ callback();
|
|
|
+ } else {
|
|
|
+ return callback(new Error('请输入正确的手机号'));
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+const getIdentifyCodeBtn = () => {
|
|
|
+ if(!state.ruleForm.userName) {
|
|
|
+ ruleFormRef.value?.validateField('userName');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!verifyPhone(state.ruleForm.userName)) {
|
|
|
+ ruleFormRef.value?.validateField('userName');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // state.loading = true;
|
|
|
+ countDown();
|
|
|
+};
|
|
|
+// 倒计时
|
|
|
+const countDown = () => {
|
|
|
+ if (count.value === 0) {
|
|
|
+ isDisabled.value = false;
|
|
|
+ click.value = '获取验证码';
|
|
|
+ count.value = 60;
|
|
|
+ return;
|
|
|
+ } else {
|
|
|
+ count.value--;
|
|
|
+ click.value = count.value + 's后重新获取';
|
|
|
+ isDisabled.value = true;
|
|
|
+ setTimeout(() => {
|
|
|
+ countDown();
|
|
|
+ }, 1000);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const ruleFormRef = ref<FormInstance>(); // 表单ref
|
|
|
+const storesThemeConfig = useThemeConfig(); // 主题配置
|
|
|
+const { themeConfig } = storeToRefs(storesThemeConfig); // 主题配置
|
|
|
+const route = useRoute(); // 路由
|
|
|
+const router = useRouter(); // 路由
|
|
|
+// 登录
|
|
|
+const onSignIn = throttle(async (formEl: FormInstance | undefined) => {
|
|
|
+ if (!formEl) return;
|
|
|
+ await formEl.validate((valid: boolean) => {
|
|
|
+ if (!valid) return;
|
|
|
+ state.loading = true;
|
|
|
+ // 新建一个JSEncrypt对象
|
|
|
+ const encryptor = new JSEncrypt({ default_key_size: '2048' });
|
|
|
+ // 设置公钥
|
|
|
+ const publicKey =
|
|
|
+ '-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgw+/x6IQPkH0A4eoF63jkLThsOXWyNBdcL9LATGy/G1yTHOr1RyKJB//iNug+V8DIoIHuFTlhgLHDbSqxvRWMONxIIF289riS6bDI4Ox/pFmOfmElFRk0lKGihaTE2Aefd6g/N+RfLLaHWztY+/voVeDTiOIw9y3tokIxjKwuJ/mQ66MkKh78AqQjjSD/3jcBP8ZhMyCJOK9XQcqvhD6WBFWkxlAqKOWggDU7YohfrbNkg3bd0oGE6zCE2EHhkcQbzGCh3lu1zf4TfKMXD+PPrr5JWDNYQTXFQklqgae+Puge7xxZGYRoi5YpIUnkQGm6zpPxhIOdxlz+Yb5geSJUQIDAQAB-----END PUBLIC KEY-----';
|
|
|
+ encryptor.setPublicKey(publicKey); // publicKey为公钥
|
|
|
+ // 加密数据
|
|
|
+ const submitObj = {
|
|
|
+ username: encryptor.encrypt(state.ruleForm.username),
|
|
|
+ password: encryptor.encrypt(state.ruleForm.password),
|
|
|
+ };
|
|
|
+ signIn(submitObj)
|
|
|
+ .then(async (res: any) => {
|
|
|
+ //登录
|
|
|
+ // 存储 token 到浏览器缓存
|
|
|
+ Cookie.set('token', res.result);
|
|
|
+ if (!themeConfig.value.isRequestRoutes) {
|
|
|
+ // 前端控制路由,2、请注意执行顺序
|
|
|
+ const isNoPower = await initFrontEndControlRoutes();
|
|
|
+ signInSuccess(isNoPower);
|
|
|
+ } else {
|
|
|
+ // 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
|
|
+ // 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
|
|
|
+ const isNoPower = await initBackEndControlRoutes();
|
|
|
+ // 执行完 initBackEndControlRoutes,再执行 signInSuccess
|
|
|
+ signInSuccess(isNoPower);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ state.loading = false;
|
|
|
+ });
|
|
|
+ });
|
|
|
+}, 1000);
|
|
|
+// 登录成功后的跳转
|
|
|
+// 时间获取
|
|
|
+const currentTime = computed(() => {
|
|
|
+ return formatAxis(new Date());
|
|
|
+});
|
|
|
+const signInSuccess = (isNoPower: boolean | undefined) => {
|
|
|
+ if (isNoPower) {
|
|
|
+ state.loading = false;
|
|
|
+ ElNotification({
|
|
|
+ title: '提示',
|
|
|
+ message: '抱歉,您没有登录权限,请联系管理员',
|
|
|
+ type: 'warning',
|
|
|
+ });
|
|
|
+ Session.clear();
|
|
|
+ Cookie.clear();
|
|
|
+ Local.clear();
|
|
|
+ } else {
|
|
|
+ // 初始化登录成功时间问候语
|
|
|
+ let currentTimeInfo = currentTime.value;
|
|
|
+ // 登录成功,跳到转首页
|
|
|
+ /*// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
|
|
|
+ if (route.query?.redirect) {
|
|
|
+ router.push({
|
|
|
+ path: <string>route.query?.redirect,
|
|
|
+ query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ router.push('/');
|
|
|
+ }*/
|
|
|
+ router.push('/');
|
|
|
+ // 设置登录成功后的时间问候语
|
|
|
+ Cookie.set('userName', state.ruleForm.username);
|
|
|
+ // 登录成功提示
|
|
|
+ // 关闭 loading
|
|
|
+ state.loading = true;
|
|
|
+ const signInText = '欢迎回来!';
|
|
|
+ ElNotification({
|
|
|
+ type: 'success',
|
|
|
+ title: `${currentTimeInfo}`,
|
|
|
+ message: `${signInText}`,
|
|
|
+ });
|
|
|
+ NextLoading.start();
|
|
|
+ }
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
+.inputDeep {
|
|
|
+ :deep(.el-input__wrapper) {
|
|
|
+ box-shadow: 0 0 0 0 var(--el-input-border-color, var(--el-border-color)) inset;
|
|
|
+ border-radius: 0;
|
|
|
+ border-bottom: 1px solid var(--el-input-border-color, var(--el-border-color));
|
|
|
+ }
|
|
|
+ :deep(.el-form-item.is-error .el-input__wrapper.is-focus) {
|
|
|
+ box-shadow: 0 0 0 0 var(--el-input-border-color, var(--el-border-color)) inset;
|
|
|
+ }
|
|
|
+}
|
|
|
.login-content-form {
|
|
|
+ font-size: var(--el-font-size-medium);
|
|
|
margin-top: 20px;
|
|
|
- @for $i from 1 through 4 {
|
|
|
- .login-animation#{$i} {
|
|
|
- opacity: 0;
|
|
|
- animation-name: error-num;
|
|
|
- animation-duration: 0.5s;
|
|
|
- animation-fill-mode: forwards;
|
|
|
- animation-delay: calc($i/10) + s;
|
|
|
- }
|
|
|
+
|
|
|
+ :deep(.el-input--large) {
|
|
|
+ font-size: var(--el-font-size-medium);
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-form-item__error) {
|
|
|
+ font-size: var(--el-font-size-medium);
|
|
|
}
|
|
|
+
|
|
|
.login-content-code {
|
|
|
width: 100%;
|
|
|
padding: 0;
|
|
@@ -74,8 +295,24 @@ const state = reactive({
|
|
|
font-weight: 300;
|
|
|
margin-top: 15px;
|
|
|
}
|
|
|
+ .login-content-submit {
|
|
|
+ width: 100%;
|
|
|
+ margin-top: 45px;
|
|
|
+ height: 50px;
|
|
|
+ border-radius: 30px;
|
|
|
+ font-size: 16px;
|
|
|
+ letter-spacing: 5px;
|
|
|
+ }
|
|
|
.login-msg {
|
|
|
- color: var(--el-text-color-placeholder);
|
|
|
+ margin-top: 10px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ color: var(--el-color-primary);
|
|
|
+ b {
|
|
|
+ color: #999;
|
|
|
+ padding-left: 4px;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
</style>
|