mcpField
C
cja-funnel-health-check

cja-funnel-health-check

github.com/adobe数据与数据库316332 次安装今天更新
代码仓库

关于 cja-funnel-health-check

这是一项 Customer Journey Analytics 技能,可将漏斗描述转化为逐步转化率和流失分析。它能找出流失最多的环节,并按受众或渠道细分结果,最后生成 HTML 报告。

主要功能

  1. 01将漏斗描述转为可测量步骤
  2. 02统计各步骤的访客或会话
  3. 03计算转化率和流失率
  4. 04找出流失最多的环节
  5. 05按维度或细分进行拆解
  6. 06生成 HTML 漏斗报告

适用场景

  1. 01查找结账访客在哪个环节流失
  2. 02衡量注册或引导流程的完成情况
  3. 03比较不同设备或渠道的漏斗流失

MCP 相关

使用 Customer Journey Analytics MCP 工具解析维度和指标、搜索维度值,并通过临时细分运行报表。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/adobe/skills --skill cja-funnel-health-check

安装后在 Claude Code 中输入/cja-funnel-health-check确认已加载。

附带文件evals/evals.json

详细介绍

漏斗健康检查(客户旅程分析)

将通俗易懂的漏斗描述转化为量化的逐步转化分析。输出会找出漏斗中流失最严重的环节,并按维度细分数据,以展示哪个受众群体或渠道的流失率最高。

当团队怀疑某个特定环节存在问题,但尚未量化时,漏斗健康检查最有价值。此技能可在一次对话中完成量化分析。


使用的 CJA MCP 工具

  • findDimensions — 解析用于筛选步骤的页面名称、事件或其他维度
  • findMetrics — 获取要衡量的基础指标(会话、访客、事件)
  • searchDimensionItems — 查找步骤名称对应的确切维度值
  • runReport(搭配 adhocSegments)— 衡量每个漏斗步骤的访客数
  • findSegments — 适用时,使用现有区段作为漏斗步骤筛选条件
  • describeSegment — 在将区段用作漏斗步骤之前,了解其逻辑

阶段 0 — 设置

  1. 调用 findDataViews 列出可用的数据视图。
  2. 如果用户未指定数据视图,则展示列表并询问要使用哪一个。
  3. 使用所选 ID 调用 setDefaultSessionDataViewId。
  4. 如果用户尚未指定漏斗阶段,请让用户定义阶段(例如:“您想分析的漏斗步骤有哪些?”)。

阶段 1 — 定义漏斗步骤

1.1 解析用户的漏斗描述

从用户的通俗描述中提取步骤顺序:

  • “首页 → 产品页 → 购物车 → 购买”
  • “注册 → 引导步骤 1 → 引导步骤 2 → 已激活”
  • “着陆页 → 潜在客户表单 → 致谢页”

每个步骤都必须对应 CJA 中可衡量的条件。步骤可以是:

  • 页面浏览:用户浏览了特定页面(通过页面名称维度解析)
  • 事件/指标:用户触发了特定操作(例如“加入购物车”)
  • 区段成员资格:用户符合预先构建的区段条件
1.2 澄清含义不明确的步骤

如果某个步骤表述含糊(例如只说“结账”,未提供页面名称),请提出一个问题:

“对于‘结账’这一步,我应该查找访问过 URL 中包含‘checkout’的页面的用户,还是触发了‘Cart Add’等特定事件的用户?”

每次最多只提出一个澄清问题。

1.3 将页面名称解析为维度值

对于基于页面的步骤,调用 searchDimensionItems 查找与步骤名称匹配的确切维度值。首先使用类似 "page name url" 的语义搜索调用 findDimensions,以确认正确的维度 ID — 在大多数 CJA 数据视图中,此 ID 为 variables/web.webPageDetails.name,而不是 variables/page:

searchDimensionItems(
  dimensionId: "variables/web.webPageDetails.name",
  searchAnd: "<step page name>",
  startDate: "<period start>",
  endDate: "<period end>",
  page: 0,
  limit: 10
)

如果有多个候选项,请向用户展示匹配结果:

