boss_jobs.html
5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
{% 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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
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 %}