快速搭建一个漂亮的AI家庭网站-相册/时间线/日历/多用户/个性化配色(上)

摘要

在这个数字化时代,家庭网站成为记录和分享家庭珍贵时刻的理想方式。本文将详细介绍如何使用Flask框架搭建一个功能齐全、界面美观的且具有AI助手功能的家庭网站。该网站包含相册、时间线、日历等核心功能,支持多用户管理和个性化配色主题。通过本教程,即使是编程初学者也能轻松构建属于自己家庭的专属网站,记录生活中的每一个重要时刻。

本文分为上中下三个部分,本文(上)先介绍基本功能和布局的实现,AI功能的实现及拓展在中和下内容中介绍。

网站截图
在这里插入图片描述
文章尾部有更多网站截图

知识点和代码介绍

1. 项目架构与技术栈

我们的家庭网站基于以下技术栈构建:

  • 后端框架:Flask
  • 数据库:SQLAlchemy (ORM)
  • 前端框架:Bootstrap 5
  • 认证系统:Flask-Login
  • 表单处理:Flask-WTF
  • 图标库:Font Awesome

项目采用蓝图(Blueprint)结构组织代码,使各功能模块独立且易于维护:

# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_migrate import Migrate
from config import Config
import datetime
from markupsafe import Markup

db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.login_message = '请先登录以访问此页面'

def create_app(config_class=Config):
    app = Flask(__name__)
    app.config.from_object(config_class)
    
    db.init_app(app)
    migrate.init_app(app, db)
    login_manager.init_app(app)
    
    # 注册自定义过滤器
    @app.template_filter('nl2br')
    def _jinja2_filter_nl2br(s):
        if s is None:
            return ""
        return Markup(s.replace('\n', '<br>'))
    
    # 注册蓝图
    from app.main import bp as main_bp
    app.register_blueprint(main_bp)
    
    from app.auth import bp as auth_bp
    app.register_blueprint(auth_bp, url_prefix='/auth')
    
    from app.album import bp as album_bp
    app.register_blueprint(album_bp, url_prefix='/album')
    
    from app.timeline import bp as timeline_bp
    app.register_blueprint(timeline_bp, url_prefix='/timeline')
    
    from app.calendar import bp as calendar_bp
    app.register_blueprint(calendar_bp, url_prefix='/calendar')
    
    return app

2. 数据库模型设计

我们设计了多个数据模型来支持网站的各项功能:

# app/models.py
from app import db, login_manager
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
from datetime import datetime

class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64), index=True, unique=True)
    email = db.Column(db.String(120), index=True, unique=True)
    password_hash = db.Column(db.String(128))
    albums = db.relationship('Album', backref='user', lazy='dynamic')
    events = db.relationship('Event', backref='user', lazy='dynamic')
    schedules = db.relationship('Schedule', backref='user', lazy='dynamic')
    
    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
        
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

