add: video info page in frontend

This commit is contained in:
alikia2x (寒寒) 2025-05-11 03:57:26 +08:00
parent c82a95d0bc
commit 980dd542ee
Signed by: alikia2x
GPG Key ID: 56209E0CCD8420C6
7 changed files with 101 additions and 43 deletions

View File

@ -1,17 +1,21 @@
const requiredEnvVars = ["DB_HOST", "DB_NAME", "DB_USER", "DB_PASSWORD", "DB_PORT", "DB_NAME_CRED"];
const unsetVars = requiredEnvVars.filter((key) => process.env[key] === undefined);
const getEnvVar = (key: string) => {
return process.env[key] || import.meta.env[key];
}
const unsetVars = requiredEnvVars.filter((key) => getEnvVar(key) === undefined);
if (unsetVars.length > 0) {
throw new Error(`Missing required environment variables: ${unsetVars.join(", ")}`);
}
const databaseHost = process.env["DB_HOST"]!;
const databaseName = process.env["DB_NAME"];
const databaseNameCred = process.env["DB_NAME_CRED"]!;
const databaseUser = process.env["DB_USER"]!;
const databasePassword = process.env["DB_PASSWORD"]!;
const databasePort = process.env["DB_PORT"]!;
const databaseHost = getEnvVar("DB_HOST")!;
const databaseName = getEnvVar("DB_NAME");
const databaseNameCred = getEnvVar("DB_NAME_CRED")!;
const databaseUser = getEnvVar("DB_USER")!;
const databasePassword = getEnvVar("DB_PASSWORD")!;
const databasePort = getEnvVar("DB_PORT")!;
export const postgresConfig = {
hostname: databaseHost,

View File

@ -3,6 +3,6 @@ const { title, description } = Astro.props;
---
<tr>
<td class="max-w-14 min-w-14 md:max-w-none md:min-w-none border dark:border-zinc-500 px-2 md:px-4 py-2 font-semibold">{title}</td>
<td class="max-w-14 min-w-14 md:max-w-24 md:min-w-24 border dark:border-zinc-500 px-2 md:px-3 py-2 font-semibold">{title}</td>
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{description}</td>
</tr>

View File

@ -7,7 +7,7 @@
};
export function changeFocusState(target: boolean) {
if (!inputElement) return; // 使用 inputElement 而不是 inputBox
if (!inputElement) return;
if (target) {
inputElement.focus();
} else {
@ -22,13 +22,13 @@
function handleKeydown(event: KeyboardEvent) {
if (event.key === "Enter") {
event.preventDefault();
const value = inputValue.trim(); // 使用绑定的变量
const value = inputValue.trim();
if (!value) return;
search(value);
}
}
let inputElement: HTMLInputElement; // 引用 input 元素
let inputElement: HTMLInputElement;
</script>
<style>

View File

@ -0,0 +1,12 @@
---
const { title, description } = Astro.props;
---
<div class="flex justify-between w-36">
<span>
{title}
</span>
<span>
{description}
</span>
</div>

View File

@ -9,22 +9,6 @@ import { getAidFromBV } from "src/db/bilibili_metadata/getAidFromBV";
import { getVideoMetadata } from "src/db/bilibili_metadata/getVideoMetadata";
import { aidExists as idExists } from "src/db/bilibili_metadata/aidExists";
const databaseHost = import.meta.env.DB_HOST;
const databaseName = import.meta.env.DB_NAME;
const databaseUser = import.meta.env.DB_USER;
const databasePassword = import.meta.env.DB_PASSWORD;
const databasePort = import.meta.env.DB_PORT;
const postgresConfig = {
hostname: databaseHost,
port: parseInt(databasePort!),
database: databaseName,
user: databaseUser,
password: databasePassword,
};
console.log(postgresConfig);
const { id } = Astro.params;
async function getVideoAid(id: string) {
@ -52,18 +36,6 @@ if (!aidExists) {
}
const videoInfo = await getVideoMetadata(aid);
const snapshots = await getAllSnapshots(aid);
interface Snapshot {
created_at: Date;
views: number;
danmakus: number;
replies: number;
coins: number;
likes: number;
favorites: number;
shares: number;
id: number;
}
---
<Layout>
@ -79,9 +51,9 @@ interface Snapshot {
<div class="overflow-x-auto max-w-full px-2">
<table class="table-fixed">
<tbody>
<MetadataRow title={id} description={videoInfo?.id} />
<MetadataRow title={videoInfo?.aid} description={videoInfo?.aid} />
<MetadataRow title={videoInfo?.bvid} description={videoInfo?.bvid} />
<MetadataRow title="ID" description={videoInfo?.id} />
<MetadataRow title="av 号" description={videoInfo?.aid} />
<MetadataRow title="BV 号" description={videoInfo?.bvid} />
<MetadataRow title="标题" description={videoInfo?.title} />
<MetadataRow title="描述" description={videoInfo?.description} />
<MetadataRow title="UID" description={videoInfo?.uid} />
@ -124,7 +96,7 @@ interface Snapshot {
</tr>
</thead>
<tbody>
{snapshots.map((snapshot: Snapshot) => (
{snapshots.map((snapshot) => (
<tr>
<td class="border dark:border-zinc-500 px-4 py-2">
{format(new Date(snapshot.created_at), "yyyy-MM-dd HH:mm:ss", {

View File

@ -0,0 +1,68 @@
---
import Layout from "@layouts/Layout.astro";
import TitleBar from "@components/TitleBar.astro";
import MetadataRow from "@components/InfoPage/MetadataRow.astro";
import { format } from "date-fns";
import { zhCN } from "date-fns/locale";
import { getAllSnapshots } from "src/db/snapshots/getAllSnapshots";
import { getAidFromBV } from "src/db/bilibili_metadata/getAidFromBV";
import { getVideoMetadata } from "src/db/bilibili_metadata/getVideoMetadata";
import { aidExists as idExists } from "src/db/bilibili_metadata/aidExists";
import StatRow from "@components/VideoInfoPage/StatRow.astro";
const { id } = Astro.params;
if (!id) {
Astro.response.status = 404;
return new Response(null, { status: 404 });
}
const backendURL = import.meta.env.BACKEND_URL;
const res = await fetch(backendURL + `video/${id}/info`);
const data = await res.json();
---
<Layout title={`${data.title ?? data.bvid} - 视频信息`}>
<TitleBar />
<main class="flex flex-col items-center min-h-screen gap-8 mt-10 md:mt-6 relative z-0 overflow-x-auto pb-8">
<div class="w-full lg:max-w-4xl lg:mx-auto lg:p-6 px-4">
<h2 class="text-lg md:text-2xl mb-2">
<a href={`https://www.bilibili.com/video/${data.bvid}`}>{data.title}</a>
</h2>
<p
class="text-sm md:text-base font-normal text-on-surface-variant
dark:text-dark-on-surface-variant mb-4"
>
<span>{data.bvid} · av{data.aid}</span><br />
<span>
发布于
{format(new Date(data.pubdate * 1000), "yyyy-MM-dd HH:mm:ss")}
</span><br />
<span>播放:{(data.stat?.view ?? 0).toLocaleString()}</span> ·
<span>弹幕:{(data.stat?.danmaku ?? 0).toLocaleString()}</span>
<br/>
<span>分区: {data.tname}, tid{data.tid} · v2: {data.tname_v2}, tid{data.tid_v2}</span>
</p>
<img src={data.pic} referrerpolicy="no-referrer" class="rounded-lg" />
<h3 class="font-medium text-lg mt-6 mb-1">简介</h3>
<pre
class="max-w-full wrap-anywhere break-all text-on-surface-variant
text-sm md:text-base whitespace-pre-wrap dark:text-dark-on-surface-variant
font-zh">{data.desc}</pre>
<div class="mb-6 mt-4">
<h2 class="mb-2 text-xl font-medium">统计数据</h2>
<div class="flex flex-col gap-1">
<StatRow title="播放" description={data.stat?.view} />
<StatRow title="点赞" description={data.stat?.like} />
<StatRow title="收藏" description={data.stat?.favorite} />
<StatRow title="硬币" description={data.stat?.coin} />
<StatRow title="评论" description={data.stat?.reply} />
<StatRow title="弹幕" description={data.stat?.danmaku} />
<StatRow title="分享" description={data.stat?.share} />
</div>
</div>
</div>
</main>
</Layout>

View File

@ -102,6 +102,8 @@
--color-dark-surface-container-low: rgb(35 25 24);
--color-surface-container-highest: rgb(241 223 220);
--color-dark-surface-container-highest: rgb(61 50 48);
--font-zh: "InterVariable", "MiSans VF", sans-serif;
}
a {