This commit is contained in:
2026-01-03 22:21:55 +08:00
parent 77e1d5ed9e
commit f2f21218a8
4 changed files with 172 additions and 147 deletions

View File

@@ -16,9 +16,9 @@ withDefaults(defineProps<Props>(), {
title: "no-title", title: "no-title",
date: "no-date", date: "no-date",
description: "no-description", description: "no-description",
image: "/blogs-img/blog.jpg", image: getRandomFallbackImage(),
alt: "no-alt", alt: "no-alt",
ogImage: "/blogs-img/blog.jpg", ogImage: getRandomFallbackImage(),
tags: () => [], tags: () => [],
categories: () => [], categories: () => [],
published: false, published: false,
@@ -35,10 +35,10 @@ withDefaults(defineProps<Props>(), {
:src="image" :src="image"
:alt="alt" /> :alt="alt" />
</div> </div>
<div class="px-5 py-5 flex flex-col flex-grow"> <div class="px-5 py-5 flex flex-col grow">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<div <div
class="flex items-center text-sm font-medium text-zinc-500 dark:text-zinc-400 whitespace-nowrap flex-shrink-0"> class="flex items-center text-sm font-medium text-zinc-500 dark:text-zinc-400 whitespace-nowrap shrink-0">
<Icon name="mdi:calendar" class="mr-1.5 opacity-70 w-4 h-4" /> <Icon name="mdi:calendar" class="mr-1.5 opacity-70 w-4 h-4" />
{{ date }} {{ date }}
</div> </div>

View File

@@ -17,6 +17,12 @@ function toggleMenu() {
isMenuOpen.value = !isMenuOpen.value; isMenuOpen.value = !isMenuOpen.value;
} }
// 移动端子菜单展开状态
const mobileOpen = ref<Record<string, boolean>>({});
function toggleMobileSubmenu(path: string) {
mobileOpen.value[path] = !mobileOpen.value[path];
}
const isLinesOpen = ref(false); const isLinesOpen = ref(false);
function toggleLines() { function toggleLines() {
isLinesOpen.value = !isLinesOpen.value; isLinesOpen.value = !isLinesOpen.value;
@@ -66,6 +72,8 @@ watch(
() => { () => {
isMenuOpen.value = false; isMenuOpen.value = false;
isLinesOpen.value = false; isLinesOpen.value = false;
// 关闭所有移动端子菜单
mobileOpen.value = {};
}, },
); );
</script> </script>
@@ -170,22 +178,56 @@ watch(
<div <div
class="inline-flex items-center h-14 bg-white/70 dark:bg-slate-900/50 backdrop-blur-xl shadow-[0_4px_20px_-2px_rgba(0,0,0,0.1)] border border-zinc-200/50 dark:border-white/5 rounded-full px-1.5 transition-all duration-300 hover:shadow-xl"> class="inline-flex items-center h-14 bg-white/70 dark:bg-slate-900/50 backdrop-blur-xl shadow-[0_4px_20px_-2px_rgba(0,0,0,0.1)] border border-zinc-200/50 dark:border-white/5 rounded-full px-1.5 transition-all duration-300 hover:shadow-xl">
<ul class="flex items-center space-x-1.5 text-lg"> <ul class="flex items-center space-x-1.5 text-lg">
<li v-for="link in siteConfig.navbar.links" :key="link.path"> <template v-for="link in siteConfig.navbar.links" :key="link.path">
<NuxtLink <li v-if="!link.children">
:to="link.path" <NuxtLink
class="relative h-12 px-3 rounded-full transition-all duration-200 flex items-center text-zinc-700 dark:text-zinc-200" :to="link.path"
:class="{ class="relative h-12 px-3 rounded-full transition-all duration-200 flex items-center text-zinc-700 dark:text-zinc-200"
'bg-white dark:bg-slate-800 shadow-sm font-bold': isActive(link.path), :class="{
'hover:bg-zinc-100 dark:hover:bg-white/10': !isActive(link.path), 'bg-white dark:bg-slate-800 shadow-sm font-bold': isActive(link.path),
}"> 'hover:bg-zinc-100 dark:hover:bg-white/10': !isActive(link.path),
<Icon }">
v-if="link.icon" <Icon
:name="link.icon" v-if="link.icon"
size="20" :name="link.icon"
class="mr-2 flex items-center" /> size="20"
<span>{{ link.name }}</span> class="mr-2 flex items-center" />
</NuxtLink> <span>{{ link.name }}</span>
</li> </NuxtLink>
</li>
<li v-else class="relative group">
<div
class="relative h-12 px-3 rounded-full transition-all duration-200 flex items-center cursor-pointer text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-white/10"
:class="{
'bg-white dark:bg-slate-800 shadow-sm font-bold': isActive(link.path),
}">
<Icon
v-if="link.icon"
:name="link.icon"
size="20"
class="mr-2 flex items-center" />
<span>{{ link.name }}</span>
<Icon name="fa6-solid:chevron-down" size="14" class="ml-1 opacity-60" />
</div>
<ul
class="absolute left-0 top-full mt-0 min-w-32 bg-white dark:bg-slate-900 border border-zinc-200/50 dark:border-white/10 rounded-xl shadow-lg z-50 hidden group-hover:block hover:block pointer-events-auto">
<li v-for="child in link.children" :key="child.path">
<NuxtLink
:to="child.path"
class="block px-4 py-2 text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-white/10 rounded-xl transition-colors">
<div class="flex items-center gap-2">
<Icon
v-if="child.icon"
:name="child.icon"
size="14"
class="mr-2 opacity-60" />
<span>{{ child.name }}</span>
</div>
</NuxtLink>
</li>
</ul>
</li>
</template>
</ul> </ul>
</div> </div>
@@ -280,26 +322,80 @@ watch(
<div class="p-2"> <div class="p-2">
<ul class="space-y-1"> <ul class="space-y-1">
<li v-for="link in siteConfig.navbar.links" :key="link.path"> <li v-for="link in siteConfig.navbar.links" :key="link.path">
<NuxtLink <template v-if="!link.children">
:to="link.path" <NuxtLink
class="flex items-center justify-between px-4 py-3.5 rounded-2xl text-zinc-700 dark:text-zinc-200 transition-all active:scale-[0.98]" :to="link.path"
:class=" class="flex items-center justify-between px-4 py-3.5 rounded-2xl text-zinc-700 dark:text-zinc-200 transition-all active:scale-[0.98]"
isActive(link.path) :class="
? 'bg-violet-500/10 text-violet-600 dark:text-violet-400 font-bold' isActive(link.path)
: 'hover:bg-zinc-100 dark:hover:bg-white/5' ? 'bg-violet-500/10 text-violet-600 dark:text-violet-400 font-bold'
"> : 'hover:bg-zinc-100 dark:hover:bg-white/5'
<div class="flex items-center gap-3"> "
<div @click="isMenuOpen = false">
class="w-8 h-8 rounded-xl flex items-center justify-center transition-colors" <div class="flex items-center gap-3">
:class=" <div
isActive(link.path) ? 'bg-violet-500/20' : 'bg-zinc-100 dark:bg-white/5' class="w-8 h-8 rounded-xl flex items-center justify-center transition-colors"
"> :class="
<Icon v-if="link.icon" :name="link.icon" size="16" /> isActive(link.path) ? 'bg-violet-500/20' : 'bg-zinc-100 dark:bg-white/5'
">
<Icon v-if="link.icon" :name="link.icon" size="16" />
</div>
<span class="text-sm">{{ link.name }}</span>
</div> </div>
<span class="text-sm">{{ link.name }}</span> <Icon name="fa6-solid:chevron-right" size="10" class="opacity-30" />
</NuxtLink>
</template>
<template v-else>
<div
class="flex items-center justify-between px-4 py-3.5 rounded-2xl text-zinc-700 dark:text-zinc-200 transition-all active:scale-[0.98]"
:class="
isActive(link.path)
? 'bg-violet-500/10 text-violet-600 dark:text-violet-400 font-bold'
: 'hover:bg-zinc-100 dark:hover:bg-white/5'
"
@click="toggleMobileSubmenu(link.path)">
<div class="flex items-center gap-3">
<div
class="w-8 h-8 rounded-xl flex items-center justify-center transition-colors"
:class="
isActive(link.path) ? 'bg-violet-500/20' : 'bg-zinc-100 dark:bg-white/5'
">
<Icon v-if="link.icon" :name="link.icon" size="16" />
</div>
<span class="text-sm">{{ link.name }}</span>
</div>
<Icon
name="fa6-solid:chevron-right"
size="10"
:class="[
'opacity-30 transition-transform',
mobileOpen[link.path] ? 'rotate-90' : '',
]" />
</div> </div>
<Icon name="fa6-solid:chevron-right" size="10" class="opacity-30" />
</NuxtLink> <transition
enter-active-class="transition duration-200"
leave-active-class="transition duration-150">
<ul v-if="mobileOpen[link.path]" class="pl-12 pr-3 pb-2 pt-2 space-y-1">
<li v-for="child in link.children" :key="child.path">
<NuxtLink
:to="child.path"
class="block px-3 py-2 rounded-lg text-sm text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-white/5"
@click="isMenuOpen = false">
<div class="flex items-center gap-2">
<Icon
v-if="child.icon"
:name="child.icon"
size="14"
class="mr-2 opacity-60" />
<span>{{ child.name }}</span>
</div>
</NuxtLink>
</li>
</ul>
</transition>
</template>
</li> </li>
</ul> </ul>
</div> </div>
@@ -437,4 +533,33 @@ header .absolute.right-0 {
margin-right: 0.25rem !important; margin-right: 0.25rem !important;
} }
} }
/* 解决 navbar 子菜单 hover 闪烁问题,提升可用性 */
.navbar-dropdown-group {
position: relative;
}
.navbar-dropdown-toggle {
cursor: pointer;
}
.navbar-dropdown-list {
display: none;
position: absolute;
left: 0;
top: 100%;
margin-top: 0.5rem;
min-width: 8rem;
background: #fff;
border-radius: 0.75rem;
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.18);
z-index: 50;
border: 1px solid rgba(200, 200, 200, 0.15);
}
.dark .navbar-dropdown-list {
background: #18181b;
border-color: rgba(255, 255, 255, 0.08);
}
.navbar-dropdown-group:hover .navbar-dropdown-list,
.navbar-dropdown-list:hover {
display: block;
}
</style> </style>

