
М
ИНОБРНАУКИ
РОССИИ
ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ
УЧРЕЖДЕНИЕ ВЫСШЕГО ОБРАЗОВАНИЯ
НИЖЕГОРОДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ
УНИВЕРСИТЕТ ИМ. Р.Е.АЛЕКСЕЕВА
Институт радиоэлектроники и информационных технологий
Кафедра «Графические информационные системы»
ОТЧЕТ |
к лабораторной работе №6.3 |
«Инициализация и валидация форм. Работа с сервером.» |
(наименование работы) |
|
по дисциплине |
«WEB-технологии» |
(наименование дисциплины) |
ПРОВЕРИЛ: |
|
|
|
|
Агафонов Н.Е. |
(подпись) |
|
(фамилия, и.,о.) |
СТУДЕНТ: |
|
|
|
|
Какушкна О.В |
(подпись) |
|
(фамилия, и.,о.) |
|
|
23-ИСТ-1-1 |
|
|
(шифр группы) |
Оглавление
МИНОБРНАУКИ РОССИИ 1
НИЖЕГОРОДСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ 1
1 Введение 3
1.1 Цели и задачи 3
1.1.1. Цель работы: 3
1.1.2. Постановка задачи: 3
2 Основная Часть 4
3 Заключение 21
Введение
Цели и задачи
Цель работы:
Работа с сервером. Создание запросов основной системы.
Постановка задачи:
Разработка основной системы
Распределение данных по страницам
Осуществление запросов к серверу
Основная Часть
Добавим добавление и отображение секций на главной странице и сделаем доступным пользователю загружать фотографии.
Создадим сервер для загрузки фотографий с помощью exspress и multer. Определим папку images для хранения загруженных файлов, создавая ее, если она не существует. Пропишем const upload = multer({ dest: uploadFolder });: настраивает multer для сохранения загружаемых файлов в папку images. app.use('/images', express.static(uploadFolder));: настраивает маршрут, позволяющий статически раздавать файлы из папки images, чтобы их можно было получить по URL /images/filename.
Далее создадим Роут для загрузки файлов:
Маршрут /upload: Позволяет загружать файл на сервер. Использует middleware upload.single('file') из multer, который позволяет загружать один файл с именем поля file из формы. При успешной загрузке (если req.file существует) формируется URL загруженного изображения, который отправляется обратно клиенту в формате JSON. Если файл не загружен, сервер возвращает статус 400 с сообщением об ошибке.
Теперь пропишем логику для создания/удаления/редактирования секции. Создание секции:
Маршрут /sections Позволяет создать новую секцию. Получает текущие секции с помощью getSections(), добавляет новую секцию в массив, и сохраняет обновленный массив обратно в db.json. Возвращает новую секцию с кодом состояния 201 (создано).
Обновление секции: Маршрут PUT /sections/:id: Позволяет обновить секцию по ID. Найденный индекс секции обновляется с данными из req.body. Если секция не найдена, возвращает статус 404 с сообщением об ошибке.
Маршрут DELETE /sections/:id: Удаляет секцию по ID. Если секция найдена, ее удаляют и сохраняют обновленный массив. При успешном удалении возвращает статус 204 (без содержимого). Если секция не найдена, возвращает статус 404 с сообщением об ошибке.
Маршрут GET /sections: Возвращает все секции из db.json. Использует функцию getSections() для получения данных и отправляет их обратно в формате JSON.
Теперь реализуем работу на странице администратора с добавлением, редактированием, удалением секции чтобы они еще отображались на главной странице. Шаблон компонента:
<app-header></app-header>
<div class="admin-container">
<div class="sidebar">
<h2>Админ-панель</h2>
<ul class="sidebar-menu">
<li><a href="teacher-add">Добавить учителя</a></li>
</ul>
</div>
<div class="content">
<h2 class="zagolovok">Создание секции</h2>
<form
id="sectionForm"
*ngIf="!editSectionId; else editTemplate"
(ngSubmit)="onSubmit()"
class="admin-form"
#myForm="ngForm"
>
<div class="form-group">
<label for="title">Заголовок</label>
<input
type="text"
id="title"
[(ngModel)]="section.title"
name="title"
required
/>
<div *ngIf="myForm.submitted && !section.title" class="error-message">
Это поле обязательно.
</div>
</div>
<div class="form-group">
<label for="instructor">Выбрать преподавателя</label>
<select
id="instructor"
[(ngModel)]="section.instructor"
name="instructor"
required
>
<option *ngFor="let teacher of teachers" [value]="teacher.id">
{{ teacher.firstName }} {{ teacher.lastName }}
</option>
</select>
<div *ngIf="myForm.submitted && !section.instructor" class="error-message">
Это поле обязательно.
</div>
</div>
<div class="form-group">
<label for="startTime">Время начала</label>
<input
type="time"
id="startTime"
[(ngModel)]="section.startTime"
name="startTime"
required
/>
<div *ngIf="myForm.submitted && !section.startTime" class="error-message">
Это поле обязательно.
</div>
</div>
<div class="form-group">
<label for="endTime">Время окончания</label>
<input
type="time"
id="endTime"
[(ngModel)]="section.endTime"
name="endTime"
required
/>
<div *ngIf="myForm.submitted && !section.endTime" class="error-message">
Это поле обязательно.
</div>
</div>
<div class="form-group">
<label for="day">День</label>
<input type="text" id="day" [(ngModel)]="section.day" name="day" required />
<div *ngIf="myForm.submitted && !section.day" class="error-message">
Это поле обязательно.
</div>
</div>
<div class="form-group">
<label for="capacity">Вместимость</label>
<input
type="number"
id="capacity"
[(ngModel)]="section.capacity"
name="capacity"
required
min="1"
/>
<div
*ngIf="myForm.submitted && (section.capacity < 1 || !section.capacity)"
class="error-message"
>
Вместимость должна быть хотя бы 1.
</div>
</div>
<div class="form-group">
<label for="image">Изображение</label>
<input
type="file"
id="image"
(change)="onFileSelected($event)"
accept="image/*"
required
/>
<button type="button" (click)="uploadImage(section)">Загрузить</button>
<div *ngIf="myForm.submitted && !section.imageUrl" class="error-message">
Это поле обязательно.
</div>
</div>
<button type="submit">Создать секцию</button>
</form>
<!-- Шаблон редактирования секции -->
<ng-template #editTemplate>
<h3 class="zagolovok">Редактирование секции</h3>
<form
id="sectionForm"
(ngSubmit)="onSaveEdit(section)"
class="admin-form"
>
<div class="form-group">
<label for="title">Заголовок</label>
<input
type="text"
id="title"
[(ngModel)]="section.title"
name="title"
required
/>
</div>
<div class="form-group">
<label for="instructor">Инструктор</label>
<select
id="instructor"
[(ngModel)]="section.instructor"
name="instructor"
required
>
<option *ngFor="let teacher of teachers" [value]="teacher.id">
{{ teacher.firstName }} {{ teacher.lastName }}
</option>
</select>
</div>
<div class="form-group">
<label for="startTime">Время начала</label>
<input
type="time"
id="startTime"
[(ngModel)]="section.startTime"
name="startTime"
required
/>
</div>
<div class="form-group">
<label for="endTime">Время окончания</label>
<input
type="time"
id="endTime"
[(ngModel)]="section.endTime"
name="endTime"
required
/>
</div>
<div class="form-group">
<label for="day">День</label>
<input type="text" id="day" [(ngModel)]="section.day" name="day" required />
</div>
<div class="form-group">
<label for="capacity">Вместимость</label>
<input
type="number"
id="capacity"
[(ngModel)]="section.capacity"
name="capacity"
required
min="1"
/>
</div>
<button type="submit">Сохранить изменения</button>
<button type="button" (click)="resetForm()">Отменить</button>
</form>
</ng-template>
<h2 class="zagolovok">Список секций</h2>
<div class="photo-container">
<div *ngFor="let section of sections" class="container_photo">
<img
[src]="section.imageUrl"
alt="{{ section.title }}"
class="section-image"
/>
<h3 class="zagolovok_section">{{ section.title }}</h3>
<div class="section-details">
<p>
<strong>Инструктор:</strong>
{{ getTeacherNameById(section.instructor) }}
</p>
<p><strong>Время начала:</strong> {{ section.startTime }}</p>
<p><strong>Время окончания:</strong> {{ section.endTime }}</p>
<p><strong>День:</strong> {{ section.day }}</p>
<p><strong>Вместимость:</strong> {{ section.capacity }}</p>
</div>
<div class="buttons">
<button (click)="onEdit(section)" class="btn" >Изменить</button>
<button (click)="onDelete(section)" class="btn" >Удалить</button>
</div>
</div>
</div>
</div>
</div>
<app-footer></app-footer>
Теперь реализуем логику компонента. ngOnInit: Вызывается при инициализации компонента. Загружает секции и учителей, вызывая методы loadSections() и loadTeachers().
onFileSelected(event: any) устанавливает выбранный файл (selectedFile), когда пользователь выбирает файл для загрузки. Загружает изображение с помощью UploadService. Если загрузка успешна, обновляет URL изображения у секции и сбрасывает выбор файла.
В UploadService :
uploadFile(file: File): Observable<any> {
const formData = new FormData();
formData.append('file', file);
return this.http.post(this.uploadUrl, formData);
}
onSubmit(): Проверяет, валидна ли созданная секция. Если валидна, генерирует ID для секции и отправляет данные на сервер через SectionService для создания новой секции.
Теперь редактирование секции onEdit(section: Section): устанавливает текущую секцию для редактирования и прокручивает страницу к форме секции с помощью scrollIntoView.
Сохранение изменений в редактируемой секции: сохраняет изменения для редактируемой секции. Если у секции изменился инструктор, обновляет секции для старого и нового инструкторов.
Удаление секции: удаляет секцию после подтверждения от пользователя. Удаляет секцию у учителя, который ее курирует. Сам метод удаления:
Теперь реализуем метод чтобы у учителя, который вел эту секцию , удалилась эта секция
После проделанных изменений пользователя нужно добавляет ID секции к курируемым секциям у соответствующего учителя.
Теперь отобразим эти секции на главной странице main-page: шаблон блока секций
<div class="photo-container">
<div *ngFor="let section of sections" class="container_photo">
<img [src]="section.imageUrl" alt="{{ section.title }}" class="section-image" />
<h3 class="zagolovok_section">{{ section.title }}</h3>
<div class="section-details">
<p><strong>Инструктор:</strong> {{ getTeacherNameById(section.instructor) }}</p>
<p><strong>Время:</strong> {{ section.startTime }} - {{ section.endTime }}</p>
<p><strong>День:</strong> {{ section.day }}</p>
<p><strong>Вместимость:</strong> {{ section.capacity }}</p>
<div *ngIf="section.capacity === 0" class="error-message">
<p>Записаться на эту секцию невозможно. Мест нет!</p>
</div>
</div>
</div>
</div>
Теперь рассмотрим логику отображения:
При загрузке страницы метод loadSections this.sectionService.getSections() делает HTTP-запрос к API (локальному, в данном случае), чтобы получить массив секций.
Метод subscribe позволяет подписаться на возвращаемый Observable. Когда данные будут загружены, функция обратного вызова получит их и присвоит переменной sections в компоненте.
А в шаблоне страницы *ngFor: Эта директива создает элементы <div> для каждой секции в массиве sections, который был загружен ранее.
Динамическое связывание: Данные секции, такие как imageUrl, title, startTime, endTime, day, capacity, отображаются с использованием {{ section.title }}
Получение имени инструктора: Для отображения имени инструктора вызывается метод getTeacherNameById(section.instructor), который ищет инструктора по его ID.