Files
sori.studio/components/admin/AdminNavPrimaryBranch.vue

215 lines
6.6 KiB
Vue

<script setup>
import AdminNavPrimaryBranch from './AdminNavPrimaryBranch.vue'
const props = defineProps({
/** buildNavigationEditorTree 결과 */
wraps: {
type: Array,
required: true
},
/** 들여쓰기 단계 */
depth: {
type: Number,
default: 0
},
/** 루트면 `'root'`, 아니면 부모 항목 id */
parentKey: {
type: String,
default: 'root'
},
/** 드래그 중인 항목 id */
draggingId: {
type: String,
default: ''
},
/** 드롭 대상 위에 올린 항목 id */
dragOverId: {
type: String,
default: ''
}
})
const emit = defineEmits([
'drag-start',
'drag-over',
'drag-end',
'drop',
'add-child',
'remove'
])
/**
* 입력·버튼 위에서는 행 드래그를 시작하지 않는다.
* @param {DragEvent} event - 이벤트
* @returns {boolean} true면 드래그를 막는다
*/
const shouldBlockRowDrag = (event) => {
const el = event.target
if (!el || typeof el.closest !== 'function') {
return false
}
return Boolean(el.closest('input, button, textarea, select, a'))
}
/**
* 드래그 시작
* @param {DragEvent} event - 이벤트
* @param {string} itemId - 항목 id
* @returns {void}
*/
const onDragStart = (event, itemId) => {
if (shouldBlockRowDrag(event)) {
event.preventDefault()
return
}
if (!event.dataTransfer) {
return
}
emit('drag-start', { parentKey: props.parentKey, itemId })
event.dataTransfer.effectAllowed = 'move'
}
/**
* 드래그 오버
* @param {DragEvent} event - 이벤트
* @param {string} itemId - 항목 id
* @returns {void}
*/
const onDragOver = (event, itemId) => {
event.preventDefault()
emit('drag-over', itemId)
}
/**
* 드롭
* @param {DragEvent} event - 이벤트
* @param {string} itemId - 대상 id
* @returns {void}
*/
const onDrop = (event, itemId) => {
event.preventDefault()
emit('drop', { parentKey: props.parentKey, targetId: itemId })
}
/**
* 드래그 종료
* @returns {void}
*/
const onDragEnd = () => {
emit('drag-end')
}
/**
* 행 하이라이트 클래스(태그 관리 메인 태그 테이블과 동일 톤)
* @param {string} itemId - 항목 id
* @returns {string}
*/
const rowStateClass = (itemId) => {
const id = String(itemId)
if (props.dragOverId === id) {
return 'bg-[#f9f9f7]'
}
if (props.draggingId === id) {
return 'opacity-50'
}
return ''
}
</script>
<template>
<div class="admin-nav-primary-branch" :class="depth ? 'mt-2 border-l border-line pl-3' : ''">
<div class="admin-nav-primary-branch__shell overflow-hidden rounded border border-line">
<table class="admin-nav-primary-branch__table w-full border-collapse text-left text-sm">
<thead v-if="depth === 0" class="admin-nav-primary-branch__head bg-[#f5f5f2] text-xs uppercase text-muted">
<tr>
<th class="admin-nav-primary-branch__cell px-4 py-3">
#
</th>
<th class="admin-nav-primary-branch__cell px-4 py-3">
라벨
</th>
<th class="admin-nav-primary-branch__cell px-4 py-3">
URL
</th>
<th class="admin-nav-primary-branch__cell px-4 py-3">
관리
</th>
</tr>
</thead>
<tbody class="admin-nav-primary-branch__body divide-y divide-line bg-white">
<template v-for="(wrap, index) in wraps" :key="wrap.item.id">
<tr
class="admin-nav-primary-branch__row cursor-move"
:class="rowStateClass(wrap.item.id)"
draggable="true"
@dragstart="onDragStart($event, wrap.item.id)"
@dragover="onDragOver($event, wrap.item.id)"
@drop="onDrop($event, wrap.item.id)"
@dragend="onDragEnd"
>
<td class="admin-nav-primary-branch__cell px-4 py-3 align-middle text-muted">
{{ index + 1 }}
</td>
<td class="admin-nav-primary-branch__cell px-4 py-3 align-middle">
<input
v-model="wrap.item.label"
class="admin-nav-primary-branch__label w-full min-w-[8rem] rounded border border-line px-3 py-2 text-sm outline-none focus:border-[#8e9cac]"
type="text"
placeholder="라벨"
required
>
</td>
<td class="admin-nav-primary-branch__cell px-4 py-3 align-middle">
<input
v-model="wrap.item.url"
class="admin-nav-primary-branch__url w-full min-w-[10rem] rounded border border-line px-3 py-2 font-mono text-sm outline-none focus:border-[#8e9cac]"
type="text"
placeholder="URL (# 또는 /경로)"
required
>
</td>
<td class="admin-nav-primary-branch__cell px-4 py-3 align-middle">
<div class="admin-nav-primary-branch__actions flex flex-wrap gap-2">
<button
class="admin-nav-primary-branch__add-child rounded border border-line px-3 py-1.5 text-xs font-semibold"
type="button"
@click="emit('add-child', wrap.item.id)"
>
하위
</button>
<button
class="admin-nav-primary-branch__remove rounded border border-red-200 px-3 py-1.5 text-xs font-semibold text-red-700"
type="button"
@click="emit('remove', wrap.item.id)"
>
삭제
</button>
</div>
</td>
</tr>
<tr v-if="wrap.children.length" class="admin-nav-primary-branch__nest bg-white">
<td class="p-0" colspan="4">
<div class="admin-nav-primary-branch__nest-inner border-t border-line bg-[#fafaf8] px-2 py-3">
<AdminNavPrimaryBranch
:wraps="wrap.children"
:depth="depth + 1"
:parent-key="String(wrap.item.id)"
:dragging-id="draggingId"
:drag-over-id="dragOverId"
@drag-start="emit('drag-start', $event)"
@drag-over="emit('drag-over', $event)"
@drag-end="emit('drag-end')"
@drop="emit('drop', $event)"
@add-child="emit('add-child', $event)"
@remove="emit('remove', $event)"
/>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</template>