up
This commit is contained in:
@@ -275,6 +275,72 @@ class CrawlerService
|
||||
return $cookies;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算最大公约数(GCD)
|
||||
* @param int $a
|
||||
* @param int $b
|
||||
* @return int
|
||||
*/
|
||||
private function gcd(int $a, int $b): int
|
||||
{
|
||||
while ($b != 0) {
|
||||
$temp = $b;
|
||||
$b = $a % $b;
|
||||
$a = $temp;
|
||||
}
|
||||
return $a;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算竞争比(格式:招聘人数:审核通过人数,简化比例,保留2位小数)
|
||||
* @param int $zprs 招聘人数
|
||||
* @param int $bkrs 审核通过人数
|
||||
* @return string
|
||||
*/
|
||||
private function calculateCompetitionRatio(int $zprs, int $bkrs): string
|
||||
{
|
||||
if ($zprs <= 0) {
|
||||
return '0:0';
|
||||
}
|
||||
|
||||
if ($bkrs <= 0) {
|
||||
return $zprs . ':0';
|
||||
}
|
||||
|
||||
// 计算最大公约数
|
||||
$gcd = $this->gcd($zprs, $bkrs);
|
||||
|
||||
// 简化比例
|
||||
$simplifiedZprs = $zprs / $gcd;
|
||||
$simplifiedBkrs = $bkrs / $gcd;
|
||||
|
||||
// 如果两个数都是整数,直接返回
|
||||
if ($simplifiedZprs == intval($simplifiedZprs) && $simplifiedBkrs == intval($simplifiedBkrs)) {
|
||||
return intval($simplifiedZprs) . ':' . intval($simplifiedBkrs);
|
||||
}
|
||||
|
||||
// 如果招聘人数是1,审核通过人数保留2位小数
|
||||
if ($simplifiedZprs == 1) {
|
||||
$bkrsFormatted = number_format($simplifiedBkrs, 2, '.', '');
|
||||
// 如果小数部分是.00,则显示为整数
|
||||
if (floatval($bkrsFormatted) == intval($bkrsFormatted)) {
|
||||
return '1:' . intval($bkrsFormatted);
|
||||
}
|
||||
return '1:' . $bkrsFormatted;
|
||||
}
|
||||
|
||||
// 否则,将招聘人数简化为1,审核通过人数按比例计算并保留2位小数
|
||||
$ratio = $simplifiedBkrs / $simplifiedZprs;
|
||||
$ratioFormatted = number_format($ratio, 2, '.', '');
|
||||
|
||||
// 如果小数部分是.00,则显示为整数
|
||||
if (floatval($ratioFormatted) == intval($ratioFormatted)) {
|
||||
return '1:' . intval($ratioFormatted);
|
||||
}
|
||||
|
||||
return '1:' . $ratioFormatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化职位信息(包含竞争比计算)
|
||||
* @param array $item 原始职位数据
|
||||
@@ -288,10 +354,12 @@ class CrawlerService
|
||||
$item = $item[0];
|
||||
}
|
||||
|
||||
// 计算竞争比(格式:招聘人数:审核通过人数)
|
||||
// 获取招聘人数和审核通过人数
|
||||
$zprs = isset($item['zprs']) ? intval($item['zprs']) : 0;
|
||||
$bkrs = isset($item['bkrs']) ? intval($item['bkrs']) : 0;
|
||||
$competitionRatio = $zprs > 0 && $bkrs > 0 ? $zprs . ':' . $bkrs : ($zprs > 0 ? $zprs . ':0' : '0:0');
|
||||
|
||||
// 计算竞争比(格式:1:比例,保留2位小数)
|
||||
$competitionRatio = $this->calculateCompetitionRatio($zprs, $bkrs);
|
||||
|
||||
return [
|
||||
'sbmc' => $item['sbmc'] ?? '', // 省份
|
||||
@@ -301,7 +369,7 @@ class CrawlerService
|
||||
'zwdm' => $item['zwdm'] ?? $zwdm, // 职位代码
|
||||
'zprs' => $zprs, // 招聘人数
|
||||
'bkrs' => $bkrs, // 审核通过人数
|
||||
'competition_ratio' => $competitionRatio, // 竞争比(格式:招聘人数:审核通过人数)
|
||||
'competition_ratio' => $competitionRatio, // 竞争比(格式:1:比例,保留2位小数)
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -233,6 +233,34 @@
|
||||
.select-all {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dsdm-checkbox-item {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dsdm-checkbox-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.dsdm-checkbox-item label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dsdm-checkbox-item input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#dsdm-list {
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -291,10 +319,16 @@
|
||||
<h2>第二步:选择地区并自动抓取</h2>
|
||||
<div id="dsdm-message"></div>
|
||||
<div class="form-group">
|
||||
<label for="dsdm">地区代码(dsdm):</label>
|
||||
<select id="dsdm">
|
||||
<option value="">请先获取地区选项</option>
|
||||
</select>
|
||||
<label>地区代码(dsdm):</label>
|
||||
<div class="select-all">
|
||||
<label style="display: inline-flex; align-items: center; cursor: pointer;">
|
||||
<input type="checkbox" id="select-all-dsdm" onchange="toggleAllDsdm()" style="width: auto; margin-right: 8px;">
|
||||
<span>全选</span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="dsdm-list" style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; border-radius: 4px; padding: 10px;">
|
||||
<div style="color: #999; text-align: center; padding: 20px;">请先获取地区选项</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button class="btn" onclick="fetchAllPositions()">自动抓取全部职位</button>
|
||||
@@ -430,17 +464,20 @@
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.code === 1) {
|
||||
const select = document.getElementById('dsdm');
|
||||
select.innerHTML = '<option value="">请选择</option>';
|
||||
|
||||
if (data.code === 1 && Array.isArray(data.data)) {
|
||||
const dsdmList = document.getElementById('dsdm-list');
|
||||
dsdmList.innerHTML = '';
|
||||
data.data.forEach(option => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = option.value;
|
||||
opt.textContent = option.text;
|
||||
select.appendChild(opt);
|
||||
const div = document.createElement('div');
|
||||
div.className = 'dsdm-checkbox-item';
|
||||
div.innerHTML = `
|
||||
<label>
|
||||
<input type="checkbox" class="dsdm-checkbox" value="${option.value}" onchange="updateSelectAllDsdm()">
|
||||
<span>${option.label || option.text} (${option.value})</span>
|
||||
</label>
|
||||
`;
|
||||
dsdmList.appendChild(div);
|
||||
});
|
||||
|
||||
showMessage('dsdm-message', '获取地区选项成功,请选择地区', 'success');
|
||||
} else {
|
||||
showMessage('dsdm-message', data.msg || '获取失败', 'error');
|
||||
@@ -451,6 +488,29 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 全选/取消全选地区
|
||||
function toggleAllDsdm() {
|
||||
const selectAll = document.getElementById('select-all-dsdm').checked;
|
||||
const checkboxes = document.querySelectorAll('.dsdm-checkbox');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = selectAll;
|
||||
});
|
||||
}
|
||||
|
||||
// 更新全选复选框状态
|
||||
function updateSelectAllDsdm() {
|
||||
const checkboxes = document.querySelectorAll('.dsdm-checkbox');
|
||||
const checkedCount = document.querySelectorAll('.dsdm-checkbox:checked').length;
|
||||
const selectAllCheckbox = document.getElementById('select-all-dsdm');
|
||||
selectAllCheckbox.checked = checkboxes.length > 0 && checkedCount === checkboxes.length;
|
||||
}
|
||||
|
||||
// 获取选中的地区代码数组
|
||||
function getSelectedDsdm() {
|
||||
const checkboxes = document.querySelectorAll('.dsdm-checkbox:checked');
|
||||
return Array.from(checkboxes).map(cb => cb.value);
|
||||
}
|
||||
|
||||
// 获取职位代码列表
|
||||
function getZwdmList() {
|
||||
const dsdm = document.getElementById('dsdm').value;
|
||||
@@ -633,7 +693,7 @@
|
||||
<td>${item.zwdm || ''}</td>
|
||||
<td>${item.zprs || 0}</td>
|
||||
<td>${item.bkrs || 0}</td>
|
||||
<td>${item.competition_ratio || '0.00'}</td>
|
||||
<td>${item.competition_ratio || '0:0'}</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
document.getElementById('result-table').style.display = 'block';
|
||||
@@ -650,15 +710,15 @@
|
||||
|
||||
// 自动抓取全部职位代码并逐条获取详情(流式展示,避免超时)
|
||||
async function fetchAllPositions() {
|
||||
const dsdm = document.getElementById('dsdm').value;
|
||||
const selectedDsdm = getSelectedDsdm();
|
||||
const examid = document.getElementById('examid').value.trim();
|
||||
const bmid = document.getElementById('bmid').value.trim();
|
||||
const userid = document.getElementById('userid').value.trim();
|
||||
const cookieData = buildCookiesPayload('result-message');
|
||||
const aa = lastAa;
|
||||
|
||||
if (!dsdm) {
|
||||
showMessage('dsdm-message', '请先选择地区', 'error');
|
||||
if (!selectedDsdm || selectedDsdm.length === 0) {
|
||||
showMessage('dsdm-message', '请先选择至少一个地区', 'error');
|
||||
return;
|
||||
}
|
||||
if (!examid || !bmid || !userid) {
|
||||
@@ -666,7 +726,7 @@
|
||||
return;
|
||||
}
|
||||
if (!aa) {
|
||||
showMessage('result-message', '请先点击“获取地区选项”生成aa', 'error');
|
||||
showMessage('result-message', '请先点击"获取地区选项"生成aa', 'error');
|
||||
return;
|
||||
}
|
||||
if (!cookieData) {
|
||||
@@ -683,50 +743,63 @@
|
||||
const tbody = document.getElementById('data-table-body');
|
||||
tbody.innerHTML = '';
|
||||
document.getElementById('result-table').style.display = 'block';
|
||||
showMessage('result-message', '正在获取职位代码列表...', 'info');
|
||||
|
||||
// 获取全部职位代码
|
||||
const listResp = await fetch('/crawler/getZwdmList', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: `dsdm=${encodeURIComponent(dsdm)}&examid=${encodeURIComponent(examid)}&bmid=${encodeURIComponent(bmid)}&userid=${encodeURIComponent(userid)}&aa=${encodeURIComponent(aa)}&cookies=${encodeURIComponent(JSON.stringify(cookieData))}`
|
||||
}).then(r => r.json()).catch(e => ({ code: 0, msg: e.message }));
|
||||
let totalCodes = 0;
|
||||
let processedCodes = 0;
|
||||
|
||||
if (listResp.code !== 1 || !Array.isArray(listResp.data) || listResp.data.length === 0) {
|
||||
showMessage('result-message', listResp.msg || '未获取到职位代码', 'error');
|
||||
// 爬取失败,恢复按钮状态
|
||||
isCrawling = false;
|
||||
document.getElementById('export-btn').disabled = false;
|
||||
document.getElementById('export-btn').textContent = '导出CSV';
|
||||
return;
|
||||
}
|
||||
// 遍历所有选中的地区
|
||||
for (let dsdmIndex = 0; dsdmIndex < selectedDsdm.length; dsdmIndex++) {
|
||||
const dsdm = selectedDsdm[dsdmIndex];
|
||||
showMessage('result-message', `正在处理地区 ${dsdm} (${dsdmIndex + 1}/${selectedDsdm.length})...`, 'info');
|
||||
|
||||
// 过滤掉152开头的职位代码
|
||||
const codes = listResp.data
|
||||
.map(it => it.zwdm)
|
||||
.filter(code => !code.startsWith('152'));
|
||||
showMessage('result-message', `共 ${codes.length} 个职位(已跳过152开头),开始逐条获取...`, 'info');
|
||||
|
||||
// 逐条获取职位详情
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
const code = codes[i];
|
||||
const infoResp = await fetch(API_BASE_URL + '/crawler/getPositionInfo', {
|
||||
// 获取该地区的全部职位代码
|
||||
const listResp = await fetch(API_BASE_URL + '/crawler/getZwdmList', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: `zwdm=${encodeURIComponent(code)}&examid=${encodeURIComponent(examid)}&cookies=${encodeURIComponent(JSON.stringify(cookieData))}`
|
||||
body: `dsdm=${encodeURIComponent(dsdm)}&examid=${encodeURIComponent(examid)}&bmid=${encodeURIComponent(bmid)}&userid=${encodeURIComponent(userid)}&aa=${encodeURIComponent(aa)}&cookies=${encodeURIComponent(JSON.stringify(cookieData))}`
|
||||
}).then(r => r.json()).catch(e => ({ code: 0, msg: e.message }));
|
||||
|
||||
if (infoResp.code === 1 && infoResp.data) {
|
||||
const item = infoResp.data;
|
||||
lastResults.push(item);
|
||||
appendResultRow(item);
|
||||
} else {
|
||||
appendErrorRow(code, infoResp.msg || '获取失败');
|
||||
if (listResp.code !== 1 || !Array.isArray(listResp.data) || listResp.data.length === 0) {
|
||||
showMessage('result-message', `地区 ${dsdm} 获取职位代码失败: ${listResp.msg || '未获取到职位代码'}`, 'error');
|
||||
continue;
|
||||
}
|
||||
|
||||
showMessage('result-message', `进度:${i + 1}/${codes.length}`, 'info');
|
||||
// 过滤掉152开头的职位代码
|
||||
const codes = listResp.data
|
||||
.map(it => it.zwdm)
|
||||
.filter(code => !code.startsWith('152'));
|
||||
|
||||
totalCodes += codes.length;
|
||||
showMessage('result-message', `地区 ${dsdm}: 共 ${codes.length} 个职位(已跳过152开头),开始逐条获取...`, 'info');
|
||||
|
||||
// 逐条获取职位详情
|
||||
for (let i = 0; i < codes.length; i++) {
|
||||
const code = codes[i];
|
||||
const infoResp = await fetch(API_BASE_URL + '/crawler/getPositionInfo', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: `zwdm=${encodeURIComponent(code)}&examid=${encodeURIComponent(examid)}&cookies=${encodeURIComponent(JSON.stringify(cookieData))}`
|
||||
}).then(r => r.json()).catch(e => ({ code: 0, msg: e.message }));
|
||||
|
||||
if (infoResp.code === 1 && infoResp.data) {
|
||||
const item = infoResp.data;
|
||||
lastResults.push(item);
|
||||
appendResultRow(item);
|
||||
} else {
|
||||
appendErrorRow(code, infoResp.msg || '获取失败');
|
||||
}
|
||||
|
||||
processedCodes++;
|
||||
showMessage('result-message', `总进度:${processedCodes}/${totalCodes} (地区 ${dsdmIndex + 1}/${selectedDsdm.length})`, 'info');
|
||||
}
|
||||
}
|
||||
|
||||
// 爬取完成,恢复按钮状态
|
||||
isCrawling = false;
|
||||
document.getElementById('export-btn').disabled = false;
|
||||
document.getElementById('export-btn').textContent = '导出CSV';
|
||||
showMessage('result-message', `爬取完成!共处理 ${processedCodes} 个职位`, 'success');
|
||||
|
||||
showMessage('result-message', `完成,共 ${lastResults.length} 条成功,失败 ${codes.length - lastResults.length} 条`, 'success');
|
||||
|
||||
// 爬取完成,启用导出按钮
|
||||
|
||||
Reference in New Issue
Block a user