Introducing new features. 社交登录、短信登录

This commit is contained in:
lbw 2023-02-13 14:44:20 +08:00
parent 058daa662b
commit 97d16c4f89
17 changed files with 541 additions and 192 deletions

View File

@ -1,33 +0,0 @@
## 组件大小写确认
组件定义导入大写
```
const UserDialog = defineAsyncComponent(() => import('./form.vue'));
const QueryTree = defineAsyncComponent(() => import('/@/components/QueryTree/index.vue'))
```
Vue 使用时 驼峰命名 <user-dialog/>
## 缺少 MessageBox 组件封装
message.ts
## 缺少多选
## form 表单 (完成)
getObj
## 缺少导出组件 (完成)
如果封装了组件,注意页面不要 import 而是直接使用全局封装的对象
## I18N 设置
- 设置通用I18N 文件 查询、删除、编辑、导出、导入、保存成功、编辑成功、删除成功、保存失败、编辑失败、删除失败
## 权限、菜单
权限注解、左侧菜单
菜单管理 iframe 等配置项

View File

@ -1,30 +1,5 @@
import request from '/@/utils/request';
/**
* request.post(xxx) post params data
*
* api接口集合
* @method signIn
* @method signOut 退
*/
// export function useLoginApi() {
// return {
// signIn: (data: object) => {
// return request({
// url: '/user/signIn',
// method: 'post',
// data,
// });
// },
// signOut: (data: object) => {
// return request({
// url: '/user/signOut',
// method: 'post',
// data,
// });
// },
// };
// }
/**
*
@ -44,6 +19,48 @@ export const login = (data: any) => {
})
}
export const loginByMobile = (mobile: any, code: any) => {
const grant_type = 'mobile'
const scope = 'server'
let basicAuth = 'Basic ' + window.btoa('app:app')
return request({
url: '/admin/oauth2/token',
headers: {
isToken: false,
'TENANT-ID': '1',
'Authorization': basicAuth
},
method: 'post',
params: { mobile: 'SMS@' + mobile, code: code, grant_type, scope }
})
}
export const loginBySocial = (state: string, code: string) => {
const grant_type = 'mobile'
const scope = 'server'
let basicAuth = 'Basic ' + window.btoa('social:social')
return request({
url: '/admin/oauth2/token',
headers: {
isToken: false,
'TENANT-ID': '1',
'Authorization': basicAuth
},
method: 'post',
params: { mobile: state + '@' + code, code: code, grant_type, scope }
})
}
export const sendMobileCode = (mobile: any) => {
return request({
url: "/admin/mobile/" + mobile,
method: "get",
})
}
/**
*
*/

View File

