261 lines
8.0 KiB
Vue
261 lines
8.0 KiB
Vue
<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-tabs v-model:activeKey="activeTab">
|
|
<a-tab-pane key="password" tab="账号密码登录">
|
|
<a-form layout="vertical" :model="pwdLoginForm" @finish="handlePasswordLogin">
|
|
<a-form-item name="username" :rules="[{ required: true, message: '请输入账号' }]">
|
|
<a-input v-model:value="pwdLoginForm.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="pwdLoginForm.password" placeholder="请输入密码" size="large">
|
|
<template #prefix><LockOutlined /></template>
|
|
</a-input-password>
|
|
</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>
|
|
</a-tab-pane>
|
|
|
|
<a-tab-pane key="phone" tab="手机验证码登录">
|
|
<a-form layout="vertical" :model="phoneLoginForm" @finish="handlePhoneLogin">
|
|
<a-form-item name="phone" :rules="[phoneValidatorRule()]">
|
|
<a-input v-model:value="phoneLoginForm.phone" placeholder="请输入手机号" size="large">
|
|
<template #prefix><MobileOutlined /></template>
|
|
</a-input>
|
|
</a-form-item>
|
|
|
|
<a-form-item name="smsCode" :rules="[{ required: true, message: '请输入短信验证码' }]">
|
|
<SmsCodeInput
|
|
ref="smsInputRef"
|
|
v-model="phoneLoginForm.smsCode"
|
|
:loading="smsSending"
|
|
:disabled="!isValidPhone(phoneLoginForm.phone)"
|
|
@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>
|
|
</a-tab-pane>
|
|
</a-tabs>
|
|
</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>
|
|
|
|
<a-modal
|
|
v-model:open="forceChangePwdOpen"
|
|
title="首次登录,请设置新密码"
|
|
:closable="false"
|
|
:mask-closable="false"
|
|
:keyboard="false"
|
|
:confirm-loading="changePwdLoading"
|
|
@ok="handleForceChangePassword"
|
|
>
|
|
<a-alert
|
|
type="warning"
|
|
show-icon
|
|
message="检测到您使用的是初始密码,为保障账号安全,请先设置新密码后再继续操作"
|
|
style="margin-bottom: 16px"
|
|
/>
|
|
<a-form layout="vertical" :model="pwdForm">
|
|
<a-form-item label="当前密码" required>
|
|
<a-input-password v-model:value="pwdForm.oldPassword" placeholder="请输入登录时使用的密码" />
|
|
</a-form-item>
|
|
<a-form-item label="新密码" required>
|
|
<a-input-password v-model:value="pwdForm.newPassword" placeholder="8位以上,含大小写字母和数字" />
|
|
</a-form-item>
|
|
<a-form-item label="确认新密码" required>
|
|
<a-input-password v-model:value="pwdForm.confirmPassword" placeholder="请再次输入新密码" />
|
|
</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, MobileOutlined } from '@ant-design/icons-vue'
|
|
import SmsCodeInput from '@/components/SmsCodeInput.vue'
|
|
import { sendSmsCodeApi, changePasswordApi } from '@/api/auth'
|
|
import { getBaseUrl, setBaseUrlOverride } from '@/api/request'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { installDynamicRoutes, collectFirstPath } from '@/router/dynamic'
|
|
import { isValidPasswordStrength, isValidPhone, phoneValidatorRule } from '@/utils/validators'
|
|
import { showErrorModal } from '@/utils/errorModal'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const authStore = useAuthStore()
|
|
|
|
const activeTab = ref('password')
|
|
const pwdLoginForm = reactive({ username: '', password: '' })
|
|
const phoneLoginForm = reactive({ phone: '', smsCode: '' })
|
|
const smsInputRef = ref(null)
|
|
const smsSending = ref(false)
|
|
const loginLoading = ref(false)
|
|
|
|
const baseUrlModalOpen = ref(false)
|
|
const baseUrlInput = ref(getBaseUrl())
|
|
|
|
const forceChangePwdOpen = ref(false)
|
|
const changePwdLoading = ref(false)
|
|
const pwdForm = reactive({ oldPassword: '', newPassword: '', confirmPassword: '' })
|
|
|
|
async function handleSendSms() {
|
|
if (!isValidPhone(phoneLoginForm.phone)) {
|
|
showErrorModal('请先输入正确的手机号')
|
|
return
|
|
}
|
|
smsSending.value = true
|
|
try {
|
|
const res = await sendSmsCodeApi({ phone: phoneLoginForm.phone, businessType: 'LOGIN' })
|
|
if (res.data.code === 200) {
|
|
message.success('验证码已发送')
|
|
smsInputRef.value?.startCountdown()
|
|
}
|
|
} finally {
|
|
smsSending.value = false
|
|
}
|
|
}
|
|
|
|
function goToFirstPage() {
|
|
installDynamicRoutes(router, authStore.menuTree)
|
|
authStore.markRoutesReady()
|
|
const target = route.query.redirect || collectFirstPath(authStore.menuTree) || '/403'
|
|
router.replace(target)
|
|
}
|
|
|
|
async function handlePasswordLogin() {
|
|
loginLoading.value = true
|
|
try {
|
|
const res = await authStore.loginByPassword(pwdLoginForm)
|
|
if (res.code === 200) {
|
|
if (authStore.mustChangePassword) {
|
|
forceChangePwdOpen.value = true
|
|
} else {
|
|
goToFirstPage()
|
|
}
|
|
}
|
|
} finally {
|
|
loginLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function handlePhoneLogin() {
|
|
loginLoading.value = true
|
|
try {
|
|
const res = await authStore.loginByPhone(phoneLoginForm)
|
|
if (res.code === 200) {
|
|
if (authStore.mustChangePassword) {
|
|
forceChangePwdOpen.value = true
|
|
} else {
|
|
goToFirstPage()
|
|
}
|
|
}
|
|
} finally {
|
|
loginLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function handleForceChangePassword() {
|
|
if (!pwdForm.oldPassword) {
|
|
showErrorModal('请输入当前密码')
|
|
return
|
|
}
|
|
if (!isValidPasswordStrength(pwdForm.newPassword)) {
|
|
showErrorModal('密码需8位以上,且包含大小写字母和数字')
|
|
return
|
|
}
|
|
if (pwdForm.newPassword !== pwdForm.confirmPassword) {
|
|
showErrorModal('两次输入的密码不一致')
|
|
return
|
|
}
|
|
changePwdLoading.value = true
|
|
try {
|
|
const res = await changePasswordApi({
|
|
oldPassword: pwdForm.oldPassword,
|
|
newPassword: pwdForm.newPassword
|
|
})
|
|
if (res.data.code === 200) {
|
|
message.success('密码修改成功')
|
|
authStore.markPasswordChanged()
|
|
forceChangePwdOpen.value = false
|
|
goToFirstPage()
|
|
}
|
|
} finally {
|
|
changePwdLoading.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, #1f5f4e 0%, #0f332b 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: #1f5f4e;
|
|
}
|
|
.login-subtitle {
|
|
text-align: center;
|
|
color: #999;
|
|
margin-bottom: 24px;
|
|
}
|
|
</style>
|