【Flask】实现一个前后端一体的项目-脚手架

发布于:2025-09-13 ⋅ 阅读:(23) ⋅ 点赞:(0)

目录

在前面的文章中我们都是实现Flask=vue2.x的前后端分离框架,那有没有前后端不分离的框架,前后端分离,对于大多数没有前端基础的,可能维护起来有点费力,涉及的知识点相对多一点,那么本篇将带你完整实现一个前后端一体的项目框架架构,包括实现用户的登录,登出,界面功能菜单显示,完整的一个后台管理系统,之后你就可以在系统中陆续实现你自己的功能

  • 实现Flask项目创建
  • 实现登录功能
  • 创建数据库,生成数据库表
  • 实现页面功能
  • 实现基础页面的展示布局

项目背景介绍

我们将基于Flask框架,实现一个完整的前后端一体的项目后台管理系统,实现简单的个人中心,,一个完整的小型项目框架架构

注意:这种前后端一体项目,只适合你永远自己实现个小项目,大项目还是使用前后端分离这种实现方式,切记这种方式不要在大型项目中尝试

概述

首先如果你要能使用前后端一体的Flask框架,前置条件你需要先具备

技术知识储备:

  • 1、熟悉Python基础语法
  • 2、熟悉Flask web框架-可参考下面链接https://dormousehole.readthedocs.io/en/latest/tutorial/index.html
  • 3、熟悉peewee ORM框架-参考下面链接
  • https://geek-docs.com/python/python-tutorial/python-peewee.html
  • 4、有一定的前端知识,了解Jinja语法,方便前后端传参
  • 5、需要有数据库的功底

先在本地创建一个Flask项目,Flask项目创建后就如下面这种效果

  • 默认Jinja模版

启动验证下项目是不是正常

浏览器访问一下地址:127.0.0.1:5000

正常可以访问,没有任何问题

项目概述

本教程将基于Flask框架,实现一个完整的前后端一体的项目,带你完整实现第一个前后端一体的完整项目和功能实现过程

数据库创建

数据库是必不可少的,在你本地先安装好数据库,这里我本地使用的是mysql,我已经安装好了,如果你没有安装,你可以先把数据库安装好,并且启用数据库连接好数据库,创建一个数据库

这里数据库的名字按照你自己命名即可,这里我命名如下(名字你随意就行)

项目结构

数据库模型

接下来需要创建对应的数据模型,生成执行后生成对应的数据表,这里我们采用peewee ORM的方式生成数据库表

注意:peewee 不会自动创建数据库,需要自己先在本地吧数据库创建好

建立数据库模型

  • 首先创建一个models包,用于存储各种模型文件

创建config文件,用于存储数据连接配置文件信息,填写上面我们创建的数据库信息

config文件中完整代码

import os

basedir = os.path.abspath(os.path.dirname(__file__))


class Config:
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'super-secret'
    DB_HOST = 'localhost'
    DB_PORT = 3306
    DB_USER = 'root'
    DB_PASSWD = 'Rebort!123'
    DB_DATABASE = 'luntanrebort'
    ITEMS_PER_PAGE = 10
    JWT_AUTH_URL_RULE = '/api/auth'

    @staticmethod
    def init_app(app):
        pass


class DevelopmentConfig(Config):
    DEBUG = True


class TestingConfig(Config):
    TESTING = True


class ProductionConfig(Config):
    PRODUCTION = True


config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}

创建基础模型,所有模型继承此模型

class BaseModel(Model):
    class Meta:
        database = db

    def __str__(self):
        r = {}
        for k in self._data.keys():
            try:
                r[k] = str(getattr(self, k))
            except:
                r[k] = json.dumps(getattr(self, k))
        return json.dumps(r, ensure_ascii=False)

用户模型

