增加风景显示
|
|
@ -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张
|
||||
```
|
||||
|
||||
你可以根据这些照片自由组织任何布局!
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
export default defineConfig({
|
||||
site: 'https://gallery.yourdomain.com',
|
||||
site: 'https://leexxx.com',
|
||||
// 或者如果是主域名:'https://yourdomain.com'
|
||||
|
||||
// 图片优化
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
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 = {
|
||||
// 首屏大图 - 使用四人合照
|
||||
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 // 是否随机选择
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 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 = {
|
|||
|
||||
<!-- 动态渲染所有 sections -->
|
||||
{galleryConfig.sections.map((section, index) => {
|
||||
// 如果 section 有 enabled 属性且为 false,则跳过
|
||||
if (section.enabled === false) return null;
|
||||
|
||||
const Component = componentMap[section.type];
|
||||
return Component ? (
|
||||
<Component {...section} data-index={index} />
|
||||
|
|
|
|||