class Album(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    description = db.Column(db.Text)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    cover_photo_id = db.Column(db.Integer, db.ForeignKey('photo.id'), nullable=True)
    photos = db.relationship('Photo', backref='album', lazy='dynamic', 
                            foreign_keys='Photo.album_id')
    cover_photo = db.relationship('Photo', uselist=False, 
                                 foreign_keys=[cover_photo_id])

class Photo(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    filename = db.Column(db.String(255), nullable=False)
    title = db.Column(db.String(100))
    description = db.Column(db.Text)
    uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
    album_id = db.Column(db.Integer, db.ForeignKey('album.id'))
    is_private = db.Column(db.Boolean, default=False)

# 其他模型:Event(时间线事件)、Schedule(日历事件)等

3. 用户认证系统

使用Flask-Login实现安全的用户认证系统:

# app/auth/routes.py
@bp.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('main.index'))
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user is None or not user.check_password(form.password.data):
            flash('用户名或密码错误')
            return redirect(url_for('auth.login'))
        login_user(user, remember=form.remember_me.data)
        next_page = request.args.get('next')
        if not next_page or url_parse(next_page).netloc != '':
            next_page = url_for('main.index')
        return redirect(next_page)
    return render_template('auth/login.html', title='登录', form=form, current_year=datetime.now().year)

4. 相册功能实现

相册功能是家庭网站的核心,支持创建相册、上传照片、幻灯片播放等功能:

# app/album/routes.py
@bp.route('/<int:album_id>/upload', methods=['GET', 'POST'])
@login_required
def upload_photo(album_id):
    """上传照片到相册"""
    album = Album.query.get_or_404(album_id)
    if album.user_id != current_user.id:
        abort(403)
    
    form = PhotoForm()
    if form.validate_on_submit():
        uploaded_files = request.files.getlist('photos')
        if uploaded_files:
            success_count = 0
            for file in uploaded_files:
                filename = save_photo(file)
                if filename:
                    photo = Photo(
                        filename=filename,
                        title=form.title.data if hasattr(form, 'title') else None,
                        description=form.description.data,
                        album_id=album.id,
                        is_private=form.is_private.data
                    )
                    db.session.add(photo)
                    success_count += 1
            
            if success_count > 0:
                db.session.commit()
                flash(f'{success_count}张照片上传成功!', 'success')
                return redirect(url_for('album.view_album', album_id=album.id))
            else:
                flash('上传失败,请确保文件格式正确', 'danger')
        else:
            flash('请选择至少一张照片', 'warning')
    
    return render_template('album/upload.html', title='上传照片', form=form, album=album)
幻灯片播放功能

我们实现了一个美观的幻灯片功能,让用户可以全屏欣赏照片:

// app/templates/album/index.html (部分JavaScript代码)
// 开始幻灯片播放
function startSlideshow(albumId) {
    // 获取相册照片
    fetch(`/album/api/photos/${albumId}`)
        .then(response => response.json())
        .then(data => {
            if (data.photos && data.photos.length > 0) {
                photos = data.photos;
                currentIndex = 0;
                
                // 显示第一张照片
                showPhoto(currentIndex);
                
                // 显示模态框
                slideshowModal.style.display = 'block';
                
                // 开始自动播放
                startAutoPlay();
            } else {
                alert('该相册没有照片可播放');
            }
        })
        .catch(error => {
            console.error('获取照片失败:', error);
            alert('获取照片失败,请稍后再试');
        });
}

5. 时间线功能

时间线功能用于记录家庭重要事件,按年份分组显示:

# app/timeline/routes.py
@bp.route('/')
def index():
    """显示时间轴"""
    events = Event.query.order_by(Event.date.desc()).all()
    # 按年份分组事件
    events_by_year = {}
    for event in events:
        year = event.date.year
        if year not in events_by_year:
            events_by_year[year] = []
        events_by_year[year].append(event)
    
    # 按年份降序排序
    sorted_years = sorted(events_by_year.keys(), reverse=True)
    
    return render_template('timeline/index.html', 
                          title='家庭大事记',
                          events_by_year=events_by_year,
                          sorted_years=sorted_years)

6. 日历功能

日历功能用于记录和管理家庭活动、纪念日等:

# app/calendar/routes.py
@bp.route('/')
def index():
    """显示日历主页"""
    return render_template('calendar/index.html', title='家庭日历')

@bp.route('/api/events')
def get_events():
    """API端点:获取日历事件"""
    start = request.args.get('start')
    end = request.args.get('end')
    
    if start and end:
        start_date = datetime.fromisoformat(start.replace('Z', '+00:00'))
        end_date = datetime.fromisoformat(end.replace('Z', '+00:00'))
        
        # 查询指定日期范围内的日程
        schedules = Schedule.query.filter(
            Schedule.start_time >= start_date,
            Schedule.start_time <= end_date
        ).all()
    else:
        # 如果没有指定日期范围,返回所有日程
        schedules = Schedule.query.all()
    
    # 转换为FullCalendar可用的格式
    events = []
    for schedule in schedules:
        # 如果是私密日程且用户未登录或不是创建者,则跳过
        if schedule.is_private and (not current_user.is_authenticated or schedule.user_id != current_user.id):
            continue
            
        event = {
            'id': schedule.id,
            'title': schedule.title,
            'start': schedule.start_time.isoformat(),
            'end': schedule.end_time.isoformat() if schedule.end_time else None,
            'description': schedule.description,
            'allDay': schedule.all_day,
            'backgroundColor': schedule.color or '#3788d8',
            'borderColor': schedule.color or '#3788d8',
            'editable': current_user.is_authenticated and schedule.user_id == current_user.id
        }
        events.append(event)
    
    return jsonify(events)

7. 个性化配色主题

我们实现了可切换的配色主题,支持多种颜色方案和暗/亮模式:

// 主题切换功能
document.addEventListener('DOMContentLoaded', function() {
    // 获取主题切换按钮和颜色选择器
    const themeToggle = document.getElementById('theme-toggle');
    const colorSchemeSelect = document.getElementById('color-scheme');
    
    // 从localStorage获取保存的主题设置
    const savedTheme = localStorage.getItem('theme') || 'dark';
    const savedColorScheme = localStorage.getItem('colorScheme') || 'blue';
    
    // 应用保存的主题设置
    document.documentElement.setAttribute('data-bs-theme', savedTheme);
    document.documentElement.setAttribute('data-color-scheme', savedColorScheme);
    
    // 更新UI以匹配当前主题
    updateThemeToggleUI(savedTheme);
    if (colorSchemeSelect) {
        colorSchemeSelect.value = savedColorScheme;
    }
    
    // 主题切换按钮点击事件
    if (themeToggle) {
        themeToggle.addEventListener('click', function() {
            const currentTheme = document.documentElement.getAttribute('data-bs-theme');
            const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
            
            // 更新主题
            document.documentElement.setAttribute('data-bs-theme', newTheme);
            localStorage.setItem('theme', newTheme);
            
            // 更新UI
            updateThemeToggleUI(newTheme);
        });
    }
    
    // 颜色方案选择事件
    if (colorSchemeSelect) {
        colorSchemeSelect.addEventListener('change', function() {
            const newColorScheme = this.value;
            
            // 更新颜色方案
            document.documentElement.setAttribute('data-color-scheme', newColorScheme);
            localStorage.setItem('colorScheme', newColorScheme);
        });
    }
    
    // 更新主题切换按钮UI
    function updateThemeToggleUI(theme) {
        if (themeToggle) {
            const icon = themeToggle.querySelector('i');
            if (theme === 'dark') {
                icon.classList.remove('fa-moon');
                icon.classList.add('fa-sun');
                themeToggle.setAttribute('title', '切换到亮色模式');
            } else {
                icon.classList.remove('fa-sun');
                icon.classList.add('fa-moon');
                themeToggle.setAttribute('title', '切换到暗色模式');
            }
        }
    }
});

CSS变量定义不同的颜色方案:

/* 定义颜色变量 */
:root {
    /* 蓝色主题 - 默认 */
    --primary-color: #0d6efd;
    --primary-color-rgb: 13, 110, 253;
    --secondary-color: #6c757d;
    --secondary-color-rgb: 108, 117, 125;
    --accent-color: #0dcaf0;
    --accent-color-rgb: 13, 202, 240;
    --success-color: #198754;
    --warning-color: #ffc107;
    --danger-color: #dc3545;
    --info-color: #0dcaf0;
    --light-color: #f8f9fa;
    --dark-color: #212529;
    
    /* 其他UI元素颜色 */
    --border-color: rgba(var(--secondary-color-rgb), 0.2);
    --hover-color: rgba(var(--primary-color-rgb), 0.1);
    --active-color: rgba(var(--primary-color-rgb), 0.2);
    --card-bg: #ffffff;
    --body-bg: #f8f9fa;
    --text-color: #212529;
    --text-muted: #6c757d;
    --link-color: var(--primary-color);
    --link-hover-color: #0a58ca;
}

/* 暗色模式 */
[data-bs-theme="dark"] {
    --card-bg: #2b3035;
    --body-bg: #212529;
    --text-color: #f8f9fa;
    --text-muted: #adb5bd;
    --border-color: rgba(255, 255, 255, 0.1);
    --link-color: #6ea8fe;
    --link-hover-color: #8bb9fe;
}

/* 紫色主题 */
[data-color-scheme="purple"] {
    --primary-color: #6f42c1;
    --primary-color-rgb: 111, 66, 193;
    --accent-color: #e83e8c;
    --accent-color-rgb: 232, 62, 140;
    --link-color: var(--primary-color);
    --link-hover-color: #5a32a3;
}

/* 绿色主题 */
[data-color-scheme="green"] {
    --primary-color: #198754;
    --primary-color-rgb: 25, 135, 84;
    --accent-color: #20c997;
    --accent-color-rgb: 32, 201, 151;
    --link-color: var(--primary-color);
    --link-hover-color: #157347;
}

/* 灰色主题 */
[data-color-scheme="gray"] {
    --primary-color: #6c757d;
    --primary-color-rgb: 108, 117, 125;
    --accent-color: #adb5bd;
    --accent-color-rgb: 173, 181, 189;
    --link-color: var(--primary-color);
    --link-hover-color: #5c636a;
}

完整代码
Github链接 https://github.com/wyg5208/family_website

总结

通过本教程,我们成功构建了一个功能齐全的家庭网站,包含以下核心功能:

  1. 用户认证系统:支持多用户注册、登录和权限管理
  2. 相册功能:创建相册、上传照片、幻灯片播放
  3. 时间线功能:记录家庭重要事件,按年份分组展示
  4. 日历功能:管理家庭活动、纪念日等
  5. 个性化配色:支持多种颜色方案和暗/亮模式切换

这个项目展示了Flask框架的强大功能和灵活性,通过蓝图结构组织代码,使项目结构清晰、易于维护。同时,我们也使用了现代前端技术,如Bootstrap 5和CSS变量,打造了响应式、美观的用户界面。

拓展思考

在完成基础功能后,我们可以考虑以下拓展方向:

  1. 社交功能:添加评论、点赞功能,让家庭成员可以互动
  2. 家庭树:实现家谱功能,展示家庭成员关系
  3. 文件共享:除照片外,支持其他类型文件的上传和共享
  4. 家庭博客:添加博客功能,记录家庭生活点滴
  5. 移动端适配:开发移动应用或优化移动端体验
  6. 数据备份:实现自动备份功能,确保珍贵回忆安全保存
  7. AI增强:集成AI功能,如照片自动分类、人脸识别等

通过这些拓展,家庭网站可以成为更加全面的家庭数字中心,满足不同家庭的多样化需求。

最后,希望这个项目能帮助更多人记录和分享家庭的珍贵时刻,创造属于自己的数字家庭空间。无论是技术爱好者还是编程初学者,都能通过这个项目学习到实用的Web开发技能,同时创建一个有意义的应用。

以下是更多网站截图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