Модули
Новости
Модуль "Новости"
Версия: 1.0
Дата: 2025-11-27
Статус: Реализован
Содержание
- Общее описание
- Текущая реализация
- Требования заказчика
- Выявленные проблемы
- Рекомендации по улучшению
1. Общее описание
1.1 Предназначение модуля
Модуль "Новости" предназначен для:
- Публикации информационных сообщений для дилеров
- Оповещения о новых продуктах, изменениях цен, акциях
- Информирования о днях рождения сотрудников и внутренних событиях
- Распространения корпоративных новостей
1.2 Роли и доступ
| Роль | Просмотр | Создание | Редактирование | Удаление |
|---|---|---|---|---|
| Администратор | ✅ | ✅ | ✅ | ✅ |
| Менеджер | ✅ | ⚠️ Настраивается | ⚠️ Настраивается | ⚠️ Настраивается |
| Дилер | ✅ (только опубликованные) | ❌ | ❌ | ❌ |
| Производство | ✅ | ❌ | ❌ | ❌ |
1.3 Политики доступа
news_can_view — Может смотреть новости
news_can_add — Может добавлять новости
news_can_edit — Может редактировать новости
news_can_delete — Может удалять новости2. Текущая реализация
2.1 Архитектура
┌─────────────────────────────────────────────────────────────────────────┐
│ МОДУЛЬ НОВОСТИ │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ routes/web.php │
│ ├── GET /news → NewsController@index → Список новостей │
│ ├── GET /news/add → NewsController@create → Форма создания │
│ └── GET /news/edit/{id}→ NewsController@show → Просмотр/Редакт. │
│ │
│ app/Http/Controllers/NewsController.php │
│ └── Роутинг по group_id: │
│ ├── Дилер (2) → templates/news/list.blade.php (карточки) │
│ │ → templates/news/show.blade.php (просмотр) │
│ └── Остальные → templates/news/table.blade.php (таблица) │
│ → templates/news/edit.blade.php (редактирование) │
│ │
│ app/Livewire/News/ │
│ ├── Listing.php — Filament Table для админов │
│ ├── Add.php — Форма создания новости │
│ └── Edit.php — Форма редактирования │
│ │
│ app/View/Components/Dealer/ │
│ ├── Widget/NewsWidget.php — Виджет на Dashboard (6 последних) │
│ └── NewsOther.php — Блок "Другие новости" (2 случайных) │
│ │
└─────────────────────────────────────────────────────────────────────────┘2.2 Модель данных
Таблица: news
| Поле | Тип | Описание |
|---|---|---|
id | bigint | Первичный ключ |
name | string(255) | Заголовок новости |
description | text | Содержимое (HTML) |
images | json | Массив путей к изображениям |
published | boolean | Флаг публикации |
publishedon | datetime | Дата публикации |
index | int | Порядок сортировки (не используется) |
created_at | timestamp | Дата создания |
updated_at | timestamp | Дата обновления |
Модель: app/Models/News.php
// Автоматическая публикация по дате
static::retrieved(function ($model) {
if(!$model->published && $model->publishedon && Carbon::now()->gt($model->publishedon)){
$model->update(['published' => true]);
}
});
// Установка даты публикации при включении флага
static::saving(function ($model) {
if ($model->isDirty('published') && $model->published === true) {
$model->publishedon = now();
}
});2.3 Интерфейс для Администратора/Менеджера
Список новостей (Filament Table)
┌─────────────────────────────────────────────────────────────────────────┐
│ Новости [+ Создать] │
├─────────────────────────────────────────────────────────────────────────┤
│ [Поиск...] [Столбцы ▾] │
├──────┬─────────────────────┬─────────────────┬────────────┬─────────────┤
│ ☐ │ Название │ Описание │ Дата публ. │ Опублик. │
├──────┼─────────────────────┼─────────────────┼────────────┼─────────────┤
│ ☐ │ Привет │ <p>Всем прив... │ 03.10.2025 │ [●] │
│ ☐ │ Заголовок новости │ Nulla qui au... │ 15.11.2025 │ [●] │
├──────┴─────────────────────┴─────────────────┴────────────┴─────────────┤
│ Показано с 1 по 2 из 2 на страницу [25 ▾] │
└─────────────────────────────────────────────────────────────────────────┘
Функции:
✅ Поиск по названию
✅ Сортировка по дате, описанию
✅ Toggle публикации прямо в таблице
✅ Удаление (одиночное и массовое)
✅ Переход к редактированию по клику
✅ Пагинация (25, 50, все)Форма редактирования
┌─────────────────────────────────────────────────────────────────────────┐
│ [← Назад] Редактирование новости [Удалить] [Сохранить] │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────┐ ┌───────────────────────────┐ │
│ │ Название* │ │ Загрузите изображения │ │
│ │ [Привет ] │ │ │ │
│ │ │ │ ┌─────────────────────┐ │ │
│ │ Дата публикации Опубликован* │ │ │ Перетащите файлы │ │ │
│ │ [03.10.2025 📅] [●] │ │ │ или выберите │ │ │
│ │ │ │ └─────────────────────┘ │ │
│ │ Описание │ │ │ │
│ │ [B][I][S][🔗][H2][H3][❝][</>][≡][#] │ │ [img1.jpg] [img2.jpg] │ │
│ │ ┌─────────────────────────────────┐ │ │ │ │
│ │ │ Всем привет! Это просто │ │ └───────────────────────────┘ │
│ │ │ новость о том, что мы │ │ │
│ │ │ запускаемся! │ │ │
│ │ └─────────────────────────────────┘ │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Поля формы:
- Название* (TextInput, max 255)
- Дата публикации (DateTimePicker)
- Опубликован* (Toggle)
- Описание (RichEditor с toolbar)
- Изображения (FileUpload, multiple, reorderable, imageEditor)2.4 Интерфейс для Дилера
Список новостей (карточки)
┌─────────────────────────────────────────────────────────────────────────┐
│ Новости │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │ ┌──────────────┐ │ │
│ │ │ [IMAGE] │ │ │ │ [IMAGE] │ │ │ │ [IMAGE] │ │ │
│ │ └──────────────┘ │ │ └──────────────┘ │ │ └──────────────┘ │ │
│ │ 03.10.2025 │ │ 10.11.2025 │ │ 05.11.2025 │ │
│ │ Привет │ │ Заголовок... │ │ Новость 3 │ │
│ │ Краткий текст... │ │ Краткий текст... │ │ Краткий текст... │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │
│ [1] [2] [→] │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Особенности:
- Только опубликованные новости (published = true)
- Пагинация по 8 записей
- Сетка 4 колонки на десктопе
- Карточки с изображением, датой, заголовком, кратким описанием (200 символов)Просмотр новости
┌─────────────────────────────────────────────────────────────────────────┐
│ [← Назад] │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────┐ ┌─────────────────────────┐ │
│ │ │ │ Другие новости │ │
│ │ Привет │ │ ┌─────────────────────┐ │ │
│ │ 03.10.2025 │ │ │ [img] Заголовок 2 │ │ │
│ │ │ │ │ Краткий текст... │ │ │
│ │ [ИЗОБРАЖЕНИЕ] │ │ └─────────────────────┘ │ │
│ │ │ │ ┌─────────────────────┐ │ │
│ │ Всем привет! Это просто новость │ │ │ [img] Заголовок 3 │ │ │
│ │ о том, что мы запускаемся! │ │ │ Краткий текст... │ │ │
│ │ │ │ └─────────────────────┘ │ │
│ └───────────────────────────────────────┘ └─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Особенности:
- Полный текст новости (HTML)
- Изображение (первое из массива)
- Блок "Другие новости" — 2 случайные новости (кроме текущей)2.5 Виджет на Dashboard дилера
// NewsWidget.php
$this->news = News::where('published', true)
->orderBy('created_at', 'desc')
->paginate(6);┌─ Новости ───────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ [IMAGE] │ │ [IMAGE] │ │ [IMAGE] │ │
│ │ 03.10.2025 │ │ 10.11.2025 │ │ │ │
│ │ Привет │ │ Заголовок │ │ │ │
│ │ Краткое... │ │ Краткое... │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘3. Требования заказчика
3.1 Из ТЗ
Источники:
/docs/Пояснительная_записка_к_системе_ЛК.docx,/docs/ТЗ_на_создание_кабинета_Дилера10_2023.docx
| Требование | Статус | Комментарий |
|---|---|---|
| Раздел "Новости" на главной странице | ✅ | Виджет на Dashboard дилера |
| Публикация информации для дилеров | ✅ | Админ/Менеджер могут создавать |
| Дни рождения сотрудников | ⚠️ | Нет отдельного типа новости |
| Внутренние коммуникации | ✅ | Общий функционал новостей |
| Отдельная страница новостей | ✅ | /news |
3.2 Неявные требования
| Функция | Описание |
|---|---|
| Категории новостей | Разделение на типы: Акции, Продукция, События |
| Целевая аудитория | Новости для конкретных групп пользователей |
| Уведомления | Push-уведомления о новых публикациях |
| Прикреплённые файлы | Возможность прикрепить документы (PDF, DOC) |
4. Выявленные проблемы
4.1 Критические
| # | Проблема | Влияние | Решение |
|---|---|---|---|
| 1 | Неиспользуемое поле index | Нет сортировки по важности | Добавить функционал закрепления новости |
| 2 | Нет валидации XSS в description | Безопасность | Санитизация HTML при сохранении |
4.2 Средние
| # | Проблема | Влияние | Решение |
|---|---|---|---|
| 3 | Одинаковый URL для просмотра и редактирования | /news/edit/{id} некорректно для дилера | Разделить на /news/{id} и /news/{id}/edit |
| 4 | Нет превью при создании | UX | Добавить предпросмотр перед публикацией |
| 5 | Отсутствует SEO | — | Добавить meta-теги (если нужно) |
| 6 | Нет подтверждения удаления в списке | UX | Есть ✅ (requiresConfirmation) |
4.3 Низкие
| # | Проблема | Влияние | Решение |
|---|---|---|---|
| 7 | Дата в карточке берётся из created_at, а не publishedon | Некорректная дата | Исправить в news-item.blade.php |
| 8 | Нет счётчика просмотров | Аналитика | Добавить поле views |
| 9 | Placeholder изображения | UX | Заменить на реальный placeholder |
5. Рекомендации по улучшению
5.1 Приоритет: Высокий
5.1.1 Исправить отображение даты в карточках
Файл: resources/views/components/dealer/news-item.blade.php
// Было:
<p class="text-[#838383] text-xs">{{ $item->created_at->format('d.m.Y') }}</p>
// Стало:
<p class="text-[#838383] text-xs">
{{ $item->publishedon ? Carbon\Carbon::parse($item->publishedon)->format('d.m.Y') : $item->created_at->format('d.m.Y') }}
</p>5.1.2 Разделить маршруты просмотра и редактирования
Файл: routes/web.php
// Было:
Route::get('/news/edit/{id}', [NewsController::class, 'show'])->name('news-edit');
// Стало:
Route::get('/news/{id}', [NewsController::class, 'show'])->name('news-show');
Route::get('/news/{id}/edit', [NewsController::class, 'edit'])->name('news-edit');5.2 Приоритет: Средний
5.2.1 Добавить категории новостей
Миграция:
Schema::create('news_categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->string('color')->nullable(); // Цвет метки
$table->timestamps();
});
Schema::table('news', function (Blueprint $table) {
$table->foreignId('category_id')->nullable()->constrained('news_categories');
});Предустановленные категории:
- 📢 Объявления
- 🎉 События
- 💰 Акции
- 📦 Продукция
- 🎂 Дни рождения
5.2.2 Добавить закрепление новости
Миграция:
Schema::table('news', function (Blueprint $table) {
$table->boolean('pinned')->default(false);
$table->integer('sort_order')->default(0);
});Логика отображения:
$news = News::where('published', true)
->orderBy('pinned', 'desc')
->orderBy('publishedon', 'desc')
->paginate(8);5.2.3 Добавить счётчик просмотров
Миграция:
Schema::table('news', function (Blueprint $table) {
$table->unsignedInteger('views')->default(0);
});Логика в контроллере:
public function show(int $id, Request $request)
{
$item = News::find($id);
if (!$item) abort(404);
// Увеличиваем счётчик просмотров
$item->increment('views');
// ...
}5.3 Приоритет: Низкий
5.3.1 Уведомления о новых новостях
При публикации новости создавать уведомления для целевой аудитории:
// В модели News
static::updated(function ($model) {
if ($model->isDirty('published') && $model->published === true) {
// Отправить уведомления всем дилерам
$dealers = User::where('group_id', 2)->get();
foreach ($dealers as $dealer) {
Notification::create([
'user_id' => $dealer->id,
'type' => 'news_published',
'notifiable_type' => News::class,
'notifiable_id' => $model->id,
'message' => 'Новая публикация: ' . $model->name,
]);
}
}
});5.3.2 Прикреплённые файлы
Добавить возможность прикреплять документы к новости:
Schema::table('news', function (Blueprint $table) {
$table->json('attachments')->nullable(); // `[{name, path, size}]`
});Приложение A: Структура файлов модуля
app/
├── Http/Controllers/
│ └── NewsController.php # Контроллер
├── Livewire/News/
│ ├── Add.php # Форма создания
│ ├── Edit.php # Форма редактирования
│ └── Listing.php # Таблица списка
├── Models/
│ └── News.php # Модель
└── View/Components/Dealer/
├── NewsOther.php # Компонент "Другие новости"
└── Widget/NewsWidget.php # Виджет для Dashboard
database/
├── factories/
│ └── NewsFactory.php # Фабрика для тестов
└── migrations/
└── 2024_03_04_175928_create_news_table.php
resources/views/
├── components/dealer/
│ ├── news.blade.php # Список новостей (виджет)
│ ├── news-item.blade.php # Карточка новости
│ └── news-other.blade.php # Блок "Другие новости"
├── livewire/news/
│ └── listing.blade.php # Шаблон таблицы
└── templates/news/
├── add.blade.php # Страница создания
├── edit.blade.php # Страница редактирования
├── list.blade.php # Список для дилера
├── show.blade.php # Просмотр для дилера
└── table.blade.php # Таблица для админаПриложение B: SQL-запросы
Все опубликованные новости
SELECT * FROM news
WHERE published = 1
ORDER BY publishedon DESC;Статистика по новостям
SELECT
COUNT(*) as total,
SUM(CASE WHEN published = 1 THEN 1 ELSE 0 END) as published,
SUM(CASE WHEN published = 0 THEN 1 ELSE 0 END) as draft,
AVG(views) as avg_views
FROM news;Документ подготовлен на основе анализа исходного кода проекта