304 lines
8.4 KiB
JavaScript
304 lines
8.4 KiB
JavaScript
// 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; |