commit 6e38e6914df5a5bb94dc5b40604edf9aea8952a7 Author: 小白 Date: Thu Mar 12 08:35:52 2026 +0800 初始化项目:云下飞个人网站 功能: - 精美欢迎页面 - 用户登录/注册 - 主题切换(明/暗模式) - 快捷导航栏(思源/Gitea/NocoDB) 技术栈:Python 3.12 + Flask 3.0 + SQLite diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6ce00d --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python + +# Virtual Environment +venv/ +.venv/ +env/ + +# Flask +instance/ +*.db + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..d54911e --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# My One Web - 云下飞的个人网站 + +基于 Python Flask 构建的个人网站,支持登录注册功能。 + +## 功能特性 + +- 🎨 精美的欢迎页面 +- 🔐 用户注册功能 +- 🔑 用户登录/登出功能 +- 💾 SQLite 数据库存储 +- 🐶 小白倾情打造 + +## 技术栈 + +- Python 3.12 +- Flask 3.0 +- Flask-Login +- Flask-SQLAlchemy +- SQLite + +## 安装运行 + +```bash +# 安装依赖 +pip install -r requirements.txt + +# 运行应用 +python app.py +``` + +## 默认账户 + +- 用户名: `yunxiafei` +- 密码: `xu123654` + +## 访问地址 + +- 首页: http://localhost:5000 +- 登录: http://localhost:5000/login +- 注册: http://localhost:5000/register + +--- + +Made with ❤️ by 小白 🐶 diff --git a/app.py b/app.py new file mode 100644 index 0000000..430ed8a --- /dev/null +++ b/app.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +云下飞的个人网站 - Flask 版本 +支持登录、注册功能 +作者:小白 🐶 +""" + +from flask import Flask, render_template, redirect, url_for, request, flash +from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user +from flask_sqlalchemy import SQLAlchemy +from werkzeug.security import generate_password_hash, check_password_hash +import os + +# 初始化应用 +app = Flask(__name__) +app.config['SECRET_KEY'] = 'xiaobai-secret-key-2026' +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False + +# 初始化数据库 +db = SQLAlchemy(app) + +# 初始化登录管理 +login_manager = LoginManager() +login_manager.init_app(app) +login_manager.login_view = 'login' + + +# 用户模型 +class User(UserMixin, db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + password_hash = db.Column(db.String(120), nullable=False) + + 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) + + +@login_manager.user_loader +def load_user(user_id): + return User.query.get(int(user_id)) + + +# 路由 - 首页 +@app.route('/') +def index(): + if current_user.is_authenticated: + return render_template('welcome.html', username=current_user.username) + return render_template('index.html') + + +# 路由 - 登录 +@app.route('/login', methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + return redirect(url_for('index')) + + if request.method == 'POST': + username = request.form.get('username') + password = request.form.get('password') + + user = User.query.filter_by(username=username).first() + + if user and user.check_password(password): + login_user(user) + flash('登录成功!欢迎回来~ 🐶', 'success') + return redirect(url_for('index')) + else: + flash('用户名或密码错误!', 'error') + + return render_template('login.html') + + +# 路由 - 注册 +@app.route('/register', methods=['GET', 'POST']) +def register(): + if current_user.is_authenticated: + return redirect(url_for('index')) + + if request.method == 'POST': + username = request.form.get('username') + password = request.form.get('password') + confirm_password = request.form.get('confirm_password') + + # 验证 + if not username or not password: + flash('用户名和密码不能为空!', 'error') + return render_template('register.html') + + if password != confirm_password: + flash('两次密码不一致!', 'error') + return render_template('register.html') + + if len(username) < 3: + flash('用户名至少3个字符!', 'error') + return render_template('register.html') + + if len(password) < 6: + flash('密码至少6个字符!', 'error') + return render_template('register.html') + + # 检查用户是否存在 + existing_user = User.query.filter_by(username=username).first() + if existing_user: + flash('用户名已存在!', 'error') + return render_template('register.html') + + # 创建新用户 + user = User(username=username) + user.set_password(password) + db.session.add(user) + db.session.commit() + + flash('注册成功!请登录~ 🎉', 'success') + return redirect(url_for('login')) + + return render_template('register.html') + + +# 路由 - 登出 +@app.route('/logout') +@login_required +def logout(): + logout_user() + flash('已退出登录!', 'success') + return redirect(url_for('index')) + + +# 创建数据库 +with app.app_context(): + db.create_all() + + # 创建默认管理员账户 + admin = User.query.filter_by(username='yunxiafei').first() + if not admin: + admin = User(username='yunxiafei') + admin.set_password('xu123654') + db.session.add(admin) + db.session.commit() + print("✅ 默认管理员账户已创建: yunxiafei / xu123654") + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=False) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ce0593f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Flask==3.0.0 +Flask-Login==0.6.3 +Flask-SQLAlchemy==3.1.1 +Werkzeug==3.0.1 diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..20139db --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,388 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --bg-gradient-start: #667eea; + --bg-gradient-end: #764ba2; + --card-bg: rgba(255, 255, 255, 0.95); + --text-primary: #333; + --text-secondary: #666; + --text-muted: #999; + --accent-color: #667eea; + --border-color: #eee; + --input-bg: white; + --input-border: #eee; + --flash-success-bg: #d4edda; + --flash-success-text: #155724; + --flash-error-bg: #f8d7da; + --flash-error-text: #721c24; +} + +[data-theme="dark"] { + --bg-gradient-start: #1a1a2e; + --bg-gradient-end: #16213e; + --card-bg: rgba(30, 30, 46, 0.95); + --text-primary: #e0e0e0; + --text-secondary: #b0b0b0; + --text-muted: #808080; + --accent-color: #a78bfa; + --border-color: #3a3a5a; + --input-bg: #2a2a3a; + --input-border: #4a4a6a; + --flash-success-bg: #1a3a2a; + --flash-success-text: #6ee7b7; + --flash-error-bg: #3a1a1a; + --flash-error-text: #f87171; +} + +body { + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + transition: background 0.3s ease; +} + +.container { + text-align: center; + padding: 60px 40px; + background: var(--card-bg); + border-radius: 20px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + max-width: 600px; + width: 90%; + animation: fadeIn 0.6s ease-out; + position: relative; + transition: background 0.3s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.avatar { + width: 120px; + height: 120px; + background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); + border-radius: 50%; + margin: 0 auto 30px; + display: flex; + justify-content: center; + align-items: center; + font-size: 60px; + color: white; + box-shadow: 0 10px 30px rgba(102, 126, 234, 0.4); + transition: all 0.3s ease; +} + +h1 { + font-size: 42px; + color: var(--text-primary); + margin-bottom: 15px; + font-weight: 700; + transition: color 0.3s ease; +} + +.welcome { + font-size: 24px; + color: var(--accent-color); + margin-bottom: 25px; + font-weight: 500; + transition: color 0.3s ease; +} + +.message { + font-size: 16px; + color: var(--text-secondary); + line-height: 1.8; + margin-bottom: 30px; + transition: color 0.3s ease; +} + +.auth-buttons { + display: flex; + gap: 15px; + justify-content: center; + margin-bottom: 20px; +} + +.btn { + display: inline-block; + padding: 12px 30px; + border-radius: 25px; + text-decoration: none; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + border: none; +} + +.btn-primary { + background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); + color: white; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 5px 20px rgba(102, 126, 234, 0.5); +} + +.btn-secondary { + background: var(--card-bg); + color: var(--accent-color); + border: 2px solid var(--accent-color); +} + +.btn-secondary:hover { + background: var(--accent-color); + color: white; +} + +.btn-danger { + background: linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%); + color: white; +} + +.btn-danger:hover { + transform: translateY(-2px); + box-shadow: 0 5px 20px rgba(238, 90, 90, 0.5); +} + +.btn-full { + width: 100%; +} + +.footer { + font-size: 14px; + color: var(--text-muted); + padding-top: 20px; + border-top: 1px solid var(--border-color); + margin-top: 20px; + transition: all 0.3s ease; +} + +.footer span { + color: var(--accent-color); + font-weight: 600; +} + +.sparkle { + animation: sparkle 2s infinite; +} + +@keyframes sparkle { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +/* 快捷导航栏 */ +.quick-nav { + position: fixed; + top: 15px; + right: 15px; + display: flex; + align-items: center; + gap: 8px; + z-index: 100; +} + +.nav-btn { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + background: rgba(255, 255, 255, 0.9); + border: none; + border-radius: 20px; + font-size: 14px; + font-weight: 500; + color: #333; + text-decoration: none; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + white-space: nowrap; +} + +[data-theme="dark"] .nav-btn { + background: rgba(40, 40, 60, 0.9); + color: #e0e0e0; +} + +.nav-btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); +} + +[data-theme="dark"] .nav-btn:hover { + background: rgba(60, 60, 80, 0.95); +} + +/* 移动端导航 */ +@media (max-width: 600px) { + .quick-nav { + position: absolute; + top: auto; + bottom: -60px; + right: 50%; + transform: translateX(50%); + background: rgba(255, 255, 255, 0.95); + padding: 10px 15px; + border-radius: 25px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + } + + [data-theme="dark"] .quick-nav { + background: rgba(40, 40, 60, 0.95); + } + + .nav-btn { + padding: 6px 10px; + font-size: 12px; + } +} + +/* 主题切换按钮 - 集成到导航栏 */ +.theme-toggle { + width: 40px; + height: 40px; + border-radius: 50%; + border: none; + background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%); + color: white; + font-size: 20px; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15); + display: flex; + align-items: center; + justify-content: center; + touch-action: manipulation; + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; + user-select: none; +} + +.theme-toggle:hover { + transform: rotate(20deg) scale(1.1); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.25); +} + +.theme-toggle:active { + transform: scale(0.95); +} + +/* 表单样式 */ +.auth-container { + max-width: 400px; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group input { + width: 100%; + padding: 15px 20px; + border: 2px solid var(--input-border); + border-radius: 25px; + font-size: 16px; + transition: all 0.3s ease; + outline: none; + background: var(--input-bg); + color: var(--text-primary); +} + +.form-group input:focus { + border-color: var(--accent-color); +} + +.form-group input::placeholder { + color: var(--text-muted); +} + +.auth-link { + margin-top: 20px; + color: var(--text-secondary); + font-size: 14px; +} + +.auth-link a { + color: var(--accent-color); + text-decoration: none; + font-weight: 600; +} + +.auth-link a:hover { + text-decoration: underline; +} + +/* 消息提示 */ +.flash-success { + background: var(--flash-success-bg); + color: var(--flash-success-text); + padding: 12px 20px; + border-radius: 10px; + margin-bottom: 20px; + border: 1px solid transparent; +} + +.flash-error { + background: var(--flash-error-bg); + color: var(--flash-error-text); + padding: 12px 20px; + border-radius: 10px; + margin-bottom: 20px; + border: 1px solid transparent; +} + +.flash-messages { + position: fixed; + top: 20px; + right: 20px; + z-index: 1000; +} + +/* 响应式 */ +@media (max-width: 480px) { + .container { + padding: 40px 25px; + } + + h1 { + font-size: 32px; + } + + .welcome { + font-size: 20px; + } + + .auth-buttons { + flex-direction: column; + } + + .btn { + width: 100%; + } + + .theme-toggle { + top: 10px; + right: 10px; + width: 40px; + height: 40px; + font-size: 20px; + } +} diff --git a/static/js/theme.js b/static/js/theme.js new file mode 100644 index 0000000..3468222 --- /dev/null +++ b/static/js/theme.js @@ -0,0 +1,102 @@ +/** + * 暗黑主题切换功能 + * 小白制作 🐶 + */ + +(function() { + 'use strict'; + + const themeKey = 'my_one_web_theme'; + + // 获取保存的主题 + function getSavedTheme() { + try { + return localStorage.getItem(themeKey) || 'light'; + } catch (e) { + return 'light'; + } + } + + // 保存主题 + function saveTheme(theme) { + try { + localStorage.setItem(themeKey, theme); + } catch (e) { + // localStorage 不可用时忽略 + } + } + + // 应用主题 + function applyTheme(theme) { + document.documentElement.setAttribute('data-theme', theme); + updateToggleButton(theme); + } + + // 更新切换按钮图标 + function updateToggleButton(theme) { + const btn = document.querySelector('.theme-toggle'); + if (btn) { + btn.textContent = theme === 'dark' ? '☀️' : '🌙'; + btn.setAttribute('aria-label', theme === 'dark' ? '切换到亮色模式' : '切换到暗黑模式'); + } + } + + // 切换主题(防抖) + let isToggling = false; + function toggleTheme(e) { + // 阻止默认行为和冒泡 + if (e) { + e.preventDefault(); + e.stopPropagation(); + } + + // 防抖:300ms 内只响应一次 + if (isToggling) return; + isToggling = true; + + const currentTheme = getSavedTheme(); + const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; + saveTheme(newTheme); + applyTheme(newTheme); + + // 300ms 后重置 + setTimeout(function() { + isToggling = false; + }, 300); + } + + // 初始化主题 + function initTheme() { + const savedTheme = getSavedTheme(); + applyTheme(savedTheme); + + // 绑定切换按钮事件 + const btn = document.querySelector('.theme-toggle'); + if (btn) { + // 点击事件 + btn.addEventListener('click', toggleTheme); + + // 触摸事件(移动端) + btn.addEventListener('touchstart', function(e) { + e.preventDefault(); + }, { passive: false }); + + btn.addEventListener('touchend', toggleTheme, { passive: true }); + + // 键盘支持(无障碍) + btn.addEventListener('keydown', function(e) { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toggleTheme(e); + } + }); + } + } + + // DOM 加载完成后初始化 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initTheme); + } else { + initTheme(); + } +})(); diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..b293c7d --- /dev/null +++ b/templates/index.html @@ -0,0 +1,39 @@ + + + + + + 欢迎 - 云下飞 + + + + +
+ 📝 思源 + 📦 Gitea + 📊 NocoDB + +
+ +
+
+ 👋 +
+

