Created
February 29, 2024 14:31
-
-
Save dansp89/d608606c391d215e84c3ea058f406e0e to your computer and use it in GitHub Desktop.
Para ajustar, apagar depois
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div @update:modelValue="$emit('update:modelValue', $event)"> | |
<el-button | |
type="" | |
@click="addModule" | |
class="mb-5 border-bottom border-secondary" | |
> | |
<i class="fa-solid fa-plus text-dark px-3"></i> | |
Adicionar Módulo | |
</el-button> | |
<el-tree | |
:data="treeData" | |
draggable | |
default-expand-all | |
node-key="id" | |
:allow-drag="allowDrag" | |
:allow-drop="allowDrop" | |
@node-drag-start="handleDragStart" | |
@node-drag-enter="handleDragEnter" | |
@node-drag-leave="handleDragLeave" | |
@node-drag-over="handleDragOver" | |
@node-drag-end="handleDragEnd" | |
@node-drop="handleDrop" | |
> | |
<template #default="{ node, data, $index }"> | |
<div class="d-flex justify-content-between w-100"> | |
<span v-if="data.type == 'module'" class="fw-bolder text-uppercase"> | |
{{ node.label }} | |
</span> | |
<span v-if="data.type == 'lesson'" class=""> | |
{{ node.label }} | |
</span> | |
<div class=""> | |
<!-- Mostrar botão de excluir para aulas --> | |
<el-button | |
v-if="data.type === 'lesson'" | |
:text="true" | |
@click.stop="deleteNode(data.id, data.type)" | |
> | |
<i class="fa-solid fa-trash-can text-danger"></i> | |
</el-button> | |
<!-- Mostrar botão editar aulas --> | |
<el-button | |
v-if="data.type === 'lesson'" | |
:text="true" | |
title="Editar aula" | |
@click.stop="openEditModal($index, node)" | |
data-bs-toggle="modal" | |
data-bs-target="#LessonModal" | |
> | |
<i class="fa-solid fa-pen-to-square text-info"></i> | |
</el-button> | |
<!-- Mostrar botão de exibir aula --> | |
<el-button v-if="data.type === 'lesson'" :text="true"> | |
<i | |
class="fa-solid fa-arrow-up-right-from-square text-primary" | |
title="Ver aula" | |
></i> | |
</el-button> | |
<!-- Mostrar botão de excluir para módulos sem aulas --> | |
<el-button | |
v-if=" | |
data.type === 'module' && | |
(!data.children || data.children.length === 0) | |
" | |
:text="true" | |
@click.stop="deleteNode(data.id, data.type)" | |
> | |
<i class="fa-solid fa-trash-can text-danger"></i> | |
</el-button> | |
<!-- Mostrar botão editar módulo --> | |
<el-button v-if="data.type === 'module'" :text="true"> | |
<i class="fa-solid fa-pen-to-square text-info"></i> | |
</el-button> | |
<!-- Botão para adicionar aula, visível apenas para módulos --> | |
<el-button | |
v-if="data.type === 'module'" | |
:text="true" | |
@click.stop="addLesson(data.id)" | |
> | |
<i class="fa-solid fa-plus text-primary"></i> | |
</el-button> | |
</div> | |
</div> | |
</template> | |
</el-tree> | |
<!-- Modal::begin::course edit --> | |
<div | |
class="modal fade" | |
id="LessonModal" | |
tabindex="-1" | |
data-bs-backdrop="static" | |
data-bs-keyboard="false" | |
aria-labelledby="LessonModalLabel" | |
aria-hidden="true" | |
> | |
<div class="modal-dialog modal-xl"> | |
<div class="modal-content"> | |
<div class="modal-header"> | |
<h1 class="modal-title fs-5" id="LessonModalLabel">Editar aula</h1> | |
<button | |
type="button" | |
class="btn-close" | |
data-bs-dismiss="modal" | |
aria-label="Close" | |
></button> | |
</div> | |
<div class="modal-body"> | |
<div class="row"> | |
<Campo | |
id="title" | |
type="text" | |
label="Título da aula" | |
col="12" | |
row="120" | |
/> | |
<Campo | |
id="description" | |
type="textarea" | |
label="Descrição da aula" | |
col="12" | |
row="120" | |
/> | |
<Campo | |
id="duration" | |
type="time" | |
label="Duração da aula" | |
col="6" | |
/> | |
<Campo | |
id="content" | |
type="editor" | |
label="Conteúdo da aula" | |
col="6" | |
row="120" | |
/> | |
<UploadDrag | |
:limit="10" | |
:limit-size="100" | |
accept="application/pdf, image/png, image/jpeg, image/jpg" | |
:multiple="true" | |
list-type="text" | |
:showFileList="true" | |
/> | |
</div> | |
</div> | |
<div class="modal-footer"> | |
<button | |
type="button" | |
class="btn btn-secondary" | |
data-bs-dismiss="modal" | |
> | |
Fechar | |
</button> | |
<button type="button" class="btn btn-primary">Salvar</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Modal::end::course edit --> | |
</div> | |
</template> | |
<script lang="ts"> | |
import { | |
defineComponent, | |
ref, | |
watch, | |
watchEffect, | |
type PropType, | |
onMounted | |
} from "vue"; | |
import type Node from "element-plus/es/components/tree/src/model/node"; | |
import type { DragEvents } from "element-plus/es/components/tree/src/model/useDragNode"; | |
import type { | |
AllowDropType, | |
NodeDropType | |
} from "element-plus/es/components/tree/src/tree.type"; | |
import { nanoid } from "nanoid"; | |
import ApiService from "@/core/services/ApiService"; | |
import Campo from "@/components/fields/Campo.vue"; | |
import UploadAvatar from "@/components/fields/uploads/Avatar.vue"; | |
// import PhotoWall from "@/components/fields/uploads/PhotoWall.vue"; | |
import UploadDrag from "@/components/fields/uploads/Drag.vue"; | |
interface Module { | |
id?: string | number; | |
title?: string; | |
label?: string; | |
active?: boolean; | |
duration?: number; | |
type?: string; | |
content?: { | |
id?: string | number; | |
title?: string; | |
label?: string; | |
description?: string; | |
url_video?: string | null; | |
content?: string; | |
duration?: number; | |
type?: string; | |
show?: boolean; | |
}[]; | |
children?: { | |
id?: string | number; | |
title?: string; | |
label?: string; | |
description?: string; | |
url_video?: string | null; | |
content?: string; | |
duration?: number; | |
type?: string; | |
show?: boolean; | |
}[]; | |
} | |
export default defineComponent({ | |
name: "course-content", | |
components: { Campo, UploadDrag }, | |
props: { | |
mode: { | |
type: String, | |
default: "create", | |
required: true, | |
validator: (value) => value === "create" || value === "view" | |
}, | |
course_id: { | |
type: [String, Number], | |
required: false // Será verificado com base no modo | |
}, | |
modelValue: { | |
type: Array as PropType<Module[]>, | |
required: true | |
} | |
}, | |
emits: [ | |
"update:modelValue", | |
"drag-start", | |
"drag-enter", | |
"drag-leave", | |
"drag-over", | |
"drag-end", | |
"drop" | |
], | |
setup(props, { emit }) { | |
const courseId = ref(props.course_id); | |
const treeData = ref([...props.modelValue]); | |
watch( | |
() => props.modelValue, | |
(newValue) => { | |
treeData.value = newValue; | |
} | |
); | |
watchEffect(() => { | |
if (props.mode === "view" && !props.course_id) { | |
console.warn("course_id é obrigatório no modo view."); | |
} | |
}); | |
watch(treeData, (newValue) => { | |
emit("update:modelValue", newValue); | |
console.log("modelValue atualizado:", newValue); | |
}); | |
const allowDrop = ( | |
draggingNode: Node, | |
dropNode: Node, | |
type: AllowDropType | |
) => { | |
// Ajuste para permitir que módulos sejam reordenados entre si. | |
if ( | |
draggingNode.data.type === "module" && | |
dropNode.data.type === "module" && | |
(type === "prev" || type === "next") | |
) { | |
return true; | |
} | |
// Permitir mover aulas apenas dentro de módulos ou entre módulos, mas não permitir que sejam pais (não podem ter filhos). | |
if (draggingNode.data.type === "lesson") { | |
// Permitir se o destino for um módulo e a aula está sendo movida para dentro dele (como filha). | |
if (dropNode.data.type === "module" && type === "inner") { | |
return true; | |
} | |
// Permitir mover aula para o mesmo nível dentro de outro módulo ou dentro do mesmo módulo (prev ou next, mas não dentro de outra aula). | |
if ( | |
dropNode.data.type === "lesson" && | |
(type === "prev" || type === "next") | |
) { | |
return true; | |
} | |
} | |
return false; // Não permite outros casos, incluindo aulas sendo pais. | |
}; | |
const allowDrag = (draggingNode: Node) => { | |
// Todos os nós podem ser arrastados, mas as restrições de onde podem ser soltos são definidas em allowDrop. | |
return true; | |
}; | |
const handleDragStart = (node: Node, ev: DragEvents) => { | |
console.log("drag start", node); | |
}; | |
const handleDragEnter = ( | |
draggingNode: Node, | |
dropNode: Node, | |
ev: DragEvents | |
) => { | |
console.log("tree drag enter:", dropNode.label); | |
}; | |
const handleDragLeave = ( | |
draggingNode: Node, | |
dropNode: Node, | |
ev: DragEvents | |
) => { | |
console.log("tree drag leave:", dropNode.label); | |
}; | |
const handleDragOver = ( | |
draggingNode: Node, | |
dropNode: Node, | |
ev: DragEvents | |
) => { | |
console.log("tree drag over:", dropNode.label); | |
}; | |
const handleDragEnd = ( | |
draggingNode: Node, | |
dropNode: Node, | |
dropType: NodeDropType, | |
ev: DragEvents | |
) => { | |
console.log("tree drag end:", dropNode && dropNode.label, dropType); | |
}; | |
const handleDrop = ( | |
draggingNode: Node, | |
dropNode: Node, | |
dropType: NodeDropType, | |
ev: DragEvents | |
) => { | |
console.log("tree drop:", dropNode.label, dropType); | |
// Aqui você deve implementar a lógica para atualizar a estrutura de dados conforme a nova posição do nó arrastado. | |
}; | |
const addModule = () => { | |
const newModule = { | |
id: nanoid(), | |
label: `# Módulo ${treeData.value.length + 1}`, | |
type: "module", | |
children: [] | |
}; | |
treeData.value.push(newModule); | |
emit("update:modelValue", treeData.value); | |
}; | |
const addLesson = (moduleId) => { | |
const id_hash = nanoid(); | |
const module = treeData.value.find((module) => module.id === moduleId); | |
// console.log("[addlesson][module]::", module); | |
// Verifica se o módulo existe e se possui a propriedade children | |
if (module && module.children) { | |
// Obtém o número de aulas existentes no módulo | |
const existingLessonsCount = module.children.length; | |
// Cria o label da nova aula com base no número de aulas existentes | |
const newLessonLabel = `# Aula ${existingLessonsCount + 1} - `; | |
// Cria a nova aula | |
const newLesson = { | |
id: id_hash, // Gerar um ID único para a nova aula | |
label: newLessonLabel, | |
type: "lesson" | |
}; | |
// Adiciona a nova aula aos children do módulo | |
module.children.push(newLesson); | |
} | |
emit("update:modelValue", treeData.value); | |
}; | |
const deleteNode = (nodeId, nodeType) => { | |
// Função genérica para encontrar e excluir o nó | |
const findAndDelete = (nodes, id) => | |
nodes.filter((node) => { | |
if (node.id === id) { | |
return false; // Exclui o nó | |
} | |
if (node.children) { | |
node.children = findAndDelete(node.children, id); // Procura recursivamente | |
} | |
return true; | |
}); | |
if (nodeType === "module") { | |
// Excluir o módulo se não tiver aulas | |
treeData.value = findAndDelete(treeData.value, nodeId); | |
} else if (nodeType === "lesson") { | |
// Excluir a aula | |
treeData.value.forEach((module) => { | |
if (module.children) { | |
module.children = module.children.filter( | |
(lesson) => lesson.id !== nodeId | |
); | |
// Atualizar os labels das aulas restantes no mesmo módulo | |
module.children.forEach((lesson, index) => { | |
const parts = lesson.label.split(" - "); | |
lesson.label = `# Aula ${index + 1} - ${ | |
parts.length > 1 ? parts[1] : "" | |
}`; | |
}); | |
} | |
}); | |
} | |
}; | |
const updateValue = () => { | |
emit("update:modelValue", treeData.value); | |
emit("drag-start", treeData.value); | |
emit("drag-enter", treeData.value); | |
emit("drag-leave", treeData.value); | |
emit("drag-over", treeData.value); | |
emit("drag-end", treeData.value); | |
emit("drop", treeData.value); | |
console.log("[CourseContent][updateValue]::", treeData.value); | |
treeData.value, JSON.parse(JSON.stringify(treeData.value)); | |
}; | |
const openEditModal = (moduleIndex: number, lessonIndex: number) => { | |
// Aqui você pode implementar a lógica para abrir o modal Bootstrap | |
// e passar os índices do módulo e da aula para o modal | |
console.log( | |
"Abrir modal de edição com índices:", | |
moduleIndex, | |
lessonIndex | |
); | |
}; | |
// Executar lógica quando o componente for montado | |
onMounted(async () => { | |
console.log(`[ContentCourse]::init(${courseId.value})`); | |
try { | |
const { data } = await ApiService.get( | |
`/lms-courses/${courseId.value}`, | |
"?populate[0]=modules.content" | |
// "?populate[0]=authors,course_image,modules.content" | |
); | |
const datas = data.data; | |
const { attributes } = datas; | |
let { modules } = attributes; | |
// begin::converter para o padrão da biblioteca de tree | |
modules = modules.map((a, x) => { | |
a["id"] = String(a?.id); | |
a["label"] = `# Módulo ${x + 1}- ${a?.title}`; | |
a["children"] = a?.content?.map((b, y) => { | |
a["id"] = String(b?.id); | |
b["label"] = `# Aula ${y + 1} - ${b?.title}`; | |
return b; | |
}); | |
return a; | |
}); | |
// end::converter para o padrão da biblioteca de tree | |
treeData.value = modules; | |
// console.log( | |
// `[ContentCourse]::init(${courseId.value})`, | |
// modules, | |
// JSON.stringify(treeData.value, null, 2) | |
// ); | |
updateValue(); | |
} catch (e) { | |
console.log("Error", e); | |
} | |
}); | |
return { | |
treeData, | |
addModule, | |
addLesson, | |
allowDrop, | |
allowDrag, | |
handleDragStart, | |
handleDragEnter, | |
handleDragLeave, | |
handleDragOver, | |
handleDragEnd, | |
handleDrop, | |
deleteNode, | |
openEditModal, | |
updateValue | |
}; | |
} | |
}); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment