Files
marketing/Техническое задание Фронтенд-приложение для AI-платформы.md
Administrator b146ec1f6f Update 10 files
- /Техническое задание №1: Основа проекта, модели данных и репозитории.md
- /Техническое задание №2: Базовое управление кампаниями и каналами (CRUD).md
- /Техническое задание №3: Модуль AI-генерации контента,md
- /Техническое задание: Модуль SMM-автоматизации.md
- /Техническое задание: Фронтенд-приложение для AI-платформы.md
- /Техническое задание №1 Основа проекта, модели данных и репозитории.md
- /Техническое задание №2 Базовое управление кампаниями и каналами (CRUD).md
- /Техническое задание №3 Модуль AI-генерации контента,md
- /Техническое задание Модуль SMM-автоматизации.md
- /Техническое задание Фронтенд-приложение для AI-платформы.md
2026-01-22 07:37:56 +00:00

465 lines
16 KiB
Markdown

# Руководство по API для фронтенда: Генерация и история отчётов
## Базовый URL
```
/api/parser/report
```
## 1. Генерация отчёта
### POST /api/parser/report/generate
Создаёт новый отчёт и возвращает файл для скачивания.
**Заголовки:**
```
Content-Type: application/json
```
**Тело запроса:**
```json
{
"reportTitle": "Еженедельный анализ новостного фона",
"authorName": "Имя Аналитика",
"companyName": "Название Компании Клиента",
"startDate": "2025-09-10T00:00:00",
"endDate": "2025-09-17T23:59:59",
"format": "PDF"
}
```
**Параметры:**
- `reportTitle` (string, обязательный) - заголовок отчёта
- `authorName` (string, обязательный) - имя автора
- `companyName` (string, обязательный) - название компании
- `startDate` (string, обязательный) - дата начала периода в формате ISO 8601
- `endDate` (string, обязательный) - дата окончания периода в формате ISO 8601
- `format` (string, обязательный) - формат файла: "PDF" или "DOCX"
**Успешный ответ (200 OK):**
```
Content-Type: application/pdf (или application/vnd.openxmlformats-officedocument.wordprocessingml.document)
Content-Disposition: attachment; filename="report_2025-09-17-1234567890.pdf"
[бинарные данные файла]
```
**Пример на JavaScript:**
```javascript
async function generateReport(reportData) {
try {
const response = await fetch('/api/parser/report/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(reportData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Получаем имя файла из заголовка
const contentDisposition = response.headers.get('Content-Disposition');
const filename = contentDisposition ? contentDisposition.split('filename=')[1].replace(/"/g, '') : 'report.pdf';
// Создаём blob и скачиваем файл
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.error('Ошибка при генерации отчёта:', error);
}
}
// Использование
const reportData = {
reportTitle: 'Еженедельный анализ новостного фона',
authorName: 'Иван Петров',
companyName: 'ООО Рога и Копыта',
startDate: '2025-01-01T00:00:00',
endDate: '2025-01-07T23:59:59',
format: 'PDF'
};
generateReport(reportData);
```
## 2. Получение истории отчётов
### GET /api/parser/report/history
Возвращает пагинированный список всех сгенерированных отчётов.
**Параметры запроса:**
- `page` (int, опциональный, по умолчанию: 0) - номер страницы (начиная с 0)
- `size` (int, опциональный, по умолчанию: 20) - количество элементов на странице
- `sort` (string, опциональный, по умолчанию: "createdAt,desc") - поле и направление сортировки
**Пример запроса:**
```
GET /api/parser/report/history?page=0&size=10&sort=createdAt,desc
```
**Успешный ответ (200 OK):**
```json
{
"content": [
{
"id": "507f1f77bcf86cd799439011",
"reportTitle": "Еженедельный анализ новостного фона",
"authorName": "Имя Аналитика",
"companyName": "Название Компании Клиента",
"startDate": "2025-09-10T00:00:00",
"endDate": "2025-09-17T23:59:59",
"format": "PDF",
"filename": "report_2025-09-17-1234567890.pdf",
"fileSize": 1234567,
"createdAt": "2025-09-17T21:11:58"
}
],
"pageable": {
"sort": {
"sorted": true,
"unsorted": false,
"empty": false
},
"pageNumber": 0,
"pageSize": 20,
"offset": 0,
"paged": true,
"unpaged": false
},
"totalElements": 1,
"totalPages": 1,
"last": true,
"first": true,
"numberOfElements": 1,
"size": 20,
"number": 0,
"sort": {
"sorted": true,
"unsorted": false,
"empty": false
},
"empty": false
}
```
**Пример на JavaScript:**
```javascript
async function getReportHistory(page = 0, size = 20, sort = 'createdAt,desc') {
try {
const params = new URLSearchParams({
page: page.toString(),
size: size.toString(),
sort: sort
});
const response = await fetch(`/api/parser/report/history?${params}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Ошибка при получении истории отчётов:', error);
throw error;
}
}
// Использование
getReportHistory(0, 10, 'createdAt,desc')
.then((data) => {
console.log('Отчёты:', data.content);
console.log('Всего элементов:', data.totalElements);
console.log('Всего страниц:', data.totalPages);
})
.catch((error) => {
console.error('Ошибка:', error);
});
```
## 3. Скачивание отчёта из истории
### GET /api/parser/report/history/{id}
Скачивает конкретный отчёт по его ID.
**Параметры:**
- `id` (string, обязательный) - ID отчёта из истории
**Успешный ответ (200 OK):**
```
Content-Type: application/pdf (или application/vnd.openxmlformats-officedocument.wordprocessingml.document)
Content-Disposition: attachment; filename="report_2025-09-17-1234567890.pdf"
[бинарные данные файла]
```
**Ошибки:**
- `404 Not Found` - отчёт с указанным ID не найден
- `500 Internal Server Error` - ошибка при чтении файла
**Пример на JavaScript:**
```javascript
async function downloadReport(reportId) {
try {
const response = await fetch(`/api/parser/report/history/${reportId}`);
if (response.status === 404) {
throw new Error('Отчёт не найден');
}
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Получаем имя файла из заголовка
const contentDisposition = response.headers.get('Content-Disposition');
const filename = contentDisposition ? contentDisposition.split('filename=')[1].replace(/"/g, '') : 'report.pdf';
// Создаём blob и скачиваем файл
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.error('Ошибка при скачивании отчёта:', error);
throw error;
}
}
// Использование
downloadReport('507f1f77bcf86cd799439011')
.then(() => console.log('Отчёт скачан'))
.catch((error) => console.error('Ошибка:', error));
```
## 4. Полный пример React компонента
```jsx
import React, { useState, useEffect } from 'react';
const ReportManager = () => {
const [reports, setReports] = useState([]);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(0);
const [totalPages, setTotalPages] = useState(0);
const [formData, setFormData] = useState({
reportTitle: '',
authorName: '',
companyName: '',
startDate: '',
endDate: '',
format: 'PDF'
});
// Загрузка истории отчётов
const loadReports = async (page = 0) => {
setLoading(true);
try {
const data = await getReportHistory(page, 10);
setReports(data.content);
setTotalPages(data.totalPages);
setCurrentPage(page);
} catch (error) {
console.error('Ошибка загрузки истории:', error);
} finally {
setLoading(false);
}
};
// Генерация нового отчёта
const handleGenerate = async (e) => {
e.preventDefault();
setLoading(true);
try {
await generateReport(formData);
// Перезагружаем историю после генерации
await loadReports(currentPage);
} catch (error) {
console.error('Ошибка генерации:', error);
} finally {
setLoading(false);
}
};
// Скачивание отчёта
const handleDownload = async (reportId) => {
try {
await downloadReport(reportId);
} catch (error) {
console.error('Ошибка скачивания:', error);
}
};
useEffect(() => {
loadReports();
}, []);
return (
<div className="report-manager">
<h2>Генерация отчётов</h2>
{/* Форма генерации */}
<form onSubmit={handleGenerate} className="report-form">
<div>
<label>Заголовок отчёта:</label>
<input type="text" value={formData.reportTitle} onChange={(e) => setFormData({ ...formData, reportTitle: e.target.value })} required />
</div>
<div>
<label>Автор:</label>
<input type="text" value={formData.authorName} onChange={(e) => setFormData({ ...formData, authorName: e.target.value })} required />
</div>
<div>
<label>Компания:</label>
<input type="text" value={formData.companyName} onChange={(e) => setFormData({ ...formData, companyName: e.target.value })} required />
</div>
<div>
<label>Дата начала:</label>
<input type="datetime-local" value={formData.startDate} onChange={(e) => setFormData({ ...formData, startDate: e.target.value })} required />
</div>
<div>
<label>Дата окончания:</label>
<input type="datetime-local" value={formData.endDate} onChange={(e) => setFormData({ ...formData, endDate: e.target.value })} required />
</div>
<div>
<label>Формат:</label>
<select value={formData.format} onChange={(e) => setFormData({ ...formData, format: e.target.value })}>
<option value="PDF">PDF</option>
<option value="DOCX">DOCX</option>
</select>
</div>
<button type="submit" disabled={loading}>
{loading ? 'Генерация...' : 'Сгенерировать отчёт'}
</button>
</form>
{/* История отчётов */}
<h2>История отчётов</h2>
{loading ? (
<div>Загрузка...</div>
) : (
<>
<div className="reports-list">
{reports.map((report) => (
<div key={report.id} className="report-item">
<h3>{report.reportTitle}</h3>
<p>Автор: {report.authorName}</p>
<p>Компания: {report.companyName}</p>
<p>
Период: {new Date(report.startDate).toLocaleDateString()} - {new Date(report.endDate).toLocaleDateString()}
</p>
<p>Формат: {report.format}</p>
<p>Размер: {(report.fileSize / 1024).toFixed(1)} KB</p>
<p>Создан: {new Date(report.createdAt).toLocaleString()}</p>
<button onClick={() => handleDownload(report.id)}>Скачать</button>
</div>
))}
</div>
{/* Пагинация */}
<div className="pagination">
<button onClick={() => loadReports(currentPage - 1)} disabled={currentPage === 0}>
Предыдущая
</button>
<span>
Страница {currentPage + 1} из {totalPages}
</span>
<button onClick={() => loadReports(currentPage + 1)} disabled={currentPage >= totalPages - 1}>
Следующая
</button>
</div>
</>
)}
</div>
);
};
export default ReportManager;
```
## 5. Обработка ошибок
### Типичные ошибки и их обработка:
1. **400 Bad Request** - неверные данные в запросе
2. **404 Not Found** - отчёт не найден (при скачивании)
3. **500 Internal Server Error** - внутренняя ошибка сервера
```javascript
async function handleApiCall(apiFunction, ...args) {
try {
return await apiFunction(...args);
} catch (error) {
if (error.message.includes('404')) {
alert('Отчёт не найден');
} else if (error.message.includes('400')) {
alert('Неверные данные запроса');
} else {
alert('Произошла ошибка: ' + error.message);
}
throw error;
}
}
```
## 6. Форматы дат
Все даты должны передаваться в формате ISO 8601:
- `2025-01-01T00:00:00` - 1 января 2025, 00:00:00
- `2025-01-01T23:59:59` - 1 января 2025, 23:59:59
Для HTML input[type="datetime-local"] используйте формат:
- `2025-01-01T00:00` (без секунд и часового пояса)
## 7. Размеры файлов
Размеры файлов возвращаются в байтах. Для отображения пользователю конвертируйте:
```javascript
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
```