1
0
Files
TaskBoard/front_vue/src/components/Column.vue
Falknat 44b6e636d4 Добавление в Архив + Фронт
1. Переписал модуль выпадающего слева меню
2. Добавил механику Архивации задач
3. Запоминания выбранного отдела
2026-01-13 07:04:10 +07:00

245 lines
4.8 KiB
Vue

<template>
<div
class="column"
@dragover.prevent="handleDragOver"
@dragenter.prevent="handleDragEnter"
@dragleave="handleDragLeave"
@drop="handleDrop"
:class="{ 'drag-over': isDragOver }"
ref="columnRef"
>
<div class="column-header">
<div class="column-title-row">
<span class="column-dot" :style="{ background: column.color }"></span>
<h2 class="column-title">{{ column.title }}</h2>
<span class="column-count">{{ column.cards.length }}</span>
</div>
<button class="column-add" @click="emit('create-task')">
<i data-lucide="plus"></i>
</button>
</div>
<div class="cards" ref="cardsRef">
<template v-for="(card, index) in column.cards" :key="card.id">
<!-- Индикатор перед карточкой -->
<div v-if="isDragOver && dropIndex === index" class="drop-indicator"></div>
<Card
:card="card"
:column-id="column.id"
:index="index"
:departments="departments"
:labels="labels"
@click="emit('open-task', card)"
@archive="emit('archive-task', $event)"
/>
</template>
<!-- Индикатор в конце списка -->
<div v-if="isDragOver && dropIndex === column.cards.length" class="drop-indicator"></div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUpdated } from 'vue'
import Card from './Card.vue'
const props = defineProps({
column: Object,
departments: {
type: Array,
default: () => []
},
labels: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['drop-card', 'open-task', 'create-task', 'archive-task'])
const refreshIcons = () => {
if (window.lucide) {
window.lucide.createIcons()
}
}
onMounted(refreshIcons)
onUpdated(refreshIcons)
const columnRef = ref(null)
const cardsRef = ref(null)
const isDragOver = ref(false)
const dropIndex = ref(-1)
let dragEnterCounter = 0
const handleDragEnter = (e) => {
dragEnterCounter++
isDragOver.value = true
}
const calculateDropIndex = (clientY) => {
if (!cardsRef.value) return props.column.cards.length
const cardElements = cardsRef.value.querySelectorAll('.card')
let index = props.column.cards.length
for (let i = 0; i < cardElements.length; i++) {
const rect = cardElements[i].getBoundingClientRect()
const cardMiddle = rect.top + rect.height / 2
if (clientY < cardMiddle) {
index = i
break
}
}
return index
}
const handleDragOver = (e) => {
isDragOver.value = true
dropIndex.value = calculateDropIndex(e.clientY)
}
const handleDragLeave = (e) => {
dragEnterCounter--
if (dragEnterCounter === 0) {
isDragOver.value = false
dropIndex.value = -1
}
}
const handleDrop = (e) => {
const cardId = parseInt(e.dataTransfer.getData('cardId'))
const fromColumnId = e.dataTransfer.getData('columnId')
const toIndex = calculateDropIndex(e.clientY)
dragEnterCounter = 0
isDragOver.value = false
dropIndex.value = -1
emit('drop-card', {
cardId,
fromColumnId,
toColumnId: props.column.id,
toIndex
})
}
</script>
<style scoped>
.column {
width: 300px;
min-width: 300px;
flex-shrink: 0;
display: flex;
flex-direction: column;
max-height: calc(100vh - 140px);
}
.column.drag-over .cards {
background: var(--accent-soft);
border-radius: 12px;
}
.column-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding: 0 4px;
}
.column-title-row {
display: flex;
align-items: center;
gap: 10px;
}
.column-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.column-title {
font-size: 14px;
font-weight: 500;
color: var(--text-secondary);
}
.column-count {
color: var(--text-muted);
font-size: 13px;
font-weight: 400;
}
.column-add {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 6px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.column-add i {
width: 16px;
height: 16px;
}
.column-add:hover {
background: var(--bg-card);
color: var(--text-primary);
}
.cards {
flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 12px;
padding: 4px;
margin: -4px;
min-height: 150px;
}
.cards::-webkit-scrollbar {
width: 4px;
}
.cards::-webkit-scrollbar-track {
background: transparent;
}
.cards::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
}
.cards::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.25);
}
.drop-indicator {
height: 4px;
background: var(--accent);
border-radius: 2px;
margin: 4px 0;
animation: pulse 1s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.5; }
50% { opacity: 1; }
}
</style>