mirror of
https://github.com/kuaifan/dootask.git
synced 2026-03-17 11:13:26 +00:00
feat: 二维码登录
This commit is contained in:
parent
a3c0decaf0
commit
49ac519a5e
@ -144,6 +144,40 @@ class UsersController extends AbstractController
|
|||||||
return Base::retSuccess($type == 'reg' ? "注册成功" : "登录成功", $user);
|
return Base::retSuccess($type == 'reg' ? "注册成功" : "登录成功", $user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} api/users/login/qrcode 02. 二维码登录
|
||||||
|
*
|
||||||
|
* @apiDescription 通过二维码code登录(或:是否登录成功)
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup users
|
||||||
|
* @apiName login__qrcode
|
||||||
|
*
|
||||||
|
* @apiParam {String} type 类型
|
||||||
|
* - login: 登录(用于:app登录)
|
||||||
|
* - status: 状态 (默认,用于:网页、客户端获取)
|
||||||
|
* @apiParam {String} code 二维码 code
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1需要、0不需要)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function login__qrcode()
|
||||||
|
{
|
||||||
|
$type = trim(Request::input('type'));
|
||||||
|
$code = trim(Request::input('code'));
|
||||||
|
//
|
||||||
|
if (strlen($code) < 32) {
|
||||||
|
return Base::retError("参数错误");
|
||||||
|
}
|
||||||
|
if ($type === 'login') {
|
||||||
|
$user = User::auth();
|
||||||
|
Cache::put("User::qrcode:" . $code, $user->userid, Carbon::now()->addMinute());
|
||||||
|
return Base::retSuccess("扫码成功");
|
||||||
|
}
|
||||||
|
// todo 登录成功
|
||||||
|
return Base::retError("No identity");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} api/users/login/needcode 02. 是否需要验证码
|
* @api {get} api/users/login/needcode 02. 是否需要验证码
|
||||||
*
|
*
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
"url": "git+https://github.com/kuaifan/dootask.git"
|
"url": "git+https://github.com/kuaifan/dootask.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@chenfengyuan/vue-qrcode": "^1.0.2",
|
||||||
"axios": "^0.24.0",
|
"axios": "^0.24.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^6.7.2",
|
"css-loader": "^6.7.2",
|
||||||
|
|||||||
@ -4,12 +4,28 @@
|
|||||||
<div class="login-body">
|
<div class="login-body">
|
||||||
<div class="login-logo no-dark-content" :class="{'can-click':needStartHome}" @click="goHome"></div>
|
<div class="login-logo no-dark-content" :class="{'can-click':needStartHome}" @click="goHome"></div>
|
||||||
<div class="login-box">
|
<div class="login-box">
|
||||||
|
<div class="login-mode-switch">
|
||||||
|
<div class="login-mode-switch-box">
|
||||||
|
<ETooltip :disabled="windowSmall || $isEEUiApp" :content="$L(loginMode=='qrcode' ? '帐号登录' : '扫码登录')" placement="left">
|
||||||
|
<span class="login-mode-switch-icon" @click="switchLoginMode">
|
||||||
|
<svg v-if="loginMode=='qrcode'" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" data-icon="PcOutlined"><path d="M23 16a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h18a2 2 0 0 1 2 2v12ZM21 4H3v9h18V4ZM3 15v1h18v-1H3Zm3 6a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H7a1 1 0 0 1-1-1Z" fill="currentColor"></path></svg>
|
||||||
|
<svg v-else viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" data-icon="QrOutlined"><path d="M6.5 7.5a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1v-1Z" fill="currentColor"></path><path d="M4.5 2.5c-1.1 0-2 .9-2 2v7c0 1.1.9 2 2 2h7c1.1 0 2-.9 2-2v-7c0-1.1-.9-2-2-2h-7Zm0 2h7v7h-7v-7ZM11 16a1 1 0 1 1 2 0 1 1 0 0 1-2 0Zm0 3.5a1 1 0 1 1 2 0v1a1 1 0 1 1-2 0v-1Zm4-7.5a1 1 0 1 1 2 0 1 1 0 0 1-2 0Zm3.5 0a1 1 0 0 1 1-1h1a1 1 0 1 1 0 2h-1a1 1 0 0 1-1-1ZM15 17c0-1.1.9-2 2-2h2.5c1.1 0 2 .9 2 2v2.5c0 1.1-.9 2-2 2H17c-1.1 0-2-.9-2-2V17Zm4.5 0H17v2.5h2.5V17Zm-15-2c-1.1 0-2 .9-2 2v2.5c0 1.1.9 2 2 2H7c1.1 0 2-.9 2-2V17c0-1.1-.9-2-2-2H4.5Zm0 2H7v2.5H4.5V17ZM15 4.5c0-1.1.9-2 2-2h2.5c1.1 0 2 .9 2 2V7c0 1.1-.9 2-2 2H17c-1.1 0-2-.9-2-2V4.5Zm4.5 0H17V7h2.5V4.5Z" fill="currentColor"></path></svg>
|
||||||
|
</span>
|
||||||
|
</ETooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="login-title">{{welcomeTitle}}</div>
|
<div class="login-title">{{welcomeTitle}}</div>
|
||||||
|
|
||||||
<div v-if="loginType=='reg'" class="login-subtitle">{{$L('输入您的信息以创建帐户。')}}</div>
|
<div class="login-subtitle">{{$L(subTitle)}}</div>
|
||||||
<div v-else class="login-subtitle">{{$L('输入您的凭证以访问您的帐户。')}}</div>
|
|
||||||
|
|
||||||
<div class="login-input">
|
<transition name="login-mode">
|
||||||
|
<div v-if="loginMode=='qrcode'" class="login-qrcode" @click="qrcodeRefresh">
|
||||||
|
<VueQrcode :value="qrcodeUrl" :options="{width:200,margin:0}"></VueQrcode>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<transition name="login-mode">
|
||||||
|
<div v-if="loginMode=='access'" class="login-access">
|
||||||
<Input
|
<Input
|
||||||
v-if="isSoftware && cacheServerUrl"
|
v-if="isSoftware && cacheServerUrl"
|
||||||
:value="$A.getDomain(cacheServerUrl)"
|
:value="$A.getDomain(cacheServerUrl)"
|
||||||
@ -80,6 +96,7 @@
|
|||||||
<div v-if="loginType=='reg'" class="login-switch">{{$L('已经有帐号?')}}<a href="javascript:void(0)" @click="loginType='login'">{{$L('登录帐号')}}</a></div>
|
<div v-if="loginType=='reg'" class="login-switch">{{$L('已经有帐号?')}}<a href="javascript:void(0)" @click="loginType='login'">{{$L('登录帐号')}}</a></div>
|
||||||
<div v-else class="login-switch">{{$L('还没有帐号?')}}<a href="javascript:void(0)" @click="loginType='reg'">{{$L('注册帐号')}}</a></div>
|
<div v-else class="login-switch">{{$L('还没有帐号?')}}<a href="javascript:void(0)" @click="loginType='reg'">{{$L('注册帐号')}}</a></div>
|
||||||
</div>
|
</div>
|
||||||
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-bottom">
|
<div class="login-bottom">
|
||||||
<Dropdown trigger="click" placement="bottom-start">
|
<Dropdown trigger="click" placement="bottom-start">
|
||||||
@ -145,8 +162,10 @@
|
|||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
import {Store} from "le5le-store";
|
import {Store} from "le5le-store";
|
||||||
import {languageList, languageType, setLanguage} from "../language";
|
import {languageList, languageType, setLanguage} from "../language";
|
||||||
|
import VueQrcode from "@chenfengyuan/vue-qrcode";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {VueQrcode},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loadIng: 0,
|
loadIng: 0,
|
||||||
@ -154,9 +173,14 @@ export default {
|
|||||||
languageList,
|
languageList,
|
||||||
languageType,
|
languageType,
|
||||||
|
|
||||||
|
qrcodeVal: '',
|
||||||
|
qrcodeTimer: null,
|
||||||
|
qrcodeLoad: false,
|
||||||
|
|
||||||
codeNeed: false,
|
codeNeed: false,
|
||||||
codeUrl: $A.apiUrl('users/login/codeimg?_=' + Math.random()),
|
codeUrl: $A.apiUrl('users/login/codeimg?_=' + Math.random()),
|
||||||
|
|
||||||
|
loginMode: 'access',
|
||||||
loginType: 'login',
|
loginType: 'login',
|
||||||
loginJump: false,
|
loginJump: false,
|
||||||
|
|
||||||
@ -189,12 +213,15 @@ export default {
|
|||||||
this.setServerUrl('').catch(_ => {});
|
this.setServerUrl('').catch(_ => {});
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
this.qrcodeTimer = setInterval(this.qrcodeStatus, 2000);
|
||||||
|
//
|
||||||
this.subscribe = Store.subscribe('useSSOLogin', () => {
|
this.subscribe = Store.subscribe('useSSOLogin', () => {
|
||||||
this.inputServerUrl();
|
this.inputServerUrl();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
clearInterval(this.qrcodeTimer);
|
||||||
if (this.subscribe) {
|
if (this.subscribe) {
|
||||||
this.subscribe.unsubscribe();
|
this.subscribe.unsubscribe();
|
||||||
this.subscribe = null;
|
this.subscribe = null;
|
||||||
@ -234,17 +261,35 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
welcomeTitle() {
|
welcomeTitle() {
|
||||||
let title = window.systemInfo.title || "DooTask";
|
if (this.loginMode == 'qrcode') {
|
||||||
|
return this.$L("扫码登录")
|
||||||
|
}
|
||||||
|
const title = window.systemInfo.title || "DooTask";
|
||||||
return "Welcome " + title
|
return "Welcome " + title
|
||||||
},
|
},
|
||||||
|
|
||||||
|
subTitle() {
|
||||||
|
const title = window.systemInfo.title || "DooTask";
|
||||||
|
if (this.loginMode == 'qrcode') {
|
||||||
|
return this.$L(`请使用${title}移动端扫描二维码。`)
|
||||||
|
}
|
||||||
|
if (this.loginType=='reg') {
|
||||||
|
return this.$L(`输入您的信息以创建帐户。`)
|
||||||
|
}
|
||||||
|
return this.$L(`输入您的凭证以访问您的帐户。`)
|
||||||
|
},
|
||||||
|
|
||||||
loginText() {
|
loginText() {
|
||||||
let text = this.loginType == 'login' ? '登录' : '注册';
|
let text = this.loginType == 'login' ? '登录' : '注册';
|
||||||
if (this.loginJump) {
|
if (this.loginJump) {
|
||||||
text += "成功..."
|
text += "成功..."
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
}
|
},
|
||||||
|
|
||||||
|
qrcodeUrl() {
|
||||||
|
return $A.apiUrl('../login?qrcode=' + this.qrcodeVal)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@ -255,11 +300,14 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
loginMode() {
|
||||||
|
this.qrcodeRefresh()
|
||||||
|
},
|
||||||
loginType(val) {
|
loginType(val) {
|
||||||
if (val == 'reg') {
|
if (val == 'reg') {
|
||||||
this.getNeedInvite();
|
this.getNeedInvite();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@ -312,6 +360,36 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
switchLoginMode() {
|
||||||
|
this.chackServerUrl(true).then(() => {
|
||||||
|
if (this.loginMode === 'qrcode') {
|
||||||
|
this.loginMode = 'access'
|
||||||
|
} else {
|
||||||
|
this.loginMode = 'qrcode'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
qrcodeRefresh() {
|
||||||
|
if (this.loginMode == 'qrcode') {
|
||||||
|
this.qrcodeVal = $A.randomString(32)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
qrcodeStatus() {
|
||||||
|
if (this.qrcodeLoad || this.loginMode != 'qrcode') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.qrcodeLoad = true
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'users/login/qrcode?code=' + this.qrcodeVal,
|
||||||
|
}).then(({data}) => {
|
||||||
|
this.$store.dispatch("handleClearCache", data).then(this.goNext);
|
||||||
|
}).finally(_ => {
|
||||||
|
this.qrcodeLoad = false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
forgotPassword() {
|
forgotPassword() {
|
||||||
$A.modalWarning("请联系管理员!");
|
$A.modalWarning("请联系管理员!");
|
||||||
},
|
},
|
||||||
@ -461,11 +539,7 @@ export default {
|
|||||||
}).then(({data}) => {
|
}).then(({data}) => {
|
||||||
$A.IDBSave("cacheLoginEmail", this.email)
|
$A.IDBSave("cacheLoginEmail", this.email)
|
||||||
this.codeNeed = false;
|
this.codeNeed = false;
|
||||||
this.$store.dispatch("handleClearCache", data).then(() => {
|
this.$store.dispatch("handleClearCache", data).then(this.goNext);
|
||||||
this.goNext();
|
|
||||||
}).catch(_ => {
|
|
||||||
this.goNext();
|
|
||||||
});
|
|
||||||
}).catch(({data, msg}) => {
|
}).catch(({data, msg}) => {
|
||||||
if (data.code === 'email') {
|
if (data.code === 'email') {
|
||||||
$A.modalWarning(msg);
|
$A.modalWarning(msg);
|
||||||
|
|||||||
@ -729,8 +729,6 @@ export default {
|
|||||||
this.$store.dispatch("handleClearCache", null).then(async () => {
|
this.$store.dispatch("handleClearCache", null).then(async () => {
|
||||||
await $A.IDBSet("clearCache", $A.randomString(6))
|
await $A.IDBSet("clearCache", $A.randomString(6))
|
||||||
$A.reloadUrl()
|
$A.reloadUrl()
|
||||||
}).catch(() => {
|
|
||||||
$A.reloadUrl()
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
case 'logout':
|
case 'logout':
|
||||||
|
|||||||
@ -141,8 +141,6 @@ export default {
|
|||||||
this.$store.dispatch("handleClearCache", null).then(async () => {
|
this.$store.dispatch("handleClearCache", null).then(async () => {
|
||||||
await $A.IDBSet("clearCache", $A.randomString(6))
|
await $A.IDBSet("clearCache", $A.randomString(6))
|
||||||
$A.reloadUrl()
|
$A.reloadUrl()
|
||||||
}).catch(() => {
|
|
||||||
$A.reloadUrl()
|
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
16
resources/assets/sass/pages/common.scss
vendored
16
resources/assets/sass/pages/common.scss
vendored
@ -698,3 +698,19 @@ body {
|
|||||||
50% { transform: translate3d(-4px, 0, 0); }
|
50% { transform: translate3d(-4px, 0, 0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*登录右侧滑入*/
|
||||||
|
.login-mode-enter-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-mode-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-mode-enter,
|
||||||
|
.login-mode-leave-to {
|
||||||
|
transform: translate(100%, 0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|||||||
69
resources/assets/sass/pages/page-login.scss
vendored
69
resources/assets/sass/pages/page-login.scss
vendored
@ -27,17 +27,58 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.login-box {
|
.login-box {
|
||||||
|
position: relative;
|
||||||
margin-top: 36px;
|
margin-top: 36px;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
box-shadow: 0 0 10px #e6ecfa;
|
box-shadow: 0 0 10px #e6ecfa;
|
||||||
|
overflow: hidden;
|
||||||
|
.login-mode-switch {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
z-index: 1;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
.login-mode-switch-box {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
transform: translate(40px,-40px) rotate(45deg);
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: rgba($primary-color, 0.8);
|
||||||
|
transition: background-color .3s;
|
||||||
|
overflow: hidden;
|
||||||
|
&:hover {
|
||||||
|
background-color: $primary-color;
|
||||||
|
}
|
||||||
|
.login-mode-switch-icon {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 32px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
color: #ffffff;
|
||||||
|
bottom: -20px;
|
||||||
|
left: 16px;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
> svg {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin-left: 13px;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.login-title {
|
.login-title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 36px;
|
margin-top: 46px;
|
||||||
}
|
}
|
||||||
.login-subtitle {
|
.login-subtitle {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@ -46,8 +87,14 @@
|
|||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
color: #AAAAAA;
|
color: #AAAAAA;
|
||||||
}
|
}
|
||||||
.login-input {
|
.login-qrcode {
|
||||||
margin: 32px 40px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 52px auto 49px;
|
||||||
|
}
|
||||||
|
.login-access {
|
||||||
|
margin: 26px 40px 30px;
|
||||||
> * {
|
> * {
|
||||||
margin-top: 26px;
|
margin-top: 26px;
|
||||||
}
|
}
|
||||||
@ -90,16 +137,6 @@
|
|||||||
.login-switch {
|
.login-switch {
|
||||||
color: #aaaaaa;
|
color: #aaaaaa;
|
||||||
}
|
}
|
||||||
.login-input-tips-box{
|
|
||||||
position: relative;
|
|
||||||
.login-input-tips{
|
|
||||||
font-size: 12px;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: -20px;
|
|
||||||
color: #c7c7c7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .ivu-poptip {
|
> .ivu-poptip {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
> .ivu-poptip-rel {
|
> .ivu-poptip-rel {
|
||||||
@ -179,6 +216,10 @@
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
|
.login-mode-switch {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.login-title {
|
.login-title {
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
}
|
}
|
||||||
@ -187,7 +228,7 @@
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-input {
|
.login-access {
|
||||||
margin: 20px 36px;
|
margin: 20px 36px;
|
||||||
|
|
||||||
.ivu-input-large {
|
.ivu-input-large {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user