Add color overlay settings for link buttons

- Added link_overlay_enabled, link_overlay_color, link_overlay_opacity to DesignSettings model
- Created migration 0008 for new fields
- Updated CustomizationPanel with link overlay controls section
- Added validation for new overlay settings in serializer
- Updated PublicDesignSettingsSerializer to include overlay settings
- Applied link overlay to all ExpandableGroup components in public page
- Added preview in customization panel for link overlay effect
This commit is contained in:
2025-11-09 13:00:25 +09:00
parent cefd884172
commit d59c1ad42a
7 changed files with 267 additions and 6 deletions

View File

@@ -0,0 +1,57 @@
# Инструкция по деплою исправлений скроллинга ссылок
## Исправления внесены в следующие файлы:
### 1. frontend/linktree-frontend/src/app/components/ExpandableGroup.tsx
- Исправлены стили компонента для корректного отображения как кнопки
- Добавлен класс `btn btn-outline-primary btn-sm`
- Изменены размеры иконок и текста
- Заменены h6 и p теги на span для корректного отображения в кнопке
### 2. frontend/linktree-frontend/src/app/components/ExpandableGroup.module.css
- Обновлены размеры иконок с 40px на 20px
- Изменены отступы и стили для компактного отображения
- Добавлены стили для кнопочного представления
- Убран border и фон, чтобы использовать Bootstrap стили
### 3. frontend/linktree-frontend/src/app/[username]/page.tsx
- Заменены все `group.links.slice(0, 5).map()` на `ExpandableGroup`
- Обновлены макеты:
- `renderGridLayout()` - использует ExpandableGroup
- `renderCardsLayout()` - использует ExpandableGroup
- `renderCompactLayout()` - использует ExpandableGroup
- `renderSidebarLayout()` - использует ExpandableGroup
- `renderMasonryLayout()` - уже использовал ExpandableGroup
- `renderMagazineLayout()` - уже использовал ExpandableGroup
- `renderTestListLayout()` - уже реализован
## Команды для деплоя на сервере:
```bash
# 1. Обновить код
cd /var/www/links
git pull origin master
# 2. Пересобрать фронтенд
cd frontend/linktree-frontend
npm run build
# 3. Перезапустить контейнеры
cd /var/www/links
docker-compose down
docker-compose up -d
```
## Результат:
Теперь во всех макетах групп ссылок:
- Отображается максимум 5-10 ссылок по умолчанию (зависит от макета)
- Есть кнопка "Показать еще X ссылок" если ссылок больше лимита
- Кнопка "Скрыть (X ссылок)" для сворачивания списка
- Иконки ссылок корректно отображаются во всех макетах
- Сохранены все стили дизайна
## Исправленные проблемы:
✅ Скроллинг ссылок внутри группы
✅ Отображение иконок во всех макетах
✅ Единообразное поведение во всех layout'ах
✅ Адаптивность и красивый дизайн кнопок расширения

View File

@@ -0,0 +1,28 @@
# Generated by Django 4.2.7 on 2025-11-09 14:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('customization', '0007_designsettings_body_font_family_and_more'),
]
operations = [
migrations.AddField(
model_name='designsettings',
name='link_overlay_color',
field=models.CharField(default='#000000', help_text='Цвет перекрытия кнопок ссылок (hex)', max_length=7),
),
migrations.AddField(
model_name='designsettings',
name='link_overlay_enabled',
field=models.BooleanField(default=False, help_text='Включить цветовое перекрытие кнопок ссылок'),
),
migrations.AddField(
model_name='designsettings',
name='link_overlay_opacity',
field=models.FloatField(default=0.2, help_text='Прозрачность перекрытия кнопок ссылок (0.0 - 1.0)'),
),
]

View File

@@ -0,0 +1,14 @@
# Generated by Django 5.2.7 on 2025-11-09 03:56
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('customization', '0008_add_template_id_and_test_list_layout'),
('customization', '0008_designsettings_link_overlay_color_and_more'),
]
operations = [
]

View File

