feat: 新增登录页(账号密码+短信验证码+BASE_URL设置)与全局空闲登出
parent
76db7bb8d7
commit
499873c157
25
src/App.vue
25
src/App.vue
|
|
@ -5,5 +5,30 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { startIdleWatcher } from '@/utils/lastActive'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
let stopIdleWatcher = null
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
stopIdleWatcher = startIdleWatcher(() => {
|
||||||
|
if (authStore.accessToken && route.path !== '/login') {
|
||||||
|
authStore.clear()
|
||||||
|
message.warning('您已长时间无操作,请重新登录')
|
||||||
|
router.replace('/login')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopIdleWatcher && stopIdleWatcher()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
<template>
|
||||||
|
<a-input-group compact class="sms-code-input">
|
||||||
|
<a-input
|
||||||
|
:value="modelValue"
|
||||||
|
placeholder="请输入短信验证码"
|
||||||
|
style="width: calc(100% - 120px)"
|
||||||
|
@update:value="$emit('update:modelValue', $event)"
|
||||||
|
/>
|
||||||
|
<a-button style="width: 120px" :disabled="counting || disabled" :loading="loading" @click="handleSend">
|
||||||
|
{{ counting ? `${remain}s后重发` : '获取验证码' }}
|
||||||
|
</a-button>
|
||||||
|
</a-input-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
modelValue: { type: String, default: '' },
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
|
loading: { type: Boolean, default: false }
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:modelValue', 'send'])
|
||||||
|
|
||||||
|
const counting = ref(false)
|
||||||
|
const remain = ref(60)
|
||||||
|
let timer = null
|
||||||
|
|
||||||
|
function startCountdown() {
|
||||||
|
counting.value = true
|
||||||
|
remain.value = 60
|
||||||
|
timer = setInterval(() => {
|
||||||
|
remain.value -= 1
|
||||||
|
if (remain.value <= 0) {
|
||||||
|
clearInterval(timer)
|
||||||
|
counting.value = false
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSend() {
|
||||||
|
emit('send')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ startCountdown })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.sms-code-input {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
<template>
|
||||||
|
<div class="login-page">
|
||||||
|
<a-button class="base-url-btn" shape="circle" @click="baseUrlModalOpen = true">
|
||||||
|
<SettingOutlined />
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<div class="login-card">
|
||||||
|
<div class="login-title">凡荣e链</div>
|
||||||
|
<div class="login-subtitle">供应链金融服务管理后台</div>
|
||||||
|
|
||||||
|
<a-form layout="vertical" :model="form" @finish="handleLogin">
|
||||||
|
<a-form-item
|
||||||
|
name="username"
|
||||||
|
:rules="[{ required: true, message: '请输入账号' }]"
|
||||||
|
>
|
||||||
|
<a-input v-model:value="form.username" placeholder="请输入账号" size="large">
|
||||||
|
<template #prefix><UserOutlined /></template>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
name="password"
|
||||||
|
:rules="[{ required: true, message: '请输入密码' }]"
|
||||||
|
>
|
||||||
|
<a-input-password v-model:value="form.password" placeholder="请输入密码" size="large">
|
||||||
|
<template #prefix><LockOutlined /></template>
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item name="code" :rules="[{ required: true, message: '请输入短信验证码' }]">
|
||||||
|
<SmsCodeInput
|
||||||
|
ref="smsInputRef"
|
||||||
|
v-model="form.code"
|
||||||
|
:loading="smsSending"
|
||||||
|
@send="handleSendSms"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item>
|
||||||
|
<a-button type="primary" html-type="submit" block size="large" :loading="loginLoading">
|
||||||
|
登录
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
v-model:open="baseUrlModalOpen"
|
||||||
|
title="接口地址设置"
|
||||||
|
@ok="handleSaveBaseUrl"
|
||||||
|
>
|
||||||
|
<a-form layout="vertical">
|
||||||
|
<a-form-item label="BASE_URL">
|
||||||
|
<a-input v-model:value="baseUrlInput" placeholder="例如 http://localhost:8888" />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { message } from 'ant-design-vue'
|
||||||
|
import { UserOutlined, LockOutlined, SettingOutlined } from '@ant-design/icons-vue'
|
||||||
|
import SmsCodeInput from '@/components/SmsCodeInput.vue'
|
||||||
|
import { sendSmsApi } from '@/api/auth'
|
||||||
|
import { getBaseUrl, setBaseUrlOverride } from '@/api/request'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import { installDynamicRoutes, collectFirstPath } from '@/router/dynamic'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
|
const form = reactive({ username: '', password: '', code: '' })
|
||||||
|
const smsInputRef = ref(null)
|
||||||
|
const smsSending = ref(false)
|
||||||
|
const loginLoading = ref(false)
|
||||||
|
|
||||||
|
const baseUrlModalOpen = ref(false)
|
||||||
|
const baseUrlInput = ref(getBaseUrl())
|
||||||
|
|
||||||
|
async function handleSendSms() {
|
||||||
|
if (!form.username || !form.password) {
|
||||||
|
message.warning('请先填写账号和密码')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
smsSending.value = true
|
||||||
|
try {
|
||||||
|
const res = await sendSmsApi({ username: form.username, password: form.password })
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
message.success('验证码已发送')
|
||||||
|
smsInputRef.value?.startCountdown()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
smsSending.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLogin() {
|
||||||
|
loginLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await authStore.login(form)
|
||||||
|
if (res.code === 200) {
|
||||||
|
installDynamicRoutes(router, authStore.menuTree)
|
||||||
|
authStore.markRoutesReady()
|
||||||
|
const target = route.query.redirect || collectFirstPath(authStore.menuTree) || '/403'
|
||||||
|
router.replace(target)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loginLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSaveBaseUrl() {
|
||||||
|
setBaseUrlOverride(baseUrlInput.value)
|
||||||
|
baseUrlModalOpen.value = false
|
||||||
|
message.success('接口地址已保存')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.login-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #1d3a6e 0%, #0b1f3f 100%);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.base-url-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
right: 24px;
|
||||||
|
}
|
||||||
|
.login-card {
|
||||||
|
width: 400px;
|
||||||
|
padding: 40px 36px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
.login-title {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
color: #1d3a6e;
|
||||||
|
}
|
||||||
|
.login-subtitle {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue