Html+css+js 写一个销售单据数据收集工具,会用到小米相机文档功能、NasCab、豆包Ai作为辅助

图:

注:

小米相机文档功能 拍下 通过 设置好 NasCab 传到 指定电脑指定目录 两把图片分为10张一组(因为豆包Ai 适图功能只能一次10张 ) 能过豆包Ai 把图片 转成json 数据 (提示词:转成json 格式数据,不稳定就给它参考数据)

转出来示例数据:

python 复制代码
[
  {
    "单据编号": "PXX-20251111-xxx",
    "客户名称": "bpc",
    "发货仓库": "kp仓库",
    "经手人": "张三",
    "录单时间": "2025-11-11 17:56:46",
    "商品明细": [
      {
        "行号": 1,
        "商品名称": "铭瑄MS-挑战者 H610M-R D4",
        "数量": 1,
        "单价": 335,
        "金额": 335,
        "备注": null
      }
    ],
    "合计数量": 1,
    "总金额": 335
  },
  {
    "单据编号": "PXX-20251126-xxx",
    "客户名称": "bpc",
    "发货仓库": "仓库",
    "经手人": "张三",
    "录单时间": "2025-11-26 15:09:30",
    "商品明细": [
      {
        "行号": 1,
        "商品名称": "西数SN7100 500G m.2",
        "数量": 1,
        "单价": 480,
        "金额": 480,
        "备注": null
      },
      {
        "行号": 2,
        "商品名称": "航嘉GS600 额定500W",
        "数量": 1,
        "单价": 180,
        "金额": 180,
        "备注": null
      },
      {
        "行号": 3,
        "商品名称": "阿斯加特金伦加TUF 3600 32G(16*2) C18 黑橙甲",
        "数量": 1,
        "单价": 980,
        "金额": 980,
        "备注": null
      },
      {
        "行号": 4,
        "商品名称": "铭瑄H311M-VH M.2",
        "数量": 1,
        "单价": 335,
        "金额": 335,
        "备注": null
      }
    ],
    "合计数量": 4,
    "总金额": 1975
  },
  {
    "单据编号": "PXX-20251208-xxx",
    "客户名称": "开平bpc",
    "发货仓库": "xx仓库",
    "经手人": "李四",
    "录单时间": "2025-12-08 16:58:06",
    "商品明细": [
      {
        "行号": 1,
        "商品名称": "昂达256G 固态",
        "数量": 1,
        "单价": 205,
        "金额": 205,
        "备注": null
      },
      {
        "行号": 2,
        "商品名称": "水星SG105C 千兆交换机",
        "数量": 1,
        "单价": 30,
        "金额": 30,
        "备注": null
      },
      {
        "行号": 3,
        "商品名称": "金刚之星 擎云3.0中箱",
        "数量": 1,
        "单价": 70,
        "金额": 70,
        "备注": null
      }
    ],
    "合计数量": 3,
    "总金额": 305
  }
]

销售单据数据收集工具 - 使用说明

一、系统概述

1.1 功能简介

本工具是一个专门用于收集、管理和分析销售单据数据的Web应用程序。支持数据导入、搜索、统计分析和导出功能。

1.2 主要特点

  • 可视化界面:现代化UI设计,操作直观

  • 数据管理:支持JSON格式数据导入导出

  • 智能搜索:多维度搜索功能

  • 统计分析:丰富的图表和统计报表

  • 数据安全:关键操作需要密码验证

  • 本地存储:数据自动保存到浏览器本地

二、核心功能说明

2.1 数据导入功能

2.1.1 支持两种导入方式:
  1. 文件导入:拖拽或选择JSON文件

  2. 文本导入:直接粘贴JSON数据

2.1.2 数据格式要求:
python 复制代码
[
  {
    "单据编号": "PXX-20250830-01139",
    "客户名称": "bpc",
    "发货仓库": "kpck",
    "经手人": "xx",
    "录单时间": "2025-08-30",
    "商品明细": [
      {
        "行号": 1,
        "商品名称": "航嘉EC0650 额定650 黑色",
        "数量": 1,
        "单价": 225,
        "金额": 225,
        "备注": null
      }
    ],
    "合计数量": 1,
    "总金额": 225
  }
]

2.2 搜索功能

2.2.1 搜索范围:
  • 🔍 全部字段:在所有字段中搜索

  • 📄 单据编号:精确搜索单据编号

  • 👥 客户名称:按客户名称搜索

  • 🏢 发货仓库:按仓库搜索

  • 👤 经手人:按经手人搜索

  • 📅 录单时间:按日期搜索

  • 📦 商品名称:在商品明细中搜索

2.2.2 搜索技巧:
  • 支持模糊搜索

  • 不区分大小写

  • 实时搜索(输入时自动搜索)

2.3 统计分析功能

2.3.1 核心指标:
  • 📄 单据总数、商品总数

  • 💰 总金额、平均金额、最高金额

  • 👥 客户数、仓库数、经手人数

  • 🎯 商品种类数

2.3.2 时间统计:
  • 📅 按日统计

  • 📆 按周统计

  • 📊 按月统计

  • 支持自定义日期范围

2.3.3 详细分析:
  • 按客户统计:客户销售额排行榜

  • 按仓库统计:各仓库业务量对比

  • 按经手人统计:员工业绩分析

  • 按商品统计:商品销量排行榜

2.4 数据管理功能

2.4.1 单据管理:
  • 📋 查看单据列表

  • 📄 查看单据详情

  • 🗑️ 删除单据(需确认)

2.4.2 数据安全:
  • 导出数据 :需要密码 123

  • 重置数据 :需要密码 123

  • 删除操作需要二次确认

三、操作指南

3.1 基础操作流程

3.1.1 首次使用:
  1. 点击"导入单据"按钮

  2. 选择导入方式(文件或文本)

  3. 验证数据格式

  4. 确认导入

3.1.2 查看单据:
  1. 在左侧列表选择单据

  2. 右侧显示详细信息

  3. 包括商品明细和合计

3.1.3 搜索单据:
  1. 点击"搜索"按钮打开搜索栏

  2. 输入搜索关键词

  3. 选择搜索字段(可选)

  4. 查看搜索结果

3.2 统计分析操作

3.2.1 查看统计:
  1. 点击"统计面板"按钮

  2. 查看核心指标

  3. 展开详细统计(可选)

3.2.2 时间分析:
  1. 设置开始和结束日期

  2. 选择统计周期(日/周/月)

  3. 点击"筛选"按钮

  4. 查看图表和数据表格

3.3 数据维护

3.3.1 导出数据:
  1. 点击"导出数据"按钮

  2. 输入密码:123

  3. 系统自动下载JSON文件

3.3.2 重置系统:
  1. 点击"重置数据"按钮

  2. 输入密码:123

  3. 确认重置操作

  4. 系统将清空所有数据

四、技术特性

4.1 数据存储

  • 存储位置:浏览器LocalStorage

  • 数据格式:标准JSON

  • 自动保存:修改后自动保存

  • 数据恢复:刷新页面数据不丢失

4.2 性能特点

  • 快速响应:所有操作即时反馈

  • 📱 响应式设计:支持手机和电脑

  • 🔄 实时更新:数据变化立即反映

  • 🎨 动画效果:平滑的界面过渡

4.3 兼容性

  • 浏览器:Chrome、Firefox、Edge、Safari

  • 设备:电脑、平板、手机

  • 分辨率:适配各种屏幕尺寸

五、使用注意事项

5.1 数据安全

  1. 密码保护:重要操作需要密码验证

  2. 数据备份:定期导出数据备份

  3. 操作确认:删除和重置操作需要确认

5.2 数据格式

  1. 必需字段:所有标红字段必须填写

  2. 数据验证:导入时会自动验证格式

  3. 重复检查:单据编号不能重复

5.3 浏览器要求

  1. 启用JavaScript:必须启用JavaScript

  2. 支持LocalStorage:浏览器需支持HTML5

  3. 屏幕尺寸:建议最小宽度320px

六、故障排除

6.1 常见问题

6.1.1 导入失败:
  • ✅ 检查JSON格式是否正确

  • ✅ 确保必需字段齐全

  • ✅ 验证数据是否符合规范

6.1.2 搜索无结果:
  • ✅ 确认搜索关键词正确

  • ✅ 检查搜索字段选择

  • ✅ 确保有相关数据

6.1.3 统计不显示:
  • ✅ 确认已导入数据

  • ✅ 检查日期范围设置

  • ✅ 刷新统计面板

6.2 数据恢复

  • 自动恢复:数据自动保存在浏览器

  • 手动恢复:从导出的JSON文件重新导入

  • 紧急恢复:联系技术支持

七、使用建议

7.1 最佳实践

  1. 定期备份:每周导出数据备份

  2. 分类管理:按客户或时间分类单据

  3. 统计分析:每月进行销售数据分析

  4. 数据清理:定期清理无效数据

7.2 效率技巧

  1. 快捷键:善用Tab键导航

  2. 批量操作:使用JSON批量导入

  3. 快速搜索:使用特定字段搜索

  4. 模板使用:保存常用JSON模板

八、版本信息

8.1 当前版本

  • 版本号:v1.0

  • 更新日期:2026年

  • 主要功能:单据管理、统计分析、数据导入导出

8.2 更新计划

  • 导出Excel格式
  • 打印功能 PDF
  • json数据转换
  • 增加后端 FastAPI