# 用户模型
class User(UserMixin, BaseModel):
    id = IntegerField(primary_key=True)  # 添加主键
    username = CharField(max_length=80, unique=True, null=False)  # Peewee 用 null=False 代替 nullable=False
    password_hash = CharField(max_length=128, null=False)  # 字段名统一为 password_hash
    email = CharField(max_length=120, unique=True, null=False)
    fullname = CharField(max_length=50, null=True)  # 真实性名(允许为空)
    phone = CharField(max_length=20, null=True)  # 电话(允许为空)
    register_time = DateTimeField(default=datetime.datetime.now)
    last_login_time = DateTimeField(null=True)
    failed_login_count = IntegerField(default=0)
    locked = BooleanField(default=False)
    avatar_url = CharField(max_length=200, default='default.png')  # Peewee 不需要 _ 前缀映射
    signature = TextField(default="这个人很懒,还没写个性签名~")
    is_admin = BooleanField(default=False)
    status = BooleanField(default=True)  # 生效失效标识

    def verify_password(self, raw_password):
        return check_password_hash(self.password_hash, raw_password)

通知数据模型

# 通知人配置
class CfgNotify(BaseModel):
    id = IntegerField(primary_key=True)  # 添加主键
    check_order = IntegerField()  # 排序
    notify_type = CharField(max_length=10)  # 通知类型:MAIL/SMS
    notify_name = CharField(max_length=50)  # 通知人姓名
    notify_number = CharField(max_length=100)  # 通知号码
    status = BooleanField(default=True)  # 生效失效标识

贴子模型

# 帖子模型
class Post(BaseModel):
    id = IntegerField(primary_key=True)
    title = CharField(max_length=100, null=False)
    content = TextField(null=False)
    time = DateTimeField(default=datetime.datetime.now)
    author_id = ForeignKeyField(User, backref='posts')  
    likes = IntegerField(default=0)
    views = IntegerField(default=0)

评论模型

# 评论模型
class Comment(BaseModel):
    id = IntegerField(primary_key=True)
    content = TextField(null=False)
    time = DateTimeField(default=datetime.datetime.now)
    author_id = ForeignKeyField(User, backref='comments')
    post_id = ForeignKeyField(Post, backref='comments', on_delete='CASCADE')  

收藏模型

# 收藏模型
class Favorite(BaseModel):
    id = IntegerField(primary_key=True)
    user_id = ForeignKeyField(User, backref='favorites')
    post_id = ForeignKeyField(Post, backref='favorited_by')
    time = DateTimeField(default=datetime.datetime.now)

    class Meta:
        indexes = (
            (('user_id', 'post_id'), True), 
        )

点赞模型

# 点赞模型
class Like(BaseModel):
    id = IntegerField(primary_key=True)
    user_id = ForeignKeyField(User, backref='likes')
    post_id = ForeignKeyField(Post, backref='post_likes')
    time = DateTimeField(default=datetime.datetime.now)

    class Meta:
        indexes = (
            (('user_id', 'post_id'), True),
        )

model模型的完整代码如下:

# -*- coding: utf-8 -*-
import os
import datetime
import json
from werkzeug.security import check_password_hash
from flask_login import UserMixin
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from configs.config import config
cfg = config[os.getenv('FLASK_CONFIG') or 'default']
# Peewee 相关导入
from peewee import (Model, CharField, BooleanField,
                    IntegerField, TextField, DateTimeField, ForeignKeyField)
from playhouse.pool import PooledMySQLDatabase
from playhouse.shortcuts import ReconnectMixin

# 配置导入(确保 conf/config.py 存在)
from configs.config import config

# 初始化配置
cfg = config[os.getenv('FLASK_CONFIG') or 'default']

# ------------------------------
# 1. 修复数据库连接
# ------------------------------
class RetryMySQLDatabase(ReconnectMixin, PooledMySQLDatabase):
    _instance = None

    @staticmethod
    def get_db_instance():
        if not RetryMySQLDatabase._instance:
            RetryMySQLDatabase._instance = RetryMySQLDatabase(
                database=cfg.DB_NAME,  
                host=cfg.DB_HOST,
                user=cfg.DB_USER,
                password=cfg.DB_PASSWD,
                port=cfg.DB_PORT,
                charset='utf8mb4',  # 添加字符集,避免中文乱码
                max_connections=32,
                stale_timeout=300
            )
        return RetryMySQLDatabase._instance

# 初始化数据库连接
db = RetryMySQLDatabase.get_db_instance()

