|
|
@ -1,12 +1,25 @@ |
|
|
|
<template> |
|
|
|
<div class="lockscreen" :class="lockClassName"> |
|
|
|
<el-card class="login-box" shadow="never"> |
|
|
|
<div |
|
|
|
class="lockscreen" |
|
|
|
:class="lockClassName" |
|
|
|
> |
|
|
|
<el-card |
|
|
|
class="login-box" |
|
|
|
shadow="never" |
|
|
|
> |
|
|
|
<div class="avatar-container"> |
|
|
|
<el-avatar size="large"> |
|
|
|
<img src="/logo.png" alt="Logo" /> |
|
|
|
<img |
|
|
|
src="/logo.png" |
|
|
|
alt="Logo" |
|
|
|
/> |
|
|
|
</el-avatar> |
|
|
|
</div> |
|
|
|
<el-form v-if="!isRegisterMode" label-position="left" label-width="0px"> |
|
|
|
<el-form |
|
|
|
v-if="!isRegisterMode" |
|
|
|
label-position="left" |
|
|
|
label-width="0px" |
|
|
|
> |
|
|
|
<el-form-item> |
|
|
|
<el-input |
|
|
|
v-model="userName" |
|
|
@ -25,13 +38,90 @@ |
|
|
|
@keyup.enter="onLogin" |
|
|
|
></el-input> |
|
|
|
</el-form-item> |
|
|
|
<el-button type="primary" @click="onLogin">登录</el-button> |
|
|
|
<div class="actions" v-if="config.userType === 'member'"> |
|
|
|
<a href="#" @click.prevent="toggleRegister">注册新用户</a> |
|
|
|
<a href="#" @click.prevent="toggleUserSwitch">切换角色</a> |
|
|
|
<el-button |
|
|
|
type="primary" |
|
|
|
@click="onLogin" |
|
|
|
>登录</el-button |
|
|
|
> |
|
|
|
<div class="divider"> |
|
|
|
<span>第三方登录</span> |
|
|
|
</div> |
|
|
|
<div class="third-party-login"> |
|
|
|
<img |
|
|
|
style=" |
|
|
|
width: 25px; |
|
|
|
height: 25px; |
|
|
|
cursor: pointer; |
|
|
|
color: #409eff; |
|
|
|
" |
|
|
|
src="../../../public/image/login/微信.png" |
|
|
|
@click="onThirdPartyLogin('wechat')" |
|
|
|
/> |
|
|
|
<img |
|
|
|
style=" |
|
|
|
width: 25px; |
|
|
|
height: 25px; |
|
|
|
cursor: pointer; |
|
|
|
color: #409eff; |
|
|
|
" |
|
|
|
src="../../../public/image/login/QQ.png" |
|
|
|
@click="onThirdPartyLogin('qq')" |
|
|
|
/> |
|
|
|
<img |
|
|
|
style=" |
|
|
|
width: 25px; |
|
|
|
height: 25px; |
|
|
|
cursor: pointer; |
|
|
|
color: #409eff; |
|
|
|
" |
|
|
|
src="../../../public/image/login/新浪微博.png" |
|
|
|
@click="onThirdPartyLogin('sina')" |
|
|
|
/> |
|
|
|
<img |
|
|
|
style=" |
|
|
|
width: 25px; |
|
|
|
height: 25px; |
|
|
|
cursor: pointer; |
|
|
|
color: #409eff; |
|
|
|
" |
|
|
|
src="../../../public/image/login/GitHub.png" |
|
|
|
@click="onThirdPartyLogin('github')" |
|
|
|
/> |
|
|
|
<img |
|
|
|
style=" |
|
|
|
width: 25px; |
|
|
|
height: 25px; |
|
|
|
cursor: pointer; |
|
|
|
color: #409eff; |
|
|
|
" |
|
|
|
src="../../../public/image/login/gitee.png" |
|
|
|
@click="onThirdPartyLogin('gitee')" |
|
|
|
/> |
|
|
|
</div> |
|
|
|
<div |
|
|
|
class="actions" |
|
|
|
v-if="config.userType === 'member'" |
|
|
|
> |
|
|
|
<a |
|
|
|
href="#" |
|
|
|
@click.prevent="toggleRegister" |
|
|
|
>注册新用户</a |
|
|
|
> |
|
|
|
<a |
|
|
|
href="#" |
|
|
|
@click.prevent="toggleUserSwitch" |
|
|
|
>切换角色</a |
|
|
|
> |
|
|
|
</div> |
|
|
|
</el-form> |
|
|
|
<el-form v-else label-position="left" label-width="0px" :model="regForm" ref="regFormRef" :rules="rules"> |
|
|
|
<el-form |
|
|
|
v-else |
|
|
|
label-position="left" |
|
|
|
label-width="0px" |
|
|
|
:model="regForm" |
|
|
|
ref="regFormRef" |
|
|
|
:rules="rules" |
|
|
|
> |
|
|
|
<el-form-item prop="username"> |
|
|
|
<el-input |
|
|
|
v-model="regForm.username" |
|
|
@ -78,9 +168,17 @@ |
|
|
|
prefix-icon="Lock" |
|
|
|
></el-input> |
|
|
|
</el-form-item> |
|
|
|
<el-button type="primary" @click="onRegister">注册</el-button> |
|
|
|
<el-button |
|
|
|
type="primary" |
|
|
|
@click="onRegister" |
|
|
|
>注册</el-button |
|
|
|
> |
|
|
|
<div class="actions"> |
|
|
|
<a href="#" @click.prevent="toggleRegister">返回登录</a> |
|
|
|
<a |
|
|
|
href="#" |
|
|
|
@click.prevent="toggleRegister" |
|
|
|
>返回登录</a |
|
|
|
> |
|
|
|
</div> |
|
|
|
</el-form> |
|
|
|
</el-card> |
|
|
@ -88,134 +186,276 @@ |
|
|
|
</template> |
|
|
|
|
|
|
|
<script lang="ts" setup> |
|
|
|
import { ref, onMounted } from 'vue'; |
|
|
|
import { useSystem } from '@/system'; |
|
|
|
import { getSystemConfig, setSystemConfig } from '@/system/config'; |
|
|
|
import { notifyError } from '@/util/msg'; |
|
|
|
import { RestartApp } from '@/util/goutil'; |
|
|
|
|
|
|
|
const sys = useSystem(); |
|
|
|
const loginCallback = sys._options.loginCallback; |
|
|
|
const config = getSystemConfig(); |
|
|
|
const lockClassName = ref('screen-show'); |
|
|
|
const isRegisterMode = ref(false); |
|
|
|
|
|
|
|
function loginSuccess() { |
|
|
|
lockClassName.value = 'screen-hidean'; |
|
|
|
import { useLoginStore } from "@/stores/login"; |
|
|
|
import { useSystem } from "@/system"; |
|
|
|
import { getSystemConfig, setSystemConfig } from "@/system/config"; |
|
|
|
import router from "@/system/router"; |
|
|
|
import { RestartApp } from "@/util/goutil"; |
|
|
|
import { notifyError } from "@/util/msg"; |
|
|
|
import { onMounted, ref } from "vue"; |
|
|
|
const store = useLoginStore(); |
|
|
|
const sys = useSystem(); |
|
|
|
const loginCallback = sys._options.loginCallback; |
|
|
|
const config = getSystemConfig(); |
|
|
|
const lockClassName = ref("screen-show"); |
|
|
|
const isRegisterMode = ref(false); |
|
|
|
function loginSuccess() { |
|
|
|
lockClassName.value = "screen-hidean"; |
|
|
|
setTimeout(() => { |
|
|
|
lockClassName.value = 'screen-hide'; |
|
|
|
lockClassName.value = "screen-hide"; |
|
|
|
}, 500); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const userName = ref(''); |
|
|
|
const userPassword = ref(''); |
|
|
|
const userName = ref(""); |
|
|
|
const userPassword = ref(""); |
|
|
|
|
|
|
|
onMounted(() => { |
|
|
|
if (config.userType === 'person') { |
|
|
|
userName.value = sys._options.login?.username || 'admin'; |
|
|
|
userPassword.value = sys._options.login?.password || ''; |
|
|
|
onMounted(() => { |
|
|
|
if (config.userType === "person") { |
|
|
|
userName.value = sys._options.login?.username || "admin"; |
|
|
|
userPassword.value = sys._options.login?.password || ""; |
|
|
|
} else { |
|
|
|
userName.value = config.userInfo.username; |
|
|
|
userPassword.value = config.userInfo.password; |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
watch( |
|
|
|
() => router.currentRoute.value.query.code, |
|
|
|
() => { |
|
|
|
onLogin(); |
|
|
|
} |
|
|
|
); |
|
|
|
|
|
|
|
async function onLogin() { |
|
|
|
const onLogin = async () => { |
|
|
|
localStorage.removeItem("godoosClientId"); |
|
|
|
if (loginCallback) { |
|
|
|
const res = await loginCallback(userName.value, userPassword.value); |
|
|
|
const platform = store.ThirdPartyPlatform; |
|
|
|
const code = router.currentRoute.value.query.code as string; |
|
|
|
|
|
|
|
// 使用映射对象,将平台名称映射到相应的参数名称 |
|
|
|
const platformCodeMap: Record<string, string> = { |
|
|
|
github: "github_code", |
|
|
|
gitee: "gitee_code", |
|
|
|
wechat: "wechat_code", |
|
|
|
qq: "qq_code", |
|
|
|
}; |
|
|
|
|
|
|
|
// 获取对应的参数名称 |
|
|
|
const codeParam = platform ? platformCodeMap[platform] : null; |
|
|
|
|
|
|
|
let res; |
|
|
|
if (codeParam) { |
|
|
|
// 第三方登录统一调用 |
|
|
|
const returnedState = router.currentRoute.value.query |
|
|
|
.state as string; |
|
|
|
|
|
|
|
if (returnedState !== store.State) { |
|
|
|
notifyError("登录失败,请重试"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
res = await loginCallback(userName.value, userPassword.value, { |
|
|
|
[codeParam]: code, |
|
|
|
}); |
|
|
|
} else { |
|
|
|
// 普通登录 |
|
|
|
res = await loginCallback(userName.value, userPassword.value); |
|
|
|
} |
|
|
|
|
|
|
|
if (res) { |
|
|
|
store.ThirdPartyPlatform = null; |
|
|
|
loginSuccess(); |
|
|
|
} else { |
|
|
|
notifyError("登录失败,请重试"); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
function toggleRegister() { |
|
|
|
const toggleRegister = () => { |
|
|
|
isRegisterMode.value = !isRegisterMode.value; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
function toggleUserSwitch() { |
|
|
|
config.userType = 'person' |
|
|
|
const toggleUserSwitch = () => { |
|
|
|
config.userType = "person"; |
|
|
|
setSystemConfig(config); |
|
|
|
RestartApp(); |
|
|
|
} |
|
|
|
const regForm = ref({ |
|
|
|
username: '', |
|
|
|
password: '', |
|
|
|
confirmPassword: '', |
|
|
|
email: '', |
|
|
|
phone: '', |
|
|
|
nickname: '', |
|
|
|
}) |
|
|
|
const regFormRef:any = ref(null); |
|
|
|
|
|
|
|
const rules = { |
|
|
|
}; |
|
|
|
|
|
|
|
const regForm = ref({ |
|
|
|
username: "", |
|
|
|
password: "", |
|
|
|
confirmPassword: "", |
|
|
|
email: "", |
|
|
|
phone: "", |
|
|
|
nickname: "", |
|
|
|
}); |
|
|
|
const regFormRef: any = ref(null); |
|
|
|
|
|
|
|
const rules = { |
|
|
|
username: [ |
|
|
|
{ required: true, message: '用户名不能为空', trigger: 'blur' }, |
|
|
|
{ min: 3, max: 20, message: '用户名长度应在3到20个字符之间', trigger: 'blur' } |
|
|
|
{ required: true, message: "用户名不能为空", trigger: "blur" }, |
|
|
|
{ |
|
|
|
min: 3, |
|
|
|
max: 20, |
|
|
|
message: "用户名长度应在3到20个字符之间", |
|
|
|
trigger: "blur", |
|
|
|
}, |
|
|
|
], |
|
|
|
password: [ |
|
|
|
{ required: true, message: '密码不能为空', trigger: 'blur' }, |
|
|
|
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' } |
|
|
|
{ required: true, message: "密码不能为空", trigger: "blur" }, |
|
|
|
{ min: 6, message: "密码长度不能小于6位", trigger: "blur" }, |
|
|
|
], |
|
|
|
confirmPassword: [ |
|
|
|
{ required: true, message: '请再次输入密码', trigger: 'blur' }, |
|
|
|
{ validator: (rule:any, value:any, callback:any) => { |
|
|
|
console.log(rule) |
|
|
|
if (value === '') { |
|
|
|
callback(new Error('请再次输入密码')); |
|
|
|
{ required: true, message: "请再次输入密码", trigger: "blur" }, |
|
|
|
{ |
|
|
|
validator: (rule: any, value: any, callback: any) => { |
|
|
|
console.log(rule); |
|
|
|
if (value === "") { |
|
|
|
callback(new Error("请再次输入密码")); |
|
|
|
} else if (value !== regForm.value.password) { |
|
|
|
callback(new Error('两次输入的密码不一致')); |
|
|
|
callback(new Error("两次输入的密码不一致")); |
|
|
|
} else { |
|
|
|
callback(); |
|
|
|
} |
|
|
|
}, trigger: 'blur' } |
|
|
|
}, |
|
|
|
trigger: "blur", |
|
|
|
}, |
|
|
|
], |
|
|
|
email: [ |
|
|
|
{ required: true, message: '邮箱不能为空', trigger: 'blur' }, |
|
|
|
{ type: 'email', message: '请输入有效的邮箱地址', trigger: ['blur', 'change'] } |
|
|
|
{ required: true, message: "邮箱不能为空", trigger: "blur" }, |
|
|
|
{ |
|
|
|
type: "email", |
|
|
|
message: "请输入有效的邮箱地址", |
|
|
|
trigger: ["blur", "change"], |
|
|
|
}, |
|
|
|
], |
|
|
|
phone: [ |
|
|
|
{ required: true, message: '手机号不能为空', trigger: 'blur' }, |
|
|
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入有效的手机号', trigger: 'blur' } |
|
|
|
{ required: true, message: "手机号不能为空", trigger: "blur" }, |
|
|
|
{ |
|
|
|
pattern: /^1[3-9]\d{9}$/, |
|
|
|
message: "请输入有效的手机号", |
|
|
|
trigger: "blur", |
|
|
|
}, |
|
|
|
], |
|
|
|
nickname: [ |
|
|
|
{ required: true, message: '昵称不能为空', trigger: 'blur' }, |
|
|
|
{ min: 2, max: 20, message: '昵称长度应在2到20个字符之间', trigger: 'blur' } |
|
|
|
] |
|
|
|
}; |
|
|
|
{ required: true, message: "昵称不能为空", trigger: "blur" }, |
|
|
|
{ |
|
|
|
min: 2, |
|
|
|
max: 20, |
|
|
|
message: "昵称长度应在2到20个字符之间", |
|
|
|
trigger: "blur", |
|
|
|
}, |
|
|
|
], |
|
|
|
}; |
|
|
|
|
|
|
|
async function onRegister() { |
|
|
|
const onRegister = async () => { |
|
|
|
try { |
|
|
|
await regFormRef.value.validate(); |
|
|
|
const save = toRaw(regForm.value); |
|
|
|
const userInfo = config.userInfo; |
|
|
|
const comp = await fetch(userInfo.url + '/member/register', { |
|
|
|
method: 'POST', |
|
|
|
const comp = await fetch(userInfo.url + "/member/register", { |
|
|
|
method: "POST", |
|
|
|
body: JSON.stringify(save), |
|
|
|
}); |
|
|
|
if(!comp.ok){ |
|
|
|
notifyError('网络错误,注册失败'); |
|
|
|
return |
|
|
|
if (!comp.ok) { |
|
|
|
notifyError("网络错误,注册失败"); |
|
|
|
return; |
|
|
|
} |
|
|
|
const res = await comp.json(); |
|
|
|
if(res.success){ |
|
|
|
notifyError('注册成功'); |
|
|
|
if (res.success) { |
|
|
|
notifyError("注册成功"); |
|
|
|
toggleRegister(); |
|
|
|
}else{ |
|
|
|
} else { |
|
|
|
notifyError(res.message); |
|
|
|
return |
|
|
|
return; |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error(error); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const onThirdPartyLogin = async (platform: string) => { |
|
|
|
let loginFunction: (() => Promise<boolean>) | undefined; |
|
|
|
// 当前选择的第三方登录方式 |
|
|
|
store.ThirdPartyPlatform = platform; |
|
|
|
switch (platform) { |
|
|
|
case "github": |
|
|
|
// 跳转到 GitHub 授权页面 |
|
|
|
loginFunction = async function () { |
|
|
|
return await authWithGithub(); |
|
|
|
}; |
|
|
|
break; |
|
|
|
case "wechat": |
|
|
|
loginFunction = async function () { |
|
|
|
return await authWithWechat(); |
|
|
|
}; |
|
|
|
break; |
|
|
|
case "qq": |
|
|
|
loginFunction = async function () { |
|
|
|
return await authWithQQ(); |
|
|
|
}; |
|
|
|
break; |
|
|
|
case "sina": |
|
|
|
loginFunction = async function () { |
|
|
|
return await authWithSina(); |
|
|
|
}; |
|
|
|
break; |
|
|
|
case "gitee": |
|
|
|
loginFunction = async function () { |
|
|
|
return await authWithGithub(); |
|
|
|
}; |
|
|
|
break; |
|
|
|
default: |
|
|
|
notifyError("不支持的第三方登录平台"); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
if (loginFunction) { |
|
|
|
const success = await loginFunction(); |
|
|
|
if (success) { |
|
|
|
loginSuccess(); |
|
|
|
} else { |
|
|
|
notifyError("登录失败"); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const authWithGithub = async (): Promise<boolean> => { |
|
|
|
// 传递state用于防止CSRF攻击,使用时间戳加随机字符串 |
|
|
|
const state = Date.now() + Math.random().toString(36).substring(2, 15); |
|
|
|
// 存储到本地 |
|
|
|
store.State = state; |
|
|
|
const url = config.userInfo.url + "/github/authorize?state=" + state; |
|
|
|
const res: any = await fetch(url, { |
|
|
|
method: "POST", |
|
|
|
body: JSON.stringify({ |
|
|
|
state: state, |
|
|
|
}), |
|
|
|
}); |
|
|
|
if (!res.ok) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
const data = await res.json(); |
|
|
|
console.log(data.data.url); |
|
|
|
window.location.href = data.data.url; |
|
|
|
return true; |
|
|
|
}; |
|
|
|
|
|
|
|
const authWithWechat = (): boolean | PromiseLike<boolean> => { |
|
|
|
throw new Error("Function not implemented."); |
|
|
|
}; |
|
|
|
|
|
|
|
const authWithQQ = (): boolean | PromiseLike<boolean> => { |
|
|
|
throw new Error("Function not implemented."); |
|
|
|
}; |
|
|
|
|
|
|
|
const authWithSina = (): boolean | PromiseLike<boolean> => { |
|
|
|
throw new Error("Function not implemented."); |
|
|
|
}; |
|
|
|
</script> |
|
|
|
|
|
|
|
<style scoped lang="scss"> |
|
|
|
.lockscreen { |
|
|
|
.lockscreen { |
|
|
|
position: absolute; |
|
|
|
top: 0; |
|
|
|
right: 0; |
|
|
@ -230,7 +470,7 @@ async function onRegister() { |
|
|
|
background-color: rgba(25, 28, 34, 0.78); |
|
|
|
backdrop-filter: blur(7px); |
|
|
|
|
|
|
|
.login-box{ |
|
|
|
.login-box { |
|
|
|
width: 300px; |
|
|
|
padding: 20px; |
|
|
|
text-align: center; |
|
|
@ -295,9 +535,50 @@ async function onRegister() { |
|
|
|
.screen-hide { |
|
|
|
display: none; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@keyframes outan { |
|
|
|
.third-party-login { |
|
|
|
margin-top: 10px; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
gap: 15px; |
|
|
|
|
|
|
|
el-icon { |
|
|
|
font-size: 24px; |
|
|
|
cursor: pointer; |
|
|
|
transition: color 0.3s ease; |
|
|
|
|
|
|
|
&:hover { |
|
|
|
color: #409eff; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.divider { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
text-align: center; |
|
|
|
margin: 20px 0; |
|
|
|
color: #999; |
|
|
|
font-size: 14px; |
|
|
|
|
|
|
|
&::before, |
|
|
|
&::after { |
|
|
|
content: ""; |
|
|
|
flex: 1; |
|
|
|
border-bottom: 1px solid #ddd; |
|
|
|
} |
|
|
|
|
|
|
|
&::before { |
|
|
|
margin-right: 0.25em; |
|
|
|
} |
|
|
|
|
|
|
|
&::after { |
|
|
|
margin-left: 0.25em; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@keyframes outan { |
|
|
|
0% { |
|
|
|
opacity: 1; |
|
|
|
} |
|
|
@ -310,5 +591,5 @@ async function onRegister() { |
|
|
|
transform: translateY(-100%); |
|
|
|
opacity: 0; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
</style> |