Compare commits

..

2 Commits

4 changed files with 69 additions and 2 deletions

View File

@@ -54,6 +54,16 @@ public class MusicDTO {
*/ */
private Long playCount; private Long playCount;
/**
* 点赞数
*/
private Integer likeCount;
/**
* 收藏数
*/
private Integer collectCount;
/** /**
* 歌手列表 * 歌手列表
*/ */

View File

@@ -650,6 +650,10 @@ public class MusicServiceImpl implements MusicService {
MusicDTO musicDTO = new MusicDTO(); MusicDTO musicDTO = new MusicDTO();
BeanUtils.copyProperties(music, musicDTO); BeanUtils.copyProperties(music, musicDTO);
// 兜底互动计数,避免前端出现 null
musicDTO.setLikeCount(music.getLikeCount() != null ? music.getLikeCount() : 0);
musicDTO.setCollectCount(music.getCollectCount() != null ? music.getCollectCount() : 0);
// 确保专辑字段正确复制 // 确保专辑字段正确复制
musicDTO.setAlbum(music.getAlbum()); musicDTO.setAlbum(music.getAlbum());

View File

@@ -54,6 +54,16 @@
<span class="label">发行日期</span> <span class="label">发行日期</span>
<span class="value">{{ music.releaseDate || '-' }}</span> <span class="value">{{ music.releaseDate || '-' }}</span>
</div> </div>
<div class="meta-item">
<span class="label">点赞数</span>
<span class="value">{{ music.likeCount ?? 0 }}</span>
</div>
<div class="meta-item">
<span class="label">收藏数</span>
<span class="value">{{ music.collectCount ?? 0 }}</span>
</div>
</div> </div>
<div class="music-actions"> <div class="music-actions">
@@ -321,7 +331,11 @@ const fetchMusicDetail = async () => {
const res = await getMusicById(id) const res = await getMusicById(id)
if (res.code === 200) { if (res.code === 200) {
if (res.data) { if (res.data) {
music.value = res.data music.value = {
...res.data,
likeCount: res.data.likeCount ?? 0,
collectCount: res.data.collectCount ?? 0
}
// 获取音乐状态 // 获取音乐状态
fetchMusicStatus() fetchMusicStatus()
} else { } else {
@@ -543,6 +557,12 @@ const toggleCollect = async () => {
if (res.code === 200) { if (res.code === 200) {
// 后端直接返回了最新的收藏状态 (true/false),直接赋值即可 // 后端直接返回了最新的收藏状态 (true/false),直接赋值即可
music.value.collected = res.data music.value.collected = res.data
// 仅在操作成功时同步更新本地收藏数
if (music.value.collected) {
music.value.collectCount = (music.value.collectCount || 0) + 1
} else {
music.value.collectCount = Math.max(0, (music.value.collectCount || 0) - 1)
}
ElMessage.success(res.message || (music.value.collected ? '收藏成功' : '已取消收藏')) ElMessage.success(res.message || (music.value.collected ? '收藏成功' : '已取消收藏'))
} else { } else {
ElMessage.error(res.message || '操作失败') ElMessage.error(res.message || '操作失败')

View File

@@ -29,6 +29,39 @@ const editForm = reactive({
gender: 0, gender: 0,
introduction: '' introduction: ''
}) })
const validateAvatar = (_rule, value, callback) => {
if (!value || !String(value).trim()) {
callback()
return
}
const avatar = String(value).trim()
// 兼容站内相对路径(如 /upload/xx、./xx、../xx、images/a.png
const hasScheme = /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(avatar)
if (!hasScheme) {
const isRelativePath = /^(\/?[^\s]+)$/.test(avatar) && !avatar.startsWith('//')
if (isRelativePath) {
callback()
return
}
callback(new Error('请输入有效头像地址http/https 或相对路径)'))
return
}
try {
const url = new URL(avatar)
if (url.protocol === 'http:' || url.protocol === 'https:') {
callback()
return
}
} catch (_e) {
// ignore
}
callback(new Error('请输入有效头像地址http/https 或相对路径)'))
}
const editFormRules = { const editFormRules = {
nickname: [ nickname: [
{ required: true, message: '请输入昵称', trigger: 'blur' }, { required: true, message: '请输入昵称', trigger: 'blur' },
@@ -38,7 +71,7 @@ const editFormRules = {
{ pattern: /^\S+@\S+\.\S+$/, message: '请输入正确的邮箱地址', trigger: 'blur' } { pattern: /^\S+@\S+\.\S+$/, message: '请输入正确的邮箱地址', trigger: 'blur' }
], ],
avatar: [ avatar: [
{ pattern: /^(https?:\/\/)?[\w\-]+(\.[\w\-]+)+([\w\-.,@?^=%&:/~+#]*[\w\-@?^=%&/~+#])?$/, message: '请输入正确的URL地址', trigger: 'blur' } { validator: validateAvatar, trigger: 'blur' }
] ]
} }
const editFormRef = ref(null) const editFormRef = ref(null)