mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-08 01:20:49 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abdb2bcd50 |
2
components.d.ts
vendored
2
components.d.ts
vendored
@@ -29,6 +29,8 @@ declare module 'vue' {
|
||||
NModal: typeof import('naive-ui')['NModal']
|
||||
NPagination: typeof import('naive-ui')['NPagination']
|
||||
NPopover: typeof import('naive-ui')['NPopover']
|
||||
NRadioButton: typeof import('naive-ui')['NRadioButton']
|
||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSlider: typeof import('naive-ui')['NSlider']
|
||||
NSpin: typeof import('naive-ui')['NSpin']
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "alger-music",
|
||||
"version": "2.4.0",
|
||||
"version": "2.5.0",
|
||||
"description": "这是一个用于音乐播放的应用程序。",
|
||||
"author": "Alger <algerkc@qq.com>",
|
||||
"main": "app.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="app-container" :class="{ mobile: isMobile, noElectron: !isElectron }">
|
||||
<n-config-provider :theme="darkTheme">
|
||||
<n-config-provider :theme="theme === 'dark' ? darkTheme : lightTheme">
|
||||
<n-dialog-provider>
|
||||
<n-message-provider>
|
||||
<router-view></router-view>
|
||||
@@ -11,7 +11,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { darkTheme } from 'naive-ui';
|
||||
import { darkTheme, lightTheme } from 'naive-ui';
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
import { isElectron } from '@/hooks/MusicHook';
|
||||
@@ -20,8 +20,13 @@ import store from '@/store';
|
||||
|
||||
import { isMobile } from './utils';
|
||||
|
||||
const theme = computed(() => {
|
||||
return store.state.theme;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch('initializeSettings');
|
||||
store.dispatch('initializeTheme');
|
||||
if (isMobile.value) {
|
||||
store.commit(
|
||||
'setMenus',
|
||||
|
||||
@@ -12,20 +12,23 @@
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
<div class="p-6 bg-black rounded-lg shadow-lg">
|
||||
<div class="p-6 rounded-lg shadow-lg bg-light dark:bg-gray-800">
|
||||
<div class="flex gap-10">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<n-image :src="alipayQR" alt="支付宝收款码" class="w-32 h-32 rounded-lg cursor-none" preview-disabled />
|
||||
<span class="text-sm text-gray-100">支付宝</span>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-200">支付宝</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<n-image :src="wechatQR" alt="微信收款码" class="w-32 h-32 rounded-lg cursor-none" preview-disabled />
|
||||
<span class="text-sm text-gray-100">微信支付</span>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-200">微信支付</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<p class="text-sm text-gray-100 text-center cursor-pointer hover:text-green-500" @click="copyQQ">
|
||||
<p
|
||||
class="text-sm text-gray-700 dark:text-gray-200 text-center cursor-pointer hover:text-green-500"
|
||||
@click="copyQQ"
|
||||
>
|
||||
QQ群:789288579
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -225,16 +225,16 @@ watch(
|
||||
<style scoped lang="scss">
|
||||
.music {
|
||||
&-title {
|
||||
@apply text-xl font-bold text-white;
|
||||
@apply text-xl font-bold text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
&-page {
|
||||
@apply px-8 w-full h-full bg-black bg-opacity-75 rounded-t-2xl;
|
||||
@apply px-8 w-full h-full bg-light dark:bg-black bg-opacity-75 dark:bg-opacity-75 rounded-t-2xl;
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
|
||||
&-close {
|
||||
@apply cursor-pointer text-white flex gap-2 items-center;
|
||||
@apply cursor-pointer text-gray-900 dark:text-white flex gap-2 items-center;
|
||||
.icon {
|
||||
@apply text-3xl;
|
||||
}
|
||||
@@ -248,7 +248,7 @@ watch(
|
||||
@apply w-[25%] flex-shrink-0 pr-8 flex flex-col;
|
||||
|
||||
.music-cover {
|
||||
@apply w-full aspect-square rounded-lg overflow-hidden mb-4;
|
||||
@apply w-full aspect-square rounded-2xl overflow-hidden mb-4;
|
||||
.cover-img {
|
||||
@apply w-full h-full object-cover;
|
||||
}
|
||||
@@ -260,22 +260,21 @@ watch(
|
||||
.creator-info {
|
||||
@apply flex items-center mb-4;
|
||||
.creator-name {
|
||||
@apply ml-2 text-sm text-gray-300;
|
||||
@apply ml-2 text-gray-700 dark:text-gray-300;
|
||||
}
|
||||
}
|
||||
|
||||
.music-desc {
|
||||
@apply text-sm text-gray-400;
|
||||
@apply text-sm text-gray-600 dark:text-gray-400 leading-relaxed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-list-container {
|
||||
@apply flex-grow min-h-0 flex flex-col relative;
|
||||
}
|
||||
|
||||
&-list {
|
||||
@apply flex-grow min-h-0;
|
||||
&-container {
|
||||
@apply flex-grow min-h-0 flex flex-col relative;
|
||||
}
|
||||
|
||||
&-content {
|
||||
@apply min-h-[calc(80vh-60px)];
|
||||
@@ -312,16 +311,10 @@ watch(
|
||||
}
|
||||
|
||||
.loading-more {
|
||||
@apply text-center text-white py-10;
|
||||
@apply text-center py-4 text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
.double-list {
|
||||
.double-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.song-item {
|
||||
background-color: #191919;
|
||||
}
|
||||
.double-item {
|
||||
@apply mb-2 bg-light-100 bg-opacity-20 dark:bg-dark-100 dark:bg-opacity-20 rounded-3xl;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -549,226 +549,57 @@ const isMobile = computed(() => store.state.isMobile);
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mv-detail {
|
||||
@apply w-full h-full bg-black relative;
|
||||
@apply h-full bg-light dark:bg-black;
|
||||
|
||||
// 添加横屏模式支持
|
||||
@media screen and (orientation: landscape) {
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
&-title {
|
||||
@apply fixed top-0 left-0 right-0 p-4 z-10 transition-opacity duration-300;
|
||||
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.7), transparent);
|
||||
|
||||
.title {
|
||||
@apply text-white text-lg font-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-container {
|
||||
@apply h-full w-full relative;
|
||||
|
||||
.video-player {
|
||||
@apply h-full w-full object-contain bg-black;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
@apply w-full h-full relative;
|
||||
transition: cursor 0.3s ease;
|
||||
|
||||
// 移动端适配
|
||||
@media (max-width: 768px) {
|
||||
.custom-controls {
|
||||
.controls-main {
|
||||
@apply flex-wrap gap-2 justify-center;
|
||||
|
||||
.left-controls,
|
||||
.right-controls {
|
||||
@apply w-full justify-center;
|
||||
}
|
||||
|
||||
.time-display {
|
||||
@apply order-first w-full text-center mb-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 调整标题样式
|
||||
.mv-detail-title {
|
||||
.title {
|
||||
@apply text-base max-w-full;
|
||||
}
|
||||
}
|
||||
|
||||
// 调整进度条
|
||||
.progress-bar {
|
||||
@apply mb-2;
|
||||
}
|
||||
.play-hint {
|
||||
@apply absolute inset-0 flex items-center justify-center bg-black bg-opacity-50;
|
||||
.n-button {
|
||||
@apply text-white hover:text-green-500 transition-colors;
|
||||
}
|
||||
}
|
||||
|
||||
&.cursor-hidden {
|
||||
* {
|
||||
cursor: none !important;
|
||||
}
|
||||
.custom-controls {
|
||||
@apply absolute bottom-0 left-0 right-0 p-4 transition-opacity duration-300;
|
||||
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
||||
|
||||
// 控制栏区域保持鼠标可见
|
||||
.custom-controls {
|
||||
* {
|
||||
cursor: default !important;
|
||||
}
|
||||
.controls-main {
|
||||
@apply flex justify-between items-center;
|
||||
|
||||
.left-controls,
|
||||
.right-controls {
|
||||
@apply flex items-center gap-2;
|
||||
|
||||
.n-button {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.n-slider {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:fullscreen,
|
||||
&:-webkit-full-screen,
|
||||
&:-moz-full-screen,
|
||||
&:-ms-fullscreen {
|
||||
background: black;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
// 确保全屏时标题栏正确显示
|
||||
.mv-detail-title {
|
||||
@apply px-8 py-6;
|
||||
|
||||
.title {
|
||||
@apply text-xl;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保全屏时控制栏正确显示
|
||||
.custom-controls {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
@apply absolute inset-0 bg-black opacity-0 transition-opacity duration-200;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:active::after {
|
||||
@apply opacity-10;
|
||||
}
|
||||
|
||||
video {
|
||||
@apply w-full h-full;
|
||||
}
|
||||
|
||||
.custom-controls {
|
||||
@apply absolute bottom-0 left-0 w-full transition-opacity duration-300 ease-in-out;
|
||||
background: linear-gradient(0deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
padding: 16px 20px;
|
||||
|
||||
&.controls-hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
@apply mb-4;
|
||||
|
||||
.progress-rail {
|
||||
@apply relative w-full h-full;
|
||||
|
||||
.progress-buffer {
|
||||
@apply absolute h-full bg-gray-600 rounded-full;
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.controls-main {
|
||||
@apply flex justify-between items-center;
|
||||
|
||||
.left-controls,
|
||||
.right-controls {
|
||||
@apply flex items-center gap-4;
|
||||
@apply text-white hover:text-green-500 transition-colors;
|
||||
}
|
||||
|
||||
.time-display {
|
||||
@apply text-sm text-white ml-2;
|
||||
}
|
||||
|
||||
.volume-control {
|
||||
@apply flex items-center gap-2;
|
||||
|
||||
.volume-slider {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.n-button {
|
||||
@apply text-white;
|
||||
|
||||
&:hover {
|
||||
@apply text-green-400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.play-hint {
|
||||
@apply absolute inset-0 flex items-center justify-center bg-black bg-opacity-50 cursor-pointer;
|
||||
z-index: 10;
|
||||
|
||||
.n-button {
|
||||
@apply text-white opacity-80 transform transition-all duration-300;
|
||||
|
||||
&:hover {
|
||||
@apply opacity-100 scale-110;
|
||||
@apply text-white text-sm ml-4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mv-detail-title {
|
||||
@apply absolute w-full left-0 top-0 px-6 py-4 transition-opacity duration-300 z-50;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 100%);
|
||||
|
||||
&.title-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-white text-lg font-medium;
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-slider {
|
||||
:deep(.n-slider) {
|
||||
--n-rail-height: 4px;
|
||||
--n-rail-color: rgba(255, 255, 255, 0.2);
|
||||
--n-fill-color: var(--primary-color);
|
||||
--n-handle-size: 12px;
|
||||
--n-handle-color: var(--primary-color);
|
||||
|
||||
&:hover {
|
||||
--n-rail-height: 6px;
|
||||
--n-handle-size: 14px;
|
||||
}
|
||||
|
||||
.n-slider-rail {
|
||||
@apply overflow-hidden;
|
||||
}
|
||||
|
||||
.n-slider-handle {
|
||||
@apply transition-opacity duration-200;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover .n-slider-handle {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary-color: #18a058;
|
||||
}
|
||||
|
||||
// 添加模式提示样式
|
||||
.mode-hint {
|
||||
@apply absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2;
|
||||
@apply flex flex-col items-center justify-center;
|
||||
@apply bg-black bg-opacity-70 rounded-lg p-4;
|
||||
z-index: 20;
|
||||
@apply absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex flex-col items-center;
|
||||
|
||||
.mode-icon {
|
||||
@apply text-white mb-2;
|
||||
@@ -779,7 +610,49 @@ const isMobile = computed(() => store.state.isMobile);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加过渡动画
|
||||
.custom-slider {
|
||||
:deep(.n-slider) {
|
||||
--n-rail-height: 4px;
|
||||
--n-rail-color: rgba(255, 255, 255, 0.2);
|
||||
--n-fill-color: #10b981;
|
||||
--n-handle-size: 12px;
|
||||
--n-handle-color: #10b981;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
@apply mb-4;
|
||||
|
||||
.progress-rail {
|
||||
@apply relative w-full h-1 bg-gray-600;
|
||||
|
||||
.progress-buffer {
|
||||
@apply absolute top-0 left-0 h-full bg-gray-400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.volume-control {
|
||||
@apply flex items-center gap-2;
|
||||
|
||||
.volume-slider {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.controls-hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.cursor-hidden {
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
.title-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
@@ -789,79 +662,4 @@ const isMobile = computed(() => store.state.isMobile);
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// 添加 tooltip 样式
|
||||
:deep(.n-tooltip) {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
// 调左侧控制按钮的样式
|
||||
.left-controls {
|
||||
@apply flex items-center gap-2;
|
||||
|
||||
.time-display {
|
||||
@apply text-sm text-white ml-4; // 增加时间显示的左边距
|
||||
}
|
||||
}
|
||||
|
||||
// 可以添加按钮禁用状态的样式
|
||||
:deep(.n-button--disabled) {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
// 添加加载动画样式
|
||||
:deep(.n-spin) {
|
||||
.n-spin-body {
|
||||
@apply text-white;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加视频播放器样式
|
||||
.video-player {
|
||||
@apply w-full h-full cursor-pointer;
|
||||
}
|
||||
|
||||
// 添加点击反馈效果
|
||||
.video-container {
|
||||
&::after {
|
||||
content: '';
|
||||
@apply absolute inset-0 bg-black opacity-0 transition-opacity duration-200;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:active::after {
|
||||
@apply opacity-10;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加鼠标隐藏样式
|
||||
.video-container {
|
||||
@apply w-full h-full relative;
|
||||
transition: cursor 0.3s ease;
|
||||
|
||||
&.cursor-hidden {
|
||||
* {
|
||||
cursor: none !important;
|
||||
}
|
||||
|
||||
// 控制栏区域保持鼠标可见
|
||||
.custom-controls {
|
||||
* {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.n-button {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.n-slider {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -132,15 +132,15 @@ onMounted(() => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
@apply text-lg font-bold mb-4;
|
||||
@apply text-lg font-bold mb-4 text-gray-900 dark:text-white;
|
||||
}
|
||||
.play-list-type {
|
||||
width: 250px;
|
||||
@apply mx-6;
|
||||
&-item,
|
||||
&-showall {
|
||||
@apply py-2 px-3 mr-3 mb-3 inline-block border border-gray-700 rounded-xl cursor-pointer hover:bg-green-600 transition;
|
||||
background-color: #1a1a1a;
|
||||
@apply bg-light dark:bg-black text-gray-900 dark:text-white;
|
||||
@apply py-2 px-3 mr-3 mb-3 inline-block border border-gray-200 dark:border-gray-700 rounded-xl cursor-pointer hover:bg-green-600 hover:text-white transition;
|
||||
}
|
||||
&-showall {
|
||||
@apply block text-center;
|
||||
|
||||
@@ -81,7 +81,7 @@ onMounted(() => {
|
||||
.recommend-album {
|
||||
@apply flex-1 mx-5;
|
||||
.title {
|
||||
@apply text-lg font-bold mb-4;
|
||||
@apply text-lg font-bold mb-4 text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.recommend-album-list {
|
||||
@@ -95,7 +95,7 @@ onMounted(() => {
|
||||
filter: brightness(50%);
|
||||
}
|
||||
&-content {
|
||||
@apply w-full h-full opacity-0 transition absolute z-10 top-0 left-0 p-4 text-xl bg-opacity-60 bg-black;
|
||||
@apply w-full h-full opacity-0 transition absolute z-10 top-0 left-0 p-4 text-xl text-white bg-opacity-60 bg-black dark:bg-opacity-60 dark:bg-black;
|
||||
}
|
||||
&-content:hover {
|
||||
opacity: 1;
|
||||
|
||||
@@ -131,14 +131,20 @@ watchEffect(() => {
|
||||
&-item {
|
||||
@apply flex-1 h-full rounded-3xl p-5 mr-5 flex flex-col justify-between overflow-hidden;
|
||||
&-bg {
|
||||
@apply bg-gray-900 bg-no-repeat bg-cover bg-center rounded-3xl absolute w-full h-full top-0 left-0 z-0;
|
||||
@apply bg-gray-900 dark:bg-gray-800 bg-no-repeat bg-cover bg-center rounded-3xl absolute w-full h-full top-0 left-0 z-0;
|
||||
filter: brightness(60%);
|
||||
}
|
||||
&-info {
|
||||
@apply flex items-center p-2;
|
||||
&-play {
|
||||
@apply w-12 h-12 bg-green-500 rounded-full flex justify-center items-center hover:bg-green-600 cursor-pointer;
|
||||
@apply w-12 h-12 bg-green-500 rounded-full flex justify-center items-center hover:bg-green-600 cursor-pointer text-white;
|
||||
}
|
||||
&-name {
|
||||
@apply text-gray-100 dark:text-gray-100;
|
||||
}
|
||||
}
|
||||
&-count {
|
||||
@apply text-gray-100 dark:text-gray-100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,17 +51,15 @@ const handlePlay = () => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
@apply text-lg font-bold mb-4;
|
||||
@apply text-lg font-bold mb-4 text-gray-900 dark:text-white;
|
||||
}
|
||||
.recommend-music {
|
||||
@apply flex-auto;
|
||||
// width: 530px;
|
||||
.text-ellipsis {
|
||||
width: 100%;
|
||||
}
|
||||
&-list {
|
||||
@apply rounded-3xl p-2 w-full border border-gray-700;
|
||||
background-color: #0d0d0d;
|
||||
@apply rounded-3xl p-2 w-full border border-gray-200 dark:border-gray-700 bg-light dark:bg-black;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<i class="iconfont icon-likefill" :class="{ 'like-active': isFavorite }" @click.stop="toggleFavorite"></i>
|
||||
</div>
|
||||
<div
|
||||
class="song-item-operating-play bg-black animate__animated"
|
||||
class="song-item-operating-play bg-gray-300 dark:bg-gray-800 animate__animated"
|
||||
:class="{ 'bg-green-600': isPlaying, animate__flipInY: playLoading }"
|
||||
@click="playMusicEvent(item)"
|
||||
>
|
||||
@@ -139,72 +139,98 @@ const toggleFavorite = async (e: Event) => {
|
||||
.text-ellipsis {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.song-item {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
@apply rounded-3xl p-3 flex items-center hover:bg-gray-800 transition;
|
||||
@apply rounded-3xl p-3 flex items-center transition bg-transparent dark:text-white text-gray-900;
|
||||
|
||||
&:hover {
|
||||
@apply bg-gray-100 dark:bg-gray-800;
|
||||
}
|
||||
|
||||
&-img {
|
||||
@apply w-12 h-12 rounded-2xl mr-4;
|
||||
}
|
||||
|
||||
&-content {
|
||||
@apply flex-1;
|
||||
|
||||
&-title {
|
||||
@apply text-base text-white;
|
||||
@apply text-base text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
&-name {
|
||||
@apply text-xs;
|
||||
@apply text-gray-400;
|
||||
@apply text-xs text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
&-operating {
|
||||
@apply flex items-center rounded-full border border-gray-700 ml-4;
|
||||
background-color: #0d0d0d;
|
||||
@apply flex items-center rounded-full ml-4 border dark:border-gray-700 border-gray-200 bg-light dark:bg-black;
|
||||
|
||||
.iconfont {
|
||||
@apply text-xl;
|
||||
}
|
||||
|
||||
.icon-likefill {
|
||||
color: #868686;
|
||||
@apply text-xl hover:text-red-600 transition;
|
||||
@apply text-xl transition text-gray-500 dark:text-gray-400 hover:text-red-500;
|
||||
}
|
||||
|
||||
&-like {
|
||||
@apply mr-2 cursor-pointer ml-4;
|
||||
}
|
||||
|
||||
.like-active {
|
||||
@apply text-red-600;
|
||||
@apply text-red-500;
|
||||
}
|
||||
|
||||
&-play {
|
||||
@apply cursor-pointer border border-gray-500 rounded-full w-10 h-10 flex justify-center items-center hover:bg-green-600 transition;
|
||||
animation-iteration-count: infinite;
|
||||
@apply cursor-pointer rounded-full w-10 h-10 flex justify-center items-center transition
|
||||
border dark:border-gray-700 border-gray-200 text-gray-900 dark:text-white;
|
||||
|
||||
&:hover,
|
||||
&.bg-green-600 {
|
||||
@apply bg-green-500 border-green-500 text-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.song-mini {
|
||||
@apply p-2 rounded-2xl;
|
||||
|
||||
.song-item {
|
||||
@apply p-0;
|
||||
|
||||
&-img {
|
||||
@apply w-10 h-10 mr-2;
|
||||
}
|
||||
|
||||
&-content {
|
||||
@apply flex-1;
|
||||
|
||||
&-title {
|
||||
@apply text-sm;
|
||||
}
|
||||
|
||||
&-name {
|
||||
@apply text-xs;
|
||||
}
|
||||
}
|
||||
|
||||
&-operating {
|
||||
@apply pl-2;
|
||||
|
||||
.iconfont {
|
||||
@apply text-base;
|
||||
}
|
||||
|
||||
&-like {
|
||||
@apply mr-1 ml-1;
|
||||
}
|
||||
|
||||
&-play {
|
||||
@apply w-8 h-8;
|
||||
}
|
||||
@@ -213,35 +239,50 @@ const toggleFavorite = async (e: Event) => {
|
||||
}
|
||||
|
||||
.song-list {
|
||||
@apply p-2 rounded-lg hover:bg-gray-800/50 border border-gray-800/50 mb-2;
|
||||
@apply p-2 rounded-lg mb-2 border dark:border-gray-800 border-gray-200;
|
||||
|
||||
&:hover {
|
||||
@apply bg-gray-50 dark:bg-gray-800;
|
||||
}
|
||||
|
||||
.song-item-img {
|
||||
@apply w-10 h-10 rounded-lg mr-3;
|
||||
}
|
||||
|
||||
.song-item-content {
|
||||
@apply flex items-center flex-1;
|
||||
|
||||
&-wrapper {
|
||||
@apply flex items-center flex-1 text-sm;
|
||||
}
|
||||
|
||||
&-title {
|
||||
@apply text-white flex-shrink-0 max-w-[45%];
|
||||
@apply flex-shrink-0 max-w-[45%] text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
&-divider {
|
||||
@apply mx-2 text-gray-400;
|
||||
@apply mx-2 text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
&-name {
|
||||
@apply text-gray-400 flex-1 min-w-0;
|
||||
@apply flex-1 min-w-0 text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
.song-item-operating {
|
||||
@apply flex items-center gap-2;
|
||||
|
||||
&-like {
|
||||
@apply cursor-pointer hover:scale-110 transition-transform;
|
||||
|
||||
.iconfont {
|
||||
@apply text-base text-gray-400 hover:text-red-500;
|
||||
@apply text-base text-gray-500 dark:text-gray-400 hover:text-red-500;
|
||||
}
|
||||
}
|
||||
|
||||
&-play {
|
||||
@apply w-7 h-7 cursor-pointer hover:scale-110 transition-transform;
|
||||
|
||||
.iconfont {
|
||||
@apply text-base;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!-- -->
|
||||
<template>
|
||||
<div v-if="isShow" class="loading-box">
|
||||
<div class="mask" :style="{ background: maskBackground }"></div>
|
||||
<div class="mask"></div>
|
||||
<div class="loading-content-box">
|
||||
<n-spin size="small" />
|
||||
<div :style="{ color: textColor }" class="tip">{{ tip }}</div>
|
||||
@@ -23,7 +23,7 @@ defineProps({
|
||||
maskBackground: {
|
||||
type: String,
|
||||
default() {
|
||||
return 'rgba(0, 0, 0, 0.8)';
|
||||
return 'rgba(0, 0, 0, 0.05)';
|
||||
},
|
||||
},
|
||||
loadingColor: {
|
||||
@@ -70,6 +70,7 @@ defineExpose({
|
||||
.mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@apply bg-light-100 bg-opacity-50 dark:bg-dark-100 dark:bg-opacity-50;
|
||||
}
|
||||
.loading-content-box {
|
||||
position: absolute;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { computed, ref } from 'vue';
|
||||
import { audioService } from '@/services/audioService';
|
||||
import store from '@/store';
|
||||
import type { ILyricText, SongResult } from '@/type/music';
|
||||
import { getTextColors } from '@/utils/linearColor';
|
||||
|
||||
const windowData = window as any;
|
||||
|
||||
@@ -18,6 +19,7 @@ export const currentLrcProgress = ref(0); // 来存储当前歌词的进度
|
||||
export const playMusic = computed(() => store.state.playMusic as SongResult); // 当前播放歌曲
|
||||
export const sound = ref<Howl | null>(audioService.getCurrentSound());
|
||||
export const isLyricWindowOpen = ref(false); // 新增状态
|
||||
export const textColors = ref(getTextColors());
|
||||
|
||||
document.onkeyup = (e) => {
|
||||
// 检查事件目标是否是输入框元素
|
||||
|
||||
@@ -11,9 +11,50 @@
|
||||
}
|
||||
|
||||
.n-slider-handle-indicator--top {
|
||||
@apply bg-transparent text-[#ffffffdd] text-2xl px-2 py-1 shadow-none mb-0 !important;
|
||||
@apply bg-transparent dark:text-[#ffffffdd] text-[#000000dd] text-2xl px-2 py-1 shadow-none mb-0 !important;
|
||||
}
|
||||
|
||||
.text-el {
|
||||
@apply overflow-ellipsis overflow-hidden whitespace-nowrap;
|
||||
}
|
||||
|
||||
|
||||
.theme-dark {
|
||||
--bg-color: #000;
|
||||
--text-color: #fff;
|
||||
--bg-color-100: #161616;
|
||||
--bg-color-200: #2d2d2d;
|
||||
--bg-color-300: #3d3d3d;
|
||||
--text-color: #f8f9fa;
|
||||
--text-color-100: #e9ecef;
|
||||
--text-color-200: #dee2e6;
|
||||
--text-color-300: #dde0e3;
|
||||
--primary-color: #22c55e;
|
||||
}
|
||||
|
||||
.theme-light {
|
||||
--bg-color: #fff;
|
||||
--text-color: #000;
|
||||
--bg-color-100: #f8f9fa;
|
||||
--bg-color-200: #e9ecef;
|
||||
--bg-color-300: #dee2e6;
|
||||
--text-color: #000;
|
||||
--text-color-100: #161616;
|
||||
--text-color-200: #2d2d2d;
|
||||
--text-color-300: #3d3d3d;
|
||||
--primary-color: #22c55e;
|
||||
}
|
||||
|
||||
.theme-gray {
|
||||
--bg-color: #f8f9fa;
|
||||
--text-color: #000;
|
||||
--bg-color-100: #e9ecef;
|
||||
--bg-color-200: #dee2e6;
|
||||
--bg-color-300: #dde0e3;
|
||||
--text-color: #000;
|
||||
--text-color-100: #161616;
|
||||
--text-color-200: #2d2d2d;
|
||||
--text-color-300: #3d3d3d;
|
||||
--primary-color: #22c55e;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="layout-page">
|
||||
<div id="layout-main" class="layout-main" :style="{ background: backgroundColor }">
|
||||
<div id="layout-main" class="layout-main">
|
||||
<title-bar v-if="isElectron" />
|
||||
<div class="layout-main-page" :class="isElectron ? '' : 'pt-6'">
|
||||
<!-- 侧边菜单栏 -->
|
||||
@@ -32,6 +32,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
@@ -47,8 +48,6 @@ const keepAliveInclude = computed(() =>
|
||||
return item.meta.keepAlive;
|
||||
})
|
||||
.map((item) => {
|
||||
// return item.name;
|
||||
// 首字母大写
|
||||
return item.name.charAt(0).toUpperCase() + item.name.slice(1);
|
||||
}),
|
||||
);
|
||||
@@ -63,42 +62,34 @@ const store = useStore();
|
||||
const isPlay = computed(() => store.state.isPlay as boolean);
|
||||
const { menus } = store.state;
|
||||
const route = useRoute();
|
||||
|
||||
const backgroundColor = ref('#000');
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout-page {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
@apply flex justify-center items-center overflow-hidden bg-black;
|
||||
@apply w-screen h-screen overflow-hidden bg-light dark:bg-black;
|
||||
}
|
||||
|
||||
.layout-main {
|
||||
@apply text-white shadow-xl flex flex-col relative transition-all;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
&-page {
|
||||
@apply flex flex-1 overflow-hidden;
|
||||
}
|
||||
.main {
|
||||
@apply flex-1 box-border flex flex-col overflow-hidden;
|
||||
height: 100%;
|
||||
&-content {
|
||||
@apply box-border flex-1 overflow-hidden;
|
||||
}
|
||||
}
|
||||
// :deep(.n-scrollbar-content) {
|
||||
// @apply pr-3;
|
||||
// }
|
||||
@apply w-full h-full relative text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
.layout-main {
|
||||
&-page {
|
||||
@apply pt-4;
|
||||
}
|
||||
}
|
||||
.layout-main-page {
|
||||
@apply flex h-full;
|
||||
}
|
||||
|
||||
.menu {
|
||||
@apply h-full;
|
||||
}
|
||||
|
||||
.main {
|
||||
@apply overflow-hidden flex-1 flex flex-col;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
@apply flex-1 overflow-hidden;
|
||||
}
|
||||
|
||||
.main-page {
|
||||
@apply h-full;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -65,9 +65,10 @@ const iconStyle = (index: number) => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-menu {
|
||||
@apply flex-col items-center justify-center px-6;
|
||||
@apply flex-col items-center justify-center px-6 bg-light dark:bg-black;
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.app-menu-item-link,
|
||||
.app-menu-header {
|
||||
@apply flex items-center justify-center;
|
||||
@@ -77,10 +78,12 @@ const iconStyle = (index: number) => {
|
||||
@apply mb-6 mt-6;
|
||||
}
|
||||
|
||||
.app-menu-item-icon:hover {
|
||||
color: #10b981 !important;
|
||||
transform: scale(1.05);
|
||||
transition: 0.2s ease-in-out;
|
||||
.app-menu-item-icon {
|
||||
@apply transition-all duration-200 text-gray-500 dark:text-gray-400;
|
||||
|
||||
&:hover {
|
||||
@apply text-green-500 scale-105;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
@@ -89,13 +92,16 @@ const iconStyle = (index: number) => {
|
||||
width: 100vw;
|
||||
position: relative;
|
||||
z-index: 999999;
|
||||
background-color: #000;
|
||||
@apply bg-light dark:bg-black border-t border-gray-200 dark:border-gray-700;
|
||||
|
||||
&-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-list {
|
||||
@apply flex justify-between;
|
||||
}
|
||||
|
||||
&-item {
|
||||
&-link {
|
||||
@apply my-4;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
>
|
||||
<div id="drawer-target">
|
||||
<div class="drawer-back"></div>
|
||||
<div class="music-img">
|
||||
<div class="music-img" :style="{ color: textColors.theme === 'dark' ? '#000000' : '#ffffff' }">
|
||||
<n-image ref="PicImgRef" :src="getImgUrl(playMusic?.picUrl, '300y300')" class="img" lazy preview-disabled />
|
||||
<div>
|
||||
<div class="music-content-name">{{ playMusic.name }}</div>
|
||||
@@ -56,7 +56,7 @@
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
import { onBeforeUnmount, ref, watch } from 'vue';
|
||||
|
||||
import { lrcArray, nowIndex, playMusic, setAudioTime, useLyricProgress } from '@/hooks/MusicHook';
|
||||
import { lrcArray, nowIndex, playMusic, setAudioTime, textColors, useLyricProgress } from '@/hooks/MusicHook';
|
||||
import { getImgUrl } from '@/utils';
|
||||
import { animateGradient, getHoverBackgroundColor, getTextColors } from '@/utils/linearColor';
|
||||
|
||||
@@ -68,9 +68,6 @@ const currentBackground = ref('');
|
||||
const animationFrame = ref<number | null>(null);
|
||||
const isDark = ref(false);
|
||||
|
||||
// 初始化 textColors
|
||||
const textColors = ref(getTextColors());
|
||||
|
||||
const props = defineProps({
|
||||
musicFull: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -6,6 +6,15 @@
|
||||
<div
|
||||
class="music-play-bar"
|
||||
:class="setAnimationClass('animate__bounceInUp') + ' ' + (musicFullVisible ? 'play-bar-opcity' : '')"
|
||||
:style="{
|
||||
color: musicFullVisible
|
||||
? textColors.theme === 'dark'
|
||||
? '#000000'
|
||||
: '#ffffff'
|
||||
: store.state.theme === 'dark'
|
||||
? '#ffffff'
|
||||
: '#000000',
|
||||
}"
|
||||
>
|
||||
<div class="music-time custom-slider">
|
||||
<n-slider v-model:value="timeSlider" :step="1" :max="allTime" :min="0" :format-tooltip="formatTooltip"></n-slider>
|
||||
@@ -117,7 +126,7 @@ import { useTemplateRef } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
import SongItem from '@/components/common/SongItem.vue';
|
||||
import { allTime, isElectron, isLyricWindowOpen, nowTime, openLyric, sound } from '@/hooks/MusicHook';
|
||||
import { allTime, isElectron, isLyricWindowOpen, nowTime, openLyric, sound, textColors } from '@/hooks/MusicHook';
|
||||
import type { SongResult } from '@/type/music';
|
||||
import { getImgUrl, secondToMinute, setAnimationClass } from '@/utils';
|
||||
|
||||
@@ -270,26 +279,26 @@ const openLyricWindow = () => {
|
||||
|
||||
.music-play-bar {
|
||||
@apply h-20 w-full absolute bottom-0 left-0 flex items-center box-border px-6 py-2 pt-3;
|
||||
@apply bg-light dark:bg-dark shadow-2xl shadow-gray-300;
|
||||
z-index: 9999;
|
||||
box-shadow: 0px 0px 10px 2px rgba(203, 203, 203, 0.034);
|
||||
background-color: #212121;
|
||||
animation-duration: 0.5s !important;
|
||||
|
||||
.music-content {
|
||||
width: 160px;
|
||||
@apply ml-4;
|
||||
|
||||
&-title {
|
||||
@apply text-base text-white;
|
||||
@apply text-base;
|
||||
}
|
||||
|
||||
&-name {
|
||||
@apply text-xs mt-1 text-gray-100;
|
||||
@apply text-xs mt-1 opacity-80;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.play-bar-opcity {
|
||||
@apply bg-transparent;
|
||||
@apply bg-transparent !important;
|
||||
box-shadow: 0 0 20px 5px #0000001d;
|
||||
}
|
||||
|
||||
@@ -301,11 +310,13 @@ const openLyricWindow = () => {
|
||||
@apply mx-6 flex-1 flex justify-center;
|
||||
|
||||
.iconfont {
|
||||
@apply text-2xl hover:text-green-500 transition;
|
||||
@apply text-2xl transition;
|
||||
@apply hover:text-green-500;
|
||||
}
|
||||
|
||||
.icon {
|
||||
@apply text-3xl hover:text-white;
|
||||
@apply text-3xl;
|
||||
@apply hover:text-green-500;
|
||||
}
|
||||
|
||||
@apply flex items-center;
|
||||
@@ -315,8 +326,8 @@ const openLyricWindow = () => {
|
||||
}
|
||||
|
||||
&-play {
|
||||
background-color: #ffffff20;
|
||||
@apply flex justify-center items-center w-20 h-12 rounded-full mx-4 hover:bg-[#ffffff40] transition;
|
||||
@apply flex justify-center items-center w-20 h-12 rounded-full mx-4 transition text-gray-500;
|
||||
@apply bg-gray-100 bg-opacity-60 hover:bg-gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,14 +340,17 @@ const openLyricWindow = () => {
|
||||
}
|
||||
.volume-icon {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@apply text-2xl hover:text-green-500 transition;
|
||||
}
|
||||
.iconfont {
|
||||
@apply text-2xl transition;
|
||||
@apply hover:text-green-500;
|
||||
}
|
||||
|
||||
.volume-slider {
|
||||
@apply absolute opacity-0 invisible transition-all duration-300 bottom-[30px] left-1/2 -translate-x-1/2 h-[180px] px-2 py-4 bg-gray-800 bg-opacity-80 rounded-xl;
|
||||
@apply absolute opacity-0 invisible transition-all duration-300 bottom-[30px] left-1/2 -translate-x-1/2 h-[180px] px-2 py-4 rounded-xl;
|
||||
@apply bg-light dark:bg-gray-800;
|
||||
@apply border border-gray-200 dark:border-gray-700;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +358,8 @@ const openLyricWindow = () => {
|
||||
@apply flex items-center mx-4;
|
||||
|
||||
.iconfont {
|
||||
@apply text-2xl hover:text-green-500 transition cursor-pointer m-4;
|
||||
@apply text-2xl transition cursor-pointer m-4;
|
||||
@apply hover:text-green-500;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +370,8 @@ const openLyricWindow = () => {
|
||||
@apply relative rounded-3xl overflow-hidden py-2;
|
||||
&-back {
|
||||
backdrop-filter: blur(20px);
|
||||
@apply absolute top-0 left-0 w-full h-full bg-gray-800 bg-opacity-75;
|
||||
@apply absolute top-0 left-0 w-full h-full;
|
||||
@apply bg-light dark:bg-black bg-opacity-75;
|
||||
}
|
||||
&-content {
|
||||
@apply mx-2;
|
||||
@@ -395,14 +411,15 @@ const openLyricWindow = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 添加自定义 slider 样式
|
||||
// 自定义滑块样式
|
||||
.custom-slider {
|
||||
:deep(.n-slider) {
|
||||
--n-rail-height: 4px;
|
||||
--n-rail-color: rgba(255, 255, 255, 0.2);
|
||||
--n-fill-color: var(--primary-color);
|
||||
--n-rail-color: theme('colors.gray.200');
|
||||
--n-rail-color-dark: theme('colors.gray.700');
|
||||
--n-fill-color: theme('colors.green.500');
|
||||
--n-handle-size: 12px;
|
||||
--n-handle-color: var(--primary-color);
|
||||
--n-handle-color: theme('colors.green.500');
|
||||
|
||||
&.n-slider--vertical {
|
||||
height: 100%;
|
||||
@@ -425,6 +442,7 @@ const openLyricWindow = () => {
|
||||
|
||||
.n-slider-rail {
|
||||
@apply overflow-hidden transition-all duration-200;
|
||||
@apply bg-gray-500 dark:bg-dark-300 bg-opacity-10 !important;
|
||||
}
|
||||
|
||||
.n-slider-handle {
|
||||
@@ -438,10 +456,6 @@ const openLyricWindow = () => {
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--primary-color: #18a058;
|
||||
}
|
||||
|
||||
.play-bar-img-wrapper {
|
||||
@apply relative cursor-pointer w-14 h-14;
|
||||
|
||||
@@ -478,7 +492,7 @@ const openLyricWindow = () => {
|
||||
}
|
||||
|
||||
.like-active {
|
||||
@apply text-red-600;
|
||||
@apply text-red-500 hover:text-red-600 !important;
|
||||
}
|
||||
|
||||
.icon-loop,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
size="medium"
|
||||
round
|
||||
:placeholder="hotSearchKeyword"
|
||||
class="border border-gray-600"
|
||||
class="border dark:border-gray-600 border-gray-200"
|
||||
@keydown.enter="search"
|
||||
>
|
||||
<template #prefix>
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="user-box">
|
||||
<n-avatar
|
||||
v-if="store.state.user"
|
||||
class="ml-2 cursor-pointer"
|
||||
class="cursor-pointer"
|
||||
circle
|
||||
size="medium"
|
||||
:src="getImgUrl(store.state.user.avatarUrl)"
|
||||
@@ -46,10 +46,23 @@
|
||||
<i class="iconfont ri-login-box-line"></i>
|
||||
<span>去登录</span>
|
||||
</div>
|
||||
<!-- 切换主题 -->
|
||||
<div class="menu-item" @click="selectItem('set')">
|
||||
<i class="iconfont ri-settings-3-line"></i>
|
||||
<span>设置</span>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<i class="iconfont" :class="isDarkTheme ? 'ri-moon-line' : 'ri-sun-line'"></i>
|
||||
<span>主题</span>
|
||||
<n-switch v-model:value="isDarkTheme" class="ml-auto">
|
||||
<template #checked>
|
||||
<i class="ri-moon-line"></i>
|
||||
</template>
|
||||
<template #unchecked>
|
||||
<i class="ri-sun-line"></i>
|
||||
</template>
|
||||
</n-switch>
|
||||
</div>
|
||||
<div class="menu-item" @click="toGithubRelease">
|
||||
<i class="iconfont ri-refresh-line"></i>
|
||||
<span>当前版本</span>
|
||||
@@ -68,6 +81,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watchEffect } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
@@ -121,6 +135,11 @@ onMounted(() => {
|
||||
loadPage();
|
||||
});
|
||||
|
||||
const isDarkTheme = computed({
|
||||
get: () => store.state.theme === 'dark',
|
||||
set: () => store.commit('toggleTheme'),
|
||||
});
|
||||
|
||||
// 搜索词
|
||||
const searchValue = ref('');
|
||||
const search = () => {
|
||||
@@ -183,14 +202,29 @@ const toGithubRelease = () => {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-box {
|
||||
@apply ml-4 flex text-lg justify-center items-center rounded-full border border-gray-600 hover:border-gray-400 transition-colors duration-200;
|
||||
background: #1a1a1a;
|
||||
@apply ml-4 flex text-lg justify-center items-center rounded-full transition-colors duration-200;
|
||||
@apply border dark:border-gray-600 border-gray-200 hover:border-gray-400 dark:hover:border-gray-400;
|
||||
@apply bg-light dark:bg-gray-800;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
@apply pb-4 pr-4;
|
||||
}
|
||||
|
||||
.search-box-input {
|
||||
@apply relative;
|
||||
|
||||
:deep(.n-input) {
|
||||
@apply bg-gray-50 dark:bg-black;
|
||||
|
||||
.n-input__input-el {
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.n-input__prefix {
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
@@ -200,20 +234,21 @@ const toGithubRelease = () => {
|
||||
}
|
||||
|
||||
.github {
|
||||
@apply cursor-pointer text-gray-100 hover:text-gray-400 text-xl ml-4 rounded-full border border-gray-600 flex justify-center items-center px-2 h-full;
|
||||
@apply cursor-pointer text-gray-900 dark:text-gray-100 hover:text-gray-600 dark:hover:text-gray-400 text-xl ml-4 rounded-full flex justify-center items-center px-2 h-full;
|
||||
@apply border dark:border-gray-600 border-gray-200 bg-light dark:bg-black;
|
||||
}
|
||||
|
||||
.user-popover {
|
||||
@apply min-w-[280px] p-0 rounded-xl overflow-hidden;
|
||||
background: #2c2c2c;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
@apply bg-light dark:bg-black;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.user-header {
|
||||
@apply flex items-center gap-2 p-3;
|
||||
border-bottom: 1px solid #3a3a3a;
|
||||
@apply flex items-center gap-2 p-3 cursor-pointer;
|
||||
@apply border-b dark:border-gray-700 border-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700;
|
||||
|
||||
.username {
|
||||
@apply text-sm font-medium text-gray-200;
|
||||
@apply text-sm font-medium text-gray-900 dark:text-gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,43 +257,20 @@ const toGithubRelease = () => {
|
||||
|
||||
.menu-item {
|
||||
@apply flex items-center px-3 py-2 text-sm cursor-pointer;
|
||||
@apply text-gray-300;
|
||||
@apply text-gray-700 dark:text-gray-300;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #3a3a3a;
|
||||
@apply bg-gray-100 dark:bg-gray-700;
|
||||
}
|
||||
|
||||
i {
|
||||
@apply mr-1 text-lg text-gray-400;
|
||||
}
|
||||
|
||||
.shortcut {
|
||||
@apply ml-auto text-xs text-gray-500;
|
||||
@apply mr-1 text-lg text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
.download-btn {
|
||||
@apply ml-auto px-2 py-0.5 text-xs rounded;
|
||||
background: #4a4a4a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.zoom-controls {
|
||||
@apply ml-auto flex items-center gap-2;
|
||||
color: #fff;
|
||||
|
||||
.zoom-btn {
|
||||
@apply px-2 py-0.5 text-sm rounded cursor-pointer;
|
||||
background: #3a3a3a;
|
||||
|
||||
&:hover {
|
||||
background: #4a4a4a;
|
||||
}
|
||||
}
|
||||
|
||||
span:not(.zoom-btn) {
|
||||
color: #fff;
|
||||
}
|
||||
@apply ml-auto text-xs px-2 py-0.5 rounded;
|
||||
@apply bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,8 @@ const drag = (event: MouseEvent) => {
|
||||
<style scoped lang="scss">
|
||||
#title-bar {
|
||||
-webkit-app-region: drag;
|
||||
@apply flex justify-between text-white px-6 py-2 select-none relative;
|
||||
@apply flex justify-between px-6 py-2 select-none relative;
|
||||
@apply text-dark dark:text-white;
|
||||
z-index: 9999999;
|
||||
}
|
||||
|
||||
@@ -66,6 +67,6 @@ const drag = (event: MouseEvent) => {
|
||||
}
|
||||
|
||||
button {
|
||||
@apply hover:text-green-500;
|
||||
@apply text-gray-600 dark:text-gray-400 hover:text-green-500;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createStore } from 'vuex';
|
||||
import { useMusicListHook } from '@/hooks/MusicListHook';
|
||||
import homeRouter from '@/router/home';
|
||||
import type { SongResult } from '@/type/music';
|
||||
import { applyTheme, getCurrentTheme, ThemeType } from '@/utils/theme';
|
||||
|
||||
// 默认设置
|
||||
const defaultSettings = {
|
||||
@@ -34,6 +35,7 @@ interface State {
|
||||
searchType: number;
|
||||
favoriteList: number[];
|
||||
playMode: number;
|
||||
theme: ThemeType;
|
||||
}
|
||||
|
||||
const state: State = {
|
||||
@@ -52,6 +54,7 @@ const state: State = {
|
||||
searchType: 1,
|
||||
favoriteList: getLocalStorageItem('favoriteList', []),
|
||||
playMode: getLocalStorageItem('playMode', 0),
|
||||
theme: getCurrentTheme(),
|
||||
};
|
||||
|
||||
const { handlePlayMusic, nextPlay, prevPlay } = useMusicListHook();
|
||||
@@ -102,6 +105,10 @@ const mutations = {
|
||||
state.playMode = state.playMode === 0 ? 1 : 0;
|
||||
localStorage.setItem('playMode', JSON.stringify(state.playMode));
|
||||
},
|
||||
toggleTheme(state: State) {
|
||||
state.theme = state.theme === 'dark' ? 'light' : 'dark';
|
||||
applyTheme(state.theme);
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
@@ -123,6 +130,9 @@ const actions = {
|
||||
}
|
||||
}
|
||||
},
|
||||
initializeTheme({ state }: { state: State }) {
|
||||
applyTheme(state.theme);
|
||||
},
|
||||
};
|
||||
|
||||
const store = createStore({
|
||||
|
||||
@@ -186,6 +186,7 @@ function hslToRgb(h: number, s: number, l: number): [number, number, number] {
|
||||
interface ITextColors {
|
||||
primary: string;
|
||||
active: string;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
// 添加新的函数
|
||||
@@ -230,6 +231,7 @@ export const getTextColors = (gradient: string = ''): ITextColors => {
|
||||
return {
|
||||
primary: isDark ? 'rgba(0, 0, 0, 0.54)' : 'rgba(255, 255, 255, 0.54)',
|
||||
active: isDark ? '#000000' : '#ffffff',
|
||||
theme: isDark ? 'dark' : 'light',
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
19
src/utils/theme.ts
Normal file
19
src/utils/theme.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export type ThemeType = 'dark' | 'light';
|
||||
|
||||
// 应用主题
|
||||
export const applyTheme = (theme: ThemeType) => {
|
||||
// 使用 Tailwind 的暗色主题类
|
||||
if (theme === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
|
||||
// 保存主题到本地存储
|
||||
localStorage.setItem('theme', theme);
|
||||
};
|
||||
|
||||
// 获取当前主题
|
||||
export const getCurrentTheme = (): ThemeType => {
|
||||
return (localStorage.getItem('theme') as ThemeType) || 'light';
|
||||
};
|
||||
@@ -151,16 +151,18 @@ const handleMore = () => {
|
||||
<style lang="scss" scoped>
|
||||
.favorite-page {
|
||||
@apply h-full flex flex-col pt-2;
|
||||
@apply bg-light dark:bg-black;
|
||||
|
||||
.favorite-header {
|
||||
@apply flex items-center justify-between flex-shrink-0 px-4;
|
||||
|
||||
h2 {
|
||||
@apply text-xl font-bold pb-2;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.favorite-count {
|
||||
@apply text-gray-400 text-sm;
|
||||
@apply text-gray-500 dark:text-gray-400 text-sm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +174,19 @@ const handleMore = () => {
|
||||
|
||||
.empty-tip {
|
||||
@apply h-full flex items-center justify-center;
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
.favorite-list {
|
||||
@apply space-y-2 pb-4 px-4;
|
||||
|
||||
&-more {
|
||||
@apply mt-4;
|
||||
|
||||
.n-button {
|
||||
@apply text-green-500 hover:text-green-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,7 +197,8 @@ const handleMore = () => {
|
||||
}
|
||||
|
||||
.no-more-tip {
|
||||
@apply text-center text-gray-400 py-4 text-sm;
|
||||
@apply text-center py-4 text-sm;
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
|
||||
@@ -125,8 +125,11 @@ const handleDelMusic = async (item: SongResult) => {
|
||||
<style scoped lang="scss">
|
||||
.history-page {
|
||||
@apply h-full w-full pt-2;
|
||||
@apply bg-light dark:bg-black;
|
||||
|
||||
.title {
|
||||
@apply pl-4 text-xl font-bold pb-2 px-4;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.history-list-content {
|
||||
@@ -138,9 +141,13 @@ const handleDelMusic = async (item: SongResult) => {
|
||||
}
|
||||
&-count {
|
||||
@apply px-4 text-lg text-center;
|
||||
@apply text-gray-600 dark:text-gray-400;
|
||||
}
|
||||
&-delete {
|
||||
@apply cursor-pointer rounded-full border-2 border-gray-400 w-8 h-8 flex justify-center items-center;
|
||||
@apply cursor-pointer rounded-full border-2 w-8 h-8 flex justify-center items-center;
|
||||
@apply border-gray-400 dark:border-gray-600;
|
||||
@apply text-gray-600 dark:text-gray-400;
|
||||
@apply hover:border-red-500 hover:text-red-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,6 +158,7 @@ const handleDelMusic = async (item: SongResult) => {
|
||||
}
|
||||
|
||||
.no-more-tip {
|
||||
@apply text-center text-gray-400 py-4 text-sm;
|
||||
@apply text-center py-4 text-sm;
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="flex gap-4 h-full p-4">
|
||||
<favorite class="flex-1 bg-[#0d0d0d] border border-[#374151] rounded-2xl overflow-hidden" />
|
||||
<history class="flex-1 bg-[#0d0d0d] border border-[#374151] rounded-2xl overflow-hidden" />
|
||||
<div class="flex gap-4 h-full pb-4">
|
||||
<favorite class="flex-item" />
|
||||
<history class="flex-item" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -10,4 +10,8 @@ import Favorite from '@/views/favorite/index.vue';
|
||||
import History from '@/views/history/index.vue';
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
.flex-item {
|
||||
@apply flex-1 bg-light-100 dark:bg-dark-100 rounded-2xl overflow-hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -29,7 +29,7 @@ defineOptions({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.main-page {
|
||||
@apply h-full w-full overflow-hidden;
|
||||
@apply h-full w-full overflow-hidden bg-light dark:bg-black;
|
||||
}
|
||||
.main-content {
|
||||
@apply mt-6 flex mb-28;
|
||||
@@ -49,7 +49,7 @@ defineOptions({
|
||||
.favorite-header {
|
||||
@apply mb-0 px-0 !important;
|
||||
h2 {
|
||||
@apply text-lg font-bold;
|
||||
@apply text-lg font-bold text-gray-900 dark:text-white;
|
||||
}
|
||||
}
|
||||
.favorite-list {
|
||||
|
||||
@@ -1,3 +1,67 @@
|
||||
<template>
|
||||
<div class="list-page">
|
||||
<!-- 修改歌单分类部分 -->
|
||||
<div class="play-list-type">
|
||||
<n-scrollbar x-scrollable>
|
||||
<div class="categories-wrapper">
|
||||
<span
|
||||
v-for="(item, index) in playlistCategory?.sub"
|
||||
:key="item.name"
|
||||
class="play-list-type-item"
|
||||
:class="[setAnimationClass('animate__bounceIn'), { active: currentType === item.name }]"
|
||||
:style="getAnimationDelay(index)"
|
||||
@click="handleClickPlaylistType(item.name)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
<!-- 歌单列表 -->
|
||||
<n-scrollbar class="recommend" :size="100" @scroll="handleScroll">
|
||||
<div v-loading="loading" class="recommend-list">
|
||||
<div
|
||||
v-for="(item, index) in recommendList"
|
||||
:key="item.id"
|
||||
class="recommend-item"
|
||||
:class="setAnimationClass('animate__bounceIn')"
|
||||
:style="getItemAnimationDelay(index)"
|
||||
@click.stop="selectRecommendItem(item)"
|
||||
>
|
||||
<div class="recommend-item-img">
|
||||
<n-image
|
||||
class="recommend-item-img-img"
|
||||
:src="getImgUrl(item.picUrl || item.coverImgUrl, '200y200')"
|
||||
width="200"
|
||||
height="200"
|
||||
lazy
|
||||
preview-disabled
|
||||
/>
|
||||
<div class="top">
|
||||
<div class="play-count">{{ formatNumber(item.playCount) }}</div>
|
||||
<i class="iconfont icon-videofill"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recommend-item-title">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="isLoadingMore" class="loading-more">
|
||||
<n-spin size="small" />
|
||||
<span class="ml-2">加载中...</span>
|
||||
</div>
|
||||
<div v-if="!hasMore && recommendList.length > 0" class="no-more">没有更多了</div>
|
||||
</n-scrollbar>
|
||||
<music-list
|
||||
v-model:show="showMusic"
|
||||
v-model:loading="listLoading"
|
||||
:name="recommendItem?.name || ''"
|
||||
:song-list="listDetail?.playlist.tracks || []"
|
||||
:list-info="listDetail?.playlist"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
@@ -137,131 +201,81 @@ watch(
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="list-page">
|
||||
<!-- 修改歌单分类部分 -->
|
||||
<div class="play-list-type">
|
||||
<n-scrollbar x-scrollable>
|
||||
<div class="categories-wrapper">
|
||||
<span
|
||||
v-for="(item, index) in playlistCategory?.sub"
|
||||
:key="item.name"
|
||||
class="play-list-type-item"
|
||||
:class="[setAnimationClass('animate__bounceIn'), { active: currentType === item.name }]"
|
||||
:style="getAnimationDelay(index)"
|
||||
@click="handleClickPlaylistType(item.name)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
<!-- 歌单列表 -->
|
||||
<n-scrollbar class="recommend" :size="100" @scroll="handleScroll">
|
||||
<div v-loading="loading" class="recommend-list">
|
||||
<div
|
||||
v-for="(item, index) in recommendList"
|
||||
:key="item.id"
|
||||
class="recommend-item"
|
||||
:class="setAnimationClass('animate__bounceIn')"
|
||||
:style="getItemAnimationDelay(index)"
|
||||
@click.stop="selectRecommendItem(item)"
|
||||
>
|
||||
<div class="recommend-item-img">
|
||||
<n-image
|
||||
class="recommend-item-img-img"
|
||||
:src="getImgUrl(item.picUrl || item.coverImgUrl, '200y200')"
|
||||
width="200"
|
||||
height="200"
|
||||
lazy
|
||||
preview-disabled
|
||||
/>
|
||||
<div class="top">
|
||||
<div class="play-count">{{ formatNumber(item.playCount) }}</div>
|
||||
<i class="iconfont icon-videofill"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recommend-item-title">{{ item.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="isLoadingMore" class="loading-more">
|
||||
<n-spin size="small" />
|
||||
<span class="ml-2">加载中...</span>
|
||||
</div>
|
||||
<div v-if="!hasMore && recommendList.length > 0" class="no-more">没有更多了</div>
|
||||
</n-scrollbar>
|
||||
<music-list
|
||||
v-model:show="showMusic"
|
||||
v-model:loading="listLoading"
|
||||
:name="recommendItem?.name || ''"
|
||||
:song-list="listDetail?.playlist.tracks || []"
|
||||
:list-info="listDetail?.playlist"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-page {
|
||||
@apply relative h-full w-full;
|
||||
@apply bg-light dark:bg-black;
|
||||
}
|
||||
|
||||
.recommend {
|
||||
@apply w-full h-full bg-none;
|
||||
@apply w-full h-full;
|
||||
|
||||
&-title {
|
||||
@apply text-lg font-bold text-white pb-2;
|
||||
@apply text-lg font-bold pb-2;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
&-list {
|
||||
@apply grid gap-x-8 gap-y-6 pb-28 pr-4;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
}
|
||||
|
||||
&-item {
|
||||
@apply flex flex-col;
|
||||
|
||||
&-img {
|
||||
@apply rounded-xl overflow-hidden relative w-full aspect-square;
|
||||
|
||||
&-img {
|
||||
@apply block w-full h-full;
|
||||
}
|
||||
|
||||
img {
|
||||
@apply absolute top-0 left-0 w-full h-full object-cover rounded-xl;
|
||||
}
|
||||
|
||||
&:hover img {
|
||||
@apply hover:scale-110 transition-all duration-300 ease-in-out;
|
||||
}
|
||||
|
||||
.top {
|
||||
@apply absolute w-full h-full top-0 left-0 flex justify-center items-center transition-all duration-300 ease-in-out cursor-pointer;
|
||||
background-color: #00000088;
|
||||
@apply bg-black bg-opacity-50;
|
||||
opacity: 0;
|
||||
|
||||
i {
|
||||
font-size: 50px;
|
||||
transition: all 0.5s ease-in-out;
|
||||
opacity: 0;
|
||||
@apply text-5xl text-white transition-all duration-500 ease-in-out opacity-0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
&:hover i {
|
||||
@apply transform scale-150 opacity-100;
|
||||
}
|
||||
|
||||
.play-count {
|
||||
@apply absolute top-2 left-2 text-sm;
|
||||
@apply absolute top-2 left-2 text-sm text-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
@apply p-2 text-sm text-white truncate;
|
||||
@apply mt-2 text-sm line-clamp-1;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-more {
|
||||
@apply flex items-center justify-center py-4 text-sm text-gray-400;
|
||||
@apply flex justify-center items-center py-4;
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
.no-more {
|
||||
@apply text-center py-4 text-sm text-gray-500;
|
||||
@apply text-center py-4;
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
@@ -277,25 +291,22 @@ watch(
|
||||
|
||||
// 添加歌单分类样式
|
||||
.play-list-type {
|
||||
.title {
|
||||
@apply text-lg font-bold mb-4;
|
||||
}
|
||||
|
||||
.categories-wrapper {
|
||||
@apply flex items-center py-2 pb-4;
|
||||
@apply flex items-center py-2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&-item {
|
||||
@apply py-2 px-3 mr-3 inline-block border border-gray-700 rounded-xl cursor-pointer transition-all duration-300;
|
||||
background-color: #1a1a1a;
|
||||
@apply py-2 px-3 mr-3 inline-block rounded-xl cursor-pointer transition-all duration-300;
|
||||
@apply bg-light dark:bg-black text-gray-900 dark:text-white;
|
||||
@apply border border-gray-200 dark:border-gray-700;
|
||||
|
||||
&:hover {
|
||||
@apply bg-green-600/50;
|
||||
@apply bg-green-50 dark:bg-green-900;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@apply bg-green-600 border-green-500;
|
||||
@apply bg-green-500 border-green-500 text-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,10 +136,11 @@ const loginPhone = async () => {
|
||||
<style lang="scss" scoped>
|
||||
.login-page {
|
||||
@apply flex flex-col items-center justify-center p-20 pt-20;
|
||||
@apply bg-light dark:bg-black;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
@apply text-2xl font-bold mb-6;
|
||||
@apply text-2xl font-bold mb-6 text-white;
|
||||
}
|
||||
|
||||
.text {
|
||||
@@ -151,11 +152,10 @@ const loginPhone = async () => {
|
||||
height: 550px;
|
||||
@apply rounded-2xl rounded-b-none bg-cover bg-no-repeat relative overflow-hidden;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' version='1.1' xmlns:xlink='http://www.w3.org/1999/xlink' xmlns:svgjs='http://svgjs.dev/svgjs' width='400' height='560' preserveAspectRatio='none' viewBox='0 0 400 560'%3e%3cg mask='url(%26quot%3b%23SvgjsMask1066%26quot%3b)' fill='none'%3e%3crect width='400' height='560' x='0' y='0' fill='rgba(24%2c 106%2c 59%2c 1)'%3e%3c/rect%3e%3cpath d='M0%2c234.738C43.535%2c236.921%2c80.103%2c205.252%2c116.272%2c180.923C151.738%2c157.067%2c188.295%2c132.929%2c207.855%2c94.924C227.898%2c55.979%2c233.386%2c10.682%2c226.119%2c-32.511C218.952%2c-75.107%2c199.189%2c-115.793%2c167.469%2c-145.113C137.399%2c-172.909%2c92.499%2c-171.842%2c55.779%2c-189.967C8.719%2c-213.196%2c-28.344%2c-282.721%2c-78.217%2c-266.382C-128.725%2c-249.834%2c-111.35%2c-166.696%2c-143.781%2c-124.587C-173.232%2c-86.348%2c-244.72%2c-83.812%2c-255.129%2c-36.682C-265.368%2c9.678%2c-217.952%2c48.26%2c-190.512%2c87.004C-167.691%2c119.226%2c-140.216%2c145.431%2c-109.013%2c169.627C-74.874%2c196.1%2c-43.147%2c232.575%2c0%2c234.738' fill='%23114b2a'%3e%3c/path%3e%3cpath d='M400 800.9010000000001C443.973 795.023 480.102 765.6 513.011 735.848 541.923 709.71 561.585 676.6320000000001 577.037 640.85 592.211 605.712 606.958 568.912 601.458 531.035 595.962 493.182 568.394 464.36400000000003 546.825 432.775 522.317 396.88300000000004 507.656 347.475 466.528 333.426 425.366 319.366 384.338 352.414 342.111 362.847 297.497 373.869 242.385 362.645 211.294 396.486 180.212 430.318 192.333 483.83299999999997 188.872 529.644 185.656 572.218 178.696 614.453 191.757 655.101 205.885 699.068 227.92 742.4110000000001 265.75 768.898 304.214 795.829 353.459 807.1220000000001 400 800.9010000000001' fill='%231f894c'%3e%3c/path%3e%3c/g%3e%3cdefs%3e%3cmask id='SvgjsMask1066'%3e%3crect width='400' height='560' fill='white'%3e%3c/rect%3e%3c/mask%3e%3c/defs%3e%3c/svg%3e");
|
||||
background-color: #383838;
|
||||
box-shadow: inset 0px 0px 20px 5px #0000005e;
|
||||
box-shadow: inset 0px 0px 20px 5px rgba(0, 0, 0, 0.37);
|
||||
|
||||
.bg {
|
||||
@apply absolute w-full h-full bg-black opacity-30;
|
||||
@apply absolute w-full h-full bg-light-100 dark:bg-dark-100 opacity-20;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
@@ -166,35 +166,42 @@ const loginPhone = async () => {
|
||||
left: 50%;
|
||||
padding: 10px;
|
||||
transform: translateX(-50%);
|
||||
color: #ffffff99;
|
||||
@apply absolute bg-black flex justify-center text-lg font-bold cursor-pointer;
|
||||
box-shadow: 10px 0px 20px #000000a9;
|
||||
@apply absolute bg-light dark:bg-dark flex justify-center text-lg font-bold cursor-pointer;
|
||||
@apply text-gray-400 hover:text-gray-800 hover:dark:text-white transition-colors;
|
||||
box-shadow: 10px 0px 20px rgba(0, 0, 0, 0.66);
|
||||
}
|
||||
|
||||
.content {
|
||||
@apply absolute w-full h-full p-4 flex flex-col items-center justify-center pb-20 text-center;
|
||||
.qr-img {
|
||||
@apply opacity-80 rounded-2xl cursor-pointer;
|
||||
@apply rounded-2xl cursor-pointer transition-opacity;
|
||||
}
|
||||
|
||||
.phone {
|
||||
animation-duration: 0.5s;
|
||||
&-page {
|
||||
background-color: #ffffffdd;
|
||||
@apply bg-light dark:bg-gray-800 bg-opacity-90 dark:bg-opacity-90;
|
||||
width: 250px;
|
||||
@apply rounded-2xl overflow-hidden;
|
||||
}
|
||||
|
||||
&-input {
|
||||
height: 40px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
@apply w-full text-black px-4 outline-none;
|
||||
@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 bg-black opacity-60;
|
||||
@apply mt-10 text-white rounded-xl;
|
||||
@apply bg-green-600 hover:bg-green-700 transition-colors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<template>
|
||||
<div class="mv-list">
|
||||
<div class="mv-list-title">
|
||||
<h2>推荐MV</h2>
|
||||
</div>
|
||||
<div class="play-list-type">
|
||||
<n-scrollbar x-scrollable>
|
||||
<div class="categories-wrapper">
|
||||
@@ -193,29 +190,32 @@ const isPrevDisabled = computed(() => currentIndex.value === 0);
|
||||
|
||||
&-title {
|
||||
@apply text-xl font-bold pb-2;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
// 添加歌单分类样式
|
||||
.play-list-type {
|
||||
.title {
|
||||
@apply text-lg font-bold mb-4;
|
||||
@apply text-lg font-bold mb-2;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.categories-wrapper {
|
||||
@apply flex items-center py-2 pb-4;
|
||||
@apply flex items-center py-2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&-item {
|
||||
@apply py-2 px-3 mr-3 inline-block border border-gray-700 rounded-xl cursor-pointer transition-all duration-300;
|
||||
background-color: #1a1a1a;
|
||||
@apply py-2 px-3 mr-3 inline-block rounded-xl cursor-pointer transition-all duration-300;
|
||||
@apply bg-light dark:bg-black text-gray-900 dark:text-white;
|
||||
@apply border border-gray-200 dark:border-gray-700;
|
||||
|
||||
&:hover {
|
||||
@apply bg-green-600/50;
|
||||
@apply bg-green-50 dark:bg-green-900;
|
||||
}
|
||||
|
||||
&.active {
|
||||
@apply bg-green-600 border-green-500;
|
||||
@apply bg-green-500 border-green-500 text-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,7 +227,9 @@ const isPrevDisabled = computed(() => currentIndex.value === 0);
|
||||
|
||||
.mv-item {
|
||||
@apply p-2 rounded-lg;
|
||||
background-color: #1f1f1f;
|
||||
@apply bg-light dark:bg-black;
|
||||
@apply border border-gray-200 dark:border-gray-700;
|
||||
|
||||
&-img {
|
||||
@apply rounded-lg overflow-hidden relative;
|
||||
aspect-ratio: 16/9;
|
||||
@@ -243,51 +245,38 @@ const isPrevDisabled = computed(() => currentIndex.value === 0);
|
||||
|
||||
.top {
|
||||
@apply absolute w-full h-full top-0 left-0 flex justify-center items-center transition-all duration-300 ease-in-out cursor-pointer;
|
||||
background-color: #0000009b;
|
||||
@apply bg-black bg-opacity-60;
|
||||
opacity: 0;
|
||||
|
||||
i {
|
||||
font-size: 40px;
|
||||
transition: all 0.5s ease-in-out;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
&:hover i {
|
||||
@apply transform scale-150 opacity-80;
|
||||
@apply text-4xl text-white;
|
||||
}
|
||||
|
||||
.play-count {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 10px;
|
||||
font-size: 14px;
|
||||
@apply absolute top-2 right-2 text-sm;
|
||||
@apply text-white text-opacity-90;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-title {
|
||||
@apply p-2 text-sm text-white truncate;
|
||||
@apply mt-2 text-sm line-clamp-1;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
.mv-list-title {
|
||||
@apply text-xl font-bold px-4;
|
||||
}
|
||||
|
||||
.mv-list-content {
|
||||
@apply px-4;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
}
|
||||
.loading-more {
|
||||
@apply text-center py-4 col-span-full;
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
.loading-more,
|
||||
.no-more {
|
||||
@apply col-span-full text-center py-4 text-gray-400;
|
||||
@apply text-center py-4 col-span-full;
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -165,21 +165,34 @@ const handlePlay = () => {
|
||||
.search-page {
|
||||
@apply flex h-full;
|
||||
}
|
||||
|
||||
.hot-search {
|
||||
@apply mr-4 rounded-xl flex-1 overflow-hidden;
|
||||
background-color: #0d0d0d;
|
||||
@apply mr-4 rounded-xl flex-1 overflow-hidden;
|
||||
@apply bg-light-100 dark:bg-dark-100;
|
||||
animation-duration: 0.2s;
|
||||
min-width: 400px;
|
||||
height: 100%;
|
||||
|
||||
&-list {
|
||||
@apply pb-28;
|
||||
}
|
||||
|
||||
&-item {
|
||||
@apply px-4 py-3 text-lg hover:bg-gray-700 rounded-xl cursor-pointer;
|
||||
@apply px-4 py-3 text-lg rounded-xl cursor-pointer;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
@apply bg-light-100 dark:bg-dark-200;
|
||||
}
|
||||
|
||||
&-count {
|
||||
@apply text-green-500 inline-block ml-3 w-8;
|
||||
@apply inline-block ml-3 w-8;
|
||||
@apply text-green-500;
|
||||
|
||||
&-3 {
|
||||
@apply text-red-600 font-bold inline-block ml-3 w-8;
|
||||
@apply font-bold inline-block ml-3 w-8;
|
||||
@apply text-red-500;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,15 +200,17 @@ const handlePlay = () => {
|
||||
|
||||
.search-list {
|
||||
@apply flex-1 rounded-xl;
|
||||
background-color: #0d0d0d;
|
||||
@apply bg-light-100 dark:bg-dark-100;
|
||||
height: 100%;
|
||||
|
||||
&-box {
|
||||
@apply pb-28;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-gray-200 text-xl font-bold my-2 mx-4;
|
||||
@apply text-xl font-bold my-2 mx-4;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
<template>
|
||||
<n-scrollbar>
|
||||
<div class="set-page" :class="setAnimationClass('animate__bounceInLeft')">
|
||||
<div v-if="isElectron" class="set-item">
|
||||
<div class="set-page">
|
||||
<div class="set-item">
|
||||
<div>
|
||||
<div class="set-item-title">主题模式</div>
|
||||
<div class="set-item-content">切换日间/夜间主题</div>
|
||||
</div>
|
||||
<n-switch v-model:value="isDarkTheme">
|
||||
<template #checked>
|
||||
<i class="ri-moon-line"></i>
|
||||
</template>
|
||||
<template #unchecked>
|
||||
<i class="ri-sun-line"></i>
|
||||
</template>
|
||||
</n-switch>
|
||||
</div>
|
||||
<!-- <div v-if="isElectron" class="set-item">
|
||||
<div>
|
||||
<div class="set-item-title">代理</div>
|
||||
<div class="set-item-content">无法听音乐时打开</div>
|
||||
</div>
|
||||
<n-switch v-model:value="setData.isProxy" />
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="set-item">
|
||||
<div>
|
||||
<div class="set-item-title">关闭动画效果</div>
|
||||
<div class="set-item-content">关闭所有页面动画效果</div>
|
||||
</div>
|
||||
<n-switch v-model:value="setData.noAnimate" />
|
||||
</div>
|
||||
@@ -52,94 +67,58 @@
|
||||
</div>
|
||||
<div>{{ setData.author }}</div>
|
||||
</div>
|
||||
|
||||
<div class="set-action">
|
||||
<n-button class="w-40 h-10" @click="handelCancel">取消</n-button>
|
||||
<n-button type="primary" class="w-40 h-10" @click="handleSave">{{
|
||||
isElectron ? '保存并重启' : '保存'
|
||||
}}</n-button>
|
||||
</div>
|
||||
<div class="mt-10">
|
||||
<p class="text-sm text-gray-100 text-center cursor-pointer hover:text-green-500" @click="copyQQ">
|
||||
QQ群:789288579
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-6 bg-black rounded-lg shadow-lg">
|
||||
<div class="text-gray-100 text-base text-center">支持作者</div>
|
||||
<div class="flex gap-60">
|
||||
<div class="flex flex-col items-center gap-2 cursor-none hover:scale-[2] transition-all z-10 bg-black">
|
||||
<n-image :src="alipay" alt="支付宝收款码" class="w-32 h-32 rounded-lg" preview-disabled />
|
||||
<span class="text-sm text-gray-100">支付宝</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center gap-2 cursor-none hover:scale-[2] transition-all z-10 bg-black">
|
||||
<n-image :src="wechat" alt="微信收款码" class="w-32 h-32 rounded-lg" preview-disabled />
|
||||
<span class="text-sm text-gray-100">微信支付</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-scrollbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
|
||||
import config from '@/../package.json';
|
||||
import alipay from '@/assets/alipay.png';
|
||||
import wechat from '@/assets/wechat.png';
|
||||
import store from '@/store';
|
||||
import { setAnimationClass } from '@/utils';
|
||||
import { isElectron } from '@/hooks/MusicHook';
|
||||
|
||||
defineOptions({
|
||||
name: 'Setting',
|
||||
});
|
||||
const message = useMessage();
|
||||
const copyQQ = () => {
|
||||
navigator.clipboard.writeText('789288579');
|
||||
message.success('已复制到剪贴板');
|
||||
};
|
||||
const store = useStore();
|
||||
|
||||
const isElectron = ref((window as any).electronAPI !== undefined);
|
||||
const router = useRouter();
|
||||
|
||||
// 使用计算属性来获取和设置数据
|
||||
const setData = computed({
|
||||
get: () => store.state.setData,
|
||||
set: (value) => store.commit('setSetData', value),
|
||||
});
|
||||
|
||||
const handelCancel = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
store.commit('setSetData', setData.value);
|
||||
if (isElectron.value) {
|
||||
(window as any).electronAPI.restart();
|
||||
}
|
||||
router.push('/');
|
||||
};
|
||||
const isDarkTheme = computed({
|
||||
get: () => store.state.theme === 'dark',
|
||||
set: () => store.commit('toggleTheme'),
|
||||
});
|
||||
|
||||
const openAuthor = () => {
|
||||
window.open(setData.value.authorUrl, '_blank');
|
||||
window.open(setData.value.authorUrl);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.set-page {
|
||||
@apply flex flex-col justify-center items-center pt-8;
|
||||
@apply p-4 bg-light dark:bg-dark;
|
||||
}
|
||||
|
||||
.set-item {
|
||||
@apply w-3/5 flex justify-between items-center mb-2 px-4 py-2 rounded-lg;
|
||||
.set-item-title {
|
||||
@apply text-gray-200 text-base;
|
||||
@apply flex items-center justify-between p-4 rounded-lg mb-4 transition-all;
|
||||
@apply bg-light dark:bg-dark text-gray-900 dark:text-white;
|
||||
@apply border border-gray-200 dark:border-gray-700;
|
||||
|
||||
&-title {
|
||||
@apply text-base font-medium mb-1;
|
||||
}
|
||||
.set-item-content {
|
||||
@apply text-gray-400 text-sm;
|
||||
|
||||
&-content {
|
||||
@apply text-sm text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@apply bg-gray-50 dark:bg-gray-800;
|
||||
}
|
||||
|
||||
&.cursor-pointer:hover {
|
||||
@apply text-green-500 bg-green-50 dark:bg-green-900;
|
||||
}
|
||||
}
|
||||
.set-action {
|
||||
@apply flex gap-3 mt-4;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,3 +1,80 @@
|
||||
<template>
|
||||
<div class="user-page">
|
||||
<div
|
||||
v-if="userDetail"
|
||||
class="left"
|
||||
:class="setAnimationClass('animate__fadeInLeft')"
|
||||
:style="{ backgroundImage: `url(${getImgUrl(user.backgroundUrl)})` }"
|
||||
>
|
||||
<div class="page">
|
||||
<div class="user-name">{{ user.nickname }}</div>
|
||||
<div class="user-info">
|
||||
<n-avatar round :size="50" :src="getImgUrl(user.avatarUrl, '50y50')" />
|
||||
<div class="user-info-list">
|
||||
<div class="user-info-item">
|
||||
<div class="label">{{ userDetail.profile.followeds }}</div>
|
||||
<div>粉丝</div>
|
||||
</div>
|
||||
<div class="user-info-item">
|
||||
<div class="label">{{ userDetail.profile.follows }}</div>
|
||||
<div>关注</div>
|
||||
</div>
|
||||
<div class="user-info-item">
|
||||
<div class="label">{{ userDetail.level }}</div>
|
||||
<div>等级</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uesr-signature">{{ userDetail.profile.signature }}</div>
|
||||
|
||||
<div class="play-list" :class="setAnimationClass('animate__fadeInLeft')">
|
||||
<div class="title">创建的歌单</div>
|
||||
<n-scrollbar>
|
||||
<div
|
||||
v-for="(item, index) in playList"
|
||||
:key="index"
|
||||
class="play-list-item"
|
||||
@click="showPlaylist(item.id, item.name)"
|
||||
>
|
||||
<n-image :src="getImgUrl(item.coverImgUrl, '50y50')" class="play-list-item-img" lazy preview-disabled />
|
||||
<div class="play-list-item-info">
|
||||
<div class="play-list-item-name">{{ item.name }}</div>
|
||||
<div class="play-list-item-count">{{ item.trackCount }}首,播放{{ item.playCount }}次</div>
|
||||
</div>
|
||||
</div>
|
||||
<play-bottom />
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isMobile" v-loading="infoLoading" class="right" :class="setAnimationClass('animate__fadeInRight')">
|
||||
<div class="title">听歌排行</div>
|
||||
<div class="record-list">
|
||||
<n-scrollbar>
|
||||
<div
|
||||
v-for="(item, index) in recordList"
|
||||
:key="item.id"
|
||||
class="record-item"
|
||||
:class="setAnimationClass('animate__bounceInUp')"
|
||||
:style="setAnimationDelay(index, 25)"
|
||||
>
|
||||
<song-item class="song-item" :item="item" @play="handlePlay" />
|
||||
<div class="play-count">{{ item.playCount }}次</div>
|
||||
</div>
|
||||
<play-bottom />
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
<music-list
|
||||
v-model:show="isShowList"
|
||||
:name="list?.name || ''"
|
||||
:song-list="list?.tracks || []"
|
||||
:list-info="list"
|
||||
:loading="listLoading"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
@@ -77,108 +154,41 @@ const handlePlay = () => {
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="user-page">
|
||||
<div
|
||||
v-if="userDetail"
|
||||
class="left"
|
||||
:class="setAnimationClass('animate__fadeInLeft')"
|
||||
:style="{ backgroundImage: `url(${getImgUrl(user.backgroundUrl)})` }"
|
||||
>
|
||||
<div class="page">
|
||||
<div class="user-name">{{ user.nickname }}</div>
|
||||
<div class="user-info">
|
||||
<n-avatar round :size="50" :src="getImgUrl(user.avatarUrl, '50y50')" />
|
||||
<div class="user-info-list">
|
||||
<div class="user-info-item">
|
||||
<div class="label">{{ userDetail.profile.followeds }}</div>
|
||||
<div>粉丝</div>
|
||||
</div>
|
||||
<div class="user-info-item">
|
||||
<div class="label">{{ userDetail.profile.follows }}</div>
|
||||
<div>关注</div>
|
||||
</div>
|
||||
<div class="user-info-item">
|
||||
<div class="label">{{ userDetail.level }}</div>
|
||||
<div>等级</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="uesr-signature">{{ userDetail.profile.signature }}</div>
|
||||
|
||||
<div class="play-list" :class="setAnimationClass('animate__fadeInLeft')">
|
||||
<div class="title">创建的歌单</div>
|
||||
<n-scrollbar>
|
||||
<div
|
||||
v-for="(item, index) in playList"
|
||||
:key="index"
|
||||
class="play-list-item"
|
||||
@click="showPlaylist(item.id, item.name)"
|
||||
>
|
||||
<n-image :src="getImgUrl(item.coverImgUrl, '50y50')" class="play-list-item-img" lazy preview-disabled />
|
||||
<div class="play-list-item-info">
|
||||
<div class="play-list-item-name">{{ item.name }}</div>
|
||||
<div class="play-list-item-count">{{ item.trackCount }}首,播放{{ item.playCount }}次</div>
|
||||
</div>
|
||||
</div>
|
||||
<play-bottom />
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isMobile" v-loading="infoLoading" class="right" :class="setAnimationClass('animate__fadeInRight')">
|
||||
<div class="title">听歌排行</div>
|
||||
<div class="record-list">
|
||||
<n-scrollbar>
|
||||
<div
|
||||
v-for="(item, index) in recordList"
|
||||
:key="item.id"
|
||||
class="record-item"
|
||||
:class="setAnimationClass('animate__bounceInUp')"
|
||||
:style="setAnimationDelay(index, 25)"
|
||||
>
|
||||
<song-item class="song-item" :item="item" @play="handlePlay" />
|
||||
<div class="play-count">{{ item.playCount }}次</div>
|
||||
</div>
|
||||
<play-bottom />
|
||||
</n-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
<music-list
|
||||
v-model:show="isShowList"
|
||||
:name="list?.name || ''"
|
||||
:song-list="list?.tracks || []"
|
||||
:list-info="list"
|
||||
:loading="listLoading"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-page {
|
||||
@apply flex h-full;
|
||||
.left {
|
||||
max-width: 600px;
|
||||
background-color: #0d0d0d;
|
||||
background-size: 100%;
|
||||
@apply flex-1 rounded-2xl overflow-hidden relative bg-no-repeat h-full;
|
||||
@apply flex-1 rounded-2xl overflow-hidden relative bg-no-repeat h-full;
|
||||
@apply bg-gray-900 dark:bg-gray-800;
|
||||
|
||||
.page {
|
||||
@apply p-4 w-full z-10 flex flex-col h-full;
|
||||
background-color: #0d0d0d66;
|
||||
@apply p-4 w-full z-10 flex flex-col h-full;
|
||||
@apply bg-black bg-opacity-40;
|
||||
}
|
||||
.title {
|
||||
@apply text-lg font-bold;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
@apply text-xl text-white font-bold opacity-70 mb-4;
|
||||
@apply text-xl font-bold mb-4;
|
||||
@apply text-white text-opacity-70;
|
||||
}
|
||||
|
||||
.uesr-signature {
|
||||
@apply text-white opacity-70 mt-4;
|
||||
@apply mt-4;
|
||||
@apply text-white text-opacity-70;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
@apply flex items-center;
|
||||
&-list {
|
||||
@apply flex justify-around w-2/5 text-center opacity-70;
|
||||
@apply flex justify-around w-2/5 text-center;
|
||||
@apply text-white text-opacity-70;
|
||||
|
||||
.label {
|
||||
@apply text-xl text-white font-bold;
|
||||
@apply text-xl font-bold text-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,10 +196,12 @@ const handlePlay = () => {
|
||||
|
||||
.right {
|
||||
@apply flex-1 ml-4 overflow-hidden h-full;
|
||||
|
||||
.record-list {
|
||||
background-color: #0d0d0d;
|
||||
@apply rounded-2xl;
|
||||
@apply bg-light dark:bg-black;
|
||||
height: calc(100% - 3.75rem);
|
||||
|
||||
.record-item {
|
||||
@apply flex items-center px-4;
|
||||
}
|
||||
@@ -199,33 +211,48 @@ const handlePlay = () => {
|
||||
}
|
||||
|
||||
.play-count {
|
||||
@apply text-white opacity-70 ml-4;
|
||||
@apply ml-4;
|
||||
@apply text-gray-600 dark:text-gray-400;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-xl text-white font-bold opacity-70 m-4;
|
||||
@apply text-xl font-bold m-4;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.play-list {
|
||||
@apply mt-4 py-4 px-2 rounded-xl flex-1 overflow-hidden;
|
||||
background-color: #000000;
|
||||
@apply bg-light dark:bg-black;
|
||||
|
||||
&-title {
|
||||
@apply text-lg text-white opacity-70;
|
||||
@apply text-lg;
|
||||
@apply text-gray-900 dark:text-white;
|
||||
}
|
||||
|
||||
&-item {
|
||||
@apply flex items-center hover:bg-gray-800 transition-all duration-200 px-2 py-1 rounded-xl cursor-pointer;
|
||||
@apply flex items-center px-2 py-1 rounded-xl cursor-pointer;
|
||||
@apply transition-all duration-200;
|
||||
@apply hover:bg-light-200 dark:hover:bg-dark-200;
|
||||
|
||||
&-img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
@apply rounded-xl;
|
||||
}
|
||||
|
||||
&-info {
|
||||
@apply ml-2;
|
||||
}
|
||||
|
||||
&-name {
|
||||
@apply text-white;
|
||||
@apply text-gray-900 dark:text-white text-base;
|
||||
}
|
||||
|
||||
&-count {
|
||||
@apply text-gray-500 dark:text-gray-400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
highlight: 'var(--highlight-color)',
|
||||
text: 'var(--text-color)',
|
||||
secondary: 'var(--text-secondary)',
|
||||
primary: {
|
||||
DEFAULT: '#000',
|
||||
light: '#fff',
|
||||
dark: '#000',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: '#6c757d',
|
||||
light: '#8c959e',
|
||||
dark: '#495057',
|
||||
},
|
||||
dark: {
|
||||
DEFAULT: '#000',
|
||||
100: '#161616',
|
||||
200: '#2d2d2d',
|
||||
300: '#3d3d3d',
|
||||
},
|
||||
light: {
|
||||
DEFAULT: '#fff',
|
||||
100: '#f8f9fa',
|
||||
200: '#e9ecef',
|
||||
300: '#dee2e6',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user