update: song page

This commit is contained in:
alikia2x (寒寒) 2025-03-30 07:31:24 +08:00
parent 4fe266ce82
commit 8b17f8177c
Signed by: alikia2x
GPG Key ID: 56209E0CCD8420C6
10 changed files with 153 additions and 66 deletions

View File

@ -14,6 +14,7 @@
}, },
"imports": { "imports": {
"@astrojs/node": "npm:@astrojs/node@^9.1.3", "@astrojs/node": "npm:@astrojs/node@^9.1.3",
"@astrojs/svelte": "npm:@astrojs/svelte@^7.0.8" "@astrojs/svelte": "npm:@astrojs/svelte@^7.0.8",
"@core/db/": "./packages/core/db/"
} }
} }

View File

@ -8,6 +8,7 @@ if (unsetVars.length > 0) {
const databaseHost = Deno.env.get("DB_HOST")!; const databaseHost = Deno.env.get("DB_HOST")!;
const databaseName = Deno.env.get("DB_NAME")!; const databaseName = Deno.env.get("DB_NAME")!;
const databaseNameCred = Deno.env.get("DB_NAME_CRED")!;
const databaseUser = Deno.env.get("DB_USER")!; const databaseUser = Deno.env.get("DB_USER")!;
const databasePassword = Deno.env.get("DB_PASSWORD")!; const databasePassword = Deno.env.get("DB_PASSWORD")!;
const databasePort = Deno.env.get("DB_PORT")!; const databasePort = Deno.env.get("DB_PORT")!;
@ -19,3 +20,11 @@ export const postgresConfig = {
user: databaseUser, user: databaseUser,
password: databasePassword, password: databasePassword,
}; };
export const postgresConfigCred = {
hostname: databaseHost,
port: parseInt(databasePort),
database: databaseNameCred,
user: databaseUser,
password: databasePassword,
}

View File

@ -1,5 +1,5 @@
import { Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts"; import { Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
import { postgresConfig } from "db/pgConfig.ts"; import { postgresConfig } from "@core/db/pgConfig.ts";
const pool = new Pool(postgresConfig, 12); const pool = new Pool(postgresConfig, 12);

View File

@ -12,8 +12,12 @@
"@astrojs/tailwind": "^6.0.2", "@astrojs/tailwind": "^6.0.2",
"astro": "^5.5.5", "astro": "^5.5.5",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"pg": "^8.11.11",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"tailwindcss": "^3.0.24", "tailwindcss": "^3.0.24",
"vite-tsconfig-paths": "^5.1.4" "vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {
"@types/pg": "^8.11.11"
} }
} }

View File

@ -1,5 +1,14 @@
<script lang="ts"> <script lang="ts">
export let autoFocus = false; let inputBox: HTMLInputElement | null = null;
export function changeFocusState(target: boolean) {
if (!inputBox) return;
if (target) {
inputBox.focus();
} else {
inputBox.blur();
}
}
function handleKeydown(event: KeyboardEvent) { function handleKeydown(event: KeyboardEvent) {
if (event.key === "Enter") { if (event.key === "Enter") {
@ -8,7 +17,7 @@
const value: string = input.value.trim(); const value: string = input.value.trim();
if (!value) return; if (!value) return;
if (value.startsWith("av")) { if (value.startsWith("av")) {
window.location.href = `/song/${value.slice(2)}/info`; window.location.href = `/song/${value}/info`;
} }
} }
} }
@ -17,9 +26,9 @@
<!-- svelte-ignore a11y_autofocus --> <!-- svelte-ignore a11y_autofocus -->
<div <div
class="absolute left-0 md:left-96 ml-4 w-[calc(100%-5rem)] md:w-[calc(100%-40rem)] 2xl:max-w-[50rem] 2xl:left-1/2 2xl:-translate-x-1/2 inline-flex items-center h-full" class="absolute left-0 md:left-96 ml-4 w-[calc(100%-5rem)] md:w-[calc(100%-40rem)] 2xl:max-w-[50rem] 2xl:left-1/2 2xl:-translate-x-1/2 inline-flex items-center h-full"
autofocus={autoFocus}
> >
<input <input
bind:this={inputBox}
type="search" type="search"
placeholder="搜索" placeholder="搜索"
class="top-0 w-full h-10 px-4 rounded-lg bg-white/80 dark:bg-zinc-800/70 class="top-0 w-full h-10 px-4 rounded-lg bg-white/80 dark:bg-zinc-800/70

View File