# ------------------------------
# 2. 基础模型类(所有模型继承此类)
# ------------------------------
class BaseModel(Model):
    class Meta:
        database = db

    def __str__(self):
        r = {}
        for k in self._data.keys():
            try:
                r[k] = str(getattr(self, k))
            except:
                r[k] = json.dumps(getattr(self, k))
        return json.dumps(r, ensure_ascii=False)

# ------------------------------
# 3. 模型定义(统一使用 Peewee 语法)
# ------------------------------

# 用户模型(仅一个 User 类,继承 BaseModel)
class User(UserMixin, BaseModel):
    id = IntegerField(primary_key=True)  # 添加主键
    username = CharField(max_length=80, unique=True, null=False)  # Peewee 用 null=False 代替 nullable=False
    password_hash = CharField(max_length=128, null=False)  # 字段名统一为 password_hash
    email = CharField(max_length=120, unique=True, null=False)
    fullname = CharField(max_length=50, null=True)  # 真实性名(允许为空)
    phone = CharField(max_length=20, null=True)  # 电话(允许为空)
    register_time = DateTimeField(default=datetime.datetime.now)
    last_login_time = DateTimeField(null=True)
    failed_login_count = IntegerField(default=0)
    locked = BooleanField(default=False)
    avatar_url = CharField(max_length=200, default='default.png')  # Peewee 不需要 _ 前缀映射
    signature = TextField(default="这个人很懒,还没写个性签名~")
    is_admin = BooleanField(default=False)
    status = BooleanField(default=True)  # 生效失效标识

    def verify_password(self, raw_password):
        return check_password_hash(self.password_hash, raw_password)

# 通知人配置
class CfgNotify(BaseModel):
    id = IntegerField(primary_key=True)  # 添加主键
    check_order = IntegerField()  # 排序
    notify_type = CharField(max_length=10)  # 通知类型:MAIL/SMS
    notify_name = CharField(max_length=50)  # 通知人姓名
    notify_number = CharField(max_length=100)  # 通知号码
    status = BooleanField(default=True)  # 生效失效标识

# 帖子模型
class Post(BaseModel):
    id = IntegerField(primary_key=True)
    title = CharField(max_length=100, null=False)
    content = TextField(null=False)
    time = DateTimeField(default=datetime.datetime.now)
    author_id = ForeignKeyField(User, backref='posts')  
    likes = IntegerField(default=0)
    views = IntegerField(default=0)

# 评论模型
class Comment(BaseModel):
    id = IntegerField(primary_key=True)
    content = TextField(null=False)
    time = DateTimeField(default=datetime.datetime.now)
    author_id = ForeignKeyField(User, backref='comments')
    post_id = ForeignKeyField(Post, backref='comments', on_delete='CASCADE')  # 级联删除

# 收藏模型
class Favorite(BaseModel):
    id = IntegerField(primary_key=True)
    user_id = ForeignKeyField(User, backref='favorites')
    post_id = ForeignKeyField(Post, backref='favorited_by')
    time = DateTimeField(default=datetime.datetime.now)

    class Meta:
        indexes = (
            (('user_id', 'post_id'), True),  # 联合唯一索引:一个用户只能收藏一个帖子一次
        )

# 点赞模型
class Like(BaseModel):
    id = IntegerField(primary_key=True)
    user_id = ForeignKeyField(User, backref='likes')
    post_id = ForeignKeyField(Post, backref='post_likes')
    time = DateTimeField(default=datetime.datetime.now)

    class Meta:
        indexes = (
            (('user_id', 'post_id'), True),  # 联合唯一索引:一个用户只能点赞一个帖子一次
        )

# ------------------------------
# 4. 用户加载函数(需在 User 模型定义后)
# ------------------------------
# 注意:login_manager 需要从 Flask 应用初始化中导入
# 此处先注释,在 Flask 应用初始化文件中配置(见下方说明)
"""
@login_manager.user_loader
def load_user(user_id):
    try:
        return User.get(User.id == int(user_id))
    except User.DoesNotExist:
        return None
"""

# ------------------------------
# 5.建表函数
# ------------------------------
def create_tables():
    # 按顺序创建表(外键依赖的表需后创建)
    tables = [User, CfgNotify, Post, Comment, Favorite, Like]
    with db.connection_context():  # 使用上下文管理器确保连接正确
        db.create_tables(tables, safe=True)  # safe=True:已存在的表不会报错
    print(f"成功创建 {len(tables)} 张表!")

