🦄 refactor: 适配 web移动端 改造

This commit is contained in:
alger
2024-05-23 17:12:35 +08:00
parent a2af0f3904
commit c09707867b
19 changed files with 265 additions and 79 deletions

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="zh">
<head>
<meta charset="UTF-8" />
@@ -15,7 +15,7 @@
--animate-delay: 0.5s;
}
</style>
</head>
</head>
<body>
<div id="app"></div>

View File

@@ -1,5 +1,5 @@
<template>
<div class="app">
<div class="app" :class="isMobile ? 'mobile' : ''">
<audio id="MusicAudio" ref="audioRef" :src="playMusicUrl" :autoplay="play"></audio>
<n-config-provider :theme="darkTheme">
<n-dialog-provider>
@@ -16,6 +16,8 @@ import { darkTheme } from 'naive-ui';
import store from '@/store';
import { isMobile } from './utils';
const playMusicUrl = computed(() => store.state.playMusicUrl as string);
// 是否播放
const play = computed(() => store.state.play as boolean);
@@ -35,4 +37,14 @@ div {
.app {
user-select: none;
}
.mobile {
.text-base {
font-size: 14px !important;
}
}
.html:has(.mobile) {
font-size: 14px;
}
</style>

View File

@@ -1,8 +1,13 @@
<template>
<n-drawer :show="show" height="70vh" placement="bottom" :drawer-style="{ backgroundColor: 'transparent' }">
<n-drawer
:show="show"
:height="isMobile ? '100vh' : '70vh'"
placement="bottom"
:drawer-style="{ backgroundColor: 'transparent' }"
>
<div class="music-page">
<i class="iconfont icon-icon_error music-close" @click="close"></i>
<div class="music-title">{{ name }}</div>
<div class="music-title text-el">{{ name }}</div>
<!-- 歌单歌曲列表 -->
<div class="music-list">
<n-scrollbar>
@@ -25,7 +30,7 @@
import { useStore } from 'vuex';
import SongItem from '@/components/common/SongItem.vue';
import { setAnimationClass, setAnimationDelay } from '@/utils';
import { isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
import PlayBottom from './common/PlayBottom.vue';
@@ -78,4 +83,10 @@ const close = () => {
height: calc(100% - 60px);
}
}
.mobile {
.music-page {
@apply px-4;
}
}
</style>

View File

@@ -74,4 +74,10 @@ onMounted(() => {
@apply block text-center;
}
}
.mobile {
.play-list-type {
@apply mx-0 w-full;
}
}
</style>

View File

@@ -1,58 +1,60 @@
<template>
<!-- 推荐歌手 -->
<div class="recommend-singer">
<div class="recommend-singer-list">
<div
class="recommend-singer-item relative"
:class="setAnimationClass('animate__backInRight')"
:style="setAnimationDelay(0, 100)"
>
<n-scrollbar :size="100" :x-scrollable="true">
<div class="recommend-singer">
<div class="recommend-singer-list">
<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"
class="recommend-singer-item relative"
:class="setAnimationClass('animate__backInRight')"
:style="setAnimationDelay(0, 100)"
>
<div class="font-bold text-xl">每日推荐列表</div>
<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 + 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>
<div class="recommend-singer-item-info z-10">
<div class="recommend-singer-item-info-play" @click="toSearchSinger(item.name)">
<i class="iconfont icon-playfill text-xl"></i>
</div>
<div class="ml-4">
<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 class="mt-2">
<p v-for="item in dayRecommendData?.dailySongs.slice(0, 5)" :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 + 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>
<div class="recommend-singer-item-info z-10">
<div class="recommend-singer-item-info-play" @click="toSearchSinger(item.name)">
<i class="iconfont icon-playfill text-xl"></i>
</div>
<div class="ml-4">
<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>
<music-list
v-if="dayRecommendData?.dailySongs.length"
v-model:show="showMusic"
name="每日推荐列表"
:song-list="dayRecommendData?.dailySongs"
/>
</div>
</n-scrollbar>
</template>
<script lang="ts" setup>
@@ -110,7 +112,7 @@ const toSearchSinger = (keyword: string) => {
height: 280px;
}
&-item {
@apply flex-1 h-full rounded-3xl p-5 mr-5 flex flex-col justify-between;
@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;
filter: brightness(60%);
@@ -123,4 +125,17 @@ const toSearchSinger = (keyword: string) => {
}
}
}
.mobile .recommend-singer {
&-list {
height: 180px;
@apply ml-4;
}
&-item {
@apply p-4 rounded-xl;
&-bg {
@apply rounded-xl;
}
}
}
</style>

