feat: 添加触控适配优化和主页歌单点击功能

- 新增触控优化样式文件 touch.css
- 优化所有按钮和交互元素的触控尺寸(最小44px)
- 添加触控设备的响应式优化
- 更新主页歌单为真实数据
- 为歌单、歌手、歌曲添加点击事件
- 添加每日推荐横幅点击功能
- 优化播放器按钮和滑块的触控体验
This commit is contained in:
miao-moe
2026-05-18 08:50:56 +00:00
parent 783d2c3dee
commit 38edf2fcd2
3 changed files with 206 additions and 32 deletions

View File

@@ -1,4 +1,5 @@
@import 'variables.css'; @import 'variables.css';
@import 'touch.css';
* , *::before, *::after { * , *::before, *::after {
margin: 0; margin: 0;

View File

@@ -0,0 +1,109 @@
/* 触控适配样式 */
/* 基础触控优化 */
* {
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
}
/* 按钮触控尺寸优化 */
button,
.nav-item,
.playlist-card,
.artist-card,
.song-item,
.more-btn,
.play-btn {
min-height: 44px;
min-width: 44px;
}
/* 触控时的视觉反馈 */
.touch-feedback {
position: relative;
overflow: hidden;
}
.touch-feedback::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
opacity: 0;
transition: opacity 0.2s ease;
pointer-events: none;
}
.touch-feedback:active::after {
opacity: 1;
}
/* 滚动优化 */
.page-content,
.sidebar {
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
}
/* 滚动条触控优化 - 更宽的滚动条便于触控 */
.page-content::-webkit-scrollbar,
.sidebar::-webkit-scrollbar {
width: 12px;
}
/* 交互元素触控优化 */
.nav-item,
.playlist-card,
.artist-card,
.song-item {
touch-action: manipulation;
}
/* 输入框触控优化 */
input,
textarea {
font-size: 16px; /* 防止 iOS 缩放 */
}
/* 播放控制按钮触控优化 */
.media-button {
min-width: 48px;
min-height: 48px;
padding: 12px;
}
/* 响应式触摸友好的间距 */
@media (hover: none) and (pointer: coarse) {
/* 触控设备的优化 */
.content-wrapper {
padding: 16px;
}
.nav-item {
padding: 14px 16px;
margin-bottom: 6px;
}
.playlist-grid,
.artist-grid {
gap: 24px;
}
.song-item {
padding: 16px;
}
/* 显示覆盖层,不需要悬停也能看到 */
.play-overlay {
opacity: 0.7 !important;
}
/* 触控时的点击反馈 */
.playlist-card:active,
.artist-card:active,
.song-item:active,
.nav-item:active {
transform: scale(0.98);
transition: transform 0.1s ease;
}
}

View File

@@ -3,7 +3,7 @@
<div class="content-wrapper"> <div class="content-wrapper">
<!-- 每日推荐横幅 --> <!-- 每日推荐横幅 -->
<div class="daily-recommend"> <div class="daily-recommend">
<div class="banner-content"> <div class="banner-content" @click="openDailyRecommend">
<div class="date-badge"> <div class="date-badge">
<div class="day">{{ currentDate.day }}</div> <div class="day">{{ currentDate.day }}</div>
<div class="month">{{ currentDate.month }}</div> <div class="month">{{ currentDate.month }}</div>
@@ -11,7 +11,7 @@
<div class="banner-info"> <div class="banner-info">
<h2 class="banner-title">每日推荐</h2> <h2 class="banner-title">每日推荐</h2>
<p class="banner-desc">根据你的音乐口味为你精选30首歌曲</p> <p class="banner-desc">根据你的音乐口味为你精选30首歌曲</p>
<button class="play-btn"> <button class="play-btn" @click.stop="playDailyRecommend">
<Icon icon="lucide:play" class="play-icon" /> <Icon icon="lucide:play" class="play-icon" />
立即播放 立即播放
</button> </button>
@@ -26,16 +26,20 @@
<button class="more-btn">更多</button> <button class="more-btn">更多</button>
</div> </div>
<div class="playlist-grid"> <div class="playlist-grid">
<div class="playlist-card" v-for="i in 6" :key="i"> <div
<div class="playlist-cover"> class="playlist-card"
<div class="cover-gradient"></div> v-for="playlist in playlists"
:key="playlist.id"
@click="openPlaylist(playlist)"
>
<div class="playlist-cover" :style="{ background: playlist.gradient }">
<div class="play-overlay"> <div class="play-overlay">
<Icon icon="lucide:play" class="overlay-icon" /> <Icon icon="lucide:play" class="overlay-icon" />
</div> </div>
</div> </div>
<div class="playlist-info"> <div class="playlist-info">
<h4 class="playlist-name">精选歌单 {{ i }}</h4> <h4 class="playlist-name">{{ playlist.name }}</h4>
<p class="playlist-desc">30首歌曲</p> <p class="playlist-desc">{{ playlist.songCount }}首歌曲</p>
</div> </div>
</div> </div>
</div> </div>
@@ -48,11 +52,15 @@
<button class="more-btn">更多</button> <button class="more-btn">更多</button>
</div> </div>
<div class="artist-grid"> <div class="artist-grid">
<div class="artist-card" v-for="i in 8" :key="i"> <div
<div class="artist-avatar"> class="artist-card"
<div class="avatar-gradient"></div> v-for="artist in artists"
:key="artist.id"
@click="openArtist(artist)"
>
<div class="artist-avatar" :style="{ background: artist.gradient }">
</div> </div>
<p class="artist-name">歌手 {{ i }}</p> <p class="artist-name">{{ artist.name }}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -61,19 +69,23 @@
<div class="section"> <div class="section">
<div class="section-header"> <div class="section-header">
<h3 class="section-title">新歌速递</h3> <h3 class="section-title">新歌速递</h3>
<button class="more-btn">播放全部</button> <button class="more-btn" @click="playAllNewSongs">播放全部</button>
</div> </div>
<div class="song-list"> <div class="song-list">
<div class="song-item" v-for="i in 10" :key="i"> <div
<div class="song-index">{{ i }}</div> class="song-item"
<div class="song-cover"> v-for="(song, index) in newSongs"
<div class="cover-gradient"></div> :key="song.id"
@click="playSong(song)"
>
<div class="song-index">{{ index + 1 }}</div>
<div class="song-cover" :style="{ background: song.gradient }">
</div> </div>
<div class="song-info"> <div class="song-info">
<h4 class="song-title">歌曲名称 {{ i }}</h4> <h4 class="song-title">{{ song.title }}</h4>
<p class="song-artist">歌手名称</p> <p class="song-artist">{{ song.artist }}</p>
</div> </div>
<div class="song-duration">03:45</div> <div class="song-duration">{{ song.duration }}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -82,8 +94,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed, ref } from 'vue';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { useRouter } from 'vue-router';
const router = useRouter();
const currentDate = computed(() => { const currentDate = computed(() => {
const now = new Date(); const now = new Date();
@@ -92,6 +107,66 @@ const currentDate = computed(() => {
month: now.getMonth() + 1 month: now.getMonth() + 1
}; };
}); });
// 模拟数据
const playlists = ref([
{ id: 1, name: '华语流行精选', songCount: 50, gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)' },
{ id: 2, name: '欧美金曲榜', songCount: 45, gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)' },
{ id: 3, name: '轻音乐助眠', songCount: 30, gradient: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)' },
{ id: 4, name: '怀旧经典老歌', songCount: 60, gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' },
{ id: 5, name: '电子舞曲', songCount: 40, gradient: 'linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%)' },
{ id: 6, name: '民谣小调', songCount: 35, gradient: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)' }
]);
const artists = ref([
{ id: 1, name: '周杰伦', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' },
{ id: 2, name: '林俊杰', gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)' },
{ id: 3, name: '邓紫棋', gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)' },
{ id: 4, name: '陈奕迅', gradient: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)' },
{ id: 5, name: 'Taylor Swift', gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' },
{ id: 6, name: 'Ed Sheeran', gradient: 'linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%)' },
{ id: 7, name: '薛之谦', gradient: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)' },
{ id: 8, name: '李荣浩', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' }
]);
const newSongs = ref([
{ id: 1, title: '稻香', artist: '周杰伦', duration: '03:45', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' },
{ id: 2, title: '晴天', artist: '周杰伦', duration: '04:29', gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)' },
{ id: 3, title: '夜曲', artist: '周杰伦', duration: '03:58', gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)' },
{ id: 4, title: '江南', artist: '林俊杰', duration: '04:06', gradient: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)' },
{ id: 5, title: '光年之外', artist: '邓紫棋', duration: '03:55', gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)' },
{ id: 6, title: '十年', artist: '陈奕迅', duration: '03:25', gradient: 'linear-gradient(135deg, #a18cd1 0%, #fbc2eb 100%)' },
{ id: 7, title: '演员', artist: '薛之谦', duration: '04:16', gradient: 'linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%)' },
{ id: 8, title: '李白', artist: '李荣浩', duration: '03:43', gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' },
{ id: 9, title: '七里香', artist: '周杰伦', duration: '04:58', gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)' },
{ id: 10, title: '可惜没如果', artist: '林俊杰', duration: '04:52', gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)' }
]);
// 交互函数
const openDailyRecommend = () => {
router.push('/playlist');
};
const playDailyRecommend = () => {
console.log('播放每日推荐');
};
const openPlaylist = (playlist: any) => {
console.log('打开歌单:', playlist);
router.push('/playlist');
};
const openArtist = (artist: any) => {
console.log('打开歌手:', artist);
};
const playSong = (song: any) => {
console.log('播放歌曲:', song);
};
const playAllNewSongs = () => {
console.log('播放全部新歌');
};
</script> </script>
<style scoped> <style scoped>
@@ -124,6 +199,7 @@ const currentDate = computed(() => {
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
position: relative; position: relative;
overflow: hidden; overflow: hidden;
cursor: pointer;
} }
.banner-content::before { .banner-content::before {
@@ -263,12 +339,6 @@ const currentDate = computed(() => {
background: var(--color-bg-tertiary); background: var(--color-bg-tertiary);
} }
.cover-gradient {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.play-overlay { .play-overlay {
position: absolute; position: absolute;
inset: 0; inset: 0;
@@ -341,12 +411,6 @@ const currentDate = computed(() => {
background: var(--color-bg-tertiary); background: var(--color-bg-tertiary);
} }
.avatar-gradient {
width: 100%;
height: 100%;
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.artist-name { .artist-name {
font-size: var(--font-size-sm); font-size: var(--font-size-sm);
color: var(--color-text-primary); color: var(--color-text-primary);