// 二维码生成器 JavaScript
class QRCodeGenerator {
constructor() {
this.apiEndpoints = [];
this.currentApiIndex = 0;
this.init();
}
// 初始化
async init() {
await this.loadApiEndpoints();
this.bindEvents();
this.setupFormValidation();
}
// 加载API接口列表
async loadApiEndpoints() {
try {
const response = await fetch('./接口集合.json');
this.apiEndpoints = await response.json();
console.log('已加载API接口:', this.apiEndpoints);
} catch (error) {
console.error('加载API接口失败:', error);
this.showError('加载配置失败,请刷新页面重试');
}
}
// 绑定事件
bindEvents() {
const form = document.getElementById('qrForm');
const retryBtn = document.querySelector('.retry-btn');
const downloadBtn = document.querySelector('.download-btn');
const copyBtn = document.querySelector('.copy-btn');
const newBtn = document.querySelector('.new-btn');
form.addEventListener('submit', (e) => this.handleSubmit(e));
retryBtn.addEventListener('click', () => this.retryGeneration());
downloadBtn.addEventListener('click', () => this.downloadQRCode());
copyBtn.addEventListener('click', () => this.copyImageLink());
newBtn.addEventListener('click', () => this.resetForm());
// 实时字符计数
const textArea = document.getElementById('text');
textArea.addEventListener('input', () => this.updateCharCount());
}
// 设置表单验证
setupFormValidation() {
const textArea = document.getElementById('text');
const form = document.getElementById('qrForm');
textArea.addEventListener('blur', () => {
if (textArea.value.trim() === '') {
this.showFieldError(textArea, '请输入要生成二维码的内容');
} else {
this.clearFieldError(textArea);
}
});
}
// 显示字段错误
showFieldError(field, message) {
this.clearFieldError(field);
field.style.borderColor = '#d32f2f';
const errorDiv = document.createElement('div');
errorDiv.className = 'field-error';
errorDiv.style.color = '#d32f2f';
errorDiv.style.fontSize = '0.8rem';
errorDiv.style.marginTop = '5px';
errorDiv.textContent = message;
field.parentNode.appendChild(errorDiv);
}
// 清除字段错误
clearFieldError(field) {
field.style.borderColor = '';
const errorDiv = field.parentNode.querySelector('.field-error');
if (errorDiv) {
errorDiv.remove();
}
}
// 更新字符计数
updateCharCount() {
const textArea = document.getElementById('text');
const text = textArea.value;
const length = text.length;
// 移除旧的计数显示
const oldCounter = textArea.parentNode.querySelector('.char-counter');
if (oldCounter) oldCounter.remove();
// 添加新的计数显示
if (length > 0) {
const counter = document.createElement('div');
counter.className = 'char-counter';
counter.style.fontSize = '0.8rem';
counter.style.color = '#666';
counter.style.textAlign = 'right';
counter.style.marginTop = '5px';
counter.textContent = `${length} 个字符`;
textArea.parentNode.appendChild(counter);
}
}
// 处理表单提交
async handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const params = {
text: formData.get('text').trim(),
size: formData.get('size'),
level: formData.get('level'),
encoding: formData.get('encoding')
};
// 验证输入
if (!params.text) {
this.showFieldError(document.getElementById('text'), '请输入要生成二维码的内容');
return;
}
this.showLoading();
await this.generateQRCode(params);
}
// 生成二维码
async generateQRCode(params) {
let success = false;
let lastError = null;
// 尝试所有API接口
for (let i = 0; i < this.apiEndpoints.length; i++) {
const apiIndex = (this.currentApiIndex + i) % this.apiEndpoints.length;
const apiUrl = this.apiEndpoints[apiIndex];
try {
console.log(`尝试API ${apiIndex + 1}:`, apiUrl);
const result = await this.callAPI(apiUrl, params);
if (result.success) {
this.currentApiIndex = apiIndex; // 记录成功的API
this.showResult(result.data, params);
success = true;
break;
}
} catch (error) {
console.warn(`API ${apiIndex + 1} 失败:`, error);
lastError = error;
}
}
if (!success) {
this.showError(lastError?.message || '所有API接口都无法访问,请稍后重试');
}
}
// 调用API
async callAPI(baseUrl, params) {
const url = new URL('/v2/qrcode', baseUrl);
// 添加查询参数
Object.entries(params).forEach(([key, value]) => {
if (value) {
url.searchParams.append(key, value);
}
});
console.log('请求URL:', url.toString());
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
try {
const response = await fetch(url.toString(), {
method: 'GET',
signal: controller.signal,
headers: {
'Accept': 'application/json, image/*'
}
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// 根据返回格式处理
if (params.encoding === 'image') {
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);
return {
success: true,
data: {
imageUrl: imageUrl,
text: params.text,
size: params.size,
level: params.level,
format: 'image'
}
};
} else {
const jsonData = await response.json();
if (jsonData.code === 0 && jsonData.data) {
return {
success: true,
data: {
imageUrl: jsonData.data.data_uri,
text: params.text,
size: params.size,
level: params.level,
format: 'json',
base64: jsonData.data.base64,
mimeType: jsonData.data.mime_type
}
};
} else {
throw new Error(jsonData.message || '生成失败');
}
}
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('请求超时,请重试');
}
throw error;
}
}
// 显示加载状态
showLoading() {
this.hideAllStates();
document.getElementById('loading').classList.remove('hidden');
const btn = document.querySelector('.generate-btn');
btn.classList.add('loading');
btn.disabled = true;
}
// 显示错误
showError(message) {
this.hideAllStates();
const errorDiv = document.getElementById('error');
const errorMessage = errorDiv.querySelector('.error-message');
errorMessage.textContent = message;
errorDiv.classList.remove('hidden');
this.resetButton();
}
// 显示结果
showResult(data, params) {
this.hideAllStates();
const resultDiv = document.getElementById('result');
const qrImage = document.getElementById('qrImage');
const resultText = document.querySelector('.result-text');
qrImage.src = data.imageUrl;
qrImage.alt = `二维码: ${data.text}`;
resultText.innerHTML = `
内容: ${this.escapeHtml(data.text)}
尺寸: ${data.size}x${data.size}
容错级别: ${data.level}
格式: ${data.format.toUpperCase()}
`;
resultDiv.classList.remove('hidden');
this.resetButton();
// 保存数据供下载使用
this.currentQRData = data;
}
// 隐藏所有状态
hideAllStates() {
document.getElementById('loading').classList.add('hidden');
document.getElementById('error').classList.add('hidden');
document.getElementById('result').classList.add('hidden');
}
// 重置按钮状态
resetButton() {
const btn = document.querySelector('.generate-btn');
btn.classList.remove('loading');
btn.disabled = false;
}
// 重试生成
async retryGeneration() {
const form = document.getElementById('qrForm');
const formData = new FormData(form);
const params = {
text: formData.get('text').trim(),
size: formData.get('size'),
level: formData.get('level'),
encoding: formData.get('encoding')
};
this.showLoading();
await this.generateQRCode(params);
}
// 下载二维码
downloadQRCode() {
if (!this.currentQRData) return;
const link = document.createElement('a');
link.href = this.currentQRData.imageUrl;
link.download = `qrcode_${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.showToast('二维码已下载');
}
// 复制图片链接
async copyImageLink() {
if (!this.currentQRData) return;
try {
await navigator.clipboard.writeText(this.currentQRData.imageUrl);
this.showToast('链接已复制到剪贴板');
} catch (error) {
console.error('复制失败:', error);
this.showToast('复制失败,请手动复制');
}
}
// 重置表单
resetForm() {
document.getElementById('qrForm').reset();
this.hideAllStates();
this.currentQRData = null;
// 清除字符计数
const counter = document.querySelector('.char-counter');
if (counter) counter.remove();
// 清除字段错误
document.querySelectorAll('input, textarea, select').forEach(field => {
this.clearFieldError(field);
});
// 聚焦到文本框
document.getElementById('text').focus();
}
// 显示提示消息
showToast(message) {
// 移除旧的toast
const oldToast = document.querySelector('.toast');
if (oldToast) oldToast.remove();
const toast = document.createElement('div');
toast.className = 'toast';
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #4caf50;
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
z-index: 1000;
animation: slideIn 0.3s ease;
`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideOut 0.3s ease';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// HTML转义
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// 添加CSS动画
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
`;
document.head.appendChild(style);
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
new QRCodeGenerator();
});
// 错误处理
window.addEventListener('error', (e) => {
console.error('全局错误:', e.error);
});
window.addEventListener('unhandledrejection', (e) => {
console.error('未处理的Promise拒绝:', e.reason);
});