This commit is contained in:
2023-10-18 21:28:55 +08:00
parent 4f102b8d6e
commit eb8df943e3
4 changed files with 274 additions and 31 deletions

View File

@@ -2,7 +2,7 @@
import { SongAuthorInfo, SongFrom, SongLanguage, SongsInfo, UserInfo } from '@/api/api-models' import { SongAuthorInfo, SongFrom, SongLanguage, SongsInfo, UserInfo } from '@/api/api-models'
import { QueryGetAPI, QueryPostAPI } from '@/api/query' import { QueryGetAPI, QueryPostAPI } from '@/api/query'
import { SONG_API_URL } from '@/data/constants' import { SONG_API_URL } from '@/data/constants'
import { refDebounced, useDebounceFn } from '@vueuse/core' import { refDebounced, useDebounceFn, useLocalStorage } from '@vueuse/core'
import { List } from 'linqts' import { List } from 'linqts'
import { import {
DataTableBaseColumn, DataTableBaseColumn,
@@ -51,6 +51,7 @@ watch(
}, 1) }, 1)
} }
) )
const volume = useLocalStorage('Settings.AplayerVolume', 0.8)
const songsInternal = ref(props.songs) const songsInternal = ref(props.songs)
const songsComputed = computed(() => { const songsComputed = computed(() => {
if (debouncedInput.value) { if (debouncedInput.value) {
@@ -70,7 +71,7 @@ const aplayerMusic = ref<{
title: string title: string
artist: string artist: string
src: string src: string
pic: string lrc: string
}>() }>()
const formRef = ref<FormInst | null>(null) const formRef = ref<FormInst | null>(null)
@@ -152,8 +153,7 @@ const authorColumn = ref<DataTableBaseColumn<SongsInfo>>({
const onAuthorClick = (author: string) => { const onAuthorClick = (author: string) => {
if (authorColumn.value.filterOptionValue == author) { if (authorColumn.value.filterOptionValue == author) {
authorColumn.value.filterOptionValue = undefined authorColumn.value.filterOptionValue = undefined
} } else {
else {
authorColumn.value.filterOptionValue = author authorColumn.value.filterOptionValue = author
} }
} }
@@ -218,11 +218,11 @@ function createColumns(): DataTableColumns<SongsInfo> {
NSpace, NSpace,
{ {
justify: 'end', justify: 'end',
size: 10 size: 10,
}, },
() => [ () => [
GetPlayButton(data), GetPlayButton(data),
data.url data.url?.endsWith('mp3') || data.url?.endsWith('flac') || data.url?.endsWith('ogg') || data.url?.endsWith('wav') || data.url?.endsWith('m4a')
? h(NTooltip, null, { ? h(NTooltip, null, {
trigger: () => trigger: () =>
h( h(
@@ -231,14 +231,7 @@ function createColumns(): DataTableColumns<SongsInfo> {
type: 'primary', type: 'primary',
size: 'small', size: 'small',
circle: true, circle: true,
onClick: () => { onClick: () => OnPlayMusic(data),
aplayerMusic.value = {
title: data.name,
artist: data.author.join('/') ?? '',
src: data.url,
pic: '',
}
},
}, },
{ {
icon: () => h(NIcon, { component: Play24Filled }), icon: () => h(NIcon, { component: Play24Filled }),
@@ -301,6 +294,87 @@ function createColumns(): DataTableColumns<SongsInfo> {
}, },
] ]
} }
function OnPlayMusic(song: SongsInfo) {
aplayerMusic.value = undefined
if (song.from == SongFrom.Netease) GetLyric(song)
else {
aplayerMusic.value = {
title: song.name,
artist: song.author.join('/') ?? '',
src: song.url,
lrc: '',
}
}
}
async function GetLyric(song: SongsInfo) {
QueryGetAPI<{ lyric: string; tlyric: string }>(SONG_API_URL + 'get-netease-lyric', { id: song.id })
.then((data) => {
console.log(mergeLyrics(data.data.lyric, data.data.tlyric))
if (data.code == 200) {
aplayerMusic.value = {
title: song.name,
artist: song.author.join('/') ?? '',
src: song.url,
lrc: data.data.tlyric ? mergeLyrics(data.data.lyric, data.data.tlyric) : data.data.lyric,
}
//aplayerMusic.value.lrc = data.data.lyric
}
})
.catch((err) => {
console.error(err)
aplayerMusic.value = {
title: song.name,
artist: song.author.join('/') ?? '',
src: song.url,
lrc: '',
}
})
}
function mergeLyrics(originalLyrics: string, translatedLyrics: string): string {
const originalLines = originalLyrics.split('\n')
const translatedLines = translatedLyrics.split('\n')
let mergedLyrics = ''
for (let i = 0; i < originalLines.length; i++) {
const originalLine = originalLines[i]?.trim()
const originalTimeMatch = originalLine?.match(/\[(\d{2}:\d{2}\.\d{2,3})\]/) // 匹配原歌词的时间字符串
let mergedLine = originalLine
if (originalTimeMatch) {
const originalTime = originalTimeMatch[1]
const translatedLineIndex = translatedLines.findIndex((line) => line.includes(originalTime))
if (translatedLineIndex !== -1) {
const translatedLine = translatedLines[translatedLineIndex]
const translatedTimeMatch = translatedLine.match(/\[(\d{2}:\d{2}\.\d{2,3})\]/) // 匹配翻译歌词的时间字符串
if (translatedTimeMatch && translatedTimeMatch[1] === originalTime) {
const translatedText = translatedLine.slice(translatedTimeMatch[0].length).trim()
if (translatedText) {
mergedLine += ` (${translatedText})`
}
translatedLines.splice(translatedLineIndex, 1) // 从翻译歌词数组中移除已匹配的行
}
}
}
if (!mergedLine.match(/^\[(\d{2}:\d{2}\.\d{2,3})\]$/)) {
//不是空行
mergedLyrics += `${mergedLine}\n`
}
}
// 将剩余的非空翻译歌词单独放在一行
for (const translatedLine of translatedLines) {
const translatedText = translatedLine.trim()
if (translatedText) {
mergedLyrics += `${translatedText}\n`
}
}
return mergedLyrics.trim()
}
function GetPlayButton(song: SongsInfo) { function GetPlayButton(song: SongsInfo) {
switch (song.from) { switch (song.from) {
case SongFrom.FiveSing: { case SongFrom.FiveSing: {
@@ -411,12 +485,16 @@ onMounted(() => {
<template> <template>
<NCard embedded size="small"> <NCard embedded size="small">
<NInput placeholder="搜索歌曲" v-model:value="searchMusicKeyword" size="small" style="max-width: 150px" /> <NSpace>
<NInput placeholder="搜索歌曲" v-model:value="searchMusicKeyword" size="small" style="width: 150px" />
<NSelect placeholder="选择歌手" v-model:value="authorColumn.filterOptionValue" :options="authorsOptions" clearable filterable size="small" style="width: 150px" />
<NButton v-if="authorColumn.filterOptionValue" type="error" @click="authorColumn.filterOptionValue = null" size="small"> 清除歌手选择 </NButton>
</NSpace>
</NCard> </NCard>
<NDivider style="margin: 5px 0 5px 0"> {{ songsComputed.length }} </NDivider> <NDivider style="margin: 5px 0 5px 0"> {{ songsComputed.length }} </NDivider>
<Transition> <Transition>
<div v-if="aplayerMusic"> <div v-if="aplayerMusic" class="song-list">
<APlayer :music="aplayerMusic" autoplay /> <APlayer :music="aplayerMusic" autoplay :showLrc="aplayerMusic.lrc != null && aplayerMusic.lrc.length > 0"/>
<NDivider style="margin: 15px 0 15px 0" /> <NDivider style="margin: 15px 0 15px 0" />
</div> </div>
</Transition> </Transition>

View File

@@ -166,7 +166,7 @@ onMounted(() => {
<NIcon :component="Moon" /> <NIcon :component="Moon" />
</template> </template>
</NSwitch> </NSwitch>
<NButton size="small" style="right: 0px; position: relative" type="primary" @click="$router.push({ name: 'user-index', params: { id: accountInfo.id } })"> 回到主页 </NButton> <NButton size="small" style="right: 0px; position: relative" type="primary" @click="$router.push({ name: 'user-index', params: { id: accountInfo.name } })"> 回到主页 </NButton>
</NSpace> </NSpace>
</template> </template>
</NPageHeader> </NPageHeader>

View File

@@ -18,8 +18,11 @@ const message = useMessage()
const fansHistory = ref<{ time: number; count: number }[]>() const fansHistory = ref<{ time: number; count: number }[]>()
const guardHistory = ref<{ time: number; count: number }[]>() const guardHistory = ref<{ time: number; count: number }[]>()
const upstatHistory = ref<{ time: number; stats: { views: number; likes: number } }[]>()
const fansOption = ref() const fansOption = ref()
const guardsOption = ref() const guardsOption = ref()
const upstatViewOption = ref()
const upstatLikeOption = ref()
async function getFansHistory() { async function getFansHistory() {
await QueryGetAPI< await QueryGetAPI<
@@ -27,9 +30,7 @@ async function getFansHistory() {
time: number time: number
count: number count: number
}[] }[]
>(HISTORY_API_URL + 'fans', { >(HISTORY_API_URL + 'fans')
id: accountInfo.value?.id,
})
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
fansHistory.value = data.data fansHistory.value = data.data
@@ -48,9 +49,7 @@ async function getGuardsHistory() {
time: number time: number
count: number count: number
}[] }[]
>(HISTORY_API_URL + 'guards', { >(HISTORY_API_URL + 'guards')
id: accountInfo.value?.id,
})
.then((data) => { .then((data) => {
if (data.code == 200) { if (data.code == 200) {
guardHistory.value = data.data guardHistory.value = data.data
@@ -63,6 +62,28 @@ async function getGuardsHistory() {
message.error('加载失败') message.error('加载失败')
}) })
} }
async function getUpstatHistory() {
await QueryGetAPI<
{
time: number
stats: {
views: number
likes: number
}
}[]
>(HISTORY_API_URL + 'upstat')
.then((data) => {
if (data.code == 200) {
upstatHistory.value = data.data
} else {
message.error('加载失败: ' + data.message)
}
})
.catch((err) => {
console.error(err)
message.error('加载失败')
})
}
function isSameDay(time1: number, time2: number) { function isSameDay(time1: number, time2: number) {
const time1Date = new Date(time1) const time1Date = new Date(time1)
const time2Date = new Date(time2) const time2Date = new Date(time2)
@@ -100,6 +121,25 @@ function getOptions() {
lastDayGuards = g.count lastDayGuards = g.count
} }
}) })
let upstatViewIncreace: { time: number; value: number }[] = []
let upstatLikeIncreace: { time: number; value: number }[] = []
if (upstatHistory.value && upstatHistory.value.length > 0) {
let lastUpstatView = upstatHistory.value[0].stats.views
let lastUpstatLike = upstatHistory.value[0].stats.likes
upstatHistory.value?.forEach((u) => {
upstatViewIncreace.push({
time: u.time,
value: u.stats.views - lastUpstatView,
})
lastUpstatView = u.stats.views
upstatLikeIncreace.push({
time: u.time,
value: u.stats.likes - lastUpstatLike,
})
lastUpstatLike = u.stats.likes
})
}
fansOption.value = { fansOption.value = {
title: { title: {
@@ -152,8 +192,6 @@ function getOptions() {
{ {
name: '粉丝数', name: '粉丝数',
type: 'line', type: 'line',
xAxisIndex: 1,
yAxisIndex: 1,
emphasis: { emphasis: {
focus: 'series', focus: 'series',
}, },
@@ -162,6 +200,7 @@ function getOptions() {
{ {
name: '增量 /日', name: '增量 /日',
type: 'line', type: 'line',
yAxisIndex: 1,
smooth: true, smooth: true,
showSymbol: false, showSymbol: false,
emphasis: { emphasis: {
@@ -193,9 +232,6 @@ function getOptions() {
{ {
type: 'value', type: 'value',
}, },
{
type: 'value',
},
], ],
xAxis: [ xAxis: [
{ {
@@ -234,11 +270,139 @@ function getOptions() {
}, },
], ],
} }
upstatViewOption.value = {
title: {
text: '投稿播放数',
left: 'left',
},
tooltip: {
trigger: 'axis',
},
legend: {},
yAxis: [
{
type: 'value',
},
{
type: 'value',
},
],
xAxis: [
{
type: 'category',
axisTick: {
alignWithLabel: true,
},
axisLine: {
onZero: false,
lineStyle: {
color: '#EE6666',
},
},
// prettier-ignore
data: upstatHistory.value?.map((f) => format(f.time, 'yyyy-MM-dd')),
},
],
series: [
{
name: '播放数',
type: 'line',
emphasis: {
focus: 'series',
},
data: upstatHistory.value?.map((f) => f.stats.views),
},
{
name: '日增',
type: 'line',
step: 'start',
yAxisIndex: 1,
emphasis: {
focus: 'series',
},
data: upstatViewIncreace.map((f) => f.value),
},
],
dataZoom: [
{
show: true,
realtime: true,
start: 0,
end: 100,
xAxisIndex: [0, 1],
},
],
}
upstatLikeOption.value = {
title: {
text: '投稿点赞数',
left: 'left',
},
tooltip: {
trigger: 'axis',
},
legend: {},
yAxis: [
{
type: 'value',
},
{
type: 'value',
},
],
xAxis: [
{
type: 'category',
axisTick: {
alignWithLabel: true,
},
axisLine: {
onZero: false,
lineStyle: {
color: '#EE6666',
},
},
// prettier-ignore
data: upstatHistory.value?.map((f) => format(f.time, 'yyyy-MM-dd')),
},
],
series: [
{
name: '点赞数',
type: 'line',
emphasis: {
focus: 'series',
},
data: upstatHistory.value?.map((f) => f.stats.likes),
},
{
name: '日增',
type: 'line',
yAxisIndex: 1,
step: 'start',
emphasis: {
focus: 'series',
},
data: upstatLikeIncreace.map((f) => f.value),
},
],
dataZoom: [
{
show: true,
realtime: true,
start: 0,
end: 100,
xAxisIndex: [0, 1],
},
],
}
} }
onMounted(async () => { onMounted(async () => {
await getFansHistory() await getFansHistory()
await getGuardsHistory() await getGuardsHistory()
await getUpstatHistory()
getOptions() getOptions()
}) })
</script> </script>
@@ -247,5 +411,7 @@ onMounted(async () => {
<NCard size="small"> <NCard size="small">
<VChart :option="fansOption" style="height: 200px" /> <VChart :option="fansOption" style="height: 200px" />
<VChart :option="guardsOption" style="height: 200px" /> <VChart :option="guardsOption" style="height: 200px" />
<VChart :option="upstatViewOption" style="height: 200px" />
<VChart :option="upstatLikeOption" style="height: 200px" />
</NCard> </NCard>
</template> </template>

View File

@@ -284,7 +284,6 @@ function reset() {
switch (currentType.value) { switch (currentType.value) {
case 'comment': { case 'comment': {
commentUsers.value = JSON.parse(JSON.stringify(originCommentUsers.value)) commentUsers.value = JSON.parse(JSON.stringify(originCommentUsers.value))
console.log(originCommentUsers.value)
break break
} }
case 'forward': { case 'forward': {