Files
hossein-por-shop/frontend/pages/article/[id].vue
T
marzban-dev 3fcfc6e130 Complete article page responsive
Co-authored-by: Copilot <copilot@github.com>
2026-05-06 14:07:21 +03:30

304 lines
10 KiB
Vue

<script lang="ts" setup>
// import
import useGetArticle from "~/composables/api/blog/useGetArticle";
// state
const router = useRouter();
const route = useRoute();
const id = route.params.id as string | undefined;
const { data: article, suspense } = useGetArticle(id);
useSeoMeta({
title: `مقاله ${article.value?.title}`,
ogImage: article.value?.cover_image,
twitterImage: article.value?.cover_image,
ogDescription: article.value?.summery,
twitterDescription: article.value?.summery,
});
// ssr
const response = await suspense();
if (response.isError) {
throw createError({
statusCode: 500,
statusMessage: `Error in categories page prefetch`,
});
}
</script>
<template>
<div class="container">
<!-- Back Button -->
<div class="w-full flex mb-6 md:mb-8 mt-4 md:mt-8 px-4">
<button
@click="router.back()"
class="flex items-center gap-2 p-2 md:p-3 rounded-lg hover:bg-slate-100 transition-colors duration-200"
>
<span class="typo-label-sm md:typo-label-md text-blue-500">بازگشت</span>
<Icon
name="ci:arrow-left"
size="20px"
class="md:!w-6 md:!h-6 **:stroke-blue-500"
/>
</button>
</div>
<!-- Hero Section -->
<div class="w-full h-60 md:h-[70svh] lg:h-[80svh] rounded-2xl md:rounded-3xl relative overflow-hidden">
<NuxtImg
class="absolute object-cover size-full"
:alt="article!.title"
:src="article!.cover_image"
preload
loading="eager"
fetch-priority="high"
/>
<div class="absolute bg-linear-to-t from-black from-10% to-transparent size-full max-md:hidden" />
<!-- Hero Content Desktop -->
<div class="absolute inset-0 flex flex-col justify-end p-6 lg:p-10 gap-6 max-md:hidden">
<!-- Title -->
<h1 class="typo-h-4 text-white pl-8 line-clamp-3">
{{ article!.title }}
</h1>
<!-- Summary -->
<div
class="typo-p-lg text-slate-200 text-justify w-[80%]"
v-html="article!.summery"
/>
<!-- Info Section -->
<div class="flex gap-0 flex-row items-center justify-between">
<!-- Left Info -->
<div class="flex items-center gap-2 md:gap-4 flex-wrap">
<!-- Author Badge -->
<div
class="w-fit pr-2 pl-5 h-[50px] rounded-full flex items-center justify-center gap-3 bg-white"
>
<div
class="relative flex items-center justify-center rounded-full overflow-hidden size-[35px]"
>
<NuxtImg
class="size-full object-cover absolute"
:src="article!.author.profile_photo"
alt="article-author"
loading="lazy"
fetch-priority="low"
/>
</div>
<span class="typo-label-sm text-nowrap">
{{ article!.author.full_name }}
</span>
</div>
<!-- Category Badge -->
<div
class="w-fit pr-4 pl-5 h-[50px] rounded-full flex items-center justify-center gap-2 border-[1.5px] border-white text-white"
>
<span class="typo-label-sm text-nowrap">
{{ article?.category.name }}
</span>
</div>
</div>
<!-- Right Info -->
<div class="flex items-center gap-4 flex-wrap">
<!-- Date Badge -->
<div
class="w-fit pr-4 pl-5 h-[50px] rounded-full flex items-center justify-center gap-2 border-[1.5px] border-white text-white"
>
<Icon
name="ci:calendar"
size="24px"
class="**:stroke-white"
/>
<span class="typo-label-sm mt-0.5 text-nowrap">
{{ article?.created_at }}
</span>
</div>
<!-- Views Badge -->
<div
class="w-fit pr-4 pl-5 h-[50px] rounded-full flex items-center justify-center gap-2 border-[1.5px] border-white text-white"
>
<Icon
name="ci:eye-open"
size="24px"
class="**:stroke-white"
/>
<span class="typo-label-sm mt-0.5 text-nowrap">
{{ article!.views }}
</span>
</div>
</div>
</div>
</div>
</div>
<!-- Hero Content Mobile -->
<div class="flex flex-col justify-end py-4 px-2 gap-5 md:hidden mt-6">
<div class="flex items-center gap-2 flex-wrap">
<!-- Date Badge -->
<div
class="w-fit flex items-center justify-center gap-1 text-black bg-black/5 rounded-full py-1.5 px-3"
>
<Icon
name="ci:calendar"
size="18px"
class="**:stroke-black"
/>
<span class="typo-label-xs mt-0.5 text-nowrap"> {{ article?.created_at }} </span>
</div>
<!-- Views Badge -->
<div
class="w-fit flex items-center justify-center gap-1 text-black bg-black/5 rounded-full py-1.5 px-3"
>
<Icon
name="ci:eye-open"
size="18px"
class="**:stroke-black"
/>
<span class="typo-label-xs mt-0.5 text-nowrap">
{{ article!.views }}
</span>
</div>
</div>
<div class="flex flex-col gap-2">
<!-- Title -->
<h1 class="typo-h-6 text-black">
{{ article!.title }}
</h1>
<!-- Summary -->
<div
class="typo-p-sm text-slate-800 w-full md:w-[80%]"
v-html="article!.summery"
/>
</div>
<!-- Info Section -->
<div class="flex items-center gap-2 md:gap-4 flex-wrap">
<!-- Author Badge -->
<div
class="w-fit pr-2 pl-4 md:pr-2 md:pl-5 h-10 md:h-[50px] rounded-full flex items-center justify-center gap-2 md:gap-3 bg-black"
>
<div
class="relative flex items-center justify-center rounded-full overflow-hidden size-7 md:size-[35px]"
>
<NuxtImg
class="size-full object-cover absolute"
:src="article!.author.profile_photo"
alt="article-author"
loading="lazy"
fetch-priority="low"
/>
</div>
<span class="typo-label-xs md:typo-label-sm text-nowrap text-white">
{{ article!.author.full_name }}
</span>
</div>
<!-- Category Badge -->
<div
class="w-fit pr-3 pl-4 md:pr-4 md:pl-5 h-10 md:h-[50px] rounded-full flex items-center justify-center gap-1 md:gap-2 border-[1.5px] border-black text-black"
>
<span class="typo-label-xs md:typo-label-sm text-nowrap"> {{ article?.category.name }}</span>
</div>
</div>
</div>
<!-- Content Section -->
<div class="flex flex-col lg:flex-row gap-4 md:gap-6 mt-6 md:mt-12">
<!-- Article Content -->
<div
class="article-content flex-1 px-2 md:px-6 lg:px-8 text-zinc-800 flex flex-col gap-4 md:gap-6"
v-html="article!.content"
/>
<!-- Sidebar -->
<aside
class="w-full lg:w-[350px] p-4 md:p-6 lg:p-8 h-fit bg-slate-100 lg:sticky lg:top-4 rounded-2xl md:rounded-3xl mt-4 md:mt-0"
>
<p class="typo-label-md text-slate-700">مقالات مرتبط</p>
</aside>
</div>
</div>
</template>
<style>
.article-content :deep(h1) {
font-size: 32px;
font-weight: 600;
color: black;
}
.article-content :deep(h2) {
font-size: 24px;
font-weight: 600;
color: black;
}
.article-content :deep(h3) {
font-size: 24px;
font-weight: 300;
color: black;
}
.article-content :deep(p),
.article-content :deep(ul) {
font-size: 14px;
font-weight: 300;
}
.article-content :deep(ul) {
list-style: disc;
}
.article-content {
line-height: 200% !important;
font-size: 14px;
color: hsl(217, 33%, 17%);
}
.article-content :deep(ul li + li) {
margin-top: 0.5rem;
}
@media (min-width: 48rem) {
.article-content :deep(h1) {
font-size: 40px;
font-weight: 600;
}
.article-content :deep(h2) {
font-size: 32px;
font-weight: 600;
}
.article-content :deep(h3) {
font-size: 24px;
font-weight: 600;
}
.article-content :deep(p),
.article-content :deep(ul) {
font-size: 16px;
font-weight: 300;
}
.article-content {
font-size: 16px;
}
}
</style>