增加风景显示
|
|
@ -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` - 首屏大图
|
- `hero` - 首屏大图
|
||||||
|
|
@ -109,6 +129,7 @@ hero: {
|
||||||
- `quad-grid` - 四宫格(2x2)
|
- `quad-grid` - 四宫格(2x2)
|
||||||
- `dual` - 双栏布局
|
- `dual` - 双栏布局
|
||||||
- `grid` - 灵活网格(支持 large 和 normal 尺寸)
|
- `grid` - 灵活网格(支持 large 和 normal 尺寸)
|
||||||
|
- `scenery` - 风景照片展示(新增!支持随机选择和分类过滤)
|
||||||
|
|
||||||
## 🔄 部署到 VPS
|
## 🔄 部署到 VPS
|
||||||
|
|
||||||
|
|
@ -129,8 +150,9 @@ git push
|
||||||
- **添加图片**:只需把新图片放到 `public/media/` 然后在配置文件中引用即可
|
- **添加图片**:只需把新图片放到 `public/media/` 然后在配置文件中引用即可
|
||||||
- **调整顺序**:在 `sections` 数组中调整顺序即可改变页面布局顺序
|
- **调整顺序**:在 `sections` 数组中调整顺序即可改变页面布局顺序
|
||||||
|
|
||||||
## 🎯 图片命名规则(你的照片)
|
## 🎯 图片命名规则
|
||||||
|
|
||||||
|
### 人物照片
|
||||||
- `0_X.JPG` - 0号人物的照片
|
- `0_X.JPG` - 0号人物的照片
|
||||||
- `1_X.JPG` - 1号人物的照片
|
- `1_X.JPG` - 1号人物的照片
|
||||||
- `2_X.JPG` - 2号人物的照片
|
- `2_X.JPG` - 2号人物的照片
|
||||||
|
|
@ -138,4 +160,23 @@ git push
|
||||||
- `0123_X.JPG` - 四人合照
|
- `0123_X.JPG` - 四人合照
|
||||||
- `23_X.JPG` - 2号和3号的合照
|
- `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张
|
||||||
|
```
|
||||||
|
|
||||||
你可以根据这些照片自由组织任何布局!
|
你可以根据这些照片自由组织任何布局!
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
## 核心特性
|
||||||
|
|
||||||
|
- ✅ 智能分组
|
||||||
|
- ✅ 随机展示
|
||||||
|
- ✅ 类别过滤
|
||||||
|
- ✅ 响应式布局
|
||||||
|
- ✅ 优雅动画
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
site: 'https://gallery.yourdomain.com',
|
site: 'https://leexxx.com',
|
||||||
// 或者如果是主域名:'https://yourdomain.com'
|
// 或者如果是主域名:'https://yourdomain.com'
|
||||||
|
|
||||||
// 图片优化
|
// 图片优化
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro"
|
"astro": "astro",
|
||||||
|
"scan-photos": "node scripts/scan-scenery-photos.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/mdx": "^4.3.12",
|
"@astrojs/mdx": "^4.3.12",
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 17 MiB |
|
After Width: | Height: | Size: 6.3 MiB |
|
After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 4.4 MiB |
|
After Width: | Height: | Size: 4.8 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
|
@ -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();
|
||||||
|
|
@ -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<string, string> = {
|
||||||
|
'nature': '5',
|
||||||
|
'urban': '6',
|
||||||
|
'plants': '7',
|
||||||
|
'animals': '8'
|
||||||
|
};
|
||||||
|
|
||||||
|
const categoryNames: Record<string, string> = {
|
||||||
|
'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<string, PhotoGroup>();
|
||||||
|
|
||||||
|
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 && (
|
||||||
|
<section class="scenery-section fade-in-section">
|
||||||
|
<div class="scenery-header">
|
||||||
|
<h2 class="scenery-title">{title}</h2>
|
||||||
|
{description && <p class="scenery-description">{description}</p>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="scenery-groups">
|
||||||
|
{selectedGroups.map((group, groupIndex) => (
|
||||||
|
<div
|
||||||
|
class="scenery-group"
|
||||||
|
style={`--group-delay: ${groupIndex * 0.2}s`}
|
||||||
|
>
|
||||||
|
<div class="group-label">
|
||||||
|
<span class="category-badge">{group.categoryName}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={`group-photos photos-${group.photos.length}`}>
|
||||||
|
{group.photos.map((photo, photoIndex) => (
|
||||||
|
<div
|
||||||
|
class="photo-item"
|
||||||
|
style={`--photo-delay: ${groupIndex * 0.2 + photoIndex * 0.1}s`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={photo.path}
|
||||||
|
alt={`${photo.categoryName} ${photo.groupId}-${photo.sequence}`}
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.scenery-section {
|
||||||
|
padding: 6rem 2rem;
|
||||||
|
background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenery-header {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto 4rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenery-title {
|
||||||
|
font-size: clamp(2rem, 5vw, 3.5rem);
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenery-description {
|
||||||
|
font-size: clamp(1rem, 2vw, 1.25rem);
|
||||||
|
color: #666;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenery-groups {
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenery-group {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
animation: groupFadeIn 0.8s ease forwards;
|
||||||
|
animation-delay: var(--group-delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-label {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-photos {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 单张照片 - 居中大图 */
|
||||||
|
.photos-1 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 两张照片 - 左右布局 */
|
||||||
|
.photos-2 {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 三张照片 - 一大两小 */
|
||||||
|
.photos-3 {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-template-rows: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photos-3 .photo-item:first-child {
|
||||||
|
grid-row: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 四张照片 - 田字格 */
|
||||||
|
.photos-4 {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-template-rows: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 五张及以上 - 瀑布流 */
|
||||||
|
.photos-5,
|
||||||
|
.photos-6,
|
||||||
|
.photos-7,
|
||||||
|
.photos-8 {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-auto-rows: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photos-5 .photo-item:first-child,
|
||||||
|
.photos-6 .photo-item:first-child,
|
||||||
|
.photos-7 .photo-item:first-child,
|
||||||
|
.photos-8 .photo-item:first-child {
|
||||||
|
grid-column: span 2;
|
||||||
|
grid-row: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-item {
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.95);
|
||||||
|
animation: photoFadeIn 0.6s ease forwards;
|
||||||
|
animation-delay: var(--photo-delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
transition: transform 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photo-item:hover img {
|
||||||
|
transform: scale(1.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes groupFadeIn {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes photoFadeIn {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式布局 */
|
||||||
|
@media (max-width: 968px) {
|
||||||
|
.scenery-section {
|
||||||
|
padding: 4rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scenery-groups {
|
||||||
|
gap: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photos-2,
|
||||||
|
.photos-3,
|
||||||
|
.photos-4 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photos-3 .photo-item:first-child,
|
||||||
|
.photos-5 .photo-item:first-child,
|
||||||
|
.photos-6 .photo-item:first-child {
|
||||||
|
grid-column: span 1;
|
||||||
|
grid-row: span 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.photos-5,
|
||||||
|
.photos-6,
|
||||||
|
.photos-7,
|
||||||
|
.photos-8 {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
grid-auto-rows: 200px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.photos-5,
|
||||||
|
.photos-6,
|
||||||
|
.photos-7,
|
||||||
|
.photos-8 {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// 画廊配置 - 所有布局和资源的单一数据源
|
// 画廊配置 - 所有布局和资源的单一数据源
|
||||||
|
// 支持的布局类型: hero, full-bleed, quad-grid, dual, grid, scenery
|
||||||
export const galleryConfig = {
|
export const galleryConfig = {
|
||||||
// 首屏大图 - 使用四人合照
|
// 首屏大图 - 使用四人合照
|
||||||
hero: {
|
hero: {
|
||||||
|
|
@ -74,6 +75,17 @@ export const galleryConfig = {
|
||||||
image: '/media/0_1.JPG',
|
image: '/media/0_1.JPG',
|
||||||
title: '青春不散场',
|
title: '青春不散场',
|
||||||
description: '愿时光不老,我们不散'
|
description: '愿时光不老,我们不散'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 6. 风景照片展示(新增)- 可通过 enabled 开关控制
|
||||||
|
{
|
||||||
|
type: 'scenery',
|
||||||
|
enabled: true, // 开关控制,设为 false 可关闭
|
||||||
|
title: '风景掠影',
|
||||||
|
description: '记录自然之美,定格时光瞒间',
|
||||||
|
categories: ['nature', 'urban', 'plants', 'animals'], // 要展示的类别
|
||||||
|
groupCount: 2, // 展示几个照片组
|
||||||
|
random: true // 是否随机选择
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
];
|
||||||
|
|
@ -7,6 +7,7 @@ import FullBleedSection from '../components/sections/FullBleedSection.astro';
|
||||||
import DualSection from '../components/sections/DualSection.astro';
|
import DualSection from '../components/sections/DualSection.astro';
|
||||||
import GridSection from '../components/sections/GridSection.astro';
|
import GridSection from '../components/sections/GridSection.astro';
|
||||||
import QuadGridSection from '../components/sections/QuadGridSection.astro';
|
import QuadGridSection from '../components/sections/QuadGridSection.astro';
|
||||||
|
import ScenerySection from '../components/sections/ScenerySection.astro';
|
||||||
|
|
||||||
// 组件映射表
|
// 组件映射表
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
|
|
@ -14,7 +15,8 @@ const componentMap = {
|
||||||
'full-bleed': FullBleedSection,
|
'full-bleed': FullBleedSection,
|
||||||
'dual': DualSection,
|
'dual': DualSection,
|
||||||
'grid': GridSection,
|
'grid': GridSection,
|
||||||
'quad-grid': QuadGridSection
|
'quad-grid': QuadGridSection,
|
||||||
|
'scenery': ScenerySection
|
||||||
};
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -34,6 +36,9 @@ const componentMap = {
|
||||||
|
|
||||||
<!-- 动态渲染所有 sections -->
|
<!-- 动态渲染所有 sections -->
|
||||||
{galleryConfig.sections.map((section, index) => {
|
{galleryConfig.sections.map((section, index) => {
|
||||||
|
// 如果 section 有 enabled 属性且为 false,则跳过
|
||||||
|
if (section.enabled === false) return null;
|
||||||
|
|
||||||
const Component = componentMap[section.type];
|
const Component = componentMap[section.type];
|
||||||
return Component ? (
|
return Component ? (
|
||||||
<Component {...section} data-index={index} />
|
<Component {...section} data-index={index} />
|
||||||
|
|
|
||||||