详情页美化: 评分明细改中文标签+五格能量条卡片, 门槛命中改绿徽章/拦截红徽章, 简历全文按段落标题分节高亮重排

This commit is contained in:
mzl92
2026-08-27 16:03:01 +08:00
parent 2faaa6f032
commit 022546f8d3
+30 -3
View File
@@ -41,6 +41,19 @@ tr:hover td{background:#f8fafc;cursor:pointer}
.err{color:var(--bad);font-size:13px;margin-bottom:8px;min-height:18px}
.note{color:var(--sub);font-size:12px;margin-top:10px;text-align:center}
.detail pre{white-space:pre-wrap;background:#f9fafb;border:1px solid var(--line);border-radius:8px;padding:12px;font-size:12px;max-height:400px;overflow:auto}
.scores{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:6px;margin:8px 0 10px}
.sc{background:#f9fafb;border:1px solid var(--line);border-radius:8px;padding:6px 10px;display:flex;align-items:center;gap:8px}
.sc .k{font-size:12px;color:var(--sub);flex:0 0 56px}
.sc .bars{display:flex;gap:2px}
.sc .bars i{width:13px;height:8px;border-radius:2px;background:#e5e7eb}
.sc .bars i.on{background:var(--ok)}
.sc b{margin-left:auto;font-size:13px}
.chips{display:flex;flex-wrap:wrap;gap:6px;margin:4px 0 12px}
.chip{padding:2px 12px;border-radius:12px;font-size:12px}
.chip.ok{background:#dcfce7;color:#15803d}
.chip.bad{background:#fee2e2;color:#b91c1c}
.resume{white-space:pre-wrap;font-size:14px;line-height:1.9;background:#f9fafb;border:1px solid var(--line);border-radius:8px;padding:14px 18px;max-height:460px;overflow:auto;color:#374151}
.secTitle{display:inline-block;font-weight:600;color:var(--pri);margin-top:8px;border-bottom:2px solid #dbeafe;padding-bottom:2px}
.kv{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px;margin-bottom:12px}
.kv div{background:#f9fafb;border-radius:6px;padding:6px 10px}.kv span{color:var(--sub)}
.bars{display:flex;align-items:flex-end;gap:10px;height:120px;padding:8px 4px}
@@ -306,7 +319,21 @@ function page(d){const n=pageCur+d;if(n>=1&&n<=pageTotal)loadList(n)}
function exportCsv(){location.href='/api/export.csv'+qs()}
async function showDetail(id){
const r=await api('/api/candidates/'+encodeURIComponent(id));
let det='';try{const j=JSON.parse(r.score_details);det=Object.entries(j).map(([k,v])=>`${k}=${Array.isArray(v)?JSON.stringify(v):v}`).join(' ')}catch{}
// 评分明细 → 中文标签 + 五格能量条
let detHtml='';
try{
const j=JSON.parse(r.score_details||'{}');
const names={industryScore:'行业匹配',tradeScore:'外贸核心',langScore:'语言能力',channelScore:'客户开发',majorScore:'专业背景',stabilityScore:'稳定性',activityScore:'活跃度'};
const dims=Object.entries(j).filter(([k])=>names[k]);
if(dims.length)detHtml+='<div class="scores">'+dims.map(([k,v])=>`<div class="sc"><span class="k">${names[k]}</span><span class="bars">${[1,2,3,4,5].map(i=>`<i class="${i<=(+v||0)?'on':''}"></i>`).join('')}</span><b>${v??'-'}</b></div>`).join('')+'</div>';
const gr=j.gateReasons?(Array.isArray(j.gateReasons)?j.gateReasons:[j.gateReasons]):[];
if(gr.length)detHtml+='<div class="chips">'+gr.map(x=>`<span class="chip ok">✓ ${esc(String(x))}</span>`).join('')+'</div>';
if(j.gateReason)detHtml+=`<div class="chips"><span class="chip bad">✗ ${esc(j.gateReason)}</span></div>`;
}catch{}
// 简历全文 → 按段落标题分节高亮
let body=esc(r.raw_text||'(无)');
for(const t of ['教育经历','工作经历','项目经历','个人优势','专业技能','语言能力','所获证书','证书','求职期望','岗位经验'])
body=body.replace(new RegExp('^('+t+')$','gm'),'<span class="secTitle">$1</span>');
$('detailBody').innerHTML=`<h3>${esc(r.name)} <span class="tag ${tagCls[r.score_level]||'no'}">${esc(r.score_level)}</span> ${r.score}分 ${r.role_tag?`<span class="tag yes">${esc(r.role_tag)}</span>`:''}</h3>
<div class="kv">
<div><span>年龄</span> ${r.age??'-'}(${r.exp_years??'?'}年经验)</div>
@@ -318,8 +345,8 @@ async function showDetail(id){
<div><span>抓取</span> ${esc(r.scraped_at)}</div>
<div><span>门槛</span> ${esc(r.notes)||'-'}</div>
</div>
${det?`<div style="margin-bottom:10px;color:var(--sub)">${esc(det)}</div>`:''}
<h3 style="font-size:13px">简历全文</h3><pre>${esc(r.raw_text)}</pre>`;
${detHtml}
<h3 style="font-size:13px">简历全文</h3><div class="resume">${body}</div>`;
$('detailModal').classList.remove('hidden');
}
async function openUserModal(){