@ -7,14 +7,19 @@
import DarkModeImage from "./DarkModeImage.svelte"; import DarkModeImage from "./DarkModeImage.svelte";
import CloseIcon from "./CloseIcon.svelte"; import CloseIcon from "./CloseIcon.svelte";
let searchBox: SearchBox | null = null;
let showSearchBox = false; let showSearchBox = false;
$: if (showSearchBox && searchBox) {
searchBox.changeFocusState(true);
}
</script> </script>
<div class="md:hidden fixed top-0 left-0 w-full h-16 bg-white/80 dark:bg-zinc-800/70 backdrop-blur-lg z-100"> <div class="md:hidden fixed top-0 left-0 w-full h-16 bg-white/80 dark:bg-zinc-800/70 backdrop-blur-lg z-100">
{#if !showSearchBox} {#if !showSearchBox}
<div class="inline-block ml-4 mt-4 text-white"> <button class="inline-block ml-4 mt-4 text-white">
<MenuIcon /> <MenuIcon />
</div> </button>
<div class="ml-8 inline-flex h-full items-center"> <div class="ml-8 inline-flex h-full items-center">
<a href="/"> <a href="/">
<DarkModeImage <DarkModeImage
@ -27,7 +32,7 @@
</div> </div>
{/if} {/if}
{#if showSearchBox} {#if showSearchBox}
<SearchBox autoFocus={true} /> <SearchBox bind:this={searchBox} />
{/if} {/if}
<button <button
class="inline-flex absolute right-0 h-full items-center mr-4" class="inline-flex absolute right-0 h-full items-center mr-4"

View File

@ -0,0 +1,10 @@
---
import Layout from '@layouts/Layout.astro';
---
<Layout>
<main class="flex flex-col items-center justify-center min-h-screen gap-8">
<h1 class="text-9xl font-thin">404</h1>
<p class="text-xl font-medium">咦……页面去哪里了(゚Д゚≡゚д゚)!?</p>
</main>
</Layout>

View File

@ -1,40 +0,0 @@
---
import Layout from "@layouts/Layout.astro";
// 路由参数
const { aid } = Astro.params;
const videoAid = Number(aid);
// 数据库查询函数
async function getVideoMetadata(aid: number) {
// TODO: 实现bilibili_metadata表查询
return {};
}
async function getVideoSnapshots(aid: number) {
// TODO: 实现video_snapshot表查询按created_at排序限制100条
return [];
}
// 获取数据
const videoInfo = await getVideoMetadata(videoAid);
const snapshots = await getVideoSnapshots(videoAid);
---
<Layout>
<div class="max-w-4xl mx-auto rounded-lg shadow p-6">
<h1 class="text-2xl font-bold mb-4">视频信息: {videoAid}</h1>
<!-- 视频基本信息 -->
<div class="mb-6 p-4 rounded-lg">
<h2 class="text-xl font-semibold mb-2">基本信息</h2>
<pre class="bg-gray-50 dark:bg-zinc-700 p-2 rounded">{JSON.stringify(videoInfo, null, 2)}</pre>
</div>
<!-- 视频快照数据 -->
<div class="p-4 rounded-lg">
<h2 class="text-xl font-semibold mb-2">历史数据 (最新100条)</h2>
<pre class="bg-gray-50 dark:bg-zinc-700 p-2 rounded">{JSON.stringify(snapshots, null, 2)}</pre>
</div>
</div>
</Layout>

View File

@ -0,0 +1,88 @@
---
import Layout from "@layouts/Layout.astro";
import pg from 'pg'
import { postgresConfig } from "@core/db/pgConfig.ts";
// 路由参数
const { id } = Astro.params;
const { Client } = pg
const client = new Client(postgresConfig);
await client.connect();
// 数据库查询函数
async function getVideoMetadata(aid: number) {
const res = await client.query('SELECT * FROM bilibili_metadata WHERE aid = $1', [aid]);
if (res.rows.length <= 0) {
return null
}
const row = res.rows[0];
if (row) {
return row;
}
return {};
}
async function getVideoSnapshots(aid: number) {
// TODO: 实现video_snapshot表查询按created_at排序限制100条
const res = await client.query('SELECT * FROM video_snapshot WHERE aid = $1 ORDER BY created_at DESC LIMIT 100', [aid]);
if (res.rows.length <= 0) {
return null
}
return res.rows;
}
async function getAidFromBV(bv: string) {
const res = await client.query('SELECT aid FROM bilibili_metadata WHERE bvid = $1', [bv]);
if (res.rows.length <= 0) {
return null
}
const row = res.rows[0];
if (row && row.aid) {
return Number(row.aid);
}
return null;
}
async function getVideoAid(id: string) {
if (id.startsWith("av")) {
return parseInt(id.slice(2));
} else if (id.startsWith("BV")) {
return getAidFromBV(id);
}
return parseInt(id);
}
// 获取数据
if (!id) {
Astro.response.status = 404;
client.end();
return new Response(null, { status: 404 });
}
const aid = await getVideoAid(id);
if (!aid || isNaN(aid)) {
Astro.response.status = 404;
client.end();
return new Response(null, { status: 404 });
}
const videoInfo = await getVideoMetadata(aid);
const snapshots = await getVideoSnapshots(aid);
client.end();
---
<Layout>
<div class="max-w-4xl mx-auto rounded-lg shadow p-6">
<h1 class="text-2xl font-bold mb-4">视频信息: {aid}</h1>
<!-- 视频基本信息 -->
<div class="mb-6 p-4 rounded-lg">
<h2 class="text-xl font-semibold mb-2">基本信息</h2>
<pre class="bg-gray-50 dark:bg-zinc-700 p-2 rounded text-wrap">{JSON.stringify(videoInfo, null, 2)}</pre>
</div>
<!-- 视频快照数据 -->
<div class="p-4 rounded-lg">
<h2 class="text-xl font-semibold mb-2">快照历史数据 (最新100条)</h2>
<pre class="bg-gray-50 dark:bg-zinc-700 p-2 rounded">{JSON.stringify(snapshots, null, 2)}</pre>
</div>
</div>
</Layout>

View File

@ -9,7 +9,8 @@
"@layouts/*": ["src/layouts/*"], "@layouts/*": ["src/layouts/*"],
"@utils/*": ["src/utils/*"], "@utils/*": ["src/utils/*"],
"@assets/*": ["src/assets/*"], "@assets/*": ["src/assets/*"],
"@styles": ["src/styles/*"] "@styles": ["src/styles/*"],
"@core/*": ["../core/*"]
} }
} }
} }