基于Flask的美国新泽西州自动售货机销售数据可视化分析系统 --- 技术文档
目录
1. 项目概述
1.1 项目背景
本系统是一个面向美国新泽西州自动售货机运营场景的销售数据可视化分析平台。系统以 Flask 为后端框架、MySQL 为数据存储、ECharts 为可视化引擎,将来自 5 个销售地点、4 大商品品类的 9,617 条交易记录,通过多维度交互式仪表盘呈现,帮助运营者快速洞察经营状况、优化商品策略、提升设备效率。
1.2 核心能力
| 能力维度 | 说明 |
|---|---|
| 经营总览 | 关键KPI指标卡片、销售趋势、品类占比、支付方式分布 |
| 时间洞察 | 日历热力图、星期维度分析、月度品类趋势、环比增长 |
| 深度分析 | 价格-品类交叉分析、地点畅销商品、核心洞察卡片 |
| 商品策略 | 商品贡献树图、SKU四象限矩阵、价格带分布、ABC分类 |
| 设备画像 | 设备效率象限图、雷达图、品类组合分析、设备排名 |
| 运营建议 | 销售预测、补货优先级、ABC分类表、品类-设备堆叠图 |
| 交易明细 | 可筛选/搜索/分页的交易列表,支持CSV导出 |
| 数据管理 | 管理员对销售记录的增删改查(CRUD) |
1.3 数据范围
- 数据来源 :
vending_machine_sales.csv(1.5 MB,9,617条记录) - 时间跨度:2025年全年(日期经过归一化处理)
- 地理覆盖:5个销售地点(Brunswick Sq Mall 等)
- 商品品类:Food、Carbonated、Non Carbonated、Water
- 支付方式:Cash、Credit




















