Rust + Axum: Добавляем поддержку мобильных устройств в Kanban-доску (тач-перетаскивание и адаптивный дизайн)
Продолжаем работать над Kanban-доской, созданной в предыдущей статье. В этот раз добавим поддержку мобильных устройств: адаптивный дизайн и тач-перетаскивание задач с помощью библиотеки SortableJS.
Добавляем поддержку мобильных устройств и тач-перетаскивания
Цель
Сделать Kanban-доску адаптивной под мобильные устройства и добавить поддержку тач-перетаскивания задач.
Что будем использовать
- SortableJS — библиотека для перетаскивания с поддержкой тача
- CSS Media Queries — адаптивный дизайн
- Axum — как и раньше, отдаёт статику
Подключаем SortableJS
1. Скачиваем библиотеку
cd /srv/apps/kanban/kanban-board wget https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js -O static/Sortable.min.js
2. Проверяем, что файл появился
ls -la static/Sortable.min.js
Обновляем шаблоны
1. templates/base.html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kanban Board</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
{% block content %}{% endblock %}
</div>
<script src="/static/Sortable.min.js"></script>
<script src="/static/script.js"></script>
</body>
</html>
Обновляем JavaScript
1. static/script.js
Замените всё содержимое:
let tasks = [];
async function loadTasks() {
const response = await fetch('/api/tasks');
tasks = await response.json();
renderTasks();
}
function renderTasks() {
const columns = { todo: [], in_progress: [], done: [] };
tasks.forEach(task => {
columns[task.status].push(task);
});
['todo', 'in_progress', 'done'].forEach(status => {
const container = document.getElementById(status);
container.innerHTML = '';
columns[status].forEach(task => {
const taskEl = document.createElement('div');
taskEl.className = 'task';
taskEl.dataset.id = task.id;
taskEl.dataset.status = task.status;
taskEl.innerHTML = `
<div class="task-header">
<div class="task-title">${task.title}</div>
<button class="delete-btn" onclick="deleteTask('${task.id}')">✕</button>
</div>
<div class="task-desc">${task.description || ''}</div>
`;
container.appendChild(taskEl);
});
// Инициализация SortableJS
new Sortable(container, {
group: 'tasks',
animation: 150,
onEnd: function (evt) {
const id = evt.item.dataset.id;
const newStatus = evt.to.id;
updateTaskStatus(id, newStatus);
}
});
});
}
async function addTask() {
const title = document.getElementById('newTaskTitle').value;
const desc = document.getElementById('newTaskDesc').value;
if (!title) return;
const response = await fetch('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, description: desc || null })
});
if (response.ok) {
document.getElementById('newTaskTitle').value = '';
document.getElementById('newTaskDesc').value = '';
loadTasks();
}
}
async function deleteTask(id) {
if (!confirm('Вы уверены, что хотите удалить задачу?')) return;
const response = await fetch(`/api/tasks/${id}`, {
method: 'DELETE'
});
if (response.ok) {
loadTasks();
}
}
async function updateTaskStatus(id, status) {
const response = await fetch(`/api/tasks/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: status })
});
if (response.ok) {
loadTasks();
}
}
loadTasks();
Обновляем CSS для адаптивности
1. static/style.css
Замените всё содержимое:
:root {
--bg-dark: #0f172a;
--bg-card: #1e293b;
--text-light: #f1f5f9;
--accent-blue: #3b82f6;
--accent-green: #10b981;
--accent-red: #ef4444;
}
body {
margin: 0;
font-family: system-ui, sans-serif;
background-color: var(--bg-dark);
color: var(--text-light);
}
.container {
padding: 20px;
}
.board h1 {
text-align: center;
margin-bottom: 30px;
color: #93c5fd;
}
.add-task {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 30px;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
.add-task input, .add-task textarea {
padding: 10px;
border-radius: 6px;
border: 1px solid #475569;
background-color: var(--bg-card);
color: var(--text-light);
}
.add-task button {
padding: 10px 15px;
background-color: var(--accent-blue);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
.columns {
display: flex;
gap: 20px;
justify-content: center;
flex-wrap: wrap;
}
.column {
background-color: #334155;
border-radius: 8px;
width: 300px;
padding: 15px;
min-height: 500px;
}
.column h2 {
margin-top: 0;
text-align: center;
color: #c7d2fe;
}
.tasks {
min-height: 450px;
padding: 10px;
}
.task {
background-color: var(--bg-card);
border-radius: 6px;
padding: 12px;
margin-bottom: 10px;
cursor: grab;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
border-left: 4px solid var(--accent-blue);
position: relative;
}
.task[data-status="in_progress"] {
border-left-color: var(--accent-green);
}
.task[data-status="done"] {
border-left-color: var(--accent-red);
}
.task:hover {
opacity: 0.9;
}
.task-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 5px;
}
.task-title {
font-weight: bold;
margin-bottom: 0;
flex-grow: 1;
}
.delete-btn {
background: none;
border: none;
color: #94a3b8;
cursor: pointer;
font-size: 16px;
padding: 0;
margin-left: 8px;
}
.delete-btn:hover {
color: var(--accent-red);
}
.task-desc {
font-size: 0.9em;
color: #cbd5e1;
}
/* Адаптив для мобильных */
@media (max-width: 768px) {
.columns {
flex-direction: column;
align-items: center;
}
.column {
width: 100%;
max-width: 300px;
}
.task {
padding: 10px;
}
.task-header {
flex-direction: column;
align-items: flex-start;
}
.delete-btn {
margin-top: 5px;
margin-left: 0;
}
}
Пересборка и запуск
1. Войдите под пользователем kanban
sudo -u kanban -H bash cd /srv/apps/kanban/kanban-board
2. Убедитесь, что cargo доступен
export PATH="$HOME/.cargo/bin:$PATH" which cargo
3. Пересоберите проект
cargo build --release
4. Выйдите из сессии
exit
5. Перезапустите сервис
sudo systemctl restart kanban-board
6. Проверьте статус
sudo systemctl status kanban-board
Проверка на мобильном устройстве
Откройте http://192.168.1.xxx:8082:
- Колонки располагаются вертикально на маленьком экране
- Можно перетаскивать задачи пальцем
- Все задачи видны в каждой колонке
- Кнопка удаления работает
Готово!
Теперь ваша Kanban-доска:
- Адаптивна под мобильные устройства
- Поддерживает тач-перетаскивание
- Совместима с десктопом
- Всё работает как раньше
Приятного использования на телефоне!