137 lines
5.3 KiB
TypeScript
137 lines
5.3 KiB
TypeScript
import { prisma } from "@/lib/prisma";
|
|
import BackButton from "@/app/components/BackButton";
|
|
import AwemeDetailClient from "./Client";
|
|
import type { Metadata } from "next";
|
|
import { getFileUrl } from "@/lib/minio";
|
|
import { AwemeData, VideoTranscript } from "./types";
|
|
|
|
export async function generateMetadata({ params }: { params: Promise<{ awemeId: string }> }): Promise<Metadata> {
|
|
const id = (await params).awemeId;
|
|
|
|
const [video, post] = await Promise.all([
|
|
prisma.video.findUnique({
|
|
where: { aweme_id: id },
|
|
select: { desc: true, author: { select: { nickname: true } } },
|
|
}),
|
|
prisma.imagePost.findUnique({
|
|
where: { aweme_id: id },
|
|
select: { desc: true, author: { select: { nickname: true } } },
|
|
})
|
|
]);
|
|
|
|
const data = video || post;
|
|
if (!data) {
|
|
return {
|
|
title: "作品不存在",
|
|
};
|
|
}
|
|
|
|
const desc = data.desc || "查看作品详情";
|
|
const author = data.author.nickname;
|
|
const title = desc.length > 50 ? `${desc.slice(0, 50)}... - ${author}` : `${desc} - ${author}`;
|
|
|
|
return {
|
|
title,
|
|
description: desc,
|
|
};
|
|
}
|
|
|
|
export default async function AwemeDetail({ params }: { params: Promise<{ awemeId: string }> }) {
|
|
const id = (await params).awemeId;
|
|
|
|
const [video, post] = await Promise.all([
|
|
prisma.video.findUnique({
|
|
where: { aweme_id: id },
|
|
include: { author: true },
|
|
}),
|
|
prisma.imagePost.findUnique({
|
|
where: { aweme_id: id },
|
|
include: { author: true, images: { orderBy: { order: "asc" } } },
|
|
})
|
|
]);
|
|
|
|
if (!video && !post) return <main className="p-8">找不到该作品</main>;
|
|
|
|
const isVideo = !!video;
|
|
|
|
// 获取评论总数
|
|
const commentsCount = await prisma.comment.count({
|
|
where: isVideo ? { videoId: id } : { imagePostId: id },
|
|
});
|
|
|
|
const aweme = isVideo ? video : post;
|
|
|
|
const data: AwemeData = {
|
|
aweme_id: aweme!.aweme_id,
|
|
desc: aweme!.desc,
|
|
created_at: aweme!.created_at,
|
|
likesCount: Number(aweme!.digg_count),
|
|
commentsCount,
|
|
author: { nickname: aweme!.author.nickname, avatar_url: getFileUrl(aweme!.author.avatar_url || 'default-avatar.png') },
|
|
...(() => {
|
|
if (isVideo) {
|
|
const aweme = video!
|
|
return {
|
|
type: "video" as const,
|
|
duration_ms: aweme!.duration_ms,
|
|
video_url: getFileUrl(aweme!.video_url),
|
|
width: aweme!.width ?? null,
|
|
height: aweme!.height ?? null,
|
|
}
|
|
} else {
|
|
const aweme = post!
|
|
return {
|
|
type: "image" as const,
|
|
images: aweme!.images.map(img => ({ ...img, url: getFileUrl(img.url), animated: getFileUrl(img.animated ?? 'default-animated.png'), })),
|
|
music_url: getFileUrl(aweme!.music_url || 'default-music.mp3'),
|
|
};
|
|
}
|
|
})()
|
|
}
|
|
|
|
const transcript: VideoTranscript | null = isVideo ? await prisma.videoTranscript.findUnique({
|
|
where: { videoId: id },
|
|
}) : null;
|
|
|
|
// Compute prev/next neighbors by created_at across videos and image posts
|
|
const currentCreatedAt = (isVideo ? video!.created_at : post!.created_at);
|
|
const [newerVideo, newerPost, olderVideo, olderPost] = await Promise.all([
|
|
prisma.video.findFirst({ where: { created_at: { gt: currentCreatedAt } }, orderBy: { created_at: "asc" }, select: { aweme_id: true, created_at: true } }),
|
|
prisma.imagePost.findFirst({ where: { created_at: { gt: currentCreatedAt } }, orderBy: { created_at: "asc" }, select: { aweme_id: true, created_at: true } }),
|
|
prisma.video.findFirst({ where: { created_at: { lt: currentCreatedAt } }, orderBy: { created_at: "desc" }, select: { aweme_id: true, created_at: true } }),
|
|
prisma.imagePost.findFirst({ where: { created_at: { lt: currentCreatedAt } }, orderBy: { created_at: "desc" }, select: { aweme_id: true, created_at: true } }),
|
|
]);
|
|
const pickPrev = (() => {
|
|
const cands: { aweme_id: string; created_at: Date }[] = [];
|
|
if (newerVideo) cands.push({ aweme_id: newerVideo.aweme_id, created_at: newerVideo.created_at });
|
|
if (newerPost) cands.push({ aweme_id: newerPost.aweme_id, created_at: newerPost.created_at });
|
|
if (cands.length === 0) return null;
|
|
cands.sort((a, b) => +a.created_at - +b.created_at);
|
|
return { aweme_id: cands[0].aweme_id };
|
|
})();
|
|
const pickNext = (() => {
|
|
const cands: { aweme_id: string; created_at: Date }[] = [];
|
|
if (olderVideo) cands.push({ aweme_id: olderVideo.aweme_id, created_at: olderVideo.created_at });
|
|
if (olderPost) cands.push({ aweme_id: olderPost.aweme_id, created_at: olderPost.created_at });
|
|
if (cands.length === 0) return null;
|
|
cands.sort((a, b) => +b.created_at - +a.created_at);
|
|
return { aweme_id: cands[0].aweme_id };
|
|
})();
|
|
const neighbors: { prev: { aweme_id: string } | null; next: { aweme_id: string } | null } = { prev: pickPrev, next: pickNext };
|
|
|
|
return (
|
|
<main className="min-h-screen w-full overflow-hidden">
|
|
{/* 顶部条改为悬浮在媒体区域之上,避免 sticky 造成 Y 方向滚动条 */}
|
|
<div className="fixed left-3 top-3 z-30">
|
|
<BackButton
|
|
hrefFallback="/"
|
|
className="inline-flex items-center justify-center w-9 h-9 rounded-full bg-white/15 text-white border border-white/20 backdrop-blur hover:bg-white/25"
|
|
/>
|
|
</div>
|
|
<AwemeDetailClient data={data} neighbors={neighbors} transcript={transcript} />
|
|
</main>
|
|
);
|
|
}
|
|
|
|
export const dynamic = "force-dynamic";
|