新增手机号登录ui

This commit is contained in:
不做码农 2023-09-07 18:42:24 +08:00
parent aef0b436c4
commit 740061502e
13 changed files with 254 additions and 98 deletions

View File

@ -15,22 +15,23 @@
background: #ffffff; background: #ffffff;
border-radius: 6px; border-radius: 6px;
width: var(--base-login-width); width: var(--base-login-width);
position: relative;
} }
} }
.title { .title {
margin: 0px auto 30px auto; margin: 10px auto 30px auto;
text-align: center; text-align: center;
// color: #fff; // color: #fff;
} }
.login-form { .login-form {
padding: 35px 15px 5px 15px; padding: 15px 25px 5px 25px;
position: relative; position: relative;
height: 300px; height: 210px;
.input-icon { .input-icon {
height: 39px; height: 30px;
width: 14px; width: 14px;
margin-left: 0px; margin-left: 0px;
} }
@ -115,3 +116,6 @@
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
} }
@media screen and (max-width: 500px) {
}

View File

@ -20,7 +20,7 @@ $panGreen: #30b08f;
--base-tags-height: 34px; --base-tags-height: 34px;
--base-header-height: 50px; --base-header-height: 50px;
//登录框宽度 //登录框宽度
--base-login-width: 320px; --base-login-width: 360px;
} }
/***侧边栏深色配置***/ /***侧边栏深色配置***/

View File

@ -7,17 +7,17 @@ export default defineComponent({
name: { name: {
type: String, type: String,
required: true, required: true,
default: '', default: ''
}, },
className: { className: {
type: String, type: String,
default: '', default: ''
}, },
// svg // svg
color: { color: {
type: String, type: String,
default: '', default: ''
}, }
}, },
setup(props) { setup(props) {
if (props.name?.startsWith('ele')) { if (props.name?.startsWith('ele')) {
@ -25,9 +25,9 @@ export default defineComponent({
h( h(
'i', 'i',
{ {
class: 'el-icon', class: 'el-icon'
}, },
[h(resolveComponent(props.name.replace('ele-', '')))], [h(resolveComponent(props.name.replace('ele-', '')))]
) )
} else if (props.name != undefined && props.name != '') { } else if (props.name != undefined && props.name != '') {
return () => return () =>
@ -38,16 +38,17 @@ export default defineComponent({
'aria-hidden': true, 'aria-hidden': true,
style: `color: ${props.color}`, style: `color: ${props.color}`,
class: `svg-icon ${props.className}`, class: `svg-icon ${props.className}`,
'shape-rendering': 'geometricPrecision'
}, },
h('use', { h('use', {
'xlink:href': `#icon-${props.name}`, 'xlink:href': `#icon-${props.name}`,
fill: `${props.color}`, fill: `${props.color}`
}), })
) )
} else { } else {
return () => h('i') return () => h('i')
} }
}, }
}) })
</script> </script>

View File

@ -90,7 +90,7 @@
"topNav": "顶部导航", "topNav": "顶部导航",
"commonFuncs": "常用功能", "commonFuncs": "常用功能",
"openWatermark": "开启水印", "openWatermark": "开启水印",
"workTime": "今日工作时长(分)", "workTime": "今日工作时长",
"onlineClientNum": "在线设备数" "onlineClientNum": "在线设备数"
}, },
"common": { "common": {

View File

@ -11,6 +11,13 @@
"invalidSession": "Invalid session, or session has expired, please log in again.", "invalidSession": "Invalid session, or session has expired, please log in again.",
"otherLoginWay": "Other", "otherLoginWay": "Other",
"register": "Sign up now", "register": "Sign up now",
"forgotPwd": "forget" "forgotPwd": "forget",
"loginway1": "account",
"loginway2": "Phone",
"loginway3": "Scan code",
"phoneCode": "Please enter SMS verification code",
"sendPhoneCode": "Send",
"input_phoneNum": "Please enter phone number",
"tip_scan_code": "Please use the mobile app to scan the code to log in"
} }
} }

View File

