update
4
.gitignore
vendored
@@ -9,3 +9,7 @@ node_modules
|
|||||||
*.log
|
*.log
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
bun.lock
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
|||||||
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Rhen Cloud
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
20
LICENSE.md
@@ -1,20 +0,0 @@
|
|||||||
Copyright (c) 2012-2023 Scott Chacon and others
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
87
README.md
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
## 📂 项目结构
|
## 📂 项目结构
|
||||||
|
|
||||||
```
|
```plaintext
|
||||||
.
|
.
|
||||||
├── app/ # 应用根目录
|
├── app/ # 应用根目录
|
||||||
│ ├── assets/ # 静态资源
|
│ ├── assets/ # 静态资源
|
||||||
@@ -30,18 +30,16 @@
|
|||||||
│ │ └── images/ # 图片资源
|
│ │ └── images/ # 图片资源
|
||||||
│ ├── components/ # Vue 组件
|
│ ├── components/ # Vue 组件
|
||||||
│ │ ├── archive/ # 归档相关组件
|
│ │ ├── archive/ # 归档相关组件
|
||||||
│ │ ├── blog/ # 博客相关组件
|
│ │ ├── posts/ # 博客相关组件
|
||||||
│ │ ├── category/ # 分类相关组件
|
│ │ ├── category/ # 分类相关组件
|
||||||
│ │ ├── content/ # 内容组件
|
│ │ ├── content/ # 内容组件
|
||||||
│ │ ├── footer/ # 页脚组件
|
|
||||||
│ │ ├── logo/ # Logo 组件
|
|
||||||
│ │ └── main/ # 主要组件
|
│ │ └── main/ # 主要组件
|
||||||
│ ├── config/ # 配置文件
|
│ ├── config/ # 配置文件
|
||||||
│ ├── data/ # 数据文件
|
│ ├── data/ # 数据文件
|
||||||
│ ├── layouts/ # 布局组件
|
│ ├── layouts/ # 布局组件
|
||||||
│ ├── pages/ # 页面路由
|
│ ├── pages/ # 页面路由
|
||||||
│ │ ├── archive/ # 归档页面
|
│ │ ├── archive/ # 归档页面
|
||||||
│ │ ├── blogs/ # 博客列表页面
|
│ │ ├── posts/ # 博客列表页面
|
||||||
│ │ ├── categories/ # 分类页面
|
│ │ ├── categories/ # 分类页面
|
||||||
│ │ ├── tags/ # 标签页面
|
│ │ ├── tags/ # 标签页面
|
||||||
│ │ └── about.vue # 关于页面
|
│ │ └── about.vue # 关于页面
|
||||||
@@ -53,7 +51,7 @@
|
|||||||
│ └── router.options.ts # 路由配置
|
│ └── router.options.ts # 路由配置
|
||||||
├── content/ # 文章内容
|
├── content/ # 文章内容
|
||||||
│ ├── about.md # 关于页内容
|
│ ├── about.md # 关于页内容
|
||||||
│ └── blogs/ # 博客文章目录
|
│ └── posts/ # 博客文章目录
|
||||||
├── public/ # 公共资源
|
├── public/ # 公共资源
|
||||||
├── server/ # 服务端代码
|
├── server/ # 服务端代码
|
||||||
│ └── api/ # API 路由
|
│ └── api/ # API 路由
|
||||||
@@ -163,19 +161,6 @@ const siteConfig = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 主题配置
|
|
||||||
|
|
||||||
修改 `tailwind.config.js` 自定义主题色:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
theme: {
|
|
||||||
colors: {
|
|
||||||
primary: '#bd83f3', // 主题色
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 评论系统
|
### 评论系统
|
||||||
|
|
||||||
在 `app/config/index.ts` 配置 Twikoo:
|
在 `app/config/index.ts` 配置 Twikoo:
|
||||||
@@ -238,14 +223,10 @@ published: true
|
|||||||
项目内置完整的 SEO 优化:
|
项目内置完整的 SEO 优化:
|
||||||
|
|
||||||
- ✅ Meta 标签(title, description, keywords)
|
- ✅ Meta 标签(title, description, keywords)
|
||||||
- ✅ Open Graph 标签(社交分享)
|
|
||||||
- ✅ Twitter Card 标签
|
|
||||||
- ✅ hreflang 标签(多语言)
|
- ✅ hreflang 标签(多语言)
|
||||||
- ✅ Canonical 标签
|
- ✅ Canonical 标签
|
||||||
- ✅ Schema.org 结构化数据
|
|
||||||
- ✅ Sitemap 自动生成
|
- ✅ Sitemap 自动生成
|
||||||
- ✅ RSS 订阅源
|
- ✅ RSS 订阅源
|
||||||
- ✅ 动态 OG 图片
|
|
||||||
|
|
||||||
## 🔧 常用命令
|
## 🔧 常用命令
|
||||||
|
|
||||||
@@ -276,11 +257,11 @@ bun run format:fix # 格式化代码
|
|||||||
|
|
||||||
## 📄 许可证
|
## 📄 许可证
|
||||||
|
|
||||||
MIT License - 详见 [LICENSE.md](LICENSE.md)
|
MIT License - 详见 [LICENSE](LICENSE)
|
||||||
|
|
||||||
## 👤 作者
|
## 👤 作者
|
||||||
|
|
||||||
**RhenCloud**
|
## RhenCloud
|
||||||
|
|
||||||
- GitHub: [@RhenCloud](https://github.com/RhenCloud)
|
- GitHub: [@RhenCloud](https://github.com/RhenCloud)
|
||||||
- Email: <i@rhen.cloud>
|
- Email: <i@rhen.cloud>
|
||||||
@@ -309,59 +290,3 @@ MIT License - 详见 [LICENSE.md](LICENSE.md)
|
|||||||
- ✅ Sitemap 和 RSS 支持
|
- ✅ Sitemap 和 RSS 支持
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Made with ❤️ by RhenCloud**
|
|
||||||
|
|
||||||
- Blog list page with search and pagination
|
|
||||||
- About me page for user info
|
|
||||||
- Auto generate table of content for blog post
|
|
||||||
- Auto generate Sitemap
|
|
||||||
- Url preview with Nuxt ogImage
|
|
||||||
- Dark and light mode
|
|
||||||
- Server Side Rendered(SSR) with Nuxt4
|
|
||||||
- RSS feed
|
|
||||||
|
|
||||||
## How to Make This Blog Template Yours in 5 Minutes
|
|
||||||
|
|
||||||
- Clone this repo or use it as a template
|
|
||||||
- Go to `./app/data/index.ts` file & add your personal info
|
|
||||||
- Then head over to the `./content/blogs` folder to add new blogs
|
|
||||||
- Voilà! You've got a personalized blog site!
|
|
||||||
|
|
||||||
## Preview
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://blog.nurriyad.com" target="_blank">
|
|
||||||
<img width="1090" src="./app/assets/images/preview1.png">
|
|
||||||
<img width="1090" src="./app/assets/images/preview2.png">
|
|
||||||
<img width="1090" src="./app/assets/images/preview3.png">
|
|
||||||
<img width="1090" src="./app/assets/images/preview4.png">
|
|
||||||
<br>
|
|
||||||
Live Demo
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## Demo
|
|
||||||
|
|
||||||
<https://blog.nurriyad.com>
|
|
||||||
|
|
||||||
> Hosted on [Vercel](https://vercel.com/): `npm run build`
|
|
||||||
|
|
||||||
## Build Setup
|
|
||||||
|
|
||||||
**Requires Node.js 20.19+**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# install dependencies
|
|
||||||
yarn install
|
|
||||||
|
|
||||||
# serve in dev mode, with hot reload at localhost:5173
|
|
||||||
yarn run dev
|
|
||||||
|
|
||||||
# build for production
|
|
||||||
yarn run build
|
|
||||||
|
|
||||||
# serve in production mode
|
|
||||||
yarn run preview
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 389 KiB |
|
Before Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 279 KiB |
@@ -27,11 +27,11 @@ withDefaults(defineProps<Props>(), {
|
|||||||
</h2>
|
</h2>
|
||||||
<div class="text-black dark:text-zinc-300 text-sm mt-2 mb-1 md:flex md:space-x-6">
|
<div class="text-black dark:text-zinc-300 text-sm mt-2 mb-1 md:flex md:space-x-6">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<LogoDate class="-translate-y-[10%]" />
|
<Icon name="mdi:calendar" class="mr-1 w-4 h-4" />
|
||||||
<p>{{ date }}</p>
|
<p>{{ date }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1 flex-wrap">
|
<div class="flex items-center gap-1 flex-wrap">
|
||||||
<LogoTag />
|
<Icon name="mdi:tag" class="w-4 h-4" />
|
||||||
<p
|
<p
|
||||||
v-for="tag in tags"
|
v-for="tag in tags"
|
||||||
:key="tag"
|
:key="tag"
|
||||||
@@ -42,7 +42,7 @@ withDefaults(defineProps<Props>(), {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex group-hover:underline text-sky-700 dark:text-sky-400 items-center pt-2">
|
<div class="flex group-hover:underline text-sky-700 dark:text-sky-400 items-center pt-2">
|
||||||
<p>Read More</p>
|
<p>Read More</p>
|
||||||
<LogoArrow />
|
<Icon name="mdi:arrow-right" class="ml-1 w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import siteConfig from "~/config";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="container mx-auto mb-5">
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 items-center">
|
|
||||||
<div class="px-6">
|
|
||||||
<h1
|
|
||||||
class="text-black dark:text-zinc-300 font-semibold leading-tight text-4xl md:text-5xl my-5">
|
|
||||||
{{ siteConfig.siteMeta.title }}
|
|
||||||
</h1>
|
|
||||||
<p class="dark:text-zinc-300">
|
|
||||||
{{ siteConfig.siteMeta.description }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="px-6 justify-self-center">
|
|
||||||
<LogoDogpow />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -24,7 +24,7 @@ withDefaults(defineProps<Props>(), {
|
|||||||
<div class="flex items-center justify-center gap-3 mb-6">
|
<div class="flex items-center justify-center gap-3 mb-6">
|
||||||
<div
|
<div
|
||||||
class="flex items-center text-sm font-bold text-violet-600 dark:text-violet-400 bg-violet-500/10 px-3 py-1 rounded-full border border-violet-500/20">
|
class="flex items-center text-sm font-bold text-violet-600 dark:text-violet-400 bg-violet-500/10 px-3 py-1 rounded-full border border-violet-500/20">
|
||||||
<LogoDate class="mr-2 w-4 h-4" />
|
<Icon name="mdi:calendar" class="mr-2 w-4 h-4" />
|
||||||
{{ date }}
|
{{ date }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 flex-wrap">
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ withDefaults(defineProps<Props>(), {
|
|||||||
<div class="px-5 py-5">
|
<div class="px-5 py-5">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<div class="flex items-center text-xs font-medium text-zinc-500 dark:text-zinc-400">
|
<div class="flex items-center text-xs font-medium text-zinc-500 dark:text-zinc-400">
|
||||||
<LogoDate class="mr-1.5 opacity-70" />
|
<Icon name="mdi:calendar" class="mr-1.5 opacity-70 w-4 h-4" />
|
||||||
{{ date }}
|
{{ date }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1.5 flex-wrap">
|
<div class="flex items-center gap-1.5 flex-wrap">
|
||||||
@@ -59,7 +59,7 @@ withDefaults(defineProps<Props>(), {
|
|||||||
<div
|
<div
|
||||||
class="flex items-center gap-1 text-sm font-bold text-violet-600 dark:text-violet-400 opacity-0 group-hover:opacity-100 transition-all duration-300 translate-y-2 group-hover:translate-y-0">
|
class="flex items-center gap-1 text-sm font-bold text-violet-600 dark:text-violet-400 opacity-0 group-hover:opacity-100 transition-all duration-300 translate-y-2 group-hover:translate-y-0">
|
||||||
<span>Read More</span>
|
<span>Read More</span>
|
||||||
<LogoArrow class="w-4 h-4" />
|
<Icon name="mdi:arrow-right" class="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<article class="group border dark:border-gray-800 m-2 rounded-2xl overflow-hidden shadow-lg text-zinc-700">
|
<article
|
||||||
|
class="group border dark:border-gray-800 m-2 rounded-2xl overflow-hidden shadow-lg text-zinc-700 bg-white/40 dark:bg-slate-900/40 backdrop-blur-md">
|
||||||
<NuxtLink to="/">
|
<NuxtLink to="/">
|
||||||
<div class="lg:h-48 md:h-36 w-full object-cover object-center group-hover:scale-[1.05] transition-all duration-500">
|
<div
|
||||||
<LogoConfused />
|
class="lg:h-48 md:h-36 w-full bg-linear-to-br from-slate-200 to-slate-300 dark:from-slate-700 dark:to-slate-800 group-hover:scale-[1.05] transition-all duration-500 flex items-center justify-center">
|
||||||
|
<Icon name="mdi:folder-open" size="80" class="text-slate-400 dark:text-slate-500" />
|
||||||
</div>
|
</div>
|
||||||
<div class="p-5">
|
<div class="p-5">
|
||||||
<h2 class="text-3xl font-semibold text-black dark:text-zinc-300 pb-1 group-hover:text-sky-700 dark:group-hover:text-sky-400">
|
<h2
|
||||||
|
class="text-3xl font-semibold text-black dark:text-zinc-300 pb-1 group-hover:text-sky-700 dark:group-hover:text-sky-400">
|
||||||
No Post Available
|
No Post Available
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="flex group-hover:underline text-sky-700 dark:text-sky-400 items-center pt-2">
|
<div class="flex group-hover:underline text-sky-700 dark:text-sky-400 items-center pt-2">
|
||||||
<p>Back To Home</p>
|
<p>Back To Home</p>
|
||||||
<LogoArrow />
|
<Icon name="mdi:arrow-right" class="ml-1 w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</article>
|
</article>
|
||||||
</template>;
|
</template>
|
||||||
|
;
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
interface Props {
|
|
||||||
title: string;
|
|
||||||
count: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
|
||||||
title: "No title available",
|
|
||||||
count: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// some random color for tags
|
|
||||||
const color = [
|
|
||||||
"#dc2626",
|
|
||||||
"#d97706",
|
|
||||||
"#65a30d",
|
|
||||||
"#059669",
|
|
||||||
"#0891b2",
|
|
||||||
"#0284c7",
|
|
||||||
"#4f46e5",
|
|
||||||
"#7c3aed",
|
|
||||||
"#c026d3",
|
|
||||||
"#db2777",
|
|
||||||
];
|
|
||||||
|
|
||||||
// get a random number
|
|
||||||
function getRandomInt(min: number, max: number) {
|
|
||||||
min = Math.ceil(min);
|
|
||||||
max = Math.floor(max);
|
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
const picAColor = ref(`${color.at(getRandomInt(0, 8))}`);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="text-[#F1F2F4] px-5 py-3 rounded hover:underline rand-bg-color hover:scale-[1.05] transition-all duration-500">
|
|
||||||
<NuxtLink :to="`/categories/${title.toLocaleLowerCase()}`" class="text-lg font-extrabold">
|
|
||||||
<h1>#{{ title }}({{ count }})</h1>
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.rand-bg-color {
|
|
||||||
background-color: v-bind(picAColor);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { categoryPage } from "~/data";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="container mx-auto">
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 items-center">
|
|
||||||
<div class="px-6">
|
|
||||||
<h1
|
|
||||||
class="text-black dark:text-zinc-300 font-semibold leading-tight text-4xl md:text-5xl my-5">
|
|
||||||
{{ categoryPage.title }}
|
|
||||||
</h1>
|
|
||||||
<p class="dark:text-zinc-300">
|
|
||||||
{{ categoryPage.description }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="px-6 justify-self-center">
|
|
||||||
<LogoDogs />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
// take category from route params & make first char upper
|
|
||||||
const category = computed(() => {
|
|
||||||
const name = route.params.category || "";
|
|
||||||
let strName = "";
|
|
||||||
|
|
||||||
if (Array.isArray(name)) strName = name.at(0) || "";
|
|
||||||
else strName = name;
|
|
||||||
return strName;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="container mx-auto">
|
|
||||||
<div class="p-6 my-4 mx-2 rounded-md bg-gray-200 dark:bg-slate-900">
|
|
||||||
<h1 class="text-black dark:text-white font-semibold leading-tight text-xl md:text-2xl">
|
|
||||||
#{{ category }}
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -11,9 +11,15 @@ useHead({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="py-5">
|
<div class="py-20">
|
||||||
<div class="container max-w-xl mx-auto">
|
<div class="container max-w-xl mx-auto text-center">
|
||||||
<Logo404 />
|
<h1 class="text-6xl font-bold text-zinc-900 dark:text-white mb-4">404</h1>
|
||||||
|
<p class="text-xl text-zinc-600 dark:text-zinc-400 mb-8">页面未找到</p>
|
||||||
|
<NuxtLink
|
||||||
|
to="/"
|
||||||
|
class="inline-block bg-violet-600 hover:bg-violet-700 text-white font-semibold py-2 px-6 rounded-lg transition-colors">
|
||||||
|
返回首页
|
||||||
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import { formatDate, getRandomFallbackImage } from "~/utils/helper";
|
import { formatDate } from "~/utils/helper";
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
title: "Archive",
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
content: "浏览所有已发布的文章,轻松找到您感兴趣的内容。",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const { data } = await useAsyncData("all-archive-post", () =>
|
const { data } = await useAsyncData("all-archive-post", () =>
|
||||||
queryCollection("content").where("published", "=", true).order("date", "DESC").all(),
|
queryCollection("content").where("published", "=", true).order("date", "DESC").all(),
|
||||||
@@ -15,7 +25,7 @@ const posts = computed(() => {
|
|||||||
path: articles.path,
|
path: articles.path,
|
||||||
title: articles.title || "no-title available",
|
title: articles.title || "no-title available",
|
||||||
description: articles.description || "no-description available",
|
description: articles.description || "no-description available",
|
||||||
image: articles.image || getRandomFallbackImage(),
|
image: articles.image || "/404/dog.svg",
|
||||||
alt: articles.alt || "no alter data available",
|
alt: articles.alt || "no alter data available",
|
||||||
date: formatDate(articles.date) || "not-date-available",
|
date: formatDate(articles.date) || "not-date-available",
|
||||||
tags: articles.tags || [],
|
tags: articles.tags || [],
|
||||||
@@ -45,8 +55,34 @@ const searchResults = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="container max-w-5xl mx-auto text-zinc-600">
|
<main class="container max-w-5xl mx-auto text-zinc-600">
|
||||||
<h1 class="text-3xl font-bold my-6">Archive</h1>
|
<!-- Hero 部分 -->
|
||||||
|
<div class="w-full py-12 mb-6">
|
||||||
|
<div class="container max-w-5xl mx-auto px-6">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<NuxtLink
|
||||||
|
to="/"
|
||||||
|
class="flex items-center gap-2 text-sm font-bold text-violet-600 dark:text-violet-400 hover:underline mb-4">
|
||||||
|
<Icon name="heroicons:arrow-left-20-solid" />
|
||||||
|
返回首页
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="p-3 bg-violet-500/10 rounded-2xl mb-4">
|
||||||
|
<Icon
|
||||||
|
name="mdi:file-document-multiple"
|
||||||
|
size="2.5em"
|
||||||
|
class="text-violet-600 dark:text-violet-400" />
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="text-4xl md:text-5xl font-bold text-zinc-800 dark:text-zinc-100 mb-4 tracking-tight">
|
||||||
|
归档
|
||||||
|
</h1>
|
||||||
|
<p class="text-zinc-600 dark:text-zinc-400 text-center max-w-2xl">
|
||||||
|
浏览所有已发布的文章,轻松找到您感兴趣的内容。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索框 -->
|
||||||
<div class="px-6 mb-6">
|
<div class="px-6 mb-6">
|
||||||
<input
|
<input
|
||||||
v-model="searchTest"
|
v-model="searchTest"
|
||||||
@@ -55,6 +91,7 @@ const searchResults = computed(() => {
|
|||||||
class="block w-full bg-[#F1F2F4] dark:bg-slate-900 dark:placeholder-zinc-500 text-zinc-300 rounded-md border-gray-300 dark:border-gray-800 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
|
class="block w-full bg-[#F1F2F4] dark:bg-slate-900 dark:placeholder-zinc-500 text-zinc-300 rounded-md border-gray-300 dark:border-gray-800 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 文章列表 -->
|
||||||
<div v-auto-animate class="space-y-5 my-5 px-4">
|
<div v-auto-animate class="space-y-5 my-5 px-4">
|
||||||
<template v-for="post in searchResults" :key="post.title">
|
<template v-for="post in searchResults" :key="post.title">
|
||||||
<ArchiveCard
|
<ArchiveCard
|
||||||
|
|||||||
@@ -49,9 +49,26 @@ useHead({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="container max-w-5xl mx-auto text-zinc-600 px-4">
|
<main class="container max-w-5xl mx-auto text-zinc-600 px-4 py-12">
|
||||||
<CategoryTopic />
|
<div class="flex flex-col items-center mb-12">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
<NuxtLink
|
||||||
|
to="/categories"
|
||||||
|
class="flex items-center gap-2 text-sm font-bold text-blue-600 dark:text-blue-400 hover:underline mb-4">
|
||||||
|
<Icon name="heroicons:arrow-left-20-solid" />
|
||||||
|
返回分类
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="p-3 bg-blue-500/10 rounded-2xl mb-4">
|
||||||
|
<Icon name="mdi:folder-multiple" size="2.5em" class="text-blue-600 dark:text-blue-400" />
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="text-4xl md:text-5xl font-bold text-zinc-800 dark:text-zinc-100 mb-4 tracking-tight">
|
||||||
|
{{ category }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-zinc-600 dark:text-zinc-400 text-center">
|
||||||
|
找到 {{ formattedData.length || 0 }} 篇分类下的文章
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
<BlogCard
|
<BlogCard
|
||||||
v-for="post in formattedData"
|
v-for="post in formattedData"
|
||||||
:key="post.title"
|
:key="post.title"
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ const { data } = await useAsyncData("all-blog-post-by-category", () =>
|
|||||||
queryCollection("content").select("path", "categories").where("published", "=", true).all(),
|
queryCollection("content").select("path", "categories").where("published", "=", true).all(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const allTags = new Map();
|
const allCategories = new Map();
|
||||||
|
|
||||||
data.value?.forEach((blog) => {
|
data.value?.forEach((blog) => {
|
||||||
const categories: Array<string> = (blog.categories as string[]) || [];
|
const categories: Array<string> = (blog.categories as string[]) || [];
|
||||||
categories.forEach((category) => {
|
categories.forEach((category) => {
|
||||||
if (allTags.has(category)) {
|
if (allCategories.has(category)) {
|
||||||
const cnt = allTags.get(category);
|
const cnt = allCategories.get(category);
|
||||||
allTags.set(category, cnt + 1);
|
allCategories.set(category, cnt + 1);
|
||||||
} else {
|
} else {
|
||||||
allTags.set(category, 1);
|
allCategories.set(category, 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -22,8 +22,7 @@ useHead({
|
|||||||
meta: [
|
meta: [
|
||||||
{
|
{
|
||||||
name: "description",
|
name: "description",
|
||||||
content:
|
content: "以下列出了我撰写的文章主题。",
|
||||||
"Below All the topics are listed on which either I have written a blog or will write a blog in near future.",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -31,9 +30,42 @@ useHead({
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main class="container max-w-5xl mx-auto text-zinc-600">
|
<main class="container max-w-5xl mx-auto text-zinc-600">
|
||||||
<CategoryHero />
|
<div class="flex flex-col items-center mb-12 py-8">
|
||||||
|
<NuxtLink
|
||||||
|
to="/"
|
||||||
|
class="flex items-center gap-2 text-sm font-bold text-blue-600 dark:text-blue-400 hover:underline mb-4">
|
||||||
|
<Icon name="heroicons:arrow-left-20-solid" />
|
||||||
|
返回首页
|
||||||
|
</NuxtLink>
|
||||||
|
<div class="p-3 bg-blue-500/10 rounded-2xl mb-4">
|
||||||
|
<Icon name="mdi:folder-multiple" size="2.5em" class="text-blue-600 dark:text-blue-400" />
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
class="text-4xl md:text-5xl font-bold text-zinc-800 dark:text-zinc-100 mb-4 tracking-tight">
|
||||||
|
分类
|
||||||
|
</h1>
|
||||||
|
<p class="text-zinc-600 dark:text-zinc-400 text-center max-w-2xl">
|
||||||
|
以下列出了我撰写的文章主题。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div class="flex flex-wrap px-6 mt-12 gap-3">
|
<div class="flex flex-wrap px-6 mt-12 gap-3">
|
||||||
<CategoryCard v-for="topic in allTags" :key="topic[0]" :title="topic[0]" :count="topic[1]" />
|
<!-- <CategoryCard v-for="topic in allCategories" :key="topic[0]" :title="topic[0]" :count="topic[1]" /> -->
|
||||||
|
<div class="flex flex-wrap justify-center gap-4">
|
||||||
|
<NuxtLink
|
||||||
|
v-for="[category, count] in allCategories"
|
||||||
|
:key="category"
|
||||||
|
:to="`/categories/${category.toLowerCase()}`"
|
||||||
|
class="group relative flex items-center gap-2 px-6 py-3 rounded-2xl bg-white/40 dark:bg-slate-900/40 backdrop-blur-md border border-white/20 dark:border-white/5 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
|
||||||
|
<span
|
||||||
|
class="text-lg font-bold text-zinc-700 dark:text-zinc-200 group-text-primary transition-colors">
|
||||||
|
#{{ category }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="flex items-center justify-center min-w-6 h-6 px-1.5 rounded-full bg-primary-10 text-primary dark:text-primary text-xs font-bold border border-primary">
|
||||||
|
{{ count }}
|
||||||
|
</span>
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ useHead({
|
|||||||
to="/tags"
|
to="/tags"
|
||||||
class="flex items-center gap-2 text-sm font-bold text-primary hover:underline mb-4">
|
class="flex items-center gap-2 text-sm font-bold text-primary hover:underline mb-4">
|
||||||
<Icon name="heroicons:arrow-left-20-solid" />
|
<Icon name="heroicons:arrow-left-20-solid" />
|
||||||
Back to all tags
|
返回标签
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<div class="p-3 bg-primary-10 rounded-2xl mb-4">
|
<div class="p-3 bg-primary-10 rounded-2xl mb-4">
|
||||||
<Icon name="fa-solid:tag" size="2.5em" class="text-primary" />
|
<Icon name="fa-solid:tag" size="2.5em" class="text-primary" />
|
||||||
@@ -67,7 +67,7 @@ useHead({
|
|||||||
#{{ tag }}
|
#{{ tag }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-zinc-600 dark:text-zinc-400 text-center">
|
<p class="text-zinc-600 dark:text-zinc-400 text-center">
|
||||||
Found {{ formattedData.length || 0 }} posts with this tag
|
找到 {{ formattedData.length || 0 }} 篇关于此标签的文章
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ useHead({
|
|||||||
meta: [
|
meta: [
|
||||||
{
|
{
|
||||||
name: "description",
|
name: "description",
|
||||||
content: "浏览所有标签,浏览我写过的文章的标签。",
|
content: "浏览所有我写过的文章的标签。",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -38,10 +38,10 @@ useHead({
|
|||||||
</div>
|
</div>
|
||||||
<h1
|
<h1
|
||||||
class="text-4xl md:text-5xl font-bold text-zinc-800 dark:text-zinc-100 mb-4 tracking-tight">
|
class="text-4xl md:text-5xl font-bold text-zinc-800 dark:text-zinc-100 mb-4 tracking-tight">
|
||||||
All Tags
|
标签
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-zinc-600 dark:text-zinc-400 text-center max-w-md">
|
<p class="text-zinc-600 dark:text-zinc-400 text-center max-w-md">
|
||||||
Browse through all the topics and technologies I've written about.
|
浏览所有我写过的文章的标签。
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 712 KiB |
|
Before Width: | Height: | Size: 816 KiB |
|
Before Width: | Height: | Size: 679 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 4.2 KiB |
BIN
public/riyad.jpg
|
Before Width: | Height: | Size: 448 KiB |