基于 Flask 的电商超市数据可视化分析系统 — 技术文档
目录
- 项目概述
- 技术栈
- 项目结构
- 系统架构
- 数据库设计
- 后端路由设计
- 数据分析引擎
- 前端页面设计
- API 接口文档
- 用户认证与权限
- 管理后台
- 部署与运行
- 依赖清单
1. 项目概述
本系统是一个基于 Flask 框架的电商超市数据可视化分析平台,通过对超市销售数据的多维度分析,提供销售趋势、产品分析、客户画像、时间序列、地理分布和数据挖掘等六大分析模块,共 45 个分析方法,生成 47 种图表,帮助管理者做出数据驱动的经营决策。
核心功能:
- 用户登录注册与权限管理(管理员/普通用户)
- 数据总览仪表盘(8 个 KPI 指标卡片 + 6 个概览图表)
- 六大分析模块(销售/产品/客户/时间/地理/数据挖掘)
- 管理后台(用户管理 + 销售数据 CRUD)
- 个人中心(头像上传、信息编辑、密码修改)
2. 技术栈
| 层级 | 技术 | 版本 | 用途 |
|---|
| 后端框架 | Flask | 3.0.0 | Web 应用框架 |
| ORM | Flask-SQLAlchemy | 3.1.1 | 数据库操作 |
| 认证 | Flask-Login | 0.6.3 | 用户会话管理 |
| 数据库 | SQLite | — | 数据持久化存储 |
| 数据分析 | pandas | 2.1.4 | 数据加载、清洗、聚合 |
| 数据分析 | numpy | 1.26.2 | 数值计算 |
| 前端模板 | Jinja2 | — | 服务端页面渲染 |
| 图表库 | ECharts | 5.x | 数据可视化(主要) |
| UI 框架 | Bootstrap | 4.x | 页面布局与组件 |
| 图标库 | Font Awesome | 4.x | 图标 |
| JS 库 | jQuery | 3.x | DOM 操作、AJAX |
| 服务端 | Werkzeug | 3.0.1 | WSGI 工具库 |
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
![]()
3. 项目结构
code/ ├── app.py # 主应用入口:路由定义、数据库初始化 ├── config.py # 配置类(密钥、数据库路径、数据文件路径) ├── requirements.txt # Python 依赖清单 ├── 技术文档.md # 本文档 │ ├── models/ # 数据模型层 │ ├── __init__.py # db 实例创建 + 模型导入 │ ├── user.py # User 模型(用户表) │ └── sales.py # SalesData 模型(销售数据表) │ ├── utils/ # 工具层 │ ├── __init__.py │ └── data_analyzer.py # SalesAnalyzer 类(45 个分析方法) │ ├── data/ # 数据文件 │ ├── sales_data.csv # 主数据源(UTF-8-sig 编码) │ ├── 超市销售分析.xls # 原始 Excel 数据 │ └── supermarket.db # SQLite 数据库文件(自动生成) │ ├── templates/ # Jinja2 模板(12 个文件) │ ├── base.html # 基础布局(侧边栏、头部、底部、CSS/JS 引入) │ ├── dashboard.html # 数据总览页 │ ├── auth/ │ │ ├── login.html # 登录页 │ │ └── register.html # 注册页 │ ├── profile/ │ │ └── index.html # 个人中心页 │ ├── admin/ │ │ └── dashboard.html # 管理后台页(用户管理 + 数据管理) │ ├── sales/ │ │ └── analysis.html # 销售分析页(8 图表) │ ├── product/ │ │ └── analysis.html # 产品分析页(8 图表) │ ├── customer/ │ │ └── analysis.html # 客户分析页(8 图表) │ ├── time/ │ │ └── analysis.html # 时间分析页(9 图表) │ ├── geo/ │ │ └── analysis.html # 地理分析页(8 图表) │ └── mining/ │ └── analysis.html # 数据挖掘页(6 图表) │ └── static/ # 静态资源 ├── css/ # 17 个 CSS 文件 │ ├── bootstrap.min.css # Bootstrap 4 │ ├── style.css # moban 模板主样式 │ └── custom.css # 自定义覆盖样式 ├── js/ # 61 个 JS 文件 │ ├── echarts.min.js # ECharts 图表库 │ ├── jquery.min.js # jQuery │ ├── bootstrap.min.js # Bootstrap JS │ └── main.js # 自定义 JS(菜单、计数器动画) └── img/ # 图片资源 ├── logo.png # 侧边栏 Logo ├── admin.jpg # 默认头像 └── auth-bg.jpg # 登录注册背景图
4. 系统架构
4.1 三层架构
┌─────────────────────────────────────────────────────┐ │ 前端展示层 │ │ Jinja2 模板 + Bootstrap + ECharts + jQuery AJAX │ │ 用户浏览器渲染页面,通过 AJAX 调用 API 获取图表数据 │ └───────────────────────┬─────────────────────────────┘ │ HTTP (GET/POST) ┌───────────────────────┴─────────────────────────────┐ │ 后端路由层 │ │ app.py: 22 个页面路由 + 6 个 API 路由族 │ │ 处理请求、调用分析方法、返回 JSON 或渲染模板 │ └───────────────────────┬─────────────────────────────┘ │ ┌───────────────┴───────────────┐ │ │ ┌───────┴────────┐ ┌─────────┴──────────┐ │ 数据分析层 │ │ 数据持久层 │ │ SalesAnalyzer │ │ SQLAlchemy ORM │ │ pandas + numpy│ │ SQLite 数据库 │ │ 45 个分析方法 │ │ User / SalesData │ │ 读取 CSV │ │ 读写 DB │ └────────────────┘ └────────────────────┘
4.2 数据流
- 页面加载:浏览器请求页面路由 → Flask 渲染 Jinja2 模板返回 HTML
- 图表加载:页面 JS 通过
fetch('/api/<module>/<chart_type>')请求 API - 数据处理:API 路由调用
SalesAnalyzer对应方法 → pandas 计算 → 返回 JSON - 图表渲染:前端 JS 将 JSON 数据传入 ECharts 配置渲染图表
4.3 关键设计模式
- 懒加载单例:
SalesAnalyzer在首次 API 调用时初始化,将 CSV 全量加载到内存 DataFrame,后续调用复用同一实例 - 路由分发模式:每个 API 路由族使用
data_map字典将 URL 中的chart_type参数映射到具体的分析方法 - AJAX 异步加载:分析页面 HTML 只包含图表容器,图表数据通过 AJAX 独立加载,互不阻塞
5. 数据库设计
5.1 User 表(users)
| 字段 | 类型 | 约束 | 说明 |
|---|
| id | Integer | 主键,自增 | 用户 ID |
| username | String(80) | UNIQUE, NOT NULL | 用户名 |
| email | String(120) | UNIQUE, NOT NULL | 邮箱 |
| password_hash | String(256) | NOT NULL | 密码哈希(werkzeug) |
| avatar | String(200) | DEFAULT ‘img/default-avatar.png’ | 头像路径 |
| role | String(20) | DEFAULT ‘user’ | 角色:user / admin |
| phone | String(20) | DEFAULT ‘’ | 手机号 |
| created_at | DateTime | SERVER DEFAULT now() | 注册时间 |
方法:
set_password(password)— 生成密码哈希check_password(password)— 验证密码is_admin()— 判断是否管理员
5.2 SalesData 表(sales_data)
| 字段 | 类型 | 对应 CSV 列名 | 说明 |
|---|
| id | Integer | — | 主键,自增 |
| order_id | String(50) | 订单编号 | 订单唯一标识 |
| order_date | String(20) | 订货日期 | 订货日期字符串 |
| ship_date | String(20) | 发货日期 | 发货日期字符串 |
| ship_mode | String(50) | 邮寄方式 | 标准/二级/一级/当日 |
| customer_id | String(50) | 客户ID | 客户唯一标识 |
| customer_name | String(100) | 客户名称 | 客户姓名 |
| segment | String(50) | 细类 | 消费者/公司/家庭办公 |
| city | String(100) | 城市 | 城市名 |
| province | String(100) | 省/自治区 | 省级行政区 |
| country | String(100) | 国家 | 国家名 |
| region | String(50) | 地区 | 东/南/西/北/中 |
| product_id | String(100) | 产品ID | 产品唯一标识 |
| category | String(50) | 大类 | 家具/办公用品/技术 |
| sub_category | String(50) | 子类 | 书架/椅子/电话 等 |
| product_name | String(200) | 产品名称 | 产品完整名称 |
| sales | Float | 销售额 | 销售金额 |
| quantity | Integer | 数量 | 购买数量 |
| discount | Float | 折扣 | 折扣率 (0~1) |
| profit | Float | 利润 | 利润金额(可为负) |
5.3 初始化逻辑
应用启动时执行init_db():
- 创建所有表(
db.create_all()) - 若
users表为空,插入两个默认用户:admin / admin123(角色:admin)user / user123(角色:user)
- 若
sales_data表为空,从data/sales_data.csv批量导入数据(每批 1000 条)
6. 后端路由设计
6.1 认证路由
| 路由 | 方法 | 函数 | 说明 |
|---|
/login | GET, POST | login() | 登录页面。POST 验证用户名密码,登录成功重定向到/ |
/register | GET, POST | register() | 注册页面。POST 创建新用户(默认角色 user) |
/logout | GET | logout() | 退出登录,重定向到/login |
6.2 页面路由(需登录)
| 路由 | 函数 | 说明 |
|---|
/ | dashboard() | 数据总览,传递 6 组统计数据给模板 |
/sales | sales_analysis() | 销售分析页(图表通过 AJAX 加载) |
/product | product_analysis() | 产品分析页 |
/customer | customer_analysis() | 客户分析页 |
/time | time_analysis() | 时间分析页 |
/geo | geo_analysis() | 地理分析页 |
/mining | mining_analysis() | 数据挖掘页 |
/profile | profile() | 个人中心页 |
6.3 API 路由(需登录,返回 JSON)
| 路由模式 | 函数 | 图表类型参数 |
|---|
/api/sales/<chart_type> | api_sales() | scatter, ship_mode, discount_impact, segment, quarterly, subcategory, city_top, heatmap |
/api/product/<chart_type> | api_product() | category_pie, subcategory_detail, product_top, quantity_dist, category_discount, price_range, profit_rate, product_trend |
/api/customer/<chart_type> | api_customer() | segment_pie, value_tiers, region_dist, frequency, scatter, retention, segment_monthly, value_heatmap |
/api/time/<chart_type> | api_time() | year_quarter_heatmap, monthly_prediction, growth_rate, shipping, year_compare, weekday, hourly, cumulative, seasonal |
/api/geo/<chart_type> | api_geo() | province_map, region_pie, province_profit, city_top, region_trend, province_quantity, region_profit_compare, logistics |
/api/mining/<chart_type> | api_mining() | rfm, basket, profit_prediction, clv, clusters, anomaly |
6.4 用户路由(需登录)
| 路由 | 方法 | 函数 | 说明 |
|---|
/profile/update | POST | profile_update() | 更新用户名、邮箱、手机号、头像 |
/profile/password | POST | change_password() | 修改密码(验证旧密码) |
6.5 管理路由(需管理员权限)
| 路由 | 方法 | 函数 | 说明 |
|---|
/admin | GET | admin_dashboard() | 管理后台(用户列表 + 分页数据列表) |
/admin/user/delete/<id> | POST | admin_delete_user() | 删除用户(不可删自己) |
/admin/user/role/<id> | POST | admin_change_role() | 修改用户角色 |
/admin/data/add | POST | admin_data_add() | 添加销售数据 |
/admin/data/edit/<id> | POST | admin_data_edit() | 编辑销售数据 |
/admin/data/delete/<id> | POST | admin_data_delete() | 删除销售数据 |
7. 数据分析引擎
7.1 SalesAnalyzer 类
文件:utils/data_analyzer.py
初始化:__init__(self, csv_path)— 读取 CSV 文件,计算派生列(年份、月份、季度、发货天数),存入内存 DataFrame。
单例获取:通过get_analyzer()函数懒加载,全局只初始化一次。
7.2 分析方法清单(45 个)
数据总览(6 个方法)
| 方法 | 返回结构 | 说明 |
|---|
get_overview_stats() | Dict | 总销售额、总利润、总订单数、客户总数、产品数量、平均客单价、平均折扣、利润率 |
get_yearly_sales() | List[Dict] | 按年汇总:年份、销售额、利润、订单数 |
get_monthly_sales() | List[Dict] | 按月汇总:月份(period)、销售额、利润 |
get_region_sales() | List[Dict] | 按地区汇总:地区、销售额、利润、订单数(降序) |
get_category_sales() | List[Dict] | 按大类汇总:大类、销售额、利润、数量 |
get_top_products(n) | List[Dict] | 销售额 Top N 产品:产品名称、销售额、利润、数量 |
销售分析(8 个方法)
| 方法 | 返回结构 | 说明 |
|---|
get_sales_profit_scatter() | List[Dict] | 散点图数据(最多 2000 条采样):销售额、利润、大类、折扣 |
get_ship_mode_analysis() | List[Dict] | 按邮寄方式:邮寄方式、销售额、利润、订单数、平均发货天数 |
get_discount_impact() | List[Dict] | 折扣影响:折扣、平均销售额、平均利润、订单数 |
get_segment_analysis() | List[Dict] | 按细类:细类、销售额、利润、客户数、平均订单金额 |
get_quarterly_trend() | List[Dict] | 季度趋势:年季(如"2023Q1")、销售额、利润 |
get_subcategory_rank() | List[Dict] | 子类排名:子类、销售额、利润、数量(升序) |
get_city_top(n) | List[Dict] | Top N 城市:城市、销售额、利润 |
get_monthly_heatmap() | List[Dict] | 月度热力图:年份、月份、销售额 |
产品分析(8 个方法)
| 方法 | 返回结构 | 说明 |
|---|
get_category_pie() | List[Dict] | 大类占比:大类、销售额 |
get_subcategory_detail() | List[Dict] | 子类明细:大类、子类、销售额、利润、数量 |
get_product_top(n) | List[Dict] | Top N 产品:产品名称、销售额、利润、数量 |
get_quantity_dist() | List[Dict] | 数量分布:数量、订单数、销售额 |
get_category_discount() | List[Dict] | 大类折扣:大类、平均折扣、最大折扣、折扣订单比例 |
get_price_range_dist() | List[Dict] | 价格区间分布:价格区间、订单数、总利润 |
get_profit_rate_by_category() | List[Dict] | 大类利润率:大类、销售额、利润、利润率 |
get_product_trend() | List[Dict] | 产品趋势:年份、大类、销售额 |
客户分析(8 个方法)
| 方法 | 返回结构 | 说明 |
|---|
get_customer_segment_pie() | List[Dict] | 细类占比:细类、销售额 |
get_customer_value_tiers() | List[Dict] | 价值分层:价值层级、客户数、平均消费、平均订单数(5 层:<1k, 1k-5k, 5k-10k, 10k-50k, >50k) |
get_customer_region_dist() | List[Dict] | 地区分布:地区、客户数、销售额 |
get_purchase_frequency() | List[Dict] | 购买频次:频次区间、客户数(5 个区间) |
get_customer_scatter() | List[Dict] | 散点图(最多 500 条):客户ID、总销售额、总利润、细类 |
get_customer_retention() | List[Dict] | 留存分析:年份、留存客户数、留存率 |
get_segment_monthly() | List[Dict] | 细类月度:月份、细类、销售额 |
get_customer_value_heatmap() | List[Dict] | 热力图:地区、细类、销售额 |
时间分析(9 个方法)
| 方法 | 返回结构 | 说明 |
|---|
get_year_quarter_heatmap() | List[Dict] | 年-季度热力图:年份、季度、销售额 |
get_monthly_trend_with_prediction() | List[Dict] | 月度趋势+预测:月份、销售额、is_prediction(含 6 个月线性回归预测) |
get_growth_rate() | List[Dict] | 增长率:年份、销售额、增长率(YoY%) |
get_shipping_analysis() | List[Dict] | 发货分析:邮寄方式、平均发货天数、订单数 |
get_year_comparison() | List[Dict] | 年度对比:年份、销售额、利润、订单数、客户数 |
get_weekday_analysis() | List[Dict] | 星期分布:星期、销售额、订单数 |
get_hourly_pattern() | List[Dict] | 月度模式:月份、销售额、订单数 |
get_cumulative_sales() | List[Dict] | 累计趋势:月份、销售额、累计销售额 |
get_seasonal_decomposition() | List[Dict] | 季节指数:月序号、销售额、季节指数 |
地理分析(8 个方法)
| 方法 | 返回结构 | 说明 |
|---|
get_province_sales_map() | List[Dict] | 省级销售地图:省/自治区、销售额、利润、订单数 |
get_region_pie() | List[Dict] | 地区占比:地区、销售额 |
get_province_profit_map() | List[Dict] | 省级利润地图:省/自治区、利润、利润率 |
get_city_top_geo(n) | List[Dict] | Top N 城市:城市、地区、销售额、利润 |
get_region_trend() | List[Dict] | 地区趋势:月份、地区、销售额 |
get_province_quantity() | List[Dict] | 省级数量:省/自治区、数量、平均数量 |
get_region_profit_compare() | List[Dict] | 地区利润对比:地区、大类、销售额、利润 |
get_logistics_efficiency() | List[Dict] | 物流效率:地区、邮寄方式、平均发货天数、订单数 |
数据挖掘(6 个方法)
| 方法 | 返回结构 | 说明 |
|---|
get_rfm_analysis() | List[Dict] | RFM 分群:客户分群、客户数、平均消费、平均频率(4 类:重要价值/发展/保持/挽留客户) |
get_market_basket() | List[Dict] | 购物篮分析:类别1、类别2、共现次数 |
get_profit_prediction_data() | List[Dict] | 利润预测数据:月份、销售额、利润、订单数、平均折扣、月份序号 |
get_customer_lifetime_value() | List[Dict] | 客户终身价值(最多 500 条):客户ID、总销售额、CLV、订单数、月均消费 |
get_sales_pattern_clusters() | List[Dict] | 聚类数据(最多 300 条):大类、总销售额、总利润、平均折扣、销售数量(标准化值) |
get_anomaly_detection() | Dict | 异常检测:normal(最多 200 条)和anomalies(最多 50 条),含 Z 分数(阈值 |z| > 2.5) |
8. 前端页面设计
8.1 基础布局(base.html)
侧边栏导航结构:
| 分组 | 菜单项 | 图标 | 路由 |
|---|
| — | 数据总览 | fa-laptop | / |
| 数据分析 | 销售分析 | fa-line-chart | /sales |
| 数据分析 | 产品分析 | fa-cube | /product |
| 数据分析 | 客户分析 | fa-users | /customer |
| 数据分析 | 时间分析 | fa-clock-o | /time |
| 数据分析 | 地理分析 | fa-map-marker | /geo |
| 高级分析 | 数据挖掘 | fa-database | /mining |
| 系统管理 | 个人中心 | fa-user | /profile |
| 系统管理 | 管理后台 | fa-cog | /admin(仅管理员可见) |
头部区域:Logo、菜单折叠按钮、页面标题、实时时钟、用户头像下拉菜单(个人中心、退出登录)
CSS/JS 加载链:
normalize.min.css → bootstrap.min.css → font-awesome.min.css → themify-icons.css → pe-icon-7-stroke.min.css → style.css → custom.css echarts.min.js → jquery.min.js → popper.min.js → bootstrap.min.js → main.js
8.2 数据总览页(dashboard.html)
KPI 卡片(8 个,两行):
- 第一行:总销售额、总利润、总订单数、客户总数
- 第二行:平均客单价、产品数量、平均折扣、利润率
图表(6 个):
- 月度销售趋势(双 Y 轴折线图:销售额 + 利润)
- 销售额 TOP10 产品(水平条形图)
- 品类销售占比(环形饼图)
- 年度销售对比(分组柱状图)
- 地区销售分布(柱状图)
8.3 分析页面模板
每个分析页面遵循统一模式:
- 页面 HTML 只包含图表容器
<div id="chart-xxx" class="chart-container"> {% block extra_js %}中定义loadChart(url, callback)函数- 每个图表通过
loadChart('/api/<module>/<type>', function(data) { ... })异步加载 - 回调函数中初始化 ECharts 实例、设置 option、绑定 resize 事件
8.4 图表配色方案
| 颜色 | 色值 | 用途 |
|---|
| 主色(金色) | #d4943a | 销售额、主要指标 |
| 绿色 | #67C23A | 利润、正值 |
| 橙色 | #F5A623 | 辅助指标 |
| 红色 | #D0021B | 负值、预警 |
| 紫色 | #ab8ce4 | 辅助系列 |
8.5 登录注册页
- 超市背景图片(
static/img/auth-bg.jpg)+ 半透明黑色遮罩 - 白色卡片居中,绿色顶部条(#00c292)
- Bootstrap 标准表单组件
- 注册页含密码强度指示器(弱/中等/强)
9. API 接口文档
9.1 通用规则
- 认证:所有 API 需要登录(
@login_required),未登录返回 302 重定向到/login - 请求方式:GET
- 响应格式:JSON
- 数据来源:
SalesAnalyzer实例方法 - 错误处理:无效的
chart_type返回{"error": "无效的图表类型"},HTTP 400
9.2 API 调用示例
// 前端调用模式functionloadChart(url,cb){fetch(url).then(r=>r.json()).then(cb).catch(e=>console.error(e));}// 使用loadChart('/api/sales/scatter',function(data){varchart=echarts.init(document.getElementById('chart-scatter'));chart.setOption({// ECharts 配置...series:[{data:data.map(d=>[d['销售额'],d['利润']])}]});});
9.3 API 返回示例
GET/api/time/growth_rate
[{"年份":2020,"销售额":2299900.86,"增长率":null},{"年份":2021,"销售额":2574078.43,"增长率":11.92},{"年份":2022,"销售额":2950065.87,"增长率":14.60},{"年份":2023,"销售额":3276097.04,"增长率":11.05}]
10. 用户认证与权限
10.1 认证机制
- 使用
Flask-Login管理用户会话 - 密码使用
werkzeug.security的generate_password_hash/check_password_hash(PBKDF2 算法) - Session 密钥配置在
config.py的SECRET_KEY
10.2 权限模型
| 角色 | 权限 |
|---|
| 普通用户 (user) | 查看所有分析页面、编辑个人信息、修改密码 |
| 管理员 (admin) | 普通用户全部权限 + 管理后台(用户管理 + 数据 CRUD) |
10.3 权限检查
- 页面路由:
@login_required装饰器 - 管理路由:
@login_required+current_user.is_admin()手动检查 - 前端侧边栏:
{% if current_user.is_admin() %}控制管理后台链接可见性
11. 管理后台
11.1 用户管理
- 用户列表表格(显示 ID、用户名、邮箱、手机号、角色、注册时间)
- 角色切换:下拉选择框直接提交表单
- 删除用户:带确认弹窗,不可删除自己
11.2 数据管理
- Tab 切换界面(用户管理 / 数据管理)
- 数据列表:分页显示(每页 20 条),含 ID、订单编号、客户、产品名称、大类、地区、销售额、利润
- 添加数据:模态框表单(19 个字段)
- 编辑数据:模态框表单(自动回填当前值)
- 删除数据:带确认弹窗
- 利润正数显示绿色,负数显示红色
11.3 Tab 状态保持
操作(添加/编辑/删除/翻页)后通过 URL 参数?tab=data保持在数据管理 Tab。
12. 部署与运行
12.1 环境准备
# 克隆项目后进入 code 目录cdcode# 安装依赖pipinstall-rrequirements.txt
12.2 启动应用
python app.py
启动时自动:
- 创建 SQLite 数据库表
- 插入默认用户(admin/user)
- 从 CSV 导入销售数据
- 在
http://localhost:5003启动服务
12.3 默认账号
| 用户名 | 密码 | 角色 |
|---|
| admin | admin123 | 管理员 |
| user | user123 | 普通用户 |
12.4 配置说明
编辑config.py可修改:
SECRET_KEY— Session 密钥(生产环境应更换)SQLALCHEMY_DATABASE_URI— 数据库连接字符串DATA_FILE— CSV 数据文件路径
13. 依赖清单
requirements.txt
Flask==3.0.0 Flask-Login==0.6.3 Flask-SQLAlchemy==3.1.1 pandas==2.1.4 numpy==1.26.2 scikit-learn==1.3.2 xlrd==2.0.1 Werkzeug==3.0.1
前端 CDN 依赖(本地已包含)
| 库 | 版本 | 文件 |
|---|
| jQuery | 3.x | static/js/jquery.min.js |
| Bootstrap | 4.x | static/css/bootstrap.min.css+static/js/bootstrap.min.js |
| ECharts | 5.x | static/js/echarts.min.js |
| Font Awesome | 4.x | static/css/font-awesome.min.css |
| Popper.js | 1.x | static/js/popper.min.js |