“我找到了以下与‘checkout’匹配的页面:/checkout/start、/checkout/payment、/checkout/review。结账步骤的入口要使用‘/checkout/start’吗?”


阶段 2 — 衡量每个步骤

对于每个漏斗步骤,构建一个临时区段,用于筛选到达该步骤的访客/会话。然后运行报表,衡量应用该区段后的基础指标(通常为独立访客数或会话数)。

2.1 为每个步骤构建临时区段

“到达第 N 步”的临时区段包括:

  • 容器:访问或人员(会话级漏斗使用访问,跨访问旅程使用人员)
  • 条件:页面名称等于“<步骤页面值>”或事件已发生
2.2 为每个步骤运行报告

为每个步骤运行一次 runReport,并应用临时细分。adhocSegments 参数需要完整的 CJA 细分定义对象,而不是简化的简写形式。正确结构如下:

runReport(
  dimensionIds: "variables/web.webPageDetails.name",
  metricIds: "metrics/visitors",
  startDate: "<period start>",
  endDate: "<period end>",
  page: 0,
  limit: 1,
  adhocSegments: [{
    "func": "segment",
    "version": [1, 0, 0],
    "container": {
      "func": "container",
      "context": "visitors",
      "pred": {
        "func": "streq",
        "val": { "func": "attr", "name": "variables/web.webPageDetails.name" },
        "str": "<step page value>"
      }
    }
  }]
)

跨访问漏斗请使用 context: "visitors"(人员级别)。会话内漏斗请使用 context: "visits"(会话级别)。指标值来自响应中的 summaryData.filteredTotals[0]。

谓词中使用的维度和值应与第 1 阶段通过 searchDimensionItems 查到的内容一致——请使用返回的确切 value 字符串。

重要提示:每个步骤都使用累积筛选器——衡量“在此期间曾到达此步骤的访客”,而不是“只访问过此页面的访客”。这样会生成经典的漏斗瀑布图。

为每个步骤 i(从 1 到 N)记录 stepCount[i]。


第 3 阶段 — 计算漏斗指标

对于每个步骤间的转化:

  • stepConversionRate[i→i+1] = stepCount[i+1] / stepCount[i] × 100
  • dropOff[i→i+1] = stepCount[i] − stepCount[i+1]
  • dropOffRate[i→i+1] = 100 − stepConversionRate[i→i+1]

整体漏斗:

  • overallConversionRate = stepCount[N] / stepCount[1] × 100
  • biggestDropOffStep = argmax(dropOff[i→i+1])——流失访客人数最多的步骤

第 4 阶段 — 可选:细分漏斗

如果用户想比较不同受众或渠道的漏斗表现,请按某个维度或细分筛选,并运行相同的步骤报告:

runReport(
  dimensionIds: "variables/device_type",
  metricIds: "metrics/visitors",
  startDate: "<range start>",
  endDate: "<range end>",
  page: 0,
  limit: 10,
  adhocSegments: [{ /* step N filter — same structure as Phase 2 */ }]
)

注意:使用 findDimensions 和 searchQuery: "device type mobile desktop",确认此数据视图的设备维度 ID(通常为 variables/device_type)。

这会按设备类型(或渠道、国家/地区)细分显示第 N 步的访客人数。如果某个细分在流失最严重的步骤中转化率显著偏低,那么这就是需要优化的目标。

建议比较的常见维度:

  • 设备类型(移动设备与桌面设备的转化率通常差异显著)
  • 营销渠道(付费用户与自然流量用户的转化率可能不同)
  • 新访客与回访访客

第 5 阶段 — 生成 HTML 漏斗报告

在页面中生成漏斗报告,并写入 /tmp/cja_funnel_health_check_report_<YYYY-MM-DD_HHMMSS>.html。