@ -11,6 +11,13 @@
"invalidSession": "无效的会话,或者会话已过期,请重新登录。", "invalidSession": "无效的会话,或者会话已过期,请重新登录。",
"otherLoginWay": "其他登录方式", "otherLoginWay": "其他登录方式",
"register": "立即注册", "register": "立即注册",
"forgotPwd": "忘记密码" "forgotPwd": "忘记密码",
"phoneCode": "请输入短信验证码",
"sendPhoneCode": "发送验证码",
"loginway1": "账号密码",
"loginway2": "手机号",
"loginway3": "扫码登录",
"input_phoneNum": "请输入手机号",
"tip_scan_code": "请使用移动端app扫码登录"
} }
} }

View File

@ -11,6 +11,13 @@
"invalidSession": "無效的會話,或者會話已過期,請重新登錄。", "invalidSession": "無效的會話,或者會話已過期,請重新登錄。",
"otherLoginWay": "其他登錄方式", "otherLoginWay": "其他登錄方式",
"register": "註冊", "register": "註冊",
"forgotPwd": "忘記密碼" "forgotPwd": "忘記密碼",
"loginway1": "賬號密碼",
"loginway2": "手機號",
"loginway3": "掃碼登錄",
"phoneCode": "請輸入短信驗證碼",
"sendPhoneCode": "發送驗證碼",
"input_phoneNum": "請輸入手機號",
"tip_scan_code": "請使用移動端app掃碼登錄"
} }
} }

View File

@ -71,13 +71,13 @@ export default {
/** /**
* 是否显示其他登录 * 是否显示其他登录
*/ */
showOtherLogin: false, showOtherLogin: true,
/** /**
* 默认大小 * 默认大小
*/ */
defaultSize: 'default', defaultSize: 'default',
/** /**
* 默认语言 * 默认语言
*/ */
defaultLang: 'zh-cn' defaultLang: 'zh-cn'
} }

View File

@ -0,0 +1,43 @@
<template>
<div class="other-login" v-if="defaultSettings.showOtherLogin">
<div class="other-tip">{{ $t('login.otherLoginWay') }}</div>
<span @click="onAuth('GITHUB')" title="github"><svg-icon name="github" className="login-icon"></svg-icon></span>
<span @click="onAuth('GITEE')" title="gitee"><svg-icon name="gitee" className="login-icon"></svg-icon></span>
</div>
</template>
<script setup>
import defaultSettings from '@/settings'
import useUserStore from '@/store/modules/user'
const userStore = useUserStore()
const { proxy } = getCurrentInstance()
function onAuth(type) {
userStore.setAuthSource(type)
switch (type) {
default:
// window.location.href = import.meta.env.VITE_APP_BASE_API + '/auth/Authorization?authSource=' + type
proxy.$modal.msg('请看文档怎么接入')
break
}
}
</script>
<style lang="scss" scoped>
.other-login {
padding: 0px 20px 10px;
.other-tip {
text-align: center;
color: #ccc;
font-size: 13px;
margin-top: 10px;
}
.login-icon {
width: 25px;
height: 25px;
margin-right: 20px;
cursor: pointer;
}
}
</style>

View File

