Skip to content

Instantly share code, notes, and snippets.

@dansp89
Created February 29, 2024 12:48
Show Gist options
  • Save dansp89/4f3d075cd16e240e9da8661090ea3cad to your computer and use it in GitHub Desktop.
Save dansp89/4f3d075cd16e240e9da8661090ea3cad to your computer and use it in GitHub Desktop.
Componente VUE3 para criar Gestão de aulas, Módulos - Aulas,
<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