- Заменен лишний div.container на стандартный container для единообразия - Заменен класс .card на .ios-card в секции активности для устранения конфликтов стилей - Добавлен card-list.css в base.html для корректного отображения карточек новостей - Добавлены CSS правила для правильного поведения .row и колонок в grid-layout - Исправлена проблема с выходом календаря за границы страницы - Убраны видимые скроллбары у modal-body и modal-content для чистого UI
16 KiB
16 KiB
Руководство по шаблонам EUSRR
Статус рефакторинга
Версия: 2.1
Дата обновления: январь 2025
Завершено: Фазы 0-5 (85%)
Архитектура шаблонов
Базовая структура
templates/
├── base.html # Корневой шаблон с navbar/sidebar
├── includes/ # Общие компоненты
│ ├── navbar.html # Верхняя навигация
│ ├── sidebar.html # Боковое меню
│ ├── rightbar_calendar.html # Календарь (237 строк, было 996)
│ └── messages.html # Django messages
│
├── admin/ # Административные override
├── auth/ # Авторизация, регистрация
├── communications/ # Чаты
├── documents/ # Документы
├── emails/ # Email-шаблоны
├── employees/ # Сотрудники, отделы
│ ├── components/ # Переиспользуемые компоненты
│ │ ├── department_controls.html
│ │ ├── department_info.html
│ │ ├── department_modals.html
│ │ └── department_sidebar.html
│ ├── _employee_edit.html # 24 строки (было 1391)
│ ├── department_detail.html # 46 строк (было 1025)
│ ├── employees_list.html
│ └── ...
│
├── feed/ # Лента новостей
├── requests_app/ # Заявления сотрудников
└── search/ # Поиск
Статические файлы
JavaScript
Утилиты (static/js/utils/):
stringUtils.js(115 строк) - esc(), norm(), escAttr(), truncate(), getInitials()timing.js(173 строки) - debounce(), throttle(), delay(), poll(), once()scroll.js(205 строк) - smoothScrollTo(), scrollToTop(), isElementInViewport()index.js- Центральная точка экспорта
Компоненты (static/js/components/):
listFilter.js(279 строк) - Универсальная фильтрация списковteamWheel.js(308 строк) - Вращающийся виджет командыcalendarWidget.js(1002 строки) - FullCalendar + CRUD событийindex.js- Центральная точка экспорта (v1.1.0)
CSS
Компоненты (static/css/components/):
chat-detail.css- Чатdepartment-list.css- Список отделовdepartment-sidebar.css- Боковая панель отделаdocument-list.css- Список документовemployee-detail.css- Детали сотрудникаcards.css- Карточки в лентеlogin.css- Страница входаlogout.css- Страница выходаnavbar.css- Верхняя навигацияpassword-forms.css- Формы паролейregister.css- Регистрацияrequest-list.css- Список заявленийrightbar-calendar.css- Календарьsearch-results.css- Результаты поискаsidebar.css- Боковое менюteam-wheel.css- Виджет команды- И другие...
Общие стили:
common.css(245 строк) - Переиспользуемые классыvariables.css- CSS-переменные и цвета
Использование компонентов
JavaScript утилиты
В base.html (глобальный импорт)
<script type="module">
import { esc, norm, escAttr, truncate, getInitials } from '{% static "js/utils/stringUtils.js" %}';
import { debounce, throttle, delay, poll, once } from '{% static "js/utils/timing.js" %}';
import { smoothScrollTo, scrollToTop, scrollToBottom, isElementInViewport } from '{% static "js/utils/scroll.js" %}';
// Экспорт в window для обратной совместимости
window.esc = esc;
window.norm = norm;
window.debounce = debounce;
// ... и т.д.
</script>
В шаблонах
{% block extra_js %}
<script type="module">
import { ListFilter, createDataAttrMatcher } from '{% static "js/components/listFilter.js" %}';
document.addEventListener('DOMContentLoaded', () => {
new ListFilter({
listSelector: '#empList',
itemSelector: '.emp-row',
searchInputSelector: '#empFilter',
matchFn: createDataAttrMatcher(['name', 'depts']),
clearButtonSelector: '#clearBtn',
emptyMessageSelector: '.empty-msg',
debounceMs: 300
});
});
</script>
{% endblock %}
CSS компоненты
Подключение в шаблоне
{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/components/cards.css' %}">
<link rel="stylesheet" href="{% static 'css/components/department-sidebar.css' %}">
{% endblock %}
Использование общих классов (common.css)
<!-- Кнопки с иконками -->
<button class="btn-icon btn-icon-primary">
<i class="bi-plus"></i>
</button>
<!-- Пустое состояние -->
<div class="empty-state">
<div class="empty-icon">📋</div>
<div class="empty-msg">Документов пока нет</div>
</div>
<!-- iOS-стиль модалок -->
<div class="ios-overlay">
<div class="ios-sheet">
<h1>Удалить сотрудника?</h1>
<p>Это действие необратимо</p>
<button class="btn btn-danger">Удалить</button>
</div>
</div>
<!-- Feed карточки -->
<div class="card">
<div class="card-header">
<img src="..." class="card-icon">
<div class="card-meta">
<div class="card-title">Иван Петров</div>
<div class="feed-ts">2 минуты назад</div>
</div>
</div>
<div class="card-body">
<p>Текст поста...</p>
</div>
</div>
Именование файлов
Шаблоны
{model}_list.html- Списки (employees_list, document_list){model}_detail.html- Детальные страницы (employee_detail, department_detail){model}_form.html- Формы создания/редактирования{model}_confirm_delete.html- Подтверждение удаления_{partial}.html- Партиалы (начинаются с_)
Компоненты
components/{name}.html- Переиспользуемые компоненты приложенияincludes/{name}.html- Глобальные компоненты
Лучшие практики
1. Структура шаблона
{% extends "base.html" %}
{% load static %}
{% block title %}Название страницы{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/pages/my-page.css' %}">
{% endblock %}
{% block content %}
{# Основной контент #}
{% endblock %}
{% block extra_js %}
<script type="module">
import { MyComponent } from '{% static "js/components/myComponent.js" %}';
new MyComponent();
</script>
{% endblock %}
2. Использование компонентов
{# С параметрами #}
{% include "employees/components/department_info.html" with department=dept %}
{# С контекстом #}
{% with user=employee %}
{% include "includes/components/user_card.html" %}
{% endwith %}
3. Стили и скрипты
✅ ПРАВИЛЬНО:
{# Стили в extra_css блоке #}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/components/cards.css' %}">
{% endblock %}
{# Скрипты в extra_js блоке #}
{% block extra_js %}
<script type="module">
import { ListFilter } from '{% static "js/components/listFilter.js" %}';
new ListFilter({ /* ... */ });
</script>
{% endblock %}
❌ НЕПРАВИЛЬНО:
{# Встроенные стили в body #}
<style>
.my-class { /* ... */ }
</style>
{# Встроенные скрипты в body #}
<script>
function myFunc() { /* ... */ }
</script>
4. Безопасность
{# Всегда используйте CSRF-защиту #}
<form method="post">
{% csrf_token %}
{# ... поля формы ... #}
</form>
{# Экранирование HTML (по умолчанию включено) #}
{{ user.bio }} {# Безопасно #}
{# Если нужен HTML (используйте с осторожностью!) #}
{{ user.bio|safe }} {# Только для доверенного контента! #}
Компоненты и их использование
ListFilter (универсальная фильтрация)
Импорт:
import { ListFilter, createDataAttrMatcher, createSelectorMatcher } from '{% static "js/components/listFilter.js" %}';
Базовое использование:
new ListFilter({
listSelector: '.employees-list',
itemSelector: '.employee-card',
searchInputSelector: '#searchInput'
});
С пользовательской функцией поиска:
new ListFilter({
listSelector: '#empList',
itemSelector: '.emp-row',
searchInputSelector: '#empFilter',
matchFn: createDataAttrMatcher(['name', 'email', 'department']),
clearButtonSelector: '#clearBtn',
emptyMessageSelector: '.no-results',
debounceMs: 300
});
TeamWheel (вращающийся виджет команды)
Импорт:
import { initTeamWheel } from '{% static "js/components/teamWheel.js" %}';
Использование:
initTeamWheel({
wheelId: 'teamWheel',
columns: 4,
speed: 30,
pauseOnHover: true
});
CalendarWidget (календарь событий)
Импорт:
import { initCalendarWidget } from '{% static "js/components/calendarWidget.js" %}';
Использование:
window.calendarWidget = initCalendarWidget({
deskContainerId: 'calendarRight',
mobContainerId: 'calendarRightMobile',
apiEventsUrl: '/api/v1/calendar/events/',
apiMyDeptsUrl: '/api/v1/departments/my-departments/',
defaultColor: '#0d6efd'
});
API виджета:
// Обновить списки недели
window.calendarWidget.updateWeekLists();
// Перезагрузить события
window.calendarWidget.refetchEvents();
// Получить список отделов
const departments = window.calendarWidget.getDepartments();
// Получить текущее состояние
const state = window.calendarWidget.getState(); // { type: 'company'|'dept', deptId: number|null }
Отладка и тестирование
Проверка подключения модулей
// В консоли браузера
console.log(window.esc); // function esc(str) { ... }
console.log(window.debounce); // function debounce(fn, ms) { ... }
Проверка работы компонентов
// ListFilter
const filter = new ListFilter({ /* ... */ });
filter.filter('test'); // Применить фильтр
filter.refresh(); // Обновить список элементов
filter.clear(); // Очистить фильтр
// CalendarWidget
window.calendarWidget.refetchEvents(); // Перезагрузить события
Django тесты
from django.test import TestCase
from django.template.loader import get_template
class TemplateRenderingTests(TestCase):
def test_employee_list_renders(self):
template = get_template('employees/employees_list.html')
self.assertIsNotNone(template)
def test_no_inline_styles(self):
"""Проверка отсутствия встроенных стилей"""
template = get_template('employees/employees_list.html')
content = template.render({})
self.assertNotIn('<style>', content)
История рефакторинга
Фаза 0: Инфраструктура ✅
- Создана структура папок для статики
- Настроена система тестирования
Фаза 1: Устранение дубликатов ✅
- Удалены неиспользуемые файлы (logout_confirm.html, password_reset_form.html)
- Объединены папки requests/ и requests_app/
- Сокращено файлов: 61 → 58
Фаза 2: JavaScript утилиты ✅
- Созданы модули: stringUtils.js, timing.js, scroll.js
- Создан компонент ListFilter.js (279 строк)
- Все утилиты импортированы в base.html
- Замена дублирующих функций в 20+ файлах
Фаза 3: CSS extraction ✅
- Создано 22 CSS-файла в components/
- Создан common.css (245 строк) с переиспользуемыми классами
- Унифицирован variables.css
- Удалено ~1500 строк встроенных стилей
- Результат: 0 встроенных
<style>блоков во всех шаблонах
Фаза 4: JavaScript extraction ✅
- Создан teamWheel.js (308 строк)
- Создан calendarWidget.js (1002 строки)
- rightbar_calendar.html сокращён с 996 до 237 строк (76% редукция)
- Обновлён components/index.js (v1.1.0)
Фаза 5: HTML decomposition (частично) 🔄
- ✅ _employee_edit.html: 1391 → 24 строки (98.3% редукция)
- ✅ department_detail.html: 1025 → 46 строк (95.5% редукция)
- ⏳ rightbar_calendar.html: В процессе дальнейшей декомпозиции
Фаза 6: Документация (текущая) 🔄
- Создан TEMPLATES_GUIDE.md
- Обновлён REFACTORING_PLAN.md
- Создана документация по компонентам
Метрики рефакторинга
До рефакторинга
- HTML файлов: 61
- Встроенного CSS: ~1500 строк
- Встроенного JavaScript: ~4500 строк
- Файлов > 1000 строк: 3
После рефакторинга
- HTML файлов: 58
- Встроенного CSS: 0 строк ✅
- Встроенного JavaScript: ~200 строк (только page-specific)
- Файлов > 1000 строк: 0 ✅
- CSS компонентов: 22 файла
- JS модулей: 6 файлов (3 utilities + 3 components)
Сокращение кода
_employee_edit.html: -98.3% (1391 → 24)department_detail.html: -95.5% (1025 → 46)rightbar_calendar.html: -76% (996 → 237)
Переиспользование
- CSS классов: 45+ общих классов в common.css
- JS функций: 14 утилит, 3 компонента
- Компонентов форм: 4 переиспользуемых партиала
Roadmap (что дальше)
Краткосрочные цели
- Декомпозировать rightbar_calendar.html HTML структуру
- Создать компоненты календаря (calendar_widget, calendar_toolbar, etc.)
- Оптимизировать CSS (минификация, удаление unused)
- Финальное тестирование всех страниц
Долгосрочные цели
- Создать Storybook для компонентов
- Настроить CSS/JS минификацию для production
- Создать визуальные регрессионные тесты
- Документировать все API компонентов
Поддержка
Вопросы и предложения: Создавайте issue в репозитории
Документация: templates/TEMPLATES_GUIDE.md (этот файл)
План рефакторинга: templates/REFACTORING_PLAN.md
CSS документация: static/css/README.md
JavaScript документация: Inline JSDoc в модулях
Версия документа: 2.1
Последнее обновление: январь 2025
Авторы: GitHub Copilot, команда разработки EUSRR