《AI大模型趣味实战》NO1:快速搭建一个漂亮的AI家庭网站-相册/时间线/日历/多用户/个性化配色(上)
在这个数字化时代,家庭网站成为记录和分享家庭珍贵时刻的理想方式。本文将详细介绍如何使用Flask框架搭建一个功能齐全、界面美观的且具有AI助手功能的家庭网站。该网站包含相册、时间线、日历等核心功能,支持多用户管理和个性化配色主题。通过本教程,即使是编程初学者也能轻松构建属于自己家庭的专属网站,记录生活中的每一个重要时刻。本文分为上中下三个部分,本文(上)先介绍基本功能和布局的实现,AI功能的实现及
快速搭建一个漂亮的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
总结
通过本教程,我们成功构建了一个功能齐全的家庭网站,包含以下核心功能:
- 用户认证系统:支持多用户注册、登录和权限管理
- 相册功能:创建相册、上传照片、幻灯片播放
- 时间线功能:记录家庭重要事件,按年份分组展示
- 日历功能:管理家庭活动、纪念日等
- 个性化配色:支持多种颜色方案和暗/亮模式切换
这个项目展示了Flask框架的强大功能和灵活性,通过蓝图结构组织代码,使项目结构清晰、易于维护。同时,我们也使用了现代前端技术,如Bootstrap 5和CSS变量,打造了响应式、美观的用户界面。
拓展思考
在完成基础功能后,我们可以考虑以下拓展方向:
- 社交功能:添加评论、点赞功能,让家庭成员可以互动
- 家庭树:实现家谱功能,展示家庭成员关系
- 文件共享:除照片外,支持其他类型文件的上传和共享
- 家庭博客:添加博客功能,记录家庭生活点滴
- 移动端适配:开发移动应用或优化移动端体验
- 数据备份:实现自动备份功能,确保珍贵回忆安全保存
- AI增强:集成AI功能,如照片自动分类、人脸识别等
通过这些拓展,家庭网站可以成为更加全面的家庭数字中心,满足不同家庭的多样化需求。
最后,希望这个项目能帮助更多人记录和分享家庭的珍贵时刻,创造属于自己的数字家庭空间。无论是技术爱好者还是编程初学者,都能通过这个项目学习到实用的Web开发技能,同时创建一个有意义的应用。
以下是更多网站截图:
更多推荐
所有评论(0)