add pages
All checks were successful
Deploy Nuxt App / deploy (push) Successful in 2m40s

This commit is contained in:
2025-06-21 09:00:38 +04:00
parent f8a632b6df
commit e396883830
19 changed files with 490 additions and 81 deletions

View File

@ -1,7 +1,7 @@
<template>
<template v-if="book">
<div class="relative z-50 min-h-screen text-white mb-[208px]">
<template v-if="!route.params.titlesSlug">
<template v-if="!route.params.chapter && !route.params.titlesSlug">
<!--верхний блок-->
<section
class="flex min-h-[600px] flex-row relative z-40 before:content-[''] before:absolute before:top-[-140px] before:bg-top before:left-0 before:w-[1280px] before:h-[1000px] before:bg-[url(/assets/img/webp/vino-galante.webp)] before:bg-no-repeat before:bg-contain mt-40"
@ -84,7 +84,10 @@
<div class="mt-28 pt-2">
<ul class="flex flex-row mr-32 items-baseline justify-between lg:whitespace-nowrap">
<li class="flex flex-row h-24 w-[105px] items-center">
<NuxtLink to="#" class="flex flex-col gap-8 items-center cursor-pointer">
<NuxtLink
:to="`/books/${route.params.slug}/chapters/${route.params.slug}/`"
class="flex flex-col gap-8 items-center cursor-pointer"
>
<img src="/img/svg/books/read.svg" alt="Читай отрывок" width="62" height="53" />
<UiParagraph size="250" as="span"> Читай отрывок </UiParagraph>

View File

