Jelajahi Sumber

feat(渠道效果分析): 完善重置按钮和导航栏层级

- 为所有表格添加独立重置按钮(13个表格)
- 导航栏展开到三级:裂变率下细分整体/头部/推荐
- 添加 .sub2 样式和对应 h4 锚点

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
yangxiaohui 2 bulan lalu
induk
melakukan
879da4a450
1 mengubah file dengan 133 tambahan dan 70 penghapusan
  1. 133 70
      tasks/渠道效果分析/visualize.py

+ 133 - 70
tasks/渠道效果分析/visualize.py

@@ -135,6 +135,7 @@ def calc_channel_cat2(data):
     cc2 = data.groupby(['channel', 'merge一级品类', 'merge二级品类']).apply(
         lambda x: pd.Series({
             '点击uv': x['点击uv'].sum(),
+            '进入分发率': (x['进入分发率'] * x['点击uv']).sum() / x['点击uv'].sum() if x['点击uv'].sum() > 0 else 0,
             '整体裂变率': (x['整体裂变率'] * x['点击uv']).sum() / x['点击uv'].sum() if x['点击uv'].sum() > 0 else 0,
             '头部裂变率': (x['头部裂变率'] * x['点击uv']).sum() / x['点击uv'].sum() if x['点击uv'].sum() > 0 else 0,
             '推荐裂变率': (x['推荐裂变率'] * x['点击uv']).sum() / x['点击uv'].sum() if x['点击uv'].sum() > 0 else 0
@@ -149,6 +150,7 @@ def calc_channel_cat2(data):
         'ror_orig': cc2.pivot_table(index='cat2_label', columns='channel', values='头部裂变率'),
         'ror_rec': cc2.pivot_table(index='cat2_label', columns='channel', values='推荐裂变率'),
         'uv': cc2.pivot_table(index='cat2_label', columns='channel', values='点击uv', fill_value=0),
+        'recommend': cc2.pivot_table(index='cat2_label', columns='channel', values='进入分发率'),
     }
 
 # 为每个日期计算矩阵数据
@@ -275,17 +277,32 @@ def gen_matrix_rows(pivot_df, valid_rows, cols, max_val, min_val=0, is_int=False
 # 为每个日期生成表格HTML
 # ============================================================
 
-# 先计算汇总数据确定有效品类和渠道
+# 先计算汇总数据确定有效品类和渠道(用于筛选,不用于排序)
 all_channel_stats = daily_data['all']['channel_stats']
 main_channels = all_channel_stats[all_channel_stats['点击uv'] > 10000]['channel'].tolist()
 all_matrix = daily_data['all']['matrix']
-valid_categories = all_matrix['uv'][all_matrix['uv'].sum(axis=1) >= 1000].index.tolist()
-heatmap_cols = [c for c in main_channels if c in all_matrix['ror'].columns]
+valid_categories_set = set(all_matrix['uv'][all_matrix['uv'].sum(axis=1) >= 1000].index.tolist())
 
 # 二级品类有效列表
 all_matrix2 = daily_data['all']['matrix2']
 cat2_uv_sum = all_matrix2['uv'].sum(axis=1)
-valid_cat2_labels = cat2_uv_sum[cat2_uv_sum >= 1000].sort_values(ascending=False).head(20).index.tolist()
+valid_cat2_set = set(cat2_uv_sum[cat2_uv_sum >= 1000].sort_values(ascending=False).head(20).index.tolist())
+
+def get_sorted_cols(matrix_uv, valid_channels):
+    """按当天各渠道总UV降序排列列"""
+    col_uv = matrix_uv.sum(axis=0)
+    valid_cols = [c for c in col_uv.index if c in valid_channels]
+    return sorted(valid_cols, key=lambda c: col_uv.get(c, 0), reverse=True)
+
+def get_sorted_rows(matrix_uv, valid_rows_set):
+    """按当天各品类总UV降序排列行"""
+    row_uv = matrix_uv.sum(axis=1)
+    valid_rows = [r for r in row_uv.index if r in valid_rows_set]
+    return sorted(valid_rows, key=lambda r: row_uv.get(r, 0), reverse=True)
+
+def gen_header(label, cols):
+    """生成表头"""
+    return "<tr><th>" + label + "</th>" + "".join([f"<th>{c}</th>" for c in cols]) + "</tr>"
 
 daily_html = {}
 for dt in date_options:
@@ -293,30 +310,32 @@ for dt in date_options:
     matrix = data['matrix']
     matrix2 = data['matrix2']
 
+    # 当天按UV排序的列和行
+    dt_cols = get_sorted_cols(matrix['uv'], main_channels)
+    dt_rows = get_sorted_rows(matrix['uv'], valid_categories_set)
+    dt_cols2 = get_sorted_cols(matrix2['uv'], main_channels)
+    dt_rows2 = get_sorted_rows(matrix2['uv'], valid_cat2_set)
+
     daily_html[dt] = {
         'channel_rows': gen_channel_rows(data['channel_stats']),
         'category_rows': gen_category_rows(data['category_stats']),
         'cat2_rows': gen_cat2_rows(data['cat2_stats']),
-        # 渠道×一级品类矩阵
-        'matrix_ror': gen_matrix_rows(matrix['ror'], valid_categories, heatmap_cols, 0.8),
-        'matrix_ror_orig': gen_matrix_rows(matrix['ror_orig'], valid_categories, heatmap_cols, 0.3),
-        'matrix_ror_rec': gen_matrix_rows(matrix['ror_rec'], valid_categories, heatmap_cols, 0.5),
-        'matrix_uv': gen_matrix_rows(matrix['uv'], valid_categories, heatmap_cols, 100000, is_int=True),
-        'matrix_recommend': gen_matrix_rows(matrix['recommend'], valid_categories, heatmap_cols, 1.0, 0.6),
-        # 渠道×二级品类矩阵
-        'matrix2_ror': gen_matrix_rows(matrix2['ror'], valid_cat2_labels, heatmap_cols, 0.8),
-        'matrix2_ror_orig': gen_matrix_rows(matrix2['ror_orig'], valid_cat2_labels, heatmap_cols, 0.3),
-        'matrix2_ror_rec': gen_matrix_rows(matrix2['ror_rec'], valid_cat2_labels, heatmap_cols, 0.5),
-        'matrix2_uv': gen_matrix_rows(matrix2['uv'], valid_cat2_labels, heatmap_cols, 100000, is_int=True),
+        # 渠道×一级品类矩阵(按当天UV排序)
+        'matrix_header': gen_header('品类', dt_cols),
+        'matrix_ror': gen_matrix_rows(matrix['ror'], dt_rows, dt_cols, 0.8),
+        'matrix_ror_orig': gen_matrix_rows(matrix['ror_orig'], dt_rows, dt_cols, 0.3),
+        'matrix_ror_rec': gen_matrix_rows(matrix['ror_rec'], dt_rows, dt_cols, 0.5),
+        'matrix_uv': gen_matrix_rows(matrix['uv'], dt_rows, dt_cols, 100000, is_int=True),
+        'matrix_recommend': gen_matrix_rows(matrix['recommend'], dt_rows, dt_cols, 1.0, 0.6),
+        # 渠道×二级品类矩阵(按当天UV排序)
+        'matrix2_header': gen_header('二级品类', dt_cols2),
+        'matrix2_ror': gen_matrix_rows(matrix2['ror'], dt_rows2, dt_cols2, 0.8),
+        'matrix2_ror_orig': gen_matrix_rows(matrix2['ror_orig'], dt_rows2, dt_cols2, 0.3),
+        'matrix2_ror_rec': gen_matrix_rows(matrix2['ror_rec'], dt_rows2, dt_cols2, 0.5),
+        'matrix2_recommend': gen_matrix_rows(matrix2['recommend'], dt_rows2, dt_cols2, 1.0, 0.6),
+        'matrix2_uv': gen_matrix_rows(matrix2['uv'], dt_rows2, dt_cols2, 100000, is_int=True),
     }
 
-# ============================================================
-# 生成表头
-# ============================================================
-
-ror_header = "<tr><th>品类</th>" + "".join([f"<th>{c}</th>" for c in heatmap_cols]) + "</tr>"
-cat2_ror_header = "<tr><th>二级品类</th>" + "".join([f"<th>{c}</th>" for c in heatmap_cols]) + "</tr>"
-
 # ============================================================
 # 生成 HTML
 # ============================================================
@@ -354,10 +373,12 @@ html_content = f"""<!DOCTYPE html>
                      font-size: 13px; transition: all 0.2s; }}
         .sidebar a:hover {{ background: #34495e; color: white; }}
         .sidebar .sub a {{ padding-left: 30px; font-size: 12px; }}
+        .sidebar .sub2 a {{ padding-left: 45px; font-size: 11px; color: #95a5a6; }}
         .container {{ margin-left: 240px; padding: 20px; max-width: 1400px; }}
         h1 {{ color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; }}
         h2 {{ color: #444; margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; }}
         h3 {{ color: #555; margin-top: 20px; }}
+        h4 {{ color: #666; margin: 15px 0 5px; font-size: 14px; }}
         .chart-container {{ background: white; padding: 20px; margin: 20px 0;
                            border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
         .summary {{ background: white; padding: 20px; margin: 20px 0;
@@ -365,8 +386,8 @@ html_content = f"""<!DOCTYPE html>
         .stat-card {{ flex: 1; min-width: 150px; padding: 15px;
                      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                      border-radius: 8px; color: white; text-align: center; }}
-        .stat-card h4 {{ margin: 0; font-size: 24px; }}
-        .stat-card p {{ margin: 5px 0 0; opacity: 0.9; }}
+        .stat-card h4 {{ margin: 0; font-size: 24px; color: white; }}
+        .stat-card p {{ margin: 5px 0 0; opacity: 0.9; color: white; }}
         table {{ width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 13px; }}
         th, td {{ padding: 6px 10px; text-align: left; border-bottom: 1px solid #ddd; }}
         th {{ background: #f8f9fa; font-weight: 600; position: sticky; top: 0; }}
@@ -425,16 +446,19 @@ html_content = f"""<!DOCTYPE html>
             <li class="sub"><a href="#sec1-2">1.2 一级品类整体</a></li>
             <li class="sub"><a href="#sec1-3">1.3 二级品类整体</a></li>
             <li><a href="#sec2">二、渠道×一级品类</a></li>
-            <li class="sub"><a href="#sec2-1">2.1 整体裂变率</a></li>
-            <li class="sub"><a href="#sec2-2">2.2 头部裂变率</a></li>
-            <li class="sub"><a href="#sec2-3">2.3 推荐裂变率</a></li>
-            <li class="sub"><a href="#sec2-4">2.4 点击UV</a></li>
-            <li class="sub"><a href="#sec2-5">2.5 进入分发率</a></li>
+            <li class="sub"><a href="#sec2-1">2.1 裂变率</a></li>
+            <li class="sub2"><a href="#sec2-1-1">整体裂变率</a></li>
+            <li class="sub2"><a href="#sec2-1-2">头部裂变率</a></li>
+            <li class="sub2"><a href="#sec2-1-3">推荐裂变率</a></li>
+            <li class="sub"><a href="#sec2-2">2.2 进入分发率</a></li>
+            <li class="sub"><a href="#sec2-3">2.3 点击UV</a></li>
             <li><a href="#sec3">三、渠道×二级品类</a></li>
-            <li class="sub"><a href="#sec3-1">3.1 整体裂变率</a></li>
-            <li class="sub"><a href="#sec3-2">3.2 头部裂变率</a></li>
-            <li class="sub"><a href="#sec3-3">3.3 推荐裂变率</a></li>
-            <li class="sub"><a href="#sec3-4">3.4 点击UV</a></li>
+            <li class="sub"><a href="#sec3-1">3.1 裂变率</a></li>
+            <li class="sub2"><a href="#sec3-1-1">整体裂变率</a></li>
+            <li class="sub2"><a href="#sec3-1-2">头部裂变率</a></li>
+            <li class="sub2"><a href="#sec3-1-3">推荐裂变率</a></li>
+            <li class="sub"><a href="#sec3-2">3.2 进入分发率</a></li>
+            <li class="sub"><a href="#sec3-3">3.3 点击UV</a></li>
         </ul>
     </nav>
 
@@ -476,7 +500,7 @@ html_content = f"""<!DOCTYPE html>
         <h3 id="sec1-1">1.1 渠道整体</h3>
         <div class="chart-container" data-section="channel">
             <div class="legend">
-                颜色越深=数值越高 | 点击表头排序
+                颜色越深=数值越高 | 点击表头排序 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
                 <div class="date-switcher table-date" data-section="channel">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
@@ -484,7 +508,7 @@ html_content = f"""<!DOCTYPE html>
                     <button class="play-btn">▶</button>
                 </div>
             </div>
-            {"".join([f'''<div class="date-content{'active' if dt == latest_date else ''}" data-date="{dt}">
+            {"".join([f'''<div class="date-content{' active' if dt == latest_date else ''}" data-date="{dt}">
                 <table class="sortable"><tr><th>渠道</th><th>点击UV</th><th>进入分发率</th><th>整体裂变率</th><th>头部裂变率</th><th>推荐裂变率</th></tr>
                 {daily_html[dt]['channel_rows']}</table></div>''' for dt in date_options])}
         </div>
@@ -492,7 +516,7 @@ html_content = f"""<!DOCTYPE html>
         <h3 id="sec1-2">1.2 一级品类整体</h3>
         <div class="chart-container" data-section="category">
             <div class="legend">
-                颜色越深=数值越高 | 点击表头排序
+                颜色越深=数值越高 | 点击表头排序 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
                 <div class="date-switcher table-date" data-section="category">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
@@ -508,7 +532,7 @@ html_content = f"""<!DOCTYPE html>
         <h3 id="sec1-3">1.3 二级品类整体</h3>
         <div class="chart-container" data-section="cat2">
             <div class="legend">
-                颜色越深=数值越高 | 点击表头排序
+                颜色越深=数值越高 | 点击表头排序 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
                 <div class="date-switcher table-date" data-section="cat2">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
@@ -523,9 +547,10 @@ html_content = f"""<!DOCTYPE html>
 
         <h2 id="sec2">二、渠道×一级品类</h2>
 
-        <h3 id="sec2-1">2.1 整体裂变率</h3>
+        <h3 id="sec2-1">2.1 裂变率</h3>
+        <h4 id="sec2-1-1">整体裂变率</h4>
         <div class="chart-container heatmap matrix-section" data-section="m1_ror">
-            <div class="legend">max=0.80 | 点击表头/行名排序
+            <div class="legend">max=0.80 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
                 <div class="date-switcher table-date" data-section="m1_ror">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
@@ -534,12 +559,12 @@ html_content = f"""<!DOCTYPE html>
                 </div>
             </div>
             {"".join([f'''<div class="date-content{' active' if dt == latest_date else ''}" data-date="{dt}">
-                <table class="sortable">{ror_header}{daily_html[dt]['matrix_ror']}</table></div>''' for dt in date_options])}
+                <table class="sortable">{daily_html[dt]['matrix_header']}{daily_html[dt]['matrix_ror']}</table></div>''' for dt in date_options])}
         </div>
 
-        <h3 id="sec2-2">2.2 头部裂变率</h3>
+        <h4 id="sec2-1-2">头部裂变率</h4>
         <div class="chart-container heatmap matrix-section" data-section="m1_orig">
-            <div class="legend">max=0.30 | 点击表头/行名排序
+            <div class="legend">max=0.30 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
                 <div class="date-switcher table-date" data-section="m1_orig">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
@@ -548,12 +573,12 @@ html_content = f"""<!DOCTYPE html>
                 </div>
             </div>
             {"".join([f'''<div class="date-content{' active' if dt == latest_date else ''}" data-date="{dt}">
-                <table class="sortable">{ror_header}{daily_html[dt]['matrix_ror_orig']}</table></div>''' for dt in date_options])}
+                <table class="sortable">{daily_html[dt]['matrix_header']}{daily_html[dt]['matrix_ror_orig']}</table></div>''' for dt in date_options])}
         </div>
 
-        <h3 id="sec2-3">2.3 推荐裂变率</h3>
+        <h4 id="sec2-1-3">推荐裂变率</h4>
         <div class="chart-container heatmap matrix-section" data-section="m1_rec">
-            <div class="legend">max=0.50 | 点击表头/行名排序
+            <div class="legend">max=0.50 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
                 <div class="date-switcher table-date" data-section="m1_rec">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
@@ -562,13 +587,13 @@ html_content = f"""<!DOCTYPE html>
                 </div>
             </div>
             {"".join([f'''<div class="date-content{' active' if dt == latest_date else ''}" data-date="{dt}">
-                <table class="sortable">{ror_header}{daily_html[dt]['matrix_ror_rec']}</table></div>''' for dt in date_options])}
+                <table class="sortable">{daily_html[dt]['matrix_header']}{daily_html[dt]['matrix_ror_rec']}</table></div>''' for dt in date_options])}
         </div>
 
-        <h3 id="sec2-4">2.4 点击UV</h3>
-        <div class="chart-container heatmap matrix-section" data-section="m1_uv">
-            <div class="legend">max=10万 | 点击表头/行名排序
-                <div class="date-switcher table-date" data-section="m1_uv">
+        <h3 id="sec2-2">2.2 进入分发率</h3>
+        <div class="chart-container heatmap matrix-section" data-section="m1_recommend">
+            <div class="legend">min=0.60, max=1.00 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
+                <div class="date-switcher table-date" data-section="m1_recommend">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
                     <button class="next-btn">▶</button>
@@ -576,13 +601,13 @@ html_content = f"""<!DOCTYPE html>
                 </div>
             </div>
             {"".join([f'''<div class="date-content{' active' if dt == latest_date else ''}" data-date="{dt}">
-                <table class="sortable">{ror_header}{daily_html[dt]['matrix_uv']}</table></div>''' for dt in date_options])}
+                <table class="sortable">{daily_html[dt]['matrix_header']}{daily_html[dt]['matrix_recommend']}</table></div>''' for dt in date_options])}
         </div>
 
-        <h3 id="sec2-5">2.5 进入分发率</h3>
-        <div class="chart-container heatmap matrix-section" data-section="m1_recommend">
-            <div class="legend">min=0.60, max=1.00 | 点击表头/行名排序
-                <div class="date-switcher table-date" data-section="m1_recommend">
+        <h3 id="sec2-3">2.3 点击UV</h3>
+        <div class="chart-container heatmap matrix-section" data-section="m1_uv">
+            <div class="legend">max=10万 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
+                <div class="date-switcher table-date" data-section="m1_uv">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
                     <button class="next-btn">▶</button>
@@ -590,14 +615,15 @@ html_content = f"""<!DOCTYPE html>
                 </div>
             </div>
             {"".join([f'''<div class="date-content{' active' if dt == latest_date else ''}" data-date="{dt}">
-                <table class="sortable">{ror_header}{daily_html[dt]['matrix_recommend']}</table></div>''' for dt in date_options])}
+                <table class="sortable">{daily_html[dt]['matrix_header']}{daily_html[dt]['matrix_uv']}</table></div>''' for dt in date_options])}
         </div>
 
         <h2 id="sec3">三、渠道×二级品类</h2>
 
-        <h3 id="sec3-1">3.1 整体裂变率</h3>
+        <h3 id="sec3-1">3.1 裂变率</h3>
+        <h4 id="sec3-1-1">整体裂变率</h4>
         <div class="chart-container heatmap matrix-section" data-section="m2_ror">
-            <div class="legend">max=0.80 | 点击表头/行名排序
+            <div class="legend">max=0.80 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
                 <div class="date-switcher table-date" data-section="m2_ror">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
@@ -606,12 +632,12 @@ html_content = f"""<!DOCTYPE html>
                 </div>
             </div>
             {"".join([f'''<div class="date-content{' active' if dt == latest_date else ''}" data-date="{dt}">
-                <table class="sortable">{cat2_ror_header}{daily_html[dt]['matrix2_ror']}</table></div>''' for dt in date_options])}
+                <table class="sortable">{daily_html[dt]['matrix2_header']}{daily_html[dt]['matrix2_ror']}</table></div>''' for dt in date_options])}
         </div>
 
-        <h3 id="sec3-2">3.2 头部裂变率</h3>
+        <h4 id="sec3-1-2">头部裂变率</h4>
         <div class="chart-container heatmap matrix-section" data-section="m2_orig">
-            <div class="legend">max=0.30 | 点击表头/行名排序
+            <div class="legend">max=0.30 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
                 <div class="date-switcher table-date" data-section="m2_orig">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
@@ -620,12 +646,12 @@ html_content = f"""<!DOCTYPE html>
                 </div>
             </div>
             {"".join([f'''<div class="date-content{' active' if dt == latest_date else ''}" data-date="{dt}">
-                <table class="sortable">{cat2_ror_header}{daily_html[dt]['matrix2_ror_orig']}</table></div>''' for dt in date_options])}
+                <table class="sortable">{daily_html[dt]['matrix2_header']}{daily_html[dt]['matrix2_ror_orig']}</table></div>''' for dt in date_options])}
         </div>
 
-        <h3 id="sec3-3">3.3 推荐裂变率</h3>
+        <h4 id="sec3-1-3">推荐裂变率</h4>
         <div class="chart-container heatmap matrix-section" data-section="m2_rec">
-            <div class="legend">max=0.50 | 点击表头/行名排序
+            <div class="legend">max=0.50 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
                 <div class="date-switcher table-date" data-section="m2_rec">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
@@ -634,12 +660,26 @@ html_content = f"""<!DOCTYPE html>
                 </div>
             </div>
             {"".join([f'''<div class="date-content{' active' if dt == latest_date else ''}" data-date="{dt}">
-                <table class="sortable">{cat2_ror_header}{daily_html[dt]['matrix2_ror_rec']}</table></div>''' for dt in date_options])}
+                <table class="sortable">{daily_html[dt]['matrix2_header']}{daily_html[dt]['matrix2_ror_rec']}</table></div>''' for dt in date_options])}
         </div>
 
-        <h3 id="sec3-4">3.4 点击UV</h3>
+        <h3 id="sec3-2">3.2 进入分发率</h3>
+        <div class="chart-container heatmap matrix-section" data-section="m2_recommend">
+            <div class="legend">min=0.60, max=1.00 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
+                <div class="date-switcher table-date" data-section="m2_recommend">
+                    <button class="prev-btn">◀</button>
+                    <select class="date-select">{date_options_html}</select>
+                    <button class="next-btn">▶</button>
+                    <button class="play-btn">▶</button>
+                </div>
+            </div>
+            {"".join([f'''<div class="date-content{' active' if dt == latest_date else ''}" data-date="{dt}">
+                <table class="sortable">{daily_html[dt]['matrix2_header']}{daily_html[dt]['matrix2_recommend']}</table></div>''' for dt in date_options])}
+        </div>
+
+        <h3 id="sec3-3">3.3 点击UV</h3>
         <div class="chart-container heatmap matrix-section" data-section="m2_uv">
-            <div class="legend">max=10万 | 点击表头/行名排序
+            <div class="legend">max=10万 <button class="reset-btn" onclick="resetSection(this.closest('.chart-container').dataset.section)">重置</button>
                 <div class="date-switcher table-date" data-section="m2_uv">
                     <button class="prev-btn">◀</button>
                     <select class="date-select">{date_options_html}</select>
@@ -648,7 +688,7 @@ html_content = f"""<!DOCTYPE html>
                 </div>
             </div>
             {"".join([f'''<div class="date-content{' active' if dt == latest_date else ''}" data-date="{dt}">
-                <table class="sortable">{cat2_ror_header}{daily_html[dt]['matrix2_uv']}</table></div>''' for dt in date_options])}
+                <table class="sortable">{daily_html[dt]['matrix2_header']}{daily_html[dt]['matrix2_uv']}</table></div>''' for dt in date_options])}
         </div>
 
     </div>