HTML 模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Funnel Health Check &mdash; {ORG_NAME} &mdash; {FUNNEL_NAME}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;900&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  :root {
    --bg: #f5f4f1;
    --surface: #ffffff;
    --ink: #1a1a1a;
    --ink-muted: #6b6b6b;
    --border: #e5e2dc;
    --header-bg: #0e0e10;
    --header-warm: #3a1010;
    --accent-red: #c8312f;
    --accent-red-bright: #ff6b68;
    --accent-red-soft: #fdecea;
    --accent-green: #1f7a4d;
    --accent-yellow: #d4a017;
  }
  body { font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
         background: var(--bg); color: var(--ink); line-height: 1.5;
         -webkit-font-smoothing: antialiased; }

  /* === Header === */
  header { background: linear-gradient(120deg, var(--header-bg) 0%, #1a0d0d 55%, var(--header-warm) 100%);
           color: #fff; padding: 56px 56px 44px; position: relative; overflow: hidden; }
  header::after { content: ""; position: absolute; right: -140px; top: -140px;
                  width: 460px; height: 460px;
                  background: radial-gradient(circle, rgba(200,49,47,.35) 0%, transparent 70%);
                  pointer-events: none; }
  .header-inner { max-width: 1080px; margin: 0 auto; position: relative; z-index: 1; }
  .eyebrow { display: inline-flex; align-items: center; gap: 8px;
             padding: 6px 14px; border: 1px solid rgba(255,107,104,.55);
             border-radius: 999px; color: var(--accent-red-bright);
             font-size: 11px; font-weight: 600; letter-spacing: 1.2px;
             text-transform: uppercase; margin-bottom: 24px;
             background: rgba(200,49,47,.10); }
  .eyebrow::before { content: ""; width: 6px; height: 6px;
                     background: var(--accent-red-bright); border-radius: 50%; }
  header h1 { font-family: "Playfair Display", Georgia, serif;
              font-size: 56px; font-weight: 700; letter-spacing: -1.5px;
              line-height: 1.05; margin-bottom: 14px; color: #fff; }
  header .lede { font-size: 16px; max-width: 560px;
                 color: rgba(255,255,255,.80); margin-bottom: 24px;
                 line-height: 1.55; }
  header .meta { display: flex; flex-wrap: wrap; gap: 22px;
                 font-size: 13px; color: rgba(255,255,255,.60); }
  header .meta span { display: inline-flex; align-items: center; gap: 6px; }
  header .meta .icon { opacity: .8; }

  /* === Tabs === */
  nav { background: var(--surface); border-bottom: 1px solid var(--border);
        padding: 0 56px; display: flex; gap: 28px;
        position: sticky; top: 0; z-index: 50; }
  nav a { display: block; padding: 16px 0; font-size: 14px;
          color: var(--ink); text-decoration: none;
          border-bottom: 2px solid transparent;
          transition: border-color .15s ease; }
  nav a:hover { border-bottom-color: var(--accent-red); }

  /* === Container === */
  .container { max-width: 1080px; margin: 0 auto; padding: 36px 56px 60px; }

  /* === Section label === */
  .section-label { font-size: 11px; font-weight: 700;
                   text-transform: uppercase; letter-spacing: 1.4px;
                   color: var(--ink-muted); margin-bottom: 14px;
                   padding-bottom: 10px; border-bottom: 1px solid var(--border); }

  /* === KPI grid === */
  .kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
             gap: 14px; margin-bottom: 36px; }
  .kpi-tile { background: var(--surface); border-radius: 8px;
              padding: 22px 22px 20px;
              border-top: 3px solid #b9b6ae;
              box-shadow: 0 1px 3px rgba(0,0,0,.05); }
  .kpi-tile.down { border-top-color: var(--accent-red); }
  .kpi-tile.up   { border-top-color: var(--accent-green); }
  .kpi-tile.flat { border-top-color: #b9b6ae; }
  .kpi-head { display: flex; justify-content: space-between;
              align-items: center; margin-bottom: 10px; }
  .kpi-label { font-size: 11px; font-weight: 700;
               text-transform: uppercase;
               color: var(--ink-muted); letter-spacing: 1px; }
  .kpi-value { font-family: "Playfair Display", Georgia, serif;
               font-weight: 700; font-size: 38px;
               line-height: 1; color: var(--ink);
               margin-bottom: 12px; }
  .pill { display: inline-flex; align-items: center; gap: 4px;
          padding: 3px 9px; border-radius: 4px;
          font-size: 12px; font-weight: 600; line-height: 1.4; }
  .pill.down { background: var(--accent-red-soft); color: var(--accent-red); }
  .pill.up   { background: #ebf5ef; color: var(--accent-green); }
  .pill.flat { background: #f1efea; color: var(--ink-muted); }
  .prior { display: block; margin-top: 10px;
           font-size: 12px; color: var(--ink-muted); }

  /* === Funnel chart wrap === */
  .chart-wrap { background: var(--surface); border-radius: 8px;
                padding: 28px 32px;
                box-shadow: 0 1px 3px rgba(0,0,0,.04);
                margin-bottom: 22px; }
  .chart-wrap h2 { font-family: "Playfair Display", Georgia, serif;
                   font-size: 18px; font-weight: 700;
                   margin-bottom: 18px;
                   padding-bottom: 12px;
                   border-bottom: 1px solid var(--border); }

  /* === Sections (collapsible tables) === */
  .section { background: var(--surface); border-radius: 8px;
             box-shadow: 0 1px 3px rgba(0,0,0,.04);
             margin-bottom: 22px; overflow: hidden; }
  .section-header { padding: 18px 28px; border-bottom: 1px solid var(--border);
                    display: flex; justify-content: space-between;
                    align-items: center; cursor: pointer; }
  .section-header h2 { font-family: "Playfair Display", Georgia, serif;
                       font-size: 18px; font-weight: 700; }
  table { width: 100%; border-collapse: collapse; font-size: 13px; }
  thead th { background: #faf8f4; padding: 12px 22px;
             text-align: left; font-weight: 600;
             text-transform: uppercase; letter-spacing: .6px;
             font-size: 11px; color: var(--ink-muted);
             border-bottom: 1px solid var(--border); }
  tbody td { padding: 12px 22px; border-bottom: 1px solid #f4f1eb; }
  tbody tr:last-child td { border-bottom: none; }

  .badge { display: inline-block; padding: 3px 9px; border-radius: 4px;
           font-size: 11px; font-weight: 600; }
  .badge.green  { background: #ebf5ef; color: var(--accent-green); }
  .badge.red    { background: var(--accent-red-soft); color: var(--accent-red); }
  .badge.yellow { background: #fef6e3; color: #b67a08; }
  .badge.grey   { background: #f1efea; color: var(--ink-muted); }

  .highlight-row td { background: var(--accent-red-soft) !important;
                      font-weight: 700; }
  .progress-bar-wrap { background: #f1efea; border-radius: 4px;
                       height: 8px; overflow: hidden;
                       width: 100%; min-width: 80px; }
  .progress-bar { height: 100%; border-radius: 4px;
                  background: linear-gradient(90deg, var(--accent-red), #8a1d1c); }

  .rec-item { display: flex; gap: 12px; align-items: flex-start;
              padding: 14px 28px; border-bottom: 1px solid #f1eeea;
              font-size: 14px; line-height: 1.55; }
  .rec-item:last-child { border-bottom: none; }

  .back-top { position: fixed; bottom: 24px; right: 24px;
              background: var(--accent-red); color: #fff;
              width: 44px; height: 44px; border-radius: 50%;
              border: none; font-size: 20px; cursor: pointer;
              box-shadow: 0 4px 12px rgba(200,49,47,0.30); }
  footer { text-align: center; padding: 32px 24px;
           font-size: 12px; color: var(--ink-muted); }

  /* === Print === */
  @media print {
    nav { display: none; position: static; }
    header { padding: 36px 32px 28px; }
    header h1 { font-size: 42px; }
    .section-header { cursor: default; }
    .kpi-row { page-break-inside: avoid; }
    .kpi-tile, .chart-wrap, .section {
      box-shadow: none; border: 1px solid var(--border);
    }
    .back-top { display: none; }
  }
</style>
</head>
<body>

<header>
  <div class="header-inner">
    <div class="eyebrow">Funnel Health Report</div>
    <h1>{ORG_NAME} Funnel Health</h1>
    <p class="lede">Step-by-step conversion for the {FUNNEL_NAME} journey across {DATE_RANGE}, with the biggest leakage point surfaced.</p>
    <div class="meta">
      <span><span class="icon">&#128197;</span> {DATE_RANGE}</span>
      <span><span class="icon">&#128202;</span> {DATA_VIEW}</span>
      <span><span class="icon">&#128340;</span> Prepared {GENERATED_DATE}</span>
    </div>
  </div>
</header>

<nav>
  <a href="#overview">Overview</a>
  <a href="#chart">Funnel Chart</a>
  <a href="#steps">Step Detail</a>
  <a href="#segments">Segment Breakdown</a>
  <a href="#recs">Recommendations</a>
</nav>

<div class="container">

  
  <div class="section-label">Funnel Summary</div>
  <div id="overview" class="kpi-row">
    <div class="kpi-tile flat">
      <div class="kpi-head"><div class="kpi-label">Entered Funnel</div></div>
      <div class="kpi-value">{STEP_1_COUNT}</div>
      <span class="prior">Step 1 visitors</span>
    </div>
    <div class="kpi-tile flat">
      <div class="kpi-head"><div class="kpi-label">Completed Funnel</div></div>
      <div class="kpi-value">{STEP_N_COUNT}</div>
      <span class="prior">Reached final step</span>
    </div>
    <div class="kpi-tile flat">
      <div class="kpi-head"><div class="kpi-label">Overall Conversion</div></div>
      <div class="kpi-value">{OVERALL_CVR}%</div>
      <span class="prior">End-to-end rate</span>
    </div>
    <div class="kpi-tile down">
      <div class="kpi-head"><div class="kpi-label">Biggest Drop-Off Step</div></div>
      <div class="kpi-value" style="font-size:22px;">{WORST_STEP_NAME}</div>
      <span class="pill down">&#9660; Worst leakage</span>
    </div>
    <div class="kpi-tile down">
      <div class="kpi-head"><div class="kpi-label">Worst Step Drop-Off</div></div>
      <div class="kpi-value">{WORST_STEP_DROPOFF}%</div>
      <span class="prior">Of visitors lost at this step</span>
    </div>
  </div>

  
  <div id="chart" class="chart-wrap">
    <h2>Funnel Visualization</h2>
    <canvas id="funnelChart" height="100"></canvas>
  </div>

  
  <div id="steps" class="section">
    <div class="section-header" onclick="toggle('steps-body')">
      <h2>Step-by-Step Analysis</h2>
      <span id="steps-body-icon">&#9662;</span>
    </div>
    <div id="steps-body">
      <table>
        <thead><tr>
          <th>Step</th>
          <th>Visitors</th>
          <th>% of Step 1</th>
          <th>Step Conversion</th>
          <th>Drop-Off</th>
          <th>Drop-Off Rate</th>
          <th>Visual</th>
        </tr></thead>
        <tbody>
          
        </tbody>
      </table>
    </div>
  </div>

  
  <div id="segments" class="section">
    <div class="section-header" onclick="toggle('seg-body')">
      <h2>Segment Breakdown at Worst Step</h2>
      <span id="seg-body-icon">&#9662;</span>
    </div>
    <div id="seg-body">
      <table>
        <thead><tr>
          <th>Segment / Dimension</th>
          <th>Entered Worst Step</th>
          <th>Passed Worst Step</th>
          <th>Conversion</th>
          <th>vs. Average</th>
        </tr></thead>
        <tbody>
          
        </tbody>
      </table>
    </div>
  </div>

  
  <div id="recs" class="section">
    <div class="section-header" onclick="toggle('rec-body')">
      <h2>Optimization Recommendations</h2>
      <span id="rec-body-icon">&#9662;</span>
    </div>
    <div id="rec-body">
      
    </div>
  </div>

</div>

<button class="back-top" onclick="window.scrollTo({top:0,behavior:'smooth'})">&uarr;</button>
<footer>Funnel Health Check &mdash; {ORG_NAME} &mdash; Generated {GENERATED_DATE}</footer>

<script>
// Funnel bar chart — recolor via design tokens.
// Stage colors fade from accent-red (worst leakage upstream) to a deeper red downstream.
const ctx = document.getElementById('funnelChart').getContext('2d');
new Chart(ctx, {
  type: 'bar',
  data: {
    labels: {STEP_NAMES_JSON},
    datasets: [{
      label: 'Visitors',
      data: {STEP_COUNTS_JSON},
      backgroundColor: [
        'rgba(200,49,47,0.90)',
        'rgba(200,49,47,0.72)',
        'rgba(200,49,47,0.54)',
        'rgba(200,49,47,0.38)',
        'rgba(200,49,47,0.22)'
      ],
      borderRadius: 6
    }]
  },
  options: {
    responsive: true,
    plugins: { legend: { display: false } },
    scales: {
      y: { beginAtZero: true, grid: { color: '#eee9df' },
           ticks: { color: '#6b6b6b' } },
      x: { grid: { display: false },
           ticks: { color: '#1a1a1a', font: { weight: '600' } } }
    }
  }
});

function toggle(id) {
  var el = document.getElementById(id);
  var ic = document.getElementById(id + '-icon');
  if (el.style.display === 'none') { el.style.display=''; ic.textContent='\u25be'; }
  else { el.style.display='none'; ic.textContent='\u25b8'; }
}
</script>
</body></html>

工作流摘要

  1. 根据用户描述解析漏斗步骤。
  2. 澄清任何含糊不清的步骤(每次只问一个问题)。
  3. 使用 searchDimensionItems 确认页面名称。
  4. 为每个步骤运行一次 runReport 并应用临时细分筛选器;记录访客人数。
  5. 计算各步骤的转化率、流失人数和流失率。
  6. 找出流失最严重的步骤。
  7. 可选:针对流失最严重的步骤运行维度细分报告。
  8. 使用 Chart.js 漏斗可视化生成 HTML 报告。
  9. 写入 /tmp/cja_funnel_health_check_report_<YYYY-MM-DD_HHMMSS>.html。
  10. 使用 open /tmp/cja_funnel_health_check_report_<YYYY-MM-DD_HHMMSS>.html 打开。
  11. 在页面中总结:“整体转化率:X%。流失最严重的步骤是 [步骤 N]:Y% 的用户流失。移动设备用户的流失率是桌面设备用户的 2 倍。”

重要注意事项

  • 只读分析。 切勿自动修改细分、计算指标或项目定义。
  • 运行前确认漏斗阶段。 含糊的阶段定义会导致结果产生误导——请先向用户澄清。
  • 注明归因模型。 漏斗转化率取决于数据视图中使用的归因模型;请在报告中说明。
  • 标记不完整数据。 如果任何阶段的计数为零或低得可疑,请在得出结论前指出可能存在跟踪缺口。
  • 限制日期范围。 超过 90 天的超长日期范围可能导致漏斗分析变慢;建议默认使用 30 天窗口。
  • 切勿假定阶段顺序。 计算流失率前,请确认这些阶段按顺序排列且互不重叠。

示例交互

“检查一下我们的结账漏斗表现——我想看看用户在哪些环节流失。”

  1. 设置: 调用 findDataViews,用户选择其电商数据视图。调用 setDefaultSessionDataViewId。
  2. 定义漏斗: 询问“结账流程包含哪些阶段?”用户回答:“商品浏览 → 加入购物车 → 开始结账 → 购买。”
  3. 分析: 对过去 30 天内的每个阶段转化运行 runReport。计算流失率:商品浏览→购物车 22%,购物车→开始结账 58%,开始结账→购买 71%。
  4. 发现: 确认购物车→开始结账是流失率最高的环节(78% 的用户流失)。按设备类型细分后发现,移动端的转化率比桌面端低 40%。
  5. 报告: 展示漏斗可视化图表,列出各阶段的流失率、主要退出细分群体,以及 3 条优先级建议。

建议逻辑

  • 最差环节流失率 > 60%:“严重流失——此环节可能存在问题,或用户预期不匹配。应优先开展 UX 调查。”
  • 移动端流失率 > 桌面端的 2 倍:“此环节的移动端 UX 需要改进——考虑设计专用的移动端流程或简化表单。”
  • 特定渠道的流失率比平均值高出 > 40%:“来自此渠道的用户可能意图不匹配——请检查着陆页是否与用户意图一致。”
  • 第 1 步人数 < 1,000:“漏斗入口量过低,无法获得统计置信度。请检查步骤定义或扩大日期范围。”

评论

支持 Markdown