修复竞争比和按钮问题

This commit is contained in:
杨志
2026-01-21 10:42:18 +08:00
parent 08cb6b2b03
commit bc622c6c34
6 changed files with 124 additions and 64 deletions

View File

@@ -4,6 +4,8 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>职位信息爬虫工具</title>
<!-- SheetJS库用于导出XLSX -->
<script src="/static/js/xlsx.full.min.js"></script>
<style>
* {
margin: 0;
@@ -331,7 +333,7 @@
</div>
</div>
<div class="action-buttons">
<button class="btn" onclick="fetchAllPositions()">自动抓取全部职位</button>
<button class="btn" id="fetch-all-btn" onclick="debouncedFetchAllPositions()">自动抓取全部职位</button>
</div>
</div>
@@ -339,7 +341,7 @@
<div class="form-section">
<h2>职位信息结果</h2>
<div class="action-buttons" style="margin-bottom: 15px;">
<button class="btn btn-secondary" id="export-btn" onclick="exportCsv()" disabled>导出CSV</button>
<button class="btn btn-secondary" id="export-btn" onclick="exportXlsx()" disabled>导出XLSX</button>
</div>
<div id="result-message"></div>
<div class="table-container" id="result-table" style="display: none;">
@@ -372,6 +374,24 @@
let lastResults = [];
let isCrawling = false; // 爬取状态标志
// 防抖函数
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 防抖包装的抓取函数300ms防抖
const debouncedFetchAllPositions = debounce(() => {
fetchAllPositions();
}, 300);
// 页面加载时自动加载用户配置
window.onload = function() {
loadUserConfig();
@@ -710,6 +730,12 @@
// 自动抓取全部职位代码并逐条获取详情(流式展示,避免超时)
async function fetchAllPositions() {
// 如果正在爬取,直接返回
if (isCrawling) {
showMessage('result-message', '正在爬取中,请勿重复点击', 'error');
return;
}
const selectedDsdm = getSelectedDsdm();
const examid = document.getElementById('examid').value.trim();
const bmid = document.getElementById('bmid').value.trim();
@@ -733,10 +759,19 @@
return;
}
// 开始爬取,禁用导出按钮
// 开始爬取,禁用所有相关按钮
isCrawling = true;
document.getElementById('export-btn').disabled = true;
document.getElementById('export-btn').textContent = '爬取中...';
const fetchBtnStart = document.getElementById('fetch-all-btn');
const exportBtnStart = document.getElementById('export-btn');
if (fetchBtnStart) {
fetchBtnStart.disabled = true;
fetchBtnStart.textContent = '爬取中...';
}
if (exportBtnStart) {
exportBtnStart.disabled = true;
exportBtnStart.textContent = '爬取中...';
}
// 清空旧数据
lastResults = [];
@@ -796,20 +831,22 @@
// 爬取完成,恢复按钮状态
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');
const fetchBtnEnd = document.getElementById('fetch-all-btn');
const exportBtnEnd = document.getElementById('export-btn');
// 爬取完成,启用导出按钮
isCrawling = false;
document.getElementById('export-btn').disabled = false;
document.getElementById('export-btn').textContent = '导出CSV';
if (fetchBtnEnd) {
fetchBtnEnd.disabled = false;
fetchBtnEnd.textContent = '自动抓取全部职位';
}
if (exportBtnEnd) {
exportBtnEnd.disabled = false;
exportBtnEnd.textContent = '导出XLSX';
}
showMessage('result-message', `爬取完成!共处理 ${processedCodes} 个职位`, 'success');
}
// 导出CSV
function exportCsv() {
// 导出XLSX
function exportXlsx() {
if (isCrawling) {
showMessage('result-message', '爬取进行中,请等待完成后再导出', 'error');
return;
@@ -818,8 +855,17 @@
showMessage('result-message', '暂无数据可导出', 'error');
return;
}
// 检查SheetJS库是否加载
if (typeof XLSX === 'undefined') {
showMessage('result-message', '导出库加载失败,请刷新页面重试', 'error');
return;
}
// 准备数据
const headers = ['省份','地区','招聘单位/用人司局','职位名称','职位代码','招聘人数','审核通过人数','竞争比'];
const lines = [headers.join(',')];
const data = [headers];
lastResults.forEach(item => {
const row = [
item.sbmc || '',
@@ -829,18 +875,58 @@
item.zwdm || '',
item.zprs || 0,
item.bkrs || 0,
item.competition_ratio || '0:0'
item.competition_ratio || '0:0' // 使用与网页显示一致的竞争比格式
];
lines.push(row.map(v => `"${String(v).replace(/"/g, '""')}"`).join(','));
data.push(row);
});
const blob = new Blob([lines.join('\n')], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'positions.csv';
link.click();
URL.revokeObjectURL(url);
showMessage('result-message', '已导出CSV', 'success');
// 创建工作簿
const wb = XLSX.utils.book_new();
const ws = XLSX.utils.aoa_to_sheet(data);
// 设置列宽
const colWidths = [
{ wch: 10 }, // 省份
{ wch: 15 }, // 地区
{ wch: 25 }, // 招聘单位/用人司局
{ wch: 25 }, // 职位名称
{ wch: 15 }, // 职位代码
{ wch: 12 }, // 招聘人数
{ wch: 15 }, // 审核通过人数
{ wch: 12 } // 竞争比
];
ws['!cols'] = colWidths;
// 设置表头样式(如果需要)
const headerRange = XLSX.utils.decode_range(ws['!ref']);
for (let col = headerRange.s.c; col <= headerRange.e.c; col++) {
const cellAddress = XLSX.utils.encode_cell({ r: 0, c: col });
if (!ws[cellAddress]) continue;
ws[cellAddress].s = {
font: { bold: true },
fill: { fgColor: { rgb: "E0E0E0" } },
alignment: { horizontal: "center", vertical: "center" }
};
}
// 竞争比列设置为文本格式,防止被当作数字或日期
const competitionColIndex = 7; // 第8列
for (let r = 1; r < data.length; r++) { // 从第2行开始跳过表头
const addr = XLSX.utils.encode_cell({ r, c: competitionColIndex });
if (ws[addr]) {
ws[addr].t = 's';
ws[addr].z = '@';
}
}
// 将工作表添加到工作簿
XLSX.utils.book_append_sheet(wb, ws, '职位信息');
// 导出文件
const fileName = 'positions_' + new Date().toISOString().slice(0, 10).replace(/-/g, '') + '.xlsx';
XLSX.writeFile(wb, fileName);
showMessage('result-message', '已导出XLSX', 'success');
}
// 显示消息