Created
February 29, 2024 12:48
-
-
Save dansp89/4f3d075cd16e240e9da8661090ea3cad to your computer and use it in GitHub Desktop.
Componente VUE3 para criar Gestão de aulas, Módulos - Aulas,
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 }"> | |
<div | |
style=" | |
display: flex; | |
align-items: center; | |
justify-content: space-between; | |
" | |
> | |
<span>{{ node.label }}</span> | |
<!-- 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"> | |
<i class="fa-solid fa-pen-to-square text-info"></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" | |
icon="el-icon-plus" | |
@click.stop="addLesson(data.id)" | |
> | |
<i class="fa-solid fa-plus text-primary"></i> | |
</el-button> | |
</div> | |
</template> | |
</el-tree> | |
</div> | |
</template> | |
<script lang="ts"> | |
import { | |
defineComponent, | |
ref, | |
watch, | |
watchEffect, | |
toRefs, | |
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"; | |
interface Module { | |
id?: string | number; | |
title?: string; | |
active?: boolean; | |
duration?: number; | |
type?: string; | |
content?: { | |
id?: string | number; | |
title?: string; | |
description?: string; | |
url_video?: string | null; | |
content?: string; | |
duration?: number; | |
type?: string; | |
show?: boolean; | |
}[]; | |
children?: { | |
id?: string | number; | |
title?: string; | |
description?: string; | |
url_video?: string | null; | |
content?: string; | |
duration?: number; | |
type?: string; | |
show?: boolean; | |
}[]; | |
} | |
export default defineComponent({ | |
name: "course-content", | |
components: {}, | |
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 newLesson = { | |
id: id_hash, // Gerar um ID único para a nova aula | |
label: `# Aula - ${id_hash}`, | |
type: "lesson" | |
}; | |
const module = treeData.value.find((module) => module.id === moduleId); | |
if (module) { | |
if (!module.children) { | |
module.children = []; | |
} | |
module.children.push(newLesson); | |
} | |
}; | |
// const deleteNode = (nodeId, nodeType) => { | |
// console.log("[deleteNode]['init']", nodeId, nodeType); | |
// try { | |
// // Função genérica para encontrar e excluir o nó | |
// const findAndDelete = (nodes, id) => { | |
// const no = JSON.parse(JSON.stringify(nodes)); | |
// console.log("[findAndDelete]", nodes, id, no); | |
// return no.filter((node) => { | |
// if (node.id === id) { | |
// return false; // Exclui o nó | |
// } | |
// if (node.content) { | |
// node.content = findAndDelete(node.content, 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.content) { | |
// module.content = findAndDelete(module.content, nodeId); | |
// } | |
// }); | |
// } | |
// } catch (e) { | |
// console.error("[deleteNode]::", e); | |
// } | |
// }; | |
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 = findAndDelete(module.children, nodeId); | |
} | |
}); | |
} | |
}; | |
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)); | |
}; | |
// 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, | |
updateValue | |
}; | |
} | |
}); | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment