USER
Вот мой фронт <!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Хика Морi - АнимеСообщество</title>
<!-- Подключение Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet">
<style>
</style>
</head>
<body>
<!-- Навигационная панель -->
<div class="navbar">
<div class="logo" onclick="showSection('feed')">Хика Морi</div>
<div class="nav-icons">
<span onclick="showSection('feed')">🏠 Лента</span>
<span onclick="showSection('createPost')">➕ Создать</span>
<span onclick="showSection('animeWatch')">🎥 Просмотр</span>
<span onclick="showSection('community')">👥 Сообщества</span>
<span onclick="showSection('profile')">👤 Профиль</span>
</div>
</div>
<!-- Главная лента -->
<div id="feed" class="section active feed">
<!-- Пример поста -->
<div class="post" data-post-id="1">
<div class="post-header">
<img src="https://i.imgur.com/8Km9tLL.jpg" alt="Аватар">
<strong>Anko_Lover</strong>
</div>
<div class="post-content">
<img src="https://cdn.myanimelist.net/images/anime/10/47347.jpg" alt="Attack on Titan">
Обожаю новый сезон "Attack on Titan"! Как вам развитие сюжета?
</div>
<div class="post-actions">
<span onclick="likePost(this)">👍 <span class="like-count">0</span></span>
<span onclick="toggleComments(this)">💬 Комментарии</span>
</div>
<div class="comments-section" style="display: none; margin-top: 15px;">
<input type="text" placeholder="Написать комментарий..." class="comment-input">
<button onclick="addComment(this)">Отправить</button>
<div class="comments-list"></div>
</div>
</div>
<!-- Дополнительные посты -->
<div class="post" data-post-id="2">
<div class="post-header">
<img src="https://i.imgur.com/8Km9tLL.jpg" alt="Аватар">
<strong>Otaku_Girl</strong>
</div>
<div class="post-content">
<img src="https://cdn.myanimelist.net/images/anime/1322/128099.jpg" alt="Naruto Shippuden">
Новые серии "Naruto Shippuden" просто супер! Нравится ли вам развитие сюжета?
</div>
<div class="post-actions">
<span onclick="likePost(this)">👍 <span class="like-count">0</span></span>
<span onclick="toggleComments(this)">💬 Комментарии</span>
</div>
<div class="comments-section" style="display: none; margin-top: 15px;">
<input type="text" placeholder="Написать комментарий..." class="comment-input">
<button onclick="addComment(this)">Отправить</button>
<div class="comments-list"></div>
</div>
</div>
<!-- Добавьте больше постов по необходимости -->
</div>
<!-- Форма создания поста -->
<div id="createPost" class="section create-post">
<h2>Создать новый пост</h2>
<textarea id="postContent" placeholder="Что у вас на уме?"></textarea>
<input type="file" id="postImages" accept="image/*" multiple>
<button onclick="createPost()">Опубликовать</button>
</div>
<!-- Раздел просмотра аниме -->
<div id="animeWatch" class="section anime-watch">
<h2>Просмотр Аниме</h2>
<!-- Поиск аниме -->
<div class="search-bar">
<input type="text" id="searchAnime" placeholder="Поиск аниме...">
<button onclick="searchAnime()">🔍</button>
</div>
<!-- Список аниме -->
<div class="anime-list" id="animeList">
<!-- Пример аниме -->
<div class="anime-item">
<img src="https://cdn.myanimelist.net/images/anime/1552/108717.jpg" alt="Attack on Titan">
<div class="anime-info">
<h3>Attack on Titan</h3>
<p>Эпическая история о борьбе человечества за выживание против титанических существ.</p>
<div class="anime-actions">
<span onclick="toggleFavorite(this)">⭐ <span class="favorite-count">0</span></span>
<span onclick="showEpisodes(this)">📺 Серии</span>
<div class="rating" onclick="rateAnime(event, this)">
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
</div>
</div>
</div>
</div>
<!-- Другие аниме -->
<div class="anime-item">
<img src="https://cdn.myanimelist.net/images/anime/13/17405.jpg" alt="Naruto Shippuden">
<div class="anime-info">
<h3>Naruto Shippuden</h3>
<p>Продолжение приключений Наруто Узумаки и его друзей в мире ниндзя.</p>
<div class="anime-actions">
<span onclick="toggleFavorite(this)">⭐ <span class="favorite-count">0</span></span>
<span onclick="showEpisodes(this)">📺 Серии</span>
<div class="rating" onclick="rateAnime(event, this)">
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
</div>
</div>
</div>
</div>
<div class="anime-item">
<img src="https://cdn.myanimelist.net/images/anime/5/73245.jpg" alt="One Piece">
<div class="anime-info">
<h3>One Piece</h3>
<p>Эпическое путешествие Лаффи и его команды в поисках легендарного сокровища - One Piece.</p>
<div class="anime-actions">
<span onclick="toggleFavorite(this)">⭐ <span class="favorite-count">0</span></span>
<span onclick="showEpisodes(this)">📺 Серии</span>
<div class="rating" onclick="rateAnime(event, this)">
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
<span class="star">☆</span>
</div>
</div>
</div>
</div>
<!-- Добавьте больше аниме по необходимости -->
</div>
<!-- Видео-плеер -->
<div class="video-player" id="videoPlayer">
<div class="player-content">
<button class="close-btn" onclick="closeVideoPlayer()">✖</button>
<video controls id="animeVideo">
<source src="" type="video/mp4">
Ваш браузер не поддерживает видео.
</video>
</div>
</div>
</div>
<!-- Раздел Сообщества -->
<div id="community" class="section community">
<div class="community-header">
<h2>Сообщества</h2>
<button onclick="openModal('createGroupModal')">Создать сообщество</button>
</div>
<div class="groups-list" id="groupsList">
<!-- Пример сообщества -->
<div class="group-item" onclick="openGroup('Атака титанов')">
<h3>Атака титанов</h3>
<p>Обсуждаем всё, что связано с "Атакой Титанов"!</p>
<div class="group-actions">
<span title="Подробнее">🔍</span>
</div>
</div>
<div class="group-item" onclick="openGroup('Наруто Шиппуден')">
<h3>Наруто Шиппуден</h3>
<p>Приключения Наруто и его друзей в мире ниндзя.</p>
<div class="group-actions">
<span title="Подробнее">🔍</span>
</div>
</div>
<!-- Добавьте больше сообществ по необходимости -->
</div>
</div>
<!-- Форма создания сообщества (Модальное окно) -->
<div class="modal" id="createGroupModal">
<div class="modal-content">
<h2>Создать новое сообщество</h2>
<input type="text" id="groupName" placeholder="Название сообщества">
<textarea id="groupDescription" placeholder="Описание сообщества"></textarea>
<div style="display: flex; justify-content: flex-end; width: 100%;">
<button onclick="createGroup()">Создать</button>
<button onclick="closeModal('createGroupModal')" class="cancel-btn">Отмена</button>
</div>
</div>
</div>
<!-- Секция группы -->
<div id="groupSection" class="section group-section">
<div class="group-header">
<h2 id="currentGroupName">Название Сообщества</h2>
<button id="createEventBtn" onclick="openModal('createEventModal')">Создать событие</button>
</div>
<div id="groupPosts" class="feed">
<!-- Посты внутри группы будут добавляться здесь -->
<!-- Пример поста внутри группы -->
<div class="post" data-post-id="group1">
<div class="post-header">
<img src="https://i.imgur.com/8Km9tLL.jpg" alt="Аватар">
<strong>Админ группы</strong>
</div>
<div class="post-content">
Добро пожаловать в сообщество "Название Сообщества"! Давайте обсудим всё, что связано с этим аниме.
</div>
<div class="post-actions">
<span onclick="likePost(this)">👍 <span class="like-count">0</span></span>
<span onclick="toggleComments(this)">💬 Комментарии</span>
</div>
<div class="comments-section" style="display: none; margin-top: 15px;">
<input type="text" placeholder="Написать комментарий..." class="comment-input">
<button onclick="addComment(this)">Отправить</button>
<div class="comments-list"></div>
</div>
</div>
<!-- Добавьте больше постов по необходимости -->
</div>
<h3>События</h3>
<div class="events-list" id="groupEvents">
<!-- События будут добавляться здесь -->
</div>
<!-- Карта события -->
<div id="eventMap" class="event-map">
<!-- Встроенная карта будет отображаться здесь -->
</div>
</div>
<!-- Форма создания события (Модальное окно) -->
<div class="modal" id="createEventModal">
<div class="modal-content">
<h2>Создать событие</h2>
<input type="text" id="eventName" placeholder="Название события">
<textarea id="eventDescription" placeholder="Описание события"></textarea>
<input type="text" id="eventLocation" placeholder="Местоположение (адрес)">
<input type="date" id="eventDate" placeholder="Дата события">
<input type="time" id="eventTime" placeholder="Время события">
<div style="display: flex; justify-content: flex-end; width: 100%;">
<button onclick="createEvent()">Создать</button>
<button onclick="closeModal('createEventModal')" class="cancel-btn">Отмена</button>
</div>
</div>
</div>
<!-- Профиль пользователя -->
<div id="profile" class="section profile">
<h2>Профиль</h2>
<div class="profile-header">
<img src="https://i.imgur.com/8Km9tLL.jpg" alt="Аватар">
<div>
<h2>Имя Пользователя</h2>
<p>@nik_name</p>
</div>
</div>
<div class="profile-content">
<h3>О себе</h3>
<p>Любитель аниме, манги и всего японского! 🎌</p>
<h3>Мои посты</h3>
<!-- Пользовательские посты будут отображаться здесь -->
<!-- Пример поста пользователя -->
<div class="post" data-post-id="user1">
<div class="post-header">
<img src="https://i.imgur.com/8Km9tLL.jpg" alt="Аватар">
<strong>Вы</strong>
</div>
<div class="post-content">
<p>Сегодня посмотрел новый эпизод "One Piece" — невероятно!</p>
</div>
<div class="post-actions">
<span onclick="likePost(this)">👍 <span class="like-count">0</span></span>
<span onclick="toggleComments(this)">💬 Комментарии</span>
</div>
<div class="comments-section" style="display: none; margin-top: 15px;">
<input type="text" placeholder="Написать комментарий..." class="comment-input">
<button onclick="addComment(this)">Отправить</button>
<div class="comments-list"></div>
</div>
</div>
<!-- Добавьте больше постов по необходимости -->
</div>
</div>
<!-- Видео-плеер -->
<div class="video-player" id="videoPlayer">
<div class="player-content">
<button class="close-btn" onclick="closeVideoPlayer()">✖</button>
<video controls id="animeVideo">
<source src="" type="video/mp4">
Ваш браузер не поддерживает видео.
</video>
</div>
</div>
<!-- Подключение Google Maps API -->
<script async
src="https://maps.googleapis.com/maps/api/js?key=YOUR_GOOGLE_MAPS_API_KEY&callback=initMap">
</script>
<!-- JavaScript для взаимодействия -->
<script>
// Инициализация карты
let map;
let markers = [];
function initMap() {
map = new google.maps.Map(document.getElementById('eventMap'), {
center: { lat: 55.7558, lng: 37.6173 }, // Центр карты, например, Москва
zoom: 10
});
}
// Функция для переключения секций
function showSection(sectionId) {
const sections = document.querySelectorAll('.section');
sections.forEach(section => {
if (section.id === sectionId) {
section.classList.add('active');
} else {
section.classList.remove('active');
}
});
// Скрываем модальные окна при переключении секций
const modals = document.querySelectorAll('.modal');
modals.forEach(modal => {
modal.style.display = 'none';
});
// Если не группа, скрываем секцию группы
if (sectionId !== 'groupSection') {
document.getElementById('groupSection').style.display = 'none';
}
}
// Функция для открытия модального окна
function openModal(modalId) {
document.getElementById(modalId).style.display = 'flex';
}
// Функция для закрытия модального окна
function closeModal(modalId) {
document.getElementById(modalId).style.display = 'none';
}
// Функция для создания нового поста
function createPost() {
const content = document.getElementById('postContent').value.trim();
const imagesInput = document.getElementById('postImages');
const imageFiles = imagesInput.files;
if (content === "" && imageFiles.length === 0) {
alert("Пожалуйста, введите текст поста или выберите изображения.");
return;
}
// Генерация уникального ID для поста
const postId = Date.now().toString();
// Создание элементов поста
const post = document.createElement('div');
post.className = 'post';
post.setAttribute('data-post-id', postId);
post.style.opacity = '0';
const postHeader = document.createElement('div');
postHeader.className = 'post-header';
postHeader.innerHTML = `
<img src="https://i.imgur.com/8Km9tLL.jpg" alt="Аватар">
<strong>Вы</strong>
`;
const postContent = document.createElement('div');
postContent.className = 'post-content';
if (content !== "") {
const text = document.createElement('p');
text.textContent = content;
postContent.appendChild(text);
}
if (imageFiles.length > 0) {
Array.from(imageFiles).forEach(file => {
const reader = new FileReader();
reader.onload = function(e) {
const img = document.createElement('img');
img.src = e.target.result;
img.alt = "Пользовательское изображение";
postContent.appendChild(img);
}
reader.readAsDataURL(file);
});
}
const postActions = document.createElement('div');
postActions.className = 'post-actions';
postActions.innerHTML = `
<span onclick="likePost(this)">👍 <span class="like-count">0</span></span>
<span onclick="toggleComments(this)">💬 Комментарии</span>
`;
const commentsSection = document.createElement('div');
commentsSection.className = 'comments-section';
commentsSection.style.display = 'none';
commentsSection.innerHTML = `
<input type="text" placeholder="Написать комментарий..." class="comment-input">
<button onclick="addComment(this)">Отправить</button>
<div class="comments-list"></div>
`;
post.appendChild(postHeader);
post.appendChild(postContent);
post.appendChild(postActions);
post.appendChild(commentsSection);
// Добавление поста в начало ленты
const feed = document.getElementById('feed');
feed.insertBefore(post, feed.firstChild);
// Сохранение поста в LocalStorage (для демонстрации)
let posts = JSON.parse(localStorage.getItem('posts')) || [];
const newPost = {
id: postId,
author: "Вы",
content: content,
images: [],
likes: 0
};
if (imageFiles.length > 0) {
Array.from(imageFiles).forEach(file => {
const reader = new FileReader();
reader.onload = function(e) {
newPost.images.push(e.target.result);
// Отображение изображений
const img = document.createElement('img');
img.src = e.target.result;
img.alt = "Пользовательское изображение";
postContent.appendChild(img);
// Обновление LocalStorage
localStorage.setItem('posts', JSON.stringify(posts));
}
reader.readAsDataURL(file);
});
}
posts.unshift(newPost);
localStorage.setItem('posts', JSON.stringify(posts));
// Анимация появления поста
setTimeout(() => {
post.style.opacity = '1';
}, 100);
// Очистка формы и переключение на ленту
document.getElementById('postContent').value = "";
imagesInput.value = "";
showSection('feed');
}
// Функция для лайка поста
function likePost(element) {
const likeCountSpan = element.querySelector('.like-count');
let count = parseInt(likeCountSpan.textContent);
count++;
likeCountSpan.textContent = count;
// Анимация увеличения сердца
element.style.transform = 'scale(1.2)';
setTimeout(() => {
element.style.transform = 'scale(1)';
}, 200);
// Сохранение лайка в LocalStorage
const post = element.closest('.post');
const postId = post.getAttribute('data-post-id');
let posts = JSON.parse(localStorage.getItem('posts')) || [];
const currentPost = posts.find(p => p.id === postId);
if (currentPost) {
currentPost.likes = count;
localStorage.setItem('posts', JSON.stringify(posts));
}
}
// Функция для переключения секции комментариев
function toggleComments(element) {
const commentsSection = element.parentElement.nextElementSibling;
if (commentsSection.style.display === 'none') {
commentsSection.style.display = 'block';
commentsSection.style.animation = 'fadeIn 0.5s ease-in';
loadComments(commentsSection);
} else {
commentsSection.style.display = 'none';
}
}
// Функция для добавления комментария
function addComment(button) {
const commentsSection = button.parentElement;
const commentInput = commentsSection.querySelector('.comment-input');
const commentText = commentInput.value.trim();
if (commentText === "") {
alert("Пожалуйста, введите комментарий.");
return;
}
const commentsList = commentsSection.querySelector('.comments-list');
const comment = document.createElement('div');
comment.style.padding = '10px 0';
comment.style.borderBottom = '1px solid #eee';
comment.textContent = commentText;
comment.style.opacity = '0';
comment.style.animation = 'fadeIn 0.3s forwards';
commentsList.appendChild(comment);
// Сохранение комментария в LocalStorage
const post = button.closest('.post');
const postId = post.getAttribute('data-post-id');
let comments = JSON.parse(localStorage.getItem(`comments-${postId}`)) || [];
comments.push(commentText);
localStorage.setItem(`comments-${postId}`, JSON.stringify(comments));
commentInput.value = "";
}
// Функция для загрузки комментариев из LocalStorage
function loadComments(commentsSection) {
const post = commentsSection.closest('.post');
const postId = post.getAttribute('data-post-id');
const comments = JSON.parse(localStorage.getItem(`comments-${postId}`)) || [];
const commentsList = commentsSection.querySelector('.comments-list');
commentsList.innerHTML = "";
comments.forEach(text => {
const comment = document.createElement('div');
comment.style.padding = '10px 0';
comment.style.borderBottom = '1px solid #eee';
comment.textContent = text;
comment.style.opacity = '0';
comment.style.animation = 'fadeIn 0.3s forwards';
commentsList.appendChild(comment);
});
}
// Функция для поиска аниме
function searchAnime() {
const query = document.getElementById('searchAnime').value.toLowerCase();
const animeItems = document.querySelectorAll('.anime-item');
animeItems.forEach(item => {
const title = item.querySelector('.anime-info h3').textContent.toLowerCase();
if (title.includes(query)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
}
// Функция для отображения списка серий
function showEpisodes(element) {
const animeItem = element.closest('.anime-item');
const animeTitle = animeItem.querySelector('.anime-info h3').textContent;
let episodes = [];
// Пример данных серий, можно заменить на реальные данные
if (animeTitle === "Attack on Titan") {
episodes = [
{number: 1, title: "To You, in 2000 Years: The Fall of Shiganshina, Part 1", video: "https://www.w3schools.com/html/mov_bbb.mp4"},
{number: 2, title: "That Day: The Fall of Shiganshina, Part 2", video: "https://www.w3schools.com/html/mov_bbb.mp4"},
{number: 3, title: "A Dim Light Amid Despair: Humanity’s Comeback, Part 1", video: "https://www.w3schools.com/html/mov_bbb.mp4"},
// Добавьте больше серий по необходимости
];
} else if (animeTitle === "Naruto Shippuden") {
episodes = [
{number: 1, title: "Homecoming", video: "https://www.w3schools.com/html/mov_bbb.mp4"},
{number: 2, title: "The Akatsuki Makes Its Move", video: "https://www.w3schools.com/html/mov_bbb.mp4"},
{number: 3, title: "The Results of Training", video: "https://www.w3schools.com/html/mov_bbb.mp4"},
// Добавьте больше серий по необходимости
];
} else if (animeTitle === "One Piece") {
episodes = [
{number: 1, title: "I'm Luffy! The Man Who's Gonna Be King of the Pirates!", video: "https://www.w3schools.com/html/mov_bbb.mp4"},
{number: 2, title: "Enter the Great Swordsman! Pirate Hunter Roronoa Zoro!", video: "https://www.w3schools.com/html/mov_bbb.mp4"},
{number: 3, title: "Morgan vs. Luffy! Who’s the Mysterious Pretty Girl?", video: "https://www.w3schools.com/html/mov_bbb.mp4"},
// Добавьте больше серий по необходимости
];
}
// Создание всплывающего окна со списком серий
const episodesModal = document.createElement('div');
episodesModal.style.position = 'fixed';
episodesModal.style.top = '0';
episodesModal.style.left = '0';
episodesModal.style.width = '100%';
episodesModal.style.height = '100%';
episodesModal.style.backgroundColor = 'rgba(0,0,0,0.8)';
episodesModal.style.display = 'flex';
episodesModal.style.justifyContent = 'center';
episodesModal.style.alignItems = 'center';
episodesModal.style.zIndex = '3000';
episodesModal.style.animation = 'fadeIn 0.5s ease-in';
const modalContent = document.createElement('div');
modalContent.style.backgroundColor = 'var(--modal-bg-color)';
modalContent.style.padding = '25px 30px';
modalContent.style.borderRadius = '12px';
modalContent.style.width = '90%';
modalContent.style.maxWidth = '700px';
modalContent.style.maxHeight = '80%';
modalContent.style.overflowY = 'auto';
modalContent.style.position = 'relative';
modalContent.style.animation = 'slideIn 0.5s ease-out';
modalContent.style.boxShadow = '0 4px 8px var(--shadow-color)';
modalContent.style.backdropFilter = 'blur(5px)';
const closeBtn = document.createElement('button');
closeBtn.textContent = '✖';
closeBtn.style.position = 'absolute';
closeBtn.style.top = '15px';
closeBtn.style.right = '15px';
closeBtn.style.backgroundColor = 'var(--primary-color)';
closeBtn.style.color = '#fff';
closeBtn.style.border = 'none';
closeBtn.style.borderRadius = '50%';
closeBtn.style.width = '35px';
closeBtn.style.height = '35px';
closeBtn.style.fontSize = '18px';
closeBtn.style.cursor = 'pointer';
closeBtn.style.transition = 'background-color var(--transition-speed) ease, transform var(--transition-speed) ease, box-shadow var(--transition-speed) ease';
closeBtn.onclick = () => {
document.body.removeChild(episodesModal);
};
closeBtn.onmouseover = () => {
closeBtn.style.backgroundColor = 'var(--secondary-color)';
closeBtn.style.transform = 'scale(1.1)';
closeBtn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.3)';
}
closeBtn.onmouseout = () => {
closeBtn.style.backgroundColor = 'var(--primary-color)';
closeBtn.style.transform = 'scale(1)';
closeBtn.style.boxShadow = 'none';
}
modalContent.appendChild(closeBtn);
const episodesList = document.createElement('ul');
episodesList.style.listStyle = 'none';
episodesList.style.padding = '0';
episodesList.style.width = '100%';
episodes.forEach(ep => {
const li = document.createElement('li');
li.style.marginBottom = '15px';
const btn = document.createElement('button');
btn.textContent = `Эпизод ${ep.number}: ${ep.title}`;
btn.style.width = '100%';
btn.style.padding = '15px';
btn.style.border = 'none';
btn.style.borderRadius = '8px';
btn.style.backgroundColor = 'var(--primary-color)';
btn.style.color = '#fff';
btn.style.cursor = 'pointer';
btn.style.transition = 'background-color var(--transition-speed) ease, transform var(--transition-speed) ease';
btn.style.fontSize = '16px';
btn.style.fontFamily = 'Roboto, sans-serif';
btn.onclick = () => {
openVideoPlayer(ep.video);
document.body.removeChild(episodesModal);
};
btn.onmouseover = () => {
btn.style.backgroundColor = 'var(--secondary-color)';
btn.style.transform = 'scale(1.05)';
}
btn.onmouseout = () => {
btn.style.backgroundColor = 'var(--primary-color)';
btn.style.transform = 'scale(1)';
}
li.appendChild(btn);
episodesList.appendChild(li);
});
modalContent.appendChild(episodesList);
episodesModal.appendChild(modalContent);
document.body.appendChild(episodesModal);
}
// Функция для открытия видео-плеера
function openVideoPlayer(videoUrl) {
const videoPlayer = document.getElementById('videoPlayer');
const videoElement = document.getElementById('animeVideo');
videoElement.src = videoUrl;
videoPlayer.style.display = 'flex';
videoElement.play();
videoPlayer.style.animation = 'fadeIn 0.5s ease-in';
}
// Функция для закрытия видео-плеера
function closeVideoPlayer() {
const videoPlayer = document.getElementById('videoPlayer');
const videoElement = document.getElementById('animeVideo');
videoElement.pause();
videoElement.src = '';
videoPlayer.style.display = 'none';
}
// Функция для добавления аниме в избранное
function toggleFavorite(element) {
const favoriteCountSpan = element.querySelector('.favorite-count');
let count = parseInt(favoriteCountSpan.textContent);
const animeItem = element.closest('.anime-item');
if (!element.classList.contains('favorite')) {
count++;
favoriteCountSpan.textContent = count;
element.classList.add('favorite');
// Анимация "звезды"
element.style.transform = 'scale(1.2)';
setTimeout(() => {
element.style.transform = 'scale(1)';
}, 200);
} else {
if (count > 0) {
count--;
favoriteCountSpan.textContent = count;
}
element.classList.remove('favorite');
// Анимация "звезды"
element.style.transform = 'scale(0.8)';
setTimeout(() => {
element.style.transform = 'scale(1)';
}, 200);
}
}
// Функция для рейтинга аниме
function rateAnime(event, element) {
const stars = element.querySelectorAll('.star');
const rating = Array.from(stars).indexOf(event.target) + 1;
stars.forEach((star, index) => {
if (index < rating) {
star.classList.add('selected');
} else {
star.classList.remove('selected');
}
});
// Можно добавить сохранение рейтинга в базе данных или локальном хранилище
}
// Закрытие видео-плеера и модальных окон при нажатии вне контента
window.onclick = function(event) {
const videoPlayer = document.getElementById('videoPlayer');
if (event.target == videoPlayer) {
closeVideoPlayer();
}
const modals = document.querySelectorAll('.modal');
modals.forEach(modal => {
if (event.target == modal) {
modal.style.display = 'none';
}
});
}
// Функция для создания сообщества
function createGroup() {
const groupName = document.getElementById('groupName').value.trim();
const groupDescription = document.getElementById('groupDescription').value.trim();
if (groupName === "" || groupDescription === "") {
alert("Пожалуйста, заполните все поля.");
return;
}
// Проверка на существование группы с таким же названием
const existingGroups = document.querySelectorAll('.group-item h3');
for (let group of existingGroups) {
if (group.textContent.toLowerCase() === groupName.toLowerCase()) {
alert("Сообщество с таким названием уже существует.");
return;
}
}
// Генерация уникального ID для группы
const groupId = Date.now().toString();
// Создание элементов сообщества
const groupItem = document.createElement('div');
groupItem.className = 'group-item';
groupItem.setAttribute('data-group-id', groupId);
groupItem.onclick = () => openGroup(groupName);
groupItem.innerHTML = `
<h3>${groupName}</h3>
<p>${groupDescription}</p>
<div class="group-actions">
<span title="Подробнее">🔍</span>
</div>
`;
document.getElementById('groupsList').appendChild(groupItem);
// Сохранение группы в LocalStorage
let groups = JSON.parse(localStorage.getItem('groups')) || [];
groups.push({
id: groupId,
name: groupName,
description: groupDescription,
posts: [],
events: []
});
localStorage.setItem('groups', JSON.stringify(groups));
// Закрытие модального окна и очистка полей
closeModal('createGroupModal');
document.getElementById('groupName').value = "";
document.getElementById('groupDescription').value = "";
}
// Функция для открытия группы
function openGroup(groupName) {
// Показываем секцию группы и скрываем другие секции
const sections = document.querySelectorAll('.section');
sections.forEach(section => {
if (section.id === 'groupSection') {
section.classList.add('active');
} else {
section.classList.remove('active');
}
});
// Закрываем все модальные окна
const modals = document.querySelectorAll('.modal');
modals.forEach(modal => {
modal.style.display = 'none';
});
// Заполняем название текущей группы
document.getElementById('currentGroupName').textContent = groupName;
// Очищаем предыдущие посты и события
document.getElementById('groupPosts').innerHTML = "";
document.getElementById('groupEvents').innerHTML = "";
document.getElementById('eventMap').innerHTML = "";
// Получение информации о группе из LocalStorage
let groups = JSON.parse(localStorage.getItem('groups')) || [];
let currentGroup = groups.find(g => g.name === groupName);
if (!currentGroup) {
alert("Сообщество не найдено.");
showSection('community');
return;
}
// Отображение постов группы
currentGroup.posts.forEach(postData => {
const post = document.createElement('div');
post.className = 'post';
post.setAttribute('data-post-id', postData.id);
const postHeader = document.createElement('div');
postHeader.className = 'post-header';
postHeader.innerHTML = `
<img src="${postData.avatar || 'https://i.imgur.com/8Km9tLL.jpg'}" alt="Аватар">
<strong>${postData.author}</strong>
`;
const postContent = document.createElement('div');
postContent.className = 'post-content';
if (postData.content) {
const text = document.createElement('p');
text.textContent = postData.content;
postContent.appendChild(text);
}
if (postData.images && postData.images.length > 0) {
postData.images.forEach(imgSrc => {
const img = document.createElement('img');
img.src = imgSrc;
img.alt = "Изображение сообщества";
postContent.appendChild(img);
});
}
const postActions = document.createElement('div');
postActions.className = 'post-actions';
postActions.innerHTML = `
<span onclick="likePost(this)">👍 <span class="like-count">${postData.likes || 0}</span></span>
<span onclick="toggleComments(this)">💬 Комментарии</span>
`;
const commentsSection = document.createElement('div');
commentsSection.className = 'comments-section';
commentsSection.style.display = 'none';
commentsSection.innerHTML = `
<input type="text" placeholder="Написать комментарий..." class="comment-input">
<button onclick="addComment(this)">Отправить</button>
<div class="comments-list"></div>
`;
post.appendChild(postHeader);
post.appendChild(postContent);
post.appendChild(postActions);
post.appendChild(commentsSection);
document.getElementById('groupPosts').appendChild(post);
});
// Отображение событий группы
currentGroup.events.forEach(eventData => {
const eventItem = document.createElement('div');
eventItem.className = 'event-item';
eventItem.innerHTML = `
<h3>${eventData.name}</h3>
<p>${eventData.description}</p>
<p><strong>Место:</strong> ${eventData.location}</p>
<p><strong>Дата и время:</strong> ${eventData.date} ${eventData.time}</p>
`;
document.getElementById('groupEvents').appendChild(eventItem);
// Добавление метки на карту
if (eventData.latitude && eventData.longitude) {
const marker = new google.maps.Marker({
position: { lat: eventData.latitude, lng: eventData.longitude },
map: map,
title: eventData.name
});
const infoWindow = new google.maps.InfoWindow({
content: `<h3>${eventData.name}</h3><p>${eventData.description}</p><p>${eventData.date} ${eventData.time}</p>`
});
marker.addListener('click', () => {
infoWindow.open(map, marker);
});
markers.push(marker);
}
});
// Центрирование карты на последнем событии или на Москве
if (currentGroup.events.length > 0) {
const lastEvent = currentGroup.events[currentGroup.events.length - 1];
if (lastEvent.latitude && lastEvent.longitude) {
map.setCenter({ lat: lastEvent.latitude, lng: lastEvent.longitude });
}
} else {
map.setCenter({ lat: 55.7558, lng: 37.6173 }); // Москва
}
}
// Функция для создания события
function createEvent() {
const eventName = document.getElementById('eventName').value.trim();
const eventDescription = document.getElementById('eventDescription').value.trim();
const eventLocation = document.getElementById('eventLocation').value.trim();
const eventDate = document.getElementById('eventDate').value;
const eventTime = document.getElementById('eventTime').value;
if (eventName === "" || eventDescription === "" || eventLocation === "" || eventDate === "" || eventTime === "") {
alert("Пожалуйста, заполните все поля.");
return;
}
// Получение координат по адресу с использованием Geocoding API
fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(eventLocation)}&key=YOUR_GOOGLE_MAPS_API_KEY`)
.then(response => response.json())
.then(data => {
if (data.status === "OK") {
const location = data.results[0].geometry.location;
// Создание метки на карте
const marker = new google.maps.Marker({
position: location,
map: map,
title: eventName
});
const infoWindow = new google.maps.InfoWindow({
content: `<h3>${eventName}</h3><p>${eventDescription}</p><p>${eventDate} ${eventTime}</p>`
});
marker.addListener('click', () => {
infoWindow.open(map, marker);
});
markers.push(marker);
// Получение текущей группы
const currentGroupName = document.getElementById('currentGroupName').textContent;
let groups = JSON.parse(localStorage.getItem('groups')) || [];
let currentGroup = groups.find(g => g.name === currentGroupName);
if (!currentGroup) {
alert("Ошибка: группа не найдена.");
return;
}
// Добавление события в группу
const newEvent = {
id: Date.now().toString(),
name: eventName,
description: eventDescription,
location: eventLocation,
date: eventDate,
time: eventTime,
latitude: location.lat,
longitude: location.lng
};
currentGroup.events.push(newEvent);
localStorage.setItem('groups', JSON.stringify(groups));
// Добавление события в список событий
const eventItem = document.createElement('div');
eventItem.className = 'event-item';
eventItem.innerHTML = `
<h3>${eventName}</h3>
<p>${eventDescription}</p>
<p><strong>Место:</strong> ${eventLocation}</p>
<p><strong>Дата и время:</strong> ${eventDate} ${eventTime}</p>
`;
document.getElementById('groupEvents').appendChild(eventItem);
// Центрирование карты на новом событии
map.setCenter(location);
// Закрытие модального окна и очистка полей
closeModal('createEventModal');
document.getElementById('eventName').value = "";
document.getElementById('eventDescription').value = "";
document.getElementById('eventLocation').value = "";
document.getElementById('eventDate').value = "";
document.getElementById('eventTime').value = "";
} else {
alert("Не удалось найти указанное местоположение.");
}
})
.catch(error => {
console.error("Ошибка при получении координат:", error);
alert("Произошла ошибка при создании события.");
});
}
// Функция для загрузки сохранённых постов и комментариев при загрузке страницы
document.addEventListener('DOMContentLoaded', () => {
loadPostsFromLocalStorage();
loadGroupsFromLocalStorage();
});
// Функция для загрузки постов из LocalStorage
function loadPostsFromLocalStorage() {
let posts = JSON.parse(localStorage.getItem('posts')) || [];
const feed = document.getElementById('feed');
posts.forEach(postData => {
const post = document.createElement('div');
post.className = 'post';
post.setAttribute('data-post-id', postData.id);
const postHeader = document.createElement('div');
postHeader.className = 'post-header';
postHeader.innerHTML = `
<img src="https://i.imgur.com/8Km9tLL.jpg" alt="Аватар">
<strong>${postData.author}</strong>
`;
const postContent = document.createElement('div');
postContent.className = 'post-content';
if (postData.content) {
const text = document.createElement('p');
text.textContent = postData.content;
postContent.appendChild(text);
}
if (postData.images && postData.images.length > 0) {
postData.images.forEach(imgSrc => {
const img = document.createElement('img');
img.src = imgSrc;
img.alt = "Пользовательское изображение";
postContent.appendChild(img);
});
}
const postActions = document.createElement('div');
postActions.className = 'post-actions';
postActions.innerHTML = `
<span onclick="likePost(this)">👍 <span class="like-count">${postData.likes || 0}</span></span>
<span onclick="toggleComments(this)">💬 Комментарии</span>
`;
const commentsSection = document.createElement('div');
commentsSection.className = 'comments-section';
commentsSection.style.display = 'none';
commentsSection.innerHTML = `
<input type="text" placeholder="Написать комментарий..." class="comment-input">
<button onclick="addComment(this)">Отправить</button>
<div class="comments-list"></div>
`;
post.appendChild(postHeader);
post.appendChild(postContent);
post.appendChild(postActions);
post.appendChild(commentsSection);
feed.appendChild(post);
});
}
// Функция для загрузки групп из LocalStorage
function loadGroupsFromLocalStorage() {
let groups = JSON.parse(localStorage.getItem('groups')) || [];
const groupsList = document.getElementById('groupsList');
groups.forEach(group => {
const groupItem = document.createElement('div');
groupItem.className = 'group-item';
groupItem.setAttribute('data-group-id', group.id);
groupItem.onclick = () => openGroup(group.name);
groupItem.innerHTML = `
<h3>${group.name}</h3>
<p>${group.description}</p>
<div class="group-actions">
<span title="Подробнее">🔍</span>
</div>
`;
groupsList.appendChild(groupItem);
});
}
// Функция для сохранения комментариев в LocalStorage
function saveComments(postId, comments) {
localStorage.setItem(`comments-${postId}`, JSON.stringify(comments));
}
</script>
</body>
</html>
напиши на пайтоне используя фласк и litelsql бэк и напиши весь код фронта и бэка ASSISTANT
Создание полноценного веб-приложения требует как фронтенда, так и бэкенда. Вы уже предоставили фронтенд на HTML, который использует LocalStorage для хранения данных. Для более устойчивой и масштабируемой системы рекомендуется использовать бэкенд с базой данных. В данном случае мы будем использовать **Flask** (микрофреймворк для Python) и **SQLite** для хранения данных.
Ниже приведены шаги и полный код для создания бэкенда и обновленного фронтенда, который взаимодействует с сервером через API.
## Архитектура Приложения
1. **Фронтенд (Client-Side):**
- HTML, CSS и JavaScript для пользовательского интерфейса.
- Использование `fetch` API для взаимодействия с сервером.
2. **Бэкенд (Server-Side):**
- Flask для создания API.
- SQLite для хранения данных (посты, комментарии, группы, события и пользователи).
- Маршруты (endpoints) для выполнения CRUD операций.
## Шаг 1: Установка Необходимых Инструментов
Убедитесь, что у вас установлены следующие инструменты:
- **Python 3.7+**
- **Flask**
- **Flask-CORS** (для разрешения кросс-доменных запросов)
- **SQLite3** (обычно входит в стандартную библиотеку Python)
Вы можете установить необходимые пакеты с помощью `pip`:
```bash
pip install Flask Flask-CORS
```
## Шаг 2: Создание Бэкенда с Flask
Создайте файл `app.py` с следующим содержимым:
```python
from flask import Flask, request, jsonify
from flask_cors import CORS
import sqlite3
from datetime import datetime
app = Flask(__name__)
CORS(app) # Разрешаем CORS для всех доменов
DATABASE = 'database.db'
def init_db():
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
# Создание таблиц
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
avatar TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
author_id INTEGER,
content TEXT,
image TEXT,
likes INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(author_id) REFERENCES users(id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
post_id INTEGER,
user_id INTEGER,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(post_id) REFERENCES posts(id),
FOREIGN KEY(user_id) REFERENCES users(id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
description TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
group_id INTEGER,
name TEXT,
description TEXT,
location TEXT,
latitude REAL,
longitude REAL,
date TEXT,
time TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(group_id) REFERENCES groups(id)
)
''')
conn.commit()
conn.close()
init_db()
# Вспомогательная функция для подключения к БД
def get_db_connection():
conn = sqlite3.connect(DATABASE)
conn.row_factory = sqlite3.Row
return conn
# Маршрут для регистрации пользователя
@app.route('/api/register', methods=['POST'])
def register():
data = request.json
username = data.get('username')
password = data.get('password')
avatar = data.get('avatar', 'https://i.imgur.com/8Km9tLL.jpg') # Стандартный аватар
if not username or not password:
return jsonify({'error': 'Username and password are required.'}), 400
conn = get_db_connection()
cursor = conn.cursor()
try:
cursor.execute('INSERT INTO users (username, password, avatar) VALUES (?, ?, ?)',
(username, password, avatar))
conn.commit()
user_id = cursor.lastrowid
except sqlite3.IntegrityError:
conn.close()
return jsonify({'error': 'Username already exists.'}), 400
conn.close()
return jsonify({'message': 'User registered successfully.', 'user_id': user_id}), 201
# Маршрут для логина пользователя
@app.route('/api/login', methods=['POST'])
def login():
data = request.json
username = data.get('username')
password = data.get('password')
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('SELECT * FROM users WHERE username = ? AND password = ?', (username, password))
user = cursor.fetchone()
conn.close()
if user:
return jsonify({'message': 'Login successful.', 'user_id': user['id'], 'username': user['username'], 'avatar': user['avatar']}), 200
else:
return jsonify({'error': 'Invalid credentials.'}), 401
# Маршрут для получения всех постов
@app.route('/api/posts', methods=['GET'])
def get_posts():
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('SELECT posts.*, users.username, users.avatar FROM posts JOIN users ON posts.author_id = users.id ORDER BY created_at DESC')
posts = cursor.fetchall()
conn.close()
posts_list = []
for post in posts:
posts_list.append({
'id': post['id'],
'author': post['username'],
'avatar': post['avatar'],
'content': post['content'],
'image': post['image'],
'likes': post['likes'],
'created_at': post['created_at']
})
return jsonify(posts_list), 200
# Маршрут для создания нового поста
@app.route('/api/posts', methods=['POST'])
def create_post():
data = request.json
author_id = data.get('author_id')
content = data.get('content')
image = data.get('image') # В данном примере предполагается, что изображение передается как URL
if not author_id:
return jsonify({'error': 'Author ID is required.'}), 400
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('INSERT INTO posts (author_id, content, image) VALUES (?, ?, ?)',
(author_id, content, image))
conn.commit()
post_id = cursor.lastrowid
conn.close()
return jsonify({'message': 'Post created successfully.', 'post_id': post_id}), 201
# Маршрут для лайка поста
@app.route('/api/posts/<int:post_id>/like', methods=['POST'])
def like_post(post_id):
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('UPDATE posts SET likes = likes + 1 WHERE id = ?', (post_id,))
conn.commit()
cursor.execute('SELECT likes FROM posts WHERE id = ?', (post_id,))
likes = cursor.fetchone()['likes']
conn.close()
return jsonify({'likes': likes}), 200
# Маршрут для получения комментариев поста
@app.route('/api/posts/<int:post_id>/comments', methods=['GET'])
def get_comments(post_id):
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('''
SELECT comments.*, users.username, users.avatar
FROM comments
JOIN users ON comments.user_id = users.id
WHERE comments.post_id = ?
ORDER BY comments.created_at ASC
''', (post_id,))
comments = cursor.fetchall()
conn.close()
comments_list = []
for comment in comments:
comments_list.append({
'id': comment['id'],
'user': comment['username'],
'avatar': comment['avatar'],
'content': comment['content'],
'created_at': comment['created_at']
})
return jsonify(comments_list), 200
# Маршрут для добавления комментария к посту
@app.route('/api/posts/<int:post_id>/comments', methods=['POST'])
def add_comment(post_id):
data = request.json
user_id = data.get('user_id')
content = data.get('content')
if not user_id or not content:
return jsonify({'error': 'User ID and content are required.'}), 400
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('INSERT INTO comments (post_id, user_id, content) VALUES (?, ?, ?)',
(post_id, user_id, content))
conn.commit()
comment_id = cursor.lastrowid
conn.close()
return jsonify({'message': 'Comment added successfully.', 'comment_id': comment_id}), 201
# Маршрут для получения всех групп
@app.route('/api/groups', methods=['GET'])
def get_groups():
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('SELECT * FROM groups ORDER BY created_at DESC')
groups = cursor.fetchall()
conn.close()
groups_list = []
for group in groups:
groups_list.append({
'id': group['id'],
'name': group['name'],
'description': group['description'],
'created_at': group['created_at']
})
return jsonify(groups_list), 200
# Маршрут для создания новой группы
@app.route('/api/groups', methods=['POST'])
def create_group():
data = request.json
name = data.get('name')
description = data.get('description')
if not name or not description:
return jsonify({'error': 'Name and description are required.'}), 400
conn = get_db_connection()
cursor = conn.cursor()
try:
cursor.execute('INSERT INTO groups (name, description) VALUES (?, ?)', (name, description))
conn.commit()
group_id = cursor.lastrowid
except sqlite3.IntegrityError:
conn.close()
return jsonify({'error': 'Group with this name already exists.'}), 400
conn.close()
return jsonify({'message': 'Group created successfully.', 'group_id': group_id}), 201
# Маршрут для получения группы по ID
@app.route('/api/groups/<int:group_id>', methods=['GET'])
def get_group(group_id):
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('SELECT * FROM groups WHERE id = ?', (group_id,))
group = cursor.fetchone()
if not group:
conn.close()
return jsonify({'error': 'Group not found.'}), 404
cursor.execute('SELECT * FROM events WHERE group_id = ?', (group_id,))
events = cursor.fetchall()
conn.close()
events_list = []
for event in events:
events_list.append({
'id': event['id'],
'name': event['name'],
'description': event['description'],
'location': event['location'],
'latitude': event['latitude'],
'longitude': event['longitude'],
'date': event['date'],
'time': event['time'],
'created_at': event['created_at']
})
group_info = {
'id': group['id'],
'name': group['name'],
'description': group['description'],
'created_at': group['created_at'],
'events': events_list
}
return jsonify(group_info), 200
# Маршрут для создания события в группе
@app.route('/api/groups/<int:group_id>/events', methods=['POST'])
def create_event(group_id):
data = request.json
name = data.get('name')
description = data.get('description')
location = data.get('location')
latitude = data.get('latitude')
longitude = data.get('longitude')
date = data.get('date')
time = data.get('time')
if not all([name, description, location, latitude, longitude, date, time]):
return jsonify({'error': 'All fields are required.'}), 400
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('SELECT * FROM groups WHERE id = ?', (group_id,))
group = cursor.fetchone()
if not group:
conn.close()
return jsonify({'error': 'Group not found.'}), 404
cursor.execute('''
INSERT INTO events (group_id, name, description, location, latitude, longitude, date, time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (group_id, name, description, location, latitude, longitude, date, time))
conn.commit()
event_id = cursor.lastrowid
conn.close()
return jsonify({'message': 'Event created successfully.', 'event_id': event_id}), 201
# Маршрут для получения событий группы
@app.route('/api/groups/<int:group_id>/events', methods=['GET'])
def get_group_events(group_id):
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('SELECT * FROM events WHERE group_id = ?', (group_id,))
events = cursor.fetchall()
conn.close()
events_list = []
for event in events:
events_list.append({
'id': event['id'],
'name': event['name'],
'description': event['description'],
'location': event['location'],
'latitude': event['latitude'],
'longitude': event['longitude'],
'date': event['date'],
'time': event['time'],
'created_at': event['created_at']
})
return jsonify(events_list), 200
# Маршрут для получения профиля пользователя
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user_profile(user_id):
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('SELECT * FROM users WHERE id = ?', (user_id,))
user = cursor.fetchone()
if not user:
conn.close()
return jsonify({'error': 'User not found.'}), 404
# Получение постов пользователя
cursor.execute('SELECT * FROM posts WHERE author_id = ? ORDER BY created_at DESC', (user_id,))
posts = cursor.fetchall()
posts_list = []
for post in posts:
posts_list.append({
'id': post['id'],
'content': post['content'],
'image': post['image'],
'likes': post['likes'],
'created_at': post['created_at']
})
user_info = {
'id': user['id'],
'username': user['username'],
'avatar': user['avatar'],
'posts': posts_list
}
conn.close()
return jsonify(user_info), 200
if __name__ == '__main__':
app.run(debug=True)
```
### Объяснение Кода Бэкенда:
- **Инициализация Базы Данных:** При запуске приложения создаются необходимые таблицы, если они еще не существуют.
- **Маршруты API:**
- **Регистрация и Логин:** Позволяют создавать новых пользователей и выполнять аутентификацию.
- **Посты:** Позволяют получать все посты, создавать новые посты и лайкать посты.
- **Комментарии:** Позволяют получать комментарии к определенному посту и добавлять новые комментарии.
- **Группы и События:** Позволяют создавать группы, получать информацию о группах, создавать события внутри групп и получать события.
- **Профиль Пользователя:** Позволяет получать информацию о пользователе и его постах.
## Шаг 3: Обновление Фронтенда для Взаимодействия с Бэкендом
Теперь необходимо обновить ваш фронтенд, чтобы он взаимодействовал с бэкендом через API. Ниже приведен обновленный код HTML и JavaScript.
### Обновленный HTML (`index.html`)
```html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Хика Морi - АнимеСообщество</title>
<!-- Подключение Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Noto+Sans+JP:wght@400;500;700&display=swap" rel="stylesheet">
<style>
/* Ваши стили здесь */
body {
font-family: 'Roboto', sans-serif;
margin: 0;
padding: 0;
background-color: #f0f2f5;
}
.navbar {
background-color: #4267B2;
color: white;
padding: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-icons span {
margin: 0 10px;
cursor: pointer;
}
.section {
display: none;
padding: 20px;
}
.section.active {
display: block;
}
.post {
background-color: white;
padding: 15px;
margin-bottom: 15px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.post-header {
display: flex;
align-items: center;
}
.post-header img {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 10px;
}
.post-content img {
max-width: 100%;
border-radius: 8px;
margin-top: 10px;
}
.post-actions span {
margin-right: 15px;
cursor: pointer;
}
.comments-section {
margin-top: 10px;
}
.comment-input {
width: 80%;
padding: 8px;
margin-right: 5px;
border-radius: 4px;
border: 1px solid #ccc;
}
.create-post textarea {
width: 100%;
height: 100px;
padding: 10px;
border-radius: 8px;
border: 1px solid #ccc;
resize: vertical;
}
.create-post input[type="file"] {
margin-top: 10px;
}
.create-post button {
margin-top: 10px;
padding: 10px 20px;
background-color: #4267B2;
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
}
/* Добавьте остальные стили по необходимости */
</style>
</head>
<body>
<!-- Навигационная панель -->
<div class="navbar">
<div class="logo" onclick="showSection('feed')">Хика Морi</div>
<div class="nav-icons">
<span onclick="showSection('feed')">🏠 Лента</span>
<span onclick="showSection('createPost')">➕ Создать</span>
<span onclick="showSection('animeWatch')">🎥 Просмотр</span>
<span onclick="showSection('community')">👥 Сообщества</span>
<span onclick="showSection('profile')">👤 Профиль</span>
<span onclick="logout()">🚪 Выйти</span>
</div>
</div>
<!-- Главная лента -->
<div id="feed" class="section active feed">
<h2>Лента Постов</h2>
<div id="postsContainer">
<!-- Посты будут загружаться здесь -->
</div>
</div>
<!-- Форма создания поста -->
<div id="createPost" class="section create-post">
<h2>Создать новый пост</h2>
<textarea id="postContent" placeholder="Что у вас на уме?"></textarea>
<input type="file" id="postImages" accept="image/*" multiple>
<button onclick="createPost()">Опубликовать</button>
</div>
<!-- Раздел просмотра аниме -->
<div id="animeWatch" class="section anime-watch">
<h2>Просмотр Аниме</h2>
<!-- Поиск аниме -->
<div class="search-bar">
<input type="text" id="searchAnime" placeholder="Поиск аниме...">
<button onclick="searchAnime()">🔍</button>
</div>
<!-- Список аниме -->
<div class="anime-list" id="animeList">
<!-- Пример аниме -->
<!-- Добавьте динамическую загрузку аниме через API -->
</div>
<!-- Видео-плеер -->
<div class="video-player" id="videoPlayer" style="display:none;">
<div class="player-content">
<button class="close-btn" onclick="closeVideoPlayer()">✖</button>
<video controls id="animeVideo" width="600">
<source src="" type="video/mp4">
Ваш браузер не поддерживает видео.
</video>
</div>
</div>
</div>
<!-- Раздел Сообщества -->
<div id="community" class="section community">
<div class="community-header">
<h2>Сообщества</h2>
<button onclick="openModal('createGroupModal')">Создать сообщество</button>
</div>
<div class="groups-list" id="groupsList">
<!-- Сообщества будут загружаться здесь -->
</div>
</div>
<!-- Форма создания сообщества (Модальное окно) -->
<div class="modal" id="createGroupModal" style="display:none;">
<div class="modal-content">
<h2>Создать новое сообщество</h2>
<input type="text" id="groupName" placeholder="Название сообщества">
<textarea id="groupDescription" placeholder="Описание сообщества"></textarea>
<div style="display: flex; justify-content: flex-end; width: 100%;">
<button onclick="createGroup()">Создать</button>
<button onclick="closeModal('createGroupModal')" class="cancel-btn">Отмена</button>
</div>
</div>
</div>
<!-- Секция группы -->
<div id="groupSection" class="section group-section">
<div class="group-header">
<h2 id="currentGroupName">Название Сообщества</h2>
<button id="createEventBtn" onclick="openModal('createEventModal')">Создать событие</button>
</div>
<div id="groupPosts" class="feed">
<!-- Посты внутри группы будут загружаться здесь -->
</div>
<h3>События</h3>
<div class="events-list" id="groupEvents">
<!-- События будут загружаться здесь -->
</div>
<!-- Карта события -->
<div id="eventMap" class="event-map">
<!-- Встроенная карта будет отображаться здесь -->
</div>
</div>
<!-- Форма создания события (Модальное окно) -->
<div class="modal" id="createEventModal" style="display:none;">
<div class="modal-content">
<h2>Создать событие</h2>
<input type="text" id="eventName" placeholder="Название события">
<textarea id="eventDescription" placeholder="Описание события"></textarea>
<input type="text" id="eventLocation" placeholder="Местоположение (адрес)">
<input type="date" id="eventDate" placeholder="Дата события">
<input type="time" id="eventTime" placeholder="Время события">
<div style="display: flex; justify-content: flex-end; width: 100%;">
<button onclick="createEvent()">Создать</button>
<button onclick="closeModal('createEventModal')" class="cancel-btn">Отмена</button>
</div>
</div>
</div>
<!-- Профиль пользователя -->
<div id="profile" class="section profile">
<h2>Профиль</h2>
<div class="profile-header">
<img id="profileAvatar" src="https://i.imgur.com/8Km9tLL.jpg" alt="Аватар">
<div>
<h2 id="profileName">Имя Пользователя</h2>
<p id="profileUsername">@nik_name</p>
</div>
</div>
<div class="profile-content">
<h3>О себе</h3>
<p id="profileBio">Любитель аниме, манги и всего японского! 🎌</p>
<h3>Мои посты</h3>
<div id="userPosts">
<!-- Пользовательские посты будут загружаться здесь -->
</div>
</div>
</div>
<!-- Видео-плеер -->
<div class="video-player" id="videoPlayer" style="display:none;">
<div class="player-content">
<button class="close-btn" onclick="closeVideoPlayer()">✖</button>
<video controls id="animeVideo" width="600">
<source src="" type="video/mp4">
Ваш браузер не поддерживает видео.
</video>
</div>
</div>
<!-- Форма входа (Модальное окно) -->
<div class="modal" id="loginModal" style="display:flex; justify-content:center; align-items:center;">
<div class="modal-content">
<h2>Вход</h2>
<input type="text" id="loginUsername" placeholder="Имя пользователя">
<input type="password" id="loginPassword" placeholder="Пароль">
<div style="display: flex; justify-content: flex-end; width: 100%;">
<button onclick="login()">Войти</button>
</div>
<p>Нет аккаунта? <a href="#" onclick="showRegister()">Зарегистрироваться</a></p>
</div>
</div>
<!-- Форма регистрации (Модальное окно) -->
<div class="modal" id="registerModal" style="display:none; justify-content:center; align-items:center;">
<div class="modal-content">
<h2>Регистрация</h2>
<input type="text" id="registerUsername" placeholder="Имя пользователя">
<input type="password" id="registerPassword" placeholder="Пароль">
<div style="display: flex; justify-content: flex-end; width: 100%;">
<button onclick="register()">Зарегистрироваться</button>
</div>
<p>Уже есть аккаунт? <a href="#" onclick="showLogin()">Войти</a></p>
</div>
</div>
<!-- Подключение Google Maps API -->
<script async
src="https://maps.googleapis.com/maps/api/js?key=YOUR_GOOGLE_MAPS_API_KEY&callback=initMap">
</script>
<!-- JavaScript для взаимодействия -->
<script>
let currentUser = null;
// Инициализация карты
let map;
let markers = [];
function initMap() {
map = new google.maps.Map(document.getElementById('eventMap'), {
center: { lat: 55.7558, lng: 37.6173 }, // Центр карты, например, Москва
zoom: 10
});
}
// Функция для переключения секций
function showSection(sectionId) {
const sections = document.querySelectorAll('.section');
sections.forEach(section => {
if (section.id === sectionId) {
section.classList.add('active');
} else {
section.classList.remove('active');
}
});
// Скрываем модальные окна при переключении секций
const modals = document.querySelectorAll('.modal');
modals.forEach(modal => {
modal.style.display = 'none';
});
// Если не группа, скрываем секцию группы
if (sectionId !== 'groupSection') {
document.getElementById('groupSection').style.display = 'none';
}
// Если секция - лента, загружаем посты
if (sectionId === 'feed') {
loadPosts();
}
// Если секция - сообщества, загружаем группы
if (sectionId === 'community') {
loadGroups();
}
// Если секция - профиль, загружаем профиль
if (sectionId === 'profile') {
loadProfile();
}
}
// Функции для открытия и закрытия модальных окон
function openModal(modalId) {
document.getElementById(modalId).style.display = 'flex';
}
function closeModal(modalId) {
document.getElementById(modalId).style.display = 'none';
}
// Регистрация пользователя
function register() {
const username = document.getElementById('registerUsername').value.trim();
const password = document.getElementById('registerPassword').value.trim();
if (username === "" || password === "") {
alert("Пожалуйста, заполните все поля.");
return;
}
fetch('http://localhost:5000/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
} else {
alert(data.message);
closeModal('registerModal');
openModal('loginModal');
}
})
.catch(error => {
console.error('Error:', error);
});
}
// Показать форму регистрации
function showRegister() {
closeModal('loginModal');
openModal('registerModal');
}
// Показать форму входа
function showLogin() {
closeModal('registerModal');
openModal('loginModal');
}
// Вход пользователя
function login() {
const username = document.getElementById('loginUsername').value.trim();
const password = document.getElementById('loginPassword').value.trim();
if (username === "" || password === "") {
alert("Пожалуйста, заполните все поля.");
return;
}
fetch('http://localhost:5000/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
} else {
currentUser = {
id: data.user_id,
username: data.username,
avatar: data.avatar
};
closeModal('loginModal');
showSection('feed');
}
})
.catch(error => {
console.error('Error:', error);
});
}
// Выход пользователя
function logout() {
currentUser = null;
showSection('loginModal');
}
// Функция для создания нового поста
function createPost() {
if (!currentUser) {
alert("Пожалуйста, войдите в систему, чтобы создавать посты.");
return;
}
const content = document.getElementById('postContent').value.trim();
const imagesInput = document.getElementById('postImages');
const imageFiles = imagesInput.files;
if (content === "" && imageFiles.length === 0) {
alert("Пожалуйста, введите текст поста или выберите изображения.");
return;
}
// Обработка изображений (предполагается, что изображение может быть загружено на сервер или хостинг)
// В данном примере мы будем использовать Base64 строки
if (imageFiles.length > 0) {
const reader = new FileReader();
reader.onload = function(e) {
const imageData = e.target.result; // Base64 строка изображения
fetch('http://localhost:5000/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
author_id: currentUser.id,
content: content,
image: imageData
})
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
} else {
alert(data.message);
document.getElementById('postContent').value = "";
imagesInput.value = "";
loadPosts();
showSection('feed');
}
})
.catch(error => {
console.error('Error:', error);
});
}
reader.readAsDataURL(imageFiles[0]);
} else {
fetch('http://localhost:5000/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
author_id: currentUser.id,
content: content,
image: null
})
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
} else {
alert(data.message);
document.getElementById('postContent').value = "";
imagesInput.value = "";
loadPosts();
showSection('feed');
}
})
.catch(error => {
console.error('Error:', error);
});
}
}
// Функция для загрузки постов
function loadPosts() {
fetch('http://localhost:5000/api/posts')
.then(response => response.json())
.then(data => {
const postsContainer = document.getElementById('postsContainer');
postsContainer.innerHTML = "";
data.forEach(post => {
const postElement = document.createElement('div');
postElement.className = 'post';
postElement.innerHTML = `
<div class="post-header">
<img src="${post.avatar}" alt="Аватар">
<strong>${post.author}</strong>
</div>
<div class="post-content">
${post.content ? `<p>${post.content}</p>` : ''}
${post.image ? `<img src="${post.image}" alt="Изображение поста">` : ''}
</div>
<div class="post-actions">
<span onclick="likePost(${post.id}, this)">👍 <span class="like-count">${post.likes}</span></span>
<span onclick="toggleComments(this, ${post.id})">💬 Комментарии</span>
</div>
<div class="comments-section" style="display: none; margin-top: 15px;">
<input type="text" placeholder="Написать комментарий..." class="comment-input" id="commentInput-${post.id}">
<button onclick="addComment(${post.id})">Отправить</button>
<div class="comments-list" id="commentsList-${post.id}"></div>
</div>
`;
postsContainer.appendChild(postElement);
});
})
.catch(error => {
console.error('Error:', error);
});
}
// Функция для лайка поста
function likePost(postId, element) {
fetch(`http://localhost:5000/api/posts/${postId}/like`, {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.likes !== undefined) {
element.querySelector('.like-count').textContent = data.likes;
}
})
.catch(error => {
console.error('Error:', error);
});
}
// Функция для переключения секции комментариев
function toggleComments(element, postId) {
const commentsSection = element.parentElement.nextElementSibling;
if (commentsSection.style.display === 'none') {
commentsSection.style.display = 'block';
loadComments(postId);
} else {
commentsSection.style.display = 'none';
}
}
// Функция для загрузки комментариев
function loadComments(postId) {
fetch(`http://localhost:5000/api/posts/${postId}/comments`)
.then(response => response.json())
.then(data => {
const commentsList = document.getElementById(`commentsList-${postId}`);
commentsList.innerHTML = "";
data.forEach(comment => {
const commentElement = document.createElement('div');
commentElement.style.padding = '10px 0';
commentElement.style.borderBottom = '1px solid #eee';
commentElement.innerHTML = `
<img src="${comment.avatar}" alt="Аватар" style="width:30px; height:30px; border-radius:50%; vertical-align: middle; margin-right:10px;">
<strong>${comment.user}</strong>
<p>${comment.content}</p>
`;
commentsList.appendChild(commentElement);
});
})
.catch(error => {
console.error('Error:', error);
});
}
// Функция для добавления комментария
function addComment(postId) {
if (!currentUser) {
alert("Пожалуйста, войдите в систему, чтобы оставлять комментарии.");
return;
}
const commentInput = document.getElementById(`commentInput-${postId}`);
const commentText = commentInput.value.trim();
if (commentText === "") {
alert("Пожалуйста, введите комментарий.");
return;
}
fetch(`http://localhost:5000/api/posts/${postId}/comments`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: currentUser.id,
content: commentText
})
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
} else {
alert(data.message);
commentInput.value = "";
loadComments(postId);
}
})
.catch(error => {
console.error('Error:', error);
});
}
// Функция для создания сообщества
function createGroup() {
const name = document.getElementById('groupName').value.trim();
const description = document.getElementById('groupDescription').value.trim();
if (name === "" || description === "") {
alert("Пожалуйста, заполните все поля.");
return;
}
fetch('http://localhost:5000/api/groups', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, description })
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
} else {
alert(data.message);
closeModal('createGroupModal');
loadGroups();
}
})
.catch(error => {
console.error('Error:', error);
});
}
// Функция для загрузки сообществ
function loadGroups() {
fetch('http://localhost:5000/api/groups')
.then(response => response.json())
.then(data => {
const groupsList = document.getElementById('groupsList');
groupsList.innerHTML = "";
data.forEach(group => {
const groupElement = document.createElement('div');
groupElement.className = 'group-item';
groupElement.style.padding = '15px';
groupElement.style.backgroundColor = '#fff';
groupElement.style.marginBottom = '10px';
groupElement.style.borderRadius = '8px';
groupElement.style.boxShadow = '0 2px 5px rgba(0,0,0,0.1)';
groupElement.style.cursor = 'pointer';
groupElement.onclick = () => openGroup(group.id);
groupElement.innerHTML = `
<h3>${group.name}</h3>
<p>${group.description}</p>
`;
groupsList.appendChild(groupElement);
});
})
.catch(error => {
console.error('Error:', error);
});
}
// Функция для открытия группы
function openGroup(groupId) {
fetch(`http://localhost:5000/api/groups/${groupId}`)
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
} else {
showSection('groupSection');
document.getElementById('currentGroupName').textContent = data.name;
loadGroupPosts(data.id);
loadGroupEvents(data.id);
}
})
.catch(error => {
console.error('Error:', error);
});
}
// Функция для загрузки постов группы
function loadGroupPosts(groupId) {
// В данном примере посты групп не реализованы на бэкенде
// Вам нужно добавить соответствующие маршруты и логику на бэкенде
// Ниже представлен пример заполнения dummy данных
const groupPostsContainer = document.getElementById('groupPosts');
groupPostsContainer.innerHTML = "<p>Посты группы будут загружены сюда.</p>";
}
// Функция для загрузки событий группы
function loadGroupEvents(groupId) {
// В данном примере события групп уже загружены при открытии группы
// Поэтому дополнительно загружать не нужно
// Однако, для полноты вы можете реализовать загрузку через отдельный маршрут
}
// Функция для создания события
function createEvent() {
const name = document.getElementById('eventName').value.trim();
const description = document.getElementById('eventDescription').value.trim();
const location = document.getElementById('eventLocation').value.trim();
const date = document.getElementById('eventDate').value;
const time = document.getElementById('eventTime').value;
if (name === "" || description === "" || location === "" || date === "" || time === "") {
alert("Пожалуйста, заполните все поля.");
return;
}
// Преобразование адреса в координаты (используя Geocoding API)
fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(location)}&key=YOUR_GOOGLE_MAPS_API_KEY`)
.then(response => response.json())
.then(data => {
if (data.status === "OK") {
const locationData = data.results[0].geometry.location;
const currentGroupName = document.getElementById('currentGroupName').textContent;
// Получение ID группы по ее названию
fetch(`http://localhost:5000/api/groups`)
.then(resp => resp.json())
.then(groups => {
const group = groups.find(g => g.name === currentGroupName);
if (!group) {
alert("Группа не найдена.");
return;
}
fetch(`http://localhost:5000/api/groups/${group.id}/events`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name,
description,
location,
latitude: locationData.lat,
longitude: locationData.lng,
date,
time
})
})
.then(resp => resp.json())
.then(eventData => {
if (eventData.error) {
alert(eventData.error);
} else {
alert(eventData.message);
closeModal('createEventModal');
loadGroupEvents(group.id);
addMarkerToMap({
name,
description,
latitude: locationData.lat,
longitude: locationData.lng,
date,
time
});
}
})
.catch(error => {
console.error('Error:', error);
});
});
} else {
alert("Не удалось найти указанное местоположение.");
}
})
.catch(error => {
console.error("Ошибка при получении координат:", error);
alert("Произошла ошибка при создании события.");
});
}
// Функция для добавления маркера на карту
function addMarkerToMap(event) {
const marker = new google.maps.Marker({
position: { lat: event.latitude, lng: event.longitude },
map: map,
title: event.name
});
const infoWindow = new google.maps.InfoWindow({
content: `<h3>${event.name}</h3><p>${event.description}</p><p>${event.date} ${event.time}</p>`
});
marker.addListener('click', () => {
infoWindow.open(map, marker);
});
markers.push(marker);
}
// Функция для отображения списка серий (может быть реализована через дополнительный API)
function showEpisodes(element) {
// Реализация зависит от вашего API или данных
}
// Функция для открытия видео-плеера
function openVideoPlayer(videoUrl) {
const videoPlayer = document.getElementById('videoPlayer');
const videoElement = document.getElementById('animeVideo');
videoElement.src = videoUrl;
videoPlayer.style.display = 'flex';
videoElement.play();
}
// Функция для закрытия видео-плеера
function closeVideoPlayer() {
const videoPlayer = document.getElementById('videoPlayer');
const videoElement = document.getElementById('animeVideo');
videoElement.pause();
videoElement.src = '';
videoPlayer.style.display = 'none';
}
// Функция для поиска аниме (может быть реализована через внешний API)
function searchAnime() {
const query = document.getElementById('searchAnime').value.toLowerCase();
const animeItems = document.querySelectorAll('.anime-item');
animeItems.forEach(item => {
const title = item.querySelector('.anime-info h3').textContent.toLowerCase();
if (title.includes(query)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
}
// Функция для загрузки профиля пользователя
function loadProfile() {
if (!currentUser) {
alert("Пожалуйста, войдите в систему, чтобы просмотреть профиль.");
showSection('loginModal');
return;
}
fetch(`http://localhost:5000/api/users/${currentUser.id}`)
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
} else {
document.getElementById('profileName').textContent = data.username;
document.getElementById('profileUsername').textContent = `@${data.username}`;
document.getElementById('profileAvatar').src = data.avatar;
document.getElementById('userPosts').innerHTML = "";
data.posts.forEach(post => {
const postElement = document.createElement('div');
postElement.className = 'post';
postElement.innerHTML = `
<div class="post-header">
<img src="${currentUser.avatar}" alt="Аватар">
<strong>Вы</strong>
</div>
<div class="post-content">
${post.content ? `<p>${post.content}</p>` : ''}
${post.image ? `<img src="${post.image}" alt="Изображение поста">` : ''}
</div>
<div class="post-actions">
<span onclick="likePost(${post.id}, this)">👍 <span class="like-count">${post.likes}</span></span>
<span onclick="toggleComments(this, ${post.id})">💬 Комментарии</span>
</div>
<div class="comments-section" style="display: none; margin-top: 15px;">
<input type="text" placeholder="Написать комментарий..." class="comment-input" id="commentInput-${post.id}">
<button onclick="addComment(${post.id})">Отправить</button>
<div class="comments-list" id="commentsList-${post.id}"></div>
</div>
`;
document.getElementById('userPosts').appendChild(postElement);
});
}
})
.catch(error => {
console.error('Error:', error);
});
}
// Функция для загрузки профиля при загрузке страницы
document.addEventListener('DOMContentLoaded', () => {
if (!currentUser) {
showSection('loginModal');
} else {
showSection('feed');
}
});
</script>
</body>
</html>
```
### Объяснение Кода Фронтенда:
- **Аутентификация:** Добавлены модальные окна для входа и регистрации пользователей. После успешного входа сохраняется информация о текущем пользователе в переменной `currentUser`.
- **Создание и Загрузка Постов:** Посты загружаются с сервера через API и отображаются на странице. Создание новых постов отправляется на сервер.
- **Лайки и Комментарии:** Лайки и комментарии обрабатываются через API.
- **Сообщества:** Возможность создавать сообщества и просматривать их. (Дополнительная логика может потребоваться для полной функциональности групповых постов и механизмов безопасности.)
- **Профиль Пользователя:** Отображает информацию о текущем пользователе и его посты.
- **Видео-плеер и Аниме:** Интерактивные элементы могут быть расширены с использованием дополнительных API для аниме.
## Шаг 4: Запуск Приложения
1. **Запустите Бэкенд:**
Убедитесь, что вы находитесь в той же директории, где находится `app.py`, и выполните команду:
```bash
python app.py
```
Бэкенд будет запущен на [http://localhost:5000](http://localhost:5000).
2. **Откройте Фронтенд:**
Просто откройте файл `index.html` в вашем браузере. Для полноценной работы может потребоваться запуск локального сервера для фронтенда. Вы можете использовать простой сервер Python:
```bash
python -m http.server 8000
```
Затем откройте [http://localhost:8000](http://localhost:8000) в браузере.
## Дополнительные Улучшения
1. **Безопасность:**
- **Хеширование Паролей:** Никогда не храните пароли в открытом виде. Используйте библиотеки, такие как `werkzeug.security`, для хеширования паролей.
- **JWT Аутентификация:** Рассмотрите возможность использования JWT для управления сессиями пользователей.
- **Валидация и Санитаризация Данных:** Всегда проверяйте и очищайте входящие данные для предотвращения атак типа SQL Injection и XSS.
2. **Оптимизация Хранения Изображений:**
- Вместо хранения изображений как Base64 строк, рекомендуется загружать их на сервер или использовать облачные хранилища (например, AWS S3) и сохранять ссылки в базе данных.
3. **Расширение Функциональности:**
- **Редактирование и Удаление Постов/Комментов.**
- **Просмотр и Управление Сообществами.**
- **Рейтинги и Избранное для Аниме.**
- **Поиск и Фильтрация Контента.**
4. **Использование Фронтенд Фреймворков:**
- Рассмотрите возможность использования фреймворков, таких как React или Vue.js, для более структурированного и мощного фронтенда.
## Заключение
Представленный выше пример демонстрирует базовую интеграцию фронтенда с бэкендом на Flask с использованием SQLite. В зависимости от ваших требований, вы можете расширить функциональность, улучшить безопасность и оптимизировать производительность приложения. Не забудьте заменить `YOUR_GOOGLE_MAPS_API_KEY` на ваш реальный ключ API для работы с картами Google.