Files
Achievement_Inputing/main/templates/main/home.html
2025-11-17 01:07:52 +08:00

346 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>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<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: #add8e6;
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>
<span class="badge">用户:{{ user_id }}</span>
</div>
<div class="muted">数据可视化概览:录入量变化、类型占比、类型变化、最近活动</div>
</div>
<div class="grid" style="margin-top:16px;">
<div class="card">
<div class="header"><h3>录入量变化近90天</h3></div>
<div id="chartTrend" style="width:100%;height:320px;"></div>
</div>
<div class="card">
<div class="header"><h3>类型占比近30天</h3></div>
<div id="chartTypes" style="width:100%;height:320px;"></div>
</div>
<div class="card">
<div class="header"><h3>类型变化近180天按周</h3></div>
<div id="chartTypesTrend" style="width:100%;height:320px;"></div>
</div>
<div class="card">
<div class="header"><h3>最近活动近7天</h3></div>
<ul id="recentList" style="list-style:none;padding:0;margin:0;"></ul>
</div>
</div>
</div>
<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 || '发生错误';
}
});
function fetchJSON(url){ return fetch(url, {credentials:'same-origin'}).then(r=>r.json()); }
function qs(params){ const u = new URLSearchParams(params); return u.toString(); }
const trendChart = echarts.init(document.getElementById('chartTrend'));
const typesChart = echarts.init(document.getElementById('chartTypes'));
const typesTrendChart = echarts.init(document.getElementById('chartTypesTrend'));
async function loadTrend(){
const url = '/elastic/analytics/trend/?' + qs({ from:'now-90d', to:'now', interval:'day' });
const res = await fetchJSON(url);
if(res.status!=='success') return;
const buckets = res.data || [];
const x = buckets.map(b=>b.key_as_string||'');
const y = buckets.map(b=>b.doc_count||0);
trendChart.setOption({
tooltip:{trigger:'axis'},
xAxis:{type:'category', data:x},
yAxis:{type:'value'},
series:[{ type:'line', areaStyle:{}, data:y, smooth:true, color:'#4f46e5' }]
});
}
async function loadTypes(){
const url = '/elastic/analytics/types/?' + qs({ from:'now-30d', to:'now', size:10 });
const res = await fetchJSON(url);
if(res.status!=='success') return;
const buckets = res.data || [];
const data = buckets.map(b=>({ name: String(b.key||'未知'), value: b.doc_count||0 }));
typesChart.setOption({
tooltip:{trigger:'item'},
legend:{type:'scroll'},
series:[{ type:'pie', radius:['40%','70%'], data }]
});
}
async function loadTypesTrend(){
const url = '/elastic/analytics/types_trend/?' + qs({ from:'now-180d', to:'now', interval:'week', size:6 });
const res = await fetchJSON(url);
if(res.status!=='success') return;
const rows = res.data || [];
const x = rows.map(r=>r.key_as_string||'');
const typeSet = new Set();
rows.forEach(r=> (r.types||[]).forEach(t=> typeSet.add(String(t.key||'未知'))));
const types = Array.from(typeSet);
const series = types.map(tp=>({
name: tp,
type:'line',
smooth:true,
data: rows.map(r=>{
const b = (r.types||[]).find(x=>String(x.key||'')===tp);
return b? b.doc_count||0 : 0;
})
}));
typesTrendChart.setOption({
tooltip:{trigger:'axis'},
legend:{type:'scroll'},
xAxis:{type:'category', data:x},
yAxis:{type:'value'},
series
});
}
function formatTime(t){
try{
const d = new Date(t);
if(String(d) !== 'Invalid Date'){
const pad = n=> String(n).padStart(2,'0');
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
}catch(e){}
return t||'';
}
async function loadRecent(){
const listEl = document.getElementById('recentList');
const url = '/elastic/analytics/recent/?' + qs({ from:'now-7d', to:'now', limit:10 });
const res = await fetchJSON(url);
if(res.status!=='success') return;
const items = res.data || [];
listEl.innerHTML = '';
items.forEach(it=>{
const li = document.createElement('li');
const t = formatTime(it.time);
const u = it.username || '';
const ty = it.type || '未知';
li.textContent = `${t}${u}${ty}`;
listEl.appendChild(li);
});
}
loadTrend();
loadTypes();
loadTypesTrend();
loadRecent();
</script>
</body>
</html>