-
-
+
+
+
+ {/* Mobile overlay */}
+ {sidebarOpen && (
+
setSidebarOpen(false)} />
+ )}
+
+ {/* Main content */}
+
+ {/* Top bar */}
+
+
+
+
+
+ {activeLesson?.title || course.title}
+
- )}
+
+
+ {course.isFree ? (
+
+ 免费
+
+ ) : (
+ <>
+
+ ¥{course.price}
+
+
+ >
+ )}
+ {course.category && (
+ {course.category.name}
+ )}
+
+
+
+ {/* Lesson content */}
+
+ {activeLesson ? (
+
+ {activeLesson.title}
+
+ {activeLesson.content ? (
+
+ {activeLesson.content}
+
+ ) : (
+
暂无内容
+ )}
+
+
+ ) : (
+
+ )}
+
-
+ >
);
-}
+}
\ No newline at end of file
diff --git a/frontend/src/app/courses/layout.tsx b/frontend/src/app/courses/layout.tsx
new file mode 100644
index 0000000..ac838d2
--- /dev/null
+++ b/frontend/src/app/courses/layout.tsx
@@ -0,0 +1,39 @@
+import type { Metadata } from 'next';
+
+export const metadata: Metadata = {
+ title: 'AI 课程 - AI 学习教程',
+ description: '宇之然 AI 课程提供全面的 AI 学习教程,包括 AI 通识、提示词工程、智能体开发、模型百科、AI 沙盒实战等课程,让每个人都能学会使用 AI。',
+ keywords: ['AI课程', 'AI教程', 'AI学习', 'AI通识', '提示词工程', '智能体教程', 'AI模型百科', 'AI沙盒', 'AI实战', 'ChatGPT教程', 'Claude教程', 'Midjourney教程', 'AI绘画教程', 'AI写作教程'],
+ robots: {
+ index: true,
+ follow: true,
+ googleBot: {
+ index: true,
+ follow: true,
+ },
+ },
+ openGraph: {
+ title: 'AI 课程 - AI 学习教程',
+ description: '宇之然 AI 课程提供全面的 AI 学习教程,让每个人都能学会使用 AI。',
+ type: 'website',
+ url: 'https://yuzhiran.com/courses',
+ images: [
+ {
+ url: '/images/og-image.png',
+ width: 1200,
+ height: 630,
+ alt: 'AI 课程',
+ },
+ ],
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title: 'AI 课程 - AI 学习教程',
+ description: '宇之然 AI 课程提供全面的 AI 学习教程,让每个人都能学会使用 AI。',
+ images: ['/images/og-image.png'],
+ },
+};
+
+export default function CoursesLayout({ children }: { children: React.ReactNode }) {
+ return children;
+}
\ No newline at end of file
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
index eda3db1..4c83f91 100755
--- a/frontend/src/app/globals.css
+++ b/frontend/src/app/globals.css
@@ -63,3 +63,130 @@
font-family: 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
}
+
+/* Article body styles for markdown content */
+.article-body {
+ font-size: 1rem;
+ line-height: 1.8;
+}
+
+.article-body h1,
+.article-body h2,
+.article-body h3 {
+ margin-top: 1.5em;
+ margin-bottom: 0.5em;
+ font-weight: 700;
+ line-height: 1.3;
+}
+
+.article-body h1 {
+ font-size: 1.5rem;
+ border-bottom: 1px solid var(--border);
+ padding-bottom: 0.5em;
+}
+
+.article-body h2 {
+ font-size: 1.25rem;
+ padding-left: 0.5em;
+ border-left: 3px solid var(--primary);
+}
+
+.article-body h3 {
+ font-size: 1.125rem;
+}
+
+.article-body p {
+ margin-bottom: 1em;
+}
+
+.article-body strong {
+ font-weight: 600;
+ color: var(--foreground);
+}
+
+.article-body a {
+ color: var(--primary);
+ text-decoration: none;
+ border-bottom: 1px solid transparent;
+ transition: border-bottom-color 0.2s;
+}
+
+.article-body a:hover {
+ border-bottom-color: var(--primary);
+}
+
+.article-body blockquote {
+ margin: 1em 0;
+ padding: 0.75em 1em;
+ border-left: 3px solid var(--primary);
+ background: var(--muted);
+ color: var(--muted-foreground);
+ border-radius: 0 0.5em 0.5em 0;
+ font-style: normal;
+}
+
+.article-body blockquote p {
+ margin-bottom: 0;
+}
+
+.article-body ul,
+.article-body ol {
+ margin-bottom: 1em;
+ padding-left: 1.5em;
+}
+
+.article-body li {
+ margin-bottom: 0.25em;
+}
+
+.article-body hr {
+ margin: 2em 0;
+ border: none;
+ border-top: 1px solid var(--border);
+}
+
+.article-body code {
+ font-family: 'Fira Code', 'JetBrains Mono', 'Menlo', monospace;
+ font-size: 0.875em;
+ padding: 0.125em 0.375em;
+ background: var(--muted);
+ border-radius: 0.25em;
+}
+
+.article-body pre {
+ margin: 1em 0;
+ padding: 1em;
+ background: var(--muted);
+ border-radius: 0.5em;
+ overflow-x: auto;
+ font-size: 0.875em;
+}
+
+.article-body pre code {
+ background: none;
+ padding: 0;
+}
+
+.article-body img {
+ max-width: 100%;
+ height: auto;
+ border-radius: 0.5em;
+ margin: 1em 0;
+}
+
+.article-body table {
+ width: 100%;
+ border-collapse: collapse;
+ margin: 1em 0;
+}
+
+.article-body th,
+.article-body td {
+ padding: 0.5em 0.75em;
+ border: 1px solid var(--border);
+}
+
+.article-body th {
+ background: var(--muted);
+ font-weight: 600;
+}
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
index 049b961..890b10e 100755
--- a/frontend/src/app/layout.tsx
+++ b/frontend/src/app/layout.tsx
@@ -9,26 +9,120 @@ export const metadata: Metadata = {
default: '宇之然 AI - AI 工具与知识社区',
template: '%s | 宇之然 AI',
},
- description: '宇之然 AI 是面向大众化分领域用户的 AI 工具与知识社区,涵盖 AI 通识、提示词工程、智能体教程、模型百科等,让每个人都能用好 AI。',
- keywords: ['AI', '人工智能', '学习', '提示词', '智能体', '大模型', '宇之然'],
- icons: {
- icon: '/favicon.png',
- shortcut: '/favicon.png',
- apple: '/icon.svg',
+ description: '宇之然 AI 是面向大众化分领域用户的 AI 工具与知识社区,涵盖 AI 通识、提示词工程、智能体教程、模型百科、AI 沙盒实战等,让每个人都能用好 AI。',
+ keywords: ['AI', '人工智能', '学习', '提示词', '智能体', '大模型', '宇之然', 'AI工具', 'AI教程', 'AI沙盒', '提示词工程', 'AI社区', '知识库', '编程学习', 'AI工具返利', 'AI工具联盟', 'AI工具推荐', 'AI工具测评'],
+ robots: {
+ index: true,
+ follow: true,
+ googleBot: {
+ index: true,
+ follow: true,
+ 'max-image-preview': 'large',
+ 'max-video-preview': -1,
+ 'max-snippet': -1,
+ },
+ },
+ authors: [{ name: '宇之然科技', url: 'https://yuzhiran.com' }],
+ creator: '宇之然科技',
+ publisher: '宇之然科技',
+ formatDetection: {
+ email: false,
+ address: false,
+ telephone: false,
+ },
+ metadataBase: new URL(SITE_URL),
+ alternates: {
+ canonical: '/',
+ languages: {
+ 'zh-CN': '/',
+ 'en': '/en',
+ },
},
openGraph: {
type: 'website',
locale: 'zh_CN',
siteName: '宇之然 AI',
title: '宇之然 AI - AI 工具与知识社区',
- description: '让每个人都能用好 AI',
+ description: '让每个人都能用好 AI - AI 工具指南、提示词库、智能体教程、模型百科、AI 沙盒实战',
url: SITE_URL,
+ images: [
+ {
+ url: '/images/og-image.png',
+ width: 1200,
+ height: 630,
+ alt: '宇之然 AI - AI 工具与知识社区',
+ },
+ ],
},
+ twitter: {
+ card: 'summary_large_image',
+ title: '宇之然 AI - AI 工具与知识社区',
+ description: '让每个人都能用好 AI - AI 工具指南、提示词库、智能体教程、模型百科、AI 沙盒实战',
+ images: ['/images/og-image.png'],
+ },
+ verification: {
+ google: 'your-google-verification-code',
+ yandex: 'your-yandex-verification-code',
+ },
+ icons: {
+ icon: [
+ { url: '/favicon.ico', sizes: 'any' },
+ { url: '/favicon.png', type: 'image/png' },
+ ],
+ shortcut: '/favicon.png',
+ apple: [
+ { url: '/apple-icon.png', sizes: '180x180', type: 'image/png' },
+ ],
+ other: [
+ {
+ rel: 'apple-touch-icon-precomposed',
+ url: '/apple-icon-precomposed.png',
+ },
+ ],
+ },
+ manifest: '/site.webmanifest',
+ appleWebApp: {
+ capable: true,
+ title: '宇之然 AI',
+ statusBarStyle: 'default',
+ },
+ themeColor: [
+ { media: '(prefers-color-scheme: light)', color: '#ffffff' },
+ { media: '(prefers-color-scheme: dark)', color: '#0a0a0a' },
+ ],
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
+ const jsonLd = {
+ '@context': 'https://schema.org',
+ '@type': 'WebSite',
+ name: '宇之然 AI',
+ url: SITE_URL,
+ description: '面向大众化分领域用户的 AI 工具与知识社区,涵盖 AI 通识、提示词工程、智能体教程、模型百科、AI 沙盒实战等',
+ potentialAction: {
+ '@type': 'SearchAction',
+ target: `${SITE_URL}/search?q={search_term_string}`,
+ 'query-input': 'required name=search_term_string',
+ },
+ publisher: {
+ '@type': 'Organization',
+ name: '北京宇之然科技中心',
+ url: SITE_URL,
+ logo: `${SITE_URL}/favicon.png`,
+ },
+ };
+
return (
+
+
+
+
+
+
{children}
', html, re.DOTALL | re.IGNORECASE)
+ assert ld_match, f"Missing JSON-LD structured data on {path}"
+ ld_content = ld_match.group(1).strip()
+ try:
+ data = json.loads(ld_content)
+ assert '@context' in data, f"JSON-LD missing @context on {path}"
+ assert '@type' in data, f"JSON-LD missing @type on {path}"
+ except json.JSONDecodeError:
+ assert False, f"Invalid JSON-LD on {path}"
+
+def check_performance(html, path="/"):
+ """检查性能相关优化"""
+ # Check for inline scripts (should be minimal)
+ inline_scripts = re.findall(r')([^<]{100,})', html, re.DOTALL | re.IGNORECASE)
+ # Large inline scripts are bad for performance
+ large_scripts = [s for s in inline_scripts if len(s) > 5000]
+ assert len(large_scripts) <= 2, f"Too many large inline scripts ({len(large_scripts)}) on {path}"
+
+ # Check for preconnect hints
+ preconnect = re.findall(r'
]+rel=["\']preconnect["\']', html, re.IGNORECASE)
+ # Not required but good to have
+ print(f" Preconnect hints: {len(preconnect)}")
+
+def check_semantic_html(html, path="/"):
+ """检查语义化 HTML"""
+ # Should have proper heading structure
+ h1_count = len(re.findall(r'