前言

Umami 是一个高颜值可自部署的统计应用,本站也一直在使用该项目统计网站的访客数据,下面分享下如何在网站前端调用umami的api进行数据展示。代码思路来源于懋和道人,后经我使用GPT-4-o优化,并最终在本站右侧广告恰饭区实现统计数据的展示。

教程

获取登录token

首先需要去https://hoppscotch.io/获取umami站点的登录token,然后按照下图序号选取,最后获取token备用

获取网站ID

前往你自己部署的Umami站点,获取已添加监控站点的站点ID备用,如下图

配置PHP代码部分

这里我选择把该部分代码作为一个独立的php文件,置于网站目录下,需要修改下面代码,填入前面两步获取的内容。每次访问该接口会在同级目录下生成umami_cache.json,这里我的方案是通过宝塔面板的计划任务,创建访问URL任务,频率每一分钟。至于该段代码是否直接置于前端,取决于你自己喜好。

<?php
header('Content-Type: application/json');
header("Access-Control-Allow-Origin: *");

// 配置 Umami API 的凭据
$apiBaseUrl = 'https://umami域名';
$token = '获取的token';
$websiteId = '获取的网站ID'; // 在这里定义网站ID
$cacheFile = 'umami_cache.json'; // 保存的json即使更新数据
$cacheTime = 600; // 缓存时间为10分钟(600秒)

// 获取当前时间戳(毫秒级)
$currentTimestamp = time() * 1000;

// Umami API 的起始时间戳(毫秒级)
$startTimestampToday = strtotime("today") * 1000;
$startTimestampYesterday = strtotime("yesterday") * 1000;

// 获取上个月的时间范围
$startTimestampLastMonth = strtotime("first day of last month") * 1000;
$endTimestampLastMonth = strtotime("last day of last month 23:59:59") * 1000;

// 获取去年全年的时间范围
$startTimestampLastYear = strtotime("first day of January last year") * 1000;
$endTimestampLastYear = strtotime("last day of December last year 23:59:59") * 1000;

// 定义 Umami API 请求函数
function fetchUmamiData($apiBaseUrl, $websiteId, $startAt, $endAt, $token) {
    $url = "$apiBaseUrl/api/websites/$websiteId/stats?" . http_build_query([
        'startAt' => $startAt,
        'endAt' => $endAt
    ]);

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        "Authorization: Bearer $token",
        "Content-Type: application/json"
    ]);
    $response = curl_exec($ch);

    if (curl_errno($ch)) {
        echo 'Curl error: ' . curl_error($ch);
        curl_close($ch);
        return null;
    }

    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    if ($httpCode != 200) {
        echo "HTTP error code: $httpCode\n";
        curl_close($ch);
        return null;
    }

    curl_close($ch);
    return json_decode($response, true);
}

// 检查缓存文件是否存在且未过期
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheTime)) {
    // 读取缓存文件
    $cachedData = file_get_contents($cacheFile);
    echo $cachedData;
} else {
    // 获取统计数据
    $todayData = fetchUmamiData($apiBaseUrl, $websiteId, $startTimestampToday, $currentTimestamp, $token);
    $yesterdayData = fetchUmamiData($apiBaseUrl, $websiteId, $startTimestampYesterday, $startTimestampToday, $token);
    $lastMonthData = fetchUmamiData($apiBaseUrl, $websiteId, $startTimestampLastMonth, $endTimestampLastMonth, $token);
    $lastYearData = fetchUmamiData($apiBaseUrl, $websiteId, $startTimestampLastYear, $endTimestampLastYear, $token);

    // 如果请求失败,返回错误信息
    if ($todayData === null || $yesterdayData === null || $lastMonthData === null || $lastYearData === null) {
        echo json_encode(["error" => "Failed to fetch data from Umami API"]);
        exit;
    }

    // 组装返回的 JSON 数据
    $responseData = [
        "today_uv" => $todayData['visitors']['value'] ?? null,
        "today_pv" => $todayData['pageviews']['value'] ?? null,
        "yesterday_uv" => $yesterdayData['visitors']['value'] ?? null,
        "yesterday_pv" => $yesterdayData['pageviews']['value'] ?? null,
        "last_month_pv" => $lastMonthData['pageviews']['value'] ?? null,
        "last_month_uv" => $lastMonthData['visitors']['value'] ?? null,
        "last_year_pv" => $lastYearData['pageviews']['value'] ?? null,
        "last_year_uv" => $lastYearData['visitors']['value'] ?? null
    ];

    // 将数据写入缓存文件
    file_put_contents($cacheFile, json_encode($responseData));

    // 输出 JSON 数据
    echo json_encode($responseData);
}
?>

配置Javascript代码部分

下面代码是我借助GPT修改过的,我直接将其放置在我主题设置里面的自定义Javascript里面,前端添加代码需加标签,下面代码需修改,umami_cache.json的文件路径支持外链,也可直接填写php接口位置,但是会影响加载速度,这里建议填写json文件的链接。

