Files
photography/docs/前端开发文档.md
2025-07-09 00:13:41 +08:00

524 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 摄影作品集网站 - 前端开发文档
## 1. 技术架构
### 1.1 技术栈选择
- **框架**Next.js 14 (App Router)
- **样式**Tailwind CSS + CSS Modules
- **状态管理**Zustand + React Query
- **图片处理**Sharp + Next.js Image
- **动画**Framer Motion
- **UI组件**Headless UI + 自定义组件
### 1.2 项目结构
```
src/
├── app/ # App Router页面
│ ├── layout.tsx # 根布局
│ ├── page.tsx # 首页
│ ├── gallery/ # 作品集页面
│ ├── timeline/ # 时间线页面
│ └── about/ # 关于页面
├── components/ # 可复用组件
│ ├── ui/ # 基础UI组件
│ ├── gallery/ # 作品集相关组件
│ ├── timeline/ # 时间线组件
│ └── layout/ # 布局组件
├── hooks/ # 自定义Hooks
├── lib/ # 工具函数
├── stores/ # 状态管理
├── types/ # TypeScript类型
└── styles/ # 全局样式
```
## 2. 核心功能实现
### 2.1 图片管理系统
#### 多格式图片处理
```typescript
// lib/imageUtils.ts
export interface ImageFormats {
raw?: string; // RAW文件路径
jpg: string; // JPG文件路径
webp?: string; // WebP优化版本
thumb: string; // 缩略图
}
export interface PhotoData {
id: string;
title: string;
description?: string;
date: string;
formats: ImageFormats;
metadata: {
camera?: string;
lens?: string;
settings?: {
iso: number;
aperture: string;
shutter: string;
focal: string;
};
};
collections: string[];
}
```
#### 响应式图片组件
```typescript
// components/ui/ResponsiveImage.tsx
interface ResponsiveImageProps {
photo: PhotoData;
sizes: string;
priority?: boolean;
className?: string;
onClick?: () => void;
}
export function ResponsiveImage({ photo, sizes, priority, className, onClick }: ResponsiveImageProps) {
return (
<div className={`relative overflow-hidden ${className}`} onClick={onClick}>
<Image
src={photo.formats.webp || photo.formats.jpg}
alt={photo.title}
fill
sizes={sizes}
priority={priority}
className="object-cover transition-transform hover:scale-105"
placeholder="blur"
blurDataURL={photo.formats.thumb}
/>
</div>
);
}
```
### 2.2 作品集网格系统
#### 自适应网格布局
```typescript
// components/gallery/PhotoGrid.tsx
export function PhotoGrid({ photos }: { photos: PhotoData[] }) {
return (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 md:gap-6">
{photos.map((photo, index) => (
<div
key={photo.id}
className={`aspect-square ${
index % 7 === 0 ? 'md:col-span-2 md:row-span-2' : ''
}`}
>
<ResponsiveImage
photo={photo}
sizes="(max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw"
priority={index < 8}
className="w-full h-full cursor-pointer"
onClick={() => openLightbox(photo)}
/>
</div>
))}
</div>
);
}
```
#### 虚拟化长列表
```typescript
// hooks/useVirtualGrid.ts
export function useVirtualGrid(items: PhotoData[], itemHeight: number) {
const [visibleItems, setVisibleItems] = useState<PhotoData[]>([]);
const [startIndex, setStartIndex] = useState(0);
const handleScroll = useCallback((scrollTop: number, containerHeight: number) => {
const newStartIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
newStartIndex + Math.ceil(containerHeight / itemHeight) + 2,
items.length
);
setStartIndex(newStartIndex);
setVisibleItems(items.slice(newStartIndex, endIndex));
}, [items, itemHeight]);
return { visibleItems, startIndex, handleScroll };
}
```
### 2.3 时间线功能
#### 时间线数据结构
```typescript
// types/timeline.ts
export interface TimelineGroup {
year: number;
months: {
[month: number]: {
photos: PhotoData[];
count: number;
};
};
}
export interface TimelineData {
groups: TimelineGroup[];
totalPhotos: number;
yearRange: [number, number];
}
```
#### 时间线组件
```typescript
// components/timeline/Timeline.tsx
export function Timeline({ data }: { data: TimelineData }) {
const [selectedYear, setSelectedYear] = useState<number>(data.yearRange[1]);
return (
<div className="flex">
{/* 年份导航 */}
<YearNavigation
years={data.groups.map(g => g.year)}
selectedYear={selectedYear}
onYearSelect={setSelectedYear}
/>
{/* 时间线内容 */}
<div className="flex-1 ml-8">
{data.groups
.filter(group => group.year === selectedYear)
.map(group => (
<YearSection key={group.year} group={group} />
))}
</div>
</div>
);
}
```
### 2.4 灯箱组件
#### 全功能图片查看器
```typescript
// components/ui/Lightbox.tsx
export function Lightbox() {
const { isOpen, currentPhoto, photos, currentIndex } = useLightboxStore();
const [isZoomed, setIsZoomed] = useState(false);
const [position, setPosition] = useState({ x: 0, y: 0 });
if (!isOpen || !currentPhoto) return null;
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-50 bg-black/90 flex items-center justify-center"
onClick={handleBackdropClick}
>
{/* 图片区域 */}
<div className="relative max-w-full max-h-full">
<motion.img
src={currentPhoto.formats.jpg}
alt={currentPhoto.title}
className={`max-w-full max-h-full ${isZoomed ? 'cursor-zoom-out' : 'cursor-zoom-in'}`}
style={isZoomed ? { transform: `scale(2) translate(${position.x}px, ${position.y}px)` } : {}}
onClick={handleImageClick}
onMouseMove={handleMouseMove}
/>
</div>
{/* 导航控件 */}
<NavigationControls
currentIndex={currentIndex}
total={photos.length}
onPrevious={goToPrevious}
onNext={goToNext}
/>
{/* 信息面板 */}
<PhotoInfoPanel photo={currentPhoto} />
</motion.div>
);
}
```
## 3. 性能优化策略
### 3.1 图片优化
#### 渐进式加载
```typescript
// hooks/useProgressiveImage.ts
export function useProgressiveImage(src: string, placeholderSrc: string) {
const [currentSrc, setCurrentSrc] = useState(placeholderSrc);
const [loading, setLoading] = useState(true);
useEffect(() => {
const img = new Image();
img.src = src;
img.onload = () => {
setCurrentSrc(src);
setLoading(false);
};
}, [src]);
return { src: currentSrc, loading };
}
```
#### 智能预加载
```typescript
// hooks/useImagePreloader.ts
export function useImagePreloader(photos: PhotoData[], currentIndex: number) {
useEffect(() => {
// 预加载当前图片的前后各2张
const preloadRange = 2;
const start = Math.max(0, currentIndex - preloadRange);
const end = Math.min(photos.length, currentIndex + preloadRange + 1);
for (let i = start; i < end; i++) {
if (i !== currentIndex) {
const img = new Image();
img.src = photos[i].formats.jpg;
}
}
}, [photos, currentIndex]);
}
```
### 3.2 状态管理优化
#### Zustand Store设计
```typescript
// stores/galleryStore.ts
interface GalleryState {
photos: PhotoData[];
collections: Collection[];
currentCollection: string | null;
loading: boolean;
error: string | null;
// Actions
setPhotos: (photos: PhotoData[]) => void;
setCurrentCollection: (id: string | null) => void;
addPhotos: (photos: PhotoData[]) => void;
}
export const useGalleryStore = create<GalleryState>((set, get) => ({
photos: [],
collections: [],
currentCollection: null,
loading: false,
error: null,
setPhotos: (photos) => set({ photos }),
setCurrentCollection: (id) => set({ currentCollection: id }),
addPhotos: (newPhotos) => set((state) => ({
photos: [...state.photos, ...newPhotos]
})),
}));
```
### 3.3 代码分割
#### 动态导入
```typescript
// app/gallery/page.tsx
const LazyPhotoGrid = dynamic(() => import('@/components/gallery/PhotoGrid'), {
loading: () => <PhotoGridSkeleton />,
ssr: false
});
const LazyLightbox = dynamic(() => import('@/components/ui/Lightbox'), {
ssr: false
});
```
## 4. 移动端优化
### 4.1 触摸手势
```typescript
// hooks/useSwipeGesture.ts
export function useSwipeGesture(onSwipeLeft: () => void, onSwipeRight: () => void) {
const touchStart = useRef<{ x: number; y: number } | null>(null);
const touchEnd = useRef<{ x: number; y: number } | null>(null);
const handleTouchStart = (e: TouchEvent) => {
touchEnd.current = null;
touchStart.current = {
x: e.targetTouches[0].clientX,
y: e.targetTouches[0].clientY,
};
};
const handleTouchEnd = () => {
if (!touchStart.current || !touchEnd.current) return;
const distance = touchStart.current.x - touchEnd.current.x;
const isLeftSwipe = distance > 50;
const isRightSwipe = distance < -50;
if (isLeftSwipe) onSwipeLeft();
if (isRightSwipe) onSwipeRight();
};
return { handleTouchStart, handleTouchEnd };
}
```
### 4.2 移动端导航
```typescript
// components/layout/MobileNavigation.tsx
export function MobileNavigation() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
{/* 汉堡菜单按钮 */}
<button
className="md:hidden fixed top-4 right-4 z-50"
onClick={() => setIsOpen(!isOpen)}
>
<HamburgerIcon />
</button>
{/* 侧边导航 */}
<AnimatePresence>
{isOpen && (
<motion.nav
initial={{ x: '100%' }}
animate={{ x: 0 }}
exit={{ x: '100%' }}
className="fixed inset-y-0 right-0 w-64 bg-black/95 backdrop-blur z-40"
>
<NavigationItems />
</motion.nav>
)}
</AnimatePresence>
</>
);
}
```
## 5. SEO优化
### 5.1 元数据管理
```typescript
// app/gallery/[collection]/page.tsx
export async function generateMetadata({ params }: { params: { collection: string } }) {
const collection = await getCollection(params.collection);
return {
title: `${collection.name} | 摄影作品集`,
description: collection.description,
openGraph: {
title: collection.name,
description: collection.description,
images: [collection.coverImage],
},
};
}
```
### 5.2 结构化数据
```typescript
// components/seo/StructuredData.tsx
export function PhotoStructuredData({ photo }: { photo: PhotoData }) {
const structuredData = {
'@context': 'https://schema.org',
'@type': 'Photograph',
name: photo.title,
description: photo.description,
contentUrl: photo.formats.jpg,
thumbnail: photo.formats.thumb,
dateCreated: photo.date,
creator: {
'@type': 'Person',
name: '摄影师姓名',
},
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
);
}
```
## 6. 部署和监控
### 6.1 构建优化
```javascript
// next.config.js
module.exports = {
images: {
domains: ['your-domain.com'],
formats: ['image/webp', 'image/avif'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256],
},
experimental: {
optimizeCss: true,
optimizePackageImports: ['framer-motion'],
},
compiler: {
removeConsole: process.env.NODE_ENV === 'production',
},
};
```
### 6.2 性能监控
```typescript
// lib/analytics.ts
export function trackPageView(url: string) {
if (typeof window !== 'undefined') {
gtag('config', 'GA_TRACKING_ID', {
page_location: url,
});
}
}
export function trackPhotoView(photoId: string) {
gtag('event', 'photo_view', {
event_category: 'engagement',
event_label: photoId,
});
}
```
## 7. 开发工具配置
### 7.1 TypeScript配置
```json
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"]
}
}
}
```
### 7.2 代码质量工具
```json
// package.json
{
"scripts": {
"lint": "next lint",
"lint:fix": "next lint --fix",
"type-check": "tsc --noEmit",
"test": "jest",
"test:watch": "jest --watch"
}
}
```