View File

@@ -4,14 +4,18 @@
<title-bar v-if="isElectron" />
<div class="layout-main-page" :class="isElectron ? '' : 'pt-6'">
<!-- 侧边菜单栏 -->
<app-menu class="menu" :menus="menus" />
<app-menu v-if="!isMobile" class="menu" :menus="menus" />
<div class="main">
<!-- 搜索栏 -->
<search-bar />
<!-- 主页面路由 -->
<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' : ''">
<router-view
v-slot="{ Component }"
class="main-page"
:class="route.meta.noScroll && !isMobile ? 'pr-3' : ''"
>
<keep-alive :include="keepAliveInclude">
<component :is="Component" />
</keep-alive>
@@ -19,6 +23,7 @@
</n-message-provider>
</div>
<play-bottom height="5rem" />
<app-menu v-if="isMobile" class="menu" :menus="menus" />
</div>
</div>
<!-- 底部音乐播放 -->
@@ -33,6 +38,7 @@ import { useStore } from 'vuex';
import PlayBottom from '@/components/common/PlayBottom.vue';
import homeRouter from '@/router/home';
import { isMobile } from '@/utils';
const keepAliveInclude = computed(() =>
homeRouter
@@ -135,18 +141,23 @@ const playMusicEvent = async () => {
&-page {
@apply flex flex-1 overflow-hidden;
}
.menu {
width: 90px;
}
.main {
@apply flex-1 box-border flex flex-col;
@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;
// :deep(.n-scrollbar-content) {
// @apply pr-3;
// }
}
.mobile {
.layout-main {
&-page {
@apply pt-4;
}
}
}
</style>

View File

@@ -82,4 +82,25 @@ const iconStyle = (index: number) => {
transform: scale(1.05);
transition: 0.2s ease-in-out;
}
.mobile {
.app-menu {
max-width: 100%;
width: 100vw;
position: relative;
z-index: 999999;
background-color: #000;
&-header {
display: none;
}
&-list {
@apply flex justify-between;
}
&-item {
&-link {
@apply my-4;
}
}
}
}
</style>

View File

@@ -179,4 +179,16 @@ defineExpose({
}
}
}
.mobile {
#drawer-target {
@apply flex-col p-4 pt-8;
.music-img {
display: none;
}
.music-lrc {
height: calc(100vh - 260px) !important;
}
}
}
</style>

View File

@@ -25,13 +25,13 @@
</div>
</div>
<div class="music-buttons">
<div @click="handlePrev">
<div class="music-buttons-prev" @click="handlePrev">
<i class="iconfont icon-prev"></i>
</div>
<div class="music-buttons-play" @click="playMusicEvent">
<i class="iconfont icon" :class="play ? 'icon-stop' : 'icon-play'"></i>
</div>
<div @click="handleEnded">
<div class="music-buttons-next" @click="handleEnded">
<i class="iconfont icon-next"></i>
</div>
</div>
@@ -59,7 +59,7 @@
</template>
解析播放
</n-tooltip> -->
<n-tooltip trigger="hover" :z-index="9999999">
<n-tooltip class="music-lyric" trigger="hover" :z-index="9999999">
<template #trigger>
<i class="iconfont ri-netease-cloud-music-line" @click="openLyric"></i>
</template>
@@ -305,4 +305,36 @@ const setMusicFull = () => {
}
}
}
.mobile {
.music-play-bar {
@apply px-4;
bottom: 70px;
}
.music-time {
display: none;
}
.ri-netease-cloud-music-line {
display: none;
}
.audio-volume {
display: none;
}
.audio-button {
@apply mx-0;
}
.music-buttons {
@apply m-0;
&-prev,
&-next {
display: none;
}
&-play {
@apply m-0;
}
}
.music-content {
flex: 1;
}
}
</style>