# ------------------------------
# 6. 执行建表(调用 create_tables())
# ------------------------------
if __name__ == '__main__':
    create_tables()  # 执行建表

由于这里我的入口不是app.py启动项目,此时将启动入口放在了manage.py中,使用Flask CLI的方式进行创建数据表

为什么 flask create-tables 需要 FLASK_APP

flask 命令本质是 Flask 官方 CLI 工具,它需要知道哪个文件定义了 Flask 应用实例(即 app = Flask(__name__)create_app())。

  • 若不设置 FLASK_APP,Flask 会自动搜索当前目录下的 app.py/wsgi.py 等文件,但如果您的应用入口是 manage.py,则必须显式指定。
  • flask --app manage.py create-tables 中的 --app manage.py 本质是临时指定 FLASK_APP,与设置环境变量作用相同。

在项目根目录下创建文件.flaskenv

文件中写入数据如下

FLASK_APP=manage.py    # 告诉 Flask 应用入口是 manage.py
FLASK_CONFIG=development  # 可选:默认环境

需要安装下环境

 pip install python-dotenv

manage.py中的完整代码

# manage.py
import os
from flask import Flask
from apis import create_app  # 确保 create_app 能正确返回 Flask 实例
from models.model import create_tables  # 确保模型导入无错误

# 1. 初始化 Flask 应用(必须在命令注册前执行)
app = create_app(os.getenv('FLASK_CONFIG') or 'default')

# 2. 关键:注册 CLI 命令(必须在 app 初始化后)
@app.cli.command("create-tables")  # 命令名:create-tables
def cli_create_tables():
    """创建所有数据库表"""
    create_tables()
    print("✅ 数据库表创建成功!")

# 3. 其他命令(可选,保持原有功能)
@app.cli.command("runserver")
def cli_runserver():
    """启动开发服务器"""
    app.run(host="0.0.0.0", port=5000, debug=True)

@app.cli.command("test")
def cli_test():
    """运行测试"""
    import unittest
    tests = unittest.TestLoader().discover('tests')
    unittest.TextTestRunner(verbosity=2).run(tests)

# 4. 若直接运行 manage.py,提示使用 Flask CLI
if __name__ == '__main__':
    print("请使用 Flask CLI 命令:")
    print("flask create-tables  # 创建表")
    print("flask runserver      # 启动服务器")

执行创建数据表的语句

flask create-tables

查看此时数据库中是不是已经有创建的数据表

表创建成功,如果你看到的和我的一样,那么此时数据库表第一步实现成功

接下来需要创建一个管理员,用于登录系统,我们直接在manage中写一个创建管理员的方法


@app.cli.command("create-admin")
def create_admin():
    from models.model import User
    from werkzeug.security import generate_password_hash
    import datetime

    if User.select().where(User.username == 'admin').exists():
        print("⚠️ 管理员已存在!")
        return

    # 模型自动处理所有默认值字段(failed_login_count/locked/avatar_url 等)
    User.create(
        id=1,
        username='admin',
        password_hash=generate_password_hash('admin'),  # 替换为实际密码
        fullname='管理员',
        email='admin@admin.com',
        phone='156341234',
        status=True,
        is_admin=True  # 显式指定管理员权限(模型默认是 False)
        # 其他字段(register_time/failed_login_count 等)无需手动指定,模型自动填充
    )
    print("✅ 管理员创建成功!")

执行创建命令

 flask create-admin

也可以直接通过SQL插入(默认用户名和密码都是admin/admin)

INSERT INTO `user` (`id`, `username`, `password`, `fullname`, `email`, `phone`, `status`)
VALUES(1, 'admin', 'pbkdf2:sha1:1000$Km1vdx3W$9aa07d3b79ab88aae53e45d26d0d4d4e097a6cd3', '管理员', 'admin@admin.com', '15685878475', 1);

编写登录页面路由

from flask import render_template, redirect, request, url_for, flash
from . import auth
from .forms import LoginForm
from app.models import User
from flask_login import login_user, logout_user, login_required


