[v4.5.0] 添加新的模板主题 retro_futurism, 灵感来源明日方舟 孤星 未来复古主义主题;添加模板调试工具 scripts/debug_render.py,方便用户在不启动 AstrBot 的情况下调试模板效果 (@Saramanda9988)

This commit is contained in:
SXP-Simon
2025-12-31 14:44:23 +08:00
parent 7583a906bb
commit d339f1b7c0
4 changed files with 4 additions and 563 deletions
+3
View File
@@ -108,6 +108,9 @@ simple 模板效果图(用于展示贡献指南)
<details> <details>
<summary>📋 点击展开查看完整更新日志</summary> <summary>📋 点击展开查看完整更新日志</summary>
### v4.5.0
- 添加新的模板主题 retro_futurism, 灵感来源明日方舟 孤星 未来复古主义主题;添加模板调试工具 scripts/debug_render.py,方便用户在不启动 AstrBot 的情况下调试模板效果 (@Saramanda9988
### v4.4.0 ### v4.4.0
- 修复可能的图片生成成功但是发送失败的情况,增加 Fallback 的 BASE64 编码的图片发送方式(@Heximiao - 修复可能的图片生成成功但是发送失败的情况,增加 Fallback 的 BASE64 编码的图片发送方式(@Heximiao
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

+1 -1
View File
@@ -14,6 +14,6 @@ help: | # 插件的帮助信息
命令: 命令:
/群分析 [天数] - 分析群聊活动 /群分析 [天数] - 分析群聊活动
/分析设置 [操作] - 管理设置(enable/disable/status/test /分析设置 [操作] - 管理设置(enable/disable/status/test
version: v4.4.0 # 插件版本号。格式:v1.1.1 或者 v1.1 version: v4.5.0 # 插件版本号。格式:v1.1.1 或者 v1.1
author: SXP-Simon # 作者 author: SXP-Simon # 作者
repo: https://github.com/SXP-Simon/astrbot-qq-group-daily-analysis # 插件的仓库地址 repo: https://github.com/SXP-Simon/astrbot-qq-group-daily-analysis # 插件的仓库地址
@@ -1,562 +0,0 @@
<!--
@name: 复古未来主义报告
@description: 结合复古工业与未来终端风格的统计报告
@author: LunaRain_079
@version: 1.0.0
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LUNABOX TERMINAL REPORT</title>
<!-- Chart.js CDN -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<!-- Fonts - Using system fonts as fallback for export compatibility -->
<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=JetBrains+Mono:wght@400;700&family=Oswald:wght@400;700&family=Noto+Sans+SC:wght@400;700&display=swap" rel="stylesheet">
<style>
/* Embedded font fallbacks for export */
@font-face {
font-family: 'Oswald-Fallback';
src: local('Impact'), local('Arial Black'), local('Helvetica Neue Bold');
font-weight: 400 700;
}
@font-face {
font-family: 'JetBrains-Fallback';
src: local('Consolas'), local('Monaco'), local('Courier New');
font-weight: 400 700;
}
@font-face {
font-family: 'NotoSans-Fallback';
src: local('Microsoft YaHei'), local('PingFang SC'), local('Hiragino Sans GB'), local('SimHei');
font-weight: 400 700;
}
:root {
/* Palette */
--c-bg: #F5EDDC;
--c-text: #181818;
--c-accent: #B2653B; /* Orange */
--c-dec-1: #5C7F71; /* Green */
--c-dec-2: #802520; /* Red */
--c-dec-3: #BA8530; /* Gold */
--c-grid: rgba(24, 24, 24, 0.1);
/* Fonts - with system fallbacks for export */
--font-display: 'Oswald', 'Oswald-Fallback', Impact, 'Arial Black', sans-serif;
--font-mono: 'JetBrains Mono', 'JetBrains-Fallback', Consolas, Monaco, 'Courier New', monospace;
--font-body: 'Noto Sans SC', 'NotoSans-Fallback', 'Microsoft YaHei', 'PingFang SC', sans-serif;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
background-color: var(--c-bg);
color: var(--c-text);
font-family: var(--font-body);
/* Grid Background */
background-image:
linear-gradient(var(--c-grid) 1px, transparent 1px),
linear-gradient(90deg, var(--c-grid) 1px, transparent 1px);
background-size: 40px 40px;
min-height: 100vh;
overflow-x: hidden;
}
/* Layout Container */
.layout {
max-width: 1200px;
margin: 0 auto;
padding: 60px 40px;
position: relative;
border-left: 1px solid var(--c-text);
border-right: 1px solid var(--c-text);
background:
linear-gradient(90deg, transparent 0%, transparent 98%, var(--c-accent) 98%, var(--c-accent) 100%);
background-size: 100% 100%;
min-height: 100vh;
}
/* Decorative Color Bar */
.color-bar {
position: absolute;
top: 60px;
left: -20px;
display: flex;
flex-direction: column;
gap: 0;
z-index: 10;
}
.c-block {
width: 60px;
height: 12px;
}
.cb-1 { background: var(--c-dec-1); }
.cb-2 { background: var(--c-dec-2); }
.cb-3 { background: var(--c-dec-3); }
/* Header */
header {
margin-bottom: 80px;
position: relative;
}
.header-meta {
font-family: var(--font-mono);
font-size: 0.8rem;
color: var(--c-text);
opacity: 0.7;
margin-bottom: 10px;
letter-spacing: 1px;
text-transform: uppercase;
}
h1 {
font-family: var(--font-display);
font-size: 5rem;
line-height: 0.9;
margin: 0;
text-transform: uppercase;
letter-spacing: -2px;
color: var(--c-text);
}
h1 .highlight {
color: var(--c-accent);
}
.date-range {
font-family: var(--font-mono);
font-size: 1.2rem;
margin-top: 20px;
padding-left: 20px;
border-left: 4px solid var(--c-accent);
display: inline-block;
}
/* Grid System */
.grid-container {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 20px;
margin-bottom: 60px;
}
/* Section Titles */
.section-label {
grid-column: span 12;
font-family: var(--font-display);
font-size: 2rem;
text-transform: uppercase;
border-bottom: 2px solid var(--c-text);
padding-bottom: 10px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.section-label span {
background: var(--c-text);
color: var(--c-bg);
padding: 2px 10px;
font-size: 1rem;
font-family: var(--font-mono);
vertical-align: middle;
}
/* Stats Big Number */
.stat-box {
grid-column: span 4;
border: 1px solid var(--c-text);
padding: 20px;
position: relative;
background: rgba(255, 255, 255, 0.5);
}
.stat-box::before {
content: '';
position: absolute;
top: 4px; left: 4px; right: 4px; bottom: 4px;
border: 1px solid var(--c-grid);
pointer-events: none;
}
.stat-label {
font-family: var(--font-mono);
font-size: 0.9rem;
text-transform: uppercase;
color: var(--c-dec-1);
margin-bottom: 10px;
display: block;
}
.stat-value {
font-family: var(--font-display);
font-size: 4rem;
color: var(--c-accent);
line-height: 1;
}
/* AI Summary - Terminal Style */
.ai-terminal {
grid-column: span 8;
background: var(--c-text);
color: var(--c-bg);
padding: 20px;
font-family: var(--font-mono);
font-size: 0.9rem;
line-height: 1.6;
position: relative;
overflow: hidden;
}
.ai-terminal::before {
content: "AI_ANALYSIS_MODULE // v.2.0";
display: block;
border-bottom: 1px solid #444;
padding-bottom: 10px;
margin-bottom: 15px;
color: var(--c-accent);
font-size: 0.7rem;
}
.cursor {
display: inline-block;
width: 8px;
height: 14px;
background: var(--c-accent);
animation: blink 1s infinite;
}
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
/* Charts */
.chart-wrapper {
grid-column: span 12;
border: 1px solid var(--c-text);
padding: 20px;
background: #fff;
position: relative;
}
.chart-wrapper.half {
grid-column: span 6;
}
.chart-deco {
position: absolute;
top: -10px;
right: 20px;
background: var(--c-bg);
padding: 0 10px;
font-family: var(--font-mono);
font-size: 0.8rem;
border: 1px solid var(--c-text);
}
/* Leaderboard - Asymmetrical List */
.game-list {
grid-column: span 12;
display: flex;
flex-direction: column;
gap: 15px;
}
.game-item {
display: grid;
grid-template-columns: 60px 80px 1fr 150px;
align-items: center;
gap: 20px;
border-bottom: 1px solid var(--c-grid);
padding-bottom: 10px;
}
.game-cover {
width: 80px;
height: 106px; /* 3:4 aspect ratio */
background: #000;
border: 1px solid var(--c-text);
overflow: hidden;
position: relative;
}
.game-cover img {
width: 100%;
height: 100%;
object-fit: cover;
filter: grayscale(100%) contrast(120%); /* Retro B&W look */
transition: filter 0.3s;
}
.game-item:hover .game-cover img {
filter: grayscale(0%) contrast(100%);
}
.game-rank {
font-family: var(--font-display);
font-size: 2rem;
color: var(--c-text);
opacity: 0.3;
}
.game-item:nth-child(1) .game-rank { color: var(--c-dec-3); opacity: 1; }
.game-item:nth-child(2) .game-rank { color: var(--c-dec-1); opacity: 1; }
.game-item:nth-child(3) .game-rank { color: var(--c-dec-2); opacity: 1; }
.game-info h3 {
margin: 0;
font-family: var(--font-body);
font-size: 1.2rem;
font-weight: 700;
}
.game-time {
font-family: var(--font-mono);
text-align: right;
color: var(--c-accent);
font-weight: bold;
}
/* Footer */
footer {
margin-top: 80px;
border-top: 4px solid var(--c-text);
padding-top: 20px;
display: flex;
justify-content: space-between;
font-family: var(--font-mono);
font-size: 0.8rem;
text-transform: uppercase;
}
/* Responsive */
@media (max-width: 768px) {
.grid-container { display: flex; flex-direction: column; }
.chart-wrapper.half { grid-column: span 12; }
h1 { font-size: 3rem; }
}
/* CRT Scanline Effect Overlay (Optional, subtle) */
.crt-overlay {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.05) 50%);
background-size: 100% 4px;
pointer-events: none;
z-index: 999;
}
</style>
</head>
<body>
<div class="crt-overlay"></div>
<div class="layout">
<!-- Decorative Bars -->
<div class="color-bar">
<div class="c-block cb-1"></div>
<div class="c-block cb-2"></div>
<div class="c-block cb-3"></div>
</div>
<header>
<div class="header-meta">System Report // {{.ExportTime}}</div>
<h1>Luna<span class="highlight">Box</span><br>Instruction<br>Catalogue</h1>
<div class="date-range">
PERIOD: {{.StartDate}} >> {{.EndDate}}
</div>
</header>
<div class="grid-container">
<!-- Stats -->
<div class="stat-box" style="grid-column: span 3;">
<span class="stat-label">Total Sessions</span>
<div class="stat-value">{{.TotalPlayCount}}</div>
</div>
<div class="stat-box" style="grid-column: span 3;">
<span class="stat-label">Total Duration</span>
<div class="stat-value" style="font-size: 2.5rem; margin-top: 10px;">{{.TotalPlayTimeStr}}</div>
</div>
</div>
<!-- AI Summary -->
{{if .AISummary}}
<div class="ai-terminal" style="grid-column: span 6;">
{{.AISummary}}<span class="cursor"></span>
</div>
{{end}}
<!-- Charts -->
{{if .ChartLabels}}
<div class="section-label">
Analytics <span>DATA_VISUALIZATION</span>
</div>
<div class="grid-container">
<div class="chart-wrapper">
<div class="chart-deco">FIGURE_01: TIME_SERIES</div>
<canvas id="trendChart" height="300"></canvas>
</div>
</div>
{{end}}
{{if .GameTrendData}}
<div class="grid-container">
<div class="chart-wrapper">
<div class="chart-deco">FIGURE_02: GAME_TRENDS</div>
<canvas id="gameTrendChart" height="300"></canvas>
</div>
</div>
{{end}}
<!-- Leaderboard -->
{{if .Leaderboard}}
<div class="section-label">
Top Records <span>RANKING_MODULE</span>
</div>
<div class="grid-container">
<div class="game-list">
{{range $index, $game := .Leaderboard}}
<div class="game-item">
<div class="game-rank">#{{add $index 1}}</div>
<div class="game-cover">
{{if $game.CoverBase64}}
<img src="{{$game.CoverBase64}}" alt="{{$game.GameName}}">
{{else if $game.CoverURL}}
<img src="{{$game.CoverURL}}" alt="{{$game.GameName}}">
{{else}}
<div style="width:100%;height:100%;background:#333;display:flex;align-items:center;justify-content:center;color:#555;font-size:0.8rem;">NO_IMG</div>
{{end}}
</div>
<div class="game-info">
<h3>{{$game.GameName}}</h3>
</div>
<div class="game-time">{{$game.DurationStr}}</div>
</div>
{{end}}
</div>
</div>
{{end}}
<footer>
<div>Generated by {{.AppName}} v{{.AppVersion}}</div>
<div>END OF LINE_</div>
</footer>
</div>
<!-- Data Injection -->
<script id="chart-data-config" type="application/json">
{
"labels": {{ .ChartLabels | safeJS }},
"data": {{ .ChartData | safeJS }},
"gameTrend": {{ .GameTrendData | safeJS }}
}
</script>
<script>
// Chart Configuration
Chart.defaults.font.family = "'JetBrains Mono', monospace";
Chart.defaults.color = '#181818';
const commonOptions = {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
labels: {
usePointStyle: true,
boxWidth: 10
}
}
},
scales: {
x: {
grid: {
color: 'rgba(24, 24, 24, 0.1)',
tickLength: 0
}
},
y: {
grid: {
color: 'rgba(24, 24, 24, 0.1)',
borderDash: [4, 4]
},
beginAtZero: true
}
}
};
// Load Data
let config = {};
try {
config = JSON.parse(document.getElementById('chart-data-config').textContent);
} catch (e) { console.error(e); }
// Render Trend Chart
if (config.labels && config.data) {
const ctx = document.getElementById('trendChart');
if (ctx) {
new Chart(ctx, {
type: 'line',
data: {
labels: config.labels,
datasets: [{
label: 'PLAY_TIME (Hours)',
data: config.data,
borderColor: '#B2653B',
backgroundColor: 'rgba(178, 101, 59, 0.1)',
borderWidth: 2,
pointBackgroundColor: '#F5EDDC',
pointBorderColor: '#B2653B',
pointRadius: 4,
pointHoverRadius: 6,
fill: true,
tension: 0 // Sharp lines for retro feel
}]
},
options: commonOptions
});
}
}
// Render Game Distribution
if (config.gameTrend) {
const ctx2 = document.getElementById('gameTrendChart');
if (ctx2) {
// Palette for multiple items
const palette = ['#B2653B', '#5C7F71', '#802520', '#BA8530', '#181818'];
// Map data to datasets
const datasets = config.gameTrend.map((ds, index) => {
const color = palette[index % palette.length];
return {
label: ds.label, // Game Name
data: ds.data, // Array of hours
borderColor: color,
backgroundColor: color,
borderWidth: 2,
pointBackgroundColor: '#F5EDDC',
pointBorderColor: color,
pointRadius: 4,
tension: 0 // Sharp lines
};
});
new Chart(ctx2, {
type: 'line',
data: {
labels: config.labels, // Date labels
datasets: datasets
},
options: commonOptions
});
}
}
</script>
</body>
</html>