@ -0,0 +1,175 @@
<template>
<div v-if="content" class="relative z-50 min-h-screen text-white mb-[208px]">
<section class="flex flex-col relative z-40 mt-40 ml-18 mr-18">
<div v-html="renderedContent" class="prose prose-invert max-w-none"></div>
</section>
</div>
<div v-else class="text-white text-center py-20">Глава не найдена.</div>
</template>
<script setup lang="ts">
// Получаем параметры из URL
const route = useRoute()
const bookSlug = route.params.slug as string
const chapterSlug = route.params.chapter as string
// Загружаем контент из markdown файла
const { data: content } = await useAsyncData(`book-${bookSlug}-${chapterSlug}`, () => {
return $fetch(`/api/content/books/${chapterSlug}`)
})
// Конвертируем markdown в HTML (простая реализация)
const renderedContent = computed(() => {
if (!content.value?.content) return ''
let html = content.value.content
// Заменяем заголовки
html = html.replace(
/^### (.*$)/gim,
'<h3 style="color: #f54b7e; font-weight: bold; margin-top: 1.5rem; margin-bottom: 0.5rem;">$1</h3>',
)
html = html.replace(
/^## (.*$)/gim,
'<h2 style="color: #f54b7e; font-weight: bold; margin-top: 2rem; margin-bottom: 1rem;">$1</h2>',
)
html = html.replace(
/^# (.*$)/gim,
'<h1 style="color: #f54b7e; font-weight: bold; font-size: 1.5em; margin-bottom: 1rem;">$1</h1>',
)
// Заменяем параграфы
html = html.replace(
/^&ensp;&ensp;(.*$)/gim,
'<p style="margin-bottom: 1rem; line-height: 1.6;">$1</p>',
)
// Заменяем жирный текст (двойные звездочки)
html = html.replace(/\*\*(.*?)\*\*/g, '<strong style=" font-weight: bold;">$1</strong>')
// Заменяем курсив (звездочки)
html = html.replace(/\*(.*?)\*/g, '<em style="font-style: italic;">$1</em>')
// Заменяем курсив (подчеркивания)
html = html.replace(/\_(.*?)\_/g, '<em style="font-style: italic;">$1</em>')
// Заменяем изображения - исправляем пути
html = html.replace(
/!\[([^\]]*)\]\(([^)]+)\)/g,
'<img src="/img/books/$2" alt="$1" style="max-width: 100%; height: auto; margin: 1rem 0; display: block;" />',
)
// Заменяем списки
html = html.replace(/^- (.*$)/gim, '<li style="margin-bottom: 0.5rem;">$1</li>')
html = html.replace(/(<li.*<\/li>)/s, '<ul style="margin-bottom: 1rem;">$1</ul>')
// Заменяем <br/> теги на HTML <br>
html = html.replace(/<br\/>/g, '<br>')
// Заменяем переносы строк
html = html.replace(/\n/g, '<br>')
return html
})
// Устанавливаем мета-теги
useHead({
title: `Глава ${chapterSlug} | Книга ${bookSlug} | Vino Galante`,
meta: [
{
name: 'description',
content: `Читайте главу ${chapterSlug} из книги ${bookSlug} автора Vino Galante`,
},
],
link: [
{
rel: 'canonical',
href: `https://ebook.miduway.space/books/${bookSlug}/chapters/${chapterSlug}`,
},
],
})
// Обработка ошибок, если файл не найден
if (!content.value) {
throw createError({
statusCode: 404,
statusMessage: 'Глава не найдена',
})
}
</script>
<style>
/* Дополнительные стили для контента */
.prose {
color: white;
max-width: none;
}
.prose h1 {
color: #f54b7e;
font-weight: bold;
font-size: 1.5em;
margin-bottom: 1rem;
}
.prose h2 {
color: #f54b7e;
font-weight: bold;
margin-top: 2rem;
margin-bottom: 1rem;
}
.prose h3 {
color: #f54b7e;
font-weight: bold;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
}
.prose p {
margin-bottom: 1rem;
line-height: 1.6;
}
.prose ul {
margin-bottom: 1rem;
}
.prose li {
margin-bottom: 0.5rem;
}
.prose strong {
font-weight: bold;
}
.prose em {
font-style: italic;
}
.prose blockquote {
border-left: 4px solid #f54b7e;
padding-left: 1rem;
margin: 1rem 0;
font-style: italic;
}
.prose img {
max-width: 100%;
height: auto;
margin: 1rem 0;
display: block;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
/* Стили для подписей к изображениям */
.prose img + br + em {
display: block;
text-align: center;
font-style: italic;
color: #f54b7e;
margin-top: 0.5rem;
margin-bottom: 1rem;
}
</style>

View File

@ -1,20 +1,12 @@
<template>
<div v-if="titles" class="relative z-50 min-h-screen text-white mb-[208px]">
<section class="flex flex-col relative z-40 mt-40 ml-18">
<UiHeading
tag="H1"
class="whitespace-pre-line [&]:font-normal mb-10 -ml-5"
size="500"
>
<UiHeading tag="H1" class="whitespace-pre-line [&]:font-normal mb-10 -ml-5" size="500">
{{ titles.title }}
</UiHeading>
<div class="flex flex-col gap-6">
<div
v-for="(section, index) in titles.sections"
:key="index"
class="flex flex-col gap-4"
>
<div v-for="(section, index) in titles.sections" :key="index" class="flex flex-col gap-4">
<!-- Main section title -->
<UiHeading tag="h2" size="300" class="text-three [&]:font-normal">
{{ section.title }}
@ -43,27 +35,14 @@
</UiHeading>
</div>
<!-- Regular subsection -->
<UiHeading
v-else
tag="H3"
size="300"
class="[&]:text-gray-200 [&]:font-normal"
>
<UiHeading v-else tag="H3" size="300" class="[&]:text-gray-200 [&]:font-normal">
{{ subsection.title }}
</UiHeading>
<!-- Items list -->
<ul
v-if="subsection.items"
class="ml-6 flex flex-col gap-2 list-decimal"
>
<li
v-for="(item, itemIndex) in subsection.items"
:key="itemIndex"
>
<UiParagraph
size="300"
class="[&]:text-gray-200 [&]:font-normal"
<ul v-if="subsection.items" class="ml-6 flex flex-col gap-2 list-decimal">
<li v-for="(item, itemIndex) in subsection.items" :key="itemIndex">
<UiParagraph size="300" class="[&]:text-gray-200 [&]:font-normal"
>&nbsp;{{ item }}</UiParagraph
>
</li>
@ -71,15 +50,8 @@
<!-- Nested subsections -->
<div v-if="subsection.subsections" class="flex flex-col gap-2">
<div
v-for="(nestedSub, nestedIndex) in subsection.subsections"
:key="nestedIndex"
>
<UiHeading
tag="H4"
size="300"
class="[&]:text-gray-200 [&]:font-normal"
>
<div v-for="(nestedSub, nestedIndex) in subsection.subsections" :key="nestedIndex">
<UiHeading tag="H4" size="300" class="[&]:text-gray-200 [&]:font-normal">
{{ nestedSub.title }}
</UiHeading>
</div>
@ -94,61 +66,60 @@
</template>
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { useRoute } from "vue-router";
import UiHeading from "@/components/Typography/UiHeading.vue";
import UiParagraph from "@/components/Typography/UiParagraph.vue";
// import { useHead } from '@vueuse/head'
import { ref, computed, watch } from 'vue'
import { useRoute } from 'vue-router'
import UiHeading from '@/components/Typography/UiHeading.vue'
import UiParagraph from '@/components/Typography/UiParagraph.vue'
interface SubsectionTitle {
text: string;
img?: string;
text: string
img?: string
}
interface Subsection {
title: string | SubsectionTitle;
items?: string[];
title: string | SubsectionTitle
items?: string[]
subsections?: Array<{
title: string;
}>;
title: string
}>
}
interface Section {
title: string;
subsections?: Subsection[];
title: string
subsections?: Subsection[]
}
interface TitlesData {
title: string;
titleMeta: string;
sections: Section[];
title: string
titleMeta: string
sections: Section[]
}
const route = useRoute();
const route = useRoute()
const currentTitlesData = ref<TitlesData | null>(null);
const currentTitlesData = ref<TitlesData | null>(null)
const titles = computed(() => currentTitlesData.value);
const titles = computed(() => currentTitlesData.value)
const loadTitlesData = async (slug: string) => {
try {
const module = await import(`./_data/${slug}.json`);
currentTitlesData.value = module.default as TitlesData;
const module = await import(`./_data/${slug}.json`)
currentTitlesData.value = module.default as TitlesData
} catch (error) {
console.error(`Ошибка при загрузке содержания с slug '${slug}':`, error);
currentTitlesData.value = null;
console.error(`Ошибка при загрузке содержания с slug '${slug}':`, error)
currentTitlesData.value = null
}
};
}
watch(
() => route.params.titlesSlug,
async (newSlug) => {
if (newSlug) {
await loadTitlesData(newSlug as string);
await loadTitlesData(newSlug as string)
}
},
{ immediate: true }
);
{ immediate: true },
)
watch(titles, (newTitles) => {
if (newTitles) {
@ -156,17 +127,17 @@ watch(titles, (newTitles) => {
title: `${newTitles.titleMeta} | Vino Galante`,
meta: [
{
name: "description",
content: "Содержание книги Vino Galante",
name: 'description',
content: 'Содержание книги Vino Galante',
},
],
link: [
{
rel: "canonical",
rel: 'canonical',
href: `https://ebook.miduway.space/books/${route.params.slug}/${route.params.titlesSlug}`,
},
],
});
})
}
});
})
</script>