@auth.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    print(form.rememberme.data)
    if form.validate_on_submit():
        try:
            user = User.get(User.username == form.username.data)
            if user.verify_password(form.password.data):
                login_user(user, form.rememberme.data)
                return redirect(request.args.get('next') or url_for('main.index'))
            else:
                flash('用户名或密码错误')
        except:
            flash('用户名或密码错误')
    return render_template('auth/login.html', form=form)


@auth.route('/logout')
@login_required
def logout():
    logout_user()
    flash('您已退出登录')
    return redirect(url_for('auth.login'))

登录时增加一个记住用户账号的功能

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired, Length


class LoginForm(FlaskForm):
    username = StringField('用户名', validators=[DataRequired(), Length(1, 64), ])
    password = PasswordField('密码', validators=[DataRequired()])
    rememberme = BooleanField('记住我')
    submit = SubmitField('提交')

编写登录页面的HTML

login.html

登录页面完整代码如下

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
    <title>管理员REBORT后台</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='plugins/bootstrap/css/bootstrap.min.css') }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='plugins/iCheck/square/blue.css') }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='plugins/pace/pace.min.css') }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='plugins/adminlte/css/AdminLTE.min.css') }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='plugins/adminlte/css/skins/skin-blue.min.css') }}">
    <link rel="stylesheet" href="{{ url_for('static', filename='css/global.css') }}">
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <style>
        /* 全局样式 */
        body {
      /* 替换为您的图片URL,确保图片路径正确 */
      background-image: url("{{ url_for('static', filename='images/rebort.jpg') }}");
      background-size: cover; /* 图片覆盖全屏 */
      background-position: center; /* 图片居中 */
      background-repeat: no-repeat; /* 不重复平铺 */
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      font-family: 'Segoe UI', 'Roboto', sans-serif;
      padding: 20px;
      /* 如果图片较亮,可添加半透明遮罩增加文字可读性 */
      position: relative;
    }

    /* 添加半透明遮罩(可选) */
    body::before {
      content: "";
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(255, 255, 255, 0.3); /* 白色遮罩,透明度80% */
      z-index: -1; /* 确保遮罩在内容下方 */
    }

        /* 登录框容器 */
        .login-container {
            width: 100%;
            max-width: 420px;
            perspective: 1000px;
        }

        /* 登录卡片 */
        .login-card {
            background: #ffffff;
            border-radius: 12px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
            overflow: hidden;
            transition: all 0.3s ease;
            transform-style: preserve-3d;
            transform: translateY(0);
        }

        .login-card:hover {
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
            transform: translateY(-5px);
        }

        /* 登录头部 */
        .login-header {
            background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
            color: white;
            padding: 25px;
            text-align: center;
            position: relative;
            overflow: hidden;
        }

        .login-header h1 {
            font-size: 24px;
            font-weight: 600;
            margin: 0;
            position: relative;
            z-index: 2;
        }

        .login-header::before {
            content: "";
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(255, 255, 255, 0.1);
            transform: skewY(-3deg);
            transform-origin: top right;
            z-index: 1;
        }

        /* 登录主体 */
        .login-body {
            padding: 30px;
        }

        .login-message {
            color: #666;
            margin-bottom: 25px;
            text-align: center;
            font-size: 15px;
        }

        /* 表单样式 */
        .form-group {
            margin-bottom: 20px;
            position: relative;
        }

        .form-control {
            height: 50px;
            padding: 10px 15px 10px 45px;
            border: 1px solid #e1e5eb;
            border-radius: 8px;
            font-size: 15px;
            transition: all 0.3s ease;
            box-shadow: none !important;
        }

        .form-control:focus {
            border-color: #3498db;
            box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.15) !important;
        }

        .form-control-icon {
            position: absolute;
            left: 15px;
            top: 50%;
            transform: translateY(-50%);
            color: #a0a6ac;
            font-size: 18px;
        }

        /* 提醒消息 */
        .alert {
            border-radius: 8px;
            margin-bottom: 20px;
            padding: 12px 15px;
            font-size: 14px;
            animation: fadeIn 0.3s ease;
        }

        /* 记住我 */
        .remember-me {
            display: flex;
            align-items: center;
            margin-bottom: 20px;
        }

        .remember-me input {
            margin-right: 8px;
        }

        .remember-me label {
            color: #555;
            font-size: 14px;
            cursor: pointer;
        }

        /* 按钮样式 */
        .btn-primary {
            background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
            border: none;
            border-radius: 8px;
            height: 50px;
            font-size: 16px;
            font-weight: 500;
            transition: all 0.3s ease;
            box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3);
        }

        .btn-primary:hover, .btn-primary:focus {
            background: linear-gradient(135deg, #2980b9 0%, #3498db 100%);
            transform: translateY(-2px);
            box-shadow: 0 6px 15px rgba(52, 152, 219, 0.35);
        }

        .btn-primary:active {
            transform: translateY(0);
        }

        /* iCheck 样式调整 */
        .icheckbox_square-blue {
            margin-top: -2px;
        }

        .icheckbox_square-blue.checked {
            background-color: #3498db;
            border-color: #3498db;
        }

        .icheckbox_square-blue.checked::after {
            border-color: white;
        }

        /* 加载动画 */
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }

        /* 响应式调整 */
        @media (max-width: 576px) {
            .login-body {
                padding: 25px 20px;
            }

            .form-control, .btn-primary {
                height: 46px;
            }
        }
    </style>
    <!--[if lt IE 9]>
    <script src="plugins/html5shiv.min.js"></script>
    <script src="plugins/respond.min.js"></script>
    <![endif]-->
