boss_jobs.html 5.54 KB
{% extends "base.html" %}
{% block title %}BOSS 岗位 - WeChat Viewer{% endblock %}
{% block body %}
<style>
body{overflow-y:auto!important;height:auto!important;}
.jobs-container{max-width:1100px;margin:0 auto;padding:20px;}
.jobs-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px;}
.jobs-header h1{font-size:20px;color:#2c3e50;}
.jobs-header button{padding:6px 14px;background:#3498db;color:white;border:none;border-radius:6px;cursor:pointer;font-size:13px;}
.summary-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:20px;}
.sum-card{background:white;border-radius:10px;padding:14px 18px;box-shadow:0 1px 3px rgba(0,0,0,0.06);}
.sum-card .num{font-size:24px;font-weight:700;color:#2c3e50;}
.sum-card .label{font-size:12px;color:#999;margin-top:4px;}
.job-table{width:100%;border-collapse:collapse;background:white;border-radius:10px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,0.06);}
.job-table th{background:#f8f9fa;font-size:12px;color:#666;padding:12px 14px;text-align:left;border-bottom:1px solid #eee;}
.job-table td{padding:12px 14px;font-size:13px;border-bottom:1px solid #f5f5f5;vertical-align:middle;}
.job-table tr:last-child td{border-bottom:none;}
.job-table tr:hover td{background:#f8f9fa;}
.job-row{cursor:pointer;}
.job-name{font-weight:600;color:#2c3e50;}
.job-id{font-size:11px;color:#999;font-family:monospace;}
.badge{font-size:11px;padding:3px 10px;border-radius:12px;font-weight:500;white-space:nowrap;}
.badge.conf{background:#e8f8f0;color:#1a9e6c;}
.badge.unconf{background:#fdf3e7;color:#d97a1e;}
.badge.unimported{background:#f5f5f5;color:#888;}
.progress-wrap{display:flex;align-items:center;gap:8px;}
.progress-bar{width:80px;height:6px;background:#eee;border-radius:3px;overflow:hidden;}
.progress-fill{height:100%;background:#3498db;border-radius:3px;}
.empty-state{text-align:center;padding:60px 20px;color:#999;}
.loading{text-align:center;padding:40px;color:#999;}
.spinner{display:inline-block;width:24px;height:24px;border:3px solid #ddd;border-top-color:#3498db;border-radius:50%;animation:spin .6s linear infinite;}
@keyframes spin{to{transform:rotate(360deg)}}
</style>
<div class="jobs-container">
<div class="jobs-header">
<h1>🏢 BOSS 岗位</h1>
<button onclick="loadJobs()">🔄 刷新</button>
</div>
<div class="summary-cards" id="summaryCards"></div>
<div id="list"><div class="loading"><div class="spinner"></div></div></div>
</div>
<script>
var allJobs=[];

function esc(s){return (s==null?'':String(s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
function fmtTime(ms){if(!ms)return '—';var d=new Date(ms);return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0')+' '+String(d.getHours()).padStart(2,'0')+':'+String(d.getMinutes()).padStart(2,'0');}

function loadJobs(){
  var el=document.getElementById('list');
  el.innerHTML='<div class="loading"><div class="spinner"></div></div>';
  fetch('/api/boss-jobs').then(function(r){return r.json()}).then(function(j){
    if(!j.ok){el.innerHTML='<div class="empty-state">加载失败</div>';return;}
    allJobs=j.data;
    renderSummary();
    render();
  }).catch(function(e){el.innerHTML='<div class="empty-state">加载失败: '+e.message+'</div>';});
}

function renderSummary(){
  var total=allJobs.length;
  var conf=allJobs.filter(function(x){return x.configured}).length;
  var candidates=allJobs.reduce(function(a,x){return a+(x.candidates||0)},0);
  var inDb=allJobs.reduce(function(a,x){return a+(x.resumes_in_db||0)},0);
  var scored=allJobs.reduce(function(a,x){return a+(x.scored||0)},0);
  document.getElementById('summaryCards').innerHTML=
    '<div class="sum-card"><div class="num">'+total+'</div><div class="label">全部岗位</div></div>'+
    '<div class="sum-card"><div class="num">'+conf+'</div><div class="label">已配筛选标准</div></div>'+
    '<div class="sum-card"><div class="num">'+candidates+'</div><div class="label">候选人(BOSS)</div></div>'+
    '<div class="sum-card"><div class="num">'+inDb+'</div><div class="label">已入库简历</div></div>'+
    '<div class="sum-card"><div class="num">'+scored+'</div><div class="label">已打分</div></div>';
}

function render(){
  var el=document.getElementById('list');
  if(!allJobs.length){el.innerHTML='<div class="empty-state">暂无岗位数据</div>';return;}
  var h='<table class="job-table"><thead><tr>';
  h+='<th>岗位</th><th>候选人</th><th>最近沟通</th><th>入库/打分</th><th>筛选标准</th><th>Skill</th></tr></thead><tbody>';
  for(var i=0;i<allJobs.length;i++){
    var j=allJobs[i];
    var confBadge=j.configured
      ? '<span class="badge conf">rubric v'+j.rubric_version+'</span>'
      : '<span class="badge unconf">未配置</span>';
    var skillName=j.skill||'resume-screener';
    var pct=j.resumes_in_db>0?Math.round((j.scored||0)/j.resumes_in_db*100):0;
    h+='<tr class="job-row" onclick="location.href=\'/boss-job/'+j.jobId+'\'">';
    h+='<td><div class="job-name">'+esc(j.jobPosition)+'</div><div class="job-id">jobId: '+esc(j.jobId)+'</div></td>';
    h+='<td>'+j.candidates+'</td>';
    h+='<td>'+fmtTime(j.latest_chat)+'</td>';
    h+='<td><div class="progress-wrap"><div class="progress-bar"><div class="progress-fill" style="width:'+pct+'%"></div></div><span>'+(j.scored||0)+'/'+j.resumes_in_db+'</span></div></td>';
    h+='<td>'+confBadge+'</td>';
    h+='<td><span style="font-size:12px;color:#666;">'+esc(skillName)+'</span></td>';
    h+='</tr>';
  }
  h+='</tbody></table>';
  el.innerHTML=h;
}

loadJobs();
</script>
{% endblock %}