@@ -594,12 +679,10 @@
-
-
@@ -612,6 +695,7 @@ import { useRoute, useRouter } from 'vue-router'
import { usePreguntaStore } from '../../../store/pregunta.store'
import { useCursoStore } from '../../../store/curso.store'
import { message } from 'ant-design-vue'
+import MarkdownLatex from '../cursos/MarkdownLatex.vue'
import {
PlusOutlined,
EditOutlined,
@@ -830,10 +914,10 @@ const getImageUrl = (path) => {
const eliminarImagenExistente = (tipo, index) => {
if (tipo === 'enunciado') {
- formPreguntaState.imagenes_existentes.splice(index, 1)
+ formPreguntaState.imagenes_existentes = formPreguntaState.imagenes_existentes.filter((_, i) => i !== index)
message.success('Imagen eliminada (se aplicará al guardar)')
} else if (tipo === 'explicacion') {
- formPreguntaState.imagenes_explicacion_existentes.splice(index, 1)
+ formPreguntaState.imagenes_explicacion_existentes = formPreguntaState.imagenes_explicacion_existentes.filter((_, i) => i !== index)
message.success('Imagen eliminada (se aplicará al guardar)')
}
}
@@ -844,6 +928,19 @@ const verImagen = (url) => {
modalImagenVisible.value = true
}
+// Handlers para vista previa en tiempo real
+const handleEnunciadoInput = () => {
+ // Actualización automática a través de v-model
+}
+
+const handleAdicionalInput = () => {
+ // Actualización automática a través de v-model
+}
+
+const handleExplicacionInput = () => {
+ // Actualización automática a través de v-model
+}
+
// Métodos principales
const goBack = () => {
router.push({ name: 'AcademiaCursos' })
@@ -976,87 +1073,74 @@ const handleModalPreguntaCancel = () => {
const submitPreguntaForm = async () => {
try {
- // Validar antes de enviar
+
const erroresValidacion = validarFormulario()
if (erroresValidacion.length > 0) {
- erroresValidacion.forEach(error => {
- message.error(error)
- })
+ erroresValidacion.forEach(error => message.error(error))
return
}
- // Crear FormData para enviar archivos
+ // Crear FormData
const formData = new FormData()
-
- // Agregar campos básicos
formData.append('curso_id', formPreguntaState.curso_id)
formData.append('enunciado', formPreguntaState.enunciado)
formData.append('nivel_dificultad', formPreguntaState.nivel_dificultad)
formData.append('activo', formPreguntaState.activo ? 1 : 0)
-
+
if (formPreguntaState.enunciado_adicional) {
formData.append('enunciado_adicional', formPreguntaState.enunciado_adicional)
}
-
if (formPreguntaState.explicacion) {
formData.append('explicacion', formPreguntaState.explicacion)
}
-
- // Agregar opciones como array (no JSON stringificado)
+
+ // Opciones
const opcionesValidas = formPreguntaState.opciones.filter(op => op && op.trim() !== '')
- opcionesValidas.forEach((opcion, index) => {
- formData.append(`opciones[${index}]`, opcion)
- })
-
- if (formPreguntaState.respuesta_correcta) {
- formData.append('respuesta_correcta', formPreguntaState.respuesta_correcta)
- }
-
- // Agregar nuevas imágenes del enunciado
+ formData.append('opciones', JSON.stringify(opcionesValidas))
+ formData.append('respuesta_correcta', formPreguntaState.respuesta_correcta)
+
+ // NUEVAS IMÁGENES DEL ENUNCIADO
imagenesEnunciadoFiles.value.forEach(file => {
if (file.originFileObj) {
formData.append('imagenes[]', file.originFileObj)
}
})
-
- // Agregar nuevas imágenes de la explicación
+
+ // NUEVAS IMÁGENES DE EXPLICACIÓN
imagenesExplicacionFiles.value.forEach(file => {
if (file.originFileObj) {
formData.append('imagenes_explicacion[]', file.originFileObj)
}
})
-
+
+ // IMÁGENES EXISTENTES (solo arrays, nunca strings dobles)
if (isEditingPregunta.value) {
- // Para edición, también enviar imágenes existentes
if (formPreguntaState.imagenes_existentes?.length) {
formData.append('imagenes_existentes', JSON.stringify(formPreguntaState.imagenes_existentes))
}
-
if (formPreguntaState.imagenes_explicacion_existentes?.length) {
formData.append('imagenes_explicacion_existentes', JSON.stringify(formPreguntaState.imagenes_explicacion_existentes))
}
-
- // IMPORTANTE: Enviar curso_id también en actualización
- formData.append('curso_id', formPreguntaState.curso_id)
-
+
+ // Llamar a store para actualizar
await preguntaStore.actualizarPregunta(formPreguntaState.id, formData)
message.success('Pregunta actualizada correctamente')
} else {
+ // Crear nueva pregunta
await preguntaStore.crearPregunta(formData)
message.success('Pregunta creada correctamente')
}
-
+
+ // Cerrar modal y limpiar formulario
modalPreguntaVisible.value = false
resetPreguntaForm()
preguntaStore.errors = null
await fetchPreguntas()
-
+
} catch (error) {
console.error('Error al guardar pregunta:', error)
- // Mostrar errores específicos del backend si existen
if (error.response && error.response.data.errors) {
- const errors = error.response.data.errors
- Object.values(errors).forEach(errorList => {
+ Object.values(error.response.data.errors).forEach(errorList => {
errorList.forEach(err => message.error(err))
})
} else {
@@ -1362,7 +1446,6 @@ onMounted(async () => {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-
-webkit-box-orient: vertical;
}
@@ -1379,6 +1462,137 @@ onMounted(async () => {
padding: 24px;
}
+/* Nuevos estilos para editor con vista previa */
+.editor-container {
+ display: flex;
+ gap: 16px;
+ margin-bottom: 16px;
+ height: 300px;
+}
+
+.editor-column,
+.preview-column {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.editor-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.editor-header span {
+ font-weight: 500;
+ color: #333;
+}
+
+.markdown-editor {
+ flex: 1;
+ resize: none;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ font-size: 14px;
+ line-height: 1.5;
+ padding: 12px;
+ border: 1px solid #d9d9d9;
+ border-radius: 6px;
+ background-color: #fafafa;
+}
+
+.markdown-editor:focus {
+ border-color: #1890ff;
+ background-color: #fff;
+}
+
+.editor-tips {
+ margin-top: 8px;
+ padding: 8px;
+ background-color: #f6ffed;
+ border-radius: 4px;
+ border: 1px solid #b7eb8f;
+ font-size: 12px;
+ color: #666;
+}
+
+.preview-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 12px;
+ border: 1px solid #d9d9d9;
+ border-radius: 6px;
+ background-color: #fff;
+ min-height: 200px;
+}
+
+.empty-preview {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ color: #999;
+ text-align: center;
+}
+
+.empty-preview p {
+ margin-top: 8px;
+ font-size: 14px;
+}
+
+.markdown-preview {
+ font-size: 14px;
+ line-height: 1.6;
+}
+
+.markdown-preview :deep(h1) {
+ font-size: 1.5em;
+ margin-bottom: 0.5em;
+}
+
+.markdown-preview :deep(h2) {
+ font-size: 1.3em;
+ margin-bottom: 0.5em;
+}
+
+.markdown-preview :deep(p) {
+ margin-bottom: 1em;
+}
+
+.markdown-preview :deep(ul),
+.markdown-preview :deep(ol) {
+ margin-left: 1.5em;
+ margin-bottom: 1em;
+}
+
+.markdown-preview :deep(code) {
+ background-color: #f5f5f5;
+ padding: 2px 4px;
+ border-radius: 3px;
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+ font-size: 0.9em;
+}
+
+.markdown-preview :deep(pre) {
+ background-color: #f5f5f5;
+ padding: 12px;
+ border-radius: 6px;
+ overflow-x: auto;
+ margin-bottom: 1em;
+}
+
+.markdown-preview :deep(blockquote) {
+ border-left: 4px solid #1890ff;
+ padding-left: 12px;
+ margin-left: 0;
+ color: #666;
+ font-style: italic;
+}
+
/* Estilos adicionales */
.section-card {
margin-bottom: 16px;
@@ -1537,7 +1751,7 @@ onMounted(async () => {
border-radius: 3px;
}
-.enunciado-completo {
+.enunciado-principal {
font-size: 16px;
line-height: 1.6;
margin-bottom: 20px;
@@ -1725,5 +1939,74 @@ onMounted(async () => {
max-width: 150px;
max-height: 150px;
}
+
+ .editor-container {
+ flex-direction: column;
+ height: auto;
+ }
+
+ .editor-column,
+ .preview-column {
+ width: 100%;
+ min-height: 200px;
+ }
+
+ .markdown-editor {
+ min-height: 150px;
+ }
+
+ .pregunta-view h4 {
+ margin-top: 16px;
+ margin-bottom: 8px;
+ color: #333;
+ }
+
+ .imagenes-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ justify-content: center;
+ margin: 8px 0;
+ }
+
+ .centered-image {
+ max-width: 100%;
+ height: auto;
+ cursor: pointer;
+ border-radius: 4px;
+ transition: transform 0.2s;
+ }
+
+ .centered-image:hover {
+ transform: scale(1.05);
+ }
+
+ .opcion-view {
+ display: flex;
+ align-items: center;
+ padding: 6px 12px;
+ margin-bottom: 6px;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+ }
+
+ .opcion-view.correcta {
+ background-color: #f6ffed;
+ border-color: #b7eb8f;
+ }
+
+ .opcion-letter {
+ font-weight: bold;
+ margin-right: 8px;
+ }
+
+ .opcion-correct {
+ margin-left: auto;
+ }
+
+ .explicacion-view,
+ .enunciado-adicional {
+ margin-top: 12px;
+ }
}
\ No newline at end of file
diff --git a/front/src/views/administrador/cursos/RenderQuillContent.vue b/front/src/views/administrador/cursos/RenderQuillContent.vue
new file mode 100644
index 0000000..6f637ff
--- /dev/null
+++ b/front/src/views/administrador/cursos/RenderQuillContent.vue
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
\ No newline at end of file