mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-17 02:07:29 +08:00
feat: 优化登录功能 添加UID登录功能
This commit is contained in:
@@ -2,27 +2,33 @@
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { onBeforeUnmount, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { getUserDetail } from '@/api/login';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { isElectron, setAnimationClass } from '@/utils';
|
||||
|
||||
defineOptions({
|
||||
name: 'CookieLogin'
|
||||
});
|
||||
|
||||
// Emits
|
||||
interface Emits {
|
||||
(e: 'loginSuccess', userProfile: any, loginType: string): void;
|
||||
(e: 'loginError', error: string): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const token = ref('');
|
||||
|
||||
// Token登录
|
||||
const loginByToken = async () => {
|
||||
if (!token.value.trim()) {
|
||||
message.error(t('login.message.tokenRequired'));
|
||||
const errorMsg = t('login.message.tokenRequired');
|
||||
message.error(errorMsg);
|
||||
emit('loginError', errorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -33,19 +39,22 @@ const loginByToken = async () => {
|
||||
// 获取用户信息验证token有效性
|
||||
const user = await getUserDetail();
|
||||
if (user.data && user.data.profile) {
|
||||
userStore.user = user.data.profile;
|
||||
localStorage.setItem('user', JSON.stringify(user.data.profile));
|
||||
message.success(t('login.message.tokenLoginSuccess'));
|
||||
router.push('/user');
|
||||
const successMsg = t('login.message.tokenLoginSuccess');
|
||||
message.success(successMsg);
|
||||
emit('loginSuccess', user.data.profile, 'token');
|
||||
} else {
|
||||
// token无效,清除localStorage
|
||||
localStorage.removeItem('token');
|
||||
message.error(t('login.message.tokenInvalid'));
|
||||
const errorMsg = t('login.message.tokenInvalid');
|
||||
message.error(errorMsg);
|
||||
emit('loginError', errorMsg);
|
||||
}
|
||||
} catch (error) {
|
||||
// token无效,清除localStorage
|
||||
localStorage.removeItem('token');
|
||||
message.error(t('login.message.tokenInvalid'));
|
||||
const errorMsg = t('login.message.tokenInvalid');
|
||||
message.error(errorMsg);
|
||||
emit('loginError', errorMsg);
|
||||
console.error('Token登录失败:', error);
|
||||
}
|
||||
};
|
||||
@@ -70,17 +79,20 @@ const handleCookieReceived = async (_event: any, cookieValue: string) => {
|
||||
// 验证Cookie有效性
|
||||
const user = await getUserDetail();
|
||||
if (user.data && user.data.profile) {
|
||||
userStore.user = user.data.profile;
|
||||
localStorage.setItem('user', JSON.stringify(user.data.profile));
|
||||
message.success(t('login.message.autoGetCookieSuccess'));
|
||||
router.push('/user');
|
||||
const successMsg = t('login.message.autoGetCookieSuccess');
|
||||
message.success(successMsg);
|
||||
emit('loginSuccess', user.data.profile, 'cookie');
|
||||
} else {
|
||||
localStorage.removeItem('token');
|
||||
message.error(t('login.message.autoGetCookieFailed'));
|
||||
const errorMsg = t('login.message.autoGetCookieFailed');
|
||||
message.error(errorMsg);
|
||||
emit('loginError', errorMsg);
|
||||
}
|
||||
} catch (error) {
|
||||
localStorage.removeItem('token');
|
||||
message.error(t('login.message.autoGetCookieFailed'));
|
||||
const errorMsg = t('login.message.autoGetCookieFailed');
|
||||
message.error(errorMsg);
|
||||
emit('loginError', errorMsg);
|
||||
console.error('自动获取Cookie失败:', error);
|
||||
}
|
||||
};
|
||||
@@ -102,18 +114,18 @@ onBeforeUnmount(() => {
|
||||
|
||||
<template>
|
||||
<div class="cookie-login" :class="setAnimationClass('animate__fadeInUp')">
|
||||
<div class="login-title">{{ t('login.title.token') }}</div>
|
||||
<div class="login-title">{{ t('login.title.cookie') }}</div>
|
||||
<div class="phone-page">
|
||||
<textarea
|
||||
v-model="token"
|
||||
class="token-input"
|
||||
:placeholder="t('login.placeholder.token')"
|
||||
:placeholder="t('login.placeholder.cookie')"
|
||||
rows="4"
|
||||
/>
|
||||
</div>
|
||||
<div class="text">{{ t('login.tokenTip') }}</div>
|
||||
<n-button class="btn-login" @click="loginByToken()">{{
|
||||
t('login.button.tokenLogin')
|
||||
t('login.button.cookieLogin')
|
||||
}}</n-button>
|
||||
<n-button v-if="isElectron" class="btn-auto-cookie" @click="autoGetCookie()" type="info">
|
||||
{{ t('login.button.autoGetCookie') }}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
<script lang="ts" setup>
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { onBeforeUnmount, onMounted } from 'vue';
|
||||
import { onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { checkQr, createQr, getQrKey, getUserDetail } from '@/api/login';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { setAnimationClass } from '@/utils';
|
||||
|
||||
defineOptions({
|
||||
name: 'QrLogin'
|
||||
});
|
||||
|
||||
// Emits
|
||||
interface Emits {
|
||||
(e: 'loginSuccess', userProfile: any, loginType: string): void;
|
||||
(e: 'loginError', error: string): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const qrUrl = ref<string>();
|
||||
const timerRef = ref(null);
|
||||
@@ -44,7 +48,9 @@ const loadLogin = async () => {
|
||||
} catch (error) {
|
||||
console.error(t('login.message.loadError'), error);
|
||||
qrStatus.value = 'expired';
|
||||
message.error(t('login.message.loadError'));
|
||||
const errorMsg = t('login.message.loadError');
|
||||
message.error(errorMsg);
|
||||
emit('loginError', errorMsg);
|
||||
} finally {
|
||||
isRefreshing.value = false;
|
||||
}
|
||||
@@ -60,7 +66,7 @@ const timerIsQr = (key: string) => {
|
||||
qrStatus.value = 'expired';
|
||||
clearInterval(timer);
|
||||
timerRef.value = null;
|
||||
message.warning('二维码已过期,请点击刷新获取新的二维码');
|
||||
message.warning(t('login.message.qrExpiredWarning'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,7 +79,7 @@ const timerIsQr = (key: string) => {
|
||||
// 已扫码,等待确认
|
||||
if (data.code === 802) {
|
||||
qrStatus.value = 'scanned';
|
||||
message.info('已扫码,请在手机上确认登录');
|
||||
message.info(t('login.message.qrScannedInfo'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,20 +88,21 @@ const timerIsQr = (key: string) => {
|
||||
qrStatus.value = 'confirmed';
|
||||
localStorage.setItem('token', data.cookie);
|
||||
const user = await getUserDetail();
|
||||
userStore.user = user.data.profile;
|
||||
localStorage.setItem('user', JSON.stringify(user.data.profile));
|
||||
message.success(t('login.message.loginSuccess'));
|
||||
const successMsg = t('login.message.loginSuccess');
|
||||
message.success(successMsg);
|
||||
emit('loginSuccess', user.data.profile, 'qr');
|
||||
|
||||
clearInterval(timer);
|
||||
timerRef.value = null;
|
||||
router.push('/user');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(t('login.message.qrCheckError'), error);
|
||||
qrStatus.value = 'expired';
|
||||
clearInterval(timer);
|
||||
timerRef.value = null;
|
||||
message.error('检查二维码状态失败,请刷新重试');
|
||||
const errorMsg = t('login.message.qrCheckFailed');
|
||||
message.error(errorMsg);
|
||||
emit('loginError', errorMsg);
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
@@ -111,15 +118,15 @@ const refreshQr = () => {
|
||||
const getStatusText = () => {
|
||||
switch (qrStatus.value) {
|
||||
case 'loading':
|
||||
return '正在加载二维码...';
|
||||
return t('login.message.qrLoading');
|
||||
case 'active':
|
||||
return t('login.qrTip');
|
||||
case 'expired':
|
||||
return '二维码已过期,请点击刷新';
|
||||
return t('login.message.qrExpired');
|
||||
case 'scanned':
|
||||
return '已扫码,请在手机上确认登录';
|
||||
return t('login.message.qrScanned');
|
||||
case 'confirmed':
|
||||
return '登录成功,正在跳转...';
|
||||
return t('login.message.qrConfirmed');
|
||||
default:
|
||||
return t('login.qrTip');
|
||||
}
|
||||
@@ -129,7 +136,7 @@ onMounted(() => {
|
||||
loadLogin();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
onUnmounted(() => {
|
||||
if (timerRef.value) {
|
||||
clearInterval(timerRef.value);
|
||||
timerRef.value = null;
|
||||
@@ -146,7 +153,7 @@ onBeforeUnmount(() => {
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="qrStatus === 'loading'" class="qr-loading">
|
||||
<n-spin size="large" />
|
||||
<div class="loading-text">正在生成二维码...</div>
|
||||
<div class="loading-text">{{ t('login.message.qrGenerating') }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码图片 -->
|
||||
@@ -155,16 +162,16 @@ onBeforeUnmount(() => {
|
||||
|
||||
<!-- 过期遮罩 -->
|
||||
<div v-if="qrStatus === 'expired'" class="expired-overlay">
|
||||
<div class="expired-text">二维码已过期</div>
|
||||
<div class="expired-text">{{ t('login.message.qrExpiredShort') }}</div>
|
||||
<n-button class="refresh-btn" type="primary" @click="refreshQr" :loading="isRefreshing">
|
||||
{{ isRefreshing ? '刷新中...' : '点击刷新' }}
|
||||
{{ isRefreshing ? t('login.button.refreshing') : t('login.button.refresh') }}
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<!-- 已扫码遮罩 -->
|
||||
<div v-if="qrStatus === 'scanned'" class="scanned-overlay">
|
||||
<div class="scanned-icon">✓</div>
|
||||
<div class="scanned-text">已扫码</div>
|
||||
<div class="scanned-text">{{ t('login.message.qrScannedShort') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -177,7 +184,7 @@ onBeforeUnmount(() => {
|
||||
<!-- 手动刷新按钮 -->
|
||||
<div v-if="qrStatus === 'active'" class="refresh-area">
|
||||
<n-button text class="manual-refresh" @click="refreshQr" :loading="isRefreshing">
|
||||
刷新二维码
|
||||
{{ t('login.button.refreshQr') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div class="uid-login">
|
||||
<div class="login-title">{{ t('login.title.uid') }}</div>
|
||||
<div class="uid-page">
|
||||
<input
|
||||
v-model="uid"
|
||||
class="uid-input"
|
||||
type="text"
|
||||
:placeholder="t('login.placeholder.uid')"
|
||||
@keyup.enter="handleLogin"
|
||||
/>
|
||||
</div>
|
||||
<div class="text">{{ t('login.uidTip') }}</div>
|
||||
<div class="warning-text">
|
||||
{{ t('login.uidWarning') }}
|
||||
</div>
|
||||
<n-button class="btn-login" :loading="loading" @click="handleLogin">
|
||||
{{ t('login.button.login') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import { loginByUid } from '@/api/login';
|
||||
|
||||
defineOptions({
|
||||
name: 'UidLogin'
|
||||
});
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
disabled: false
|
||||
});
|
||||
|
||||
// Emits
|
||||
interface Emits {
|
||||
(e: 'loginSuccess', userProfile: any, loginType: string): void;
|
||||
(e: 'loginError', error: string): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const message = useMessage();
|
||||
|
||||
// 状态
|
||||
const uid = ref('');
|
||||
const loading = ref(false);
|
||||
|
||||
// UID登录处理
|
||||
const handleLogin = async () => {
|
||||
if (props.disabled || loading.value) return;
|
||||
|
||||
if (!uid.value.trim()) {
|
||||
const errorMsg = t('login.message.uidRequired');
|
||||
message.error(errorMsg);
|
||||
emit('loginError', errorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
const { data } = await loginByUid(uid.value);
|
||||
|
||||
if (data && data.profile) {
|
||||
const successMsg = t('login.message.uidLoginSuccess');
|
||||
message.success(successMsg);
|
||||
emit('loginSuccess', data.profile, 'uid');
|
||||
} else {
|
||||
const errorMsg = t('login.message.uidInvalid');
|
||||
message.error(errorMsg);
|
||||
emit('loginError', errorMsg);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('UID登录失败:', error);
|
||||
let errorMsg = t('login.message.uidLoginFailed');
|
||||
|
||||
if (error.response?.status === 404 || error.response?.data?.code === 404) {
|
||||
errorMsg = t('login.message.uidInvalid');
|
||||
}
|
||||
|
||||
message.error(errorMsg);
|
||||
emit('loginError', errorMsg);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 重置表单
|
||||
const reset = () => {
|
||||
uid.value = '';
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
reset
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.uid-login {
|
||||
animation-duration: 0.5s;
|
||||
width: 250px;
|
||||
|
||||
.login-title {
|
||||
@apply text-2xl font-bold mb-6 text-white;
|
||||
}
|
||||
|
||||
.text {
|
||||
@apply mt-4 text-white text-xs;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
@apply mt-2 text-orange-400 text-xs text-center max-w-xs;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.uid-page {
|
||||
@apply bg-light dark:bg-gray-800 bg-opacity-90 dark:bg-opacity-90;
|
||||
@apply rounded-2xl overflow-hidden;
|
||||
}
|
||||
|
||||
.uid-input {
|
||||
height: 40px;
|
||||
@apply w-full px-4 outline-none;
|
||||
@apply text-gray-900 dark:text-white bg-transparent;
|
||||
@apply border-b border-gray-200 dark:border-gray-700;
|
||||
@apply placeholder-gray-500 dark:placeholder-gray-400;
|
||||
|
||||
&:focus {
|
||||
@apply border-green-500;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
width: 250px;
|
||||
height: 40px;
|
||||
@apply mt-10 text-white rounded-xl;
|
||||
@apply bg-green-600 hover:bg-green-700 transition-colors;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user