
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
<template>
<div>
<div v-for="(lists, idx) of lists" :key="lists.sanctnId || idx" class="draggable-item-wrapper">
<div class="drop-zone" @dragover.prevent="handleDragOver($event, idx)" @dragenter.prevent="handleDragEnter($event, idx)" @dragleave="handleDragLeave($event, idx)" @drop.prevent="handleDrop($event, idx)" :class="{
'drop-active': currentDropTarget === idx,
'drop-visible': isDragging && shouldShowDropZone(idx),
'drop-hidden': isDragging && !shouldShowDropZone(idx)
}">
<div class="drop-indicator">여기에 놓기</div>
</div>
<div class="d-flex addapproval draggable-item" draggable="true" @dragstart="handleDragStart(idx, $event)" @dragend="handleDragEnd" :class="{ 'being-dragged': currentDraggedIndex === idx }">
<select class="form-select" v-model="lists.sanctnSe" style="width: 110px;" @mousedown.stop>
<option v-for="(item, idx) of approvalCodes" :key="idx" :value="item.code"> {{ item.codeNm }} </option>
</select>
<div class="d-flex align-items-center border-x">
<p>{{ lists.confmerNm }} {{ lists.clsfNm }} ({{ lists.sanctnOrdr }})</p>
<button type="button" @click="removeApproval(idx)" @mousedown.stop>
<CloseOutlined />
</button>
</div>
</div>
</div>
<!-- 마지막 드롭존 -->
<div class="drop-zone" @dragover.prevent="handleDragOver($event, lists.length)" @dragenter.prevent="handleDragEnter($event, lists.length)" @dragleave="handleDragLeave($event, lists.length)" @drop.prevent="handleDrop($event, lists.length)" :class="{
'drop-active': currentDropTarget === lists.length,
'drop-visible': isDragging && shouldShowLastDropZone(),
'drop-hidden': isDragging && !shouldShowLastDropZone()
}">
<div class="drop-indicator">여기에 놓기</div>
</div>
</div>
</template>
<script>
import { CloseOutlined } from '@ant-design/icons-vue';
export default {
name: 'SanctnList',
components: {
CloseOutlined
},
props: {
lists: {
type: Array,
default: () => [],
}
},
emits: ['update:lists', 'delSanctn'],
data() {
return {
approvalCodes: [], // 결재 코드 목록
currentDraggedIndex: null,
currentDropTarget: null,
}
},
computed: {
// 드래그 중인지 여부
isDragging() {
return this.currentDraggedIndex !== null;
},
},
async created() {
this.loadApprovalCodes(); // 코드 목록 초기화
},
methods: {
// 결재 코드 목록 로드
async loadApprovalCodes() {
try {
this.approvalCodes = await this.$findChildCodes('sanctn_code');
} catch (error) {
console.error('결재 코드 로드 실패:', error);
this.approvalCodes = [];
}
},
// 드롭존 표시 여부 계산
shouldShowDropZone(targetIndex) {
if (!this.isDragging) return true;
// 현재 드래그 중인 아이템의 앞뒤 드롭존은 숨김
const adjacentIndexes = [this.currentDraggedIndex, this.currentDraggedIndex + 1];
return !adjacentIndexes.includes(targetIndex);
},
// 마지막 드롭존 표시 여부 계산
shouldShowLastDropZone() {
if (!this.isDragging) return true;
// 마지막 아이템을 드래그 중이면 마지막 드롭존 숨김
return this.currentDraggedIndex !== this.lists.length - 1;
},
// 드래그 시작
handleDragStart(itemIndex, event) {
this.currentDraggedIndex = itemIndex;
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', itemIndex.toString());
},
// 드래그 오버 (드롭 허용)
handleDragOver(event, dropIndex) {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
},
// 드래그 진입
handleDragEnter(event, dropIndex) {
if (this.isDragging) {
this.currentDropTarget = dropIndex;
}
},
// 드래그 떠남
handleDragLeave(event, dropIndex) {
const dropZone = event.currentTarget;
const rect = dropZone.getBoundingClientRect();
const { clientX: x, clientY: y } = event;
// 드롭존 영역을 완전히 벗어났는지 확인
const isOutside = x < rect.left || x > rect.right || y < rect.top || y > rect.bottom;
if (isOutside && this.currentDropTarget === dropIndex) {
this.currentDropTarget = null;
}
},
// 드롭 처리
handleDrop(event, dropIndex) {
if (!this.isDragging || this.currentDraggedIndex === dropIndex) {
this.resetDragState();
return;
}
this.reorderApprovalList(dropIndex);
this.resetDragState();
},
// 드래그 종료
handleDragEnd() {
this.resetDragState();
},
// 결재 목록 재정렬
reorderApprovalList(dropIndex) {
let finalDropIndex = dropIndex;
// 드래그한 아이템이 드롭 위치보다 앞에 있으면 인덱스 조정
if (this.currentDraggedIndex < dropIndex) {
finalDropIndex = dropIndex - 1;
}
const reorderedList = [...this.lists];
const [draggedItem] = reorderedList.splice(this.currentDraggedIndex, 1);
reorderedList.splice(finalDropIndex, 0, draggedItem);
// 결재 순서 재설정
this.updateApprovalOrder(reorderedList);
this.$emit('update:lists', reorderedList);
},
// 결재 순서 업데이트
updateApprovalOrder(approvalList) {
approvalList.forEach((item, index) => {
item.sanctnOrdr = index + 1;
});
},
// 결재자 삭제
removeApproval(itemIndex) {
this.$emit('delSanctn', itemIndex);
},
// 드래그 상태 초기화
resetDragState() {
this.currentDraggedIndex = null;
this.currentDropTarget = null;
},
}
};
</script>
<style scoped>
.draggable-item-wrapper {
position: relative;
}
.draggable-item {
transition: all 0.3s ease;
border: 2px solid transparent;
background: white;
border-radius: 8px;
padding: 8px;
cursor: grab;
}
.draggable-item:active {
cursor: grabbing;
}
.being-dragged {
opacity: 0.5;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
transform: rotate(2deg);
}
.drop-zone {
height: 0;
margin: 0;
padding: 0;
border: 2px dashed transparent;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
opacity: 0;
overflow: hidden;
line-height: 0;
font-size: 0;
}
.drop-zone.drop-visible {
height: 40px;
margin: 8px 0;
opacity: 1;
border-color: #ddd;
font-size: 14px;
line-height: normal;
}
.drop-zone.drop-hidden {
height: 0;
margin: 0;
padding: 0;
opacity: 0;
pointer-events: none;
line-height: 0;
font-size: 0;
}
.drop-zone.drop-active {
border-color: #007bff !important;
background-color: #e3f2fd;
box-shadow: 0 0 10px rgba(0, 123, 255, 0.3);
}
.drop-indicator {
color: #007bff;
font-weight: bold;
font-size: inherit;
pointer-events: none;
user-select: none;
}
.addapproval {
margin-bottom: 8px;
}
.addapproval:last-child {
margin-bottom: 0;
}
</style>