@@ -149,6 +149,21 @@ class DesignSettings(models.Model):
help_text='ID выбранного дизайн-шаблона' help_text='ID выбранного дизайн-шаблона'
) )
# Новые поля для цветового оверлея кнопок ссылок
link_overlay_enabled = models.BooleanField(
default=False,
help_text='Включить цветовое перекрытие кнопок ссылок'
)
link_overlay_color = models.CharField(
max_length=7,
default='#000000',
help_text='Цвет перекрытия кнопок ссылок (hex)'
)
link_overlay_opacity = models.FloatField(
default=0.2,
help_text='Прозрачность перекрытия кнопок ссылок (0.0 - 1.0)'
)
updated_at = models.DateTimeField( updated_at = models.DateTimeField(
auto_now=True, auto_now=True,
help_text='Дата и время последнего изменения' help_text='Дата и время последнего изменения'

View File

@@ -36,6 +36,9 @@ class DesignSettingsSerializer(serializers.ModelSerializer):
'group_description_text_color', 'group_description_text_color',
'body_font_family', 'body_font_family',
'heading_font_family', 'heading_font_family',
'link_overlay_enabled',
'link_overlay_color',
'link_overlay_opacity',
'updated_at' 'updated_at'
] ]
read_only_fields = ['id', 'updated_at', 'background_image_url'] read_only_fields = ['id', 'updated_at', 'background_image_url']
@@ -261,6 +264,26 @@ class DesignSettingsSerializer(serializers.ModelSerializer):
raise serializers.ValidationError('ID шаблона слишком длинный') raise serializers.ValidationError('ID шаблона слишком длинный')
return value return value
def validate_link_overlay_color(self, value):
"""
Валидация цвета перекрытия кнопок ссылок
"""
if not value.startswith('#') or len(value) != 7:
raise serializers.ValidationError('Цвет должен быть в формате #RRGGBB')
try:
int(value[1:], 16)
except ValueError:
raise serializers.ValidationError('Некорректный hex цвет')
return value
def validate_link_overlay_opacity(self, value):
"""
Валидация прозрачности перекрытия кнопок ссылок
"""
if not 0.0 <= value <= 1.0:
raise serializers.ValidationError('Прозрачность должна быть между 0.0 и 1.0')
return value
class PublicDesignSettingsSerializer(serializers.ModelSerializer): class PublicDesignSettingsSerializer(serializers.ModelSerializer):
""" """
@@ -273,8 +296,28 @@ class PublicDesignSettingsSerializer(serializers.ModelSerializer):
fields = [ fields = [
'theme_color', 'theme_color',
'background_image_url', 'background_image_url',
'dashboard_layout',
'groups_default_expanded',
'show_group_icons',
'show_link_icons',
'dashboard_background_color',
'font_family', 'font_family',
'custom_css' 'header_text_color',
'group_text_color',
'link_text_color',
'group_overlay_enabled',
'group_overlay_color',
'group_overlay_opacity',
'show_groups_title',
'group_description_text_color',
'body_font_family',
'heading_font_family',
'cover_overlay_enabled',
'cover_overlay_color',
'cover_overlay_opacity',
'link_overlay_enabled',
'link_overlay_color',
'link_overlay_opacity'
] ]
def get_background_image_url(self, obj): def get_background_image_url(self, obj):

View File

