Исправлена подсветка шаблонов, добавлен макет тестовый список, исправлены проблемы со шрифтами

- Добавлено поле template_id в модель DesignSettings
- Исправлена логика подсветки выбранного шаблона в TemplatesSelector
- Добавлен новый макет 'test-list' - полный несворачиваемый список
- Обновлены шрифты с поддержкой CSS переменных
- Создан CSS модуль для тестового списка
- Обеспечена совместимость иконок во всех макетах
This commit is contained in:
2025-11-09 11:53:17 +09:00
parent 0c1a39f07d
commit 90ac03663f
8 changed files with 429 additions and 88 deletions

View File

@@ -0,0 +1,133 @@
/* Стили для макета "Тестовый список" */
.testListLayout {
max-width: 800px;
margin: 0 auto;
/* Применяем пользовательские шрифты через CSS переменные */
font-family: var(--user-font-family, 'Inter', sans-serif);
}
.testListLayout h5 {
font-family: var(--user-heading-font-family, var(--user-font-family, 'Inter', sans-serif));
}
.linkGroup {
margin-bottom: 2rem;
border: none;
background: transparent;
}
.groupHeader {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white;
padding: 1rem;
border-radius: 8px;
margin-bottom: 0.75rem;
font-weight: 600;
font-size: 1.125rem;
display: flex;
align-items: center;
font-family: var(--user-heading-font-family, var(--user-font-family, 'Inter', sans-serif));
}
.groupLinks {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0;
background: transparent;
}
.linkItem {
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 0.75rem 1rem;
transition: all 0.2s ease;
display: flex;
align-items: center;
text-decoration: none;
color: #475569;
font-family: var(--user-body-font-family, var(--user-font-family, 'Inter', sans-serif));
}
.linkItem:hover {
background: #f1f5f9;
border-color: #6366f1;
transform: translateX(4px);
color: inherit;
text-decoration: none;
}
.linkIcon {
margin-right: 0.75rem;
width: 20px;
height: 20px;
flex-shrink: 0;
border-radius: 4px;
object-fit: cover;
}
.linkTitle {
font-weight: 500;
color: #334155;
flex-grow: 1;
font-family: inherit;
}
.linkDescription {
color: #6b7280;
font-size: 0.875rem;
margin-left: auto;
opacity: 0.8;
font-family: inherit;
}
/* Для групп с иконками в заголовке */
.groupHeader .linkIcon {
margin-right: 0.5rem;
width: 24px;
height: 24px;
}
/* Адаптивность */
@media (max-width: 768px) {
.testListLayout {
max-width: 100%;
padding: 0 1rem;
}
.groupHeader {
padding: 0.75rem;
font-size: 1rem;
}
.linkItem {
padding: 0.5rem 0.75rem;
}
.linkDescription {
display: none;
}
}
/* Темная тема поддержка */
@media (prefers-color-scheme: dark) {
.linkItem {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.2);
color: #e2e8f0;
}
.linkItem:hover {
background: rgba(255, 255, 255, 0.2);
border-color: #8b5cf6;
}
.linkTitle {
color: #f1f5f9;
}
.linkDescription {
color: #cbd5e1;
}
}

View File

