Files
tourrism_site/src/helpers/site-settings.js
Andrey K. Choi ed871fc4d1 🎨 Добавлен полный редактор стилей и поле image_url для туров
 Новые функции:
- Поле image_url в модели туров для изменения изображений через админ-панель
- Расширенная модель настроек сайта с категориями: colors, typography, images, effects, layout
- Динамический CSS генератор на основе настроек (/dynamic-styles.css)
- API для управления настройками сайта (/api/site-settings)

🎯 Редактор стилей:
- Управление цветами (основные, акцентные, текст, фон)
- Настройка типографики (шрифты, размеры, межстрочный интервал)
- Управление изображениями (фоны, логотипы, фавикон)
- Эффекты (прозрачность, тени, размытие, скругления)
- Макет (высота секций, размеры контейнеров)
- Пользовательский CSS код

🛠️ Техническая реализация:
- SiteSettingsHelper с кешированием для производительности
- CSS переменные для динамического изменения стилей
- Автоматическая миграция базы данных
- Интеграция с AdminJS для удобного управления
- Загрузка настроек в шаблоны для доступности

📊 База данных:
- Расширена таблица site_settings (добавлено поле category)
- Новые типы настроек: color, file
- 27 предустановленных настроек для полного контроля над дизайном
- Автоматическое применение миграций при старте приложения
2025-11-29 22:03:00 +09:00

190 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import db from '../config/database.js';
class SiteSettingsHelper {
static cache = new Map();
static lastUpdate = null;
static cacheTimeout = 5 * 60 * 1000; // 5 минут
/**
* Получить все настройки сайта с кешированием
*/
static async getAllSettings() {
const now = Date.now();
// Проверяем кеш
if (this.lastUpdate && (now - this.lastUpdate) < this.cacheTimeout && this.cache.size > 0) {
return Object.fromEntries(this.cache);
}
try {
const result = await db.query('SELECT setting_key, setting_value, setting_type FROM site_settings ORDER BY category, setting_key');
// Очищаем кеш и заполняем новыми данными
this.cache.clear();
for (const row of result.rows) {
let value = row.setting_value;
// Преобразуем значение в зависимости от типа
switch (row.setting_type) {
case 'number':
value = parseFloat(value) || 0;
break;
case 'boolean':
value = value === 'true' || value === '1';
break;
case 'json':
try {
value = JSON.parse(value);
} catch {
value = {};
}
break;
}
this.cache.set(row.setting_key, value);
}
this.lastUpdate = now;
return Object.fromEntries(this.cache);
} catch (error) {
console.error('Error loading site settings:', error);
return {};
}
}
/**
* Получить настройку по ключу
*/
static async getSetting(key, defaultValue = null) {
const settings = await this.getAllSettings();
return settings[key] !== undefined ? settings[key] : defaultValue;
}
/**
* Установить настройку
*/
static async setSetting(key, value, type = 'text', description = '', category = 'general') {
try {
await db.query(`
INSERT INTO site_settings (setting_key, setting_value, setting_type, description, category, updated_at)
VALUES ($1, $2, $3, $4, $5, NOW())
ON CONFLICT (setting_key)
DO UPDATE SET
setting_value = EXCLUDED.setting_value,
setting_type = EXCLUDED.setting_type,
description = EXCLUDED.description,
category = EXCLUDED.category,
updated_at = NOW()
`, [key, String(value), type, description, category]);
// Обновляем кеш
this.cache.set(key, value);
return true;
} catch (error) {
console.error('Error setting site setting:', error);
return false;
}
}
/**
* Получить настройки по категории
*/
static async getSettingsByCategory(category) {
try {
const result = await db.query(
'SELECT setting_key, setting_value, setting_type FROM site_settings WHERE category = $1 ORDER BY setting_key',
[category]
);
const settings = {};
for (const row of result.rows) {
let value = row.setting_value;
switch (row.setting_type) {
case 'number':
value = parseFloat(value) || 0;
break;
case 'boolean':
value = value === 'true' || value === '1';
break;
case 'json':
try {
value = JSON.parse(value);
} catch {
value = {};
}
break;
}
settings[row.setting_key] = value;
}
return settings;
} catch (error) {
console.error('Error loading settings by category:', error);
return {};
}
}
/**
* Генерировать CSS переменные из настроек
*/
static async generateCSSVariables() {
const settings = await this.getAllSettings();
let css = ':root {\n';
// Цвета
if (settings.primary_color) css += ` --primary-color: ${settings.primary_color};\n`;
if (settings.secondary_color) css += ` --secondary-color: ${settings.secondary_color};\n`;
if (settings.accent_color) css += ` --accent-color: ${settings.accent_color};\n`;
if (settings.text_color) css += ` --text-color: ${settings.text_color};\n`;
if (settings.background_color) css += ` --background-color: ${settings.background_color};\n`;
if (settings.card_background) css += ` --card-background: ${settings.card_background};\n`;
// Типографика
if (settings.font_family_primary) css += ` --font-family-primary: ${settings.font_family_primary};\n`;
if (settings.font_family_display) css += ` --font-family-display: ${settings.font_family_display};\n`;
if (settings.font_size_base) css += ` --font-size-base: ${settings.font_size_base}px;\n`;
if (settings.line_height_base) css += ` --line-height-base: ${settings.line_height_base};\n`;
// Эффекты
if (settings.hero_overlay_opacity) css += ` --hero-overlay-opacity: ${settings.hero_overlay_opacity};\n`;
if (settings.hero_overlay_color) css += ` --hero-overlay-color: ${settings.hero_overlay_color};\n`;
if (settings.card_shadow) css += ` --card-shadow: ${settings.card_shadow};\n`;
if (settings.border_radius) css += ` --border-radius: ${settings.border_radius}px;\n`;
if (settings.blur_effect) css += ` --blur-effect: ${settings.blur_effect}px;\n`;
// Макет
if (settings.hero_height_desktop) css += ` --hero-height-desktop: ${settings.hero_height_desktop}vh;\n`;
if (settings.hero_height_mobile) css += ` --hero-height-mobile: ${settings.hero_height_mobile}vh;\n`;
if (settings.compact_hero_height) css += ` --compact-hero-height: ${settings.compact_hero_height}vh;\n`;
if (settings.container_max_width) css += ` --container-max-width: ${settings.container_max_width}px;\n`;
if (settings.navbar_height) css += ` --navbar-height: ${settings.navbar_height}px;\n`;
// Анимации
if (settings.animation_duration) css += ` --animation-duration: ${settings.animation_duration}s;\n`;
css += '}\n';
// Добавляем пользовательский CSS
if (settings.custom_css) {
css += '\n/* Пользовательский CSS */\n' + settings.custom_css + '\n';
}
return css;
}
/**
* Очистить кеш настроек
*/
static clearCache() {
this.cache.clear();
this.lastUpdate = null;
}
}
export default SiteSettingsHelper;