254 lines
10 KiB
JavaScript
254 lines
10 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
|
const numberInput = document.getElementById('numberInput');
|
|
const fileInput = document.getElementById('fileInput');
|
|
const dropzone = document.getElementById('dropzone');
|
|
const splitMode = document.getElementById('splitMode');
|
|
const splitValue = document.getElementById('splitValue');
|
|
const splitBtn = document.getElementById('splitBtn');
|
|
const autoDedupe = document.getElementById('autoDedupe');
|
|
const resultsContainer = document.getElementById('resultsContainer');
|
|
const totalCountEl = document.getElementById('totalCount');
|
|
const uniqueCountEl = document.getElementById('uniqueCount');
|
|
const dynamicCountsContainer = document.getElementById('dynamicCountsContainer');
|
|
const dynamicInputsList = document.getElementById('dynamicInputsList');
|
|
const unitText = document.getElementById('unitText');
|
|
const batchCountBadge = document.getElementById('batchCountBadge');
|
|
|
|
let allNumbers = [];
|
|
|
|
// Helper: Extract numbers from text (Flexible digit extraction)
|
|
const extractNumbers = (text) => {
|
|
// First try to match standard 11-digit mobile numbers
|
|
let matches = text.match(/1[3-9]\d{9}/g) || [];
|
|
|
|
// If no mobile numbers found, try matching any sequence of 5-15 digits (IDs, account numbers, etc.)
|
|
if (matches.length === 0) {
|
|
matches = text.match(/\d{5,15}/g) || [];
|
|
}
|
|
return matches;
|
|
};
|
|
|
|
// Update stats and allNumbers array
|
|
const updateStats = () => {
|
|
const text = numberInput.value;
|
|
const extracted = extractNumbers(text);
|
|
const unique = [...new Set(extracted)];
|
|
|
|
totalCountEl.textContent = extracted.length.toLocaleString();
|
|
uniqueCountEl.textContent = unique.length.toLocaleString();
|
|
|
|
allNumbers = autoDedupe.checked ? unique : extracted;
|
|
};
|
|
|
|
numberInput.addEventListener('input', updateStats);
|
|
autoDedupe.addEventListener('change', updateStats);
|
|
|
|
// Dynamic UI for split modes
|
|
const updateSplitUI = () => {
|
|
const mode = splitMode.value;
|
|
if (mode === 'count') {
|
|
unitText.textContent = '份';
|
|
generateDynamicInputs();
|
|
} else {
|
|
unitText.textContent = '条';
|
|
dynamicCountsContainer.classList.add('d-none');
|
|
}
|
|
};
|
|
|
|
const generateDynamicInputs = () => {
|
|
const numBatches = parseInt(splitValue.value);
|
|
dynamicInputsList.innerHTML = '';
|
|
|
|
if (isNaN(numBatches) || numBatches <= 1 || splitMode.value !== 'count') {
|
|
dynamicCountsContainer.classList.add('d-none');
|
|
return;
|
|
}
|
|
|
|
dynamicCountsContainer.classList.remove('d-none');
|
|
|
|
// Create N-1 inputs because the last one is always the remainder
|
|
for (let i = 0; i < numBatches - 1; i++) {
|
|
const div = document.createElement('div');
|
|
div.className = 'input-group input-group-sm mb-2';
|
|
div.innerHTML = `
|
|
<span class="input-group-text bg-white border-2 fw-bold" style="width: 100px;">第 ${i + 1} 份数量</span>
|
|
<input type="number" class="form-control border-2 batch-size-input" placeholder="留空则自动分配" min="1">
|
|
`;
|
|
dynamicInputsList.appendChild(div);
|
|
}
|
|
};
|
|
|
|
splitMode.addEventListener('change', updateSplitUI);
|
|
splitValue.addEventListener('input', generateDynamicInputs);
|
|
|
|
// File handling
|
|
const handleFile = (file) => {
|
|
if (!file) return;
|
|
const reader = new FileReader();
|
|
reader.onload = (event) => {
|
|
const content = event.target.result;
|
|
numberInput.value += (numberInput.value ? '\n' : '') + content;
|
|
updateStats();
|
|
fileInput.value = '';
|
|
};
|
|
reader.readAsText(file);
|
|
};
|
|
|
|
dropzone.addEventListener('click', () => fileInput.click());
|
|
fileInput.addEventListener('change', (e) => handleFile(e.target.files[0]));
|
|
|
|
// Drag and Drop
|
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
|
dropzone.addEventListener(eventName, (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
}, false);
|
|
});
|
|
|
|
['dragenter', 'dragover'].forEach(eventName => {
|
|
dropzone.addEventListener(eventName, () => dropzone.classList.add('drag-active'), false);
|
|
});
|
|
|
|
['dragleave', 'drop'].forEach(eventName => {
|
|
dropzone.addEventListener(eventName, () => dropzone.classList.remove('drag-active'), false);
|
|
});
|
|
|
|
dropzone.addEventListener('drop', (e) => {
|
|
const dt = e.dataTransfer;
|
|
const file = dt.files[0];
|
|
handleFile(file);
|
|
}, false);
|
|
|
|
// Splitting logic
|
|
splitBtn.addEventListener('click', () => {
|
|
updateStats();
|
|
if (allNumbers.length === 0) {
|
|
alert('请先导入或粘贴号码!');
|
|
return;
|
|
}
|
|
|
|
const mode = splitMode.value;
|
|
const val = parseInt(splitValue.value);
|
|
|
|
if (isNaN(val) || val <= 0) {
|
|
alert('请输入有效的分批数值!');
|
|
return;
|
|
}
|
|
|
|
let batches = [];
|
|
let tempNumbers = [...allNumbers];
|
|
|
|
if (mode === 'count') {
|
|
const sizeInputs = document.querySelectorAll('.batch-size-input');
|
|
let hasAnySpecificSize = false;
|
|
let specificSizes = [];
|
|
|
|
sizeInputs.forEach(input => {
|
|
const s = parseInt(input.value);
|
|
if (!isNaN(s) && s > 0) {
|
|
hasAnySpecificSize = true;
|
|
specificSizes.push(s);
|
|
} else {
|
|
specificSizes.push(null);
|
|
}
|
|
});
|
|
|
|
if (hasAnySpecificSize) {
|
|
// If some sizes are specified, follow them.
|
|
for (let i = 0; i < val - 1; i++) {
|
|
const size = specificSizes[i];
|
|
if (size !== null && size > 0) {
|
|
batches.push(tempNumbers.splice(0, size));
|
|
} else {
|
|
// For empty inputs when others are filled, we calculate an average of the remaining
|
|
const remainingSlots = val - batches.length;
|
|
const avg = Math.ceil(tempNumbers.length / remainingSlots);
|
|
batches.push(tempNumbers.splice(0, avg));
|
|
}
|
|
}
|
|
// Last batch always gets the remainder
|
|
batches.push(tempNumbers);
|
|
} else {
|
|
// Default even split if no inputs are filled
|
|
const batchSize = Math.ceil(tempNumbers.length / val);
|
|
for (let i = 0; i < val; i++) {
|
|
if (i === val - 1) {
|
|
batches.push(tempNumbers); // Last one takes all remainder
|
|
} else {
|
|
batches.push(tempNumbers.splice(0, batchSize));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Split by fixed size per batch (mode === 'size')
|
|
while (tempNumbers.length > 0) {
|
|
batches.push(tempNumbers.splice(0, val));
|
|
}
|
|
}
|
|
|
|
renderResults(batches);
|
|
});
|
|
|
|
const renderResults = (batches) => {
|
|
resultsContainer.innerHTML = '';
|
|
// Filter out empty batches if they occurred due to split logic
|
|
const finalBatches = batches.filter(b => b.length > 0);
|
|
|
|
batchCountBadge.textContent = `${finalBatches.length} 个批次`;
|
|
batchCountBadge.classList.remove('d-none');
|
|
|
|
finalBatches.forEach((batch, index) => {
|
|
const card = document.createElement('div');
|
|
card.className = 'card batch-card animate-fadeIn';
|
|
|
|
const previewText = batch.slice(0, 15).join(', ');
|
|
const hasMore = batch.length > 15;
|
|
|
|
card.innerHTML = `
|
|
<div class="card-body p-4">
|
|
<div class="batch-header">
|
|
<div class="batch-title">
|
|
<span class="badge bg-primary" style="background: var(--primary-gradient) !important;">第 ${index + 1} 份</span>
|
|
<span class="badge-count">${batch.length.toLocaleString()} 条</span>
|
|
</div>
|
|
<button class="btn btn-primary btn-sm px-3 download-btn">
|
|
<i data-lucide="download" style="width: 14px;"></i> 下载
|
|
</button>
|
|
</div>
|
|
<div class="mb-3">
|
|
<input type="text" class="form-control form-control-sm bg-light border-0 remark-input" placeholder="备注名称(即文件名)">
|
|
</div>
|
|
<div class="small text-muted font-monospace bg-light p-2 rounded" style="font-size: 0.75rem; word-break: break-all;">
|
|
预览: ${batch.length > 0 ? previewText + (hasMore ? ' ...' : '') : '无号码'}
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
resultsContainer.appendChild(card);
|
|
|
|
// Download handler
|
|
card.querySelector('.download-btn').addEventListener('click', () => {
|
|
const remark = card.querySelector('.remark-input').value.trim() ||
|
|
`batch_${index + 1}_${batch.length}`;
|
|
const blob = new Blob([batch.join('\n')], { type: 'text/plain;charset=utf-8' });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = `${remark}.txt`;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
});
|
|
});
|
|
|
|
if (window.lucide) {
|
|
lucide.createIcons();
|
|
}
|
|
|
|
resultsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
};
|
|
|
|
// Initial UI Setup
|
|
updateSplitUI();
|
|
}); |