Files
Achievement_Inputing/main/templates/main/home.html
2025-11-15 20:21:25 +08:00

343 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>数据管理系统</title>
<style>
body {
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
background: #fafafa;
}
/* 导航栏样式 */
.sidebar {
position: fixed;
top: 0;
left: 0;
width: 180px;
height: 100vh;
background: #1e1e2e;
color: white;
padding: 20px;
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
z-index: 1000;
display: flex;
flex-direction: column;
align-items: center;
}
.user-id {
text-align: center;
margin-bottom: auto;
}
.sidebar h3 {
margin-top: 0;
font-size: 18px;
color: #ff79c6;
text-align: center;
margin-bottom: 20px;
}
.navigation-links {
width: 100%;
margin-top: 60px;
}
.sidebar a,
.sidebar button {
display: block;
color: #8be9fd;
text-decoration: none;
margin: 10px 0;
font-size: 16px;
padding: 15px;
border-radius: 4px;
background: transparent;
border: none;
cursor: pointer;
width: calc(100% - 40px);
text-align: left;
transition: all 0.2s ease;
}
.sidebar a:hover,
.sidebar button:hover {
color: #ff79c6;
background-color: rgba(139, 233, 253, 0.2);
}
/* 主内容区 */
.main-content {
margin-left: 200px;
padding: 20px;
color: #333;
}
.card {
background: #fff;
border-radius: 14px;
box-shadow: 0 10px 24px rgba(31,35,40,0.08);
padding: 20px;
}
.grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.badge {
background: #eef2ff;
color: #3730a3;
border-radius: 999px;
padding: 4px 10px;
font-size: 12px;
}
.legend {
display: flex;
gap: 12px;
align-items: center;
}
.legend .dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
}
.muted {
color: #6b7280;
font-size: 12px;
}
.btn {
padding: 8px 12px;
border: none;
border-radius: 8px;
cursor: pointer;
}
.btn-primary {
background: #4f46e5;
color: #fff;
}
</style>
</head>
<body>
<!-- 左侧固定栏目 -->
<div class="sidebar">
<div class="user-id">
<h3>用户ID{{ user_id }}</h3>
</div>
<div class="navigation-links">
<a href="{% url 'main:home' %}" onclick="return handleNavClick(this, '/');">主页</a>
<a href="{% url 'elastic:upload_page' %}" onclick="return handleNavClick(this, '/elastic/upload/');">图片上传与识别</a>
<a href="{% url 'elastic:manage_page' %}" onclick="return handleNavClick(this, '/elastic/manage/');">数据管理</a>
{% if is_admin %}
<a href="{% url 'elastic:user_manage' %}" onclick="return handleNavClick(this, '/elastic/user_manage/');">用户管理</a>
{% endif %}
<button id="logoutBtn">退出登录</button>
<div id="logoutMsg"></div>
{% csrf_token %}
</div>
</div>
<!-- 主内容区域 -->
<div class="main-content">
<div class="card">
<div class="header">
<h2>数据概览</h2>
<div style="display:flex; gap:8px; align-items:center;">
<span class="badge">用户:{{ user_id }}</span>
{% if is_admin %}
<button id="triggerAnalyze" class="btn btn-primary">手动开始分析</button>
{% endif %}
</div>
</div>
<div class="grid-3">
<div>
<div class="legend"><span class="dot" style="background:#4f46e5;"></span><span class="muted">最近十天录入</span></div>
<canvas id="chartDays" height="140"></canvas>
</div>
<div>
<div class="legend"><span class="dot" style="background:#16a34a;"></span><span class="muted">最近十周录入</span></div>
<canvas id="chartWeeks" height="140"></canvas>
</div>
<div>
<div class="legend"><span class="dot" style="background:#ea580c;"></span><span class="muted">最近十个月录入</span></div>
<canvas id="chartMonths" height="140"></canvas>
</div>
</div>
</div>
<div class="grid" style="margin-top:16px;">
<div class="card">
<div class="header"><h2>近1个月成果类型</h2></div>
<canvas id="pie1m" height="200"></canvas>
</div>
<div class="card">
<div class="header"><h2>近12个月成果类型</h2></div>
<canvas id="pie12m" height="200"></canvas>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// 获取CSRF令牌的函数
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
// 导航点击处理函数提供备用URL
function handleNavClick(element, fallbackUrl) {
// 尝试使用Django模板生成的URL如果失败则使用备用URL
try {
// 如果模板渲染正常直接返回true让默认行为处理
return true;
} catch (e) {
// 如果模板渲染有问题使用备用URL
window.location.href = fallbackUrl;
return false;
}
}
// 修复用户管理链接跳转问题
document.addEventListener('DOMContentLoaded', function() {
// 为用户管理链接添加事件监听器,确保正确跳转
const userManagementLink = document.querySelector('a[href*="get_users"]');
if (userManagementLink) {
userManagementLink.addEventListener('click', function(e) {
// 阻止默认行为
e.preventDefault();
// 获取备用URL
const fallbackUrl = this.getAttribute('onclick').match(/'([^']+)'/g)[1].replace(/'/g, '');
// 直接跳转到用户管理页面
window.location.href = fallbackUrl;
});
}
});
// 登出功能
document.getElementById('logoutBtn').addEventListener('click', async () => {
const msg = document.getElementById('logoutMsg');
msg.textContent = '';
const csrftoken = getCookie('csrftoken');
try {
const resp = await fetch('/accounts/logout/', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken || ''
},
body: JSON.stringify({})
});
const data = await resp.json();
if (!resp.ok || !data.ok) {
throw new Error('登出失败');
}
document.cookie = 'sessionid=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
document.cookie = 'csrftoken=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
window.location.href = data.redirect_url;
} catch (e) {
msg.textContent = e.message || '发生错误';
}
});
async function loadAnalytics() {
const resp = await fetch('/elastic/analytics/overview/');
const d = await resp.json();
if (!resp.ok || d.status !== 'success') return;
const data = d.data || {};
renderLine('chartDays', data.last_10_days || [], '#4f46e5');
renderLine('chartWeeks', data.last_10_weeks || [], '#16a34a');
renderLine('chartMonths', data.last_10_months || [], '#ea580c');
renderPie('pie1m', data.type_pie_1m || []);
renderPie('pie12m', data.type_pie_12m || []);
}
const btn = document.getElementById('triggerAnalyze');
if (btn) {
btn.addEventListener('click', async () => {
btn.disabled = true;
btn.textContent = '分析中…';
try {
const resp = await fetch('/elastic/analytics/overview/?force=1');
const d = await resp.json();
if (!resp.ok || d.status !== 'success') throw new Error('分析失败');
window.location.reload();
} catch (e) {
btn.textContent = '重试';
btn.disabled = false;
}
});
}
function hexWithAlpha(hex, alphaHex) {
if (!hex || !hex.startsWith('#')) return hex;
if (hex.length === 7) return hex + alphaHex;
return hex;
}
function renderLine(id, items, color) {
const ctx = document.getElementById(id);
const labels = items.map(x => x.label);
const values = items.map(x => x.count);
new Chart(ctx, {
type: 'line',
data: {
labels,
datasets: [{
data: values,
borderColor: color,
backgroundColor: hexWithAlpha(color, '26'),
tension: 0.25,
fill: true,
pointRadius: 3,
}]
},
options: {
responsive: true,
plugins: { legend: { display: false } },
animation: { duration: 800, easing: 'easeOutQuart' },
scales: {
x: { grid: { display: false } },
y: { grid: { color: 'rgba(31,35,40,0.06)' }, beginAtZero: true }
}
}
});
}
function renderPie(id, items) {
const ctx = document.getElementById(id);
const labels = items.map(x => x.type);
const values = items.map(x => x.count);
const colors = ['#2563eb','#22c55e','#f59e0b','#ef4444','#a855f7','#06b6d4','#84cc16','#ec4899','#475569','#d946ef'];
new Chart(ctx, {
type: 'doughnut',
data: {
labels,
datasets: [{ data: values, backgroundColor: colors.slice(0, labels.length) }]
},
options: {
responsive: true,
animation: { duration: 900, easing: 'easeOutQuart' },
plugins: { legend: { position: 'bottom' } }
}
});
}
loadAnalytics();
</script>
</body>
</html>