Загрузка данных
Всё в полном порядке, не переживайте! На демонстрационных экзаменах структуру проекта можно делить по-разному. Разделение на **10 PHP-файлов** — это даже более грамотный подход. Это называется разделением логики (когда обработка данных и внешний вид страницы не перемешиваются в одном файле).
Давайте перестроим структуру именно так, как вам нужно. Мы разделим код так, чтобы каждый шаг был изолирован, а дизайн для смартфона объединим.
### Архитектура из 10 PHP-файлов и 1 HTML/CSS
Вот как теперь выглядит ваша папка moinesam в Visual Studio Code. Создайте именно эти файлы:
1. db.php — подключение к вашей базе данных в phpMyAdmin.
2. header.php — верхняя часть сайта (шапка, меню), чтобы не копировать её на все страницы.
3. footer.php — нижняя часть сайта (подвал с копирайтом).
4. index.php — главная страница (проверяет, вошел ли пользователь, и перенаправляет его).
5. register.php — страница с формой регистрации (HTML).
6. register_handler.php — **(Обработчик)** код, который берет данные из формы регистрации и отправляет в базу данных.
7. login.php — страница с формой авторизации (HTML).
8. login_handler.php — **(Обработчик)** код, который проверяет логин и пароль в базе данных.
9. dashboard.php — личный кабинет пользователя (создание заявки и просмотр истории).
10. admin.php — панель администратора для управления статусами.
**И 1 отдельный файл для мобильного дизайна:**
* Внутри папки создайте файл mobile.html (внутри него будет и HTML, и CSS в теге <style>), где последовательно один за другим будут нарисованы экраны для телефона 390x844 px.
### Пошаговый план реализации в VS Code
Давайте напишем код для этих файлов, чтобы они заработали как единое целое.
#### 1. Файл db.php (Подключение)
Этот код мы уже знаем. Он просто связывает систему с MySQL.
```php
<?php
if (session_status() === PHP_SESSION_NONE) { session_start(); }
try {
$pdo = new PDO("mysql:host=127.0.0.1;dbname=cleaning_db;charset=utf8mb4", "root", "root", [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
} catch (\PDOException $e) {
die("Ошибка подключения: " . $e->getMessage());
}
?>
```
#### 2. Файл header.php (Шапка сайта)
```php
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Портал клининга «Мой Не Сам»</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f9f9f9; }
nav { background: #333; padding: 10px; border-radius: 5px; margin-bottom: 20px; }
nav a { color: white; margin-right: 15px; text-decoration: none; }
.error { color: red; font-weight: bold; }
</style>
</head>
<body>
<nav>
<a href="index.php">Главная</a>
<?php if(isset($_SESSION['user_id'])): ?>
<a href="dashboard.php">Личный кабинет</a>
<a href="logout.php">Выйти</a>
<?php else: ?>
<a href="login.php">Войти</a>
<a href="register.php">Регистрация</a>
<?php endif; ?>
</nav>
```
#### 3. Файл footer.php (Подвал сайта)
```php
<hr>
<p style="text-align: center; color: #666;">© 2026 Портал клининговых услуг «Мой Не Сам»</p>
</body>
</html>
```
#### 4. Файл index.php (Главная)
```php
<?php
require_once 'db.php';
header('Location: login.php'); // Сразу перенаправляем на страницу входа по умолчанию
exit;
?>
```
#### 5. Файл register.php (Форма регистрации)
```php
<?php require_once 'db.php'; include 'header.php'; ?>
<h2>Регистрация в системе</h2>
<?php if(isset($_SESSION['reg_error'])): ?>
<p class="error"><?= $_SESSION['reg_error']; unset($_SESSION['reg_error']); ?></p>
<?php endif; ?>
<form action="register_handler.php" method="POST">
<p><input type="text" name="full_name" placeholder="ФИО" required></p>
<p><input type="text" name="phone" placeholder="Телефон" required></p>
<p><input type="email" name="email" placeholder="E-mail" required></p>
<p><input type="text" name="login" placeholder="Логин (уникальный)" required></p>
<p><input type="password" name="password" placeholder="Пароль" required></p>
<button type="submit">Зарегистрироваться</button>
</form>
<?php include 'footer.php'; ?>
```
#### 6. Файл register_handler.php (Обработчик регистрации)
Этот файл не выводит дизайн, он только обрабатывает данные.
```php
<?php
require_once 'db.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$login = trim($_POST['login']);
// Проверяем, нет ли уже такого логина
$check = $pdo->prepare("SELECT id FROM users WHERE login = ?");
$check->execute([$login]);
if ($check->fetch()) {
$_SESSION['reg_error'] = "Этот логин уже занят!";
header('Location: register.php');
exit;
}
// Сохраняем пользователя (пароль хэшируем для безопасности)
$hashed_password = password_hash($_POST['password'], PASSWORD_DEFAULT);
$stmt = $pdo->prepare("INSERT INTO users (full_name, phone, email, login, password) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$_POST['full_name'], $_POST['phone'], $_POST['email'], $login, $hashed_password]);
header('Location: login.php');
exit;
}
?>
```
#### 7. Файл login.php (Форма авторизации)
```php
<?php require_once 'db.php'; include 'header.php'; ?>
<h2>Авторизация</h2>
<?php if(isset($_SESSION['login_error'])): ?>
<p class="error"><?= $_SESSION['login_error']; unset($_SESSION['login_error']); ?></p>
<?php endif; ?>
<form action="login_handler.php" method="POST">
<p><input type="text" name="login" placeholder="Логин" required></p>
<p><input type="password" name="password" placeholder="Пароль" required></p>
<button type="submit">Войти</button>
</form>
<?php include 'footer.php'; ?>
```
#### 8. Файл login_handler.php (Обработчик авторизации)
```php
<?php
require_once 'db.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$login = trim($_POST['login']);
$password = $_POST['password'];
// Проверка на жестко заданного админа из задания
if ($login === 'adminka' && $password === 'password') {
$_SESSION['user_id'] = 'admin';
$_SESSION['role'] = 'admin';
header('Location: admin.php');
exit;
}
// Ищем обычного пользователя
$stmt = $pdo->prepare("SELECT * FROM users WHERE login = ?");
$stmt->execute([$login]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['role'] = 'user';
header('Location: dashboard.php');
exit;
} else {
$_SESSION['login_error'] = "Некорректный логин или пароль!";
header('Location: login.php');
exit;
}
}
?>
```
#### 9. Файл dashboard.php (Личный кабинет и создание заявки)
Здесь по заданию пользователь видит историю и может отправить новую заявку.
```php
<?php
require_once 'db.php';
if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'user') { header('Location: login.php'); exit; }
// Если отправлена новая заявка
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$stmt = $pdo->prepare("INSERT INTO requests (user_id, address, contact_data, service_type, request_date, request_time, payment_type) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([
$_SESSION['user_id'], $_POST['address'], $_POST['contact_data'],
$_POST['service_type'], $_POST['request_date'], $_POST['request_time'], $_POST['payment_type']
]);
header('Location: dashboard.php'); exit;
}
// Получаем историю заявок пользователя
$stmt = $pdo->prepare("SELECT * FROM requests WHERE user_id = ? ORDER BY id DESC");
$stmt->execute([$_SESSION['user_id']]);
$my_requests = $stmt->fetchAll();
include 'header.php';
?>
<h2>Оставить заявку на уборку</h2>
<form method="POST">
<p><input type="text" name="address" placeholder="Ваш адрес" required></p>
<p><input type="text" name="contact_data" placeholder="Контактные данные" required></p>
<p>Вид услуги:
<select name="service_type" required>
<option>Общий клининг</option>
<option>Генеральная уборка</option>
<option>Послестроительная уборка</option>
<option>Химчистка ковров и мебели</option>
</select>
</p>
<p>Дата: <input type="date" name="request_date" required></p>
<p>Время: <input type="time" name="request_time" required></p>
<p>Оплата:
<input type="radio" name="payment_type" value="Наличные" checked> Наличные
<input type="radio" name="payment_type" value="Банковская карта"> Карта
</p>
<button type="submit">Сформировать заявку</button>
</form>
<h2>История ваших заявок</h2>
<table border="1" cellpadding="10" cellspacing="0">
<tr><th>ID</th><th>Услуга</th><th>Дата/Время</th><th>Статус</th><th>Причина отмены</th></tr>
<?php foreach($my_requests as $req): ?>
<tr>
<td><?= $req['id'] ?></td>
<td><?= $req['service_type'] ?></td>
<td><?= $req['request_date'] ?> <?= $req['request_time'] ?></td>
<td><?= $req['status'] ?></td>
<td><?= $req['reject_reason'] ?? '-' ?></td>
</tr>
<?php endforeach; ?>
</table>
<?php include 'footer.php'; ?>
```
#### 10. Файл admin.php (Панель Администратора)
```php
<?php
require_once 'db.php';
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') { header('Location: login.php'); exit; }
// Обновление статуса администратором
if (isset($_POST['change_status'])) {
$reason = ($_POST['status'] === 'Отменено') ? $_POST['reject_reason'] : null;
$stmt = $pdo->prepare("UPDATE requests SET status = ?, reject_reason = ? WHERE id = ?");
$stmt->execute([$_POST['status'], $reason, $_POST['req_id']]);
header('Location: admin.php'); exit;
}
// Подгружаем вообще все заявки из базы данных
$requests = $pdo->query("SELECT requests.*, users.full_name FROM requests JOIN users ON requests.user_id = users.id ORDER BY requests.id DESC")->fetchAll();
include 'header.php';
?>
<h2>Панель администратора</h2>
<table border="1" cellpadding="10" cellspacing="0">
<tr>
<th>ФИО клиента</th><th>Контакты</th><th>Адрес</th><th>Услуга</th><th>Статус</th><th>Действие</th>
</tr>
<?php foreach($requests as $r): ?>
<tr>
<td><?= htmlspecialchars($r['full_name']) ?></td>
<td><?= htmlspecialchars($r['contact_data']) ?></td>
<td><?= htmlspecialchars($r['address']) ?></td>
<td><?= $r['service_type'] ?></td>
<td><strong><?= $r['status'] ?></strong></td>
<td>
<form method="POST">
<input type="hidden" name="req_id" value="<?= $r['id'] ?>">
<select name="status">
<option value="В работе">В работу</option>
<option value="Выполнено">Выполнено</option>
<option value="Отменено">Отменить</option>
</select>
<input type="text" name="reject_reason" placeholder="Причина отмены (если отмена)">
<button type="submit" name="change_status">Ок</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php include 'footer.php'; ?>
```
### Один файл мобильного дизайна (mobile.html)
Поскольку в задании сказано, что интегрировать этот дизайн в код не нужно, мы создаем красивую страницу-презентацию, где один под другим отображаются макеты экранов размером ровно 390x844 px.
Создайте файл mobile.html и вставьте туда этот код:
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Мобильный дизайн (390x844)</title>
<style>
body { background: #f0f2f5; display: flex; flex-direction: column; align-items: center; gap: 40px; padding: 20px; font-family: sans-serif; }
.phone-mockup { width: 390px; height: 844px; background: white; border: 8px solid #222; border-radius: 40px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); overflow-y: auto; padding: 30px 20px; box-sizing: border-box; display: flex; flex-direction: column; gap: 20px; }
.screen-title { text-align: center; font-size: 14px; color: #888; font-weight: bold; text-transform: uppercase; margin-bottom: -20px; }
h1 { font-size: 24px; text-align: center; color: #0056b3; }
input, select { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 8px; box-sizing: border-box; }
button { background: #0056b3; color: white; border: none; padding: 14px; border-radius: 8px; width: 100%; cursor: pointer; font-size: 16px; }
.card { border: 1px solid #eee; padding: 15px; border-radius: 8px; background: #fafafa; }
</style>
</head>
<body>
<div class="screen-title">Экран авторизации</div>
<div class="phone-mockup">
<h1>Мой Не Сам</h1>
<p style="text-align:center; color:#666;">Вход для клиентов</p>
<input type="text" placeholder="Логин">
<input type="password" placeholder="Пароль">
<button>Войти</button>
<p style="text-align:center; font-size:14px;"><a href="#">Регистрация</a></p>
</div>
<div class="screen-title">Экран регистрации</div>
<div class="phone-mockup">
<h1>Регистрация</h1>
<input type="text" placeholder="ФИО">
<input type="text" placeholder="Телефон">
<input type="email" placeholder="E-mail">
<input type="text" placeholder="Придумайте логин">
<input type="password" placeholder="Придумайте пароль">
<button>Зарегистрироваться</button>
</div>
<div class="screen-title">Создание заявки и история</div>
<div class="phone-mockup">
<h2 style="color:#0056b3; font-size:20px;">Новая заявка</h2>
<input type="text" placeholder="Адрес доставки услуг">
<select><option>Общий клининг</option><option>Генеральная уборка</option></select>
<input type="date">
<input type="time">
<button>Отправить заявку</button>
<h2 style="font-size:18px; margin-top:20px;">Мои заявки</h2>
<div class="card">
<strong>Генеральная уборка</strong><br>
<span style="font-size:12px; color:#999;">12.06.2026 в 14:00</span><br>
<span style="color:green; font-size:14px;">Статус: Выполнено</span>
</div>
</div>
</body>
</html>
```
### Как это теперь запустить?
1. Включаете OpenServer.
2. В браузере открываете http://localhost/moinesam/login.php — это рабочий сайт. Вы можете зарегистрироваться, войти под пользователем, создать заявку, а потом зайти под админом (adminka / password) и изменить её статус.
3. Чтобы показать экспертам мобильный дизайн, откройте в браузере http://localhost/moinesam/mobile.html. Они увидят красивые макеты мобильного приложения ровно в требуемом разрешении.