@@ -814,8 +854,8 @@ html_content = f"""<!DOCTYPE html>
         // 所有可切换的 section
         const allSections = [
             'channel', 'category', 'cat2',
-            'm1_ror', 'm1_orig', 'm1_rec', 'm1_uv', 'm1_recommend',
-            'm2_ror', 'm2_orig', 'm2_rec', 'm2_uv'
+            'm1_ror', 'm1_orig', 'm1_rec', 'm1_recommend', 'm1_uv',
+            'm2_ror', 'm2_orig', 'm2_rec', 'm2_recommend', 'm2_uv'
         ];
         allSections.forEach(section => switchDate(section, date));
         document.querySelector('#global-switcher .date-select').value = date;
@@ -858,6 +898,29 @@ html_content = f"""<!DOCTYPE html>
         playIntervals[key] = setInterval(play, 1500);
     }}
 
+    // 保存每个表格的原始HTML(用于重置)
+    const originalContent = {{}};
+    document.querySelectorAll('.chart-container[data-section]').forEach(container => {{
+        const section = container.dataset.section;
+        originalContent[section] = {{}};
+        container.querySelectorAll('.date-content').forEach(content => {{
+            const date = content.dataset.date;
+            originalContent[section][date] = content.innerHTML;
+        }});
+    }});
+
+    // 重置单个表格排序(恢复原始HTML)
+    function resetSection(section) {{
+        const container = document.querySelector(`.chart-container[data-section="${{section}}"]`);
+        if (!container || !originalContent[section]) return;
+        container.querySelectorAll('.date-content').forEach(content => {{
+            const date = content.dataset.date;
+            if (originalContent[section][date]) {{
+                content.innerHTML = originalContent[section][date];
+            }}
+        }});
+    }}
+
     // 绑定全局切换器
     const globalSwitcher = document.querySelector('#global-switcher');
     globalSwitcher.querySelector('.date-select').addEventListener('change', e => switchAll(e.target.value));