Add comprehensive group customization features

- Add group overlay color and opacity settings
- Add font customization (body and heading fonts)
- Add group description text color control
- Add option to hide 'Groups' title
- Update frontend DesignSettings interface
- Update CustomizationPanel with new UI controls
- Update Django model with new fields
- Create migration for new customization options
- Update DRF serializer with validation
This commit is contained in:
2025-11-09 10:27:04 +09:00
parent 6035cf8d10
commit 92e2854575
5 changed files with 351 additions and 3 deletions

View File

@@ -20,6 +20,14 @@ interface DesignSettings {
cover_overlay_enabled?: boolean
cover_overlay_color?: string
cover_overlay_opacity?: number
// Новые опции кастомизации
group_overlay_enabled?: boolean
group_overlay_color?: string
group_overlay_opacity?: number
show_groups_title?: boolean
group_description_text_color?: string
body_font_family?: string
heading_font_family?: string
}
interface CustomizationPanelProps {
@@ -96,6 +104,13 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
formData.append('cover_overlay_enabled', (settings.cover_overlay_enabled || false).toString())
formData.append('cover_overlay_color', settings.cover_overlay_color || '#000000')
formData.append('cover_overlay_opacity', (settings.cover_overlay_opacity || 0.3).toString())
formData.append('group_overlay_enabled', (settings.group_overlay_enabled || false).toString())
formData.append('group_overlay_color', settings.group_overlay_color || '#000000')
formData.append('group_overlay_opacity', (settings.group_overlay_opacity || 0.3).toString())
formData.append('show_groups_title', (settings.show_groups_title !== false).toString())
formData.append('group_description_text_color', settings.group_description_text_color || '#666666')
formData.append('body_font_family', settings.body_font_family || 'sans-serif')
formData.append('heading_font_family', settings.heading_font_family || 'sans-serif')
formData.append('background_image', backgroundImageFile)
const response = await fetch(`${API}/api/customization/settings/`, {
@@ -132,7 +147,14 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
link_text_color: settings.link_text_color || '#666666',
cover_overlay_enabled: settings.cover_overlay_enabled || false,
cover_overlay_color: settings.cover_overlay_color || '#000000',
cover_overlay_opacity: settings.cover_overlay_opacity || 0.3
cover_overlay_opacity: settings.cover_overlay_opacity || 0.3,
group_overlay_enabled: settings.group_overlay_enabled || false,
group_overlay_color: settings.group_overlay_color || '#000000',
group_overlay_opacity: settings.group_overlay_opacity || 0.3,
show_groups_title: settings.show_groups_title !== false,
group_description_text_color: settings.group_description_text_color || '#666666',
body_font_family: settings.body_font_family || 'sans-serif',
heading_font_family: settings.heading_font_family || 'sans-serif'
}
const response = await fetch(`${API}/api/customization/settings/`, {
@@ -505,6 +527,127 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
</label>
</div>
</div>
{/* Новые настройки */}
<div className="col-12 mb-3">
<div className="form-check form-switch">
<input
className="form-check-input"
type="checkbox"
checked={settings.show_groups_title !== false}
onChange={(e) => handleChange('show_groups_title', e.target.checked)}
/>
<label className="form-check-label">
Показывать заголовок "Группы ссылок"
</label>
</div>
</div>
<div className="col-md-6 mb-3">
<label className="form-label">Цвет описаний групп</label>
<div className="input-group">
<input
type="color"
className="form-control form-control-color"
value={settings.group_description_text_color || '#666666'}
onChange={(e) => handleChange('group_description_text_color', e.target.value)}
/>
<input
type="text"
className="form-control"
value={settings.group_description_text_color || '#666666'}
onChange={(e) => handleChange('group_description_text_color', e.target.value)}
/>
</div>
</div>
{/* Перекрытие групп цветом */}
<div className="col-12 mb-3">
<div className="card">
<div className="card-header">
<h6 className="mb-0">Цветовое перекрытие групп</h6>
</div>
<div className="card-body">
<div className="form-check mb-3">
<input
className="form-check-input"
type="checkbox"
id="groupOverlayEnabled"
checked={settings.group_overlay_enabled || false}
onChange={(e) => handleChange('group_overlay_enabled', e.target.checked)}
/>
<label className="form-check-label" htmlFor="groupOverlayEnabled">
Включить цветовое перекрытие групп
</label>
</div>
{settings.group_overlay_enabled && (
<>
<div className="row">
<div className="col-6 mb-3">
<label className="form-label">Цвет перекрытия</label>
<div className="d-flex gap-2">
<input
type="color"
className="form-control form-control-color"
value={settings.group_overlay_color || '#000000'}
onChange={(e) => handleChange('group_overlay_color', e.target.value)}
title="Выберите цвет перекрытия"
/>
<input
type="text"
className="form-control"
value={settings.group_overlay_color || '#000000'}
onChange={(e) => handleChange('group_overlay_color', e.target.value)}
placeholder="#000000"
title="Hex код цвета"
/>
</div>
</div>
<div className="col-6 mb-3">
<label className="form-label">
Прозрачность ({Math.round((settings.group_overlay_opacity || 0.3) * 100)}%)
</label>
<input
type="range"
className="form-range"
min="0"
max="1"
step="0.1"
value={settings.group_overlay_opacity || 0.3}
onChange={(e) => handleChange('group_overlay_opacity', parseFloat(e.target.value))}
title="Настройка прозрачности перекрытия"
/>
</div>
</div>
{/* Preview */}
<div className="mb-3">
<label className="form-label">Предварительный просмотр</label>
<div className="position-relative rounded" style={{ height: '80px', border: '1px solid #dee2e6', overflow: 'hidden' }}>
<div
className="w-100 h-100 d-flex align-items-center justify-content-center text-white fw-bold"
style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
}}
>
Пример группы
</div>
<div
className="position-absolute top-0 start-0 w-100 h-100"
style={{
backgroundColor: settings.group_overlay_color || '#000000',
opacity: settings.group_overlay_opacity || 0.3
}}
></div>
</div>
</div>
</>
)}
</div>
</div>
</div>
<div className="col-12">
<div className="alert alert-info">
<i className="bi bi-info-circle me-2"></i>
@@ -520,8 +663,11 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
{activeTab === 'advanced' && (
<div className="tab-pane fade show active">
<div className="row">
<div className="col-12 mb-3">
<label className="form-label">Шрифт</label>
<div className="col-12 mb-4">
<h6 className="text-muted">Настройки шрифтов</h6>
</div>
<div className="col-md-6 mb-3">
<label className="form-label">Основной шрифт</label>
<select
className="form-select"
value={settings.font_family}
@@ -533,8 +679,61 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
<option value="Inter, sans-serif">Inter</option>
<option value="Roboto, sans-serif">Roboto</option>
<option value="Open Sans, sans-serif">Open Sans</option>
<option value="Source Sans Pro, sans-serif">Source Sans Pro</option>
<option value="Lato, sans-serif">Lato</option>
<option value="Nunito, sans-serif">Nunito</option>
</select>
</div>
<div className="col-md-6 mb-3">
<label className="form-label">Шрифт заголовков</label>
<select
className="form-select"
value={settings.heading_font_family || settings.font_family}
onChange={(e) => handleChange('heading_font_family', e.target.value)}
>
<option value="">Как основной</option>
<option value="sans-serif">Sans Serif</option>
<option value="serif">Serif</option>
<option value="monospace">Monospace</option>
<option value="Inter, sans-serif">Inter</option>
<option value="Roboto, sans-serif">Roboto</option>
<option value="Open Sans, sans-serif">Open Sans</option>
<option value="Source Sans Pro, sans-serif">Source Sans Pro</option>
<option value="Lato, sans-serif">Lato</option>
<option value="Nunito, sans-serif">Nunito</option>
<option value="Playfair Display, serif">Playfair Display</option>
<option value="Merriweather, serif">Merriweather</option>
<option value="Oswald, sans-serif">Oswald</option>
<option value="Montserrat, sans-serif">Montserrat</option>
</select>
</div>
<div className="col-md-6 mb-3">
<label className="form-label">Шрифт основного текста</label>
<select
className="form-select"
value={settings.body_font_family || settings.font_family}
onChange={(e) => handleChange('body_font_family', e.target.value)}
>
<option value="">Как основной</option>
<option value="sans-serif">Sans Serif</option>
<option value="serif">Serif</option>
<option value="monospace">Monospace</option>
<option value="Inter, sans-serif">Inter</option>
<option value="Roboto, sans-serif">Roboto</option>
<option value="Open Sans, sans-serif">Open Sans</option>
<option value="Source Sans Pro, sans-serif">Source Sans Pro</option>
<option value="Lato, sans-serif">Lato</option>
<option value="Nunito, sans-serif">Nunito</option>
<option value="Georgia, serif">Georgia</option>
<option value="Times New Roman, serif">Times New Roman</option>
</select>
</div>
<div className="col-12 mb-4">
<hr />
<h6 className="text-muted">Дополнительные настройки</h6>
</div>
<div className="col-12 mb-3">
<label className="form-label">Дополнительный CSS</label>
<textarea