add algam

This commit is contained in:
2025-09-22 11:54:42 +05:00
parent 3c418a97b4
commit 5b9c55a4dc
34 changed files with 4700 additions and 426 deletions

View File

@@ -0,0 +1,304 @@
// API конфигурация
const API_BASE_URL = 'http://localhost:8000/api/v1';
// Класс для работы с API
class API {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
// Общий метод для HTTP запросов
async request(endpoint, options = {}) {
const url = `${this.baseUrl}${endpoint}`;
const config = {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
};
try {
const response = await fetch(url, config);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || `HTTP error! status: ${response.status}`);
}
// Для некоторых запросов может быть пустой ответ
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
}
return response;
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
// GET запрос
async get(endpoint, params = {}) {
const queryString = new URLSearchParams(params).toString();
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
return this.request(url);
}
// POST запрос
async post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
}
// PUT запрос
async put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
}
// DELETE запрос
async delete(endpoint) {
return this.request(endpoint, {
method: 'DELETE'
});
}
// Загрузка файла
async uploadFile(endpoint, formData) {
return this.request(endpoint, {
method: 'POST',
body: formData,
headers: {} // Убираем Content-Type для FormData
});
}
// === Методы для работы с тестами ===
async getTests(params = {}) {
return this.get('/tests', params);
}
async getTestById(id) {
return this.get(`/tests/${id}`);
}
async createTest(testData) {
return this.post('/tests', testData);
}
async updateTest(id, testData) {
return this.put(`/tests/${id}`, testData);
}
async deleteTest(id) {
return this.delete(`/tests/${id}`);
}
// === Методы для работы с категориями ===
async getCategories() {
return this.get('/test-categories');
}
async getCategoryById(id) {
return this.get(`/test-categories/${id}`);
}
async createCategory(categoryData) {
return this.post('/test-categories', categoryData);
}
async updateCategory(id, categoryData) {
return this.put(`/test-categories/${id}`, categoryData);
}
async deleteCategory(id) {
return this.delete(`/test-categories/${id}`);
}
// === Методы для работы с вопросами ===
async getQuestionsByTest(testId) {
return this.get(`/questions/test/${testId}`);
}
async getQuestionById(id) {
return this.get(`/questions/${id}`);
}
async createQuestion(questionData) {
return this.post('/questions', questionData);
}
async updateQuestion(id, questionData) {
return this.put(`/questions/${id}`, questionData);
}
async deleteQuestion(id) {
return this.delete(`/questions/${id}`);
}
// === Методы для работы с вариантами ответов ===
async getAnswerOptionsByQuestion(questionId) {
return this.get(`/answer-options/question/${questionId}`);
}
async createAnswerOption(optionData) {
return this.post('/answer-options', optionData);
}
async updateAnswerOption(id, optionData) {
return this.put(`/answer-options/${id}`, optionData);
}
async deleteAnswerOption(id) {
return this.delete(`/answer-options/${id}`);
}
// === Методы для работы с изображениями ===
async uploadImage(file, altText = null) {
const formData = new FormData();
formData.append('file', file);
if (altText) {
formData.append('alt_text', altText);
}
return this.uploadFile('/images/upload', formData);
}
async getImage(imageId) {
return this.request(`/images/${imageId}`, {
headers: {}
});
}
async deleteImage(id) {
return this.delete(`/images/${id}`);
}
// === Служебные методы ===
async checkHealth() {
return this.get('/health');
}
}
// Создаем глобальный экземпляр API
const api = new API(API_BASE_URL);
// Утилиты для работы с API
// Показать уведомление об ошибке
function showApiError(error, customMessage = null) {
const message = customMessage || error.message || 'Произошла ошибка при обращении к API';
showNotification(message, 'danger');
console.error('API Error:', error);
}
// Показать уведомление об успехе
function showApiSuccess(message) {
showNotification(message, 'success');
}
// Показать загрузку
function showLoading(show = true) {
const spinner = document.getElementById('loadingSpinner');
if (spinner) {
spinner.style.display = show ? 'block' : 'none';
}
}
// Функция для показа уведомлений
function showNotification(message, type = 'info', duration = 5000) {
const notificationsContainer = document.getElementById('notifications');
if (!notificationsContainer) return;
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
notificationsContainer.appendChild(alertDiv);
// Автоматически удаляем уведомление через заданное время
setTimeout(() => {
if (alertDiv && alertDiv.parentNode) {
alertDiv.remove();
}
}, duration);
}
// Форматирование даты
function formatDate(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleString('ru-RU', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
}
// Получение URL изображения
function getImageUrl(imageId) {
return `${API_BASE_URL}/images/${imageId}`;
}
// Валидация данных формы
function validateForm(formId) {
const form = document.getElementById(formId);
if (!form) return false;
const requiredFields = form.querySelectorAll('[required]');
let isValid = true;
requiredFields.forEach(field => {
field.classList.remove('is-invalid');
if (!field.value.trim()) {
field.classList.add('is-invalid');
isValid = false;
}
});
return isValid;
}
// Очистка формы
function clearForm(formId) {
const form = document.getElementById(formId);
if (!form) return;
form.reset();
// Удаляем классы валидации
const fields = form.querySelectorAll('.is-invalid');
fields.forEach(field => field.classList.remove('is-invalid'));
// Очищаем скрытые поля
const hiddenFields = form.querySelectorAll('input[type="hidden"]');
hiddenFields.forEach(field => field.value = '');
}
// Экспорт для использования в других скриптах
window.api = api;
window.showApiError = showApiError;
window.showApiSuccess = showApiSuccess;
window.showLoading = showLoading;
window.showNotification = showNotification;
window.formatDate = formatDate;
window.getImageUrl = getImageUrl;
window.validateForm = validateForm;
window.clearForm = clearForm;