From b5260f1836538e56fb95f27c48f11b9b31fc316b Mon Sep 17 00:00:00 2001 From: lsy Date: Sun, 23 Mar 2025 01:42:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=AF=95=E4=B8=9A=E8=AE=BE=E8=AE=A1,=E7=AC=AC?= =?UTF-8?q?=E4=B8=80=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rust/svg/vite.config.ts | 3 + web/blog/echoer | 1 + web/blog/echoes | 1 + web/czr/app/components/Footer.tsx | 48 + web/czr/app/components/HomeCarousel.tsx | 129 +++ web/czr/app/components/Layout.tsx | 18 + web/czr/app/routes/_index.tsx | 151 +-- web/czr/app/routes/about.tsx | 106 ++- web/czr/app/routes/innovations.tsx | 206 ++-- web/czr/app/routes/solutions.tsx | 146 ++- web/czr/server/entry.server.js | 42 - web/czr/server/env.ts | 32 - web/czr/server/express.ts | 71 -- web/czr/server/production.js | 23 - web/czr/server/static.js | 31 - web/czr/src/entry.client.tsx | 7 - web/graduation/README.md | 48 + web/graduation/astro.config.mjs | 11 + web/graduation/package.json | 23 + web/graduation/public/favicon.svg | 20 + web/graduation/src/assets/astro.svg | 1 + web/graduation/src/assets/background.svg | 1 + .../src/components/DarkModeTransition.astro | 41 + .../src/components/ThemeToggle.astro | 70 ++ .../aceternity/AnimatedGallery.astro | 109 +++ .../src/components/aceternity/FlipCard.astro | 63 ++ .../aceternity/FloatingNavbar.astro | 54 ++ .../components/aceternity/GradientText.astro | 63 ++ .../src/components/aceternity/HoverGlow.astro | 70 ++ .../aceternity/MagneticElement.astro | 144 +++ .../components/aceternity/MorphingText.astro | 152 +++ .../components/aceternity/NeonButton.astro | 159 ++++ .../components/aceternity/ParallaxCard.astro | 237 +++++ .../aceternity/ParallaxSection.astro | 91 ++ .../aceternity/ParticleButton.astro | 129 +++ .../components/aceternity/ScrollReveal.astro | 149 +++ .../components/aceternity/SmoothScroll.astro | 106 +++ .../components/aceternity/SpotlightCard.astro | 265 ++++++ .../src/components/common/CardGrid.astro | 30 + .../src/components/common/CarouselCard.astro | 117 +++ .../components/common/CategoryFilter.astro | 40 + .../src/components/common/ItemCard.astro | 69 ++ .../src/components/common/PageHeader.astro | 18 + .../src/components/common/SearchBar.astro | 33 + .../components/common/SectionContainer.astro | 61 ++ .../components/common/SectionHeading.astro | 39 + .../common/SmoothCardCarousel.astro | 331 +++++++ .../attractions/chengde-summer-resort.md | 64 ++ web/graduation/src/content/config.ts | 75 ++ .../src/content/cuisine/donkey-meat-burger.md | 93 ++ web/graduation/src/content/culture/jingju.md | 86 ++ .../src/content/travel/summer-guide.md | 193 ++++ web/graduation/src/layouts/MainLayout.astro | 145 +++ .../src/pages/attractions/[slug].astro | 308 ++++++ .../src/pages/attractions/index.astro | 661 +++++++++++++ web/graduation/src/pages/cuisine/[slug].astro | 177 ++++ web/graduation/src/pages/cuisine/index.astro | 893 ++++++++++++++++++ web/graduation/src/pages/culture/[slug].astro | 193 ++++ web/graduation/src/pages/culture/index.astro | 763 +++++++++++++++ .../src/pages/hover-effects-new.astro | 227 +++++ web/graduation/src/pages/index.astro | 96 ++ web/graduation/src/pages/travel/[slug].astro | 249 +++++ web/graduation/src/pages/travel/index.astro | 452 +++++++++ web/graduation/src/styles/global.css | 229 +++++ web/graduation/tsconfig.json | 5 + 65 files changed, 8107 insertions(+), 561 deletions(-) create mode 160000 web/blog/echoer create mode 160000 web/blog/echoes create mode 100644 web/czr/app/components/Footer.tsx create mode 100644 web/czr/app/components/HomeCarousel.tsx create mode 100644 web/czr/app/components/Layout.tsx delete mode 100644 web/czr/server/entry.server.js delete mode 100644 web/czr/server/env.ts delete mode 100644 web/czr/server/express.ts delete mode 100644 web/czr/server/production.js delete mode 100644 web/czr/server/static.js delete mode 100644 web/czr/src/entry.client.tsx create mode 100644 web/graduation/README.md create mode 100644 web/graduation/astro.config.mjs create mode 100644 web/graduation/package.json create mode 100644 web/graduation/public/favicon.svg create mode 100644 web/graduation/src/assets/astro.svg create mode 100644 web/graduation/src/assets/background.svg create mode 100644 web/graduation/src/components/DarkModeTransition.astro create mode 100644 web/graduation/src/components/ThemeToggle.astro create mode 100644 web/graduation/src/components/aceternity/AnimatedGallery.astro create mode 100644 web/graduation/src/components/aceternity/FlipCard.astro create mode 100644 web/graduation/src/components/aceternity/FloatingNavbar.astro create mode 100644 web/graduation/src/components/aceternity/GradientText.astro create mode 100644 web/graduation/src/components/aceternity/HoverGlow.astro create mode 100644 web/graduation/src/components/aceternity/MagneticElement.astro create mode 100644 web/graduation/src/components/aceternity/MorphingText.astro create mode 100644 web/graduation/src/components/aceternity/NeonButton.astro create mode 100644 web/graduation/src/components/aceternity/ParallaxCard.astro create mode 100644 web/graduation/src/components/aceternity/ParallaxSection.astro create mode 100644 web/graduation/src/components/aceternity/ParticleButton.astro create mode 100644 web/graduation/src/components/aceternity/ScrollReveal.astro create mode 100644 web/graduation/src/components/aceternity/SmoothScroll.astro create mode 100644 web/graduation/src/components/aceternity/SpotlightCard.astro create mode 100644 web/graduation/src/components/common/CardGrid.astro create mode 100644 web/graduation/src/components/common/CarouselCard.astro create mode 100644 web/graduation/src/components/common/CategoryFilter.astro create mode 100644 web/graduation/src/components/common/ItemCard.astro create mode 100644 web/graduation/src/components/common/PageHeader.astro create mode 100644 web/graduation/src/components/common/SearchBar.astro create mode 100644 web/graduation/src/components/common/SectionContainer.astro create mode 100644 web/graduation/src/components/common/SectionHeading.astro create mode 100644 web/graduation/src/components/common/SmoothCardCarousel.astro create mode 100644 web/graduation/src/content/attractions/chengde-summer-resort.md create mode 100644 web/graduation/src/content/config.ts create mode 100644 web/graduation/src/content/cuisine/donkey-meat-burger.md create mode 100644 web/graduation/src/content/culture/jingju.md create mode 100644 web/graduation/src/content/travel/summer-guide.md create mode 100644 web/graduation/src/layouts/MainLayout.astro create mode 100644 web/graduation/src/pages/attractions/[slug].astro create mode 100644 web/graduation/src/pages/attractions/index.astro create mode 100644 web/graduation/src/pages/cuisine/[slug].astro create mode 100644 web/graduation/src/pages/cuisine/index.astro create mode 100644 web/graduation/src/pages/culture/[slug].astro create mode 100644 web/graduation/src/pages/culture/index.astro create mode 100644 web/graduation/src/pages/hover-effects-new.astro create mode 100644 web/graduation/src/pages/index.astro create mode 100644 web/graduation/src/pages/travel/[slug].astro create mode 100644 web/graduation/src/pages/travel/index.astro create mode 100644 web/graduation/src/styles/global.css create mode 100644 web/graduation/tsconfig.json diff --git a/rust/svg/vite.config.ts b/rust/svg/vite.config.ts index 8b0f57b..4d39f66 100644 --- a/rust/svg/vite.config.ts +++ b/rust/svg/vite.config.ts @@ -4,4 +4,7 @@ import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + server:{ + host:"0.0.0.0" + } }) diff --git a/web/blog/echoer b/web/blog/echoer new file mode 160000 index 0000000..5a6c020 --- /dev/null +++ b/web/blog/echoer @@ -0,0 +1 @@ +Subproject commit 5a6c020920c343afe497583a19a7be3f128e1edd diff --git a/web/blog/echoes b/web/blog/echoes new file mode 160000 index 0000000..9f5c3f6 --- /dev/null +++ b/web/blog/echoes @@ -0,0 +1 @@ +Subproject commit 9f5c3f602557996d71aa698bd112656212c17060 diff --git a/web/czr/app/components/Footer.tsx b/web/czr/app/components/Footer.tsx new file mode 100644 index 0000000..ffb46b0 --- /dev/null +++ b/web/czr/app/components/Footer.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +export default function Footer() { + return ( + + ); +} \ No newline at end of file diff --git a/web/czr/app/components/HomeCarousel.tsx b/web/czr/app/components/HomeCarousel.tsx new file mode 100644 index 0000000..3b26bb8 --- /dev/null +++ b/web/czr/app/components/HomeCarousel.tsx @@ -0,0 +1,129 @@ +import { useState, useEffect } from 'react'; + +const images = [ + { + url: "/b1.jpg", + alt: "绿色科技", + title: "创新环保技术" + }, + { + url: "/b2.jpg", + alt: "可持续发展", + title: "可持续未来" + }, + { + url: "/b3.jpg", + alt: "科技创新", + title: "引领科技创新" + } +]; + +export default function HomeCarousel() { + const [currentIndex, setCurrentIndex] = useState(0); + + useEffect(() => { + const timer = setInterval(() => { + setCurrentIndex((prevIndex) => + prevIndex === images.length - 1 ? 0 : prevIndex + 1 + ); + }, 5000); + + return () => clearInterval(timer); + }, []); + + const nextSlide = () => { + setCurrentIndex((prevIndex) => + prevIndex === images.length - 1 ? 0 : prevIndex + 1 + ); + }; + + const prevSlide = () => { + setCurrentIndex((prevIndex) => + prevIndex === 0 ? images.length - 1 : prevIndex - 1 + ); + }; + + return ( +
+
+ {/* 图片容器 */} +
+ {images.map((image, index) => ( +
+ {image.alt} +
+
+

{image.title}

+
+
+
+ ))} +
+ + {/* 修改控制按钮样式 */} + + + + {/* 指示器 */} +
+ {images.map((_, index) => ( +
+
+
+ ); +} \ No newline at end of file diff --git a/web/czr/app/components/Layout.tsx b/web/czr/app/components/Layout.tsx new file mode 100644 index 0000000..459f21f --- /dev/null +++ b/web/czr/app/components/Layout.tsx @@ -0,0 +1,18 @@ +import Navigation from "./Navigation"; +import Footer from "./Footer"; + +interface LayoutProps { + children: React.ReactNode; +} + +export default function Layout({ children }: LayoutProps) { + return ( +
+ +
+ {children} +
+
+
+ ); +} \ No newline at end of file diff --git a/web/czr/app/routes/_index.tsx b/web/czr/app/routes/_index.tsx index 4991238..1e8d125 100644 --- a/web/czr/app/routes/_index.tsx +++ b/web/czr/app/routes/_index.tsx @@ -1,4 +1,6 @@ import type { MetaFunction } from "@remix-run/node"; +import Layout from "~/components/Layout"; +import HomeCarousel from "~/components/HomeCarousel"; export const meta: MetaFunction = () => { return [ @@ -9,138 +11,27 @@ export const meta: MetaFunction = () => { export default function Index() { return ( -
- - {/* Hero区域 */} -
-
-
-

- 创新科技,守护地球 -

-

- 致力于开发环保科技解决方案,用创新力量推动可持续发展 -

- + +
+ {/* Hero区域 */} +
+
+ +
+

+ 创新科技,守护地球 +

+

+ 致力于开发环保科技解决方案,用创新力量推动可持续发展 +

+ +
- - {/* 核心技术 */} -
-
-

核心创新技术

-
-
-
- - - -
-

智能环保系统

-

采用AI技术优化资源利用,提高能源使用效率

-
- -
-
- - - -
-

新能源转换

-

创新能源转换技术,实现清洁能源的高效利用

-
- -
-
- - - -
-

生态监测

-

全方位环境监测系统,保护生态平衡

-
-
-
-
- - {/* 解决方案 */} -
-
-

创新解决方案

-
-
-

智慧城市环保系统

-

整合城市环境数据,提供智能化环保解决方案

-
    -
  • • 空气质量实时监测
  • -
  • • 智能垃圾分类系统
  • -
  • • 城市能源管理优化
  • -
-
- -
-

工业节能方案

-

为工业企业提供全方位的节能减排解决方案

-
    -
  • • 能源使用效率优化
  • -
  • • 废物循环利用系统
  • -
  • • 清洁生产技术改造
  • -
-
-
-
-
- - {/* 页脚 */} -
-
-
-
-

关于我们

-

新纪元科技致力于环保科技创新,为地球可持续发展贡献力量

-
-
-

联系方式

-

电话:400-888-8888

-

邮箱:contact@xingjiyuan.com

-
-
-

解决方案

- -
-
-

公司地址

-

中国上海市浦东新区科技创新大道888号

-
-
-
-

© 2024 新纪元科技 版权所有

-
-
-
-
+ ); } \ No newline at end of file diff --git a/web/czr/app/routes/about.tsx b/web/czr/app/routes/about.tsx index c6d3b9b..a92e851 100644 --- a/web/czr/app/routes/about.tsx +++ b/web/czr/app/routes/about.tsx @@ -1,4 +1,6 @@ import type { MetaFunction } from "@remix-run/node"; +import Layout from "~/components/Layout"; + export const meta: MetaFunction = () => { return [ { title: "关于我们 - 新纪元科技" }, @@ -8,60 +10,70 @@ export const meta: MetaFunction = () => { export default function About() { return ( -
- - {/* 页面标题 */} -
-
-

- 关于我们 -

-

- 致力于用科技创新推动环保事业发展 -

+ +
+ {/* 页面标题 */} +
+
+

+ 关于我们 +

+

+ 致力于用科技创新推动环保事业发展 +

+
-
- {/* 公司介绍 */} -
-
-
-
-

- 公司简介 -

-

- 新纪元科技成立于2020年,是一家专注于环保科技创新的高新技术企业。我们致力于通过技术创新解决环境问题,推动可持续发展。 -

-

- 公司拥有一支专业的研发团队,在环保技术领域具有深厚的积累和创新能力。 -

-
-
-

- 愿景使命 -

-
-
-

愿景

-

成为全球领先的环保科技创新企业

-
-
-

使命

-

用科技创新守护地球家园

+ {/* 公司介绍 */} +
+
+
+
+

+ 公司简介 +

+

+ 新纪元科技成立于2020年,是一家专注于环保科技创新的高新技术企业。我们致力于通过技术创新解决环境问题,推动可持续发展。 +

+

+ 公司拥有一支专业的研发团队,在环保技术领域具有深厚的积累和创新能力。 +

+
+
+

+ 愿景使命 +

+
+
+

+ + + + 愿景 +

+

+ 成为全球领先的环保科技创新企业 +

+
+
+

+ + + + 使命 +

+

+ 用科技创新守护地球家园 +

+
- - {/* 页脚 */} -
- {/* 同首页页脚内容 */} -
-
+ ); } \ No newline at end of file diff --git a/web/czr/app/routes/innovations.tsx b/web/czr/app/routes/innovations.tsx index 9e63e47..dfe8255 100644 --- a/web/czr/app/routes/innovations.tsx +++ b/web/czr/app/routes/innovations.tsx @@ -2,6 +2,7 @@ import type { MetaFunction } from "@remix-run/node"; import { ImageLoader } from "hooks/ParticleImage"; import { useLoaderData } from "@remix-run/react"; import { Carousel } from "~/components/Carousel"; +import Layout from "~/components/Layout"; export const meta: MetaFunction = () => { return [ @@ -34,124 +35,121 @@ export default function Innovations() { const { isClient, innovations } = useLoaderData(); return ( -
- {/* 头部区域:标题 + 轮播图 */} -
-
- {/* 标题部分 */} -
-

- 创新技术 -

-

- 引领环保科技发展,推动行业技术革新 -

-
+ +
+ {/* 头部区域:标题 + 轮播图 */} +
+
+ {/* 标题部分 */} +
+

+ 创新技术 +

+

+ 引领环保科技发展,推动行业技术革新 +

+
- {/* 轮播图部分 */} - {isClient ? ( -
-
-
-
- ({ - content: ( -
- -
-
-

{innovation.title}

-

探索环保科技的无限可能

+ {/* 轮播图部分 */} + {isClient ? ( +
+
+
+
+ ({ + content: ( +
+ +
+
+

{innovation.title}

+

探索环保科技的无限可能

+
-
- ), - }))} - interval={5000} - /> + ), + }))} + interval={5000} + /> +
-
- ) : ( -
-
-
- )} + ) : ( +
+
+
+ )} +
-
- {/* 技术详情部分 */} -
-
-

- - 核心技术详解 - -

-
-
-
- - - + {/* 技术详情部分 */} +
+
+

+ + 核心技术详解 + +

+
+
+
+ + + +
+

+ AI环境优化 + +

+

+ 运用人工智能技术,实现环境数据的智能分析和决策优化,提供精准的环境治理方案。 +

-

- AI环境优化 - -

-

- 运用人工智能技术,实现环境数据的智能分析和决策优化,提供精准的环境治理方案。 -

-
-
-
- - - +
+
+ + + +
+

+ 清洁能源转换 + +

+

+ 创新的能源转换技术,提高清洁能源利用效率,推动色能源革命。 +

-

- 清洁能源转换 - -

-

- 创新的能源转换技术,提高清洁能源利用效率,推动色能源革命。 -

-
-
-
- - - +
+
+ + + +
+

+ 生态修复系统 + +

+

+ 综合性生态环境修复解决方案,助力自然生态系统恢复与保护 +

-

- 生态修复系统 - -

-

- 综合性生态环境修复解决方案,助力自然生态系统恢复与保护 -

- - {/* 页脚 */} -
- {/* 同首页页脚内容 */} -
-
+ ); } \ No newline at end of file diff --git a/web/czr/app/routes/solutions.tsx b/web/czr/app/routes/solutions.tsx index 5ff58a4..21ba1db 100644 --- a/web/czr/app/routes/solutions.tsx +++ b/web/czr/app/routes/solutions.tsx @@ -1,4 +1,5 @@ import type { MetaFunction } from "@remix-run/node"; +import Layout from "~/components/Layout"; import { ImageLoader } from "hooks/ParticleImage"; import { useLoaderData } from "@remix-run/react"; @@ -17,89 +18,86 @@ export default function Solutions() { const { isClient } = useLoaderData(); return ( -
- {/* 页面标题和轮播图 */} -
-
-
- {isClient ? ( -
- {/* 轮播图代码从这里开始 */} -
- ) : ( -
- )} -

- 解决方案 -

-

- 为不同行业提供定制化的环保科技解决方案,助力企业实现可持续发展 -

+ +
+ {/* 页面标题和轮播图 */} +
+
+
+ {isClient ? ( +
+ {/* 轮播图代码从这里开始 */} +
+ ) : ( +
+ )} +

+ 解决方案 +

+

+ 为不同行业提供定制化的环保科技解决方案,助力企业实现可持续发展 +

+
-
- {/* 解决方案详情 */} -
-
-
-
-

- 智慧城市解决方案 - -

-
-

通过智能技术优化城市环境管理

-
    -
  • - • 智能环境监测系统 -
  • -
  • - • 城市垃圾分类管理 -
  • -
  • - • 智慧能源管理平台 -
  • -
  • - • 城市空气质量优化 -
  • -
+ {/* 解决方案详情 */} +
+
+
+
+

+ 智慧城市解决方案 + +

+
+

通过智能技能优化城市环境管理

+
    +
  • + • 智能环境监测系统 +
  • +
  • + • 城市垃圾分类管理 +
  • +
  • + • 智慧能源管理平台 +
  • +
  • + • 城市空气质量优化 +
  • +
+
-
-
-

- 工业节能方案 - -

-
-

帮助工业企业实现节能减排

-
    -
  • - • 工业能源审计 -
  • -
  • - • 节能改造方案 -
  • -
  • - • 废物循环利用 -
  • -
  • - • 清洁生产技术 -
  • -
+
+

+ 工业节能方案 + +

+
+

帮助工业企业实现节能减排

+
    +
  • + • 工业能源审计 +
  • +
  • + • 节能改造方案 +
  • +
  • + • 废物循环利用 +
  • +
  • + • 清洁生产技术 +
  • +
+
- - {/* 页脚 - 可以提取为共享组件 */} -
- {/* 同首页页脚内容 */} -
-
+ ); } \ No newline at end of file diff --git a/web/czr/server/entry.server.js b/web/czr/server/entry.server.js deleted file mode 100644 index fd8609e..0000000 --- a/web/czr/server/entry.server.js +++ /dev/null @@ -1,42 +0,0 @@ -import { renderToString } from 'react-dom/server'; -import { RemixServer } from '@remix-run/react'; -import { createReadStream, createWriteStream } from 'fs'; -import { mkdir } from 'fs/promises'; -import { dirname, resolve } from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const projectRoot = resolve(__dirname, '..'); - -async function generateHTML() { - try { - const distDir = resolve(projectRoot, 'dist'); - await mkdir(distDir, { recursive: true }); - - const html = ` - - - - - - Your App - - -
- - -`; - - const indexPath = resolve(distDir, 'index.html'); - const writer = createWriteStream(indexPath); - writer.write(html); - writer.end(); - - console.log('HTML file generated successfully at:', indexPath); - } catch (error) { - console.error('Error generating HTML:', error); - process.exit(1); - } -} - -generateHTML(); \ No newline at end of file diff --git a/web/czr/server/env.ts b/web/czr/server/env.ts deleted file mode 100644 index c4c71ef..0000000 --- a/web/czr/server/env.ts +++ /dev/null @@ -1,32 +0,0 @@ -import fs from "fs/promises"; -import path from "path"; - -export async function readEnvFile() { - const envPath = path.resolve(process.cwd(), ".env"); - try { - const content = await fs.readFile(envPath, "utf-8"); - return content.split("\n").reduce( - (acc, line) => { - const [key, value] = line.split("=").map((s) => s.trim()); - if (key && value) { - acc[key] = value.replace(/["']/g, ""); - } - return acc; - }, - {} as Record, - ); - } catch { - return {}; - } -} - -export async function writeEnvFile(env: Record) { - const envPath = path.resolve(process.cwd(), ".env"); - const content = Object.entries(env) - .map( - ([key, value]) => - `${key}=${typeof value === "string" ? `"${value}"` : value}`, - ) - .join("\n"); - await fs.writeFile(envPath, content, "utf-8"); -} diff --git a/web/czr/server/express.ts b/web/czr/server/express.ts deleted file mode 100644 index 385a3ca..0000000 --- a/web/czr/server/express.ts +++ /dev/null @@ -1,71 +0,0 @@ -import express from "express"; -import cors from "cors"; -import { DEFAULT_CONFIG } from "../app/env"; -import { readEnvFile, writeEnvFile } from "./env"; - -const app = express(); -const address = process.env.VITE_ADDRESS ?? DEFAULT_CONFIG.VITE_ADDRESS; -const port = Number(process.env.VITE_PORT ?? DEFAULT_CONFIG.VITE_PORT); - -const ALLOWED_ORIGIN = `http://${address}:${port}`; -// 配置 CORS,只允许来自 Vite 服务器的请求 -app.use( - cors({ - origin: (origin, callback) => { - if (!origin || origin === ALLOWED_ORIGIN) { - callback(null, true); - } else { - callback(new Error("不允许的来源")); - } - }, - credentials: true, - }), -); - -// 添加 IP 和端口检查中间件 -const checkAccessMiddleware = ( - req: express.Request, - res: express.Response, - next: express.NextFunction, -) => { - const clientIp = req.ip === "::1" ? "localhost" : req.ip; - const clientPort = Number(req.get("origin")?.split(":").pop() ?? 0); - - const isLocalIp = clientIp === "localhost" || clientIp === "127.0.0.1"; - const isAllowedPort = clientPort === port; - - if (isLocalIp && isAllowedPort) { - next(); - } else { - res.status(403).json({ - error: "禁止访问", - detail: `仅允许 ${address}:${port} 访问`, - }); - } -}; - -app.use(checkAccessMiddleware); -app.use(express.json()); - -app.get("/env", async (req, res) => { - try { - const envData = await readEnvFile(); - res.json(envData); - } catch (error) { - res.status(500).json({ error: "读取环境变量失败" }); - } -}); - -app.post("/env", async (req, res) => { - try { - const newEnv = req.body; - await writeEnvFile(newEnv); - res.json({ success: true }); - } catch (error) { - res.status(500).json({ error: "更新环境变量失败" }); - } -}); - -app.listen(port + 1, address, () => { - console.log(`内部服务器运行在 http://${address}:${port + 1}`); -}); diff --git a/web/czr/server/production.js b/web/czr/server/production.js deleted file mode 100644 index f1959c1..0000000 --- a/web/czr/server/production.js +++ /dev/null @@ -1,23 +0,0 @@ -import express from 'express'; -import { createRequestHandler } from "@remix-run/express"; -import * as build from "../build/server/index.js"; - -const app = express(); - -// 静态文件服务 -app.use(express.static("public")); -app.use(express.static("build/client")); - -// Remix 请求处理 -app.all( - "*", - createRequestHandler({ - build, - mode: "production", - }) -); - -const port = process.env.PORT || 3000; -app.listen(port, () => { - console.log(`Express server listening on port ${port}`); -}); \ No newline at end of file diff --git a/web/czr/server/static.js b/web/czr/server/static.js deleted file mode 100644 index 3a8cc70..0000000 --- a/web/czr/server/static.js +++ /dev/null @@ -1,31 +0,0 @@ -import express from 'express'; -import { dirname, resolve } from 'path'; -import { fileURLToPath } from 'url'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const projectRoot = resolve(__dirname, '..'); - -const app = express(); -const port = process.env.PORT || 3000; - -// 设置静态文件目录 -app.use(express.static(resolve(projectRoot, 'dist'))); - -// 所有路由都返回 index.html -app.get('*', (req, res) => { - res.sendFile(resolve(projectRoot, 'dist', 'index.html')); -}); - -// 确保dist目录存在 -import { mkdir } from 'fs/promises'; -try { - await mkdir(resolve(projectRoot, 'dist'), { recursive: true }); -} catch (error) { - console.error('Error creating dist directory:', error); -} - -app.listen(port, () => { - console.log(`Server running at http://localhost:${port}`); - console.log('Static files directory:', resolve(projectRoot, 'dist')); - console.log('Index file path:', resolve(projectRoot, 'dist', 'index.html')); -}); \ No newline at end of file diff --git a/web/czr/src/entry.client.tsx b/web/czr/src/entry.client.tsx deleted file mode 100644 index c1d76b4..0000000 --- a/web/czr/src/entry.client.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import { RemixBrowser } from "@remix-run/react"; -import { startTransition } from "react"; -import { hydrateRoot } from "react-dom/client"; - -startTransition(() => { - hydrateRoot(document, ); -}); \ No newline at end of file diff --git a/web/graduation/README.md b/web/graduation/README.md new file mode 100644 index 0000000..ff19a3e --- /dev/null +++ b/web/graduation/README.md @@ -0,0 +1,48 @@ +# Astro Starter Kit: Basics + +```sh +npm create astro@latest -- --template basics +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics) +[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics) +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json) + +> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! + +![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554) + +## 🚀 Project Structure + +Inside of your Astro project, you'll see the following folders and files: + +```text +/ +├── public/ +│ └── favicon.svg +├── src/ +│ ├── layouts/ +│ │ └── Layout.astro +│ └── pages/ +│ └── index.astro +└── package.json +``` + +To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/). + +## 🧞 Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +| :------------------------ | :----------------------------------------------- | +| `npm install` | Installs dependencies | +| `npm run dev` | Starts local dev server at `localhost:4321` | +| `npm run build` | Build your production site to `./dist/` | +| `npm run preview` | Preview your build locally, before deploying | +| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | +| `npm run astro -- --help` | Get help using the Astro CLI | + +## 👀 Want to learn more? + +Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). diff --git a/web/graduation/astro.config.mjs b/web/graduation/astro.config.mjs new file mode 100644 index 0000000..508cbec --- /dev/null +++ b/web/graduation/astro.config.mjs @@ -0,0 +1,11 @@ +// @ts-check +import { defineConfig } from 'astro/config'; + +import tailwindcss from '@tailwindcss/vite'; + +// https://astro.build/config +export default defineConfig({ + vite: { + plugins: [tailwindcss()] + } +}); \ No newline at end of file diff --git a/web/graduation/package.json b/web/graduation/package.json new file mode 100644 index 0000000..6e89f65 --- /dev/null +++ b/web/graduation/package.json @@ -0,0 +1,23 @@ +{ + "name": "graduation", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@studio-freight/lenis": "^1.0.42", + "@tailwindcss/vite": "^4.0.15", + "@types/react": "^19.0.12", + "@types/react-dom": "^19.0.4", + "aceternity-ui": "^0.2.2", + "astro": "^5.5.3", + "framer-motion": "^12.5.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwindcss": "^4.0.15" + } +} diff --git a/web/graduation/public/favicon.svg b/web/graduation/public/favicon.svg new file mode 100644 index 0000000..8530f7d --- /dev/null +++ b/web/graduation/public/favicon.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/web/graduation/src/assets/astro.svg b/web/graduation/src/assets/astro.svg new file mode 100644 index 0000000..8cf8fb0 --- /dev/null +++ b/web/graduation/src/assets/astro.svg @@ -0,0 +1 @@ + diff --git a/web/graduation/src/assets/background.svg b/web/graduation/src/assets/background.svg new file mode 100644 index 0000000..4b2be0a --- /dev/null +++ b/web/graduation/src/assets/background.svg @@ -0,0 +1 @@ + diff --git a/web/graduation/src/components/DarkModeTransition.astro b/web/graduation/src/components/DarkModeTransition.astro new file mode 100644 index 0000000..d8ef164 --- /dev/null +++ b/web/graduation/src/components/DarkModeTransition.astro @@ -0,0 +1,41 @@ +--- +// 黑暗模式过渡组件 - 提供平滑过渡的CSS +--- + + \ No newline at end of file diff --git a/web/graduation/src/components/ThemeToggle.astro b/web/graduation/src/components/ThemeToggle.astro new file mode 100644 index 0000000..45f5fea --- /dev/null +++ b/web/graduation/src/components/ThemeToggle.astro @@ -0,0 +1,70 @@ +--- +interface Props { + class?: string; +} + +const { class: className = '' } = Astro.props; +--- + + + + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/AnimatedGallery.astro b/web/graduation/src/components/aceternity/AnimatedGallery.astro new file mode 100644 index 0000000..98ace07 --- /dev/null +++ b/web/graduation/src/components/aceternity/AnimatedGallery.astro @@ -0,0 +1,109 @@ +--- +interface Image { + src: string; + alt: string; + width?: number; + height?: number; +} + +interface Props { + images: Image[]; + className?: string; + gap?: number; + direction?: 'left' | 'right'; + speed?: 'slow' | 'normal' | 'fast'; +} + +const { + images, + className = "", + gap = 16, + direction = 'left', + speed = 'normal' +} = Astro.props; + +// 计算速度 +const getSpeed = () => { + switch(speed) { + case 'slow': return '60s'; + case 'fast': return '20s'; + default: return '40s'; + } +}; + +const speedValue = getSpeed(); +--- + + + + + + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/FlipCard.astro b/web/graduation/src/components/aceternity/FlipCard.astro new file mode 100644 index 0000000..dcf9b62 --- /dev/null +++ b/web/graduation/src/components/aceternity/FlipCard.astro @@ -0,0 +1,63 @@ +--- +interface Props { + frontTitle?: string; + backTitle?: string; + className?: string; +} + +const { + frontTitle = "正面", + backTitle = "背面", + className = "" +} = Astro.props; +--- + +
+
+
+

{frontTitle}

+
+ +
+
+
+

{backTitle}

+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/FloatingNavbar.astro b/web/graduation/src/components/aceternity/FloatingNavbar.astro new file mode 100644 index 0000000..d9e6a91 --- /dev/null +++ b/web/graduation/src/components/aceternity/FloatingNavbar.astro @@ -0,0 +1,54 @@ +--- +interface Props { + links: Array<{ + label: string; + href: string; + isActive?: boolean; + }>; + className?: string; +} + +const { links, className = "" } = Astro.props; +--- + +
+
+
+ +
+
+ + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/GradientText.astro b/web/graduation/src/components/aceternity/GradientText.astro new file mode 100644 index 0000000..7fb5299 --- /dev/null +++ b/web/graduation/src/components/aceternity/GradientText.astro @@ -0,0 +1,63 @@ +--- +interface Props { + words: string[]; + baseText?: string; + className?: string; + duration?: number; // 每个词的显示时间(毫秒) +} + +const { + words, + baseText = "河北", + className = "", + duration = 2000 +} = Astro.props; + +const id = `gradient-text-${Math.random().toString(36).substring(2, 11)}`; +--- + +
+
+ {baseText} +
+ {words[0]} +
+
+
+ + + + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/HoverGlow.astro b/web/graduation/src/components/aceternity/HoverGlow.astro new file mode 100644 index 0000000..17d4830 --- /dev/null +++ b/web/graduation/src/components/aceternity/HoverGlow.astro @@ -0,0 +1,70 @@ +--- +interface Props { + color?: string; + intensity?: 'subtle' | 'medium' | 'strong'; + className?: string; +} + +const { + color = "rgba(75, 107, 255, 0.5)", + intensity = 'medium', + className = "" +} = Astro.props; + +// 根据强度调整发光范围 +const getGlowSize = () => { + switch(intensity) { + case 'subtle': return '100px'; + case 'strong': return '180px'; + default: return '140px'; // medium + } +}; + +const glowSize = getGlowSize(); +const id = `hover-glow-${Math.random().toString(36).substring(2, 11)}`; +--- + +
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/MagneticElement.astro b/web/graduation/src/components/aceternity/MagneticElement.astro new file mode 100644 index 0000000..3b33906 --- /dev/null +++ b/web/graduation/src/components/aceternity/MagneticElement.astro @@ -0,0 +1,144 @@ +--- +interface Props { + strength?: number; // 磁性强度,1-10之间 + className?: string; + radius?: number; // 影响半径(像素) + smoothing?: number; // 平滑程度 (0-1) +} + +const { + strength = 5, + className = "", + radius = 150, + smoothing = 0.15 +} = Astro.props; + +const magnetId = `magnetic-${Math.random().toString(36).substring(2, 11)}`; +--- + +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/MorphingText.astro b/web/graduation/src/components/aceternity/MorphingText.astro new file mode 100644 index 0000000..4e7ddf1 --- /dev/null +++ b/web/graduation/src/components/aceternity/MorphingText.astro @@ -0,0 +1,152 @@ +--- +interface Props { + phrases: string[]; + className?: string; + textSize?: string; +} + +const { + phrases = ["河北旅游", "河北文化", "河北美食"], + className = "", + textSize = "text-7xl sm:text-8xl md:text-9xl" +} = Astro.props; + +const id = `morphing-text-${Math.random().toString(36).substr(2, 9)}`; +--- + +
+ +
+ + +
+ + + +
+ + + + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/NeonButton.astro b/web/graduation/src/components/aceternity/NeonButton.astro new file mode 100644 index 0000000..4160d92 --- /dev/null +++ b/web/graduation/src/components/aceternity/NeonButton.astro @@ -0,0 +1,159 @@ +--- +interface Props { + text?: string; + href?: string; + className?: string; + color?: string; + glowSize?: 'small' | 'medium' | 'large'; + glowIntensity?: 'low' | 'medium' | 'high'; + pulseAnimation?: boolean; +} + +const { + text = "霓虹按钮", + href, + className = "", + color = "rgb(101, 111, 247)", // 紫色 + glowSize = 'medium', + glowIntensity = 'medium', + pulseAnimation = true +} = Astro.props; + +// 计算发光尺寸 +const getGlowSizePx = (size: string) => { + switch (size) { + case 'small': return 10; + case 'large': return 30; + default: return 20; + } +}; + +// 计算发光强度值 +const getIntensityValue = (intensity: string) => { + switch (intensity) { + case 'low': return 0.6; + case 'high': return 1; + default: return 0.8; + } +}; + +const glowSizePx = getGlowSizePx(glowSize); +const intensityValue = getIntensityValue(glowIntensity); + +const buttonId = `neon-btn-${Math.random().toString(36).substring(2, 11)}`; +--- + +
+ {href ? ( + + {text} + + ) : ( + + )} +
+ + + + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/ParallaxCard.astro b/web/graduation/src/components/aceternity/ParallaxCard.astro new file mode 100644 index 0000000..9f0141d --- /dev/null +++ b/web/graduation/src/components/aceternity/ParallaxCard.astro @@ -0,0 +1,237 @@ +--- +interface Props { + className?: string; + glareEnabled?: boolean; + maxGlare?: number; // 0-1 之间 + depth?: number; // 视差深度,1-10之间 + backgroundImage?: string; +} + +const { + className = "", + glareEnabled = true, + maxGlare = 0.5, + depth = 5, + backgroundImage +} = Astro.props; + +const cardId = `parallax-card-${Math.random().toString(36).substring(2, 11)}`; +--- + +
+ {backgroundImage && ( +
+ )} +
+ +
+ {glareEnabled &&
} +
+ + + + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/ParallaxSection.astro b/web/graduation/src/components/aceternity/ParallaxSection.astro new file mode 100644 index 0000000..c53b9ec --- /dev/null +++ b/web/graduation/src/components/aceternity/ParallaxSection.astro @@ -0,0 +1,91 @@ +--- +interface Props { + images: Array<{ + src: string; + alt: string; + speed?: number; // 滚动速度因子,正数向下,负数向上 + }>; + heading: string; + subheading?: string; + className?: string; +} + +const { images, heading, subheading, className = "" } = Astro.props; +--- + +
+ +
+
+ { + images.map((image, index) => ( +
+
+ {image.alt} +
+
+ )) + } +
+ + +
+
+

+ {heading} +

+ { + subheading && ( +

{subheading}

+ ) + } + +
+
+
+ + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/ParticleButton.astro b/web/graduation/src/components/aceternity/ParticleButton.astro new file mode 100644 index 0000000..b101cea --- /dev/null +++ b/web/graduation/src/components/aceternity/ParticleButton.astro @@ -0,0 +1,129 @@ +--- +interface Props { + text?: string; + href?: string; + className?: string; + color?: string; + particleCount?: number; +} + +const { + text = "点击我", + href, + className = "", + color = "#6366f1", + particleCount = 30 +} = Astro.props; + +const buttonId = `particle-btn-${Math.random().toString(36).substring(2, 11)}`; +--- + +
+ {href ? ( + + {text} + + ) : ( + + )} +
+
+ + + + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/ScrollReveal.astro b/web/graduation/src/components/aceternity/ScrollReveal.astro new file mode 100644 index 0000000..d53350d --- /dev/null +++ b/web/graduation/src/components/aceternity/ScrollReveal.astro @@ -0,0 +1,149 @@ +--- +interface Props { + animation?: 'fade' | 'slide-up' | 'slide-down' | 'slide-left' | 'slide-right' | 'scale' | 'rotate'; + duration?: number; // 动画持续时间,毫秒 + delay?: number; // 延迟时间,毫秒 + threshold?: number; // 触发阈值,0-1之间 + once?: boolean; // 是否只触发一次 + className?: string; +} + +const { + animation = 'fade', + duration = 800, + delay = 0, + threshold = 0.3, + once = true, + className = "" +} = Astro.props; + +// 生成唯一ID +const id = `scroll-reveal-${Math.random().toString(36).substring(2, 11)}`; + +// 设定初始隐藏样式 +const getInitialStyles = () => { + switch(animation) { + case 'fade': + return 'opacity: 0;'; + case 'slide-up': + return 'opacity: 0; transform: translateY(40px);'; + case 'slide-down': + return 'opacity: 0; transform: translateY(-40px);'; + case 'slide-left': + return 'opacity: 0; transform: translateX(40px);'; + case 'slide-right': + return 'opacity: 0; transform: translateX(-40px);'; + case 'scale': + return 'opacity: 0; transform: scale(0.9);'; + case 'rotate': + return 'opacity: 0; transform: rotate(-5deg);'; + default: + return 'opacity: 0;'; + } +}; + +const initialStyles = getInitialStyles(); +--- + +
+ +
+ + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/SmoothScroll.astro b/web/graduation/src/components/aceternity/SmoothScroll.astro new file mode 100644 index 0000000..efa9a3c --- /dev/null +++ b/web/graduation/src/components/aceternity/SmoothScroll.astro @@ -0,0 +1,106 @@ +--- +interface Props { + enabled?: boolean; + duration?: number; // 滚动持续时间,毫秒 + easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out'; +} + +const { + enabled = true, + duration = 800, + easing = 'ease-out' +} = Astro.props; + +// 将缓动函数名转换为贝塞尔曲线 +const getBezier = () => { + switch(easing) { + case 'linear': return 'cubic-bezier(0, 0, 1, 1)'; + case 'ease-in': return 'cubic-bezier(0.42, 0, 1, 1)'; + case 'ease-out': return 'cubic-bezier(0, 0, 0.58, 1)'; + case 'ease-in-out': return 'cubic-bezier(0.42, 0, 0.58, 1)'; + default: return 'cubic-bezier(0, 0, 0.58, 1)'; // ease-out + } +}; + +const bezier = getBezier(); +--- + + + + + \ No newline at end of file diff --git a/web/graduation/src/components/aceternity/SpotlightCard.astro b/web/graduation/src/components/aceternity/SpotlightCard.astro new file mode 100644 index 0000000..ea66342 --- /dev/null +++ b/web/graduation/src/components/aceternity/SpotlightCard.astro @@ -0,0 +1,265 @@ +--- +interface Props { + title: string; + description?: string; + image?: string; + link?: string; + className?: string; + color?: string; + spotlightSize?: 'small' | 'medium' | 'large'; + spotlightIntensity?: 'subtle' | 'medium' | 'strong'; +} + +const { + title, + description, + image, + link, + className = "", + color = "rgba(75, 107, 255, 0.3)", // 默认是蓝色光晕 + spotlightSize = 'medium', + spotlightIntensity = 'medium' +} = Astro.props; + +const id = `spotlight-card-${Math.random().toString(36).substring(2, 11)}`; + +// 计算光晕大小 +const sizeMap = { + small: '70%', + medium: '100%', + large: '130%' +}; + +// 计算光晕强度 +const intensityMap = { + subtle: '0.15', + medium: '0.25', + strong: '0.4' +}; + +const spotlightSizeValue = sizeMap[spotlightSize]; +const spotlightIntensityValue = intensityMap[spotlightIntensity]; +--- + +
+
+
+
+ + {image && ( +
+
+ {title} 图片 +
+
+ )} + +

+ {title} +

+ + {description && ( +

+ {description} +

+ )} + + {link && ( + + 查看详情 → + + )} + + +
+ + + + \ No newline at end of file diff --git a/web/graduation/src/components/common/CardGrid.astro b/web/graduation/src/components/common/CardGrid.astro new file mode 100644 index 0000000..57a1cea --- /dev/null +++ b/web/graduation/src/components/common/CardGrid.astro @@ -0,0 +1,30 @@ +--- +interface Props { + columns?: 1 | 2 | 3 | 4; + gap?: string; + className?: string; +} + +const { + columns = 3, + gap = "gap-8", + className = "" +} = Astro.props; + +// 根据列数动态设置网格类 +const getGridClass = () => { + switch(columns) { + case 1: return 'grid-cols-1'; + case 2: return 'grid-cols-1 md:grid-cols-2'; + case 3: return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3'; + case 4: return 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4'; + default: return 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3'; + } +}; + +const gridClass = getGridClass(); +--- + +
+ +
\ No newline at end of file diff --git a/web/graduation/src/components/common/CarouselCard.astro b/web/graduation/src/components/common/CarouselCard.astro new file mode 100644 index 0000000..9ccd3ee --- /dev/null +++ b/web/graduation/src/components/common/CarouselCard.astro @@ -0,0 +1,117 @@ +--- +interface Props { + title: string; + description?: string; + image?: string; + imageAlt?: string; + link?: string; + linkText?: string; + tags?: string[]; + className?: string; +} + +const { + title, + description, + image, + imageAlt = "", + link, + linkText = "详细信息", + tags = [], + className = "" +} = Astro.props; +--- + +
+ {image && ( +
+
+ {imageAlt || title} +
+
+
+ )} + +
+

{title}

+ + {description && ( +

{description}

+ )} + + {tags.length > 0 && ( +
+ {tags.map(tag => ( + + {tag} + + ))} +
+ )} + + + + {link && ( + + {linkText} → + + )} +
+
+ + \ No newline at end of file diff --git a/web/graduation/src/components/common/CategoryFilter.astro b/web/graduation/src/components/common/CategoryFilter.astro new file mode 100644 index 0000000..ccae281 --- /dev/null +++ b/web/graduation/src/components/common/CategoryFilter.astro @@ -0,0 +1,40 @@ +--- +interface Category { + name: string; + icon?: string; + count?: number; + isActive?: boolean; +} + +interface Props { + categories: Category[]; + showAllLabel?: string; + allActive?: boolean; +} + +const { + categories, + showAllLabel = "全部", + allActive = true +} = Astro.props; +--- + +
+
+
+ + + {categories.map(category => ( + + ))} +
+
+
\ No newline at end of file diff --git a/web/graduation/src/components/common/ItemCard.astro b/web/graduation/src/components/common/ItemCard.astro new file mode 100644 index 0000000..713cbba --- /dev/null +++ b/web/graduation/src/components/common/ItemCard.astro @@ -0,0 +1,69 @@ +--- +interface Props { + title: string; + description?: string; + image?: string; + imageAlt?: string; + link?: string; + linkText?: string; + tags?: string[]; + location?: string; + className?: string; +} + +const { + title, + description, + image, + imageAlt = "", + link, + linkText = "详细信息", + tags = [], + location, + className = "" +} = Astro.props; +--- + +
+ {image && ( +
+
+ {imageAlt || title} +
+ {location && ( +
+ {location} +
+ )} +
+ )} + +
+

{title}

+ + {description && ( +

{description}

+ )} + + {tags.length > 0 && ( +
+ {tags.map(tag => ( + + {tag} + + ))} +
+ )} + + + + {link && ( + + {linkText} → + + )} +
+
\ No newline at end of file diff --git a/web/graduation/src/components/common/PageHeader.astro b/web/graduation/src/components/common/PageHeader.astro new file mode 100644 index 0000000..9be8cfe --- /dev/null +++ b/web/graduation/src/components/common/PageHeader.astro @@ -0,0 +1,18 @@ +--- +interface Props { + title: string; + subtitle?: string; +} + +const { title, subtitle } = Astro.props; +--- + +
+
+
+

{title}

+ {subtitle &&

{subtitle}

} + +
+
+
\ No newline at end of file diff --git a/web/graduation/src/components/common/SearchBar.astro b/web/graduation/src/components/common/SearchBar.astro new file mode 100644 index 0000000..02c9da1 --- /dev/null +++ b/web/graduation/src/components/common/SearchBar.astro @@ -0,0 +1,33 @@ +--- +interface Props { + placeholder?: string; + buttonText?: string; + className?: string; +} + +const { + placeholder = "搜索...", + buttonText = "搜索", + className = "" +} = Astro.props; +--- + +
+
+
+
+ + + + + +
+
+
+
\ No newline at end of file diff --git a/web/graduation/src/components/common/SectionContainer.astro b/web/graduation/src/components/common/SectionContainer.astro new file mode 100644 index 0000000..248078f --- /dev/null +++ b/web/graduation/src/components/common/SectionContainer.astro @@ -0,0 +1,61 @@ +--- +interface Props { + bgColor?: 'white' | 'gray' | 'primary' | 'none'; + padding?: 'none' | 'sm' | 'md' | 'lg' | 'xl'; + maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | 'full' | 'none'; + className?: string; +} + +const { + bgColor = 'white', + padding = 'lg', + maxWidth = 'lg', + className = "" +} = Astro.props; + +// 设置背景颜色 +const getBgColorClass = () => { + switch(bgColor) { + case 'white': return 'bg-white dark:bg-color-dark-bg'; + case 'gray': return 'bg-gray-100 dark:bg-color-dark-surface'; + case 'primary': return 'bg-color-primary-50 dark:bg-color-dark-primary-50'; + case 'none': return ''; + default: return 'bg-white dark:bg-color-dark-bg'; + } +}; + +// 设置内边距 +const getPaddingClass = () => { + switch(padding) { + case 'none': return ''; + case 'sm': return 'py-4 px-4'; + case 'md': return 'py-8 px-4 sm:px-6'; + case 'lg': return 'py-12 px-4 sm:px-6 lg:px-8'; + case 'xl': return 'py-16 px-4 sm:px-6 lg:px-8'; + default: return 'py-12 px-4 sm:px-6 lg:px-8'; + } +}; + +// 设置最大宽度 +const getMaxWidthClass = () => { + switch(maxWidth) { + case 'none': return ''; + case 'sm': return 'max-w-2xl mx-auto'; + case 'md': return 'max-w-4xl mx-auto'; + case 'lg': return 'max-w-7xl mx-auto'; + case 'xl': return 'max-w-screen-2xl mx-auto'; + case 'full': return 'w-full'; + default: return 'max-w-7xl mx-auto'; + } +}; + +const bgColorClass = getBgColorClass(); +const paddingClass = getPaddingClass(); +const maxWidthClass = getMaxWidthClass(); +--- + +
+
+ +
+
\ No newline at end of file diff --git a/web/graduation/src/components/common/SectionHeading.astro b/web/graduation/src/components/common/SectionHeading.astro new file mode 100644 index 0000000..08c2c08 --- /dev/null +++ b/web/graduation/src/components/common/SectionHeading.astro @@ -0,0 +1,39 @@ +--- +interface Props { + title: string; + subtitle?: string; + alignment?: 'left' | 'center' | 'right'; + marginBottom?: string; + className?: string; +} + +const { + title, + subtitle, + alignment = 'center', + marginBottom = 'mb-8', + className = "" +} = Astro.props; + +// 设置对齐方式 +const getAlignmentClass = () => { + switch(alignment) { + case 'left': return 'text-left'; + case 'right': return 'text-right'; + case 'center': return 'text-center'; + default: return 'text-center'; + } +}; + +const alignmentClass = getAlignmentClass(); +--- + +
+

{title}

+ {subtitle && ( +

+ {subtitle} +

+ )} + +
\ No newline at end of file diff --git a/web/graduation/src/components/common/SmoothCardCarousel.astro b/web/graduation/src/components/common/SmoothCardCarousel.astro new file mode 100644 index 0000000..066ace9 --- /dev/null +++ b/web/graduation/src/components/common/SmoothCardCarousel.astro @@ -0,0 +1,331 @@ +--- +interface Props { + autoplay?: boolean; + autoplaySpeed?: number; // 毫秒 + showArrows?: boolean; + showDots?: boolean; + cardGap?: number; // px + className?: string; +} + +const { + autoplay = true, + autoplaySpeed = 5000, + showArrows = true, + showDots = true, + cardGap = 16, + className = "" +} = Astro.props; + +const carouselId = `carousel-${Math.random().toString(36).substring(2, 11)}`; +--- + + + + + + \ No newline at end of file diff --git a/web/graduation/src/content/attractions/chengde-summer-resort.md b/web/graduation/src/content/attractions/chengde-summer-resort.md new file mode 100644 index 0000000..758fff8 --- /dev/null +++ b/web/graduation/src/content/attractions/chengde-summer-resort.md @@ -0,0 +1,64 @@ +--- +title: 承德避暑山庄 +description: 中国清代皇家园林,世界文化遗产,占地564万平方米,是世界上最大的皇家园林之一。清朝皇帝夏天在此避暑,处理政务。园内山水相融,景色优美,建筑风格多样。 +featured: true +image: https://picsum.photos/seed/chengde/800/600 +location: 承德市 +tags: ['世界文化遗产', '皇家园林', '历史建筑'] +pubDate: 2023-03-15 +updatedDate: 2023-12-01 +--- + +# 承德避暑山庄 + +## 景点概况 + +承德避暑山庄,又名"热河行宫",位于河北省承德市双桥区,是清代皇帝夏天避暑和处理政务的场所,也是中国现存最大的古代帝王宫苑。避暑山庄始建于1703年(清康熙四十二年),历经康熙、雍正、乾隆三朝,耗时89年建成。 + +园内建筑面积8.7万平方米,整个山庄依山就势,园林结构严谨,建筑布局灵活,集中国园林艺术之精华,融汉、蒙、满等民族建筑风格于一体。 + +## 历史沿革 + +避暑山庄创建于1703年,是清代康熙皇帝为避暑与抵御北方少数民族威胁而建。山庄的选址有着重要的战略意义,位于进出关内外的交通要道和游牧民族与农耕地区的交界处。 + +康熙年间主要建成了宫殿区、湖区和部分平原区,雍正时期进行了扩建,而在乾隆年间则达到鼎盛,修建了大部分的建筑群和外八庙。 + +## 建筑特色 + +避暑山庄布局巧妙,融宫殿建筑与自然景观为一体,被划分为宫殿区、湖区、平原区和山区四个部分: + +1. **宫殿区**:位于山庄南部,是皇帝处理政务和日常起居的场所,建筑富丽堂皇。 +2. **湖区**:位于山庄中部,包括八个大小不一的湖泊,湖中点缀着岛屿和各式亭台楼阁。 +3. **平原区**:位于湖区以北,模仿内蒙古草原风光,建有蒙古包等。 +4. **山区**:位于山庄北部,占总面积四分之三,主要是自然风景区,点缀有寺庙和亭台。 + +## 文化价值 + +避暑山庄不仅是清朝政治中心之一,也是中国古代园林艺术的杰出代表。它体现了"天人合一"的中国传统哲学思想,以及清代统治者"慎终怀远"的政治理念。 + +1987年,承德避暑山庄及周围寺庙被联合国教科文组织列入世界文化遗产名录。 + +## 著名景点 + +避暑山庄内有72景,其中最著名的有: + +- **烟波致爽**:湖区主要景点,可欣赏湖光山色。 +- **月色江声**:观赏月亮和聆听流水声的最佳地点。 +- **芳泉映柳**:有泉水环绕的柳树园地。 +- **松风水月**:可欣赏松树、微风、湖水和月色的胜景。 +- **四知书屋**:乾隆皇帝读书的地方。 + +## 交通指南 + +- **地址**:河北省承德市双桥区武烈河北路178号 +- **公交**:乘坐1路、6路公交车可直达 +- **自驾**:从北京出发,沿京承高速公路行驶约230公里可达 + +## 参观提示 + +- 开放时间:8:00-17:30(旺季),8:30-17:00(淡季) +- 门票信息:淡季80元,旺季100元(含园内电瓶车) +- 建议游览时间:一整天 +- 最佳游览季节:夏秋季(7-10月) + +来避暑山庄,感受"塞外江南"的独特魅力,领略清朝帝王的皇家气派! \ No newline at end of file diff --git a/web/graduation/src/content/config.ts b/web/graduation/src/content/config.ts new file mode 100644 index 0000000..a48b549 --- /dev/null +++ b/web/graduation/src/content/config.ts @@ -0,0 +1,75 @@ +import { defineCollection, z } from 'astro:content'; + +// 景点集合的Schema +const attractionsCollection = defineCollection({ + type: 'content', + schema: z.object({ + title: z.string(), + description: z.string(), + featured: z.boolean().default(false), + image: z.string().optional(), + city: z.string().optional(), + tags: z.array(z.string()).default([]), + pubDate: z.date().optional(), + }), +}); + +// 文化集合的Schema +const cultureCollection = defineCollection({ + type: 'content', + schema: z.object({ + title: z.string(), + description: z.string(), + category: z.string(), + featured: z.boolean().default(false), + image: z.string().optional(), + city: z.string().optional(), + tags: z.array(z.string()).default([]), + pubDate: z.date().optional(), + }), +}); + +// 美食集合的Schema +const cuisineCollection = defineCollection({ + type: 'content', + schema: z.object({ + title: z.string(), + description: z.string(), + category: z.string(), + featured: z.boolean().default(false), + image: z.string().optional(), + city: z.string().optional(), + ingredients: z.array(z.string()).default([]), + taste: z.string().optional(), + cookTime: z.string().optional(), + difficulty: z.string().optional(), + tags: z.array(z.string()).default([]), + pubDate: z.date().optional(), + }), +}); + +// 旅游攻略集合的Schema +const travelCollection = defineCollection({ + type: 'content', + schema: z.object({ + title: z.string(), + description: z.string(), + season: z.enum(['春季', '夏季', '秋季', '冬季']).optional(), + type: z.string(), + featured: z.boolean().default(false), + image: z.string().optional(), + city: z.string().optional(), + days: z.number().optional(), + difficulty: z.enum(['简单', '中等', '困难']).optional(), + tags: z.array(z.string()).default([]), + pubDate: z.date().optional(), + }), +}); + +// 导出集合配置 +export const collections = { + 'attractions': attractionsCollection, + 'culture': cultureCollection, + 'cuisine': cuisineCollection, + 'travel': travelCollection, +}; diff --git a/web/graduation/src/content/cuisine/donkey-meat-burger.md b/web/graduation/src/content/cuisine/donkey-meat-burger.md new file mode 100644 index 0000000..c70faec --- /dev/null +++ b/web/graduation/src/content/cuisine/donkey-meat-burger.md @@ -0,0 +1,93 @@ +--- +title: 保定驴肉火烧 +description: 河北保定的传统小吃,以烤制的火烧饼夹入精选的驴肉,肉质鲜美,饼酥脆可口,香气四溢,是保定最具代表性的地方风味美食。 +category: 传统小吃 +featured: true +image: https://picsum.photos/seed/donkey-burger/800/600 +origin: 保定市 +ingredients: ['驴肉', '火烧饼', '青椒', '香菜', '五香粉', '酱汁'] +taste: 鲜香酥脆 +tags: ['河北名吃', '传统美食', '地方特色'] +pubDate: 2023-04-05 +updatedDate: 2023-10-20 +--- + +# 保定驴肉火烧 + +## 美食简介 + +保定驴肉火烧是河北保定地区的传统名吃,距今已有上百年的历史。它由两块圆形的酥脆火烧饼夹着精细切片的卤驴肉制成,佐以青椒、香菜等配料,香气四溢,口感丰富,是河北最具代表性的地方风味小吃之一。 + +驴肉火烧之所以能够成为保定的招牌美食,在于其选料严格、工艺精湛、味道独特。驴肉肉质细嫩且低脂肪、高蛋白,火烧饼则酥脆可口,两者搭配堪称绝配。 + +## 历史渊源 + +关于保定驴肉火烧的起源,有多种说法。最具代表性的说法认为,它始于清朝末年的保定城内。当时的保定是京畿重地,商业繁荣,小吃文化发达。一家姓茹的老字号饭馆为了充分利用食材,将卤制的驴肉切片夹在当地特色的火烧饼中售卖,没想到大受欢迎,逐渐成为保定的特色美食。 + +另一种说法将其起源追溯到更早的明代,传说明朝大将徐达攻打保定时,因军粮不足,士兵只能以驴肉夹饼充饥,没想到这种简单的吃法味道鲜美,战后流传开来。 + +## 制作工艺 + +保定驴肉火烧的制作分为两大部分:火烧饼的制作和驴肉的卤制。 + +### 火烧饼制作 +1. **和面**:选用优质面粉,加入适量清水、食用碱和盐,揉至光滑。 +2. **发酵**:将和好的面团放置发酵至体积增大约一倍。 +3. **整形**:将面团分割成小剂子,压扁成圆饼状。 +4. **烤制**:传统方法是在炉壁上烤制,使饼两面呈金黄色,外酥里嫩。 + +### 驴肉卤制 +1. **选料**:选用新鲜的驴肉,以后腿肉为佳。 +2. **清洗**:将驴肉彻底清洗,去除血水。 +3. **卤制**:将肉放入配有十余种香料的卤水中,小火慢炖3-4小时。 +4. **冷却**:卤好后取出,自然冷却。 +5. **切片**:将卤好的驴肉切成薄片,约2-3毫米厚。 + +### 组合制作 +1. 将火烧饼从中间切开,不要切断。 +2. 在切开的饼内放入适量切好的驴肉片。 +3. 加入青椒丝、香菜等配料。 +4. 可根据个人口味加入特制的酱料。 + +## 营养价值 + +驴肉被称为"黑色的肉中之王",具有极高的营养价值: +- 高蛋白低脂肪:蛋白质含量高达20%以上,脂肪含量仅为2%左右。 +- 富含微量元素:含有丰富的铁、锌、硒等微量元素。 +- 氨基酸丰富:含有人体所需的多种必需氨基酸。 +- 药用价值:中医认为驴肉性平味甘,具有补血益气、滋阴养胃的功效。 + +火烧饼则提供了充足的碳水化合物,两者搭配营养均衡,既美味又健康。 + +## 品尝之道 + +品尝保定驴肉火烧有几个关键要点: +1. **趁热吃**:火烧刚出炉时最为酥脆,驴肉热时肉汁丰富。 +2. **细嚼慢咽**:充分品味火烧的酥香和驴肉的鲜美。 +3. **配料搭配**:可根据个人口味添加青椒、香菜、蒜泥等。 +4. **饮品搭配**:传统上配以豆汁或小米粥,现代也可配清茶或啤酒。 + +## 保定名店 + +保定有多家历史悠久的驴肉火烧老字号,其中最具代表性的有: + +1. **茹记驴肉火烧**:创建于清末,被誉为驴肉火烧的鼻祖,多代传承的秘制配方。 + - 地址:保定市莲池区裕华西路 + +2. **万顺斋驴肉火烧**:百年老店,以肉质鲜嫩、火烧酥脆著称。 + - 地址:保定市新市区东风路 + +3. **德馨斋驴肉火烧**:创建于1936年,特点是驴肉卤制时间长,味道更浓郁。 + - 地址:保定市莲池区东三省胡同 + +## 现代传承 + +随着时代的发展,保定驴肉火烧已走出河北,在全国范围内获得了广泛认可。许多保定人将驴肉火烧的制作技艺带到全国各地,开设专门店铺。同时,现代工艺也在不断改进传统制作方法,如采用真空包装技术延长驴肉保鲜期,研发速冻火烧等,使这一传统美食更加方便快捷。 + +保定市政府也高度重视这一文化遗产的保护和传承,定期举办驴肉火烧文化节,评选"驴肉火烧名店",并支持相关研究和创新,确保这一传统美食在现代社会继续发扬光大。 + +## 旅游提示 + +来保定旅游的游客可以将品尝正宗的驴肉火烧作为必不可少的行程之一。最好选择当地知名的老字号店铺,体验最地道的口味。很多游客也会购买真空包装的驴肉带回家与亲友分享,成为保定特色伴手礼。 + +品尝驴肉火烧时,可以搭配游览保定的古莲花池、直隶总督署等历史景点,体验完整的保定文化之旅。 \ No newline at end of file diff --git a/web/graduation/src/content/culture/jingju.md b/web/graduation/src/content/culture/jingju.md new file mode 100644 index 0000000..60e61af --- /dev/null +++ b/web/graduation/src/content/culture/jingju.md @@ -0,0 +1,86 @@ +--- +title: 京剧 +description: 中国国粹,河北是京剧重要的发源地之一。京剧融合了多种地方戏曲艺术,包括河北梆子等,形成了独特的艺术风格。以生、旦、净、丑四种角色为主,通过唱念做打展现戏剧内容。 +category: 戏曲艺术 +featured: true +image: https://picsum.photos/seed/jingju/800/600 +area: 河北全省 +tags: ['非物质文化遗产', '传统戏曲', '国粹'] +pubDate: 2023-02-10 +updatedDate: 2023-11-15 +--- + +# 京剧 + +## 艺术概况 + +京剧,被誉为中国国粹,是中国最具代表性的传统戏曲艺术形式之一。它形成于19世纪中叶的北京,但河北作为其重要的发源地之一,对京剧的形成和发展有着不可忽视的贡献。京剧通过唱念做打四种基本功,结合音乐、舞蹈、文学、武术等多种艺术形式,以程式化的表演形式展现丰富多彩的历史故事和人物形象。 + +## 历史渊源 + +京剧的形成可以追溯到清朝乾隆五十五年(1790年),当时以徽班为首的安徽戏曲团体进入北京,与来自湖北的汉调艺人以及当地的昆曲、高腔等艺术形式相互融合,逐渐形成了具有独特风格的京剧。 + +在这一过程中,源自河北的"河北梆子"对京剧的音乐、唱腔和表演风格都产生了重要影响。河北梆子作为京剧"四大声腔"之一,以其高亢激昂的唱腔特点,为京剧增添了独特的艺术魅力。 + +## 艺术特色 + +京剧的主要艺术特色包括: + +1. **角色行当**:分为生(男性角色)、旦(女性角色)、净(性格豪放的男性角色,脸谱化妆)、丑(滑稽角色)四大类,每类又有细分。 + +2. **表演程式**:以"唱、念、做、打"为基本功,其中: + - 唱:以二黄和西皮两大声腔为主 + - 念:包括韵白和京白两种念白方式 + - 做:程式化的动作和身段 + - 打:武打动作和技巧 + +3. **脸谱艺术**:通过不同的颜色和图案表现角色的性格特点,如红色代表忠义,黑色代表刚正不阿,蓝色代表勇猛等。 + +4. **音乐伴奏**:以弦乐和打击乐为主,包括京胡、月琴、三弦、笛子、唢呐等乐器。 + +## 河北京剧 + +河北京剧具有鲜明的地方特色,既保留了京剧的基本程式,又融入了河北地方戏曲的特点。河北京剧演员讲究字正腔圆,唱腔铿锵有力,表演朴实生动,深受当地观众喜爱。 + +河北京剧的代表剧目包括《四郎探母》、《铡美案》、《赵氏孤儿》等,这些剧目通过悲壮、激昂的表演风格,展示了英雄人物的气概和民族精神。 + +## 代表剧目 + +京剧的经典剧目数以千计,其中最具代表性的包括: + +- **《霸王别姬》**:讲述西楚霸王项羽和虞姬的爱情悲剧。 +- **《贵妃醉酒》**:展现杨贵妃醉酒时的娇态和哀怨。 +- **《三岔口》**:以武打见长的戏曲,展示了高超的武功技艺。 +- **《四郎探母》**:讲述杨四郎回到大宋探望母亲的感人故事。 +- **《赵氏孤儿》**:表现忠臣程婴舍己为人的高尚品格。 + +## 传承保护 + +2010年,京剧被联合国教科文组织列入"人类非物质文化遗产代表作名录"。河北省一直致力于京剧艺术的保护和传承工作: + +1. 成立专业京剧团体,如河北京剧院、石家庄京剧团等。 +2. 举办京剧表演赛事和艺术节,推广京剧艺术。 +3. 在学校开展京剧教育,培养年轻一代对传统文化的兴趣。 +4. 支持京剧名家开展收徒传艺活动,确保技艺代代相传。 + +## 欣赏指南 + +观赏京剧可以从以下几个方面入手: + +1. **角色辨认**:了解生旦净丑的不同特点和表演风格。 +2. **唱腔欣赏**:聆听京剧独特的板式变化和韵味。 +3. **表演程式**:欣赏演员的身段、手势和眼神等细节。 +4. **故事情节**:了解剧目的历史背景和故事内容。 + +初次接触京剧的观众可以从《贵妃醉酒》、《三岔口》等易于理解的剧目入手,循序渐进地领略京剧的艺术魅力。 + +## 观演信息 + +在河北欣赏正宗京剧表演,可以关注以下场所: + +- **河北省京剧院**:定期举办各类京剧演出 +- **河北大剧院**:邀请国内知名京剧团体演出 +- **各市文化中心**:不定期举办京剧专场演出 +- **传统戏楼**:如保定古莲花池戏楼,提供传统戏台演出体验 + +京剧作为中华文化的瑰宝,凝聚着中国人的智慧和艺术追求。在河北这片文化沃土上,京剧艺术不断焕发出新的生机和活力。 \ No newline at end of file diff --git a/web/graduation/src/content/travel/summer-guide.md b/web/graduation/src/content/travel/summer-guide.md new file mode 100644 index 0000000..6e9cce7 --- /dev/null +++ b/web/graduation/src/content/travel/summer-guide.md @@ -0,0 +1,193 @@ +--- +title: 河北夏季避暑休闲之旅 +description: 河北夏季有众多避暑胜地,秦皇岛北戴河海滨、承德避暑山庄、张家口崇礼都是理想的避暑休闲目的地,享受凉爽惬意的假期。 +season: 夏季 +type: 旅行贴士 +featured: true +image: https://picsum.photos/seed/hebei-summer/800/600 +days: 5 +difficulty: 简单 +tags: ['避暑胜地', '海滨度假', '休闲旅游'] +pubDate: 2023-05-20 +updatedDate: 2023-11-30 +--- + +# 河北夏季避暑休闲之旅 + +## 攻略概述 + +夏季是旅游的旺季,但高温往往让人望而却步。而河北省凭借其独特的地理位置和多样的地形条件,拥有众多避暑胜地,是夏季旅游的理想选择。从东部的海滨度假区到北部的山地避暑胜地,再到历史悠久的皇家园林,河北为游客提供了多种避暑休闲的选择。 + +本攻略将为您详细介绍河北夏季最佳的避暑目的地、精心规划的5日行程、特色体验活动以及实用的旅行贴士,帮助您度过一个凉爽舒适的夏日假期。 + +## 最佳旅行时间 + +**6月中旬至8月底**是游览河北避暑胜地的黄金时期。此时河北内陆地区气温较高,而本攻略推荐的避暑目的地气温适宜,通常比内陆地区低5-10℃。尤其是7月中旬到8月中旬,正值内陆高温季节,河北的避暑胜地优势更为明显。 + +## 推荐目的地 + +### 1. 秦皇岛北戴河 + +北戴河是中国最著名的海滨避暑胜地之一,夏季平均气温比内陆低8℃左右。这里拥有绵延的金色沙滩、清澈的海水和舒适的海风,是消暑纳凉的绝佳去处。 + +**主要景点**: +- **北戴河海滨**:绵延数公里的沙滩,海水清澈,适合游泳、沙滩排球等活动。 +- **鸽子窝公园**:融合了园林景观与海滨风光,是观赏日出的最佳地点。 +- **老虎石海上公园**:因礁石形似虎头而得名,海景壮观。 +- **联峰山公园**:山顶可俯瞰整个北戴河海滨。 + +**特色体验**: +- 清晨在沙滩漫步,感受第一缕阳光 +- 品尝新鲜的海鲜大餐 +- 参加沙滩排球或沙雕比赛 +- 在海边露营,欣赏星空 + +### 2. 承德避暑山庄 + +作为清代皇家避暑胜地,避暑山庄不仅拥有丰富的历史文化遗产,还因其独特的地理位置和园林设计,成为夏季理想的避暑地。山庄内山水相融,植被茂密,气温比北京低5-8℃。 + +**主要景点**: +- **避暑山庄主体**:包括宫殿区、湖区、平原区和山区,布局巧妙,景色宜人。 +- **外八庙**:包括普宁寺、普佑寺等,展现了清代多民族融合的建筑艺术。 +- **磬锤峰**:避暑山庄附近的自然景观,形似磬锤,景色壮观。 + +**特色体验**: +- 穿着清代服饰在山庄内拍照留念 +- 参加山庄内的文化体验活动,如宫廷茶艺、满族剪纸等 +- 在园内的亭台楼阁中休憩,感受皇家避暑的惬意 + +### 3. 张家口崇礼 + +崇礼因2022年冬奥会而闻名,但其实夏季的崇礼也是绝佳的避暑胜地。这里海拔较高,夏季平均气温仅为20℃左右,草原青翠,空气清新,是避暑休闲的理想选择。 + +**主要景点**: +- **崇礼滑雪场**:夏季转型为高山草甸和户外运动基地。 +- **太舞滑雪小镇**:冬奥会场馆,夏季可参观奥运遗址并体验各种户外活动。 +- **草原天路**:盘山公路,沿途风景如画,是自驾游的绝佳线路。 +- **大境门**:历史悠久的古代关隘,现为重要的旅游景点。 + +**特色体验**: +- 高山徒步或山地自行车 +- 草原露营,观星 +- 体验草原骑马、射箭等活动 +- 品尝当地特色美食如莜面、烤全羊等 + +## 5日精选行程 + +### Day 1:抵达秦皇岛 +- **上午**:抵达秦皇岛,入住北戴河海滨酒店 +- **下午**:游览北戴河海滨,在沙滩上休闲放松,适应海边环境 +- **晚上**:在海边餐厅享用新鲜海鲜大餐,体验当地夜市文化 + +### Day 2:深度游北戴河 +- **上午**:参观鸽子窝公园,欣赏日出和海滨风光 +- **下午**:游览老虎石海上公园,体验海边刺激项目 +- **晚上**:在沙滩参加篝火晚会或沙滩音乐节(夏季常有活动) + +### Day 3:前往承德 +- **上午**:从秦皇岛出发前往承德(车程约3小时) +- **下午**:抵达承德后入住酒店,游览承德市区,调整状态 +- **晚上**:品尝承德特色菜,如承德火锅、双鱼涮肉等 + +### Day 4:游览避暑山庄 +- **全天**:游览避暑山庄及外八庙,深度了解清代历史文化 +- **推荐路线**:上午游览宫殿区和湖区,下午游览平原区和部分外八庙 +- **晚上**:观看承德民俗文化表演,体验满族文化 + +### Day 5:前往崇礼 +- **上午**:从承德出发前往崇礼(车程约3.5小时) +- **下午**:抵达崇礼,游览太舞滑雪小镇,体验夏季高山活动 +- **晚上**:在山间酒店或民宿住宿,享受凉爽的夏夜 + +## 交通指南 + +### 外部交通 +- **航空**:秦皇岛山海关机场和张家口宁远机场都有定期航班 +- **铁路**:京津冀地区高铁网络发达,从北京到秦皇岛、承德、张家口均有高速列车 +- **公路**:京承高速、京沈高速等多条高速公路连接主要城市 + +### 内部交通 +- **租车自驾**:最为便捷的出行方式,特别是在崇礼地区 +- **公共交通**:各城市均有完善的公交系统,景区间有旅游专线 +- **网约车**:主要城市都有滴滴等网约车服务 +- **景区交通**:大型景区内有电瓶车等交通工具 + +## 住宿推荐 + +### 秦皇岛北戴河 +- **高端选择**:北戴河嘉轩酒店、秦皇岛海景国际大酒店 +- **中端选择**:北戴河阿那亚一宿、如家精选酒店 +- **经济选择**:北戴河海滨青年旅舍、驿家365连锁酒店 + +### 承德 +- **高端选择**:承德喜来登酒店、承德避暑山庄皇冠假日酒店 +- **中端选择**:承德金龙温泉酒店、承德万豪酒店 +- **经济选择**:如家快捷酒店、汉庭酒店 + +### 崇礼 +- **高端选择**:崇礼富龙滑雪场酒店、万龙滑雪场度假酒店 +- **中端选择**:太舞滑雪小镇酒店、崇礼云顶酒店 +- **经济选择**:崇礼家庭民宿、青年旅舍 + +## 美食指南 + +### 秦皇岛 +- **海鲜**:螃蟹、对虾、扇贝等各类新鲜海鲜 +- **特色小吃**:面茶、烧烤、杂鱼汤等 + +### 承德 +- **满族菜**:关东糟熘鱼、清蒸鹿尾、八旗布丁等 +- **地方特色**:承德火锅、双鱼涮肉、驴肉火烧 + +### 崇礼 +- **农家菜**:莜面、土豆、山野菜等 +- **烤全羊**:草原特色美食 +- **奶制品**:酸奶、奶豆腐、奶茶等 + +## 旅行贴士 + +### 装备建议 +- **衣物**:虽是避暑胜地,但早晚温差大,建议带薄外套 +- **防晒**:高原和海边紫外线强,做好防晒措施 +- **泳装**:在北戴河需要,可以海滨游泳 +- **徒步鞋**:适合在崇礼的山地徒步 + +### 安全提示 +- **海边安全**:注意遵守游泳区域规定,不要在无人看管的海域游泳 +- **高原反应**:崇礼海拔虽不算高,但部分敏感人群可能有轻微高原反应 +- **天气变化**:山区天气多变,出行前查看天气预报 + +### 其他建议 +- **最佳游览时间**:避开周末和节假日,人流量会少很多 +- **预订建议**:夏季是旅游旺季,建议提前1-2个月预订酒店和交通 +- **当地习俗**:承德有满族和蒙古族居民,崇礼有蒙古族传统,注意尊重当地习俗 + +## 花费参考 + +### 交通费用 +- 北京到秦皇岛高铁:约150-250元/人 +- 秦皇岛到承德汽车:约150元/人 +- 承德到崇礼汽车:约180元/人 +- 租车费用:约300-500元/天 + +### 住宿费用 +- 高端酒店:800-2000元/晚 +- 中端酒店:400-800元/晚 +- 经济型住宿:150-400元/晚 + +### 餐饮费用 +- 海鲜大餐:200-500元/人 +- 普通正餐:50-100元/人 +- 特色小吃:20-50元/人 + +### 门票费用 +- 北戴河景区:大多免费或30-50元 +- 避暑山庄:115-145元(淡旺季不同) +- 外八庙联票:约100元 +- 崇礼景区:多为30-80元 + +## 总结 + +河北夏季避暑休闲之旅集海滨风光、皇家园林与高山草原于一体,为游客提供多样化的避暑选择。在炎炎夏日,您可以在北戴河感受海风拂面的舒适,在避暑山庄体验皇家的惬意生活,在崇礼的高山草原呼吸最清新的空气。这条路线既能让您避开酷暑,又能体验河北丰富的自然风光和人文历史,是夏季旅行的绝佳选择。 + +祝您旅途愉快! \ No newline at end of file diff --git a/web/graduation/src/layouts/MainLayout.astro b/web/graduation/src/layouts/MainLayout.astro new file mode 100644 index 0000000..0636b76 --- /dev/null +++ b/web/graduation/src/layouts/MainLayout.astro @@ -0,0 +1,145 @@ +--- +import "../styles/global.css"; +import ThemeToggle from "../components/ThemeToggle.astro"; +import DarkModeTransition from "../components/DarkModeTransition.astro"; +import SmoothScroll from "../components/aceternity/SmoothScroll.astro"; + +interface Props { + title: string; + description?: string; +} + +const { title, description = "河北游礼宣传网站 - 探索河北的文化与魅力" } = Astro.props; +--- + + + + + + + + + + {title} + + + + + +
+
+
+
+ + + + + + + +
+ + + + +
+
+ + + +
+
+ +
+ +
+ + +
+ + + + \ No newline at end of file diff --git a/web/graduation/src/pages/attractions/[slug].astro b/web/graduation/src/pages/attractions/[slug].astro new file mode 100644 index 0000000..c9167d3 --- /dev/null +++ b/web/graduation/src/pages/attractions/[slug].astro @@ -0,0 +1,308 @@ +--- +import { getCollection, getEntry, type CollectionEntry } from "astro:content"; +import MainLayout from "../../layouts/MainLayout.astro"; +import ScrollReveal from "../../components/aceternity/ScrollReveal.astro"; + +// 定义Props类型 +export interface Props { + entry: CollectionEntry<"attractions">; +} + +// 生成静态路径 +export async function getStaticPaths() { + const attractions = await getCollection("attractions"); + return attractions.map((entry: CollectionEntry<"attractions">) => ({ + params: { slug: entry.slug }, + props: { entry }, + })); +} + +// 获取当前景点数据 +const { entry } = Astro.props; +const { Content } = await entry.render(); + +// 获取相关景点 +const allAttractions = await getCollection("attractions"); +const relatedAttractions = allAttractions + .filter( + (item: CollectionEntry<"attractions">) => + item.slug !== entry.slug && + item.data.tags.some((tag: string) => entry.data.tags.includes(tag)) + ) + .slice(0, 3); +--- + + + +
+
+
+
+ +
+ 首页 + / + 景点 + / + {entry.data.title} +
+ +

{entry.data.title}

+ +
+ {entry.data.city && ( +
+ 📍 {entry.data.city} +
+ )} + + {entry.data.pubDate && ( +
+ 📅 {new Date(entry.data.pubDate).toLocaleDateString('zh-CN')} +
+ )} +
+ +
+ {entry.data.tags.map((tag: string) => ( + + {tag} + + ))} +
+ +

{entry.data.description}

+
+
+
+
+ + +
+
+
+ +
+ +
+ +
+
+
+ + +
+ + +
+
+ {entry.data.title} 图片 +
+
+
+ + + +
+

景点信息

+ +
+ {entry.data.city && ( +
+ 位置: + {entry.data.city} +
+ )} + +
+ 景点类型: +
+ {entry.data.tags.map((tag: string) => ( + + {tag} + + ))} +
+
+ + {entry.data.pubDate && ( +
+ 发布时间: + {new Date(entry.data.pubDate).toLocaleDateString('zh-CN')} +
+ )} + +
+
+
+ + +
+

+ + + + 交通指南 +

+
    +
  • + 🚌 + 公交路线: 10路, 15路, 22路到河北博物馆站下车 +
  • +
  • + 🚗 + 自驾路线: 导航至"{entry.data.title}"即可 +
  • +
  • + 🚄 + 高铁/火车: 到达石家庄站后可换乘公交或出租车 +
  • +
+
+ + +
+

+ + + + 参观信息 +

+
+

+ + 开放时间: 09:00-17:00 (周一至周日) +

+

+ 🎫 + 门票: 成人票50元, 学生票25元 +

+

+ 📞 + 咨询电话: 0311-12345678 +

+
+
+ + +
+

+ + + + 周边推荐 +

+
    +
  • + 🏯 + 历史文化街区 (步行10分钟) +
  • +
  • + 🍜 + 老字号餐馆一条街 (步行15分钟) +
  • +
  • + 🏞️ + 城市公园 (步行20分钟) +
  • +
+
+ + + {relatedAttractions.length > 0 && ( +
+

相关景点

+
+ {relatedAttractions.map((attraction: CollectionEntry<"attractions">) => ( + +
+
+ {attraction.data.title.substring(0, 1)} +
+
+

+ {attraction.data.title} +

+

+ {attraction.data.description} +

+
+
+
+ ))} +
+
+ )} + + + + + 返回所有景点 + + +
+
+
+
+ + + +
\ No newline at end of file diff --git a/web/graduation/src/pages/attractions/index.astro b/web/graduation/src/pages/attractions/index.astro new file mode 100644 index 0000000..f670ecb --- /dev/null +++ b/web/graduation/src/pages/attractions/index.astro @@ -0,0 +1,661 @@ +--- +import MainLayout from "../../layouts/MainLayout.astro"; +import { getCollection, type CollectionEntry } from "astro:content"; +import ScrollReveal from "../../components/aceternity/ScrollReveal.astro"; + +// 获取景点内容集合 +const attractions = await getCollection("attractions"); + +// 按照日期排序 +const sortByDate = (a: T, b: T): number => { + return new Date(b.data.pubDate || b.data.updatedDate || 0).getTime() - + new Date(a.data.pubDate || a.data.updatedDate || 0).getTime(); +}; + +// 按发布日期排序 +const sortedAttractions = [...attractions].sort(sortByDate); + +// 提取所有标签 +const allTags: {name: string, count: number}[] = []; +sortedAttractions.forEach((attraction: CollectionEntry<"attractions">) => { + attraction.data.tags.forEach((tag: string) => { + const existingTag = allTags.find(t => t.name === tag); + if (existingTag) { + existingTag.count++; + } else { + allTags.push({ name: tag, count: 1 }); + } + }); +}); + +// 按照标签出现次数排序 +allTags.sort((a, b) => b.count - a.count); + +// 获取所有分类并计数 (使用可选链以防属性不存在) +const categories: {name: string, count: number}[] = []; +sortedAttractions.forEach((attraction: CollectionEntry<"attractions">) => { + // 从city或title中提取分类信息(因为原数据模型似乎没有category字段) + const category = attraction.data.city?.split(',')[0] || '其他景点'; + const existingCategory = categories.find(c => c.name === category); + if (existingCategory) { + existingCategory.count++; + } else { + categories.push({ name: category, count: 1 }); + } +}); + +// 按照分类出现次数排序 +categories.sort((a, b) => b.count - a.count); + +// 获取所有城市并计数 (从city中提取城市信息) +const cities: {name: string, count: number}[] = []; +sortedAttractions.forEach((attraction: CollectionEntry<"attractions">) => { + if (attraction.data.city) { + const city = attraction.data.city.split(',').pop()?.trim() || '其他地区'; + const existingCity = cities.find(c => c.name === city); + if (existingCity) { + existingCity.count++; + } else { + cities.push({ name: city, count: 1 }); + } + } +}); + +// 按照城市出现次数排序 +cities.sort((a, b) => b.count - a.count); + +// 分页逻辑 +const itemsPerPage = 9; +const page = 1; // 当前页码,实际应用中应该从查询参数获取 +const totalPages = Math.ceil(sortedAttractions.length / itemsPerPage); +const currentPageAttractions = sortedAttractions.slice((page - 1) * itemsPerPage, page * itemsPerPage); + +// 搜索和筛选逻辑(实际应用中应该根据查询参数来筛选) +const searchQuery = ''; +const selectedCategory = ''; +const selectedCity = ''; +const selectedTags: string[] = []; +const sortBy: 'date' | 'name' = 'date'; + +// 辅助函数,用于获取景点的分类(从city提取或默认值) +const getCategory = (attraction: CollectionEntry<"attractions">) => { + return attraction.data.city?.split(',')[0] || '其他景点'; +}; + +// 辅助函数,用于获取景点的城市(从city提取或默认值) +const getCity = (attraction: CollectionEntry<"attractions">) => { + return attraction.data.city?.split(',').pop()?.trim() || '其他地区'; +}; +--- + + + +
+ +
+
+ +
+
+ +
+
+
+
+ +

+ 河北 + · + 景观 +

+ + +
+ ISO 100 + | + f/2.8 + | + 1/250s + | + 24-70mm +
+
+ +

+ 通过镜头捕捉河北的自然与人文之美,每一处景点都是一幅值得细细品味的画作 +

+ + +
+
+ + + + + EXPLORE +
+
+
+
+
+ + + + + + +
+
+
+ +
+
+
+ +
+
+ +
+ + + +
+
+ +
+ + +
+ +

按分类浏览

+
+ {categories.slice(0, 8).map(category => ( +
+ {category.name} + ({category.count}) +
+ ))} +
+
+ + +
+ +

按城市浏览

+
+ {cities.slice(0, 6).map(city => ( +
+ {city.name} + ({city.count}) +
+ ))} +
+
+ + +
+ +

特色标签

+
+ {allTags.slice(0, 12).map(tag => ( +
+ #{tag.name} + ({tag.count}) +
+ ))} +
+
+
+ + +
+
+
+ + + +
+

+ 筛选条件就像相机参数,调整它们以找到最适合你的河北景观视角。 +

+
+
+
+
+ + +
+ + {(searchQuery || selectedCategory || selectedCity || selectedTags.length > 0) && ( +
+
+
FILTER
+ + {/* 这里显示筛选条件 */} + + +
+
+ )} + + +
+ {currentPageAttractions.map((attraction, index) => { + // 为每个卡片生成不同的颜色方案 + const colorSchemes = [ + { from: 'from-primary-900/80', via: 'via-primary-900/60', to: 'to-transparent', border: 'border-primary-200 dark:border-primary-800', hover: 'group-hover:border-primary-300 dark:group-hover:border-primary-700', badges: 'bg-primary-900/70 text-primary-100' }, + { from: 'from-secondary-900/80', via: 'via-primary-900/60', to: 'to-transparent', border: 'border-secondary-200 dark:border-secondary-800', hover: 'group-hover:border-secondary-300 dark:group-hover:border-secondary-700', badges: 'bg-secondary-900/70 text-secondary-100' }, + { from: 'from-accent-900/80', via: 'via-accent-900/60', to: 'to-transparent', border: 'border-accent-200 dark:border-accent-800', hover: 'group-hover:border-accent-300 dark:group-hover:border-accent-700', badges: 'bg-accent-900/70 text-accent-100' } + ]; + const colorScheme = colorSchemes[index % colorSchemes.length]; + + return ( + + +
+ +
+
+ {attraction.data.title} +
+ + +
+
+
+ +
+
+ + + + {getCategory(attraction)} +
+ +
+ + + + + {getCity(attraction)} +
+
+ + +

{attraction.data.title}

+ + +

+ {attraction.data.description} +

+ + +
+ {attraction.data.tags.slice(0, 3).map((tag: string, i: number) => { + const tagColors = ['bg-primary-500/40', 'bg-secondary-500/40', 'bg-accent-500/40']; + return ( + + #{tag} + + ); + })} + {attraction.data.tags.length > 3 && ( + + +{attraction.data.tags.length - 3} + + )} +
+
+
+
+
+ + +
+ EXPLORE +
+ 查看详情 + + + +
+
+
+
+
+ ); + })} +
+ + + {totalPages > 1 && ( + + )} + + +
+
+
+ "最美的景观并非仅存于远方,而在于观察者如何用心去发现" +
+
+ — 河北风光摄影集 — +
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/web/graduation/src/pages/cuisine/[slug].astro b/web/graduation/src/pages/cuisine/[slug].astro new file mode 100644 index 0000000..9222cdd --- /dev/null +++ b/web/graduation/src/pages/cuisine/[slug].astro @@ -0,0 +1,177 @@ +--- +import { getCollection, getEntry, type CollectionEntry } from "astro:content"; +import MainLayout from "../../layouts/MainLayout.astro"; +import ScrollReveal from "../../components/aceternity/ScrollReveal.astro"; + +// 定义Props类型 +export interface Props { + entry: CollectionEntry<"cuisine">; +} + +// 生成静态路径 +export async function getStaticPaths() { + const cuisines = await getCollection("cuisine"); + return cuisines.map((entry) => ({ + params: { slug: entry.slug }, + props: { entry }, + })); +} + +// 获取当前美食数据 +const { entry } = Astro.props; +const { Content } = await entry.render(); + +// 获取相关美食 +const allCuisines = await getCollection("cuisine"); +const relatedCuisines = allCuisines + .filter( + (item) => + item.slug !== entry.slug && + (item.data.category === entry.data.category || + item.data.tags.some((tag) => entry.data.tags.includes(tag))) + ) + .slice(0, 3); +--- + + + +
+ +
+ {entry.data.title} +
+
+ + +
+
+ +
+ +
+ 首页 + / + 美食 + / + {entry.data.title} +
+ +

{entry.data.title}

+ + +
+ {entry.data.tags.map((tag) => ( + + {tag} + + ))} +
+ +

{entry.data.description}

+
+
+
+
+
+ + +
+
+
+ +
+ +
+
+ +
+
+
+
+ + +
+ + {entry.data.ingredients && entry.data.ingredients.length > 0 && ( + +
+

+ 🥘 + 主要食材 +

+
+ {entry.data.ingredients.map((ingredient) => ( +
+ + {ingredient} +
+ ))} +
+
+
+ )} + + + +
+

+ ⏱️ + 烹饪信息 +

+
+
+ 烹饪时间 + {entry.data.cookTime || '45分钟'} +
+
+ 难度等级 + {entry.data.difficulty || '中等'} +
+
+
+
+ + + {relatedCuisines.length > 0 && ( + +
+

+ 🍽️ + 相关美食 +

+
+ {relatedCuisines.map((cuisine) => ( + +
+ {cuisine.data.title} +
+

+ {cuisine.data.title} +

+

+ {cuisine.data.category} +

+
+
+
+ ))} +
+
+
+ )} +
+
+
+
+
diff --git a/web/graduation/src/pages/cuisine/index.astro b/web/graduation/src/pages/cuisine/index.astro new file mode 100644 index 0000000..872b198 --- /dev/null +++ b/web/graduation/src/pages/cuisine/index.astro @@ -0,0 +1,893 @@ +--- +import MainLayout from "../../layouts/MainLayout.astro"; +import { getCollection, type CollectionEntry } from "astro:content"; +import ScrollReveal from "../../components/aceternity/ScrollReveal.astro"; + +// 获取美食内容集合 +const cuisines = await getCollection("cuisine"); + +// 按照日期排序 +const sortByDate = (a: T, b: T): number => { + return new Date(b.data.pubDate || b.data.updatedDate || 0).getTime() - + new Date(a.data.pubDate || a.data.updatedDate || 0).getTime(); +}; + +// 按发布日期排序 +const sortedCuisines = [...cuisines].sort(sortByDate); + +// 提取所有标签 +const allTags: {name: string, count: number}[] = []; +sortedCuisines.forEach((cuisine: CollectionEntry<"cuisine">) => { + cuisine.data.tags.forEach((tag: string) => { + const existingTag = allTags.find(t => t.name === tag); + if (existingTag) { + existingTag.count++; + } else { + allTags.push({ name: tag, count: 1 }); + } + }); +}); + +// 按照标签出现次数排序 +allTags.sort((a, b) => b.count - a.count); + +// 获取所有分类并计数 +const categories: {name: string, count: number}[] = []; +sortedCuisines.forEach((cuisine: CollectionEntry<"cuisine">) => { + if (cuisine.data.category) { + const existingCategory = categories.find(c => c.name === cuisine.data.category); + if (existingCategory) { + existingCategory.count++; + } else { + categories.push({ name: cuisine.data.category, count: 1 }); + } + } +}); + +// 按照分类出现次数排序 +categories.sort((a, b) => b.count - a.count); + +// 获取所有产地并计数 +const citys: {name: string, count: number}[] = []; +sortedCuisines.forEach((cuisine: CollectionEntry<"cuisine">) => { + if (cuisine.data.city) { + const existingcity = citys.find(o => o.name === cuisine.data.city); + if (existingcity) { + existingcity.count++; + } else { + citys.push({ name: cuisine.data.city, count: 1 }); + } + } +}); + +// 按照产地出现次数排序 +citys.sort((a, b) => b.count - a.count); + +// 获取所有口味并计数 +const tastes: {name: string, count: number}[] = []; +sortedCuisines.forEach((cuisine: CollectionEntry<"cuisine">) => { + if (cuisine.data.taste) { + const existingTaste = tastes.find(t => t.name === cuisine.data.taste); + if (existingTaste) { + existingTaste.count++; + } else { + tastes.push({ name: cuisine.data.taste, count: 1 }); + } + } +}); + +// 按照口味出现次数排序 +tastes.sort((a, b) => b.count - a.count); + +// 分页逻辑 +const itemsPerPage = 9; +// 从URL参数获取当前页码 +const url = new URL(Astro.request.url); +const page = parseInt(url.searchParams.get('page') || '1'); +const totalPages = Math.ceil(sortedCuisines.length / itemsPerPage); +const currentPageCuisines = sortedCuisines.slice((page - 1) * itemsPerPage, page * itemsPerPage); + +// 搜索和筛选逻辑(实际应用中应该根据查询参数来筛选) +const searchQuery = ''; +const selectedCategory = ''; +const selectedcity = ''; +const selectedTaste = ''; +const selectedTags: string[] = []; +const sortBy: 'date' | 'name' = 'date'; + +// 分页参数 +const queryParams = ''; // 在实际应用中,这里应该是基于筛选条件构建的查询字符串 +--- + + + +
+ +
+
+
+ + +
+
+ +
+
+
+
+ + +
+

河北美食食谱

+
+

+ 收集自河北各地的传统美食配方, +
家传秘方与地方特色,尽在此食谱 +

+
+ + + + + + + + + +
+
+ + +
+
+
+ + +
+
+
+ +
+
+ +
+ +
+

+ + + + 食谱检索 +

+
+ +
+ + + +
+
+
+ + +
+

+ + + + 菜系 +

+
+ {categories.map((category, index) => ( + + ))} +
+
+ + +
+

+ + + + + 地域特色 +

+
+ {citys.map((city) => ( + + ))} +
+
+ + +
+

+ + + + 味道特点 +

+
+ {tastes.map((taste) => ( + + ))} +
+
+ + +
+

+ + + + 主要食材 +

+
+ {allTags.map((tag, i) => { + // 为标签生成不同的颜色 + const colors = ['amber', 'red', 'green', 'orange', 'amber', 'red']; + const colorClasses = { + 'amber': 'border-amber-200 dark:border-amber-800 text-amber-700 dark:text-amber-300 hover:text-amber-900 dark:hover:text-amber-200 hover:border-amber-400 dark:hover:border-amber-700 bg-amber-50/50 dark:bg-amber-900/20', + 'red': 'border-red-200 dark:border-red-800 text-red-700 dark:text-red-300 hover:text-red-900 dark:hover:text-red-200 hover:border-red-400 dark:hover:border-red-700 bg-red-50/50 dark:bg-red-900/20', + 'green': 'border-green-200 dark:border-green-800 text-green-700 dark:text-green-300 hover:text-green-900 dark:hover:text-green-200 hover:border-green-400 dark:hover:border-green-700 bg-green-50/50 dark:bg-green-900/20', + 'orange': 'border-orange-200 dark:border-orange-800 text-orange-700 dark:text-orange-300 hover:text-orange-900 dark:hover:text-orange-200 hover:border-orange-400 dark:hover:border-orange-700 bg-orange-50/50 dark:bg-orange-900/20' + }; + const color = colors[i % colors.length] as keyof typeof colorClasses; + return ( +
+ {tag.name} +
+ ); + })} +
+
+
+ + +
+
+
+ + + +
+
+

食谱小贴士

+

+ 烹饪是一门艺术,每一道河北美食都融合了独特的地域文化和历史传承,讲究用料、火候与调味的绝妙平衡。 +

+
+
+
+
+
+ + +
+ + {(searchQuery || selectedCategory || selectedcity || selectedTaste || selectedTags.length > 0) && ( +
+
+
筛选条件
+ + {/* 筛选条件显示 */} + + +
+
+ )} + + +
+ {currentPageCuisines.map((cuisine, index) => { + // 随机食谱纸张背景颜色 + const paperColors = [ + 'bg-amber-50/80 border-amber-200 dark:bg-amber-900/30 dark:border-amber-800', + 'bg-orange-50/80 border-orange-200 dark:bg-orange-900/30 dark:border-orange-800', + 'bg-red-50/80 border-red-200 dark:bg-red-900/30 dark:border-red-800', + 'bg-green-50/80 border-green-200 dark:bg-green-900/30 dark:border-green-800' + ]; + const paperColor = paperColors[index % paperColors.length]; + + return ( + + +
+ +
+
+ {cuisine.data.title} +
+ + +
+
+ + {cuisine.data.category && ( +
+ {cuisine.data.category} +
+ )} + + + {Math.random() > 0.7 && ( +
+
+ 推荐 +
+
+ )} +
+ + +
+

+ {cuisine.data.title} +

+ + +
+ {cuisine.data.city && ( +
+ {cuisine.data.city} +
+ )} + {cuisine.data.taste && ( +
+ {cuisine.data.taste} +
+ )} +
+ + +

+ {cuisine.data.description} +

+ + +
+ {cuisine.data.tags.slice(0, 3).map((tag: string, i: number) => ( + + #{tag} + + ))} + {cuisine.data.tags.length > 3 && ( + + +{cuisine.data.tags.length - 3} + + )} +
+ + +
+
+ 查看详细食谱 + + + +
+ + + {Math.random() > 0.5 && ( + 传统 + )} + {Math.random() > 0.7 && ( + 家常 + )} +
+
+
+
+
+ ); + })} +
+ + + + + +
+
+
+ Chef Hat +
+

+ 河北美食宝库收录了{cuisines.length}+种传统佳肴与地方特色小吃。 + 每一道食谱都承载着我们的文化记忆和烹饪智慧。尽情探索,找到属于你的美食灵感! +

+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/web/graduation/src/pages/culture/[slug].astro b/web/graduation/src/pages/culture/[slug].astro new file mode 100644 index 0000000..96b4c28 --- /dev/null +++ b/web/graduation/src/pages/culture/[slug].astro @@ -0,0 +1,193 @@ +--- +import { getCollection, getEntry, type CollectionEntry } from "astro:content"; +import MainLayout from "../../layouts/MainLayout.astro"; +import ScrollReveal from "../../components/aceternity/ScrollReveal.astro"; + +// 定义Props类型 +export interface Props { + entry: CollectionEntry<"culture">; +} + +// 生成静态路径 +export async function getStaticPaths() { + const cultures = await getCollection("culture"); + return cultures.map((entry) => ({ + params: { slug: entry.slug }, + props: { entry }, + })); +} + +// 获取当前文化数据 +const { entry } = Astro.props; +const { Content } = await entry.render(); + +// 获取相关文化 +const allCultures = await getCollection("culture"); +const relatedCultures = allCultures + .filter( + (item) => + item.slug !== entry.slug && + item.data.tags.some((tag) => entry.data.tags.includes(tag)) + ) + .slice(0, 3); +--- + + + +
+
+
+
+ +
+ 首页 + / + 文化 + / + {entry.data.title} +
+ +

{entry.data.title}

+ +
+ {entry.data.city && ( +
+ 📍 {entry.data.city} +
+ )} + + {entry.data.pubDate && ( +
+ 📅 {new Date(entry.data.pubDate).toLocaleDateString('zh-CN')} +
+ )} + +
+ 🏷️ {entry.data.category} +
+
+ +
+ {entry.data.tags.map((tag) => ( + + {tag} + + ))} +
+ +

{entry.data.description}

+
+
+
+
+ + +
+
+
+ +
+ +
+ +
+
+
+ + +
+ + +
+
+ {entry.data.title} 图片 +
+
+
+ + + +
+

文化信息

+ +
+ {entry.data.city && ( +
+ 分布地区: + {entry.data.city} +
+ )} + +
+ 文化类型: + {entry.data.category} +
+ +
+ 特色标签: +
+ {entry.data.tags.map((tag) => ( + + {tag} + + ))} +
+
+ + {entry.data.pubDate && ( +
+ 发布时间: + {new Date(entry.data.pubDate).toLocaleDateString('zh-CN')} +
+ )} + +
+
+
+ + + {relatedCultures.length > 0 && ( + + + + )} + + + + + 返回所有文化 + + +
+
+
+
+
\ No newline at end of file diff --git a/web/graduation/src/pages/culture/index.astro b/web/graduation/src/pages/culture/index.astro new file mode 100644 index 0000000..0c78d40 --- /dev/null +++ b/web/graduation/src/pages/culture/index.astro @@ -0,0 +1,763 @@ +--- +import MainLayout from "../../layouts/MainLayout.astro"; +import { getCollection, type CollectionEntry } from "astro:content"; +import ScrollReveal from "../../components/aceternity/ScrollReveal.astro"; + +// 获取文化内容集合 +const cultures = await getCollection("culture"); + +// 按照日期排序 +const sortByDate = (a: T, b: T): number => { + return new Date(b.data.pubDate || b.data.updatedDate || 0).getTime() - + new Date(a.data.pubDate || a.data.updatedDate || 0).getTime(); +}; + +// 按发布日期排序 +const sortedCultures = [...cultures].sort(sortByDate); + +// 提取所有标签 +const allTags: {name: string, count: number}[] = []; +sortedCultures.forEach((culture: CollectionEntry<"culture">) => { + culture.data.tags.forEach((tag: string) => { + const existingTag = allTags.find(t => t.name === tag); + if (existingTag) { + existingTag.count++; + } else { + allTags.push({ name: tag, count: 1 }); + } + }); +}); + +// 按照标签出现次数排序 +allTags.sort((a, b) => b.count - a.count); + +// 获取所有分类并计数 +const categories: {name: string, count: number}[] = []; +sortedCultures.forEach((culture: CollectionEntry<"culture">) => { + if (culture.data.category) { + const existingCategory = categories.find(c => c.name === culture.data.category); + if (existingCategory) { + existingCategory.count++; + } else { + categories.push({ name: culture.data.category, count: 1 }); + } + } +}); + +// 按照分类出现次数排序 +categories.sort((a, b) => b.count - a.count); + +// 获取所有城市并计数 +const cities: {name: string, count: number}[] = []; +sortedCultures.forEach((culture: CollectionEntry<"culture">) => { + if (culture.data.city) { + const existingCity = cities.find(c => c.name === culture.data.city); + if (existingCity) { + existingCity.count++; + } else { + cities.push({ name: culture.data.city, count: 1 }); + } + } +}); + +// 按照城市出现次数排序 +cities.sort((a, b) => b.count - a.count); + +// 提取所有历史时期 +const periods: {name: string, count: number}[] = []; +sortedCultures.forEach((culture: CollectionEntry<"culture">) => { + if ((culture.data as any).period) { + const existingPeriod = periods.find(p => p.name === (culture.data as any).period); + if (existingPeriod) { + existingPeriod.count++; + } else { + periods.push({ name: (culture.data as any).period, count: 1 }); + } + } +}); + +// 按照历史时期出现次数排序 +periods.sort((a, b) => b.count - a.count); + +// 分页逻辑 +const itemsPerPage = 9; +const page = 1; // 当前页码,实际应用中应该从查询参数获取 +const totalPages = Math.ceil(sortedCultures.length / itemsPerPage); +const currentPageCultures = sortedCultures.slice((page - 1) * itemsPerPage, page * itemsPerPage); + +// 搜索和筛选逻辑(实际应用中应该根据查询参数来筛选) +const searchQuery = ''; +const selectedCategory = ''; +const selectedPeriod = ''; +const selectedTags: string[] = []; +const sortBy: 'date' | 'name' = 'date'; +--- + + + +
+ +
+
+
+ + +
+
+ + +
+
+ +
+ +
+ + +
+ + +
+
+
+
+ +
+

河北文化典藏

+

文化瑰宝

+
+
+ +

+ 典藏千年冀州文明,承载河北厚重历史文化积淀, +
以字画诗词、戏曲非遗,述说河北文化的绵长与精彩 +

+ + +
+
+ 周 · 秦 · 汉 · 唐 · 宋 · 元 · 明 · 清 · 民国 · 现代 +
+
+
+
+
+
+ + +
+
+
+ + + +
+
+ +
+
+ +
+
+ +

+ 河北,古称"冀州",是中华文明的发祥地之一。这片土地上流传着众多的文化瑰宝,从京剧、评剧等传统戏曲,到皮影、剪纸等民间艺术,从千年古刹到悠久历史的传统习俗,共同构成了河北独特而丰富的文化景观。 +

+
+
+ +
+ +
+
+ +
+

+ + + + 典籍检索 +

+ +
+ +
+ + + +
+
+
+ + +
+

+ + + + 文化分类 +

+ +
+ {categories.map((category) => ( +
+
+
+ {category.name} + ({category.count}) +
+
+ ))} +
+
+ + +
+

+ + + + + 地域分布 +

+ +
+ {cities.map((city) => ( +
+
+
+ {city.name} + ({city.count}) +
+
+ ))} +
+
+ + +
+

+ + + + 历史朝代 +

+ +
+ {periods.map((period) => ( +
+
+
+ {period.name} + ({period.count}) +
+
+ ))} +
+
+ + +
+

+ + + + 特色标签 +

+ +
+ {allTags.map((tag) => ( + + {tag.name} + + ))} +
+
+
+
+ + + +
+
+
+
+ + + + diff --git a/web/graduation/src/pages/hover-effects-new.astro b/web/graduation/src/pages/hover-effects-new.astro new file mode 100644 index 0000000..91d5a7a --- /dev/null +++ b/web/graduation/src/pages/hover-effects-new.astro @@ -0,0 +1,227 @@ +--- +import MainLayout from "../layouts/MainLayout.astro"; +import FlipCard from "../components/aceternity/FlipCard.astro"; +import ParticleButton from "../components/aceternity/ParticleButton.astro"; +import MagneticElement from "../components/aceternity/MagneticElement.astro"; +import ParallaxCard from "../components/aceternity/ParallaxCard.astro"; +import NeonButton from "../components/aceternity/NeonButton.astro"; +import SpotlightCard from "../components/aceternity/SpotlightCard.astro"; +import HoverGlow from "../components/aceternity/HoverGlow.astro"; +import ScrollReveal from "../components/aceternity/ScrollReveal.astro"; +--- + + +
+
+
+

酷炫交互效果展示

+

探索我们网站上使用的各种交互和悬停效果

+
+ + +
+ + +
+

3D翻转卡片

+
+ +
+ 长城 +

中国最具代表性的文化象征

+
+
+

河北拥有全国最长的长城线,包括八达岭、金山岭等著名景点。

+ 了解更多 +
+
+ + +
+ 蔚县剪纸 +

传统民间艺术

+
+
+

蔚县剪纸以其独特的造型和鲜明的色彩闻名,展现了浓郁的民俗风情。

+ 了解更多 +
+
+ + +
+ 保定莲花酥 +

清代宫廷点心

+
+
+

酥脆可口,形如莲花,是保定地区传统的伴手礼。

+ 了解更多 +
+
+
+
+ + +
+

粒子特效按钮

+
+ + + + + +
+
+ + +
+

磁性吸附效果

+
+ +
+ 弱吸附效果 +
+
+ + +
+ 中吸附效果 +
+
+ + +
+ 强吸附效果 +
+
+
+
+ + +
+

3D视差卡片

+
+ +

承德避暑山庄

+

中国清代皇家园林,世界文化遗产

+ 了解更多 +
+ + +

秦皇岛海滨

+

碧海蓝天,金色沙滩

+ 了解更多 +
+
+
+ + +
+

霓虹灯按钮

+
+ + + + + +
+
+ + +
+

聚光灯卡片

+
+ +
+
+ 旅游景点类型: + 历史文化 + 古建筑 + 名城古镇 +
+
+ 了解更多 → +
+
+
+ + +
+

悬停发光效果

+
+ +
+

内画鼻烟壶

+

河北衡水传统工艺品,在小小的鼻烟壶内壁上绘制精美图案。

+
+
+ + +
+

曲阳石雕

+

历史悠久的传统工艺,技艺精湛,形态逼真。

+
+
+ + +
+

景泰蓝

+

一种金属胎掐丝珐琅工艺品,色彩华丽绚烂。

+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/web/graduation/src/pages/index.astro b/web/graduation/src/pages/index.astro new file mode 100644 index 0000000..52a6fcd --- /dev/null +++ b/web/graduation/src/pages/index.astro @@ -0,0 +1,96 @@ +--- +import MainLayout from "../layouts/MainLayout.astro"; +import MorphingText from "../components/aceternity/MorphingText.astro"; + +// 导航数据 +--- + + + +
+ +
+ + +
+ + +
+ + +
+
+ +
+ + +
+ + + +
+ + \ No newline at end of file diff --git a/web/graduation/src/pages/travel/[slug].astro b/web/graduation/src/pages/travel/[slug].astro new file mode 100644 index 0000000..b633943 --- /dev/null +++ b/web/graduation/src/pages/travel/[slug].astro @@ -0,0 +1,249 @@ +--- +import { getCollection, getEntry, type CollectionEntry } from "astro:content"; +import MainLayout from "../../layouts/MainLayout.astro"; +import ScrollReveal from "../../components/aceternity/ScrollReveal.astro"; + +// 定义Props类型 +export interface Props { + entry: CollectionEntry<"travel">; +} + +// 生成静态路径 +export async function getStaticPaths() { + const travels = await getCollection("travel"); + return travels.map((entry) => ({ + params: { slug: entry.slug }, + props: { entry }, + })); +} + +// 获取当前旅行攻略数据 +const { entry } = Astro.props; +const { Content } = await entry.render(); + +// 获取相关旅行攻略 +const allTravels = await getCollection("travel"); +const relatedTravels = allTravels + .filter( + (item) => + item.slug !== entry.slug && + (item.data.season === entry.data.season || + item.data.type === entry.data.type || + item.data.tags.some((tag) => entry.data.tags.includes(tag))) + ) + .slice(0, 3); +--- + + + +
+
+
+
+ +
+ 首页 + / + 旅行攻略 + / + {entry.data.title} +
+ +

{entry.data.title}

+ +
+ {entry.data.season && ( +
+ 🌤️ {entry.data.season} +
+ )} + + {entry.data.type && ( +
+ 📋 {entry.data.type} +
+ )} + + {entry.data.days && ( +
+ ⏱️ {entry.data.days}天行程 +
+ )} + + {entry.data.difficulty && ( +
+ 🏔️ 难度:{entry.data.difficulty} +
+ )} +
+ +
+ {entry.data.tags.map((tag) => ( + + {tag} + + ))} +
+ +

{entry.data.description}

+
+
+
+
+ + +
+
+
+ +
+ +
+ +
+
+
+ + +
+ + +
+
+ {entry.data.title} 图片 +
+
+
+ + + +
+

攻略信息

+ +
+ {entry.data.season && ( +
+ 适宜季节: + {entry.data.season} +
+ )} + + {entry.data.type && ( +
+ 攻略类型: + {entry.data.type} +
+ )} + + {entry.data.days && ( +
+ 行程天数: + {entry.data.days}天 +
+ )} + + {entry.data.difficulty && ( +
+ 难度级别: + {entry.data.difficulty} +
+ )} + +
+ 旅行主题: +
+ {entry.data.tags.map((tag) => ( + + {tag} + + ))} +
+
+ + {entry.data.pubDate && ( +
+ 发布时间: + {new Date(entry.data.pubDate).toLocaleDateString('zh-CN')} +
+ )} + +
+
+
+ + + +
+

旅行小贴士

+ +
+
+ +

出行前查看天气预报,准备合适的衣物

+
+
+ +

提前规划路线,预订住宿和交通

+
+
+ +

携带必要的药品和紧急联系方式

+
+
+ +

尊重当地风俗习惯,做文明旅行者

+
+
+
+
+ + + {relatedTravels.length > 0 && ( + + + + )} + + + + + 返回所有攻略 + + +
+
+
+
+
\ No newline at end of file diff --git a/web/graduation/src/pages/travel/index.astro b/web/graduation/src/pages/travel/index.astro new file mode 100644 index 0000000..8cabc1d --- /dev/null +++ b/web/graduation/src/pages/travel/index.astro @@ -0,0 +1,452 @@ +--- +import MainLayout from "../../layouts/MainLayout.astro"; +import { getCollection, type CollectionEntry } from "astro:content"; +import ScrollReveal from "../../components/aceternity/ScrollReveal.astro"; + +// 获取旅行攻略集合 +const travels = await getCollection("travel"); + +// 按发布日期排序 +function sortByDate(a: T, b: T): number { + return new Date(b.data.pubDate || b.data.updatedDate || 0).getTime() - + new Date(a.data.pubDate || a.data.updatedDate || 0).getTime(); +} + +const sortedTravels = [...travels].sort(sortByDate); + +// 提取所有标签并按数量排序 +const allTags: {name: string, count: number}[] = []; +travels.forEach((travel) => { + travel.data.tags.forEach((tag: string) => { + const existing = allTags.find(t => t.name === tag); + if (existing) { + existing.count += 1; + } else { + allTags.push({ name: tag, count: 1 }); + } + }); +}); + +const sortedTags = [...allTags].sort((a, b) => b.count - a.count); + +// 提取所有季节 +const allSeasons = new Set(); +travels.forEach((travel) => { + if (travel.data.season) { + allSeasons.add(travel.data.season); + } +}); +const seasons = Array.from(allSeasons); + +// 提取所有类型 +const allTypes = new Set(); +travels.forEach((travel) => { + if (travel.data.type) { + allTypes.add(travel.data.type); + } +}); +const types = Array.from(allTypes); + +// 提取所有难度 +const allDifficulties = new Set(); +travels.forEach((travel) => { + if (travel.data.difficulty) { + allDifficulties.add(travel.data.difficulty); + } +}); +const difficulties = Array.from(allDifficulties); + +// 提取所有城市 +const cities: {name: string, count: number}[] = []; +travels.forEach((travel) => { + if (travel.data.city) { + const existingCity = cities.find(c => c.name === travel.data.city); + if (existingCity) { + existingCity.count++; + } else { + cities.push({ name: travel.data.city, count: 1 }); + } + } +}); + +// 按城市出现次数排序 +cities.sort((a, b) => b.count - a.count); + +// 分页相关 +const itemsPerPage = 10; +const totalPages = Math.ceil(sortedTravels.length / itemsPerPage); +const currentPage = 1; +const visibleTravels = sortedTravels.slice( + (currentPage - 1) * itemsPerPage, + currentPage * itemsPerPage +); +--- + + + +
+ +
+
+ +
+
+ +
+
+
+

+ 河北私人旅行笔记 +

+
+
+
+ +
+ +
+ +
+ +

+ 这里不是旅行社的标准路线,而是我私人的旅途记录和探索笔记... +

+

+ 河北的每一处风景都有自己的故事,我带着好奇心和相机,在乡间小路、古老城墙和山林溪流间留下足迹。现在,我愿与你分享这些未经商业包装的真实体验。 +

+
+ + +
+

+ + 地域指南 +

+
+
+ +

北部:长城脚下的古朴村落

+
+
+ +

南部:太行山下的幽静峡谷

+
+
+ +

东部:渤海湾的日出时分

+
+
+ +

西部:坝上草原的风与云

+
+
+
+
+
+
+
+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +

+ 旅行者笔记 +

+ + +
+ +
+ +
+ + + +
+
+
+ + +
+

+ + 最适季节 +

+
+ {seasons.map((season) => ( + + ))} +
+
+ + +
+

+ + 旅行方式 +

+
+ {types.map((type) => ( + + ))} +
+
+ + +
+

+ + 目的地城市 +

+
+ {cities.map((city) => ( + + ))} +
+
+ + +
+

+ + 旅行灵感 +

+
+ {sortedTags.slice(0, 10).map((tag) => ( + + # {tag.name} + + ))} +
+
+
+
+
+ + +
+ +
+

+ 这些是我在河北各地的旅行笔记,不是官方推荐,而是个人探索与体验的记录。希望能给你不一样的旅行灵感... +

+
+ + + + + +
+
+ + + {totalPages > 1 && ( + + )} + {totalPages > 2 && ( + + )} + {totalPages > 1 && ( + + )} +
+
+ + +
+

+ " + 旅行不在于目的地的远近,而在于看世界的眼光。河北的每一处风景,都值得用心发现... + " +

+
+
+
+
+
+
+
+
+
+ + + + diff --git a/web/graduation/src/styles/global.css b/web/graduation/src/styles/global.css new file mode 100644 index 0000000..87ecb69 --- /dev/null +++ b/web/graduation/src/styles/global.css @@ -0,0 +1,229 @@ +@import "tailwindcss"; + +/* 定义深色模式选择器 */ +@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); + +@theme { + /* 主色调 - 使用琥珀色系为主题色 */ + --color-primary-50: #fffbeb; + --color-primary-100: #fef3c7; + --color-primary-200: #fde68a; + --color-primary-300: #fcd34d; + --color-primary-400: #fbbf24; + --color-primary-500: #f59e0b; + --color-primary-600: #d97706; + --color-primary-700: #b45309; + --color-primary-800: #92400e; + --color-primary-900: #78350f; + --color-primary-950: #451a03; + + /* 辅助色调 - 暖棕色 */ + --color-secondary-50: #faf5f0; + --color-secondary-100: #f8f0e5; + --color-secondary-200: #f1e0cb; + --color-secondary-300: #e6c9a5; + --color-secondary-400: #d9ac7c; + --color-secondary-500: #c6915a; + --color-secondary-600: #b67d49; + --color-secondary-700: #96663d; + --color-secondary-800: #7d5636; + --color-secondary-900: #67472e; + --color-secondary-950: #3a2719; + + /* 强调色调 - 深红褐色 */ + --color-accent-50: #fdf2f2; + --color-accent-100: #f8e1e1; + --color-accent-200: #f3c7c7; + --color-accent-300: #e89f9f; + --color-accent-400: #dc7676; + --color-accent-500: #c95252; + --color-accent-600: #af3f3f; + --color-accent-700: #923636; + --color-accent-800: #783333; + --color-accent-900: #653131; + --color-accent-950: #3b1b1b; + + /* 中性色调 */ + --color-gray-50: #f8fafc; + --color-gray-100: #f1f5f9; + --color-gray-200: #e2e8f0; + --color-gray-300: #cbd5e1; + --color-gray-400: #94a3b8; + --color-gray-500: #64748b; + --color-gray-600: #475569; + --color-gray-700: #334155; + --color-gray-800: #1e293b; + --color-gray-900: #0f172a; + --color-gray-950: #020617; + + /* 深色模式颜色 */ + --color-dark-bg: #1a1613; + --color-dark-surface: #282420; + --color-dark-card: #332f2b; + --color-dark-border: #49443d; + --color-dark-text: #f1ece4; + --color-dark-text-secondary: #c4b9aa; + + /* 特殊主题背景 */ + --color-paper-light: #f8f5e8; + --color-paper-dark: #2d2822; + --color-recipe-light: #fdf7ed; + --color-recipe-dark: #302a23; + --color-scroll-light: #f5f1e6; + --color-scroll-dark: #2b2720; + --color-travel-light: #fef8e8; + --color-travel-dark: #2e2921; + + /* 黑暗模式中的强调色 */ + --color-dark-primary-50: #362713; + --color-dark-primary-100: #453319; + --color-dark-primary-200: #604826; + --color-dark-primary-300: #7c5d32; + --color-dark-primary-400: #9b763d; + --color-dark-primary-500: #bd914a; + --color-dark-primary-600: #d7ab65; + --color-dark-primary-700: #e9c689; + --color-dark-primary-800: #f5e0b3; + --color-dark-primary-900: #faefdb; +} + +:root { + /* 基础色调 */ + --bg-primary: var(--color-gray-50); + --bg-secondary: var(--color-gray-100); + --text-primary: var(--color-gray-900); + --text-secondary: var(--color-gray-700); + --border-color: var(--color-gray-300); + + /* 主题色调 */ + --theme-primary: var(--color-primary-500); + --theme-primary-light: var(--color-primary-400); + --theme-primary-dark: var(--color-primary-600); + --theme-primary-bg: var(--color-primary-50); + --theme-primary-bg-hover: var(--color-primary-100); + + /* 页面特殊背景 */ + --bg-paper: var(--color-paper-light); + --bg-recipe: var(--color-recipe-light); + --bg-scroll: var(--color-scroll-light); + --bg-travel: var(--color-travel-light); + + background-color: var(--bg-primary); + color: var(--text-primary); +} + +/* 深色模式样式 */ +[data-theme='dark'] { + /* 基础色调 */ + --bg-primary: var(--color-dark-bg); + --bg-secondary: var(--color-dark-surface); + --text-primary: var(--color-dark-text); + --text-secondary: var(--color-dark-text-secondary); + --border-color: var(--color-dark-border); + + /* 主题色调 */ + --theme-primary: var(--color-dark-primary-500); + --theme-primary-light: var(--color-dark-primary-400); + --theme-primary-dark: var(--color-dark-primary-600); + --theme-primary-bg: var(--color-dark-primary-100); + --theme-primary-bg-hover: var(--color-dark-primary-200); + + /* 页面特殊背景 */ + --bg-paper: var(--color-paper-dark); + --bg-recipe: var(--color-recipe-dark); + --bg-scroll: var(--color-scroll-dark); + --bg-travel: var(--color-travel-dark); + + background-color: var(--bg-primary); + color: var(--text-primary); +} + +/* 统一颜色类 */ +.bg-theme-primary { background-color: var(--theme-primary); } +.bg-theme-primary-light { background-color: var(--theme-primary-light); } +.bg-theme-primary-dark { background-color: var(--theme-primary-dark); } +.bg-theme-primary-bg { background-color: var(--theme-primary-bg); } + +.text-theme-primary { color: var(--theme-primary); } +.text-theme-primary-light { color: var(--theme-primary-light); } +.text-theme-primary-dark { color: var(--theme-primary-dark); } + +.border-theme-primary { border-color: var(--theme-primary); } +.border-theme-primary-light { border-color: var(--theme-primary-light); } +.border-theme-primary-dark { border-color: var(--theme-primary-dark); } + +/* 主题特殊背景 */ +.bg-scroll-bg { background-color: var(--bg-scroll); } +.bg-scroll-bg-dark { background-color: var(--bg-scroll); } +.bg-recipe-paper-light { background-color: var(--bg-recipe); } +.bg-recipe-paper-dark { background-color: var(--bg-recipe); } +.bg-ancient-paper { background-color: var(--bg-paper); } +.bg-ancient-paper-dark { background-color: var(--bg-paper); } + +/* 黑暗模式下的卡片样式覆盖 */ +[data-theme='dark'] .bg-white { + background-color: var(--color-dark-card); +} + +/* 黑暗模式下的文本颜色覆盖 */ +[data-theme='dark'] .text-gray-900 { + color: var(--color-dark-text); +} + +[data-theme='dark'] .text-gray-700, +[data-theme='dark'] .text-gray-600 { + color: var(--color-dark-text-secondary); +} + +/* 兼容性覆盖:琥珀色/棕色 */ +[data-theme='light'] .text-amber-700, +[data-theme='light'] .text-amber-800, +[data-theme='light'] .text-brown-700 { + color: var(--color-primary-700); +} + +[data-theme='dark'] .text-amber-300, +[data-theme='dark'] .text-amber-400, +[data-theme='dark'] .text-brown-300 { + color: var(--color-dark-primary-600); +} + +[data-theme='light'] .bg-amber-50, +[data-theme='light'] .bg-amber-100 { + background-color: var(--color-primary-50); +} + +[data-theme='dark'] .bg-amber-900, +[data-theme='dark'] .bg-slate-900 { + background-color: var(--color-dark-bg); +} + +/* 表单元素在黑暗模式下的适配 */ +[data-theme='dark'] input, +[data-theme='dark'] select, +[data-theme='dark'] textarea { + background-color: var(--color-dark-card); + border-color: var(--color-dark-border); + color: var(--color-dark-text); +} + +/* 文本和链接颜色在黑暗模式下的调整 */ +[data-theme='dark'] a:not([class]) { + color: var(--theme-primary); +} + +[data-theme='dark'] a:not([class]):hover { + color: var(--theme-primary-light); +} + +/* 黑暗模式下的阴影调整 */ +[data-theme='dark'] .shadow-md, +[data-theme='dark'] .shadow-lg { + --tw-shadow-color: rgba(0, 0, 0, 0.4); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +/* 黑暗模式下的渐变背景调整 */ +[data-theme='dark'] .bg-gradient-to-r { + background-image: linear-gradient(to right, var(--color-dark-primary-200), var(--color-dark-primary-300)); +} \ No newline at end of file diff --git a/web/graduation/tsconfig.json b/web/graduation/tsconfig.json new file mode 100644 index 0000000..8bf91d3 --- /dev/null +++ b/web/graduation/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +}