feat: 重构博客为水墨纸质风格 + 搭建后台管理系统

- 重新设计全站 UI:parchment/ink/terracotta 水墨纸质色系,宋式 serif 排版
- 新增页面:文章列表、文章详情、分类、标签、关于
- GSAP ScrollTrigger 滚动动画 + 逐字揭示效果
- 后台管理系统 /admin:文章/分类/标签 CRUD,JSON 文件存储
- 登录认证(cookie session)
- 设计系统文档 UI.md
This commit is contained in:
胡旭
2026-06-24 08:45:28 +08:00
parent 507f12e501
commit dce8fe62ea
35 changed files with 3124 additions and 96 deletions
+153
View File
@@ -0,0 +1,153 @@
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
import path from "path";
const DATA_DIR = path.join(process.cwd(), "src/data/storage");
// Ensure storage directory exists
if (!existsSync(DATA_DIR)) {
mkdirSync(DATA_DIR, { recursive: true });
}
export interface Post {
id: string;
slug: string;
title: string;
excerpt: string;
content: string;
date: string;
category: string;
tags: string[];
readingTime: number;
featured: boolean;
status: "draft" | "published";
createdAt: string;
updatedAt: string;
}
export interface Category {
id: string;
name: string;
description: string;
}
export interface Tag {
id: string;
name: string;
}
function readJSON<T>(filename: string, fallback: T): T {
const filepath = path.join(DATA_DIR, filename);
if (!existsSync(filepath)) return fallback;
try {
return JSON.parse(readFileSync(filepath, "utf-8"));
} catch {
return fallback;
}
}
function writeJSON(filename: string, data: unknown) {
const filepath = path.join(DATA_DIR, filename);
writeFileSync(filepath, JSON.stringify(data, null, 2), "utf-8");
}
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
}
// ── Posts ──
export function getPosts(): Post[] {
return readJSON<Post[]>("posts.json", []);
}
export function getPost(id: string): Post | undefined {
return getPosts().find((p) => p.id === id);
}
export function getPostBySlug(slug: string): Post | undefined {
return getPosts().find((p) => p.slug === slug);
}
export function createPost(data: Omit<Post, "id" | "createdAt" | "updatedAt">): Post {
const posts = getPosts();
const now = new Date().toISOString();
const post: Post = {
...data,
id: generateId(),
createdAt: now,
updatedAt: now,
};
posts.unshift(post);
writeJSON("posts.json", posts);
return post;
}
export function updatePost(id: string, data: Partial<Post>): Post | null {
const posts = getPosts();
const index = posts.findIndex((p) => p.id === id);
if (index === -1) return null;
posts[index] = { ...posts[index], ...data, updatedAt: new Date().toISOString() };
writeJSON("posts.json", posts);
return posts[index];
}
export function deletePost(id: string): boolean {
const posts = getPosts();
const filtered = posts.filter((p) => p.id !== id);
if (filtered.length === posts.length) return false;
writeJSON("posts.json", filtered);
return true;
}
// ── Categories ──
export function getCategories(): Category[] {
return readJSON<Category[]>("categories.json", []);
}
export function createCategory(data: Omit<Category, "id">): Category {
const categories = getCategories();
const cat: Category = { ...data, id: generateId() };
categories.push(cat);
writeJSON("categories.json", categories);
return cat;
}
export function updateCategory(id: string, data: Partial<Category>): Category | null {
const categories = getCategories();
const index = categories.findIndex((c) => c.id === id);
if (index === -1) return null;
categories[index] = { ...categories[index], ...data };
writeJSON("categories.json", categories);
return categories[index];
}
export function deleteCategory(id: string): boolean {
const categories = getCategories();
const filtered = categories.filter((c) => c.id !== id);
if (filtered.length === categories.length) return false;
writeJSON("categories.json", filtered);
return true;
}
// ── Tags ──
export function getTags(): Tag[] {
return readJSON<Tag[]>("tags.json", []);
}
export function createTag(data: Omit<Tag, "id">): Tag {
const tags = getTags();
const tag: Tag = { ...data, id: generateId() };
tags.push(tag);
writeJSON("tags.json", tags);
return tag;
}
export function deleteTag(id: string): boolean {
const tags = getTags();
const filtered = tags.filter((t) => t.id !== id);
if (filtered.length === tags.length) return false;
writeJSON("tags.json", filtered);
return true;
}