update: song page
This commit is contained in:
parent
4fe266ce82
commit
8b17f8177c
@ -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/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
}
|
@ -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);
|
||||||
|
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
10
packages/frontend/src/pages/404.astro
Normal file
10
packages/frontend/src/pages/404.astro
Normal 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>
|
@ -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>
|
|
88
packages/frontend/src/pages/song/[id]/info.astro
Normal file
88
packages/frontend/src/pages/song/[id]/info.astro
Normal 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>
|
@ -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/*"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user