增加了图表[0.2.7.2][ci]

This commit is contained in:
2026-03-04 19:54:20 +08:00
parent 14e407d06a
commit 418cc798df
2 changed files with 88 additions and 8 deletions

View File

@@ -589,6 +589,25 @@ def analytics_recent(limit: int = 10, gte: str = None, lte: str = None):
pass pass
return "" return ""
def _extract_detail(s: str):
if not s:
return ""
try:
obj = json.loads(s)
if isinstance(obj, dict):
# 尝试获取常见的标题字段
for key in ["标题", "名称", "项目名称", "成果名称", "软件名称", "专利名称", "获奖名称", "证书名称", "姓名"]:
v = obj.get(key)
if isinstance(v, str) and v:
return v
# 如果没有找到常见标题,尝试获取第一个非"数据类型"的字符串值
for k, v in obj.items():
if k != "数据类型" and isinstance(v, str) and v and len(v) < 50:
return v
except Exception:
pass
return ""
search = AchievementDocument.search() search = AchievementDocument.search()
body = { body = {
"size": max(1, min(limit, 100)), "size": max(1, min(limit, 100)),
@@ -619,11 +638,13 @@ def analytics_recent(limit: int = 10, gte: str = None, lte: str = None):
except Exception: except Exception:
uname = None uname = None
tval = _extract_type(getattr(hit, 'data', '')) tval = _extract_type(getattr(hit, 'data', ''))
dval = _extract_detail(getattr(hit, 'data', ''))
results.append({ results.append({
"_id": hit.meta.id, "_id": hit.meta.id,
"writer_id": w, "writer_id": w,
"username": uname or "", "username": uname or "",
"type": tval or "", "type": tval or "",
"detail": dval or "",
"time": getattr(hit, 'time', None) "time": getattr(hit, 'time', None)
}) })
return results return results

View File

@@ -67,7 +67,10 @@
<div id="chartTrend" style="width:100%;height:320px;"></div> <div id="chartTrend" style="width:100%;height:320px;"></div>
</div> </div>
<div class="card"> <div class="card">
<div class="header"><h3>类型占比近30天</h3></div> <div class="header">
<h3>类型占比近30天</h3>
<button id="toggleTypesChartBtn" class="btn btn-primary" style="font-size: 12px; padding: 4px 8px;">切换图表</button>
</div>
<div id="chartTypes" style="width:100%;height:320px;"></div> <div id="chartTypes" style="width:100%;height:320px;"></div>
</div> </div>
<div class="card"> <div class="card">
@@ -170,19 +173,74 @@
}); });
} }
let typesChartData = [];
let currentChartType = 'pie';
let typesChartInterval = null;
async function loadTypes(){ async function loadTypes(){
const url = '/elastic/analytics/types/?' + qs({ from:'now-30d', to:'now', size:10 }); const url = '/elastic/analytics/types/?' + qs({ from:'now-30d', to:'now', size:10 });
const res = await fetchJSON(url); const res = await fetchJSON(url);
if(res.status!=='success') return; if(res.status!=='success') return;
const buckets = res.data || []; const buckets = res.data || [];
const data = buckets.map(b=>({ name: String(b.key||'未知'), value: b.doc_count||0 })); typesChartData = buckets.map(b=>({ name: String(b.key||'未知'), value: b.doc_count||0 }));
typesChart.setOption({ renderTypesChart();
tooltip:{trigger:'item'}, startTypesChartRotation();
legend:{type:'scroll'},
series:[{ type:'pie', radius:['40%','70%'], data }]
});
} }
function renderTypesChart() {
if (currentChartType === 'pie') {
typesChart.setOption({
tooltip:{trigger:'item'},
legend:{type:'scroll', top:'bottom'},
grid: { top: 0, bottom: 0, left: 0, right: 0 },
xAxis: { show: false },
yAxis: { show: false },
series:[{
type:'pie',
radius:['40%','70%'],
center: ['50%', '50%'],
data: typesChartData,
label: { show: false },
itemStyle: { borderRadius: 10, borderColor: '#fff', borderWidth: 2 }
}]
}, true);
} else {
const names = typesChartData.map(d => d.name);
const values = typesChartData.map(d => d.value);
typesChart.setOption({
tooltip:{trigger:'axis', axisPointer:{type:'shadow'}},
legend:{show: false},
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
xAxis: { type: 'category', data: names, show: true },
yAxis: { type: 'value', show: true },
series: [{
type: 'bar',
data: values,
itemStyle: { color: '#5470c6' },
barWidth: '60%'
}]
}, true);
}
}
function toggleChartType() {
currentChartType = currentChartType === 'pie' ? 'bar' : 'pie';
renderTypesChart();
}
function startTypesChartRotation() {
if (typesChartInterval) clearInterval(typesChartInterval);
typesChartInterval = setInterval(() => {
toggleChartType();
}, 5000);
}
document.getElementById('toggleTypesChartBtn').addEventListener('click', () => {
toggleChartType();
// Reset timer on manual interaction
startTypesChartRotation();
});
async function loadTypesTrend(){ async function loadTypesTrend(){
const url = '/elastic/analytics/types_trend/?' + qs({ from:'now-180d', to:'now', interval:'week', size:6 }); const url = '/elastic/analytics/types_trend/?' + qs({ from:'now-180d', to:'now', interval:'week', size:6 });
const res = await fetchJSON(url); const res = await fetchJSON(url);
@@ -233,7 +291,8 @@
const t = formatTime(it.time); const t = formatTime(it.time);
const u = it.username || ''; const u = it.username || '';
const ty = it.type || '未知'; const ty = it.type || '未知';
li.textContent = `${t}${u}${ty}`; const de = it.detail ? `${it.detail}` : '';
li.textContent = `${t}${u}${ty}${de}`;
listEl.appendChild(li); listEl.appendChild(li);
}); });
} }