/* ─── 全局 i18n 系统 ─── */
(function(){ 'use strict';
const LANG_KEY = 'fakeapi_lang';
const DEFAULT_LANG = 'zh';
let currentLang = localStorage.getItem(LANG_KEY) || DEFAULT_LANG;
const translations = {
zh: {
appTitle: 'FakeAPI',
appSubtitle: '假数据生成器 · 支持用户 / 文章 / 评论 / 产品 / 地址',
typeLabel: '数据类型',
countLabel: '数量',
fieldsLabel: '包含字段',
formatLabel: '输出格式',
generateBtn: '生成数据',
previewTitle: '预览',
statsWaiting: '等待生成',
placeholder: '选择参数后点击「生成数据」',
copyBtn: '复制到剪贴板',
downloadBtn: '下载文件',
regenerateBtn: '重新生成',
toastCopied: '已复制到剪贴板',
toastDownloaded: '文件已下载',
toastError: '生成失败,请重试'
},
en: {
appTitle: 'FakeAPI',
appSubtitle: 'Fake Data Generator · Users / Articles / Comments / Products / Addresses',
typeLabel: 'Data Type',
countLabel: 'Count',
fieldsLabel: 'Fields',
formatLabel: 'Format',
generateBtn: 'Generate',
previewTitle: 'Preview',
statsWaiting: 'Awaiting generation',
placeholder: 'Select parameters and click „Generate”',
copyBtn: 'Copy to Clipboard',
downloadBtn: 'Download File',
regenerateBtn: 'Regenerate',
toastCopied: 'Copied to clipboard',
toastDownloaded: 'File downloaded',
toastError: 'Generation failed, please retry'
}
};
function t(key) {
const lang = translations[currentLang];
return lang && lang[key] !== undefined ? lang[key] : (translations[DEFAULT_LANG][key] || key);
}
window.__ = t;
function applyI18n() {
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
if (key) {
const text = t(key);
if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') {
el.placeholder = text;
} else {
el.textContent = text;
}
}
});
}
window.switchLang = function(lang) {
if (translations[lang]) {
currentLang = lang;
localStorage.setItem(LANG_KEY, lang);
applyI18n();
// 更新动态文本(统计、toast等由各自逻辑触发)
if (typeof updateStatsUI === 'function') updateStatsUI(state.currentData);
}
};
// 初始化时应用
document.addEventListener('DOMContentLoaded', applyI18n);
})();
/* ─── 定义 escHtml(原有脚本缺失) ─── */
if (typeof escHtml === 'undefined') {
window.escHtml = function(str) {
return String(str)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
};
}
/* ─── 补全被截断的脚本 ─── */
(function() {
// 确保 state 已定义
if (typeof state === 'undefined') return;
// ── 数字输入强制 min/max ──
const countInput = document.getElementById('countInput');
if (countInput) {
const clamp = function() {
let val = parseInt(countInput.value, 10);
if (isNaN(val)) val = parseInt(countInput.min, 10) || 5;
const min = parseInt(countInput.min, 10) || 1;
const max = parseInt(countInput.max, 10) || 1000;
val = Math.max(min, Math.min(max, val));
countInput.value = val;
};
countInput.addEventListener('change', clamp);
countInput.addEventListener('blur', clamp);
}
// ── 生成按钮 loading + 防重复 ──
const generateBtn = document.getElementById('generateBtn');
const regenerateBtn = document.getElementById('regenerateBtn');
const copyBtn = document.getElementById('copyBtn');
const downloadBtn = document.getElementById('downloadBtn');
function setLoading(btn, loading) {
if (!btn) return;
if (loading) {
btn.classList.add('loading');
btn.disabled = true;
} else {
btn.classList.remove('loading');
btn.disabled = false;
}
}
// ── 主生成函数 ──
window.generateData = function() {
try {
setLoading(generateBtn, true);
if (regenerateBtn) setLoading(regenerateBtn, true);
const type = state.currentType || 'user';
const count = parseInt(countInput.value, 10) || 5;
const format = state.currentFormat || 'json';
const fields = getFieldsForType(type);
const enabled = state.enabledFields[type] || fields.map(f => f.key);
const data = [];
for (let i = 0; i < count; i++) {
const item = {};
fields.forEach(f => {
if (enabled.includes(f.key)) {
item[f.key] = f.gen();
}
});
data.push(item);
}
state.currentData = data;
renderPreview(data, format);
updateStatsUI(data);
} catch (err) {
console.error('Generation failed:', err);
showToast(window.__ ? window.__('toastError') : '生成失败,请重试', 'error');
} finally {
setLoading(generateBtn, false);
if (regenerateBtn) setLoading(regenerateBtn, false);
}
};
// ── 渲染预览 ──
window.renderPreview = function(data, format) {
const previewBody = document.getElementById('previewBody');
const placeholder = document.getElementById('placeholder');
if (!previewBody) return;
// 隐藏占位
if (placeholder) placeholder.style.display = 'none';
let html = '';
if (format === 'json') {
html = syntaxHighlight(JSON.stringify(data, null, 2));
} else {
// CSV
const fields = getFieldsForType(state.currentType);
const enabled = state.enabledFields[state.currentType] || fields.map(f => f.key);
const header = enabled.join(',');
let rows = data.map(item => enabled.map(k => item[k]).join(',')).join('
');
html = escHtml(header) + '
' + rows;
}
previewBody.innerHTML = html || '' + (window.__ ? window.__('placeholder') : '暂无数据') + '
';
};
// ── 语法高亮 ──
function syntaxHighlight(json) {
const lines = json.split('\n');
return lines.map(line => {
// 简单的转义 + 高亮
let str = escHtml(line);
str = str.replace(/("(?:[^"\\]|\\.)*")\s*:/g, '$1:');
str = str.replace(/("(?:[^"\\]|\\.)*")/g, '$1');
str = str.replace(/\b(\d+\.?\d*)\b/g, '$1');
str = str.replace(/\b(true|false)\b/g, '$1');
return str;
}).join('\n');
}
// ── 更新统计 ──
window.updateStatsUI = function(data) {
const statsInfo = document.getElementById('statsInfo');
if (!statsInfo) return;
if (data && data.length) {
statsInfo.textContent = data.length + ' 条记录 · ' + state.currentFormat.toUpperCase();
} else {
statsInfo.textContent = window.__ ? window.__('statsWaiting') : '等待生成';
}
};
// ── Toast ──
window.showToast = function(msg, type) {
const el = document.getElementById('toast');
if (!el) return;
el.textContent = msg;
el.className = 'toast' + (type ? ' ' + type : '');
el.classList.add('show');
clearTimeout(el._hideTimer);
el._hideTimer = setTimeout(() => el.classList.remove('show'), 3000);
};
// ── 绑定生成事件 ──
if (generateBtn) {
generateBtn.addEventListener('click', function(e) {
e.preventDefault();
generateData();
});
}
if (regenerateBtn) {
regenerateBtn.addEventListener('click', function(e) {
e.preventDefault();
generateData();
});
}
// ── 复制 ──
if (copyBtn) {
copyBtn.addEventListener('click', function() {
try {
const text = state.currentData ? JSON.stringify(state.currentData, null, 2) : '';
if (!text) {
showToast('没有数据可复制', 'error');
return;
}
navigator.clipboard.writeText(text).then(() => {
showToast(window.__ ? window.__('toastCopied') : '已复制到剪贴板', 'success');
}).catch(() => {
// 降级
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.remove();
showToast(window.__ ? window.__('toastCopied') : '已复制到剪贴板', 'success');
});
} catch (err) {
showToast('复制失败', 'error');
}
});
}
// ── 下载 ──
if (downloadBtn) {
downloadBtn.addEventListener('click', function() {
try {
const data = state.currentData;
if (!data || !data.length) {
showToast('没有数据可下载', 'error');
return;
}
const format = state.currentFormat;
const content = format === 'json' ? JSON.stringify(data, null, 2) : toCSV(data);
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'fakeapi_data.' + format;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
showToast(window.__ ? window.__('toastDownloaded') : '文件已下载', 'success');
} catch (err) {
showToast('下载失败', 'error');
}
});
}
// ── CSV 转换 ──
function toCSV(data) {
if (!data || !data.length) return '';
const headers = Object.keys(data[0]);
const lines = [headers.join(',')];
data.forEach(row => {
const vals = headers.map(h => {
let v = row[h];
if (typeof v === 'string' && (v.includes(',') || v.includes('\n') || v.includes('"'))) {
v = '"' + v.replace(/"/g, '""') + '"';
}
return v;
});
lines.push(vals.join(','));
});
return lines.join('\n');
}
// ── 生成时已有的监听(补全被截断的点击处理) ──
// 注意:原有脚本中的点击处理已不完整,我们在此重新绑定,但为了防止重复,先移除旧监听再绑定
const fieldContainer = document.getElementById('fieldCheckboxes');
if (fieldContainer) {
// 移除旧的内联 onclick(已截断,但可能会有残留事件),使用事件委托
fieldContainer.addEventListener('click', function(e) {
const label = e.target.closest('.field-checkbox');
if (!label) return;
label.classList.toggle('checked');
const key = label.dataset.key;
if (!key) return;
const type = state.currentType;
if (!state.enabledFields[type]) state.enabledFields[type] = (getFieldsForType(type) || []).map(f => f.key);
const arr = state.enabledFields[type];
if (label.classList.contains('checked')) {
if (!arr.includes(key)) arr.push(key);
} else {
const idx = arr.indexOf(key);
if (idx > -1) arr.splice(idx, 1);
}
});
}
// ── 导航栏初始化 ──
if (typeof NavBar !== 'undefined' && NavBar.init) {
try { NavBar.init(); } catch(e) { console.warn('NavBar init failed', e); }
}
})();
/* ─── 全局错误保护 ─── */
window.addEventListener('error', function(e) {
console.error('Global error caught:', e.message);
// 避免界面完全崩溃
return true;
});
window.addEventListener('unhandledrejection', function(e) {
console.error('Unhandled promise rejection:', e.reason);
});