@@ -7,6 +7,7 @@ import Link from 'next/link'
import { useEffect, useState, Fragment } from 'react'
import FontLoader from '../components/FontLoader'
import ExpandableGroup from '../components/ExpandableGroup'
import styles from './TestListLayout.module.css'
interface LinkItem {
id: number
@@ -836,9 +837,79 @@ export default function UserPage({
</div>
)
// Тестовый список - все группы и ссылки в одном списке
const renderTestListLayout = () => (
<div className={styles.testListLayout}>
{(designSettings.show_groups_title !== false) && (
<h5 className="mb-4" style={{
color: designSettings.header_text_color || designSettings.theme_color,
fontFamily: designSettings.heading_font_family || designSettings.font_family
}}>
Все группы и ссылки
</h5>
)}
{data!.groups.map((group) => (
<div key={group.id} className={styles.linkGroup}>
{/* Заголовок группы */}
<div className={styles.groupHeader}>
{group.icon_url && designSettings.show_group_icons && (
<img
src={group.icon_url}
width={24}
height={24}
className={styles.linkIcon}
alt={group.name}
/>
)}
<span className={styles.linkTitle}>{group.name}</span>
{group.is_favorite && (
<i className="bi bi-star-fill text-warning ms-2"></i>
)}
</div>
{/* Описание группы если есть */}
{group.description && (
<p className={`${styles.linkDescription} mb-3 ps-2`}>
{group.description}
</p>
)}
{/* Ссылки группы */}
<div className={styles.groupLinks}>
{group.links.map((link) => (
<a
key={link.id}
href={link.url}
target="_blank"
rel="noopener noreferrer"
className={styles.linkItem}
>
{link.icon_url && designSettings.show_link_icons && (
<img
src={link.icon_url}
width={20}
height={20}
className={styles.linkIcon}
alt={link.title}
/>
)}
<span className={styles.linkTitle}>{link.title}</span>
{link.description && (
<small className={styles.linkDescription}>{link.description}</small>
)}
</a>
))}
</div>
</div>
))}
</div>
)
// Основная функция рендеринга групп в зависимости от выбранного макета
const renderGroupsLayout = () => {
switch (designSettings.dashboard_layout) {
case 'test-list':
return renderTestListLayout()
case 'list':
return renderListLayout()
case 'grid':
@@ -888,8 +959,17 @@ export default function UserPage({
backgroundAttachment: 'fixed',
minHeight: '100vh',
paddingTop: '2rem', // отступ сверху для рамки фона
paddingBottom: '2rem' // отступ снизу для рамки фона
}
paddingBottom: '2rem', // отступ снизу для рамки фона
// CSS переменные для использования в стилях
'--user-font-family': designSettings.font_family,
'--user-heading-font-family': designSettings.heading_font_family || designSettings.font_family,
'--user-body-font-family': designSettings.body_font_family || designSettings.font_family,
'--user-theme-color': designSettings.theme_color,
'--user-header-text-color': designSettings.header_text_color || designSettings.theme_color,
'--user-group-text-color': designSettings.group_text_color || '#333333',
'--user-link-text-color': designSettings.link_text_color || '#666666',
'--user-group-description-text-color': designSettings.group_description_text_color || '#666666'
} as React.CSSProperties
return (
<>

View File

@@ -6,6 +6,7 @@ import { designTemplates, DesignTemplate } from '../constants/designTemplates'
interface DesignSettings {
id?: number
template_id?: string
theme_color: string
background_image?: string
background_image_url?: string
@@ -92,6 +93,7 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
const formData = new FormData()
// Добавляем все настройки
formData.append('template_id', settings.template_id || '')
formData.append('theme_color', settings.theme_color)
formData.append('dashboard_layout', settings.dashboard_layout)
formData.append('groups_default_expanded', settings.groups_default_expanded.toString())
@@ -135,6 +137,7 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
} else {
// Если файл не выбран, отправляем только JSON настройки (картинка остается прежней)
const editableSettings = {
template_id: settings.template_id,
theme_color: settings.theme_color,
dashboard_layout: settings.dashboard_layout,
groups_default_expanded: settings.groups_default_expanded,
@@ -144,7 +147,6 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
font_family: settings.font_family,
custom_css: settings.custom_css,
header_text_color: settings.header_text_color || '#000000',
header_text_color: settings.header_text_color || '#000000',
group_text_color: settings.group_text_color || '#333333',
link_text_color: settings.link_text_color || '#666666',
cover_overlay_enabled: settings.cover_overlay_enabled || false,
@@ -195,10 +197,31 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
setSettings(prev => ({
...prev,
...template.settings,
id: prev.id // Сохраняем оригинальный ID
id: prev.id, // Сохраняем оригинальный ID
template_id: template.id // Добавляем ID шаблона для отслеживания
}))
}
// Определяем текущий шаблон
const getCurrentTemplateId = () => {
// Если есть сохраненный template_id
if ((settings as any).template_id) {
return (settings as any).template_id
}
// Или пытаемся определить по совпадению настроек
for (const template of designTemplates) {
if (
template.settings.theme_color === settings.theme_color &&
template.settings.background_color === settings.dashboard_background_color &&
template.settings.dashboard_layout === settings.dashboard_layout
) {
return template.id
}
}
return undefined
}
if (!isOpen) return null
return (
@@ -328,6 +351,12 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
label: 'Журнальный',
icon: 'bi-newspaper',
description: 'Стиль журнала с крупными изображениями'
},
{
value: 'test-list',
label: 'Тестовый список',
icon: 'bi-list-check',
description: 'Полный несворачиваемый список всех групп и ссылок'
}
].map((layout) => (
<div key={layout.value} className="col-md-6 col-lg-4">
@@ -683,7 +712,7 @@ export function CustomizationPanel({ isOpen, onClose, onSettingsUpdate }: Custom
<div className="tab-pane fade show active">
<TemplatesSelector
onTemplateSelect={handleTemplateSelect}
currentTemplate={undefined} // Можно добавить определение текущего шаблона
currentTemplate={getCurrentTemplateId()}
/>
</div>
)}

View File

@@ -2,7 +2,6 @@
import React from 'react'
import { designTemplates, DesignTemplate } from '../constants/designTemplates'
import styles from './TemplatesSelector.module.css'
interface TemplatesSelectorProps {
onTemplateSelect: (template: DesignTemplate) => void
@@ -10,13 +9,16 @@ interface TemplatesSelectorProps {
}
export function TemplatesSelector({ onTemplateSelect, currentTemplate }: TemplatesSelectorProps) {
const handleTemplateClick = (template: DesignTemplate) => {
const handleTemplateClick = (template: DesignTemplate, event: React.MouseEvent) => {
event.preventDefault()
event.stopPropagation()
console.log('Template clicked:', template.name)
console.log('onTemplateSelect function:', onTemplateSelect)
onTemplateSelect(template)
}
return (
<div className={styles.templateSelector}>
<div>
<h6 className="mb-3">
<i className="bi bi-palette me-2"></i>
Готовые шаблоны
@@ -24,90 +26,72 @@ export function TemplatesSelector({ onTemplateSelect, currentTemplate }: Templat
<div className="row g-3">
{designTemplates.map((template) => (
<div key={template.id} className="col-md-6 col-lg-4">
<div
className={`${styles.templateCard} card h-100 ${currentTemplate === template.id ? `${styles.selected} border-primary` : 'border-secondary'}`}
onClick={() => handleTemplateClick(template)}
<button
className={`btn w-100 h-100 p-0 border ${currentTemplate === template.id ? 'border-primary' : 'border-secondary'}`}
onClick={(e) => handleTemplateClick(template, e)}
type="button"
>
<div
className={styles.templatePreview}
style={{
background: template.settings.background_color,
}}
>
{/* Мини-превью дизайна */}
<div className={styles.previewContent}>
<div>
<div
className={styles.previewHeader}
style={{
backgroundColor: template.settings.header_text_color,
}}
></div>
<div
className={styles.previewSubtitle}
style={{
backgroundColor: template.settings.group_text_color,
}}
></div>
</div>
<div>
<div
className={styles.previewButton}
style={{
backgroundColor: template.settings.theme_color,
}}
></div>
<div
className={styles.previewText}
style={{
backgroundColor: template.settings.link_text_color,
}}
></div>
<div className="card h-100">
<div
className="card-img-top"
style={{
height: '120px',
background: template.settings.background_color || '#ffffff',
position: 'relative'
}}
>
{/* Простое превью */}
<div className="p-3 h-100 d-flex flex-column justify-content-between">
<div>
<div
style={{
height: '20px',
backgroundColor: template.settings.header_text_color || '#000',
borderRadius: '4px',
opacity: 0.8,
width: '70%',
marginBottom: '8px'
}}
></div>
<div
style={{
height: '12px',
backgroundColor: template.settings.group_text_color || '#666',
borderRadius: '2px',
opacity: 0.6,
width: '50%'
}}
></div>
</div>
<div>
<div
style={{
height: '24px',
backgroundColor: template.settings.theme_color || '#007bff',
borderRadius: '6px',
width: '80%',
marginBottom: '4px'
}}
></div>
</div>
</div>
</div>
{/* Overlay для демонстрации */}
{template.settings.group_overlay_enabled && (
<div
className={styles.overlayDemo}
style={{
backgroundColor: template.settings.group_overlay_color || '#000000',
opacity: template.settings.group_overlay_opacity || 0.3,
}}
></div>
)}
<div className="card-body">
<h6 className="card-title">{template.name}</h6>
<p className="card-text small text-muted">{template.description}</p>
{currentTemplate === template.id && (
<div className="mt-2">
<span className="badge bg-primary">
<i className="bi bi-check-lg me-1"></i>
Выбран
</span>
</div>
)}
</div>
</div>
<div className="card-body p-3">
<h6
className="card-title mb-2"
style={{
fontFamily: template.settings.heading_font_family || template.settings.font_family,
color: template.settings.header_text_color
}}
>
{template.name}
</h6>
<p
className="card-text small mb-0"
style={{
fontFamily: template.settings.body_font_family || template.settings.font_family,
color: template.settings.group_description_text_color
}}
>
{template.description}
</p>
{currentTemplate === template.id && (
<div className="mt-2">
<span className="badge bg-primary">
<i className="bi bi-check-lg me-1"></i>
Выбран
</span>
</div>
)}
</div>
</div>
</button>
</div>
))}
</div>

View File

@@ -358,5 +358,79 @@ export const designTemplates: DesignTemplate[] = [
}
`
}
},
{
id: 'test-list',
name: 'Тестовый список',
description: 'Полный несворачиваемый список всех групп и ссылок',
preview: '/templates/test-list.jpg',
settings: {
theme_color: '#6366f1',
background_color: '#f8fafc',
font_family: "'Inter', sans-serif",
heading_font_family: "'Inter', sans-serif",
body_font_family: "'Inter', sans-serif",
header_text_color: '#1e293b',
group_text_color: '#334155',
link_text_color: '#475569',
group_description_text_color: '#64748b',
dashboard_layout: 'list',
group_overlay_enabled: false,
show_groups_title: true,
custom_css: `
.test-list-layout .link-group {
margin-bottom: 2rem;
border: none;
background: transparent;
}
.test-list-layout .group-header {
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white;
padding: 1rem;
border-radius: 8px;
margin-bottom: 0.75rem;
font-weight: 600;
font-size: 1.125rem;
}
.test-list-layout .group-links {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0;
background: transparent;
}
.test-list-layout .link-item {
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 0.75rem 1rem;
transition: all 0.2s ease;
display: flex;
align-items: center;
text-decoration: none;
color: #475569;
}
.test-list-layout .link-item:hover {
background: #f1f5f9;
border-color: #6366f1;
transform: translateX(4px);
}
.test-list-layout .link-icon {
margin-right: 0.75rem;
width: 20px;
height: 20px;
flex-shrink: 0;
}
.test-list-layout .link-title {
font-weight: 500;
color: #334155;
}
.test-list-layout .expandable-group {
/* Принудительно отключаем сворачивание */
.show-more-button { display: none !important; }
.group-links { max-height: none !important; overflow: visible !important; }
}
`
}
}
]