@@ -143,7 +143,11 @@ function initDatabase() {
|
||||
)`);
|
||||
|
||||
db.get(`SELECT COUNT(*) as cnt FROM settings`, (err, row) => {
|
||||
if (row && row.cnt > 0) { resolve(); return; }
|
||||
if (row && row.cnt > 0) {
|
||||
db.run(`INSERT OR IGNORE INTO settings (key, value) VALUES ('logo_path', './img/logo.webp')`);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
seedAll().then(resolve).catch(reject);
|
||||
});
|
||||
});
|
||||
@@ -274,6 +278,7 @@ async function seedAll() {
|
||||
]);
|
||||
|
||||
await runEach('INSERT OR IGNORE INTO settings (key, value) VALUES (?, ?)', [
|
||||
['logo_path', './img/logo.webp'],
|
||||
['contact_phone_800', '8 (800) 555-29-31'], ['contact_phone_ekb', '8 (343) 237-25-66'],
|
||||
['contact_email', 'sts_zakaz@mail.ru'], ['contact_address', 'г. Екатеринбург, ул. Московская, 11, оф. 702'],
|
||||
['contact_work_hours', 'Пн–Пт: 9:00 – 18:00|Сб: 10:00 – 15:00'], ['contact_map_image', './img/secret.webp'],
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const sharp = require('sharp');
|
||||
|
||||
let db;
|
||||
|
||||
function init(database) { db = database; }
|
||||
@@ -5,6 +10,60 @@ function init(database) { db = database; }
|
||||
function setupRoutes(app, auth) {
|
||||
const { authenticateToken, requireAdmin } = auth;
|
||||
|
||||
const imgDir = path.join(__dirname, '..', '..', 'public', 'img');
|
||||
const favDir = path.join(__dirname, '..', '..', 'public');
|
||||
if (!fs.existsSync(imgDir)) fs.mkdirSync(imgDir, { recursive: true });
|
||||
|
||||
const storage = multer.memoryStorage();
|
||||
const uploadLogo = multer({
|
||||
storage,
|
||||
limits: { fileSize: 5 * 1024 * 1024 },
|
||||
fileFilter: (req, file, cb) => {
|
||||
const allowed = ['.jpg', '.jpeg', '.png', '.webp', '.svg', '.gif', '.bmp'];
|
||||
const ext = path.extname(file.originalname).toLowerCase();
|
||||
if (!allowed.includes(ext)) return cb(new Error('Недопустимый формат файла'));
|
||||
cb(null, true);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/admin/settings/logo', authenticateToken, requireAdmin, uploadLogo.single('logo'), async (req, res) => {
|
||||
if (!req.file) return res.status(400).json({ error: 'Файл не загружен' });
|
||||
|
||||
try {
|
||||
const ext = path.extname(req.file.originalname).toLowerCase();
|
||||
const logoWebp = path.join(imgDir, 'logo.webp');
|
||||
const fav32 = path.join(favDir, 'favicon-32x32.png');
|
||||
const fav16 = path.join(favDir, 'favicon-16x16.png');
|
||||
const apple = path.join(favDir, 'apple-touch-icon.png');
|
||||
|
||||
const pipeline = sharp(req.file.buffer);
|
||||
|
||||
if (ext !== '.svg') {
|
||||
await pipeline.clone().webp({ quality: 85 }).toFile(logoWebp);
|
||||
} else {
|
||||
fs.writeFileSync(logoWebp, req.file.buffer);
|
||||
}
|
||||
|
||||
if (ext !== '.svg') {
|
||||
await pipeline.clone().resize(32, 32, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toFile(fav32);
|
||||
await pipeline.clone().resize(16, 16, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toFile(fav16);
|
||||
await pipeline.clone().resize(180, 180, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }).png().toFile(apple);
|
||||
} else {
|
||||
fs.writeFileSync(fav32, req.file.buffer);
|
||||
fs.writeFileSync(fav16, req.file.buffer);
|
||||
fs.writeFileSync(apple, req.file.buffer);
|
||||
}
|
||||
|
||||
const logoPath = './img/logo.webp';
|
||||
db.run(`INSERT INTO settings (key, value) VALUES ('logo_path', ?) ON CONFLICT(key) DO UPDATE SET value = ?`, [logoPath, logoPath]);
|
||||
|
||||
res.json({ path: logoPath, url: logoPath });
|
||||
} catch (e) {
|
||||
console.error('Ошибка при обработке логотипа:', e);
|
||||
res.status(500).json({ error: 'Ошибка обработки изображения' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/admin/settings', authenticateToken, requireAdmin, (req, res) => {
|
||||
db.all(`SELECT * FROM settings ORDER BY id`, (err, rows) => {
|
||||
if (err) return res.status(500).json({ error: 'Ошибка БД' });
|
||||
|
||||
@@ -813,6 +813,14 @@ window.tab_settings = function(tabId) {
|
||||
|
||||
document.getElementById('mainContent').innerHTML = h() +
|
||||
'<div class="settings-grid">' +
|
||||
'<div class="settings-card"><h3>Логотип сайта</h3>' +
|
||||
'<div id="logoPreview" style="margin-bottom:10px">' +
|
||||
(s.logo_path ? '<img src="' + s.logo_path + '" class="preview-img" style="max-height:80px">' : '<span class="text-muted">Логотип не загружен</span>') +
|
||||
'</div>' +
|
||||
'<input type="file" id="logoFileInput" accept=".jpg,.jpeg,.png,.webp,.svg,.gif,.bmp" style="margin-bottom:8px;width:100%">' +
|
||||
'<button class="btn btn-sm btn-primary" onclick="winUploadLogo()"><i class="fas fa-upload"></i> Загрузить логотип</button>' +
|
||||
'<p style="font-size:.75rem;color:var(--text-gray);margin-top:3px">При загрузке автоматически создаются favicon и apple-touch-icon</p>' +
|
||||
'</div>' +
|
||||
'<div class="settings-card"><h3>Контакты</h3>' +
|
||||
contactKeys.map(function(k) { return '<p style="margin-bottom:4px"><strong>' + k + ':</strong> <span class="text-muted">' + esc(String(s[k]||'').substring(0,80)) + '</span></p>'; }).join('') +
|
||||
'<button class="btn btn-sm btn-primary" style="margin-top:8px" onclick="winEditSettings(\'contacts\')"><i class="fas fa-edit"></i> Редактировать</button>' +
|
||||
@@ -824,6 +832,25 @@ window.tab_settings = function(tabId) {
|
||||
'</div>';
|
||||
|
||||
window._settings = s;
|
||||
window.winUploadLogo = async function() {
|
||||
var inp = document.getElementById('logoFileInput');
|
||||
if (!inp.files.length) { toast('Выберите файл','error'); return; }
|
||||
var fd = new FormData();
|
||||
fd.append('logo', inp.files[0]);
|
||||
try {
|
||||
var r = await fetch(BASE + '/settings/logo', {
|
||||
method: 'POST', headers: {'Authorization':'Bearer ' + token}, body: fd
|
||||
}).then(function(x) { return x.json(); });
|
||||
if (r.path) {
|
||||
toast('Логотип обновлён! Favicon сгенерированы');
|
||||
loadCurrentTab();
|
||||
} else {
|
||||
toast(r.error || 'Ошибка загрузки','error');
|
||||
}
|
||||
} catch(e) {
|
||||
toast('Ошибка загрузки','error');
|
||||
}
|
||||
};
|
||||
window.winEditSettings = function(mode) {
|
||||
var s = window._settings;
|
||||
var keys = mode === 'contacts' ? contactKeys : formKeys;
|
||||
|
||||
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 468 B |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 136 KiB |
@@ -5,6 +5,9 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<title>СТС-Авто — Демонтаж нефтегазопроводов по России, утилизация отходов</title>
|
||||
<meta name="description" content="Демонтаж нефтегазопроводов по России, утилизация отходов. Собственная спецтехника. Лицензии СРО и НОСТРОЙ. Более {years} лет опыта.">
|
||||
<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">
|
||||
<style>
|
||||
@@ -1130,6 +1133,10 @@ img { max-width: 100%; display: block; }
|
||||
|
||||
function initSiteData(data) {
|
||||
SITE = data;
|
||||
if (data.settings && data.settings.logo_path) {
|
||||
var logoImg = document.querySelector('.logo img');
|
||||
if (logoImg) logoImg.src = data.settings.logo_path;
|
||||
}
|
||||
renderHero(data);
|
||||
renderAbout(data);
|
||||
renderServices(data);
|
||||
|
||||
6
public/robots.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Disallow: /admin
|
||||
Disallow: /api/
|
||||
|
||||
Sitemap: https://sts-avto.com/sitemap.xml
|
||||
28
public/sitemap.xml
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://sts-avto.com/</loc>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sts-avto.com/privacy.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sts-avto.com/terms.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sts-avto.com/cookies.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://sts-avto.com/requisites.html</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.3</priority>
|
||||
</url>
|
||||
</urlset>
|
||||