@ -0,0 +1,106 @@
<template>
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form" v-show="loginType == 1">
<el-form-item prop="phoneNum">
<el-input v-model="loginForm.phoneNum" type="phone" :maxlength="11" auto-complete="off" :placeholder="$t('login.input_phoneNum')">
<template #prefix>
<svg-icon name="phone" class="input-icon" />
</template>
</el-input>
</el-form-item>
<el-form-item prop="phoneCode">
<el-input v-model="loginForm.phoneCode" type="number" auto-complete="off" :placeholder="$t('login.phoneCode')" @keyup.enter="handleLogin">
<template #prefix>
<svg-icon name="validCode" class="input-icon" />
</template>
<template #append>
<el-button @click="handleSendCode" v-if="!showCounddown">{{ $t('login.sendPhoneCode') }}</el-button>
<el-countdown :value="countdownValue" format="mm:ss" @finish="handleFinish" v-else />
</template>
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaOnOff != 'off'">
<el-input v-model="loginForm.code" auto-complete="off" :placeholder="$t('login.captcha')" style="width: 63%" @keyup.enter="handleLogin">
<template #prefix>
<svg-icon name="validCode" class="input-icon" />
</template>
</el-input>
<div class="login-code">
<el-image :src="codeUrl" @click="getCode" class="login-code-img" />
</div>
</el-form-item>
<el-form-item style="width: 100%" :style="{ 'margin-top': captchaOnOff == 'off' ? '40px' : '' }">
<el-button :loading="loading" size="default" round type="primary" style="width: 100%" @click.prevent="handleLogin">
<span v-if="!loading">{{ $t('login.btnLogin') }}</span>
<span v-else> 中...</span>
</el-button>
</el-form-item>
</el-form>
</template>
<script setup name="phonelogin">
import { getCodeImg } from '@/api/system/login'
import useUserStore from '@/store/modules/user'
const route = useRoute()
const { proxy } = getCurrentInstance()
const loginForm = ref({
password: '',
rememberMe: false,
code: '',
uuid: '',
phoneCode: '',
phoneNum: ''
})
const loginRules = {
phoneNum: [{ required: true, trigger: 'blur', message: '请输入手机号码', pattern: /^1\d{10}$/ }],
phoneCode: [{ required: true, trigger: 'blur', message: '请输入短信验证码' }],
code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
}
const loginType = ref(1)
const codeUrl = ref('')
const loading = ref(false)
//
const captchaOnOff = ref('')
const redirect = ref()
redirect.value = route.query.redirect
function handleLogin() {
proxy.$refs.loginRef.validate((valid) => {
if (valid) {
loading.value = true
// cookie
proxy.$modal.msg('敬请期待')
}
})
}
function getCode() {
getCodeImg().then((res) => {
codeUrl.value = 'data:image/gif;base64,' + res.data.img
loginForm.value.uuid = res.data.uuid
captchaOnOff.value = res.data.captchaOff
})
}
const showCounddown = ref(false)
const countdownValue = ref(0)
function handleSendCode() {
showCounddown.value = true
countdownValue.value = Date.now() + 1000 * 60
}
function handleFinish() {
showCounddown.value = false
}
getCode()
</script>
<style lang="scss" scoped>
@import '@/assets/styles/login.scss';
.forget-pwd {
color: #ccc;
margin-right: 10px;
cursor: pointer;
}
</style>

View File

