/** * SMS 验证码服务前端 JavaScript * 主要功能:短信记录查询和验证码获取 */ document.addEventListener("DOMContentLoaded", function() { // 初始化常量和变量 const PAGE_SIZE = 10; let currentPage = 1; let totalRecords = 0; // DOM 元素 const codeQueryForm = document.getElementById('codeQueryForm'); const queryResult = document.getElementById('queryResult'); const resultContent = document.getElementById('resultContent'); const smsRecordsTable = document.getElementById('smsRecordsTable'); const loadingIndicator = document.getElementById('loadingIndicator'); const noRecords = document.getElementById('noRecords'); const pagination = document.getElementById('pagination'); const refreshButton = document.querySelector('.refresh-records'); // 绑定事件监听器 if (codeQueryForm) { codeQueryForm.addEventListener('submit', function(e) { e.preventDefault(); queryVerificationCode(); }); } if (refreshButton) { refreshButton.addEventListener('click', function() { loadSMSRecords(currentPage); }); } // 初始化工具提示 initializeTooltips(); // 加载短信记录 loadSMSRecords(currentPage); // 监听窗口大小变化,调整表格显示 window.addEventListener('resize', function() { adjustTableColumns(); }); /** * 初始化工具提示 */ function initializeTooltips() { // 如果Bootstrap tooltip可用 if (typeof bootstrap !== 'undefined' && bootstrap.Tooltip) { const tooltips = document.querySelectorAll('[data-bs-toggle="tooltip"]'); tooltips.forEach(tooltip => { new bootstrap.Tooltip(tooltip); }); } } /** * 加载短信记录 * @param {number} page - 页码 * @param {number} limit - 每页记录数 */ function loadSMSRecords(page = 1, limit = PAGE_SIZE) { const offset = (page - 1) * limit; // 显示加载指示器 if (loadingIndicator) { loadingIndicator.classList.remove('d-none'); } // 隐藏无记录提示 if (noRecords) { noRecords.classList.add('d-none'); } // 清空表格内容 if (smsRecordsTable) { const tableBody = smsRecordsTable.querySelector('tbody'); if (tableBody) { tableBody.innerHTML = ''; } } // 获取短信记录 fetch(`/v1/sms/history?limit=${limit}&offset=${offset}`) .then(response => { if (!response.ok) { throw new Error('网络响应异常'); } return response.json(); }) .then(records => { // 隐藏加载指示器 if (loadingIndicator) { loadingIndicator.classList.add('d-none'); } if (records.length === 0) { // 显示无记录提示 if (noRecords) { noRecords.classList.remove('d-none'); } if (pagination) { pagination.innerHTML = ''; } } else { // 更新记录总数 totalRecords = records.length === limit ? -1 : offset + records.length; // 渲染记录 renderRecords(records); // 生成分页 generatePagination(page, limit, records.length === limit); // 调整表格列 adjustTableColumns(); } }) .catch(error => { console.error('获取短信记录失败:', error); showToast('error', '获取短信记录失败', error.message); // 隐藏加载指示器 if (loadingIndicator) { loadingIndicator.classList.add('d-none'); } if (smsRecordsTable) { const tableBody = smsRecordsTable.querySelector('tbody'); if (tableBody) { tableBody.innerHTML = ` 加载失败: ${error.message} `; } } }); } /** * 渲染短信记录到表格 * @param {Array} records - 短信记录数组 */ function renderRecords(records) { const tableBody = smsRecordsTable.querySelector('tbody'); if (!tableBody) return; tableBody.innerHTML = ''; records.forEach(record => { const row = document.createElement('tr'); row.classList.add('fade-in'); // 格式化日期时间 const date = new Date(record.receive_time); const formattedDate = formatDateTime(date); // 应用敏感信息掩码 const maskedSMS = maskSensitiveInfo(record.sms); const maskedFrom = maskSensitiveInfo(record.from_ || record.from); const maskedSimSlot = record.sim_slot ? maskSensitiveInfo(record.sim_slot) : ''; // 为验证码创建适当的徽章 const codeElement = record.extracted_code ? `${record.extracted_code}` : `未提取`; row.innerHTML = ` ${record.id} ${maskedFrom} ${maskedSMS} ${codeElement} ${formattedDate} `; tableBody.appendChild(row); }); // 添加查看按钮事件监听 tableBody.querySelectorAll('.view-sms').forEach(button => { button.addEventListener('click', function() { const smsId = this.getAttribute('data-id'); viewSMSDetail(smsId); }); }); // 重新初始化工具提示 initializeTooltips(); } /** * 生成分页控件 * @param {number} currentPage - 当前页码 * @param {number} limit - 每页记录数 * @param {boolean} hasMore - 是否有更多记录 */ function generatePagination(currentPage, limit, hasMore) { if (!pagination) return; pagination.innerHTML = ''; // 上一页按钮 const prevItem = document.createElement('li'); prevItem.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`; prevItem.innerHTML = ``; if (currentPage > 1) { prevItem.querySelector('a').addEventListener('click', (e) => { e.preventDefault(); loadSMSRecords(currentPage - 1, limit); }); } pagination.appendChild(prevItem); // 当前页 const currentItem = document.createElement('li'); currentItem.className = 'page-item active'; currentItem.innerHTML = `${currentPage}`; pagination.appendChild(currentItem); // 下一页按钮 const nextItem = document.createElement('li'); nextItem.className = `page-item ${!hasMore ? 'disabled' : ''}`; nextItem.innerHTML = ``; if (hasMore) { nextItem.querySelector('a').addEventListener('click', (e) => { e.preventDefault(); loadSMSRecords(currentPage + 1, limit); }); } pagination.appendChild(nextItem); } /** * 查询验证码 */ function queryVerificationCode() { const phoneNumber = document.getElementById('phoneNumber').value; const platformKeyword = document.getElementById('platformKeyword').value; const timeout = document.getElementById('timeout').value; if (!phoneNumber) { showToast('warning', '请输入手机号码', '请输入有效的手机号码以查询验证码'); return; } // 显示查询结果区域 if (queryResult) { queryResult.classList.remove('d-none'); } // 显示加载状态 if (resultContent) { resultContent.innerHTML = `

正在查询验证码,请稍候...

`; } // 构建请求URL let url = `/v1/sms/code?phone_number=${encodeURIComponent(phoneNumber)}&wait_timeout=${timeout}`; if (platformKeyword) { url += `&platform_keyword=${encodeURIComponent(platformKeyword)}`; } // 发送请求 fetch(url) .then(response => { if (!response.ok) { throw new Error('网络响应异常'); } return response.json(); }) .then(data => { // 显示查询结果 if (resultContent) { if (data.result === 'ok') { const maskedSmsExcerpt = maskSensitiveInfo(data.data.sms_excerpt); resultContent.innerHTML = `
验证码获取成功!

验证码:
${data.data.code}
接收时间:
${formatDateTime(new Date(data.data.received_time))}
短信内容:
${maskedSmsExcerpt}...
`; // 添加复制验证码功能 const copyButton = resultContent.querySelector('.copy-code'); if (copyButton) { copyButton.addEventListener('click', function() { const code = this.getAttribute('data-code'); copyToClipboard(code); showToast('success', '已复制验证码', `验证码 ${code} 已复制到剪贴板`); }); } } else { resultContent.innerHTML = `
未找到验证码

${data.message}

请检查手机号码是否正确,或稍后再试。

`; } } // 刷新短信记录,以便查看最新记录 loadSMSRecords(1); }) .catch(error => { console.error('查询验证码失败:', error); if (resultContent) { resultContent.innerHTML = `
查询失败

发生错误: ${escapeHtml(error.message)}

请检查网络连接或稍后再试。

`; } }); } /** * 查看短信详情 * @param {string} smsId - 短信ID */ function viewSMSDetail(smsId) { // 显示加载中模态框 showLoadingModal('正在加载短信详情...'); fetch(`/v1/sms/${smsId}`) .then(response => { if (!response.ok) { throw new Error('网络响应异常'); } return response.json(); }) .then(data => { // 关闭加载中模态框 closeModal('loadingModal'); // 应用敏感信息掩码 const maskedSMS = maskSensitiveInfo(data.sms); const maskedFrom = maskSensitiveInfo(data.from_ || data.from); const maskedSimSlot = data.sim_slot ? maskSensitiveInfo(data.sim_slot) : '未知'; const maskedPhoneNumber = data.phone_number ? maskSensitiveInfo(data.phone_number) : '未提取'; // 创建详情模态框 const modalHtml = ` `; // 添加到DOM并显示 const modalContainer = document.createElement('div'); modalContainer.innerHTML = modalHtml; document.body.appendChild(modalContainer); const modal = new bootstrap.Modal(document.getElementById('smsDetailModal')); modal.show(); // 添加复制验证码功能 const copyButton = document.querySelector('.copy-code'); if (copyButton) { copyButton.addEventListener('click', function() { const code = this.getAttribute('data-code'); copyToClipboard(code); showToast('success', '已复制验证码', `验证码 ${code} 已复制到剪贴板`); }); } // 模态框关闭后从DOM中移除 document.getElementById('smsDetailModal').addEventListener('hidden.bs.modal', function () { this.remove(); }); }) .catch(error => { // 关闭加载中模态框 closeModal('loadingModal'); console.error('获取短信详情失败:', error); showToast('error', '获取短信详情失败', error.message); }); } /** * 显示加载中模态框 * @param {string} message - 加载提示信息 */ function showLoadingModal(message) { const modalHtml = ` `; const modalContainer = document.createElement('div'); modalContainer.innerHTML = modalHtml; document.body.appendChild(modalContainer); const modal = new bootstrap.Modal(document.getElementById('loadingModal')); modal.show(); } /** * 关闭模态框 * @param {string} modalId - 模态框ID */ function closeModal(modalId) { const modalElement = document.getElementById(modalId); if (modalElement) { const modal = bootstrap.Modal.getInstance(modalElement); if (modal) { modal.hide(); modalElement.addEventListener('hidden.bs.modal', function() { this.remove(); }); } else { modalElement.remove(); } } } /** * 复制文本到剪贴板 * @param {string} text - 要复制的文本 */ function copyToClipboard(text) { // 创建临时文本区域 const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); // 执行复制命令 try { document.execCommand('copy'); } catch (err) { console.error('复制失败:', err); } // 移除临时文本区域 document.body.removeChild(textArea); } /** * 显示提示消息 * @param {string} type - 提示类型(success, warning, error, info) * @param {string} title - 提示标题 * @param {string} message - 提示内容 */ function showToast(type, title, message) { // 映射类型到Bootstrap颜色 const typeMap = { 'success': 'bg-success', 'warning': 'bg-warning', 'error': 'bg-danger', 'info': 'bg-info' }; // 映射类型到图标 const iconMap = { 'success': 'fas fa-check-circle', 'warning': 'fas fa-exclamation-triangle', 'error': 'fas fa-times-circle', 'info': 'fas fa-info-circle' }; // 创建Toast元素 const toastId = `toast-${Date.now()}`; const toastHtml = ` `; // 创建或获取Toast容器 let toastContainer = document.querySelector('.toast-container'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.className = 'toast-container position-fixed bottom-0 end-0 p-3'; document.body.appendChild(toastContainer); } // 添加Toast到容器 const toastElement = document.createElement('div'); toastElement.innerHTML = toastHtml; toastContainer.appendChild(toastElement.firstChild); // 显示Toast const toast = new bootstrap.Toast(document.getElementById(toastId)); toast.show(); // Toast关闭后移除DOM元素 document.getElementById(toastId).addEventListener('hidden.bs.toast', function() { this.remove(); if (toastContainer.children.length === 0) { toastContainer.remove(); } }); } /** * 格式化日期时间 * @param {Date} date - 日期对象 * @returns {string} 格式化后的日期时间字符串 */ function formatDateTime(date) { if (!date || isNaN(date.getTime())) return '未知时间'; return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' }); } /** * 转义HTML特殊字符 * @param {string} text - 要转义的文本 * @returns {string} 转义后的文本 */ function escapeHtml(text) { if (!text) return ''; return text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * 根据窗口宽度调整表格列显示 */ function adjustTableColumns() { if (!smsRecordsTable) return; const tableHeaders = smsRecordsTable.querySelectorAll('th'); // 如果窗口宽度小于768px,隐藏某些列 if (window.innerWidth < 768) { // 例如,隐藏ID列和操作列之外的第3列 tableHeaders.forEach((th, index) => { if (index === 2) { // 短信内容列 th.style.width = '30%'; } }); } else { // 重置列宽 tableHeaders.forEach((th, index) => { if (index === 2) { // 短信内容列 th.style.width = 'auto'; } }); } } }); // 在文档加载完成后添加样式类 document.addEventListener('DOMContentLoaded', function() { // 为导航栏添加阴影效果 const navbar = document.querySelector('.navbar'); if (navbar) { window.addEventListener('scroll', function() { if (window.scrollY > 0) { navbar.classList.add('navbar-shadow'); } else { navbar.classList.remove('navbar-shadow'); } }); } }); // 处理敏感信息的函数 function maskPhoneNumber(phone) { if (!phone) return ''; // 查找符合手机号格式的内容 const phonePattern = /1[3-9]\d{9}/g; return phone.replace(phonePattern, match => { // 保留前3位和后4位,中间用星号替换 return match.substring(0, 3) + '****' + match.substring(match.length - 4); }); } function maskSensitiveInfo(text) { if (!text) return ''; // 处理可能包含的手机号 let maskedText = maskPhoneNumber(text); // 处理身份证号 (18位或15位) const idCardPattern = /(\d{6})\d{8,11}(\d{2}[0-9Xx]?)/g; maskedText = maskedText.replace(idCardPattern, '$1********$2'); return maskedText; }