fix(songitem): 添加到歌单权限判断改为响应式并提示不可用原因

- 权限判断从 localStorage 快照改为 userStore 响应式状态,登录/登出后菜单状态即时刷新
- 菜单项不再灰色禁用,未登录或 UID 登录时点击给出明确提示(新增 5 语言文案)

Closes #706
This commit is contained in:
alger
2026-07-05 14:09:57 +08:00
parent f4346f4c79
commit a3840e2bae
6 changed files with 90 additions and 71 deletions
+2
View File
@@ -13,6 +13,8 @@ export default {
}, },
message: { message: {
downloading: 'Downloading, please wait...', downloading: 'Downloading, please wait...',
addToPlaylistNeedLogin:
'Please log in with Cookie or QR code to add songs to a playlist (not available for UID login)',
downloadFailed: 'Download failed', downloadFailed: 'Download failed',
downloadQueued: 'Added to download queue', downloadQueued: 'Added to download queue',
addedToNextPlay: 'Added to play next', addedToNextPlay: 'Added to play next',
+35 -33
View File
@@ -1,33 +1,35 @@
export default { export default {
menu: { menu: {
play: '再生', play: '再生',
playNext: '次に再生', playNext: '次に再生',
download: '楽曲をダウンロード', download: '楽曲をダウンロード',
downloadLyric: '歌詞をダウンロード', downloadLyric: '歌詞をダウンロード',
addToPlaylist: 'プレイリストに追加', addToPlaylist: 'プレイリストに追加',
favorite: 'いいね', favorite: 'いいね',
unfavorite: 'いいね解除', unfavorite: 'いいね解除',
removeFromPlaylist: 'プレイリストから削除', removeFromPlaylist: 'プレイリストから削除',
dislike: '嫌い', dislike: '嫌い',
undislike: '嫌い解除' undislike: '嫌い解除'
}, },
message: { message: {
downloading: 'ダウンロード中です。しばらくお待ちください...', downloading: 'ダウンロード中です。しばらくお待ちください...',
downloadFailed: 'ダウンロードに失敗しました', addToPlaylistNeedLogin:
downloadQueued: 'ダウンロードキューに追加しました', 'プレイリストに追加するには Cookie または QR コードでログインしてください(UID ログインでは利用できません)',
addedToNextPlay: '次の再生に追加しました', downloadFailed: 'ダウンロードに失敗しました',
getUrlFailed: downloadQueued: 'ダウンロードキューに追加しました',
'音楽ダウンロードアドレスの取得に失敗しました。ログインしているか確認してください', addedToNextPlay: '次の再生に追加しました',
noLyric: 'この楽曲には歌詞がありません', getUrlFailed:
lyricDownloaded: '歌詞のダウンロードが完了しました', '音楽ダウンロードアドレスの取得に失敗しました。ログインしているか確認してください',
lyricDownloadFailed: '歌詞のダウンロードに失敗しました' noLyric: 'この楽曲には歌詞がありません',
}, lyricDownloaded: '歌詞のダウンロードが完了しました',
dialog: { lyricDownloadFailed: '歌詞のダウンロードに失敗しました'
dislike: { },
title: 'お知らせ!', dialog: {
content: 'この楽曲を嫌いにしますか?再度アクセスすると毎日のおすすめから除外されます。', dislike: {
positiveText: '嫌い', title: 'お知らせ!',
negativeText: 'キャンセル' content: 'この楽曲を嫌いにしますか?再度アクセスすると毎日のおすすめから除外されます。',
} positiveText: '嫌い',
} negativeText: 'キャンセル'
}; }
}
};
+34 -32
View File
@@ -1,32 +1,34 @@
export default { export default {
menu: { menu: {
play: '재생', play: '재생',
playNext: '다음에 재생', playNext: '다음에 재생',
download: '곡 다운로드', download: '곡 다운로드',
downloadLyric: '가사 다운로드', downloadLyric: '가사 다운로드',
addToPlaylist: '플레이리스트에 추가', addToPlaylist: '플레이리스트에 추가',
favorite: '좋아요', favorite: '좋아요',
unfavorite: '좋아요 취소', unfavorite: '좋아요 취소',
removeFromPlaylist: '플레이리스트에서 삭제', removeFromPlaylist: '플레이리스트에서 삭제',
dislike: '싫어요', dislike: '싫어요',
undislike: '싫어요 취소' undislike: '싫어요 취소'
}, },
message: { message: {
downloading: '다운로드 중입니다. 잠시 기다려주세요...', downloading: '다운로드 중입니다. 잠시 기다려주세요...',
downloadFailed: '다운로드 실패', addToPlaylistNeedLogin:
downloadQueued: '다운로드 대기열에 추가됨', '플레이리스트에 추가하려면 Cookie 또는 QR 코드로 로그인하세요 (UID 로그인은 사용 불가)',
addedToNextPlay: '다음 재생에 추가됨', downloadFailed: '다운로드 실패',
getUrlFailed: '음악 다운로드 주소 가져오기 실패, 로그인 상태를 확인하세요', downloadQueued: '다운로드 대기열에 추가됨',
noLyric: '이 곡에는 가사가 없습니다', addedToNextPlay: '다음 재생에 추가됨',
lyricDownloaded: '가사 다운로드 완료', getUrlFailed: '음악 다운로드 주소 가져오기 실패, 로그인 상태를 확인하세요',
lyricDownloadFailed: '가사 다운로드 실패' noLyric: '이 곡에는 가사가 없습니다',
}, lyricDownloaded: '가사 다운로드 완료',
dialog: { lyricDownloadFailed: '가사 다운로드 실패'
dislike: { },
title: '알림!', dialog: {
content: '이 곡을 싫어한다고 확인하시겠습니까? 다시 들어가면 일일 추천에서 제외됩니다.', dislike: {
positiveText: '싫어요', title: '알림!',
negativeText: '취소' content: '이 곡을 싫어한다고 확인하시겠습니까? 다시 들어가면 일일 추천에서 제외됩니다.',
} positiveText: '싫어요',
} negativeText: '취소'
}; }
}
};
+1
View File
@@ -13,6 +13,7 @@ export default {
}, },
message: { message: {
downloading: '正在下载中,请稍候...', downloading: '正在下载中,请稍候...',
addToPlaylistNeedLogin: '请使用 Cookie 或扫码登录后再添加到歌单(UID 登录无法使用此功能)',
downloadFailed: '下载失败', downloadFailed: '下载失败',
downloadQueued: '已加入下载队列', downloadQueued: '已加入下载队列',
addedToNextPlay: '已添加到下一首播放', addedToNextPlay: '已添加到下一首播放',
+1
View File
@@ -13,6 +13,7 @@ export default {
}, },
message: { message: {
downloading: '正在下載中,請稍候...', downloading: '正在下載中,請稍候...',
addToPlaylistNeedLogin: '請使用 Cookie 或掃碼登入後再新增至播放清單(UID 登入無法使用此功能)',
downloadFailed: '下載失敗', downloadFailed: '下載失敗',
downloadQueued: '已加入下載佇列', downloadQueued: '已加入下載佇列',
addedToNextPlay: '已新增至下一首播放', addedToNextPlay: '已新增至下一首播放',
@@ -15,13 +15,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { MenuOption } from 'naive-ui'; import type { MenuOption } from 'naive-ui';
import { NDropdown, NEllipsis, NImage } from 'naive-ui'; import { createDiscreteApi, NDropdown, NEllipsis, NImage } from 'naive-ui';
import { computed, h, inject } from 'vue'; import { computed, h, inject } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useUserStore } from '@/store';
import type { SongResult } from '@/types/music'; import type { SongResult } from '@/types/music';
import { getImgUrl, isElectron } from '@/utils'; import { getImgUrl, isElectron } from '@/utils';
import { hasPermission } from '@/utils/auth';
const { message } = createDiscreteApi(['message']);
const { t } = useI18n(); const { t } = useI18n();
@@ -50,6 +52,13 @@ const emits = defineEmits([
const openPlaylistDrawer = inject<(songId: number | string) => void>('openPlaylistDrawer'); const openPlaylistDrawer = inject<(songId: number | string) => void>('openPlaylistDrawer');
const userStore = useUserStore();
// 是否具有真实登录权限(Cookie/扫码登录)。
// 用 userStore 的响应式状态而非直接读 localStorage
// 后者不具备响应性,登录/登出后菜单禁用状态不会刷新(#706)
const hasRealAuth = computed(() => !!userStore.user && userStore.loginType !== 'uid');
// 渲染歌曲预览 // 渲染歌曲预览
const renderSongPreview = () => { const renderSongPreview = () => {
return h( return h(
@@ -123,8 +132,6 @@ const renderSongPreview = () => {
// 下拉菜单选项 // 下拉菜单选项
const dropdownOptions = computed<MenuOption[]>(() => { const dropdownOptions = computed<MenuOption[]>(() => {
const hasRealAuth = hasPermission(true);
const options: MenuOption[] = [ const options: MenuOption[] = [
{ {
key: 'header', key: 'header',
@@ -160,10 +167,10 @@ const dropdownOptions = computed<MenuOption[]>(() => {
icon: () => h('i', { class: 'iconfont ri-file-text-line' }) icon: () => h('i', { class: 'iconfont ri-file-text-line' })
}, },
{ {
// 不做灰色禁用:点击时提示不可用原因,避免用户不知道"为什么用不了"#706
label: t('songItem.menu.addToPlaylist'), label: t('songItem.menu.addToPlaylist'),
key: 'addToPlaylist', key: 'addToPlaylist',
icon: () => h('i', { class: 'iconfont ri-folder-add-line' }), icon: () => h('i', { class: 'iconfont ri-folder-add-line' })
disabled: !hasRealAuth
}, },
{ {
label: props.isFavorite ? t('songItem.menu.unfavorite') : t('songItem.menu.favorite'), label: props.isFavorite ? t('songItem.menu.unfavorite') : t('songItem.menu.favorite'),
@@ -216,6 +223,10 @@ const handleSelect = (key: string | number) => {
emits('play-next'); emits('play-next');
break; break;
case 'addToPlaylist': case 'addToPlaylist':
if (!hasRealAuth.value) {
message.warning(t('songItem.message.addToPlaylistNeedLogin'));
break;
}
openPlaylistDrawer?.(props.item.id); openPlaylistDrawer?.(props.item.id);
break; break;
case 'favorite': case 'favorite':