mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-05-17 02:07:29 +08:00
@@ -1,10 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { checkQr, createQr, getQrKey, getUserDetail, loginByCellphone } from '@/api/login';
|
||||
import { loginByCellphone } from '@/api/login';
|
||||
import CookieLogin from '@/components/login/CookieLogin.vue';
|
||||
import QrLogin from '@/components/login/QrLogin.vue';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { setAnimationClass } from '@/utils';
|
||||
|
||||
@@ -16,79 +17,10 @@ const { t } = useI18n();
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const isQr = ref(true);
|
||||
const isTokenLogin = ref(false);
|
||||
|
||||
const qrUrl = ref<string>();
|
||||
const userStore = useUserStore();
|
||||
|
||||
onMounted(() => {
|
||||
loadLogin();
|
||||
});
|
||||
|
||||
const timerRef = ref(null);
|
||||
|
||||
const loadLogin = async () => {
|
||||
try {
|
||||
if (timerRef.value) {
|
||||
clearInterval(timerRef.value);
|
||||
timerRef.value = null;
|
||||
}
|
||||
if (!isQr.value) return;
|
||||
const qrKey = await getQrKey();
|
||||
const key = qrKey.data.data.unikey;
|
||||
const { data } = await createQr(key);
|
||||
qrUrl.value = data.data.qrimg;
|
||||
|
||||
const timer = timerIsQr(key);
|
||||
timerRef.value = timer as any;
|
||||
} catch (error) {
|
||||
console.error(t('login.message.loadError'), error);
|
||||
}
|
||||
};
|
||||
|
||||
const timerIsQr = (key: string) => {
|
||||
const timer = setInterval(async () => {
|
||||
try {
|
||||
const { data } = await checkQr(key);
|
||||
|
||||
if (data.code === 800) {
|
||||
clearInterval(timer);
|
||||
timerRef.value = null;
|
||||
}
|
||||
if (data.code === 803) {
|
||||
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'));
|
||||
|
||||
clearInterval(timer);
|
||||
timerRef.value = null;
|
||||
router.push('/user');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(t('login.message.qrCheckError'), error);
|
||||
clearInterval(timer);
|
||||
timerRef.value = null;
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
return timer;
|
||||
};
|
||||
|
||||
// 离开页面时
|
||||
onBeforeUnmount(() => {
|
||||
if (timerRef.value) {
|
||||
clearInterval(timerRef.value);
|
||||
timerRef.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
// 是否扫码登陆
|
||||
// const chooseQr = () => {
|
||||
// isQr.value = !isQr.value;
|
||||
// loadLogin();
|
||||
// };
|
||||
|
||||
// 手机号登录
|
||||
const phone = ref('');
|
||||
const password = ref('');
|
||||
@@ -110,12 +42,17 @@ const loginPhone = async () => {
|
||||
<div class="phone-login">
|
||||
<div class="bg"></div>
|
||||
<div class="content">
|
||||
<div v-if="isQr" class="phone" :class="setAnimationClass('animate__fadeInUp')">
|
||||
<div class="login-title">{{ t('login.title.qr') }}</div>
|
||||
<img class="qr-img" :src="qrUrl" />
|
||||
<div class="text">{{ t('login.qrTip') }}</div>
|
||||
<!-- 二维码登录组件 -->
|
||||
<div v-if="isQr && !isTokenLogin" class="phone">
|
||||
<qr-login />
|
||||
</div>
|
||||
<div v-else class="phone" :class="setAnimationClass('animate__fadeInUp')">
|
||||
|
||||
<!-- 手机号登录 -->
|
||||
<div
|
||||
v-else-if="!isQr && !isTokenLogin"
|
||||
class="phone"
|
||||
:class="setAnimationClass('animate__fadeInUp')"
|
||||
>
|
||||
<div class="login-title">{{ t('login.title.phone') }}</div>
|
||||
<div class="phone-page">
|
||||
<input
|
||||
@@ -134,11 +71,18 @@ const loginPhone = async () => {
|
||||
<div class="text">{{ t('login.phoneTip') }}</div>
|
||||
<n-button class="btn-login" @click="loginPhone()">{{ t('login.button.login') }}</n-button>
|
||||
</div>
|
||||
|
||||
<!-- Cookie登录组件 -->
|
||||
<div v-else-if="isTokenLogin" class="phone">
|
||||
<cookie-login />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<!-- <div class="title" @click="chooseQr()">
|
||||
{{ isQr ? t('login.button.switchToPhone') : t('login.button.switchToQr') }}
|
||||
</div> -->
|
||||
<div class="login-switch">
|
||||
<div class="title" @click="isTokenLogin = !isTokenLogin">
|
||||
{{ isTokenLogin ? t('login.button.backToQr') : t('login.button.switchToToken') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -184,9 +128,6 @@ const loginPhone = async () => {
|
||||
|
||||
.content {
|
||||
@apply absolute w-full h-full p-4 flex flex-col items-center justify-center pb-20 text-center;
|
||||
.qr-img {
|
||||
@apply rounded-2xl cursor-pointer transition-opacity;
|
||||
}
|
||||
|
||||
.phone {
|
||||
animation-duration: 0.5s;
|
||||
@@ -208,6 +149,7 @@ const loginPhone = async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
width: 250px;
|
||||
height: 40px;
|
||||
|
||||
@@ -108,6 +108,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Token管理 -->
|
||||
<div class="set-item">
|
||||
<div>
|
||||
<div class="set-item-title">{{ t('settings.basic.tokenManagement') }}</div>
|
||||
<div class="set-item-content">
|
||||
<div class="text-sm text-gray-500 mb-2">
|
||||
{{ t('settings.basic.tokenStatus') }}:
|
||||
{{
|
||||
currentToken ? t('settings.basic.tokenSet') : t('settings.basic.tokenNotSet')
|
||||
}}
|
||||
</div>
|
||||
<div v-if="currentToken" class="text-xs text-gray-400 mb-2 font-mono break-all">
|
||||
{{ currentToken.substring(0, 50) }}...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<n-button size="small" @click="showTokenModal = true">
|
||||
{{
|
||||
currentToken ? t('settings.basic.modifyToken') : t('settings.basic.setToken')
|
||||
}}
|
||||
</n-button>
|
||||
<n-button v-if="currentToken" size="small" type="error" @click="clearToken">
|
||||
{{ t('settings.basic.clearToken') }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="set-item">
|
||||
<div>
|
||||
<div class="set-item-title">{{ t('settings.basic.animation') }}</div>
|
||||
@@ -524,6 +552,44 @@
|
||||
<remote-control-setting v-model:visible="showRemoteControlModal" />
|
||||
</template>
|
||||
|
||||
<!-- Token设置弹窗 -->
|
||||
<n-modal v-model:show="showTokenModal" preset="dialog" title="Cookie设置">
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="ri-key-line"></i>
|
||||
<span>Cookie设置</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mb-2">
|
||||
请输入网易云音乐的Cookie:
|
||||
</div>
|
||||
<n-input
|
||||
v-model:value="tokenInput"
|
||||
type="textarea"
|
||||
placeholder="请粘贴完整的Cookie..."
|
||||
:rows="6"
|
||||
:autosize="{ minRows: 4, maxRows: 8 }"
|
||||
style="font-family: monospace; font-size: 12px"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
<p>• Cookie通常以 "MUSIC_U=" 开头</p>
|
||||
<p>• 可以从浏览器开发者工具的网络请求中获取</p>
|
||||
<p>• Cookie设置后将自动保存到本地存储</p>
|
||||
</div>
|
||||
</div>
|
||||
<template #action>
|
||||
<div class="flex gap-2">
|
||||
<n-button @click="showTokenModal = false">取消</n-button>
|
||||
<n-button type="primary" @click="saveToken" :disabled="!tokenInput.trim()">
|
||||
保存Cookie
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
<!-- 清除缓存弹窗 -->
|
||||
<clear-cache-settings v-model:show="showClearCacheModal" @confirm="clearCache" />
|
||||
</div>
|
||||
@@ -536,6 +602,7 @@ import { computed, h, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import localData from '@/../main/set.json';
|
||||
import { getUserDetail } from '@/api/login';
|
||||
import Coffee from '@/components/Coffee.vue';
|
||||
import DonationList from '@/components/common/DonationList.vue';
|
||||
import PlayBottom from '@/components/common/PlayBottom.vue';
|
||||
@@ -991,6 +1058,84 @@ const showMusicSourcesModal = ref(false);
|
||||
|
||||
// 远程控制设置弹窗
|
||||
const showRemoteControlModal = ref(false);
|
||||
|
||||
// Token管理相关
|
||||
const showTokenModal = ref(false);
|
||||
const tokenInput = ref('');
|
||||
const currentToken = ref(localStorage.getItem('token') || '');
|
||||
|
||||
// 保存Token
|
||||
const saveToken = async () => {
|
||||
if (!tokenInput.value.trim()) {
|
||||
message.error('请输入Token');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 临时保存原有token
|
||||
const originalToken = localStorage.getItem('token');
|
||||
|
||||
// 设置新token
|
||||
localStorage.setItem('token', tokenInput.value.trim());
|
||||
|
||||
// 验证token有效性
|
||||
const user = await getUserDetail();
|
||||
if (user.data && user.data.profile) {
|
||||
// token有效,更新用户信息
|
||||
userStore.setUser(user.data.profile);
|
||||
currentToken.value = tokenInput.value.trim();
|
||||
message.success('Token设置成功');
|
||||
showTokenModal.value = false;
|
||||
tokenInput.value = '';
|
||||
|
||||
// 刷新当前页面
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
// token无效,恢复原有token
|
||||
if (originalToken) {
|
||||
localStorage.setItem('token', originalToken);
|
||||
} else {
|
||||
localStorage.removeItem('token');
|
||||
}
|
||||
message.error('Token无效,请检查后重试');
|
||||
}
|
||||
} catch (error) {
|
||||
// token无效,恢复原有token
|
||||
const originalToken = localStorage.getItem('token');
|
||||
if (originalToken) {
|
||||
localStorage.setItem('token', originalToken);
|
||||
} else {
|
||||
localStorage.removeItem('token');
|
||||
}
|
||||
message.error('Token无效,请检查后重试');
|
||||
console.error('Token验证失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 清除Token
|
||||
const clearToken = () => {
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
currentToken.value = '';
|
||||
userStore.user = null;
|
||||
message.success('Token已清除');
|
||||
|
||||
// 刷新页面
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
// 监听localStorage中token的变化
|
||||
watch(
|
||||
() => localStorage.getItem('token'),
|
||||
(newToken) => {
|
||||
currentToken.value = newToken || '';
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user