130-基于Python的体育用品销售数据可视化分析系统

体育用品销售数据可视化分析系统 --- 完整技术文档

版本:v1.0 | 最后更新:2026-05-20


目录

  1. 项目概述
  2. 技术栈与依赖
  3. 项目目录结构
  4. 数据模型
  5. 后端架构设计
  6. 前端架构设计
  7. [API 接口规范](#API 接口规范)
  8. 前端模块详细设计
  9. 样式系统与响应式设计
  10. 核心算法详解
  11. 关键实现细节
  12. 启动与部署
  13. 扩展建议

一、项目概述

1.1 系统简介

本系统是一个面向体育用品销售场景的 数据可视化分析平台,采用 Python Flask 后端 + 原生 HTML/CSS/JS 前端的轻量级架构。系统从 Excel 文件加载 1000 条销售订单数据,提供七大功能模块:

模块 功能定位 核心能力
经营总览 Dashboard KPI 指标、趋势图、品类/区域/客户摘要
深度分析 Analysis 洞察卡片、利润排行、利润率排行、客户价值、低利润预警
销售预测 Forecast 线性回归+移动平均混合预测,置信度衰减
可视图表 Charts 12 种独立图表面板(柱状图、环形图、气泡图、热力图等)
订单明细 Records 可排序、可分页、支持筛选联动的订单列表
数据管理 Data CRUD 操作、批量导入、Excel/CSV 导出
个人中心 Profile 用户偏好、筛选快照、退出登录

1.2 设计目标

  • 零外部前端依赖:不使用 ECharts、Chart.js、D3 等第三方图表库,所有图表通过原生 SVG 字符串拼接实现
  • 单文件后端 :全部后端逻辑集中在 app.py 一个文件中,便于理解和维护
  • Excel 即数据库 :以 .xlsx 文件作为唯一数据存储,降低部署门槛
  • 筛选全局联动:顶部筛选栏的所有控件变更后,全页面数据同步刷新















二、技术栈与依赖

2.1 后端技术栈

技术 版本要求 用途
Python >= 3.8 运行环境
Flask >= 3.1.0 Web 框架,路由、模板渲染、JSON API
pandas >= 2.2.0 数据加载、筛选、聚合、类型转换
openpyxl >= 3.1.0 Excel 文件读写引擎

2.2 前端技术栈

技术 用途
HTML5 页面结构,Jinja2 模板引擎渲染
CSS3 样式系统,CSS Grid 布局,CSS 自定义属性(设计令牌),conic-gradient 环形图
ES6+ JavaScript 业务逻辑,Fetch API 异步请求,DOM 操作
SVG 所有图表渲染(polyline、rect、circle、path 等原生元素)

2.3 依赖安装

bash 复制代码
pip install -r requirements.txt

requirements.txt 内容:

复制代码
Flask>=3.1.0
pandas>=2.2.0
openpyxl>=3.1.0

三、项目目录结构

复制代码
code/
├── app.py                                  # Flask 后端主程序(908 行)
├── requirements.txt                        # Python 依赖声明
├── 体育用品销售数据_1000行.xlsx            # 数据源文件(1000 行 × 14 列)
├── README.md                               # 项目说明文档
├── 技术文档.md                             # 本文档
├── .gitignore                              # Git 忽略规则
├── templates/                              # Jinja2 模板目录
│   ├── index.html                          # 主仪表盘页面(607 行)
│   └── auth.html                           # 登录注册页面(89 行)
├── static/                                 # 静态资源目录
│   ├── css/
│   │   └── app.css                         # 全局样式表(1972 行)
│   └── js/
│       ├── app.js                          # 主应用逻辑(1401 行)
│       └── auth.js                         # 登录注册逻辑(145 行)
└── server*.log                             # 运行日志(已 gitignore)

文件职责划分:

文件 行数 职责
app.py 908 路由定义、数据加载、筛选引擎、聚合函数、预测算法、CRUD 操作、错误处理
index.html 607 主页面 HTML 结构,7 个 page-section、2 个 modal、筛选面板
auth.html 89 登录/注册双面板页面,Tab 切换
app.css 1972 设计令牌、Grid 布局、响应式断点、图表样式、动画、Toast 通知
app.js 1401 页面路由、数据请求、图表渲染、CRUD 交互、用户认证、筛选联动
auth.js 145 登录注册表单处理、localStorage 读写、密码编码

四、数据模型

4.1 数据源

  • 文件名体育用品销售数据_1000行.xlsx
  • 格式 :Microsoft Excel .xlsx(由 openpyxl 引擎读写)
  • 规模:1000 行 × 14 列
  • 角色:系统唯一数据存储,无数据库

4.2 字段定义

字段名 含义 类型 示例 说明
订单ID 订单编号 文本 ORD000001 唯一标识,格式 ORD + 6 位数字
销售日期 下单日期 日期 2024-03-15 YYYY-MM-DD 格式
产品类别 商品分类 文本 运动鞋 如运动鞋、健身器材、球类等
产品名称 具体商品 文本 跑步鞋A 产品级别标识
单位成本 采购单价 数值 200.00 元,进货成本
销售单价 零售单价 数值 500.00 元,对外售价
订单数量 购买件数 整数 3 ---
销售总金额 订单金额 数值 1500.00 派生:销售单价 × 订单数量
总成本 订单成本 数值 600.00 派生:单位成本 × 订单数量
总利润 订单利润 数值 900.00 派生:销售总金额 − 总成本
利润率 利润占比 数值 0.6000 派生:总利润 / 销售总金额
销售区域 大区 文本 华东 如华东、华南、华北、华中、西南、西北、东北
所属省份 省份 文本 上海 ---
客户类型 客户分类 文本 企业 如个人、企业、团购等

4.3 派生字段

系统在数据加载时自动计算两个额外字段(不写入 Excel):

字段 计算方式 用途
月份 销售日期.dt.to_period("M").astype(str) → 如 2024-03 月度聚合分组键
销售日期文本 销售日期.dt.strftime("%Y-%m-%d") → 如 2024-03-15 前端展示

4.4 数据加载流程

复制代码
load_sales_data()(带 @lru_cache 缓存)
  ├── 读取 Excel 文件 (pd.read_excel)
  ├── 校验必需列是否存在(缺失则抛 ValueError)
  ├── 日期列 → pd.to_datetime(无效值变 NaT)
  ├── 数值列 → pd.to_numeric + fillna(0)
  ├── 文本列 → fillna("未知").astype(str).strip()
  ├── 丢弃日期为空的行 (dropna)
  ├── 计算「月份」和「销售日期文本」
  └── 返回 DataFrame

五、后端架构设计

5.1 整体架构

复制代码
┌─────────────────────────────────────────────────┐
│                   Flask App                      │
├──────────┬──────────┬──────────┬─────────────────┤
│ 页面路由  │ 数据 API  │ CRUD API │   错误处理器    │
│ 3 个 GET  │ 8 个 GET  │ 5 个     │   2 个          │
├──────────┴──────────┴──────────┴─────────────────┤
│              筛选引擎 apply_filters()              │
├──────────────────────────────────────────────────┤
│         数据层 load_sales_data() + lru_cache      │
├──────────────────────────────────────────────────┤
│            Excel 文件 (.xlsx)                     │
└──────────────────────────────────────────────────┘

5.2 常量定义(app.py 顶部)

python 复制代码
BASE_DIR = Path(__file__).resolve().parent
DATA_FILE = BASE_DIR / "体育用品销售数据_1000行.xlsx"

DATE_COLUMN = "销售日期"
NUMERIC_COLUMNS = ["单位成本", "销售单价", "订单数量", "销售总金额", "总成本", "总利润", "利润率"]

FILTER_COLUMNS = {
    "category": "产品类别",
    "region": "销售区域",
    "province": "所属省份",
    "customer_type": "客户类型",
}

TABLE_COLUMNS = [
    "订单ID", "销售日期", "产品类别", "产品名称", "销售区域", "所属省份",
    "客户类型", "订单数量", "销售总金额", "总成本", "总利润", "利润率",
]

SORTABLE_COLUMNS = set(TABLE_COLUMNS) | {"单位成本", "销售单价"}
REQUIRED_COLUMNS = set(TABLE_COLUMNS) | {"单位成本", "销售单价"}

5.3 数据加载层

python 复制代码
@lru_cache(maxsize=1)
def load_sales_data() -> pd.DataFrame:
  • 使用 functools.lru_cache(maxsize=1) 实现单例缓存
  • 首次调用读取 Excel 文件,后续调用直接返回内存中的 DataFrame
  • 所有 CRUD 操作后调用 load_sales_data.cache_clear() 强制刷新

5.4 筛选引擎

python 复制代码
def apply_filters(df: pd.DataFrame) -> pd.DataFrame:

request.args 读取 URL 查询参数,依次应用以下筛选:

参数 筛选逻辑 示例
start_date df[销售日期] >= pd.to_datetime(start_date) 2024-01-01
end_date df[销售日期] <= pd.to_datetime(end_date) 2024-12-31
category df[产品类别] == value 精确匹配 运动鞋
region df[销售区域] == value 精确匹配 华东
province df[所属省份] == value 精确匹配 上海
customer_type df[客户类型] == value 精确匹配 企业
keyword 订单ID / 产品名称 / 所属省份 三列模糊搜索(str.contains 跑步

5.5 核心聚合函数

函数 签名 输出
grouped_metrics(df, column, limit, ascending) 分组列名 + 可选限制数 [{name, sales, profit, quantity, orders, margin}]
monthly_trend(df) --- [{month, sales, profit, quantity, orders}]
monthly_growth(df) --- [{month, sales, profit, orders, sales_growth, profit_growth, order_growth}]
weekday_metrics(df) --- [{name, sales, profit, quantity, orders}](周一~周日)
price_band_metrics(df) --- [{name, sales, profit, quantity, orders, margin}](6 个价格带)
category_efficiency(df) --- [{name, sales, profit, quantity, orders, avg_price, margin}]
product_profit_matrix(df, limit) 取前 N 条 [{name, category, sales, profit, quantity, orders, margin}]
region_category_stack(df) --- {regions, categories, series}
customer_value_metrics(df) --- [{name, sales, profit, quantity, orders, aov, margin}]
category_region_matrix(df) --- {categories, regions, values}
linear_forecast(values, steps) 历史序列 + 步数 [预测值, ...]

5.6 数据持久化

python 复制代码
def save_data(df: pd.DataFrame) -> None:
    output = df.copy()
    output[DATE_COLUMN] = pd.to_datetime(output[DATE_COLUMN], errors="coerce")
    output.to_excel(DATA_FILE, index=False, engine="openpyxl")
    load_sales_data.cache_clear()
  • 所有写操作(新增、编辑、删除、导入)最终调用 save_data()
  • 写入后自动清除 lru_cache,确保下次读取为最新数据
  • 订单 ID 自动生成逻辑(next_order_id):扫描所有现有 ID 的数字部分,取最大值 +1,格式化为 ORD000001

5.7 辅助工具函数

函数 用途
money(value) 保留 2 位小数的浮点数格式化
ratio(value) 保留 4 位小数的比率格式化
growth_ratio(current, previous) 计算环比增长率,前值为 0 时返回 0
query_int(name, default, minimum, maximum) 从 URL 参数读取整数,带默认值和范围校验
parse_record_body() 从 JSON 请求体解析 9 个订单字段
compute_derived(row) 根据基础字段计算 4 个派生字段(销售总金额、总成本、总利润、利润率)
table_rows(df) 将 DataFrame 转换为前端可用的字典列表

六、前端架构设计

6.1 整体架构:服务端渲染 SPA

复制代码
首次加载 ──→ Flask 渲染 index.html(Jinja2)──→ 浏览器加载 HTML + CSS + JS
                                                         │
页面切换 ──→ JS 切换 .page-section.active ──→ 无刷新跳转
                                                         │
数据加载 ──→ Fetch API 请求 /api/* ──→ JSON 响应 ──→ DOM 渲染
                                                         │
URL 同步 ──→ window.location.hash ──→ 支持浏览器前进/后退

6.2 页面路由机制

app.js 中通过 setActivePage(page) 函数实现:

  1. 更新页面标题、描述(从 pageMeta 映射表读取)
  2. 切换侧边栏导航的 .active
  3. 切换对应的 .page-section.active 类(CSS 控制显隐)
  4. 更新 window.location.hash
  5. 对数据页面隐藏/显示筛选面板和导出按钮
  6. 切换到数据管理页时自动加载记录

pageMeta 映射表:

javascript 复制代码
const pageMeta = {
  overview: ["Dashboard", "销售经营总览", "查看核心 KPI、销售趋势和筛选范围内的整体表现。"],
  analysis: ["Analysis", "销售经营分析", "按品类、区域、客户和产品利润效率拆解经营表现。"],
  forecast: ["Forecast", "销售趋势预测", "基于历史月份走势预估未来 6 个月的销售额、利润和订单量。"],
  charts:   ["Charts", "图表中心", "集中查看品类、区域、省份、产品和热力矩阵图表。"],
  records:  ["Records", "订单明细", "检索、排序和导出筛选范围内的订单数据。"],
  data:     ["Data", "数据管理", "新增、编辑、删除、导入和导出销售订单数据。"],
  profile:  ["Profile", "个人中心", "管理账户偏好和当前分析上下文。"],
  auth:     ["Account", "登录注册", "进入系统或创建新的演示账户。"],
};

6.3 全局筛选联动

所有数据页面共享顶部筛选栏,包含 7 个控件:

控件 HTML 元素 触发方式 联动行为
开始日期 <input type="date"> change 事件 立即刷新全部数据
结束日期 <input type="date"> change 事件 立即刷新全部数据
产品类别 <select> change 事件 立即刷新全部数据
销售区域 <select> change 事件 立即刷新全部数据
所属省份 <select> change 事件 立即刷新全部数据
客户类型 <select> change 事件 立即刷新全部数据
关键词 <input type="search"> Enter 触发刷新

重置按钮恢复默认值后刷新,刷新按钮手动触发刷新。

刷新流程(refreshAll 函数):

javascript 复制代码
async function refreshAll(resetPage = true) {
  if (resetPage) { state.page = 1; state.dmPage = 1; }
  await Promise.all([
    loadOverview(),    // /api/overview
    loadAnalysis(),    // /api/analysis
    loadForecast(),    // /api/forecast
    loadRecords(),     // /api/records
    loadCharts(),      // /api/charts
  ]);
  if (state.activePage === "data") await loadDmRecords();
  renderFilterSnapshot();
}

使用 Promise.all 并行请求 5 个 API 端点,最大化加载效率。

6.4 状态管理

javascript 复制代码
const state = {
  page: 1,           // 订单明细当前页码
  pageSize: 12,      // 订单明细每页条数
  options: null,     // /api/options 响应缓存
  activePage: "overview",  // 当前激活页面
  recordTotal: 0,    // 订单总数
  user: null,        // 当前用户对象
  dmPage: 1,         // 数据管理当前页码
  dmPageSize: 15,    // 数据管理每页条数
  dmTotal: 0,        // 数据管理总数
};

6.5 用户认证方案

采用 纯客户端认证(演示用途):

存储结构(localStorage):

json 复制代码
{
  "name": "张三",
  "account": "zhangsan",
  "password": "base64编码后的密码",
  "role": "体育用品销售运营",
  "defaultPage": "overview"
}

流程:

  • 注册 :用户名 + 密码 → btoa(unescape(encodeURIComponent(password))) 编码 → 存入 localStorage → 自动登录
  • 登录:读取 localStorage → 比对账号和编码后的密码 → 匹配则登录成功
  • 会话state.user 对象 + localStorage 持久化,刷新页面不丢失
  • 退出 :清除 state.user + 移除 localStorage 项

6.6 工具函数

函数 用途
escapeHtml(value) XSS 防护,转义 & < > " '
showToast(message, type) 底部浮层通知,success(绿) / error(红),2.5 秒自动消失
money(value) 格式化为人民币(如 ¥1,500
number(value) 千分位格式化(如 1,000
percent(value) 百分比格式化(如 60.00%
shortMoney(value) 简写金额(如 12.3万1.5亿
fillSelect(select, values, placeholder) 填充下拉选项
getFilters() 从筛选控件读取当前值,构建 URLSearchParams
fetchJson(url) Fetch + JSON 解析 + 错误处理
setLoading(container) 显示"加载中"占位
setEmpty(container, text) 显示"暂无数据"占位

七、API 接口规范

7.1 页面路由(GET,返回 HTML)

方法 路由 函数 说明
GET / index() 渲染 index.html 主仪表盘
GET /login login() 渲染 auth.html(mode="login")
GET /register register() 渲染 auth.html(mode="register")

7.2 数据 API(GET,返回 JSON)

GET /api/options --- 筛选选项

返回所有下拉框的可选值。

响应结构:

json 复制代码
{
  "date_min": "2024-01-01",
  "date_max": "2024-12-31",
  "categories": ["健身器材", "球类", "运动服饰", "运动鞋", "..."],
  "regions": ["华东", "华南", "华北", "华中", "西南", "西北", "东北"],
  "provinces": ["上海", "江苏", "浙江", "..."],
  "customer_types": ["个人", "企业", "团购"]
}
GET /api/overview --- 经营总览

返回 KPI 指标、月度趋势、分组统计和热力矩阵。

响应结构:

json 复制代码
{
  "kpis": {
    "sales": 1234567.89,
    "profit": 234567.89,
    "cost": 1000000.00,
    "orders": 1000,
    "quantity": 2500,
    "average_order_value": 1234.57,
    "profit_margin": 0.1900
  },
  "trend": [
    {"month": "2024-01", "sales": 100000, "profit": 20000, "quantity": 200, "orders": 80}
  ],
  "category": [
    {"name": "运动鞋", "sales": 500000, "profit": 100000, "quantity": 800, "orders": 300, "margin": 0.20}
  ],
  "region": ["..."],
  "province_top": ["..."],
  "product_top": ["..."],
  "customer_type": ["..."],
  "category_region": {
    "categories": ["运动鞋", "健身器材"],
    "regions": ["华东", "华南"],
    "values": [
      {"category": "运动鞋", "region": "华东", "sales": 50000}
    ]
  }
}
GET /api/charts --- 图表数据

返回 7 种图表所需的数据集。

响应结构:

json 复制代码
{
  "monthly_growth": [
    {"month": "2024-01", "sales": 100000, "profit": 20000, "orders": 80,
     "sales_growth": 0, "profit_growth": 0, "order_growth": 0}
  ],
  "weekday": [
    {"name": "周一", "sales": 50000, "profit": 10000, "quantity": 100, "orders": 40}
  ],
  "price_band": [
    {"name": "0-100", "sales": 30000, "profit": 5000, "quantity": 300, "orders": 150, "margin": 0.17}
  ],
  "category_efficiency": [
    {"name": "运动鞋", "sales": 500000, "profit": 100000, "quantity": 800, "orders": 300, "avg_price": 450, "margin": 0.20}
  ],
  "product_profit_matrix": [
    {"name": "跑步鞋A", "category": "运动鞋", "sales": 50000, "profit": 10000, "quantity": 100, "orders": 50, "margin": 0.20}
  ],
  "region_category_stack": {
    "regions": ["华东", "华南"],
    "categories": ["运动鞋", "健身器材"],
    "series": [
      {"region": "华东", "values": [{"category": "运动鞋", "sales": 50000, "share": 0.45}]}
    ]
  },
  "customer_type": ["..."]
}
GET /api/analysis --- 深度分析

返回洞察卡片、排行榜和预警数据。

响应结构:

json 复制代码
{
  "monthly_compare": {
    "current_month": "2024-12",
    "previous_month": "2024-11",
    "sales_growth": 0.05,
    "profit_growth": 0.03,
    "order_growth": 0.02
  },
  "insights": [
    {
      "title": "核心品类",
      "value": "运动鞋",
      "detail": "贡献销售额 500,000,利润率 20.00%。",
      "tone": "primary"
    },
    {
      "title": "重点区域",
      "value": "华东",
      "detail": "区域销售额 400,000,订单 300 单。",
      "tone": "success"
    },
    {
      "title": "利润效率",
      "value": "华南",
      "detail": "该区域利润率 22.50%,适合优先复盘打法。",
      "tone": "warning"
    },
    {
      "title": "整体健康度",
      "value": "19.00%",
      "detail": "筛选范围内共 1000 单,销售额 1,234,567,利润 234,567。",
      "tone": "neutral"
    }
  ],
  "category_profit": ["..."],
  "region_margin": ["..."],
  "customer_value": [
    {"name": "企业", "sales": 500000, "profit": 100000, "quantity": 500, "orders": 200, "aov": 2500, "margin": 0.20}
  ],
  "low_margin_products": [
    {"name": "瑜伽垫B", "sales": 20000, "profit": 500, "quantity": 200, "orders": 100, "margin": 0.025}
  ]
}

洞察卡片生成逻辑:

  1. 核心品类:取销售额最高的品类
  2. 重点区域:取销售额最高的区域
  3. 利润效率:取利润率最高的区域
  4. 整体健康度:综合利润率 + 总订单数 + 总销售额
GET /api/forecast --- 销售预测

请求参数:

参数 类型 默认 范围 说明
steps int 6 1-12 预测期数

响应结构:

json 复制代码
{
  "history": [
    {"month": "2024-01", "sales": 100000, "profit": 20000, "orders": 80}
  ],
  "forecast": [
    {"month": "2025-01", "sales": 110000, "profit": 22000, "orders": 85, "confidence": 0.90},
    {"month": "2025-02", "sales": 112000, "profit": 22500, "orders": 86, "confidence": 0.86}
  ],
  "summary": {
    "last_month": "2024-12",
    "next_month": "2025-01",
    "next_month_sales": 110000,
    "estimated_growth": 0.05,
    "method": "近月移动均值与线性趋势混合预测"
  }
}
GET /api/records --- 订单列表

请求参数:

参数 类型 默认 说明
page int 1 页码(从 1 开始)
page_size int 12 每页条数(5-100)
sort_by string 销售日期 排序列(见 SORTABLE_COLUMNS)
sort_order string desc 排序方向(asc/desc)
+ 所有筛选参数 见 5.4 筛选引擎

响应结构:

json 复制代码
{
  "page": 1,
  "page_size": 12,
  "total": 1000,
  "pages": 84,
  "rows": [
    {
      "订单ID": "ORD001000",
      "销售日期": "2024-12-31",
      "产品类别": "运动鞋",
      "产品名称": "跑步鞋A",
      "销售区域": "华东",
      "所属省份": "上海",
      "客户类型": "个人",
      "订单数量": 2,
      "销售总金额": 1000.00,
      "总成本": 400.00,
      "总利润": 600.00,
      "利润率": 0.6000
    }
  ]
}
GET /api/export.csv --- CSV 导出
  • 返回 UTF-8 BOM 编码的 CSV 文件( 前缀确保 Excel 正确识别中文)
  • 文件名:sales_filtered.csv
  • 按销售日期降序排列
GET /api/export.xlsx --- Excel 导出
  • 返回 .xlsx 格式文件
  • 文件名:sales_filtered.xlsx
  • 使用 openpyxl 引擎写入 BytesIO 缓冲区

7.3 CRUD API

GET /api/records/{id} --- 查询单条记录

响应: 完整记录对象(14 个字段),日期格式化为 YYYY-MM-DD。

错误: 404 {"error": "未找到记录"}

POST /api/records --- 新增订单

请求体(JSON):

json 复制代码
{
  "销售日期": "2024-06-01",
  "产品类别": "运动鞋",
  "产品名称": "跑步鞋A",
  "销售区域": "华东",
  "所属省份": "上海",
  "客户类型": "个人",
  "订单数量": 2,
  "单位成本": 200,
  "销售单价": 500
}

处理流程:

  1. parse_record_body() 解析 9 个基础字段
  2. compute_derived() 计算 4 个派生字段
  3. next_order_id() 自动生成订单 ID
  4. 拼接到现有 DataFrame 末尾
  5. save_data() 写入 Excel 并清除缓存

响应:

json 复制代码
{"success": true, "订单ID": "ORD001001", "total": 1001}
PUT /api/records/{id} --- 编辑订单

请求体: 同 POST,包含要更新的字段。

处理流程:

  1. 定位目标行(df[订单ID] == record_id
  2. 解析并计算派生字段
  3. 更新 DataFrame 中对应行的所有字段
  4. 重新计算 月份销售日期文本
  5. save_data() 写入

响应:

json 复制代码
{"success": true, "订单ID": "ORD001001"}
DELETE /api/records/{id} --- 删除订单

处理: 过滤掉目标行 → save_data() 写入。

响应:

json 复制代码
{"success": true, "total": 999}
POST /api/import --- 批量导入

请求: multipart/form-data

字段 类型 说明
file File .xlsx / .xls / .csv 文件
mode string append(追加)或 replace(覆盖)

处理流程:

  1. 根据文件扩展名选择 pd.read_csvpd.read_excel
  2. 校验必需列
  3. 类型转换(日期、数值)
  4. 追加模式:与现有数据合并;替换模式:直接使用上传数据
  5. 按订单ID去重(drop_duplicates,保留最后一条)
  6. 计算派生字段
  7. save_data() 写入

响应:

json 复制代码
{"success": true, "imported": 50, "total": 1050}

7.4 错误处理

错误处理器 触发条件 响应
@app.errorhandler(FileNotFoundError) 数据文件不存在 500 + 错误信息
@app.errorhandler(ValueError) 数据文件缺少必需列 500 + 错误信息

八、前端模块详细设计

8.1 经营总览(overview)

布局结构:

复制代码
┌─────────────────────────────────────────┐
│  KPI 卡片区(8 张,4×2 Grid)            │
│  销售总额 │ 订单数量 │ 客单价 │ 利润率    │
│  品类数量 │ 客户类型 │ 最佳品类│ 核心区域  │
├──────────────┬──────────────┬────────────┤
│ 品类销售TOP5 │ 区域销售分布  │ 客户类型    │
│ (进度条)     │ (SVG环形图)  │ (进度条)    │
├──────────────┴──────────────┴────────────┤
│  经营洞察卡片(3 列)                      │
│  核心品类 │ 重点区域 │ 利润效率            │
├─────────────────────────────────────────┤
│  月度销售与利润趋势(SVG 折线图)          │
└─────────────────────────────────────────┘

KPI 卡片数据映射:

卡片 主值 副值 色调
销售总额 money(kpis.sales) 利润 money(kpis.profit) primary (蓝)
订单数量 number(kpis.orders) 商品件数 number(kpis.quantity) success (绿)
平均客单价 money(kpis.average_order_value) 总成本 money(kpis.cost) warning (橙)
综合利润率 percent(kpis.profit_margin) 按销售额加权计算 neutral (灰)
品类数量 N 个 覆盖 N 个区域 primary
客户类型 N 类 产品 N 种 success
最佳品类 品类名 销售额 ¥XXX warning
核心区域 区域名 利润率 XX% neutral

区域销售分布 SVG 环形图实现:

使用自定义 SVG 弧形路径(arcPath 函数)绘制,而非 CSS conic-gradient

  • 通过极坐标转直角坐标计算弧形起点和终点
  • 使用 <path d="M...L...A...Z"> 绘制扇形
  • 中心 <circle> 创建环形效果
  • 中心文字显示总额

8.2 深度分析(analysis)

布局结构:

复制代码
┌─────────────────────────────────────────┐
│  洞察卡片(4 列 Grid)                    │
│  核心品类 │ 重点区域 │ 利润效率 │ 整体健康 │
├──────────────┬──────────────────────────┤
│ 品类利润排行  │ 区域利润率排行             │
│ (水平柱状图) │ (排名列表)                │
├──────────────┼──────────────────────────┤
│ 客户价值分析  │ 低利润产品关注             │
│ (表格)       │ (排名列表)                │
└──────────────┴──────────────────────────┘

洞察卡片渲染:

每张卡片包含:

  • SVG 图标(折线图/勾号/感叹号/笑脸,根据 tone 选择)
  • 标题(如"核心品类")
  • 主值(如"运动鞋")
  • 描述(如"贡献销售额 500,000,利润率 20.00%。")
  • 装饰性背景元素 .insight-deco

8.3 销售预测(forecast)

布局结构:

复制代码
┌─────────────────────────────────────────┐
│  预测摘要卡片(4 张)                     │
│  预测月份 │ 预计销售额 │ 预计增长 │ 方法   │
├─────────────────────────────────────────┤
│  预测趋势图(SVG 折线图)                 │
│  历史实线 + 预测虚线                      │
├─────────────────────────────────────────┤
│  预测明细表                               │
│  月份 │ 预计销售额 │ 预计利润 │ 订单 │ 置信│
└─────────────────────────────────────────┘

预测图表实现:

  • 历史数据:蓝色实线 (line-sales)
  • 预测数据:橙色虚线 (line-forecast)
  • 预测线从历史最后一个点开始,形成连续效果
  • 使用 buildTrendSvg() 统一渲染

8.4 可视图表(charts)

包含 12 个独立图表面板,采用 2 列 Grid 布局(panel-wide 占满整行):

图表 渲染函数 类型 技术实现
品类贡献 renderBars() 水平柱状图 HTML <div> 进度条
区域销售结构 renderDonut() 环形图 CSS conic-gradient
省份销售 TOP 12 renderBars() 水平柱状图 HTML <div> 进度条
热销产品 TOP 10 renderBars() 水平柱状图 HTML <div> 进度条
月度增长趋势 renderMonthlyGrowth() 分组柱状图 SVG <rect> 双条
星期分布 renderWeekdayChart() 垂直柱状图 SVG <rect>
价格带分析 renderPriceBandChart() 水平柱状图 SVG <rect>
产品利润矩阵 renderProductProfit() 气泡图 SVG <circle>
客户类型对比 renderCustomerTypeChart() 水平柱状图 SVG <rect>
品类效率分析 renderCategoryEfficiency() 双层条形图 SVG <rect> 双层
区域品类结构 renderRegionCategoryStack() 堆叠柱状图 SVG <rect> 拼接
品类×区域热力 renderHeatmap() 热力图 CSS Grid + 背景色

8.5 订单明细(records)

布局结构:

复制代码
┌─────────────────────────────────────────┐
│  标题 + 记录数 + 排序控件                 │
├─────────────────────────────────────────┤
│  订单表格(11 列)                        │
│  订单ID│日期│品类│产品│区域│省份│客户│数量││
│  销售额│利润│利润率                       │
├─────────────────────────────────────────┤
│  分页控件:上一页 │ 页码信息 │ 下一页      │
└─────────────────────────────────────────┘

排序支持:销售日期、销售总金额、总利润、利润率、订单数量,升降序可选。

8.6 数据管理(data)

功能清单:

  • 查看:与订单明细类似的分页表格,但操作列替代利润率列
  • 新增:弹窗表单(10 个字段),订单ID 自动生成,实时计算预计销售额和利润
  • 编辑:弹窗表单,预填现有数据
  • 删除:确认对话框后删除
  • 导入:弹窗,支持 .xlsx/.xls/.csv,追加/替换两种模式
  • 导出 :按钮直接打开 /api/export.xlsx 下载

新增/编辑弹窗表单字段:

字段 输入类型 必填 说明
订单ID readonly --- 新增时显示"自动生成",编辑时显示现有ID
销售日期 date ---
产品类别 text ---
产品名称 text ---
销售区域 text ---
所属省份 text ---
客户类型 text ---
订单数量 number (min=1) 默认 1
单位成本 number (step=0.01) ---
销售单价 number (step=0.01) ---

实时预览: updateFormPreview() 监听数量、成本、单价的 input 事件,实时计算并显示预计销售额和利润。

8.7 个人中心(profile)

布局结构:

复制代码
┌──────────────────┬──────────────────────────┐
│  用户头像         │  账户偏好设置              │
│  姓名、角色       │  昵称、角色、默认页面       │
│  订单数、模块数   │  保存按钮、退出按钮         │
├──────────────────┴──────────────────────────┤
│  当前筛选快照(3×2 Grid)                     │
│  日期范围 │ 产品类别 │ 销售区域               │
│  所属省份 │ 客户类型 │ 关键词                 │
└─────────────────────────────────────────────┘

九、样式系统与响应式设计

9.1 设计令牌(CSS 自定义属性)

css 复制代码
:root {
  --primary: #1e40af;        /* 主色蓝 */
  --teal: #0d9488;           /* 辅助青 */
  --green: #059669;          /* 成功绿 */
  --amber: #d97706;          /* 警告橙 */
  --red: #dc2626;            /* 错误红 */
  --surface: #ffffff;        /* 卡片背景 */
  --text: #0f172a;           /* 主文字 */
  --muted: #64748b;          /* 次要文字 */
  --line: #e2e8f0;           /* 边框线 */
  --shadow: 0 1px 3px ...;   /* 默认阴影 */
  --shadow-strong: 0 4px ...;/* 悬停阴影 */
  --radius: 12px;            /* 圆角 */
}

9.2 布局体系

整体布局: CSS Grid 两栏

css 复制代码
.app-shell {
  display: grid;
  grid-template-columns: 268px 1fr;
}

各区域 Grid 布局:

区域 Grid 定义 说明
KPI 卡片 grid-template-columns: repeat(4, 1fr) 4 列等宽
洞察卡片 grid-template-columns: repeat(4, 1fr) 4 列等宽
概览摘要 grid-template-columns: repeat(3, 1fr) 3 列等宽
图表面板 grid-template-columns: repeat(2, 1fr) 2 列,.panel-wide 跨 2 列
筛选面板 grid-template-columns: repeat(6, 1fr) 6 列,关键词跨 2 列
个人中心 grid-template-columns: 280px 1fr 1fr 3 列不等宽

9.3 响应式断点

断点 适配设备 主要变化
<= 1180px 平板 KPI/洞察 2 列,图表单列,摘要单列
<= 760px 手机 侧边栏隐藏(display: none),所有网格单列
<= 520px 小屏手机 筛选面板单列,字体缩小
prefers-reduced-motion 无障碍 禁用所有 transitionanimation

9.4 图表样式

SVG 图表通用样式:

css 复制代码
.grid-line { stroke: #edf2f7; stroke-width: 1; }
.axis-line { stroke: #cfd8e3; stroke-width: 1; }
.line-sales { fill: none; stroke: #1e40af; stroke-width: 2.5; }
.line-profit { fill: none; stroke: #15803d; stroke-width: 2.5; }
.line-forecast { fill: none; stroke: #f59e0b; stroke-width: 2.5; stroke-dasharray: 8 4; }

环形图(CSS conic-gradient):

css 复制代码
.donut {
  width: 200px; height: 200px;
  border-radius: 50%;
  background: conic-gradient(...);
  /* 内圈白色圆形通过伪元素或内层 div 实现 */
}

热力图(CSS Grid):

css 复制代码
.heatmap-grid {
  display: grid;
  grid-template-columns: 150px repeat(N, minmax(96px, 1fr));
}
.heatmap-cell {
  background: rgba(30, 64, 175, 0.18~1.0); /* 根据数值调整透明度 */
}

9.5 通知系统(Toast)

css 复制代码
.toast {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  opacity: 0; transition: opacity 0.3s;
  pointer-events: none;
}
.toast-show { opacity: 1; }
.toast-success { background: #059669; }
.toast-error { background: #dc2626; }

十、核心算法详解

10.1 线性回归 + 移动平均混合预测

python 复制代码
def linear_forecast(values: list[float], steps: int) -> list[float]:
    # 1. 计算线性回归斜率
    count = len(values)
    x_avg = (count - 1) / 2
    y_avg = sum(values) / count
    slope = sum((i - x_avg) * (v - y_avg) for i, v in enumerate(values)) / sum((i - x_avg)**2 for i in range(count))

    # 2. 计算最近 3 期移动平均
    moving_average = sum(values[-3:]) / min(len(values), 3)

    # 3. 混合预测
    last_value = values[-1]
    predictions = []
    for step in range(1, steps + 1):
        trend_value = last_value + slope * step          # 线性趋势
        blended = trend_value * 0.65 + moving_average * 0.35  # 65% 趋势 + 35% 均值
        predictions.append(max(blended, 0))               # 负值截断为 0
    return predictions

置信度衰减:

python 复制代码
confidence = max(0.62, 0.9 - index * 0.04)
  • 第 1 期:0.90
  • 第 2 期:0.86
  • 第 3 期:0.82
  • ...
  • 第 7 期及以后:0.62(下限)

10.2 订单 ID 生成算法

python 复制代码
def next_order_id(df: pd.DataFrame) -> str:
    if df.empty:
        return "ORD000001"
    existing = df["订单ID"].astype(str).tolist()
    max_num = 0
    for oid in existing:
        digits = "".join(c for c in oid if c.isdigit())
        if digits:
            max_num = max(max_num, int(digits))
    return f"ORD{max_num + 1:06d}"
  • 遍历所有现有 ID,提取数字部分
  • 取最大值 +1
  • 格式化为 ORD + 6 位零填充数字

10.3 派生字段计算

python 复制代码
def compute_derived(row: dict) -> dict:
    quantity = int(row.get("订单数量", 1))
    unit_cost = float(row.get("单位成本", 0))
    unit_price = float(row.get("销售单价", 0))
    row["销售总金额"] = round(unit_price * quantity, 2)
    row["总成本"] = round(unit_cost * quantity, 2)
    row["总利润"] = round(row["销售总金额"] - row["总成本"], 2)
    row["利润率"] = round(row["总利润"] / row["销售总金额"], 4) if row["销售总金额"] else 0
    return row

10.4 SVG 折线图坐标计算

javascript 复制代码
// buildTrendSvg() 核心坐标映射
const x = (index) => pad.left + index * xStep;
const y = (value) => pad.top + plotH - (Number(value) / maxValue) * plotH;

// 网格线(5 条:0%, 25%, 50%, 75%, 100%)
const gridLines = [0, 0.25, 0.5, 0.75, 1].map((ratioValue) => {
  const yy = pad.top + plotH * ratioValue;
  const label = shortMoney(maxValue * (1 - ratioValue));
  return `<line .../><text ...>${label}</text>`;
});

// 数据点连线
const points = item.values.map((value, index) => svgPoint(x(index), y(value))).join(" ");
return `<polyline points="${points}"></polyline>`;

10.5 气泡图半径映射

javascript 复制代码
// 气泡半径 = 基础半径 + 比例缩放
const ratio = qMax > qMin ? (Number(d.quantity) - qMin) / (qMax - qMin) : 0.5;
const radius = 6 + ratio * 14;  // 范围:6px ~ 20px

// 颜色映射(利润率阈值)
const color = Number(d.margin) >= 0.4 ? "#15803d"   // 绿:高利润
            : Number(d.margin) >= 0.2 ? "#f59e0b"   // 橙:中利润
            : "#dc2626";                              // 红:低利润

10.6 热力图颜色强度

javascript 复制代码
const intensity = value / maxValue;
const bg = `rgba(30, 64, 175, ${0.18 + intensity * 0.82})`;
  • 最小值:rgba(30, 64, 175, 0.18) --- 几乎透明
  • 最大值:rgba(30, 64, 175, 1.0) --- 完全不透明深蓝

十一、关键实现细节

11.1 无第三方图表库的 SVG 图表

所有图表通过 JavaScript 字符串拼接生成 SVG 标记,优势:

  • 零外部依赖,页面加载无需下载额外 JS/CSS
  • 完全可控的样式和交互
  • 通过 <title> 元素实现原生浏览器 tooltip
  • 图表尺寸通过 viewBox + width="100%" 实现响应式

11.2 LRU 缓存策略

load_sales_data() 使用 functools.lru_cache(maxsize=1)

  • 读操作:命中缓存,毫秒级响应,适合高频查询场景
  • 写操作 :CRUD/导入后调用 cache_clear() 强制刷新
  • 限制:单进程、内存级缓存,适合中小数据量(本系统 1000 行)
  • 注意:多进程部署时每个进程有独立缓存,需改用 Redis 等外部缓存

11.3 XSS 防护

前端所有动态内容通过 escapeHtml() 函数转义:

javascript 复制代码
function escapeHtml(value) {
  return String(value ?? "")
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

所有 API 返回的数据在渲染到 HTML 前都经过此函数处理。

11.4 CSV 导出 BOM 编码

python 复制代码
csv_text = output.to_csv(index=False, encoding="utf-8-sig")

使用 utf-8-sig 编码自动添加 BOM 头(\xEF\xBB\xBF),确保 Microsoft Excel 正确识别中文编码。

11.5 数据导入去重

python 复制代码
result = result.drop_duplicates(subset=["订单ID"], keep="last").reset_index(drop=True)
  • 按订单ID 去重
  • keep="last":保留最后一条(即新导入的数据覆盖旧数据)
  • 适用于追加模式下的数据更新场景

11.6 表单实时预览

javascript 复制代码
[els.formQuantity, els.formUnitCost, els.formUnitPrice].forEach((el) => {
  el.addEventListener("input", updateFormPreview);
});

function updateFormPreview() {
  var sales = price * qty;
  var profit = (price - cost) * qty;
  els.previewSales.textContent = money(sales);
  els.previewProfit.textContent = money(profit);
}

用户输入数量、成本、单价时,实时计算并显示预计销售额和利润。


十二、启动与部署

12.1 环境要求

要求 版本
Python >= 3.8
pip 最新版
操作系统 Windows / macOS / Linux

12.2 安装与启动

bash 复制代码
# 1. 进入项目目录
cd code

# 2. 安装依赖
pip install -r requirements.txt

# 3. 启动服务
python app.py

默认监听 http://127.0.0.1:5050

12.3 环境变量

变量 默认值 说明
PORT 5050 服务监听端口
bash 复制代码
# 自定义端口
PORT=8080 python app.py

12.4 数据文件要求

确保 体育用品销售数据_1000行.xlsx 位于项目根目录(与 app.py 同级)。系统首次启动时自动加载并缓存。

12.5 生产部署建议

bash 复制代码
# 使用 Gunicorn(Linux/macOS)
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:5050 app:app

# 使用 waitress(Windows)
pip install waitress
waitress-serve --port=5050 app:app

注意事项:

  • LRU 缓存在多 worker 模式下每个进程独立,需改用 Redis
  • Excel 文件写入不支持并发,多进程写入可能导致数据损坏
  • 建议在 Nginx 反向代理后部署

十三、扩展建议

方向 现状 建议
数据存储 Excel 文件 迁移至 SQLite(轻量)或 PostgreSQL(生产级),支持并发写入
用户认证 localStorage 客户端认证 引入 Flask-Login + 服务端 Session + 密码哈希(bcrypt)
图表交互 静态 SVG + 原生 tooltip 集成 ECharts 或 Chart.js 实现缩放、钻取、动画
缓存 LRU 内存缓存 使用 Redis 替代,支持多进程/多服务器共享
部署 单进程 Flask 开发服务器 Gunicorn + Nginx 反向代理 + Docker 容器化
测试 添加 pytest 单元测试覆盖聚合函数和 API 端点
API 安全 无校验 增加分页参数校验、速率限制、CORS 配置、API Key 认证
数据量 1000 行 分页加载 + 数据库索引 + 后端分页优化
国际化 中文硬编码 提取文案到配置文件,支持多语言切换
日志 无结构化日志 引入 Python logging 模块,记录访问日志和错误日志
相关推荐
Reload.1 小时前
CZ航司,shopping JS逆向 acw_sc__v2
开发语言·javascript·python·网络爬虫·ecmascript
码界筑梦坊1 小时前
131-基于Flask的美国新泽西州自动售货机销售数据可视化分析系统
开发语言·python·信息可视化·数据分析·flask·毕业设计
努力努力再努力wz1 小时前
【QT入门系列】QWidget 六大常用属性详解:windowOpacity、cursor、font、focus、toolTip 与 styleSheet
android·开发语言·数据结构·c++·qt·mysql·算法
子榆.1 小时前
CANN PyTorch适配器深度拆解:从.cuda()到.npu()到底发生了什么
人工智能·pytorch·python
chushiyunen1 小时前
python使用笔记(linux环境)
linux·笔记·python
神仙别闹1 小时前
基于MFC(C++)实现(界面)学委作业管理系统
开发语言·c++·mfc
三品吉他手会点灯1 小时前
C语言学习笔记 - 41.数据类型 - scanf函数核心知识点复习
c语言·开发语言·笔记·学习
撩得Android一次心动1 小时前
C语言基础笔记3【个人用】
android·c语言·开发语言·笔记
谢白羽1 小时前
Voicebox 深度指南:开源本地 AI 语音工作室完整评测与上手教程
人工智能·python·开源·tts·voicebox