one-page-builder / static /script.js
duqing2026's picture
init
1d8e262
Raw
History Blame Contribute Delete
11.2 kB
document.addEventListener('DOMContentLoaded', () => {
// State
let features = [
{ icon: 'fa-bolt', title: '极速构建', desc: '无需编写代码,所见即所得,快速生成高品质页面。' },
{ icon: 'fa-mobile-screen', title: '响应式设计', desc: '完美适配手机、平板和桌面端,随时随地展示。' },
{ icon: 'fa-code', title: '代码导出', desc: '支持一键导出标准 HTML/Tailwind 代码,方便二次开发。' }
];
// DOM Elements
const inputs = {
brand: document.getElementById('input-brand'),
headline: document.getElementById('input-headline'),
subhead: document.getElementById('input-subhead'),
ctaText: document.getElementById('input-cta-text'),
ctaLink: document.getElementById('input-cta-link'),
copyright: document.getElementById('input-copyright')
};
const featuresContainer = document.getElementById('features-container');
const addFeatureBtn = document.getElementById('add-feature-btn');
const previewFrame = document.getElementById('preview-frame');
const themeRadios = document.querySelectorAll('input[name="theme"]');
const downloadBtn = document.getElementById('downloadBtn');
// Tab Switching
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
document.querySelectorAll('.tab-btn').forEach(b => {
b.classList.remove('active', 'bg-indigo-100', 'text-indigo-700');
b.classList.add('text-gray-500', 'hover:bg-gray-100');
});
e.target.classList.add('active', 'bg-indigo-100', 'text-indigo-700');
e.target.classList.remove('text-gray-500', 'hover:bg-gray-100');
document.querySelectorAll('.tab-pane').forEach(p => p.classList.add('hidden'));
document.getElementById(`tab-${e.target.dataset.tab}`).classList.remove('hidden');
});
});
// Feature Management
function renderFeaturesList() {
featuresContainer.innerHTML = '';
features.forEach((feature, index) => {
const div = document.createElement('div');
div.className = 'bg-white border border-gray-200 rounded-lg p-3 relative group';
div.innerHTML = `
<button class="delete-feature absolute top-2 right-2 text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity" data-index="${index}">
<i class="fa-solid fa-trash"></i>
</button>
<div class="mb-2">
<label class="block text-xs text-gray-500 mb-1">图标 (FontAwesome Class)</label>
<input type="text" class="feature-icon w-full text-xs border-gray-300 rounded p-1 border" value="${feature.icon}" data-index="${index}">
</div>
<div class="mb-2">
<input type="text" class="feature-title w-full font-bold text-sm border-gray-300 rounded p-1 border" value="${feature.title}" data-index="${index}" placeholder="标题">
</div>
<div>
<textarea class="feature-desc w-full text-xs border-gray-300 rounded p-1 border" rows="2" data-index="${index}" placeholder="描述">${feature.desc}</textarea>
</div>
`;
featuresContainer.appendChild(div);
});
// Add event listeners for dynamic inputs
document.querySelectorAll('.feature-icon').forEach(el => el.addEventListener('input', (e) => {
features[e.target.dataset.index].icon = e.target.value;
updatePreview();
}));
document.querySelectorAll('.feature-title').forEach(el => el.addEventListener('input', (e) => {
features[e.target.dataset.index].title = e.target.value;
updatePreview();
}));
document.querySelectorAll('.feature-desc').forEach(el => el.addEventListener('input', (e) => {
features[e.target.dataset.index].desc = e.target.value;
updatePreview();
}));
document.querySelectorAll('.delete-feature').forEach(el => el.addEventListener('click', (e) => {
features.splice(e.currentTarget.dataset.index, 1);
renderFeaturesList();
updatePreview();
}));
}
addFeatureBtn.addEventListener('click', () => {
features.push({ icon: 'fa-star', title: '新特性', desc: '描述这个特性的优势...' });
renderFeaturesList();
updatePreview();
});
// Core Generation Logic
function getThemeColors(theme) {
switch(theme) {
case 'dark':
return {
bg: 'bg-gray-900',
text: 'text-white',
primary: 'bg-indigo-600 hover:bg-indigo-700',
cardBg: 'bg-gray-800',
cardText: 'text-gray-300',
navBg: 'bg-gray-900/90',
footerBg: 'bg-gray-950'
};
case 'minimal':
return {
bg: 'bg-white',
text: 'text-gray-900',
primary: 'bg-black hover:bg-gray-800',
cardBg: 'bg-gray-50',
cardText: 'text-gray-600',
navBg: 'bg-white/90',
footerBg: 'bg-gray-100'
};
case 'startup':
return {
bg: 'bg-white',
text: 'text-gray-800',
primary: 'bg-emerald-500 hover:bg-emerald-600',
cardBg: 'bg-white shadow-lg border border-gray-100',
cardText: 'text-gray-600',
navBg: 'bg-white/90',
footerBg: 'bg-gray-50'
};
case 'modern':
default:
return {
bg: 'bg-white',
text: 'text-gray-900',
primary: 'bg-blue-600 hover:bg-blue-700',
cardBg: 'bg-white shadow-md border border-gray-100',
cardText: 'text-gray-500',
navBg: 'bg-white/90',
footerBg: 'bg-gray-50'
};
}
}
function generateHTML() {
const selectedTheme = document.querySelector('input[name="theme"]:checked').value;
const colors = getThemeColors(selectedTheme);
const data = {
brand: inputs.brand.value,
headline: inputs.headline.value,
subhead: inputs.subhead.value,
ctaText: inputs.ctaText.value,
ctaLink: inputs.ctaLink.value,
copyright: inputs.copyright.value
};
const featuresHTML = features.map(f => `
<div class="${colors.cardBg} p-6 rounded-xl transition-transform hover:-translate-y-1">
<div class="w-12 h-12 rounded-lg ${colors.primary} bg-opacity-10 flex items-center justify-center mb-4 text-2xl">
<i class="fa-solid ${f.icon} ${selectedTheme === 'dark' ? 'text-indigo-400' : (selectedTheme === 'startup' ? 'text-emerald-600' : 'text-blue-600')}"></i>
</div>
<h3 class="text-xl font-bold mb-2 ${colors.text}">${f.title}</h3>
<p class="${colors.cardText}">${f.desc}</p>
</div>
`).join('');
return `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${data.brand}</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
</head>
<body class="${colors.bg} font-sans antialiased selection:bg-blue-500 selection:text-white">
<!-- Navbar -->
<nav class="fixed w-full z-50 ${colors.navBg} backdrop-blur-sm border-b border-gray-200/10">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<div class="flex-shrink-0 font-bold text-xl ${colors.text}">
${data.brand}
</div>
<div class="hidden md:block">
<a href="${data.ctaLink}" class="${colors.primary} text-white px-5 py-2 rounded-full font-medium transition-colors shadow-lg shadow-blue-500/30">
${data.ctaText}
</a>
</div>
</div>
</div>
</nav>
<!-- Hero -->
<section class="pt-32 pb-20 px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto text-center">
<h1 class="text-4xl sm:text-6xl font-extrabold tracking-tight ${colors.text} mb-6 leading-tight">
${data.headline.replace(/\n/g, '<br>')}
</h1>
<p class="text-xl sm:text-2xl ${colors.cardText} mb-10 max-w-3xl mx-auto">
${data.subhead}
</p>
<div class="flex justify-center gap-4">
<a href="${data.ctaLink}" class="${colors.primary} text-white px-8 py-4 rounded-full font-bold text-lg transition-transform hover:scale-105 shadow-xl">
${data.ctaText}
</a>
<a href="#features" class="px-8 py-4 rounded-full font-bold text-lg border ${selectedTheme === 'dark' ? 'border-gray-700 text-white hover:bg-gray-800' : 'border-gray-300 text-gray-700 hover:bg-gray-50'} transition-colors">
了解更多
</a>
</div>
</section>
<!-- Features -->
<section id="features" class="py-20 ${selectedTheme === 'minimal' ? 'bg-gray-50' : ''}">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
${featuresHTML}
</div>
</div>
</section>
<!-- Footer -->
<footer class="${colors.footerBg} py-12 border-t border-gray-200/10">
<div class="max-w-7xl mx-auto px-4 text-center ${colors.cardText}">
<p>${data.copyright}</p>
</div>
</footer>
</body>
</html>`;
}
function updatePreview() {
const html = generateHTML();
const iframe = document.getElementById('preview-frame');
iframe.srcdoc = html;
}
// Initialize
renderFeaturesList();
updatePreview();
// Event Listeners
Object.values(inputs).forEach(input => {
input.addEventListener('input', updatePreview);
});
themeRadios.forEach(radio => {
radio.addEventListener('change', updatePreview);
});
// Download Logic
downloadBtn.addEventListener('click', () => {
const zip = new JSZip();
const html = generateHTML();
zip.file("index.html", html);
zip.file("README.txt", "Generated by One-Page Builder.\nDeploy this index.html anywhere (GitHub Pages, Vercel, Netlify).");
zip.generateAsync({type:"blob"})
.then(function(content) {
saveAs(content, "website-export.zip");
});
});
});