diff --git a/src/main/java/com/xhhao/dataStatistics/service/SettingConfigGetter.java b/src/main/java/com/xhhao/dataStatistics/service/SettingConfigGetter.java index 9642b44..6310313 100644 --- a/src/main/java/com/xhhao/dataStatistics/service/SettingConfigGetter.java +++ b/src/main/java/com/xhhao/dataStatistics/service/SettingConfigGetter.java @@ -13,6 +13,7 @@ public interface SettingConfigGetter { class BasicsConfig { public static final String GROUP = "basics"; private String title; + private Boolean enableMomentHeatmap; } @Data class UmamiConfig { diff --git a/src/main/java/com/xhhao/dataStatistics/service/impl/StatisticalServiceImpl.java b/src/main/java/com/xhhao/dataStatistics/service/impl/StatisticalServiceImpl.java index 79a3909..efffada 100644 --- a/src/main/java/com/xhhao/dataStatistics/service/impl/StatisticalServiceImpl.java +++ b/src/main/java/com/xhhao/dataStatistics/service/impl/StatisticalServiceImpl.java @@ -6,8 +6,10 @@ import java.time.Instant; import java.time.LocalDate; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -18,25 +20,35 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.xhhao.dataStatistics.common.Constants; +import com.xhhao.dataStatistics.service.SettingConfigGetter; import com.xhhao.dataStatistics.service.StatisticalService; import com.xhhao.dataStatistics.vo.PieChartVO; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import run.halo.app.core.extension.content.Category; import run.halo.app.core.extension.content.Comment; import run.halo.app.core.extension.content.Post; import run.halo.app.core.extension.content.Tag; +import run.halo.app.extension.GroupVersionKind; import run.halo.app.extension.ListOptions; import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.extension.Unstructured; +import run.halo.app.extension.index.query.Queries; +import run.halo.app.extension.router.selector.FieldSelector; @Slf4j @Component @RequiredArgsConstructor public class StatisticalServiceImpl implements StatisticalService { - + + private static final GroupVersionKind MOMENT_GVK = + new GroupVersionKind("moment.halo.run", "v1alpha1", "Moment"); + private final ReactiveExtensionClient client; + private final SettingConfigGetter settingConfigGetter; private final ObjectMapper objectMapper = new ObjectMapper(); /** @@ -69,6 +81,10 @@ public void clearCache() { private Mono buildPieChartVO() { PieChartVO pieChartVO = new PieChartVO(); + Mono enableMomentHeatmapMono = settingConfigGetter.getBasicsConfig() + .map(config -> Boolean.TRUE.equals(config.getEnableMomentHeatmap())) + .defaultIfEmpty(false); + Mono> tagsMono = client.listAll(Tag.class, new ListOptions(), Sort.by(Sort.Order.desc("metadata.creationTimestamp"))) .map(tag -> { @@ -89,11 +105,23 @@ private Mono buildPieChartVO() { }) .collectList(); - Mono> articlesMono = client.listAll(Post.class, new ListOptions(), + // 文章按日聚合 + Mono> postsByDateMono = client.listAll(Post.class, new ListOptions(), Sort.by(Sort.Order.desc("metadata.creationTimestamp"))) .filter(post -> post.getSpec().getPublishTime() != null) .collectList() - .map(this::buildArticleList); + .map(posts -> posts.stream() + .collect(Collectors.groupingBy(post -> { + Instant publishTime = post.getSpec().getPublishTime(); + if (publishTime != null) { + return toDateStr(publishTime); + } + return toDateStr(post.getMetadata().getCreationTimestamp()); + }, Collectors.collectingAndThen(Collectors.counting(), Long::intValue)))); + + // 瞬间按日聚合(受开关控制) + Mono> momentsByDateMono = enableMomentHeatmapMono + .flatMap(enabled -> enabled ? getMomentCountsByDate() : Mono.just(Map.of())); Mono> commentsMono = client.listAll(Comment.class, new ListOptions(), Sort.by(Sort.Order.desc("metadata.creationTimestamp"))) @@ -108,30 +136,99 @@ private Mono buildPieChartVO() { .take(10) .collectList(); - return Mono.zip(tagsMono, categoriesMono, articlesMono, commentsMono, top10ArticlesMono) + return Mono.zip(tagsMono, categoriesMono, postsByDateMono, momentsByDateMono, + commentsMono, top10ArticlesMono, enableMomentHeatmapMono) .map(tuple -> { pieChartVO.setTags(tuple.getT1()); pieChartVO.setCategories(tuple.getT2()); - pieChartVO.setArticles(tuple.getT3()); - pieChartVO.setComments(tuple.getT4()); - pieChartVO.setTop10Articles(tuple.getT5()); + + Map postsByDate = tuple.getT3(); + Map momentsByDate = tuple.getT4(); + boolean enableMoment = tuple.getT7(); + + pieChartVO.setArticles(buildArticleList(postsByDate, momentsByDate)); + pieChartVO.setComments(tuple.getT5()); + pieChartVO.setTop10Articles(tuple.getT6()); + pieChartVO.setEnableMomentHeatmap(enableMoment); return pieChartVO; }); } - private List buildArticleList(List posts) { - Map postsByDate = posts.stream() - .collect(Collectors.groupingBy(post -> { - Instant publishTime = post.getSpec().getPublishTime(); - if (publishTime != null) { - return publishTime.atZone(Constants.DEFAULT_ZONE_ID) - .toLocalDate().toString(); + /** + * 动态查询 moments 并按日期聚合计数 + */ + private Mono> getMomentCountsByDate() { + ListOptions listOptions = new ListOptions(); + listOptions.setFieldSelector(FieldSelector.of(Queries.and( + Queries.equal("spec.visible", "PUBLIC"), + Queries.equal("spec.approved", "true") + ))); + + return Flux.fromIterable(client.indexedQueryEngine().retrieveAll( + MOMENT_GVK, + listOptions, + Sort.by(Sort.Order.desc("spec.releaseTime")))) + .flatMap(name -> client.fetch(MOMENT_GVK, name)) + .filter(this::isPublicApprovedMoment) + .collectList() + .map(moments -> { + Map map = new HashMap<>(); + for (Unstructured moment : moments) { + extractMomentDate(moment).ifPresent(dateStr -> + map.merge(dateStr, 1, Integer::sum)); } - return post.getMetadata().getCreationTimestamp() - .atZone(Constants.DEFAULT_ZONE_ID) - .toLocalDate().toString(); - }, Collectors.collectingAndThen(Collectors.counting(), Long::intValue))); + return map; + }) + .onErrorResume(e -> { + log.warn("查询瞬间数据失败(可能未安装 moments 插件): {}", e.getMessage()); + return Mono.just(Map.of()); + }); + } + + /** + * 判断瞬间是否为公开已审核状态 + */ + private boolean isPublicApprovedMoment(Unstructured unstructured) { + try { + Map data = unstructured.getData(); + Map spec = Unstructured.getNestedMap(data, "spec").orElse(null); + if (spec == null) return false; + Object visible = spec.get("visible"); + if (visible != null && !"PUBLIC".equals(visible.toString())) return false; + Object approved = spec.get("approved"); + if (approved != null && !Boolean.parseBoolean(approved.toString())) return false; + return true; + } catch (Exception e) { + return false; + } + } + + /** + * 提取瞬间的日期字符串(优先 spec.releaseTime,回退 metadata.creationTimestamp) + */ + private Optional extractMomentDate(Unstructured unstructured) { + try { + Map data = unstructured.getData(); + Optional releaseTime = Unstructured.getNestedInstant(data, "spec", "releaseTime"); + if (releaseTime.isPresent()) { + return Optional.of(toDateStr(releaseTime.get())); + } + Instant creation = unstructured.getMetadata().getCreationTimestamp(); + if (creation != null) { + return Optional.of(toDateStr(creation)); + } + } catch (Exception e) { + log.debug("提取瞬间日期失败: {}", e.getMessage()); + } + return Optional.empty(); + } + + private String toDateStr(Instant instant) { + return instant.atZone(Constants.DEFAULT_ZONE_ID).toLocalDate().toString(); + } + private List buildArticleList(Map postsByDate, + Map momentsByDate) { LocalDate today = LocalDate.now(Constants.DEFAULT_ZONE_ID); LocalDate startDate = today.minusYears(1); @@ -139,10 +236,14 @@ private List buildArticleList(List posts) { .limit(java.time.temporal.ChronoUnit.DAYS.between(startDate, today.plusDays(1))) .map(date -> { String dateStr = date.toString(); + int articleCount = postsByDate.getOrDefault(dateStr, 0); + int momentCount = momentsByDate.getOrDefault(dateStr, 0); PieChartVO.Article articleVO = new PieChartVO.Article(); articleVO.setName(dateStr); articleVO.setDate(date.atStartOfDay(Constants.DEFAULT_ZONE_ID).toLocalDateTime()); - articleVO.setTotal(postsByDate.getOrDefault(dateStr, 0)); + articleVO.setArticleTotal(articleCount); + articleVO.setMomentTotal(momentCount); + articleVO.setTotal(articleCount + momentCount); return articleVO; }) .sorted(Comparator.comparing(PieChartVO.Article::getDate).reversed()) diff --git a/src/main/java/com/xhhao/dataStatistics/vo/PieChartVO.java b/src/main/java/com/xhhao/dataStatistics/vo/PieChartVO.java index 5734dc9..210d7ad 100644 --- a/src/main/java/com/xhhao/dataStatistics/vo/PieChartVO.java +++ b/src/main/java/com/xhhao/dataStatistics/vo/PieChartVO.java @@ -8,6 +8,8 @@ @Data public class PieChartVO { + private Boolean enableMomentHeatmap; + private List tags = new ArrayList<>(); @Data public static class Tag { @@ -29,6 +31,8 @@ public static class Article { private String name; // 名称 private LocalDateTime date; // 日期 private Integer total; // 总数 + private Integer articleTotal; // 文章数 + private Integer momentTotal; // 瞬间数 } // 评论 diff --git a/src/main/resources/extensions/settings.yaml b/src/main/resources/extensions/settings.yaml index cdd2e2e..54cd694 100644 --- a/src/main/resources/extensions/settings.yaml +++ b/src/main/resources/extensions/settings.yaml @@ -62,6 +62,13 @@ spec: border: "1px solid #d1e7ff" cursor: "pointer" children: "复制群号" + - $formkit: checkbox + name: enableMomentHeatmap + id: enableMomentHeatmap + key: enableMomentHeatmap + label: 热力图统计瞬间 + value: false + help: 开启后将瞬间并入热力图统计,悬浮提示增加"瞬间"明细 - group: umami label: umami设置 formSchema: diff --git a/src/main/resources/static/js/siteCharts.js b/src/main/resources/static/js/siteCharts.js index 0f138d9..1107c25 100644 --- a/src/main/resources/static/js/siteCharts.js +++ b/src/main/resources/static/js/siteCharts.js @@ -313,7 +313,7 @@ return charts; } - function renderArticleHeatmap(container, articles) { + function renderArticleHeatmap(container, articles, enableMomentHeatmap = false) { const chartArea = createSection( container, '文章发布趋势', @@ -332,8 +332,16 @@ } date.setHours(0, 0, 0, 0); const key = formatDateYMD(date); - const total = Number(article.total ?? article.count ?? 0); - dataMap.set(key, (dataMap.get(key) || 0) + total); + const articleTotal = Number(article.articleTotal ?? article.total ?? article.count ?? 0); + const momentTotal = Number(article.momentTotal ?? 0); + const rawTotal = Number(article.total ?? 0); + const total = Number.isFinite(rawTotal) ? rawTotal : (articleTotal + momentTotal); + const current = dataMap.get(key) || { total: 0, articleTotal: 0, momentTotal: 0 }; + dataMap.set(key, { + total: current.total + total, + articleTotal: current.articleTotal + articleTotal, + momentTotal: current.momentTotal + momentTotal + }); }); if (!dataMap.size) { @@ -356,7 +364,7 @@ new Date(firstMonday.getTime() + index * 7 * DAY_IN_MS) ); - const maxValue = Math.max(...dataMap.values(), 0); + const maxValue = Math.max(...[...dataMap.values()].map(v => v.total), 0); const card = document.createElement('div'); card.className = 'xhhaocom-chartboard-card xhhaocom-chartboard-card--heatmap'; @@ -451,7 +459,27 @@ }; const showTooltip = (event, dateKey, value) => { - tooltip.innerHTML = `${dateKey}${value ? `发布 ${value} 篇文章` : '无文章发布'}`; + const articleTotal = value?.articleTotal ?? 0; + const momentTotal = value?.momentTotal ?? 0; + const lines = [`${dateKey}`]; + + if (enableMomentHeatmap) { + if (articleTotal > 0) { + lines.push(`已发布${articleTotal}篇文章`); + } + if (momentTotal > 0) { + lines.push(`已发布${momentTotal}条瞬间`); + } + if (articleTotal === 0 && momentTotal === 0) { + lines.push('当日无内容发布'); + } + } else if (articleTotal > 0) { + lines.push(`已发布${articleTotal}篇文章`); + } else { + lines.push('当日无文章发布'); + } + + tooltip.innerHTML = lines.join(''); tooltip.style.display = 'flex'; const cardRect = card.getBoundingClientRect(); @@ -541,10 +569,10 @@ if (!isWithinRange) { cell.classList.add('is-outside'); } else { - const value = dataMap.get(dayKey) || 0; - const level = computeLevel(value); + const value = dataMap.get(dayKey) || { total: 0, articleTotal: 0, momentTotal: 0 }; + const level = computeLevel(value.total); cell.dataset.level = level.toString(); - cell.dataset.value = value.toString(); + cell.dataset.value = value.total.toString(); cell.dataset.date = dayKey; const handleMouseMove = event => showTooltip(event, dayKey, value); @@ -898,7 +926,8 @@ } if (enabledTypes.includes('articles')) { - charts.push(...renderArticleHeatmap(container, data.articles)); + charts.push(...renderArticleHeatmap(container, data.articles, + Boolean(data.enableMomentHeatmap))); } if (enabledTypes.includes('comments')) { diff --git a/src/main/resources/static/min/siteCharts.min.js b/src/main/resources/static/min/siteCharts.min.js index f40f878..baefa9c 100644 --- a/src/main/resources/static/min/siteCharts.min.js +++ b/src/main/resources/static/min/siteCharts.min.js @@ -1,4 +1 @@ -!function(){"use strict";var e;let t=["#3b82f6","#10b981","#f59e0b","#ef4444","#8b5cf6","#ec4899","#14b8a6","#f97316","#6366f1","#0ea5e9"],a=new Map;function r(e){let t=Number(e)||0;return t>=1e6?(t/1e6).toFixed(1)+"M":t>=1e3?(t/1e3).toFixed(1)+"K":t.toString()}function o(e,t,a){return t.split(".").reduce((e,t)=>{if(e&&Object.prototype.hasOwnProperty.call(e,t))return e[t]},e)??a}function n(e,t,a){let r=document.createElement("section");r.className="xhhaocom-chartboard-section";let o=document.createElement("header");o.className="xhhaocom-chartboard-section__header",o.innerHTML=` -
${t}
- ${a?`
${a}
`:""} - `,r.appendChild(o);let n=document.createElement("div");return n.className="xhhaocom-chartboard-section__body",r.appendChild(n),e.appendChild(r),n}function l(e,t){let a=document.createElement("div");a.className="xhhaocom-chartboard-card";let r=document.createElement("div");r.className="xhhaocom-chartboard-card__canvas";let o=document.createElement("canvas");if(r.appendChild(o),a.appendChild(r),t){let n=document.createElement("footer");n.className="xhhaocom-chartboard-card__footer",n.textContent=t,a.appendChild(n)}return e.appendChild(a),o}function i(e){const t=e.getFullYear(),a=String(e.getMonth()+1).padStart(2,"0"),r=String(e.getDate()).padStart(2,"0");return`${t}-${a}-${r}`}let d=["rgba(255, 99, 132, 0.22)","rgba(255, 159, 64, 0.22)","rgba(255, 205, 86, 0.22)","rgba(75, 192, 192, 0.22)","rgba(54, 162, 235, 0.22)","rgba(153, 102, 255, 0.22)","rgba(201, 203, 207, 0.22)","rgba(236, 72, 153, 0.22)","rgba(16, 185, 129, 0.22)","rgba(14, 165, 233, 0.22)"],s=["rgb(255, 99, 132)","rgb(255, 159, 64)","rgb(255, 205, 86)","rgb(75, 192, 192)","rgb(54, 162, 235)","rgb(153, 102, 255)","rgb(201, 203, 207)","rgb(236, 72, 153)","rgb(16, 185, 129)","rgb(14, 165, 233)"];function c(e){return Array.from({length:e},(e,t)=>({background:d[t%d.length],border:s[t%s.length]}))}function h(){!function e(t,a=50){if("undefined"!=typeof Chart){t();return}if(a<=0){console.error("[ChartBoard] Chart.js 加载超时");return}setTimeout(()=>e(t,a-1),100)}(()=>{document.querySelectorAll(".xhhaocom-chartboard").forEach(e=>{if(!e.hasAttribute("data-initialized")){var d;e.setAttribute("data-initialized","true"),(d=e).classList.add("xhhaocom-chartboard"),d.innerHTML='
数据加载中…
',fetch("/apis/api.data.statistics.xhhao.com/v1alpha1/chart/data").then(e=>{if(!e.ok)throw Error(`HTTP ${e.status}`);return e.json()}).then(e=>(function e(d,s){(function e(t){let r=a.get(t);r&&(r.forEach(e=>{e?.destroy&&e.destroy()}),a.delete(t))})(d),d.innerHTML="";let h=d.getAttribute("data-types"),m=h?h.split(",").map(e=>e.trim()).filter(Boolean):["tags","categories","articles","comments","topArticles"],p=[];if(m.includes("tags")||m.includes("categories")){let $=m.includes("tags")?s.tags:null,u=m.includes("categories")?s.categories:null;p.push(...function e(a,r,i){let d=n(a,"标签与分类统计","展示全部标签和分类的文章数量占比"),s=[],c=(r||[]).map(e=>({name:e?.name??o(e,"spec.displayName")??o(e,"metadata.name")??"未命名标签",count:Number(e?.count??e?.total??o(e,"status.visiblePostCount",0))})).filter(e=>e.count>0).sort((e,t)=>t.count-e.count),h=(i||[]).map(e=>({name:e?.name??o(e,"spec.displayName")??o(e,"metadata.name")??"未命名分类",count:Number(e?.total??e?.count??o(e,"status.visiblePostCount",0))})).filter(e=>e.count>0).sort((e,t)=>t.count-e.count);if(!c.length&&!h.length)return d.innerHTML='
暂无标签或分类数据
',[];let m=[];if(c.length){let p=(r?.length||0)-c.length,$=p>0?`已使用标签 ${c.length} 个(另有 ${p} 个未使用)`:`已使用标签 ${c.length} 个`,u=l(d,$),g=u.closest(".xhhaocom-chartboard-card");g&&g.classList.add("xhhaocom-chartboard-card--animated");let b=new Chart(u,{type:"doughnut",data:{labels:c.map(e=>e.name),datasets:[{data:c.map(e=>e.count),backgroundColor:c.map((e,a)=>t[a%t.length]),borderWidth:2,borderColor:"#ffffff",cutout:"55%",hoverOffset:8,hoverBorderWidth:3}]},options:{maintainAspectRatio:!1,animation:{animateRotate:!0,animateScale:!0,duration:1200,easing:"easeOutQuart"},interaction:{intersect:!1,mode:"point"},plugins:{legend:{display:!1},tooltip:{enabled:!0,backgroundColor:"rgba(0, 0, 0, 0.8)",padding:12,cornerRadius:8,displayColors:!0,callbacks:{label:e=>`${e.label}: ${e.raw} 篇文章`}}},onHover(e,t){u.style.cursor=t.length>0?"pointer":"default"}}});b.canvas.style.height="220px",b.canvas.style.maxHeight="220px",b.resize(),m.push(b),g&&s.push(g)}if(h.length){let _=(i?.length||0)-h.length,f=_>0?`已使用分类 ${h.length} 个(另有 ${_} 个未使用)`:`已使用分类 ${h.length} 个`,v=l(d,f),x=v.closest(".xhhaocom-chartboard-card");x&&x.classList.add("xhhaocom-chartboard-card--animated");let y=[...h].sort((e,t)=>e.count-t.count),C=new Chart(v,{type:"line",data:{labels:y.map(e=>e.name),datasets:[{label:"文章数量",data:y.map(e=>e.count),borderColor:t[0],backgroundColor:t[0]+"20",borderWidth:3,fill:!0,tension:.4,pointRadius:5,pointHoverRadius:8,pointBackgroundColor:t[0],pointBorderColor:"#ffffff",pointBorderWidth:2,pointHoverBackgroundColor:t[0],pointHoverBorderColor:"#ffffff",pointHoverBorderWidth:3}]},options:{maintainAspectRatio:!1,animation:{duration:1500,easing:"easeOutQuart"},interaction:{intersect:!1,mode:"index"},scales:{x:{beginAtZero:!1,grid:{display:!1},ticks:{font:{size:11},maxRotation:45,minRotation:0}},y:{beginAtZero:!0,grid:{color:"rgba(0, 0, 0, 0.05)",drawBorder:!1},ticks:{font:{size:11},callback:e=>Number(e)}}},plugins:{legend:{display:!1},tooltip:{enabled:!0,backgroundColor:"rgba(0, 0, 0, 0.8)",padding:12,cornerRadius:8,displayColors:!0,callbacks:{label:e=>`${e.label}: ${e.raw} 篇文章`}}},onHover(e,t){v.style.cursor=t.length>0?"pointer":"default"}}});m.push(C),x&&s.push(x)}return 1===s.length&&(s[0].style.gridColumn="span 2"),m}(d,$,u))}m.includes("articles")&&p.push(...function e(t,a){let r=n(t,"文章发布趋势","按日期统计文章发布数量"),o=new Map;if((a||[]).forEach(e=>{let t=e.date||e.name;if(!t)return;let a=new Date(t);if(Number.isNaN(a.valueOf()))return;a.setHours(0,0,0,0);let r=i(a),n=Number(e.total??e.count??0);o.set(r,(o.get(r)||0)+n)}),!o.size)return r.innerHTML='
暂无文章数据
',[];let l=new Date;l.setHours(0,0,0,0);let d=new Date(l),s=new Date(d.getTime()-314496e5),c=new Date(s),h=(c.getDay()+6)%7;c.setDate(c.getDate()-h);let m=Math.ceil((Math.floor((d-c)/864e5)+1)/7),p=Array.from({length:m},(e,t)=>new Date(c.getTime()+7*t*864e5)),$=Math.max(...o.values(),0),u=document.createElement("div");u.className="xhhaocom-chartboard-card xhhaocom-chartboard-card--heatmap",u.style.gridColumn="1 / -1";let g=document.createElement("div");g.className="xhhaocom-chartboard-heatmap";let b=document.createElement("div");b.className="xhhaocom-chartboard-heatmap__tooltip",b.style.display="none",u.appendChild(b);let _=document.createElement("div");_.className="xhhaocom-chartboard-heatmap__months";let f=document.createElement("div");f.className="xhhaocom-chartboard-heatmap__weekdays",["一","二","三","四","五","六","日"].forEach(e=>{let t=document.createElement("div");t.className="xhhaocom-chartboard-heatmap__weekday",t.textContent=e,f.appendChild(t)});let v=document.createElement("div");v.className="xhhaocom-chartboard-heatmap__grid";let x=()=>{let e=u.getBoundingClientRect();if(0===e.width){requestAnimationFrame(x);return}let t=window.innerWidth<=768;if(t){let a="12px";_.style.gridTemplateColumns=`repeat(${m}, ${a})`,v.style.gridTemplateColumns=`repeat(${m}, ${a})`,document.documentElement.style.setProperty("--chartboard-heatmap-cell",a),document.documentElement.style.setProperty("--chartboard-heatmap-cell-width",a);return}let r=e.width-40-30-10,o=`${Math.max(8,Math.floor((r-(m-1)*4)/m))}px`;_.style.gridTemplateColumns=`repeat(${m}, ${o})`,v.style.gridTemplateColumns=`repeat(${m}, ${o})`,document.documentElement.style.setProperty("--chartboard-heatmap-cell",o),document.documentElement.style.setProperty("--chartboard-heatmap-cell-width",o)},y=new ResizeObserver(()=>{x()}),C,w=()=>{clearTimeout(C),C=setTimeout(()=>{x()},150)},E=e=>{if(!e||!$)return 0;if($<=1)return e>0?1:0;let t=Math.max(1,Math.ceil(.25*$)),a=Math.max(t+1,Math.ceil(.5*$));return e>=Math.max(a+1,Math.ceil(.75*$))?4:e>=a?3:e>=t?2:1},L=(e,t,a)=>{b.innerHTML=`${t}${a?`发布 ${a} 篇文章`:"无文章发布"}`,b.style.display="flex";let r=u.getBoundingClientRect(),o=b.getBoundingClientRect(),n=e.clientX-r.left+12,l=e.clientY-r.top-o.height-10;n+o.width>r.width&&(n=r.width-o.width-8),l<0&&(l=e.clientY-r.top+12),b.style.transform=`translate(${Math.round(n)}px, ${Math.round(l)}px)`},k=()=>{b.style.display="none",b.style.transform="translate(-9999px, -9999px)"},N=["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],H=[];{let T=new Date(s.getFullYear(),s.getMonth(),1),R=new Date(d.getFullYear(),d.getMonth(),1);for(;T<=R;){let M=new Date(T),B=new Date(T.getFullYear(),T.getMonth()+1,0),A=Md?new Date(d):B,z=Math.floor((A-c)/864e5),S=Math.floor((P-c)/864e5),O=Math.max(0,Math.min(m-1,Math.floor(z/7))),W=Math.max(O+1,Math.min(m,Math.floor(S/7)+1));H.push({label:N[T.getMonth()],start:O,end:W}),T.setMonth(T.getMonth()+1)}}if(H.length){let D=[],F=0;H.forEach(e=>{let t=Math.max(F,e.start),a=Math.max(t+1,e.end);t=Math.min(t,m-1),a=Math.min(a,m),!(t>=m)&&(D.push({label:e.label,start:t,end:a}),F=a)}),H=D}p.forEach((e,t)=>{let a=document.createElement("div");a.className="xhhaocom-chartboard-heatmap__column";for(let r=0;r<7;r++){let n=new Date(e.getTime()+864e5*r),l=document.createElement("div");l.className="xhhaocom-chartboard-heatmap__day";let c=i(n),h=n>=s&&n<=d;if(h){let m=o.get(c)||0,p=E(m);l.dataset.level=p.toString(),l.dataset.value=m.toString(),l.dataset.date=c;let $=e=>L(e,c,m);l.addEventListener("mouseenter",$),l.addEventListener("mousemove",$),l.addEventListener("mouseleave",k)}else l.classList.add("is-outside");a.appendChild(l)}v.appendChild(a)}),u.addEventListener("mouseleave",k);let Y=0;if(H.forEach(e=>{if(e.start>Y){let t=document.createElement("div");t.className="xhhaocom-chartboard-heatmap__month is-placeholder",t.style.gridColumn=`span ${e.start-Y}`,_.appendChild(t)}let a=Math.max(1,e.end-e.start),r=document.createElement("div");r.className="xhhaocom-chartboard-heatmap__month",r.textContent=e.label,r.style.gridColumn=`span ${a}`,_.appendChild(r),Y=e.end}),Y{let t=document.createElement("span");t.className="xhhaocom-chartboard-heatmap__legend-dot",t.dataset.level=e.toString(),q.appendChild(t)});let K=document.createElement("span");return K.textContent="较多",q.appendChild(K),Z.appendChild(q),g.appendChild(Z),u.appendChild(g),r.appendChild(u),y.observe(u),window.addEventListener("resize",w),window.addEventListener("orientationchange",w),x(),[{type:"heatmap"}]}(d,s.articles)),m.includes("comments")&&p.push(...function e(t,a){let r=n(t,"评论活跃用户","按评论作者统计评论数量");if(!a?.length)return r.innerHTML='
暂无评论数据
',[];let o=a.map(e=>({name:e?.username||e?.name||e?.email||"匿名",count:Number(e?.count??0)})).filter(e=>e.count>0).sort((e,t)=>t.count-e.count).slice(0,10);if(!o.length)return r.innerHTML='
暂无评论数据
',[];let i=l(r,`活跃评论用户 Top ${o.length}`),d=i.closest(".xhhaocom-chartboard-card");d&&d.classList.add("xhhaocom-chartboard-card--animated");let s=c(o.length),h=new Chart(i,{type:"bar",data:{labels:o.map(e=>e.name),datasets:[{label:"评论数量",data:o.map(e=>e.count),backgroundColor:s.map(e=>e.background),borderColor:s.map(e=>e.border),borderWidth:1.5,borderRadius:{topLeft:14,topRight:14,bottomLeft:14,bottomRight:14},barPercentage:.65,categoryPercentage:.6}]},options:{maintainAspectRatio:!1,animation:{duration:1400,easing:"easeOutQuart"},interaction:{mode:"index",intersect:!1},scales:{y:{beginAtZero:!0,grid:{color:"rgba(148, 163, 184, 0.18)",drawBorder:!1,borderDash:[4,4]},ticks:{precision:0,font:{size:12}}},x:{grid:{drawBorder:!1},ticks:{font:{size:12},autoSkip:!1}}},plugins:{legend:{display:!1},tooltip:{backgroundColor:"rgba(15, 23, 42, 0.88)",cornerRadius:8,padding:12,displayColors:!1,callbacks:{title:e=>e[0]?.label||"",label:e=>`评论 ${e.raw} 次`}}},onHover(e,t){i.style.cursor=t.length?"pointer":"default"}}});return[h]}(d,s.comments)),m.includes("topArticles")&&p.push(...function e(t,a){let o=n(t,"热门文章 Top10","按访问量排序的热门文章");if(!a?.length)return o.innerHTML='
暂无热门文章数据
',[];let i=a.map(e=>({name:e.name||"未命名文章",views:Number(e.views??e.count??0)})).filter(e=>e.views>0).sort((e,t)=>t.views-e.views).slice(0,10);if(!i.length)return o.innerHTML='
暂无热门文章数据
',[];let d=l(o,`热门文章 Top ${i.length}`),s=d.closest(".xhhaocom-chartboard-card");s&&s.classList.add("xhhaocom-chartboard-card--animated");let h=c(i.length),m=window.innerWidth<=768,p=new Chart(d,{type:"bar",data:{labels:i.map(e=>e.name.length>16?e.name.slice(0,16)+"…":e.name),datasets:[{label:"访问量",data:i.map(e=>e.views),backgroundColor:h.map(e=>e.background),borderColor:h.map(e=>e.border),borderWidth:1.5,borderRadius:{topLeft:14,topRight:14,bottomLeft:14,bottomRight:14},barPercentage:.65,categoryPercentage:.6}]},options:{maintainAspectRatio:!1,animation:{duration:1500,easing:"easeOutQuart"},interaction:{mode:"index",intersect:!1},scales:{y:{beginAtZero:!0,grid:{color:"rgba(148, 163, 184, 0.18)",drawBorder:!1,borderDash:[4,4]},ticks:{callback:e=>r(e),font:{size:12}}},x:{grid:{drawBorder:!1},ticks:{display:!m,font:{size:12},autoSkip:!1}}},plugins:{legend:{display:!1},tooltip:{backgroundColor:"rgba(15, 23, 42, 0.88)",cornerRadius:8,padding:12,displayColors:!1,callbacks:{title:e=>e[0]?.label||"",label:e=>`访问量 ${r(e.raw)}`}}},onHover(e,t){d.style.cursor=t.length?"pointer":"default"}}});return[p]}(d,s.top10Articles));let g=p.filter(e=>e&&"function"==typeof e.destroy);g.length>0&&a.set(d,g),0===d.children.length&&(d.innerHTML='
暂无可展示的数据
')})(d,e||{})).catch(e=>{console.error("[ChartBoard] fetch error:",e),d.innerHTML=`
获取图表数据失败:${e.message}
`})}})})}if(e=h,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",e):e(),"undefined"!=typeof MutationObserver){let m=new MutationObserver(()=>{h()});m.observe(document.body,{childList:!0,subtree:!0})}}(); +!function(){"use strict";const t=["#3b82f6","#10b981","#f59e0b","#ef4444","#8b5cf6","#ec4899","#14b8a6","#f97316","#6366f1","#0ea5e9"],e=864e5,a=new Map;function o(t){const e=Number(t)||0;return e>=1e6?(e/1e6).toFixed(1)+"M":e>=1e3?(e/1e3).toFixed(1)+"K":e.toString()}function n(t,e,a=void 0){return e.split(".").reduce((t,e)=>{if(t&&Object.prototype.hasOwnProperty.call(t,e))return t[e]},t)??a}function r(t,e,a){const o=document.createElement("section");o.className="xhhaocom-chartboard-section";const n=document.createElement("header");n.className="xhhaocom-chartboard-section__header",n.innerHTML=`\n
${e}
\n ${a?`
${a}
`:""}\n `,o.appendChild(n);const r=document.createElement("div");return r.className="xhhaocom-chartboard-section__body",o.appendChild(r),t.appendChild(o),r}function c(t,e){const a=document.createElement("div");a.className="xhhaocom-chartboard-card";const o=document.createElement("div");o.className="xhhaocom-chartboard-card__canvas";const n=document.createElement("canvas");if(o.appendChild(n),a.appendChild(o),e){const t=document.createElement("footer");t.className="xhhaocom-chartboard-card__footer",t.textContent=e,a.appendChild(t)}return t.appendChild(a),n}function s(t){return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}`}const i=["rgba(255, 99, 132, 0.22)","rgba(255, 159, 64, 0.22)","rgba(255, 205, 86, 0.22)","rgba(75, 192, 192, 0.22)","rgba(54, 162, 235, 0.22)","rgba(153, 102, 255, 0.22)","rgba(201, 203, 207, 0.22)","rgba(236, 72, 153, 0.22)","rgba(16, 185, 129, 0.22)","rgba(14, 165, 233, 0.22)"],d=["rgb(255, 99, 132)","rgb(255, 159, 64)","rgb(255, 205, 86)","rgb(75, 192, 192)","rgb(54, 162, 235)","rgb(153, 102, 255)","rgb(201, 203, 207)","rgb(236, 72, 153)","rgb(16, 185, 129)","rgb(14, 165, 233)"];function l(t){return Array.from({length:t},(t,e)=>({background:i[e%i.length],border:d[e%d.length]}))}function h(i,d){!function(t){const e=a.get(t);e&&(e.forEach(t=>{t?.destroy&&t.destroy()}),a.delete(t))}(i),i.innerHTML="";const h=i.getAttribute("data-types"),m=h?h.split(",").map(t=>t.trim()).filter(Boolean):["tags","categories","articles","comments","topArticles"],p=[];if(m.includes("tags")||m.includes("categories")){const e=m.includes("tags")?d.tags:null,a=m.includes("categories")?d.categories:null;p.push(...function(e,a,o){const s=r(e,"标签与分类统计","展示全部标签和分类的文章数量占比"),i=[],d=(a||[]).map(t=>({name:t?.name??n(t,"spec.displayName")??n(t,"metadata.name")??"未命名标签",count:Number(t?.count??t?.total??n(t,"status.visiblePostCount",0))})).filter(t=>t.count>0).sort((t,e)=>e.count-t.count),l=(o||[]).map(t=>({name:t?.name??n(t,"spec.displayName")??n(t,"metadata.name")??"未命名分类",count:Number(t?.total??t?.count??n(t,"status.visiblePostCount",0))})).filter(t=>t.count>0).sort((t,e)=>e.count-t.count);if(!d.length&&!l.length)return s.innerHTML='
暂无标签或分类数据
',[];const h=[];if(d.length){const e=(a?.length||0)-d.length,o=c(s,e>0?`已使用标签 ${d.length} 个(另有 ${e} 个未使用)`:`已使用标签 ${d.length} 个`),n=o.closest(".xhhaocom-chartboard-card");n&&n.classList.add("xhhaocom-chartboard-card--animated");const r=new Chart(o,{type:"doughnut",data:{labels:d.map(t=>t.name),datasets:[{data:d.map(t=>t.count),backgroundColor:d.map((e,a)=>t[a%t.length]),borderWidth:2,borderColor:"#ffffff",cutout:"55%",hoverOffset:8,hoverBorderWidth:3}]},options:{maintainAspectRatio:!1,animation:{animateRotate:!0,animateScale:!0,duration:1200,easing:"easeOutQuart"},interaction:{intersect:!1,mode:"point"},plugins:{legend:{display:!1},tooltip:{enabled:!0,backgroundColor:"rgba(0, 0, 0, 0.8)",padding:12,cornerRadius:8,displayColors:!0,callbacks:{label:t=>`${t.label}: ${t.raw} 篇文章`}}},onHover:(t,e)=>{o.style.cursor=e.length>0?"pointer":"default"}}});r.canvas.style.height="220px",r.canvas.style.maxHeight="220px",r.resize(),h.push(r),n&&i.push(n)}if(l.length){const e=(o?.length||0)-l.length,a=c(s,e>0?`已使用分类 ${l.length} 个(另有 ${e} 个未使用)`:`已使用分类 ${l.length} 个`),n=a.closest(".xhhaocom-chartboard-card");n&&n.classList.add("xhhaocom-chartboard-card--animated");const r=[...l].sort((t,e)=>t.count-e.count),d=new Chart(a,{type:"line",data:{labels:r.map(t=>t.name),datasets:[{label:"文章数量",data:r.map(t=>t.count),borderColor:t[0],backgroundColor:t[0]+"20",borderWidth:3,fill:!0,tension:.4,pointRadius:5,pointHoverRadius:8,pointBackgroundColor:t[0],pointBorderColor:"#ffffff",pointBorderWidth:2,pointHoverBackgroundColor:t[0],pointHoverBorderColor:"#ffffff",pointHoverBorderWidth:3}]},options:{maintainAspectRatio:!1,animation:{duration:1500,easing:"easeOutQuart"},interaction:{intersect:!1,mode:"index"},scales:{x:{beginAtZero:!1,grid:{display:!1},ticks:{font:{size:11},maxRotation:45,minRotation:0}},y:{beginAtZero:!0,grid:{color:"rgba(0, 0, 0, 0.05)",drawBorder:!1},ticks:{font:{size:11},callback:t=>Number(t)}}},plugins:{legend:{display:!1},tooltip:{enabled:!0,backgroundColor:"rgba(0, 0, 0, 0.8)",padding:12,cornerRadius:8,displayColors:!0,callbacks:{label:t=>`${t.label}: ${t.raw} 篇文章`}}},onHover:(t,e)=>{a.style.cursor=e.length>0?"pointer":"default"}}});h.push(d),n&&i.push(n)}return 1===i.length&&(i[0].style.gridColumn="span 2"),h}(i,e,a))}m.includes("articles")&&p.push(...function(t,a,o=!1){const n=r(t,"文章发布趋势","按日期统计文章发布数量"),c=new Map;if((a||[]).forEach(t=>{const e=t.date||t.name;if(!e)return;const a=new Date(e);if(Number.isNaN(a.valueOf()))return;a.setHours(0,0,0,0);const o=s(a),n=Number(t.articleTotal??t.total??t.count??0),r=Number(t.momentTotal??0),i=Number(t.total??0),d=Number.isFinite(i)?i:n+r,l=c.get(o)||{total:0,articleTotal:0,momentTotal:0};c.set(o,{total:l.total+d,articleTotal:l.articleTotal+n,momentTotal:l.momentTotal+r})}),!c.size)return n.innerHTML='
暂无文章数据
',[];const i=new Date;i.setHours(0,0,0,0);const d=new Date(i),l=new Date(d.getTime()-314496e5),h=new Date(l),m=(h.getDay()+6)%7;h.setDate(h.getDate()-m);const p=Math.floor((d-h)/e)+1,u=Math.ceil(p/7),g=Array.from({length:u},(t,a)=>new Date(h.getTime()+7*a*e)),b=Math.max(...[...c.values()].map(t=>t.total),0),f=document.createElement("div");f.className="xhhaocom-chartboard-card xhhaocom-chartboard-card--heatmap",f.style.gridColumn="1 / -1";const v=document.createElement("div");v.className="xhhaocom-chartboard-heatmap";const x=document.createElement("div");x.className="xhhaocom-chartboard-heatmap__tooltip",x.style.display="none",f.appendChild(x);const y=document.createElement("div");y.className="xhhaocom-chartboard-heatmap__months";const C=document.createElement("div");C.className="xhhaocom-chartboard-heatmap__weekdays",["一","二","三","四","五","六","日"].forEach(t=>{const e=document.createElement("div");e.className="xhhaocom-chartboard-heatmap__weekday",e.textContent=t,C.appendChild(e)});const w=document.createElement("div");w.className="xhhaocom-chartboard-heatmap__grid";const M=()=>{const t=f.getBoundingClientRect();if(0===t.width)return void requestAnimationFrame(M);if(window.innerWidth<=768){const t="12px";return y.style.gridTemplateColumns=`repeat(${u}, ${t})`,w.style.gridTemplateColumns=`repeat(${u}, ${t})`,document.documentElement.style.setProperty("--chartboard-heatmap-cell",t),void document.documentElement.style.setProperty("--chartboard-heatmap-cell-width",t)}const e=t.width-40-30-10,a=`${Math.max(8,Math.floor((e-4*(u-1))/u))}px`;y.style.gridTemplateColumns=`repeat(${u}, ${a})`,w.style.gridTemplateColumns=`repeat(${u}, ${a})`,document.documentElement.style.setProperty("--chartboard-heatmap-cell",a),document.documentElement.style.setProperty("--chartboard-heatmap-cell-width",a)},E=new ResizeObserver(()=>{M()});let $;const _=()=>{clearTimeout($),$=setTimeout(()=>{M()},150)},T=t=>{if(!t||!b)return 0;if(b<=1)return t>0?1:0;const e=Math.max(1,Math.ceil(.25*b)),a=Math.max(e+1,Math.ceil(.5*b));return t>=Math.max(a+1,Math.ceil(.75*b))?4:t>=a?3:t>=e?2:1},N=(t,e,a)=>{const n=a?.articleTotal??0,r=a?.momentTotal??0,c=[`${e}`];o?(n>0&&c.push(`已发布${n}篇文章`),r>0&&c.push(`已发布${r}条瞬间`),0===n&&0===r&&c.push("当日无内容发布")):n>0?c.push(`已发布${n}篇文章`):c.push("当日无文章发布"),x.innerHTML=c.join(""),x.style.display="flex";const s=f.getBoundingClientRect(),i=x.getBoundingClientRect();let d=t.clientX-s.left+12,l=t.clientY-s.top-i.height-10;d+i.width>s.width&&(d=s.width-i.width-8),l<0&&(l=t.clientY-s.top+12),x.style.transform=`translate(${Math.round(d)}px, ${Math.round(l)}px)`},L=()=>{x.style.display="none",x.style.transform="translate(-9999px, -9999px)"},k=["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"];let H=[];{const t=new Date(l.getFullYear(),l.getMonth(),1),a=new Date(d.getFullYear(),d.getMonth(),1);for(;t<=a;){const a=new Date(t),o=new Date(t.getFullYear(),t.getMonth()+1,0),n=ad?new Date(d):o,c=Math.floor((n-h)/e),s=Math.floor((r-h)/e),i=Math.max(0,Math.min(u-1,Math.floor(c/7))),m=Math.max(i+1,Math.min(u,Math.floor(s/7)+1));H.push({label:k[t.getMonth()],start:i,end:m}),t.setMonth(t.getMonth()+1)}}if(H.length){const t=[];let e=0;H.forEach(a=>{let o=Math.max(e,a.start),n=Math.max(o+1,a.end);o=Math.min(o,u-1),n=Math.min(n,u),o>=u||(t.push({label:a.label,start:o,end:n}),e=n)}),H=t}g.forEach((t,a)=>{const o=document.createElement("div");o.className="xhhaocom-chartboard-heatmap__column";for(let a=0;a<7;a++){const n=new Date(t.getTime()+a*e),r=document.createElement("div");r.className="xhhaocom-chartboard-heatmap__day";const i=s(n);if(n>=l&&n<=d){const t=c.get(i)||{total:0,articleTotal:0,momentTotal:0},e=T(t.total);r.dataset.level=e.toString(),r.dataset.value=t.total.toString(),r.dataset.date=i;const a=e=>N(e,i,t);r.addEventListener("mouseenter",a),r.addEventListener("mousemove",a),r.addEventListener("mouseleave",L)}else r.classList.add("is-outside");o.appendChild(r)}w.appendChild(o)}),f.addEventListener("mouseleave",L);let R=0;if(H.forEach(t=>{if(t.start>R){const e=document.createElement("div");e.className="xhhaocom-chartboard-heatmap__month is-placeholder",e.style.gridColumn="span "+(t.start-R),y.appendChild(e)}const e=Math.max(1,t.end-t.start),a=document.createElement("div");a.className="xhhaocom-chartboard-heatmap__month",a.textContent=t.label,a.style.gridColumn=`span ${e}`,y.appendChild(a),R=t.end}),R{const e=document.createElement("span");e.className="xhhaocom-chartboard-heatmap__legend-dot",e.dataset.level=t.toString(),A.appendChild(e)});const z=document.createElement("span");return z.textContent="较多",A.appendChild(z),D.appendChild(A),v.appendChild(D),f.appendChild(v),n.appendChild(f),E.observe(f),window.addEventListener("resize",_),window.addEventListener("orientationchange",_),M(),[{type:"heatmap"}]}(i,d.articles,Boolean(d.enableMomentHeatmap))),m.includes("comments")&&p.push(...function(t,e){const a=r(t,"评论活跃用户","按评论作者统计评论数量");if(!e?.length)return a.innerHTML='
暂无评论数据
',[];const o=e.map(t=>({name:t?.username||t?.name||t?.email||"匿名",count:Number(t?.count??0)})).filter(t=>t.count>0).sort((t,e)=>e.count-t.count).slice(0,10);if(!o.length)return a.innerHTML='
暂无评论数据
',[];const n=c(a,`活跃评论用户 Top ${o.length}`),s=n.closest(".xhhaocom-chartboard-card");s&&s.classList.add("xhhaocom-chartboard-card--animated");const i=l(o.length);return[new Chart(n,{type:"bar",data:{labels:o.map(t=>t.name),datasets:[{label:"评论数量",data:o.map(t=>t.count),backgroundColor:i.map(t=>t.background),borderColor:i.map(t=>t.border),borderWidth:1.5,borderRadius:{topLeft:14,topRight:14,bottomLeft:14,bottomRight:14},barPercentage:.65,categoryPercentage:.6}]},options:{maintainAspectRatio:!1,animation:{duration:1400,easing:"easeOutQuart"},interaction:{mode:"index",intersect:!1},scales:{y:{beginAtZero:!0,grid:{color:"rgba(148, 163, 184, 0.18)",drawBorder:!1,borderDash:[4,4]},ticks:{precision:0,font:{size:12}}},x:{grid:{drawBorder:!1},ticks:{font:{size:12},autoSkip:!1}}},plugins:{legend:{display:!1},tooltip:{backgroundColor:"rgba(15, 23, 42, 0.88)",cornerRadius:8,padding:12,displayColors:!1,callbacks:{title:t=>t[0]?.label||"",label:t=>`评论 ${t.raw} 次`}}},onHover:(t,e)=>{n.style.cursor=e.length?"pointer":"default"}}})]}(i,d.comments)),m.includes("topArticles")&&p.push(...function(t,e){const a=r(t,"热门文章 Top10","按访问量排序的热门文章");if(!e?.length)return a.innerHTML='
暂无热门文章数据
',[];const n=e.map(t=>({name:t.name||"未命名文章",views:Number(t.views??t.count??0)})).filter(t=>t.views>0).sort((t,e)=>e.views-t.views).slice(0,10);if(!n.length)return a.innerHTML='
暂无热门文章数据
',[];const s=c(a,`热门文章 Top ${n.length}`),i=s.closest(".xhhaocom-chartboard-card");i&&i.classList.add("xhhaocom-chartboard-card--animated");const d=l(n.length),h=window.innerWidth<=768;return[new Chart(s,{type:"bar",data:{labels:n.map(t=>t.name.length>16?t.name.slice(0,16)+"…":t.name),datasets:[{label:"访问量",data:n.map(t=>t.views),backgroundColor:d.map(t=>t.background),borderColor:d.map(t=>t.border),borderWidth:1.5,borderRadius:{topLeft:14,topRight:14,bottomLeft:14,bottomRight:14},barPercentage:.65,categoryPercentage:.6}]},options:{maintainAspectRatio:!1,animation:{duration:1500,easing:"easeOutQuart"},interaction:{mode:"index",intersect:!1},scales:{y:{beginAtZero:!0,grid:{color:"rgba(148, 163, 184, 0.18)",drawBorder:!1,borderDash:[4,4]},ticks:{callback:t=>o(t),font:{size:12}}},x:{grid:{drawBorder:!1},ticks:{display:!h,font:{size:12},autoSkip:!1}}},plugins:{legend:{display:!1},tooltip:{backgroundColor:"rgba(15, 23, 42, 0.88)",cornerRadius:8,padding:12,displayColors:!1,callbacks:{title:t=>t[0]?.label||"",label:t=>`访问量 ${o(t.raw)}`}}},onHover:(t,e)=>{s.style.cursor=e.length?"pointer":"default"}}})]}(i,d.top10Articles));const u=p.filter(t=>t&&"function"==typeof t.destroy);u.length>0&&a.set(i,u),0===i.children.length&&(i.innerHTML='
暂无可展示的数据
')}function m(t,e=50){"undefined"==typeof Chart?e<=0?console.error("[ChartBoard] Chart.js 加载超时"):setTimeout(()=>m(t,e-1),100):t()}function p(){m(()=>{document.querySelectorAll(".xhhaocom-chartboard").forEach(t=>{t.hasAttribute("data-initialized")||(t.setAttribute("data-initialized","true"),function(t){t.classList.add("xhhaocom-chartboard"),t.innerHTML='
数据加载中…
',fetch("/apis/api.data.statistics.xhhao.com/v1alpha1/chart/data").then(t=>{if(!t.ok)throw new Error(`HTTP ${t.status}`);return t.json()}).then(e=>h(t,e||{})).catch(e=>{console.error("[ChartBoard] fetch error:",e),t.innerHTML=`
获取图表数据失败:${e.message}
`})}(t))})})}var u;if(u=p,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",u):u(),"undefined"!=typeof MutationObserver){new MutationObserver(()=>{p()}).observe(document.body,{childList:!0,subtree:!0})}}(); \ No newline at end of file