View File

@@ -40,7 +40,15 @@ const siteConfig = {
links: [ links: [
// { name: "Home", path: "/", icon: "fa6-solid:house" }, // { name: "Home", path: "/", icon: "fa6-solid:house" },
{ name: "归档", path: "/archive", icon: "fa-solid:newspaper" }, { name: "归档", path: "/archive", icon: "fa-solid:newspaper" },
{ name: "分类", path: "/categories", icon: "fa-solid:folder" }, {
name: "分类",
path: "/categories",
icon: "fa-solid:folder",
// children: [
// { name: "前端", path: "/categories/frontend", icon: "fa-solid:code" },
// { name: "后端", path: "/categories/backend", icon: "fa-solid:server" },
// ],
},
{ name: "标签", path: "/tags", icon: "fa-solid:tags" }, { name: "标签", path: "/tags", icon: "fa-solid:tags" },
{ name: "关于", path: "/about", icon: "fa-solid:user" }, { name: "关于", path: "/about", icon: "fa-solid:user" },
], ],

View File

@@ -1,108 +0,0 @@
// export const navbarData = {
// homeTitle: "Riyad's Blog",
// };
// export const navItems = [
// { label: "Home", path: "/" },
// { label: "Archive", path: "/archive" },
// { label: "Categories", path: "/categories" },
// { label: "About", path: "/about" },
// ];
export const footerData = {
author: "Al Asad Nur Riyad",
aboutAuthor:
"Hi! I am Riyad, a Tech enthusiast, problem solver and software engineer. Currently working at FieldNation LLC.",
authorInterest:
"I have a fair amount of knowledge of Javascript, Typescript, VueJs, and Nuxt. If you have an interesting idea, either open source or paid let's connect.",
aboutTheSite:
"This is a personal blog site built with Nuxt3, TailwindCSS, NuxtContent, Nuxt Icon. Currently it's deployed in Vercel.",
};
// export const homePage = {
// title: "Welcome To My Blog Site",
// description:
// "Get Web Development, Javascript, Typescript, NodeJs, Vue, and Nuxt, Related Articles, Tips, Learning resources and more.",
// };
export const archivePage = {
title: "All Archives",
description: "Here you will find all the blog posts I have written & published on this site.",
};
export const categoryPage = {
title: "Categories",
description:
"Blow this category is generated from all the tags are mentioned in the different blog post",
};
export const aboutPage = {
title: "Al Asad Nur Riyad",
description: "Software Engineer, Problem Solver, Web Enthusiast.",
aboutMe:
"Hello, fellow human! I'm a software wizard who spends most of his day crafting code spells at @FieldNation in the Workplace Operation team. When I'm not crafting code, you can find me summoning solutions to problems on online judges. Just don't ask me to cast any love spells, my magic only works on machines!",
};
export const seoData = {
title: `Riyad's Blog | Riyads Blog`,
ogTitle: `Let's learn Javascript, Typescript, Vue, Nuxt, & Problem Solving - Riyads Blog | Riyad's Blog`,
description: `Hi I am Riyad. A Software Engineer at FieldNation, with over 3.5+ years experience in software development. - Riyads Blog | Riyad's Blog`,
twitterDescription: `Riyad's Blog, where I play around with Nuxt, Vue, and more and showcase my blog, resources, etc - Riyads Blog | Riyad's Blog`,
image:
"https://res.cloudinary.com/dmecmyphj/image/upload/v1673548905/nuxt-blog/cover_ntgs6u.webp",
mySite: "https://blog-nurriyad.vercel.app",
twitterHandle: "@qdnvubp",
mailAddress: "asadnurriyad@gmail.com",
};
// export const socialLinks = {
// githubLink: "https://github.com/nurRiyad",
// linkedinLink: "https://www.linkedin.com/in/nur-riyad/",
// twitterLink: "https://twitter.com/qdnvubp",
// stackoverflowLink: "https://stackoverflow.com/users/16781395/nur-riyad",
// };
export const siteMetaData = [
{
name: "description",
content: seoData.description,
},
// Test on: https://developers.facebook.com/tools/debug/ or https://socialsharepreview.com/
{ property: "og:site_name", content: seoData.mySite },
{ property: "og:type", content: "website" },
{
property: "og:url",
content: seoData.mySite,
},
{
property: "og:title",
content: seoData.ogTitle,
},
{
property: "og:description",
content: seoData.description,
},
{
property: "og:image",
content: seoData.image,
},
// Test on: https://cards-dev.twitter.com/validator or https://socialsharepreview.com/
{ name: "twitter:site", content: seoData.twitterHandle },
{ name: "twitter:card", content: "summary_large_image" },
{
name: "twitter:url",
content: seoData.mySite,
},
{
name: "twitter:title",
content: seoData.ogTitle,
},
{
name: "twitter:description",
content: seoData.twitterDescription,
},
{
name: "twitter:image",
content: seoData.image,
},
];