async function loadAndDisplayData() {  
  try {  
      const response = await fetch('这里修改为umami_cache.json的文件路径,支持外链');  
      if (!response.ok) throw new Error('Network response was not ok');  
      const data = await response.json();  

      const mainContainer = document.getElementById('showumami');  
      mainContainer.innerHTML = '';  

      function createStatsBadge(uvLabel, uvValue, pvLabel, pvValue, uvColorClass, pvColorClass) {  
          const uvBadge = document.createElement('div');  
          uvBadge.className = 'stats-badge';  

          const uvSubject = document.createElement('span');  
          uvSubject.className = 'stats-subject';  
          uvSubject.textContent = uvLabel;  

          const uvValueSpan = document.createElement('span');  
          uvValueSpan.className = `stats-value ${uvColorClass}`;  
          uvValueSpan.textContent = uvValue;  

          uvBadge.appendChild(uvSubject);  
          uvBadge.appendChild(uvValueSpan);  

          const pvBadge = document.createElement('div');  
          pvBadge.className = 'stats-badge';  

          const pvSubject = document.createElement('span');  
          pvSubject.className = 'stats-subject';  
          pvSubject.textContent = pvLabel;  

          const pvValueSpan = document.createElement('span');  
          pvValueSpan.className = `stats-value ${pvColorClass}`;  
          pvValueSpan.textContent = pvValue;  

          pvBadge.appendChild(pvSubject);  
          pvBadge.appendChild(pvValueSpan);  

          mainContainer.appendChild(uvBadge);  
          mainContainer.appendChild(pvBadge);  
      }  

      const statsPairs = [  
          { uvLabel: '今日[UV]', uv: data.today_uv, pvLabel: '今日[PV]', pv: data.today_pv, uvColor: 'stats-bg-green', pvColor: 'stats-bg-green' },  
          { uvLabel: '昨日[UV]', uv: data.yesterday_uv, pvLabel: '昨日[PV]', pv: data.yesterday_pv, uvColor: 'stats-bg-blue', pvColor: 'stats-bg-blue' },  
          { uvLabel: '本月[UV]', uv: data.last_month_uv, pvLabel: '本月[PV]', pv: data.last_month_pv, uvColor: 'stats-bg-pearl', pvColor: 'stats-bg-pearl' }
      ];  

      statsPairs.forEach(pair => {  
          createStatsBadge(pair.uvLabel, pair.uv, pair.pvLabel, pair.pv, pair.uvColor, pair.pvColor);  
      });  

  } catch (error) {  
      console.error('Error loading or parsing data:', error);  
      document.getElementById('main').textContent = 'Error loading data';  
  }  
}  

loadAndDisplayData();

前端调用

在你需要展示数据的地方添加下方代码即可

<div id="showumami"></div>

设置CSS样式

这里我借助GPT-4-o根据博客底部badge样式写了个比较合适的CSS,我直接将其置于主题设置的自定义CS栏目中。

/* 主容器样式,确保子元素在一行中平分宽度,调整间距 */
#showumami {
  display: flex;
  flex-wrap: wrap;
  gap: 2px; /* 左右间距和上下间距为5px */
}

/* 定义新的stats-badge组件的样式,设置为原高度的70% */
.stats-badge {
  display: flex;
  width: calc(50% - 5px);
  border-radius: 5px;
  text-shadow: none;
  font-size: 10px; /* 字体大小为原来的70% */
  color: #fff;
  line-height: 14px; /* 行高调整为原来的70% */
  margin-bottom: 3.5px; /* 下间距为原来的70% */
  height: 15px; /* badge高度调整为原来的70% */
}

/* 左侧区域宽度根据文本内容严格调整 */
.stats-badge .stats-subject {
  display: flex;
  align-items: center;
  background-color: #4d4d4d;
  padding: 3.5px; /* 内边距为原来的70% */
  border-top-left-radius: 5px;
  border-bottom-left-radius: 5px;
  white-space: nowrap; /* 防止文字换行 */
}

/* 右侧区域根据左侧宽度自动调整 */
.stats-badge .stats-value {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3.5px; /* 内边距为原来的70% */
  border-top-right-radius: 5px;
  border-bottom-right-radius: 5px;
  flex-grow: 1; /* 使右侧部分自适应剩余空间 */
}

/* 定义不同背景色的class */
.stats-bg-green {
  background-color: #3bca6e;
}

.stats-bg-blue {
  background-color: #007ec6;
}

.stats-bg-pearl {
  background-color: #FA92B5;
}

代码参考

前端调用 Umami API 数据并渲染展示

🏷本文标签:Umami,️数据统计
最后修改:2024 年 08 月 19 日
如果觉得我的文章对你有用,请随意赞赏