@@ -3823,6 +3833,7 @@
Top 5 期刊明细
}
let JOURNAL_OPEN_HANDLER_BOUND=false;
+let KEYWORD_GALLERY_HANDLER_BOUND=false;
function handleJournalOpenClick(event){
const rawTarget = event.target;
const target = rawTarget && typeof rawTarget.closest==='function' ? rawTarget.closest('[data-journal-open]') : null;
@@ -3833,6 +3844,15 @@
Top 5 期刊明细
openJournalDetail(dim, Number.isFinite(index)?index:0);
}
+function handleKeywordGalleryClick(event){
+ const rawTarget = event.target;
+ const trigger = rawTarget && typeof rawTarget.closest==='function' ? rawTarget.closest('[data-topicviz-gallery]') : null;
+ if(!trigger) return;
+ event.preventDefault();
+ const dim = trigger.dataset?.topicvizGallery==='field' ? 'field' : 'topic';
+ openKeywordGallery(dim);
+}
+
function renderDeferredJournalCharts(){
document.querySelectorAll('[data-journal-deferred="1"]').forEach(el=>{
const dim = el.dataset.journalDim==='field' ? 'field' : 'topic';
@@ -4786,6 +4806,217 @@
Top 5 期刊明细
}
}
+function openKeywordGallery(dim){
+ const key = dim==='field' ? 'field' : 'topic';
+ const entries = getKeywordDashboardData(key);
+ if(!entries.length){
+ alert('暂无关键词数据。');
+ return;
+ }
+ const galleryWindow = window.open('', '_blank');
+ if(!galleryWindow){
+ alert('无法打开新标签页,请检查浏览器的弹出窗口设置。');
+ return;
+ }
+ const dimLabel = TOPIC_VIZ_LABELS[key] || '高频关键词';
+ const totalDocs = entries.reduce((sum,item)=>sum+safeNumber(item.documentCount||0),0);
+ const totalTokens = entries.reduce((sum,item)=>sum+safeNumber(item.totalTokens||0),0);
+ const summaryParts=[`分类 ${entries.length} 个`];
+ if(totalDocs>0){ summaryParts.push(`覆盖文献 ${numberFormatter.format(totalDocs)}`); }
+ if(totalTokens>0){ summaryParts.push(`总词频 ${numberFormatter.format(totalTokens)}`); }
+ const summaryText = summaryParts.join(' · ');
+ const locale = (()=>{
+ try{
+ const resolved=numberFormatter.resolvedOptions();
+ if(resolved && resolved.locale){
+ return resolved.locale;
+ }
+ }catch(e){}
+ return 'zh-CN';
+ })();
+ const doc = galleryWindow.document;
+ doc.open();
+ doc.write(`
+
+
+
+
高频关键词期刊展示版
+
+
+
+
+
+
+
+
+
+
+`);
+ doc.close();
+ galleryWindow.__KEYWORD_GALLERY_PAYLOAD__={
+ dim:key,
+ dimLabel,
+ summary:summaryText,
+ entries,
+ locale
+ };
+ const bootstrap=doc.createElement('script');
+ bootstrap.type='module';
+ bootstrap.textContent=`(() => {
+ const payload = window.__KEYWORD_GALLERY_PAYLOAD__ || {};
+ delete window.__KEYWORD_GALLERY_PAYLOAD__;
+ const root = document.getElementById('keyword_gallery_root');
+ if(!root){
+ return;
+ }
+ const entries = Array.isArray(payload.entries) ? payload.entries : [];
+ const dimensionEl = document.getElementById('keyword_gallery_dimension');
+ const titleEl = document.getElementById('keyword_gallery_title');
+ const summaryEl = document.getElementById('keyword_gallery_summary');
+ const grid = document.getElementById('keyword_gallery_grid');
+ const locale = typeof payload.locale === 'string' && payload.locale ? payload.locale : 'zh-CN';
+ let formatter;
+ try{
+ formatter = new Intl.NumberFormat(locale);
+ }catch(err){
+ formatter = new Intl.NumberFormat('zh-CN');
+ }
+ if(dimensionEl){
+ const text = typeof payload.dimLabel === 'string' ? payload.dimLabel : '';
+ dimensionEl.textContent = text;
+ if(!text){ dimensionEl.classList.add('hidden'); }
+ }
+ const titleText = (payload.dimLabel ? payload.dimLabel + ' · ' : '') + '高频关键词期刊展示版';
+ if(titleEl){
+ titleEl.textContent = titleText;
+ }
+ document.title = titleText;
+ if(summaryEl){
+ if(payload.summary){
+ summaryEl.textContent = payload.summary;
+ summaryEl.classList.remove('hidden');
+ }else{
+ summaryEl.textContent = '';
+ summaryEl.classList.add('hidden');
+ }
+ }
+ if(!grid){
+ return;
+ }
+ grid.innerHTML='';
+ if(!entries.length){
+ const empty=document.createElement('p');
+ empty.className='gallery-empty';
+ empty.textContent='暂无关键词数据。';
+ grid.appendChild(empty);
+ return;
+ }
+ entries.forEach((entry, idx)=>{
+ const card=document.createElement('article');
+ card.className='gallery-card';
+ const title=document.createElement('h2');
+ title.className='gallery-card-title';
+ const heading = typeof entry.displayLabel === 'string' && entry.displayLabel ? entry.displayLabel : (typeof entry.label === 'string' && entry.label ? entry.label : '分类 ' + (idx+1));
+ title.textContent = heading;
+ card.appendChild(title);
+ const metaParts=[];
+ const docCount = Number.isFinite(entry.documentCount) ? entry.documentCount : Number(entry.documentCount);
+ if(Number.isFinite(docCount) && docCount>0){
+ metaParts.push('文献 ' + formatter.format(docCount) + ' 篇');
+ }
+ const tokenCount = Number.isFinite(entry.totalTokens) ? entry.totalTokens : Number(entry.totalTokens);
+ if(Number.isFinite(tokenCount) && tokenCount>0){
+ metaParts.push('词频 ' + formatter.format(tokenCount));
+ }
+ if(metaParts.length){
+ const meta=document.createElement('p');
+ meta.className='gallery-card-meta';
+ meta.textContent=metaParts.join(' · ');
+ card.appendChild(meta);
+ }
+ const keywords = Array.isArray(entry.keywords) ? entry.keywords : [];
+ const weights = Array.isArray(entry.weights) ? entry.weights : [];
+ const counts = Array.isArray(entry.counts) ? entry.counts : [];
+ if(!keywords.length){
+ const empty=document.createElement('p');
+ empty.className='gallery-keyword-empty';
+ empty.textContent='暂无关键词数据。';
+ card.appendChild(empty);
+ }else{
+ const list=document.createElement('ol');
+ list.className='gallery-keyword-list';
+ keywords.forEach((keyword, kIdx)=>{
+ const item=document.createElement('li');
+ item.className='gallery-keyword-item';
+ const rank=document.createElement('span');
+ rank.className='gallery-keyword-rank';
+ rank.textContent=String(kIdx+1).padStart(2,'0');
+ const label=document.createElement('span');
+ label.className='gallery-keyword-label';
+ label.textContent=String(keyword||'');
+ const weight=document.createElement('span');
+ weight.className='gallery-keyword-weight';
+ const weightVal=Number(weights[kIdx]);
+ if(Number.isFinite(weightVal) && weightVal>0){
+ const percent=weightVal*100;
+ weight.textContent=percent>=10?percent.toFixed(1)+'%':percent.toFixed(2)+'%';
+ }else{
+ weight.textContent='—';
+ }
+ const count=document.createElement('span');
+ count.className='gallery-keyword-count';
+ const countVal=Number(counts[kIdx]);
+ if(Number.isFinite(countVal) && countVal>0){
+ count.textContent='词频 '+formatter.format(countVal);
+ }else{
+ count.textContent='';
+ count.classList.add('hidden');
+ }
+ item.appendChild(rank);
+ item.appendChild(label);
+ item.appendChild(weight);
+ item.appendChild(count);
+ list.appendChild(item);
+ });
+ card.appendChild(list);
+ }
+ grid.appendChild(card);
+ });
+ })();`;
+ doc.body.appendChild(bootstrap);
+}
+
function renderTopicVizScatter(dim){
const cfg = getTopicVizConfig(dim);
const chartEl = getTopicVizChartEl(dim, 'scatter');
@@ -6386,6 +6617,10 @@
Top 5 期刊明细
document.addEventListener('click', handleJournalOpenClick);
JOURNAL_OPEN_HANDLER_BOUND=true;
}
+ if(!KEYWORD_GALLERY_HANDLER_BOUND){
+ document.addEventListener('click', handleKeywordGalleryClick);
+ KEYWORD_GALLERY_HANDLER_BOUND=true;
+ }
document.querySelectorAll('[data-chart-toggle]').forEach(wrapper=>{
const category=wrapper?.dataset?.chartToggle;
if(!category) return;
diff --git a/static/style.css b/static/style.css
index 2191203..c92c3a4 100644
--- a/static/style.css
+++ b/static/style.css
@@ -121,9 +121,14 @@ button.is-disabled{ cursor:not-allowed; }
.topic-viz-content{display:grid;gap:1.5rem;}
@media (min-width:768px){.topic-viz-content{grid-template-columns:repeat(2,minmax(0,1fr));}}
.topic-viz-card{display:flex;flex-direction:column;gap:0.85rem;border-radius:1.2rem;background:rgba(248,250,252,0.9);border:1px solid rgba(148,163,184,0.35);padding:1rem;box-shadow:0 24px 45px -40px rgba(15,23,42,0.55);min-height:100%;}
-.topic-viz-card-header{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:0.5rem;}
+.topic-viz-card-header{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:0.75rem;}
+.topic-viz-card-heading{display:flex;flex-direction:column;gap:0.3rem;min-width:0;}
.topic-viz-card-title{font-size:0.95rem;font-weight:600;color:#0f172a;}
.topic-viz-hint{font-size:0.75rem;color:#64748b;}
+.topic-viz-card-actions{display:flex;align-items:center;gap:0.5rem;flex-wrap:wrap;}
+.topic-viz-gallery-btn{display:inline-flex;align-items:center;justify-content:center;padding:0.32rem 0.85rem;border-radius:0.75rem;font-size:0.72rem;font-weight:600;color:#1d4ed8;background:rgba(191,219,254,0.35);border:1px solid rgba(37,99,235,0.3);transition:background-color .15s ease,color .15s ease,border-color .15s ease,box-shadow .15s ease;}
+.topic-viz-gallery-btn:hover{background:rgba(191,219,254,0.55);color:#1e3a8a;border-color:rgba(37,99,235,0.45);box-shadow:0 12px 28px -22px rgba(37,99,235,0.55);}
+.topic-viz-gallery-btn:focus-visible{outline:2px solid rgba(37,99,235,0.45);outline-offset:2px;}
.topic-viz-chart{position:relative;width:100%;min-height:280px;border-radius:1rem;background:rgba(255,255,255,0.85);border:1px solid rgba(148,163,184,0.3);padding:0.5rem;display:flex;flex-direction:column;gap:0.75rem;justify-content:flex-start;overflow:auto;}
.topic-viz-scatter{position:relative;width:100%;min-height:280px;border-radius:1rem;background:rgba(255,255,255,0.85);border:1px solid rgba(148,163,184,0.3);padding:0.5rem;}
.word-cloud-panels{display:grid;gap:1.5rem;}