merge: branch main into dev

This commit is contained in:
alikia2x 2024-07-25 02:52:45 +08:00
commit b4eab1b1ff
17 changed files with 222 additions and 83 deletions

View File

@ -4,11 +4,11 @@ AquaVox 是一个为中文虚拟歌手爱好者献上的产品。
> VOCALOID IS ALIVE.
这是一个**开源、本地优先、有B站良好支持、界面美观优雅**的**音乐播放器**。
这是一个 **开源、本地优先、有 B 站良好支持、界面美观优雅** **音乐播放器**
## 使用
当前 AquaVox 的公开预览版位于[aquavox.a2x.pub](https://aquavox.a2x.pub)上。
当前 AquaVox 的公开预览版位于 [aquavox.app](https://aquavox.app) 上。
[![wakatime](https://wakatime.com/badge/user/018f0628-909b-47e4-bcfd-0153235426d9/project/b67c03ef-ee0b-45f2-85ec-d9c60269cc55.svg)](https://wakatime.com/badge/user/018f0628-909b-47e4-bcfd-0153235426d9/project/b67c03ef-ee0b-45f2-85ec-d9c60269cc55)
@ -16,22 +16,22 @@ AquaVox 是一个为中文虚拟歌手爱好者献上的产品。
**开发者寒寒的话:**
AquaVox 这个播放器项目的灵感根本上来自于Apple Music以其优雅流畅的界面设计而著称。
AquaVox 这个播放器项目的灵感根本上来自于 Apple Music以其优雅流畅的界面设计而著称。
我的中V曲之前基本是在B站听的而中文虚拟歌手社区也基本上扎根于B站。但在B站听歌也有不少劣势——
首先自然是B站本身并不是一个音乐软件或播放器自然在相关的功能上并没有过多开发“听视频”的功能也仅在移动端可用
其次不少人听歌还是有看歌词的需求的而许多中V曲发布时附上的PV中的歌词为了美观歌词并不会以一个常规视频的字幕样式呈现而是采用了美术字等更为艺术化的表现形式这使得查看歌词并不方便更不用说在“听视频”模式下PV根本没有播放的情况。
我的中 V 曲之前基本是在 B 站听的,而中文虚拟歌手社区也基本上扎根于 B 站。但在 B 站听歌也有不少劣势——
首先自然是 B 站本身并不是一个音乐软件或播放器,自然在相关的功能上并没有过多开发,“听视频”的功能也仅在移动端可用;
其次,不少人听歌还是有看歌词的需求的,而许多中 V 曲发布时附上的 PV 中的歌词,为了美观,歌词并不会以一个常规视频的字幕样式呈现,而是采用了美术字等更为艺术化的表现形式,这使得查看歌词并不方便,更不用说在“听视频”模式下 PV 根本没有播放的情况。
而后来我选择了通过将B站的歌曲导入到Apple Music的方案而为什么选择AM呢尽管很多中V曲在网易云等平台上有更丰富的资源但我依然选择了AM。很大一个原因自然是我对AM播放器界面和交互设计及用户体验的喜爱但另一方面网易云的中V曲库其实也并不算全最终还要使用导入的方法才能将自己所有喜欢的歌囊括其中那么既然要导入何不直接将歌曲全部导入AM呢
而后来,我选择了通过将 B 站的歌曲导入到 Apple Music 的方案,而为什么选择 AM 呢?尽管很多中 V 曲在网易云等平台上有更丰富的资源,但我依然选择了 AM。很大一个原因自然是我对 AM 播放器界面和交互设计及用户体验的喜爱,但另一方面,网易云的中 V 曲库其实也并不算全,最终还要使用导入的方法才能将自己所有喜欢的歌囊括其中,那么既然要导入,何不直接将歌曲全部导入 AM 呢?
但很快我也发现了AM的一个致命问题自行导入的歌曲没有动态歌词的功能只能以一个静态的模式查看全部的歌词。而动态歌词的漂亮设计是我很大一部分喜欢Apple Music的原因但我自己导入的歌曲却无法享受这个功能不是很令人失望吗
但很快,我也发现了 AM 的一个致命问题:自行导入的歌曲没有动态歌词的功能,只能以一个静态的模式查看全部的歌词。而动态歌词的漂亮设计是我很大一部分喜欢 Apple Music 的原因,但我自己导入的歌曲却无法享受这个功能,不是很令人失望吗?
因此最后我还是最终决定自行开发一个播放器加上所有我喜欢的东西——Apple Music的页面设计和交互、从B站直接获取的曲库、通过网页、PWA和Electron使全平台有一致的体验。
因此最后我还是最终决定自行开发一个播放器加上所有我喜欢的东西——Apple Music 的页面设计和交互、从 B 站直接获取的曲库、通过网页、PWA Electron 使全平台有一致的体验。
## “赠品”
**开发者寒寒的话:**
在熟虑后,我决定让 AquaVox 不仅是一个播放器。更进一步我希望它是一个属于整个中文虚拟歌手社区的数据库。从音源、作者P主及staff、虚拟歌手、歌曲元信息、动态歌词在整个链路上成为一个中文虚拟歌手的终极“Archive”。
在熟虑后,我决定让 AquaVox 不仅是一个播放器。更进一步我希望它是一个属于整个中文虚拟歌手社区的数据库。从音源、作者P 主及 staff、虚拟歌手、歌曲元信息、动态歌词在整个链路上成为一个中文虚拟歌手的终极“Archive”。
因此,我们需要你的帮助。

View File

@ -43,5 +43,6 @@
"views": 502678,
"publishTime": "2024-04-18 12:00:00",
"updateTime": "2024-07-12 02:49:26",
"netEaseID": 2145230796,
"lyric": "[ti: 天山之外]\n[ar: 洛天依]\n[al: 游四方]\n[00:00.000]\n[00:20.890] 旷野是 自由的家\n[00:29.680] 顶冰花 雪里生芽\n[00:39.950] 光的延伸 让香弥漫夕阳下\n[00:49.310] 可妈妈 天山外是哪\n[00:59.130] 我是否如她挺拔 四季更迭洁白无暇\n[01:04.070] 你有绝世风华 芦苇悠悠 粼粼之下\n[01:08.910] 不必如四季伟大\n[01:11.260] 天山不曾 规定谁 像她 随心而去吧\n[01:17.020] 高空下\n[01:18.550] 自由它该乘骏马\n[01:20.880] 绿浪滚滚中散着发\n[01:23.360] 随风起舞落下\n[01:25.430] 也算生命别样优雅\n[01:28.110] 乘风随心而去吧\n[01:30.470] 向着无边的旷野 飞吧\n[01:35.890] 去浪迹天涯\n[01:57.190] 辽阔的 不止晚霞\n[02:06.430] 又盛开 天山梦话\n[02:16.430] 光的延伸 是你和我 放不下\n[02:25.930] 记忆里 天山下的家\n[02:35.280] 我是否如她挺拔 四季更迭洁白无暇\n[02:40.140] 你有绝世风华 芦苇悠悠粼粼之下\n[02:44.870] 不必如四季伟大\n[02:47.290] 天山不曾 规定谁像她随心而去吧\n[02:53.250] 高空下\n[02:54.520] 自由它该乘骏马\n[02:56.880] 绿浪滚滚中散着发\n[02:59.380] 随风起舞落下\n[03:01.720] 也算生命别样优雅\n[03:04.080] 乘风随心而去吧\n[03:06.500] 向着无边的旷野 飞吧\n[03:11.920] 去浪迹天涯\n[03:14.580]"
}

View File

@ -0,0 +1,42 @@
{
"id": "BV1Au4y1a7cN",
"name": "姑苏画舫录",
"url": "https://www.bilibili.com/video/BV1Au4y1a7cN",
"singer": [
"洛天依", "乐正绫"
],
"producer": "青天纤云-TsingClouds",
"tuning": [
"鬼面P"
],
"lyricist": [
"青天纤云·纳兰清婧"
],
"composer": [
"砖厂浪人"
],
"arranger": [
"砖厂浪人"
],
"mixing": [
"莫逆iMoNe"
],
"pv": [
"結月林", "墨雨清泉"
],
"illustrator": [
"小夏Elin", "绫也舒芙蕾", "景育"
],
"harmony": [
"Vsinger ACE全员"
],
"instruments": [],
"songURL": ["https://assets.aquavox.app/public/BV1Au4y1a7cN.flac"],
"coverURL": ["https://assets.aquavox.app/public/BV1Au4y1a7cN.jpg"],
"duration": 320.00,
"views": 100002,
"publishTime": "2023-11-04 18:00:00",
"updateTime": "2024-07-16 15:14:30",
"netEaseID": 2094903334,
"lyric": ""
}

File diff suppressed because one or more lines are too long

View File

@ -85,5 +85,6 @@
"views": 2733456,
"publishTime": "2024-02-09 20:31:23",
"updateTime": "2024-07-12 00:23:22",
"netEaseID": 2124462309,
"lyric": "[ti: 大哉乾元]\n[ar: 洛天依]\n[al: 2024哔哩哔哩拜年纪]\n[tool: 歌词滚动姬 https://lrc-maker.github.io]\n[length: 04:19.536]\n[00:05.630] 经起幽明 悟处通玄\n[00:09.680] 首窥龙堑 见岳见渊\n[00:13.640] 道不善宣 义不善绻\n[00:17.270] 源流万世 大哉乾元!\n[00:21.837]\n[00:36.390] 不曾闻日月争辉\n[00:38.170] 坎离复往 立下恒规\n[00:40.330] 照东南 有坤徇乾 承西北\n[00:43.680] 天道自昆仑巍巍\n[00:45.590] 翻起华夏巽震艮兑\n[00:47.810] 万象予万灵得见 两相盈岁\n[00:51.210] 潜龙长生应紫微\n[00:53.000] 惟向四方五气寻遂\n[00:55.200] 燧火旁八卦百草揆经纬\n[00:58.220] 正位 纪天下一归\n[01:00.370] 不消祈天退水\n[01:02.590] 初难知一念一决生龙髓\n[01:05.930] 百家注龙慧 千军起龙威 砥淬\n[01:10.260] 妙笔生文穗 罡风抚长麾\n[01:13.030] 始见龙形汇 以天田冲腾直向九陲\n[01:20.490] 龙震于疆 万里宁壤 天地皆可往\n[01:24.140] 龙秀于象 引仙来访 诗蜀道河江\n[01:27.940] 龙明于章 执笔成鉴 映五千煌煌\n[01:31.820] 不独九州五岳 帝王将相见苍茫\n[01:35.340] 龙泽于汤 唤水筑乡 单舟见京杭\n[01:39.040] 龙健于常 百音同讲 道一种炎黄\n[01:42.740] 龙景于康 见之庙堂 亦显于曲坊\n[01:46.580] 不劳此间祥云瑞兽频频诰春长!\n[01:57.280] 干支移晷又几回\n[01:59.500] 揽尽天骄襄助一醉\n[02:01.790] 虽万言竟道不尽无字碑\n[02:04.680] 临渊乾乾 君子催\n[02:06.600] 或跃 无咎相随\n[02:09.130] 同为龙 却与往昔不连讳\n[02:12.390] 且待飞龙归 簸却沧溟水 如沸\n[02:16.700] 有龙掸风雷 见首不见尾\n[02:19.540] 苏苏万物蜕 证元亨利贞变易轮回\n[02:27.080] 龙华于旸 红旗漫卷 新水濯旧隍\n[02:30.740] 龙泰于霜 烽烟消长 更赳赳昂昂\n[02:34.430] 龙温于壮 留待潺潺 驰涌成泱泱\n[02:38.260] 好教流光紫极 鹊渡银潢伴流觞\n[02:41.660] 龙韧于刚 龙吟激荡 云止聆佳响\n[02:45.490] 龙德于昌 喜见船马 纵横间丰仓\n[02:49.150] 龙眷于邦 情习众广 仍化为一方\n[02:52.930] 其妙错综复杂 不孤兵车付一匡!\n[02:56.602]\n[03:40.980] 此去向东 瀚海游龙 滔滔几万重\n[03:44.620] 一跃破空 乘风逐虹 猎猎青云中\n[03:48.250] 天音入梦 扶摇上穹 矫矫游星宫\n[03:52.080] 犹念神州谷稻耕耘收藏守时无?\n[03:55.670] 一息一动 似异似同 无之以为用\n[03:59.240] 天地辰龙 龙生九种 但两爻合共\n[04:03.130] 假逢童蒙 欲解懵懂 何处有真龙\n[04:06.830] 只道「大哉乾元」秩秩幽幽必然中\n[04:10.350] 也道「大哉乾元」切切实实一言中!\n[04:14.693]"
}

View File

@ -37,5 +37,6 @@
"views": 3021,
"publishTime": "2024-07-12 00:00:00",
"updateTime": "2024-07-12 01:49:30",
"netEaseID": 2606318125,
"lyric": "[ti: 唱给锦依卫]\n[ar: 洛天依]\n[tool: 歌词滚动姬 https://lrc-maker.github.io]\n[00:00.000]\n[00:11.400] 推开窗,又是一缕曙光轻拂你脸庞\n[00:17.100] 风轻扬,浮现初见时你青涩模样\n[00:22.830] 齐开唱,歌声驱散你眼神中的迷茫\n[00:28.770] 飘过秋叶与春花,流转几轮冬与夏\n[00:34.410] 乘着馨风去远航,有你长夜也明亮\n[00:40.230] 携手向梦的彼方,追寻心中那道光\n[00:46.800] 一起唱下去吧\n[00:50.880] 用你我梦想\n[00:53.610] 希望光芒为你点亮\n[00:58.230] 续写未来篇章\n[01:02.400] 纵岁月漫长\n[01:05.220] 会永远传唱\n[01:09.797]\n[01:32.370] 指尖淌,自由弦音随着风儿去流浪\n[01:38.040] 回头望,路上不时也会跌跌撞撞\n[01:44.010] 别彷徨,遗忘那过去的灰心和失望\n[01:49.740] 有你相伴的过往,每篇故事都珍藏\n[01:55.470] 载着梦想去远航,你我并肩去闯荡\n[02:01.260] 执笔最美的诗行,烟火为你而绽放\n[02:07.830] 一起唱下去吧\n[02:11.640] 星澜里荡漾\n[02:14.700] 留下最绚烂的光芒\n[02:19.350] 唱你我的梦想\n[02:23.370] 在舞台中央\n[02:26.190] 交织中回响\n[02:31.020] 一起唱下去吧\n[02:34.980] 用你我梦想\n[02:37.800] 将希望光芒再点亮\n[02:42.450] 续写未来篇章\n[02:46.500] 纵岁月沧桑\n[02:49.410] 永远在你身旁\n[02:53.364]"
}

View File

@ -0,0 +1,62 @@
<script lang="ts">
import formatDuration from "$lib/formatDuration";
import { formatViews } from "$lib/formatViews";
export let songData: MusicMetadata;
</script>
<div>
<div
class="relative w-56 h-56 bg-zinc-300 dark:bg-zinc-600 rounded-lg overflow-hidden
shadow-lg cursor-pointer justify-self-center"
>
<div
class="absolute top-0 left-0 w-full h-full duration-100
z-10 opacity-0 hover:opacity-100 bg-[rgba(0,0,0,0.15)]"
>
<a href={songData.url} class="absolute z-10 h-full w-full">
</a>
<a
class="brightness-125 absolute top-2 right-2 w-8 h-8 rounded-full
bg-[rgba(49,49,49,0.7)] backdrop-blur-lg z-30 hover:bg-red-500"
href={`/database/edit/${songData.id}`}
>
<img class="relative w-4 h-4 top-2 left-2 scale-90" src="/edit.svg" alt="编辑" />
</a>
</div>
<img src={songData.coverURL[0]} class="w-56 h-56" alt="" />
<div
class="absolute bottom-0 w-full h-28 backdrop-blur-xl"
style="mask-image: linear-gradient(to top, black 50%, transparent);"
>
<div class="absolute bottom-0 w-full h-16 pl-2">
<span
class="font-semibold text-2xl text-white"
style="text-shadow: 0px 0px 4px rgba(65, 65, 65, .6);">{songData.name}</span
>
<br />
<span
class="relative inline-block whitespace-nowrap text-white w-28
overflow-hidden text-ellipsis"
style="text-shadow: 0px 0px 4px rgba(65, 65, 65, .6);"
>
{songData.producer}
</span>
<div
class="absolute right-2 bottom-2 text-right text-white"
style="text-shadow: 0px 0px 4px rgba(65, 65, 65, .6);"
>
{#if songData.duration}
<span>{formatDuration(songData.duration)}</span>
{/if}
<br />
{#if songData.views}
<span>{formatViews(songData.views)}播放</span>
{/if}
</div>
</div>
</div>
</div>
</div>

View File

@ -11,6 +11,7 @@
export let originalLyrics: LrcJsonData;
export let progress: number;
// Local state and variables
let getLyricIndex: Function;
let debugMode = false;
if (localStorage.getItem('debugMode') == null) {
@ -31,11 +32,13 @@
let currentLyricTopMargin = 288;
// References to lyric elements
let refs: HTMLParagraphElement[] = [];
let _refs: any[] = [];
$: refs = _refs.filter(Boolean);
$: getLyricIndex = createLyricsSearcher(originalLyrics);
// Helper function to get CSS class for a lyric based on its index and progress
function getClass(lyricIndex: number, progress: number) {
if (!originalLyrics.scripts) return 'previous-lyric';
if (currentLyricIndex === lyricIndex) return 'current-lyric';
@ -76,8 +79,8 @@
for (let i = processingLineIndex; i < refs.length; i++) {
refs[i].style.transition =
'transform 0s, filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
const h = refs[i].getBoundingClientRect().height;
refs[i].style.transform = `translateY(${-h}px)`;
const height = refs[i].getBoundingClientRect().height;
refs[i].style.transform = `translateY(${-height}px)`;
}
}
@ -101,7 +104,8 @@
scriptScrolling = false;
}
async function b(currentLyric: HTMLParagraphElement) {
// Scroll the lyrics container to the given lyric
async function scrollToLyric(currentLyric: HTMLParagraphElement) {
if (!originalLyrics || !originalLyrics.scripts || !lyricsContainer) return;
scriptScrolling = true;
lyricsContainer.scrollTop += currentLyric.getBoundingClientRect().top - currentLyricTopMargin;
@ -133,18 +137,18 @@
}
});
// progressBarRaw is used to detect progress changes at system-level (not in AquaVox)
// Handle progress changes at system level
progressBarRaw.subscribe((progress: number) => {
if ($userAdjustingProgress === false && getLyricIndex) {
if (Math.abs(localProgress - progress) > 0.6) {
const currentLyric = refs[getLyricIndex(progress)];
b(currentLyric);
scrollToLyric(currentLyric);
}
localProgress = progress;
}
});
// progressBarSlideValue is to detect progress bar sliding event
// Handle progress bar sliding events
progressBarSlideValue.subscribe((_) => {
if ($userAdjustingProgress === false && getLyricIndex) {
lastAdjustProgress = currentPositionIndex;
@ -325,7 +329,7 @@
}
.no-scrollbar {
scrollbar-width: 10px;
scrollbar-width: none;
}
.no-scrollbar::-webkit-scrollbar {

5
src/lib/getVersion.ts Normal file
View File

@ -0,0 +1,5 @@
import * as pjson from "../../package.json";
export default function getVersion(){
return pjson.version;
}

View File

@ -19,5 +19,6 @@ interface MusicMetadata {
views: number | null;
publishTime: string | null;
updateTime: string | null;
netEaseID: number | null;
lyric: string | null;
}

View File

@ -1,5 +1,6 @@
<script lang="ts">
import extractFileName from '$lib/extractFileName';
import getVersion from '$lib/getVersion';
import toHumanSize from '$lib/humanSize';
import localforage from '$lib/storage';
interface Song {
@ -77,7 +78,7 @@
</ul>
</div>
<p>
AquaVox 1.10.1 · 早期公开预览 · 源代码参见
AquaVox {getVersion()} · 早期公开预览 · 源代码参见
<a href="https://github.com/alikia2x/aquavox">GitHub</a>
</p>
<a href="/import">导入音乐</a> <br />

View File

@ -0,0 +1,17 @@
import { songData } from '$lib/server/cache.js';
import { loadData } from '$lib/server/database/loadData.js';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = () => {
loadData();
const songIDList = songData.keys().slice(0, 20);
const songDataList = [];
for (const songID of songIDList) {
songDataList.push(songData.get(songID)!);
}
return {
songDataList: songDataList
};
};
export const ssr = true;

View File

@ -1,20 +1,8 @@
<script lang="ts">
import formatDuration from '$lib/formatDuration';
import { formatViews } from '$lib/formatViews';
import { onMount } from 'svelte';
let songList: MusicMetadata[] = [];
onMount(async () => {
fetch('/api/database/songs')
.then((response) => response.json())
.then((data) => {
songList = data;
})
.catch((error) => {
console.log(error);
return [];
});
});
import SongCard from '$lib/components/database/songCard.svelte';
import type { PageServerData } from './$types';
export let data: PageServerData;
let songList: MusicMetadata[] = data.songDataList;
</script>
<svelte:head>
@ -33,57 +21,14 @@
>
</div>
<div class="grid mb-32" style="grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
<div
class="relative grid mb-32"
style="grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
justify-content: space-between;
gap: 2rem 1rem;">
gap: 2rem 1rem;"
>
{#each songList as song}
<a
class="relative w-56 h-56 bg-zinc-300 dark:bg-zinc-600 rounded-lg overflow-hidden
shadow-lg cursor-pointer justify-self-center"
href={song.url}
>
<div
class="absolute top-0 left-0 w-full h-full duration-100
z-10 opacity-0 hover:opacity-100 bg-[rgba(0,0,0,0.15)]"
>
<a
class="brightness-125 absolute top-2 right-2 w-8 h-8 rounded-full
bg-[rgba(49,49,49,0.7)] backdrop-blur-lg z-10 hover:bg-red-500"
href={`/database/edit/${song.id}`}
>
<img class="relative w-4 h-4 top-2 left-2 scale-90" src="/edit.svg" alt="编辑" />
</a>
</div>
<img src={song.coverURL[0]} class="w-56 h-56" alt="" />
<div
class="absolute bottom-0 w-full h-28 backdrop-blur-xl"
style="mask-image: linear-gradient(to top, black 50%, transparent);"
>
<div class="absolute bottom-0 w-full h-16 pl-2">
<span
class="font-semibold text-2xl text-white"
style="text-shadow: 0px 0px 4px rgba(65, 65, 65, .6);">{song.name}</span
>
<br />
<span
class="relative inline-block whitespace-nowrap text-white w-40
overflow-hidden text-ellipsis"
style="text-shadow: 0px 0px 4px rgba(65, 65, 65, .6);"
>
{song.singer.join(', ')}
</span>
<div class="absolute right-2 bottom-2 text-right">
{#if song.duration}
<span style="text-shadow: 0px 0px 4px rgba(65, 65, 65, .6);">{formatDuration(song.duration)}</span>
{/if}
<br />
{#if song.views}
<span style="text-shadow: 0px 0px 4px rgba(65, 65, 65, .6);">{formatViews(song.views)}播放</span>
{/if}
</div>
</div>
</div>
</a>
<SongCard songData={song}/>
{/each}
</div>
</div>

View File

@ -0,0 +1,18 @@
import { songData } from '$lib/server/cache.js';
import { loadData } from '$lib/server/database/loadData.js';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = ({ params }) => {
const offset = (parseInt(params.id) - 1) * 20;
loadData();
const songIDList = songData.keys().slice(offset, offset + 20);
const songDataList = [];
for (const songID of songIDList) {
songDataList.push(songData.get(songID)!);
}
return {
songDataList: songDataList
};
};
export const ssr = true;

View File

@ -0,0 +1,34 @@
<script lang="ts">
import SongCard from '$lib/components/database/songCard.svelte';
import type { PageServerData } from './$types';
export let data: PageServerData;
let songList: MusicMetadata[] = data.songDataList;
</script>
<svelte:head>
<title>AquaVox 音乐数据库</title>
</svelte:head>
<h1 class="text-3xl text-red-500"><a href="/">AquaVox</a></h1>
<div>
<div class="flex justify-between items-center h-20 mb-8">
<h1>AquaVox 音乐数据库</h1>
<a
href="/database/submit"
class="h-12 w-24 border-black dark:border-white border-2 flex items-center justify-center rounded-lg"
>提交新曲</a
>
</div>
<div
class="relative grid mb-32"
style="grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
justify-content: space-between;
gap: 2rem 1rem;"
>
{#each songList as song}
<SongCard songData={song}/>
{/each}
</div>
</div>

View File

@ -22,6 +22,7 @@
views: null,
publishTime: null,
updateTime: getCurrentFormattedDateTime(),
netEaseID: null,
lyric: null
};
let editingData: string = JSON.stringify(templateSongData, null, 8);

View File

@ -24,5 +24,10 @@ export default defineConfig({
rollupOptions: {
plugins: [rollupNodePolyFill()]
}
},
server: {
fs: {
allow: ["./package.json"]
}
}
});