add: video info page in frontend
This commit is contained in:
parent
c82a95d0bc
commit
980dd542ee
@ -1,17 +1,21 @@
|
|||||||
const requiredEnvVars = ["DB_HOST", "DB_NAME", "DB_USER", "DB_PASSWORD", "DB_PORT", "DB_NAME_CRED"];
|
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) {
|
if (unsetVars.length > 0) {
|
||||||
throw new Error(`Missing required environment variables: ${unsetVars.join(", ")}`);
|
throw new Error(`Missing required environment variables: ${unsetVars.join(", ")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const databaseHost = process.env["DB_HOST"]!;
|
const databaseHost = getEnvVar("DB_HOST")!;
|
||||||
const databaseName = process.env["DB_NAME"];
|
const databaseName = getEnvVar("DB_NAME");
|
||||||
const databaseNameCred = process.env["DB_NAME_CRED"]!;
|
const databaseNameCred = getEnvVar("DB_NAME_CRED")!;
|
||||||
const databaseUser = process.env["DB_USER"]!;
|
const databaseUser = getEnvVar("DB_USER")!;
|
||||||
const databasePassword = process.env["DB_PASSWORD"]!;
|
const databasePassword = getEnvVar("DB_PASSWORD")!;
|
||||||
const databasePort = process.env["DB_PORT"]!;
|
const databasePort = getEnvVar("DB_PORT")!;
|
||||||
|
|
||||||
export const postgresConfig = {
|
export const postgresConfig = {
|
||||||
hostname: databaseHost,
|
hostname: databaseHost,
|
||||||
|
@ -3,6 +3,6 @@ const { title, description } = Astro.props;
|
|||||||
---
|
---
|
||||||
|
|
||||||
<tr>
|
<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>
|
<td class="break-all max-w-[calc(100vw-4.5rem)] border dark:border-zinc-500 px-4 py-2">{description}</td>
|
||||||
</tr>
|
</tr>
|
@ -7,7 +7,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function changeFocusState(target: boolean) {
|
export function changeFocusState(target: boolean) {
|
||||||
if (!inputElement) return; // 使用 inputElement 而不是 inputBox
|
if (!inputElement) return;
|
||||||
if (target) {
|
if (target) {
|
||||||
inputElement.focus();
|
inputElement.focus();
|
||||||
} else {
|
} else {
|
||||||
@ -22,13 +22,13 @@
|
|||||||
function handleKeydown(event: KeyboardEvent) {
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const value = inputValue.trim(); // 使用绑定的变量
|
const value = inputValue.trim();
|
||||||
if (!value) return;
|
if (!value) return;
|
||||||
search(value);
|
search(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputElement: HTMLInputElement; // 引用 input 元素
|
let inputElement: HTMLInputElement;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
12
packages/frontend/src/components/VideoInfoPage/StatRow.astro
Normal file
12
packages/frontend/src/components/VideoInfoPage/StatRow.astro
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
const { title, description } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="flex justify-between w-36">
|
||||||
|
<span>
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{description}
|
||||||
|
</span>
|
||||||
|
</div>
|
@ -9,22 +9,6 @@ import { getAidFromBV } from "src/db/bilibili_metadata/getAidFromBV";
|
|||||||
import { getVideoMetadata } from "src/db/bilibili_metadata/getVideoMetadata";
|
import { getVideoMetadata } from "src/db/bilibili_metadata/getVideoMetadata";
|
||||||
import { aidExists as idExists } from "src/db/bilibili_metadata/aidExists";
|
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;
|
const { id } = Astro.params;
|
||||||
|
|
||||||
async function getVideoAid(id: string) {
|
async function getVideoAid(id: string) {
|
||||||
@ -52,18 +36,6 @@ if (!aidExists) {
|
|||||||
}
|
}
|
||||||
const videoInfo = await getVideoMetadata(aid);
|
const videoInfo = await getVideoMetadata(aid);
|
||||||
const snapshots = await getAllSnapshots(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>
|
<Layout>
|
||||||
@ -79,9 +51,9 @@ interface Snapshot {
|
|||||||
<div class="overflow-x-auto max-w-full px-2">
|
<div class="overflow-x-auto max-w-full px-2">
|
||||||
<table class="table-fixed">
|
<table class="table-fixed">
|
||||||
<tbody>
|
<tbody>
|
||||||
<MetadataRow title={id} description={videoInfo?.id} />
|
<MetadataRow title="ID" description={videoInfo?.id} />
|
||||||
<MetadataRow title={videoInfo?.aid} description={videoInfo?.aid} />
|
<MetadataRow title="av 号" description={videoInfo?.aid} />
|
||||||
<MetadataRow title={videoInfo?.bvid} description={videoInfo?.bvid} />
|
<MetadataRow title="BV 号" description={videoInfo?.bvid} />
|
||||||
<MetadataRow title="标题" description={videoInfo?.title} />
|
<MetadataRow title="标题" description={videoInfo?.title} />
|
||||||
<MetadataRow title="描述" description={videoInfo?.description} />
|
<MetadataRow title="描述" description={videoInfo?.description} />
|
||||||
<MetadataRow title="UID" description={videoInfo?.uid} />
|
<MetadataRow title="UID" description={videoInfo?.uid} />
|
||||||
@ -124,7 +96,7 @@ interface Snapshot {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{snapshots.map((snapshot: Snapshot) => (
|
{snapshots.map((snapshot) => (
|
||||||
<tr>
|
<tr>
|
||||||
<td class="border dark:border-zinc-500 px-4 py-2">
|
<td class="border dark:border-zinc-500 px-4 py-2">
|
||||||
{format(new Date(snapshot.created_at), "yyyy-MM-dd HH:mm:ss", {
|
{format(new Date(snapshot.created_at), "yyyy-MM-dd HH:mm:ss", {
|
||||||
|
68
packages/frontend/src/pages/video/[id]/info.astro
Normal file
68
packages/frontend/src/pages/video/[id]/info.astro
Normal 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>
|
@ -102,6 +102,8 @@
|
|||||||
--color-dark-surface-container-low: rgb(35 25 24);
|
--color-dark-surface-container-low: rgb(35 25 24);
|
||||||
--color-surface-container-highest: rgb(241 223 220);
|
--color-surface-container-highest: rgb(241 223 220);
|
||||||
--color-dark-surface-container-highest: rgb(61 50 48);
|
--color-dark-surface-container-highest: rgb(61 50 48);
|
||||||
|
|
||||||
|
--font-zh: "InterVariable", "MiSans VF", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
Loading…
Reference in New Issue
Block a user