From b9285616fbaec8d25ceef6c5f4afd7765924e45d Mon Sep 17 00:00:00 2001 From: NateAFish <47712194+izukuuuu@users.noreply.github.com> Date: Mon, 20 Oct 2025 21:23:42 +0800 Subject: [PATCH] Add journal layout for topic keyword gallery --- static/index.html | 243 +++++++++++++++++++++++++++++++++++++++++++++- static/style.css | 7 +- 2 files changed, 245 insertions(+), 5 deletions(-) diff --git a/static/index.html b/static/index.html index b56c94b..33e38d1 100644 --- a/static/index.html +++ b/static/index.html @@ -598,8 +598,13 @@

分类列表

-

高频关键词

-
+
+

高频关键词

+
+
+
+ +
@@ -626,8 +631,13 @@

分类列表

-

高频关键词

-
+
+

高频关键词

+
+
+
+ +
@@ -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(` + + + + 高频关键词期刊展示版 + + + + +
+ + +
CommCoder Studio · 高频关键词期刊展示版
+
+ +`); + 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;}