mirror of
https://github.com/lqtmcstudio/QZMusic_PC.git
synced 2026-06-20 23:35:06 +08:00
Compare commits
2 Commits
master
...
v2-touch-o
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cf94fd08b | ||
|
|
38edf2fcd2 |
19
README.md
19
README.md
@@ -3,6 +3,18 @@
|
|||||||
一款简洁美观的跨平台音乐播放器,提供优雅的桌面音乐体验
|
一款简洁美观的跨平台音乐播放器,提供优雅的桌面音乐体验
|
||||||
安卓版本请访问:[官方网站](https://music.qz.shiqianjiang.cn)
|
安卓版本请访问:[官方网站](https://music.qz.shiqianjiang.cn)
|
||||||
|
|
||||||
|
## ⚠️ 项目状态
|
||||||
|
|
||||||
|
> 🔔 **重要提示**:
|
||||||
|
> 本项目**正在积极开发维护中**,目前仍处于早期阶段,可能存在以下问题:
|
||||||
|
> - 部分功能尚未完善或稳定
|
||||||
|
> - 可能存在 Bug 和兼容性问
|
||||||
|
> - UI/UX 可能在后续版本中调整
|
||||||
|
>
|
||||||
|
> 如果您在使用过程中遇到任何问题,欢迎提交 [Issue](https://github.com/miao-moe/QZMusic_PC/issues) 反馈,我们会尽快处理!
|
||||||
|
>
|
||||||
|
> 感谢您的理解与支持 🙏
|
||||||
|
|
||||||
## 🛠 技术架构
|
## 🛠 技术架构
|
||||||
|
|
||||||
| 技术 | 说明 |
|
| 技术 | 说明 |
|
||||||
@@ -24,13 +36,14 @@
|
|||||||
> - AMLL 提供功能接口,不关联具体音乐数据源
|
> - AMLL 提供功能接口,不关联具体音乐数据源
|
||||||
|
|
||||||
## 🛠 开发者(排名不分先后)
|
## 🛠 开发者(排名不分先后)
|
||||||
- (以下内容实时更新,更新日期:2026.1.21)
|
- (以下内容实时更新,更新日期:2026.5.23)
|
||||||
- -蜻蜓T-T ([B站](https://space.bilibili.com/3546554124209112) [Github](https://github.com/lqtmcstudio)) | 插件系统 音频系统 UI设计/开发 后端
|
- 蜻蜓T-T ([B站](https://space.bilibili.com/3546554124209112) [Github](https://github.com/lqtmcstudio)) | 插件系统 音频系统 UI设计/开发 后端
|
||||||
- 时迁酱 ([个人主页](https://shiqianjiang.cn)) 登录/鉴权系统
|
- 时迁酱 ([个人主页](https://shiqianjiang.cn)) 登录/鉴权系统
|
||||||
|
- miao-moe ([GitHub](https://github.com/miao-moe) [博客](https://miao-moe.cn)) | 触控适配优化 UI/UX 改进
|
||||||
|
|
||||||
## 📄 开源协议
|
## 📄 开源协议
|
||||||
|
|
||||||
本项目采用 [**AGPL v3**](https://github.com/lqtmcstudio/QZMusic_PC/blob/master/LICENSE) 开源协议。
|
本项目采用 [**AGPL v3**](https://github.com/miao-moe/QZMusic_PC/blob/master/LICENSE) 开源协议。
|
||||||
|
|
||||||
## 使用工具
|
## 使用工具
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@import 'variables.css';
|
@import 'variables.css';
|
||||||
|
@import 'touch.css';
|
||||||
|
|
||||||
* , *::before, *::after {
|
* , *::before, *::after {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
109
src/renderer/src/styles/touch.css
Normal file
109
src/renderer/src/styles/touch.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user