修改登陆api

This commit is contained in:
Kunagisa 2025-06-04 23:50:46 +08:00
parent 4f5e971cb6
commit 60667511b4
4 changed files with 349 additions and 218 deletions

View File

@ -5,7 +5,6 @@
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<script src="https://v-cn.vaptcha.com/v3.js"></script>
<title>红色警戒3数据分析中心</title> <title>红色警戒3数据分析中心</title>
</head> </head>
<body> <body>

View File

@ -50,39 +50,45 @@ import { loginSuccess } from '../utils/jwt';
// } // }
// ) // )
export const userLogin = async (username, password, server, token) => { // 获取验证码
export const getCaptcha = async () => {
try {
const response = await axiosInstance.get('/captcha');
return response.data;
} catch (error) {
throw error;
}
};
// 用户登录
export const userLogin = async (username, password, token, captcha) => {
try { try {
// console.log('登录请求参数:', { username, password, server, token }); // 保留此调试日志以备将来使用,或按需移除
const response = await axiosInstance.post('/user/login', { const response = await axiosInstance.post('/user/login', {
username, username,
password, password,
server, token,
token captcha
}); });
if (response.data.access_token) { if (response.data.access_token) {
loginSuccess(response.data.access_token, username); // 使用 username 作为 userId loginSuccess(response.data.access_token);
} }
return response.data; return response.data;
} catch (error) { } catch (error) {
// 错误将由响应拦截器统一处理和记录,这里可以直接抛出
throw error; throw error;
} }
}; };
export const userRegister = async (qq_code, password, server, token) => { // 用户注册
export const userRegister = async (qq_code, password, token, captcha) => {
try { try {
const requestData = { const response = await axiosInstance.post('/user/register', {
qq_code, qq_code,
password, password,
server, token,
token captcha
}; });
// console.log('注册请求参数:', requestData); // 保留此调试日志以备将来使用,或按需移除
const response = await axiosInstance.post('/user/register', requestData);
// console.log('注册响应数据:', response.data); // 保留此调试日志以备将来使用,或按需移除
return response.data; return response.data;
} catch (error) { } catch (error) {
throw error; throw error;

View File

@ -26,40 +26,26 @@
/> />
<span class="error-message" v-if="passwordError">{{ passwordError }}</span> <span class="error-message" v-if="passwordError">{{ passwordError }}</span>
</div> </div>
<div class="input-container"> <div class="input-container captcha-container">
<!-- 人机验证 --> <label for="captcha">验证码</label>
<div id="VAPTCHAContainer" style="width: 100%; height: 36px;"> <div class="captcha-wrapper">
<div class="VAPTCHA-init-main"> <input
<div class="VAPTCHA-init-loading"> type="text"
<a href="/" target="_blank"> id="captcha"
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48px" v-model="captcha"
height="60px" viewBox="0 0 24 30" placeholder="请输入验证码"
style="enable-background: new 0 0 50 50; width: 14px; height: 14px; vertical-align: middle" @blur="validateCaptcha"
xml:space="preserve"> :class="{ 'error': captchaError }"
<rect x="0" y="9.22656" width="4" height="12.5469" fill="#CCCCCC"> />
<animate attributeName="height" attributeType="XML" values="5;21;5" begin="0s" dur="0.6s" <img
repeatCount="indefinite"></animate> v-if="captchaImage"
<animate attributeName="y" attributeType="XML" values="13; 5; 13" begin="0s" dur="0.6s" :src="captchaImage"
repeatCount="indefinite"></animate> alt="验证码"
</rect> class="captcha-image"
<rect x="10" y="5.22656" width="4" height="20.5469" fill="#CCCCCC"> @click="refreshCaptcha"
<animate attributeName="height" attributeType="XML" values="5;21;5" begin="0.15s" dur="0.6s" />
repeatCount="indefinite"></animate>
<animate attributeName="y" attributeType="XML" values="13; 5; 13" begin="0.15s" dur="0.6s"
repeatCount="indefinite"></animate>
</rect>
<rect x="20" y="8.77344" width="4" height="13.4531" fill="#CCCCCC">
<animate attributeName="height" attributeType="XML" values="5;21;5" begin="0.3s" dur="0.6s"
repeatCount="indefinite"></animate>
<animate attributeName="y" attributeType="XML" values="13; 5; 13" begin="0.3s" dur="0.6s"
repeatCount="indefinite"></animate>
</rect>
</svg>
</a>
<span class="VAPTCHA-text">Vaptcha Initializing...</span>
</div>
</div>
</div> </div>
<span class="error-message" v-if="captchaError">{{ captchaError }}</span>
</div> </div>
<div class="login-button"> <div class="login-button">
<button type="submit" :disabled="!isFormValid">登录</button> <button type="submit" :disabled="!isFormValid">登录</button>
@ -74,18 +60,19 @@
<script setup> <script setup>
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { userLogin } from '../api/login' import { userLogin, getCaptcha } from '../api/login'
const router = useRouter() const router = useRouter()
const VAPTCHAObj = ref(null)
const username = ref('') const username = ref('')
const password = ref('') const password = ref('')
const captcha = ref('')
const captchaImage = ref('')
const captchaToken = ref('')
// //
const usernameError = ref('') const usernameError = ref('')
const passwordError = ref('') const passwordError = ref('')
const captchaError = ref('')
const isVaptchaVerified = ref(false)
const validateUsername = () => { const validateUsername = () => {
if (!username.value) { if (!username.value) {
@ -121,40 +108,43 @@ const validatePassword = () => {
return true return true
} }
const validateCaptcha = () => {
if (!captcha.value) {
captchaError.value = '请输入验证码'
return false
}
if (captcha.value.length !== 4) {
captchaError.value = '验证码长度不正确'
return false
}
captchaError.value = ''
return true
}
const isFormValid = computed(() => { const isFormValid = computed(() => {
return !usernameError.value && return !usernameError.value &&
!passwordError.value && !passwordError.value &&
!captchaError.value &&
username.value && username.value &&
password.value && password.value &&
VAPTCHAObj.value && // captcha.value
isVaptchaVerified.value //
}) })
const refreshCaptcha = async () => {
try {
const response = await getCaptcha()
// 使APIimgbase64
captchaImage.value = `data:image/png;base64,${response.img}`
captchaToken.value = response.token
captcha.value = '' //
} catch (error) {
console.error('获取验证码失败:', error)
alert('获取验证码失败,请刷新页面重试')
}
}
onMounted(() => { onMounted(() => {
window.vaptcha({ refreshCaptcha()
vid: '6828264bdc0ff12924d9bfe3',
mode: 'click',
scene: 1,
container: '#VAPTCHAContainer',
area: 'auto'
}).then(function (obj) {
VAPTCHAObj.value = obj
obj.render()
//
obj.listen('pass', function () {
isVaptchaVerified.value = true
})
//
obj.listen('fail', function () {
isVaptchaVerified.value = false
})
//
obj.listen('close', function () {
isVaptchaVerified.value = false
})
})
}) })
const handleLogin = async () => { const handleLogin = async () => {
@ -163,58 +153,37 @@ const handleLogin = async () => {
if (!validateForm()) { if (!validateForm()) {
return return
} }
//
if (!VAPTCHAObj.value) {
alert('请完成人机验证')
return
}
const serverToken = await VAPTCHAObj.value.getServerToken()
console.log('获取到的 serverToken:', serverToken)
if (!serverToken || !serverToken.token || !serverToken.server) {
alert('获取验证token失败请重新验证')
return
}
const registerData = {
username: username.value,
password: password.value,
server: serverToken.server,
token: serverToken.token
}
// API // API
const response = await userLogin( const response = await userLogin(
registerData.username, username.value,
registerData.password, password.value,
registerData.server, captchaToken.value,
registerData.token captcha.value
) )
// //
if (response.access_token) { if (response.access_token) {
// token ID (QQ)
localStorage.setItem('access_token', response.access_token) localStorage.setItem('access_token', response.access_token)
localStorage.setItem('user_id', username.value) // username.value QQ localStorage.setItem('user_id', username.value)
router.push('/') router.push('/')
} else { } else {
throw new Error('登录失败:未获取到访问令牌') throw new Error('登录失败:未获取到访问令牌')
} }
} catch (error) { } catch (error) {
console.error('登录失败:', error) console.error('登录失败:', error)
if (error.response && error.response.status === 401) { if (error.response && error.response.status === 401) {
alert('错误的用户名或密码,或未注册') alert('错误的用户名或密码')
} else { } else {
alert(error.response?.data?.message || error.message || '登录失败,请稍后重试') alert(error.response?.data?.message || error.message || '登录失败,请稍后重试')
} }
//
refreshCaptcha()
} }
} }
const validateForm = () => { const validateForm = () => {
if (!username.value || !password.value) { return validateUsername() && validatePassword() && validateCaptcha()
alert('请输入用户名和密码')
return false
}
return true
} }
</script> </script>
@ -351,5 +320,98 @@ const validateForm = () => {
.input-container input.error { .input-container input.error {
border-color: #f56c6c; border-color: #f56c6c;
} }
.captcha-container {
margin-bottom: 16px;
width: 100%;
}
.captcha-wrapper {
display: flex;
gap: 8px;
align-items: center;
width: 100%;
position: relative;
}
.captcha-wrapper input {
flex: 1;
height: 40px;
border: 1px solid #d0d7de;
border-radius: 6px;
padding: 0 12px;
font-size: 15px;
background: #f7fbfd;
transition: border 0.2s;
width: calc(100% - 120px); /* 减去验证码图片的宽度和间距 */
}
.captcha-wrapper input:focus {
border: 1.5px solid #409eff;
outline: none;
background: #fff;
}
.captcha-image {
width: 110px;
height: 40px;
border-radius: 6px;
cursor: pointer;
object-fit: cover;
border: 1px solid #d0d7de;
transition: all 0.3s;
}
.captcha-image:hover {
opacity: 0.8;
}
/* 移动端适配 */
@media screen and (max-width: 480px) {
.login-form {
width: 90%;
max-width: 340px;
padding: 24px 20px 20px 20px;
}
.captcha-wrapper {
gap: 6px;
}
.captcha-wrapper input {
width: calc(100% - 100px);
}
.captcha-image {
width: 90px;
}
.input-container input,
.captcha-wrapper input {
height: 38px;
font-size: 14px;
}
.login-button button {
height: 40px;
font-size: 15px;
}
}
/* 超小屏幕适配 */
@media screen and (max-width: 320px) {
.login-form {
padding: 20px 16px 16px 16px;
}
.captcha-wrapper input {
width: calc(100% - 90px);
padding: 0 8px;
}
.captcha-image {
width: 80px;
}
}
</style> </style>

View File

@ -20,40 +20,26 @@
@blur="validateConfirmPassword" :class="{ 'error': confirmPasswordError }" /> @blur="validateConfirmPassword" :class="{ 'error': confirmPasswordError }" />
<span class="error-message" v-if="confirmPasswordError">{{ confirmPasswordError }}</span> <span class="error-message" v-if="confirmPasswordError">{{ confirmPasswordError }}</span>
</div> </div>
<div class="input-container"> <div class="input-container captcha-container">
<!-- 人机验证 --> <label for="captcha">验证码</label>
<div id="VAPTCHAContainer" style="width: 100%; height: 36px;"> <div class="captcha-wrapper">
<div class="VAPTCHA-init-main"> <input
<div class="VAPTCHA-init-loading"> type="text"
<a href="/" target="_blank"> id="captcha"
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48px" v-model="captcha"
height="60px" viewBox="0 0 24 30" placeholder="请输入验证码"
style="enable-background: new 0 0 50 50; width: 14px; height: 14px; vertical-align: middle" @blur="validateCaptcha"
xml:space="preserve"> :class="{ 'error': captchaError }"
<rect x="0" y="9.22656" width="4" height="12.5469" fill="#CCCCCC"> />
<animate attributeName="height" attributeType="XML" values="5;21;5" begin="0s" dur="0.6s" <img
repeatCount="indefinite"></animate> v-if="captchaImage"
<animate attributeName="y" attributeType="XML" values="13; 5; 13" begin="0s" dur="0.6s" :src="captchaImage"
repeatCount="indefinite"></animate> alt="验证码"
</rect> class="captcha-image"
<rect x="10" y="5.22656" width="4" height="20.5469" fill="#CCCCCC"> @click="refreshCaptcha"
<animate attributeName="height" attributeType="XML" values="5;21;5" begin="0.15s" dur="0.6s" />
repeatCount="indefinite"></animate>
<animate attributeName="y" attributeType="XML" values="13; 5; 13" begin="0.15s" dur="0.6s"
repeatCount="indefinite"></animate>
</rect>
<rect x="20" y="8.77344" width="4" height="13.4531" fill="#CCCCCC">
<animate attributeName="height" attributeType="XML" values="5;21;5" begin="0.3s" dur="0.6s"
repeatCount="indefinite"></animate>
<animate attributeName="y" attributeType="XML" values="13; 5; 13" begin="0.3s" dur="0.6s"
repeatCount="indefinite"></animate>
</rect>
</svg>
</a>
<span class="VAPTCHA-text">Vaptcha Initializing...</span>
</div>
</div>
</div> </div>
<span class="error-message" v-if="captchaError">{{ captchaError }}</span>
</div> </div>
<div class="login-button"> <div class="login-button">
<button type="submit" :disabled="!isFormValid">注册</button> <button type="submit" :disabled="!isFormValid">注册</button>
@ -68,13 +54,15 @@
<script setup> <script setup>
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { userRegister } from "@/api/login.js"; import { userRegister, getCaptcha } from "@/api/login.js";
const router = useRouter() const router = useRouter()
const VAPTCHAObj = ref(null)
const username = ref('') const username = ref('')
const password = ref('') const password = ref('')
const confirmPassword = ref('') const confirmPassword = ref('')
const captcha = ref('')
const captchaImage = ref('')
const captchaToken = ref('')
// emit // emit
const emit = defineEmits(['login']) const emit = defineEmits(['login'])
@ -83,8 +71,7 @@ const emit = defineEmits(['login'])
const usernameError = ref('') const usernameError = ref('')
const passwordError = ref('') const passwordError = ref('')
const confirmPasswordError = ref('') const confirmPasswordError = ref('')
const captchaError = ref('')
// //
const validateUsername = () => { const validateUsername = () => {
@ -126,92 +113,76 @@ const validateConfirmPassword = () => {
confirmPasswordError.value = '' confirmPasswordError.value = ''
return true return true
} }
const isVaptchaVerified = ref(false)
const validateCaptcha = () => {
if (!captcha.value) {
captchaError.value = '请输入验证码'
return false
}
if (captcha.value.length !== 4) {
captchaError.value = '验证码长度不正确'
return false
}
captchaError.value = ''
return true
}
// //
const isFormValid = computed(() => { const isFormValid = computed(() => {
return !usernameError.value && return !usernameError.value &&
!passwordError.value && !passwordError.value &&
!confirmPasswordError.value && !confirmPasswordError.value &&
!captchaError.value &&
username.value && username.value &&
password.value && password.value &&
confirmPassword.value && confirmPassword.value &&
VAPTCHAObj.value && // captcha.value
isVaptchaVerified.value //
}) })
const refreshCaptcha = async () => {
try {
const response = await getCaptcha()
captchaImage.value = `data:image/png;base64,${response.img}`
captchaToken.value = response.token
captcha.value = '' //
} catch (error) {
console.error('获取验证码失败:', error)
alert('获取验证码失败,请刷新页面重试')
}
}
//
onMounted(() => { onMounted(() => {
window.vaptcha({ refreshCaptcha()
vid: '6828264bdc0ff12924d9bfe3',
mode: 'click',
scene: 2,
container: '#VAPTCHAContainer',
area: 'auto'
}).then(function (obj) {
VAPTCHAObj.value = obj
obj.render()
//
obj.listen('pass', function () {
isVaptchaVerified.value = true
console.log('验证通过')
})
//
obj.listen('fail', function () {
isVaptchaVerified.value = false
console.log('验证失败')
})
//
obj.listen('close', function () {
isVaptchaVerified.value = false
console.log('验证关闭')
})
})
}) })
const handleRegister = async () => { const handleRegister = async () => {
try {
// //
if (!validateUsername() || !validatePassword() || !validateConfirmPassword()) { if (!validateUsername() || !validatePassword() || !validateConfirmPassword() || !validateCaptcha()) {
return return
} }
//
if (!isVaptchaVerified.value || !VAPTCHAObj.value) {
alert('请完成人机验证')
return
}
// token
const serverToken = await VAPTCHAObj.value.getServerToken()
console.log('获取到的 serverToken:', serverToken)
if (!serverToken || !serverToken.token || !serverToken.server) {
alert('获取验证token失败请重新验证')
return
}
//
const registerData = {
qq_code: username.value,
password: password.value,
server: serverToken.server,
token: serverToken.token
}
console.log('注册请求参数:', registerData)
// //
await userRegister( await userRegister(
registerData.qq_code, username.value,
registerData.password, password.value,
registerData.server, captchaToken.value,
registerData.token captcha.value
) )
alert('注册成功') alert('注册成功')
// //
emit('login') emit('login')
} catch (error) {
console.error('注册失败:', error)
if (error.response && error.response.status === 400) {
alert('该QQ号已被注册')
} else {
alert(error.response?.data?.message || error.message || '注册失败,请稍后重试')
}
//
refreshCaptcha()
}
} }
</script> </script>
@ -355,4 +326,97 @@ const handleRegister = async () => {
color: #666; color: #666;
vertical-align: middle; vertical-align: middle;
} }
.captcha-container {
margin-bottom: 16px;
width: 100%;
}
.captcha-wrapper {
display: flex;
gap: 8px;
align-items: center;
width: 100%;
position: relative;
}
.captcha-wrapper input {
flex: 1;
height: 40px;
border: 1px solid #d0d7de;
border-radius: 6px;
padding: 0 12px;
font-size: 15px;
background: #f7fbfd;
transition: border 0.2s;
width: calc(100% - 120px); /* 减去验证码图片的宽度和间距 */
}
.captcha-wrapper input:focus {
border: 1.5px solid #409eff;
outline: none;
background: #fff;
}
.captcha-image {
width: 110px;
height: 40px;
border-radius: 6px;
cursor: pointer;
object-fit: cover;
border: 1px solid #d0d7de;
transition: all 0.3s;
}
.captcha-image:hover {
opacity: 0.8;
}
/* 移动端适配 */
@media screen and (max-width: 480px) {
.login-form {
width: 90%;
max-width: 340px;
padding: 24px 20px 20px 20px;
}
.captcha-wrapper {
gap: 6px;
}
.captcha-wrapper input {
width: calc(100% - 100px);
}
.captcha-image {
width: 90px;
}
.input-container input,
.captcha-wrapper input {
height: 38px;
font-size: 14px;
}
.login-button button {
height: 40px;
font-size: 15px;
}
}
/* 超小屏幕适配 */
@media screen and (max-width: 320px) {
.login-form {
padding: 20px 16px 16px 16px;
}
.captcha-wrapper input {
width: calc(100% - 90px);
padding: 0 8px;
}
.captcha-image {
width: 80px;
}
}
</style> </style>