1 Python Flask 框架简介
Flask 是一个轻量级的 Python Web 框架,基于 Werkzeug WSGI 工具包和 Jinja2 模板引擎。它被设计为简单易用,同时具备高度可扩展性,适合快速开发小型到中型 Web 应用。
1.1 核心特性
- 轻量级与模块化
Flask 核心功能简洁,仅包含路由、请求处理和模板渲染等基础功能。其他功能(如数据库支持、表单验证)通过扩展实现,开发者可以按需选择。
- 内置开发服务器与调试器
Flask 提供本地开发服务器和交互式调试器,方便快速测试和问题排查。
- RESTful 请求支持
默认支持 HTTP 方法(GET/POST/PUT/DELETE),适合构建 RESTful API。
- Jinja2 模板引擎
支持模板继承、变量替换和逻辑控制,便于动态生成 HTML。
1.2 基本代码示例
以下是一个简单的 Flask 应用代码:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return "Hello, Flask!"
if __name__ == '__main__':
app.run(debug=True)
运行后访问 http://localhost:5000
将显示 "Hello, Flask!"。
1.3 适用场景
- 原型开发与小规模应用
- 微服务架构中的独立服务
- 需要高度自定义的中型项目
Flask 的灵活性和简洁性使其成为 Python Web 开发的热门选择,尤其适合初学者和需要快速迭代的项目。
2. AI助手协助实现Flask框架开发及代码优化
2.1 AI助手在Flask网页开发中的核心作用
现代Flask开发中,AI助手能显著提升效率。通过代码生成、调试辅助和自动化测试,开发者可以更专注于业务逻辑而非重复性工作。AI助手能快速生成Flask路由模板、表单验证代码或数据库模型,减少基础代码的编写时间。例如创建用户登录系统时,AI可自动生成基于Flask-Login的完整实现代码。
AI助手还能解析错误信息并提供修复方案。当出现"404 Not Found"或"500 Internal Server Error"时,它能分析日志并定位问题根源。对于常见错误如循环导入或模板渲染失败,AI能提供针对性解决建议,大幅缩短调试时间。
2.2 关键功能实现方法
利用AI生成Flask项目基础结构只需明确需求描述。输入"创建包含用户认证和REST API的Flask项目",AI会输出包含蓝本结构、配置文件和依赖列表的完整项目框架。典型输出包括app/init.py的工厂函数、auth/routes.py的登录逻辑以及api/resources.py的端点定义。
数据库集成方面,AI能生成SQLAlchemy模型定义。描述数据关系如"用户拥有多个博客文章",AI将输出包含外键关系的模型类代码。它还能自动生成迁移脚本,确保数据库模式与模型保持同步。对于复杂查询,AI可优化SQL语句并建议合适的索引策略。
2.3 前端与后端协同开发
AI助手在模板渲染环节表现突出。给定页面结构和数据需求,它能生成Jinja2模板代码。需要显示用户仪表盘时,AI会创建包含循环语句、条件判断和过滤器链的HTML文件。对Bootstrap集成,AI能建议响应式布局方案并生成对应的CSS类组合。
异步任务处理中,AI可配置Celery工作队列。描述"需要后台处理图片上传"的任务,AI将生成包含任务定义、队列设置和结果存储的完整实现。对于WebSocket通信,它能快速搭建Flask-SocketIO环境并处理实时事件。
2.4 性能优化与安全加固
AI工具能分析路由性能瓶颈。输入"/api/data端点响应缓慢",AI会建议添加缓存机制或数据库查询优化。它能识别N+1查询问题并推荐加载策略调整方案。对于高并发场景,AI可指导Gunicorn配置调优和负载均衡设置。
安全防护方面,AI自动检查常见漏洞。它会标记未过滤的用户输入、缺少CSRF保护的表单以及明文密码存储等问题。AI能生成符合OWASP标准的防护代码,包括参数化查询、内容安全策略和速率限制实现。对于身份验证流程,AI会建议多因素认证集成方案。
2.5 测试与部署自动化
AI可生成单元测试和集成测试套件。描述"测试用户注册流程",AI将输出包含边界值测试、异常情况处理的测试用例。它能模拟各种HTTP请求并验证响应状态和数据完整性。对于持续集成,AI可配置GitHub Actions工作流实现自动测试和代码质量检查。
部署环节中,AI能生成Dockerfile和docker-compose.yml文件。根据"部署到AWS ECS"的需求,AI会提供包含健康检查、日志收集和自动扩展的云架构方案。它能指导配置Nginx反向代理和SSL证书安装,确保生产环境的最佳实践。
2.5 实际开发场景应用
构建电商网站时,AI可快速实现核心功能模块。产品目录页面需要分页显示和搜索过滤,AI会生成包含Flask-SQLAlchemy查询对象的视图函数。购物车功能涉及会话管理,AI将创建安全的cookie处理方案。支付集成方面,AI能建议合适的SDK并处理webhook验证。
内容管理系统开发中,AI协助实现富文本编辑和媒体上传。它推荐使用Flask-CKEditor扩展并配置云存储后端。对于权限管理,AI生成基于角色的访问控制代码,精确控制不同用户组的操作权限。缓存策略上,AI会按内容类型设计不同的失效机制。
3. 实际案例
在自动化测试中,通常会用到tkinter, tkinter 是 Python 的标准 GUI(图形用户界面)库,基于 Tk GUI 工具包开发。它提供跨平台支持(Windows/macOS/Linux),通过简单直观的接口实现窗口、按钮、文本框等组件的创建。
在开发用户界面时,选择Flask(配合HTML/CSS/JavaScript作为前端)替代Tkinter作为GUI解决方案,能带来显著优势:
-
跨平台性和可访问性:Tkinter应用通常需要用户在本地安装Python环境和相关依赖,限制了使用场景。而基于Flask的Web应用,通过浏览器访问,可在任何设备(如Windows、macOS、Linux或移动端)上运行,无需额外安装。用户只需输入URL即可访问,大大提升了便捷性和覆盖范围。
-
用户界面丰富度和交互性:Tkinter的UI组件相对基础,开发复杂界面(如动态图表或响应式布局)较困难。Flask结合现代Web技术(如HTML5、CSS3和JavaScript框架),允许创建高度定制化、美观的界面,支持动画、实时数据更新和高级交互(如拖拽操作),提升用户体验。
-
可扩展性和并发支持:Tkinter应用多为单机版,难以处理多用户并发请求。Flask作为后端框架,可轻松集成数据库(如SQLite或MySQL)和API,支持多用户同时访问,适用于需要分布式部署的场景(如在线工具或企业系统)。此外,Web应用可通过负载均衡扩展,处理高流量。
-
维护和部署便捷性:Tkinter应用更新时需重新分发安装包,增加维护成本。Flask应用部署到Web服务器(如Nginx或Apache)后,更新只需修改服务器端代码,用户端自动获取最新版本,简化了迭代和bug修复。
-
生态系统和社区支持:Flask拥有庞大的开源社区和丰富插件(如Flask-SQLAlchemy或Flask-WTF),加速开发进程。相比之下,Tkinter的扩展性较弱,新功能实现受限。Web技术也更符合现代开发趋势,便于集成云服务或第三方API。
以下提供一个实际自动化代码生成前端界面开发案例:包括用户登录,账号注册,功能界面:
3.1 app.py
import hashlib
import json
import os
import time
import re
from flask import Flask, render_template, redirect, url_for, flash, session, request, jsonify
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length, EqualTo, ValidationError
# 模拟用户数据库
users_db = {"User":
{
'username': "User",
'password': hashlib.sha256("123".encode()).hexdigest(),
'phone': ""
},
}
class RegistrationForm(FlaskForm):
username = StringField('用户名', validators=[
DataRequired('用户名不能为空'),
Length(min=3, max=20, message='用户名长度应在3-20个字符之间')
])
password = PasswordField('密码', validators=[
DataRequired('密码不能为空'),
Length(min=8, message='密码至少需要8个字符')
])
confirm_password = PasswordField('确认密码', validators=[
DataRequired('请确认密码'),
EqualTo('password', '两次输入的密码不一致')
])
phone = StringField('手机号码')
agree_terms = BooleanField('同意条款', validators=[DataRequired('您必须同意服务条款才能注册')])
submit = SubmitField('注册')
@staticmethod
def validate_username(username):
# 检查用户名是否已存在
if username.data in users_db:
raise ValidationError('该用户名已被注册,请选择其他用户名')
@staticmethod
def validate_password(password):
# 密码强度验证
if len(password.data) < 8:
raise ValidationError('密码长度至少为8个字符')
if not re.search(r'[A-Z]', password.data):
raise ValidationError('密码应包含至少一个大写字母')
if not re.search(r'[a-z]', password.data):
raise ValidationError('密码应包含至少一个小写字母')
if not re.search(r'[0-9]', password.data):
raise ValidationError('密码应包含至少一个数字')
@staticmethod
def validate_phone(phone):
# 手机号验证(可选)
if phone.data:
if not re.match(r'^1[3-9]\d{9}$', phone.data):
raise ValidationError('请输入有效的手机号码')
class CodeGenerateAPP:
def __init__(self):
self.app = Flask(__name__)
self.app.secret_key = 'your_secret_key_here'
self.users_db = users_db # 用户数据库
self.config = self.load_config()
self.file_attr_mapping = [
('demo.c', 'origin_demo_c_file'),
('demo.h', 'origin_demo_h_file')
]
# 注册路由
self.register_routes()
@staticmethod
def load_config():
"""加载配置文件"""
with open(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'conf.json'), 'r', encoding='utf-8') as f:
return json.load(f)
def register_routes(self):
"""注册所有路由"""
# 首页
@self.app.route('/')
def home():
return render_template('home.html')
# 登录页
@self.app.route('/login', methods=['GET', 'POST'])
def login():
error_message = None
if request.method == 'POST':
username = request.form['username'].strip()
password = request.form['password']
# 检查用户是否已注册
if username in self.users_db:
user = self.users_db[username]
# 验证密码
input_hash = hashlib.sha256(password.encode()).hexdigest()
if input_hash == user['password']:
session['username'] = username
return redirect(url_for('dashboard'))
else:
error_message = "密码不正确"
else:
error_message = "当前账户未注册"
return render_template('login.html',
error_message=error_message,
request=request)
# 注册页
@self.app.route('/register', methods=['GET', 'POST'])
def register():
if 'username' in session:
return redirect(url_for('dashboard'))
form = RegistrationForm()
if form.validate_on_submit():
# 创建用户对象
user = {
'username': form.username.data,
'password': hashlib.sha256(form.password.data.encode()).hexdigest(),
'phone': form.phone.data
}
self.users_db[form.username.data] = user
session['username'] = form.username.data
flash(f'欢迎 {form.username.data},您已成功注册!', 'success')
return redirect(url_for('dashboard'))
if form.errors:
for field, errors in form.errors.items():
for error in errors:
flash(f'{error}', 'danger')
return render_template('register.html', form=form)
# 主页
@self.app.route('/dashboard')
def dashboard():
if 'username' not in session:
return redirect(url_for('login'))
return render_template('dashboard.html',
car_platform_list=self.config['car_platform_list'],
username=session['username'])
# MCU Project解析
@self.app.route('/parse_code_project', methods=['POST'])
def parse_code_project():
data = request.get_json()
result = {}
# 模拟解析原始文件夹的过程
time.sleep(2)
if result:
result['status'] = 'success'
else:
result['status'] = 'fail'
return jsonify(result)
# 处理生成代码请求
@self.app.route('/generateEdr', methods=['POST'])
def generate_edr():
return self.simulate_processing("Code Generation", 2)
# 退出登录
@self.app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('home'))
@staticmethod
def save_session_data(data):
"""保存用户数据到session"""
session['car_platform'] = data.get('car_platform')
session['over_write_flag'] = data.get('over_write_flag', False)
session['author'] = data.get('author')
session['version'] = data.get('version')
session['changelog'] = data.get('changelog')
session['code_project_folder'] = data.get('code_project_folder')
# 保存特定操作的额外数据
if 'Requirements_Path' in data:
session['Requirements_Path'] = data.get('Requirements_Path')
def simulate_processing(self, operation_name, sleep_time):
"""模拟处理过程"""
data = request.json
self.save_session_data(data)
print(f"Received {operation_name} request:")
for key, value in data.items():
print(f"{key}: {value}")
# 模拟处理时间
time.sleep(sleep_time)
# 生成模拟输出路径
output_path = f"D:/output/{operation_name.replace(' ', '_')}_{int(time.time())}.txt"
return jsonify({
'status': 'success',
'message': f'{operation_name} completed successfully!',
'output_path': output_path
})
def run(self, **kwargs):
"""运行Flask应用"""
self.app.run(**kwargs)
if __name__ == '__main__':
app = CodeGenerateAPP()
app.run(debug=True, host='0.0.0.0', port=5002) # 'localhost'
3.2 templates html模板
3.2.1 home.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>代码生成工具</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2c3e50;
--accent-color: #e74c3c;
--light-color: #ecf0f1;
--dark-color: #34495e;
}
body {
background: linear-gradient(135deg, #1a2980, #26d0ce);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
padding: 20px 0;
}
/* 修复UI靠左问题 */
.container {
width: 100%;
max-width: 1400px; /* 增加最大宽度 */
margin: 0 auto; /* 确保居中 */
padding: 0 15px;
}
.card {
border-radius: 15px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
border: none;
transition: all 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);
}
.card-header {
background: linear-gradient(135deg, var(--primary-color), #1a6fc4);
color: white;
padding: 25px 20px;
font-weight: 600;
border: none;
}
.card-body {
padding: 30px;
}
.lead {
font-size: 1.25rem;
line-height: 1.7;
}
.display-4 {
font-weight: 800;
letter-spacing: -0.5px;
}
.card-title {
font-weight: 700;
color: var(--secondary-color);
margin-bottom: 15px;
}
.card-text {
color: #666;
line-height: 1.7;
}
.animation-container {
position: relative;
height: 150px;
margin-bottom: 30px;
}
.floating-icon {
position: absolute;
font-size: 3rem;
color: rgba(255, 255, 255, 0.7);
animation: float 6s infinite ease-in-out;
}
.floating-icon:nth-child(1) {
top: 10px;
left: 10%;
animation-delay: 0s;
}
.floating-icon:nth-child(2) {
top: 40px;
right: 15%;
animation-delay: 1s;
}
.floating-icon:nth-child(3) {
bottom: 20px;
left: 20%;
animation-delay: 2s;
}
.floating-icon:nth-child(4) {
bottom: 50px;
right: 25%;
animation-delay: 3s;
}
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-20px);
}
100% {
transform: translateY(0px);
}
}
/* 安全登录按钮样式 */
.login-btn-container {
margin: 30px auto;
max-width: 500px;
}
.login-btn {
display: block;
width: 100%;
padding: 30px;
text-align: center;
background: linear-gradient(135deg, #3498db, #1a6fc4);
color: white;
border-radius: 15px;
font-size: 1.8rem;
font-weight: bold;
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
border: none;
cursor: pointer;
text-decoration: none;
}
.login-btn:hover {
transform: translateY(-7px);
box-shadow: 0 15px 30px rgba(0,0,0,0.3);
background: linear-gradient(135deg, #2980b9, #155a8a);
}
.login-btn i {
font-size: 4rem;
margin-bottom: 20px;
display: block;
transition: transform 0.3s ease;
}
.login-btn:hover i {
transform: scale(1.1);
}
.login-btn-text {
font-size: 2.2rem;
letter-spacing: 1px;
margin-bottom: 10px;
display: block;
}
.login-btn-subtext {
font-size: 1.2rem;
opacity: 0.9;
font-weight: normal;
}
.login-btn::after {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: rgba(255,255,255,0.1);
transform: rotate(30deg);
transition: all 0.6s ease;
}
.login-btn:hover::after {
transform: rotate(30deg) translate(20%, 20%);
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0.7);
}
70% {
box-shadow: 0 0 0 15px rgba(52, 152, 219, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0);
}
}
.feature-icon {
background: linear-gradient(135deg, var(--primary-color), #1a6fc4);
width: 100px;
height: 100px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
font-size: 40px;
color: white;
transition: all 0.3s ease;
}
.login-btn .feature-icon {
margin-top: -20px;
margin-bottom: 30px;
}
</style>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card shadow-lg">
<div class="card-header text-center">
<h1 class="display-4 fw-bold"><i class="fas fa-lock me-2"></i>代码生成工具</h1>
</div>
<div class="card-body">
<div class="animation-container d-none d-md-block">
<div class="floating-icon">
<i class="fas fa-code"></i>
</div>
<div class="floating-icon">
<i class="fas fa-microchip"></i>
</div>
<div class="floating-icon">
<i class="fas fa-shield-alt"></i>
</div>
<div class="floating-icon">
<i class="fas fa-bolt"></i>
</div>
</div>
<p class="lead text-center text-secondary mb-3">
一个基于 Flask 的代码生成工具系统,提供安全、高效的代码生成解决方案
</p>
<!-- 安全登录按钮 -->
<div class="login-btn-container">
<a href="{{ url_for('login') }}" class="login-btn pulse">
<div class="feature-icon">
<i class="fas fa-user-shield"></i>
</div>
<span class="login-btn-text">安全登录</span>
<span class="login-btn-subtext">点击进入登录页面</span>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-5">
<div class="col-12 text-center">
<p class="text-white mb-0">© <span id="currentYear"></span> 代码生成工具 | 版本 1.0.0 | Author: XXXX</p>
<p class="text-white-50">自动化代码生成平台</p>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 显示当前年份
document.getElementById('currentYear').textContent = new Date().getFullYear();
// 添加动画效果
document.addEventListener('DOMContentLoaded', function() {
// 安全登录按钮动画
const loginBtn = document.querySelector('.login-btn');
setInterval(() => {
loginBtn.classList.toggle('pulse');
}, 2000);
// 功能卡片动画
const featureCards = document.querySelectorAll('.card');
featureCards.forEach((card, index) => {
setTimeout(() => {
card.style.opacity = '0';
card.style.transform = 'translateY(20px)';
setTimeout(() => {
card.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
}, 100);
}, 300 * index);
});
});
</script>
</body>
</html>
3.2.2 login.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户登录 - 代码生成工具</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2c3e50;
--accent-color: #e74c3c;
--light-color: #ecf0f1;
--dark-color: #34495e;
}
body {
background: linear-gradient(135deg, #1a2980, #26d0ce);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
padding: 20px 0;
}
/* 修复UI靠左问题 */
.container {
width: 100%;
max-width: 1400px; /* 增加最大宽度 */
margin: 0 auto; /* 确保居中 */
padding: 0 15px;
}
.auth-card {
border-radius: 15px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
border: none;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.95);
}
.auth-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);
}
.card-header {
background: linear-gradient(135deg, var(--primary-color), #1a6fc4);
color: white;
padding: 25px 20px;
font-weight: 600;
border: none;
}
.card-body {
padding: 30px;
}
.form-control {
border-radius: 8px;
padding: 12px 15px;
border: 1px solid #ddd;
transition: all 0.3s ease;
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.25rem rgba(52, 152, 219, 0.25);
}
.input-group-text {
background: #f8f9fa;
border: 1px solid #ddd;
border-radius: 8px 0 0 8px;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), #1a6fc4);
border: none;
padding: 12px 30px;
font-weight: 600;
font-size: 18px;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
border-radius: 8px;
}
.btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 6px 20px rgba(52, 152, 219, 0.4);
background: linear-gradient(135deg, #2980b9, #1a6fc4);
}
.btn-outline-primary {
border: 2px solid var(--primary-color);
color: var(--primary-color);
font-weight: 600;
padding: 12px 30px;
font-size: 18px;
transition: all 0.3s ease;
border-radius: 8px;
}
.btn-outline-primary:hover {
background: var(--primary-color);
color: white;
transform: translateY(-3px);
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);
}
.form-label {
font-weight: 600;
color: var(--secondary-color);
margin-bottom: 8px;
}
.auth-footer {
border-top: 1px solid #eee;
padding-top: 20px;
margin-top: 20px;
}
.password-toggle {
cursor: pointer;
background: #f8f9fa;
border: 1px solid #ddd;
border-left: none;
border-radius: 0 8px 8px 0;
padding: 0 15px;
display: flex;
align-items: center;
}
.password-toggle:hover {
background: #e9ecef;
}
.alert-danger {
border-radius: 8px;
padding: 15px;
background: linear-gradient(135deg, rgba(231, 76, 60, 0.1), rgba(231, 76, 60, 0.05));
border: 1px solid rgba(231, 76, 60, 0.2);
}
.social-login {
display: flex;
justify-content: center;
gap: 15px;
margin: 20px 0;
}
.social-btn {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: white;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.social-btn:hover {
transform: translateY(-3px);
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
}
.google-btn {
background: linear-gradient(135deg, #DB4437, #c5392c);
}
.github-btn {
background: linear-gradient(135deg, #333, #222);
}
.microsoft-btn {
background: linear-gradient(135deg, #0078d7, #0063b1);
}
.divider {
display: flex;
align-items: center;
text-align: center;
margin: 20px 0;
color: #6c757d;
}
.divider::before,
.divider::after {
content: '';
flex: 1;
border-bottom: 1px solid #dee2e6;
}
.divider::before {
margin-right: 15px;
}
.divider::after {
margin-left: 15px;
}
.brand-logo {
text-align: center;
margin-bottom: 30px;
}
.brand-logo img {
height: 60px;
margin-bottom: 10px;
}
.remember-forgot {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.form-check-input:checked {
background-color: var(--primary-color);
border-color: var(--primary-color);
}
.modal-content {
border-radius: 15px;
overflow: hidden;
}
.modal-header {
background: linear-gradient(135deg, #e74c3c, #c0392b);
}
.password-strength {
height: 4px;
background: #eee;
border-radius: 2px;
margin-top: 5px;
overflow: hidden;
}
.strength-meter {
height: 100%;
width: 0;
background: #e74c3c;
transition: all 0.3s ease;
}
.password-hints {
font-size: 0.85rem;
color: #6c757d;
margin-top: 5px;
}
</style>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="auth-card">
<div class="card-header text-center py-4">
<h1 class="h3 mb-0"><i class="fas fa-lock me-2"></i>代码生成工具</h1>
<p class="mb-0 mt-2">用户登录</p>
</div>
<div class="card-body p-4">
<div class="brand-logo">
<i class="fas fa-code fa-3x text-primary mb-2"></i>
<h3 class="mb-0">用户登录</h3>
</div>
<!-- 错误消息容器 -->
{% if error_message %}
<div class="alert alert-danger d-flex align-items-center mb-4" role="alert">
<i class="fas fa-exclamation-circle me-3 fa-2x"></i>
<div>
<h5 class="alert-heading">登录失败</h5>
<p class="mb-0">{{ error_message }}</p>
</div>
</div>
{% endif %}
<form action="{{ url_for('login') }}" method="POST" id="loginForm">
<input type="hidden" name="next" value="{{ request.args.get('next', '') }}">
<div class="mb-4">
<label for="username" class="form-label">用户名</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-user"></i></span>
<input type="text" class="form-control form-control-lg" id="username" name="username"
placeholder="请输入用户名" required autofocus
value="{{ request.form.username if request.form.username else '' }}">
</div>
</div>
<div class="mb-4">
<label for="password" class="form-label">密码</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
<input type="password" class="form-control form-control-lg" id="password" name="password"
placeholder="请输入密码" required>
<span class="password-toggle" id="passwordToggle">
<i class="fas fa-eye"></i>
</span>
</div>
<div class="password-strength mt-2">
<div class="strength-meter" id="passwordStrength"></div>
</div>
<div class="password-hints">
密码长度至少8位,包含字母和数字
</div>
</div>
<div class="remember-forgot">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="remember" name="remember">
<label class="form-check-label" for="remember">记住我</label>
</div>
<div>
<a href="#" class="text-decoration-none">忘记密码?</a>
</div>
</div>
<div class="d-grid mb-3">
<button type="submit" class="btn btn-primary btn-lg py-3">
<i class="fas fa-sign-in-alt me-2"></i>登录
</button>
</div>
<div class="auth-footer text-center">
<p class="text-muted mb-2">还没有账户?</p>
<a href="{{ url_for('register') }}" class="btn btn-outline-primary">
<i class="fas fa-user-plus me-2"></i>立即注册
</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- 弹窗模态框 -->
{% if error_message %}
<div class="modal fade" id="errorModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-exclamation-triangle me-2"></i>账户未注册</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="d-flex align-items-center">
<div class="flex-shrink-0 me-3">
<i class="fas fa-user-slash text-danger" style="font-size: 2.5rem;"></i>
</div>
<div class="flex-grow-1">
<h5>账户未注册</h5>
<p class="mb-0">您输入的账户尚未注册,请先注册新账户</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<a href="{{ url_for('register') }}" class="btn btn-primary">
<i class="fas fa-user-plus me-1"></i>立即注册
</a>
</div>
</div>
</div>
</div>
{% endif %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 页面加载完成后自动显示错误弹窗
document.addEventListener('DOMContentLoaded', function() {
{% if error_message %}
const errorModal = new bootstrap.Modal(document.getElementById('errorModal'));
errorModal.show();
{% endif %}
// 密码显示/隐藏切换
const passwordToggle = document.getElementById('passwordToggle');
const passwordInput = document.getElementById('password');
passwordToggle.addEventListener('click', function() {
const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password';
passwordInput.setAttribute('type', type);
passwordToggle.innerHTML = type === 'password' ?
'<i class="fas fa-eye"></i>' :
'<i class="fas fa-eye-slash"></i>';
});
// 密码强度检测
passwordInput.addEventListener('input', function() {
const password = passwordInput.value;
let strength = 0;
if (password.length > 0) strength += 20;
if (password.length >= 8) strength += 20;
if (/[A-Z]/.test(password)) strength += 20;
if (/[0-9]/.test(password)) strength += 20;
if (/[^A-Za-z0-9]/.test(password)) strength += 20;
document.getElementById('passwordStrength').style.width = strength + '%';
// 设置颜色
const meter = document.getElementById('passwordStrength');
if (strength < 40) {
meter.style.backgroundColor = '#e74c3c';
} else if (strength < 80) {
meter.style.backgroundColor = '#f39c12';
} else {
meter.style.backgroundColor = '#2ecc71';
}
});
// 表单提交动画
const loginForm = document.getElementById('loginForm');
loginForm.addEventListener('submit', function() {
const submitBtn = loginForm.querySelector('button[type="submit"]');
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>登录中...';
submitBtn.disabled = true;
});
});
</script>
</body>
</html>
3.2.3 register.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">">
<title>用户注册 - 代码生成工具</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2c3e50;
--accent-color: #e74c3c;
--light-color: #ecf0f1;
--dark-color: #34495e;
}
body {
background: linear-gradient(135deg, #1a2980, #26d0ce);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
padding: 20px 0;
}
/* 修复UI靠左问题 */
.container {
width: 100%;
max-width: 1400px; /* 增加最大宽度 */
margin: 0 auto; /* 确保居中 */
padding: 0 15px;
}
.auth-card {
border-radius: 15px;
overflow: hidden;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
border: none;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.97);
}
.auth-card:hover {
transform: translateY(-7px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}
.card-header {
background: linear-gradient(135deg, var(--primary-color), #1a6fc4);
color: white;
padding: 25px 20px;
font-weight: 600;
border: none;
text-align: center;
}
.card-body {
padding: 30px;
}
.form-control {
border-radius: 10px;
padding: 14px 18px;
border: 1px solid #ddd;
transition: all 0.3s ease;
font-size: 1.05rem;
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.3rem rgba(52, 152, 219, 0.2);
}
.input-group-text {
background: #f8f9fa;
border: 1px solid #ddd;
border-radius: 10px 0 0 10px;
padding: 0 15px;
min-width: 50px;
display: flex;
justify-content: center;
align-items: center;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), #1a6fc4);
border: none;
padding: 15px 30px;
font-weight: 600;
font-size: 1.1rem;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.3);
border-radius: 10px;
letter-spacing: 0.5px;
}
.btn-primary:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(52, 152, 219, 0.4);
background: linear-gradient(135deg, #2980b9, #1a6fc4);
}
.btn-outline-primary {
border: 2px solid var(--primary-color);
color: var(--primary-color);
font-weight: 600;
padding: 12px 30px;
font-size: 1rem;
transition: all 0.3s ease;
border-radius: 10px;
}
.btn-outline-primary:hover {
background: var(--primary-color);
color: white;
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.3);
}
.form-label {
font-weight: 600;
color: var(--secondary-color);
margin-bottom: 10px;
font-size: 1.05rem;
}
.auth-footer {
border-top: 1px solid #eee;
padding-top: 20px;
margin-top: 20px;
}
.password-toggle {
cursor: pointer;
background: #f8f9fa;
border: 1px solid #ddd;
border-left: none;
border-radius: 0 10px 10px 0;
padding: 0 15px;
display: flex;
align-items: center;
min-width: 50px;
justify-content: center;
}
.password-toggle:hover {
background: #e9ecef;
}
.progress {
height: 8px;
border-radius: 4px;
margin-top: 8px;
}
.progress-bar {
transition: width 0.5s ease;
}
.strength-indicator {
display: flex;
justify-content: space-between;
margin-top: 5px;
font-size: 0.9rem;
}
.strength-point {
flex: 1;
height: 4px;
background: #e9ecef;
border-radius: 2px;
margin: 0 2px;
}
.strength-point.active {
background: var(--primary-color);
}
.password-hints {
font-size: 0.9rem;
color: #6c757d;
margin-top: 8px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.password-hint {
display: flex;
align-items: center;
gap: 5px;
}
.hint-icon {
color: #adb5bd;
font-size: 0.8rem;
}
.hint-icon.valid {
color: #2ecc71;
}
.terms-container {
background: #f8f9fa;
border-radius: 10px;
padding: 15px;
margin-top: 10px;
border: 1px solid #eee;
}
.terms-scroll {
max-height: 150px;
overflow-y: auto;
padding-right: 10px;
margin-bottom: 15px;
font-size: 0.9rem;
color: #6c757d;
}
.form-check-input {
width: 1.2em;
height: 1.2em;
margin-top: 0.2em;
}
.form-check-label {
font-size: 0.95rem;
}
.brand-logo {
text-align: center;
margin-bottom: 25px;
}
.brand-logo i {
font-size: 2.5rem;
color: var(--primary-color);
margin-bottom: 10px;
display: inline-block;
}
.brand-logo h3 {
font-weight: 700;
color: var(--secondary-color);
margin-bottom: 5px;
}
.brand-logo p {
color: #6c757d;
margin-bottom: 0;
}
.password-container {
position: relative;
}
.password-toggle-container {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
z-index: 5;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(52, 152, 219, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(52, 152, 219, 0);
}
}
.pulse-animation {
animation: pulse 2s infinite;
}
.submit-btn-container {
position: relative;
overflow: hidden;
border-radius: 10px;
}
.submit-btn-container::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: rgba(255, 255, 255, 0.1);
transform: rotate(30deg);
transition: all 0.6s ease;
}
.submit-btn-container:hover::before {
transform: rotate(30deg) translate(20%, 20%);
}
/* 错误提示样式增强 */
.invalid-feedback {
display: none;
font-size: 0.9rem;
margin-top: 5px;
padding: 8px 12px;
background-color: rgba(231, 76, 60, 0.1);
border-radius: 8px;
border-left: 3px solid #e74c3c;
}
.is-invalid {
border-color: #e74c3c !important;
}
.is-invalid:focus {
box-shadow: 0 0 0 0.25rem rgba(231, 76, 60, 0.25) !important;
}
.form-check-input.is-invalid {
border-color: #e74c3c;
}
</style>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="auth-card">
<div class="card-header py-4">
<h3 class="mb-0"><i class="fas fa-user-plus me-2"></i>创建新账户</h3>
</div>
<div class="card-body p-4">
<div class="brand-logo">
<i class="fas fa-code"></i>
<h3>代码生成工具</h3>
<p>创建您的专属账户</p>
</div>
<form action="{{ url_for('register') }}" method="POST" id="registerForm" novalidate>
{{ form.hidden_tag() }}
<!-- 用户名 -->
<div class="mb-4">
<label for="username" class="form-label">用户名</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-user"></i></span>
<input type="text" class="form-control form-control-lg" id="username" name="username"
placeholder="请输入用户名 (3-20字符)" required
value="{{ request.form.username if request.form.username else '' }}">
</div>
<div class="invalid-feedback" id="usernameFeedback">
用户名长度应在3-20个字符之间
</div>
</div>
<!-- 密码 -->
<div class="mb-4">
<label for="password" class="form-label">密码</label>
<div class="input-group password-container">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
<input type="password" class="form-control form-control-lg" id="password" name="password"
placeholder="请输入密码 (至少8字符)" required autocomplete="new-password">
<span class="password-toggle" id="passwordToggle">
<i class="fas fa-eye"></i>
</span>
</div>
<!-- 密码强度指示器 -->
<div class="strength-indicator mt-3">
<div class="strength-point" id="strength1"></div>
<div class="strength-point" id="strength2"></div>
<div class="strength-point" id="stre极3"></div>
<div class="strength-point" id="strength4"></div>
<div class="strength-point" id="strength5"></div>
</div>
<!-- 密码要求提示 -->
<div class="password-hints mt-2">
<div class="password-hint" id="lengthHint">
<span class="hint-icon"><i class="fas fa-circle"></i></span>
<span>至少8个字符</span>
</div>
<div class="password-hint" id="numberHint">
<span class="hint-icon"><i class="fas fa-circle"></i></span>
<span>包含数字</span>
</div>
<div class="password-hint" id="letterHint">
<span class="hint-icon"><i class="fas fa-circle"></i></span>
<span>包含字母</span>
</div>
</div>
<div class="invalid-feedback" id="passwordFeedback">
密码至少需要8个字符,包含字母和数字
</div>
</div>
<!-- 确认密码 -->
<div class="mb-4">
<label for="confirm_password" class="form-label">确认密码</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
<input type="password" class="form-control form-control-lg" id="confirm_password"
name="confirm_password" placeholder="请再次输入密码" required>
</div>
<div class="invalid-feedback" id="confirmPasswordFeedback">
两次输入的密码不一致
</div>
</div>
<!-- 手机号码 - 非必填 -->
<div class="mb-4">
<label for="phone" class="form-label">
手机号码
<span class="optional-label">(选填)</span>
</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-mobile-alt"></i></span>
<input type="tel" class="form-control form-control-lg" id="phone" name="phone"
placeholder="请输入11位手机号码"
value="{{ request.form.phone if request.form.phone else '' }}">
</div>
<div class="invalid-feedback" id="phoneFeedback">
请输入有效的手机号码
</div>
</div>
<!-- 条款同意 -->
<div class="mb-4">
<div class="terms-container">
<h6 class="fw-bold">服务条款</h6>
<div class="terms-scroll">
<p>1. 用户在使用本服务前需仔细阅读本协议,注册成功后即视为同意本协议全部内容。</p>
<p>2. 用户应妥善保管账号和密码,并对利用该账号和密码所进行的一切活动负全部责任。</p>
<p>3. 用户需遵守国家相关法律法规,不得利用本服务进行任何违法或不正当的活动。</p>
<p>4. 本平台尊重并保护所有用户的个人隐私权,不会泄露用户注册资料及保存在本平台中的非公开内容。</p>
<p>5. 本平台保留随时修改本协议条款的权利,修改后的协议一旦公布即有效代替原来的协议。</p>
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="agree_terms" name="agree_terms" required>
<label class="form-check-label" for="agree_terms">
我已阅读并同意 <a href="#" class="text-decoration-none">服务条款</a> 和 <a href="#" class="text-decoration-none">隐私政策</a>
</label>
</div>
</div>
<div class="invalid-feedback" id="termsFeedback">
您必须同意服务条款才能注册
</div>
</div>
<!-- 注册按钮 - 移除了动画效果 -->
<div class="mb-3">
<button type="submit" class="btn btn-primary btn-lg w-100 py-3">
<i class="fas fa-user-plus me-2"></i>立即注册
</button>
</div>
<!-- 已有账户 -->
<div class="auth-footer text-center">
<p class="text-muted mb-2">已有账户?</p>
<a href="{{ url_for('login') }}" class="btn btn-outline-primary">
<i class="fas fa-sign-in-alt me-2"></i>立即登录
</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('registerForm');
const usernameInput = document.getElementById('username');
const passwordInput = document.getElementById('password');
const confirmPasswordInput = document.getElementById('confirm_password');
const phoneInput = document.getElementById('phone');
const termsCheckbox = document.getElementById('agree_terms');
const passwordToggle = document.getElementById('passwordToggle');
// 显示所有错误信息
function showAllErrors() {
// 用户名验证
if (usernameInput.value.length < 3 || usernameInput.value.length > 20) {
usernameInput.classList.add('is-invalid');
document.getElementById('usernameFeedback').style.display = 'block';
}
// 密码验证
if (passwordInput.value.length < 8) {
passwordInput.classList.add('is-invalid');
document.getElementById('passwordFeedback').style.display = 'block';
}
// 确认密码验证
if (passwordInput.value !== confirmPasswordInput.value) {
confirmPasswordInput.classList.add('is-invalid');
document.getElementById('confirmPasswordFeedback').style.display = '极block';
}
// 手机号验证 - 仅当有输入时验证
if (phoneInput.value.trim() !== '') {
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(phoneInput.value)) {
phoneInput.classList.add('is-invalid');
document.getElementById('phoneFeedback').style.display = 'block';
}
}
// 条款同意验证
if (!termsCheckbox.checked) {
termsCheckbox.classList.add('is-invalid');
document.getElementById('termsFeedback').style.display = 'block';
}
}
// 密码可见性切换
passwordToggle.addEventListener('click', function() {
const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password';
passwordInput.setAttribute('type', type);
passwordToggle.innerHTML = type === 'password' ?
'<i class="fas fa-eye"></i>' :
'<i class="fas fa-eye-slash"></i>';
});
// 密码强度检测和提示
passwordInput.addEventListener('input', function() {
const password = this.value;
// 密码要求检查
const hasLength = password.length >= 8;
const hasNumber = /\d/.test(password);
const hasLetter = /[a-zA-Z]/.test(password);
// 更新提示图标
document.getElementById('lengthHint').querySelector('.hint-icon').className =
`hint-icon fas ${hasLength ? 'fa-check valid' : 'fa-circle'}`;
document.getElementById('numberHint').querySelector('.hint-icon').className =
`hint-icon fas ${hasNumber ? 'fa-check valid' : 'fa-circle'}`;
document.getElementById('letterHint').querySelector('.hint-icon').className =
`hint-icon fas ${hasLetter ? 'fa-check valid' : 'fa-circle'}`;
// 密码强度计算
let strength = 0;
if (hasLength) strength += 2;
if (hasNumber) strength += 1;
if (hasLetter) strength += 1;
if (password.length > 12) strength += 1;
if (/[^A-Za-z0-9]/.test(password)) strength += 1;
// 重置所有强度点
for (let i = 1; i <= 5; i++) {
document.getElementById(`strength${i}`).classList.remove('active');
}
// 激活强度点
for (let i = 1; i <= strength; i++) {
document.getElementById(`strength${i}`).classList.add('active');
}
// 设置强度点颜色
if (strength <= 2) {
for (let i = 1; i <= 5; i++) {
const el = document.getElementById(`strength${i}`);
if (el.classList.contains('active')) {
el.style.background = '#e74c3c';
}
}
} else if (strength <= 4) {
for (let i = 1; i <= 5; i++) {
const el = document.getElementById(`strength${i}`);
if (el.classList.contains('active')) {
el.style.background = '#f39c12';
}
}
} else {
for (let i = 1; i <= 5; i++) {
const el = document.getElementById(`strength${i}`);
if (el.classList.contains('active')) {
el.style.background = '#2ecc71';
}
}
}
});
// 密码确认验证
confirmPasswordInput.addEventListener('input', function() {
if (passwordInput.value !== this.value) {
this.classList.add('is-invalid');
document.getElementById('confirmPasswordFeedback').style.display = 'block';
} else {
this.classList.remove('is-invalid');
document.getElementById('confirmPasswordFeedback').style.display = 'none';
}
});
// 手机号码验证 - 仅当有输入时验证
phoneInput.addEventListener('input', function() {
if (this.value.trim() === '') {
this.classList.remove('is-invalid');
document.getElementById('phoneFeedback').style.display = 'none';
return;
}
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(this.value)) {
this.classList.add('is-invalid');
document.getElementById('phoneFeedback').style.display = 'block';
} else {
this.classList.remove('is-invalid');
document.getElementById('phoneFeedback').style.display = 'none';
}
});
// 输入时移除错误状态
const inputs = form.querySelectorAll('input');
inputs.forEach(input => {
input.addEventListener('input', function() {
this.classList.remove('is-invalid');
const feedbackId = this.id + 'Feedback';
if (document.getElementById(feedbackId)) {
document.getElementById(feedbackId).style.display = 'none';
}
});
});
// 条款复选框点击事件
termsCheckbox.addEventListener('change', function() {
if (this.checked) {
this.classList.remove('is-invalid');
document.getElementById('termsFeedback').style.display = 'none';
}
});
// 表单提交验证
form.addEventListener('submit', function(event) {
let isValid = true;
// 用户名验证
if (usernameInput.value.length < 3 || usernameInput.value.length > 20) {
usernameInput.classList.add('is-invalid');
document.getElementById('usernameFeedback').style.display = 'block';
isValid = false;
}
// 密码验证
if (passwordInput.value.length < 8) {
passwordInput.classList.add('is-invalid');
document.getElementById('passwordFeedback').style.display = 'block';
isValid = false;
}
// 确认密码验证
if (passwordInput.value !== confirmPasswordInput.value) {
confirmPasswordInput.classList.add('is-invalid');
document.getElementById('confirmPasswordFeedback').style.display = 'block';
isValid = false;
}
// 手机号验证 - 仅当有输入时验证
if (phoneInput.value.trim() !== '') {
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(phoneInput.value)) {
phoneInput.classList.add('is-invalid');
document.getElementById('phoneFeedback').style.display = 'block';
isValid = false;
}
}
// 条款同意验证
if (!termsCheckbox.checked) {
termsCheckbox.classList.add('is-invalid');
document.getElementById('termsFeedback').style.display = 'block';
isValid = false;
}
if (!isValid) {
event.preventDefault();
event.stopPropagation();
// 显示所有错误信息
showAllErrors();
// 滚动到第一个错误位置
const firstError = form.querySelector('.is-invalid');
if (firstError) {
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
} else {
// 显示加载状态
const submitBtn = form.querySelector('button[type="submit"]');
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>注册中...';
submitBtn.disabled = true;
// 实际提交表单
console.log('表单提交中...');
}
});
});
</script>
</body>
</html>
3.2.4 dashboard.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> Tool Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2c3e50;
--accent-color: #e74c3c;
--light-color: #ecf0f1;
--dark-color: #34495e;
--success-color: #2ecc71;
--warning-color: #f39c12;
--info-color: #1abc9c;
}
body {
background: linear-gradient(135deg, #1a2980, #26d0ce);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
min-height: 100vh;
padding: 20px 0;
color: #333;
margin: 0;
overflow-x: hidden;
}
/* 修复UI靠左问题 */
.container {
width: 100%;
max-width: 1400px; /* 增加最大宽度 */
margin: 0 auto; /* 确保居中 */
padding: 0 15px;
}
.dashboard-header {
background: linear-gradient(135deg, var(--secondary-color), var(--dark-color));
color: white;
padding: 25px 0;
margin-bottom: 30px;
box-shadow: 0 6px 15px rgba(0,0,0,0.2);
border-radius: 0 0 20px 20px;
}
.logo-container {
text-align: center;
margin-bottom: 20px;
}
.logo {
max-width: 120px;
height: auto;
filter: drop-shadow(0 0 5px rgba(255,255,255,0.5));
}
.section-card {
border-radius: 15px;
box-shadow: 0 10px 25px rgba(0,0,0,0.15);
margin-bottom: 30px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
border: none;
background: rgba(255, 255, 255, 0.97);
overflow: hidden;
will-change: transform; /* 启用硬件加速 */
}
.section-card:hover {
transform: translateY(-8px);
box-shadow: 0 15px 35px rgba(0,0,0,0.25);
}
.card-header {
background: linear-gradient(135deg, var(--primary-color), #1a6fc4);
color: white;
border-radius: 0 !important;
padding: 18px 25px;
font-weight: 600;
border: none;
font-size: 1.2rem;
}
.card-body {
padding: 30px;
}
.form-group {
margin-bottom: 25px;
}
.form-label {
font-weight: 600;
color: var(--secondary-color);
margin-bottom: 10px;
font-size: 1.05rem;
}
.form-control {
border-radius: 10px;
padding: 14px 18px;
border: 1px solid #ddd;
transition: all 0.3s ease;
font-size: 1.05rem;
background: rgba(255, 255, 255, 0.95);
}
.form-control:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 0.3rem rgba(52, 152, 219, 0.2);
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), #1a6fc4);
border: none;
padding: 15px 30px;
font-weight: 600;
font-size: 1.1rem;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(52, 152, 219, 0.3);
border-radius: 10px;
letter-spacing: 0.5px;
}
.btn-primary:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(52, 152, 219, 0.4);
background: linear-gradient(135deg, #2980b9, #1a6fc4);
}
.btn-action {
width: 100%;
margin: 10px 0;
padding: 14px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.btn-action:hover {
transform: translateY(-3px);
box-shadow: 0 7px 14px rgba(0,0,0,0.15);
}
.btn-action i {
margin-right: 10px;
font-size: 1.2rem;
}
.btn-success {
background: linear-gradient(135deg, var(--success-color), #27ae60);
}
.btn-secondary {
background: linear-gradient(135deg, #7f8c8d, #95a5a6);
}
.btn-info {
background: linear-gradient(135deg, var(--info-color), #16a085);
}
.btn-warning {
background: linear-gradient(135deg, var(--warning-color), #e67e22);
}
.btn-dark {
background: linear-gradient(135deg, var(--dark-color), #2c3e50);
}
.status-indicator {
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
.status-active {
background-color: var(--success-color);
box-shadow: 0 0 8px var(--success-color);
}
.status-inactive {
background-color: var(--accent-color);
}
.file-path {
background-color: var(--light-color);
padding: 10px 15px;
border-radius: 8px;
font-size: 14px;
margin-top: 8px;
word-break: break-all;
border-left: 3px solid var(--primary-color);
}
.progress-container {
height: 8px;
background-color: #e9ecef;
border-radius: 4px;
margin-top: 10px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: linear-gradient(to right, var(--primary-color), #1a6fc4);
width: 0%;
transition: width 0.5s ease;
}
.section-title {
color: var(--secondary-color);
margin-bottom: 25px;
padding-bottom: 15px;
border-bottom: 2px solid var(--primary-color);
font-weight: 600;
font-size: 1.3rem;
display: flex;
align-items: center;
}
.tooltip-icon {
color: var(--primary-color);
margin-left: 8px;
cursor: help;
font-size: 1.1rem;
}
.action-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 20px;
}
.config-badge {
background: rgba(52, 152, 219, 0.15);
color: var(--primary-color);
border-radius: 20px;
padding: 8px 15px;
font-size: 0.95rem;
display: inline-flex;
align-items: center;
margin: 0 8px 8px 0;
border: 1px solid rgba(52, 152, 219, 0.3);
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.config-badge i {
margin-right: 8px;
font-size: 0.9rem;
}
.dynamic-badge {
background: rgba(46, 204, 113, 0.15);
color: var(--success-color);
border: 1px solid rgba(46, 204, 113, 0.3);
}
.result-panel {
background: rgba(236, 240, 241, 0.95);
border-left: 4px solid var(--primary-color);
padding: 20px;
margin-top: 25px;
display: none;
animation: fadeIn 0.5s;
border-radius: 10px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.result-title {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.result-icon {
font-size: 1.8rem;
margin-right: 15px;
}
.success-icon {
color: var(--success-color);
}
.error-icon {
color: var(--accent-color);
}
.project-section {
margin-top: 30px;
padding-top: 25px;
border-top: 1px solid rgba(0,0,0,0.1);
}
.input-group .btn {
border-radius: 0 10px 10px 0;
padding: 14px 20px;
font-weight: 600;
}
.input-group-text {
background: #f8f9fa;
border: 1px solid #ddd;
border-radius: 10px 0 0 10px;
padding: 0 15px;
min-width: 50px;
display: flex;
justify-content: center;
align-items: center;
}
footer {
background: linear-gradient(135deg, var(--dark-color), var(--secondary-color));
color: white;
text-align: center;
padding: 25px 0;
margin-top: 40px;
border-radius: 20px 20px 0 0;
box-shadow: 0 -5px 15px rgba(0,0,0,0.1);
}
.form-switch .form-check-input {
width: 3em;
height: 1.5em;
}
.form-switch .form-check-label {
font-weight: 500;
color: var(--secondary-color);
font-size: 1.05rem;
}
.action-btn-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 15px;
margin-top: 25px;
}
</style>
</head>
<body>
<div class="dashboard-header">
<div class="container">
<h1 class="text-center"> Code Generate Tool </h1>
<p class="text-center">Automated Code Generation</p>
</div>
</div>
<div class="container">
<!-- User Info Section -->
<div class="row mb-4">
<div class="col-md-6">
<div class="section-card perf-optimized">
<div class="card-header">
<i class="fas fa-user-circle me-2"></i>Basic Information
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="form-label">Author</label>
<input type="text" class="form-control" id="authorInput" value="{{ username }}">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label">Version</label>
<input type="text" class="form-control" id="versionInput" value="v2.1.5">
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">Change Log</label>
<textarea class="form-control" id="changeLog" rows="1" placeholder="Fill In Change Log"></textarea>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label class="form-label">Car Platform</label>
<select class="form-select" id="carPlatformSelect">
{% for platform in car_platform_list %}
<option value="{{ platform }}">{{ platform }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="form-group">
<div class="form-check form-switch">
<!-- 移除 checked 属性 -->
<input class="form-check-input" type="checkbox" id="overwriteToggle">
<label class="form-check-label" for="overwriteToggle">Overwrite Original Code Files</label>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="section-card perf-optimized">
<div class="card-header">
<i class="fas fa-cog me-2"></i>Configuration
</div>
<div class="card-body">
<h5 class="section-title"><i class="fas fa-microchip me-2"></i> Configuration</h5>
<div id="configBadges" class="mb-4">
<span class="config-badge dynamic-badge">
<i class="fas fa-car"></i> EVA3
</span>
</div>
<div class="project-section">
<div class="form-group">
<label class="form-label"> Project Folder</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-folder"></i></span>
<input type="text" class="form-control" id="codeProjectFolder" placeholder="Select or enter code project folder path">
<button class="btn btn-outline-primary" type="button" id="importProjectFolderBtn">
<i class="fas fa-folder-open folder-icon"></i> Import
</button>
</div>
</div>
<div class="d-grid mt-3">
<button class="btn btn-primary perf-optimized" id="CodeProjectParse">
<i class="fas fa-bolt me-2"></i> Code Parse
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Code Generate Section -->
<div class="card section-card perf-optimized">
<div class="card-header">
<i class="fas fa-car me-2"></i>Code Generate
</div>
<div class="card-body">
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label class="form-label">Requirement Path</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-file-excel"></i></span>
<input type="text" class="form-control" id="Requirements_Path" placeholder="Select or Enter Requirement Path">
<button class="btn btn-outline-primary" type="button">Import</button>
</div>
</div>
</div>
</div>
<div class="d-grid mt-3">
<button class="btn btn-primary btn-action perf-optimized" id="generateCodeBtn">
<i class="fas fa-file-code"></i> Generate Code
</button>
</div>
<div class="result-panel perf-optimized" id="edrResult">
<div class="result-title">
<i class="fas fa-check-circle result-icon success-icon"></i>
<h5>Operation Result</h5>
</div>
<p id="edrResultMessage"></p>
</div>
</div>
</div>
</div>
<footer class="bg-dark text-white text-center py-4 mt-5">
<div class="container">
<p>© <span id="currentYear"></span> CodeGenerate Tool Dashboard | Version 1.0.0 | Author: XXX</p>
<p>Automated Code Generation Platform</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// 性能优化:减少DOM操作
const perfOptimizedElements = [
'.section-card',
'.btn-action',
'.progress-bar',
'.result-panel'
];
// 添加硬件加速类
document.addEventListener('DOMContentLoaded', function() {
// 确保所有复选框默认未选中
document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
checkbox.checked = false;
});
perfOptimizedElements.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
el.classList.add('perf-optimized');
});
});
// 优化事件委托
setupEventDelegation();
// 初始化页面
initializePage();
});
// 事件委托优化(减少事件监听器数量)
function setupEventDelegation() {
document.body.addEventListener('click', function(e) {
// 处理导入按钮
if (e.target.closest('.btn-outline-primary')) {
handleImportButton(e.target);
e.stopPropagation();
}
// 处理解析按钮
if (e.target.id === 'CodeProjectParse') {
handleProjectParse();
}
// 处理其他操作按钮
const actionButtons = [
'generateCodeBtn'
];
if (actionButtons.includes(e.target.id)) {
handleActionButton(e.target.id);
}
});
// 下拉框变更事件
const dropdowns = [
'carPlatformSelect'
];
dropdowns.forEach(id => {
document.getElementById(id).addEventListener('change', updateConfigBadges);
});
}
// 页面初始化
function initializePage() {
// 获取初始车平台值
const carPlatform = document.getElementById('carPlatformSelect').value;
// 添加车平台变更事件监听器
document.getElementById('carPlatformSelect').addEventListener('change', function() {
const carPlatform = this.value;
});
}
// 更新配置徽章函数
function updateConfigBadges() {
const badgesContainer = document.getElementById('configBadges');
const carPlatform = document.getElementById('carPlatformSelect').value;
badgesContainer.innerHTML = `
<span class="config-badge dynamic-badge">
<i class="fas fa-car"></i> ${carPlatform}
</span>
`;
}
// 处理导入按钮
function handleImportButton(button) {
const inputGroup = button.closest('.input-group');
if (!inputGroup) return;
const input = inputGroup.querySelector('.form-control');
if (!input) return;
// 获取当前路径或默认路径
let currentPath = input.value.trim();
if (!currentPath) {
// 根据输入框ID设置默认路径
if (input.id === 'ProjectFolder') {
currentPath = 'D:/projects';
}
// 其他输入框的默认路径...
}
// 显示单次确认对话框
const newPath = prompt('请输入文件路径:', currentPath);
if (newPath !== null) {
// 统一使用左斜杠
const normalizedPath = newPath.replace(/\\/g, '/');
input.value = normalizedPath;
// 自动聚焦到输入框
setTimeout(() => input.focus(), 100);
}
}
// 优化后的项目解析函数
function handleProjectParse() {
const projectFolder = document.getElementById('codeProjectFolder').value;
if (!projectFolder) {
showAlert('⚠️ Project Folder路径为空!请先导入项目仓库', 'warning');
document.getElementById('codeProjectFolder').focus();
return;
}
const button = document.getElementById('CodeProjectParse');
const originalText = button.innerHTML;
button.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Parsing...`;
button.disabled = true;
// 创建进度条容器
const progressContainer = document.createElement('div');
progressContainer.className = 'progress-container mt-3';
progressContainer.innerHTML = `
<div class="progress" style="height: 10px;">
<div id="parseProgress" class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 0%"></div>
</div>
<div id="parseStatus" class="text-center small mt-2">初始化解析...</div>
`;
// 插入进度条
const codeSection = document.querySelector('.project-section');
if (!document.querySelector('.progress-container')) {
codeSection.appendChild(progressContainer);
}
// 使用轻量级进度更新
const progress = {
current: 0,
max: 100,
interval: setInterval(() => {
progress.current += 5;
if (progress.current > 100) progress.current = 100;
updateProgress(progress.current);
if (progress.current === 100) {
clearInterval(progress.interval);
}
}, 200)
};
// 收集数据
const data = {
code_project_folder: projectFolder,
car_platform: document.getElementById('carPlatformSelect').value,
};
// 发送解析请求
fetch('/parse_code_project', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
clearInterval(progress.interval);
updateProgress(100);
if (result.status === 'success') {
updatePathFields(result);
showAlert('✅ 项目解析完成!所有路径已自动填充', 'success');
} else {
showAlert(`❌ 解析失败: ${result.message}`, 'error');
}
button.innerHTML = originalText;
button.disabled = false;
})
.catch(error => {
clearInterval(progress.interval);
updateProgress(100);
showAlert(`⚠️ 网络错误: ${error.message}`, 'error');
button.innerHTML = originalText;
button.disabled = false;
});
}
// 轻量级进度更新
function updateProgress(percent) {
const progressBar = document.getElementById('parseProgress');
const statusText = document.getElementById('parseStatus');
if (progressBar) {
progressBar.style.width = `${percent}%`;
}
if (statusText) {
statusText.textContent = `解析中... ${percent}%`;
}
}
// 更新所有路径输入框
function updatePathFields(result) {
const pathMap = {
'Requirements_Path': 'Requirements_Path'
};
for (const [apiKey, fieldId] of Object.entries(pathMap)) {
if (result[apiKey]) {
const inputElement = document.getElementById(fieldId);
if (inputElement) {
// 统一使用左斜杠
const normalizedPath = result[apiKey].replace(/\\/g, '/');
inputElement.value = normalizedPath;
} else {
console.warn(`Element with id '${fieldId}' not found.`);
}
}
}
}
// 轻量级提示
function showAlert(message, type) {
// 移除现有提示
const existingAlert = document.getElementById('globalAlert');
if (existingAlert) existingAlert.remove();
const alert = document.createElement('div');
alert.id = 'globalAlert';
alert.className = `alert alert-${type === 'error' ? 'danger' : type} fixed-top mx-auto mt-3`;
alert.style.maxWidth = '600px';
alert.style.zIndex = '1060';
alert.innerHTML = `
<div class="d-flex align-items-center">
<i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-triangle'} me-2"></i>
<div>${message}</div>
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert"></button>
</div>
`;
document.body.appendChild(alert);
// 5秒后自动消失
setTimeout(() => {
if (alert.parentNode) {
alert.parentNode.removeChild(alert);
}
}, 5000);
}
// 处理按钮操作
function handleActionButton(buttonId) {
// 按钮ID到后端端点的映射
const endpointMap = {
'generateExtractionBtn': 'generateExtraction',
'arxmlCheckBtn': 'arxmlCheck',
'generateDomainBBtn': 'generateDomainB',
'generateVcodmBtn': 'generateVcodm',
'generateCaplBtn': 'generateCapl',
'calculateSocketBtn': 'calculateSocket',
'generateAdapterBtn': 'generateAdapter',
'generateOneTruckBtn': 'generateOneTruck',
'generateServiceManagerBtn': 'generateServiceManager',
'generateCodeBtn': 'generateEdr'
};
const endpoint = endpointMap[buttonId];
if (!endpoint) {
console.error(`Unknown button ID: ${buttonId}`);
return;
}
const button = document.getElementById(buttonId);
const originalText = button.innerHTML;
const resultPanelId = buttonId.replace('Btn', 'Result');
const resultPanel = document.getElementById(resultPanelId);
// 隐藏结果面板
if (resultPanel) resultPanel.style.display = 'none';
// 禁用按钮并显示加载动画
button.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Processing...`;
button.disabled = true;
// 收集通用数据
const data = {
author: document.getElementById('authorInput').value,
version: document.getElementById('versionInput').value,
changelog: document.getElementById('changeLog').value,
car_platform: document.getElementById('carPlatformSelect').value,
over_write_flag: document.getElementById('overwriteToggle').checked,
code_project_folder: document.getElementById('codeProjectFolder').value
};
// 添加特定于操作的数据
if (buttonId === 'generateCodeBtn') {
Object.assign(data, {
Requirements_Path: document.getElementById('Requirements_Path').value
});
}
// 发送到后端
fetch(`/${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
// 显示结果
if (resultPanel) {
document.getElementById(`${resultPanelId}Message`).textContent = result.message;
if (result.output_path) {
const outputPathElement = document.getElementById(`${resultPanelId}OutputPath`);
if (outputPathElement) outputPathElement.textContent = result.output_path;
}
resultPanel.style.display = 'block';
}
// 重置按钮
button.innerHTML = originalText;
button.disabled = false;
})
.catch(error => {
console.error('Error:', error);
if (resultPanel) {
document.getElementById(`${resultPanelId}Message`).textContent = 'Error: ' + error.message;
resultPanel.style.display = 'block';
}
// 重置按钮
button.innerHTML = originalText;
button.disabled = false;
});
}
// 显示当前年份
document.getElementById('currentYear').textContent = new Date().getFullYear();
</script>
</body>
</html>
4 总结
本文介绍了Python轻量级Web框架Flask及其在实际项目开发中的应用。主要内容包括:
- Flask核心特性:轻量模块化设计、内置开发服务器、RESTful支持和Jinja2模板引擎
- AI在Flask开发中的应用:代码生成、错误调试、性能优化和安全加固
- 实际案例演示:通过用户认证系统展示Flask项目结构,包括app.py主程序、用户注册/登录表单验证和Dashboard界面模板
- 技术对比:分析了Flask相较于Tkinter在跨平台性、界面丰富度和可扩展性方面的优势
案例展示了完整的Flask项目结构,包含路由定义、表单处理、会话管理和前端模板集成,体现了Flask快速开发Web应用的能力。