2. 系统架构
2.1 整体架构图
┌─────────────────────────────────────────────────────────┐
│ 浏览器 (Browser) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Bootstrap │ │ ECharts │ │ CSS │ │ JS │ │
│ │ 5.3.3 │ │ 5.5.1 │ │ styles │ │dashboard │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└────────────────────────┬────────────────────────────────┘
│ HTTP (GET/POST/PUT/DELETE)
▼
┌─────────────────────────────────────────────────────────┐
│ Flask Application (app.py) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 路由层 │ │ 认证装饰 │ │ 过滤构建 │ │ 错误处理 │ │
│ │ Routes │ │ Decorators│ │build_where│ │ Handler │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 模板渲染 │ │ JSON序列化│ │
│ │ Jinja2 │ │json_value │ │
│ └──────────┘ └──────────┘ │
└────────────────────────┬────────────────────────────────┘
│ PyMySQL (TCP 3306)
▼
┌─────────────────────────────────────────────────────────┐
│ MySQL 数据库 │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ users │ │ vending_sales│ │ import_logs │ │
│ └──────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
▲
│ CSV 导入
┌─────────────────────────────────────────────────────────┐
│ vending_machine_sales.csv │
│ (9,617条原始交易记录 + 脚本工具) │
└─────────────────────────────────────────────────────────┘
2.2 请求处理流程
浏览器发起请求
│
▼
Flask 路由匹配
│
▼
@app.before_request ──→ load_logged_in_user() 从 session 加载用户
│
▼
装饰器检查 ──→ @login_required / @admin_required
│
├─ 页面请求 ──→ render_template() ──→ Jinja2 渲染 HTML
│
└─ API请求 ──→ build_where() 构建动态 WHERE 子句
│
▼
SQL 查询 (PyMySQL + DictCursor)
│
▼
json_rows() 序列化 Decimal/date
│
▼
jsonify() 返回 JSON 响应
2.3 架构特点
- 单文件后端 :所有路由、API端点、辅助函数集中在
app.py(1,167行),无蓝图拆分 - 连接按需分配 :通过 Flask
g对象实现每个请求一个数据库连接,请求结束自动关闭 - 统一过滤机制 :
build_where()函数为所有API端点提供一致的动态SQL过滤能力 - 前端无框架:使用原生 JavaScript + ECharts,无 React/Vue 等框架依赖
3. 技术栈与依赖
3.1 后端技术栈
| 技术 | 版本要求 | 用途 |
|---|---|---|
| Python | 3.10+ | 运行时环境 |
| Flask | >= 3.1 | Web框架,路由、模板、会话管理 |
| PyMySQL | >= 1.1 | MySQL数据库驱动,纯Python实现 |
| Werkzeug | >= 3.1 | WSGI工具库,密码哈希(generate_password_hash / check_password_hash) |
| pandas | >= 2.2 | 仅在Jupyter探索笔记本中使用,应用代码未引用 |
3.2 前端技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Bootstrap | 5.3.3 (CDN) | 响应式布局、表单、组件 |
| Bootstrap Icons | 1.11.3 (CDN) | 图标库 |
| ECharts | 5.5.1 (CDN) | 数据可视化图表引擎 |
| Google Fonts | - | Fira Sans(正文)+ Fira Code(等宽数字) |
| 原生 JavaScript | ES6+ | 页面逻辑、API调用、图表渲染 |
3.3 数据库
| 技术 | 说明 |
|---|---|
| MySQL | 5.7+ / 8.0+,字符集 utf8mb4,排序规则 utf8mb4_unicode_ci |
| 存储引擎 | InnoDB |
3.4 依赖安装
bash
pip install -r requirements.txt
requirements.txt 内容:
Flask>=3.1
PyMySQL>=1.1
pandas>=2.2
Werkzeug>=3.1
4. 项目目录结构
America/
│
├── app.py # 主应用文件 (1,167行)
│ # - 所有页面路由 (12个)
│ # - 所有API端点 (22个)
│ # - 认证/授权装饰器
│ # - 辅助函数
│ # - 错误处理
│
├── config.py # 配置类 (21行)
│ # - 数据库连接参数
│ # - 应用密钥
│ # - CSV路径、显示年份、分页大小
│
├── database.py # 数据库模块 (250行)
│ # - 连接管理
│ # - Schema DDL(3张表)
│ # - 管理员种子数据
│ # - CSV导入逻辑
│
├── requirements.txt # Python依赖清单
├── vending_machine_sales.csv # 原始数据文件 (1.5MB, 9,618行)
├── 关于自动售货机销售数据集的探索.ipynb # 数据探索Jupyter笔记本
├── .gitignore # Git忽略规则
├── 技术文档.md # 本文档
│
├── scripts/ # 工具脚本
│ ├── setup_database.py # 一键初始化:建库、建表、种子数据、导入CSV
│ ├── convert_sales_year.py # CSV文件内日期年份转换
│ └── shift_database_year.py # 数据库内日期年份迁移
│
├── static/ # 静态资源
│ ├── css/
│ │ └── styles.css # 自定义样式 (942行)
│ └── js/
│ └── dashboard.js # 前端逻辑 (1,333行)
│
└── templates/ # Jinja2模板
├── auth_base.html # 认证页面基础布局(登录/注册分屏)
├── login.html # 登录页
├── register.html # 注册页
├── base.html # 主应用布局(侧边栏+顶栏)
├── dashboard.html # 经营总览
├── time_insights.html # 时间洞察
├── analysis.html # 深度分析
├── product_strategy.html # 商品策略
├── machine_portrait.html # 设备画像
├── operations.html # 运营建议
├── records.html # 交易明细
├── profile.html # 个人中心
├── admin_data.html # 数据管理(管理员)
└── db_error.html # 数据库错误页
5. 数据库设计
5.1 ER关系图
┌──────────────────────┐ ┌──────────────────────────────────┐
│ users │ │ vending_sales │
├──────────────────────┤ ├──────────────────────────────────┤
│ PK id INT │ │ PK id BIGINT │
│ username VARCHAR │ │ status VARCHAR(40) │
│ email VARCHAR │ │ device_id VARCHAR(80) │
│ password_hash │ │ location VARCHAR(120) │
│ real_name VARCHAR│ │ machine VARCHAR(160) │
│ phone VARCHAR │ │ product VARCHAR(255) │
│ role VARCHAR │ │ category VARCHAR(80) │
│ last_login DATETIME│ │ transaction_id BIGINT │
│ created_at │ │ trans_date DATE │
│ updated_at │ │ payment_type VARCHAR(40) │
└──────────────────────┘ │ r_coil INT │
│ r_price DECIMAL(10,2) │
│ r_qty INT │
│ m_coil INT │
│ m_price DECIMAL(10,2) NULL │
│ m_qty INT │
│ line_total DECIMAL(10,2) │
│ trans_total DECIMAL(10,2) │
│ prcd_date DATE │
│ created_at DATETIME │
└──────────────────────────────────┘
┌──────────────────────┐
│ import_logs │
├──────────────────────┤
│ PK id INT │
│ file_name VARCHAR│
│ row_count INT │
│ imported_by VARCHAR│
│ imported_at DATETIME│
└──────────────────────┘
5.2 表结构详细定义
5.2.1 users 表 --- 用户账户
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | INT | AUTO_INCREMENT, PK | 用户ID |
| username | VARCHAR(80) | NOT NULL, UNIQUE | 用户名(登录凭证) |
| VARCHAR(160) | NOT NULL, UNIQUE | 邮箱(登录凭证+唯一性) | |
| password_hash | VARCHAR(255) | NOT NULL | Werkzeug生成的密码哈希 |
| real_name | VARCHAR(80) | DEFAULT '' | 真实姓名 |
| phone | VARCHAR(40) | DEFAULT '' | 联系电话 |
| role | VARCHAR(30) | DEFAULT 'analyst' | 角色:analyst(分析师) / admin(管理员) |
| last_login | DATETIME | NULL | 最后登录时间 |
| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| updated_at | DATETIME | DEFAULT CURRENT_TIMESTAMP ON UPDATE | 更新时间 |
默认种子数据 :admin / admin123 / admin@example.com,角色为 admin。
5.2.2 vending_sales 表 --- 销售记录
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | BIGINT | AUTO_INCREMENT, PK | 记录ID |
| status | VARCHAR(40) | NOT NULL | 交易状态(如 "Processed") |
| device_id | VARCHAR(80) | NOT NULL | 设备编号(如 "VJ300320611") |
| location | VARCHAR(120) | NOT NULL | 销售地点(如 "Brunswick Sq Mall") |
| machine | VARCHAR(160) | NOT NULL | 机器名称(如 "BSQ Mall x1366 - ATT") |
| product | VARCHAR(255) | NOT NULL | 商品名称 |
| category | VARCHAR(80) | NOT NULL | 商品品类(Food/Carbonated/Non Carbonated/Water) |
| transaction_id | BIGINT | NOT NULL | 交易编号 |
| trans_date | DATE | NOT NULL | 交易日期 |
| payment_type | VARCHAR(40) | NOT NULL | 支付方式(Cash/Credit) |
| r_coil | INT | NOT NULL | 实际货道编号 |
| r_price | DECIMAL(10,2) | NOT NULL | 实际售价(美元) |
| r_qty | INT | NOT NULL | 实际数量 |
| m_coil | INT | NOT NULL | 主机货道编号 |
| m_price | DECIMAL(10,2) | NULL | 主机价格(可为空) |
| m_qty | INT | NOT NULL | 主机数量 |
| line_total | DECIMAL(10,2) | NOT NULL | 行小计金额 |
| trans_total | DECIMAL(10,2) | NOT NULL | 交易总金额 |
| prcd_date | DATE | NOT NULL | 处理日期 |
| created_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 记录创建时间 |
索引:
| 索引名 | 字段 | 用途 |
|---|---|---|
| idx_sales_date | trans_date | 日期范围过滤、趋势分析 |
| idx_sales_location | location | 地点筛选、地点维度聚合 |
| idx_sales_machine | machine | 机器筛选、设备效率分析 |
| idx_sales_category | category | 品类筛选、品类维度聚合 |
| idx_sales_product | product | 商品筛选、商品排名 |
| idx_sales_payment | payment_type | 支付方式筛选 |
| idx_sales_transaction | transaction_id | 交易去重统计 |
5.2.3 import_logs 表 --- 导入日志
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | INT | AUTO_INCREMENT, PK | 日志ID |
| file_name | VARCHAR(255) | NOT NULL | 导入文件名 |
| row_count | INT | NOT NULL | 导入行数 |
| imported_by | VARCHAR(80) | DEFAULT 'system' | 导入操作者 |
| imported_at | DATETIME | DEFAULT CURRENT_TIMESTAMP | 导入时间 |
6. 后端设计
6.1 应用初始化 (app.py 第26-28行)
python
app = Flask(__name__)
app.config.from_object(Config)
Flask应用从 config.Config 类加载配置,所有配置项均支持环境变量覆盖。
6.2 数据库连接管理
连接工厂 (database.py 第35-47行):
python
def connect(use_database=True):
kwargs = {
"host": Config.DB_HOST,
"port": Config.DB_PORT,
"user": Config.DB_USER,
"password": Config.DB_PASSWORD,
"charset": Config.DB_CHARSET,
"cursorclass": DictCursor, # 返回字典而非元组
"autocommit": False, # 手动事务管理
}
if use_database:
kwargs["database"] = Config.DB_NAME
return pymysql.connect(**kwargs)
请求级连接 (app.py 第38-48行):
python
def get_db():
if "db" not in g:
g.db = connect()
return g.db
@app.teardown_appcontext
def close_db(error=None):
conn = g.pop("db", None)
if conn is not None:
conn.close()
每个HTTP请求通过Flask的 g 对象获取一个数据库连接,请求结束后自动关闭,避免连接泄漏。
6.3 认证与授权
6.3.1 用户加载 (app.py 第104-116行)
python
@app.before_request
def load_logged_in_user():
user_id = session.get("user_id")
g.user = None
if user_id is not None:
g.user = query_one(
"SELECT id, username, email, real_name, phone, role, last_login, created_at "
"FROM users WHERE id=%s", (user_id,))
每次请求前从 session 中读取 user_id,查询数据库加载用户信息到 g.user。
6.3.2 登录验证装饰器 (app.py 第82-89行)
python
def login_required(view):
@wraps(view)
def wrapped_view(*args, **kwargs):
if g.user is None:
return redirect(url_for("login", next=request.path))
return view(*args, **kwargs)
return wrapped_view
6.3.3 管理员验证装饰器 (app.py 第92-101行)
python
def admin_required(view):
@wraps(view)
def wrapped_view(*args, **kwargs):
if g.user is None:
return redirect(url_for("login", next=request.path))
if g.user.get("role") != "admin":
return jsonify({"success": False, "message": "权限不足,仅管理员可操作。"}), 403
return view(*args, **kwargs)
return wrapped_view
6.4 统一过滤机制
6.4.1 过滤参数收集 (app.py 第124-129行)
python
FILTER_MAP = {
"location": "location",
"machine": "machine",
"category": "category",
"payment_type": "payment_type",
}
def get_filter_values():
values = {key: request.args.get(key, "").strip() for key in FILTER_MAP}
values["date_from"] = request.args.get("date_from", "").strip()
values["date_to"] = request.args.get("date_to", "").strip()
values["search"] = request.args.get("search", "").strip()
return values
6.4.2 动态WHERE构建 (app.py 第132-155行)
python
def build_where(filters=None, include_search=False):
filters = filters or get_filter_values()
clauses = ["1=1"]
params = []
for arg_name, column_name in FILTER_MAP.items():
value = filters.get(arg_name)
if value:
clauses.append(f"{column_name}=%s")
params.append(value)
if filters.get("date_from"):
clauses.append("trans_date >= %s")
params.append(filters["date_from"])
if filters.get("date_to"):
clauses.append("trans_date <= %s")
params.append(filters["date_to"])
if include_search and filters.get("search"):
keyword = f"%{filters['search']}%"
clauses.append("(product LIKE %s OR machine LIKE %s OR location LIKE %s OR category LIKE %s)")
params.extend([keyword, keyword, keyword, keyword])
return " WHERE " + " AND ".join(clauses), params
此函数是系统的核心过滤引擎,所有 22 个 API 端点均调用它来构建参数化的 SQL WHERE 子句,确保:
- 支持按地点、机器、品类、支付方式精确过滤
- 支持日期范围过滤(
trans_date >= AND <=) - 支持关键词模糊搜索(可选,仅交易明细使用)
- 全部使用参数化查询,防止SQL注入
6.5 JSON序列化辅助
python
def json_value(value):
if isinstance(value, Decimal):
return float(value) # MySQL DECIMAL → Python float
if isinstance(value, date):
return value.isoformat() # Python date → "2025-01-15"
return value
def json_rows(rows):
return [{key: json_value(value) for key, value in row.items()} for row in rows]
6.6 错误处理
python
@app.errorhandler(pymysql.MySQLError)
def handle_database_error(error):
if request.path.startswith("/api/"):
return jsonify({"success": False, "message": str(error)}), 500
return render_template("db_error.html", error=error), 500
数据库异常统一捕获:API请求返回JSON错误,页面请求渲染错误模板。
6.7 页面路由一览
| 路由 | 方法 | 装饰器 | 模板 | 说明 |
|---|---|---|---|---|
/ |
GET | --- | --- | 重定向到 dashboard 或 login |
/login |
GET/POST | --- | login.html |
登录(支持用户名或邮箱) |
/register |
GET/POST | --- | register.html |
用户注册 |
/logout |
GET | @login_required |
--- | 清除session,跳转登录 |
/dashboard |
GET | @login_required |
dashboard.html |
经营总览 |
/time-insights |
GET | @login_required |
time_insights.html |
时间洞察 |
/analysis |
GET | @login_required |
analysis.html |
深度分析 |
/product-strategy |
GET | @login_required |
product_strategy.html |
商品策略 |
/machine-portrait |
GET | @login_required |
machine_portrait.html |
设备画像 |
/operations |
GET | @login_required |
operations.html |
运营建议 |
/records |
GET | @login_required |
records.html |
交易明细 |
/records/export |
GET | @login_required |
--- | CSV导出 |
/profile |
GET/POST | @login_required |
profile.html |
个人中心 |
/admin/data |
GET | @admin_required |
admin_data.html |
数据管理 |
7. 前端设计
7.1 布局系统
7.1.1 认证页面布局 (auth_base.html)
采用左右分屏设计:
- 左侧:品牌展示区,深色背景,显示系统名称、数据集统计(9,617条记录、5个地点、4个品类)
- 右侧:表单区域,包含登录或注册表单
7.1.2 主应用布局 (base.html)
┌──────────────────────────────────────────────────────┐
│ 侧边栏 (260px固定) │ 主内容区 (可滚动) │
│ │ ┌────────────────────────┐ │
│ ┌──────────────────┐ │ │ 顶栏:页面标题+用户菜单 │ │
│ │ 系统Logo/标题 │ │ ├────────────────────────┤ │
│ ├──────────────────┤ │ │ │ │
│ │ 经营总览 │ │ │ 页面内容区域 │ │
│ │ 时间洞察 │ │ │ (卡片+图表+表格) │ │
│ │ 深度分析 │ │ │ │ │
│ │ 商品策略 │ │ │ │ │
│ │ 设备画像 │ │ │ │ │
│ │ 运营建议 │ │ │ │ │
│ │ 交易明细 │ │ │ │ │
│ │ ────────────── │ │ │ │ │
│ │ 个人中心 │ │ │ │ │
│ │ 数据管理 (admin) │ │ │ │ │
│ └──────────────────┘ │ └────────────────────────┘ │
└──────────────────────────────────────────────────────┘
- 侧边栏:深海军蓝背景(
#0f1f3d),260px固定宽度 - 主内容区:可滚动,最大宽度自适应
- 响应式:940px以下侧边栏收缩为图标网格,640px以下单列布局
7.2 CSS设计系统 (static/css/styles.css)
7.2.1 色彩方案
| 色彩角色 | 色值 | 用途 |
|---|---|---|
| 主色 (Primary) | #1e40af |
按钮、链接、活跃状态 |
| 强调色 (Accent) | #f59e0b |
高亮指标、重要标记 |
| 侧边栏背景 | #0f1f3d |
深海军蓝 |
| 成功色 | 绿色系 | 正增长、成功提示 |
| 危险色 | 玫红色系 | 负增长、警告提示 |
| 表面色 | 浅灰白 | 卡片背景、分割线 |
7.2.2 卡片系统
.metric-card:KPI指标卡片,带图标、数值、标签.insight-card:洞察卡片,带标题、描述、徽章.signal-card:信号卡片,用于设备画像.panel:通用内容面板,带阴影和圆角- 所有卡片支持 hover 上浮效果(
transform: translateY(-2px))
7.2.3 响应式断点
| 断点 | 布局变化 |
|---|---|
| > 1180px | 双列网格布局 |
| 940px - 1180px | 侧边栏收缩为图标网格 |
| 640px - 940px | 单列布局 |
| < 640px | 紧凑单列,缩小间距 |
7.2.4 无障碍支持
css
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; animation: none !important; }
}
7.3 JavaScript架构 (static/js/dashboard.js)
7.3.1 启动流程
javascript
document.addEventListener("DOMContentLoaded", boot);
async function boot() {
const page = document.body.dataset.page; // 从 <body data-page="xxx"> 读取
const options = await fetchJson("/api/options");
fillSelect("#filter-location", options.locations);
fillSelect("#filter-machine", options.machines);
// ... 填充所有过滤器下拉框
const loaders = {
dashboard: loadDashboard,
time_insights: loadTimeInsights,
analysis: loadAnalysis,
product_strategy: loadProductStrategy,
machine_portrait: loadMachinePortrait,
operations: loadOperations,
records: loadRecords,
admin_data: loadAdminSales,
};
if (loaders[page]) loaders[page]();
}
7.3.2 API调用封装
javascript
async function fetchJson(url) {
const resp = await fetch(url);
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const json = await resp.json();
if (!json.success) throw new Error(json.message || "请求失败");
return json.data;
}
7.3.3 过滤器参数收集
javascript
function paramsFromForm() {
const params = new URLSearchParams();
document.querySelectorAll(".filter-bar select, .filter-bar input").forEach(el => {
if (el.value) params.set(el.name, el.value);
});
return params;
}
7.3.4 页面加载器并行策略
每个页面的加载函数使用 Promise.all() 并行请求多个API端点:
javascript
async function loadDashboard() {
const params = paramsFromForm();
const [summary, trend, category, topProducts, machineSales, payment, locationCat] =
await Promise.all([
fetchJson(`/api/summary?${params}`),
fetchJson(`/api/sales-trend?${params}`),
fetchJson(`/api/category-share?${params}`),
fetchJson(`/api/top-products?${params}`),
fetchJson(`/api/machine-sales?${params}`),
fetchJson(`/api/payment-mix?${params}`),
fetchJson(`/api/location-category?${params}`),
]);
renderKPICards(summary);
renderTrendChart(trend);
// ... 渲染所有图表
}
7.3.5 图表类型汇总
| 图表类型 | ECharts图表 | 使用页面 |
|---|---|---|
| 折线+柱状组合图 | type: 'line' + type: 'bar' |
经营总览(销售趋势) |
| 饼图/环形图 | type: 'pie' |
经营总览(品类占比)、经营总览(支付分布) |
| 水平柱状图 | type: 'bar' (横向) |
经营总览(Top15商品)、深度分析(价格品类) |
| 日历热力图 | type: 'heatmap' + calendar |
时间洞察 |
| 堆叠面积图 | type: 'line' + stack + areaStyle |
时间洞察(月度品类趋势) |
| 树图 | type: 'treemap' |
商品策略(品类贡献) |
| 散点图 | type: 'scatter' |
商品策略(SKU矩阵)、设备画像(效率象限) |
| 堆叠柱状图 | type: 'bar' + stack |
商品策略(价格带)、设备画像(品类组合) |
| 雷达图 | type: 'radar' |
运营建议(设备效率对比) |
| 预测折线图 | type: 'line' (历史+预测双系列) |
运营建议(销售预测) |
8. 功能模块详解
8.1 经营总览 (Dashboard)
页面文件 :templates/dashboard.html
加载函数 :loadDashboard()
调用API :/api/summary, /api/sales-trend, /api/category-share, /api/top-products, /api/machine-sales, /api/payment-mix, /api/location-category
功能说明:
| 组件 | 说明 |
|---|---|
| KPI指标卡片 (x4) | 总销售额、总交易笔数、总销量、机器/商品数量 |
| 销售趋势图 | 按月或按日的销售额折线+交易笔数柱状组合图 |
| 品类占比饼图 | 4个品类的销售额占比环形图 |
| Top15商品柱状图 | 按销售额或销量排名的水平柱状图 |
| 机器销售排名 | 各机器的销售额排名柱状图 |
| 支付方式分布 | Cash/Credit的交易笔数占比饼图 |
| 地点-品类热力图 | 地点×品类的销售额交叉热力矩阵 |
8.2 时间洞察 (Time Insights)
页面文件 :templates/time_insights.html
加载函数 :loadTimeInsights()
调用API :/api/time-calendar, /api/weekday-performance, /api/month-category-trend, /api/growth-overview
功能说明:
| 组件 | 说明 |
|---|---|
| 指标卡片 (x4) | 活跃天数、峰值日、最佳星期、最佳增长月份 |
| 日历热力图 | 全年365天的每日销售额热力图,颜色深浅表示销售额高低 |
| 星期维度分析 | 周一至周日的平均日销售额对比柱状图 |
| 月度品类趋势 | 4个品类的月度销售额堆叠面积图 |
| 增长明细表 | 每月的销售额、销量、笔数、客单价、日均销售额、环比增长率 |
8.3 深度分析 (Analysis)
页面文件 :templates/analysis.html
加载函数 :loadAnalysis()
调用API :/api/insights, /api/price-category, /api/location-products
功能说明:
| 组件 | 说明 |
|---|---|
| 核心洞察卡片 (x4) | 销售额最高机器、销量最高商品、销量最高地点、支付方式排名 |
| 价格-品类柱状图 | Top20品类+价格组合的水平柱状图 |
| 地点畅销商品表 | 每个地点的最畅销商品、销售额、销量 |
| 分析发现 | 关键数据洞察总结 |
8.4 商品策略 (Product Strategy)
页面文件 :templates/product_strategy.html
加载函数 :loadProductStrategy()
调用API :/api/category-share, /api/product-matrix, /api/category-price-bands, /api/top-products
功能说明:
| 组件 | 说明 |
|---|---|
| 指标卡片 (x4) | 明星SKU数量、Top商品、补货压力SKU、长尾SKU数量 |
| 品类贡献树图 | 按品类→商品层级展示销售额贡献的矩形树图 |
| SKU四象限矩阵 | 以销售额为X轴、销量为Y轴的散点图,划分为四个象限 |
| 价格带堆叠柱状图 | 各品类在5个价格带(<1.50, 1.50-$2.49, ...)的销售额分布 |
| 策略表格 | 商品详情表,含SKU定位标签(明星/利润/走量/长尾) |
SKU四象限分类逻辑:
高销售额
│
利润SKU │ 明星SKU
(高销低量) │ (高销高量)
│
─────────────────────┼─────────────────── 高销量
│
长尾观察 │ 走量SKU
(低销低量) │ (低销高量)
│
低销售额
阈值:销售额和销量均取第70百分位数作为分界线
8.5 设备画像 (Machine Portrait)
页面文件 :templates/machine_portrait.html
加载函数 :loadMachinePortrait()
调用API :/api/machine-efficiency, /api/machine-sales, /api/category-machine
功能说明:
| 组件 | 说明 |
|---|---|
| 指标卡片 (x4) | 机器总数、收入最高机器、日均表现最佳、低效预警数量 |
| 效率象限散点图 | X轴=日均销售额,Y轴=SKU效率(销售额/SKU数),气泡大小=总销量 |
| 日均销售排名柱状图 | 各机器的日均销售额排名 |
| 品类组合堆叠柱状图 | 各机器的品类销售额占比堆叠图 |
| 设备画像详情表 | 每台机器的详细效率指标 |
8.6 运营建议 (Operations)
页面文件 :templates/operations.html
加载函数 :loadOperations()
调用API :/api/forecast, /api/machine-efficiency, /api/category-machine, /api/product-abc, /api/replenishment
功能说明:
| 组件 | 说明 |
|---|---|
| 预测英雄卡片 | 未来14天预测销售额、基准日均销售额 |
| 高优先级补货数 | 需要优先补货的商品数量 |
| ABC覆盖率 | A类商品数量及其销售额占比 |
| 预测折线图 | 历史60天实际销售额 + 7日移动平均 + 14天预测线 |
| 设备雷达图 | 多维度效率对比(日均销售额、SKU数、品类数、活跃天数、客单价) |
| 品类-设备堆叠图 | 各机器的品类组合销售额 |
| ABC分类表 | 商品ABC分级(A=累计70%、B=累计90%、C=其余) |
| 补货优先级表 | Top30补货候选商品,含优先级标签和建议操作 |
预测算法 (/api/forecast):
python
# 1. 取最近14天的日均销售额作为基准
recent = data[-14:]
baseline = sum(item["sales"] for item in recent) / len(recent)
# 2. 对未来每一天应用周期调整
for offset in range(1, days + 1):
weekly_adjustment = 1 + (((offset - 1) % 7) - 3) * 0.015
forecast_sales = baseline * weekly_adjustment
补货优先级规则 (/api/replenishment):
| 优先级 | 条件 | 标签颜色 | 建议操作 |
|---|---|---|---|
| 高 | 总销量 >= 100 或 日均销量 >= 1.2 | 红色 (danger) | 优先补货 |
| 中 | 总销量 >= 45 或 日均销量 >= 0.55 | 黄色 (warning) | 稳定补货 |
| 低 | 其他 | 灰色 (secondary) | 观察陈列 |
8.7 交易明细 (Records)
页面文件 :templates/records.html
加载函数 :loadRecords()
调用API :/api/records, /records/export
功能说明:
- 分页表格展示所有交易记录(默认每页20条)
- 支持按地点、机器、品类、支付方式、日期范围过滤
- 支持关键词搜索(商品名、机器名、地点名、品类名)
- 支持CSV导出(UTF-8 BOM编码,兼容Excel中文)
8.8 个人中心 (Profile)
页面文件 :templates/profile.html
路由处理 :/profile (GET/POST)
功能说明:
- 查看/编辑个人资料(真实姓名、电话、邮箱)
- 修改密码(需验证原密码,新密码至少6位)
- 邮箱唯一性校验
8.9 数据管理 (Admin Data)
页面文件 :templates/admin_data.html
加载函数 :loadAdminSales()
权限要求 :仅 admin 角色
功能说明:
- 分页表格展示所有销售记录(含全部18个字段)
- 弹窗表单新增记录
- 弹窗表单编辑记录
- 确认后删除记录
9. API接口文档
9.1 通用说明
- 基础路径 :所有API以
/api/开头 - 认证:所有API需要登录(session cookie)
- 响应格式:
json
{
"success": true,
"data": { ... }
}
错误响应:
json
{
"success": false,
"message": "错误描述"
}
- 过滤参数(所有分析类API通用):
| 参数 | 类型 | 说明 |
|---|---|---|
| location | string | 地点精确匹配 |
| machine | string | 机器精确匹配 |
| category | string | 品类精确匹配 |
| payment_type | string | 支付方式精确匹配 |
| date_from | string | 起始日期 (YYYY-MM-DD) |
| date_to | string | 结束日期 (YYYY-MM-DD) |
9.2 接口列表
GET /api/options --- 获取过滤器选项
说明:返回所有过滤器下拉框的可选值。
响应:
json
{
"success": true,
"data": {
"locations": ["Brunswick Sq Mall", ...],
"machines": ["BSQ Mall x1366 - ATT", ...],
"categories": ["Carbonated", "Food", "Non Carbonated", "Water"],
"payments": ["Cash", "Credit"],
"dates": { "min_date": "2025-01-01", "max_date": "2025-12-31" }
}
}
GET /api/summary --- 经营汇总KPI
响应:
json
{
"success": true,
"data": {
"row_count": 9617,
"transaction_count": 8500,
"product_count": 120,
"machine_count": 5,
"sales_total": 28500.00,
"quantity_total": 9617,
"avg_line_total": 2.96,
"min_date": "2025-01-01",
"max_date": "2025-12-31"
}
}
GET /api/sales-trend --- 销售趋势
参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
| granularity | month | month 按月 / day 按日 |
响应:
json
{
"success": true,
"data": [
{ "period": "2025-01", "sales": 2500.00, "quantity": 850, "transactions": 720 },
...
]
}
GET /api/time-calendar --- 日历热力图数据
响应:
json
{
"success": true,
"data": [
{
"date": "2025-01-01",
"week_index": 1,
"weekday": 2,
"sales": 150.50,
"quantity": 52,
"transactions": 45
},
...
]
}
GET /api/weekday-performance --- 星期维度表现
响应:
json
{
"success": true,
"data": [
{
"weekday": 0,
"sales": 3500.00,
"quantity": 1200,
"transactions": 1000,
"active_days": 52,
"avg_line_total": 2.92,
"daily_sales": 67.31,
"daily_quantity": 23.08
},
...
]
}
weekday 值:0=周一, 1=周二, ..., 6=周日
GET /api/month-category-trend --- 月度品类趋势
响应:
json
{
"success": true,
"data": [
{ "period": "2025-01", "category": "Carbonated", "sales": 800.00, "quantity": 280 },
{ "period": "2025-01", "category": "Food", "sales": 600.00, "quantity": 200 },
...
]
}
GET /api/growth-overview --- 月度增长概览
响应:
json
{
"success": true,
"data": [
{
"period": "2025-01",
"sales": 2500.00,
"quantity": 850,
"transactions": 720,
"active_days": 31,
"avg_ticket": 3.47,
"daily_sales": 80.65,
"mom_rate": null
},
{
"period": "2025-02",
"sales": 2800.00,
"mom_rate": 12.00
},
...
]
}
mom_rate:环比增长率(%),第一个月为 null。
GET /api/forecast --- 销售预测
参数:
| 参数 | 默认值 | 范围 | 说明 |
|---|---|---|---|
| days | 14 | 7-45 | 预测天数 |
响应:
json
{
"success": true,
"data": {
"history": [
{ "date": "2025-11-01", "sales": 100.00, "quantity": 35, "moving_avg": 95.50 },
...
],
"forecast": [
{ "date": "2025-12-18", "sales": 98.50, "quantity": 34 },
...
],
"baseline": 97.50,
"projected_sales": 1379.00
}
}
GET /api/category-share --- 品类占比
响应:
json
{
"success": true,
"data": [
{ "name": "Carbonated", "sales": 8500.00, "quantity": 2800, "row_count": 2800 },
...
]
}
GET /api/machine-efficiency --- 设备效率
响应:
json
{
"success": true,
"data": [
{
"location": "Brunswick Sq Mall",
"machine": "BSQ Mall x1366 - ATT",
"sales": 6500.00,
"quantity": 2200,
"row_count": 2200,
"transactions": 1900,
"active_days": 300,
"product_count": 45,
"category_count": 4,
"avg_line_total": 2.95,
"daily_sales": 21.67,
"daily_quantity": 7.33,
"sales_per_sku": 144.44
},
...
]
}
GET /api/category-machine --- 品类×设备交叉
响应:
json
{
"success": true,
"data": [
{ "machine": "BSQ Mall x1366 - ATT", "category": "Carbonated", "sales": 2000.00, "quantity": 700 },
...
]
}
GET /api/replenishment --- 补货建议
响应:
json
{
"success": true,
"data": [
{
"location": "Brunswick Sq Mall",
"machine": "BSQ Mall x1366 - ATT",
"product": "Red Bull - Energy Drink",
"category": "Carbonated",
"quantity": 150,
"sales": 525.00,
"active_days": 120,
"transactions": 140,
"avg_price": 3.50,
"daily_quantity": 1.25,
"priority": "高",
"action": "优先补货",
"badge": "danger"
},
...
]
}
GET /api/product-abc --- ABC分类
响应:
json
{
"success": true,
"data": [
{
"product": "Red Bull - Energy Drink",
"category": "Carbonated",
"sales": 1500.00,
"quantity": 430,
"grade": "A",
"cumulative_ratio": 5.26,
"sales_ratio": 5.26
},
...
]
}
分类规则:A类=累计占比≤70%,B类=累计占比≤90%,C类=其余。
GET /api/product-matrix --- SKU矩阵
响应:
json
{
"success": true,
"data": [
{
"product": "Red Bull - Energy Drink",
"category": "Carbonated",
"sales": 1500.00,
"quantity": 430,
"transactions": 400,
"active_days": 200,
"machine_count": 3,
"avg_price": 3.50,
"daily_quantity": 2.15,
"sales_per_machine": 500.00,
"position": "明星 SKU",
"badge": "primary"
},
...
]
}
position 值:明星 SKU / 利润 SKU / 走量 SKU / 长尾观察
GET /api/category-price-bands --- 品类价格带
响应:
json
{
"success": true,
"data": [
{
"category": "Carbonated",
"price_band": "$2.50-$3.49",
"sort_order": 3,
"sales": 3000.00,
"quantity": 1000,
"product_count": 15
},
...
]
}
价格带定义:< $1.50, $1.50-$2.49, $2.50-$3.49, $3.50-$4.49, >= $4.50
GET /api/top-products --- Top商品
参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
| metric | sales | sales 按销售额排序 / quantity 按销量排序 |
响应:
json
{
"success": true,
"data": [
{ "product": "...", "category": "...", "sales": 1500.00, "quantity": 430, "transactions": 400 },
...
]
}
返回前15条。
GET /api/machine-sales --- 机器销售排名
响应:
json
{
"success": true,
"data": [
{
"location": "Brunswick Sq Mall",
"machine": "BSQ Mall x1366 - ATT",
"sales": 6500.00,
"quantity": 2200,
"transactions": 1900,
"avg_line_total": 2.95
},
...
]
}
GET /api/payment-mix --- 支付方式分布
响应:
json
{
"success": true,
"data": [
{ "payment_type": "Credit", "sales": 18000.00, "quantity": 6000, "row_count": 6000, "transactions": 5200 },
{ "payment_type": "Cash", "sales": 10500.00, "quantity": 3617, "row_count": 3617, "transactions": 3300 }
]
}
GET /api/location-category --- 地点×品类交叉
响应:
json
{
"success": true,
"data": [
{ "location": "Brunswick Sq Mall", "category": "Carbonated", "sales": 2000.00, "quantity": 700 },
...
]
}
GET /api/price-category --- 价格×品类Top20
响应:
json
{
"success": true,
"data": [
{ "category": "Carbonated", "price": 3.50, "label": "Carbonated $3.50", "sales": 1500.00, "quantity": 430 },
...
]
}
GET /api/location-products --- 地点畅销商品
响应:
json
{
"success": true,
"data": [
{ "location": "Brunswick Sq Mall", "product": "Red Bull", "sales": 500.00, "quantity": 143 },
...
]
}
每个地点返回销量最高的一个商品。
GET /api/insights --- 核心洞察
响应:
json
{
"success": true,
"data": {
"top_machine": { "machine": "...", "location": "...", "sales": 6500.00, "quantity": 2200 },
"top_product": { "product": "...", "category": "...", "sales": 1500.00, "quantity": 430 },
"top_location": { "location": "...", "sales": 8000.00, "quantity": 2800 },
"payment_rank": [
{ "payment_type": "Credit", "row_count": 6000 },
{ "payment_type": "Cash", "row_count": 3617 }
]
}
}
GET /api/records --- 交易记录分页
额外参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
| page | 1 | 页码 |
| size | 20 | 每页条数 (5-100) |
| search | --- | 关键词搜索 |
响应:
json
{
"success": true,
"data": [
{
"id": 1,
"trans_date": "2025-01-01",
"location": "...",
"machine": "...",
"product": "...",
"category": "...",
"payment_type": "Credit",
"r_price": 3.50,
"r_qty": 1,
"line_total": 3.50,
"trans_total": 3.50,
"transaction_id": 14515778905
},
...
],
"page": 1,
"size": 20,
"total": 9617
}
GET /records/export --- CSV导出
说明:导出当前过滤条件下的所有交易记录为CSV文件。
响应 :Content-Type: text/csv; charset=utf-8,文件名 vending_sales_export.csv,UTF-8 BOM编码。
GET /api/admin/sales --- 管理员记录列表
权限 :仅 admin 角色
参数 :同 /api/records,额外返回全部18个字段。
POST /api/admin/sales --- 新增记录
权限 :仅 admin 角色
请求体 (JSON):
json
{
"status": "Processed",
"device_id": "VJ300320611",
"location": "Brunswick Sq Mall",
"machine": "BSQ Mall x1366 - ATT",
"product": "Red Bull - Energy Drink",
"category": "Carbonated",
"transaction_id": 14515778905,
"trans_date": "2025-01-01",
"payment_type": "Credit",
"r_coil": 148,
"r_price": 3.50,
"r_qty": 1,
"m_coil": 148,
"m_price": 3.50,
"m_qty": 1,
"line_total": 3.50,
"trans_total": 3.50,
"prcd_date": "2025-01-01"
}
GET /api/admin/sales/{id} --- 获取单条记录
权限 :仅 admin 角色
PUT /api/admin/sales/{id} --- 更新记录
权限 :仅 admin 角色,请求体同新增。
DELETE /api/admin/sales/{id} --- 删除记录
权限 :仅 admin 角色
10. 数据处理流程
10.1 CSV导入流程
vending_machine_sales.csv
│
▼
import_sales_csv() [database.py]
│
├─ 1. init_schema() --- 确保数据库和表存在
│
├─ 2. TRUNCATE TABLE vending_sales --- 清空旧数据
│
├─ 3. 逐行读取CSV (UTF-8-BOM编码)
│ │
│ ▼
│ _row_to_tuple() --- 字段映射与类型转换
│ ├─ 文本字段:_clean_text() 去除空白
│ ├─ 日期字段:_parse_date() MM/DD/YYYY → date对象,年份替换为 DATA_DISPLAY_YEAR
│ ├─ 金额字段:_parse_decimal() 字符串 → Decimal
│ └─ 整数字段:_parse_int() 字符串 → int
│
├─ 4. 批量插入 (每500条一批)
│ cursor.executemany(insert_sql, batch)
│
├─ 5. 记录导入日志到 import_logs 表
│
└─ 6. conn.commit() 提交事务
10.2 日期归一化处理
原始CSV中的日期可能是任意年份,系统通过 _parse_date() 函数将所有日期的年份统一替换为 DATA_DISPLAY_YEAR(默认2025):
python
def _parse_date(value):
parsed = datetime.strptime(value.strip(), "%m/%d/%Y").date()
try:
return parsed.replace(year=Config.DATA_DISPLAY_YEAR)
except ValueError:
# 处理2月29日等闰年边界情况
return parsed.replace(year=Config.DATA_DISPLAY_YEAR, day=28)
10.3 工具脚本
scripts/setup_database.py --- 一键初始化
bash
python scripts/setup_database.py
执行流程:
- 创建数据库(如不存在)
- 创建三张表(users, vending_sales, import_logs)
- 插入默认管理员账户
- 导入CSV数据到 vending_sales 表
scripts/convert_sales_year.py --- CSV日期年份转换
bash
python scripts/convert_sales_year.py 2024
直接修改CSV文件中的日期年份,用于将数据集调整到不同年份展示。
scripts/shift_database_year.py --- 数据库日期年份迁移
bash
python scripts/shift_database_year.py 2024
直接在数据库中更新已导入记录的日期年份,无需重新导入CSV。
11. 部署与运行
11.1 环境要求
| 组件 | 要求 |
|---|---|
| Python | 3.10 或更高版本 |
| MySQL | 5.7 或更高版本 |
| pip | 最新版本 |
11.2 安装步骤
步骤一:克隆/下载项目
bash
cd "F:\code\131-基于Flask的美国新泽西州自动售货机销售数据可视化分析系统\America"
步骤二:安装Python依赖
bash
pip install -r requirements.txt
步骤三:配置MySQL
确保MySQL服务已启动,默认配置为:
- 主机:
localhost - 端口:
3306 - 用户:
root - 密码:
123456
如需修改,设置环境变量或编辑 config.py。
步骤四:初始化数据库
bash
python scripts/setup_database.py
此命令会自动创建数据库、建表、插入管理员账户、导入CSV数据。
步骤五:启动应用
bash
python app.py
应用将在 http://127.0.0.1:5000 启动(Flask开发服务器,debug模式)。
步骤六:访问系统
- 打开浏览器访问
http://127.0.0.1:5000 - 使用默认管理员账户登录:
- 用户名:
admin - 密码:
admin123
- 用户名:
- 也可注册新账户(默认角色为
analyst)
11.3 生产环境部署建议
当前系统使用Flask内置开发服务器,生产环境建议:
-
WSGI服务器:使用 Gunicorn 或 uWSGI 替代 Flask 内置服务器
bashpip install gunicorn gunicorn -w 4 -b 0.0.0.0:5000 app:app -
反向代理:使用 Nginx 作为反向代理,处理静态文件和SSL
-
密钥安全 :通过环境变量设置
SECRET_KEY,不要使用默认值 -
数据库安全:修改默认MySQL密码,限制访问权限
12. 配置说明
12.1 配置项一览 (config.py)
| 配置项 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|
| SECRET_KEY | SECRET_KEY |
design-131-sell-dev-secret |
Flask session密钥 |
| DB_HOST | DB_HOST |
localhost |
MySQL主机地址 |
| DB_PORT | DB_PORT |
3306 |
MySQL端口 |
| DB_USER | DB_USER |
root |
MySQL用户名 |
| DB_PASSWORD | DB_PASSWORD |
123456 |
MySQL密码 |
| DB_NAME | DB_NAME |
design_131_sell |
数据库名 |
| DB_CHARSET | --- | utf8mb4 |
数据库字符集 |
| CSV_PATH | CSV_PATH |
项目目录/vending_machine_sales.csv |
CSV文件路径 |
| DATA_DISPLAY_YEAR | DATA_DISPLAY_YEAR |
2025 |
数据展示年份 |
| RECORD_PAGE_SIZE | RECORD_PAGE_SIZE |
20 |
交易记录分页大小 |
12.2 环境变量配置示例
bash
# Windows PowerShell
$env:SECRET_KEY = "my-production-secret-key"
$env:DB_PASSWORD = "strong-password"
$env:DB_NAME = "vending_analytics"
# Linux/macOS
export SECRET_KEY="my-production-secret-key"
export DB_PASSWORD="strong-password"
export DB_NAME="vending_analytics"
13. 安全设计
13.1 认证安全
| 措施 | 实现 |
|---|---|
| 密码存储 | Werkzeug generate_password_hash() --- PBKDF2-SHA256 + 随机盐 |
| 密码验证 | Werkzeug check_password_hash() --- 时序安全比较 |
| 会话管理 | Flask session(服务端签名的cookie) |
| 密码策略 | 最少6位,注册和修改时强制校验 |
13.2 授权安全
| 措施 | 实现 |
|---|---|
| 路由保护 | @login_required 装饰器,未登录重定向到登录页 |
| 角色控制 | @admin_required 装饰器,非管理员返回403 |
| 页面级控制 | 侧边栏管理员入口仅对 admin 角色可见 |
13.3 数据安全
| 措施 | 实现 |
|---|---|
| SQL注入防护 | 全部使用参数化查询(%s 占位符),无字符串拼接SQL |
| XSS防护 | Jinja2模板默认自动转义HTML |
| CSRF | Flask session cookie 的 SameSite 属性(未显式设置CSRF token) |
13.4 当前安全局限
| 问题 | 说明 |
|---|---|
| 无CSRF Token | 表单提交缺少CSRF保护,存在跨站请求伪造风险 |
| 无登录限流 | 无登录失败次数限制,存在暴力破解风险 |
| 无HTTPS | 开发环境未配置SSL/TLS |
| 默认密钥 | SECRET_KEY 使用硬编码默认值,生产环境必须修改 |
| 无审计日志 | 除导入日志外,无用户操作审计 |
14. 已知问题与改进方向
14.1 已知问题
| 编号 | 问题 | 说明 |
|---|---|---|
| 1 | pandas依赖冗余 | requirements.txt 中声明了 pandas>=2.2,但应用代码未引用 |
| 2 | 单文件架构 | app.py 1,167行,dashboard.js 1,333行,可维护性随功能增长下降 |
| 3 | 无前端构建工具 | CSS/JS无压缩、无Source Map、无模块化 |
| 4 | 预测算法简单 | 基于移动平均+周期调整,未考虑季节性、趋势、异常值 |
| 5 | 无数据校验 | API端点对输入数据的校验不够严格 |
| 6 | 连接池缺失 | 每次请求新建数据库连接,高并发下性能瓶颈 |
14.2 改进方向
| 方向 | 建议 |
|---|---|
| 架构重构 | 拆分Flask蓝图(Blueprint),按功能模块组织路由 |
| 前端工程化 | 引入Vite/Webpack打包,使用Vue/React重构前端 |
| 数据库优化 | 引入连接池(SQLAlchemy + Pool),优化慢查询 |
| 认证增强 | 添加CSRF Token、登录限流、JWT token支持 |
| 预测模型 | 使用Prophet或ARIMA等时间序列模型替代简单移动平均 |
| 测试覆盖 | 添加单元测试(pytest)和API集成测试 |
| 部署优化 | Docker容器化部署,CI/CD流水线 |
| 数据源扩展 | 支持实时数据接入、多数据源合并 |
附录A:CSV数据字段映射
| CSV列名 | 数据库字段 | 类型转换 | 说明 |
|---|---|---|---|
| Status | status | _clean_text() |
交易状态 |
| Device ID | device_id | _clean_text() |
设备编号 |
| Location | location | _clean_text() |
销售地点 |
| Machine | machine | _clean_text() |
机器名称 |
| Product | product | _clean_text() |
商品名称 |
| Category | category | _clean_text() |
商品品类 |
| Transaction | transaction_id | _parse_int() |
交易编号 |
| TransDate | trans_date | _parse_date() + 年份替换 |
交易日期 |
| Type | payment_type | _clean_text() |
支付方式 |
| RCoil | r_coil | _parse_int() |
实际货道 |
| RPrice | r_price | _parse_decimal() |
实际售价 |
| RQty | r_qty | _parse_int() |
实际数量 |
| MCoil | m_coil | _parse_int() |
主机货道 |
| MPrice | m_price | _parse_decimal() |
主机价格 |
| MQty | m_qty | _parse_int() |
主机数量 |
| LineTotal | line_total | _parse_decimal() |
行小计 |
| TransTotal | trans_total | _parse_decimal() |
交易总额 |
| Prcd Date | prcd_date | _parse_date() + 年份替换 |
处理日期 |
附录B:前端页面与API对应关系
| 页面 | data-page | 调用的API端点 |
|---|---|---|
| 经营总览 | dashboard |
/api/options, /api/summary, /api/sales-trend, /api/category-share, /api/top-products, /api/machine-sales, /api/payment-mix, /api/location-category |
| 时间洞察 | time_insights |
/api/options, /api/time-calendar, /api/weekday-performance, /api/month-category-trend, /api/growth-overview |
| 深度分析 | analysis |
/api/options, /api/insights, /api/price-category, /api/location-products |
| 商品策略 | product_strategy |
/api/options, /api/category-share, /api/product-matrix, /api/category-price-bands, /api/top-products |
| 设备画像 | machine_portrait |
/api/options, /api/machine-efficiency, /api/machine-sales, /api/category-machine |
| 运营建议 | operations |
/api/options, /api/forecast, /api/machine-efficiency, /api/category-machine, /api/product-abc, /api/replenishment |
| 交易明细 | records |
/api/options, /api/records |
| 数据管理 | admin_data |
/api/options, /api/admin/sales |
附录C:ECharts配置参考
系统中所有图表均使用 ECharts 5.5.1,以下为核心配置模式:
javascript
// 通用图表初始化模式
const chart = echarts.init(document.getElementById("chart-dom"));
chart.setOption({
title: { text: "图表标题", left: "center" },
tooltip: { trigger: "axis" },
legend: { bottom: 0 },
xAxis: { type: "category", data: [...] },
yAxis: { type: "value" },
series: [{ type: "bar", data: [...] }]
});
// 窗口自适应
window.addEventListener("resize", () => chart.resize());
文档版本:1.0
最后更新:2025-05-20
项目版本:基于Flask的美国新泽西州自动售货机销售数据可视化分析系统