</head>
<body>
    <div class="login-container">
        <div class="login-card">
            <div class="login-header">
                <h1>使用管理员密码登录</h1>
            </div>
            <div class="login-body">
                <p class="login-message">请输入管理员帐户及密码</p>
                <form method="post">
                    {{form.hidden_tag()}}

                    <div class="form-group">
                        <i class="fa fa-user form-control-icon"></i>
                        {{form.username(class_="form-control",placeholder="用户名")}}
                    </div>

                    <div class="form-group">
                        <i class="fa fa-lock form-control-icon"></i>
                        {{form.password(class_="form-control",placeholder="密码")}}
                    </div>

                    {% for message in get_flashed_messages() %}
                    <div class="alert alert-warning">
                        <button type="button" class="close" data-dismiss="alert">&times;</button>
                        {{ message }}
                    </div>
                    {% endfor %}

                    <div class="remember-me">
                        <div class="checkbox icheck">
                            <label>
                                {{form.rememberme()}}{{form.rememberme.label}}
                            </label>
                        </div>
                    </div>

                    {{form.submit(class_="btn btn-primary btn-block")}}
                </form>
            </div>
        </div>
    </div>

    <script src="{{ url_for('static', filename='plugins/jQuery/jquery-2.2.3.min.js') }}"></script>
    <script src="{{ url_for('static', filename='plugins/bootstrap/js/bootstrap.min.js') }}"></script>
    <script src="{{ url_for('static', filename='plugins/iCheck/icheck.min.js') }}"></script>
    <script src="{{ url_for('static', filename='plugins/pace/pace.min.js') }}"></script>
    <script src="{{ url_for('static', filename='js/global.js') }}"></script>
    <script>
        function initPage() {
            // 初始化iCheck插件
            $('input').iCheck({
                checkboxClass: 'icheckbox_square-blue',
                radioClass: 'iradio_square-blue',
                increaseArea: '20%'
            });

            // 表单字段聚焦效果
            $('.form-control').on('focus', function() {
                $(this).parent().find('.form-control-icon').css('color', '#3498db');
            }).on('blur', function() {
                $(this).parent().find('.form-control-icon').css('color', '#a0a6ac');
            });

            // 添加页面载入动画
            Pace.on('done', function() {
                $('.login-card').addClass('loaded');
            });
        }
    </script>
</body>
</html>

此时启动后端项目,查看此时登录界面是不是正常显示成功

登录页展示成功,至此,登录功能已全部实现

接着陆续实现其他的功能页面

这里由于功能页面太多,这里就不一一贴出每个页面的代码了,最终实现后的效果如下

登录后页面展示如下

至此,就完成了一个前后端一体的脚手架管理后台


网站公告

今日签到

点亮在社区的每一天
去签到