feat(music): 添加点赞功能并优化音乐解析

- 在 MusicPlayer 组件中添加点赞功能,使用 Check 和 Pointer 图标表示点赞状态
- 引入 toggleLikeMusic API 接口
- 优化歌词解析逻辑,提高代码可读性和性能
- 修改 UserLikeMusic 实体类,使用复合主键设计
This commit is contained in:
ikmkj
2025-07-26 00:44:34 +08:00
parent 9b54cda795
commit ce33571719
2 changed files with 33 additions and 35 deletions

View File

@@ -1,31 +1,28 @@
package com.test.musichouduan.entity;
import jakarta.persistence.*;
import lombok.Data;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* 用户点赞音乐实体类
*/
@Data
@Entity
@Table(name = "user_like_music", uniqueConstraints = {
@UniqueConstraint(columnNames = {"userId", "musicId"})
})
public class UserLikeMusic {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Entity
@Data
@Table(name = "user_like_music")
@IdClass(UserLikeMusic.class)
public class UserLikeMusic implements Serializable {
private Long id;
@Column(nullable = false)
@Id
@Column(name = "user_id")
private Long userId;
@Column(nullable = false)
@Id
@Column(name = "music_id")
private Long musicId;
@CreationTimestamp
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
}

View File

@@ -105,7 +105,7 @@
<el-tooltip :content="currentMusic.liked ? '取消点赞' : '点赞'" placement="top">
<el-button circle @click="toggleLike">
<el-icon>
<component :is="currentMusic.liked ? 'Pointer' : 'Pointer'" />
<component :is="currentMusic.liked ? 'Check' : 'Pointer'" />
</el-icon>
</el-button>
</el-tooltip>
@@ -296,11 +296,11 @@ import { ref, computed, watch, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { usePlayerStore } from '../stores/player'
import { useUserStore } from '../stores/user'
import { processMusicUrl, toggleCollectMusic, getMusicStatus } from '../api/music'
import { processMusicUrl, toggleCollectMusic, getMusicStatus, toggleLikeMusic } from '../api/music'
import { ElMessage } from 'element-plus'
import {
VideoPlay, VideoPause, Back, Right, List, Close, Delete,
Mute, VideoCamera, Microphone, Star, StarFilled, ArrowDown, Pointer
Mute, VideoCamera, Microphone, Star, StarFilled, ArrowDown, Pointer, Check
} from '@element-plus/icons-vue'
const router = useRouter()
@@ -380,26 +380,27 @@ const loopModeText = computed(() => {
// 解析歌词
const parsedLyric = computed(() => {
if (!currentMusic.value || !currentMusic.value.lyric) return []
const lyric = currentMusic.value.lyric
const lines = lyric.split('\n')
const timeRegExp = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/g
const result = []
if (!currentMusic.value || !currentMusic.value.lyric) return [];
const lyric = currentMusic.value.lyric;
const lines = lyric.split('\n');
const result = [];
const timeRegExp = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/g;
for (const line of lines) {
const timeMatches = [...line.matchAll(timeRegExp)]
if (timeMatches.length > 0) {
const match = timeMatches
const min = parseInt(match)
const sec = parseInt(match)
const ms = parseInt(match)
const time = min * 60 + sec + ms / 1000
let text = line.replace(timeRegExp, '').trim()
if (text) {
result.push({ time, text })
const text = line.replace(timeRegExp, '').trim();
if (text) {
timeRegExp.lastIndex = 0;
let match;
while ((match = timeRegExp.exec(line)) !== null) {
const min = parseInt(match, 10);
const sec = parseInt(match, 10);
const ms = parseInt((match || '0').padEnd(3, '0'), 10);
const time = min * 60 + sec + ms / 1000;
result.push({ time, text });
}
}
}
return result.sort((a, b) => a.time - b.time)
return result.sort((a, b) => a.time - b.time);
})
// 格式化时间