🎨 Добавлен полный редактор стилей и поле 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 предустановленных настроек для полного контроля над дизайном
- Автоматическое применение миграций при старте приложения
This commit is contained in:
2025-11-29 22:03:00 +09:00
parent a461fea9d9
commit ed871fc4d1
8 changed files with 528 additions and 28 deletions

View File

@@ -42,6 +42,7 @@ const Routes = sequelize.define('routes', {
price: { type: DataTypes.DECIMAL(10, 2) },
duration: { type: DataTypes.INTEGER },
max_group_size: { type: DataTypes.INTEGER },
image_url: { type: DataTypes.STRING },
is_featured: { type: DataTypes.BOOLEAN, defaultValue: false },
is_active: { type: DataTypes.BOOLEAN, defaultValue: true },
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW },
@@ -187,6 +188,20 @@ const Holidays = sequelize.define('holidays', {
tableName: 'holidays'
});
// Модель настроек сайта
const SiteSettings = sequelize.define('site_settings', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
setting_key: { type: DataTypes.STRING, allowNull: false, unique: true },
setting_value: { type: DataTypes.TEXT },
setting_type: { type: DataTypes.ENUM('text', 'number', 'boolean', 'json', 'color', 'file'), defaultValue: 'text' },
description: { type: DataTypes.TEXT },
category: { type: DataTypes.STRING, defaultValue: 'general' },
updated_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
}, {
timestamps: false,
tableName: 'site_settings'
});
// Определение связей между моделями
Guides.hasOne(GuideSchedules, { foreignKey: 'guide_id' });
GuideSchedules.belongsTo(Guides, { foreignKey: 'guide_id' });
@@ -222,8 +237,8 @@ const adminJsOptions = {
options: {
parent: { name: 'Контент', icon: 'DocumentText' },
listProperties: ['id', 'title', 'type', 'price', 'duration', 'is_active', 'created_at'],
editProperties: ['title', 'description', 'content', 'type', 'difficulty_level', 'price', 'duration', 'max_group_size', 'is_featured', 'is_active'],
showProperties: ['id', 'title', 'description', 'content', 'type', 'difficulty_level', 'price', 'duration', 'max_group_size', 'is_featured', 'is_active', 'created_at', 'updated_at'],
editProperties: ['title', 'description', 'content', 'type', 'difficulty_level', 'price', 'duration', 'max_group_size', 'image_url', 'is_featured', 'is_active'],
showProperties: ['id', 'title', 'description', 'content', 'type', 'difficulty_level', 'price', 'duration', 'max_group_size', 'image_url', 'is_featured', 'is_active', 'created_at', 'updated_at'],
filterProperties: ['title', 'type', 'is_active'],
properties: {
title: {
@@ -263,6 +278,10 @@ const adminJsOptions = {
type: 'number',
isRequired: true,
},
image_url: {
type: 'string',
description: 'URL изображения тура (например: /images/tours/seoul-1.jpg)'
},
is_featured: { type: 'boolean' },
is_active: { type: 'boolean' },
created_at: {
@@ -633,6 +652,58 @@ const adminJsOptions = {
}
},
}
},
{
resource: SiteSettings,
options: {
parent: { name: 'Администрирование', icon: 'Settings' },
listProperties: ['id', 'setting_key', 'setting_value', 'category', 'updated_at'],
editProperties: ['setting_key', 'setting_value', 'setting_type', 'description', 'category'],
showProperties: ['id', 'setting_key', 'setting_value', 'setting_type', 'description', 'category', 'updated_at'],
filterProperties: ['setting_key', 'category', 'setting_type'],
properties: {
setting_key: {
isTitle: true,
isRequired: true,
description: 'Уникальный ключ настройки (например: primary_color, hero_background_url)'
},
setting_value: {
type: 'textarea',
isRequired: true,
description: 'Значение настройки (цвет в HEX, URL изображения, текст и т.д.)'
},
setting_type: {
availableValues: [
{ value: 'text', label: 'Текст' },
{ value: 'number', label: 'Число' },
{ value: 'boolean', label: 'Да/Нет' },
{ value: 'json', label: 'JSON' },
{ value: 'color', label: 'Цвет (HEX)' },
{ value: 'file', label: 'Файл/URL' }
],
isRequired: true
},
description: {
type: 'textarea',
description: 'Описание назначения этой настройки'
},
category: {
availableValues: [
{ value: 'general', label: 'Общие' },
{ value: 'theme', label: 'Тема и стили' },
{ value: 'colors', label: 'Цвета' },
{ value: 'typography', label: 'Типографика' },
{ value: 'images', label: 'Изображения' },
{ value: 'effects', label: 'Эффекты' },
{ value: 'layout', label: 'Макет' }
],
defaultValue: 'general'
},
updated_at: {
isVisible: { list: true, filter: true, show: true, edit: false },
}
},
}
}
],
rootPath: '/admin',