完成所有页面,除了文化筛选有问题
This commit is contained in:
parent
3a93ac41b7
commit
9134f0b67a
@ -2,18 +2,20 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
import react from '@astrojs/react';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
plugins: [tailwindcss()]
|
||||
},
|
||||
|
||||
|
||||
// 服务器配置
|
||||
server: {
|
||||
port: 3000,
|
||||
host: true
|
||||
},
|
||||
|
||||
|
||||
// Markdown 配置
|
||||
markdown: {
|
||||
syntaxHighlight: 'prism',
|
||||
@ -23,5 +25,7 @@ export default defineConfig({
|
||||
langs: [],
|
||||
wrap: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
integrations: [react()]
|
||||
});
|
@ -9,15 +9,17 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/react": "^4.2.3",
|
||||
"@studio-freight/lenis": "^1.0.42",
|
||||
"@tailwindcss/vite": "^4.0.15",
|
||||
"@types/react": "^19.0.12",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"aceternity-ui": "^0.2.2",
|
||||
"astro": "^5.5.3",
|
||||
"echarts": "^5.6.0",
|
||||
"framer-motion": "^12.5.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"tailwindcss": "^4.0.15"
|
||||
}
|
||||
}
|
||||
|
1
web/graduation/src/assets/hebei.json
Normal file
1
web/graduation/src/assets/hebei.json
Normal file
File diff suppressed because one or more lines are too long
257
web/graduation/src/components/HebeiMap.tsx
Normal file
257
web/graduation/src/components/HebeiMap.tsx
Normal file
@ -0,0 +1,257 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import * as echarts from 'echarts';
|
||||
import hebeiJson from '../assets/hebei.json';
|
||||
|
||||
// 定义正确的GeoJSON类型
|
||||
interface GeoJSONFeature {
|
||||
type: 'Feature';
|
||||
properties: {
|
||||
name: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
geometry: any;
|
||||
}
|
||||
|
||||
interface GeoJSONFeatureCollection {
|
||||
type: 'FeatureCollection';
|
||||
features: GeoJSONFeature[];
|
||||
}
|
||||
|
||||
interface Article {
|
||||
title: string;
|
||||
description: string;
|
||||
city: string[];
|
||||
slug: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface HebeiMapProps {
|
||||
articles: Article[];
|
||||
}
|
||||
|
||||
// ECharts事件参数类型
|
||||
interface EChartsClickParams {
|
||||
name: string;
|
||||
value?: any;
|
||||
dataIndex?: number;
|
||||
seriesIndex?: number;
|
||||
data?: any;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const HebeiMap = ({ articles }: HebeiMapProps) => {
|
||||
const mapRef = useRef<HTMLDivElement>(null);
|
||||
const [selectedCity, setSelectedCity] = useState<string>('');
|
||||
const [filteredArticles, setFilteredArticles] = useState<Article[]>([]);
|
||||
const [isMobile, setIsMobile] = useState<boolean>(false);
|
||||
const [chart, setChart] = useState<echarts.ECharts | null>(null);
|
||||
|
||||
// 记录上次点击的城市,用于比较
|
||||
const lastClickedCity = useRef<string>('');
|
||||
|
||||
// 检测是否为移动设备
|
||||
useEffect(() => {
|
||||
const checkMobile = () => {
|
||||
const mobile = window.innerWidth < 768;
|
||||
setIsMobile(mobile);
|
||||
};
|
||||
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', checkMobile);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 地图初始化
|
||||
useEffect(() => {
|
||||
if (!mapRef.current) return;
|
||||
|
||||
// 确保GeoJSON格式正确
|
||||
const geoJson: GeoJSONFeatureCollection = {
|
||||
type: 'FeatureCollection',
|
||||
features: hebeiJson.features.map(feature => ({
|
||||
type: 'Feature',
|
||||
properties: feature.properties,
|
||||
geometry: feature.geometry
|
||||
}))
|
||||
};
|
||||
|
||||
// 注册地图数据
|
||||
echarts.registerMap('hebei', geoJson);
|
||||
|
||||
// 解决移动端多次初始化问题
|
||||
if (chart) {
|
||||
chart.dispose();
|
||||
}
|
||||
|
||||
const newChart = echarts.init(mapRef.current);
|
||||
setChart(newChart);
|
||||
|
||||
const option = {
|
||||
title: {
|
||||
text: '河北省地图',
|
||||
left: 'center',
|
||||
textStyle: {
|
||||
fontSize: isMobile ? 14 : 16
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
show: false
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '河北省',
|
||||
type: 'map',
|
||||
map: 'hebei',
|
||||
roam: isMobile, // 移动端允许缩放和平移
|
||||
zoom: isMobile ? 1.2 : 1, // 移动端稍微放大一点
|
||||
itemStyle: {
|
||||
normal: {
|
||||
areaColor: '#e0f3f8',
|
||||
borderColor: '#fff'
|
||||
},
|
||||
emphasis: {
|
||||
areaColor: '#74add1'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: isMobile ? 10 : 12
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
data: hebeiJson.features.map(feature => ({
|
||||
name: feature.properties.name,
|
||||
value: 0
|
||||
}))
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
newChart.setOption(option);
|
||||
|
||||
// 点击事件处理 - 修改为点击同一城市时取消选择
|
||||
const clickHandler = (params: EChartsClickParams) => {
|
||||
const cityName = params.name.replace('市', '');
|
||||
|
||||
// 如果点击的是已选中的城市,则取消选择
|
||||
if (cityName === lastClickedCity.current) {
|
||||
lastClickedCity.current = '';
|
||||
setSelectedCity('');
|
||||
setFilteredArticles([]);
|
||||
} else {
|
||||
// 否则选中新城市并筛选文章
|
||||
lastClickedCity.current = cityName;
|
||||
setSelectedCity(cityName);
|
||||
const cityArticles = articles.filter(article =>
|
||||
article.city.some(city => city.includes(cityName))
|
||||
);
|
||||
setFilteredArticles(cityArticles);
|
||||
}
|
||||
};
|
||||
|
||||
// 移除之前的点击事件(防止重复绑定)
|
||||
newChart.off('click');
|
||||
newChart.on('click', clickHandler);
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
newChart.off('click');
|
||||
newChart.dispose();
|
||||
};
|
||||
}, [articles, isMobile]); // 从依赖项中移除selectedCity
|
||||
|
||||
// 窗口大小变化时调整地图大小
|
||||
useEffect(() => {
|
||||
if (chart) {
|
||||
chart.resize();
|
||||
}
|
||||
}, [isMobile, chart]);
|
||||
|
||||
// 获取文章类型对应的图标
|
||||
const getTypeIcon = (type: string) => {
|
||||
switch (type) {
|
||||
case 'attractions':
|
||||
return '🏛️';
|
||||
case 'culture':
|
||||
return '🎭';
|
||||
case 'cuisine':
|
||||
return '🍜';
|
||||
case 'travel':
|
||||
return '🏞️';
|
||||
default:
|
||||
return '📄';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto px-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="md:col-span-2">
|
||||
<div
|
||||
ref={mapRef}
|
||||
style={{
|
||||
height: isMobile ? '400px' : '600px',
|
||||
width: '100%'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={`md:col-span-1 ${isMobile && selectedCity ? 'mt-4' : ''}`}>
|
||||
{selectedCity ? (
|
||||
<div className="bg-white p-4 rounded-lg shadow">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-bold">{selectedCity}相关文章</h2>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // 阻止事件冒泡
|
||||
lastClickedCity.current = ''; // 重置上次点击的城市
|
||||
setSelectedCity('');
|
||||
setFilteredArticles([]);
|
||||
}}
|
||||
className="text-gray-500 hover:text-gray-700 p-2"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
{filteredArticles.length > 0 ? (
|
||||
<ul className="space-y-4 max-h-[500px] overflow-y-auto">
|
||||
{filteredArticles.map((article, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="border-b pb-2 hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // 阻止事件冒泡
|
||||
window.location.href = article.slug;
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="text-xl">{getTypeIcon(article.type)}</span>
|
||||
<div>
|
||||
<h3 className="font-semibold hover:text-blue-600">{article.title}</h3>
|
||||
<p className="text-gray-600 text-sm">{article.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-gray-500">暂无相关文章</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white p-4 rounded-lg shadow">
|
||||
<p className="text-gray-500 text-center">点击地图选择城市查看相关文章</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HebeiMap;
|
@ -2,7 +2,7 @@
|
||||
import "../styles/global.css";
|
||||
import ThemeToggle from "../components/ThemeToggle.astro";
|
||||
import DarkModeTransition from "../components/DarkModeTransition.astro";
|
||||
import SmoothScroll from "../components/aceternity/SmoothScroll.astro";
|
||||
import SmoothScroll from "../components/SmoothScroll.astro";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
@ -49,6 +49,7 @@ const { title, description = "河北游礼宣传网站 - 探索河北的文化
|
||||
<a href="/culture" class="text-gray-800 hover:text-color-primary-600 dark:text-gray-200 dark:hover:text-color-primary-400 text-shadow-sm">文化</a>
|
||||
<a href="/cuisine" class="text-gray-800 hover:text-color-primary-600 dark:text-gray-200 dark:hover:text-color-primary-400 text-shadow-sm">美食</a>
|
||||
<a href="/travel" class="text-gray-800 hover:text-color-primary-600 dark:text-gray-200 dark:hover:text-color-primary-400 text-shadow-sm">旅游攻略</a>
|
||||
<a href="/map" class="text-gray-800 hover:text-color-primary-600 dark:text-gray-200 dark:hover:text-color-primary-400 text-shadow-sm">地图导航</a>
|
||||
</nav>
|
||||
|
||||
<!-- 主题切换按钮和移动端菜单 -->
|
||||
@ -72,6 +73,7 @@ const { title, description = "河北游礼宣传网站 - 探索河北的文化
|
||||
<a href="/culture" class="px-3 py-2 rounded-md text-gray-700 hover:bg-white/20 dark:text-gray-300 dark:hover:bg-white/10">文化</a>
|
||||
<a href="/cuisine" class="px-3 py-2 rounded-md text-gray-700 hover:bg-white/20 dark:text-gray-300 dark:hover:bg-white/10">美食</a>
|
||||
<a href="/travel" class="px-3 py-2 rounded-md text-gray-700 hover:bg-white/20 dark:text-gray-300 dark:hover:bg-white/10">旅游攻略</a>
|
||||
<a href="/map" class="px-3 py-2 rounded-md text-gray-700 hover:bg-white/20 dark:text-gray-300 dark:hover:bg-white/10">地图导航</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,30 +0,0 @@
|
||||
---
|
||||
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();
|
||||
---
|
||||
|
||||
<div class={`grid ${gridClass} ${gap} ${className}`}>
|
||||
<slot />
|
||||
</div>
|
@ -1,40 +0,0 @@
|
||||
---
|
||||
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;
|
||||
---
|
||||
|
||||
<section class="py-8 bg-white dark:bg-color-dark-surface border-b border-gray-200 dark:border-color-dark-border">
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<button class={`px-4 py-2 rounded-full font-medium transition-colors ${allActive ? 'bg-color-primary-500 text-white dark:bg-color-dark-primary-600 dark:text-white dark:hover:bg-color-dark-primary-500' : 'bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-color-dark-card dark:text-gray-300 dark:hover:bg-color-dark-border'}`}>
|
||||
{showAllLabel}
|
||||
</button>
|
||||
|
||||
{categories.map(category => (
|
||||
<button class={`px-4 py-2 rounded-full font-medium transition-colors ${category.isActive ? 'bg-color-primary-500 text-white dark:bg-color-dark-primary-600 dark:text-white dark:hover:bg-color-dark-primary-500' : 'bg-gray-100 text-gray-700 hover:bg-gray-200 dark:bg-color-dark-card dark:text-gray-300 dark:hover:bg-color-dark-border'}`}>
|
||||
{category.icon && <span class="mr-1">{category.icon}</span>}
|
||||
{category.name}
|
||||
{category.count !== undefined && (
|
||||
<span class="ml-1 text-xs opacity-70">{`(${category.count})`}</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
@ -1,18 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
const { title, subtitle } = Astro.props;
|
||||
---
|
||||
|
||||
<section class="py-12 bg-gradient-to-r from-amber-500 to-amber-700 text-white dark:from-amber-800/70 dark:to-amber-950/80">
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<h1 class="text-4xl font-bold mb-4">{title}</h1>
|
||||
{subtitle && <p class="text-xl opacity-90">{subtitle}</p>}
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
@ -1,33 +0,0 @@
|
||||
---
|
||||
interface Props {
|
||||
placeholder?: string;
|
||||
buttonText?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
placeholder = "搜索...",
|
||||
buttonText = "搜索",
|
||||
className = ""
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<section class={`py-8 bg-white dark:bg-color-dark-bg ${className}`}>
|
||||
<div class="container mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
class="w-full px-4 py-3 pl-12 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-color-primary-500 focus:border-color-primary-500 shadow-sm dark:bg-color-dark-card dark:border-color-dark-border dark:text-white dark:placeholder-gray-400"
|
||||
/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 absolute left-3 top-3.5 text-gray-400 dark:text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
<button class="absolute right-3 top-3 px-3 py-1 bg-color-primary-500 text-white rounded-md hover:bg-color-primary-600 transition-colors dark:bg-color-dark-primary-600 dark:text-white dark:hover:bg-color-dark-primary-500">
|
||||
{buttonText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
@ -0,0 +1,38 @@
|
||||
---
|
||||
title: "衡水老白干火锅 - 酒香四溢的本地特色美食"
|
||||
description: "衡水老白干火锅是一种独具地方特色的饮食方式,以衡水老白干白酒为汤底,配以牛羊杂、豆制品等,酒香浓郁,辣中带麻,是冬日里驱寒暖胃的绝佳选择。"
|
||||
category: "地方火锅"
|
||||
featured: true
|
||||
image: "/images/cuisine/hengshui-baigan-hotpot.jpg"
|
||||
city: ["衡水"]
|
||||
ingredients: ["衡水老白干", "牛杂", "羊杂", "豆腐皮", "白菜", "粉条", "辣椒", "花椒", "葱", "姜", "蒜"]
|
||||
taste: "香辣浓烈"
|
||||
cookTime: "1.5小时"
|
||||
difficulty: "中等"
|
||||
tags: ["火锅", "衡水美食", "白酒火锅", "地方特色", "冬季美食"]
|
||||
pubDate: 2025-04-10
|
||||
---
|
||||
|
||||
衡水老白干火锅,是衡水地区冬天非常流行的一道地方美食。不同于普通火锅以清汤或红油为底,这种火锅独具一格地用衡水特产白酒 —— 老白干为汤底,加热时酒香四溢,令人食欲大开。
|
||||
|
||||
## 地道食材
|
||||
|
||||
火锅的主料通常选用本地现宰的**牛羊杂碎**,经过反复洗净、焯水,再加以**葱姜蒜、花椒、辣椒**等辅料,焖煮入味。配菜方面,**豆腐皮、白菜、粉条**是不可或缺的经典搭配,既吸汤入味,又能平衡肉类的油腻。
|
||||
|
||||
## 烹饪方式
|
||||
|
||||
老白干火锅的灵魂在于“酒煮”。火锅上桌后,加入一整瓶衡水老白干,点火后酒精会快速挥发,只留下浓郁酒香,并把肉类与香料的味道彻底激发出来,形成一种极具辨识度的香辣口感。
|
||||
|
||||
## 口感特点
|
||||
|
||||
- **第一口**:酒香扑鼻,带着微醺的刺激感;
|
||||
- **第二口**:辣味与香味交织,肉质酥烂入味;
|
||||
- **第三口**:热气腾腾,身体从内而外都暖起来。
|
||||
|
||||
## 小贴士
|
||||
|
||||
- 初次尝试者可将酒量减少一半,适应其特殊香气;
|
||||
- 建议搭配**馒头、凉菜**中和辣度;
|
||||
- 餐后可饮**绿豆汤或酸奶**缓解燥热。
|
||||
|
||||
衡水老白干火锅不仅是一道美味,更是一种文化,它把地方酒文化与老百姓的餐桌生活完美融合,是冬天不可错过的一场味觉盛宴。
|
@ -0,0 +1,32 @@
|
||||
---
|
||||
title: "邢台邢窑白瓷 - 古老瓷器的匠心传承"
|
||||
description: "邢台邢窑白瓷是中国古代瓷器的代表之一,以其精致的制作工艺和洁白的釉色闻名。作为中国四大名窑之一,邢窑白瓷承载着悠久的历史与文化,至今仍影响着瓷器工艺的传承与发展。"
|
||||
category: "瓷器文化"
|
||||
featured: true
|
||||
image: "/images/culture/xingtai-xingyao-ceramics.jpg"
|
||||
city: ["邢台"]
|
||||
tags: ["非物质文化遗产", "瓷器", "传统文化", "中国工艺", "历史遗产"]
|
||||
pubDate: 2025-04-10
|
||||
---
|
||||
|
||||
邢台是邢窑白瓷的发源地,邢窑瓷器距今已有上千年历史。它是中国四大名窑之一,历史上以“白如玉、明如镜、薄如纸、声如磬”而闻名。邢窑的瓷器不仅在中国历史上占据重要地位,也是世界瓷器文化的瑰宝。
|
||||
|
||||
## 邢窑的历史与特点
|
||||
|
||||
邢窑起源于隋唐时期,盛于宋代,特别是在唐代的中期至宋代初期,邢窑白瓷达到了鼎盛时期。邢窑的白瓷以其洁白如玉的釉色、细腻的质地和精致的制作工艺著称。它的瓷器多为白瓷,釉面光洁透明,色泽洁白如玉,极具艺术价值。
|
||||
|
||||
## 邢窑瓷器的工艺
|
||||
|
||||
邢窑白瓷的制作工艺非常考究。首先选取上好的瓷土,经过严格的挑选和处理,确保瓷土的纯净。然后采用传统的手工制作工艺,经过高温烧制,使瓷器釉色光洁,质地坚硬。邢窑的瓷器多以碗、盘、瓶、壶等日常器物为主,但也不乏精美的艺术品和祭祀用品。
|
||||
|
||||
## 邢台邢窑遗址
|
||||
|
||||
今天,邢台的邢窑遗址已经成为国家重点文物保护单位,是研究古代瓷器文化的宝贵资料。游客可以参观邢窑遗址,欣赏到遗址出土的古瓷器以及现代工艺师制作的邢窑仿制品,感受这份千年瓷器文化的魅力。
|
||||
|
||||
## 如何体验邢台邢窑文化
|
||||
|
||||
- **参观邢窑遗址**:了解邢窑的历史背景和文化底蕴。
|
||||
- **瓷器制作体验**:在当地陶瓷厂参加瓷器制作体验,亲手制作一件瓷器。
|
||||
- **购买瓷器**:邢台当地有许多手工瓷器店,可以购买到精美的邢窑瓷器作为纪念。
|
||||
|
||||
邢台的邢窑白瓷是中华文化的重要组成部分,是中国瓷器文化的象征。如果你有兴趣了解中国传统瓷器的历史和工艺,不妨亲自前往邢台,体验这片瓷器的发源地,感受它的独特魅力。
|
@ -0,0 +1,44 @@
|
||||
---
|
||||
title: "张家口崇礼三日滑雪之旅 - 冰雪世界里的畅快驰骋"
|
||||
description: "一条适合冬季出行的滑雪旅游路线,深入张家口崇礼核心滑雪区,覆盖万龙、太舞、云顶三大雪场,体验冬奥同款雪道,享受雪域假期的极致快感。"
|
||||
season: "冬季"
|
||||
type: "滑雪游"
|
||||
featured: true
|
||||
image: "/images/travel/chongli-winter.jpg"
|
||||
city: ["张家口", "崇礼"]
|
||||
days: 3
|
||||
difficulty: "中等"
|
||||
tags: ["滑雪", "冬奥", "冰雪旅游", "自然风光", "户外运动"]
|
||||
pubDate: 2025-04-10
|
||||
---
|
||||
|
||||
张家口崇礼作为2022年冬奥会核心赛区,是国内首屈一指的滑雪胜地。这条三日滑雪之旅适合中等体力及以上的滑雪爱好者,覆盖最具代表性的三个雪场,同时兼顾美食与休闲放松,让你的冬日假期动静皆宜、精彩纷呈。
|
||||
|
||||
## 第一天:抵达崇礼 · 太舞滑雪度假区
|
||||
|
||||
- **上午**:从北京乘高铁前往太子城站,车程约50分钟,抵达后入住太舞滑雪小镇。
|
||||
- **中午**:在太舞镇内餐厅享用热腾腾的铁锅炖或牛肉汤锅。
|
||||
- **下午**:在太舞滑雪场体验雪道,初学者可报名滑雪教练课。
|
||||
- **晚上**:在雪场旁的酒吧或咖啡厅放松,感受冬夜雪镇的静谧。
|
||||
|
||||
## 第二天:万龙滑雪场挑战进阶雪道
|
||||
|
||||
- **上午**:早餐后乘坐接驳巴士前往万龙滑雪场。
|
||||
- **全天滑雪体验**:万龙以天然粉雪和长雪期著称,适合中高级玩家挑战不同等级雪道。
|
||||
- **午餐推荐**:滑雪场内小木屋风格的山顶餐厅,边吃边看雪景。
|
||||
- **晚上**:返回酒店,可选择泡温泉或在雪场小镇逛夜市。
|
||||
|
||||
## 第三天:云顶滑雪公园体验冬奥同款赛道
|
||||
|
||||
- **上午**:退房后前往云顶滑雪公园,这里是冬奥自由式滑雪与单板项目的举办地。
|
||||
- **体验推荐**:尝试U型槽、障碍技巧等特色道具雪道。
|
||||
- **中午**:在云顶小镇内享用快餐或热饮简餐。
|
||||
- **下午**:搭乘返程高铁返回北京,结束三日雪地之旅。
|
||||
|
||||
## 实用建议
|
||||
|
||||
- 滑雪衣裤、手套、头盔可现场租赁,建议自带雪镜和保暖内衣。
|
||||
- 旺季期间(元旦至春节)需提前两周以上预订酒店与雪票。
|
||||
- 防晒霜和唇膏必备,紫外线强且空气干燥。
|
||||
|
||||
崇礼不仅拥有世界级滑雪设施,更是一个可以全身心放松、沉浸在冰雪之中的地方。如果你向往冬天的速度与激情,不妨安排一次崇礼三日之行,为这个冬天留下一段纯白记忆。
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
import { getCollection, getEntry, type CollectionEntry } from "astro:content";
|
||||
import MainLayout from "../../layouts/MainLayout.astro";
|
||||
import { getCollection, type CollectionEntry } from "astro:content";
|
||||
import MainLayout from "../../components/MainLayout.astro";
|
||||
|
||||
// 定义Props类型
|
||||
export interface Props {
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
import MainLayout from "../../layouts/MainLayout.astro";
|
||||
import MainLayout from "../../components/MainLayout.astro";
|
||||
import { getCollection, type CollectionEntry } from "astro:content";
|
||||
|
||||
// 获取景点内容集合
|
||||
@ -75,11 +75,6 @@ let filteredAttractions = sortedAttractions;
|
||||
const totalPages = Math.ceil(filteredAttractions.length / itemsPerPage);
|
||||
const currentPageAttractions = filteredAttractions.slice((page - 1) * itemsPerPage, page * itemsPerPage);
|
||||
|
||||
// 辅助函数,用于获取景点的分类(从city提取或默认值)
|
||||
const getCategory = (attraction: CollectionEntry<"attractions">) => {
|
||||
return attraction.data.city && attraction.data.city.length > 0 ? attraction.data.city[0] : '其他景点';
|
||||
};
|
||||
|
||||
---
|
||||
|
||||
<MainLayout title="景点 - 河北游礼">
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
import { getCollection, getEntry, type CollectionEntry } from "astro:content";
|
||||
import MainLayout from "../../layouts/MainLayout.astro";
|
||||
import { getCollection, type CollectionEntry } from "astro:content";
|
||||
import MainLayout from "../../components/MainLayout.astro";
|
||||
|
||||
// 定义Props类型
|
||||
export interface Props {
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
import MainLayout from "../../layouts/MainLayout.astro";
|
||||
import MainLayout from "../../components/MainLayout.astro";
|
||||
import { getCollection, type CollectionEntry } from "astro:content";
|
||||
|
||||
// 获取美食内容集合
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
import { getCollection, getEntry, type CollectionEntry } from "astro:content";
|
||||
import MainLayout from "../../layouts/MainLayout.astro";
|
||||
import { getCollection, type CollectionEntry } from "astro:content";
|
||||
import MainLayout from "../../components/MainLayout.astro";
|
||||
|
||||
// 定义Props类型
|
||||
export interface Props {
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
import { getCollection, type CollectionEntry } from "astro:content";
|
||||
import MainLayout from "../../layouts/MainLayout.astro";
|
||||
import MainLayout from "../../components/MainLayout.astro";
|
||||
|
||||
// 获取URL参数
|
||||
const { searchParams } = Astro.url;
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
import MainLayout from "../layouts/MainLayout.astro";
|
||||
import MorphingText from "../components/aceternity/MorphingText.astro";
|
||||
import MainLayout from "../components/MainLayout.astro";
|
||||
import MorphingText from "../components/MorphingText.astro";
|
||||
|
||||
// 导航数据
|
||||
---
|
||||
|
52
web/graduation/src/pages/map.astro
Normal file
52
web/graduation/src/pages/map.astro
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
import MainLayout from "../components/MainLayout.astro";
|
||||
import HebeiMap from '../components/HebeiMap';
|
||||
import { getCollection } from 'astro:content';
|
||||
|
||||
// 获取所有集合的数据
|
||||
const attractions = await getCollection('attractions');
|
||||
const culture = await getCollection('culture');
|
||||
const cuisine = await getCollection('cuisine');
|
||||
const travel = await getCollection('travel');
|
||||
|
||||
// 合并所有文章并提取需要的字段
|
||||
const allArticles = [
|
||||
...attractions.map(entry => ({
|
||||
title: entry.data.title,
|
||||
description: entry.data.description,
|
||||
city: entry.data.city,
|
||||
slug: `/attractions/${entry.slug}`,
|
||||
type: 'attractions'
|
||||
})),
|
||||
...culture.map(entry => ({
|
||||
title: entry.data.title,
|
||||
description: entry.data.description,
|
||||
city: entry.data.city,
|
||||
slug: `/culture/${entry.slug}`,
|
||||
type: 'culture'
|
||||
})),
|
||||
...cuisine.map(entry => ({
|
||||
title: entry.data.title,
|
||||
description: entry.data.description,
|
||||
city: entry.data.city,
|
||||
slug: `/cuisine/${entry.slug}`,
|
||||
type: 'cuisine'
|
||||
})),
|
||||
...travel.map(entry => ({
|
||||
title: entry.data.title,
|
||||
description: entry.data.description,
|
||||
city: entry.data.city,
|
||||
slug: `/travel/${entry.slug}`,
|
||||
type: 'travel'
|
||||
}))
|
||||
];
|
||||
---
|
||||
|
||||
<MainLayout title="河北地图 - 文章导航">
|
||||
<main class="pt-16 md:pt-20 pb-4 md:pb-8">
|
||||
<div class="container mx-auto px-2 md:px-4">
|
||||
<p class="text-sm md:text-base text-gray-600 text-center mb-6">点击地图上的城市查看相关文章</p>
|
||||
<HebeiMap client:load articles={allArticles} />
|
||||
</div>
|
||||
</main>
|
||||
</MainLayout>
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
import { getCollection, getEntry, type CollectionEntry } from "astro:content";
|
||||
import MainLayout from "../../layouts/MainLayout.astro";
|
||||
import { getCollection, type CollectionEntry } from "astro:content";
|
||||
import MainLayout from "../../components/MainLayout.astro";
|
||||
|
||||
// 定义Props类型
|
||||
export interface Props {
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
import { getCollection, type CollectionEntry } from "astro:content";
|
||||
import MainLayout from "../../layouts/MainLayout.astro";
|
||||
import MainLayout from "../../components/MainLayout.astro";
|
||||
|
||||
// 获取所有旅行数据并按发布日期排序
|
||||
const allTravels = await getCollection("travel");
|
||||
|
@ -1,5 +1,19 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
"include": [
|
||||
".astro/types.d.ts",
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react"
|
||||
},
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"components/*": ["./src/components/*"]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user