This commit is contained in:
@@ -12,63 +12,65 @@ function loadEquipmentGallery(data) {
|
||||
});
|
||||
}
|
||||
|
||||
function loadSiteData() {
|
||||
const data = {};
|
||||
|
||||
function loadTable(table, key) {
|
||||
return new Promise((resolve) => {
|
||||
db.all(`SELECT * FROM ${table} ORDER BY sort_order`, (err, rows) => {
|
||||
if (err) { data[key] = []; resolve(); return; }
|
||||
if (table === 'equipment' || table === 'partners') {
|
||||
rows = rows.filter(r => r.is_active == 1);
|
||||
}
|
||||
if (table === 'services' || table === 'work_schemes') {
|
||||
rows = rows.filter(r => r.is_active == 1);
|
||||
}
|
||||
data[key] = rows;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadSettings(table, key, transformFn) {
|
||||
return new Promise((resolve) => {
|
||||
db.all(`SELECT * FROM ${table} ORDER BY id`, (err, rows) => {
|
||||
if (err) { data[key] = {}; resolve(); return; }
|
||||
if (transformFn) {
|
||||
data[key] = transformFn(rows);
|
||||
} else {
|
||||
const obj = {};
|
||||
rows.forEach(r => { obj[r.key] = r.value; });
|
||||
data[key] = obj;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
loadSettings('hero_settings', 'hero'),
|
||||
loadTable('hero_stats', 'hero_stats'),
|
||||
loadSettings('about_settings', 'about'),
|
||||
loadTable('about_features', 'about_features'),
|
||||
loadTable('services', 'services'),
|
||||
loadTable('work_schemes', 'work_schemes'),
|
||||
loadTable('process_steps', 'process_steps'),
|
||||
loadTable('equipment', 'equipment'),
|
||||
loadEquipmentGallery(data),
|
||||
loadTable('geo_regions', 'geo_regions'),
|
||||
loadSettings('geo_settings', 'geo'),
|
||||
loadTable('licenses', 'licenses'),
|
||||
loadTable('license_gallery', 'license_gallery'),
|
||||
loadTable('public_activities', 'public_activities'),
|
||||
loadTable('activity_gallery', 'activity_gallery'),
|
||||
loadTable('partners', 'partners'),
|
||||
loadSettings('settings', 'settings')
|
||||
]).then(() => data);
|
||||
}
|
||||
|
||||
function setupRoutes(app) {
|
||||
app.get('/api/site-data', (req, res) => {
|
||||
const data = {};
|
||||
|
||||
function loadTable(table, key) {
|
||||
return new Promise((resolve) => {
|
||||
db.all(`SELECT * FROM ${table} ORDER BY sort_order`, (err, rows) => {
|
||||
if (err) { data[key] = []; resolve(); return; }
|
||||
if (table === 'equipment' || table === 'partners') {
|
||||
rows = rows.filter(r => r.is_active == 1);
|
||||
}
|
||||
if (table === 'services' || table === 'work_schemes') {
|
||||
rows = rows.filter(r => r.is_active == 1);
|
||||
}
|
||||
data[key] = rows;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadSettings(table, key, transformFn) {
|
||||
return new Promise((resolve) => {
|
||||
db.all(`SELECT * FROM ${table} ORDER BY id`, (err, rows) => {
|
||||
if (err) { data[key] = {}; resolve(); return; }
|
||||
if (transformFn) {
|
||||
data[key] = transformFn(rows);
|
||||
} else {
|
||||
const obj = {};
|
||||
rows.forEach(r => { obj[r.key] = r.value; });
|
||||
data[key] = obj;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
loadSettings('hero_settings', 'hero'),
|
||||
loadTable('hero_stats', 'hero_stats'),
|
||||
loadSettings('about_settings', 'about'),
|
||||
loadTable('about_features', 'about_features'),
|
||||
loadTable('services', 'services'),
|
||||
loadTable('work_schemes', 'work_schemes'),
|
||||
loadTable('process_steps', 'process_steps'),
|
||||
loadTable('equipment', 'equipment'),
|
||||
loadEquipmentGallery(data),
|
||||
loadTable('geo_regions', 'geo_regions'),
|
||||
loadSettings('geo_settings', 'geo'),
|
||||
loadTable('licenses', 'licenses'),
|
||||
loadTable('license_gallery', 'license_gallery'),
|
||||
loadTable('public_activities', 'public_activities'),
|
||||
loadTable('activity_gallery', 'activity_gallery'),
|
||||
loadTable('partners', 'partners'),
|
||||
loadSettings('settings', 'settings')
|
||||
]).then(() => {
|
||||
res.json(data);
|
||||
});
|
||||
loadSiteData().then(data => res.json(data));
|
||||
});
|
||||
|
||||
app.post('/api/leads', (req, res) => {
|
||||
@@ -83,4 +85,4 @@ function setupRoutes(app) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { init, setupRoutes };
|
||||
module.exports = { init, setupRoutes, loadSiteData };
|
||||
|
||||
250
modules/ssrRenderer.js
Normal file
250
modules/ssrRenderer.js
Normal file
@@ -0,0 +1,250 @@
|
||||
const yearsSince2011 = new Date().getFullYear() - 2011;
|
||||
|
||||
function esc(s) { return String(s||'').replace(/&/g,'&').replace(/"/g,'"').replace(/</g,'<').replace(/>/g,'>'); }
|
||||
|
||||
function imgSrc(path) {
|
||||
if (!path) return './img/hero-bg.webp';
|
||||
if (path.startsWith('./')) return path;
|
||||
if (path.startsWith('http')) return path;
|
||||
if (/^(equipment|partner|license|activity|hero)_images\//.test(path)) return '/data/' + path;
|
||||
return './img/' + path;
|
||||
}
|
||||
|
||||
function renderHero(data) {
|
||||
var h = data.hero || {};
|
||||
var stats = data.hero_stats || [];
|
||||
var phone800 = h.phone_800 || '8 (800) 555-29-31';
|
||||
var phoneEkb = h.phone_ekb || '8 (343) 237-25-66';
|
||||
var email = h.email || 'sts_zakaz@mail.ru';
|
||||
var address = h.address || 'г. Екатеринбург, ул. Московская, 11, оф. 702';
|
||||
|
||||
var contactsHtml = '';
|
||||
if (phone800 && phoneEkb) {
|
||||
contactsHtml += '<div class="hero-contact-item"><svg viewBox="0 0 24 24"><path d="M6.62 10.79a15.053 15.053 0 006.59 6.59l2.2-2.2a1 1 0 011.01-.24c1.12.37 2.33.57 3.58.57a1 1 0 011 1V20a1 1 0 01-1 1A17 17 0 013 4a1 1 0 011-1h3.5a1 1 0 011 1c0 1.25.2 2.46.57 3.58a1 1 0 01-.24 1.01l-2.2 2.2z"/></svg><div><a href="tel:' + phone800.replace(/\D/g,'') + '">' + phone800 + '</a><br><a href="tel:' + phoneEkb.replace(/\D/g,'') + '">' + phoneEkb + '</a></div></div>';
|
||||
}
|
||||
contactsHtml += '<div class="hero-contact-item"><svg viewBox="0 0 24 24"><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/></svg><a href="mailto:' + email + '">' + email + '</a></div>';
|
||||
contactsHtml += '<div class="hero-contact-item"><svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5A2.5 2.5 0 1112 6.5a2.5 2.5 0 010 5z"/></svg><span>' + address + '</span></div>';
|
||||
|
||||
var statsHtml = '';
|
||||
stats.forEach(function(s) {
|
||||
var val = s.is_dynamic == 1 ? yearsSince2011 : s.value;
|
||||
statsHtml += '<div class="hero-stat"><div class="hero-stat-number">' + val + '</div><div class="hero-stat-label">' + s.label + '</div></div>';
|
||||
});
|
||||
|
||||
var badgeHtml = h.badge_text ? '<div class="hero-badge"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"/></svg> ' + h.badge_text + '</div>' : '';
|
||||
var h1text = (h.h1_text || 'демонтаж нефтегазопроводов по России, утилизация отходов').replace('{years}', yearsSince2011);
|
||||
|
||||
return badgeHtml +
|
||||
'<h1>' + h1text + '</h1>' +
|
||||
'<p class="hero-subtitle">' + (h.subtitle || '').replace('{years}', yearsSince2011) + '</p>' +
|
||||
'<div class="hero-contacts">' + contactsHtml + '</div>' +
|
||||
'<div class="hero-buttons">' +
|
||||
'<a href="' + (h.button1_link || '#contacts') + '" class="btn btn-primary">' + (h.button1_text || 'Оставить заявку') + '</a>' +
|
||||
'<a href="' + (h.button2_link || '#services') + '" class="btn btn-outline">' + (h.button2_text || 'Наши услуги') + '</a>' +
|
||||
'</div>' +
|
||||
'<div class="hero-stats">' + statsHtml + '</div>';
|
||||
}
|
||||
|
||||
function renderAbout(data) {
|
||||
var a = data.about || {};
|
||||
var features = data.about_features || [];
|
||||
var featsHtml = features.map(function(f) {
|
||||
return '<div class="about-feature"><div class="about-feature-icon"><svg viewBox="0 0 24 24">' + (f.svg_icon || '') + '</svg></div><div class="about-feature-text"><strong>' + f.title + '</strong>' + (f.description || '') + '</div></div>';
|
||||
}).join('');
|
||||
|
||||
return '<div class="section-header"><h2 class="section-title">О <span>компании</span></h2><p class="section-subtitle">' + (a.section_subtitle || '') + '</p></div>' +
|
||||
'<div class="about-grid">' +
|
||||
'<div class="about-image"><img src="' + imgSrc(a.about_image) + '" alt="" loading="lazy"></div>' +
|
||||
'<div class="about-text"><h3>' + (a.main_heading || '') + '</h3>' +
|
||||
(a.paragraph_1 ? '<p>' + a.paragraph_1 + '</p>' : '') +
|
||||
(a.paragraph_2 ? '<p>' + a.paragraph_2 + '</p>' : '') +
|
||||
(a.paragraph_3 ? '<p>' + a.paragraph_3 + '</p>' : '') +
|
||||
'<div class="about-features">' + featsHtml + '</div></div></div>';
|
||||
}
|
||||
|
||||
function renderServices(data) {
|
||||
var services = data.services || [];
|
||||
var st = data.settings || {};
|
||||
var cardsHtml = services.map(function(s) {
|
||||
var items = [];
|
||||
try { items = JSON.parse(s.items || '[]'); } catch(e) {}
|
||||
return '<div class="service-card' + (s.is_large == 1 ? ' large' : '') + '">' +
|
||||
'<div class="service-icon"><svg viewBox="0 0 24 24">' + (s.svg_icon || '') + '</svg></div>' +
|
||||
'<h3>' + s.title + '</h3><p>' + (s.description || '') + '</p>' +
|
||||
(items.length ? '<ul class="service-list">' + items.map(function(i) { return '<li>' + i + '</li>'; }).join('') + '</ul>' : '') +
|
||||
'</div>';
|
||||
}).join('');
|
||||
return '<div class="section-header center"><h2 class="section-title">Наши <span>услуги</span></h2><p class="section-subtitle">' + (st.section_services_subtitle || '') + '</p></div>' +
|
||||
'<div class="services-grid">' + cardsHtml + '</div>';
|
||||
}
|
||||
|
||||
function renderSchemes(data) {
|
||||
var schemes = data.work_schemes || [];
|
||||
var st = data.settings || {};
|
||||
var cardsHtml = schemes.map(function(s) {
|
||||
var items = [];
|
||||
try { items = JSON.parse(s.items || '[]'); } catch(e) {}
|
||||
return '<div class="service-card">' +
|
||||
'<div class="service-icon"><svg viewBox="0 0 24 24">' + (s.svg_icon || '') + '</svg></div>' +
|
||||
'<h3>' + s.title + '</h3><p>' + (s.description || '') + '</p>' +
|
||||
(items.length ? '<ul class="service-list">' + items.map(function(i) { return '<li>' + i + '</li>'; }).join('') + '</ul>' : '') +
|
||||
'</div>';
|
||||
}).join('');
|
||||
return '<div class="section-header center"><h2 class="section-title">Гибкие <span>схемы работы</span></h2><p class="section-subtitle">' + (st.section_schemes_subtitle || '') + '</p></div>' +
|
||||
'<div class="services-grid">' + cardsHtml + '</div>';
|
||||
}
|
||||
|
||||
function renderProcess(data) {
|
||||
var steps = data.process_steps || [];
|
||||
var st = data.settings || {};
|
||||
var stepsHtml = steps.map(function(s) {
|
||||
return '<div class="process-step"><div class="process-number">' + (s.step_number || s.sort_order + 1) + '</div><div class="process-content"><h3>' + s.title + '</h3><p>' + (s.description || '') + '</p></div></div>';
|
||||
}).join('');
|
||||
return '<div class="section-header center"><h2 class="section-title">Этапы <span>работы</span></h2><p class="section-subtitle">' + (st.section_process_subtitle || '') + '</p></div>' +
|
||||
'<div class="process-timeline">' + stepsHtml + '</div>';
|
||||
}
|
||||
|
||||
function renderEquipment(data) {
|
||||
var eq = data.equipment || [];
|
||||
var gallery = data.equipment_gallery || [];
|
||||
var st = data.settings || {};
|
||||
|
||||
function getImages(e) {
|
||||
var imgs = [];
|
||||
if (gallery.length) {
|
||||
gallery.filter(function(g) { return g.equipment_id === e.id; }).forEach(function(g) {
|
||||
if (g.image_path) imgs.push(g.image_path);
|
||||
});
|
||||
}
|
||||
if (imgs.length === 0 && e.image_path) imgs.push(e.image_path);
|
||||
return imgs;
|
||||
}
|
||||
|
||||
var cardsHtml = eq.map(function(e) {
|
||||
var specs = [];
|
||||
try { specs = JSON.parse(e.specs || '[]'); } catch(e2) {}
|
||||
var imgs = getImages(e);
|
||||
var imgHtml = imgs.length > 0
|
||||
? '<img src="' + imgSrc(imgs[0]) + '" loading="lazy" alt="' + esc(e.name) + '" style="width:100%;height:100%;object-fit:cover">'
|
||||
: '<div style="width:100%;height:100%;background:var(--bg-alt);display:flex;align-items:center;justify-content:center;color:var(--text-light)">Нет фото</div>';
|
||||
|
||||
return '<div class="equipment-card"><div class="equipment-card-image">' +
|
||||
imgHtml +
|
||||
'<div class="equip-overlay"><span class="brand">' + esc(e.brand || '') + '</span></div>' +
|
||||
'</div><div class="equipment-card-body"><h3>' + esc(e.name) + '</h3>' +
|
||||
(e.qty ? '<div class="equipment-qty">' + esc(e.qty) + '</div>' : '') +
|
||||
(specs.length ? '<div class="equipment-specs">' + specs.map(function(sp) { return '<div class="equipment-spec"><span class="equipment-spec-label">' + esc(sp.label) + '</span><span class="equipment-spec-value">' + esc(sp.value) + '</span></div>'; }).join('') + '</div>' : '') +
|
||||
'</div></div>';
|
||||
}).join('');
|
||||
|
||||
return '<div class="section-header center"><h2 class="section-title">Наша <span>спецтехника</span></h2><p class="section-subtitle">' + esc(st.section_equipment_subtitle || '') + '</p></div>' +
|
||||
'<div class="equipment-grid">' + cardsHtml + '</div>';
|
||||
}
|
||||
|
||||
function renderGeography(data) {
|
||||
var geo = data.geo || {};
|
||||
var regions = data.geo_regions || [];
|
||||
var st = data.settings || {};
|
||||
var regionsHtml = regions.map(function(r) {
|
||||
return '<div class="geo-region"><svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5A2.5 2.5 0 1112 6.5a2.5 2.5 0 010 5z"/></svg><span>' + (r.name || '') + '</span></div>';
|
||||
}).join('');
|
||||
return '<div class="section-header center"><h2 class="section-title">География <span>присутствия</span></h2><p class="section-subtitle">' + (geo.section_subtitle || st.section_services_subtitle || '') + '</p></div>' +
|
||||
'<div class="geo-content"><div><div class="geo-regions">' + regionsHtml + '</div>' +
|
||||
(geo.difficult_title ? '<div class="geo-hard"><h4>' + geo.difficult_title + '</h4><p>' + (geo.difficult_desc || '') + '</p></div>' : '') +
|
||||
'</div><div><div class="geo-map"><img src="' + imgSrc(geo.map_image) + '" loading="lazy" alt=""></div></div></div>';
|
||||
}
|
||||
|
||||
function renderLicenses(data) {
|
||||
var licenses = data.licenses || [];
|
||||
var gallery = data.license_gallery || [];
|
||||
var st = data.settings || {};
|
||||
var cardsHtml = licenses.map(function(l) {
|
||||
return '<div class="license-card"><div class="license-icon"><svg viewBox="0 0 24 24">' + (l.svg_icon || '') + '</svg></div><div><h4>' + l.title + '</h4><p>' + (l.description || '') + '</p></div></div>';
|
||||
}).join('');
|
||||
var galleryHtml = gallery.map(function(g) {
|
||||
return '<div class="license-gallery-item" data-src="' + imgSrc(g.image_path) + '" data-title="' + esc(g.title) + '" data-desc="' + esc(g.description || '') + '">' +
|
||||
'<div class="license-gallery-thumb"><img src="' + imgSrc(g.image_path) + '" loading="lazy" alt="' + esc(g.title) + '"><div class="license-gallery-zoom"><svg viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg></div></div>' +
|
||||
'<div class="license-gallery-body"><h4>' + g.title + '</h4><p>' + (g.description || '') + '</p></div></div>';
|
||||
}).join('');
|
||||
return '<div class="section-header center"><h2 class="section-title">Лицензии и <span>разрешения</span></h2><p class="section-subtitle">' + (st.section_licenses_subtitle || '') + '</p></div>' +
|
||||
'<div class="licenses-grid">' + cardsHtml + '</div>' +
|
||||
(gallery.length ? '<div class="license-gallery">' + galleryHtml + '</div>' : '');
|
||||
}
|
||||
|
||||
function renderActivity(data) {
|
||||
var acts = data.public_activities || [];
|
||||
var gallery = data.activity_gallery || [];
|
||||
var st = data.settings || {};
|
||||
var cardsHtml = acts.map(function(a) {
|
||||
return '<div class="activity-card"><div class="activity-card-icon"><svg viewBox="0 0 24 24">' + (a.svg_icon || '') + '</svg></div><h4>' + a.title + '</h4><p>' + (a.description || '') + '</p></div>';
|
||||
}).join('');
|
||||
var galleryHtml = gallery.length ? '<div class="activity-gallery"><div class="activity-gallery-title">Благодарственные письма</div>' +
|
||||
gallery.map(function(g) {
|
||||
return '<div class="license-gallery-item" data-src="' + imgSrc(g.image_path) + '" data-title="' + esc(g.title) + '" data-desc="' + esc(g.description || '') + '">' +
|
||||
'<div class="license-gallery-thumb"><img src="' + imgSrc(g.image_path) + '" loading="lazy" alt="' + esc(g.title) + '"><div class="license-gallery-zoom"><svg viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg></div></div>' +
|
||||
'<div class="license-gallery-body"><h4>' + g.title + '</h4><p>' + (g.description || '') + '</p></div></div>';
|
||||
}).join('') + '</div>' : '';
|
||||
return '<div class="section-header center"><h2 class="section-title">Общественная <span>деятельность</span></h2><p class="section-subtitle">' + (st.section_activity_subtitle || '') + '</p></div>' +
|
||||
'<div class="activity-cards">' + cardsHtml + '</div>' + galleryHtml;
|
||||
}
|
||||
|
||||
function renderPartners(data) {
|
||||
var partners = data.partners || [];
|
||||
var st = data.settings || {};
|
||||
var duplicated = partners.concat(partners);
|
||||
var trackHtml = duplicated.map(function(p) {
|
||||
return '<img src="' + imgSrc(p.logo_path) + '" loading="lazy" alt="' + esc(p.name || '') + '">';
|
||||
}).join('');
|
||||
return '<div class="section-header center"><h2 class="section-title">Наши <span>партнёры</span></h2><p class="section-subtitle">' + (st.section_partners_subtitle || '') + '</p></div>' +
|
||||
'<div class="partners-scroll"><div class="partners-track">' + trackHtml + '</div></div>';
|
||||
}
|
||||
|
||||
function renderContacts(data) {
|
||||
var s = data.settings || {};
|
||||
var h = data.hero || {};
|
||||
var phone800 = h.phone_800 || s.contact_phone_800 || '8 (800) 555-29-31';
|
||||
var phoneEkb = h.phone_ekb || s.contact_phone_ekb || '8 (343) 237-25-66';
|
||||
var email = h.email || s.contact_email || 'sts_zakaz@mail.ru';
|
||||
var address = h.address || s.contact_address || 'г. Екатеринбург, ул. Московская, 11, оф. 702';
|
||||
var workHours = (s.contact_work_hours || 'Пн–Пт: 9:00 – 18:00|Сб: 10:00 – 15:00').replace(/\|/g, '<br>');
|
||||
var mapImg = s.contact_map_image || './img/secret.webp';
|
||||
|
||||
return '<div class="section-header center"><h2 class="section-title">Свяжитесь <span>с нами</span></h2><p class="section-subtitle">' + (s.section_contacts_subtitle || '') + '</p></div>' +
|
||||
'<div class="contacts-grid"><div>' +
|
||||
'<div class="contact-info-list">' +
|
||||
'<div class="contact-info-item"><div class="contact-info-icon"><svg viewBox="0 0 24 24"><path d="M6.62 10.79a15.053 15.053 0 006.59 6.59l2.2-2.2a1 1 0 011.01-.24c1.12.37 2.33.57 3.58.57a1 1 0 011 1V20a1 1 0 01-1 1A17 17 0 013 4a1 1 0 011-1h3.5a1 1 0 011 1c0 1.25.2 2.46.57 3.58a1 1 0 01-.24 1.01l-2.2 2.2z"/></svg></div><div><h4>Телефон</h4><a href="tel:' + phone800.replace(/\D/g,'') + '">' + phone800 + '</a><br><a href="tel:' + phoneEkb.replace(/\D/g,'') + '">' + phoneEkb + '</a></div></div>' +
|
||||
'<div class="contact-info-item"><div class="contact-info-icon"><svg viewBox="0 0 24 24"><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/></svg></div><div><h4>Email</h4><a href="mailto:' + email + '">' + email + '</a></div></div>' +
|
||||
'<div class="contact-info-item"><div class="contact-info-icon"><svg viewBox="0 0 24 24"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5A2.5 2.5 0 1112 6.5a2.5 2.5 0 010 5z"/></svg></div><div><h4>Адрес</h4><p>' + address + '</p></div></div>' +
|
||||
'<div class="contact-info-item"><div class="contact-info-icon"><svg viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></svg></div><div><h4>Режим работы</h4><p>' + workHours + '</p></div></div>' +
|
||||
'</div><div class="contact-map"><img src="' + imgSrc(mapImg) + '" loading="lazy" alt=""></div>' +
|
||||
'</div><div>' +
|
||||
'<div class="contact-form" id="contactForm"><h3>' + (s.form_title || 'Оставить заявку') + '</h3>' +
|
||||
'<p class="form-subtitle">' + (s.form_subtitle || 'Заполните форму и мы свяжемся с вами') + '</p>' +
|
||||
'<form id="feedbackForm" onsubmit="return handleFormSubmit(event)">' +
|
||||
'<div class="form-group"><label for="name">Ваше имя *</label><input type="text" id="name" name="name" placeholder="Иван Иванов" required></div>' +
|
||||
'<div class="form-group"><label for="phone">Телефон *</label><input type="tel" id="phone" name="phone" placeholder="+7 (___) ___-__-__" required></div>' +
|
||||
'<div class="form-group"><label for="message">Сообщение</label><textarea id="message" name="message" placeholder="Опишите ваш запрос..."></textarea></div>' +
|
||||
'<div class="form-group"><label class="form-consent-label"><input type="checkbox" name="consent" required id="consent"><span>Я согласен на <a href="/privacy.html" target="_blank">обработку персональных данных</a></span></label></div>' +
|
||||
'<button type="submit" class="btn btn-primary">Отправить заявку</button>' +
|
||||
'</form>' +
|
||||
'<div class="form-success" id="formSuccess"><svg viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg><h4>Заявка отправлена!</h4><p>Мы свяжемся с вами в ближайшее время</p></div>' +
|
||||
'</div></div></div>';
|
||||
}
|
||||
|
||||
function renderAll(data) {
|
||||
return {
|
||||
heroContent: renderHero(data),
|
||||
aboutContainer: renderAbout(data),
|
||||
servicesContainer: renderServices(data),
|
||||
schemesContainer: renderSchemes(data),
|
||||
processContainer: renderProcess(data),
|
||||
equipmentContainer: renderEquipment(data),
|
||||
geographyContainer: renderGeography(data),
|
||||
licensesContainer: renderLicenses(data),
|
||||
activityContainer: renderActivity(data),
|
||||
contactsContainer: renderContacts(data),
|
||||
partnersContainer: renderPartners(data)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { renderAll };
|
||||
@@ -3,13 +3,55 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>СТС-Авто — Демонтаж нефтегазопроводов по России, утилизация отходов</title>
|
||||
<meta name="description" content="Демонтаж нефтегазопроводов по России, утилизация отходов. Собственная спецтехника. Лицензии СРО и НОСТРОЙ. Более {years} лет опыта.">
|
||||
<title>Демонтаж нефтегазопроводов, утилизация отходов, рекультивация земель — СТС-Авто</title>
|
||||
<meta name="description" content="Более 15 лет опыта в демонтаже трубопроводов, утилизации промышленных отходов и рекультивации земель по всей России. Работаем с объектами любого масштаба.">
|
||||
<link rel="canonical" href="https://sts-avto.com/">
|
||||
<meta property="og:title" content="Демонтаж и утилизация — СТС-Авто">
|
||||
<meta property="og:description" content="Профессиональный демонтаж нефтегазопроводов и рекультивация земель.">
|
||||
<meta property="og:image" content="https://sts-avto.com/img/og-image.webp">
|
||||
<meta property="og:url" content="https://sts-avto.com/">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:locale" content="ru_RU">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="Демонтаж и утилизация — СТС-Авто">
|
||||
<meta name="twitter:description" content="Профессиональный демонтаж нефтегазопроводов и рекультивация земель.">
|
||||
<meta name="twitter:image" content="https://sts-avto.com/img/og-image.webp">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
"name": "СТС-Авто",
|
||||
"url": "https://sts-avto.com/",
|
||||
"logo": "https://sts-avto.com/img/logo.webp",
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"addressLocality": "Екатеринбург",
|
||||
"streetAddress": "ул. Московская, 11, оф. 702"
|
||||
},
|
||||
"telephone": "+7-800-555-29-31",
|
||||
"areaServed": {
|
||||
"@type": "Country",
|
||||
"name": "Россия"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{ "@type": "ListItem", "position": 1, "name": "Главная", "item": "https://sts-avto.com/" },
|
||||
{ "@type": "ListItem", "position": 2, "name": "Услуги", "item": "https://sts-avto.com/#services" }
|
||||
]
|
||||
}
|
||||
</script>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800;900&family=Roboto+Condensed:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" media="print" onload="this.media='all'">
|
||||
<noscript><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"></noscript>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800;900&family=Roboto+Condensed:wght@400;500;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'">
|
||||
<noscript><link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800;900&family=Roboto+Condensed:wght@400;500;700&display=swap" rel="stylesheet"></noscript>
|
||||
<style>
|
||||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
@@ -701,7 +743,10 @@ img { max-width: 100%; display: block; }
|
||||
|
||||
<section class="hero" id="hero">
|
||||
<div class="hero-bg"><img src="./img/hero-bg.webp" alt="" fetchpriority="high"></div>
|
||||
<div class="container"><div class="hero-content" id="heroContent"></div></div>
|
||||
<div class="container">
|
||||
<h1 style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0">Демонтаж трубопроводов и утилизация отходов по всей России</h1>
|
||||
<div class="hero-content" id="heroContent"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section section-alt" id="about">
|
||||
@@ -783,7 +828,7 @@ img { max-width: 100%; display: block; }
|
||||
<svg viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>
|
||||
</button>
|
||||
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" defer></script>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
31
server.js
31
server.js
@@ -46,7 +46,16 @@ async function convertImagesToWebp() {
|
||||
}
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.get('/robots.txt', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'robots.txt'));
|
||||
});
|
||||
|
||||
app.get('/sitemap.xml', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'sitemap.xml'));
|
||||
});
|
||||
|
||||
app.use(express.static(path.join(__dirname, 'public'), { index: false }));
|
||||
|
||||
const dataDir = path.join(__dirname, 'data');
|
||||
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir);
|
||||
@@ -80,6 +89,7 @@ const settingsModule = require('./modules/settings');
|
||||
const usersModule = require('./modules/users');
|
||||
const leadsModule = require('./modules/leads');
|
||||
const siteDataModule = require('./modules/siteData');
|
||||
const ssrRenderer = require('./modules/ssrRenderer');
|
||||
|
||||
[
|
||||
crudModule, authModule, heroModule, aboutModule,
|
||||
@@ -114,11 +124,22 @@ app.get('/admin', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'admin.html'));
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
const years = new Date().getFullYear() - 2011;
|
||||
app.get('/', async (req, res) => {
|
||||
const htmlPath = path.join(__dirname, 'public', 'index.html');
|
||||
const html = fs.readFileSync(htmlPath, 'utf8');
|
||||
res.send(html.replace(/\{years\}/g, String(years)));
|
||||
let html = fs.readFileSync(htmlPath, 'utf8');
|
||||
|
||||
try {
|
||||
const siteData = await siteDataModule.loadSiteData();
|
||||
const ssrHtml = ssrRenderer.renderAll(siteData);
|
||||
for (const [containerId, content] of Object.entries(ssrHtml)) {
|
||||
const placeholder = 'id="' + containerId + '">';
|
||||
html = html.replace(placeholder, placeholder + content);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('SSR render error:', err.message);
|
||||
}
|
||||
|
||||
res.send(html);
|
||||
});
|
||||
|
||||
convertImagesToWebp().then(() => console.log('Images ready'));
|
||||
|
||||
Reference in New Issue
Block a user