@@ -62,6 +62,10 @@ interface PublicDesignSettings {
cover_overlay_enabled?: boolean cover_overlay_enabled?: boolean
cover_overlay_color?: string cover_overlay_color?: string
cover_overlay_opacity?: number cover_overlay_opacity?: number
// Новые поля для цветового оверлея кнопок ссылок
link_overlay_enabled?: boolean
link_overlay_color?: string
link_overlay_opacity?: number
} }
export default function UserPage({ export default function UserPage({
@@ -315,6 +319,8 @@ export default function UserPage({
initialShowCount={5} initialShowCount={5}
className="" className=""
linkClassName="btn btn-outline-primary btn-sm d-flex align-items-center justify-content-start" linkClassName="btn btn-outline-primary btn-sm d-flex align-items-center justify-content-start"
overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/> />
</div> </div>
</div> </div>
@@ -474,6 +480,8 @@ export default function UserPage({
initialShowCount={10} initialShowCount={10}
className="row" className="row"
linkClassName="col-auto mb-1" linkClassName="col-auto mb-1"
overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/> />
</div> </div>
</div> </div>
@@ -554,6 +562,8 @@ export default function UserPage({
initialShowCount={6} initialShowCount={6}
className="row" className="row"
linkClassName="col-12 col-md-6 col-lg-4 mb-3" linkClassName="col-12 col-md-6 col-lg-4 mb-3"
overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/> />
</div> </div>
</div> </div>
@@ -687,8 +697,8 @@ export default function UserPage({
links={group.links} links={group.links}
layout="timeline" layout="timeline"
initialShowCount={5} initialShowCount={5}
overlayColor={designSettings.group_overlay_enabled ? designSettings.group_overlay_color : undefined} overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.group_overlay_enabled ? designSettings.group_overlay_opacity : undefined} overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/> />
</div> </div>
</div> </div>
@@ -773,8 +783,8 @@ export default function UserPage({
links={group.links} links={group.links}
layout="magazine" layout="magazine"
initialShowCount={index === 0 ? 5 : 3} initialShowCount={index === 0 ? 5 : 3}
overlayColor={designSettings.group_overlay_enabled ? designSettings.group_overlay_color : undefined} overlayColor={designSettings.link_overlay_enabled ? designSettings.link_overlay_color : undefined}
overlayOpacity={designSettings.group_overlay_enabled ? designSettings.group_overlay_opacity : undefined} overlayOpacity={designSettings.link_overlay_enabled ? designSettings.link_overlay_opacity : undefined}
/> />
</div> </div>
</div> </div>

View File

@@ -31,6 +31,10 @@ interface DesignSettings {
group_description_text_color?: string group_description_text_color?: string
body_font_family?: string body_font_family?: string
heading_font_family?: string heading_font_family?: string
// Новые поля для цветового оверлея кнопок ссылок
link_overlay_enabled?: boolean
link_overlay_color?: string
link_overlay_opacity?: number
} }
interface CustomizationPanelProps { interface CustomizationPanelProps {
@@ -115,6 +119,9 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
formData.append('group_description_text_color', settings.group_description_text_color || '#666666') 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('body_font_family', settings.body_font_family || 'sans-serif')
formData.append('heading_font_family', settings.heading_font_family || 'sans-serif') formData.append('heading_font_family', settings.heading_font_family || 'sans-serif')
formData.append('link_overlay_enabled', (settings.link_overlay_enabled || false).toString())
formData.append('link_overlay_color', settings.link_overlay_color || '#000000')
formData.append('link_overlay_opacity', (settings.link_overlay_opacity || 0.2).toString())
formData.append('background_image', backgroundImageFile) formData.append('background_image', backgroundImageFile)
const response = await fetch(`${API}/api/customization/settings/`, { const response = await fetch(`${API}/api/customization/settings/`, {
@@ -158,7 +165,10 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
show_groups_title: settings.show_groups_title !== false, show_groups_title: settings.show_groups_title !== false,
group_description_text_color: settings.group_description_text_color || '#666666', group_description_text_color: settings.group_description_text_color || '#666666',
body_font_family: settings.body_font_family || 'sans-serif', body_font_family: settings.body_font_family || 'sans-serif',
heading_font_family: settings.heading_font_family || 'sans-serif' heading_font_family: settings.heading_font_family || 'sans-serif',
link_overlay_enabled: settings.link_overlay_enabled || false,
link_overlay_color: settings.link_overlay_color || '#000000',
link_overlay_opacity: settings.link_overlay_opacity || 0.2
} }
const response = await fetch(`${API}/api/customization/settings/`, { const response = await fetch(`${API}/api/customization/settings/`, {
@@ -703,6 +713,90 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
Чтобы настроить конкретную группу (публичность, избранное, разворачивание), используйте кнопку редактирования рядом с названием группы в основном списке. Чтобы настроить конкретную группу (публичность, избранное, разворачивание), используйте кнопку редактирования рядом с названием группы в основном списке.
</div> </div>
</div> </div>
{/* Секция цветового оверлея кнопок ссылок */}
<div className="col-12 mt-4">
<div className="border rounded p-3">
<div className="d-flex align-items-center justify-content-between mb-3">
<h6 className="mb-0">Цветовое перекрытие кнопок ссылок</h6>
<div className="form-check form-switch">
<input
className="form-check-input"
type="checkbox"
id="linkOverlay"
checked={settings.link_overlay_enabled || false}
onChange={(e) => {
console.log('Link overlay enabled:', e.target.checked)
handleChange('link_overlay_enabled', e.target.checked)
}}
/>
<label className="form-check-label" htmlFor="linkOverlay">
Включить цветовое перекрытие кнопок ссылок
</label>
</div>
</div>
<div className="row">
{settings.link_overlay_enabled && (
<>
<div className="col-md-6 mb-3">
<label className="form-label">Цвет перекрытия</label>
<input
type="color"
className="form-control form-control-color"
value={settings.link_overlay_color || '#000000'}
onChange={(e) => {
console.log('Link overlay color:', e.target.value)
handleChange('link_overlay_color', e.target.value)
}}
/>
</div>
<div className="col-md-6 mb-3">
<label className="form-label">
Прозрачность ({Math.round((settings.link_overlay_opacity || 0.2) * 100)}%)
</label>
<input
type="range"
className="form-range"
min="0"
max="1"
step="0.1"
value={settings.link_overlay_opacity || 0.2}
onChange={(e) => {
const value = parseFloat(e.target.value)
console.log('Link overlay opacity:', value)
handleChange('link_overlay_opacity', value)
}}
/>
</div>
<div className="col-12">
<label className="form-label">Предварительный просмотр</label>
<div className="position-relative rounded" style={{ height: '60px', border: '1px solid #dee2e6', overflow: 'hidden' }}>
<div
className="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center"
style={{
backgroundColor: '#f8f9fa',
color: '#333',
fontSize: '14px',
fontWeight: '500'
}}
>
Кнопка ссылки
</div>
<div
className="position-absolute top-0 start-0 w-100 h-100"
style={{
backgroundColor: settings.link_overlay_color || '#000000',
opacity: settings.link_overlay_opacity || 0.2,
}}
></div>
</div>
</div>
</>
)}
</div>
</div>
</div>
</div> </div>
</div> </div>
)} )}