feat: 添加每日推荐 样式, 请求等大量优化

This commit is contained in:
alger
2024-05-22 12:07:48 +08:00
parent c6f1e0b233
commit 32b39c7927
23 changed files with 409 additions and 103 deletions

1
components.d.ts vendored
View File

@@ -23,6 +23,7 @@ declare module 'vue' {
NPopover: typeof import('naive-ui')['NPopover']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSlider: typeof import('naive-ui')['NSlider']
NSpin: typeof import('naive-ui')['NSpin']
NSwitch: typeof import('naive-ui')['NSwitch']
NTooltip: typeof import('naive-ui')['NTooltip']
PlayBottom: typeof import('./src/components/common/PlayBottom.vue')['default']

View File

@@ -40,7 +40,7 @@
"eslint-plugin-vue": "^9.21.1",
"eslint-plugin-vue-scoped-css": "^2.7.2",
"lodash": "^4.17.21",
"naive-ui": "^2.34.4",
"naive-ui": "^2.38.2",
"postcss": "^7.0.36",
"prettier": "^3.2.5",
"remixicon": "^4.2.0",

View File

@@ -3,7 +3,9 @@
<audio id="MusicAudio" ref="audioRef" :src="playMusicUrl" :autoplay="play"></audio>
<n-config-provider :theme="darkTheme">
<n-dialog-provider>
<router-view></router-view>
<keep-alive>
<router-view></router-view>
</keep-alive>
</n-dialog-provider>
</n-config-provider>
</div>

View File

@@ -1,4 +1,6 @@
import { IData } from '@/type';
import { IAlbumNew } from '@/type/album';
import { IDayRecommend } from '@/type/day_recommend';
import { IRecommendMusic } from '@/type/music';
import { IPlayListSort } from '@/type/playlist';
import { IHotSearch, ISearchKeyword } from '@/type/search';
@@ -39,6 +41,11 @@ export const getRecommendMusic = (params: IRecommendMusicParams) => {
return request.get<IRecommendMusic>('/personalized/newsong', { params });
};
// 获取每日推荐
export const getDayRecommend = () => {
return request.get<IData<IDayRecommend>>('/recommend/songs');
};
// 获取最新专辑推荐
export const getNewAlbum = () => {
return request.get<IAlbumNew>('/album/newest');

View File

@@ -10,7 +10,7 @@
v-for="(item, index) in songList"
:key="item.id"
:class="setAnimationClass('animate__bounceInUp')"
:style="setAnimationDelay(index, 100)"
:style="setAnimationDelay(index, 50)"
>
<song-item :item="formatDetail(item)" @play="handlePlay" />
</div>

View File

@@ -3,11 +3,34 @@
<div class="recommend-singer">
<div class="recommend-singer-list">
<div
v-for="(item, index) in hotSingerData?.artists"
class="recommend-singer-item relative"
:class="setAnimationClass('animate__backInRight')"
:style="setAnimationDelay(0, 100)"
>
<div
:style="setBackgroundImg(getImgUrl(dayRecommendData?.dailySongs[0].al.picUrl, '300y300'))"
class="recommend-singer-item-bg"
></div>
<div
class="recommend-singer-item-count p-2 text-base text-gray-200 z-10 cursor-pointer"
@click="showMusic = true"
>
<div class="font-bold text-xl">每日推荐列表</div>
<div class="mt-2">
<p v-for="item in dayRecommendData?.dailySongs.slice(0, 8)" :key="item.id" class="text-el">
{{ item.name }}
<br />
</p>
</div>
</div>
</div>
<div
v-for="(item, index) in hotSingerData?.artists.slice(0, 4)"
:key="item.id"
class="recommend-singer-item relative"
:class="setAnimationClass('animate__backInRight')"
:style="setAnimationDelay(index, 100)"
:style="setAnimationDelay(index + 1, 100)"
>
<div :style="setBackgroundImg(getImgUrl(item.picUrl, '300y300'))" class="recommend-singer-item-bg"></div>
<div class="recommend-singer-item-count p-2 text-base text-gray-200 z-10">{{ item.musicSize }}</div>
@@ -16,34 +39,58 @@
<i class="iconfont icon-playfill text-xl"></i>
</div>
<div class="ml-4">
<div class="recommend-singer-item-info-name">{{ item.name }}</div>
<div class="recommend-singer-item-info-name">{{ item.name }}</div>
<div class="recommend-singer-item-info-name text-el">{{ item.name }}</div>
<div class="recommend-singer-item-info-name text-el">{{ item.name }}</div>
</div>
</div>
</div>
</div>
<music-list
v-if="dayRecommendData?.dailySongs.length"
v-model:show="showMusic"
name="每日推荐列表"
:song-list="dayRecommendData?.dailySongs"
/>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { getHotSinger } from '@/api/home';
import { getDayRecommend, getHotSinger } from '@/api/home';
import router from '@/router';
import { IDayRecommend } from '@/type/day_recommend';
import type { IHotSinger } from '@/type/singer';
import { getImgUrl, setAnimationClass, setAnimationDelay, setBackgroundImg } from '@/utils';
// 歌手信息
const hotSingerData = ref<IHotSinger>();
const dayRecommendData = ref<IDayRecommend>();
const showMusic = ref(false);
// // 加载推荐歌手
// const loadSingerList = async () => {
// const { data } = await getHotSinger({ offset: 0, limit: 5 });
// hotSingerData.value = data;
// };
// 加载推荐歌手
const loadSingerList = async () => {
const { data } = await getHotSinger({ offset: 0, limit: 5 });
hotSingerData.value = data;
};
// const loadDayRecommend = async () => {
// const { data } = await getDayRecommend();
// dayRecommendData.value = data.data;
// };
// 页面初始化
onMounted(() => {
loadSingerList();
onMounted(async () => {
try {
const [{ data: singerData }, { data: dayRecommend }] = await Promise.all([
getHotSinger({ offset: 0, limit: 5 }),
getDayRecommend(),
]);
hotSingerData.value = singerData;
dayRecommendData.value = dayRecommend.data;
} catch (error) {
console.error('error', error);
}
});
const toSearchSinger = (keyword: string) => {
@@ -66,7 +113,7 @@ const toSearchSinger = (keyword: string) => {
@apply flex-1 h-full rounded-3xl p-5 mr-5 flex flex-col justify-between;
&-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;
filter: brightness(80%);
filter: brightness(60%);
}
&-info {
@apply flex items-center p-2;

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="isPlay" class="bottom"></div>
<div v-if="isPlay" class="bottom" :style="{ height }"></div>
</template>
<script setup lang="ts">
@@ -7,6 +7,12 @@ import { useStore } from 'vuex';
const store = useStore();
const isPlay = computed(() => store.state.isPlay as boolean);
defineProps({
height: {
type: String,
default: undefined,
},
});
</script>
<style lang="scss" scoped>

View File

@@ -9,3 +9,7 @@
background-color: #111111;
width: 100%;
}
.text-el {
@apply overflow-ellipsis overflow-hidden whitespace-nowrap;
}

View File

@@ -9,26 +9,16 @@
<!-- 搜索栏 -->
<search-bar />
<!-- 主页面路由 -->
<div class="main-content bg-black pb-" :native-scrollbar="false" :class="isPlay ? 'pb-20' : ''">
<div class="main-content bg-black" :native-scrollbar="false">
<n-message-provider>
<router-view v-slot="{ Component }" class="main-page" :class="route.meta.noScroll ? 'pr-3' : ''">
<template v-if="route.meta.noScroll">
<keep-alive v-if="!route.meta.noKeepAlive">
<component :is="Component" />
</keep-alive>
<component :is="Component" v-else />
</template>
<template v-else>
<n-scrollbar>
<keep-alive v-if="!route.meta.noKeepAlive">
<component :is="Component" />
</keep-alive>
<component :is="Component" v-else />
</n-scrollbar>
</template>
<keep-alive :include="keepAliveInclude">
<component :is="Component" />
</keep-alive>
</router-view>
</n-message-provider>
</div>
<play-bottom height="5rem" />
</div>
</div>
<!-- 底部音乐播放 -->
@@ -41,6 +31,21 @@
import { useRoute } from 'vue-router';
import { useStore } from 'vuex';
import PlayBottom from '@/components/common/PlayBottom.vue';
import homeRouter from '@/router/home';
const keepAliveInclude = computed(() =>
homeRouter
.filter((item) => {
return item.meta.keepAlive;
})
.map((item) => {
// return item.name;
// 首字母大写
return item.name.charAt(0).toUpperCase() + item.name.slice(1);
}),
);
const AppMenu = defineAsyncComponent(() => import('./components/AppMenu.vue'));
const PlayBar = defineAsyncComponent(() => import('./components/PlayBar.vue'));
const SearchBar = defineAsyncComponent(() => import('./components/SearchBar.vue'));

View File

@@ -68,8 +68,9 @@ const loadPage = async () => {
localStorage.setItem('user', JSON.stringify(data.profile));
};
loadPage();
watchEffect(() => {
loadPage();
if (store.state.user) {
userSetOptions.value = USER_SET_OPTIONS;
} else {
@@ -118,6 +119,7 @@ const selectItem = async (key: string) => {
logout().then(() => {
store.state.user = null;
localStorage.clear();
router.push('/login');
});
break;
case 'login':

View File

@@ -5,6 +5,7 @@ const layoutRouter = [
meta: {
title: '首页',
icon: 'icon-Home',
keepAlive: true,
},
component: () => import('@/views/home/index.vue'),
},
@@ -14,8 +15,8 @@ const layoutRouter = [
meta: {
title: '搜索',
noScroll: true,
noKeepAlive: true,
icon: 'icon-Search',
keepAlive: true,
},
component: () => import('@/views/search/index.vue'),
},
@@ -25,6 +26,7 @@ const layoutRouter = [
meta: {
title: '歌单',
icon: 'icon-Paper',
keepAlive: true,
},
component: () => import('@/views/list/index.vue'),
},
@@ -34,6 +36,7 @@ const layoutRouter = [
meta: {
title: 'MV',
icon: 'icon-recordfill',
keepAlive: true,
},
component: () => import('@/views/mv/index.vue'),
},
@@ -43,6 +46,7 @@ const layoutRouter = [
meta: {
title: '历史',
icon: 'icon-a-TicketStar',
keepAlive: true,
},
component: () => import('@/views/history/index.vue'),
},
@@ -51,8 +55,8 @@ const layoutRouter = [
name: 'user',
meta: {
title: '用户',
noKeepAlive: true,
icon: 'icon-Profile',
keepAlive: true,
noScroll: true,
},
component: () => import('@/views/user/index.vue'),

168
src/type/day_recommend.ts Normal file
View File

@@ -0,0 +1,168 @@
export interface IDayRecommend {
dailySongs: DailySong[];
orderSongs: any[];
recommendReasons: RecommendReason[];
mvResourceInfos: null;
}
interface RecommendReason {
songId: number;
reason: string;
reasonId: string;
targetUrl: null;
}
interface DailySong {
name: string;
id: number;
pst: number;
t: number;
ar: Ar[];
alia: string[];
pop: number;
st: number;
rt: null | string;
fee: number;
v: number;
crbt: null;
cf: string;
al: Al;
dt: number;
h: H;
m: H;
l: H;
sq: H | null;
hr: H | null;
a: null;
cd: string;
no: number;
rtUrl: null;
ftype: number;
rtUrls: any[];
djId: number;
copyright: number;
s_id: number;
mark: number;
originCoverType: number;
originSongSimpleData: OriginSongSimpleDatum | null;
tagPicList: null;
resourceState: boolean;
version: number;
songJumpInfo: null;
entertainmentTags: null;
single: number;
noCopyrightRcmd: null;
rtype: number;
rurl: null;
mst: number;
cp: number;
mv: number;
publishTime: number;
reason: null | string;
videoInfo: VideoInfo;
recommendReason: null | string;
privilege: Privilege;
alg: string;
tns?: string[];
s_ctrp?: string;
}
interface Privilege {
id: number;
fee: number;
payed: number;
realPayed: number;
st: number;
pl: number;
dl: number;
sp: number;
cp: number;
subp: number;
cs: boolean;
maxbr: number;
fl: number;
pc: null;
toast: boolean;
flag: number;
paidBigBang: boolean;
preSell: boolean;
playMaxbr: number;
downloadMaxbr: number;
maxBrLevel: string;
playMaxBrLevel: string;
downloadMaxBrLevel: string;
plLevel: string;
dlLevel: string;
flLevel: string;
rscl: null;
freeTrialPrivilege: FreeTrialPrivilege;
rightSource: number;
chargeInfoList: ChargeInfoList[];
}
interface ChargeInfoList {
rate: number;
chargeUrl: null;
chargeMessage: null;
chargeType: number;
}
interface FreeTrialPrivilege {
resConsumable: boolean;
userConsumable: boolean;
listenType: number;
cannotListenReason: number;
playReason: null;
}
interface VideoInfo {
moreThanOne: boolean;
video: Video | null;
}
interface Video {
vid: string;
type: number;
title: string;
playTime: number;
coverUrl: string;
publishTime: number;
artists: null;
alias: null;
}
interface OriginSongSimpleDatum {
songId: number;
name: string;
artists: Artist[];
albumMeta: Artist;
}
interface Artist {
id: number;
name: string;
}
interface H {
br: number;
fid: number;
size: number;
vd: number;
sr: number;
}
interface Al {
id: number;
name: string;
picUrl: string;
tns: string[];
pic_str?: string;
pic: number;
}
interface Ar {
id: number;
name: string;
tns: any[];
alias: any[];
}

View File

@@ -1,4 +1,5 @@
export interface IData<T> {
code: number;
data: T;
result: T;
}

View File

@@ -54,7 +54,7 @@ export const getMusicProxyUrl = (url: string) => {
return `${ProxyUrl}/mc?url=${PUrl}`;
};
export const getImgUrl = computed(() => (url: string, size: string = '') => {
export const getImgUrl = computed(() => (url: string | undefined, size: string = '') => {
const bdUrl = 'https://image.baidu.com/search/down?url=';
const imgUrl = encodeURIComponent(`${url}?param=${size}`);
return `${bdUrl}${imgUrl}`;

View File

@@ -27,6 +27,10 @@
import { useMusicHistory } from '@/hooks/MusicHistoryHook';
import { setAnimationClass, setAnimationDelay } from '@/utils';
defineOptions({
name: 'History',
});
const { delMusic, musicList } = useMusicHistory();
</script>
@@ -38,7 +42,7 @@ const { delMusic, musicList } = useMusicHistory();
}
.history-list-content {
@apply px-4 mt-2;
@apply px-4 mt-2 pb-28;
.history-item {
@apply flex items-center justify-between;
&-content {

View File

@@ -1,16 +1,18 @@
<template>
<div class="main-page">
<!-- 推荐歌手 -->
<recommend-singer />
<div class="main-content">
<!-- 歌单分类列表 -->
<playlist-type />
<!-- 本周最热音乐 -->
<recommend-songlist />
<!-- 推荐最新专辑 -->
<recommend-album />
<n-scrollbar :size="100">
<div class="main-page">
<!-- 推荐歌手 -->
<recommend-singer />
<div class="main-content">
<!-- 歌单分类列表 -->
<playlist-type />
<!-- 本周最热音乐 -->
<recommend-songlist />
<!-- 推荐最新专辑 -->
<recommend-album />
</div>
</div>
</div>
</n-scrollbar>
</template>
<script lang="ts" setup>
@@ -18,6 +20,9 @@ const RecommendSinger = defineAsyncComponent(() => import('@/components/Recommen
const PlaylistType = defineAsyncComponent(() => import('@/components/PlaylistType.vue'));
const RecommendSonglist = defineAsyncComponent(() => import('@/components/RecommendSonglist.vue'));
const RecommendAlbum = defineAsyncComponent(() => import('@/components/RecommendAlbum.vue'));
defineOptions({
name: 'Home',
});
</script>
<style lang="scss" scoped>
@@ -25,6 +30,6 @@ const RecommendAlbum = defineAsyncComponent(() => import('@/components/Recommend
@apply h-full w-full;
}
.main-content {
@apply mt-6 flex pb-28;
@apply mt-6 flex mb-28;
}
</style>

View File

@@ -2,12 +2,15 @@
import { useRoute } from 'vue-router';
import { getListByCat, getListDetail, getRecommendList } from '@/api/list';
import PlayBottom from '@/components/common/PlayBottom.vue';
import MusicList from '@/components/MusicList.vue';
import type { IRecommendItem } from '@/type/list';
import type { IListDetail } from '@/type/listDetail';
import { formatNumber, getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
defineOptions({
name: 'List',
});
const recommendList = ref();
const showMusic = ref(false);
@@ -82,7 +85,6 @@ watch(
<div class="recommend-item-title">{{ item.name }}</div>
</div>
</div>
<play-bottom />
</n-scrollbar>
<music-list
v-if="listDetail?.playlist"

View File

@@ -4,9 +4,13 @@ import { onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { checkQr, createQr, getLoginStatus, getQrKey, getUserDetail, loginByCellphone } from '@/api/login';
import { checkQr, createQr, getQrKey, getUserDetail, loginByCellphone } from '@/api/login';
import { setAnimationClass } from '@/utils';
defineOptions({
name: 'Login',
});
const message = useMessage();
const store = useStore();
const router = useRouter();
@@ -16,37 +20,53 @@ onMounted(() => {
loadLogin();
});
const timerRef = ref(null);
const loadLogin = async () => {
const qrKey = await getQrKey();
const key = qrKey.data.data.unikey;
const { data } = await createQr(key);
qrUrl.value = data.data.qrimg;
timerIsQr(key);
try {
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('加载登录信息时出错:', error);
}
};
// 使用 ref 来保存定时器,便于在任何地方清除它
const timerIsQr = (key: string) => {
const timer = setInterval(async () => {
const { data } = await checkQr(key);
try {
const { data } = await checkQr(key);
if (data.code === 800) {
clearInterval(timer);
}
if (data.code === 803) {
// 将token存入localStorage
localStorage.setItem('token', data.cookie);
const user = await getUserDetail();
store.state.user = user.data.profile;
message.success('登录成功');
if (data.code === 800) {
clearInterval(timer);
timerRef.value = null;
}
if (data.code === 803) {
localStorage.setItem('token', data.cookie);
const user = await getUserDetail();
store.state.user = user.data.profile;
message.success('登录成功');
await getLoginStatus().then((res) => {
console.log(res);
});
clearInterval(timer);
setTimeout(() => {
clearInterval(timer);
timerRef.value = null;
router.push('/user');
}, 1000);
}
} catch (error) {
console.error('检查二维码状态时出错:', error);
// 在出现错误时清除定时器
clearInterval(timer);
timerRef.value = null;
}
}, 5000);
}, 2000);
return timer;
};
// 是否扫码登陆

View File

@@ -48,6 +48,10 @@
<script setup lang="ts">
import { useIpcRenderer } from '@vueuse/electron';
defineOptions({
name: 'Lyric',
});
const ipcRenderer = useIpcRenderer();
const lyricData = ref({
@@ -74,7 +78,7 @@ const lyricSetting = ref({
? JSON.parse(localStorage.getItem('lyricData') || '')
: {
isTop: false,
theme: 'light',
theme: 'dark',
isLock: false,
}),
});

View File

@@ -53,6 +53,10 @@ import { getMvUrl, getTopMv } from '@/api/mv';
import { IMvItem } from '@/type/mv';
import { formatNumber, getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
defineOptions({
name: 'Mv',
});
const showMv = ref(false);
const mvList = ref<Array<IMvItem>>([]);
const playMvItem = ref<IMvItem>();
@@ -91,7 +95,7 @@ const close = () => {
}
&-content {
@apply grid gap-6 pb-4 mt-2;
@apply grid gap-6 pb-28 mt-2;
grid-template-columns: repeat(auto-fill, minmax(14%, 1fr));
}

View File

@@ -19,30 +19,32 @@
<!-- 搜索到的歌曲列表 -->
<n-layout class="search-list" :class="setAnimationClass('animate__fadeInUp')" :native-scrollbar="false">
<div class="title">{{ hotKeyword }}</div>
<div class="search-list-box">
<template v-if="searchDetail">
<div
v-for="(item, index) in searchDetail?.songs"
:key="item.id"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 50)"
>
<song-item :item="item" @play="handlePlay" />
</div>
<template v-for="(list, key) in searchDetail">
<template v-if="key.toString() !== 'songs'">
<div
v-for="(item, index) in list"
:key="item.id"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 50)"
>
<SearchItem :item="item" />
</div>
<n-spin :show="searchDetailLoading">
<div class="search-list-box">
<template v-if="searchDetail">
<div
v-for="(item, index) in searchDetail?.songs"
:key="item.id"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 50)"
>
<song-item :item="item" @play="handlePlay" />
</div>
<template v-for="(list, key) in searchDetail">
<template v-if="key.toString() !== 'songs'">
<div
v-for="(item, index) in list"
:key="item.id"
:class="setAnimationClass('animate__bounceInRight')"
:style="setAnimationDelay(index, 50)"
>
<SearchItem :item="item" />
</div>
</template>
</template>
</template>
</template>
</div>
</div>
</n-spin>
</n-layout>
</div>
</template>
@@ -59,10 +61,15 @@ import SongItem from '@/components/common/SongItem.vue';
import type { IHotSearch } from '@/type/search';
import { setAnimationClass, setAnimationDelay } from '@/utils';
defineOptions({
name: 'Search',
});
const route = useRoute();
const router = useRouter();
const searchDetail = ref<any>();
const searchType = ref(Number(route.query.type) || 1);
const searchDetailLoading = ref(false);
// 热搜列表
const hotSearchData = ref<IHotSearch>();
@@ -93,6 +100,8 @@ const loadSearch = async (keywords: any) => {
hotKeyword.value = keywords;
searchDetail.value = undefined;
if (!keywords) return;
searchDetailLoading.value = true;
const { data } = await getSearch({ keywords, type: searchType.value });
const songs = data.result.songs || [];
@@ -111,6 +120,8 @@ const loadSearch = async (keywords: any) => {
songs,
albums,
};
searchDetailLoading.value = false;
};
loadSearch(route.query.keyword);

View File

@@ -34,6 +34,10 @@ import { useRouter } from 'vue-router';
import store from '@/store';
defineOptions({
name: 'Setting',
});
const setData = ref(store.state.setData);
const router = useRouter();

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { useRouter } from 'vue-router';
import { onBeforeRouteUpdate, useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { getListDetail } from '@/api/list';
@@ -12,6 +12,10 @@ import type { Playlist } from '@/type/listDetail';
import type { IUserDetail } from '@/type/user';
import { getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
defineOptions({
name: 'User',
});
const store = useStore();
const router = useRouter();
const userDetail = ref<IUserDetail>();
@@ -35,7 +39,7 @@ const loadPage = async () => {
recordList.value = recordData.allData;
};
watchEffect(() => {
onMounted(() => {
const localUser = localStorage.getItem('user');
store.state.user = localUser ? JSON.parse(localUser) : null;
user = store.state.user;
@@ -100,7 +104,7 @@ const handlePlay = () => {
<div class="uesr-signature">{{ userDetail.profile.signature }}</div>
<div class="play-list" :class="setAnimationClass('animate__fadeInLeft')">
<div class="play-list-title">创建的歌单</div>
<div class=" ">创建的歌单</div>
<n-scrollbar>
<div v-for="(item, index) in playList" :key="index" class="play-list-item" @click="showPlaylist(item.id)">
<n-image :src="getImgUrl(item.coverImgUrl, '50y50')" class="play-list-item-img" lazy preview-disabled />
@@ -167,10 +171,11 @@ const handlePlay = () => {
}
.right {
@apply flex-1 ml-4;
@apply flex-1 ml-4 overflow-hidden h-full;
.record-list {
background-color: #0d0d0d;
@apply rounded-2xl h-full;
@apply rounded-2xl;
height: calc(100% - 3.75rem);
.record-item {
@apply flex items-center px-4;
}