<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Экспорт PNG — Домашняя бухгалтерия</title>
<style>
body { background: #121212; color: #fff; font-family: 'Segoe UI', Arial, sans-serif; padding: 20px; }
.toolbar { margin-bottom: 20px; display: flex; gap: 12px; }
button { background: #2563eb; color: #fff; border: none; padding: 12px 20px; border-radius: 8px; cursor: pointer; font-size: 15px; font-weight: 600; }
button:hover { background: #1d4ed8; }
canvas { background: #ffffff; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); display: block; margin-bottom: 30px; max-width: 100%; }
</style>
</head>
<body>
<div class="toolbar">
<button onclick="downloadCanvas('archCanvas', 'Архитектура_проекта.png')">⬇️ Скачать Архитектура_проекта.png</button>
<button onclick="downloadCanvas('uiCanvas', 'Макеты_интерфейса.png')">⬇️ Скачать Макеты_интерфейса.png</button>
</div>
<h3>1. Схема архитектуры проекта (Этап 5)</h3>
<canvas id="archCanvas" width="950" height="680"></canvas>
<h3>2. Макеты интерфейса 375×812 с подписями элементов (Этап 7)</h3>
<canvas id="uiCanvas" width="1350" height="900"></canvas>
<script>
function roundRect(ctx, x, y, w, h, r, fill, stroke) {
ctx.beginPath();
ctx.roundRect(x, y, w, h, r);
if (fill) { ctx.fillStyle = fill; ctx.fill(); }
if (stroke) { ctx.strokeStyle = stroke; ctx.lineWidth = 2; ctx.stroke(); }
}
function drawArrow(ctx, x1, y1, x2, y2, label) {
ctx.strokeStyle = '#334155';
ctx.fillStyle = '#334155';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
const angle = Math.atan2(y2 - y1, x2 - x1);
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x2 - 10 * Math.cos(angle - Math.PI / 6), y2 - 10 * Math.sin(angle - Math.PI / 6));
ctx.lineTo(x2 - 10 * Math.cos(angle + Math.PI / 6), y2 - 10 * Math.sin(angle + Math.PI / 6));
ctx.closePath();
ctx.fill();
if (label) {
ctx.font = 'italic 13px Segoe UI';
ctx.fillStyle = '#0f172a';
ctx.fillText(label, (x1 + x2) / 2 + 10, (y1 + y2) / 2 + 4);
}
}
// 1. Отрисовка архитектуры
const arch = document.getElementById('archCanvas').getContext('2d');
arch.fillStyle = '#ffffff';
arch.fillRect(0, 0, 950, 680);
arch.fillStyle = '#0f172a';
arch.font = 'bold 20px Segoe UI';
arch.fillText('Архитектура приложения «Домашняя бухгалтерия»', 220, 35);
// Блоки
roundRect(arch, 120, 60, 360, 95, 10, '#dbeafe', '#3b82f6');
arch.fillStyle = '#1e3a8a'; arch.font = 'bold 16px Segoe UI';
arch.fillText('UI-модуль (Presentation Layer)', 175, 90);
arch.fillStyle = '#1e293b'; arch.font = '14px Segoe UI';
arch.fillText('Экраны: Главная | Добавить | История | Отчёты', 145, 120);
arch.fillText('Ввод данных и отображение состояния', 175, 142);
roundRect(arch, 120, 215, 360, 115, 10, '#dcfce7', '#22c55e');
arch.fillStyle = '#14532d'; arch.font = 'bold 16px Segoe UI';
arch.fillText('Модуль логики (Business Logic)', 175, 245);
arch.fillStyle = '#1e293b'; arch.font = '14px Segoe UI';
arch.fillText('Класс FinanceManager:', 145, 270);
arch.fillText('• расчёт баланса (get_balance)', 145, 292);
arch.fillText('• фильтрация и отчёты по категориям', 145, 314);
roundRect(arch, 580, 215, 320, 115, 10, '#fef9c3', '#eab308');
arch.fillStyle = '#713f12'; arch.font = 'bold 16px Segoe UI';
arch.fillText('Модуль моделей (Domain Models)', 605, 245);
arch.fillStyle = '#1e293b'; arch.font = '14px Segoe UI';
arch.fillText('• Класс Operation (id, amount, type,', 600, 272);
arch.fillText(' category_id, date, comment)', 600, 292);
arch.fillText('• Класс Category (id, name, icon)', 600, 314);
roundRect(arch, 120, 390, 360, 105, 10, '#ffedd5', '#f97316');
arch.fillStyle = '#7c2d12'; arch.font = 'bold 16px Segoe UI';
arch.fillText('Модуль данных (Data Access Layer)', 160, 420);
arch.fillStyle = '#1e293b'; arch.font = '14px Segoe UI';
arch.fillText('• DatabaseConnection (Singleton)', 145, 448);
arch.fillText('• OperationRepository / CategoryRepository', 145, 472);
roundRect(arch, 120, 555, 360, 90, 10, '#f1f5f9', '#64748b');
arch.fillStyle = '#0f172a'; arch.font = 'bold 16px Segoe UI';
arch.fillText('Локальная БД SQLite (finance.db)', 170, 588);
arch.font = '14px Segoe UI';
arch.fillText('Таблицы: operations, categories', 195, 618);
// Стрелки
drawArrow(arch, 300, 155, 300, 215, 'События UI / Данные операций');
drawArrow(arch, 480, 272, 580, 272, 'Объекты моделей');
drawArrow(arch, 300, 330, 300, 390, 'Вызовы методов репозитория');
drawArrow(arch, 300, 495, 300, 555, 'SQL-запросы (SELECT / INSERT)');
drawArrow(arch, 480, 442, 740, 330, 'Маппинг строк БД в объекты');
// 2. Отрисовка макетов 375x812
const ui = document.getElementById('uiCanvas').getContext('2d');
ui.fillStyle = '#f8fafc';
ui.fillRect(0, 0, 1350, 900);
function drawPhone(x, y, title, screenNum) {
roundRect(ui, x, y, 375, 812, 28, '#ffffff', '#1e293b');
// Header
roundRect(ui, x, y, 375, 64, 28, '#1e293b', '#1e293b');
ui.fillRect(x, y + 32, 375, 32);
ui.fillStyle = '#ffffff';
ui.font = 'bold 17px Segoe UI';
ui.fillText(title, x + 24, y + 40);
// Подпись экрана сверху
ui.fillStyle = '#0f172a';
ui.font = 'bold 16px Segoe UI';
ui.fillText('Экран ' + screenNum + ' (375×812 px)', x + 95, y - 14);
}
// Экран 1: Главная
drawPhone(40, 50, 'Домашняя бухгалтерия', '1: Главная');
roundRect(ui, 64, 140, 327, 90, 12, '#eff6ff', '#93c5fd');
ui.fillStyle = '#64748b'; ui.font = '13px Segoe UI';
ui.fillText('[Label: Текущий баланс]', 78, 165);
ui.fillStyle = '#0f172a'; ui.font = 'bold 26px Segoe UI';
ui.fillText('Баланс: 15 000 ₽', 78, 205);
roundRect(ui, 64, 250, 327, 52, 10, '#dcfce7', '#22c55e');
ui.fillStyle = '#166534'; ui.font = 'bold 16px Segoe UI';
ui.fillText('+ Добавить доход [Button]', 110, 282);
roundRect(ui, 64, 316, 327, 52, 10, '#fee2e2', '#ef4444');
ui.fillStyle = '#991b1b'; ui.font = 'bold 16px Segoe UI';
ui.fillText('- Добавить расход [Button]', 110, 348);
ui.fillStyle = '#0f172a'; ui.font = 'bold 16px Segoe UI';
ui.fillText('Последние операции [ListView]:', 64, 410);
roundRect(ui, 64, 425, 327, 55, 8, '#f8fafc', '#cbd5e1');
ui.fillStyle = '#1e293b'; ui.font = '15px Segoe UI';
ui.fillText('🛒 Продукты', 78, 458);
ui.fillStyle = '#dc2626'; ui.font = 'bold 15px Segoe UI';
ui.fillText('- 1 500 ₽', 305, 458);
roundRect(ui, 64, 490, 327, 55, 8, '#f8fafc', '#cbd5e1');
ui.fillStyle = '#1e293b'; ui.font = '15px Segoe UI';
ui.fillText('💼 Зарплата', 78, 523);
ui.fillStyle = '#16a34a'; ui.font = 'bold 15px Segoe UI';
ui.fillText('+ 30 000 ₽', 295, 523);
roundRect(ui, 40, 798, 375, 64, 16, '#f1f5f9', '#cbd5e1');
ui.fillStyle = '#1e293b'; ui.font = '14px Segoe UI';
ui.fillText('🏠 Главная 📊 Отчёты ⚙️ Настройки [NavBar]', 60, 835);
// Экран 2: Добавить операцию
drawPhone(485, 50, '← Добавить операцию', '2: Добавить операцию');
ui.fillStyle = '#0f172a'; ui.font = '15px Segoe UI';
ui.fillText('Тип операции [RadioGroup]:', 510, 150);
ui.font = 'bold 15px Segoe UI';
ui.fillText('○ Доход ● Расход', 510, 178);
ui.font = '14px Segoe UI'; ui.fillStyle = '#475569';
ui.fillText('Сумма операции [TextInput / Number]:', 510, 225);
roundRect(ui, 510, 235, 325, 46, 8, '#ffffff', '#94a3b8');
ui.fillStyle = '#0f172a'; ui.font = '15px Segoe UI';
ui.fillText('1500.00 ₽', 525, 263);
ui.font = '14px Segoe UI'; ui.fillStyle = '#475569';
ui.fillText('Категория [Dropdown Select]:', 510, 315);
roundRect(ui, 510, 325, 325, 46, 8, '#ffffff', '#94a3b8');
ui.fillStyle = '#0f172a'; ui.font = '15px Segoe UI';
ui.fillText('▼ Выбрать категорию (Продукты)', 525, 353);
ui.font = '14px Segoe UI'; ui.fillStyle = '#475569';
ui.fillText('Дата операции [DatePicker]:', 510, 405);
roundRect(ui, 510, 415, 325, 46, 8, '#ffffff', '#94a3b8');
ui.fillStyle = '#0f172a'; ui.font = '15px Segoe UI';
ui.fillText('📅 14.10.2026', 525, 443);
ui.font = '14px Segoe UI'; ui.fillStyle = '#475569';
ui.fillText('Комментарий [TextInput]:', 510, 495);
roundRect(ui, 510, 505, 325, 65, 8, '#ffffff', '#94a3b8');
ui.fillStyle = '#64748b'; ui.font = '14px Segoe UI';
ui.fillText('Покупка продуктов на неделю...', 525, 535);
roundRect(ui, 510, 610, 325, 54, 10, '#2563eb', '#1d4ed8');
ui.fillStyle = '#ffffff'; ui.font = 'bold 16px Segoe UI';
ui.fillText('Сохранить [Submit Button]', 575, 642);
// Экран 3: История
drawPhone(930, 50, '← История операций', '3: История');
ui.font = '14px Segoe UI'; ui.fillStyle = '#475569';
ui.fillText('Фильтр по категориям и дате [Dropdown]:', 955, 145);
roundRect(ui, 955, 155, 325, 44, 8, '#f8fafc', '#94a3b8');
ui.fillStyle = '#0f172a'; ui.font = '15px Segoe UI';
ui.fillText('▼ Фильтр: Все категории', 970, 182);
ui.fillStyle = '#475569'; ui.font = 'bold 14px Segoe UI';
ui.fillText('14.10.2026 [Group Header]', 955, 235);
roundRect(ui, 955, 245, 325, 55, 8, '#f8fafc', '#cbd5e1');
ui.fillStyle = '#0f172a'; ui.font = '15px Segoe UI';
ui.fillText('• Продукты — 1 500 ₽', 970, 278);
roundRect(ui, 955, 310, 325, 55, 8, '#f8fafc', '#cbd5e1');
ui.fillStyle = '#0f172a'; ui.font = '15px Segoe UI';
ui.fillText('• Зарплата + 30 000 ₽', 970, 343);
ui.fillStyle = '#475569'; ui.font = 'bold 14px Segoe UI';
ui.fillText('13.10.2026 [Group Header]', 955, 400);
roundRect(ui, 955, 410, 325, 55, 8, '#f8fafc', '#cbd5e1');
ui.fillStyle = '#0f172a'; ui.font = '15px Segoe UI';
ui.fillText('• Транспорт — 300 ₽', 970, 443);
roundRect(ui, 930, 798, 375, 64, 16, '#f1f5f9', '#cbd5e1');
ui.fillStyle = '#1e293b'; ui.font = '14px Segoe UI';
ui.fillText('🏠 Главная 📊 Отчёты ⚙️ Настройки [NavBar]', 950, 835);
function downloadCanvas(id, filename) {
const link = document.createElement('a');
link.download = filename;
link.href = document.getElementById(id).toDataURL('image/png');
link.click();
}
</script>
</body>
</html>