diff --git a/GALLERY-README.md b/GALLERY-README.md index a0fdf99..822507c 100644 --- a/GALLERY-README.md +++ b/GALLERY-README.md @@ -102,6 +102,26 @@ hero: { } ``` +#### 示例 4:添加风景照片展示(新功能!) + +```javascript +{ + type: 'scenery', + enabled: true, // 开关控制 + title: '风景搅影', + description: '记录自然之美', + categories: ['nature', 'urban', 'plants', 'animals'], // 要展示的类别 + groupCount: 2, // 展示几个照片组 + random: true // 是否随机选择 +} +``` + +**风景类别说明:** +- `nature` - 纯自然风景 (5_x_x.JPG) +- `urban` - 非自然风景/城市 (6_x_x.JPG) +- `plants` - 植物 (7_x_x.JPG) +- `animals` - 动物 (8_x_x.JPG) + ## 📦 支持的布局类型 - `hero` - 首屏大图 @@ -109,6 +129,7 @@ hero: { - `quad-grid` - 四宫格(2x2) - `dual` - 双栏布局 - `grid` - 灵活网格(支持 large 和 normal 尺寸) +- `scenery` - 风景照片展示(新增!支持随机选择和分类过滤) ## 🔄 部署到 VPS @@ -129,8 +150,9 @@ git push - **添加图片**:只需把新图片放到 `public/media/` 然后在配置文件中引用即可 - **调整顺序**:在 `sections` 数组中调整顺序即可改变页面布局顺序 -## 🎯 图片命名规则(你的照片) +## 🎯 图片命名规则 +### 人物照片 - `0_X.JPG` - 0号人物的照片 - `1_X.JPG` - 1号人物的照片 - `2_X.JPG` - 2号人物的照片 @@ -138,4 +160,23 @@ git push - `0123_X.JPG` - 四人合照 - `23_X.JPG` - 2号和3号的合照 +### 风景照片(新增) +- `5_Y_Z.JPG` - 纯自然风景 +- `6_Y_Z.JPG` - 非自然风景(建筑、城市等) +- `7_Y_Z.JPG` - 植物 +- `8_Y_Z.JPG` - 动物 + +**编号说明:** +- 第一位:类型(5/6/7/8) +- 第二位(Y):关联组号,相同数字代表有关联的照片 +- 第三位(Z):顺序编号 + +**示例:** +``` +5_1_1.JPG ← 纯自然风景,第1组,第1张 +5_1_2.JPG ← 纯自然风景,第1组,第2张(与上一张相关) +5_2_1.JPG ← 纯自然风景,第2组,第1张(独立场景) +6_1_1.JPG ← 非自然风景,第1组,第1张 +``` + 你可以根据这些照片自由组织任何布局! diff --git a/SCENERY-GUIDE.md b/SCENERY-GUIDE.md new file mode 100644 index 0000000..7f67043 --- /dev/null +++ b/SCENERY-GUIDE.md @@ -0,0 +1,81 @@ +# 🌄 风景照片功能使用指南 + +## 快速开始 + +### 1. 添加风景照片 + +将风景照片放到 `public/media/` 目录,按照命名规则命名: +```bash +public/media/ +├── 5_1_1.JPG # 纯自然风景,第1组,第1张 +├── 5_1_2.JPG # 纯自然风景,第1组,第2张 +├── 6_1_1.jpeg # 非自然风景,第1组,第1张 +└── 7_1_1.jpeg # 植物,第1组,第1张 +``` + +### 2. 更新照片列表 +```bash +npm run scan-photos +``` + +### 3. 在配置中启用 + +编辑 `src/data/gallery-config.js`: +```javascript +{ + type: 'scenery', + enabled: true, + title: '风景掠影', + description: '记录自然之美,定格时光瞬间', + categories: ['nature', 'urban', 'plants', 'animals'], + groupCount: 2, + random: true +} +``` + +## 命名规则 + +### 格式 +``` +[类型]_[组号]_[序号].[扩展名] +``` + +- **类型**:5=自然风景, 6=城市风景, 7=植物, 8=动物 +- **组号**:相同数字代表关联照片 +- **序号**:组内排序 + +### 示例 +``` +5_1_1.JPG ← 第1组自然风景第1张 +5_1_2.JPG ← 第1组自然风景第2张(与上一张相关) +5_2_1.JPG ← 第2组自然风景第1张(独立场景) +``` + +## 配置选项 +```javascript +{ + enabled: true, // 开关控制 + categories: ['nature'], // 类别过滤 + groupCount: 2, // 显示几组 + random: true // 是否随机 +} +``` + +## 布局效果 + +- 1张:居中大图 +- 2张:左右平铺 +- 3张:一大两小 +- 4张:田字格 +- 5+张:瀑布流 + +## 常见问题 + +**Q: 如何添加新照片?** +A: 放到 `public/media/`,运行 `npm run scan-photos` + +**Q: 可以临时关闭吗?** +A: 设置 `enabled: false` + +**Q: 支持哪些格式?** +A: JPG, JPEG, PNG, WebP diff --git a/SCENERY-IMPLEMENTATION.md b/SCENERY-IMPLEMENTATION.md new file mode 100644 index 0000000..846763b --- /dev/null +++ b/SCENERY-IMPLEMENTATION.md @@ -0,0 +1,52 @@ +# 🎨 风景照片功能 - 实现总结 + +## ✅ 完成的功能 + +### 核心组件 +- ScenerySection.astro - 风景展示组件 +- 自动识别照片类型(5/6/7/8) +- 智能分组和布局 +- 响应式设计 + +### 配置系统 +- scenery-photos.js - 照片配置 +- gallery-config.js - 主配置 +- enabled 开关控制 + +### 自动化工具 +- scan-scenery-photos.js - 扫描脚本 +- npm scripts 集成 + +## 文件结构 +``` +gallery/ +├── src/ +│ ├── components/sections/ +│ │ └── ScenerySection.astro +│ └── data/ +│ ├── scenery-photos.js +│ └── gallery-config.js +├── scripts/ +│ └── scan-scenery-photos.js +└── SCENERY-GUIDE.md +``` + +## 使用方法 +```bash +# 1. 扫描照片 +npm run scan-photos + +# 2. 启动开发 +npm run dev + +# 3. 访问 +http://localhost:4321 +``` + +## 核心特性 + +- ✅ 智能分组 +- ✅ 随机展示 +- ✅ 类别过滤 +- ✅ 响应式布局 +- ✅ 优雅动画 diff --git a/astro.config.mjs b/astro.config.mjs index 77d4156..607e5f3 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,7 +1,7 @@ import { defineConfig } from 'astro/config'; export default defineConfig({ - site: 'https://gallery.yourdomain.com', + site: 'https://leexxx.com', // 或者如果是主域名:'https://yourdomain.com' // 图片优化 diff --git a/package.json b/package.json index 75fe997..eb2b2b6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "dev": "astro dev", "build": "astro build", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "scan-photos": "node scripts/scan-scenery-photos.js" }, "dependencies": { "@astrojs/mdx": "^4.3.12", diff --git a/public/media/0123_3.jpeg b/public/media/0123_3.jpeg new file mode 100644 index 0000000..072f72a Binary files /dev/null and b/public/media/0123_3.jpeg differ diff --git a/public/media/5_1_1.JPG b/public/media/5_1_1.JPG new file mode 100644 index 0000000..4d05817 Binary files /dev/null and b/public/media/5_1_1.JPG differ diff --git a/public/media/5_1_2.JPG b/public/media/5_1_2.JPG new file mode 100644 index 0000000..995d127 Binary files /dev/null and b/public/media/5_1_2.JPG differ diff --git a/public/media/5_1_3.JPG b/public/media/5_1_3.JPG new file mode 100644 index 0000000..40a0dfc Binary files /dev/null and b/public/media/5_1_3.JPG differ diff --git a/public/media/5_1_4.JPG b/public/media/5_1_4.JPG new file mode 100644 index 0000000..48b0dfb Binary files /dev/null and b/public/media/5_1_4.JPG differ diff --git a/public/media/5_1_5.jpeg b/public/media/5_1_5.jpeg new file mode 100644 index 0000000..acd0739 Binary files /dev/null and b/public/media/5_1_5.jpeg differ diff --git a/public/media/5_2_1.JPG b/public/media/5_2_1.JPG new file mode 100644 index 0000000..c87fbf7 Binary files /dev/null and b/public/media/5_2_1.JPG differ diff --git a/public/media/6_1_1.jpeg b/public/media/6_1_1.jpeg new file mode 100644 index 0000000..87c3107 Binary files /dev/null and b/public/media/6_1_1.jpeg differ diff --git a/public/media/6_2_1.jpeg b/public/media/6_2_1.jpeg new file mode 100644 index 0000000..b6b525b Binary files /dev/null and b/public/media/6_2_1.jpeg differ diff --git a/public/media/7_1_1.jpeg b/public/media/7_1_1.jpeg new file mode 100644 index 0000000..7abfa8b Binary files /dev/null and b/public/media/7_1_1.jpeg differ diff --git a/scripts/scan-scenery-photos.js b/scripts/scan-scenery-photos.js new file mode 100755 index 0000000..2686a7a --- /dev/null +++ b/scripts/scan-scenery-photos.js @@ -0,0 +1,114 @@ +#!/usr/bin/env node + +/** + * 自动扫描风景照片脚本 + */ + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const MEDIA_DIR = path.join(__dirname, '../public/media'); +const OUTPUT_FILE = path.join(__dirname, '../src/data/scenery-photos.js'); + +// 风景照片的命名规则:5_、6_、7_、8_ 开头 +const SCENERY_PATTERN = /^[5-8]_\d+_\d+\.(jpg|jpeg|png|webp)$/i; + +// 分类映射 +const CATEGORIES = { + '5': { name: '纯自然风景', files: [] }, + '6': { name: '非自然风景', files: [] }, + '7': { name: '植物', files: [] }, + '8': { name: '动物', files: [] } +}; + +function scanPhotos() { + console.log('🔍 正在扫描风景照片...'); + + const files = fs.readdirSync(MEDIA_DIR); + const sceneryFiles = files.filter(file => SCENERY_PATTERN.test(file)); + + console.log(`✅ 找到 ${sceneryFiles.length} 张风景照片`); + + sceneryFiles.forEach(file => { + const category = file[0]; + if (CATEGORIES[category]) { + CATEGORIES[category].files.push(file); + } + }); + + console.log('\n📊 照片分类统计:'); + Object.entries(CATEGORIES).forEach(([key, { name, files }]) => { + console.log(` ${key}_ ${name}: ${files.length} 张`); + }); + + return sceneryFiles.sort(); +} + +function generateConfigFile() { + const timestamp = new Date().toISOString(); + + const lines = []; + Object.entries(CATEGORIES).forEach(([key, { name, files }]) => { + if (files.length > 0) { + lines.push(` // ${name} (${key}_)`); + files.forEach(file => lines.push(` '${file}',`)); + lines.push(''); + } + }); + + if (lines.length > 0 && lines[lines.length - 1] === '') { + lines.pop(); + lines[lines.length - 1] = lines[lines.length - 1].replace(/,$/, ''); + } + + return `/** + * 风景照片配置文件 + * + * ⚠️ 此文件由脚本自动生成,请勿手动编辑 + * 生成时间: ${timestamp} + * + * 命名规则: + * - 5_Y_Z.JPG - 纯自然风景 + * - 6_Y_Z.JPG - 非自然风景(建筑、城市等) + * - 7_Y_Z.JPG - 植物 + * - 8_Y_Z.JPG - 动物 + */ + +export const sceneryPhotos = [ +${lines.join('\n')} +]; +`; +} + +function main() { + console.log('🎨 Gallery 风景照片扫描工具\n'); + + if (!fs.existsSync(MEDIA_DIR)) { + console.error(`❌ 错误: 找不到 media 目录: ${MEDIA_DIR}`); + process.exit(1); + } + + const photos = scanPhotos(); + + if (photos.length === 0) { + console.log('\n⚠️ 警告: 未找到符合命名规则的风景照片'); + return; + } + + const content = generateConfigFile(); + + const dir = path.dirname(OUTPUT_FILE); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + + fs.writeFileSync(OUTPUT_FILE, content, 'utf8'); + console.log(`\n✅ 配置文件已更新: ${path.relative(process.cwd(), OUTPUT_FILE)}`); + console.log('\n🎉 完成!\n'); +} + +main(); diff --git a/src/components/sections/ScenerySection.astro b/src/components/sections/ScenerySection.astro new file mode 100644 index 0000000..cc96f25 --- /dev/null +++ b/src/components/sections/ScenerySection.astro @@ -0,0 +1,349 @@ +--- +/** + * 风景照片展示组件 + * 支持随机选择风景照片组进行展示 + */ + +interface Props { + title?: string; + description?: string; + categories?: ('nature' | 'urban' | 'plants' | 'animals')[]; + groupCount?: number; + random?: boolean; +} + +const { + title = '风景掠影', + description = '记录自然之美,定格时光瞬间', + categories = ['nature', 'urban', 'plants', 'animals'], + groupCount = 2, + random = true +} = Astro.props; + +// 类别映射 +const categoryPrefixes: Record = { + 'nature': '5', + 'urban': '6', + 'plants': '7', + 'animals': '8' +}; + +const categoryNames: Record = { + 'nature': '自然风光', + 'urban': '城市印象', + 'plants': '花草世界', + 'animals': '生灵物语' +}; + +// 导入风景照片配置 +import { sceneryPhotos } from '../../data/scenery-photos.js'; + +const mediaPath = '/media/'; + +// 解析照片信息 +interface PhotoInfo { + filename: string; + path: string; + category: string; + categoryName: string; + groupId: string; + sequence: string; +} + +function parsePhoto(filename: string): PhotoInfo | null { + const match = filename.match(/^([5-8])_(\d+)_(\d+)\./); + if (!match) return null; + + const [, typeId, groupId, sequence] = match; + const categoryKey = Object.keys(categoryPrefixes).find( + key => categoryPrefixes[key] === typeId + ); + + if (!categoryKey) return null; + + return { + filename, + path: mediaPath + filename, + category: categoryKey, + categoryName: categoryNames[categoryKey], + groupId, + sequence + }; +} + +// 按类别和组号分组照片 +interface PhotoGroup { + category: string; + categoryName: string; + groupId: string; + photos: PhotoInfo[]; +} + +function groupPhotos(photos: PhotoInfo[]): PhotoGroup[] { + const groups = new Map(); + + photos.forEach(photo => { + const key = `${photo.category}_${photo.groupId}`; + if (!groups.has(key)) { + groups.set(key, { + category: photo.category, + categoryName: photo.categoryName, + groupId: photo.groupId, + photos: [] + }); + } + groups.get(key)!.photos.push(photo); + }); + + groups.forEach(group => { + group.photos.sort((a, b) => parseInt(a.sequence) - parseInt(b.sequence)); + }); + + return Array.from(groups.values()); +} + +// 处理照片 +const parsedPhotos = sceneryPhotos + .map(parsePhoto) + .filter((p): p is PhotoInfo => p !== null) + .filter(p => categories.includes(p.category as any)); + +const photoGroups = groupPhotos(parsedPhotos); + +// 随机或按顺序选择照片组 +let selectedGroups = photoGroups; +if (random && photoGroups.length > groupCount) { + selectedGroups = [...photoGroups] + .sort(() => Math.random() - 0.5) + .slice(0, groupCount); +} else { + selectedGroups = photoGroups.slice(0, groupCount); +} +--- + +{selectedGroups.length > 0 && ( +
+
+

{title}

+ {description &&

{description}

} +
+ +
+ {selectedGroups.map((group, groupIndex) => ( +
+
+ {group.categoryName} +
+ +
+ {group.photos.map((photo, photoIndex) => ( +
+ {`${photo.categoryName} +
+ ))} +
+
+ ))} +
+
+)} + + diff --git a/src/data/gallery-config.js b/src/data/gallery-config.js index 1b1a03f..5ae7dbb 100644 --- a/src/data/gallery-config.js +++ b/src/data/gallery-config.js @@ -1,4 +1,5 @@ // 画廊配置 - 所有布局和资源的单一数据源 +// 支持的布局类型: hero, full-bleed, quad-grid, dual, grid, scenery export const galleryConfig = { // 首屏大图 - 使用四人合照 hero: { @@ -74,6 +75,17 @@ export const galleryConfig = { image: '/media/0_1.JPG', title: '青春不散场', description: '愿时光不老,我们不散' + }, + + // 6. 风景照片展示(新增)- 可通过 enabled 开关控制 + { + type: 'scenery', + enabled: true, // 开关控制,设为 false 可关闭 + title: '风景掠影', + description: '记录自然之美,定格时光瞒间', + categories: ['nature', 'urban', 'plants', 'animals'], // 要展示的类别 + groupCount: 2, // 展示几个照片组 + random: true // 是否随机选择 } ] }; diff --git a/src/data/scenery-photos.js b/src/data/scenery-photos.js new file mode 100644 index 0000000..4dd61f1 --- /dev/null +++ b/src/data/scenery-photos.js @@ -0,0 +1,29 @@ +/** + * 风景照片配置文件 + * + * ⚠️ 此文件由脚本自动生成,请勿手动编辑 + * 生成时间: 2025-12-06T10:14:14.920Z + * + * 命名规则: + * - 5_Y_Z.JPG - 纯自然风景 + * - 6_Y_Z.JPG - 非自然风景(建筑、城市等) + * - 7_Y_Z.JPG - 植物 + * - 8_Y_Z.JPG - 动物 + */ + +export const sceneryPhotos = [ + // 纯自然风景 (5_) + '5_1_1.JPG', + '5_1_2.JPG', + '5_1_3.JPG', + '5_1_4.JPG', + '5_1_5.jpeg', + '5_2_1.JPG', + + // 非自然风景 (6_) + '6_1_1.jpeg', + '6_2_1.jpeg', + + // 植物 (7_) + '7_1_1.jpeg' +]; diff --git a/src/pages/index.astro b/src/pages/index.astro index 494b548..138c5f2 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -7,6 +7,7 @@ import FullBleedSection from '../components/sections/FullBleedSection.astro'; import DualSection from '../components/sections/DualSection.astro'; import GridSection from '../components/sections/GridSection.astro'; import QuadGridSection from '../components/sections/QuadGridSection.astro'; +import ScenerySection from '../components/sections/ScenerySection.astro'; // 组件映射表 const componentMap = { @@ -14,7 +15,8 @@ const componentMap = { 'full-bleed': FullBleedSection, 'dual': DualSection, 'grid': GridSection, - 'quad-grid': QuadGridSection + 'quad-grid': QuadGridSection, + 'scenery': ScenerySection }; --- @@ -34,6 +36,9 @@ const componentMap = { {galleryConfig.sections.map((section, index) => { + // 如果 section 有 enabled 属性且为 false,则跳过 + if (section.enabled === false) return null; + const Component = componentMap[section.type]; return Component ? (