refactor: simplify homepage layout, consolidate landing page components

Merge hero/stats/features/footer sections into home-page-content.tsx,
remove separate landing-page.tsx. Update styles, layout, navbar, and
backend config.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-22 11:24:34 +08:00
parent ddbb79b9f6
commit 09621ef767
12 changed files with 425 additions and 1046 deletions
+5
View File
@@ -140,6 +140,11 @@ def ensure_directories():
os.path.dirname(settings.log_file), os.path.dirname(settings.log_file),
] ]
# SQLite 数据库目录
if not is_postgresql_database():
db_path = settings.database_url.replace("sqlite:///", "")
directories.append(os.path.dirname(db_path))
for directory in directories: for directory in directories:
Path(directory).mkdir(parents=True, exist_ok=True) Path(directory).mkdir(parents=True, exist_ok=True)
+1
View File
@@ -51,6 +51,7 @@ LOG_FILE=../runtime/logs/app.log
# 前端配置 # 前端配置
NEXT_PUBLIC_BACKEND_URL=http://localhost:8000 NEXT_PUBLIC_BACKEND_URL=http://localhost:8000
NEXT_PUBLIC_API_URL=http://localhost:8000
NEXTAUTH_SECRET=your-nextauth-secret-change-in-production NEXTAUTH_SECRET=your-nextauth-secret-change-in-production
NEXTAUTH_URL=http://localhost:8001 NEXTAUTH_URL=http://localhost:8001
+11 -11
View File
@@ -19,17 +19,6 @@
} }
} }
--animate-spotlight: spotlight 2s ease 0.75s 1 forwards;
@keyframes spotlight {
0% {
opacity: 0;
transform: translate(-72%, -62%) scale(0.5);
}
to {
opacity: 1;
transform: translate(-50%, -40%) scale(1);
}
}
} }
@theme inline { @theme inline {
@@ -175,6 +164,7 @@
/* MIT 风格的字体层次 */ /* MIT 风格的字体层次 */
h1 { h1 {
font-family: var(--font-serif), 'Noto Serif SC', Georgia, serif;
font-size: 2.5rem; font-size: 2.5rem;
font-weight: 700; font-weight: 700;
line-height: 1.2; line-height: 1.2;
@@ -182,6 +172,7 @@
} }
h2 { h2 {
font-family: var(--font-serif), 'Noto Serif SC', Georgia, serif;
font-size: 2rem; font-size: 2rem;
font-weight: 600; font-weight: 600;
line-height: 1.3; line-height: 1.3;
@@ -330,3 +321,12 @@ textarea {
button { button {
cursor: pointer; cursor: pointer;
} }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
+8 -3
View File
@@ -1,10 +1,15 @@
import type { Metadata, Viewport } from "next"; import type { Metadata, Viewport } from "next";
import { Inter } from "next/font/google"; import { Inter, Noto_Serif_SC } from "next/font/google";
import { ThemeProvider } from "next-themes"; import { ThemeProvider } from "next-themes";
import { Toaster } from "sonner"; import { Toaster } from "sonner";
import "./globals.css"; import "./globals.css";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });
const notoSerifSC = Noto_Serif_SC({
subsets: ["latin"],
weight: ["600", "700"],
variable: "--font-serif",
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "国土空间规划课程智能体", title: "国土空间规划课程智能体",
@@ -25,7 +30,7 @@ export default function RootLayout({
}) { }) {
return ( return (
<html lang="zh-CN" suppressHydrationWarning> <html lang="zh-CN" suppressHydrationWarning>
<body className={inter.className}> <body className={`${inter.variable} ${notoSerifSC.variable} ${inter.className}`}>
<ThemeProvider <ThemeProvider
attribute="class" attribute="class"
defaultTheme="system" defaultTheme="system"
@@ -1,148 +0,0 @@
"use client";
import Link from "next/link";
import { motion } from "framer-motion";
import {
MessageSquare,
BookOpen,
Image,
BarChart3,
ArrowRight,
CheckCircle
} from "lucide-react";
import { BentoCard, BentoGrid } from "@/components/magicui/bento-grid";
import { User } from "@/types";
import { cn } from "@/lib/utils";
interface FeaturesSectionProps {
isAuthenticated: boolean;
user: User | null;
}
export default function FeaturesSection({ isAuthenticated, user }: FeaturesSectionProps) {
const features = [
{
Icon: MessageSquare,
name: "智能问答",
description: "基于大模型的智能问答系统,支持多轮对话和上下文理解",
href: isAuthenticated ? "/chat" : "#",
cta: isAuthenticated ? "开始对话" : "了解更多",
className: "lg:col-start-1 lg:col-end-2 lg:row-start-1 lg:row-end-3",
stats: isAuthenticated ? { count: 0, label: "次对话" } : undefined,
},
{
Icon: BookOpen,
name: "知识库管理",
description: "多模态知识库,支持文档上传、向量化和智能检索",
href: isAuthenticated ? "/knowledge" : "#",
cta: isAuthenticated ? "管理文档" : "了解更多",
className: "lg:col-start-1 lg:col-end-2 lg:row-start-3 lg:row-end-4",
stats: isAuthenticated ? { count: 0, label: "个文档" } : undefined,
},
{
Icon: Image,
name: "空间出图",
description: "结合空间规划知识的图像生成和分析功能",
href: isAuthenticated ? "/spatial" : "#",
cta: isAuthenticated ? "生成图像" : "了解更多",
className: "lg:col-start-2 lg:col-end-3 lg:row-start-1 lg:row-end-2",
stats: isAuthenticated ? { count: 0, label: "张图片" } : undefined,
},
{
Icon: BarChart3,
name: "学习分析",
description: "学习数据统计和可视化,帮助了解学习进度",
href: isAuthenticated ? "/analytics" : "#",
cta: isAuthenticated ? "查看分析" : "了解更多",
className: "lg:col-start-2 lg:col-end-3 lg:row-start-2 lg:row-end-3",
stats: isAuthenticated ? { count: 0, label: "个报告" } : undefined,
},
];
return (
<section className="py-24 sm:py-32">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<h2 className="text-base font-semibold leading-7 text-primary">
{isAuthenticated ? "功能模块" : "核心功能"}
</h2>
<p className="mt-2 text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
{isAuthenticated ? "选择您需要的功能" : "强大的AI学习助手"}
</p>
<p className="mt-6 text-lg leading-8 text-gray-600">
{isAuthenticated
? "点击下方卡片开始使用各项功能"
: "基于先进的大模型技术,为您提供专业的国土空间规划学习体验"
}
</p>
</div>
<div className="mx-auto mt-16 max-w-2xl sm:mt-20 lg:mt-24 lg:max-w-none">
<BentoGrid className="grid grid-cols-1 lg:grid-cols-3 gap-4">
{features.map((feature) => {
if (isAuthenticated) {
// 登录状态:使用自定义卡片
return (
<Link key={feature.name} href={feature.href} className="block h-full">
<motion.div
className={cn(
"group relative col-span-3 flex flex-col justify-between overflow-hidden rounded-xl h-full",
"bg-background [box-shadow:0_0_0_1px_rgba(0,0,0,.03),0_2px_4px_rgba(0,0,0,.05),0_12px_24px_rgba(0,0,0,.05)]",
"dark:bg-background transform-gpu dark:[box-shadow:0_-20px_80px_-20px_#ffffff1f_inset] dark:[border:1px_solid_rgba(255,255,255,.1)]",
feature.className
)}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
>
<div className="z-10 flex transform-gpu flex-col gap-1 p-6 transition-all duration-300 group-hover:-translate-y-5">
<feature.Icon className="h-12 w-12 origin-left transform-gpu text-neutral-700 transition-all duration-300 ease-in-out group-hover:scale-75 dark:text-neutral-300" />
<h3 className="text-xl font-semibold text-neutral-700 dark:text-neutral-300">
{feature.name}
</h3>
<p className="max-w-lg text-neutral-400">{feature.description}</p>
{feature.stats && (
<div className="flex items-center text-sm text-gray-500 mt-4">
<span className="font-medium">
{feature.stats.count} {feature.stats.label}
</span>
</div>
)}
</div>
<motion.div
className="pointer-events-none absolute bottom-0 flex w-full translate-y-10 transform-gpu flex-row items-center p-4 opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100"
whileHover={{ x: 4 }}
transition={{ duration: 0.2 }}
>
<span className="flex items-center text-sm font-medium text-neutral-700 dark:text-neutral-300 pointer-events-auto">
{feature.cta}
<ArrowRight className="ms-2 h-4 w-4" />
</span>
</motion.div>
<div className="pointer-events-none absolute inset-0 transform-gpu transition-all duration-300 group-hover:bg-black/[.03] group-hover:dark:bg-neutral-800/10" />
</motion.div>
</Link>
);
} else {
// 未登录状态:使用标准 BentoCard
return (
<BentoCard
key={feature.name}
className={feature.className}
Icon={feature.Icon}
name={feature.name}
description={feature.description}
cta={feature.cta}
href={feature.href}
/>
);
}
})}
</BentoGrid>
</div>
</div>
</section>
);
}
-159
View File
@@ -1,159 +0,0 @@
"use client";
import Link from "next/link";
import { BookOpen, Mail, Phone, MapPin, MessageCircle } from "lucide-react";
export default function Footer() {
const currentYear = new Date().getFullYear();
const footerLinks = {
quickLinks: [
{ name: "首页", href: "/" },
{ name: "智能问答", href: "/chat" },
{ name: "知识库管理", href: "/knowledge" },
{ name: "空间出图", href: "/spatial" },
{ name: "学习分析", href: "/analytics" },
],
resources: [
{ name: "课程大纲", href: "#" },
{ name: "线上讲座", href: "#" },
{ name: "资料下载", href: "#" },
{ name: "案例分析", href: "#" },
{ name: "交流论坛", href: "#" },
],
};
const contactInfo = [
{ icon: Mail, text: "contact@HIT-agent.com", href: "mailto:contact@HIT-agent.com" },
{ icon: Phone, text: "0451-8641-2114", href: "tel:0451-8641-2114" },
{ icon: MapPin, text: "哈尔滨市南岗区西大直街92号", href: "#" },
];
return (
<footer className="relative border-t bg-slate-900 dark:bg-slate-950 text-slate-300">
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Main Footer Content */}
<div className="py-12 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{/* Brand Column */}
<div className="lg:col-span-1">
<div className="flex items-center space-x-2 mb-4">
<div className="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
<BookOpen className="w-6 h-6 text-white" />
</div>
<span className="text-lg font-bold text-white">
</span>
</div>
<p className="text-sm text-slate-400 mb-6 max-w-xs">
</p>
{/* Social Links */}
<div className="flex items-center gap-3">
<Link
href="#"
className="w-9 h-9 rounded-lg bg-slate-800 hover:bg-slate-700 flex items-center justify-center transition-colors"
aria-label="微信"
>
<MessageCircle className="w-4 h-4" />
</Link>
<Link
href="#"
className="w-9 h-9 rounded-lg bg-slate-800 hover:bg-slate-700 flex items-center justify-center transition-colors"
aria-label="微博"
>
<MessageCircle className="w-4 h-4" />
</Link>
<Link
href="#"
className="w-9 h-9 rounded-lg bg-slate-800 hover:bg-slate-700 flex items-center justify-center transition-colors"
aria-label="通知"
>
<MessageCircle className="w-4 h-4" />
</Link>
</div>
</div>
{/* Quick Links */}
<div>
<h4 className="font-semibold mb-4 text-white"></h4>
<ul className="space-y-3">
{footerLinks.quickLinks.map((link) => (
<li key={link.name}>
<Link
href={link.href}
className="text-sm text-slate-400 hover:text-white transition-colors"
>
{link.name}
</Link>
</li>
))}
</ul>
</div>
{/* Resources Links */}
<div>
<h4 className="font-semibold mb-4 text-white"></h4>
<ul className="space-y-3">
{footerLinks.resources.map((link) => (
<li key={link.name}>
<Link
href={link.href}
className="text-sm text-slate-400 hover:text-white transition-colors"
>
{link.name}
</Link>
</li>
))}
</ul>
</div>
{/* Contact Info */}
<div>
<h4 className="font-semibold mb-4 text-white"></h4>
<ul className="space-y-3">
{contactInfo.map((contact, index) => (
<li key={index}>
{contact.href !== "#" ? (
<a
href={contact.href}
className="text-sm text-slate-400 hover:text-white transition-colors flex items-start gap-2"
>
<contact.icon className="w-4 h-4 mt-0.5 flex-shrink-0" />
<span>{contact.text}</span>
</a>
) : (
<div className="text-sm text-slate-400 flex items-start gap-2">
<contact.icon className="w-4 h-4 mt-0.5 flex-shrink-0" />
<span>{contact.text}</span>
</div>
)}
</li>
))}
</ul>
</div>
</div>
{/* Bottom Bar */}
<div className="py-6 border-t border-slate-800">
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
<div className="text-sm text-slate-400">
© {currentYear}
</div>
<div className="flex items-center gap-6 text-sm text-slate-400">
<Link href="#" className="hover:text-white transition-colors">
</Link>
<Link href="#" className="hover:text-white transition-colors">
使
</Link>
<Link href="#" className="hover:text-white transition-colors">
</Link>
</div>
</div>
</div>
</div>
</footer>
);
}
-130
View File
@@ -1,130 +0,0 @@
"use client";
import Link from "next/link";
import { motion } from "framer-motion";
import { Button } from "@/components/ui/button";
import { AuroraText } from "@/components/magicui/aurora-text";
import { User } from "@/types";
import { MessageSquare, FileText, Image, BarChart3, ArrowRight } from "lucide-react";
interface HeroSectionProps {
isAuthenticated: boolean;
user: User | null;
}
export default function HeroSection({ isAuthenticated, user }: HeroSectionProps) {
const features = [
{
name: "智能问答",
href: "/chat",
icon: MessageSquare,
description: "基于大模型的智能问答系统,专业知识即时解答",
color: "from-blue-500 to-blue-600"
},
{
name: "知识库管理",
href: "/knowledge",
icon: FileText,
description: "多模态知识库,文档上传与智能检索",
color: "from-green-500 to-green-600"
},
{
name: "空间出图",
href: "/spatial",
icon: Image,
description: "结合空间规划的AI图像生成与分析",
color: "from-purple-500 to-purple-600"
},
{
name: "学习分析",
href: "/analytics",
icon: BarChart3,
description: "学习数据统计,追踪您的学习进度",
color: "from-orange-500 to-orange-600"
},
];
return (
<section className="flex min-h-[90vh] w-full flex-col items-center justify-center py-20 relative">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* 描述区域 */}
<motion.div
className="text-center mb-16"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
<p className="text-lg leading-8 text-gray-600 max-w-2xl mx-auto">
</p>
{!isAuthenticated && (
<div className="mt-10 flex items-center justify-center gap-x-6">
<Button size="lg" asChild>
<Link href="/register">使</Link>
</Button>
<Button variant="outline" size="lg" asChild>
<Link href="/login"></Link>
</Button>
</div>
)}
</motion.div>
{/* 功能卡片 - 登录后显示 */}
{isAuthenticated && (
<motion.div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"
initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
>
{features.map((feature, index) => (
<Link
key={feature.name}
href={feature.href}
className="block group"
>
<motion.div
className="relative p-6 rounded-2xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 hover:border-blue-300 dark:hover:border-blue-700 hover:shadow-xl transition-all duration-300"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 + index * 0.1 }}
whileHover={{ y: -8, scale: 1.02 }}
>
{/* 图标 */}
<div className={`w-14 h-14 rounded-xl bg-gradient-to-r ${feature.color} flex items-center justify-center mb-4 group-hover:scale-110 transition-transform duration-300`}>
<feature.icon className="w-7 h-7 text-white" />
</div>
{/* 标题 */}
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
{feature.name}
</h3>
{/* 描述 */}
<p className="text-sm text-gray-600 dark:text-gray-400 mb-4">
{feature.description}
</p>
{/* 箭头 */}
<div className="flex items-center text-sm font-medium text-blue-600 dark:text-blue-400">
<span>使</span>
<ArrowRight className="ml-2 w-4 h-4 group-hover:translate-x-2 transition-transform duration-300" />
</div>
{/* 装饰性背景 */}
<div className="absolute inset-0 rounded-2xl bg-gradient-to-br from-blue-50/50 to-purple-50/50 dark:from-blue-950/20 dark:to-purple-950/20 opacity-0 group-hover:opacity-100 transition-opacity duration-300 -z-10" />
</motion.div>
</Link>
))}
</motion.div>
)}
</div>
</section>
);
}
+388 -119
View File
@@ -1,23 +1,246 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState, useEffect, useMemo } from "react";
import { useAuthStore } from "@/store/auth"; import { useAuthStore } from "@/store/auth";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import Navbar from "./navbar"; import Navbar from "./navbar";
import { MessageSquare, Database, Image, BookOpen, Users, Award, ArrowRight, Mail, Phone, MapPin, GraduationCap } from "lucide-react"; import { BookOpen, Users, ArrowRight, Mail, Phone, MapPin, Search } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { analyticsAPI } from "@/lib/api"; import { analyticsAPI } from "@/lib/api";
const modules = [
{
title: "课程内容",
description: "系统化的国土空间规划课程体系,涵盖总体规划、详细规划、专项规划等核心知识模块",
href: "/course-content",
tags: ["课程体系"],
visual: "course" as const,
},
{
title: "智能问答",
description: "基于RAG技术的专业对话系统,精准解答国土空间规划领域的专业问题",
href: "/chat",
tags: ["RAG"],
visual: "chat" as const,
},
{
title: "知识库",
description: "多格式文档智能处理与知识管理,构建专业领域的结构化知识体系",
href: "/knowledge",
tags: ["向量检索"],
visual: "knowledge" as const,
},
{
title: "空间设计",
description: "AI驱动的空间规划图像生成工具,支持文生图与图生图多种创作模式",
href: "/spatial",
tags: ["AI绘图"],
visual: "spatial" as const,
},
{
title: "论坛社区",
description: "课程讨论与学习经验交流平台,促进师生互动与同伴学习",
href: "/forum",
tags: ["协作"],
visual: "forum" as const,
},
];
const visualStyles: Record<string, { bg: string }> = {
course: { bg: "from-emerald-950 to-emerald-900/80" },
chat: { bg: "from-slate-950 to-slate-900/80" },
knowledge: { bg: "from-amber-950 to-amber-900/80" },
forum: { bg: "from-sky-950 to-sky-900/80" },
spatial: { bg: "from-violet-950 to-violet-900/80" },
};
function ModuleIllustration({ visual }: { visual: string }) {
const illustrations: Record<string, React.ReactNode> = {
// 等高线地形图纹理 — 课程体系的系统性层次
course: (
<svg viewBox="0 0 320 192" className="absolute inset-0 w-full h-full" fill="none" preserveAspectRatio="xMidYMid slice">
{/* 网格底纹 */}
<defs>
<pattern id="grid-c" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="white" strokeWidth="0.3" opacity="0.08"/>
</pattern>
</defs>
<rect width="320" height="192" fill="url(#grid-c)" />
{/* 等高线组 */}
<ellipse cx="120" cy="100" rx="110" ry="70" stroke="white" strokeWidth="0.8" opacity="0.12"/>
<ellipse cx="120" cy="100" rx="85" ry="54" stroke="white" strokeWidth="0.8" opacity="0.15"/>
<ellipse cx="120" cy="100" rx="60" ry="38" stroke="white" strokeWidth="0.8" opacity="0.18"/>
<ellipse cx="120" cy="100" rx="35" ry="22" stroke="white" strokeWidth="0.8" opacity="0.22"/>
<ellipse cx="120" cy="100" rx="14" ry="9" stroke="white" strokeWidth="1" opacity="0.3"/>
{/* 副等高线 */}
<ellipse cx="230" cy="65" rx="60" ry="40" stroke="white" strokeWidth="0.6" opacity="0.1"/>
<ellipse cx="230" cy="65" rx="35" ry="23" stroke="white" strokeWidth="0.6" opacity="0.14"/>
<ellipse cx="230" cy="65" rx="14" ry="9" stroke="white" strokeWidth="0.8" opacity="0.2"/>
{/* 标注线 */}
<line x1="120" y1="100" x2="230" y2="65" stroke="white" strokeWidth="0.4" opacity="0.1" strokeDasharray="4 4"/>
<circle cx="120" cy="100" r="3" fill="white" opacity="0.25"/>
<circle cx="230" cy="65" r="2.5" fill="white" opacity="0.2"/>
</svg>
),
// 神经网络/对话流 — AI 问答的连接性
chat: (
<svg viewBox="0 0 320 192" className="absolute inset-0 w-full h-full" fill="none" preserveAspectRatio="xMidYMid slice">
{/* 节点网络 */}
<circle cx="80" cy="50" r="4" fill="white" opacity="0.2"/>
<circle cx="160" cy="30" r="3" fill="white" opacity="0.15"/>
<circle cx="240" cy="55" r="5" fill="white" opacity="0.22"/>
<circle cx="50" cy="120" r="3.5" fill="white" opacity="0.18"/>
<circle cx="140" cy="100" r="6" fill="white" opacity="0.25"/>
<circle cx="220" cy="130" r="4" fill="white" opacity="0.2"/>
<circle cx="280" cy="95" r="3" fill="white" opacity="0.14"/>
<circle cx="100" cy="160" r="4" fill="white" opacity="0.16"/>
<circle cx="190" cy="155" r="3.5" fill="white" opacity="0.18"/>
<circle cx="260" cy="165" r="2.5" fill="white" opacity="0.12"/>
{/* 连接线 */}
<line x1="80" y1="50" x2="160" y2="30" stroke="white" strokeWidth="0.6" opacity="0.12"/>
<line x1="160" y1="30" x2="240" y2="55" stroke="white" strokeWidth="0.6" opacity="0.1"/>
<line x1="80" y1="50" x2="140" y2="100" stroke="white" strokeWidth="0.6" opacity="0.14"/>
<line x1="140" y1="100" x2="240" y2="55" stroke="white" strokeWidth="0.6" opacity="0.12"/>
<line x1="50" y1="120" x2="140" y2="100" stroke="white" strokeWidth="0.5" opacity="0.1"/>
<line x1="140" y1="100" x2="220" y2="130" stroke="white" strokeWidth="0.6" opacity="0.14"/>
<line x1="220" y1="130" x2="280" y2="95" stroke="white" strokeWidth="0.5" opacity="0.1"/>
<line x1="240" y1="55" x2="280" y2="95" stroke="white" strokeWidth="0.5" opacity="0.1"/>
<line x1="50" y1="120" x2="100" y2="160" stroke="white" strokeWidth="0.5" opacity="0.08"/>
<line x1="100" y1="160" x2="190" y2="155" stroke="white" strokeWidth="0.5" opacity="0.1"/>
<line x1="220" y1="130" x2="190" y2="155" stroke="white" strokeWidth="0.5" opacity="0.1"/>
<line x1="190" y1="155" x2="260" y2="165" stroke="white" strokeWidth="0.4" opacity="0.08"/>
{/* 光晕效果 — 核心节点 */}
<circle cx="140" cy="100" r="18" stroke="white" strokeWidth="0.4" opacity="0.06"/>
<circle cx="140" cy="100" r="30" stroke="white" strokeWidth="0.3" opacity="0.04"/>
</svg>
),
// 分层知识架构 — 向量检索的结构化组织
knowledge: (
<svg viewBox="0 0 320 192" className="absolute inset-0 w-full h-full" fill="none" preserveAspectRatio="xMidYMid slice">
{/* 横向分层线 */}
<line x1="30" y1="40" x2="290" y2="40" stroke="white" strokeWidth="0.5" opacity="0.08"/>
<line x1="30" y1="75" x2="290" y2="75" stroke="white" strokeWidth="0.5" opacity="0.08"/>
<line x1="30" y1="110" x2="290" y2="110" stroke="white" strokeWidth="0.5" opacity="0.08"/>
<line x1="30" y1="145" x2="290" y2="145" stroke="white" strokeWidth="0.5" opacity="0.08"/>
{/* 文档块 — 层叠的矩形 */}
<rect x="40" y="20" width="55" height="35" rx="2" stroke="white" strokeWidth="0.6" opacity="0.12"/>
<rect x="45" y="25" width="55" height="35" rx="2" stroke="white" strokeWidth="0.6" opacity="0.15"/>
<rect x="50" y="30" width="55" height="35" rx="2" stroke="white" strokeWidth="0.8" opacity="0.2" fill="white" fillOpacity="0.04"/>
{/* 知识图谱节点群 */}
<circle cx="190" cy="55" r="22" stroke="white" strokeWidth="0.5" opacity="0.08"/>
<circle cx="190" cy="55" r="12" stroke="white" strokeWidth="0.6" opacity="0.14"/>
<circle cx="190" cy="55" r="4" fill="white" opacity="0.2"/>
<circle cx="220" cy="80" r="3" fill="white" opacity="0.15"/>
<circle cx="165" cy="78" r="3" fill="white" opacity="0.15"/>
<circle cx="200" cy="35" r="2.5" fill="white" opacity="0.12"/>
<circle cx="175" cy="38" r="2.5" fill="white" opacity="0.12"/>
<line x1="190" y1="55" x2="220" y2="80" stroke="white" strokeWidth="0.4" opacity="0.1"/>
<line x1="190" y1="55" x2="165" y2="78" stroke="white" strokeWidth="0.4" opacity="0.1"/>
<line x1="190" y1="55" x2="200" y2="35" stroke="white" strokeWidth="0.4" opacity="0.1"/>
<line x1="190" y1="55" x2="175" y2="38" stroke="white" strokeWidth="0.4" opacity="0.1"/>
{/* 索引/向量条 */}
<rect x="55" y="85" width="12" height="40" rx="1" fill="white" opacity="0.06"/>
<rect x="72" y="90" width="12" height="35" rx="1" fill="white" opacity="0.08"/>
<rect x="89" y="82" width="12" height="43" rx="1" fill="white" opacity="0.1"/>
<rect x="106" y="88" width="12" height="37" rx="1" fill="white" opacity="0.07"/>
{/* 连接弧线 */}
<path d="M105 47 Q160 90 185 55" stroke="white" strokeWidth="0.5" opacity="0.1" strokeDasharray="3 3"/>
{/* 底部散点 */}
<circle cx="60" cy="160" r="2" fill="white" opacity="0.1"/>
<circle cx="110" cy="165" r="1.5" fill="white" opacity="0.08"/>
<circle cx="200" cy="155" r="2.5" fill="white" opacity="0.1"/>
<circle cx="250" cy="162" r="1.5" fill="white" opacity="0.08"/>
</svg>
),
// 等高线地形 + 流动曲线 — 空间设计的地形分析
spatial: (
<svg viewBox="0 0 320 192" className="absolute inset-0 w-full h-full" fill="none" preserveAspectRatio="xMidYMid slice">
{/* 流动地形线 */}
<path d="M-10 130 Q40 110 80 120 T160 105 T240 115 T330 95" stroke="white" strokeWidth="0.8" opacity="0.15"/>
<path d="M-10 105 Q50 85 100 95 T180 80 T260 90 T330 70" stroke="white" strokeWidth="0.8" opacity="0.18"/>
<path d="M-10 80 Q60 60 110 70 T200 55 T280 65 T330 45" stroke="white" strokeWidth="0.8" opacity="0.2"/>
<path d="M-10 55 Q70 35 120 45 T220 30 T300 40 T330 20" stroke="white" strokeWidth="1" opacity="0.25"/>
<path d="M-10 30 Q80 10 130 20 T240 5 T330 15" stroke="white" strokeWidth="1.2" opacity="0.3"/>
{/* 网格线 — 坐标参考 */}
<line x1="80" y1="0" x2="80" y2="192" stroke="white" strokeWidth="0.3" opacity="0.05"/>
<line x1="160" y1="0" x2="160" y2="192" stroke="white" strokeWidth="0.3" opacity="0.05"/>
<line x1="240" y1="0" x2="240" y2="192" stroke="white" strokeWidth="0.3" opacity="0.05"/>
{/* 数据点 */}
<circle cx="80" cy="120" r="2.5" fill="white" opacity="0.25"/>
<circle cx="160" cy="105" r="3" fill="white" opacity="0.3"/>
<circle cx="240" cy="115" r="2.5" fill="white" opacity="0.25"/>
<circle cx="200" cy="55" r="2" fill="white" opacity="0.2"/>
{/* 辅助标注线 */}
<line x1="160" y1="105" x2="160" y2="140" stroke="white" strokeWidth="0.4" opacity="0.08" strokeDasharray="2 3"/>
<line x1="80" y1="120" x2="80" y2="140" stroke="white" strokeWidth="0.4" opacity="0.08" strokeDasharray="2 3"/>
</svg>
),
// 星座/人际网络 — 论坛社区的连接
forum: (
<svg viewBox="0 0 320 192" className="absolute inset-0 w-full h-full" fill="none" preserveAspectRatio="xMidYMid slice">
{/* 主要节点 */}
<circle cx="70" cy="45" r="3" fill="white" opacity="0.22"/>
<circle cx="150" cy="25" r="4" fill="white" opacity="0.25"/>
<circle cx="250" cy="40" r="3" fill="white" opacity="0.2"/>
<circle cx="110" cy="95" r="5" fill="white" opacity="0.28"/>
<circle cx="200" cy="85" r="3.5" fill="white" opacity="0.22"/>
<circle cx="40" cy="140" r="3" fill="white" opacity="0.18"/>
<circle cx="130" cy="150" r="4" fill="white" opacity="0.22"/>
<circle cx="230" cy="130" r="3" fill="white" opacity="0.18"/>
<circle cx="290" cy="100" r="2.5" fill="white" opacity="0.15"/>
<circle cx="280" cy="160" r="3" fill="white" opacity="0.16"/>
<circle cx="170" cy="170" r="2.5" fill="white" opacity="0.14"/>
{/* 连线 */}
<line x1="70" y1="45" x2="150" y2="25" stroke="white" strokeWidth="0.5" opacity="0.1"/>
<line x1="150" y1="25" x2="250" y2="40" stroke="white" strokeWidth="0.5" opacity="0.1"/>
<line x1="70" y1="45" x2="110" y2="95" stroke="white" strokeWidth="0.5" opacity="0.12"/>
<line x1="110" y1="95" x2="200" y2="85" stroke="white" strokeWidth="0.5" opacity="0.12"/>
<line x1="200" y1="85" x2="250" y2="40" stroke="white" strokeWidth="0.5" opacity="0.1"/>
<line x1="40" y1="140" x2="110" y2="95" stroke="white" strokeWidth="0.4" opacity="0.08"/>
<line x1="110" y1="95" x2="130" y2="150" stroke="white" strokeWidth="0.4" opacity="0.1"/>
<line x1="130" y1="150" x2="170" y2="170" stroke="white" strokeWidth="0.4" opacity="0.08"/>
<line x1="200" y1="85" x2="230" y2="130" stroke="white" strokeWidth="0.5" opacity="0.12"/>
<line x1="230" y1="130" x2="280" y2="160" stroke="white" strokeWidth="0.4" opacity="0.08"/>
<line x1="200" y1="85" x2="290" y2="100" stroke="white" strokeWidth="0.4" opacity="0.08"/>
{/* 光晕 — 中心节点 */}
<circle cx="110" cy="95" r="16" stroke="white" strokeWidth="0.3" opacity="0.06"/>
<circle cx="110" cy="95" r="28" stroke="white" strokeWidth="0.2" opacity="0.04"/>
{/* 背景散点 */}
<circle cx="20" cy="70" r="1" fill="white" opacity="0.06"/>
<circle cx="180" cy="50" r="1" fill="white" opacity="0.06"/>
<circle cx="300" cy="30" r="1" fill="white" opacity="0.06"/>
<circle cx="90" cy="175" r="1" fill="white" opacity="0.06"/>
</svg>
),
};
return <>{illustrations[visual]}</>;
}
const allTags = Array.from(new Set(modules.flatMap((m) => m.tags)));
export default function HomePageContent() { export default function HomePageContent() {
const { isAuthenticated, user } = useAuthStore(); const { isAuthenticated, user } = useAuthStore();
const [stats, setStats] = useState([ const [stats, setStats] = useState([
{ label: "活跃用户", value: "1,200+" }, { label: "活跃用户", value: "" },
{ label: "知识文档", value: "5,000+" }, { label: "知识文档", value: "" },
{ label: "问答对话", value: "50,000+" }, { label: "问答对话", value: "" },
{ label: "生成图像", value: "10,000+" } { label: "生成图像", value: "" },
]); ]);
const [isLoadingStats, setIsLoadingStats] = useState(true); const [isLoadingStats, setIsLoadingStats] = useState(true);
const [searchQuery, setSearchQuery] = useState("");
const [activeTag, setActiveTag] = useState<string | null>(null);
const filteredModules = useMemo(() => {
return modules.filter((m) => {
const matchesSearch =
!searchQuery ||
m.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
m.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
m.tags.some((t) => t.toLowerCase().includes(searchQuery.toLowerCase()));
const matchesTag = !activeTag || m.tags.includes(activeTag);
return matchesSearch && matchesTag;
});
}, [searchQuery, activeTag]);
useEffect(() => { useEffect(() => {
const loadPlatformStats = async () => { const loadPlatformStats = async () => {
@@ -25,26 +248,20 @@ export default function HomePageContent() {
setIsLoadingStats(true); setIsLoadingStats(true);
const data = await analyticsAPI.getPlatformStats(); const data = await analyticsAPI.getPlatformStats();
// 格式化数字,添加千分位分隔符
const formatNumber = (num: number) => { const formatNumber = (num: number) => {
if (num >= 10000) { if (num >= 10000) return `${(num / 10000).toFixed(1)}万+`;
return `${(num / 10000).toFixed(1)}+`; if (num >= 1000) return `${(num / 1000).toFixed(1)}+`;
} else if (num >= 1000) { return `${num}`;
return `${(num / 1000).toFixed(1)}千+`;
}
return `${num}+`;
}; };
setStats([ setStats([
{ label: "活跃用户", value: formatNumber(data.active_users) }, { label: "活跃用户", value: formatNumber(data.active_users) },
{ label: "知识文档", value: formatNumber(data.knowledge_documents) }, { label: "知识文档", value: formatNumber(data.knowledge_documents) },
{ label: "问答对话", value: formatNumber(data.qa_dialogues) }, { label: "问答对话", value: formatNumber(data.qa_dialogues) },
{ label: "生成图像", value: formatNumber(data.generated_images) } { label: "生成图像", value: formatNumber(data.generated_images) },
]); ]);
} catch (error: any) { } catch {
console.warn("加载平台统计数据失败,使用默认值:", error?.message || error); // 后端不可用时保持占位符
// 保持默认值,不显示错误给用户
// 如果后端API不可用,继续显示默认的占位数据
} finally { } finally {
setIsLoadingStats(false); setIsLoadingStats(false);
} }
@@ -53,63 +270,26 @@ export default function HomePageContent() {
loadPlatformStats(); loadPlatformStats();
}, []); }, []);
const features = [
{
icon: GraduationCap,
title: "课程内容",
description: "查看和管理国土空间规划课程内容,获取系统化的学习资源",
href: "/course-content"
},
{
icon: MessageSquare,
title: "智能问答",
description: "基于RAG技术的智能对话系统,提供专业准确的国土空间规划知识解答",
href: "/chat"
},
{
icon: Database,
title: "知识库",
description: "构建和管理专业知识库,支持多种文档格式的智能处理和分析",
href: "/knowledge"
},
{
icon: Users,
title: "论坛社区",
description: "参与系统优化建议与课程反馈的讨论,与同伴交流学习经验",
href: "/forum"
},
{
icon: Image,
title: "空间设计",
description: "AI驱动的空间规划图像生成,支持多种设计风格和规划类型",
href: "/spatial"
}
];
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
{/* 导航栏 */}
<Navbar isAuthenticated={isAuthenticated} user={user} /> <Navbar isAuthenticated={isAuthenticated} user={user} />
{/* Hero 区域 - 全屏背景 */} {/* Hero */}
<section className="relative min-h-screen flex items-center justify-center border-b border-border overflow-hidden w-full"> <section className="relative min-h-screen flex items-center justify-center border-b border-border overflow-hidden w-full">
{/* 背景图片 - 充满整个屏幕 */}
<div className="absolute inset-0 z-0 w-full h-full"> <div className="absolute inset-0 z-0 w-full h-full">
<img <img
src="/heilongjiang-spatial-planning.png" src="/heilongjiang-spatial-planning.png"
alt="黑龙江国土空间规划" alt="黑龙江国土空间规划"
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> />
{/* 渐变遮罩,确保文字可读性 */} <div className="absolute inset-0 bg-background/60"></div>
<div className="absolute inset-0 bg-gradient-to-b from-background/40 via-background/30 to-background/50"></div>
</div> </div>
{/* 内容 */}
<div className="relative z-10 max-w-4xl mx-auto px-4 text-center w-full"> <div className="relative z-10 max-w-4xl mx-auto px-4 text-center w-full">
<h1 className="text-5xl font-bold text-foreground mb-6 drop-shadow-lg"> <h1 className="text-5xl font-bold text-foreground mb-6 drop-shadow-lg">
</h1> </h1>
<p className="text-xl text-foreground mb-8 leading-relaxed drop-shadow-md"> <p className="text-xl text-foreground/80 mb-8 leading-relaxed drop-shadow-md max-w-2xl mx-auto">
AI技术的智能学习平台 AI技术的智能学习平台
</p> </p>
<div className="flex flex-col sm:flex-row gap-4 justify-center"> <div className="flex flex-col sm:flex-row gap-4 justify-center">
@@ -134,33 +314,94 @@ export default function HomePageContent() {
</div> </div>
</section> </section>
{/* 主要内容 */} {/* 平台模块 — Senseable 风格网格 */}
<main className="mit-container"> <main className="mit-container">
<section className="py-16">
{/* 功能特性 */} <div className="mb-10">
<section className="py-12 mt-0"> <h2 className="text-3xl font-bold text-foreground mb-2"></h2>
<div className="text-center mb-12"> <p className="text-muted-foreground">AI技术</p>
<h2 className="text-3xl font-bold text-foreground mb-4"></h2>
<p className="text-lg text-muted-foreground">
AI技术
</p>
</div> </div>
<div className="mit-grid"> {/* 搜索与过滤 */}
{features.map((feature, index) => ( <div className="flex flex-col sm:flex-row gap-4 mb-8">
<Card key={index} className="group cursor-pointer hover:scale-105 transition-transform duration-200"> <div className="relative flex-1 max-w-md">
<CardHeader> <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<div className="w-16 h-16 bg-primary/10 rounded-lg flex items-center justify-center mb-4 group-hover:bg-primary/20 transition-colors"> <input
<feature.icon className="w-8 h-8 text-primary" /> type="text"
placeholder="搜索模块..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full h-10 pl-10 pr-4 rounded-md border border-border bg-background text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
<div className="flex flex-wrap gap-2">
<button
onClick={() => setActiveTag(null)}
className={`px-3 py-1.5 text-xs rounded-full border transition-colors ${
activeTag === null
? "bg-foreground text-background border-foreground"
: "bg-background text-muted-foreground border-border hover:border-foreground/30"
}`}
>
</button>
{allTags.map((tag) => (
<button
key={tag}
onClick={() => setActiveTag(activeTag === tag ? null : tag)}
className={`px-3 py-1.5 text-xs rounded-full border transition-colors ${
activeTag === tag
? "bg-foreground text-background border-foreground"
: "bg-background text-muted-foreground border-border hover:border-foreground/30"
}`}
>
{tag}
</button>
))}
</div>
</div>
{/* 模块卡片网格 */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredModules.map((mod) => {
const style = visualStyles[mod.visual];
return (
<Link
key={mod.title}
href={mod.href}
className="group block border border-border rounded-lg overflow-hidden hover:border-foreground/20 transition-colors"
>
{/* 视觉区域 */}
<div className={`relative h-48 bg-gradient-to-br ${style.bg} overflow-hidden`}>
<ModuleIllustration visual={mod.visual} />
</div> </div>
<CardTitle className="text-xl">{feature.title}</CardTitle> {/* 信息区域 */}
<CardDescription className="text-base leading-relaxed"> <div className="p-5">
{feature.description} <h3 className="text-lg font-semibold text-foreground mb-2">{mod.title}</h3>
</CardDescription> <p className="text-sm text-muted-foreground leading-relaxed mb-3">
</CardHeader> {mod.description}
</Card> </p>
))} <div className="flex flex-wrap gap-1.5">
{mod.tags.map((tag) => (
<span
key={tag}
className="px-2 py-0.5 text-xs bg-muted text-muted-foreground rounded"
>
{tag}
</span>
))}
</div>
</div>
</Link>
);
})}
</div> </div>
{filteredModules.length === 0 && (
<div className="text-center py-16 text-muted-foreground">
</div>
)}
</section> </section>
</main> </main>
@@ -168,16 +409,13 @@ export default function HomePageContent() {
<section className="border-y bg-muted/50 py-16"> <section className="border-y bg-muted/50 py-16">
<div className="mit-container"> <div className="mit-container">
<div className="text-center mb-12"> <div className="text-center mb-12">
<h2 className="text-3xl font-bold text-foreground mb-4"></h2> <h2 className="text-3xl font-bold text-foreground mb-2"></h2>
<p className="text-lg text-muted-foreground">
</p>
</div> </div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-8"> <div className="grid grid-cols-2 md:grid-cols-4 gap-8">
{stats.map((stat, index) => ( {stats.map((stat) => (
<div key={index} className="text-center"> <div key={stat.label} className="text-center">
<div className="text-3xl font-bold text-primary mb-2"> <div className="text-3xl font-bold text-foreground mb-2 font-mono tabular-nums">
{isLoadingStats ? "..." : stat.value} {isLoadingStats ? "..." : stat.value}
</div> </div>
<div className="text-sm text-muted-foreground">{stat.label}</div> <div className="text-sm text-muted-foreground">{stat.label}</div>
@@ -187,23 +425,22 @@ export default function HomePageContent() {
</div> </div>
</section> </section>
{/* 统一封底区域 */} {/* Footer */}
<footer className="border-t bg-card/50"> <footer className="border-t bg-card/50">
<div className="mit-container"> <div className="mit-container">
{/* 主要内容区域 - 三列布局(中间留空) */}
<div className="py-12 border-b border-border/50"> <div className="py-12 border-b border-border/50">
<div className="grid grid-cols-1 lg:grid-cols-[2fr_2fr_1fr] gap-8 lg:gap-12"> <div className="grid grid-cols-1 md:grid-cols-3 gap-10 md:gap-8 lg:gap-12">
{/* 第一列:关于项目 */} {/* 关于项目 */}
<div> <div>
<h2 className="text-2xl font-bold text-foreground mb-4"></h2> <h2 className="text-xl font-bold text-foreground mb-3"></h2>
<p className="text-muted-foreground leading-relaxed mb-6"> <p className="text-sm text-muted-foreground leading-relaxed mb-5">
AI技术的智能学习平台RAG技术AI能力 AI技术的国土空间规划智能学习平台RAG检索增强生成
</p> </p>
<div className="flex flex-col sm:flex-row gap-3"> <div className="flex gap-3">
<Button asChild variant="outline" size="sm"> <Button asChild variant="outline" size="sm">
<a href="https://tsp.spacekg.com/#/" target="_blank" rel="noopener noreferrer"> <a href="https://tsp.spacekg.com/#/" target="_blank" rel="noopener noreferrer">
<BookOpen className="mr-2 h-4 w-4" /> <BookOpen className="mr-2 h-4 w-4" />
</a> </a>
</Button> </Button>
<Button asChild variant="outline" size="sm"> <Button asChild variant="outline" size="sm">
@@ -215,38 +452,57 @@ export default function HomePageContent() {
</div> </div>
</div> </div>
{/* 第二列:空白(用于增加间距) */} {/* 快速链接 */}
<div className="hidden lg:block"></div>
{/* 第三列:联系我们 */}
<div> <div>
<h3 className="text-lg font-semibold text-foreground mb-4"></h3> <h3 className="text-lg font-semibold text-foreground mb-4"></h3>
<ul className="space-y-2.5">
{modules.map((mod) => (
<li key={mod.href}>
<Link href={mod.href} className="text-sm text-muted-foreground hover:text-foreground transition-colors">
{mod.title}
</Link>
</li>
))}
<li>
<Link href="/analytics" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
</Link>
</li>
<li>
<Link href="/settings" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
</Link>
</li>
</ul>
</div>
{/* 联系方式 */}
<div>
<h3 className="text-lg font-semibold text-foreground mb-4"></h3>
<ul className="space-y-3"> <ul className="space-y-3">
<li className="flex items-start gap-3"> <li className="flex items-start gap-3">
<Mail className="w-5 h-5 text-muted-foreground mt-0.5 flex-shrink-0" /> <Mail className="w-4 h-4 text-muted-foreground mt-0.5 flex-shrink-0" />
<div> <div>
<p className="text-sm text-muted-foreground"></p> <p className="text-xs text-muted-foreground"></p>
<a href="mailto:contact@hit-agent.com" className="text-sm text-foreground hover:text-primary transition-colors"> <a href="mailto:contact@hit-agent.com" className="text-sm text-foreground hover:text-primary transition-colors">
contact@hit-agent.com contact@hit-agent.com
</a> </a>
</div> </div>
</li> </li>
<li className="flex items-start gap-3"> <li className="flex items-start gap-3">
<Phone className="w-5 h-5 text-muted-foreground mt-0.5 flex-shrink-0" /> <Phone className="w-4 h-4 text-muted-foreground mt-0.5 flex-shrink-0" />
<div> <div>
<p className="text-sm text-muted-foreground"></p> <p className="text-xs text-muted-foreground"></p>
<a href="tel:+86-451-86412114" className="text-sm text-foreground hover:text-primary transition-colors"> <a href="tel:+86-451-86412114" className="text-sm text-foreground hover:text-primary transition-colors">
+86-451-86412114 +86-451-86412114
</a> </a>
</div> </div>
</li> </li>
<li className="flex items-start gap-3"> <li className="flex items-start gap-3">
<MapPin className="w-5 h-5 text-muted-foreground mt-0.5 flex-shrink-0" /> <MapPin className="w-4 h-4 text-muted-foreground mt-0.5 flex-shrink-0" />
<div> <div>
<p className="text-sm text-muted-foreground"></p> <p className="text-xs text-muted-foreground"></p>
<p className="text-sm text-foreground"> <p className="text-sm text-foreground">西92</p>
西92
</p>
</div> </div>
</li> </li>
</ul> </ul>
@@ -254,17 +510,30 @@ export default function HomePageContent() {
</div> </div>
</div> </div>
{/* Footer信息部分 */} {/* 底部版权 */}
<div className="py-8"> <div className="py-6">
<div className="text-center"> <div className="flex flex-col sm:flex-row items-center justify-between gap-4">
<div className="flex items-center justify-center gap-2 mb-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 bg-gradient-to-r from-blue-600 to-blue-800 rounded-lg flex items-center justify-center"> <div className="w-7 h-7 bg-primary rounded flex items-center justify-center">
<BookOpen className="w-4 h-4 text-white" /> <BookOpen className="w-3.5 h-3.5 text-white" />
</div> </div>
<span className="text-lg font-semibold"></span> <span className="text-sm font-medium text-foreground"></span>
</div>
<div className="flex items-center gap-4">
<a href="https://www.hit.edu.cn" target="_blank" rel="noopener noreferrer" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
</a>
<span className="text-muted-foreground/40">|</span>
<a href="https://arch.hit.edu.cn" target="_blank" rel="noopener noreferrer" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
</a>
<span className="text-muted-foreground/40">|</span>
<a href="https://tsp.spacekg.com/#/" target="_blank" rel="noopener noreferrer" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
</a>
</div> </div>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Copyright © {new Date().getFullYear()} &copy; {new Date().getFullYear()} HIT
</p> </p>
</div> </div>
</div> </div>
+2 -2
View File
@@ -23,7 +23,7 @@ export default function Navbar({ isAuthenticated, user }: NavbarProps) {
<div className="flex justify-between items-center h-16"> <div className="flex justify-between items-center h-16">
{/* Logo 和标题 */} {/* Logo 和标题 */}
<Link href="/" className="flex items-center space-x-3 hover:opacity-80 transition-opacity cursor-pointer"> <Link href="/" className="flex items-center space-x-3 hover:opacity-80 transition-opacity cursor-pointer">
<div className="w-10 h-10 bg-gradient-to-r from-blue-600 to-blue-800 rounded-lg flex items-center justify-center shadow-sm"> <div className="w-10 h-10 bg-primary rounded-lg flex items-center justify-center shadow-sm">
<BookOpen className="w-6 h-6 text-white" /> <BookOpen className="w-6 h-6 text-white" />
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col">
@@ -80,7 +80,7 @@ export default function Navbar({ isAuthenticated, user }: NavbarProps) {
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="flex items-center space-x-2 px-3 hover:bg-muted/50"> <Button variant="ghost" className="flex items-center space-x-2 px-3 hover:bg-muted/50">
<Avatar className="w-8 h-8"> <Avatar className="w-8 h-8">
<AvatarFallback className="bg-gradient-to-r from-blue-600 to-blue-800 text-white text-sm font-medium"> <AvatarFallback className="bg-primary text-primary-foreground text-sm font-medium">
{user.full_name?.[0] || user.username[0] || "U"} {user.full_name?.[0] || user.username[0] || "U"}
</AvatarFallback> </AvatarFallback>
</Avatar> </Avatar>
-75
View File
@@ -1,75 +0,0 @@
"use client";
import { NumberTicker } from "@/components/magicui/number-ticker";
import { User } from "@/types";
interface StatsSectionProps {
isAuthenticated: boolean;
user: User | null;
}
export default function StatsSection({ isAuthenticated, user }: StatsSectionProps) {
// 未登录状态 - 全局统计
const globalStats = [
{ label: "智能问答", value: 1000, description: "问题解答" },
{ label: "知识库", value: 50, description: "专业文档" },
{ label: "用户", value: 500, description: "活跃用户" },
{ label: "准确率", value: 95, description: "回答准确率", suffix: "%" },
];
// 登录后状态 - 个人统计(模拟数据,实际应该从API获取)
const personalStats = [
{ label: "我的问答", value: 0, description: "已提问" },
{ label: "我的文档", value: 0, description: "已上传" },
{ label: "学习时长", value: 0, description: "分钟" },
{ label: "知识点", value: 0, description: "已掌握", suffix: "%" },
];
const stats = isAuthenticated ? personalStats : globalStats;
return (
<section className="py-24 sm:py-32 bg-gray-50">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="mx-auto max-w-2xl text-center">
<h2 className="text-base font-semibold leading-7 text-primary">
{isAuthenticated ? "学习统计" : "平台数据"}
</h2>
<p className="mt-2 text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
{isAuthenticated ? "您的学习成果" : "值得信赖的数据"}
</p>
<p className="mt-6 text-lg leading-8 text-gray-600">
{isAuthenticated
? "跟踪您的学习进度,见证知识积累的过程"
: "基于真实用户数据,展示平台的专业性和可靠性"
}
</p>
</div>
<div className="mx-auto mt-16 max-w-2xl sm:mt-20 lg:mt-24 lg:max-w-none">
<dl className="grid grid-cols-1 gap-x-8 gap-y-16 text-center lg:grid-cols-4">
{stats.map((stat) => (
<div key={stat.label} className="mx-auto flex max-w-xs flex-col gap-y-4">
<dt className="text-base leading-7 text-gray-600">
{stat.label}
</dt>
<dd className="order-first text-3xl font-semibold tracking-tight text-gray-900 sm:text-5xl">
<NumberTicker value={stat.value} />
{stat.suffix}
</dd>
<dd className="text-sm leading-6 text-gray-500">
{stat.description}
</dd>
</div>
))}
</dl>
</div>
</div>
</section>
);
}
-389
View File
@@ -1,389 +0,0 @@
"use client";
import { useState } from "react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import {
BookOpen,
MessageSquare,
Image,
BarChart3,
ArrowRight,
CheckCircle,
Users,
Zap,
Brain,
Database,
Mail,
Phone,
MapPin
} from "lucide-react";
import { FlickeringGrid } from "@/components/magicui/flickering-grid";
import { AuroraText } from "@/components/magicui/aurora-text";
import { NumberTicker } from "@/components/magicui/number-ticker";
import { BentoCard, BentoGrid } from "@/components/magicui/bento-grid";
import { ThemeToggle } from "@/components/ui/theme-toggle";
export default function LandingPage() {
const [isHovered, setIsHovered] = useState<string | null>(null);
const features = [
{
icon: MessageSquare,
name: "智能问答",
description: "基于大模型的智能问答系统,支持多轮对话和上下文理解",
href: "#",
cta: "了解更多",
className: "lg:col-start-1 lg:col-end-2 lg:row-start-1 lg:row-end-3",
},
{
icon: BookOpen,
name: "知识库管理",
description: "多模态知识库,支持文档上传、向量化和智能检索",
href: "#",
cta: "了解更多",
className: "lg:col-start-1 lg:col-end-2 lg:row-start-3 lg:row-end-4",
},
{
icon: Image,
name: "空间出图",
description: "结合空间规划知识的图像生成和分析功能",
href: "#",
cta: "了解更多",
className: "lg:col-start-2 lg:col-end-3 lg:row-start-1 lg:row-end-2",
},
{
icon: BarChart3,
name: "学习分析",
description: "学习数据统计和可视化,帮助了解学习进度",
href: "#",
cta: "了解更多",
className: "lg:col-start-2 lg:col-end-3 lg:row-start-2 lg:row-end-3",
},
];
const stats = [
{ label: "智能问答", value: 1000, description: "问题解答" },
{ label: "知识库", value: 50, description: "专业文档" },
{ label: "用户", value: 500, description: "活跃用户" },
{ label: "准确率", value: 95, description: "回答准确率", suffix: "%" },
];
return (
<div className="min-h-screen bg-app">
{/* 导航栏 */}
<nav className="border-b bg-background/80 backdrop-blur-sm sticky top-0 z-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-gradient-to-r from-blue-500 to-purple-600 rounded-xl flex items-center justify-center shadow-lg">
<BookOpen className="w-6 h-6 text-white" />
</div>
<span className="text-xl font-bold">
</span>
</div>
<div className="flex items-center space-x-4">
<ThemeToggle />
<Button variant="ghost" asChild>
<Link href="/login"></Link>
</Button>
<Button asChild>
<Link href="/register"></Link>
</Button>
</div>
</div>
</div>
</nav>
{/* 主要内容 */}
<main>
{/* 英雄区域 */}
<section className="flex h-[90vh] w-full flex-col items-center justify-center pb-15 relative">
<FlickeringGrid
className="absolute inset-0 z-0 [mask-image:radial-gradient(800px_circle_at_center,white,transparent)]"
squareSize={4}
gridGap={4}
color="#60A5FA"
maxOpacity={0.133}
flickerChance={0.1}
/>
<div className="relative z-10 flex flex-col items-center justify-center gap-12 px-4">
<div className="inline-flex items-center px-4 py-2 rounded-full bg-secondary/50 backdrop-blur-sm border border-border mb-4">
<span className="text-sm font-medium">🚀 AI驱动的智能学习平台</span>
</div>
<h1 className="text-center text-4xl font-bold md:text-6xl">
<span className="bg-gradient-to-r from-foreground via-foreground/80 to-foreground/60 bg-clip-text text-transparent">
{" "}
</span>
<AuroraText></AuroraText>
<br />
<span className="text-3xl md:text-5xl"></span>
</h1>
<p className="max-w-4xl p-2 text-center text-sm opacity-85 md:text-xl text-muted-foreground">
</p>
<div className="flex flex-col sm:flex-row gap-6 justify-center">
<Button size="lg" className="text-lg" asChild>
<Link href="/register">
<span className="flex items-center">
<ArrowRight className="ml-2 w-5 h-5" />
</span>
</Link>
</Button>
<Button variant="outline" size="lg" className="text-lg" asChild>
<Link href="/login"></Link>
</Button>
</div>
</div>
</section>
{/* 统计数据 */}
<section className="py-20 bg-secondary/30">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 lg:gap-8">
{stats.map((stat, index) => (
<div
key={index}
className="group text-center p-6 rounded-2xl bg-card backdrop-blur-sm border hover:shadow-lg transition-all duration-300 hover:scale-105"
>
<div className="text-3xl sm:text-4xl font-bold mb-2 group-hover:text-primary transition-colors">
<NumberTicker value={stat.value} />
{stat.suffix || '+'}
</div>
<div className="text-sm sm:text-base font-semibold mb-1">
{stat.label}
</div>
<div className="text-xs sm:text-sm text-muted-foreground">
{stat.description}
</div>
</div>
))}
</div>
</div>
</section>
{/* 核心功能 - Bento Grid */}
<section className="py-24">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="text-center mb-20">
<h2 className="text-4xl font-bold mb-6">
</h2>
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
AI技术
</p>
</div>
<BentoGrid className="lg:grid-cols-2 lg:grid-rows-3">
{features.map((feature) => (
<BentoCard
key={feature.name}
Icon={feature.icon}
name={feature.name}
description={feature.description}
href={feature.href}
cta={feature.cta}
className={feature.className}
/>
))}
</BentoGrid>
</div>
</section>
{/* 技术优势 */}
<section className="py-24 bg-secondary/30">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-center">
<div>
<h2 className="text-4xl font-bold mb-8">
</h2>
<div className="space-y-6">
<div className="flex items-start space-x-4 p-6 rounded-2xl bg-card backdrop-blur-sm border hover:shadow-md transition-all duration-300">
<div className="w-12 h-12 bg-gradient-to-r from-green-500 to-emerald-500 rounded-xl flex items-center justify-center flex-shrink-0">
<Brain className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="text-xl font-bold mb-3">
</h3>
<p className="text-muted-foreground leading-relaxed">
Qwen3-30B模型
</p>
</div>
</div>
<div className="flex items-start space-x-4 p-6 rounded-2xl bg-card backdrop-blur-sm border hover:shadow-md transition-all duration-300">
<div className="w-12 h-12 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-xl flex items-center justify-center flex-shrink-0">
<Database className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="text-xl font-bold mb-3">
RAG知识检索
</h3>
<p className="text-muted-foreground leading-relaxed">
</p>
</div>
</div>
<div className="flex items-start space-x-4 p-6 rounded-2xl bg-card backdrop-blur-sm border hover:shadow-md transition-all duration-300">
<div className="w-12 h-12 bg-gradient-to-r from-purple-500 to-pink-500 rounded-xl flex items-center justify-center flex-shrink-0">
<Image className="w-6 h-6 text-white" />
</div>
<div>
<h3 className="text-xl font-bold mb-3">
</h3>
<p className="text-muted-foreground leading-relaxed">
</p>
</div>
</div>
</div>
</div>
<div className="relative">
<div className="bg-gradient-to-br from-blue-600 via-purple-600 to-pink-600 rounded-3xl p-8 lg:p-12 text-white shadow-2xl">
<div className="flex items-center space-x-4 mb-8">
<div className="w-16 h-16 bg-white/20 rounded-2xl flex items-center justify-center">
<Zap className="w-8 h-8" />
</div>
<h3 className="text-3xl font-bold">AI驱动</h3>
</div>
<p className="text-xl mb-8 leading-relaxed opacity-90">
AI技术
</p>
<div className="flex items-center space-x-4 text-lg">
<Users className="w-6 h-6" />
<span className="font-semibold">500+ </span>
</div>
</div>
</div>
</div>
</div>
</section>
{/* CTA区域 */}
<section className="py-24 bg-gradient-to-r from-blue-600 via-purple-600 to-pink-600 relative overflow-hidden">
<div className="max-w-4xl mx-auto text-center px-4 sm:px-6 lg:px-8 relative z-10">
<h2 className="text-4xl sm:text-5xl font-bold text-white mb-6">
</h2>
<p className="text-xl text-white/90 mb-12 max-w-2xl mx-auto">
AI驱动的国土空间规划学习平台
</p>
<div className="flex flex-col sm:flex-row gap-6 justify-center">
<Button size="lg" className="w-full sm:w-auto bg-white text-blue-600 hover:bg-white/90 px-8 py-4 text-lg font-semibold shadow-2xl" asChild>
<Link href="/register">
<span className="flex items-center">
<ArrowRight className="ml-2 w-5 h-5" />
</span>
</Link>
</Button>
<Button size="lg" variant="outline" className="w-full sm:w-auto text-white border-white/30 hover:bg-white/10 px-8 py-4 text-lg font-semibold" asChild>
<Link href="/login"></Link>
</Button>
</div>
</div>
</section>
</main>
{/* 统一封底区域 */}
<footer className="bg-card text-foreground border-t">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* 主要内容区域 - 三列布局(中间留空) */}
<div className="py-12 border-b border-border/50">
<div className="grid grid-cols-1 lg:grid-cols-[2fr_2fr_1fr] gap-8 lg:gap-12">
{/* 第一列:关于项目 */}
<div>
<h2 className="text-2xl font-bold text-foreground mb-4"></h2>
<p className="text-muted-foreground leading-relaxed mb-6">
AI技术的智能学习平台RAG技术AI能力
</p>
<div className="flex flex-col sm:flex-row gap-3">
<Button asChild variant="outline" size="sm">
<a href="https://tsp.spacekg.com/#/" target="_blank" rel="noopener noreferrer">
<BookOpen className="mr-2 h-4 w-4" />
</a>
</Button>
<Button asChild variant="outline" size="sm">
<a href="https://homepage.hit.edu.cn/wusongtao" target="_blank" rel="noopener noreferrer">
<Users className="mr-2 h-4 w-4" />
</a>
</Button>
</div>
</div>
{/* 第二列:展示图片 */}
<div className="hidden lg:flex items-center justify-center">
<div className="relative w-full max-w-xs">
<img
src="/heilongjiang-spatial-planning.png"
alt="黑龙江国土空间规划"
className="w-full h-auto rounded-lg shadow-lg object-cover"
/>
</div>
</div>
{/* 第三列:联系我们 */}
<div>
<h3 className="text-lg font-semibold text-foreground mb-4"></h3>
<ul className="space-y-3">
<li className="flex items-start gap-3">
<Mail className="w-5 h-5 text-muted-foreground mt-0.5 flex-shrink-0" />
<div>
<p className="text-sm text-muted-foreground"></p>
<a href="mailto:contact@hit-agent.com" className="text-sm text-foreground hover:text-primary transition-colors">
contact@hit-agent.com
</a>
</div>
</li>
<li className="flex items-start gap-3">
<Phone className="w-5 h-5 text-muted-foreground mt-0.5 flex-shrink-0" />
<div>
<p className="text-sm text-muted-foreground"></p>
<a href="tel:+86-451-86412114" className="text-sm text-foreground hover:text-primary transition-colors">
+86-451-86412114
</a>
</div>
</li>
<li className="flex items-start gap-3">
<MapPin className="w-5 h-5 text-muted-foreground mt-0.5 flex-shrink-0" />
<div>
<p className="text-sm text-muted-foreground"></p>
<p className="text-sm text-foreground">
西92
</p>
</div>
</li>
</ul>
</div>
</div>
</div>
{/* Footer信息部分 */}
<div className="py-8">
<div className="text-center">
<div className="flex items-center justify-center gap-2 mb-3">
<div className="w-8 h-8 bg-gradient-to-r from-blue-600 to-blue-800 rounded-lg flex items-center justify-center">
<BookOpen className="w-4 h-4 text-white" />
</div>
<span className="text-lg font-semibold"></span>
</div>
<p className="text-xs text-muted-foreground">
Copyright © {new Date().getFullYear()}
</p>
</div>
</div>
</div>
</footer>
</div>
);
}
+1 -1
View File
@@ -28,7 +28,7 @@ export const authOptions: NextAuthOptions = {
const { username, password } = validatedFields.data; const { username, password } = validatedFields.data;
// 调用后端API进行认证 // 调用后端API进行认证
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/login`, { const response = await fetch(`${process.env.BACKEND_INTERNAL_URL || 'http://127.0.0.1:8000'}/auth/login`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",