做题记录3(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 {
           --bg: #f8f9fa;
           --card-bg: #ffffff;
           --text: #2c3e50;
           --text-secondary: #5a6c7d;
           --border: #e2e8f0;
           --accent: #4f6ef7;
           --accent-hover: #3b54d4;
           --tag-green: #e6f7ed;
           --tag-green-text: #1a7f4b;
           --tag-orange: #fff7ed;
           --tag-orange-text: #b3531a;
           --tag-red: #fef2f2;
           --tag-red-text: #b91c1c;
           --tag-blue: #eff6ff;
           --tag-blue-text: #1d4ed8;
           --tag-purple: #faf5ff;
           --tag-purple-text: #6d28d9;
           --tag-gray: #f3f4f6;
           --tag-gray-text: #4b5563;
           --row-hover: #f1f5f9;
           --shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
           --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04);
           --radius: 10px;
           --radius-sm: 6px;
           --transition: 0.2s ease;
       }

       * {
           margin: 0;
           padding: 0;
           box-sizing: border-box;
       }

       body {
           font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', sans-serif;
           background: #f0f2f5;
           color: var(--text);
           line-height: 1.6;
           min-height: 100vh;
           padding: 16px;
       }

       .container {
           max-width: 1300px;
           margin: 0 auto;
       }

       /* Header Card */
       .header {
           background: var(--card-bg);
           border-radius: var(--radius);
           padding: 20px 24px;
           margin-bottom: 16px;
           box-shadow: var(--shadow);
           display: flex;
           flex-wrap: wrap;
           align-items: center;
           justify-content: space-between;
           gap: 14px;
           border: 1px solid var(--border);
       }
       .header-left {
           display: flex;
           align-items: center;
           gap: 12px;
           flex-wrap: wrap;
       }
       .header-icon {
           width: 40px;
           height: 40px;
           background: linear-gradient(135deg, #4f6ef7, #7c5cfc);
           border-radius: 10px;
           display: flex;
           align-items: center;
           justify-content: center;
           font-size: 20px;
           color: #fff;
           flex-shrink: 0;
       }
       .header h1 {
           font-size: 1.35rem;
           font-weight: 700;
           color: #1a202c;
           letter-spacing: -0.3px;
           white-space: nowrap;
       }
       .header .stats {
           display: flex;
           gap: 10px;
           flex-wrap: wrap;
           font-size: 0.85rem;
           color: var(--text-secondary);
       }
       .header .stats span {
           background: #f8fafc;
           padding: 4px 10px;
           border-radius: 20px;
           font-weight: 500;
           white-space: nowrap;
           border: 1px solid #e8ecf1;
       }
       .header .stats .count-num {
           font-weight: 700;
           color: var(--accent);
           font-size: 1rem;
       }

       /* Controls */
       .controls {
           display: flex;
           gap: 10px;
           flex-wrap: wrap;
           align-items: center;
       }
       .search-box {
           position: relative;
       }
       .search-box input {
           padding: 9px 14px 9px 36px;
           border: 1.5px solid #dde1e7;
           border-radius: 24px;
           font-size: 0.9rem;
           width: 240px;
           transition: var(--transition);
           background: #fafbfc;
           color: var(--text);
           outline: none;
       }
       .search-box input:focus {
           border-color: var(--accent);
           box-shadow: 0 0 0 3px rgba(79, 110, 247, 0.1);
           background: #fff;
           width: 280px;
       }
       .search-box .search-icon {
           position: absolute;
           left: 12px;
           top: 50%;
           transform: translateY(-50%);
           color: #94a3b8;
           font-size: 15px;
           pointer-events: none;
       }
       .btn {
           display: inline-flex;
           align-items: center;
           gap: 6px;
           padding: 9px 18px;
           border-radius: 24px;
           font-size: 0.9rem;
           font-weight: 600;
           cursor: pointer;
           border: none;
           transition: var(--transition);
           white-space: nowrap;
           text-decoration: none;
           letter-spacing: 0.2px;
       }
       .btn-download {
           background: var(--accent);
           color: #fff;
           box-shadow: 0 2px 6px rgba(79, 110, 247, 0.3);
       }
       .btn-download:hover {
           background: var(--accent-hover);
           box-shadow: 0 4px 14px rgba(79, 110, 247, 0.4);
           transform: translateY(-1px);
       }
       .btn-download:active {
           transform: translateY(0);
       }
       .btn-outline {
           background: #fff;
           color: var(--text);
           border: 1.5px solid #dde1e7;
       }
       .btn-outline:hover {
           background: #f8fafc;
           border-color: #c4cad4;
       }
       .btn-sm {
           padding: 5px 12px;
           font-size: 0.78rem;
           border-radius: 16px;
       }

       /* Table Card */
       .table-card {
           background: var(--card-bg);
           border-radius: var(--radius);
           box-shadow: var(--shadow);
           overflow: hidden;
           border: 1px solid var(--border);
       }
       .table-wrapper {
           overflow-x: auto;
           max-height: 75vh;
           overflow-y: auto;
           scroll-behavior: smooth;
       }
       .table-wrapper::-webkit-scrollbar {
           height: 6px;
           width: 6px;
       }
       .table-wrapper::-webkit-scrollbar-track {
           background: transparent;
       }
       .table-wrapper::-webkit-scrollbar-thumb {
           background: #cbd5e1;
           border-radius: 3px;
       }
       .table-wrapper::-webkit-scrollbar-thumb:hover {
           background: #a0aec0;
       }

       table {
           width: 100%;
           border-collapse: collapse;
           font-size: 0.88rem;
           min-width: 800px;
           table-layout: auto;
       }
       thead {
           position: sticky;
           top: 0;
           z-index: 10;
           background: #fafbfc;
       }
       thead th {
           padding: 13px 14px;
           text-align: left;
           font-weight: 700;
           font-size: 0.8rem;
           text-transform: uppercase;
           letter-spacing: 0.5px;
           color: #5a6c7d;
           border-bottom: 2px solid #e2e8f0;
           white-space: nowrap;
           cursor: pointer;
           user-select: none;
           transition: background 0.15s;
           position: relative;
       }
       thead th:hover {
           background: #eef1f6;
           color: #2c3e50;
       }
       thead th .sort-arrow {
           margin-left: 4px;
           font-size: 0.65rem;
           opacity: 0.4;
           transition: opacity 0.15s;
       }
       thead th.sorted .sort-arrow {
           opacity: 1;
           color: var(--accent);
       }
       thead th.col-id {
           width: 90px;
           min-width: 80px;
       }
       thead th.col-date {
           width: 80px;
           min-width: 70px;
       }
       thead th.col-diff {
           width: 130px;
           min-width: 110px;
       }
       thead th.col-rating {
           width: 90px;
           min-width: 70px;
       }
       thead th.col-algo {
           min-width: 240px;
       }

       tbody tr {
           transition: background 0.12s;
           border-bottom: 1px solid #f1f5f9;
       }
       tbody tr:hover {
           background: var(--row-hover);
       }
       tbody tr:last-child {
           border-bottom: none;
       }
       tbody td {
           padding: 11px 14px;
           vertical-align: middle;
           color: #334155;
       }
       .prob-id {
           font-family: 'SF Mono', 'Cascadia Code', 'Consolas', 'Menlo', 'Monaco', 'Microsoft YaHei', monospace;
           font-weight: 700;
           color: #1e293b;
           font-size: 0.9rem;
           letter-spacing: -0.2px;
       }
       .prob-id a {
           color: var(--accent);
           text-decoration: none;
           transition: color 0.15s;
       }
       .prob-id a:hover {
           color: var(--accent-hover);
           text-decoration: underline;
           text-underline-offset: 3px;
       }

       /* Tags */
       .tag {
           display: inline-block;
           padding: 4px 10px;
           border-radius: 14px;
           font-size: 0.76rem;
           font-weight: 600;
           letter-spacing: 0.2px;
           white-space: nowrap;
       }
       .tag-diff-pup {
           background: #e8f5e9;
           color: #2e7d32;
       }
       .tag-diff-pup-plus {
           background: #dcedc8;
           color: #33691e;
       }
       .tag-diff-improve {
           background: #fff3e0;
           color: #e65100;
       }
       .tag-diff-improve-plus {
           background: #ffe0b2;
           color: #bf360c;
       }
       .tag-diff-prov {
           background: #fce4ec;
           color: #880e4f;
       }
       .tag-diff-noi {
           background: #ede7f6;
           color: #4527a0;
       }
       .tag-diff-ct {
           background: #ffebee;
           color: #b71c1c;
           font-weight: 700;
       }
       .tag-rating-good {
           background: #e6f7ed;
           color: #1a7f4b;
       }
       .tag-rating-great {
           background: #d4f1e4;
           color: #0d5e33;
           font-weight: 700;
       }
       .tag-rating-bad {
           background: #fef2f2;
           color: #b91c1c;
       }
       .tag-rating-hard {
           background: #fff7ed;
           color: #c2410c;
           font-weight: 700;
       }
       .tag-rating-water {
           background: #f0f9ff;
           color: #0369a1;
       }

       .algo-list {
           display: flex;
           flex-wrap: wrap;
           gap: 4px;
       }
       .algo-tag {
           display: inline-block;
           background: #f1f5f9;
           color: #475569;
           padding: 3px 8px;
           border-radius: 10px;
           font-size: 0.75rem;
           font-weight: 500;
           white-space: nowrap;
           letter-spacing: 0.1px;
           border: 1px solid #e8ecf1;
       }

       /* Toast */
       .toast {
           position: fixed;
           bottom: 30px;
           left: 50%;
           transform: translateX(-50%) translateY(100px);
           background: #1e293b;
           color: #fff;
           padding: 12px 22px;
           border-radius: 28px;
           font-size: 0.88rem;
           font-weight: 600;
           letter-spacing: 0.3px;
           z-index: 1000;
           opacity: 0;
           transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
           pointer-events: none;
           box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
       }
       .toast.show {
           opacity: 1;
           transform: translateX(-50%) translateY(0);
       }

       .no-results {
           text-align: center;
           padding: 40px 20px;
           color: #94a3b8;
           font-size: 0.95rem;
       }
       .no-results .icon {
           font-size: 2.5rem;
           display: block;
           margin-bottom: 8px;
       }

       @media (max-width: 768px) {
           .header {
               flex-direction: column;
               align-items: flex-start;
           }
           .controls {
               width: 100%;
           }
           .search-box input {
               width: 100%;
           }
           .search-box input:focus {
               width: 100%;
           }
           .table-wrapper {
               max-height: 60vh;
           }
           table {
               font-size: 0.8rem;
               min-width: 650px;
           }
           thead th,
           tbody td {
               padding: 8px 10px;
           }
           .btn {
               padding: 7px 14px;
               font-size: 0.82rem;
           }
       }
   </style>
</head>
<body>
   <div class="container">
       <!-- Header -->
       <div class="header">
           <div class="header-left">
               <div class="header-icon">📋</div>
               <div>
                   <h1>算法刷题记录</h1>
                   <div class="stats">
                       <span>总计 <strong class="count-num" id="totalCount">0</strong> 题</span>
                       <span>日期范围 <strong>05-16 ~ 07-15</strong></span>
                   </div>
               </div>
           </div>
           <div class="controls">
               <div class="search-box">
                   <span class="search-icon">🔍</span>
                   <input type="text" id="searchInput" placeholder="搜索题目编号、算法、备注..." autocomplete="off">
               </div>
               <button class="btn btn-download" onclick="downloadCSV()" title="下载为CSV文件(可用Excel打开)">
                   📥 下载CSV
               </button>
               <button class="btn btn-outline btn-sm" onclick="resetTable()" title="清除搜索与排序">
                   🔄 重置
               </button>
           </div>
       </div>

       <!-- Table -->
       <div class="table-card">
           <div class="table-wrapper" id="tableWrapper">
               <table id="dataTable">
                   <thead>
                       <tr>
                           <th class="col-id" data-sort="id" onclick="sortTable('id')">
                               题目编号 <span class="sort-arrow">⇅</span>
                           </th>
                           <th class="col-date" data-sort="date" onclick="sortTable('date')">
                               日期 <span class="sort-arrow">⇅</span>
                           </th>
                           <th class="col-diff" data-sort="difficulty" onclick="sortTable('difficulty')">
                               难度 <span class="sort-arrow">⇅</span>
                           </th>
                           <th class="col-rating" data-sort="rating" onclick="sortTable('rating')">
                               评价 <span class="sort-arrow">⇅</span>
                           </th>
                           <th class="col-algo" data-sort="algo" onclick="sortTable('algo')">
                               算法 / 备注 <span class="sort-arrow">⇅</span>
                           </th>
                       </tr>
                   </thead>
                   <tbody id="tableBody"></tbody>
               </table>
               <div class="no-results" id="noResults" style="display:none;">
                   <span class="icon">😕</span>
                   没有匹配的题目记录
               </div>
           </div>
       </div>
   </div>

   <!-- Toast -->
   <div class="toast" id="toast"></div>

   <script>
       // ==================== 数据定义 ====================
       const problemData = [
           { id: 'P4562', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高', difficultyRank: 4,
               rating: '好题', ratingRank: 2, algo: '数学, 组合数学, 期望' },
           { id: 'P5377', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高', difficultyRank: 4,
               rating: '好题', ratingRank: 2, algo: '平面图欧拉公式, 组合数学, 排列组合' },
           { id: 'P2290', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高', difficultyRank: 4,
               rating: '好题', ratingRank: 2, algo: '数学, 组合数学, 排列组合, Prüfer 序列' },
           { id: 'P2606', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高', difficultyRank: 4,
               rating: '屎题', ratingRank: -1, algo: '动态规划 DP, 数学, 组合数学, Lucas 定理' },
           { id: 'P4454', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高', difficultyRank: 4,
               rating: '好题', ratingRank: 2, algo: '枚举, 素数判断, 逆元, 大步小步算法 BSGS' },
           { id: 'P2485', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高', difficultyRank: 4,
               rating: '屎题', ratingRank: -1, algo: '大步小步算法 BSGS, 拉完了, 勾十毒瘤题' },
           { id: 'P1298', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '大好题', ratingRank: 3, algo: 'Stern-Brocot 树' },
           { id: 'P10321', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高', difficultyRank: 4,
               rating: '好题', ratingRank: 2, algo: '数学, 数论, 素数判断, 最大公约数 gcd' },
           { id: 'P4139', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '好题', ratingRank: 2, algo: '欧拉函数' },
           { id: 'P4343', date: '2025-07-15', dateDisplay: '07-15', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '二分' },
           { id: 'P4095', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高', difficultyRank: 4,
               rating: '好题', ratingRank: 2, algo: '枚举, 分治, 背包 DP, 进制' },
           { id: 'P5771', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高', difficultyRank: 4,
               rating: '好题', ratingRank: 2, algo: '网络流, 二分图' },
           { id: 'P3376', date: '2025-07-15', dateDisplay: '07-15', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '好题', ratingRank: 2, algo: '网络流' },
           { id: 'P3518', date: '2025-07-14', dateDisplay: '07-14', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '数学, 最大公约数 gcd' },
           { id: 'P8449', date: '2025-07-13', dateDisplay: '07-13', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '' },
           { id: 'P2851', date: '2025-07-13', dateDisplay: '07-13', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '动态规划 DP, 背包 DP, 进制' },
           { id: 'P5749', date: '2025-07-13', dateDisplay: '07-13', difficulty: '提高', difficultyRank: 4,
               rating: '大好题', ratingRank: 3, algo: '贪心, 线段树, 树状数组' },
           { id: 'P3459', date: '2025-07-13', dateDisplay: '07-13', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '线段树, 树状数组, 深度优先搜索 DFS' },
           { id: 'P3558', date: '2025-07-13', dateDisplay: '07-13', difficulty: '普及+/提高', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '动态规划 DP' },
           { id: 'P9525', date: '2025-07-13', dateDisplay: '07-13', difficulty: '提高', difficultyRank: 4,
               rating: '大好题', ratingRank: 3, algo: '贪心, 优先队列' },
           { id: 'P4568', date: '2025-07-12', dateDisplay: '07-12', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '图论建模, 最短路' },
           { id: 'P3846', date: '2025-07-12', dateDisplay: '07-12', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '搜索, 数学, 数论, 素数判断, 大步小步算法 BSGS' },
           { id: 'P1197', date: '2025-07-12', dateDisplay: '07-12', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '并查集, 连通块' },
           { id: 'P2312', date: '2025-07-12', dateDisplay: '07-12', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '数学, 枚举, 哈希 hashing' },
           { id: 'P1052', date: '2025-07-12', dateDisplay: '07-12', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '动态规划 DP, 数学, 离散化, 动态规划优化, 矩阵加速, 不定方程' },
           { id: 'P3951', date: '2025-07-12', dateDisplay: '07-12', difficulty: '普及', difficultyRank: 2,
               rating: '水题', ratingRank: 0, algo: '数学, 不定方程, 中国剩余定理 CRT, 构造' },
           { id: 'P1169', date: '2025-07-05', dateDisplay: '07-05', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '动态规划 DP' },
           { id: 'P2106', date: '2025-07-05', dateDisplay: '07-05', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '动态规划 DP, 数学, 倍增, 数位 DP' },
           { id: 'P3147', date: '2025-06-30', dateDisplay: '06-30', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '动态规划 DP, 贪心' },
           { id: 'P2254', date: '2025-06-30', dateDisplay: '06-30', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '好题', ratingRank: 2, algo: '动态规划 DP, 贪心' },
           { id: 'P2569', date: '2025-06-30', dateDisplay: '06-30', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '动态规划 DP' },
           { id: 'P7469', date: '2025-06-30', dateDisplay: '06-30', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '字符串' },
           { id: 'P3694', date: '2025-06-30', dateDisplay: '06-30', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '枚举, 前缀和, 状压 DP' },
           { id: 'P3092', date: '2025-06-30', dateDisplay: '06-30', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '二分, 单调队列, 前缀和, 状压 DP' },
           { id: 'P6007', date: '2025-06-30', dateDisplay: '06-30', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '动态规划 DP, 树状数组, 动态规划优化' },
           { id: 'P1966', date: '2025-06-30', dateDisplay: '06-30', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '树状数组, 离散化, 排序' },
           { id: 'P1073', date: '2025-06-30', dateDisplay: '06-30', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '动态规划 DP, 搜索, 图论, 最短路' },
           { id: 'P8865', date: '2025-06-30', dateDisplay: '06-30', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '数学, 枚举, 组合数学, 前缀和' },
           { id: 'P6424', date: '2025-06-28', dateDisplay: '06-28', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '数学' },
           { id: 'P4092', date: '2025-06-28', dateDisplay: '06-28', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '搜索, 线段树, 并查集, 树状数组' },
           { id: 'P4827', date: '2025-06-26', dateDisplay: '06-26', difficulty: 'NOI/NOI+/CTS', difficultyRank: 8,
               rating: '难题', ratingRank: 4, algo: '动态规划 DP, 数学, 组合数学' },
           { id: 'P4827', date: '2025-06-26', dateDisplay: '06-26', difficulty: 'NOI/NOI+/CTS', difficultyRank: 8,
               rating: '难题', ratingRank: 4, algo: '动态规划 DP, 数学, 组合数学' },
           { id: 'P2391', date: '2025-06-25', dateDisplay: '06-25', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '模拟, 线段树, 并查集' },
           { id: 'P3188', date: '2025-06-23', dateDisplay: '06-23', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '好题', ratingRank: 2, algo: '动态规划 DP, 背包 DP, 进制' },
           { id: 'P1450', date: '2025-06-23', dateDisplay: '06-23', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '动态规划 DP' },
           { id: 'P5505', date: '2025-06-23', dateDisplay: '06-23', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '组合数学' },
           { id: 'P3761', date: '2025-06-23', dateDisplay: '06-23', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '枚举' },
           { id: 'P4302', date: '2025-06-23', dateDisplay: '06-23', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '字符串, 动态规划 DP, 搜索, 枚举' },
           { id: 'P5300', date: '2025-06-23', dateDisplay: '06-23', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '位运算, 单调栈' },
           { id: 'P3306', date: '2025-06-23', dateDisplay: '06-23', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '好题', ratingRank: 2, algo: '数论, 逆元, 大步小步算法 BSGS' },
           { id: 'P8251', date: '2025-06-23', dateDisplay: '06-23', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '好题', ratingRank: 2, algo: '倍增' },
           { id: 'P4609', date: '2025-06-21', dateDisplay: '06-21', difficulty: '省选/NOI−', difficultyRank: 7,
               rating: '好题', ratingRank: 2, algo: '数学' },
           { id: 'P6620', date: '2025-06-21', dateDisplay: '06-21', difficulty: '省选/NOI−', difficultyRank: 7,
               rating: '好题', ratingRank: 2, algo: '数学' },
           { id: 'P1655', date: '2025-06-12', dateDisplay: '06-12', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '动态规划 DP' },
           { id: 'P1955', date: '2025-06-10', dateDisplay: '06-10', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '并查集' },
           { id: 'P2024', date: '2025-06-10', dateDisplay: '06-10', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '并查集' },
           { id: 'P3957', date: '2025-06-07', dateDisplay: '06-07', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '动态规划 D' },
           { id: 'P1641', date: '2025-06-05', dateDisplay: '06-05', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: 'Catalan 数, 逆元, Lucas 定理' },
           { id: 'P1044', date: '2025-06-05', dateDisplay: '06-05', difficulty: '普及−', difficultyRank: 1, rating: '好题',
               ratingRank: 2, algo: 'Catalan 数, 栈' },
           { id: 'P2518', date: '2025-06-02', dateDisplay: '06-02', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '动态规划 DP' },
           { id: 'P10238', date: '2025-05-30', dateDisplay: '05-30', difficulty: '提高', difficultyRank: 4,
           rating: '好题', ratingRank: 2, algo: '并查集' },
           { id: 'P3304', date: '2025-05-31', dateDisplay: '05-31', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '并查集' },
           { id: 'P5454', date: '2025-05-29', dateDisplay: '05-29', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '' },
           { id: 'P5367', date: '2025-05-29', dateDisplay: '05-29', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '线段树, 平衡树' },
           { id: 'P2617', date: '2025-05-27', dateDisplay: '05-27', difficulty: '省选/NOI−', difficultyRank: 7,
               rating: '好题', ratingRank: 2, algo: '莫队, 线段树' },
           { id: 'P3045', date: '2025-05-26', dateDisplay: '05-26', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '好题', ratingRank: 2, algo: '贪心, 堆, 凸完全单调性(wqs 二分)' },
           { id: 'P3527', date: '2025-05-26', dateDisplay: '05-26', difficulty: '省选/NOI−', difficultyRank: 7,
               rating: '好题', ratingRank: 2, algo: '贪心, 堆, 凸完全单调性(wqs 二分)' },
           { id: 'P3834', date: '2025-05-26', dateDisplay: '05-26', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '好题', ratingRank: 2, algo: '莫队, 线段树, 离散化, 可持久化线段树' },
           { id: 'P2624', date: '2025-05-26', dateDisplay: '05-26', difficulty: '省选/NOI−', difficultyRank: 7,
               rating: '好题', ratingRank: 2, algo: '高精度, 素数判断, Prüfer 序列' },
           { id: 'P2290', date: '2025-05-22', dateDisplay: '05-22', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '数学, 组合数学' },
           { id: 'P6086', date: '2025-05-22', dateDisplay: '05-22', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: 'Prüfer 序列' },
           { id: 'P7976', date: '2025-05-22', dateDisplay: '05-22', difficulty: '提高', difficultyRank: 4, rating: '好题',
               ratingRank: 2, algo: '数学, 数位 DP, Lucas 定理' },
           { id: 'P5376', date: '2025-05-22', dateDisplay: '05-22', difficulty: '省选/NOI−', difficultyRank: 7,
               rating: '好题', ratingRank: 2, algo: '组合数学, 排列组合, 容斥原理' },
           { id: 'P6456', date: '2025-05-16', dateDisplay: '05-16', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '二分, 哈希 hashing, 后缀数组 SA' },
           { id: 'P8617', date: '2025-05-16', dateDisplay: '05-16', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '二分, 后缀自动机 SAM, 哈希 hashing' },
           { id: 'U41492', date: '2025-05-16', dateDisplay: '05-16', difficulty: '普及+/提高−', difficultyRank: 3,
               rating: '好题', ratingRank: 2, algo: '树剖' },
           { id: 'P1903', date: '2025-05-16', dateDisplay: '05-16', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '好题', ratingRank: 2, algo: '莫队, cdq 分治, 树套树, 分块' },
           { id: 'P3604', date: '2025-05-16', dateDisplay: '05-16', difficulty: '省选/NOI−', difficultyRank: 7,
               rating: '好题', ratingRank: 2, algo: '莫队, 状压 D' },
           { id: 'P4867', date: '2025-05-16', dateDisplay: '05-16', difficulty: '省选/NOI−', difficultyRank: 7,
               rating: '好题', ratingRank: 2, algo: '莫队, 树状数组, 分块' },
           { id: 'P4137', date: '2025-05-16', dateDisplay: '05-16', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '好题', ratingRank: 2,
               algo: '莫队, 线段树, cdq 分治, 可持久化线段树, 可持久化, 分块, 扫描线, 整体二分, bitset' },
           { id: 'P5268', date: '2025-05-16', dateDisplay: '05-16', difficulty: '提高+/省选−', difficultyRank: 5,
               rating: '好题', ratingRank: 2, algo: '莫队, 容斥原理, 差分' },
       ];

       // ==================== 状态管理 ====================
       let currentSort = { field: 'date', direction: 'desc' }; // 默认按日期降序
       let filteredData = [...problemData];

       // ==================== 渲染表格 ====================
       function getDifficultyTagClass(difficulty) {
           const d = difficulty.trim();
           if (d === '普及−' || d === '普及') return 'tag-diff-pup';
           if (d.includes('普及+/提高') || d === '普及+/提高−' || d === '普及+/提高') return 'tag-diff-pup-plus';
           if (d === '提高') return 'tag-diff-improve';
           if (d === '提高+/省选−') return 'tag-diff-improve-plus';
           if (d === '省选/NOI−') return 'tag-diff-prov';
           if (d.includes('NOI') || d.includes('CTS')) return 'tag-diff-ct';
           return 'tag-diff-improve';
       }

       function getRatingTagClass(rating) {
           const r = rating.trim();
           if (r === '大好题') return 'tag-rating-great';
           if (r === '好题') return 'tag-rating-good';
           if (r === '屎题') return 'tag-rating-bad';
           if (r === '难题') return 'tag-rating-hard';
           if (r === '水题') return 'tag-rating-water';
           return 'tag-rating-good';
       }

       function renderTable(data) {
           const tbody = document.getElementById('tableBody');
           const noResults = document.getElementById('noResults');
           const totalCount = document.getElementById('totalCount');

           totalCount.textContent = data.length;

           if (data.length === 0) {
               tbody.innerHTML = '';
               noResults.style.display = 'block';
               return;
           }
           noResults.style.display = 'none';

           tbody.innerHTML = data.map(item => {
               const algoTags = item.algo ?
                   item.algo.split(/[,,]+/).map(a => a.trim()).filter(a => a).map(a =>
                       `<span class="algo-tag">${escapeHTML(a)}</span>`
                   ).join('') :
                   '<span class="algo-tag" style="color:#94a3b8;">—</span>';

               const probLink = item.id.startsWith('U') ?
                   escapeHTML(item.id) :
                   `<a href="https://www.luogu.com.cn/problem/${escapeHTML(item.id)}" target="_blank" rel="noopener" title="在洛谷打开 ${escapeHTML(item.id)}">${escapeHTML(item.id)}</a>`;

               return `
                   <tr>
                       <td><span class="prob-id">${probLink}</span></td>
                       <td>${escapeHTML(item.dateDisplay)}</td>
                       <td><span class="tag ${getDifficultyTagClass(item.difficulty)}">${escapeHTML(item.difficulty)}</span></td>
                       <td><span class="tag ${getRatingTagClass(item.rating)}">${escapeHTML(item.rating)}</span></td>
                       <td><div class="algo-list">${algoTags}</div></td>
                   </tr>
               `;
           }).join('');

           // 更新排序指示器
           document.querySelectorAll('thead th').forEach(th => {
               th.classList.remove('sorted');
               if (th.dataset.sort === currentSort.field) {
                   th.classList.add('sorted');
                   const arrow = th.querySelector('.sort-arrow');
                   if (arrow) {
                       arrow.textContent = currentSort.direction === 'asc' ? '▲' : '▼';
                   }
               } else {
                   const arrow = th.querySelector('.sort-arrow');
                   if (arrow) arrow.textContent = '⇅';
               }
           });
       }

       function escapeHTML(str) {
           const div = document.createElement('div');
           div.textContent = str;
           return div.innerHTML;
       }

       // ==================== 排序 ====================
       function sortTable(field) {
           if (currentSort.field === field) {
               currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
           } else {
               currentSort.field = field;
               // 默认降序
               currentSort.direction = (field === 'date') ? 'desc' : 'asc';
           }

           const dir = currentSort.direction === 'asc' ? 1 : -1;

           filteredData.sort((a, b) => {
               let valA, valB;
               switch (field) {
                   case 'id':
                       valA = a.id;
                       valB = b.id;
                       return dir * valA.localeCompare(valB, undefined, { numeric: true, sensitivity: 'base' });
                   case 'date':
                       valA = a.date;
                       valB = b.date;
                       return dir * valA.localeCompare(valB);
                   case 'difficulty':
                       valA = a.difficultyRank;
                       valB = b.difficultyRank;
                       return dir * (valA - valB);
                   case 'rating':
                       valA = a.ratingRank;
                       valB = b.ratingRank;
                       return dir * (valA - valB);
                   case 'algo':
                       valA = a.algo || '';
                       valB = b.algo || '';
                       return dir * valA.localeCompare(valB, 'zh-CN');
                   default:
                       return 0;
               }
           });

           renderTable(filteredData);
           showToast(`已按「${getFieldLabel(field)}」${currentSort.direction === 'asc' ? '升序' : '降序'}排列`);
       }

       function getFieldLabel(field) {
           const map = { id: '题目编号', date: '日期', difficulty: '难度', rating: '评价', algo: '算法备注' };
           return map[field] || field;
       }

       // ==================== 搜索 ====================
       function filterData(query) {
           const q = query.toLowerCase().trim();
           if (!q) {
               filteredData = [...problemData];
           } else {
               filteredData = problemData.filter(item => {
                   return (
                       item.id.toLowerCase().includes(q) ||
                       item.dateDisplay.includes(q) ||
                       item.difficulty.toLowerCase().includes(q) ||
                       item.rating.toLowerCase().includes(q) ||
                       (item.algo || '').toLowerCase().includes(q)
                   );
               });
           }
           // 重新应用排序
           const dir = currentSort.direction === 'asc' ? 1 : -1;
           const field = currentSort.field;
           filteredData.sort((a, b) => {
               let valA, valB;
               switch (field) {
                   case 'id':
                       return dir * a.id.localeCompare(b.id, undefined, { numeric: true, sensitivity: 'base' });
                   case 'date':
                       return dir * a.date.localeCompare(b.date);
                   case 'difficulty':
                       return dir * (a.difficultyRank - b.difficultyRank);
                   case 'rating':
                       return dir * (a.ratingRank - b.ratingRank);
                   case 'algo':
                       return dir * ((a.algo || '').localeCompare(b.algo || '', 'zh-CN'));
                   default:
                       return 0;
               }
           });
           renderTable(filteredData);
       }

       function resetTable() {
           document.getElementById('searchInput').value = '';
           currentSort = { field: 'date', direction: 'desc' };
           filteredData = [...problemData];
           filteredData.sort((a, b) => b.date.localeCompare(a.date));
           renderTable(filteredData);
           showToast('已重置搜索与排序');
       }

       // ==================== CSV下载 ====================
       function downloadCSV() {
           const dataToExport = filteredData.length > 0 ? filteredData : problemData;
           const BOM = '\uFEFF';
           const header = '题目编号,日期,难度,评价,算法/备注\n';
           const rows = dataToExport.map(item => {
               const algo = item.algo || '';
               // CSV转义:包含逗号、双引号或换行符的字段用双引号包裹
               const escapeCSV = (str) => {
                   if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes(',')) {
                       return '"' + str.replace(/"/g, '""') + '"';
                   }
                   return str;
               };
               return [escapeCSV(item.id), escapeCSV(item.dateDisplay), escapeCSV(item.difficulty),
                   escapeCSV(item.rating), escapeCSV(algo)
               ].join(',');
           }).join('\n');

           const csvContent = BOM + header + rows;
           const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
           const url = URL.createObjectURL(blob);
           const link = document.createElement('a');
           link.href = url;
           const now = new Date();
           const dateStr = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,'0')}-${String(now.getDate()).padStart(2,'0')}`;
           link.download = `算法刷题记录_${dateStr}.csv`;
           document.body.appendChild(link);
           link.click();
           document.body.removeChild(link);
           URL.revokeObjectURL(url);
           showToast('✅ CSV文件下载成功!可用Excel / WPS / Google Sheets打开');
       }

       // ==================== Toast ====================
       let toastTimer;

       function showToast(message) {
           const toast = document.getElementById('toast');
           toast.textContent = message;
           toast.classList.add('show');
           clearTimeout(toastTimer);
           toastTimer = setTimeout(() => {
               toast.classList.remove('show');
           }, 2000);
       }

       // ==================== 初始化 ====================
       function init() {
           // 默认按日期降序排列
           filteredData = [...problemData];
           filteredData.sort((a, b) => b.date.localeCompare(a.date));
           currentSort = { field: 'date', direction: 'desc' };
           renderTable(filteredData);

           // 搜索事件监听
           const searchInput = document.getElementById('searchInput');
           searchInput.addEventListener('input', (e) => {
               filterData(e.target.value);
           });
           // 也支持回车搜索
           searchInput.addEventListener('keydown', (e) => {
               if (e.key === 'Escape') {
                   resetTable();
               }
           });

           // 更新总计数
           document.getElementById('totalCount').textContent = problemData.length;

           console.log(`✅ 算法刷题记录已加载,共 ${problemData.length} 条记录`);
           console.log('💡 提示:点击表头可排序 | 搜索框可筛选 | 点击"下载CSV"导出表格');
       }

       document.addEventListener('DOMContentLoaded', init);
   </script>
</body>
</html>
posted @ 2026-07-24 14:23  wmq2012  阅读(7)  评论(0)    收藏  举报