View File

@@ -145,4 +145,10 @@ const selectItem = async (key: string) => {
.search-box-input {
@apply relative;
}
.mobile {
.search-box {
@apply pl-4;
}
}
</style>

View File

@@ -17,6 +17,7 @@ interface State {
playListIndex: number;
setData: any;
lyric: any;
isMobile: boolean;
}
const state: State = {
@@ -30,6 +31,7 @@ const state: State = {
playListIndex: 0,
setData: null,
lyric: {},
isMobile: false,
};
const windowData = window as any;

View File

@@ -1,3 +1,7 @@
import { computed } from 'vue';
import store from '@/store';
// 设置歌手背景图片
export const setBackgroundImg = (url: String) => {
return `background-image:url(${url})`;
@@ -59,3 +63,15 @@ export const getImgUrl = computed(() => (url: string | undefined, size: string =
const imgUrl = encodeURIComponent(`${url}?param=${size}`);
return `${bdUrl}${imgUrl}`;
});
export const isMobile = computed(() => {
const flag = navigator.userAgent.match(
/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i,
);
store.state.isMobile = !!flag;
// 给html标签 添加mobile
if (flag) document.documentElement.classList.add('mobile');
return !!flag;
});

View File

@@ -38,7 +38,7 @@ const { delMusic, musicList } = useMusicHistory();
.history-page {
@apply h-full w-full pt-2;
.title {
@apply text-xl font-bold;
@apply pl-4 text-xl font-bold;
}
.history-list-content {

View File

@@ -1,11 +1,11 @@
<template>
<n-scrollbar :size="100">
<n-scrollbar :size="100" :x-scrollable="false">
<div class="main-page">
<!-- 推荐歌手 -->
<recommend-singer />
<div class="main-content">
<!-- 歌单分类列表 -->
<playlist-type />
<playlist-type v-if="!isMobile" />
<!-- 本周最热音乐 -->
<recommend-songlist />
<!-- 推荐最新专辑 -->
@@ -16,6 +16,8 @@
</template>
<script lang="ts" setup>
import { isMobile } from '@/utils';
const RecommendSinger = defineAsyncComponent(() => import('@/components/RecommendSinger.vue'));
const PlaylistType = defineAsyncComponent(() => import('@/components/PlaylistType.vue'));
const RecommendSonglist = defineAsyncComponent(() => import('@/components/RecommendSonglist.vue'));
@@ -27,9 +29,13 @@ defineOptions({
<style lang="scss" scoped>
.main-page {
@apply h-full w-full;
@apply h-full w-full overflow-hidden;
}
.main-content {
@apply mt-6 flex mb-28;
}
.mobile .main-content {
@apply flex-col mx-4;
}
</style>

View File

@@ -14,9 +14,11 @@ defineOptions({
const recommendList = ref();
const showMusic = ref(false);
const recommendItem = ref<IRecommendItem>();
const listDetail = ref<IListDetail>();
const recommendItem = ref<IRecommendItem | null>();
const listDetail = ref<IListDetail | null>();
const selectRecommendItem = async (item: IRecommendItem) => {
recommendItem.value = null;
listDetail.value = null;
showMusic.value = true;
const { data } = await getListDetail(item.id);
recommendItem.value = item;
@@ -98,7 +100,7 @@ watch(
<style lang="scss" scoped>
.list-page {
@apply relative h-full w-full;
@apply relative h-full w-full px-4;
}
.recommend {
@@ -149,4 +151,10 @@ watch(
}
}
}
.mobile {
.recommend-list {
grid-template-columns: repeat(auto-fill, minmax(25%, 1fr));
}
}
</style>

View File

@@ -5,7 +5,7 @@ import { useRouter } from 'vue-router';
import { useStore } from 'vuex';
import { checkQr, createQr, getQrKey, getUserDetail, loginByCellphone } from '@/api/login';
import { setAnimationClass } from '@/utils';
import { isMobile, setAnimationClass } from '@/utils';
defineOptions({
name: 'Login',
@@ -71,7 +71,7 @@ const timerIsQr = (key: string) => {
};
// 是否扫码登陆
const isQr = ref(true);
const isQr = ref(!isMobile.value);
const chooseQr = () => {
isQr.value = !isQr.value;
};

View File

@@ -87,7 +87,7 @@ const close = () => {
<style scoped lang="scss">
.mv-list {
@apply relative h-full w-full;
@apply relative h-full w-full px-4;
&-title {
@apply text-xl font-bold;
@@ -173,4 +173,10 @@ const close = () => {
@apply top-0;
}
}
.mobile {
.mv-list-content {
grid-template-columns: repeat(auto-fill, minmax(30%, 1fr));
}
}
</style>

View File

@@ -1,6 +1,11 @@
<template>
<div class="search-page">
<n-layout class="hot-search" :class="setAnimationClass('animate__fadeInDown')" :native-scrollbar="false">
<n-layout
v-if="isMobile ? !searchDetail : true"
class="hot-search"
:class="setAnimationClass('animate__fadeInDown')"
:native-scrollbar="false"
>
<div class="title">热搜列表</div>
<div class="hot-search-list">
<template v-for="(item, index) in hotSearchData?.data" :key="index">
@@ -17,7 +22,12 @@
</div>
</n-layout>
<!-- 搜索到的歌曲列表 -->
<n-layout class="search-list" :class="setAnimationClass('animate__fadeInUp')" :native-scrollbar="false">
<n-layout
v-if="isMobile ? searchDetail : true"
class="search-list"
:class="setAnimationClass('animate__fadeInUp')"
:native-scrollbar="false"
>
<div class="title">{{ hotKeyword }}</div>
<n-spin :show="searchDetailLoading">
<div class="search-list-box">
@@ -59,7 +69,7 @@ import { getHotSearch } from '@/api/home';
import { getSearch } from '@/api/search';
import SongItem from '@/components/common/SongItem.vue';
import type { IHotSearch } from '@/type/search';
import { setAnimationClass, setAnimationDelay } from '@/utils';
import { isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
defineOptions({
name: 'Search',
@@ -195,4 +205,10 @@ const handlePlay = () => {
.title {
@apply text-gray-200 text-xl font-bold my-2 mx-4;
}
.mobile {
.hot-search {
@apply mr-0 w-full;
}
}
</style>

View File

@@ -10,7 +10,7 @@ import SongItem from '@/components/common/SongItem.vue';
import MusicList from '@/components/MusicList.vue';
import type { Playlist } from '@/type/listDetail';
import type { IUserDetail } from '@/type/user';
import { getImgUrl, setAnimationClass, setAnimationDelay } from '@/utils';
import { getImgUrl, isMobile, setAnimationClass, setAnimationDelay } from '@/utils';
defineOptions({
name: 'User',
@@ -120,7 +120,7 @@ const handlePlay = () => {
</div>
</div>
</div>
<div class="right" :class="setAnimationClass('animate__fadeInRight')">
<div v-if="!isMobile" class="right" :class="setAnimationClass('animate__fadeInRight')">
<div class="title">听歌排行</div>
<div class="record-list">
<n-scrollbar>
@@ -217,4 +217,10 @@ const handlePlay = () => {
}
}
}
.mobile {
.user-page {
@apply px-4;
}
}
</style>