From 76eefdc151093a0b0be68481597140011e3e2383 Mon Sep 17 00:00:00 2001 From: mzaxd Date: Sun, 1 Feb 2026 16:53:09 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20Next.js=2015=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=80=A7=E6=94=B9=E8=BF=9B=E5=92=8C=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E6=A0=87=E5=87=86=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/schema.prisma | 65 +++++++++++-------- src/app/[locale]/keyword-cloud/page.tsx | 63 ++++++++++-------- .../keyword-cloud/keywords/[quarter]/route.ts | 19 +++--- 3 files changed, 81 insertions(+), 66 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 38e6549..c5d3864 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,22 +8,23 @@ datasource db { url = env("DATABASE_URL") } -model external_links { - id String @id +model ExternalLink { + id String @id @default(cuid()) type LinkType url String title String? projectId String - projects projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) @@unique([projectId, url]) @@index([projectId], map: "idx_link_projectId") @@index([type], map: "idx_link_type") @@index([type, url], map: "idx_link_type_url") @@index([url], map: "idx_link_url") + @@map("external_links") } -model keyword_cloud_error_logs { +model KeywordCloudErrorLog { id Int @id @default(autoincrement()) quarter String keyword String? @@ -34,9 +35,10 @@ model keyword_cloud_error_logs { @@index([errorType], map: "idx_keywordCloudErrorLog_errorType") @@index([quarter], map: "idx_keywordCloudErrorLog_quarter") + @@map("keyword_cloud_error_logs") } -model keywords { +model Keyword { id Int @id @default(autoincrement()) word String trendScore Int @@ -47,16 +49,17 @@ model keywords { detailPointsEn Json? visualConfig Json createdAt DateTime @default(now()) - updatedAt DateTime - quarters quarters @relation(fields: [quarterId], references: [id], onDelete: Cascade) + updatedAt DateTime @updatedAt + quarter Quarter @relation(fields: [quarterId], references: [id], onDelete: Cascade) @@index([quarterId], map: "idx_keyword_quarterId") @@index([trendScore], map: "idx_keyword_trendScore") @@index([word], map: "idx_keyword_word") + @@map("keywords") } -model project_discovery_tasks { - id String @id +model ProjectDiscoveryTask { + id String @id @default(cuid()) status TaskStatus @default(PENDING) sourceUrl String sourceType String @default("manual") @@ -69,26 +72,28 @@ model project_discovery_tasks { createdAt DateTime @default(now()) startedAt DateTime? completedAt DateTime? - updatedAt DateTime - projects projects? @relation(fields: [projectId], references: [id]) + updatedAt DateTime @updatedAt + project Project? @relation(fields: [projectId], references: [id]) @@index([projectId], map: "idx_task_project_id") @@index([sourceUrl], map: "idx_task_source_url") @@index([status, createdAt], map: "idx_task_status_created") + @@map("project_discovery_tasks") } -model project_tags { +model ProjectTag { projectId String tagId String - projects projects @relation(fields: [projectId], references: [id], onDelete: Cascade) - tags tags @relation(fields: [tagId], references: [id], onDelete: Cascade) + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) @@id([projectId, tagId]) @@index([tagId]) + @@map("project_tags") } -model projects { - id String @id +model Project { + id String @id @default(cuid()) name String nameEn String? slug String @unique @@ -99,19 +104,20 @@ model projects { status ProjectStatus @default(ACTIVE) source String? createdAt DateTime @default(now()) - updatedAt DateTime + updatedAt DateTime @updatedAt embedding Unsupported("vector")? embeddingUpdatedAt DateTime? - external_links external_links[] - project_discovery_tasks project_discovery_tasks[] - project_tags project_tags[] + links ExternalLink[] + projectDiscoveryTasks ProjectDiscoveryTask[] + tags ProjectTag[] @@index([embedding], map: "idx_project_embedding_cosine") @@index([slug], map: "idx_project_slug") @@index([status, createdAt], map: "idx_project_status_createdAt") + @@map("projects") } -model quarters { +model Quarter { id Int @id @default(autoincrement()) quarter String @unique title String @@ -121,25 +127,27 @@ model quarters { displayOrder Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) - updatedAt DateTime - keywords keywords[] + updatedAt DateTime @updatedAt + keywords Keyword[] @@index([displayOrder], map: "idx_quarter_displayOrder") @@index([quarter], map: "idx_quarter_quarter") + @@map("quarters") } -model tags { - id String @id +model Tag { + id String @id @default(cuid()) name String @unique nameEn String? slug String @unique createdAt DateTime @default(now()) - project_tags project_tags[] + projects ProjectTag[] @@index([slug], map: "idx_tag_slug") + @@map("tags") } -model visual_style_rules { +model VisualStyleRule { id Int @id @default(autoincrement()) name String @unique minScore Int @@ -151,10 +159,11 @@ model visual_style_rules { priority Int @default(0) enabled Boolean @default(true) createdAt DateTime @default(now()) - updatedAt DateTime + updatedAt DateTime @updatedAt @@index([enabled], map: "idx_visualStyleRule_enabled") @@index([minScore, maxScore], map: "idx_visualStyleRule_scoreRange") + @@map("visual_style_rules") } enum LinkType { diff --git a/src/app/[locale]/keyword-cloud/page.tsx b/src/app/[locale]/keyword-cloud/page.tsx index 3d59a6f..93024e7 100644 --- a/src/app/[locale]/keyword-cloud/page.tsx +++ b/src/app/[locale]/keyword-cloud/page.tsx @@ -1,41 +1,44 @@ -import { KeywordCloud } from './components/KeywordCloud'; -import { getTranslations } from 'next-intl/server'; +import { KeywordCloud } from "./components/KeywordCloud"; +import { getTranslations } from "next-intl/server"; interface PageProps { - params: { - locale: string; - }; - searchParams: { - quarter?: string; - }; + params?: Promise<{ + locale?: string | string[]; + }>; + searchParams?: Promise<{ + quarter?: string | string[]; + }>; } -export async function generateMetadata({ - params -}: { - params: Promise<{ locale: string }> -}) { - const { locale } = await params; - const t = await getTranslations('keywordCloud'); +export async function generateMetadata({ params }: PageProps) { + const resolvedParams = (await params) ?? {}; + const rawLocale = resolvedParams.locale; + const locale = (Array.isArray(rawLocale) ? rawLocale[0] : rawLocale) ?? "zh"; + const t = await getTranslations("keywordCloud"); return { - title: t('metaTitle'), - description: t('metaDescription'), + title: t("metaTitle"), + description: t("metaDescription"), }; } export default async function KeywordCloudPage({ searchParams, params }: PageProps) { - const { locale } = await params; - const t = await getTranslations('keywordCloud'); + const resolvedParams = (await params) ?? {}; + const rawLocale = resolvedParams.locale; + const locale = (Array.isArray(rawLocale) ? rawLocale[0] : rawLocale) ?? "zh"; + + const resolvedSearchParams = (await searchParams) ?? {}; + const t = await getTranslations("keywordCloud"); // 如果 URL 中有 quarter 参数,使用它;否则使用默认季度 - const quarter = searchParams.quarter || '2024-Q1'; + const rawQuarter = resolvedSearchParams.quarter; + const quarter = (Array.isArray(rawQuarter) ? rawQuarter[0] : rawQuarter) || "2024-Q1"; const texts = { - loading: t('loading'), - loadFailed: t('loadFailed'), - retry: t('retry'), - hotKeyword: t('hotKeyword'), + loading: t("loading"), + loadFailed: t("loadFailed"), + retry: t("retry"), + hotKeyword: t("hotKeyword"), }; return ( @@ -43,13 +46,19 @@ export default async function KeywordCloudPage({ searchParams, params }: PagePro {/* 页面标题 */}
- {t('badge')} + {t("badge")}

- {t('title')} {t('titleHighlight')} + {t("title")}{" "} + + {t("titleHighlight")} +

- {t('subtitle')} + {t("subtitle")}

diff --git a/src/app/api/keyword-cloud/keywords/[quarter]/route.ts b/src/app/api/keyword-cloud/keywords/[quarter]/route.ts index 6ae9e04..0ec9c95 100644 --- a/src/app/api/keyword-cloud/keywords/[quarter]/route.ts +++ b/src/app/api/keyword-cloud/keywords/[quarter]/route.ts @@ -1,25 +1,22 @@ -import { NextResponse } from 'next/server'; -import { getKeywordsByQuarter } from '@/hooks/useKeywordCloud'; +import { NextResponse } from "next/server"; +import { getKeywordsByQuarter } from "@/hooks/useKeywordCloud"; -export const dynamic = 'force-dynamic'; +export const dynamic = "force-dynamic"; /** * GET /api/keyword-cloud/keywords/[quarter] * 获取指定季度的关键词 */ -export async function GET( - request: Request, - { params }: { params: { quarter: string } } -) { +export async function GET(request: Request, { params }: { params: Promise<{ quarter: string }> }) { try { - const { quarter } = params; + const { quarter } = await params; // 验证 quarter 格式 if (!/^\d{4}-Q[1-4]$/.test(quarter)) { return NextResponse.json( { success: false, - error: 'Invalid quarter format. Expected: YYYY-QN', + error: "Invalid quarter format. Expected: YYYY-QN", }, { status: 400 } ); @@ -47,11 +44,11 @@ export async function GET( keywords: data.keywords, }); } catch (error) { - console.error('Error fetching keywords:', error); + console.error("Error fetching keywords:", error); return NextResponse.json( { success: false, - error: 'Failed to fetch keywords', + error: "Failed to fetch keywords", }, { status: 500 } );