欢迎

+

Welcome!

+

+ 这是您的个人服务器
+ 由 小白 🐶 为您精心打造
+ 祝您使用愉快! +

+
+ 登录 + 注册 +
+ +
+ + + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..14f171f --- /dev/null +++ b/templates/login.html @@ -0,0 +1,55 @@ + + + + + + 登录 - 云下飞 + + + + +
+ 📝 思源 + 📦 Gitea + 📊 NocoDB + +
+ +
+
+ 🔐 +
+

用户登录

+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + +
+
+ +
+
+ +
+ +
+ + + + + +
+ + + diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 0000000..4d30294 --- /dev/null +++ b/templates/register.html @@ -0,0 +1,58 @@ + + + + + + 注册 - 云下飞 + + + + +
+ 📝 思源 + 📦 Gitea + 📊 NocoDB + +
+ +
+
+ 📝 +
+

用户注册

+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + +
+
+ +
+
+ +
+
+ +
+ +
+ + + + + +
+ + + diff --git a/templates/welcome.html b/templates/welcome.html new file mode 100644 index 0000000..3633c9e --- /dev/null +++ b/templates/welcome.html @@ -0,0 +1,42 @@ + + + + + + 欢迎回来 - {{ username }} + + + +
+ +
+ 🎉 +
+

欢迎回来,{{ username }}!

+

Welcome Back!

+

+ 您已成功登录
+ 这是您的专属空间
+ 小白一直陪伴着您 🐶 +

+ + +
+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for category, message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endif %} + {% endwith %} + + + +