mirror of
https://gitee.com/log4j/pig-ui.git
synced 2024-12-23 05:40:20 +08:00
✨ Introducing new features. 社交登录、短信登录
This commit is contained in:
parent
058daa662b
commit
97d16c4f89
@ -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 等配置项
|
@ -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",
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
|
@ -3,6 +3,7 @@ export default {
|
||||
label: {
|
||||
one1: 'User name login',
|
||||
two2: 'Mobile number',
|
||||
three3: 'social login'
|
||||
},
|
||||
link: {
|
||||
one3: 'Third party login',
|
||||
|
@ -3,6 +3,7 @@ export default {
|
||||
label: {
|
||||
one1: '用户名登录',
|
||||
two2: '手机号登录',
|
||||
three3: '社交登录'
|
||||
},
|
||||
link: {
|
||||
one3: '第三方登录',
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -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) => {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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']);
|
||||
|
@ -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(() => {
|
||||
|
@ -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"] }]
|
||||
}
|
||||
)
|
||||
|
49
src/views/login/component/authredirect.vue
Normal file
49
src/views/login/component/authredirect.vue
Normal 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>
|
@ -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);
|
||||
}
|
||||
|
@ -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>
|
114
src/views/login/component/social.vue
Normal file
114
src/views/login/component/social.vue
Normal 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>
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user