代码:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>销售单据数据收集工具 - 搜索版</title>
    <style>
        :root {
            --primary: #3498db;
            --primary-dark: #2980b9;
            --secondary: #2ecc71;
            --danger: #e74c3c;
            --warning: #f39c12;
            --info: #17a2b8;
            --dark: #2c3e50;
            --light: #ecf0f1;
            --gray: #95a5a6;
            --border: #e0e7ee;
            --shadow: 0 4px 12px rgba(0,0,0,0.08);
            --radius: 10px;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }

        body {
            background: linear-gradient(135deg, #f5f7fa 0%, #e4eef5 100%);
            color: #333;
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 1400px;
            margin: 0 auto;
            padding: 20px;
        }

        header {
            background: white;
            border-radius: var(--radius);
            padding: 20px 30px;
            margin-bottom: 20px;
            box-shadow: var(--shadow);
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 15px;
        }

        h1 {
            color: var(--dark);
            font-size: 1.8rem;
            font-weight: 600;
            display: flex;
            align-items: center;
            gap: 12px;
        }

        .logo {
            background: var(--primary);
            color: white;
            width: 40px;
            height: 40px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
            font-size: 1.2rem;
        }

        .actions {
            display: flex;
            gap: 12px;
            flex-wrap: wrap;
        }

        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 8px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 0.95rem;
        }

        .btn-primary {
            background: var(--primary);
            color: white;
        }

        .btn-primary:hover {
            background: var(--primary-dark);
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(52, 152, 219, 0.3);
        }

        .btn-secondary {
            background: var(--secondary);
            color: white;
        }

        .btn-secondary:hover {
            background: #27ae60;
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(46, 204, 113, 0.3);
        }

        .btn-danger {
            background: var(--danger);
            color: white;
        }

        .btn-danger:hover {
            background: #c0392b;
            transform: translateY(-2px);
        }

        .btn-outline {
            background: transparent;
            border: 2px solid var(--primary);
            color: var(--primary);
        }

        .btn-outline:hover {
            background: var(--primary);
            color: white;
        }

        .btn-sm {
            padding: 6px 12px;
            font-size: 0.85rem;
        }

        /* 搜索区域样式 */
        .search-container {
            background: white;
            border-radius: var(--radius);
            padding: 15px 20px;
            margin-bottom: 15px;
            box-shadow: var(--shadow);
            display: none;
        }

        .search-container.active {
            display: block;
            animation: fadeIn 0.3s ease;
        }

        .search-box {
            display: flex;
            gap: 10px;
            align-items: center;
            flex-wrap: wrap;
        }

        .search-input {
            flex: 1;
            min-width: 200px;
            padding: 10px 15px;
            border: 1px solid var(--border);
            border-radius: 8px;
            font-size: 1rem;
            transition: all 0.2s;
        }

        .search-input:focus {
            outline: none;
            border-color: var(--primary);
            box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.15);
        }

        .search-select {
            padding: 10px 15px;
            border: 1px solid var(--border);
            border-radius: 8px;
            font-size: 0.95rem;
            background: white;
            cursor: pointer;
            min-width: 150px;
        }

        .search-actions {
            display: flex;
            gap: 8px;
            align-items: center;
        }

        .search-info {
            font-size: 0.85rem;
            color: var(--gray);
            padding: 5px 10px;
            background: var(--light);
            border-radius: 6px;
            display: none;
        }

        .search-info.active {
            display: inline-block;
        }

        .search-info strong {
            color: var(--primary);
            font-weight: 600;
        }

        /* 统计面板样式 */
        .stats-panel {
            background: white;
            border-radius: var(--radius);
            padding: 20px;
            margin-bottom: 20px;
            box-shadow: var(--shadow);
            display: none;
        }

        .stats-panel.active {
            display: block;
            animation: fadeIn 0.3s ease;
        }

        .stats-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid var(--border);
        }

        .stats-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 15px;
            margin-bottom: 20px;
        }

        .stat-card {
            background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
            border: 1px solid var(--border);
            border-radius: 8px;
            padding: 15px;
            transition: all 0.2s;
            position: relative;
            overflow: hidden;
        }

        .stat-card:hover {
            transform: translateY(-3px);
            box-shadow: 0 6px 16px rgba(0,0,0,0.1);
        }

        .stat-card::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            width: 4px;
            height: 100%;
            background: var(--primary);
        }

        .stat-card.secondary::before { background: var(--secondary); }
        .stat-card.danger::before { background: var(--danger); }
        .stat-card.warning::before { background: var(--warning); }
        .stat-card.info::before { background: var(--info); }

        .stat-label {
            font-size: 0.85rem;
            color: var(--gray);
            font-weight: 500;
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            gap: 6px;
        }

        .stat-value {
            font-size: 1.8rem;
            font-weight: 700;
            color: var(--dark);
            line-height: 1.2;
        }

        .stat-unit {
            font-size: 0.9rem;
            color: var(--gray);
            font-weight: 500;
            margin-left: 4px;
        }

        /* 时间统计区域 */
        .time-stats-section {
            background: #f8fafc;
            border-radius: 8px;
            padding: 20px;
            border: 1px solid var(--border);
            margin-top: 15px;
        }

        .time-controls {
            display: flex;
            gap: 10px;
            margin-bottom: 15px;
            flex-wrap: wrap;
            align-items: center;
        }

        .time-controls input[type="date"] {
            padding: 8px 12px;
            border: 1px solid var(--border);
            border-radius: 6px;
            font-size: 0.9rem;
        }

        .time-controls select {
            padding: 8px 12px;
            border: 1px solid var(--border);
            border-radius: 6px;
            font-size: 0.9rem;
            background: white;
        }

        .time-chart {
            margin-top: 15px;
            padding: 15px;
            background: white;
            border-radius: 6px;
            border: 1px solid var(--border);
        }

        .chart-bar {
            display: flex;
            align-items: flex-end;
            gap: 8px;
            height: 150px;
            margin: 10px 0;
            padding: 10px 0;
            border-bottom: 1px solid var(--border);
        }

        .bar-item {
            flex: 1;
            background: var(--primary);
            border-radius: 4px 4px 0 0;
            min-height: 5px;
            position: relative;
            transition: all 0.3s ease;
            cursor: pointer;
        }

        .bar-item:hover {
            background: var(--primary-dark);
            transform: scaleY(1.05);
        }

        .bar-item .bar-label {
            position: absolute;
            bottom: -25px;
            left: 50%;
            transform: translateX(-50%);
            font-size: 0.75rem;
            color: var(--gray);
            white-space: nowrap;
        }

        .bar-item .bar-value {
            position: absolute;
            top: -25px;
            left: 50%;
            transform: translateX(-50%);
            font-size: 0.75rem;
            font-weight: 600;
            color: var(--dark);
            white-space: nowrap;
        }

        .chart-empty {
            text-align: center;
            padding: 30px;
            color: var(--gray);
            font-style: italic;
        }

        /* 详情统计表格 */
        .detail-stats {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 15px;
            margin-top: 15px;
        }

        @media (max-width: 768px) {
            .detail-stats {
                grid-template-columns: 1fr;
            }
        }

        .detail-table-card {
            background: #f8fafc;
            border-radius: 8px;
            padding: 15px;
            border: 1px solid var(--border);
        }

        .detail-table-card h4 {
            margin-bottom: 12px;
            color: var(--dark);
            font-size: 1rem;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .detail-table {
            width: 100%;
            border-collapse: collapse;
            font-size: 0.85rem;
        }

        .detail-table th {
            background: white;
            padding: 8px;
            text-align: left;
            font-weight: 600;
            color: var(--dark);
            border-bottom: 2px solid var(--border);
        }

        .detail-table td {
            padding: 8px;
            border-bottom: 1px solid var(--border);
        }

        .detail-table tr:hover {
            background: rgba(52, 152, 219, 0.05);
        }

        .progress-bar {
            width: 100%;
            height: 6px;
            background: #e9ecef;
            border-radius: 3px;
            overflow: hidden;
            margin-top: 6px;
        }

        .progress-fill {
            height: 100%;
            background: var(--primary);
            transition: width 0.3s ease;
        }

        /* 主内容区域 */
        .main-content {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
            margin-bottom: 20px;
        }

        @media (max-width: 900px) {
            .main-content {
                grid-template-columns: 1fr;
            }
        }

        .card {
            background: white;
            border-radius: var(--radius);
            padding: 20px;
            box-shadow: var(--shadow);
            overflow: hidden;
        }

        .card-header {
            padding-bottom: 15px;
            border-bottom: 1px solid var(--border);
            margin-bottom: 15px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 10px;
        }

        .card-title {
            font-size: 1.2rem;
            font-weight: 600;
            color: var(--dark);
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .badge {
            background: var(--light);
            color: var(--dark);
            padding: 4px 10px;
            border-radius: 20px;
            font-size: 0.8rem;
            font-weight: 600;
        }

        /* 单据列表样式 */
        .invoice-list {
            max-height: 500px;
            overflow-y: auto;
            padding-right: 8px;
        }

        .invoice-item {
            padding: 15px;
            border: 1px solid var(--border);
            border-radius: 8px;
            margin-bottom: 12px;
            cursor: pointer;
            transition: all 0.2s;
            background: white;
        }

        .invoice-item:hover {
            border-color: var(--primary);
            box-shadow: 0 2px 8px rgba(52, 152, 219, 0.15);
            transform: translateY(-2px);
        }

        .invoice-item.active {
            border-color: var(--primary);
            background: rgba(52, 152, 219, 0.05);
            border-left: 4px solid var(--primary);
        }

        .invoice-item.highlight {
            background: rgba(255, 193, 7, 0.1);
            border-color: var(--warning);
        }

        .invoice-header {
            display: flex;
            justify-content: space-between;
            margin-bottom: 8px;
        }

        .invoice-number {
            font-weight: 600;
            color: var(--dark);
        }

        .invoice-date {
            color: var(--gray);
            font-size: 0.85rem;
        }

        .invoice-details {
            font-size: 0.9rem;
            color: #555;
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
        }

        .invoice-details span {
            display: flex;
            align-items: center;
            gap: 5px;
        }

        /* 详情区域样式 */
        .detail-container {
            min-height: 350px;
        }

        .empty-state {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100%;
            color: var(--gray);
            gap: 15px;
            padding: 40px;
            text-align: center;
        }

        .empty-state svg {
            width: 60px;
            height: 60px;
            opacity: 0.3;
        }

        .detail-view {
            display: none;
        }

        .detail-view.active {
            display: block;
            animation: fadeIn 0.3s ease;
        }

        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .detail-section {
            margin-bottom: 20px;
            padding-bottom: 15px;
            border-bottom: 1px dashed var(--border);
        }

        .detail-section h3 {
            margin-bottom: 12px;
            color: var(--dark);
            font-size: 1.1rem;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .detail-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 12px;
        }

        .detail-item {
            display: flex;
            flex-direction: column;
            gap: 4px;
        }

        .detail-label {
            font-size: 0.85rem;
            color: var(--gray);
            font-weight: 500;
        }

        .detail-value {
            font-size: 1rem;
            color: var(--dark);
            font-weight: 500;
        }

        /* 商品明细表格 */
        .products-table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 10px;
            border-radius: 8px;
            overflow: hidden;
        }

        .products-table th {
            background: var(--primary);
            color: white;
            padding: 12px 15px;
            text-align: left;
            font-weight: 500;
        }

        .products-table td {
            padding: 10px 15px;
            border-bottom: 1px solid var(--border);
        }

        .products-table tr:last-child td {
            border-bottom: none;
        }

        .products-table tr:hover {
            background: rgba(52, 152, 219, 0.05);
        }

        .total-row {
            background: rgba(52, 152, 219, 0.1);
            font-weight: 600;
        }

        /* 导入区域样式 */
        .import-container {
            background: white;
            border-radius: var(--radius);
            padding: 25px;
            box-shadow: var(--shadow);
        }

        .import-zone {
            border: 2px dashed var(--border);
            border-radius: 8px;
            padding: 30px;
            text-align: center;
            transition: all 0.3s;
            background: #f8fafc;
            margin-bottom: 20px;
        }

        .import-zone.dragover {
            border-color: var(--primary);
            background: rgba(52, 152, 219, 0.05);
            transform: scale(1.02);
        }

        .import-zone svg {
            width: 50px;
            height: 50px;
            color: var(--gray);
            margin-bottom: 15px;
        }

        .file-input-wrapper {
            position: relative;
            display: inline-block;
            margin-top: 15px;
        }

        .file-input {
            position: absolute;
            opacity: 0;
            width: 100%;
            height: 100%;
            cursor: pointer;
        }

        .file-input-label {
            display: inline-block;
            padding: 10px 20px;
            background: var(--primary);
            color: white;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.2s;
        }

        .file-input-label:hover {
            background: var(--primary-dark);
            transform: translateY(-2px);
        }

        .text-input-area {
            margin-top: 20px;
        }

        .text-input-area label {
            display: block;
            margin-bottom: 8px;
            font-weight: 500;
            color: var(--dark);
        }

        textarea {
            width: 100%;
            min-height: 150px;
            padding: 12px;
            border: 1px solid var(--border);
            border-radius: 8px;
            font-family: 'Courier New', monospace;
            font-size: 0.9rem;
            resize: vertical;
        }

        textarea:focus {
            outline: none;
            border-color: var(--primary);
            box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.15);
        }

        .import-actions {
            display: flex;
            gap: 12px;
            margin-top: 15px;
            justify-content: flex-end;
        }

        .preview-section {
            margin-top: 20px;
            padding: 20px;
            background: #f8fafc;
            border-radius: 8px;
            border: 1px solid var(--border);
            display: none;
        }

        .preview-section.active {
            display: block;
            animation: fadeIn 0.3s ease;
        }

        .preview-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
        }

        .preview-content {
            max-height: 300px;
            overflow-y: auto;
            background: white;
            padding: 15px;
            border-radius: 6px;
            border: 1px solid var(--border);
        }

        .preview-content pre {
            margin: 0;
            font-size: 0.85rem;
            white-space: pre-wrap;
            word-wrap: break-word;
        }

        .validation-result {
            margin-top: 15px;
            padding: 12px;
            border-radius: 6px;
            font-size: 0.9rem;
            display: none;
        }

        .validation-result.success {
            background: rgba(46, 204, 113, 0.1);
            border: 1px solid var(--secondary);
            color: #27ae60;
            display: block;
        }

        .validation-result.error {
            background: rgba(231, 76, 60, 0.1);
            border: 1px solid var(--danger);
            color: #c0392b;
            display: block;
        }

        .hidden {
            display: none !important;
        }

        /* 提示框 */
        .toast {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: var(--dark);
            color: white;
            padding: 12px 25px;
            border-radius: 8px;
            box-shadow: var(--shadow);
            transform: translateY(100px);
            opacity: 0;
            transition: all 0.3s;
            z-index: 1000;
        }

        .toast.show {
            transform: translateY(0);
            opacity: 1;
        }

        .toast.success {
            background: var(--secondary);
        }

        .toast.error {
            background: var(--danger);
        }

        /* 滚动条美化 */
        ::-webkit-scrollbar {
            width: 8px;
            height: 8px;
        }

        ::-webkit-scrollbar-track {
            background: #f1f1f1;
            border-radius: 4px;
        }

        ::-webkit-scrollbar-thumb {
            background: #c1c1c1;
            border-radius: 4px;
        }

        ::-webkit-scrollbar-thumb:hover {
            background: #a8a8a8;
        }

        /* 示例JSON */
        .example-json {
            margin-top: 15px;
            padding: 15px;
            background: #f0f8ff;
            border-radius: 6px;
            border: 1px solid #d0e8ff;
            font-size: 0.85rem;
        }

        .example-json summary {
            cursor: pointer;
            font-weight: 500;
            color: var(--primary);
            margin-bottom: 8px;
        }

        .example-json pre {
            margin: 0;
            white-space: pre-wrap;
            word-wrap: break-word;
            font-size: 0.8rem;
            color: #555;
        }

        /* 统计详情展开/收起 */
        .toggle-stats {
            background: var(--light);
            border: 1px solid var(--border);
            border-radius: 6px;
            padding: 10px 15px;
            cursor: pointer;
            transition: all 0.2s;
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-top: 10px;
        }

        .toggle-stats:hover {
            background: #e8f4f8;
            border-color: var(--primary);
        }

        .toggle-stats.expanded {
            background: var(--primary);
            color: white;
            border-color: var(--primary);
        }

        .stats-details {
            display: none;
            margin-top: 10px;
            animation: fadeIn 0.3s ease;
        }

        .stats-details.active {
            display: block;
        }

        .empty-stats {
            text-align: center;
            padding: 20px;
            color: var(--gray);
            font-style: italic;
        }

        /* 响应式调整 */
        @media (max-width: 768px) {
            .stats-grid {
                grid-template-columns: 1fr;
            }

            .detail-stats {
                grid-template-columns: 1fr;
            }

            .header {
                flex-direction: column;
                align-items: flex-start;
            }

            .actions {
                width: 100%;
                justify-content: flex-start;
            }

            .time-controls {
                flex-direction: column;
                align-items: stretch;
            }

            .time-controls input,
            .time-controls select {
                width: 100%;
            }

            .search-box {
                flex-direction: column;
            }

            .search-input {
                width: 100%;
            }

            .search-actions {
                width: 100%;
                justify-content: space-between;
            }

            .search-select {
                width: 100%;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1><div class="logo">SD</div>销售单据数据收集工具</h1>
            <div class="actions">
                <button class="btn btn-primary" id="importBtn">
                    <span>📥</span> 导入单据
                </button>
                <button class="btn btn-secondary" id="exportBtn">
                    <span>📤</span> 导出数据
                </button>
                <button class="btn btn-outline" id="toggleStatsBtn">
                    <span>📊</span> 统计面板
                </button>
                <button class="btn btn-outline" id="resetBtn">
                    重置数据
                </button>
            </div>
        </header>

        <!-- 搜索区域 -->
        <div class="search-container" id="searchContainer">
            <div class="search-box">
                <input type="text" id="searchInput" class="search-input" placeholder="🔍 搜索单据编号、客户名称、仓库、经手人、商品名称...">
                <select id="searchField" class="search-select">
                    <option value="all">全部字段</option>
                    <option value="单据编号">单据编号</option>
                    <option value="客户名称">客户名称</option>
                    <option value="发货仓库">发货仓库</option>
                    <option value="经手人">经手人</option>
                    <option value="录单时间">录单时间</option>
                    <option value="商品名称">商品名称</option>
                </select>
                <div class="search-actions">
                    <button class="btn btn-primary" id="searchBtn">搜索</button>
                    <button class="btn btn-outline" id="clearSearchBtn">清空</button>
                    <span class="search-info" id="searchInfo"></span>
                </div>
            </div>
        </div>

        <!-- 统计面板 -->
        <div class="stats-panel" id="statsPanel">
            <div class="stats-header">
                <h2 class="card-title">📈 数据统计分析</h2>
                <div class="actions">
                    <button class="btn btn-sm btn-outline" id="refreshStatsBtn">刷新统计</button>
                </div>
            </div>

            <!-- 核心指标卡片 -->
            <div class="stats-grid" id="coreStats">
                <!-- 动态生成 -->
            </div>

            <!-- 时间统计区域 -->
            <div class="time-stats-section" id="timeStatsSection">
                <h4>📅 按时间统计</h4>
                <div class="time-controls">
                    <input type="date" id="startDate" placeholder="开始日期">
                    <input type="date" id="endDate" placeholder="结束日期">
                    <select id="timeGroup">
                        <option value="day">按日统计</option>
                        <option value="week">按周统计</option>
                        <option value="month">按月统计</option>
                    </select>
                    <button class="btn btn-sm btn-primary" id="filterTimeBtn">筛选</button>
                    <button class="btn btn-sm btn-outline" id="resetTimeBtn">重置</button>
                </div>
                <div id="timeStatsResult">
                    <div class="empty-stats">请选择日期范围查看统计</div>
                </div>
                <div class="time-chart" id="timeChart">
                    <div class="chart-empty">暂无图表数据</div>
                </div>
            </div>

            <!-- 详情统计展开/收起 -->
            <div class="toggle-stats" id="toggleStatsDetails">
                <span>📊 查看详细统计分析</span>
                <span id="toggleIcon">▼</span>
            </div>

            <!-- 详细统计内容 -->
            <div class="stats-details" id="statsDetails">
                <div class="detail-stats">
                    <!-- 按客户统计 -->
                    <div class="detail-table-card">
                        <h4>👥 按客户统计</h4>
                        <div id="customerStats"></div>
                    </div>

                    <!-- 按仓库统计 -->
                    <div class="detail-table-card">
                        <h4>🏢 按仓库统计</h4>
                        <div id="warehouseStats"></div>
                    </div>

                    <!-- 按经手人统计 -->
                    <div class="detail-table-card">
                        <h4>👤 按经手人统计</h4>
                        <div id="handlerStats"></div>
                    </div>

                    <!-- 按商品统计 -->
                    <div class="detail-table-card">
                        <h4>📦 按商品统计</h4>
                        <div id="productStats"></div>
                    </div>
                </div>
            </div>
        </div>

        <div class="main-content">
            <!-- 单据列表 -->
            <div class="card">
                <div class="card-header">
                    <div class="card-title">📋 单据列表</div>
                    <div class="actions">
                        <button class="btn btn-sm btn-outline" id="toggleSearchBtn">🔍 搜索</button>
                        <div class="badge" id="invoiceCount">0 单</div>
                    </div>
                </div>
                <div class="invoice-list" id="invoiceList">
                    <!-- 单据列表将通过JS动态生成 -->
                </div>
            </div>

            <!-- 单据详情 -->
            <div class="card">
                <div class="card-header">
                    <div class="card-title">📄 单据详情</div>
                    <div class="actions">
                        <button class="btn btn-outline btn-sm hidden" id="deleteBtn">删除</button>
                    </div>
                </div>
                <div class="detail-container">
                    <div class="empty-state" id="emptyState">
                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                            <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
                            <line x1="9" y1="9" x2="15" y2="9"></line>
                            <line x1="9" y1="13" x2="15" y2="13"></line>
                            <line x1="9" y1="17" x2="11" y2="17"></line>
                        </svg>
                        <p>请选择左侧单据查看详情</p>
                    </div>
                    <div class="detail-view" id="detailView">
                        <!-- 详情内容将通过JS动态生成 -->
                    </div>
                </div>
            </div>
        </div>

        <!-- 导入区域 -->
        <div class="import-container hidden" id="importContainer">
            <div class="card-header">
                <div class="card-title">📥 导入单据数据</div>
                <div class="actions">
                    <button class="btn btn-danger" id="cancelImportBtn">取消</button>
                </div>
            </div>

            <div class="import-zone" id="dropZone">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
                    <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                    <polyline points="17 8 12 3 7 8"></polyline>
                    <line x1="12" y1="3" x2="12" y2="15"></line>
                </svg>
                <h3>拖拽JSON文件到此处</h3>
                <p>或点击下方按钮选择文件</p>
                <div class="file-input-wrapper">
                    <input type="file" id="fileInput" class="file-input" accept=".json">
                    <label for="fileInput" class="file-input-label">选择JSON文件</label>
                </div>
            </div>

            <div class="text-input-area">
                <label for="jsonTextarea">或者直接粘贴JSON数据:</label>
                <textarea id="jsonTextarea" placeholder='[
  {
    "单据编号": "PXX-20250830-01139",
    "客户名称": "bpc",
    "发货仓库": "xx仓库",
    "经手人": "张三",
    "录单时间": "2025-08-30",
    "商品明细": [
      {
        "行号": 1,
        "商品名称": "航嘉EC0650 额定650 黑色",
        "数量": 1,
        "单价": 225,
        "金额": 225,
        "备注": null
      }
    ],
    "合计数量": 1,
    "总金额": 225
  }
]'></textarea>

                <details class="example-json">
                    <summary>查看JSON格式示例</summary>
                    <pre>[
  {
    "单据编号": "PXX-20250830-01139",
    "客户名称": "bpc",
    "发货仓库": "xx仓库",
    "经手人": "张三",
    "录单时间": "2025-08-30",
    "商品明细": [
      {
        "行号": 1,
        "商品名称": "航嘉EC0650 额定650 黑色",
        "数量": 1,
        "单价": 225,
        "金额": 225,
        "备注": null
      }
    ],
    "合计数量": 1,
    "总金额": 225
  }
]</pre>
                </details>

                <div class="import-actions">
                    <button class="btn btn-outline" id="clearJsonBtn">清空</button>
                    <button class="btn btn-primary" id="parseJsonBtn">解析数据</button>
                </div>

                <div class="validation-result" id="validationResult"></div>

                <div class="preview-section" id="previewSection">
                    <div class="preview-header">
                        <h4>📋 导入预览</h4>
                        <div class="stats" id="previewStats"></div>
                    </div>
                    <div class="preview-content">
                        <pre id="previewContent"></pre>
                    </div>
                    <div class="import-actions">
                        <button class="btn btn-secondary" id="confirmImportBtn">确认导入</button>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- 提示框 -->
    <div class="toast" id="toast"></div>

    <script>
        // 应用状态
        let appState = {
            invoices: [],
            selectedInvoice: null,
            parsedData: null,
            isImporting: false,
            statsExpanded: false,
            filteredTimeData: [],
            searchResults: [],
            isSearching: false,
            searchQuery: '',
            searchField: 'all'
        };

        // DOM 元素
        const elements = {
            // 搜索相关
            searchContainer: document.getElementById('searchContainer'),
            searchInput: document.getElementById('searchInput'),
            searchField: document.getElementById('searchField'),
            searchBtn: document.getElementById('searchBtn'),
            clearSearchBtn: document.getElementById('clearSearchBtn'),
            searchInfo: document.getElementById('searchInfo'),
            toggleSearchBtn: document.getElementById('toggleSearchBtn'),

            statsPanel: document.getElementById('statsPanel'),
            toggleStatsBtn: document.getElementById('toggleStatsBtn'),
            refreshStatsBtn: document.getElementById('refreshStatsBtn'),
            coreStats: document.getElementById('coreStats'),
            toggleStatsDetails: document.getElementById('toggleStatsDetails'),
            statsDetails: document.getElementById('statsDetails'),
            toggleIcon: document.getElementById('toggleIcon'),
            customerStats: document.getElementById('customerStats'),
            warehouseStats: document.getElementById('warehouseStats'),
            handlerStats: document.getElementById('handlerStats'),
            productStats: document.getElementById('productStats'),

            // 时间统计相关
            timeStatsSection: document.getElementById('timeStatsSection'),
            startDate: document.getElementById('startDate'),
            endDate: document.getElementById('endDate'),
            timeGroup: document.getElementById('timeGroup'),
            filterTimeBtn: document.getElementById('filterTimeBtn'),
            resetTimeBtn: document.getElementById('resetTimeBtn'),
            timeStatsResult: document.getElementById('timeStatsResult'),
            timeChart: document.getElementById('timeChart'),

            invoiceList: document.getElementById('invoiceList'),
            invoiceCount: document.getElementById('invoiceCount'),
            emptyState: document.getElementById('emptyState'),
            detailView: document.getElementById('detailView'),
            importBtn: document.getElementById('importBtn'),
            exportBtn: document.getElementById('exportBtn'),
            resetBtn: document.getElementById('resetBtn'),
            deleteBtn: document.getElementById('deleteBtn'),
            importContainer: document.getElementById('importContainer'),
            cancelImportBtn: document.getElementById('cancelImportBtn'),
            dropZone: document.getElementById('dropZone'),
            fileInput: document.getElementById('fileInput'),
            jsonTextarea: document.getElementById('jsonTextarea'),
            clearJsonBtn: document.getElementById('clearJsonBtn'),
            parseJsonBtn: document.getElementById('parseJsonBtn'),
            validationResult: document.getElementById('validationResult'),
            previewSection: document.getElementById('previewSection'),
            previewContent: document.getElementById('previewContent'),
            previewStats: document.getElementById('previewStats'),
            confirmImportBtn: document.getElementById('confirmImportBtn'),
            toast: document.getElementById('toast')
        };

        // 密码验证函数
        function promptPassword(action) {
            const password = prompt(`请输入密码以${action === 'export' ? '导出' : '重置'}数据:`);
            
            if (password === null) {
                // 用户点击取消
                return;
            }
            
            if (password === '123') {
                // 密码正确,执行相应操作
                if (action === 'export') {
                    exportData();
                } else if (action === 'reset') {
                    resetData();
                }
            } else {
                showToast('密码错误!', 'error');
            }
        }

        // 初始化应用
        function initApp() {
            // 从localStorage加载数据
            const savedData = localStorage.getItem('invoiceData');
            if (savedData) {
                appState.invoices = JSON.parse(savedData);
            }

            renderInvoiceList();
            updateInvoiceCount();

            // 搜索相关事件
            elements.toggleSearchBtn.addEventListener('click', toggleSearch);
            elements.searchBtn.addEventListener('click', performSearch);
            elements.clearSearchBtn.addEventListener('click', clearSearch);
            elements.searchInput.addEventListener('input', debounce(performSearch, 300));
            elements.searchField.addEventListener('change', () => {
                if (elements.searchInput.value.trim()) {
                    performSearch();
                }
            });

            // 统计相关事件
            elements.toggleStatsBtn.addEventListener('click', toggleStatsPanel);
            elements.refreshStatsBtn.addEventListener('click', updateStats);
            elements.toggleStatsDetails.addEventListener('click', toggleStatsDetails);

            // 时间统计事件
            elements.filterTimeBtn.addEventListener('click', filterByTime);
            elements.resetTimeBtn.addEventListener('click', resetTimeFilter);

            // 导入导出事件
            elements.importBtn.addEventListener('click', showImportArea);
            elements.exportBtn.addEventListener('click', () => promptPassword('export'));
            elements.resetBtn.addEventListener('click', () => promptPassword('reset'));
            elements.deleteBtn.addEventListener('click', deleteInvoice);
            elements.cancelImportBtn.addEventListener('click', hideImportArea);
            elements.clearJsonBtn.addEventListener('click', clearJsonInput);
            elements.parseJsonBtn.addEventListener('click', parseJsonData);
            elements.confirmImportBtn.addEventListener('click', confirmImport);

            // 文件上传事件
            elements.fileInput.addEventListener('change', handleFileSelect);

            // 拖拽事件
            elements.dropZone.addEventListener('dragover', handleDragOver);
            elements.dropZone.addEventListener('dragleave', handleDragLeave);
            elements.dropZone.addEventListener('drop', handleDrop);

            // 初始更新统计(如果有数据)
            if (appState.invoices.length > 0) {
                updateStats();
            }

            // 设置默认日期范围(最近30天)
            setDefaultDateRange();
        }

        // 防抖函数
        function debounce(func, wait) {
            let timeout;
            return function executedFunction(...args) {
                const later = () => {
                    clearTimeout(timeout);
                    func(...args);
                };
                clearTimeout(timeout);
                timeout = setTimeout(later, wait);
            };
        }

        // 切换搜索显示
        function toggleSearch() {
            const isVisible = elements.searchContainer.classList.contains('active');

            if (isVisible) {
                elements.searchContainer.classList.remove('active');
                elements.toggleSearchBtn.innerHTML = '🔍 搜索';
                clearSearch(); // 隐藏时清空搜索
            } else {
                elements.searchContainer.classList.add('active');
                elements.toggleSearchBtn.innerHTML = '✕ 关闭搜索';
                elements.searchInput.focus();
            }
        }

        // 执行搜索
        function performSearch() {
            const query = elements.searchInput.value.trim();
            const field = elements.searchField.value;

            if (!query) {
                if (appState.isSearching) {
                    clearSearch();
                }
                return;
            }

            appState.searchQuery = query;
            appState.searchField = field;
            appState.isSearching = true;

            // 执行搜索
            const results = appState.invoices.filter(invoice => {
                if (field === 'all') {
                    // 搜索所有字段(包括商品名称)
                    const matchBasic = (
                        invoice.单据编号.toLowerCase().includes(query.toLowerCase()) ||
                        invoice.客户名称.toLowerCase().includes(query.toLowerCase()) ||
                        invoice.发货仓库.toLowerCase().includes(query.toLowerCase()) ||
                        invoice.经手人.toLowerCase().includes(query.toLowerCase()) ||
                        invoice.录单时间.toLowerCase().includes(query.toLowerCase())
                    );

                    // 搜索商品名称
                    const matchProducts = invoice.商品明细.some(prod =>
                        prod.商品名称.toLowerCase().includes(query.toLowerCase())
                    );

                    return matchBasic || matchProducts;
                } else if (field === '商品名称') {
                    // 仅搜索商品名称
                    return invoice.商品明细.some(prod =>
                        prod.商品名称.toLowerCase().includes(query.toLowerCase())
                    );
                } else {
                    // 搜索指定字段
                    const fieldValue = invoice[field].toString().toLowerCase();
                    return fieldValue.includes(query.toLowerCase());
                }
            });

            appState.searchResults = results;
            renderInvoiceList(results);
            updateSearchInfo(results.length);
        }

        // 清空搜索
        function clearSearch() {
            elements.searchInput.value = '';
            appState.isSearching = false;
            appState.searchResults = [];
            appState.searchQuery = '';
            appState.searchField = 'all';
            elements.searchInfo.classList.remove('active');
            renderInvoiceList();
            updateInvoiceCount();
        }

        // 更新搜索信息
        function updateSearchInfo(count) {
            if (appState.isSearching) {
                elements.searchInfo.innerHTML = `找到 <strong>${count}</strong> 条结果`;
                elements.searchInfo.classList.add('active');
            } else {
                elements.searchInfo.classList.remove('active');
            }
        }

        // 设置默认日期范围
        function setDefaultDateRange() {
            const today = new Date();
            const thirtyDaysAgo = new Date(today);
            thirtyDaysAgo.setDate(today.getDate() - 30);

            elements.endDate.value = formatDateForInput(today);
            elements.startDate.value = formatDateForInput(thirtyDaysAgo);
        }

        // 格式化日期为输入框格式
        function formatDateForInput(date) {
            return date.toISOString().split('T')[0];
        }

        // 切换统计面板显示
        function toggleStatsPanel() {
            const isHidden = elements.statsPanel.style.display === 'none' || elements.statsPanel.classList.contains('hidden');

            if (isHidden) {
                elements.statsPanel.style.display = 'block';
                elements.statsPanel.classList.add('active');
                updateStats();
                elements.toggleStatsBtn.innerHTML = '<span>📊</span> 收起统计';
                elements.toggleStatsBtn.classList.add('btn-secondary');
                elements.toggleStatsBtn.classList.remove('btn-outline');
            } else {
                elements.statsPanel.style.display = 'none';
                elements.statsPanel.classList.remove('active');
                elements.toggleStatsBtn.innerHTML = '<span>📊</span> 统计面板';
                elements.toggleStatsBtn.classList.remove('btn-secondary');
                elements.toggleStatsBtn.classList.add('btn-outline');
            }
        }

        // 切换详细统计展开/收起
        function toggleStatsDetails() {
            appState.statsExpanded = !appState.statsExpanded;

            if (appState.statsExpanded) {
                elements.statsDetails.classList.add('active');
                elements.toggleStatsDetails.classList.add('expanded');
                elements.toggleIcon.textContent = '▲';
                elements.toggleStatsDetails.querySelector('span').textContent = '▲ 收起详细统计分析';
                updateDetailStats();
            } else {
                elements.statsDetails.classList.remove('active');
                elements.toggleStatsDetails.classList.remove('expanded');
                elements.toggleIcon.textContent = '▼';
                elements.toggleStatsDetails.querySelector('span').textContent = '📊 查看详细统计分析';
            }
        }

        // 更新统计面板
        function updateStats() {
            if (appState.invoices.length === 0) {
                elements.coreStats.innerHTML = `
                    <div class="empty-stats">
                        暂无数据,请导入单据后查看统计
                    </div>
                `;
                return;
            }

            // 计算核心指标
            const totalInvoices = appState.invoices.length;
            const totalProducts = appState.invoices.reduce((sum, inv) => sum + inv.合计数量, 0);
            const totalAmount = appState.invoices.reduce((sum, inv) => sum + inv.总金额, 0);
            const avgAmount = totalInvoices > 0 ? totalAmount / totalInvoices : 0;
            const maxAmount = Math.max(...appState.invoices.map(inv => inv.总金额));

            // 计算客户、仓库、经手人数量
            const uniqueCustomers = new Set(appState.invoices.map(inv => inv.客户名称)).size;
            const uniqueWarehouses = new Set(appState.invoices.map(inv => inv.发货仓库)).size;
            const uniqueHandlers = new Set(appState.invoices.map(inv => inv.经手人)).size;

            // 计算商品种类
            const allProducts = new Set();
            appState.invoices.forEach(inv => {
                inv.商品明细.forEach(prod => allProducts.add(prod.商品名称));
            });
            const uniqueProducts = allProducts.size;

            // 计算时间范围
            const dates = appState.invoices.map(inv => new Date(inv.录单时间));
            const minDate = new Date(Math.min(...dates));
            const maxDate = new Date(Math.max(...dates));
            const dateRange = `${minDate.toLocaleDateString()} - ${maxDate.toLocaleDateString()}`;

            elements.coreStats.innerHTML = `
                <div class="stat-card">
                    <div class="stat-label">📄 单据总数</div>
                    <div class="stat-value">${totalInvoices}<span class="stat-unit">单</span></div>
                </div>
                <div class="stat-card secondary">
                    <div class="stat-label">📦 商品总数</div>
                    <div class="stat-value">${totalProducts}<span class="stat-unit">件</span></div>
                </div>
                <div class="stat-card danger">
                    <div class="stat-label">💰 总金额</div>
                    <div class="stat-value">¥${totalAmount.toFixed(2)}</div>
                </div>
                <div class="stat-card warning">
                    <div class="stat-label">📊 平均单据金额</div>
                    <div class="stat-value">¥${avgAmount.toFixed(2)}</div>
                </div>
                <div class="stat-card info">
                    <div class="stat-label">🎯 最高单据金额</div>
                    <div class="stat-value">¥${maxAmount.toFixed(2)}</div>
                </div>
                <div class="stat-card" style="background: linear-gradient(135deg, #e3f2fd 0%, #ffffff 100%);">
                    <div class="stat-label">👥 客户/仓库/经手人</div>
                    <div class="stat-value" style="font-size: 1.3rem;">${uniqueCustomers}/${uniqueWarehouses}/${uniqueHandlers}</div>
                    <div style="font-size: 0.8rem; color: var(--gray); margin-top: 4px;">客户/仓库/经手人</div>
                </div>
                <div class="stat-card" style="background: linear-gradient(135deg, #f3e5f5 0%, #ffffff 100%);">
                    <div class="stat-label">🎯 商品种类</div>
                    <div class="stat-value">${uniqueProducts}<span class="stat-unit">种</span></div>
                </div>
                <div class="stat-card" style="background: linear-gradient(135deg, #fff3e0 0%, #ffffff 100%);">
                    <div class="stat-label">📅 数据时间范围</div>
                    <div class="stat-value" style="font-size: 1.1rem; line-height: 1.4;">${dateRange}</div>
                </div>
            `;
        }

        // 按时间筛选
        function filterByTime() {
            if (appState.invoices.length === 0) {
                showToast('暂无数据', 'error');
                return;
            }

            const startDate = elements.startDate.value;
            const endDate = elements.endDate.value;
            const groupBy = elements.timeGroup.value;

            if (!startDate || !endDate) {
                showToast('请选择开始和结束日期', 'error');
                return;
            }

            if (new Date(startDate) > new Date(endDate)) {
                showToast('开始日期不能晚于结束日期', 'error');
                return;
            }

            // 筛选数据
            const filtered = appState.invoices.filter(inv => {
                const invDate = inv.录单时间.split(' ')[0]; // 只取日期部分
                return invDate >= startDate && invDate <= endDate;
            });

            if (filtered.length === 0) {
                elements.timeStatsResult.innerHTML = `
                    <div class="empty-stats">
                        该时间段内无数据<br>
                        <small>请选择其他日期范围</small>
                    </div>
                `;
                elements.timeChart.innerHTML = '<div class="chart-empty">暂无图表数据</div>';
                return;
            }

            appState.filteredTimeData = filtered;
            renderTimeStats(filtered, groupBy);
            renderTimeChart(filtered, groupBy);
        }

        // 重置时间筛选
        function resetTimeFilter() {
            setDefaultDateRange();
            elements.timeStatsResult.innerHTML = '<div class="empty-stats">请选择日期范围查看统计</div>';
            elements.timeChart.innerHTML = '<div class="chart-empty">暂无图表数据</div>';
            appState.filteredTimeData = [];
        }

        // 渲染时间统计
        function renderTimeStats(data, groupBy) {
            const grouped = groupDataByTime(data, groupBy);

            let html = `
                <table class="detail-table">
                    <thead>
                        <tr>
                            <th>时间周期</th>
                            <th>单据数</th>
                            <th>商品数</th>
                            <th>总金额</th>
                            <th>平均单据</th>
                        </tr>
                    </thead>
                    <tbody>
            `;

            const totalData = {
                invoices: 0,
                products: 0,
                amount: 0
            };

            Object.entries(grouped).forEach(([period, items]) => {
                const periodInvoices = items.length;
                const periodProducts = items.reduce((sum, inv) => sum + inv.合计数量, 0);
                const periodAmount = items.reduce((sum, inv) => sum + inv.总金额, 0);
                const periodAvg = periodInvoices > 0 ? periodAmount / periodInvoices : 0;

                totalData.invoices += periodInvoices;
                totalData.products += periodProducts;
                totalData.amount += periodAmount;

                html += `
                    <tr>
                        <td><strong>${period}</strong></td>
                        <td>${periodInvoices}</td>
                        <td>${periodProducts}</td>
                        <td>¥${periodAmount.toFixed(2)}</td>
                        <td>¥${periodAvg.toFixed(2)}</td>
                    </tr>
                `;
            });

            const overallAvg = totalData.invoices > 0 ? totalData.amount / totalData.invoices : 0;

            html += `
                    </tbody>
                    <tfoot>
                        <tr style="font-weight: 700; background: var(--light);">
                            <td>合计</td>
                            <td>${totalData.invoices}</td>
                            <td>${totalData.products}</td>
                            <td>¥${totalData.amount.toFixed(2)}</td>
                            <td>¥${overallAvg.toFixed(2)}</td>
                        </tr>
                    </tfoot>
                </table>
                <div style="margin-top: 10px; font-size: 0.85rem; color: var(--gray);">
                    统计周期: ${groupBy === 'day' ? '按日' : groupBy === 'week' ? '按周' : '按月'} |
                    数据范围: ${data.length} 个单据
                </div>
            `;

            elements.timeStatsResult.innerHTML = html;
        }

        // 渲染时间图表
        function renderTimeChart(data, groupBy) {
            const grouped = groupDataByTime(data, groupBy);
            const entries = Object.entries(grouped);

            if (entries.length === 0) {
                elements.timeChart.innerHTML = '<div class="chart-empty">暂无图表数据</div>';
                return;
            }

            // 计算每个周期的总金额
            const chartData = entries.map(([period, items]) => ({
                period,
                amount: items.reduce((sum, inv) => sum + inv.总金额, 0),
                count: items.length
            }));

            // 找到最大值用于缩放
            const maxAmount = Math.max(...chartData.map(d => d.amount));

            let html = '<div class="chart-bar">';

            chartData.forEach(item => {
                const height = maxAmount > 0 ? (item.amount / maxAmount * 100) : 0;
                html += `
                    <div class="bar-item" style="height: ${height}%;" title="${item.period}: ¥${item.amount.toFixed(2)} (${item.count}单)">
                        <div class="bar-value">¥${item.amount.toFixed(0)}</div>
                        <div class="bar-label">${item.period}</div>
                    </div>
                `;
            });

            html += '</div>';

            html += `
                <div style="margin-top: 10px; font-size: 0.8rem; color: var(--gray); text-align: center;">
                    鼠标悬停查看详细金额 | 共 ${chartData.length} 个周期
                </div>
            `;

            elements.timeChart.innerHTML = html;
        }

        // 按时间分组数据
        function groupDataByTime(data, groupBy) {
            const grouped = {};

            data.forEach(inv => {
                let key;
                const date = new Date(inv.录单时间.split(' ')[0]);

                if (groupBy === 'day') {
                    key = inv.录单时间.split(' ')[0]; // YYYY-MM-DD
                } else if (groupBy === 'week') {
                    // 获取周起始日期(周一)
                    const day = date.getDay();
                    const diff = date.getDate() - day + (day === 0 ? -6 : 1);
                    const weekStart = new Date(date.setDate(diff));
                    key = `${weekStart.getFullYear()}-${String(weekStart.getMonth() + 1).padStart(2, '0')}-${String(weekStart.getDate()).padStart(2, '0')} (周)`;
                } else if (groupBy === 'month') {
                    key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
                }

                if (!grouped[key]) {
                    grouped[key] = [];
                }
                grouped[key].push(inv);
            });

            // 按键排序
            const sorted = {};
            Object.keys(grouped).sort().forEach(key => {
                sorted[key] = grouped[key];
            });

            return sorted;
        }

        // 更新详细统计
        function updateDetailStats() {
            if (appState.invoices.length === 0) return;

            // 按客户统计
            const customerMap = new Map();
            appState.invoices.forEach(inv => {
                const key = inv.客户名称;
                if (!customerMap.has(key)) {
                    customerMap.set(key, { count: 0, amount: 0 });
                }
                const data = customerMap.get(key);
                data.count++;
                data.amount += inv.总金额;
            });

            const customerStatsHTML = generateDetailTable(
                Array.from(customerMap.entries()).map(([name, data]) => ({
                    name,
                    count: data.count,
                    amount: data.amount
                })),
                '客户名称'
            );
            elements.customerStats.innerHTML = customerStatsHTML || '<div class="empty-stats">无数据</div>';

            // 按仓库统计
            const warehouseMap = new Map();
            appState.invoices.forEach(inv => {
                const key = inv.发货仓库;
                if (!warehouseMap.has(key)) {
                    warehouseMap.set(key, { count: 0, amount: 0 });
                }
                const data = warehouseMap.get(key);
                data.count++;
                data.amount += inv.总金额;
            });

            const warehouseStatsHTML = generateDetailTable(
                Array.from(warehouseMap.entries()).map(([name, data]) => ({
                    name,
                    count: data.count,
                    amount: data.amount
                })),
                '仓库名称'
            );
            elements.warehouseStats.innerHTML = warehouseStatsHTML || '<div class="empty-stats">无数据</div>';

            // 按经手人统计
            const handlerMap = new Map();
            appState.invoices.forEach(inv => {
                const key = inv.经手人;
                if (!handlerMap.has(key)) {
                    handlerMap.set(key, { count: 0, amount: 0 });
                }
                const data = handlerMap.get(key);
                data.count++;
                data.amount += inv.总金额;
            });

            const handlerStatsHTML = generateDetailTable(
                Array.from(handlerMap.entries()).map(([name, data]) => ({
                    name,
                    count: data.count,
                    amount: data.amount
                })),
                '经手人'
            );
            elements.handlerStats.innerHTML = handlerStatsHTML || '<div class="empty-stats">无数据</div>';

            // 按商品统计
            const productMap = new Map();
            appState.invoices.forEach(inv => {
                inv.商品明细.forEach(prod => {
                    const key = prod.商品名称;
                    if (!productMap.has(key)) {
                        productMap.set(key, { quantity: 0, amount: 0 });
                    }
                    const data = productMap.get(key);
                    data.quantity += prod.数量;
                    data.amount += prod.金额;
                });
            });

            const productStatsHTML = generateProductTable(
                Array.from(productMap.entries()).map(([name, data]) => ({
                    name,
                    quantity: data.quantity,
                    amount: data.amount
                }))
            );
            elements.productStats.innerHTML = productStatsHTML || '<div class="empty-stats">无数据</div>';
        }

        // 生成详情表格
        function generateDetailTable(data, nameHeader) {
            if (!data || data.length === 0) return '';

            const totalAmount = data.reduce((sum, item) => sum + item.amount, 0);
            const maxAmount = Math.max(...data.map(item => item.amount));

            let html = `
                <table class="detail-table">
                    <thead>
                        <tr>
                            <th>${nameHeader}</th>
                            <th>单据数</th>
                            <th>金额</th>
                            <th>占比</th>
                        </tr>
                    </thead>
                    <tbody>
            `;

            data.forEach(item => {
                const percentage = totalAmount > 0 ? (item.amount / totalAmount * 100).toFixed(1) : 0;
                const isTop = item.amount === maxAmount;

                html += `
                    <tr style="${isTop ? 'background: rgba(46, 204, 113, 0.1); font-weight: 600;' : ''}">
                        <td>${item.name} ${isTop ? '🏆' : ''}</td>
                        <td>${item.count}</td>
                        <td>¥${item.amount.toFixed(2)}</td>
                        <td>
                            ${percentage}%
                            <div class="progress-bar">
                                <div class="progress-fill" style="width: ${percentage}%"></div>
                            </div>
                        </td>
                    </tr>
                `;
            });

            html += `
                    </tbody>
                    <tfoot>
                        <tr style="font-weight: 700; background: var(--light);">
                            <td>合计</td>
                            <td>${data.reduce((sum, item) => sum + item.count, 0)}</td>
                            <td>¥${totalAmount.toFixed(2)}</td>
                            <td>100%</td>
                        </tr>
                    </tfoot>
                </table>
            `;

            return html;
        }

        // 生成商品统计表格
        function generateProductTable(data) {
            if (!data || data.length === 0) return '';

            const totalAmount = data.reduce((sum, item) => sum + item.amount, 0);
            const totalQuantity = data.reduce((sum, item) => sum + item.quantity, 0);
            const maxAmount = Math.max(...data.map(item => item.amount));

            let html = `
                <table class="detail-table">
                    <thead>
                        <tr>
                            <th>商品名称</th>
                            <th>销量</th>
                            <th>销售额</th>
                            <th>占比</th>
                        </tr>
                    </thead>
                    <tbody>
            `;

            data.forEach(item => {
                const percentage = totalAmount > 0 ? (item.amount / totalAmount * 100).toFixed(1) : 0;
                const isTop = item.amount === maxAmount;

                html += `
                    <tr style="${isTop ? 'background: rgba(52, 152, 219, 0.1); font-weight: 600;' : ''}">
                        <td>${item.name} ${isTop ? '🥇' : ''}</td>
                        <td>${item.quantity}</td>
                        <td>¥${item.amount.toFixed(2)}</td>
                        <td>
                            ${percentage}%
                            <div class="progress-bar">
                                <div class="progress-fill" style="width: ${percentage}%"></div>
                            </div>
                        </td>
                    </tr>
                `;
            });

            html += `
                    </tbody>
                    <tfoot>
                        <tr style="font-weight: 700; background: var(--light);">
                            <td>合计</td>
                            <td>${totalQuantity}</td>
                            <td>¥${totalAmount.toFixed(2)}</td>
                            <td>100%</td>
                        </tr>
                    </tfoot>
                </table>
            `;

            return html;
        }

        // 显示导入区域
        function showImportArea() {
            elements.importContainer.classList.remove('hidden');
            elements.importContainer.scrollIntoView({ behavior: 'smooth' });
            elements.importBtn.disabled = true;
        }

        // 隐藏导入区域
        function hideImportArea() {
            elements.importContainer.classList.add('hidden');
            elements.importBtn.disabled = false;
            clearJsonInput();
            hidePreview();
            hideValidation();
        }

        // 清空JSON输入
        function clearJsonInput() {
            elements.jsonTextarea.value = '';
            elements.fileInput.value = '';
        }

        // 隐藏预览
        function hidePreview() {
            elements.previewSection.classList.remove('active');
            appState.parsedData = null;
        }

        // 隐藏验证结果
        function hideValidation() {
            elements.validationResult.className = 'validation-result';
            elements.validationResult.style.display = 'none';
        }

        // 拖拽处理
        function handleDragOver(e) {
            e.preventDefault();
            elements.dropZone.classList.add('dragover');
        }

        function handleDragLeave(e) {
            e.preventDefault();
            elements.dropZone.classList.remove('dragover');
        }

        function handleDrop(e) {
            e.preventDefault();
            elements.dropZone.classList.remove('dragover');

            const files = e.dataTransfer.files;
            if (files.length > 0) {
                processFile(files[0]);
            }
        }

        // 文件选择处理
        function handleFileSelect(e) {
            const file = e.target.files[0];
            if (file) {
                processFile(file);
            }
        }

        // 处理文件
        function processFile(file) {
            if (file.type !== 'application/json' && !file.name.endsWith('.json')) {
                showToast('请选择JSON文件', 'error');
                return;
            }

            const reader = new FileReader();
            reader.onload = function(e) {
                try {
                    const json = JSON.parse(e.target.result);
                    elements.jsonTextarea.value = JSON.stringify(json, null, 2);
                    showToast('文件已加载,请确认导入', 'success');
                } catch (error) {
                    showToast('JSON文件格式错误: ' + error.message, 'error');
                }
            };
            reader.readAsText(file);
        }

        // 解析JSON数据
        function parseJsonData() {
            const jsonText = elements.jsonTextarea.value.trim();

            if (!jsonText) {
                showToast('请上传文件或粘贴JSON数据', 'error');
                return;
            }

            try {
                const data = JSON.parse(jsonText);

                // 验证数据格式
                const validation = validateData(data);

                if (!validation.valid) {
                    showValidation(false, validation.message);
                    return;
                }

                // 保存解析的数据
                appState.parsedData = validation.processedData;

                // 显示预览
                showPreview(validation.processedData, validation.stats);

                showValidation(true, `✅ 数据格式正确!共 ${validation.stats.totalInvoices} 个单据,${validation.stats.totalProducts} 个商品条目`);

            } catch (error) {
                showValidation(false, `JSON解析错误: ${error.message}`);
            }
        }

        // 验证数据格式
        function validateData(data) {
            const stats = {
                totalInvoices: 0,
                totalProducts: 0,
                validInvoices: 0
            };

            // 如果是单个对象,转换为数组
            const dataArray = Array.isArray(data) ? data : [data];
            const processedData = [];

            for (const invoice of dataArray) {
                // 检查必需字段
                const requiredFields = ['单据编号', '客户名称', '发货仓库', '经手人', '录单时间', '商品明细'];
                for (const field of requiredFields) {
                    if (!invoice[field]) {
                        return { valid: false, message: `缺少必需字段: ${field}` };
                    }
                }

                // 检查商品明细
                if (!Array.isArray(invoice.商品明细) || invoice.商品明细.length === 0) {
                    return { valid: false, message: `单据 ${invoice.单据编号} 的商品明细不能为空` };
                }

                // 验证并计算商品明细
                const processedProducts = [];
                let totalQuantity = 0;
                let totalAmount = 0;

                for (const product of invoice.商品明细) {
                    const requiredProductFields = ['行号', '商品名称', '数量', '单价', '金额'];
                    for (const field of requiredProductFields) {
                        if (product[field] === undefined || product[field] === null) {
                            return { valid: false, message: `商品缺少字段: ${field}` };
                        }
                    }

                    // 验证数值
                    if (typeof product.数量 !== 'number' || product.数量 <= 0) {
                        return { valid: false, message: `商品 ${product.商品名称} 的数量必须是正数` };
                    }
                    if (typeof product.单价 !== 'number' || product.单价 < 0) {
                        return { valid: false, message: `商品 ${product.商品名称} 的单价不能为负数` };
                    }
                    if (typeof product.金额 !== 'number' || product.金额 < 0) {
                        return { valid: false, message: `商品 ${product.商品名称} 的金额不能为负数` };
                    }

                    // 自动计算金额验证
                    const calculatedAmount = product.数量 * product.单价;
                    if (Math.abs(calculatedAmount - product.金额) > 0.01) {
                        // 允许微小差异,但给出警告
                        console.warn(`商品 ${product.商品名称} 金额不匹配: 计算值 ${calculatedAmount}, 提供值 ${product.金额}`);
                    }

                    processedProducts.push({
                        行号: product.行号,
                        商品名称: product.商品名称,
                        数量: product.数量,
                        单价: product.单价,
                        金额: product.金额,
                        备注: product.备注 || null
                    });

                    totalQuantity += product.数量;
                    totalAmount += product.金额;
                }

                // 构建处理后的单据
                const processedInvoice = {
                    单据编号: invoice.单据编号,
                    客户名称: invoice.客户名称,
                    发货仓库: invoice.发货仓库,
                    经手人: invoice.经手人,
                    录单时间: invoice.录单时间,
                    商品明细: processedProducts,
                    合计数量: totalQuantity,
                    总金额: parseFloat(totalAmount.toFixed(2))
                };

                processedData.push(processedInvoice);
                stats.totalInvoices++;
                stats.totalProducts += processedProducts.length;
                stats.validInvoices++;
            }

            return { valid: true, processedData, stats };
        }

        // 显示验证结果
        function showValidation(isSuccess, message) {
            elements.validationResult.textContent = message;
            elements.validationResult.className = `validation-result ${isSuccess ? 'success' : 'error'}`;
            elements.validationResult.style.display = 'block';
        }

        // 显示预览
        function showPreview(data, stats) {
            elements.previewContent.textContent = JSON.stringify(data, null, 2);

            elements.previewStats.innerHTML = `
                <div class="stat-item">单据数: <strong>${stats.totalInvoices}</strong></div>
                <div class="stat-item">商品条目: <strong>${stats.totalProducts}</strong></div>
            `;

            elements.previewSection.classList.add('active');
            elements.previewSection.scrollIntoView({ behavior: 'smooth' });
        }

        // 确认导入
        function confirmImport() {
            if (!appState.parsedData) {
                showToast('没有可导入的数据', 'error');
                return;
            }

            // 检查单据编号冲突
            const existingNumbers = new Set(appState.invoices.map(inv => inv.单据编号));
            const newInvoices = [];
            const conflicts = [];

            for (const invoice of appState.parsedData) {
                if (existingNumbers.has(invoice.单据编号)) {
                    conflicts.push(invoice.单据编号);
                } else {
                    newInvoices.push(invoice);
                }
            }

            if (conflicts.length > 0) {
                const proceed = confirm(
                    `以下单据编号已存在,将被跳过:\n${conflicts.join('\n')}\n\n是否继续导入其他单据?`
                );
                if (!proceed) {
                    return;
                }
            }

            if (newInvoices.length === 0) {
                showToast('所有导入的单据编号都已存在', 'error');
                return;
            }

            // 添加新单据
            appState.invoices.push(...newInvoices);
            saveToLocalStorage();
            renderInvoiceList();
            updateInvoiceCount();

            // 更新统计
            if (elements.statsPanel.style.display !== 'none') {
                updateStats();
            }

            showToast(`成功导入 ${newInvoices.length} 个单据`, 'success');

            // 自动选中第一个新单据
            if (newInvoices.length > 0) {
                selectInvoice(newInvoices[0]);
            }

            hideImportArea();
        }

        // 渲染单据列表
        function renderInvoiceList(data = null) {
            const displayData = data || (appState.isSearching ? appState.searchResults : appState.invoices);

            elements.invoiceList.innerHTML = '';

            if (displayData.length === 0) {
                const message = appState.isSearching ? '未找到匹配的单据' : '暂无单据,请导入数据';
                elements.invoiceList.innerHTML = `
                    <div class="empty-state" style="padding: 20px; min-height: 200px;">
                        <p>${message}</p>
                    </div>
                `;
                return;
            }

            displayData.forEach((invoice, index) => {
                const item = document.createElement('div');
                item.className = 'invoice-item';

                // 高亮搜索结果
                if (appState.isSearching) {
                    item.classList.add('highlight');
                }

                if (appState.selectedInvoice && appState.selectedInvoice.单据编号 === invoice.单据编号) {
                    item.classList.add('active');
                }

                item.innerHTML = `
                    <div class="invoice-header">
                        <div class="invoice-number">${invoice.单据编号}</div>
                        <div class="invoice-date">${invoice.录单时间}</div>
                    </div>
                    <div class="invoice-details">
                        <span>👤 ${invoice.客户名称}</span>
                        <span>🏢 ${invoice.发货仓库}</span>
                        <span>👤 ${invoice.经手人}</span>
                    </div>
                    <div class="invoice-details">
                        <span>📦 ${invoice.合计数量} 件</span>
                        <span>💰 ¥${invoice.总金额}</span>
                    </div>
                `;

                item.addEventListener('click', () => selectInvoice(invoice));
                elements.invoiceList.appendChild(item);
            });

            // 更新计数
            if (appState.isSearching) {
                elements.invoiceCount.textContent = `${displayData.length} / ${appState.invoices.length} 单`;
            } else {
                elements.invoiceCount.textContent = `${displayData.length} 单`;
            }
        }

        // 选择单据
        function selectInvoice(invoice) {
            appState.selectedInvoice = invoice;
            renderInvoiceList();
            renderInvoiceDetail();

            // 显示删除按钮
            elements.deleteBtn.classList.remove('hidden');
        }

        // 渲染单据详情
        function renderInvoiceDetail() {
            if (!appState.selectedInvoice) {
                elements.emptyState.style.display = 'flex';
                elements.detailView.classList.remove('active');
                return;
            }

            elements.emptyState.style.display = 'none';
            elements.detailView.classList.add('active');

            const inv = appState.selectedInvoice;

            // 生成商品明细表格
            let productsHTML = `
                <table class="products-table">
                    <thead>
                        <tr>
                            <th>行号</th>
                            <th>商品名称</th>
                            <th>数量</th>
                            <th>单价</th>
                            <th>金额</th>
                        </tr>
                    </thead>
                    <tbody>
            `;

            inv.商品明细.forEach(product => {
                productsHTML += `
                    <tr>
                        <td>${product.行号}</td>
                        <td>${product.商品名称}</td>
                        <td>${product.数量}</td>
                        <td>¥${product.单价}</td>
                        <td>¥${product.金额}</td>
                    </tr>
                `;
            });

            productsHTML += `
                    <tr class="total-row">
                        <td colspan="3">合计</td>
                        <td>${inv.合计数量} 件</td>
                        <td>¥${inv.总金额}</td>
                    </tr>
                </tbody>
                </table>
            `;

            elements.detailView.innerHTML = `
                <div class="detail-section">
                    <h3>📋 基本信息</h3>
                    <div class="detail-grid">
                        <div class="detail-item">
                            <div class="detail-label">单据编号</div>
                            <div class="detail-value">${inv.单据编号}</div>
                        </div>
                        <div class="detail-item">
                            <div class="detail-label">客户名称</div>
                            <div class="detail-value">${inv.客户名称}</div>
                        </div>
                        <div class="detail-item">
                            <div class="detail-label">发货仓库</div>
                            <div class="detail-value">${inv.发货仓库}</div>
                        </div>
                        <div class="detail-item">
                            <div class="detail-label">经手人</div>
                            <div class="detail-value">${inv.经手人}</div>
                        </div>
                        <div class="detail-item">
                            <div class="detail-label">录单时间</div>
                            <div class="detail-value">${inv.录单时间}</div>
                        </div>
                    </div>
                </div>

                <div class="detail-section">
                    <h3>📦 商品明细</h3>
                    ${productsHTML}
                </div>
            `;
        }

        // 删除单据
        function deleteInvoice() {
            if (!appState.selectedInvoice) return;

            if (confirm(`确定要删除单据 ${appState.selectedInvoice.单据编号} 吗?`)) {
                appState.invoices = appState.invoices.filter(
                    inv => inv.单据编号 !== appState.selectedInvoice.单据编号
                );

                appState.selectedInvoice = null;
                saveToLocalStorage();
                renderInvoiceList();
                renderInvoiceDetail();
                updateInvoiceCount();
                elements.deleteBtn.classList.add('hidden');

                // 更新统计
                if (elements.statsPanel.style.display !== 'none') {
                    updateStats();
                    // 如果有时间筛选,也更新时间统计
                    if (appState.filteredTimeData.length > 0) {
                        filterByTime();
                    }
                }

                // 如果正在搜索,更新搜索结果
                if (appState.isSearching) {
                    performSearch();
                }

                showToast('单据已删除', 'success');
            }
        }

        // 导出数据
        function exportData() {
            if (appState.invoices.length === 0) {
                showToast('没有数据可导出', 'error');
                return;
            }

            const dataStr = JSON.stringify(appState.invoices, null, 2);
            const dataBlob = new Blob([dataStr], { type: 'application/json' });

            const link = document.createElement('a');
            link.href = URL.createObjectURL(dataBlob);
            link.download = `销售单据_${new Date().toISOString().slice(0,10)}.json`;
            link.click();

            showToast('数据导出成功', 'success');
        }

        // 重置数据
        function resetData() {
            if (confirm('确定要清空所有数据吗?此操作不可恢复。')) {
                appState.invoices = [];
                appState.selectedInvoice = null;
                appState.filteredTimeData = [];
                appState.searchResults = [];
                appState.isSearching = false;
                saveToLocalStorage();
                renderInvoiceList();
                renderInvoiceDetail();
                updateInvoiceCount();
                elements.deleteBtn.classList.add('hidden');

                // 清空统计
                if (elements.statsPanel.style.display !== 'none') {
                    updateStats();
                    elements.timeStatsResult.innerHTML = '<div class="empty-stats">请选择日期范围查看统计</div>';
                    elements.timeChart.innerHTML = '<div class="chart-empty">暂无图表数据</div>';
                }

                // 清空搜索
                if (elements.searchContainer.classList.contains('active')) {
                    clearSearch();
                }

                showToast('数据已清空', 'success');
            }
        }

        // 保存到localStorage
        function saveToLocalStorage() {
            localStorage.setItem('invoiceData', JSON.stringify(appState.invoices));
        }

        // 更新单据计数
        function updateInvoiceCount() {
            elements.invoiceCount.textContent = `${appState.invoices.length} 单`;
        }

        // 显示提示
        function showToast(message, type = 'success') {
            elements.toast.textContent = message;
            elements.toast.className = `toast ${type}`;
            elements.toast.classList.add('show');

            setTimeout(() => {
                elements.toast.classList.remove('show');
            }, 3000);
        }

        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', initApp);
    </script>
</body>
</html>

附加:

用Nginx 作服务器 ,后端FastAPI , 外网 用 节点小宝

相关推荐
摘星编程2 小时前
React Native for OpenHarmony 实战:Battery 电池状态详解
javascript·react native·react.js
全栈前端老曹2 小时前
【前端】Hammer.js 快速上手入门教程
开发语言·前端·javascript·vue·react·移动端开发·hammer.js
亮子AI2 小时前
【NestJS】为什么return不返回客户端?
前端·javascript·git·nestjs
Filotimo_2 小时前
Vue3 + Element Plus 表格复选框踩坑记录
javascript·vue.js·elementui
曹天骄2 小时前
我是如何用 Cloudflare Worker 实现 HTML 灰度发布与两级缓存的
java·缓存·html
pas1362 小时前
32-mini-vue 更新element的children-双端对比 diff 算法
javascript·vue.js·算法
恋爱绝缘体12 小时前
CSS3 多媒体查询实例【1】
前端·css·css3
写代码的【黑咖啡】2 小时前
Python中的BeautifulSoup:强大的HTML/XML解析库
python·html·beautifulsoup
写bug的可宋2 小时前
【Electron】解决Electron使用阿里iconfont不生效问题(react+vite)
javascript·react.js·electron