init: Initialize the basic project.
This commit is contained in:
193
nl-vue/src/views/auth/findPwd/emailFindForm.vue
Normal file
193
nl-vue/src/views/auth/findPwd/emailFindForm.vue
Normal file
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<a-form ref="emailResetFormRef" :model="emailFormData" :rules="formRules">
|
||||
<a-form-item name="email">
|
||||
<a-input v-model:value="emailFormData.email" :placeholder="$t('login.emailPlaceholder')" size="large">
|
||||
<template #prefix>
|
||||
<mail-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="emailValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input
|
||||
v-model:value="emailFormData.emailValidCode"
|
||||
:placeholder="$t('login.emailCodePlaceholder')"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<mail-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<a-button size="large" class="xn-wd" @click="getEmailValidCode" :disabled="state.smsSendBtn">{{
|
||||
(!state.smsSendBtn && $t('login.getSmsCode')) || state.time + ' s'
|
||||
}}</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="newPassword">
|
||||
<a-input-password
|
||||
v-model:value="emailFormData.newPassword"
|
||||
:placeholder="$t('login.newPwdPlaceholder')"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="7">
|
||||
<a-button class="xn-wd" round size="large" href="/login">{{ $t('login.backLogin') }}</a-button>
|
||||
</a-col>
|
||||
<a-col :span="17">
|
||||
<a-button type="primary" class="xn-wd" :loading="islogin" round size="large" @click="submitReset">{{
|
||||
$t('login.restPassword')
|
||||
}}</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:width="400"
|
||||
:title="$t('login.machineValidation')"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<a-form ref="emailLoginFormModalRef" :model="emailFormModalData" :rules="formModalRules">
|
||||
<a-form-item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input
|
||||
v-model:value="emailFormModalData.validCode"
|
||||
:placeholder="$t('login.validLaceholder')"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<verified-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img :src="validCodeBase64" class="xn-findform-line" @click="getPhonePicCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup name="emailFindForm">
|
||||
import { message } from 'ant-design-vue'
|
||||
import router from '@/router'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import smCrypto from '@/utils/smCrypto'
|
||||
const emailResetFormRef = ref()
|
||||
const emailFormData = ref({})
|
||||
const islogin = ref(false)
|
||||
let state = ref({
|
||||
time: 60,
|
||||
smsSendBtn: false
|
||||
})
|
||||
let formRules = ref({})
|
||||
const emailValidCodeReqNo = ref('')
|
||||
|
||||
// 点击获取邮箱验证码
|
||||
const getEmailValidCode = () => {
|
||||
formRules.value.email = [required(), rules.email]
|
||||
delete formRules.value.emailValidCode
|
||||
delete formRules.value.newPassword
|
||||
emailResetFormRef.value.validate().then(() => {
|
||||
// 显示弹框
|
||||
visible.value = true
|
||||
// 获取内部图片验证码
|
||||
getPhonePicCaptcha()
|
||||
})
|
||||
}
|
||||
// 点击找回按钮
|
||||
const submitReset = () => {
|
||||
formRules.value.email = [required(), rules.email]
|
||||
formRules.value.emailValidCode = [required(), rules.number]
|
||||
formRules.value.newPassword = [required()]
|
||||
|
||||
emailResetFormRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
emailFormData.value.validCode = emailFormData.value.emailValidCode
|
||||
emailFormData.value.validCodeReqNo = emailValidCodeReqNo.value
|
||||
emailFormData.value.newPassword = smCrypto.doSm2Encrypt(emailFormData.value.newPassword)
|
||||
islogin.value = true
|
||||
userCenterApi
|
||||
.userFindPasswordByEmail(emailFormData.value)
|
||||
.then(() => {
|
||||
router.replace({
|
||||
path: '/'
|
||||
})
|
||||
message.success('找回成功')
|
||||
})
|
||||
.finally(() => {
|
||||
islogin.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// 弹框的
|
||||
const visible = ref(false)
|
||||
const emailLoginFormModalRef = ref()
|
||||
const emailFormModalData = ref({})
|
||||
const validCodeBase64 = ref('')
|
||||
const formModalRules = {
|
||||
validCode: [required(), rules.lettersNum]
|
||||
}
|
||||
const getPhonePicCaptcha = () => {
|
||||
userCenterApi.userGetPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
emailFormModalData.value.validCodeReqNo = data.validCodeReqNo
|
||||
})
|
||||
}
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
}
|
||||
const handleOk = () => {
|
||||
// 获取到里面的验证码,并发送邮箱
|
||||
emailLoginFormModalRef.value.validate().then(() => {
|
||||
visible.value = false
|
||||
// 发送邮箱,首先拿到刚刚输入的邮箱
|
||||
emailFormModalData.value.email = emailFormData.value.email
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.smsSendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.smsSendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
|
||||
userCenterApi
|
||||
.userFindPasswordGetEmailValidCode(emailFormModalData.value)
|
||||
.then((data) => {
|
||||
emailValidCodeReqNo.value = data
|
||||
visible.value = false
|
||||
setTimeout(hide, 500)
|
||||
})
|
||||
.catch(() => {
|
||||
setTimeout(hide, 100)
|
||||
clearInterval(interval)
|
||||
state.value.smsSendBtn = false
|
||||
})
|
||||
.finally(() => {
|
||||
emailFormModalData.value.validCode = ''
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
257
nl-vue/src/views/auth/findPwd/index.vue
Normal file
257
nl-vue/src/views/auth/findPwd/index.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div class="login-wrapper">
|
||||
<div class="login_background">
|
||||
<div class="logo_background">
|
||||
<a
|
||||
:class="{ 'no-link': !sysBaseConfig.SNOWY_SYS_COPYRIGHT_URL }"
|
||||
:href="sysBaseConfig.SNOWY_SYS_COPYRIGHT_URL"
|
||||
target="_blank"
|
||||
@click="handleLink"
|
||||
>
|
||||
<img :alt="sysBaseConfig.SNOWY_SYS_NAME" :src="sysBaseConfig.SNOWY_SYS_LOGO" />
|
||||
<label>{{ sysBaseConfig.SNOWY_SYS_NAME }}</label>
|
||||
</a>
|
||||
</div>
|
||||
<div class="version">
|
||||
<p>{{ sysBaseConfig.SNOWY_SYS_DEFAULT_DESCRRIPTION }}</p>
|
||||
<p>{{ sysBaseConfig.SNOWY_SYS_COPYRIGHT }} {{ sysBaseConfig.SNOWY_SYS_VERSION }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login_main">
|
||||
<div class="login-form">
|
||||
<a-card>
|
||||
<div class="login-header">
|
||||
<h2>{{ $t('login.forgetPassword') }}</h2>
|
||||
</div>
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="userPhone" :tab="$t('login.restPhoneType')">
|
||||
<phone-find-form />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="userEmail" :tab="$t('login.restEmailType')" force-render>
|
||||
<email-find-form />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import PhoneFindForm from './phoneFindForm.vue'
|
||||
import EmailFindForm from './emailFindForm.vue'
|
||||
import { globalStore } from '@/store'
|
||||
|
||||
const store = globalStore()
|
||||
const activeKey = ref('userPhone')
|
||||
const sysBaseConfig = computed(() => {
|
||||
return store.sysBaseConfig
|
||||
})
|
||||
// logo链接
|
||||
const handleLink = (e) => {
|
||||
if (!sysBaseConfig.value.SNOWY_SYS_COPYRIGHT_URL) {
|
||||
e?.stopPropagation()
|
||||
e?.preventDefault()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.login-wrapper {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
}
|
||||
.login_background {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-image: url(/img/login_background.png);
|
||||
position: relative;
|
||||
}
|
||||
@keyframes myfirst {
|
||||
0% {
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
50% {
|
||||
left: 50px;
|
||||
top: 0px;
|
||||
}
|
||||
100% {
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes myfirst /* Safari and Chrome */ {
|
||||
0% {
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
50% {
|
||||
left: 50px;
|
||||
top: 0px;
|
||||
}
|
||||
100% {
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
}
|
||||
.login_adv__title h2 {
|
||||
font-size: 40px;
|
||||
}
|
||||
.login_adv__title h4 {
|
||||
font-size: 18px;
|
||||
margin-top: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.login_adv__title p {
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
line-height: 1.8;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
.login_adv__title div {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.login_adv__title div span {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.login_adv__title div i {
|
||||
font-size: 40px;
|
||||
}
|
||||
.login_adv__title div i.add {
|
||||
font-size: 20px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
/*background-image:linear-gradient(transparent, #000);*/
|
||||
.login_main {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.login-form {
|
||||
width: 450px;
|
||||
margin-top: 110px;
|
||||
}
|
||||
.login-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.login-header .logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.login-header .logo img {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
vertical-align: bottom;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.login-header .logo label {
|
||||
font-size: 24px;
|
||||
}
|
||||
.login-header h2 {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.login-oauth {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.login_config {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
.logo_background {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50px;
|
||||
height: 60px;
|
||||
padding-left: 56px;
|
||||
width: 100%;
|
||||
background: -webkit-gradient(
|
||||
linear,
|
||||
right top,
|
||||
left top,
|
||||
from(rgba(67, 147, 250, 0.5)),
|
||||
to(rgba(133, 182, 252, 0.5))
|
||||
);
|
||||
background: linear-gradient(120deg, rgb(255 255 255 / 90%), rgba(255, 255, 255, 0));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.logo_background img {
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.logo_background label {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
}
|
||||
.logo_background a {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.logo_background a.no-link,
|
||||
.logo_background a.no-link label {
|
||||
cursor: default;
|
||||
}
|
||||
.logo_background a label {
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.login_background .version {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
font-weight: 300;
|
||||
padding: 0 56px;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
}
|
||||
.login_background .version p {
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.login-form {
|
||||
width: 340px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
.login_main {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.logo_background {
|
||||
padding-left: 40px;
|
||||
}
|
||||
.login-form {
|
||||
width: 100%;
|
||||
padding: 20px 40px;
|
||||
top: 15%;
|
||||
}
|
||||
.login_background .version {
|
||||
padding: 0 20px;
|
||||
}
|
||||
.login_background .version p:first-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
194
nl-vue/src/views/auth/findPwd/phoneFindForm.vue
Normal file
194
nl-vue/src/views/auth/findPwd/phoneFindForm.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<a-form ref="phoneLoginFormRef" :model="phoneFormData" :rules="formRules">
|
||||
<a-form-item name="phone">
|
||||
<a-input v-model:value="phoneFormData.phone" :placeholder="$t('login.phonePlaceholder')" size="large">
|
||||
<template #prefix>
|
||||
<mobile-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="phoneValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input
|
||||
v-model:value="phoneFormData.phoneValidCode"
|
||||
:placeholder="$t('login.smsCodePlaceholder')"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<mail-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<a-button size="large" class="xn-wd" @click="getPhoneValidCode" :disabled="state.smsSendBtn">{{
|
||||
(!state.smsSendBtn && $t('login.getSmsCode')) || state.time + ' s'
|
||||
}}</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item name="newPassword">
|
||||
<a-input-password
|
||||
v-model:value="phoneFormData.newPassword"
|
||||
:placeholder="$t('login.newPwdPlaceholder')"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="7">
|
||||
<a-button class="xn-wd" round size="large" href="/login">{{ $t('login.backLogin') }}</a-button>
|
||||
</a-col>
|
||||
<a-col :span="17">
|
||||
<a-button type="primary" class="xn-wd" :loading="islogin" round size="large" @click="submitReset">{{
|
||||
$t('login.restPassword')
|
||||
}}</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:width="400"
|
||||
:title="$t('login.machineValidation')"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<a-form ref="phoneLoginFormModalRef" :model="phoneFormModalData" :rules="formModalRules">
|
||||
<a-form-item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input
|
||||
v-model:value="phoneFormModalData.validCode"
|
||||
:placeholder="$t('login.validLaceholder')"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<verified-outlined class="xn-color-00025" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img :src="validCodeBase64" class="xn-findform-line" @click="getPhonePicCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup name="phoneFindForm">
|
||||
import { message } from 'ant-design-vue'
|
||||
import { nextTick } from 'vue'
|
||||
import router from '@/router'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import smCrypto from '@/utils/smCrypto'
|
||||
const phoneLoginFormRef = ref()
|
||||
const phoneFormData = ref({})
|
||||
const islogin = ref(false)
|
||||
let state = ref({
|
||||
time: 60,
|
||||
smsSendBtn: false
|
||||
})
|
||||
let formRules = ref({})
|
||||
const phoneValidCodeReqNo = ref('')
|
||||
|
||||
// 点击获取短信验证码
|
||||
const getPhoneValidCode = () => {
|
||||
formRules.value.phone = [required(), rules.phone]
|
||||
delete formRules.value.phoneValidCode
|
||||
delete formRules.value.newPassword
|
||||
phoneLoginFormRef.value.validate().then(() => {
|
||||
// 显示弹框
|
||||
visible.value = true
|
||||
// 获取内部图片验证码
|
||||
getPhonePicCaptcha()
|
||||
})
|
||||
}
|
||||
// 点击找回按钮
|
||||
const submitReset = () => {
|
||||
formRules.value.phone = [required(), rules.phone]
|
||||
formRules.value.phoneValidCode = [required(), rules.number]
|
||||
formRules.value.newPassword = [required()]
|
||||
|
||||
phoneLoginFormRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
phoneFormData.value.validCode = phoneFormData.value.phoneValidCode
|
||||
phoneFormData.value.validCodeReqNo = phoneValidCodeReqNo.value
|
||||
phoneFormData.value.newPassword = smCrypto.doSm2Encrypt(phoneFormData.value.newPassword)
|
||||
islogin.value = true
|
||||
userCenterApi
|
||||
.userFindPasswordByPhone(phoneFormData.value)
|
||||
.then(() => {
|
||||
router.replace({
|
||||
path: '/'
|
||||
})
|
||||
message.success('找回成功')
|
||||
})
|
||||
.finally(() => {
|
||||
islogin.value = false
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// 弹框的
|
||||
const visible = ref(false)
|
||||
const phoneLoginFormModalRef = ref()
|
||||
const phoneFormModalData = ref({})
|
||||
const validCodeBase64 = ref('')
|
||||
const formModalRules = {
|
||||
validCode: [required(), rules.lettersNum]
|
||||
}
|
||||
const getPhonePicCaptcha = () => {
|
||||
userCenterApi.userGetPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
phoneFormModalData.value.validCodeReqNo = data.validCodeReqNo
|
||||
})
|
||||
}
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
}
|
||||
const handleOk = () => {
|
||||
// 获取到里面的验证码,并发送短信
|
||||
phoneLoginFormModalRef.value.validate().then(() => {
|
||||
visible.value = false
|
||||
// 发送短信,首先拿到刚刚输入的手机号
|
||||
phoneFormModalData.value.phone = phoneFormData.value.phone
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.smsSendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.smsSendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
|
||||
userCenterApi
|
||||
.userFindPasswordGetPhoneValidCode(phoneFormModalData.value)
|
||||
.then((data) => {
|
||||
phoneValidCodeReqNo.value = data
|
||||
visible.value = false
|
||||
setTimeout(hide, 500)
|
||||
})
|
||||
.catch(() => {
|
||||
setTimeout(hide, 100)
|
||||
clearInterval(interval)
|
||||
state.value.smsSendBtn = false
|
||||
})
|
||||
.finally(() => {
|
||||
phoneFormModalData.value.validCode = ''
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
105
nl-vue/src/views/auth/login/callback.vue
Normal file
105
nl-vue/src/views/auth/login/callback.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="login-wrapper">
|
||||
<div class="login_background">
|
||||
<div class="logo_background">
|
||||
<a
|
||||
:class="{ 'no-link': !sysBaseConfig.SNOWY_SYS_COPYRIGHT_URL }"
|
||||
:href="sysBaseConfig.SNOWY_SYS_COPYRIGHT_URL"
|
||||
target="_blank"
|
||||
@click="handleLink"
|
||||
>
|
||||
<img :alt="sysBaseConfig.SNOWY_SYS_NAME" :src="sysBaseConfig.SNOWY_SYS_LOGO" />
|
||||
<label>{{ sysBaseConfig.SNOWY_SYS_NAME }}</label>
|
||||
</a>
|
||||
</div>
|
||||
<div class="version">
|
||||
<p>{{ sysBaseConfig.SNOWY_SYS_DEFAULT_DESCRRIPTION }}</p>
|
||||
<p>{{ sysBaseConfig.SNOWY_SYS_COPYRIGHT }} {{ sysBaseConfig.SNOWY_SYS_VERSION }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login_main">
|
||||
<div class="login-form">
|
||||
<a-card>
|
||||
<div class="login-header">
|
||||
<h2>三方登录</h2>
|
||||
</div>
|
||||
<a-spin tip="正在登录中...">
|
||||
<div class="h-[300px]">
|
||||
<a-skeleton />
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="loginCallback">
|
||||
import { message } from 'ant-design-vue'
|
||||
import tool from '@/utils/tool'
|
||||
import router from '@/router'
|
||||
import thirdApi from '@/api/auth/thirdApi'
|
||||
import loginApi from '@/api/auth/loginApi'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import dictApi from '@/api/dev/dictApi'
|
||||
import { globalStore } from '@/store'
|
||||
|
||||
const store = globalStore()
|
||||
const sysBaseConfig = computed(() => {
|
||||
return store.sysBaseConfig
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 获取当前url
|
||||
const url = new URL(window.location.href)
|
||||
let argLength = 0
|
||||
const params = {}
|
||||
url.searchParams.forEach((value, key) => {
|
||||
argLength += 1
|
||||
params[key] = value
|
||||
})
|
||||
// 当然了,不可能只有一个参数
|
||||
if (argLength < 2) {
|
||||
window.location.href = '/login'
|
||||
return
|
||||
}
|
||||
|
||||
thirdApi
|
||||
.thirdCallback(params)
|
||||
.then((data) => {
|
||||
tool.data.set('TOKEN', data)
|
||||
// 获取登录的用户信息
|
||||
loginApi.getLoginUser().then((loginUser) => {
|
||||
tool.data.set('USER_INFO', loginUser)
|
||||
})
|
||||
userCenterApi.userLoginMenu().then((menu) => {
|
||||
const indexMenu = menu[0].children[0].path
|
||||
tool.data.set('MENU', menu)
|
||||
// 重置系统默认应用
|
||||
tool.data.set('SNOWY_MENU_MODULE_ID', menu[0].id)
|
||||
router.replace({
|
||||
path: indexMenu
|
||||
})
|
||||
message.success('登录成功')
|
||||
dictApi.dictTree().then((dictData) => {
|
||||
// 设置字典到store中
|
||||
tool.data.set('DICT_TYPE_TREE_DATA', dictData)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
window.location.href = '/login'
|
||||
})
|
||||
})
|
||||
// logo链接
|
||||
const handleLink = (e) => {
|
||||
if (!sysBaseConfig.value.SNOWY_SYS_COPYRIGHT_URL) {
|
||||
e?.stopPropagation()
|
||||
e?.preventDefault()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import 'login';
|
||||
</style>
|
||||
178
nl-vue/src/views/auth/login/login.less
Normal file
178
nl-vue/src/views/auth/login/login.less
Normal file
@@ -0,0 +1,178 @@
|
||||
.login-icon-gray {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.login-validCode-img {
|
||||
border: 1px solid var(--border-color-split);
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
}
|
||||
.login-wrapper{
|
||||
width: 100vw;
|
||||
height:100vh;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
}
|
||||
.login_background {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-image: url(/img/login_background.png);
|
||||
position: relative;
|
||||
}
|
||||
@keyframes myfirst {
|
||||
0% {
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
50% {
|
||||
left: 50px;
|
||||
top: 0;
|
||||
}
|
||||
100% {
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes myfirst {
|
||||
0% {
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
50% {
|
||||
left: 50px;
|
||||
top: 0;
|
||||
}
|
||||
100% {
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.login_adv__title h2 {
|
||||
font-size: 40px;
|
||||
}
|
||||
.login_adv__title h4 {
|
||||
font-size: 18px;
|
||||
margin-top: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.login_adv__title p {
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
line-height: 1.8;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
.login_adv__title div {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.login_adv__title div span {
|
||||
margin-right: 15px;
|
||||
}
|
||||
.login_adv__title div i {
|
||||
font-size: 40px;
|
||||
}
|
||||
.login_adv__title div i.add {
|
||||
font-size: 20px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
/*background-image:linear-gradient(transparent, #000);*/
|
||||
.login_main {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.login-form {
|
||||
width: 450px;
|
||||
margin-top: 110px;
|
||||
}
|
||||
.login-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.login-header h2 {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.login_config {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
.logo_background{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50px;
|
||||
height: 60px;
|
||||
padding-left: 56px;
|
||||
width: 100%;
|
||||
background: linear-gradient(120deg, rgb(255 255 255 / 90%), rgba(255, 255, 255, 0));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.logo_background a{
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.logo_background a.no-link,
|
||||
.logo_background a.no-link label{
|
||||
cursor: default;
|
||||
}
|
||||
.logo_background img{
|
||||
height:40px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.logo_background a label{
|
||||
font-size:24px;
|
||||
color:#fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.login_background .version{
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color:#fff;
|
||||
font-weight: 300;
|
||||
padding: 0 56px;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
bottom:12px;
|
||||
}
|
||||
.login_background .version p{
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
margin-bottom:6px;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.login-form {
|
||||
width: 340px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 1000px) {
|
||||
.login_main {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left:0;
|
||||
right:0;
|
||||
}
|
||||
.login-form {
|
||||
width: 100%;
|
||||
padding: 20px 40px;
|
||||
top:15%
|
||||
}
|
||||
.logo_background{
|
||||
padding-left:40px;
|
||||
}
|
||||
.login_background .version{
|
||||
padding:0 20px;
|
||||
}
|
||||
.login_background .version p:first-child{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
273
nl-vue/src/views/auth/login/login.vue
Normal file
273
nl-vue/src/views/auth/login/login.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<template>
|
||||
<div class="login-wrapper">
|
||||
<div class="login_background">
|
||||
<div class="logo_background">
|
||||
<a
|
||||
:class="{ 'no-link': !sysBaseConfig.SNOWY_SYS_COPYRIGHT_URL }"
|
||||
:href="sysBaseConfig.SNOWY_SYS_COPYRIGHT_URL"
|
||||
target="_blank"
|
||||
@click="handleLink"
|
||||
>
|
||||
<img :alt="sysBaseConfig.SNOWY_SYS_NAME" :src="sysBaseConfig.SNOWY_SYS_LOGO" />
|
||||
<label>{{ sysBaseConfig.SNOWY_SYS_NAME }}</label>
|
||||
</a>
|
||||
</div>
|
||||
<div class="version">
|
||||
<p>{{ sysBaseConfig.SNOWY_SYS_DEFAULT_DESCRRIPTION }}</p>
|
||||
<p>{{ sysBaseConfig.SNOWY_SYS_COPYRIGHT }} {{ sysBaseConfig.SNOWY_SYS_VERSION }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="login_main">
|
||||
<div class="login_config">
|
||||
<a-dropdown>
|
||||
<global-outlined />
|
||||
<template #overlay>
|
||||
<a-menu>
|
||||
<a-menu-item
|
||||
v-for="item in lang"
|
||||
:key="item.value"
|
||||
:command="item"
|
||||
:class="{ selected: config.lang === item.value }"
|
||||
@click="configLang(item.value)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<div class="login-form">
|
||||
<a-card>
|
||||
<div class="login-header">
|
||||
<h2>{{ $t('login.signInTitle') }}</h2>
|
||||
</div>
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="userAccount" :tab="$t('login.accountPassword')">
|
||||
<a-form ref="loginForm" :model="ruleForm" :rules="rules">
|
||||
<a-form-item name="account">
|
||||
<a-input
|
||||
v-model:value="ruleForm.account"
|
||||
:placeholder="$t('login.accountPlaceholder')"
|
||||
size="large"
|
||||
@keyup.enter="login"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserOutlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="password">
|
||||
<a-input-password
|
||||
v-model:value="ruleForm.password"
|
||||
:placeholder="$t('login.PWPlaceholder')"
|
||||
size="large"
|
||||
autocomplete="off"
|
||||
@keyup.enter="login"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-form-item name="validCode" v-if="captchaOpen === 'true'">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input
|
||||
v-model:value="ruleForm.validCode"
|
||||
:placeholder="$t('login.validLaceholder')"
|
||||
size="large"
|
||||
@keyup.enter="login"
|
||||
>
|
||||
<template #prefix>
|
||||
<verified-outlined class="login-icon-gray" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img :src="validCodeBase64" class="login-validCode-img" @click="loginCaptcha" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a href="/findpwd" class="xn-color-0d84ff">{{ $t('login.forgetPassword') }}?</a>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" class="w-full" :loading="loading" round size="large" @click="login"
|
||||
>{{ $t('login.signIn') }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="userSms" :tab="$t('login.phoneSms')" force-render>
|
||||
<phone-login-form />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<three-login />
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import loginApi from '@/api/auth/loginApi'
|
||||
const PhoneLoginForm = defineAsyncComponent(() => import('./phoneLoginForm.vue'))
|
||||
import ThreeLogin from './threeLogin.vue'
|
||||
import smCrypto from '@/utils/smCrypto'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { afterLogin } from './util'
|
||||
import configData from '@/config'
|
||||
import configApi from '@/api/dev/configApi'
|
||||
import tool from '@/utils/tool'
|
||||
import { globalStore, iframeStore, keepAliveStore, viewTagsStore } from '@/store'
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const activeKey = ref('userAccount')
|
||||
const captchaOpen = ref(configData.SYS_BASE_CONFIG.SNOWY_SYS_DEFAULT_CAPTCHA_OPEN)
|
||||
const validCodeBase64 = ref('')
|
||||
const loading = ref(false)
|
||||
|
||||
const ruleForm = reactive({
|
||||
validCode: '',
|
||||
validCodeReqNo: '',
|
||||
autologin: false
|
||||
})
|
||||
// 如果是开发环境,填充用户名密码
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
ruleForm.account = 'superAdmin'
|
||||
ruleForm.password = '123456'
|
||||
}
|
||||
|
||||
const rules = reactive({
|
||||
account: [required(proxy.$t('login.accountError'), 'blur')],
|
||||
password: [required(proxy.$t('login.PWError'), 'blur')]
|
||||
})
|
||||
const lang = ref([
|
||||
{
|
||||
name: '简体中文',
|
||||
value: 'zh-cn'
|
||||
},
|
||||
{
|
||||
name: 'English',
|
||||
value: 'en'
|
||||
}
|
||||
])
|
||||
const config = ref({
|
||||
lang: tool.data.get('APP_LANG') || proxy.$CONFIG.LANG,
|
||||
theme: tool.data.get('APP_THEME') || 'default'
|
||||
})
|
||||
|
||||
const store = globalStore()
|
||||
const kStore = keepAliveStore()
|
||||
const iStore = iframeStore()
|
||||
const vStore = viewTagsStore()
|
||||
|
||||
const setSysBaseConfig = store.setSysBaseConfig
|
||||
const clearKeepLive = kStore.clearKeepLive
|
||||
const clearIframeList = iStore.clearIframeList
|
||||
const clearViewTags = vStore.clearViewTags
|
||||
|
||||
const sysBaseConfig = computed(() => {
|
||||
return store.sysBaseConfig
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
let formData = ref(configData.SYS_BASE_CONFIG)
|
||||
configApi
|
||||
.configSysBaseList()
|
||||
.then((data) => {
|
||||
if (data) {
|
||||
data.forEach((item) => {
|
||||
formData.value[item.configKey] = item.configValue
|
||||
})
|
||||
captchaOpen.value = formData.value.SNOWY_SYS_DEFAULT_CAPTCHA_OPEN
|
||||
tool.data.set('SNOWY_SYS_BASE_CONFIG', formData.value)
|
||||
setSysBaseConfig(formData.value)
|
||||
refreshSwitch()
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
clearViewTags()
|
||||
clearKeepLive()
|
||||
clearIframeList()
|
||||
})
|
||||
|
||||
// 监听语言
|
||||
watch(
|
||||
() => config.value.lang,
|
||||
(newValue) => {
|
||||
proxy.$i18n.locale = newValue
|
||||
tool.data.set('APP_LANG', newValue)
|
||||
}
|
||||
)
|
||||
// 主题
|
||||
watch(
|
||||
() => config.value.theme,
|
||||
(newValue) => {
|
||||
document.body.setAttribute('data-theme', newValue)
|
||||
}
|
||||
)
|
||||
// 通过开关加载内容
|
||||
const refreshSwitch = () => {
|
||||
// 判断是否开启验证码
|
||||
if (captchaOpen.value === 'true') {
|
||||
// 加载验证码
|
||||
loginCaptcha()
|
||||
// 加入校验
|
||||
rules.validCode = [required(proxy.$t('login.validError'), 'blur')]
|
||||
}
|
||||
}
|
||||
// 获取验证码
|
||||
const loginCaptcha = () => {
|
||||
loginApi.getPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
ruleForm.validCodeReqNo = data.validCodeReqNo
|
||||
})
|
||||
}
|
||||
//登陆
|
||||
const loginForm = ref()
|
||||
const login = async () => {
|
||||
loginForm.value
|
||||
.validate()
|
||||
.then(async () => {
|
||||
loading.value = true
|
||||
const loginData = {
|
||||
account: ruleForm.account,
|
||||
// 密码进行SM2加密,传输过程中看到的只有密文,后端存储使用hash
|
||||
password: smCrypto.doSm2Encrypt(ruleForm.password),
|
||||
validCode: ruleForm.validCode,
|
||||
validCodeReqNo: ruleForm.validCodeReqNo
|
||||
}
|
||||
// 获取token
|
||||
try {
|
||||
const loginToken = await loginApi.login(loginData)
|
||||
await afterLogin(loginToken)
|
||||
} catch (err) {
|
||||
loading.value = false
|
||||
if (captchaOpen.value === 'true') {
|
||||
loginCaptcha()
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
const configLang = (key) => {
|
||||
config.value.lang = key
|
||||
}
|
||||
// logo链接
|
||||
const handleLink = (e) => {
|
||||
if (!sysBaseConfig.value.SNOWY_SYS_COPYRIGHT_URL) {
|
||||
e?.stopPropagation()
|
||||
e?.preventDefault()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="less">
|
||||
@import 'login';
|
||||
.xn-color-0d84ff {
|
||||
color: #0d84ff;
|
||||
}
|
||||
</style>
|
||||
167
nl-vue/src/views/auth/login/phoneLoginForm.vue
Normal file
167
nl-vue/src/views/auth/login/phoneLoginForm.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<a-form ref="phoneLoginFormRef" :model="phoneFormData" :rules="formRules">
|
||||
<a-form-item name="phone">
|
||||
<a-input v-model:value="phoneFormData.phone" :placeholder="$t('login.phonePlaceholder')" size="large">
|
||||
<template #prefix>
|
||||
<mobile-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item name="phoneValidCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input
|
||||
v-model:value="phoneFormData.phoneValidCode"
|
||||
:placeholder="$t('login.smsCodePlaceholder')"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<mail-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<a-button size="large" class="xn-wd" @click="getPhoneValidCode" :disabled="state.smsSendBtn">
|
||||
{{ (!state.smsSendBtn && $t('login.getSmsCode')) || state.time + ' s' }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" class="xn-wd" :loading="loading" round size="large" @click="submitLogin">
|
||||
{{ $t('login.signIn') }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:width="400"
|
||||
:title="$t('login.machineValidation')"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<a-form ref="phoneLoginFormModalRef" :model="phoneFormModalData" :rules="formModalRules">
|
||||
<a-form-item name="validCode">
|
||||
<a-row :gutter="8">
|
||||
<a-col :span="17">
|
||||
<a-input
|
||||
v-model:value="phoneFormModalData.validCode"
|
||||
:placeholder="$t('login.validLaceholder')"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<verified-outlined class="text-black text-opacity-25" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-col>
|
||||
<a-col :span="7">
|
||||
<img
|
||||
:src="validCodeBase64"
|
||||
class="xn-findform-line"
|
||||
@click="getPhonePicCaptcha"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup name="smsLoginForm">
|
||||
import { message } from 'ant-design-vue'
|
||||
import { required, rules } from '@/utils/formRules'
|
||||
import loginApi from '@/api/auth/loginApi'
|
||||
import { afterLogin } from './util'
|
||||
|
||||
const phoneLoginFormRef = ref()
|
||||
const phoneFormData = ref({})
|
||||
const loading = ref(false)
|
||||
let state = ref({
|
||||
time: 60,
|
||||
smsSendBtn: false
|
||||
})
|
||||
let formRules = ref({})
|
||||
const phoneValidCodeReqNo = ref('')
|
||||
|
||||
// 点击获取短信验证码
|
||||
const getPhoneValidCode = () => {
|
||||
formRules.value.phone = [required('请输入11位手机号'), rules.phone]
|
||||
delete formRules.value.phoneValidCode
|
||||
phoneLoginFormRef.value.validate().then(() => {
|
||||
// 显示弹框
|
||||
visible.value = true
|
||||
// 获取内部图片验证码
|
||||
getPhonePicCaptcha()
|
||||
})
|
||||
}
|
||||
|
||||
// 点击登录按钮
|
||||
const submitLogin = async () => {
|
||||
formRules.value.phone = [required('请输入11位手机号'), rules.phone]
|
||||
formRules.value.phoneValidCode = [required('请输入短信验证码'), rules.number]
|
||||
|
||||
const validate = await phoneLoginFormRef.value.validate().catch(() => {})
|
||||
if (!validate) return false
|
||||
|
||||
phoneFormData.value.validCode = phoneFormData.value.phoneValidCode
|
||||
// delete phoneFormData.value.phoneValidCode
|
||||
phoneFormData.value.validCodeReqNo = phoneValidCodeReqNo.value
|
||||
loading.value = true
|
||||
loginApi.loginByPhone(phoneFormData.value).then((token) => {
|
||||
afterLogin(token)
|
||||
}).catch((err) => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
// 弹框的
|
||||
const visible = ref(false)
|
||||
const phoneLoginFormModalRef = ref()
|
||||
const phoneFormModalData = ref({})
|
||||
const validCodeBase64 = ref('')
|
||||
const validCodeReqNo = ref('')
|
||||
const formModalRules = {
|
||||
validCode: [required('请输入图形验证码'), rules.lettersNum]
|
||||
}
|
||||
const getPhonePicCaptcha = () => {
|
||||
loginApi.getPicCaptcha().then((data) => {
|
||||
validCodeBase64.value = data.validCodeBase64
|
||||
phoneFormModalData.value.validCodeReqNo = data.validCodeReqNo
|
||||
})
|
||||
}
|
||||
const handleCancel = () => {
|
||||
visible.value = false
|
||||
}
|
||||
const handleOk = () => {
|
||||
// 获取到里面的验证码,并发送短信
|
||||
phoneLoginFormModalRef.value.validate().then(() => {
|
||||
visible.value = false
|
||||
// 发送短信,首先拿到刚刚输入的手机号
|
||||
phoneFormModalData.value.phone = phoneFormData.value.phone
|
||||
// 禁用发送按钮,并设置为倒计时
|
||||
state.value.smsSendBtn = true
|
||||
const interval = window.setInterval(() => {
|
||||
if (state.value.time-- <= 0) {
|
||||
state.value.time = 60
|
||||
state.value.smsSendBtn = false
|
||||
window.clearInterval(interval)
|
||||
}
|
||||
}, 1000)
|
||||
const hide = message.loading('验证码发送中..', 0)
|
||||
|
||||
loginApi
|
||||
.getPhoneValidCode(phoneFormModalData.value)
|
||||
.then((data) => {
|
||||
phoneValidCodeReqNo.value = data
|
||||
visible.value = false
|
||||
setTimeout(hide, 500)
|
||||
phoneFormModalData.value.validCode = ''
|
||||
})
|
||||
.catch(() => {
|
||||
setTimeout(hide, 100)
|
||||
clearInterval(interval)
|
||||
state.value.smsSendBtn = false
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
24
nl-vue/src/views/auth/login/threeLogin.vue
Normal file
24
nl-vue/src/views/auth/login/threeLogin.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<a-divider>{{ $t('login.signInOther') }}</a-divider>
|
||||
<div class="login-oauth layout-center">
|
||||
<a-space align="start">
|
||||
<a @click="getLoginRenderUrl('gitee')"><GiteeIcon /></a>
|
||||
<a-button type="primary" shape="circle">
|
||||
<wechat-filled />
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="threeLogin">
|
||||
import thirdApi from '@/api/auth/thirdApi'
|
||||
|
||||
const getLoginRenderUrl = (platform) => {
|
||||
const param = {
|
||||
platform: platform
|
||||
}
|
||||
thirdApi.thirdRender(param).then((data) => {
|
||||
window.location.href = data.authorizeUrl
|
||||
})
|
||||
}
|
||||
</script>
|
||||
49
nl-vue/src/views/auth/login/util.js
Normal file
49
nl-vue/src/views/auth/login/util.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import dictApi from '@/api/dev/dictApi'
|
||||
import router from '@/router'
|
||||
import tool from '@/utils/tool'
|
||||
import { message } from 'ant-design-vue'
|
||||
import routerUtil from '@/utils/routerUtil'
|
||||
import { useMenuStore } from '@/store/menu'
|
||||
import { userStore } from '@/store/user'
|
||||
|
||||
export const afterLogin = async (loginToken) => {
|
||||
const menuStore = useMenuStore()
|
||||
tool.data.set('TOKEN', loginToken)
|
||||
// 初始化用户信息
|
||||
await userStore().initUserInfo()
|
||||
|
||||
// 获取用户的菜单
|
||||
const menu = await userCenterApi.userLoginMenu()
|
||||
let indexMenu = routerUtil.getIndexMenu(menu).path
|
||||
await menuStore.fetchMenu()
|
||||
// 重置系统默认应用
|
||||
tool.data.set('SNOWY_MENU_MODULE_ID', menu[0].id)
|
||||
message.success('登录成功')
|
||||
if (tool.data.get('LAST_VIEWS_PATH')) {
|
||||
// 如果有缓存,将其登录跳转到最后访问的路由
|
||||
indexMenu = tool.data.get('LAST_VIEWS_PATH')
|
||||
}
|
||||
// 如果存在退出后换新账号登录,进行重新匹配,匹配无果则默认首页
|
||||
if (menu) {
|
||||
let routerTag = 0
|
||||
menu.forEach((item) => {
|
||||
if (item.children) {
|
||||
if (JSON.stringify(item.children).indexOf(indexMenu) > -1) {
|
||||
routerTag++
|
||||
}
|
||||
}
|
||||
})
|
||||
if (routerTag === 0) {
|
||||
// 取首页
|
||||
indexMenu = routerUtil.getIndexMenu(menu).path
|
||||
}
|
||||
}
|
||||
dictApi.dictTree().then((data) => {
|
||||
// 设置字典到store中
|
||||
tool.data.set('DICT_TYPE_TREE_DATA', data)
|
||||
})
|
||||
await router.replace({
|
||||
path: indexMenu
|
||||
})
|
||||
}
|
||||
92
nl-vue/src/views/auth/monitor/analyse.vue
Normal file
92
nl-vue/src/views/auth/monitor/analyse.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="xn-pb10">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-card class="snowy-monitor-card" :bordered="false">
|
||||
<template #cover>
|
||||
<team-outlined style="color: #69c0ff" class="snowy-monitor-card-icon" />
|
||||
<div class="snowy-monitor-card-div">
|
||||
<span class="snowy-monitor-card-span">当前会话数:</span
|
||||
><span class="snowy-monitor-card-span">{{ analysisObj.currentSessionTotalCount }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="snowy-monitor-card" :bordered="false">
|
||||
<template #cover>
|
||||
<verified-outlined class="snowy-monitor-card-icon xn-color-ff9c6e" />
|
||||
<div class="snowy-monitor-card-div">
|
||||
<span class="snowy-monitor-card-span">最大签发令牌:</span
|
||||
><span class="snowy-monitor-card-span">{{ analysisObj.maxTokenCount }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="snowy-monitor-card" :bordered="false">
|
||||
<template #cover>
|
||||
<rise-outlined class="snowy-monitor-card-icon xn-color-ff85c0" />
|
||||
<div class="snowy-monitor-card-div">
|
||||
<span class="snowy-monitor-card-span">1小时内新增:</span
|
||||
><span class="snowy-monitor-card-span">{{ analysisObj.oneHourNewlyAdded }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-card class="snowy-monitor-card" :bordered="false">
|
||||
<template #cover>
|
||||
<pie-chart-outlined class="snowy-monitor-card-icon xn-color-5cdbd3" />
|
||||
<div class="snowy-monitor-card-div">
|
||||
<span class="snowy-monitor-card-span">B/C端占比:</span
|
||||
><span class="snowy-monitor-card-span">{{ analysisObj.proportionOfBAndC }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="monitorAnalyse">
|
||||
import monitorApi from '@/api/auth/monitorApi'
|
||||
|
||||
// 预置空数据
|
||||
const analysisObj = ref({
|
||||
currentSessionTotalCount: '0',
|
||||
maxTokenCount: '0',
|
||||
oneHourNewlyAdded: '0',
|
||||
proportionOfBAndC: '0/0'
|
||||
})
|
||||
monitorApi.monitorAnalysis().then((res) => {
|
||||
analysisObj.value = res
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.snowy-monitor-card {
|
||||
height: 100px;
|
||||
}
|
||||
.snowy-monitor-card-icon {
|
||||
font-size: 30px;
|
||||
padding-top: 18px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.snowy-monitor-card-div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.snowy-monitor-card-span {
|
||||
font-size: 16px;
|
||||
}
|
||||
.xn-color-ff9c6e {
|
||||
color: #ff9c6e;
|
||||
}
|
||||
.xn-color-ff85c0 {
|
||||
color: #ff85c0;
|
||||
}
|
||||
.xn-color-5cdbd3 {
|
||||
color: #5cdbd3;
|
||||
}
|
||||
</style>
|
||||
95
nl-vue/src/views/auth/monitor/bTab.vue
Normal file
95
nl-vue/src/views/auth/monitor/bTab.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<s-table ref="tableRef" :columns="columns" :data="loadDataB" :alert="false" bordered :row-key="(record) => record.id">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'avatar'">
|
||||
<a-avatar :src="record.avatar" class="xn-wh25" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'tokenNumber'">
|
||||
{{ record.tokenSignList.length }}
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a @click="tokenInfoList.onOpen(record.tokenSignList, 'B')">令牌列表</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-space>
|
||||
<a-popconfirm title="确定要强退此用户吗?" @confirm="bExit(record)">
|
||||
<a-button type="link" danger size="small">强退</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</s-table>
|
||||
<token-info-list ref="tokenInfoList" @successful="tableRef.refresh()" />
|
||||
</template>
|
||||
|
||||
<script setup name="monitorBTab">
|
||||
import monitorApi from '@/api/auth/monitorApi'
|
||||
import TokenInfoList from './tokenInfoList.vue'
|
||||
const tableRef = ref(null)
|
||||
const tokenInfoList = ref()
|
||||
const columns = [
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'avatar',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '账号',
|
||||
dataIndex: 'account'
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '登录地点',
|
||||
dataIndex: 'latestLoginAddress',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '最后登录时间',
|
||||
dataIndex: 'lastLoginTime',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '登录IP',
|
||||
dataIndex: 'latestLoginIp',
|
||||
width: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '令牌数',
|
||||
dataIndex: 'tokenNumber',
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
width: '150px',
|
||||
scopedSlots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
// 列表数据
|
||||
const loadDataB = (parameter) => {
|
||||
return monitorApi.monitorBPage(parameter).then((res) => {
|
||||
return res
|
||||
})
|
||||
}
|
||||
// 强退B端session
|
||||
const bExit = (record) => {
|
||||
let params = [
|
||||
{
|
||||
userId: record.id
|
||||
}
|
||||
]
|
||||
monitorApi.monitorBExit(params).then(() => {
|
||||
tableRef.value.refresh(true)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-wrapper {
|
||||
margin-top: -16px !important;
|
||||
}
|
||||
</style>
|
||||
95
nl-vue/src/views/auth/monitor/cTab.vue
Normal file
95
nl-vue/src/views/auth/monitor/cTab.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<s-table ref="tableRef" :columns="columns" :data="loadDataC" :alert="false" bordered :row-key="(record) => record.id">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'avatar'">
|
||||
<a-avatar :src="record.avatar" class="xn-wh25" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'tokenNumber'">
|
||||
{{ record.tokenSignList.length }}
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a @click="tokenInfoList.onOpen(record.tokenSignList)">令牌列表</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-space>
|
||||
<a-popconfirm title="确定要强退此用户吗?" @confirm="bExit(record)">
|
||||
<a-button type="link" danger size="small">强退</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</s-table>
|
||||
<token-info-list ref="tokenInfoList" @successful="tableRef.refresh()" />
|
||||
</template>
|
||||
|
||||
<script setup name="monitorCTab">
|
||||
import monitorApi from '@/api/auth/monitorApi'
|
||||
import TokenInfoList from './tokenInfoList.vue'
|
||||
const tableRef = ref(null)
|
||||
const tokenInfoList = ref()
|
||||
const columns = [
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'avatar',
|
||||
width: 60
|
||||
},
|
||||
{
|
||||
title: '账号',
|
||||
dataIndex: 'account'
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '登录地点',
|
||||
dataIndex: 'latestLoginAddress',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '最后登录时间',
|
||||
dataIndex: 'lastLoginTime',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '登录IP',
|
||||
dataIndex: 'latestLoginIp',
|
||||
width: 120,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '令牌数',
|
||||
dataIndex: 'tokenNumber',
|
||||
width: 70
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
width: '150px',
|
||||
scopedSlots: { customRender: 'action' }
|
||||
}
|
||||
]
|
||||
// 列表数据
|
||||
const loadDataC = (parameter) => {
|
||||
return monitorApi.monitorCPage(parameter).then((res) => {
|
||||
return res
|
||||
})
|
||||
}
|
||||
// 强退B端session
|
||||
const bExit = (record) => {
|
||||
let params = [
|
||||
{
|
||||
userId: record.id
|
||||
}
|
||||
]
|
||||
monitorApi.monitorCExit(params).then(() => {
|
||||
tableRef.value.refresh(true)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-wrapper {
|
||||
margin-top: -16px !important;
|
||||
}
|
||||
</style>
|
||||
20
nl-vue/src/views/auth/monitor/index.vue
Normal file
20
nl-vue/src/views/auth/monitor/index.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<analyse />
|
||||
<a-card :bordered="false" :body-style="{ 'padding-top': '10px' }">
|
||||
<a-tabs v-model:activeKey="activeKey">
|
||||
<a-tab-pane key="1" tab="B端会话">
|
||||
<monitor-b-tab />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" tab="C端会话" force-render>
|
||||
<monitor-c-tab />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup name="authMonitor">
|
||||
import Analyse from './analyse.vue'
|
||||
import MonitorBTab from './bTab.vue'
|
||||
import MonitorCTab from './cTab.vue'
|
||||
const activeKey = ref('1')
|
||||
</script>
|
||||
183
nl-vue/src/views/auth/monitor/tokenInfoList.vue
Normal file
183
nl-vue/src/views/auth/monitor/tokenInfoList.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<xn-form-container title="令牌列表" :width="650" :visible="visible" :destroy-on-close="true" @close="onClose">
|
||||
<a-button
|
||||
danger
|
||||
class="xn-mb10"
|
||||
:disabled="selectedRowKeys.length === 0"
|
||||
:loading="beatchExitLoading"
|
||||
@click="beachExitTokenValue"
|
||||
>批量强退</a-button
|
||||
>
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data-source="loadData"
|
||||
:row-selection="rowSelection"
|
||||
bordered
|
||||
:row-key="(record) => record.tokenValue"
|
||||
size="small"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'tokenDevice'">
|
||||
<a-tag v-if="record.tokenDevice === 'PC'" color="blue">
|
||||
{{ $TOOL.dictTypeData('AUTH_DEVICE_TYPE', record.tokenDevice) }}
|
||||
</a-tag>
|
||||
<a-tag v-if="record.tokenDevice === 'APP'" color="purple">
|
||||
{{ $TOOL.dictTypeData('AUTH_DEVICE_TYPE', record.tokenDevice) }}
|
||||
</a-tag>
|
||||
<a-tag v-if="record.tokenDevice === 'MINI'" color="orange">
|
||||
{{ $TOOL.dictTypeData('AUTH_DEVICE_TYPE', record.tokenDevice) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'tokenTimeout'">
|
||||
<a-tooltip>
|
||||
<template #title>{{ record.tokenTimeout }}</template>
|
||||
<a-progress
|
||||
v-if="record.tokenTimeoutPercent * 100 > 80"
|
||||
:percent="record.tokenTimeoutPercent * 100"
|
||||
:show-info="false"
|
||||
status="success"
|
||||
/>
|
||||
<a-progress
|
||||
v-if="record.tokenTimeoutPercent * 100 > 20 && record.tokenTimeoutPercent * 100 < 80"
|
||||
:percent="record.tokenTimeoutPercent * 100"
|
||||
:show-info="false"
|
||||
status="active"
|
||||
/>
|
||||
<a-progress
|
||||
v-if="record.tokenTimeoutPercent * 100 < 20"
|
||||
:percent="record.tokenTimeoutPercent * 100"
|
||||
:show-info="false"
|
||||
status="exception"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'tokenValue'">
|
||||
<a-tooltip>
|
||||
<template #title>{{ record.tokenValue }}</template>
|
||||
{{ record.tokenValue }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'action'">
|
||||
<a-popconfirm title="确定要强退此令牌吗?" @confirm="exitToken(record)">
|
||||
<a-button type="link" danger size="small" :loading="exitLoading">强退</a-button>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</xn-form-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import monitorApi from '@/api/auth/monitorApi'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
const columns = [
|
||||
{
|
||||
title: '登录设备',
|
||||
dataIndex: 'tokenDevice',
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '有效期',
|
||||
dataIndex: 'tokenTimeout'
|
||||
},
|
||||
{
|
||||
title: '令牌',
|
||||
dataIndex: 'tokenValue',
|
||||
ellipsis: true,
|
||||
width: 80
|
||||
}
|
||||
]
|
||||
if (hasPerm('authForceQuit')) {
|
||||
columns.push({
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
align: 'center',
|
||||
width: '100px',
|
||||
scopedSlots: { customRender: 'action' }
|
||||
})
|
||||
}
|
||||
// 字段数据
|
||||
const loadData = ref([])
|
||||
// 默认是关闭状态
|
||||
const visible = ref(false)
|
||||
// 多选的
|
||||
const selectedRowKeys = ref([])
|
||||
const exitLoading = ref(false)
|
||||
const beatchExitLoading = ref(false)
|
||||
const monitorType = ref()
|
||||
// 定义emit事件
|
||||
const emit = defineEmits({ successful: null })
|
||||
// 打开抽屉
|
||||
const onOpen = (tokenInfoList, type) => {
|
||||
monitorType.value = type
|
||||
loadData.value = cloneDeep(tokenInfoList)
|
||||
visible.value = true
|
||||
}
|
||||
// 关闭抽屉
|
||||
const onClose = () => {
|
||||
loadData.value = []
|
||||
monitorType.value = ''
|
||||
visible.value = false
|
||||
}
|
||||
// 多选
|
||||
const rowSelection = {
|
||||
onChange: (rowKeys) => {
|
||||
selectedRowKeys.value = rowKeys
|
||||
}
|
||||
}
|
||||
// 删除
|
||||
const exitToken = (record) => {
|
||||
let params = [
|
||||
{
|
||||
tokenValue: record.tokenValue
|
||||
}
|
||||
]
|
||||
exitLoading.value = true
|
||||
if (monitorType.value === 'B') {
|
||||
monitorApi.monitorTokenBExit(params).then(() => {
|
||||
exitTask(params)
|
||||
exitLoading.value = false
|
||||
})
|
||||
} else {
|
||||
monitorApi.monitorTokenCExit(params).then(() => {
|
||||
exitTask(params)
|
||||
exitLoading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
// 批量强退
|
||||
const beachExitTokenValue = () => {
|
||||
let params = []
|
||||
selectedRowKeys.value.forEach((key) => {
|
||||
params.push({ tokenValue: key })
|
||||
})
|
||||
beatchExitLoading.value = true
|
||||
if (monitorType.value === 'B') {
|
||||
monitorApi.monitorTokenBExit(params).then(() => {
|
||||
exitTask(params)
|
||||
selectedRowKeys.value = []
|
||||
beatchExitLoading.value = false
|
||||
})
|
||||
} else {
|
||||
monitorApi.monitorTokenCExit(params).then(() => {
|
||||
exitTask(params)
|
||||
selectedRowKeys.value = []
|
||||
beatchExitLoading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
const exitTask = (params) => {
|
||||
loadData.value.forEach((item, index, array) => {
|
||||
params.forEach((items) => {
|
||||
if (item.tokenValue === items.tokenValue) {
|
||||
delete array[index]
|
||||
}
|
||||
})
|
||||
})
|
||||
emit('successful')
|
||||
}
|
||||
// 调用这个函数将子组件的一些数据和方法暴露出去
|
||||
defineExpose({
|
||||
onOpen
|
||||
})
|
||||
</script>
|
||||
102
nl-vue/src/views/auth/third/index.vue
Normal file
102
nl-vue/src/views/auth/third/index.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<a-card :bordered="false" :body-style="{ 'padding-bottom': '0px' }" class="mb-2">
|
||||
<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
|
||||
<a-row :gutter="24">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="关键词" name="searchKey">
|
||||
<a-input v-model:value="searchFormState.searchKey" placeholder="请输入用户名或昵称关键词"></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="分类" name="category">
|
||||
<a-select
|
||||
v-model:value="searchFormState.category"
|
||||
placeholder="请选择分类"
|
||||
:options="categoryOptions"
|
||||
:getPopupContainer="(trigger) => trigger.parentNode"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-button type="primary" @click="tableRef.refresh(true)">查询</a-button>
|
||||
<a-button class="xn-mg08" @click="reset">重置</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
<a-card :bordered="false">
|
||||
<s-table
|
||||
ref="tableRef"
|
||||
:columns="columns"
|
||||
:data="loadData"
|
||||
:alert="false"
|
||||
bordered
|
||||
:row-key="(record) => record.id"
|
||||
:tool-config="toolConfig"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.dataIndex === 'avatar'">
|
||||
<a-avatar :src="record.avatar" style="margin-bottom: -5px; margin-top: -5px" />
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'category'">
|
||||
{{ $TOOL.dictTypeData('THIRD_CATEGORY', record.category) }}
|
||||
</template>
|
||||
</template>
|
||||
</s-table>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup name="authThird">
|
||||
import thirdApi from '@/api/auth/thirdApi'
|
||||
import tool from '@/utils/tool'
|
||||
const searchFormState = ref({})
|
||||
const searchFormRef = ref()
|
||||
const tableRef = ref()
|
||||
const toolConfig = { refresh: true, height: true, columnSetting: false, striped: false }
|
||||
const columns = [
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'avatar',
|
||||
align: 'center',
|
||||
width: '80px'
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'nickname',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'gender',
|
||||
width: '100px'
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
dataIndex: 'category'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
ellipsis: true,
|
||||
sorter: true
|
||||
}
|
||||
]
|
||||
const loadData = (parameter) => {
|
||||
return thirdApi.thirdPage(Object.assign(parameter, searchFormState.value)).then((res) => {
|
||||
return res
|
||||
})
|
||||
}
|
||||
// 重置
|
||||
const reset = () => {
|
||||
searchFormRef.value.resetFields()
|
||||
tableRef.value.refresh(true)
|
||||
}
|
||||
// 分类
|
||||
const categoryOptions = tool.dictList('THIRD_CATEGORY')
|
||||
</script>
|
||||
Reference in New Issue
Block a user