feat: 收藏夹系统 + Studio 趋势图 + 通知偏好设置
- 新增收藏夹系统 (Folder/FolderItem 模型、CRUD API、前端管理页) - 新增文章详情页收藏按钮 (书签图标、ToggleFavorite 一键收藏) - 新增 Studio 趋势图 (赋能值/评论/点赞/收藏 4 线图, 7d/30d/90d) - 新增通知偏好设置页 (评论/回复/点赞/关注 4 类开关) - 后端通知列表支持按偏好过滤 (排除已关闭的通知类型) - 下载 Chart.js 到本地静态资源 (static/js/chart.umd.min.js) - 补充收藏夹卡片、开关滑块、趋势图网格等 CSS 样式
This commit is contained in:
@ -31,7 +31,7 @@
|
||||
<div class="post-detail-body" id="postContent"></div>
|
||||
</article>
|
||||
|
||||
<!-- 赞/踩按钮 -->
|
||||
<!-- 赞/踩/收藏按钮 -->
|
||||
<div class="post-reactions" id="postReactions" data-post-id="{{.Post.ID}}">
|
||||
<button class="reaction-btn reaction-like-btn" id="likeBtn" title="点赞">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20"><path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3H14zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"/></svg>
|
||||
@ -40,6 +40,10 @@
|
||||
<button class="reaction-btn reaction-dislike-btn" id="dislikeBtn" title="踩">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20"><path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3H10zM17 2h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"/></svg>
|
||||
</button>
|
||||
<button class="reaction-btn reaction-fav-btn" id="favBtn" title="收藏">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" width="20" height="20"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>
|
||||
<span class="reaction-like-count" id="favCount">{{.Post.FavoritesCount}}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{if .IsLoggedIn}}
|
||||
@ -340,6 +344,63 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// ========== 收藏按钮 ==========
|
||||
(function() {
|
||||
var favBtn = document.getElementById('favBtn');
|
||||
var favCountEl = document.getElementById('favCount');
|
||||
var container = document.getElementById('postReactions');
|
||||
if (!favBtn || !container) return;
|
||||
|
||||
var postId = container.dataset.postId;
|
||||
var csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
||||
var csrfToken = csrfMeta ? csrfMeta.getAttribute('content') : '';
|
||||
|
||||
// 页面加载时获取收藏状态
|
||||
fetch('/api/posts/' + postId + '/folder-status')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(d) {
|
||||
if (d.success && d.data && d.data.favorited) {
|
||||
favBtn.classList.add('active');
|
||||
}
|
||||
})
|
||||
.catch(function() {});
|
||||
|
||||
favBtn.addEventListener('click', function() {
|
||||
favBtn.disabled = true;
|
||||
fetch('/api/posts/' + postId + '/favorite', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }
|
||||
})
|
||||
.then(function(r) {
|
||||
if (r.status === 401) { window.location.href = '/login?redirect=/posts/' + postId; return null; }
|
||||
return r.json();
|
||||
})
|
||||
.then(function(d) {
|
||||
favBtn.disabled = false;
|
||||
if (!d) return;
|
||||
if (d.success) {
|
||||
var isFav = d.data && d.data.favorited;
|
||||
if (isFav) {
|
||||
favBtn.classList.add('active');
|
||||
} else {
|
||||
favBtn.classList.remove('active');
|
||||
}
|
||||
if (favCountEl && d.data && typeof d.data.favorites_count === 'number') {
|
||||
favCountEl.textContent = d.data.favorites_count;
|
||||
} else if (favCountEl) {
|
||||
var cur = parseInt(favCountEl.textContent) || 0;
|
||||
favCountEl.textContent = isFav ? cur + 1 : Math.max(0, cur - 1);
|
||||
}
|
||||
} else {
|
||||
alert(d.message || '操作失败');
|
||||
}
|
||||
})
|
||||
.catch(function() { favBtn.disabled = false; });
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// ========== 评论系统 ==========
|
||||
(function() {
|
||||
|
||||
@ -40,6 +40,20 @@
|
||||
</svg>
|
||||
我的域能
|
||||
</a>
|
||||
<a href="/settings/favorites"
|
||||
class="settings-nav-item {{if eq .ActiveTab "favorites"}}active{{end}}">
|
||||
<svg class="settings-nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/>
|
||||
</svg>
|
||||
我的收藏夹
|
||||
</a>
|
||||
<a href="/settings/notify"
|
||||
class="settings-nav-item {{if eq .ActiveTab "notify"}}active{{end}}">
|
||||
<svg class="settings-nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
||||
</svg>
|
||||
通知偏好
|
||||
</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
@ -181,6 +195,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else if eq .ActiveTab "favorites"}}
|
||||
<!-- 我的收藏夹 -->
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<h2>我的收藏夹</h2>
|
||||
<p class="settings-card-desc">管理和查看你的收藏夹</p>
|
||||
</div>
|
||||
<div class="settings-card-body">
|
||||
<div class="fav-toolbar">
|
||||
<button class="settings-save-btn" id="createFolderBtn">+ 新建收藏夹</button>
|
||||
</div>
|
||||
<div class="fav-list" id="favFolderList">
|
||||
<div class="energy-log-empty">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else if eq .ActiveTab "notify"}}
|
||||
<!-- 通知偏好 -->
|
||||
<div class="settings-card">
|
||||
<div class="settings-card-header">
|
||||
<h2>通知偏好</h2>
|
||||
<p class="settings-card-desc">管理你的通知推送偏好,关闭后不影响通知记录存储</p>
|
||||
</div>
|
||||
<div class="settings-card-body" id="notifyPrefsBody">
|
||||
<div class="energy-log-empty">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<!-- 账号信息 -->
|
||||
<div class="settings-card">
|
||||
@ -981,10 +1022,172 @@
|
||||
|
||||
function formatTime(isoStr) {
|
||||
if (!isoStr) return '';
|
||||
// '2026-06-01T12:39:41+08:00' → '2026-06-01 12:39:41'
|
||||
return isoStr.replace('T', ' ').replace(/\+.*/, '');
|
||||
}
|
||||
})();
|
||||
|
||||
// ===== 收藏夹 Tab =====
|
||||
(function () {
|
||||
var listEl = document.getElementById('favFolderList');
|
||||
var createBtn = document.getElementById('createFolderBtn');
|
||||
if (!listEl) return;
|
||||
|
||||
function loadFolders() {
|
||||
fetch('/api/folders')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (!d.success || !d.data || !d.data.folders || d.data.folders.length === 0) {
|
||||
listEl.innerHTML = '<div class="energy-log-empty">暂无收藏夹</div>';
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
d.data.folders.forEach(function (f) {
|
||||
var isDefault = f.is_default ? ' (默认)' : '';
|
||||
var pub = f.is_public ? '公开' : '私密';
|
||||
html += '<div class="fav-folder-card">'
|
||||
+ '<div class="fav-folder-info">'
|
||||
+ '<span class="fav-folder-name">' + escapeHTML(f.name) + isDefault + '</span>'
|
||||
+ '<span class="fav-folder-meta">' + (f.item_count || 0) + ' 篇文章 · ' + pub + '</span>'
|
||||
+ '</div>'
|
||||
+ '<div class="fav-folder-actions">'
|
||||
+ (f.is_default ? '' : '<button class="fav-folder-del" data-id="' + f.id + '">删除</button>')
|
||||
+ '</div>'
|
||||
+ '</div>';
|
||||
});
|
||||
listEl.innerHTML = html;
|
||||
|
||||
// 绑定删除按钮
|
||||
listEl.querySelectorAll('.fav-folder-del').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var id = btn.getAttribute('data-id');
|
||||
if (!confirm('确定要删除此收藏夹?其中的收藏内容也将被清除。')) return;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('DELETE', '/api/folders/' + id, true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) return;
|
||||
try {
|
||||
var r = JSON.parse(xhr.responseText);
|
||||
if (r.success) {
|
||||
showSettingsToast('收藏夹已删除');
|
||||
loadFolders();
|
||||
} else {
|
||||
showSettingsToast(r.message || '删除失败', true);
|
||||
}
|
||||
} catch (e) {
|
||||
showSettingsToast('删除失败', true);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function () {
|
||||
listEl.innerHTML = '<div class="energy-log-empty">加载失败</div>';
|
||||
});
|
||||
}
|
||||
|
||||
if (createBtn) {
|
||||
createBtn.addEventListener('click', function () {
|
||||
var name = prompt('请输入收藏夹名称(最多50个字符):');
|
||||
if (!name || !name.trim()) return;
|
||||
if (name.trim().length > 50) {
|
||||
showSettingsToast('收藏夹名称不能超过 50 个字符', true);
|
||||
return;
|
||||
}
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/folders', true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) return;
|
||||
try {
|
||||
var r = JSON.parse(xhr.responseText);
|
||||
if (r.success) {
|
||||
showSettingsToast('收藏夹已创建');
|
||||
loadFolders();
|
||||
} else {
|
||||
showSettingsToast(r.message || '创建失败', true);
|
||||
}
|
||||
} catch (e) {
|
||||
showSettingsToast('创建失败', true);
|
||||
}
|
||||
};
|
||||
xhr.send(JSON.stringify({ name: name.trim(), description: '', is_public: false }));
|
||||
});
|
||||
}
|
||||
|
||||
loadFolders();
|
||||
})();
|
||||
|
||||
// ===== 通知偏好 Tab =====
|
||||
(function () {
|
||||
var bodyEl = document.getElementById('notifyPrefsBody');
|
||||
if (!bodyEl) return;
|
||||
|
||||
function loadPrefs() {
|
||||
fetch('/api/settings/notify-prefs')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (!d.success || !d.data) {
|
||||
bodyEl.innerHTML = '<div class="energy-log-empty">加载失败</div>';
|
||||
return;
|
||||
}
|
||||
var prefs = d.data;
|
||||
var items = [
|
||||
{ key: 'comment', label: '评论通知', desc: '有人评论我的文章时通知我' },
|
||||
{ key: 'comment_reply', label: '回复通知', desc: '有人回复我的评论时通知我' },
|
||||
{ key: 'like_aggregated', label: '点赞通知', desc: '每日汇总的点赞通知' },
|
||||
{ key: 'follow', label: '关注通知', desc: '有人关注我时通知我' }
|
||||
];
|
||||
var html = '';
|
||||
items.forEach(function (item) {
|
||||
var checked = prefs[item.key] ? 'checked' : '';
|
||||
html += '<div class="notify-pref-row">'
|
||||
+ '<div class="notify-pref-info">'
|
||||
+ '<span class="notify-pref-label">' + item.label + '</span>'
|
||||
+ '<span class="notify-pref-desc">' + item.desc + '</span>'
|
||||
+ '</div>'
|
||||
+ '<label class="notify-pref-toggle">'
|
||||
+ '<input type="checkbox" class="notify-pref-cb" data-key="' + item.key + '" ' + checked + '>'
|
||||
+ '<span class="notify-pref-slider"></span>'
|
||||
+ '</label>'
|
||||
+ '</div>';
|
||||
});
|
||||
bodyEl.innerHTML = html;
|
||||
|
||||
// 绑定切换事件
|
||||
bodyEl.querySelectorAll('.notify-pref-cb').forEach(function (cb) {
|
||||
cb.addEventListener('change', function () {
|
||||
var key = cb.getAttribute('data-key');
|
||||
var enabled = cb.checked;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('PUT', '/api/settings/notify-prefs', true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) return;
|
||||
try {
|
||||
var r = JSON.parse(xhr.responseText);
|
||||
if (r.success) {
|
||||
showSettingsToast('通知偏好已更新');
|
||||
} else {
|
||||
showSettingsToast(r.message || '更新失败', true);
|
||||
cb.checked = !enabled;
|
||||
}
|
||||
} catch (e) {
|
||||
showSettingsToast('更新失败', true);
|
||||
cb.checked = !enabled;
|
||||
}
|
||||
};
|
||||
xhr.send(JSON.stringify({ key: key, enabled: enabled }));
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function () {
|
||||
bodyEl.innerHTML = '<div class="energy-log-empty">加载失败</div>';
|
||||
});
|
||||
}
|
||||
|
||||
loadPrefs();
|
||||
})();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@ -40,26 +40,44 @@
|
||||
</div>
|
||||
|
||||
<div class="studio-section">
|
||||
<h2 class="section-title">状态分布</h2>
|
||||
<div class="status-bar">
|
||||
{{if gt .Overview.ApprovedCount 0}}
|
||||
<div class="status-bar-segment status-bar-approved" style="flex:{{.Overview.ApprovedCount}}">已发布 {{.Overview.ApprovedCount}}</div>
|
||||
{{end}}
|
||||
{{if gt .Overview.DraftCount 0}}
|
||||
<div class="status-bar-segment status-bar-draft" style="flex:{{.Overview.DraftCount}}">草稿 {{.Overview.DraftCount}}</div>
|
||||
{{end}}
|
||||
{{if gt .Overview.PendingCount 0}}
|
||||
<div class="status-bar-segment status-bar-pending" style="flex:{{.Overview.PendingCount}}">审核中 {{.Overview.PendingCount}}</div>
|
||||
{{end}}
|
||||
{{if gt .Overview.RejectedCount 0}}
|
||||
<div class="status-bar-segment status-bar-rejected" style="flex:{{.Overview.RejectedCount}}">已退回 {{.Overview.RejectedCount}}</div>
|
||||
{{end}}
|
||||
<div class="trends-header">
|
||||
<h2 class="section-title">交互趋势</h2>
|
||||
<div class="trends-period">
|
||||
<button class="trends-period-btn active" data-period="7d">7 天</button>
|
||||
<button class="trends-period-btn" data-period="30d">30 天</button>
|
||||
<button class="trends-period-btn" data-period="90d">90 天</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="studio-section">
|
||||
<div class="studio-placeholder">
|
||||
<p>📈 更详细的阅读趋势、互动数据将在后续版本中推出</p>
|
||||
<div class="trends-grid">
|
||||
<div class="trends-card">
|
||||
<h3 class="trends-card-title">赋能值</h3>
|
||||
<div class="trends-chart-wrap">
|
||||
<canvas id="energyChart"></canvas>
|
||||
</div>
|
||||
<div class="trends-loading" id="energyLoading">加载中...</div>
|
||||
</div>
|
||||
<div class="trends-card">
|
||||
<h3 class="trends-card-title">评论量</h3>
|
||||
<div class="trends-chart-wrap">
|
||||
<canvas id="commentsChart"></canvas>
|
||||
</div>
|
||||
<div class="trends-loading" id="commentsLoading">加载中...</div>
|
||||
</div>
|
||||
<div class="trends-card">
|
||||
<h3 class="trends-card-title">点赞量</h3>
|
||||
<div class="trends-chart-wrap">
|
||||
<canvas id="likesChart"></canvas>
|
||||
</div>
|
||||
<div class="trends-loading" id="likesLoading">加载中...</div>
|
||||
</div>
|
||||
<div class="trends-card">
|
||||
<h3 class="trends-card-title">收藏量</h3>
|
||||
<div class="trends-chart-wrap">
|
||||
<canvas id="favoritesChart"></canvas>
|
||||
</div>
|
||||
<div class="trends-loading" id="favoritesLoading">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
@ -68,5 +86,182 @@
|
||||
|
||||
{{template "layout/footer.html" .}}
|
||||
|
||||
<script src="/static/js/chart.umd.min.js?v={{assetV "/static/js/chart.umd.min.js"}}"></script>
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var charts = {};
|
||||
var currentPeriod = '7d';
|
||||
|
||||
var chartColors = {
|
||||
energy: { border: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
|
||||
comments: { border: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
|
||||
likes: { border: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
|
||||
favorites: { border: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' }
|
||||
};
|
||||
|
||||
function fillDates(points, days) {
|
||||
var now = new Date();
|
||||
var map = {};
|
||||
points.forEach(function (p) {
|
||||
map[p.date] = p.value;
|
||||
});
|
||||
var result = [];
|
||||
var labels = [];
|
||||
for (var i = days - 1; i >= 0; i--) {
|
||||
var d = new Date(now);
|
||||
d.setDate(d.getDate() - i);
|
||||
var key = d.toISOString().slice(0, 10);
|
||||
labels.push(key.slice(5)); // MM-DD
|
||||
result.push(map[key] || 0);
|
||||
}
|
||||
var valueSum = 0;
|
||||
for (var j = 0; j < result.length; j++) { valueSum += result[j]; }
|
||||
return { labels: labels, values: result, hasData: valueSum > 0 };
|
||||
}
|
||||
|
||||
function createChart(canvasId, color, label) {
|
||||
var ctx = document.getElementById(canvasId).getContext('2d');
|
||||
return new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [{
|
||||
label: label,
|
||||
data: [],
|
||||
borderColor: color.border,
|
||||
backgroundColor: color.bg,
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
pointRadius: 2,
|
||||
pointHoverRadius: 5
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
ticks: { font: { size: 10 }, maxRotation: 45, minRotation: 45 },
|
||||
grid: { display: false }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
font: { size: 10 },
|
||||
precision: 0,
|
||||
callback: function (v) { return v === Math.floor(v) ? v : ''; }
|
||||
},
|
||||
grid: { color: 'rgba(0,0,0,0.05)' }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function destroyCharts() {
|
||||
Object.keys(charts).forEach(function (key) {
|
||||
if (charts[key]) {
|
||||
charts[key].destroy();
|
||||
delete charts[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showLoading(loadingId) {
|
||||
var el = document.getElementById(loadingId);
|
||||
if (el) el.style.display = 'block';
|
||||
}
|
||||
|
||||
function hideLoading(loadingId) {
|
||||
var el = document.getElementById(loadingId);
|
||||
if (el) el.style.display = 'none';
|
||||
}
|
||||
|
||||
function loadTrends(period) {
|
||||
currentPeriod = period;
|
||||
|
||||
destroyCharts();
|
||||
|
||||
var days = period === '30d' ? 30 : period === '90d' ? 90 : 7;
|
||||
|
||||
showLoading('energyLoading');
|
||||
showLoading('commentsLoading');
|
||||
showLoading('likesLoading');
|
||||
showLoading('favoritesLoading');
|
||||
|
||||
var canvasIds = ['energyChart', 'commentsChart', 'likesChart', 'favoritesChart'];
|
||||
canvasIds.forEach(function (id) {
|
||||
var canvas = document.getElementById(id);
|
||||
canvas.style.display = 'none';
|
||||
});
|
||||
|
||||
fetch('/api/studio/trends?period=' + period)
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (!d.success || !d.data) {
|
||||
canvasIds.forEach(function (id) {
|
||||
hideLoading(id.replace('Chart', 'Loading').replace('Chart', 'Loading'));
|
||||
});
|
||||
canvasIds.forEach(function (id) {
|
||||
document.getElementById(id).style.display = 'block';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var maps = [
|
||||
{ id: 'energyChart', key: 'energy', color: chartColors.energy, label: '赋能值', loading: 'energyLoading' },
|
||||
{ id: 'commentsChart', key: 'comments', color: chartColors.comments, label: '评论', loading: 'commentsLoading' },
|
||||
{ id: 'likesChart', key: 'likes', color: chartColors.likes, label: '点赞', loading: 'likesLoading' },
|
||||
{ id: 'favoritesChart', key: 'favorites', color: chartColors.favorites, label: '收藏', loading: 'favoritesLoading' }
|
||||
];
|
||||
|
||||
maps.forEach(function (m) {
|
||||
var fd = fillDates(d.data[m.key] || [], days);
|
||||
hideLoading(m.loading);
|
||||
var canvas = document.getElementById(m.id);
|
||||
canvas.style.display = 'block';
|
||||
|
||||
if (!fd.hasData && fd.values.every(function (v) { return v === 0; })) {
|
||||
canvas.parentElement.innerHTML = '<div class="trends-no-data">暂无数据</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
charts[m.id] = createChart(m.id, m.color, m.label);
|
||||
charts[m.id].data.labels = fd.labels;
|
||||
charts[m.id].data.datasets[0].data = fd.values;
|
||||
charts[m.id].update();
|
||||
});
|
||||
})
|
||||
.catch(function () {
|
||||
canvasIds.forEach(function (id) {
|
||||
hideLoading(id.replace('Chart', 'Loading'));
|
||||
document.getElementById(id).style.display = 'block';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 绑定期间按钮
|
||||
document.querySelectorAll('.trends-period-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
document.querySelectorAll('.trends-period-btn').forEach(function (b) {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
loadTrends(btn.getAttribute('data-period'));
|
||||
});
|
||||
});
|
||||
|
||||
// 初始加载
|
||||
if (document.getElementById('energyChart')) {
|
||||
loadTrends('7d');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user