@ -31,7 +31,7 @@
<el-card style="height: 100%"> <el-card style="height: 100%">
<div class="text-warning mb10">{{ currentTime }} {{ weekName }}</div> <div class="text-warning mb10">{{ currentTime }} {{ weekName }}</div>
<div class="work-wrap"> <div class="work-wrap">
<el-statistic :title="$t('layout.workTime')" :value="Math.ceil(onlineInfo.todayOnlineTime, 2)" /> <el-statistic :title="$t('layout.workTime')" :formatter="workTimeFormatter" :value="onlineInfo.todayOnlineTime" />
<el-statistic :title="$t('layout.onlineClientNum')" :value="onlineInfo.clientNum" /> <el-statistic :title="$t('layout.onlineClientNum')" :value="onlineInfo.clientNum" />
</div> </div>
</el-card> </el-card>
@ -98,6 +98,10 @@ import PieChart from './dashboard/PieChart'
import BarChart from './dashboard/BarChart' import BarChart from './dashboard/BarChart'
// import WordCloudChat from './dashboard/WordCloud.vue' // import WordCloudChat from './dashboard/WordCloud.vue'
import CommonMenu from './components/CommonMenu' import CommonMenu from './components/CommonMenu'
import dayjs from 'dayjs'
//
import duration from 'dayjs/plugin/duration'
dayjs.extend(duration)
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import useSocketStore from '@/store/modules/socket' import useSocketStore from '@/store/modules/socket'
@ -144,6 +148,9 @@ handleSetLineChartData('newVisitis')
function handleAdd() { function handleAdd() {
proxy.$modal.msg('请通过搜索添加') proxy.$modal.msg('请通过搜索添加')
} }
function workTimeFormatter(val) {
return dayjs.duration(val * 60, 'second').format('HH时mm分')
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -2,14 +2,19 @@
<starBackground></starBackground> <starBackground></starBackground>
<div class="login-wrap"> <div class="login-wrap">
<div class="login"> <div class="login">
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form" v-if="!showQrLogin"> <h3 class="title">{{ defaultSettings.title }}</h3>
<h3 class="title">{{ defaultSettings.title }}</h3>
<div class="scan-wrap" @click="handleShowQrLogin()"> <LangSelect title="多语言设置" class="langSet" />
<svg-icon name="qr" class="icon" />
<div class="scan-delta"></div> <div style="padding: 0 25px 5px 25px">
</div> <el-tabs v-model="loginType" @tab-click="handleLoginType">
<LangSelect title="多语言设置" class="langSet" /> <el-tab-pane :label="$t('login.loginway1')" :name="1"></el-tab-pane>
<el-tab-pane :label="$t('login.loginway2')" :name="2"></el-tab-pane>
<el-tab-pane :label="$t('login.loginway3')" :name="3"></el-tab-pane>
</el-tabs>
</div>
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form" v-show="loginType == 1">
<el-form-item prop="username"> <el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" auto-complete="off" :placeholder="$t('login.account')"> <el-input v-model="loginForm.username" type="text" auto-complete="off" :placeholder="$t('login.account')">
<template #prefix> <template #prefix>
@ -43,34 +48,24 @@
</span> </span>
</div> </div>
<el-form-item style="width: 100%" :style="{ 'margin-top': captchaOnOff == 'off' ? '40px' : '' }"> <el-form-item style="width: 100%" :style="{ 'margin-top': captchaOnOff == 'off' ? '20px' : '' }">
<el-button :loading="loading" size="default" type="primary" style="width: 100%" @click.prevent="handleLogin"> <el-button :loading="loading" size="default" round type="primary" style="width: 100%" @click.prevent="handleLogin">
<span v-if="!loading">{{ $t('login.btnLogin') }}</span> <span v-if="!loading">{{ $t('login.btnLogin') }}</span>
<span v-else> 中...</span> <span v-else> 中...</span>
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<div class="qr-wrap login-form" v-else> <div class="qr-wrap login-form" v-show="loginType == 3">
<h3 class="title">移动端扫码登录</h3>
<div class="scan-wrap" @click="handleShowQrLogin()">
<svg-icon name="pc" class="icon" />
<div class="scan-delta"></div>
</div>
<div class="login-scan-container"> <div class="login-scan-container">
<div ref="imgContainerRef" id="imgContainer" class="qrCode"></div> <div ref="imgContainerRef" id="imgContainer" class="qrCode"></div>
<div class="mt10 text-muted">请使用移动端app扫码登录</div> <div class="mt10 text-muted">{{ $t('login.tip_scan_code') }}</div>
</div> </div>
</div> </div>
<div class="other-login" v-if="defaultSettings.showOtherLogin"> <phoneLogin v-show="loginType == 2"></phoneLogin>
<el-divider>{{ $t('login.otherLoginWay') }}</el-divider> <oauthLogin></oauthLogin>
<span @click="onAuth('GITHUB')" title="github"><svg-icon name="github" className="login-icon"></svg-icon></span>
<span @click="onAuth('GITEE')" title="gitee"><svg-icon name="gitee" className="login-icon"></svg-icon></span>
</div>
</div> </div>
<!-- 底部 -->
<div class="el-login-footer"> <div class="el-login-footer">
<div v-html="defaultSettings.copyright"></div> <div v-html="defaultSettings.copyright"></div>
</div> </div>
@ -87,6 +82,9 @@ import LangSelect from '@/components/LangSelect/index.vue'
import useUserStore from '@/store/modules/user' import useUserStore from '@/store/modules/user'
import QRCode from 'qrcodejs2-fixes' import QRCode from 'qrcodejs2-fixes'
import { verifyScan, generateQrcode } from '@/api/system/login' import { verifyScan, generateQrcode } from '@/api/system/login'
import oauthLogin from './components/Login/oauthLogin.vue'
import phoneLogin from './components/Login/phoneLogin.vue'
var visitorId = '' var visitorId = ''
const fpPromise = import('https://openfpcdn.io/fingerprintjs/v3').then((FingerprintJS) => FingerprintJS.load()) const fpPromise = import('https://openfpcdn.io/fingerprintjs/v3').then((FingerprintJS) => FingerprintJS.load())
@ -108,7 +106,7 @@ const loginRules = {
password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }], password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
code: [{ required: true, trigger: 'change', message: '请输入验证码' }] code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
} }
const loginType = ref(1)
const codeUrl = ref('') const codeUrl = ref('')
const loading = ref(false) const loading = ref(false)
// //
@ -178,15 +176,6 @@ function getCookie() {
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe) rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
} }
} }
function onAuth(type) {
userStore.setAuthSource(type)
switch (type) {
default:
window.location.href = import.meta.env.VITE_APP_BASE_API + '/auth/Authorization?authSource=' + type
break
}
}
function handleForgetPwd() { function handleForgetPwd() {
proxy.$modal.msg('请联系管理员') proxy.$modal.msg('请联系管理员')
} }
@ -194,15 +183,11 @@ function handleForgetPwd() {
const interval = ref(null) const interval = ref(null)
const showQrLogin = ref(false) const showQrLogin = ref(false)
function handleShowQrLogin() { function handleShowQrLogin() {
showQrLogin.value = !showQrLogin.value // showQrLogin.value = !showQrLogin.value
if (showQrLogin.value) { nextTick(() => {
nextTick(() => { generateCode()
generateCode() })
})
} else {
clearQr()
}
} }
// //
function generateCode() { function generateCode() {
@ -259,6 +244,15 @@ function getUuid() {
URL.revokeObjectURL(temp_url) URL.revokeObjectURL(temp_url)
return uuid.substr(uuid.lastIndexOf('/') + 1) return uuid.substr(uuid.lastIndexOf('/') + 1)
} }
function handleLoginType(t) {
const val = t.paneName
if (val == 3) {
handleShowQrLogin()
} else {
clearQr()
}
}
getCode() getCode()
getCookie() getCookie()
</script> </script>
@ -270,15 +264,6 @@ getCookie()
margin-right: 10px; margin-right: 10px;
cursor: pointer; cursor: pointer;
} }
.login-icon {
width: 30px;
height: 30px;
margin-right: 20px;
cursor: pointer;
}
.other-login {
padding: 0px 10px 5px;
}
.qrCode { .qrCode {
width: 160px; width: 160px;
height: 160px; height: 160px;

View File

@ -36,19 +36,20 @@
</div> </div>
</el-form-item> </el-form-item>
<el-form-item style="width: 100%"> <el-form-item style="width: 100%">
<el-button :loading="loading" type="primary" size="default" style="width: 100%" @click.prevent="handleRegister"> <el-button :loading="loading" type="primary" size="default" round style="width: 100%" @click.prevent="handleRegister">
<span v-if="!loading"> </span> <span v-if="!loading">{{ $t('login.register') }}</span>
<span v-else> 中...</span> <span v-else> 中...</span>
</el-button> </el-button>
<div style="text-align: center">
<router-link class="link-type" :to="'/login'">使用已有账户登录</router-link>
</div>
</el-form-item> </el-form-item>
<div style="text-align: center">
<router-link class="link-type" :to="'/login'">使用已有账户登录</router-link>
</div>
</el-form> </el-form>
<oauthLogin></oauthLogin>
</div> </div>
<!-- 底部 --> <!-- 底部 -->
<div class="el-register-footer"> <div class="el-register-footer">
<div v-html="defaultSettings.copyright"></div> <div v-html="copyRight"></div>
</div> </div>
</div> </div>
</template> </template>
@ -58,6 +59,7 @@ import starBackground from '@/views/components/starBackground.vue'
import { getCodeImg, register } from '@/api/system/login' import { getCodeImg, register } from '@/api/system/login'
import defaultSettings from '@/settings' import defaultSettings from '@/settings'
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
import oauthLogin from './components/Login/oauthLogin.vue'
const { proxy } = getCurrentInstance() const { proxy } = getCurrentInstance()
const router = useRouter() const router = useRouter()
const codeUrl = ref('') const codeUrl = ref('')
@ -159,28 +161,15 @@ getCode()
flex-direction: column; flex-direction: column;
background: radial-gradient(220% 105% at top center, #1b2947 10%, #4b76a7 40%, #81acae 65%, #f7f7b6); background: radial-gradient(220% 105% at top center, #1b2947 10%, #4b76a7 40%, #81acae 65%, #f7f7b6);
} }
.login-form {
height: 320px;
}
.title { .title {
margin: 0px auto 30px auto; margin: 0px auto 30px auto;
text-align: center; text-align: center;
// color: #fff; // color: #fff;
} }
.register-form {
background: #fff;
width: var(--base-login-width);
padding: 35px 15px 5px 15px;
.el-input {
height: 38px;
input {
height: 38px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 2px;
}
}
.register-tip { .register-tip {
font-size: 13px; font-size: 13px;
text-align: center; text-align: center;