Skip to content

Instantly share code, notes, and snippets.

@dansp89
Created February 29, 2024 14:31
Show Gist options
  • Save dansp89/d608606c391d215e84c3ea058f406e0e to your computer and use it in GitHub Desktop.
Save dansp89/d608606c391d215e84c3ea058f406e0e to your computer and use it in GitHub Desktop.
Para ajustar, apagar depois
<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