简介:这是一套基于 Python、Flask、LayUI 与 MySQL 搭建的网站模板,面向具备一定 Python 基础、希望快速构建后台管理或中小型 Web 项目的开发者,尤其适合课程设计、毕业设计与练手项目。资源包共 233 个文件,约 1.29MB,其中 58 个 py 文件承载 Flask 路由与业务逻辑,32 个 html 与 36 个 js 负责页面结构与交互,9 个 css 及 gif、png、jpg 等图片资源用于界面美化,另含字体、图标与说明文档,整体结构完整、层次清晰。模板整合了 LayUI 前端框架与 MySQL 数据存储,涵盖登录、表格、表单等常见模块,可直接作为项目脚手架二次开发。目前已有 1584 人学习下载,适合想了解 Flask 项目组织方式、前后端配合与数据库接入的读者参考借鉴。
1. 从一份 Flask 全栈模板说起:谁需要它,能省掉哪些重复劳动
如果你写过两个以上的后台管理系统,大概率经历过这样的循环:建项目、配数据库、写登录、搭权限、调表格分页、改弹窗样式,最后发现真正跟业务相关的代码不到三成。这份python + flask + LayUI + mysql 网站模板.zip就是冲着这个循环来的——它把 Flask 后端、LayUI 前端、MySQL 数据层预先拼装成一个可运行的骨架,登录、菜单、用户管理、角色权限、日志这些通用模块已经落地,拿到手改改配置就能跑起来。
它适合三类人:一是刚学完 Flask 语法、想找一个完整项目练手的新手,能直接看到路由、模型、模板、静态资源怎么串起来;二是接私活或做内部工具的后端开发者,需要一套能快速改出后台的底座;三是教 Web 开发课程的老师,拿它当课堂案例比从零敲更省课时。不适合的是追求前后端分离、想用 Vue/React 重写前端的人——这套模板是服务端渲染路线,LayUI 直接嵌在 Jinja2 模板里,硬拆反而费劲。
我拿到压缩包后第一件事不是急着跑,而是先看目录结构和依赖清单,确认它用的是 Flask 哪个大版本、SQLAlchemy 还是原生驱动、LayUI 是本地引入还是 CDN。这几项决定了后面能不能顺利复现,也决定了你改代码时会不会踩到版本坑。下面按「先看清结构 → 再跑起来 → 再改业务 → 再避坑」的顺序拆一遍。
2. 拆开压缩包先看什么:目录结构、依赖与数据库初始化
2.1 目录结构与各文件职责
解压后典型结构大致如下(不同打包者命名略有差异,但核心目录跑不掉):
website-template/ ├── app.py # 应用入口,创建 Flask 实例、注册蓝图 ├── config.py # 数据库连接、密钥、调试开关等配置 ├── requirements.txt # Python 依赖清单 ├── models/ # SQLAlchemy 模型定义 │ ├── __init__.py │ ├── user.py # 用户表、角色表 │ └── log.py # 操作日志表 ├── views/ # 蓝图路由 │ ├── auth.py # 登录、登出、注册 │ ├── admin.py # 后台首页、用户管理 │ └── api.py # 表格数据接口 ├── templates/ # Jinja2 模板 │ ├── base.html # 公共布局,引入 LayUI │ ├── login.html │ └── admin/ │ ├── index.html │ └── user_list.html ├── static/ │ ├── layui/ # LayUI 框架文件 │ ├── css/ │ └── js/ └── sql/ └── init.sql # 建库建表语句与初始数据先确认requirements.txt里 Flask 和 SQLAlchemy 的版本。常见组合是Flask==2.x配Flask-SQLAlchemy==3.x,如果打包者用的是 Flask 1.x,那@app.before_first_request这类写法在 2.x 里已经移除,直接跑会报AttributeError。我一般会先把版本号抄下来,再决定是降级环境还是改代码。
2.2 依赖安装与虚拟环境
不要往全局 Python 里装,用 venv 隔离:
# 创建虚拟环境,python3 -m venv 是标准做法 python3 -m venv venv # 激活:Linux/macOS source venv/bin/activate # 激活:Windows venv\Scripts\activate # 安装依赖,-i 指定国内镜像加速 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple逻辑说明:venv 把依赖锁在项目目录内,避免和系统里其他 Flask 项目打架。-i参数只是换源,不影响包内容。如果requirements.txt里某个包版本写得太死导致装不上,先注释掉那一行,装完其余再单独pip install 包名==可用版本。
参数提醒:Flask-SQLAlchemy在 3.x 里要求SQLALCHEMY_DATABASE_URI必须带驱动名,MySQL 场景下是mysql+pymysql://或mysql+mysqldb://。如果配置里只写了mysql://,启动时会报No module named 'MySQLdb',这是新手最常翻的车。
2.3 数据库初始化:建库、导表、改连接串
先建库,再导入sql/init.sql:
# 登录 MySQL,创建数据库,字符集用 utf8mb4 支持中文和 emoji mysql -u root -p -e "CREATE DATABASE website_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" # 导入建表语句和初始数据 mysql -u root -p website_db < sql/init.sql然后改config.py里的连接串:
# config.py import os class Config: # 格式:mysql+pymysql://用户名:密码@主机:端口/库名?charset=utf8mb4 SQLALCHEMY_DATABASE_URI = os.environ.get( 'DATABASE_URL', 'mysql+pymysql://root:你的密码@127.0.0.1:3306/website_db?charset=utf8mb4' ) SQLALCHEMY_TRACK_MODIFICATIONS = False # 关掉信号追踪,省内存 SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-key-change-me') # session 加密用逻辑说明:SQLALCHEMY_TRACK_MODIFICATIONS设为 False 是官方推荐,开着会额外消耗内存且没实际用途。SECRET_KEY用于签名 session cookie,模板里默认值必须换掉,否则任何人都能伪造登录态。用os.environ.get包一层是为了部署时能用环境变量覆盖,不用改代码。
参数说明:charset=utf8mb4不能省,LayUI 表格里如果出现中文乱码或 emoji 报错,九成是这里写成了utf8。端口默认 3306,如果本机 MySQL 改过端口,同步改连接串。
2.4 启动与首次登录
# 开发模式启动,debug=True 时改代码自动重载 python app.py浏览器打开http://127.0.0.1:5000,用init.sql里预置的账号登录(常见是 admin/admin123 之类,具体看 sql 文件注释)。如果页面能出但样式全丢,检查static/layui/目录是否完整,以及base.html里引入路径是/static/layui/css/layui.css还是 CDN 地址——离线环境下 CDN 拉不到就会裸奔。
3. 把模板改成自己的业务:路由、模型与 LayUI 表格对接
3.1 新增一张业务表的完整链路
假设要加一个「项目管理」模块,从模型到页面走一遍。先在models/下建project.py:
# models/project.py from models import db # 假设 models/__init__.py 里初始化了 db = SQLAlchemy() class Project(db.Model): __tablename__ = 'project' id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.String(100), nullable=False, comment='项目名称') owner = db.Column(db.String(50), nullable=False, comment='负责人') status = db.Column(db.SmallInteger, default=0, comment='0进行中 1已完成 2已暂停') created_at = db.Column(db.DateTime, server_default=db.func.now()) def to_dict(self): # 转成字典,方便 jsonify 返回给 LayUI 表格 return { 'id': self.id, 'name': self.name, 'owner': self.owner, 'status': self.status, 'created_at': self.created_at.strftime('%Y-%m-%d %H:%M:%S') if self.created_at else '' }逻辑说明:to_dict是给前端表格用的,LayUI 的table组件要求返回 JSON 数组,字段名和cols配置对应。server_default=db.func.now()让数据库自己填时间,比在 Python 里datetime.now()更稳,不受应用服务器时区影响。
参数说明:status用 SmallInteger 而不是字符串,前端用templet做映射显示,查询和索引效率更高。nullable=False的字段在插入时必须给值,否则报 IntegrityError。
3.2 蓝图路由与分页接口
在views/下建project.py:
# views/project.py from flask import Blueprint, render_template, request, jsonify from models import db from models.project import Project project_bp = Blueprint('project', __name__, url_prefix='/project') @project_bp.route('/list') def project_list(): # 渲染页面,表格数据由下面的接口异步拉 return render_template('admin/project_list.html') @project_bp.route('/api/list') def project_api_list(): page = request.args.get('page', 1, type=int) # 当前页码 limit = request.args.get('limit', 10, type=int) # 每页条数 keyword = request.args.get('keyword', '', type=str) # 搜索关键词 query = Project.query if keyword: query = query.filter(Project.name.like(f'%{keyword}%')) pagination = query.order_by(Project.id.desc()).paginate( page=page, per_page=limit, error_out=False ) return jsonify({ 'code': 0, 'msg': '', 'count': pagination.total, # LayUI 要求的总条数 'data': [item.to_dict() for item in pagination.items] })逻辑说明:LayUI 的 table 组件默认按{code, msg, count, data}解析响应,code为 0 表示成功,count是总记录数用于分页控件,data是当前页数组。paginate的error_out=False保证页码越界时返回空列表而不是 404。
参数说明:page和limit从 query string 取,type=int会自动转换,传非数字时用默认值。order_by(Project.id.desc())让新数据在前,避免翻页时顺序错乱。搜索用like做模糊匹配,数据量大时要加索引,否则全表扫描。
3.3 LayUI 表格配置与状态映射
templates/admin/project_list.html核心部分:
<!-- 引入 LayUI 后,用 table.render 渲染 --> <script> layui.use(['table', 'form'], function(){ var table = layui.table; table.render({ elem: '#projectTable', url: '/project/api/list', // 对应上面的接口 method: 'get', page: true, // 开启分页 limit: 10, cols: [[ {field: 'id', title: 'ID', width: 80, sort: true}, {field: 'name', title: '项目名称', minWidth: 150}, {field: 'owner', title: '负责人', width: 120}, {field: 'status', title: '状态', width: 100, templet: function(d){ // 状态码转文字,避免前端显示 0/1/2 var map = {0: '进行中', 1: '已完成', 2: '已暂停'}; return map[d.status] || '未知'; }}, {field: 'created_at', title: '创建时间', width: 180} ]] }); }); </script>逻辑说明:templet是 LayUI 提供的自定义渲染钩子,d是当前行数据对象。用映射表把数字状态转成中文,比在后端拼字符串更灵活,改文案不用动 Python。
参数说明:minWidth保证列在窄屏下不被压没,sort: true让该列可点击排序(需要后端支持排序参数,模板里默认没做,要自己加order_by)。limit和接口里的默认limit保持一致,否则首屏条数对不上。
3.4 权限拦截:别让接口裸奔
模板自带的登录校验通常只拦页面路由,API 接口容易被忽略。加一个装饰器:
# utils/auth.py from functools import wraps from flask import session, jsonify, redirect, url_for def login_required(func): @wraps(func) def wrapper(*args, **kwargs): if not session.get('user_id'): # 判断是不是 AJAX 请求,是就返回 JSON,否则跳登录页 if request.is_json or request.path.startswith('/api'): return jsonify({'code': 401, 'msg': '未登录'}), 401 return redirect(url_for('auth.login')) return func(*args, **kwargs) return wrapper逻辑说明:@wraps保留原函数元信息,避免 Flask 注册路由时端点名冲突。区分 AJAX 和页面请求是为了让前端表格收到 401 后能弹提示,而不是被重定向到登录页 HTML 导致 JSON 解析失败。
参数说明:session.get('user_id')的 key 要和登录时写入的一致,模板里常见的是user_id或username,改之前先搜一遍。返回 401 状态码让前端table组件的error回调能触发。
4. 避坑与排查:这套模板最容易翻车的五个地方
4.1 启动报 No module named 'MySQLdb'
现象:pip install -r requirements.txt成功后python app.py仍报ModuleNotFoundError: No module named 'MySQLdb'。
原因:连接串写的是mysql://,SQLAlchemy 默认去找 MySQLdb 驱动,而 requirements 里装的是 PyMySQL。
解决:把连接串改成mysql+pymysql://,或在入口文件加import pymysql; pymysql.install_as_MySQLdb()。前者更干净,推荐改连接串。
4.2 LayUI 表格数据不显示,控制台报 code 解析失败
现象:页面表格一直转圈,Network 里接口返回了数据,但表格空白。
原因:接口返回的 JSON 字段名不是 LayUI 要求的code/msg/count/data,比如用了total而不是count,或者code返回了 200 而不是 0。
解决:对照 LayUI 文档改jsonify的 key。code必须是 0 才走成功分支,count必须是数字。如果后端框架习惯返回{success: true},在接口层包一层转换,别去改前端解析逻辑。
4.3 中文乱码或 emoji 插入报错
现象:列表里中文显示成问号,或插入带 emoji 的文本时数据库报Incorrect string value。
原因:建库时字符集用了utf8(MySQL 的 utf8 实际是 utf8mb3,最多 3 字节),emoji 需要 4 字节。
解决:ALTER DATABASE website_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;,同时确认表和字段也是 utf8mb4,连接串带?charset=utf8mb4。三处缺一不可。
4.4 修改模型后数据库没变化
现象:给模型加了字段,重启应用后查询报Unknown column。
原因:这套模板通常没有集成 Flask-Migrate,模型改动不会自动同步到数据库。
解决:手动ALTER TABLE加字段,或者引入 Flask-Migrate 做版本管理。我一般开发阶段直接改表,上线前用迁移脚本固化。别指望db.create_all()能改已有表结构,它只建不存在的表。
4.5 生产环境用 debug 模式导致代码泄露
现象:部署后访问出错页面,直接显示完整堆栈和源码片段。
原因:app.run(debug=True)没关,或 WSGI 入口没覆盖配置。
解决:生产用 gunicorn/uwsgi 拉起,debug设 False,SECRET_KEY换成随机长字符串并通过环境变量注入。模板里的默认密钥和调试开关是给开发用的,上线前必须过一遍配置检查。
5. 进阶:把模板变成可维护项目的三个习惯
第一个习惯是给配置分环境。别把所有东西堆在config.py里,拆成DevConfig、ProdConfig,用APP_ENV环境变量切换。数据库密码、密钥这些敏感项一律走环境变量,代码里只留读取逻辑。我见过太多人把生产密码提交到仓库,改起来要翻半天历史记录。
第二个习惯是给接口加统一响应封装。模板里每个接口各写各的jsonify,字段名容易飘。抽一个response_ok(data, count)和response_error(msg, code),所有接口走同一套出口。这样前端解析逻辑稳定,加日志和埋点也只用改一处。
第三个习惯是定期验证登录态和权限边界。具体做法:用普通账号登录后,直接手敲管理员接口地址,看是否返回 403 或 401。这套模板的权限控制粒度取决于打包者实现,有的只做了菜单隐藏没做接口拦截,前端藏了按钮但接口照样能调。我一般会写个简单的 pytest 用例,拿普通用户 session 去请求管理接口,断言状态码不是 200。
# tests/test_auth.py def test_normal_user_cannot_access_admin_api(client): # 用普通账号登录 client.post('/auth/login', data={'username': 'user', 'password': 'user123'}) # 直接请求管理员接口 resp = client.get('/admin/api/user/list') # 断言被拦截,而不是返回数据 assert resp.status_code in (401, 403)这个用例跑通,说明接口层做了校验;如果返回 200 且带数据,说明权限只做在模板层,得补装饰器。从那以后我每次拿到这类后台模板,都先跑一遍越权测试再动业务代码,省得上线后被扫出来。希望帮到你。
本文还有配套的精品资源,点击获取