@ -3,6 +3,7 @@ export default {
label: {
one1: 'User name login',
two2: 'Mobile number',
three3: 'social login'
},
link: {
one3: 'Third party login',

View File

@ -3,6 +3,7 @@ export default {
label: {
one1: '用户名登录',
two2: '手机号登录',
three3: '社交登录'
},
link: {
one3: '第三方登录',

View File

@ -88,7 +88,7 @@ router.beforeEach(async (to, from, next) => {
NProgress.configure({ showSpinner: false });
if (to.meta.title) NProgress.start();
const token = Session.get('token');
if (to.path === '/login' && !token) {
if (to.meta.isAuth !== undefined && !to.meta.isAuth) {
next();
NProgress.done();
} else {

View File

@ -22,6 +22,7 @@ declare module 'vue-router' {
title?: string;
isLink?: string;
isHide?: boolean;
isAuth?: boolean;
isKeepAlive?: boolean;
isAffix?: boolean;
isIframe?: boolean;
@ -123,6 +124,16 @@ export const staticRoutes: Array<RouteRecordRaw> = [
component: () => import('/@/views/login/index.vue'),
meta: {
title: '登录',
isAuth: false
},
},
{
path: '/authredirect',
name: 'authredirect',
component: () => import('/@/views/login/component/authredirect.vue'),
meta: {
title: "登录",
isAuth: false
}
}
];

View File

@ -1,7 +1,6 @@
import {defineStore} from 'pinia';
import Cookies from 'js-cookie';
import {Session} from '/@/utils/storage';
import {getUserInfo, login} from '/@//api/login/index'
import { defineStore } from 'pinia';
import { Session } from '/@/utils/storage';
import { getUserInfo, login, loginByMobile, loginBySocial } from '/@//api/login/index'
import other from '/@/utils/other'
/**
@ -39,6 +38,32 @@ export const useUserInfo = defineStore('userInfo', {
})
})
},
async loginByMobile(data: any) {
return new Promise((resolve, reject) => {
loginByMobile(data.mobile, data.code).then((res: any) => {
// 存储token 信息
Session.set('token', res.access_token);
Session.set('refresh_token', res.refresh_token);
resolve(res)
}).catch((err: any) => {
reject(err)
})
})
},
async loginBySocial(state: string, code: string) {
return new Promise((resolve, reject) => {
loginBySocial(state, code).then((res: any) => {
// 存储token 信息
Session.set('token', res.access_token);
Session.set('refresh_token', res.refresh_token);
resolve(res)
}).catch((err: any) => {
reject(err)
})
})
},
async setUserInfos() {
getUserInfo().then((res: any) => {

View File

@ -10,8 +10,6 @@ import { Local } from '/@/utils/storage';
import { verifyUrl } from '/@/utils/toolsValidate';
import request from "/@/utils/request";
import { useMessage } from "/@/hooks/message";
import { cloneDeep } from 'lodash-es';
import { isObject } from './is';
// @ts-ignore
import * as CryptoJS from "crypto-js";
@ -178,7 +176,42 @@ export function handleOpenLink(val: RouteItem) {
else window.open(`${origin}${pathname}#${val.meta?.isLink}`);
}
/**
*
*/
export const openWindow = (url: string, title: string, w: number, h: number) => {
// Fixes dual-screen position Most browsers Firefox
const dualScreenLeft =
window.screenLeft !== undefined ? window.screenLeft : screen.left;
const dualScreenTop =
window.screenTop !== undefined ? window.screenTop : screen.top;
const width = window.innerWidth
? window.innerWidth
: document.documentElement.clientWidth
? document.documentElement.clientWidth
: screen.width;
const height = window.innerHeight
? window.innerHeight
: document.documentElement.clientHeight
? document.documentElement.clientHeight
: screen.height;
const left = width / 2 - w / 2 + dualScreenLeft;
const top = height / 2 - h / 2 + dualScreenTop;
return window.open(
url,
title,
"toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=yes, copyhistory=no, width=" +
w +
", height=" +
h +
", top=" +
top +
", left=" +
left
);
};
/**
*
*/
@ -291,10 +324,33 @@ const other = {
},
toUnderline: (str: string) => {
return toUnderline(str)
},
openWindow: (url: string, title: string, w: number, h: number) => {
return openWindow(url, title, w, h)
},
getQueryString: (url: string, paraName: string) => {
return getQueryString(url, paraName)
}
};
export function getQueryString(url: string, paraName: string) {
const arrObj = url.split("?");
if (arrObj.length > 1) {
const arrPara = arrObj[1].split("&");
let arr;
for (let i = 0; i < arrPara.length; i++) {
arr = arrPara[i].split("=");
// eslint-disable-next-line eqeqeq
if (arr != null && arr[0] == paraName) {
return arr[1];
}
}
return "";
} else {
return "";
}
}
/**
*
* @param data

View File

@ -44,5 +44,40 @@ export const rule = {
} else {
callback()
}
},
/**
*
*/
validatePhone(rule: any, value: any, callback: any) {
var isPhone = /^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$/
if (!isPhone.test(value)) {
callback(new Error('请输入合法手机号'))
} else {
callback()
}
},
/**
*
*/
validatenull(val: any) {
if (typeof val === 'boolean') {
return false
}
if (typeof val === 'number') {
return false
}
if (val instanceof Array) {
if (val.length === 0) return true
} else if (val instanceof Object) {
if (JSON.stringify(val) === '{}') return true
} else {
if (val === 'null' || val == null || val === 'undefined' || val === undefined || val === '') return true
return false
}
return false
}
}

View File

@ -43,7 +43,8 @@
</el-dialog>
</template>
<script setup lang="ts" name="systemRoleDialog">import { rule } from '/@/utils/validate';
<script setup lang="ts" name="systemRoleDialog">
import { rule } from '/@/utils/validate';
// /
const emit = defineEmits(['refresh']);

View File

@ -1,6 +1,6 @@
<template>
<el-dialog v-model="visible" :close-on-click-modal="false"
:title="form.id ? $t('common.editBtn') : $t('common.addBtn')" draggable>
:title="form.id ? $t('common.editBtn') : $t('common.addBtn')" draggable>
<el-form ref="dataFormRef" :model="form" :rules="dataRules" formDialogRef label-width="90px">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
@ -14,56 +14,56 @@
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('social.remark')" prop="remark">
<el-input v-model="form.remark" :placeholder="t('social.inputRemarkTip')"/>
<el-input v-model="form.remark" :placeholder="t('social.inputRemarkTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('social.appId')" prop="appId">
<el-input v-model="form.appId" :placeholder="t('social.inputAppIdTip')"/>
<el-input v-model="form.appId" :placeholder="t('social.inputAppIdTip')" />
</el-form-item>
</el-col>
<el-col :span="12" class="mb20">
<el-form-item :label="t('social.appSecret')" prop="appSecret">
<el-input v-model="form.appSecret" :placeholder="t('social.inputAppSecretTip')"/>
<el-input v-model="form.appSecret" :placeholder="t('social.inputAppSecretTip')" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('social.redirectUrl')" prop="redirectUrl">
<el-input v-model="form.redirectUrl" :placeholder="t('social.inputRedirectUrlTip')" type="textarea"/>
<el-input v-model="form.redirectUrl" :placeholder="t('social.inputRedirectUrlTip')" type="textarea" />
</el-form-item>
</el-col>
<el-col :span="24" class="mb20">
<el-form-item :label="t('social.ext')" prop="ext">
<el-input v-model="form.ext" :placeholder="t('social.inputExtTip')" type="textarea"/>
<el-input v-model="form.ext" :placeholder="t('social.inputExtTip')" type="textarea" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button formDialogRef @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button formDialogRef type="primary" @click="onSubmit">{{ $t('common.confirmButtonText') }}</el-button>
</span>
<span class="dialog-footer">
<el-button formDialogRef @click="visible = false">{{ $t('common.cancelButtonText') }}</el-button>
<el-button formDialogRef type="primary" @click="onSubmit">{{ $t('common.confirmButtonText') }}</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" name="AppSocialDetailsDialog" setup>
// /
import {useDict} from '/@/hooks/dict';
import {useMessage} from "/@/hooks/message";
import {addObj, getObj, putObj} from '/@/api/admin/social'
import {useI18n} from "vue-i18n"
import { useDict } from '/@/hooks/dict';
import { useMessage } from "/@/hooks/message";
import { addObj, getObj, putObj } from '/@/api/admin/social'
import { useI18n } from "vue-i18n"
const emit = defineEmits(['refresh']);
const {t} = useI18n();
const { t } = useI18n();
//
const dataFormRef = ref();
const visible = ref(false)
//
const {social_type} = useDict('social_type')
const { social_type } = useDict('social_type')
//
const form = reactive({
@ -78,9 +78,9 @@ const form = reactive({
//
const dataRules = ref({
type: [{required: true, message: '类型不能为空', trigger: 'blur'}],
appId: [{required: true, message: 'appId不能为空', trigger: 'blur'}],
appSecret: [{required: true, message: 'appSecret不能为空', trigger: 'blur'}],
type: [{ required: true, message: '类型不能为空', trigger: 'blur' }],
appId: [{ required: true, message: 'appId不能为空', trigger: 'blur' }],
appSecret: [{ required: true, message: 'appSecret不能为空', trigger: 'blur' }],
})
//
@ -107,6 +107,14 @@ const onSubmit = () => {
return false
}
if (form.appSecret && form.appSecret.indexOf("******") >= 0) {
form.appSecret = ''
}
if (form.appId && form.appId.indexOf("******") >= 0) {
form.appId = ''
}
//
if (form.id) {
putObj(form).then(() => {

View File

@ -85,6 +85,7 @@ import { depttree } from '/@/api/admin/dept'
import { useDict } from "/@/hooks/dict";
import { useI18n } from "vue-i18n";
import { useMessage } from '/@/hooks/message';
import { rule } from '/@/utils/validate';
const { t } = useI18n()
@ -166,11 +167,7 @@ const dataRules = ref(
dept: [{ required: true, message: "部门不能为空", trigger: "blur" }],
role: [{ required: true, message: "角色不能为空", trigger: "blur" }],
post: [{ required: true, message: "岗位不能为空", trigger: "blur" }],
phone: [{ required: true, message: "手机号不能为空", trigger: "blur" }, {
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
message: "请输入正确的手机号码",
trigger: "blur"
}, { validator: validatePhone, trigger: 'blur' }, { validator: validatePhone, trigger: 'blur' }],
phone: [{ required: true, message: "手机号不能为空", trigger: "blur" }, { validator: rule.validatePhone, trigger: 'blur' }, { validator: validatePhone, trigger: 'blur' }],
email: [{ type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"] }]
}
)

View File

@ -0,0 +1,49 @@
<script setup lang="ts" name="authredirect">
import request from '/@/utils/request';
import { ElMessageBox } from 'element-plus';
import other from "/@/utils/other";
import { rule } from '/@/utils/validate';
import { Session } from '/@/utils/storage';
import { useUserInfo } from '/@/stores/userInfo';
onMounted(async () => {
const url = window.location.search
.replace('#/authredirect', '')
.replaceAll('/', '')
// code ticket
let code = other.getQueryString(url, 'code')
if (rule.validatenull(code)) {
code = other.getQueryString(url, 'ticket')
}
//
let state = other.getQueryString(url, 'state')
let type = state.split('-')[1]
state = state.split('-')[0]
//
if (type === 'LOGIN') {
Session.clear();
await useUserInfo().loginBySocial(state, code)
window.close()
} else {
bind(state, code)
}
})
const bind = (state: string, code: string) => {
request({
url: '/admin/social/bind',
method: 'post',
params: { state, code }
}).then(() => {
ElMessageBox.alert('社交账号绑定成功', '成功', {
confirmButtonText: '确定',
callback: () => {
window.close()
}
})
})
}
</script>

View File

@ -1,15 +1,17 @@
<template>
<el-form size="large" class="login-content-form">
<el-form-item class="login-animation1">
<el-input text :placeholder="$t('mobile.placeholder1')" v-model="state.ruleForm.userName" clearable autocomplete="off">
<el-form size="large" class="login-content-form" ref="loginFormRef" :rules="loginRules" :model="loginForm">
<el-form-item class="login-animation1" prop="mobile">
<el-input text :placeholder="$t('mobile.placeholder1')" v-model="loginForm.mobile" clearable
autocomplete="off">
<template #prefix>
<i class="iconfont icon-dianhua el-input__icon"></i>
</template>
</el-input>
</el-form-item>
<el-form-item class="login-animation2">
<el-form-item class="login-animation2" prop="code">
<el-col :span="15">
<el-input text maxlength="4" :placeholder="$t('mobile.placeholder2')" v-model="state.ruleForm.code" clearable autocomplete="off">
<el-input text maxlength="4" :placeholder="$t('mobile.placeholder2')" v-model="loginForm.code" clearable
autocomplete="off">
<template #prefix>
<el-icon class="el-input__icon"><ele-Position /></el-icon>
</template>
@ -17,11 +19,14 @@
</el-col>
<el-col :span="1"></el-col>
<el-col :span="8">
<el-button v-waves class="login-content-code">{{ $t('mobile.codeText') }}</el-button>
<el-button v-waves class="login-content-code" @click="handleSendCode">{{
$t('mobile.codeText')
}}</el-button>
</el-col>
</el-form-item>
<el-form-item class="login-animation3">
<el-button round type="primary" v-waves class="login-content-submit">
<el-button round type="primary" v-waves class="login-content-submit" @click="handleLogin"
:loading="loading">
<span>{{ $t('mobile.btnText') }}</span>
</el-button>
</el-form-item>
@ -31,19 +36,66 @@
<script setup lang="ts" name="loginMobile">
import { reactive } from 'vue';
import { sendMobileCode } from '/@/api/login';
import { useMessage } from '/@/hooks/message';
import { useUserInfo } from '/@/stores/userInfo';
import { rule } from '/@/utils/validate';
const emit = defineEmits(['signInSuccess']);
const loginFormRef = ref()
const loading = ref(false)
//
const state = reactive({
ruleForm: {
userName: '',
code: '',
},
const loginForm = reactive({
mobile: '',
code: ''
});
const loginRules = reactive({
mobile: [{ required: true, trigger: "blur", validator: rule.validatePhone }],
code: [{
required: true, trigger: "blur", message: "请输入验证码"
}],
})
const handleSendCode = async () => {
loginFormRef.value.validateField("mobile", (valid: boolean) => {
if (!valid) {
return false
}
})
sendMobileCode(loginForm.mobile).then((response: any) => {
if (response.data) {
useMessage().success("验证码发送成功");
} else {
useMessage().error(response.msg);
}
});
}
const handleLogin = async () => {
loginFormRef.value.validate((valid: boolean) => {
if (!valid) {
return false
}
})
loading.value = true
await useUserInfo().loginByMobile(loginForm)
//
emit('signInSuccess')
loading.value = false;
}
</script>
<style scoped lang="scss">
.login-content-form {
margin-top: 20px;
@for $i from 1 through 4 {
.login-animation#{$i} {
opacity: 0;
@ -53,16 +105,19 @@ const state = reactive({
animation-delay: calc($i/10) + s;
}
}
.login-content-code {
width: 100%;
padding: 0;
}
.login-content-submit {
width: 100%;
letter-spacing: 2px;
font-weight: 300;
margin-top: 15px;
}
.login-msg {
color: var(--el-text-color-placeholder);
}

View File

@ -26,52 +26,26 @@
<Verify @success="verifySuccess" :mode="'pop'" :captchaType="'blockPuzzle'"
:imgSize="{ width: '330px', height: '155px' }" ref="verifyref" />
</el-form-item>
<!-- <el-form-item class="login-animation3">-->
<!-- <el-col :span="15">-->
<!-- <el-input-->
<!-- text-->
<!-- maxlength="4"-->
<!-- :placeholder="$t('account.accountPlaceholder3')"-->
<!-- v-model="state.ruleForm.code"-->
<!-- clearable-->
<!-- autocomplete="off"-->
<!-- >-->
<!-- <template #prefix>-->
<!-- <el-icon class="el-input__icon"><ele-Position /></el-icon>-->
<!-- </template>-->
<!-- </el-input>-->
<!-- </el-col>-->
<!-- <el-col :span="1"></el-col>-->
<!-- <el-col :span="8">-->
<!-- <el-button class="login-content-code" v-waves>1234</el-button>-->
<!-- </el-col>-->
<!-- </el-form-item>-->
<el-form-item class="login-animation4">
<el-button type="primary" class="login-content-submit" round v-waves @click="handleLogin"
:loading="state.loading.signIn">
:loading="loading">
<span>{{ $t('account.accountBtnText') }}</span>
</el-button>
</el-form-item>
<div class="font12 mt30 login-animation4 login-msg">{{ $t('mobile.msgText') }}</div>
</el-form>
</template>
<script setup lang="ts" name="loginAccount">
import { reactive, computed, defineAsyncComponent, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
import { initBackEndControlRoutes } from '/@/router/backEnd';
import { Session } from '/@/utils/storage';
import { formatAxis } from '/@/utils/formatTime';
import { NextLoading } from '/@/utils/loading';
import { useUserInfo } from '/@/stores/userInfo'
<script setup lang="ts" name="password">
import { reactive, defineAsyncComponent, ref } from 'vue';
import { useUserInfo } from '/@/stores/userInfo';
const Verify = defineAsyncComponent(() => import('/@/components/verifition/Verify.vue'))
//
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const emit = defineEmits(['signInSuccess']);
const loading = ref(false);
const state = reactive({
isShowPassword: false,
ruleForm: {
@ -79,59 +53,27 @@ const state = reactive({
password: '123456',
code: '',
randomStr: 'blockPuzzle'
},
loading: {
signIn: false,
},
}
});
// @ts-ignore
const verifyref = ref<InstanceType<typeof Verify>>(null)
//
const currentTime = computed(() => {
return formatAxis(new Date());
});
const handleLogin = () => {
verifyref.value.show();
}
//
const onSignIn = async () => {
state.loading.signIn = true;
loading.value = true;
// token
await useUserInfo().login(state.ruleForm)
//
emit('signInSuccess')
loading.value = false
};
const isNoPower = await initBackEndControlRoutes();
// initBackEndControlRoutes signInSuccess
signInSuccess(isNoPower);
};
//
const signInSuccess = (isNoPower: boolean | undefined) => {
if (isNoPower) {
ElMessage.warning('抱歉,您没有登录权限');
Session.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('/');
}
//
const signInText = t('signInText');
ElMessage.success(`${currentTimeInfo}${signInText}`);
// loading
NextLoading.start();
}
state.loading.signIn = false;
};
const verifySuccess = (params: any) => {
state.ruleForm.code = params.captchaVerification;
@ -176,5 +118,9 @@ const verifySuccess = (params: any) => {
font-weight: 300;
margin-top: 15px;
}
.login-msg {
color: var(--el-text-color-placeholder);
}
}
</style>

View File

@ -0,0 +1,114 @@
<template>
<div class="social-container">
<div @click="handleClick('cas')">
<span :style="{ backgroundColor: '#e6a23c' }" class="container">
<i class="iconfont icon-login" />
</span>
<p class="title">CAS</p>
</div>
<div @click="handleClick('wechat')">
<span :style="{ backgroundColor: '#6ba2d6' }" class="container">
<i icon-class="wechat" class="iconfont icon-weixin" />
</span>
<p class="title">微信</p>
</div>
<div @click="handleClick('tencent')">
<span :style="{ backgroundColor: '#8dc349' }" class="container">
<i icon-class="qq" class="iconfont icon-qq" />
</span>
<p class="title">QQ</p>
</div>
<div @click="handleClick('gitee')">
<span :style="{ backgroundColor: '#bf3030' }" class="container">
<i icon-class="qq" class="iconfont icon-logo_gitee_icon" />
</span>
<p class="title">Gitee</p>
</div>
<div @click="handleClick('osc')">
<span :style="{ backgroundColor: '#007B25' }" class="container">
<i icon-class="qq" class="iconfont icon-oschina" />
</span>
<p class="title">开源中国</p>
</div>
</div>
</template>
<script setup lang="ts" name="loginSocial">
import Cookies from 'js-cookie';
import other from '/@/utils/other';
const emit = defineEmits(['signInSuccess']);
const winOpen = ref()
const timer = ref()
const handleClick = (thirdpart: string) => {
let url: string = ''
const redirect_uri = encodeURIComponent(window.location.origin + '/#/authredirect')
if (thirdpart === 'wechat') {
const appid = 'wxd1678d3f83b1d83a'
url = `https://open.weixin.qq.com/connect/qrconnect?appid=${appid}&redirect_uri=${redirect_uri}&state=WX-LOGIN&response_type=code&scope=snsapi_login#wechat_redirect`
} else if (thirdpart === 'tencent') {
const client_id = '101322838'
url = `https://graph.qq.com/oauth2.0/authorize?response_type=code&state=QQ-LOGIN&client_id=${client_id}&redirect_uri=${redirect_uri}`
} else if (thirdpart === 'gitee') {
const client_id = '0c29cfd9cb1e0037fc837521bc08c1a7483d8fd9b3e123d46beec59a5544a881'
url = `https://gitee.com/oauth/authorize?response_type=code&client_id=${client_id}&state=GITEE-LOGIN&redirect_uri=${redirect_uri}`
} else if (thirdpart === 'osc') {
const client_id = 'uLJ41IGu7qAGmzSVHwF4'
url = `https://www.oschina.net/action/oauth2/authorize?response_type=code&client_id=${client_id}'&state=OSC-LOGIN&redirect_uri=${redirect_uri}`
} else if (thirdpart === 'cas') {
let returnUrl = encodeURIComponent(window.location.origin + '?state=CAS-LOGIN/#/authredirect')
url = `http://127.0.0.1:8080/cas/login?service=${returnUrl}`
}
winOpen.value = other.openWindow(url, thirdpart, 540, 540)
}
onMounted(() => {
timer.value = window.setInterval(() => {
//
if (winOpen.value && winOpen.value.closed == true) {
window.clearInterval(timer.value)
// token
if (Cookies.get('token')) {
emit('signInSuccess')
}
}
}, 500);
})
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
.social-container {
margin: 20px 0;
display: flex;
align-items: center;
justify-content: space-around;
.box {
cursor: pointer;
}
.iconfont {
color: #fff;
font-size: 30px;
}
.container {
$height: 50px;
display: inline-block;
width: $height;
height: $height;
line-height: $height;
text-align: center;
border-radius: 4px;
margin-bottom: 10px;
}
.title {
text-align: center;
}
}
</style>

View File

@ -15,25 +15,25 @@
</div>
<div class="login-right flex">
<div class="login-right-warp flex-margin">
<span class="login-right-warp-one"></span>
<span class="login-right-warp-two"></span>
<div class="login-right-warp-mian">
<div class="login-right-warp-main-title">{{ getThemeConfig.globalTitle }} 欢迎您</div>
<div class="login-right-warp-main-form">
<div v-if="!state.isScan">
<div>
<el-tabs v-model="state.tabsActiveName">
<!-- 用户名密码登录 -->
<el-tab-pane :label="$t('label.one1')" name="account">
<Account />
<Password @signInSuccess="signInSuccess" />
</el-tab-pane>
<!-- 手机号登录 -->
<el-tab-pane :label="$t('label.two2')" name="mobile">
<Mobile />
<Mobile @signInSuccess="signInSuccess" />
</el-tab-pane>
<!-- 社交登录 -->
<el-tab-pane :label="$t('label.three3')" name="social">
<Social @signInSuccess="signInSuccess" />
</el-tab-pane>
</el-tabs>
</div>
<div class="login-content-main-sacn" @click="state.isScan = !state.isScan">
<i class="iconfont" :class="state.isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
<div class="login-content-main-sacn-delta"></div>
</div>
</div>
</div>
</div>
@ -49,17 +49,26 @@ import { NextLoading } from '/@/utils/loading';
import logoMini from '/@/assets/logo-mini.svg';
import loginMain from '/@/assets/login-main.svg';
import loginBg from '/@/assets/login-bg.svg';
import { useI18n } from 'vue-i18n';
import { formatAxis } from '/@/utils/formatTime';
import { useMessage } from '/@/hooks/message';
import { Session } from '/@/utils/storage';
import { initBackEndControlRoutes } from '/@/router/backEnd';
//
const Account = defineAsyncComponent(() => import('/@/views/login/component/account.vue'));
const Mobile = defineAsyncComponent(() => import('/@/views/login/component/mobile.vue'));
const Password = defineAsyncComponent(() => import('./component/password.vue'));
const Mobile = defineAsyncComponent(() => import('./component/mobile.vue'));
const Social = defineAsyncComponent(() => import('./component/social.vue'));
//
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const storesThemeConfig = useThemeConfig()
const { themeConfig } = storeToRefs(storesThemeConfig)
const { t } = useI18n()
const route = useRoute()
const router = useRouter()
const state = reactive({
tabsActiveName: 'account',
isScan: false,
tabsActiveName: 'account'
});
//
@ -69,18 +78,53 @@ const getThemeConfig = computed(() => {
//
onMounted(() => {
NextLoading.done();
});
})
//
const currentTime = computed(() => {
return formatAxis(new Date());
})
//
const signInSuccess = async () => {
const isNoPower = await initBackEndControlRoutes();
if (isNoPower) {
useMessage().wraning('抱歉,您没有登录权限');
Session.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('/');
}
//
const signInText = t('signInText');
useMessage().success(`${currentTimeInfo}${signInText}`);
// loading
NextLoading.start();
}
};
</script>
<style scoped lang="scss">
.login-container {
height: 100%;
background: var(--el-color-white);
.login-left {
flex: 1;
position: relative;
background-color: rgba(211, 239, 255, 1);
margin-right: 100px;
.login-left-logo {
display: flex;
align-items: center;
@ -89,24 +133,29 @@ onMounted(() => {
left: 80px;
z-index: 1;
animation: logoAnimation 0.3s ease;
img {
width: 52px;
height: 52px;
}
.login-left-logo-text {
display: flex;
flex-direction: column;
span {
margin-left: 10px;
font-size: 28px;
color: #26a59a;
}
.login-left-logo-text-msg {
font-size: 12px;
color: #32a99e;
}
}
}
.login-left-img {
position: absolute;
top: 50%;
@ -114,20 +163,24 @@ onMounted(() => {
transform: translate(-50%, -50%);
width: 100%;
height: 52%;
img {
width: 100%;
height: 100%;
animation: error-num 0.6s ease;
}
}
.login-left-waves {
position: absolute;
top: 0;
right: -100px;
}
}
.login-right {
width: 700px;
.login-right-warp {
border: 1px solid var(--el-color-primary-light-3);
border-radius: 3px;
@ -136,12 +189,14 @@ onMounted(() => {
position: relative;
overflow: hidden;
background-color: var(--el-color-white);
.login-right-warp-one,
.login-right-warp-two {
position: absolute;
display: block;
width: inherit;
height: inherit;
&::before,
&::after {
content: '';
@ -149,6 +204,7 @@ onMounted(() => {
z-index: 1;
}
}
.login-right-warp-one {
&::before {
filter: hue-rotate(0deg);
@ -159,6 +215,7 @@ onMounted(() => {
background: linear-gradient(90deg, transparent, var(--el-color-primary));
animation: loginLeft 3s linear infinite;
}
&::after {
filter: hue-rotate(60deg);
top: -100%;
@ -170,6 +227,7 @@ onMounted(() => {
animation-delay: 0.7s;
}
}
.login-right-warp-two {
&::before {
filter: hue-rotate(120deg);
@ -181,6 +239,7 @@ onMounted(() => {
animation: loginRight 3s linear infinite;
animation-delay: 1.4s;
}
&::after {
filter: hue-rotate(300deg);
bottom: -100%;
@ -192,10 +251,12 @@ onMounted(() => {
animation-delay: 2.1s;
}
}
.login-right-warp-mian {
display: flex;
flex-direction: column;
height: 100%;
.login-right-warp-main-title {
height: 130px;
line-height: 130px;
@ -206,9 +267,11 @@ onMounted(() => {
animation-delay: 0.3s;
color: var(--el-text-color-primary);
}
.login-right-warp-main-form {
flex: 1;
padding: 0 50px 50px;
.login-content-main-sacn {
position: absolute;
top: 0;
@ -219,6 +282,7 @@ onMounted(() => {
cursor: pointer;
transition: all ease 0.3s;
color: var(--el-color-primary);
&-delta {
position: absolute;
width: 35px;
@ -229,11 +293,13 @@ onMounted(() => {
background: var(--el-color-white);
transform: rotate(-45deg);
}
&:hover {
opacity: 1;
transition: all ease 0.3s;
color: var(--el-color-primary) !important;
}
i {
width: 47px;
height: 50px;