mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-09-12 04:59:24 +08:00
feat: 新增手机号登录、邮箱登录
This commit is contained in:
@@ -8,6 +8,26 @@ export function accountLogin(req: Auth.AccountLoginReq) {
|
|||||||
return http.post<Auth.LoginResp>(`${BASE_URL}/account`, req)
|
return http.post<Auth.LoginResp>(`${BASE_URL}/account`, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @desc 手机号登录 */
|
||||||
|
export function phoneLogin(req: Auth.PhoneLoginReq) {
|
||||||
|
return http.post<Auth.LoginResp>(`${BASE_URL}/phone`, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @desc 邮箱登录 */
|
||||||
|
export function emailLogin(req: Auth.EmailLoginReq) {
|
||||||
|
return http.post<Auth.LoginResp>(`${BASE_URL}/email`, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @desc 三方账号登录 */
|
||||||
|
export function socialLogin(source: string, req: any) {
|
||||||
|
return http.post<Auth.LoginResp>(`/oauth/${source}`, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @desc 三方账号登录授权 */
|
||||||
|
export function socialAuth(source: string) {
|
||||||
|
return http.get<Auth.SocialAuthAuthorizeResp>(`/oauth/${source}`)
|
||||||
|
}
|
||||||
|
|
||||||
/** @desc 退出登录 */
|
/** @desc 退出登录 */
|
||||||
export function logout() {
|
export function logout() {
|
||||||
return http.post(`${BASE_URL}/logout`)
|
return http.post(`${BASE_URL}/logout`)
|
||||||
@@ -22,8 +42,3 @@ export const getUserInfo = () => {
|
|||||||
export const getUserRoute = () => {
|
export const getUserRoute = () => {
|
||||||
return http.get<Auth.RouteItem[]>(`${BASE_URL}/route`)
|
return http.get<Auth.RouteItem[]>(`${BASE_URL}/route`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @desc 第三方登录授权 */
|
|
||||||
export function socialAuth(source: string) {
|
|
||||||
return http.get<Auth.SocialAuthAuthorizeResp>(`/oauth/${source}`)
|
|
||||||
}
|
|
@@ -40,6 +40,7 @@ export interface RouteItem {
|
|||||||
affix: boolean
|
affix: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 账号登录请求参数 */
|
||||||
export interface AccountLoginReq {
|
export interface AccountLoginReq {
|
||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
@@ -47,6 +48,18 @@ export interface AccountLoginReq {
|
|||||||
uuid: string
|
uuid: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 手机号登录请求参数 */
|
||||||
|
export interface PhoneLoginReq {
|
||||||
|
phone: string
|
||||||
|
captcha: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 邮箱登录请求参数 */
|
||||||
|
export interface EmailLoginReq {
|
||||||
|
email: string
|
||||||
|
captcha: string
|
||||||
|
}
|
||||||
|
|
||||||
// 登录响应类型
|
// 登录响应类型
|
||||||
export interface LoginResp {
|
export interface LoginResp {
|
||||||
token: string
|
token: string
|
||||||
|
@@ -7,11 +7,13 @@ const BASE_URL = '/captcha'
|
|||||||
export function getImageCaptcha() {
|
export function getImageCaptcha() {
|
||||||
return http.get<Common.ImageCaptchaResp>(`${BASE_URL}/img`)
|
return http.get<Common.ImageCaptchaResp>(`${BASE_URL}/img`)
|
||||||
}
|
}
|
||||||
/**@desc 获取手机验证码 */
|
|
||||||
export function getPhoneCaptcha(query: { phone: string }) {
|
/** @desc 获取短信验证码 */
|
||||||
|
export function getSmsCaptcha(query: { phone: string }) {
|
||||||
return http.get<boolean>(`${BASE_URL}/sms`, query)
|
return http.get<boolean>(`${BASE_URL}/sms`, query)
|
||||||
}
|
}
|
||||||
/**@desc 获取邮箱验证码 */
|
|
||||||
|
/** @desc 获取邮箱验证码 */
|
||||||
export function getEmailCaptcha(query: { email: string }) {
|
export function getEmailCaptcha(query: { email: string }) {
|
||||||
return http.get<boolean>(`${BASE_URL}/mail`, query)
|
return http.get<boolean>(`${BASE_URL}/mail`, query)
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-button size="mini" class="gi_hover_btn" @click="handleToggleTheme">
|
<a-button size="mini" class="gi_hover_btn" @click="handleToggleTheme">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<icon-sun-fill :size="18" v-if="appStore.theme === 'light'"></icon-sun-fill>
|
<icon-moon-fill v-if="appStore.theme === 'light'" :size="18" />
|
||||||
<icon-moon-fill :size="18" v-else></icon-moon-fill>
|
<icon-sun-fill v-else :size="18" />
|
||||||
</template>
|
</template>
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -45,10 +45,6 @@ const toHome = () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
// .logo {
|
|
||||||
// width: 24px;
|
|
||||||
// height: 24px;
|
|
||||||
// }
|
|
||||||
.system-name {
|
.system-name {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -62,7 +58,7 @@ const toHome = () => {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.system-name {
|
.system-name {
|
||||||
padding-left: 10px;
|
padding-left: 8px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
transition: color 0.3s;
|
transition: color 0.3s;
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@@ -1,8 +1,18 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, reactive, computed } from 'vue'
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { resetRouter } from '@/router'
|
import { resetRouter } from '@/router'
|
||||||
import { accountLogin as accountLoginApi, logout as logoutApi, getUserInfo as getUserInfoApi } from '@/apis'
|
import {
|
||||||
import { socialAuth, type UserInfo } from '@/apis'
|
accountLogin as accountLoginApi,
|
||||||
|
phoneLogin as phoneLoginApi,
|
||||||
|
emailLogin as emailLoginApi,
|
||||||
|
socialLogin as socialLoginApi,
|
||||||
|
logout as logoutApi,
|
||||||
|
getUserInfo as getUserInfoApi,
|
||||||
|
type AccountLoginReq,
|
||||||
|
type PhoneLoginReq,
|
||||||
|
type EmailLoginReq,
|
||||||
|
type UserInfo
|
||||||
|
} from '@/apis'
|
||||||
import { setToken, clearToken, getToken } from '@/utils/auth'
|
import { setToken, clearToken, getToken } from '@/utils/auth'
|
||||||
import { resetHasRouteFlag } from '@/router/permission'
|
import { resetHasRouteFlag } from '@/router/permission'
|
||||||
import getAvatar from '@/utils/avatar'
|
import getAvatar from '@/utils/avatar'
|
||||||
@@ -31,21 +41,33 @@ const storeSetup = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
const accountLogin = async (params: any) => {
|
const accountLogin = async (req: AccountLoginReq) => {
|
||||||
const res = await accountLoginApi(params)
|
const res = await accountLoginApi(req)
|
||||||
setToken(res.data.token)
|
setToken(res.data.token)
|
||||||
token.value = res.data.token
|
token.value = res.data.token
|
||||||
}
|
}
|
||||||
// 三方账号身份登录
|
|
||||||
const socialLogin = async (source: string, req: any) => {
|
// 邮箱登录
|
||||||
try {
|
const emailLogin = async (req: EmailLoginReq) => {
|
||||||
const res = await socialAuth(source, req)
|
const res = await emailLoginApi(req)
|
||||||
setToken(res.data.token)
|
setToken(res.data.token)
|
||||||
} catch (err) {
|
token.value = res.data.token
|
||||||
clearToken()
|
|
||||||
throw err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 手机号登录
|
||||||
|
const phoneLogin = async (req: PhoneLoginReq) => {
|
||||||
|
const res = await phoneLoginApi(req)
|
||||||
|
setToken(res.data.token)
|
||||||
|
token.value = res.data.token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 三方账号登录
|
||||||
|
const socialLogin = async (source: string, req: any) => {
|
||||||
|
const res = await socialLoginApi(source, req)
|
||||||
|
setToken(res.data.token)
|
||||||
|
token.value = res.data.token
|
||||||
|
}
|
||||||
|
|
||||||
// 退出登录
|
// 退出登录
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -88,6 +110,8 @@ const storeSetup = () => {
|
|||||||
roles,
|
roles,
|
||||||
permissions,
|
permissions,
|
||||||
accountLogin,
|
accountLogin,
|
||||||
|
emailLogin,
|
||||||
|
phoneLogin,
|
||||||
socialLogin,
|
socialLogin,
|
||||||
logout,
|
logout,
|
||||||
logoutCallBack,
|
logoutCallBack,
|
||||||
|
@@ -8,20 +8,14 @@
|
|||||||
size="large"
|
size="large"
|
||||||
@submit="handleLogin"
|
@submit="handleLogin"
|
||||||
>
|
>
|
||||||
<a-form-item field="username">
|
<a-form-item field="username" hide-label>
|
||||||
<a-input v-model="form.username" placeholder="请输入用户名" allow-clear>
|
<a-input v-model="form.username" placeholder="请输入用户名" allow-clear />
|
||||||
<template #prefix><icon-user :stroke-width="1" :style="{ fontSize: '16px' }" /></template>
|
|
||||||
</a-input>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item field="password">
|
<a-form-item field="password" hide-label>
|
||||||
<a-input-password v-model="form.password" placeholder="请输入密码">
|
<a-input-password v-model="form.password" placeholder="请输入密码" />
|
||||||
<template #prefix><icon-lock :stroke-width="1" :style="{ fontSize: '16px' }" /></template>
|
|
||||||
</a-input-password>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item field="captcha" hide-label>
|
<a-form-item field="captcha" hide-label>
|
||||||
<a-input v-model="form.captcha" placeholder="请输入验证码" :max-length="4" allow-clear style="flex: 1 1">
|
<a-input v-model="form.captcha" placeholder="请输入验证码" :max-length="4" allow-clear style="flex: 1 1" />
|
||||||
<template #prefix><icon-check-circle :stroke-width="1" :style="{ fontSize: '16px' }" /></template>
|
|
||||||
</a-input>
|
|
||||||
<img :src="captchaImgBase64" alt="验证码" class="captcha" @click="getCaptcha" />
|
<img :src="captchaImgBase64" alt="验证码" class="captcha" @click="getCaptcha" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
@@ -32,7 +26,7 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item>
|
<a-form-item>
|
||||||
<a-space direction="vertical" fill class="w-full">
|
<a-space direction="vertical" fill class="w-full">
|
||||||
<a-button class="btn" type="primary" size="large" long :loading="loading" html-type="submit">登录</a-button>
|
<a-button class="btn" type="primary" :loading="loading" html-type="submit" size="large" long>立即登录</a-button>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
@@ -70,13 +64,13 @@ const rules: FormInstance['rules'] = {
|
|||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { loading, setLoading } = useLoading()
|
const loading = ref(false)
|
||||||
// 登录
|
// 登录
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
try {
|
try {
|
||||||
const isInvalid = await formRef.value?.validate()
|
const isInvalid = await formRef.value?.validate()
|
||||||
if (isInvalid) return
|
if (isInvalid) return
|
||||||
setLoading(true)
|
loading.value = true
|
||||||
await userStore.accountLogin({
|
await userStore.accountLogin({
|
||||||
username: form.username,
|
username: form.username,
|
||||||
password: encryptByRsa(form.password) || '',
|
password: encryptByRsa(form.password) || '',
|
||||||
@@ -97,7 +91,7 @@ const handleLogin = async () => {
|
|||||||
getCaptcha()
|
getCaptcha()
|
||||||
form.captcha = ''
|
form.captcha = ''
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,14 +116,23 @@ onMounted(() => {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arco-input-wrapper.arco-input-error {
|
.arco-input-wrapper.arco-input-error {
|
||||||
background-color: rgb(var(--danger-1));
|
background-color: rgb(var(--danger-1));
|
||||||
border-color: rgb(var(--danger-4));
|
border-color: rgb(var(--danger-3));
|
||||||
}
|
}
|
||||||
|
.arco-input-wrapper.arco-input-error:hover {
|
||||||
|
background-color: rgb(var(--danger-1));
|
||||||
|
border-color: rgb(var(--danger-6));
|
||||||
|
}
|
||||||
|
|
||||||
.arco-input-wrapper :deep(.arco-input) {
|
.arco-input-wrapper :deep(.arco-input) {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
}
|
}
|
||||||
|
.arco-input-wrapper:hover {
|
||||||
|
border-color: rgb(var(--arcoblue-6));
|
||||||
|
}
|
||||||
|
|
||||||
.captcha {
|
.captcha {
|
||||||
width: 111px;
|
width: 111px;
|
||||||
|
156
src/views/login/components/email/index.vue
Normal file
156
src/views/login/components/email/index.vue
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<template>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
:label-col-style="{ display: 'none' }"
|
||||||
|
:wrapper-col-style="{ flex: 1 }"
|
||||||
|
size="large"
|
||||||
|
@submit="handleLogin"
|
||||||
|
>
|
||||||
|
<a-form-item field="email" hide-label>
|
||||||
|
<a-input v-model="form.email" placeholder="请输入邮箱" allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="captcha" hide-label>
|
||||||
|
<a-input v-model="form.captcha" placeholder="请输入验证码" :max-length="4" allow-clear style="flex: 1 1" />
|
||||||
|
<a-button
|
||||||
|
class="captcha-btn"
|
||||||
|
:loading="captchaLoading"
|
||||||
|
:disabled="captchaDisable"
|
||||||
|
size="large"
|
||||||
|
@click="onCaptcha"
|
||||||
|
>
|
||||||
|
{{ captchaBtnName }}
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-space direction="vertical" fill class="w-full">
|
||||||
|
<a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long
|
||||||
|
>立即登录</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// import { getEmailCaptcha } from '@/apis'
|
||||||
|
import { Message, type FormInstance } from '@arco-design/web-vue'
|
||||||
|
import { useUserStore } from '@/stores'
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const form = reactive({
|
||||||
|
email: '',
|
||||||
|
captcha: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules: FormInstance['rules'] = {
|
||||||
|
email: [{ required: true, message: '请输入邮箱' }],
|
||||||
|
captcha: [{ required: true, message: '请输入验证码' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false)
|
||||||
|
// 登录
|
||||||
|
const handleLogin = async () => {
|
||||||
|
try {
|
||||||
|
const isInvalid = await formRef.value?.validate()
|
||||||
|
if (isInvalid) return
|
||||||
|
loading.value = true
|
||||||
|
await userStore.emailLogin(form)
|
||||||
|
const { redirect, ...othersQuery } = router.currentRoute.value.query
|
||||||
|
router.push({
|
||||||
|
path: (redirect as string) || '/',
|
||||||
|
query: {
|
||||||
|
...othersQuery
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Message.success('欢迎使用')
|
||||||
|
} catch (error) {
|
||||||
|
form.captcha = ''
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const captchaTime = ref(60)
|
||||||
|
const captchaTimer = ref()
|
||||||
|
// 重置验证码
|
||||||
|
const resetCaptcha = () => {
|
||||||
|
window.clearInterval(captchaTimer.value)
|
||||||
|
captchaTime.value = 60
|
||||||
|
captchaBtnName.value = '获取验证码'
|
||||||
|
captchaDisable.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const captchaBtnName = ref('获取验证码')
|
||||||
|
const captchaLoading = ref(false)
|
||||||
|
const captchaDisable = ref(false)
|
||||||
|
// 获取验证码
|
||||||
|
const onCaptcha = async () => {
|
||||||
|
try {
|
||||||
|
if (captchaLoading.value) return
|
||||||
|
const isInvalid = await formRef.value?.validateField('email')
|
||||||
|
if (isInvalid) return
|
||||||
|
captchaLoading.value = true
|
||||||
|
captchaBtnName.value = '发送中...'
|
||||||
|
// await getEmailCaptcha({
|
||||||
|
// email: form.email
|
||||||
|
// })
|
||||||
|
captchaLoading.value = false
|
||||||
|
captchaDisable.value = true
|
||||||
|
captchaBtnName.value = `获取验证码(${(captchaTime.value -= 1)}s)`
|
||||||
|
// Message.success('邮件发送成功')
|
||||||
|
Message.success('仅提供效果演示,实际使用请查看代码取消相关注释')
|
||||||
|
captchaTimer.value = window.setInterval(() => {
|
||||||
|
captchaTime.value -= 1
|
||||||
|
captchaBtnName.value = `获取验证码(${captchaTime.value}s)`
|
||||||
|
if (captchaTime.value <= 0) {
|
||||||
|
resetCaptcha()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
} catch (error) {
|
||||||
|
resetCaptcha()
|
||||||
|
} finally {
|
||||||
|
captchaLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.arco-input-wrapper,
|
||||||
|
:deep(.arco-select-view-single) {
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-input-wrapper.arco-input-error {
|
||||||
|
background-color: rgb(var(--danger-1));
|
||||||
|
border-color: rgb(var(--danger-3));
|
||||||
|
}
|
||||||
|
.arco-input-wrapper.arco-input-error:hover {
|
||||||
|
background-color: rgb(var(--danger-1));
|
||||||
|
border-color: rgb(var(--danger-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-input-wrapper :deep(.arco-input) {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
.arco-input-wrapper:hover {
|
||||||
|
border-color: rgb(var(--arcoblue-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-btn {
|
||||||
|
height: 40px;
|
||||||
|
margin-left: 12px;
|
||||||
|
min-width: 98px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
159
src/views/login/components/phone/index.vue
Normal file
159
src/views/login/components/phone/index.vue
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
:label-col-style="{ display: 'none' }"
|
||||||
|
:wrapper-col-style="{ flex: 1 }"
|
||||||
|
size="large"
|
||||||
|
@submit="handleLogin"
|
||||||
|
>
|
||||||
|
<a-form-item field="phone" hide-label>
|
||||||
|
<a-input v-model="form.phone" placeholder="请输入手机号" :max-length="11" allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="captcha" hide-label>
|
||||||
|
<a-input v-model="form.captcha" placeholder="请输入验证码" :max-length="4" allow-clear style="flex: 1 1" />
|
||||||
|
<a-button
|
||||||
|
class="captcha-btn"
|
||||||
|
:loading="captchaLoading"
|
||||||
|
:disabled="captchaDisable"
|
||||||
|
size="large"
|
||||||
|
@click="onCaptcha"
|
||||||
|
>
|
||||||
|
{{ captchaBtnName }}
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item>
|
||||||
|
<a-space direction="vertical" fill class="w-full">
|
||||||
|
<a-button disabled class="btn" type="primary" :loading="loading" html-type="submit" size="large" long
|
||||||
|
>立即登录</a-button
|
||||||
|
>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// import { getSmsCaptcha } from '@/apis'
|
||||||
|
import { Message, type FormInstance } from '@arco-design/web-vue'
|
||||||
|
import { useUserStore } from '@/stores'
|
||||||
|
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const form = reactive({
|
||||||
|
phone: '',
|
||||||
|
captcha: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules: FormInstance['rules'] = {
|
||||||
|
phone: [
|
||||||
|
{ required: true, message: '请输入手机号' },
|
||||||
|
{ match: /^1[3-9]\d{9}$/, message: '请输入正确的手机号' }
|
||||||
|
],
|
||||||
|
captcha: [{ required: true, message: '请输入验证码' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const router = useRouter()
|
||||||
|
const loading = ref(false)
|
||||||
|
// 登录
|
||||||
|
const handleLogin = async () => {
|
||||||
|
try {
|
||||||
|
const isInvalid = await formRef.value?.validate()
|
||||||
|
if (isInvalid) return
|
||||||
|
loading.value = true
|
||||||
|
await userStore.phoneLogin(form)
|
||||||
|
const { redirect, ...othersQuery } = router.currentRoute.value.query
|
||||||
|
router.push({
|
||||||
|
path: (redirect as string) || '/',
|
||||||
|
query: {
|
||||||
|
...othersQuery
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Message.success('欢迎使用')
|
||||||
|
} catch (error) {
|
||||||
|
form.captcha = ''
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const captchaTime = ref(60)
|
||||||
|
const captchaTimer = ref()
|
||||||
|
// 重置验证码
|
||||||
|
const resetCaptcha = () => {
|
||||||
|
window.clearInterval(captchaTimer.value)
|
||||||
|
captchaTime.value = 60
|
||||||
|
captchaBtnName.value = '获取验证码'
|
||||||
|
captchaDisable.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const captchaBtnName = ref('获取验证码')
|
||||||
|
const captchaLoading = ref(false)
|
||||||
|
const captchaDisable = ref(false)
|
||||||
|
// 获取验证码
|
||||||
|
const onCaptcha = async () => {
|
||||||
|
try {
|
||||||
|
if (captchaLoading.value) return
|
||||||
|
const isInvalid = await formRef.value?.validateField('phone')
|
||||||
|
if (isInvalid) return
|
||||||
|
captchaLoading.value = true
|
||||||
|
captchaBtnName.value = '发送中...'
|
||||||
|
// await getSmsCaptcha({
|
||||||
|
// phone: form.phone
|
||||||
|
// })
|
||||||
|
captchaLoading.value = false
|
||||||
|
captchaDisable.value = true
|
||||||
|
captchaBtnName.value = `获取验证码(${(captchaTime.value -= 1)}s)`
|
||||||
|
// Message.success('短信发送成功')
|
||||||
|
Message.success('仅提供效果演示,实际使用请查看代码取消相关注释')
|
||||||
|
captchaTimer.value = window.setInterval(() => {
|
||||||
|
captchaTime.value -= 1
|
||||||
|
captchaBtnName.value = `获取验证码(${captchaTime.value}s)`
|
||||||
|
if (captchaTime.value <= 0) {
|
||||||
|
resetCaptcha()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
} catch (error) {
|
||||||
|
resetCaptcha()
|
||||||
|
} finally {
|
||||||
|
captchaLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.arco-input-wrapper,
|
||||||
|
:deep(.arco-select-view-single) {
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-input-wrapper.arco-input-error {
|
||||||
|
background-color: rgb(var(--danger-1));
|
||||||
|
border-color: rgb(var(--danger-3));
|
||||||
|
}
|
||||||
|
.arco-input-wrapper.arco-input-error:hover {
|
||||||
|
background-color: rgb(var(--danger-1));
|
||||||
|
border-color: rgb(var(--danger-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-input-wrapper :deep(.arco-input) {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
.arco-input-wrapper:hover {
|
||||||
|
border-color: rgb(var(--arcoblue-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-btn {
|
||||||
|
height: 40px;
|
||||||
|
margin-left: 12px;
|
||||||
|
min-width: 98px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -14,15 +14,21 @@
|
|||||||
</a-col>
|
</a-col>
|
||||||
<a-col :xs="24" :sm="12" :md="11">
|
<a-col :xs="24" :sm="12" :md="11">
|
||||||
<div class="login-right">
|
<div class="login-right">
|
||||||
<a-tabs class="login-right__form">
|
<h3 class="login-right__title" v-if="isEmailLogin">邮箱登录</h3>
|
||||||
|
<EmailLogin v-if="isEmailLogin" />
|
||||||
|
<a-tabs v-else class="login-right__form">
|
||||||
<a-tab-pane title="账号登录" key="1">
|
<a-tab-pane title="账号登录" key="1">
|
||||||
<Account />
|
<AccountLogin />
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane title="手机号登录" key="2">
|
||||||
|
<PhoneLogin />
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane title="手机号登录" key="2" disabled></a-tab-pane>
|
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
<div class="login-right__oauth">
|
<div class="login-right__oauth">
|
||||||
<a-divider orientation="center">其他登录方式</a-divider>
|
<a-divider orientation="center">其他登录方式</a-divider>
|
||||||
<div class="list">
|
<div class="list">
|
||||||
|
<div v-if="isEmailLogin" class="mode item" @click="toggleLoginMode"><icon-user /> 账号/手机号登录</div>
|
||||||
|
<div v-else class="mode item" @click="toggleLoginMode"><icon-email /> 邮箱登录</div>
|
||||||
<a class="item" title="使用 Gitee 账号登录" @click="onOauth('gitee')">
|
<a class="item" title="使用 Gitee 账号登录" @click="onOauth('gitee')">
|
||||||
<GiSvgIcon name="gitee" :size="24" />
|
<GiSvgIcon name="gitee" :size="24" />
|
||||||
</a>
|
</a>
|
||||||
@@ -49,7 +55,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { socialAuth } from '@/apis'
|
import { socialAuth } from '@/apis'
|
||||||
import Background from './components/background/index.vue'
|
import Background from './components/background/index.vue'
|
||||||
import Account from './components/account/index.vue'
|
import AccountLogin from './components/account/index.vue'
|
||||||
|
import PhoneLogin from './components/phone/index.vue'
|
||||||
|
import EmailLogin from './components/email/index.vue'
|
||||||
import { useAppStore } from '@/stores'
|
import { useAppStore } from '@/stores'
|
||||||
|
|
||||||
defineOptions({ name: 'Login' })
|
defineOptions({ name: 'Login' })
|
||||||
@@ -58,6 +66,12 @@ const appStore = useAppStore()
|
|||||||
const title = computed(() => appStore.getTitle())
|
const title = computed(() => appStore.getTitle())
|
||||||
const logo = computed(() => appStore.getLogo())
|
const logo = computed(() => appStore.getLogo())
|
||||||
|
|
||||||
|
const isEmailLogin = ref(false)
|
||||||
|
// 切换登录模式
|
||||||
|
const toggleLoginMode = () => {
|
||||||
|
isEmailLogin.value = !isEmailLogin.value
|
||||||
|
}
|
||||||
|
|
||||||
// 第三方登录授权
|
// 第三方登录授权
|
||||||
const onOauth = async (source: string) => {
|
const onOauth = async (source: string) => {
|
||||||
const { data } = await socialAuth(source)
|
const { data } = await socialAuth(source)
|
||||||
@@ -88,13 +102,13 @@ const onOauth = async (source: string) => {
|
|||||||
img {
|
img {
|
||||||
width: 34px;
|
width: 34px;
|
||||||
height: 34px;
|
height: 34px;
|
||||||
margin-right: 10px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&-box {
|
&-box {
|
||||||
width: 86%;
|
width: 86%;
|
||||||
max-width: 850px;
|
max-width: 850px;
|
||||||
height: 480px;
|
height: 490px;
|
||||||
display: flex;
|
display: flex;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
box-shadow: 0 2px 4px 2px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 2px 4px 2px rgba(0, 0, 0, 0.08);
|
||||||
@@ -131,9 +145,18 @@ const onOauth = async (source: string) => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 30px 30px 0;
|
padding: 30px 30px 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
&__title {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 32px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
&__form {
|
&__form {
|
||||||
:deep(.arco-tabs-nav::before) {
|
:deep(.arco-tabs-nav-tab) {
|
||||||
display: none;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
:deep(.arco-tabs-tab) {
|
:deep(.arco-tabs-tab) {
|
||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
@@ -143,18 +166,22 @@ const onOauth = async (source: string) => {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
}
|
}
|
||||||
|
:deep(.arco-tabs-content) {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
:deep(.arco-tabs-tab-active),
|
:deep(.arco-tabs-tab-active),
|
||||||
:deep(.arco-tabs-tab-title:hover) {
|
:deep(.arco-tabs-tab-title:hover) {
|
||||||
color: rgb(var(--arcoblue-6));
|
color: rgb(var(--arcoblue-6));
|
||||||
}
|
}
|
||||||
|
:deep(.arco-tabs-nav::before) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
:deep(.arco-tabs-tab-title:before) {
|
:deep(.arco-tabs-tab-title:before) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
:deep(.arco-tabs-content) {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&__oauth {
|
&__oauth {
|
||||||
|
margin-top: auto;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
:deep(.arco-divider-text) {
|
:deep(.arco-divider-text) {
|
||||||
color: var(--color-text-4);
|
color: var(--color-text-4);
|
||||||
@@ -162,9 +189,6 @@ const onOauth = async (source: string) => {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
}
|
}
|
||||||
:deep(.arco-divider-horizontal) {
|
|
||||||
border-bottom: 1px solid rgb(229, 230, 235);
|
|
||||||
}
|
|
||||||
.list {
|
.list {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -173,6 +197,35 @@ const onOauth = async (source: string) => {
|
|||||||
.item {
|
.item {
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
}
|
}
|
||||||
|
.mode {
|
||||||
|
color: var(--color-text-2);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid var(--color-border-3);
|
||||||
|
border-radius: 32px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
height: 32px;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
.icon {
|
||||||
|
width: 21px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mode svg {
|
||||||
|
font-size: 16px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.mode:hover,
|
||||||
|
.mode svg:hover {
|
||||||
|
background: rgba(var(--primary-6), 0.05);
|
||||||
|
border: 1px solid rgb(var(--primary-3));
|
||||||
|
color: rgb(var(--arcoblue-6));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -36,7 +36,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { getPhoneCaptcha, getEmailCaptcha, updateUserEmail } from '@/apis'
|
import { getSmsCaptcha, getEmailCaptcha, updateUserEmail } from '@/apis'
|
||||||
import { encryptByRsa } from '@/utils/encrypt'
|
import { encryptByRsa } from '@/utils/encrypt'
|
||||||
import * as Regexp from '@/utils/regexp'
|
import * as Regexp from '@/utils/regexp'
|
||||||
import { Message, type Modal } from '@arco-design/web-vue'
|
import { Message, type Modal } from '@arco-design/web-vue'
|
||||||
@@ -60,7 +60,7 @@ const onSendCaptcha = () => {
|
|||||||
// 发送验证码
|
// 发送验证码
|
||||||
if (verifyType.value === 'phone') {
|
if (verifyType.value === 'phone') {
|
||||||
//手机号
|
//手机号
|
||||||
getPhoneCaptcha({ phone: form.newPhone }).then((res) => {
|
getSmsCaptcha({ phone: form.newPhone }).then((res) => {
|
||||||
console.log(res)
|
console.log(res)
|
||||||
})
|
})
|
||||||
} else if (verifyType.value === 'email') {
|
} else if (verifyType.value === 'email') {
|
||||||
|
Reference in New Issue
Block a user