mirror of
https://github.com/RhenCloud/Cloud-Home.git
synced 2026-01-22 17:39:07 +08:00
style(app): 使用 NuxtImg 替代 img 标签
在多个组件中将 `img` 标签替换为 `NuxtImg` 标签,提升图片加载的性能和优化。例如,在 `AboutSection.vue`、`FriendsSection.vue`、`HeroSection.vue`、`ProjectsSection.vue`、`SitesSection.vue` 和 `SkillsSection.vue` 中的图片标签。 refactor(app): 扩展 `nuxt.config.ts` 配置 扩展了 `nuxt.config.ts` 配置文件中的模块配置,添加了 `@nuxt/image` 和 `@nuxt/eslint` 模块。同时,优化了 `routeRules` 配置,以支持预渲染和增量静态生成。
This commit is contained in:
@@ -1,18 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-shell" :style="backgroundStyle">
|
<div class="app-shell" :style="backgroundStyle">
|
||||||
<div class="background-overlay" :style="overlayStyle"></div>
|
<div class="background-overlay" :style="overlayStyle"/>
|
||||||
<button
|
<button
|
||||||
class="background-toggle"
|
class="background-toggle"
|
||||||
@click="hideComponents = !hideComponents"
|
|
||||||
:title="hideComponents ? '显示内容' : '隐藏内容'"
|
:title="hideComponents ? '显示内容' : '隐藏内容'"
|
||||||
:class="{ active: hideComponents }"
|
:class="{ active: hideComponents }"
|
||||||
|
@click="hideComponents = !hideComponents"
|
||||||
>
|
>
|
||||||
<span class="toggle-icon">{{ hideComponents ? "👁️" : "🙈" }}</span>
|
<span class="toggle-icon">{{ hideComponents ? "👁️" : "🙈" }}</span>
|
||||||
<span class="toggle-label">{{ hideComponents ? "显示" : "隐藏" }}</span>
|
<span class="toggle-label">{{ hideComponents ? "显示" : "隐藏" }}</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="content-stack">
|
<div class="content-stack">
|
||||||
<Transition name="fade-down">
|
<Transition name="fade-down">
|
||||||
<main class="app-body" v-if="!hideComponents" key="content">
|
<main v-if="!hideComponents" key="content" class="app-body">
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</main>
|
</main>
|
||||||
</Transition>
|
</Transition>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<PageSwitcher v-if="!hideComponents" key="switcher" />
|
<PageSwitcher v-if="!hideComponents" key="switcher" />
|
||||||
</Transition>
|
</Transition>
|
||||||
<Transition name="fade-down">
|
<Transition name="fade-down">
|
||||||
<FooterSection v-if="!hideComponents" :contact="contact" key="footer" />
|
<FooterSection v-if="!hideComponents" key="footer" :contact="contact" />
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
<MusicPlayer />
|
<MusicPlayer />
|
||||||
|
|||||||
@@ -4,8 +4,10 @@
|
|||||||
<p class="text-text-muted text-sm m-0 mb-3 block">关于我 · About Me</p>
|
<p class="text-text-muted text-sm m-0 mb-3 block">关于我 · About Me</p>
|
||||||
|
|
||||||
<div class="flex flex-wrap justify-center gap-3.5">
|
<div class="flex flex-wrap justify-center gap-3.5">
|
||||||
<article v-if="age"
|
<article
|
||||||
class="flex-1 min-w-[140px] flex items-center justify-between gap-2 bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2.5 px-3.5 shadow-md-dark transition-all duration-300 hover:-translate-y-1 hover:border-primary/40 hover:shadow-lg-dark hover:bg-gradient-to-br hover:from-primary/6">
|
v-if="age"
|
||||||
|
class="flex-1 min-w-[140px] flex items-center justify-between gap-2 bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2.5 px-3.5 shadow-md-dark transition-all duration-300 hover:-translate-y-1 hover:border-primary/40 hover:shadow-lg-dark hover:bg-gradient-to-br hover:from-primary/6"
|
||||||
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-xl leading-none">🎂</span>
|
<span class="text-xl leading-none">🎂</span>
|
||||||
<h3 class="m-0 text-sm font-semibold text-white/90">年龄</h3>
|
<h3 class="m-0 text-sm font-semibold text-white/90">年龄</h3>
|
||||||
@@ -15,8 +17,10 @@
|
|||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article v-if="profile?.gender"
|
<article
|
||||||
class="flex-1 min-w-[140px] flex items-center justify-between gap-2 bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2.5 px-3.5 shadow-md-dark transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-lg-dark">
|
v-if="profile?.gender"
|
||||||
|
class="flex-1 min-w-[140px] flex items-center justify-between gap-2 bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2.5 px-3.5 shadow-md-dark transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-lg-dark"
|
||||||
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-xl leading-none">⚧️</span>
|
<span class="text-xl leading-none">⚧️</span>
|
||||||
<h3 class="m-0 text-sm font-semibold text-white/90">性别</h3>
|
<h3 class="m-0 text-sm font-semibold text-white/90">性别</h3>
|
||||||
@@ -26,8 +30,10 @@
|
|||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article v-if="profile?.pronouns"
|
<article
|
||||||
class="flex-1 min-w-[140px] flex items-center justify-between gap-2 bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2.5 px-3.5 shadow-md-dark transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-lg-dark">
|
v-if="profile?.pronouns"
|
||||||
|
class="flex-1 min-w-[140px] flex items-center justify-between gap-2 bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2.5 px-3.5 shadow-md-dark transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-lg-dark"
|
||||||
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-xl leading-none">🗣️</span>
|
<span class="text-xl leading-none">🗣️</span>
|
||||||
<h3 class="m-0 text-sm font-semibold text-white/90">代词</h3>
|
<h3 class="m-0 text-sm font-semibold text-white/90">代词</h3>
|
||||||
@@ -37,8 +43,10 @@
|
|||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article v-if="profile?.location"
|
<article
|
||||||
class="flex-1 min-w-[140px] flex items-center justify-between gap-2 bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2.5 px-3.5 shadow-md-dark transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-lg-dark">
|
v-if="profile?.location"
|
||||||
|
class="flex-1 min-w-[140px] flex items-center justify-between gap-2 bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2.5 px-3.5 shadow-md-dark transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-lg-dark"
|
||||||
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-xl leading-none">📍</span>
|
<span class="text-xl leading-none">📍</span>
|
||||||
<h3 class="m-0 text-sm font-semibold text-white/90">地区</h3>
|
<h3 class="m-0 text-sm font-semibold text-white/90">地区</h3>
|
||||||
@@ -50,8 +58,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3.5 mt-2.5">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3.5 mt-2.5">
|
||||||
<article v-for="item in items" :key="item.title"
|
<article
|
||||||
class="bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-3 shadow-md-dark transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-lg-dark">
|
v-for="item in items"
|
||||||
|
:key="item.title"
|
||||||
|
class="bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-3 shadow-md-dark transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/40 hover:shadow-lg-dark"
|
||||||
|
>
|
||||||
<div class="flex items-center gap-2 mb-1.5">
|
<div class="flex items-center gap-2 mb-1.5">
|
||||||
<span class="text-2xl leading-none">{{ item.icon }}</span>
|
<span class="text-2xl leading-none">{{ item.icon }}</span>
|
||||||
<h3 class="m-0 text-base font-semibold">{{ item.title }}</h3>
|
<h3 class="m-0 text-base font-semibold">{{ item.title }}</h3>
|
||||||
@@ -66,8 +77,14 @@
|
|||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
items: Array,
|
items: {
|
||||||
profile: Object,
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const age = computed(() => {
|
const age = computed(() => {
|
||||||
|
|||||||
@@ -1,38 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<footer class="card text-center mt-auto w-full flex flex-col gap-1">
|
<footer class="card text-center mt-auto w-full flex flex-col gap-1">
|
||||||
<!-- 一言 -->
|
<!-- 一言 -->
|
||||||
<p class="text-text-muted text-sm m-0 italic" v-if="showHitokoto && quote">
|
<p v-if="showHitokoto && quote" class="text-text-muted text-sm m-0 italic">
|
||||||
「{{ quote }}」<span v-if="from" class="ml-1.5">—— {{ from }}</span>
|
「{{ quote }}」<span v-if="from" class="ml-1.5">—— {{ from }}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- 访问统计 -->
|
<!-- 访问统计 -->
|
||||||
<p class="text-text-muted text-xs m-0" v-if="showStats && !statsError">
|
<p v-if="showStats && !statsError" class="text-text-muted text-xs m-0">
|
||||||
👁️ {{ visitors }} · 📊 {{ pageviews }}
|
👁️ {{ visitors }} · 📊 {{ pageviews }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- 备案信息 -->
|
<!-- 备案信息 -->
|
||||||
<p class="text-text-muted text-xs m-0" v-if="contact.beian">
|
<p v-if="contact?.beian" class="text-text-muted text-xs m-0">
|
||||||
<a :href="contact.beianLink || 'https://beian.miit.gov.cn/'" target="_blank" rel="noreferrer"
|
<NuxtLink
|
||||||
class="opacity-85 transition-all duration-200 hover:text-primary hover:opacity-100">
|
:to="contact.beianLink || '/'"
|
||||||
|
class="opacity-85 transition-all duration-200 hover:text-primary hover:opacity-100"
|
||||||
|
>
|
||||||
{{ contact.beian }}
|
{{ contact.beian }}
|
||||||
</a>
|
</NuxtLink>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- 框架与技术栈信息 -->
|
<!-- 框架与技术栈信息 -->
|
||||||
<p class="text-text-muted text-xs m-0">
|
<p class="text-text-muted text-xs m-0">
|
||||||
Powered by
|
Powered by
|
||||||
<a href="https://nuxt.com" target="_blank" rel="noreferrer"
|
<a
|
||||||
class="text-primary hover:text-accent transition-colors">Nuxt 4</a>
|
href="https://nuxt.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
class="text-primary hover:text-accent transition-colors"
|
||||||
|
>Nuxt 4</a
|
||||||
|
>
|
||||||
·
|
·
|
||||||
<a href="https://tailwindcss.com" target="_blank" rel="noreferrer"
|
<a
|
||||||
class="text-primary hover:text-accent transition-colors">Tailwind CSS</a>
|
href="https://tailwindcss.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
class="text-primary hover:text-accent transition-colors"
|
||||||
|
>Tailwind CSS</a
|
||||||
|
>
|
||||||
·
|
·
|
||||||
<a href="https://vuejs.org" target="_blank" rel="noreferrer"
|
<a
|
||||||
class="text-primary hover:text-accent transition-colors">Vue 3</a>
|
href="https://vuejs.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
class="text-primary hover:text-accent transition-colors"
|
||||||
|
>Vue 3</a
|
||||||
|
>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- 自定义 HTML -->
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||||
<div v-if="contact.customHtml" v-html="contact.customHtml"></div>
|
<div v-if="contact?.customHtml" v-html="contact.customHtml" />
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -40,7 +57,7 @@
|
|||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { useRuntimeConfig } from "#imports";
|
import { useRuntimeConfig } from "#imports";
|
||||||
import siteConfig from "~/config/siteConfig";
|
import siteConfig from "~/config/siteConfig";
|
||||||
const props = defineProps({ contact: Object });
|
const contact = siteConfig.footer || {};
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const quote = ref("");
|
const quote = ref("");
|
||||||
const from = ref("");
|
const from = ref("");
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
<h2 class="m-0 mb-1 text-lg font-semibold">友情链接</h2>
|
<h2 class="m-0 mb-1 text-lg font-semibold">友情链接</h2>
|
||||||
<p class="text-text-muted text-sm m-0 mb-3 block">欢迎互换友链 · Friends</p>
|
<p class="text-text-muted text-sm m-0 mb-3 block">欢迎互换友链 · Friends</p>
|
||||||
<div class="grid grid-cols-1 gap-4 w-full max-w-[1100px] mx-auto sm:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 w-full max-w-[1100px] mx-auto sm:grid-cols-2">
|
||||||
<article v-for="f in displayedFriends" :key="f.url"
|
<article
|
||||||
|
v-for="f in displayedFriends" :key="f.url"
|
||||||
class="rounded-[14px] border border-white/10 bg-gradient-to-br from-white/5 to-white/0 px-4 py-3.5 transition-all duration-200 hover:-translate-y-[3px] hover:border-pink-400/50 w-[290px] h-[145px] flex flex-col">
|
class="rounded-[14px] border border-white/10 bg-gradient-to-br from-white/5 to-white/0 px-4 py-3.5 transition-all duration-200 hover:-translate-y-[3px] hover:border-pink-400/50 w-[290px] h-[145px] flex flex-col">
|
||||||
<div class="flex items-center justify-between mb-1.5">
|
<div class="flex items-center justify-between mb-1.5">
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
<div class="flex items-center gap-2 min-w-0">
|
||||||
<img v-if="f.avatar" :src="f.avatar" :alt="f.name" loading="lazy"
|
<NuxtImg
|
||||||
|
v-if="f.avatar" :src="f.avatar" :alt="f.name" loading="lazy"
|
||||||
class="w-12 h-12 rounded-full object-cover border border-white/15" />
|
class="w-12 h-12 rounded-full object-cover border border-white/15" />
|
||||||
<h3 class="m-0 font-semibold text-base whitespace-nowrap overflow-hidden text-ellipsis">
|
<h3 class="m-0 font-semibold text-base whitespace-nowrap overflow-hidden text-ellipsis">
|
||||||
{{ f.name }}
|
{{ f.name }}
|
||||||
@@ -20,31 +22,35 @@
|
|||||||
{{ f.desc || "一个有趣的站点" }}
|
{{ f.desc || "一个有趣的站点" }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a :href="f.url" target="_blank" rel="noreferrer"
|
<NuxtLink
|
||||||
|
:to="f.url"
|
||||||
class="inline-flex items-center gap-1.5 mt-auto shrink-0 font-semibold text-pink-300 hover:text-pink-400 transition-all duration-200 hover:gap-2">
|
class="inline-flex items-center gap-1.5 mt-auto shrink-0 font-semibold text-pink-300 hover:text-pink-400 transition-all duration-200 hover:gap-2">
|
||||||
访问 →
|
访问 →
|
||||||
</a>
|
</NuxtLink>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<section class="card flex flex-col gap-2.5">
|
<section class="card flex flex-col gap-2.5">
|
||||||
<div class="flex justify-center items-center align-center flex-wrap">
|
<div class="flex justify-center items-center align-center flex-wrap">
|
||||||
<button @click="openForm"
|
<button
|
||||||
class="px-3 py-2 rounded-2xl border border-primary/50 bg-primary/12 text-text-primary cursor-pointer transition-all duration-200 hover:bg-primary/20 hover:border-primary/80 hover:shadow-lg hover:shadow-primary/25">
|
class="px-3 py-2 rounded-2xl border border-primary/50 bg-primary/12 text-text-primary cursor-pointer transition-all duration-200 hover:bg-primary/20 hover:border-primary/80 hover:shadow-lg hover:shadow-primary/25"
|
||||||
|
@click="openForm">
|
||||||
申请友链
|
申请友链
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div v-if="showDialog" class="fixed inset-0 bg-black/45 backdrop-blur-sm flex items-center justify-center z-50"
|
<div
|
||||||
|
v-if="showDialog" class="fixed inset-0 bg-black/45 backdrop-blur-sm flex items-center justify-center z-50"
|
||||||
@click.self="closeDialog">
|
@click.self="closeDialog">
|
||||||
<div
|
<div
|
||||||
class="min-w-[280px] max-w-[420px] bg-gradient-to-br from-pink-500/12 to-white/8 border border-white/15 rounded-2xl p-4 shadow-xl">
|
class="min-w-[280px] max-w-[420px] bg-gradient-to-br from-pink-500/12 to-white/8 border border-white/15 rounded-2xl p-4 shadow-xl">
|
||||||
<h3 class="m-0 mb-2">{{ dialogTitle }}</h3>
|
<h3 class="m-0 mb-2">{{ dialogTitle }}</h3>
|
||||||
<p class="text-text-muted text-sm mb-4">{{ dialogText }}</p>
|
<p class="text-text-muted text-sm mb-4">{{ dialogText }}</p>
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<button @click="closeDialog"
|
<button
|
||||||
class="px-3 py-2 rounded-2xl border border-primary/50 bg-primary/12 text-text-primary cursor-pointer hover:bg-primary/20 transition-all">
|
class="px-3 py-2 rounded-2xl border border-primary/50 bg-primary/12 text-text-primary cursor-pointer hover:bg-primary/20 transition-all"
|
||||||
|
@click="closeDialog">
|
||||||
好的
|
好的
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +60,8 @@
|
|||||||
|
|
||||||
<!-- 申请友链模态弹窗 -->
|
<!-- 申请友链模态弹窗 -->
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div v-if="showFormModal"
|
<div
|
||||||
|
v-if="showFormModal"
|
||||||
class="fixed inset-0 bg-black/45 backdrop-blur-sm flex items-center justify-center z-50"
|
class="fixed inset-0 bg-black/45 backdrop-blur-sm flex items-center justify-center z-50"
|
||||||
@click.self="showFormModal = false">
|
@click.self="showFormModal = false">
|
||||||
<div
|
<div
|
||||||
@@ -63,55 +70,64 @@
|
|||||||
|
|
||||||
<div class="mb-4 text-sm text-text-primary">
|
<div class="mb-4 text-sm text-text-primary">
|
||||||
<div class="mb-2 font-semibold">请在申请前在你站点添加以下信息(示例 JSON):</div>
|
<div class="mb-2 font-semibold">请在申请前在你站点添加以下信息(示例 JSON):</div>
|
||||||
<pre
|
<pre class="bg-white/6 border border-white/10 rounded-lg p-3 text-xs overflow-auto">
|
||||||
class="bg-white/6 border border-white/10 rounded-lg p-3 text-xs overflow-auto"><code>{{ exampleJson }}</code></pre>
|
<code>{{ exampleJson }}</code>
|
||||||
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form @submit.prevent="submitForm" class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<form class="grid grid-cols-1 sm:grid-cols-2 gap-3" @submit.prevent="submitForm">
|
||||||
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold sm:col-span-2">
|
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold sm:col-span-2">
|
||||||
网站名称 *
|
网站名称 *
|
||||||
<input v-model="form.name" required placeholder="网站名称"
|
<input
|
||||||
class="px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary focus:outline-none" />
|
v-model="form.name" required placeholder="网站名称"
|
||||||
|
class="px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary focus:outline-none" >
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<!-- URL 与 Email 同行 -->
|
<!-- URL 与 Email 同行 -->
|
||||||
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold">
|
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold">
|
||||||
网站链接 *
|
网站链接 *
|
||||||
<input v-model="form.url" type="url" required placeholder="https://example.com"
|
<input
|
||||||
class="px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary focus:outline-none" />
|
v-model="form.url" type="url" required placeholder="https://example.com"
|
||||||
|
class="px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary focus:outline-none" >
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold">
|
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold">
|
||||||
联系邮箱 *
|
联系邮箱 *
|
||||||
<input v-model="form.email" type="email" required placeholder="example@example.com"
|
<input
|
||||||
class="px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary focus:outline-none" />
|
v-model="form.email" type="email" required placeholder="example@example.com"
|
||||||
|
class="px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary focus:outline-none" >
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<!-- 描述 与 头像 同行 -->
|
<!-- 描述 与 头像 同行 -->
|
||||||
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold">
|
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold">
|
||||||
网站描述
|
网站描述
|
||||||
<input v-model="form.desc" placeholder="可选"
|
<input
|
||||||
class="px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary focus:outline-none" />
|
v-model="form.desc" placeholder="可选"
|
||||||
|
class="px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary focus:outline-none" >
|
||||||
</label>
|
</label>
|
||||||
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold">
|
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold">
|
||||||
头像链接
|
头像链接
|
||||||
<input v-model="form.avatar" type="url" placeholder="可选,展示头像"
|
<input
|
||||||
class="px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary focus:outline-none" />
|
v-model="form.avatar" type="url" placeholder="可选,展示头像"
|
||||||
|
class="px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary focus:outline-none" >
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold sm:col-span-2">
|
<label class="flex flex-col gap-1 text-sm text-text-primary font-semibold sm:col-span-2">
|
||||||
想说的话
|
想说的话
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<textarea v-model="form.message" placeholder="可选,最多50字" maxlength="50"
|
<textarea
|
||||||
class="flex-1 px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary h-24 resize-none"></textarea>
|
v-model="form.message" placeholder="可选,最多50字" maxlength="50"
|
||||||
|
class="flex-1 px-2.5 py-2 rounded-xl border border-white/20 bg-white/8 text-text-primary h-24 resize-none"/>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="sm:col-span-2 flex items-center justify-center gap-3 mt-2">
|
<div class="sm:col-span-2 flex items-center justify-center gap-3 mt-2">
|
||||||
<button type="button" @click="showFormModal = false"
|
<button
|
||||||
class="px-3 py-2 rounded-2xl border border-white/10 bg-white/6">
|
type="button" class="px-3 py-2 rounded-2xl border border-white/10 bg-white/6"
|
||||||
|
@click="showFormModal = false">
|
||||||
取消
|
取消
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" :disabled="loading"
|
<button
|
||||||
|
type="submit" :disabled="loading"
|
||||||
class="px-3 py-2 rounded-2xl border border-primary/50 bg-primary/12 text-text-primary disabled:opacity-50">
|
class="px-3 py-2 rounded-2xl border border-primary/50 bg-primary/12 text-text-primary disabled:opacity-50">
|
||||||
{{ loading ? "提交中..." : "提交" }}
|
{{ loading ? "提交中..." : "提交" }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -4,25 +4,32 @@
|
|||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<h3 class="m-0 mb-1">提交热力图</h3>
|
<h3 class="m-0 mb-1">提交热力图</h3>
|
||||||
<p class="text-text-muted text-sm m-0 mb-3 block">我的提交热力图 · Acitivity Heatmap</p>
|
<p class="text-text-muted text-sm m-0 mb-3 block">我的提交热力图 · Acitivity Heatmap</p>
|
||||||
<img :src="github.heatmapUrl" alt="GitHub Heatmap" loading="lazy"
|
<NuxtImg
|
||||||
|
:src="github.heatmapUrl"
|
||||||
|
alt="GitHub Heatmap"
|
||||||
|
loading="lazy"
|
||||||
class="rounded-xl border border-white/10 hover:border-primary/30 transition-all duration-200"
|
class="rounded-xl border border-white/10 hover:border-primary/30 transition-all duration-200"
|
||||||
class="w-full rounded-2xl border border-white/10" />
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<h3 class="m-0 mb-1">常用语言</h3>
|
<h3 class="m-0 mb-1">常用语言</h3>
|
||||||
<p class="text-text-muted text-sm m-0 mb-3 block">我常用的语言 · Languages</p>
|
<p class="text-text-muted text-sm m-0 mb-3 block">我常用的语言 · Languages</p>
|
||||||
<ul class="list-none p-0 m-0 flex flex-col gap-2.5">
|
<ul class="list-none p-0 m-0 flex flex-col gap-2.5">
|
||||||
<li v-for="lang in topLanguages" :key="lang.name"
|
<li
|
||||||
class="bg-white/5 border border-white/10 rounded-xl p-2.5">
|
v-for="lang in topLanguages"
|
||||||
|
:key="lang.name"
|
||||||
|
class="bg-white/5 border border-white/10 rounded-xl p-2.5"
|
||||||
|
>
|
||||||
<div class="flex items-center gap-2 font-semibold mb-1.5">
|
<div class="flex items-center gap-2 font-semibold mb-1.5">
|
||||||
<span class="w-2.5 h-2.5 rounded-full inline-block"
|
<span
|
||||||
:style="{ background: colorFor(lang.name) }"></span>
|
class="w-2.5 h-2.5 rounded-full inline-block"
|
||||||
|
:style="{ background: colorFor(lang.name) }"
|
||||||
|
/>
|
||||||
<span class="text-text-primary">{{ lang.name }}</span>
|
<span class="text-text-primary">{{ lang.name }}</span>
|
||||||
<span class="text-text-muted text-sm">{{ lang.percent }}%</span>
|
<span class="text-text-muted text-sm">{{ lang.percent }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-2 rounded-full bg-white/5 overflow-hidden">
|
<div class="h-2 rounded-full bg-white/5 overflow-hidden">
|
||||||
<span class="block h-full rounded-full transition-all duration-300"
|
<span class="block h-full rounded-full transition-all duration-300" :style="barStyle(lang)" />
|
||||||
:style="barStyle(lang)"></span>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -32,7 +39,14 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
const props = defineProps({ github: Object });
|
const props = defineProps({
|
||||||
|
github: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({ languages: [] }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const github = props.github;
|
const github = props.github;
|
||||||
|
|
||||||
const palette = ["#7cc1ff", "#6bdba6", "#ffd166", "#f497da", "#9b8cfc", "#5ce1e6", "#ffa3a3"];
|
const palette = ["#7cc1ff", "#6bdba6", "#ffd166", "#f497da", "#9b8cfc", "#5ce1e6", "#ffa3a3"];
|
||||||
|
|||||||
@@ -2,10 +2,14 @@
|
|||||||
<section class="card grid grid-cols-[120px_1fr] gap-4 items-center hover:shadow-lg-dark group">
|
<section class="card grid grid-cols-[120px_1fr] gap-4 items-center hover:shadow-lg-dark group">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div
|
<div
|
||||||
class="absolute inset-0 rounded-full bg-gradient-to-br from-primary/30 to-accent/20 blur-xl group-hover:blur-2xl transition-all duration-300 opacity-0 group-hover:opacity-100">
|
class="absolute inset-0 rounded-full bg-gradient-to-br from-primary/30 to-accent/20 blur-xl group-hover:blur-2xl transition-all duration-300 opacity-0 group-hover:opacity-100"
|
||||||
</div>
|
/>
|
||||||
<img class="relative w-30 h-30 rounded-full object-cover border-2 border-primary/40 shadow-md-dark bg-white transition-transform duration-300 group-hover:scale-105"
|
<NuxtImg
|
||||||
:src="profile.avatar" alt="avatar" loading="lazy" />
|
class="relative w-30 h-30 rounded-full object-cover border-2 border-primary/40 shadow-md-dark bg-white transition-transform duration-300 group-hover:scale-105"
|
||||||
|
:src="profile.avatar"
|
||||||
|
alt="avatar"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<h1 class="text-2xl font-bold">{{ profile.name }}</h1>
|
<h1 class="text-2xl font-bold">{{ profile.name }}</h1>
|
||||||
@@ -16,5 +20,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps({ profile: Object });
|
import siteConfig from "../config/siteConfig";
|
||||||
|
|
||||||
|
const { profile } = defineProps({
|
||||||
|
profile: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => siteConfig.profile || {},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="music.enable && (music.playlistId || music.songId)" class="netease-mini-player"
|
<div
|
||||||
|
v-if="music.enable && (music.playlistId || music.songId)" class="netease-mini-player"
|
||||||
:data-playlist-id="music.mode === 'floating' ? music.playlistId : undefined"
|
:data-playlist-id="music.mode === 'floating' ? music.playlistId : undefined"
|
||||||
:data-song-id="music.mode === 'embed' ? music.songId : undefined" :data-embed="music.mode === 'embed'"
|
:data-song-id="music.mode === 'embed' ? music.songId : undefined" :data-embed="music.mode === 'embed'"
|
||||||
:data-position="music.position" :data-lyric="music.lyric" :data-theme="music.theme"
|
:data-position="music.position" :data-lyric="music.lyric" :data-theme="music.theme"
|
||||||
:data-autoplay="music.autoplay" :data-default-minimized="music.defaultMinimized"
|
:data-autoplay="music.autoplay" :data-default-minimized="music.defaultMinimized"
|
||||||
:data-auto-pause="music.autoPause" :data-api-urls="JSON.stringify(music.apiUrls)"></div>
|
:data-auto-pause="music.autoPause" :data-api-urls="JSON.stringify(music.apiUrls)"/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="my-4 mx-auto max-w-3xl w-full px-4 py-3 grid grid-cols-[auto_1fr_auto] gap-3 items-center">
|
<div class="my-4 mx-auto max-w-3xl w-full px-4 py-3 grid grid-cols-[auto_1fr_auto] gap-3 items-center">
|
||||||
<button :disabled="currentIndex <= 0" @click="goPrev"
|
<button
|
||||||
class="bg-white/10 text-text-primary border border-white/15 rounded-2xl px-3 py-2 cursor-pointer transition-all duration-200 hover:bg-primary/20 hover:border-primary/40 hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed">
|
:disabled="currentIndex <= 0" class="bg-white/10 text-text-primary border border-white/15 rounded-2xl px-3 py-2 cursor-pointer transition-all duration-200 hover:bg-primary/20 hover:border-primary/40 hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
@click="goPrev">
|
||||||
上一页
|
上一页
|
||||||
</button>
|
</button>
|
||||||
<div class="flex gap-2 flex-wrap justify-center">
|
<div class="flex gap-2 flex-wrap justify-center">
|
||||||
<button v-for="item in pages" :key="item.name" :class="{
|
<button
|
||||||
|
v-for="item in pages" :key="item.name" :class="{
|
||||||
'bg-primary/30 border-primary/60 text-primary shadow-lg shadow-primary/25':
|
'bg-primary/30 border-primary/60 text-primary shadow-lg shadow-primary/25':
|
||||||
item.name === route.name,
|
item.name === route.name,
|
||||||
}" @click="router.push({ name: item.name })"
|
}" class="px-2.5 py-2 bg-white/10 text-text-primary border border-white/15 rounded-2xl cursor-pointer transition-all duration-200 hover:bg-white/15 hover:border-primary/40"
|
||||||
class="px-2.5 py-2 bg-white/10 text-text-primary border border-white/15 rounded-2xl cursor-pointer transition-all duration-200 hover:bg-white/15 hover:border-primary/40">
|
@click="router.push({ name: item.name })">
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button :disabled="currentIndex >= pages.length - 1" @click="goNext"
|
<button
|
||||||
class="bg-white/10 text-text-primary border border-white/15 rounded-2xl px-3 py-2 cursor-pointer transition-all duration-200 hover:bg-primary/20 hover:border-primary/40 hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed">
|
:disabled="currentIndex >= pages.length - 1" class="bg-white/10 text-text-primary border border-white/15 rounded-2xl px-3 py-2 cursor-pointer transition-all duration-200 hover:bg-primary/20 hover:border-primary/40 hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
@click="goNext">
|
||||||
下一页
|
下一页
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,8 +3,11 @@
|
|||||||
<h2 class="m-0 mb-1 text-lg font-semibold">项目作品</h2>
|
<h2 class="m-0 mb-1 text-lg font-semibold">项目作品</h2>
|
||||||
<p class="text-sm text-white/60 mb-3">一些正在维护或已发布的项目 · Projects</p>
|
<p class="text-sm text-white/60 mb-3">一些正在维护或已发布的项目 · Projects</p>
|
||||||
<div class="grid grid-cols-1 gap-4 w-full max-w-[1100px] mx-auto sm:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 w-full max-w-[1100px] mx-auto sm:grid-cols-2">
|
||||||
<article v-for="p in projects" :key="p.url"
|
<article
|
||||||
class="rounded-[14px] border border-white/10 bg-gradient-to-br from-white/5 to-white/0 px-4 py-3.5 transition-all duration-200 hover:-translate-y-[3px] hover:border-yellow-400/50 w-[290px] h-[145px] flex flex-col">
|
v-for="p in projects"
|
||||||
|
:key="p.url"
|
||||||
|
class="rounded-[14px] border border-white/10 bg-gradient-to-br from-white/5 to-white/0 px-4 py-3.5 transition-all duration-200 hover:-translate-y-[3px] hover:border-yellow-400/50 w-[290px] h-[145px] flex flex-col"
|
||||||
|
>
|
||||||
<div class="flex items-center justify-between mb-1.5">
|
<div class="flex items-center justify-between mb-1.5">
|
||||||
<h3 class="font-medium truncate">
|
<h3 class="font-medium truncate">
|
||||||
{{ p.name }}
|
{{ p.name }}
|
||||||
@@ -17,10 +20,12 @@
|
|||||||
{{ p.desc }}
|
{{ p.desc }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a :href="p.url" target="_blank" rel="noreferrer"
|
<NuxtLink
|
||||||
class="inline-flex items-center gap-1.5 mt-auto shrink-0 font-semibold text-yellow-300 hover:text-yellow-400 transition-all duration-200 hover:gap-2">
|
:to="p.url"
|
||||||
|
class="inline-flex items-center gap-1.5 mt-auto shrink-0 font-semibold text-yellow-300 hover:text-yellow-400 transition-all duration-200 hover:gap-2"
|
||||||
|
>
|
||||||
查看仓库 →
|
查看仓库 →
|
||||||
</a>
|
</NuxtLink>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -28,6 +33,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps({
|
defineProps({
|
||||||
projects: Array,
|
projects: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,8 +5,11 @@
|
|||||||
<p class="text-sm text-white/60 mb-3">正在运行的站点 · Websites</p>
|
<p class="text-sm text-white/60 mb-3">正在运行的站点 · Websites</p>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-4 w-full max-w-[1100px] mx-auto sm:grid-cols-2">
|
<div class="grid grid-cols-1 gap-4 w-full max-w-[1100px] mx-auto sm:grid-cols-2">
|
||||||
<article v-for="site in sites" :key="site.url"
|
<article
|
||||||
class="rounded-[14px] border border-white/10 bg-gradient-to-br from-white/5 to-white/0 px-4 py-3.5 transition-all duration-200 hover:-translate-y-[3px] hover:border-blue-400/50 w-[290px] h-[145px] flex flex-col">
|
v-for="site in sites"
|
||||||
|
:key="site.url"
|
||||||
|
class="rounded-[14px] border border-white/10 bg-gradient-to-br from-white/5 to-white/0 px-4 py-3.5 transition-all duration-200 hover:-translate-y-[3px] hover:border-blue-400/50 w-[290px] h-[145px] flex flex-col"
|
||||||
|
>
|
||||||
<div class="flex items-center justify-between mb-1.5">
|
<div class="flex items-center justify-between mb-1.5">
|
||||||
<h3 class="font-medium truncate">
|
<h3 class="font-medium truncate">
|
||||||
{{ site.name }}
|
{{ site.name }}
|
||||||
@@ -19,10 +22,12 @@
|
|||||||
{{ site.desc }}
|
{{ site.desc }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a :href="site.url" target="_blank" rel="noreferrer"
|
<NuxtLink
|
||||||
class="inline-flex items-center gap-1.5 mt-auto shrink-0 font-semibold text-blue-300 hover:text-blue-400 transition-all duration-200 hover:gap-2">
|
:to="site.url"
|
||||||
|
class="inline-flex items-center gap-1.5 mt-auto shrink-0 font-semibold text-blue-300 hover:text-blue-400 transition-all duration-200 hover:gap-2"
|
||||||
|
>
|
||||||
查看 →
|
查看 →
|
||||||
</a>
|
</NuxtLink>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -30,6 +35,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps({
|
defineProps({
|
||||||
sites: Array,
|
sites: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,15 +5,18 @@
|
|||||||
<p class="text-text-muted text-sm m-0 mb-3">我常用的工具与技术 · Skills & Technologies</p>
|
<p class="text-text-muted text-sm m-0 mb-3">我常用的工具与技术 · Skills & Technologies</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
<article v-for="group in skills" :key="group.title"
|
<article
|
||||||
|
v-for="group in skills" :key="group.title"
|
||||||
class="bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-3.5 shadow-md-dark transition-all duration-300 hover:-translate-y-1 hover:border-primary/50 hover:shadow-lg-dark hover:bg-gradient-to-br hover:from-primary/8">
|
class="bg-gradient-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-3.5 shadow-md-dark transition-all duration-300 hover:-translate-y-1 hover:border-primary/50 hover:shadow-lg-dark hover:bg-gradient-to-br hover:from-primary/8">
|
||||||
<header class="mb-3">
|
<header class="mb-3">
|
||||||
<h3 class="text-base font-semibold m-0">{{ group.title }}</h3>
|
<h3 class="text-base font-semibold m-0">{{ group.title }}</h3>
|
||||||
</header>
|
</header>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span v-for="item in group.items" :key="item"
|
<span
|
||||||
|
v-for="item in group.items" :key="item"
|
||||||
class="inline-flex items-center p-1.5 rounded-2xl bg-primary/14 border border-primary/18 transition-all duration-200 hover:bg-primary/24 hover:border-primary/40 hover:scale-110">
|
class="inline-flex items-center p-1.5 rounded-2xl bg-primary/14 border border-primary/18 transition-all duration-200 hover:bg-primary/24 hover:border-primary/40 hover:scale-110">
|
||||||
<img :src="iconSrc(item)" :alt="item" :title="item" loading="lazy"
|
<NuxtImg
|
||||||
|
:src="iconSrc(item)" :alt="item" :title="item" loading="lazy"
|
||||||
class="w-7 h-7 rounded-2xl" />
|
class="w-7 h-7 rounded-2xl" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,21 +3,36 @@
|
|||||||
<h2 class="m-0 mb-1 text-lg font-semibold">社交链接</h2>
|
<h2 class="m-0 mb-1 text-lg font-semibold">社交链接</h2>
|
||||||
<p class="text-text-muted text-sm m-0 mb-3 block">社交账号 · Links</p>
|
<p class="text-text-muted text-sm m-0 mb-3 block">社交账号 · Links</p>
|
||||||
<div class="flex flex-wrap gap-2.5">
|
<div class="flex flex-wrap gap-2.5">
|
||||||
<a v-for="link in links" :key="link.url" :href="link.url" target="_blank" rel="noreferrer"
|
<template v-for="link in links" :key="link.url">
|
||||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-white/10 backdrop-blur-sm border border-white/10 text-text-primary text-sm font-medium transition-all duration-200 hover:bg-primary/20 hover:border-primary/40 hover:text-primary hover:-translate-y-1">
|
<NuxtLink
|
||||||
|
:to="link.url"
|
||||||
|
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-white/10 backdrop-blur-sm border border-white/10 text-text-primary text-sm font-medium transition-all duration-200 hover:bg-primary/20 hover:border-primary/40 hover:text-primary hover:-translate-y-1"
|
||||||
|
>
|
||||||
<span v-if="iconFor(link)" class="inline-flex items-center justify-center w-5 h-5">
|
<span v-if="iconFor(link)" class="inline-flex items-center justify-center w-5 h-5">
|
||||||
<i v-if="iconFor(link).fa" :class="iconFor(link).fa"></i>
|
<i v-if="iconFor(link).fa" :class="iconFor(link).fa" />
|
||||||
<img v-else :src="iconFor(link).src" :alt="link.name" loading="lazy" class="w-full h-full" />
|
<NuxtImg
|
||||||
|
v-else
|
||||||
|
:src="iconFor(link).src"
|
||||||
|
:alt="link.name"
|
||||||
|
loading="lazy"
|
||||||
|
class="w-full h-full"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span>{{ link.name }}</span>
|
<span>{{ link.name }}</span>
|
||||||
</a>
|
</NuxtLink>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted } from "vue";
|
import { onMounted } from "vue";
|
||||||
const props = defineProps({ links: Array });
|
defineProps({
|
||||||
|
links: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const iconMap = {
|
const iconMap = {
|
||||||
bilibili: "fa-brands fa-bilibili",
|
bilibili: "fa-brands fa-bilibili",
|
||||||
|
|||||||
@@ -6,8 +6,11 @@
|
|||||||
<button class="tab-button" :class="{ active: activeTab === 'github' }" @click="activeTab = 'github'">
|
<button class="tab-button" :class="{ active: activeTab === 'github' }" @click="activeTab = 'github'">
|
||||||
GitHub
|
GitHub
|
||||||
</button>
|
</button>
|
||||||
<button class="tab-button" :class="{ active: activeTab === 'wakatime' }"
|
<button
|
||||||
@click="activeTab = 'wakatime'">
|
class="tab-button"
|
||||||
|
:class="{ active: activeTab === 'wakatime' }"
|
||||||
|
@click="activeTab = 'wakatime'"
|
||||||
|
>
|
||||||
Wakatime
|
Wakatime
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,7 +21,7 @@
|
|||||||
<div class="heatmap">
|
<div class="heatmap">
|
||||||
<h3>提交热力图</h3>
|
<h3>提交热力图</h3>
|
||||||
<p class="muted">我的提交热力图 · Activity Heatmap</p>
|
<p class="muted">我的提交热力图 · Activity Heatmap</p>
|
||||||
<img :src="github.heatmapUrl" alt="GitHub Heatmap" loading="lazy" />
|
<NuxtImg :src="github.heatmapUrl" alt="GitHub Heatmap" loading="lazy" />
|
||||||
</div>
|
</div>
|
||||||
<div class="lang-wrap">
|
<div class="lang-wrap">
|
||||||
<h3>常用语言</h3>
|
<h3>常用语言</h3>
|
||||||
@@ -27,12 +30,12 @@
|
|||||||
<ul class="list lang-list">
|
<ul class="list lang-list">
|
||||||
<li v-for="lang in githubLanguages" :key="lang.name" class="lang-row">
|
<li v-for="lang in githubLanguages" :key="lang.name" class="lang-row">
|
||||||
<div class="lang-label">
|
<div class="lang-label">
|
||||||
<span class="dot" :style="{ background: colorFor(lang.name, 'github') }"></span>
|
<span class="dot" :style="{ background: colorFor(lang.name, 'github') }" />
|
||||||
<span class="lang-name">{{ lang.name }}</span>
|
<span class="lang-name">{{ lang.name }}</span>
|
||||||
<span class="lang-percent">{{ lang.percent }}%</span>
|
<span class="lang-percent">{{ lang.percent }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="lang-bar">
|
<div class="lang-bar">
|
||||||
<span class="lang-bar-fill" :style="barStyle(lang, 'github')"></span>
|
<span class="lang-bar-fill" :style="barStyle(lang, 'github')" />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -67,45 +70,51 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="lang-wrap" v-if="currentWakatimeData?.languages && currentWakatimeData.languages.length">
|
<div v-if="currentWakatimeData?.languages && currentWakatimeData.languages.length" class="lang-wrap">
|
||||||
<h3>编程语言</h3>
|
<h3>编程语言</h3>
|
||||||
<p class="muted">语言使用统计 · Languages</p>
|
<p class="muted">语言使用统计 · Languages</p>
|
||||||
<div class="lang-chart">
|
<div class="lang-chart">
|
||||||
<ul class="list lang-list">
|
<ul class="list lang-list">
|
||||||
<li v-for="lang in wakatimeLanguages" :key="lang.name" class="lang-row">
|
<li v-for="lang in wakatimeLanguages" :key="lang.name" class="lang-row">
|
||||||
<div class="lang-label">
|
<div class="lang-label">
|
||||||
<span class="dot" :style="{ background: colorFor(lang.name, 'wakatime') }"></span>
|
<span class="dot" :style="{ background: colorFor(lang.name, 'wakatime') }" />
|
||||||
<span class="lang-name">{{ lang.name }}</span>
|
<span class="lang-name">{{ lang.name }}</span>
|
||||||
<span class="lang-percent">{{ lang.percent }}%</span>
|
<span class="lang-percent">{{ lang.percent }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="lang-bar">
|
<div class="lang-bar">
|
||||||
<span class="lang-bar-fill" :style="barStyle(lang, 'wakatime')"></span>
|
<span class="lang-bar-fill" :style="barStyle(lang, 'wakatime')" />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wakatime-tabs" v-if="allTimeData">
|
<div v-if="allTimeData" class="wakatime-tabs">
|
||||||
<div class="wakatime-mini-tabs">
|
<div class="wakatime-mini-tabs">
|
||||||
<button class="wakatime-tab-button" :class="{ active: wakatimeActiveTab === 'weekly' }"
|
<button
|
||||||
@click="wakatimeActiveTab = 'weekly'">
|
class="wakatime-tab-button"
|
||||||
|
:class="{ active: wakatimeActiveTab === 'weekly' }"
|
||||||
|
@click="wakatimeActiveTab = 'weekly'"
|
||||||
|
>
|
||||||
最近7天
|
最近7天
|
||||||
</button>
|
</button>
|
||||||
<button class="wakatime-tab-button" :class="{ active: wakatimeActiveTab === 'allTime' }"
|
<button
|
||||||
@click="wakatimeActiveTab = 'allTime'">
|
class="wakatime-tab-button"
|
||||||
|
:class="{ active: wakatimeActiveTab === 'allTime' }"
|
||||||
|
@click="wakatimeActiveTab = 'allTime'"
|
||||||
|
>
|
||||||
所有时间
|
所有时间
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-wrap" v-if="statusData">
|
<div v-if="statusData" class="status-wrap">
|
||||||
<h3>当前状态</h3>
|
<h3>当前状态</h3>
|
||||||
<p class="muted">实时状态 · Current Status</p>
|
<p class="muted">实时状态 · Current Status</p>
|
||||||
<div class="status-item">
|
<div class="status-item">
|
||||||
<span class="status-indicator" :class="{ active: statusData.is_coding }"></span>
|
<span class="status-indicator" :class="{ active: statusData.is_coding }" />
|
||||||
<span class="status-text">{{ statusData.is_coding ? "正在编码" : "未在编码" }}</span>
|
<span class="status-text">{{ statusData.is_coding ? "正在编码" : "未在编码" }}</span>
|
||||||
<span class="status-project" v-if="statusData.project">{{ statusData.project }}</span>
|
<span v-if="statusData.project" class="status-project">{{ statusData.project }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,7 +124,16 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from "vue";
|
import { ref, onMounted, computed } from "vue";
|
||||||
|
|
||||||
const props = defineProps({ github: Object, wakatime: Object });
|
const props = defineProps({
|
||||||
|
github: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
wakatime: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
const github = props.github;
|
const github = props.github;
|
||||||
const wakatime = props.wakatime;
|
const wakatime = props.wakatime;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="card" v-if="showComponent && (weeklyData || allTimeData)">
|
<section v-if="showComponent && (weeklyData || allTimeData)" class="card">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h2>Wakatime</h2>
|
<h2>Wakatime</h2>
|
||||||
<div class="tabs">
|
<div class="tabs">
|
||||||
<button class="tab-button" :class="{ active: activeTab === 'weekly' }" @click="activeTab = 'weekly'">
|
<button class="tab-button" :class="{ active: activeTab === 'weekly' }" @click="activeTab = 'weekly'">
|
||||||
最近7天
|
最近7天
|
||||||
</button>
|
</button>
|
||||||
<button class="tab-button" :class="{ active: activeTab === 'allTime' }" @click="activeTab = 'allTime'"
|
<button
|
||||||
v-if="allTimeData">
|
v-if="allTimeData"
|
||||||
|
class="tab-button"
|
||||||
|
:class="{ active: activeTab === 'allTime' }"
|
||||||
|
@click="activeTab = 'allTime'"
|
||||||
|
>
|
||||||
所有时间
|
所有时间
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,32 +40,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="lang-wrap" v-if="currentData.languages && currentData.languages.length">
|
<div v-if="currentData.languages && currentData.languages.length" class="lang-wrap">
|
||||||
<h3>编程语言</h3>
|
<h3>编程语言</h3>
|
||||||
<p class="muted">语言使用统计 · Languages</p>
|
<p class="muted">语言使用统计 · Languages</p>
|
||||||
<div class="lang-chart">
|
<div class="lang-chart">
|
||||||
<ul class="list lang-list">
|
<ul class="list lang-list">
|
||||||
<li v-for="lang in topLanguages" :key="lang.name" class="lang-row">
|
<li v-for="lang in topLanguages" :key="lang.name" class="lang-row">
|
||||||
<div class="lang-label">
|
<div class="lang-label">
|
||||||
<span class="dot" :style="{ background: colorFor(lang.name) }"></span>
|
<span class="dot" :style="{ background: colorFor(lang.name) }" />
|
||||||
<span class="lang-name">{{ lang.name }}</span>
|
<span class="lang-name">{{ lang.name }}</span>
|
||||||
<span class="lang-percent">{{ lang.percent }}%</span>
|
<span class="lang-percent">{{ lang.percent }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="lang-bar">
|
<div class="lang-bar">
|
||||||
<span class="lang-bar-fill" :style="barStyle(lang)"></span>
|
<span class="lang-bar-fill" :style="barStyle(lang)" />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="status-wrap" v-if="statusData">
|
<div v-if="statusData" class="status-wrap">
|
||||||
<h3>当前状态</h3>
|
<h3>当前状态</h3>
|
||||||
<p class="muted">实时状态 · Current Status</p>
|
<p class="muted">实时状态 · Current Status</p>
|
||||||
<div class="status-item">
|
<div class="status-item">
|
||||||
<span class="status-indicator" :class="{ active: statusData.is_coding }"></span>
|
<span class="status-indicator" :class="{ active: statusData.is_coding }" />
|
||||||
<span class="status-text">{{ statusData.is_coding ? "正在编码" : "未在编码" }}</span>
|
<span class="status-text">{{ statusData.is_coding ? "正在编码" : "未在编码" }}</span>
|
||||||
<span class="status-project" v-if="statusData.project">{{ statusData.project }}</span>
|
<span v-if="statusData.project" class="status-project">{{ statusData.project }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -70,7 +74,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from "vue";
|
import { ref, onMounted, computed } from "vue";
|
||||||
|
|
||||||
const props = defineProps({ wakatime: Object });
|
const props = defineProps({
|
||||||
|
wakatime: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: () => ({ languages: [] }),
|
||||||
|
},
|
||||||
|
});
|
||||||
const wakatime = props.wakatime;
|
const wakatime = props.wakatime;
|
||||||
|
|
||||||
const weeklyData = ref(null);
|
const weeklyData = ref(null);
|
||||||
@@ -79,7 +89,7 @@ const statusData = ref(null);
|
|||||||
const showComponent = ref(true);
|
const showComponent = ref(true);
|
||||||
const activeTab = ref("weekly");
|
const activeTab = ref("weekly");
|
||||||
|
|
||||||
const palette = ["#7cc1ff", "#6bdba6", "#ffd166", "#f497da", "#9b8cfc", "#5ce1e6", "#ffa3a3"];
|
const palette = ["#7cc1ff", "#6bdba6", "#ffd166", "#201a1fff", "#9b8cfc", "#5ce1e6", "#ffa3a3"];
|
||||||
|
|
||||||
const currentData = computed(() => {
|
const currentData = computed(() => {
|
||||||
return activeTab.value === "weekly" ? weeklyData.value : allTimeData.value;
|
return activeTab.value === "weekly" ? weeklyData.value : allTimeData.value;
|
||||||
|
|||||||
@@ -6,11 +6,22 @@
|
|||||||
<h2 class="m-0 mb-1 text-lg font-semibold">留言板</h2>
|
<h2 class="m-0 mb-1 text-lg font-semibold">留言板</h2>
|
||||||
<p class="text-sm text-white/60 mb-3">在这里留下想说的话吧 · Comments</p>
|
<p class="text-sm text-white/60 mb-3">在这里留下想说的话吧 · Comments</p>
|
||||||
<div class="giscus-wrapper">
|
<div class="giscus-wrapper">
|
||||||
<component v-if="GiscusComponent" :is="GiscusComponent" :repo="giscus.repo" :repo-id="giscus.repoId"
|
<component
|
||||||
:category="giscus.category" :category-id="giscus.categoryId" :mapping="giscus.mapping"
|
:is="GiscusComponent"
|
||||||
:strict="giscus.strict" :reactions-enabled="giscus.reactionsEnabled"
|
v-if="GiscusComponent"
|
||||||
:emit-metadata="giscus.emitMetadata" :input-position="giscus.inputPosition"
|
:repo="giscus.repo"
|
||||||
:theme="'/css/giscus.css'" lang="zh-CN" class="giscus" />
|
:repo-id="giscus.repoId"
|
||||||
|
:category="giscus.category"
|
||||||
|
:category-id="giscus.categoryId"
|
||||||
|
:mapping="giscus.mapping"
|
||||||
|
:strict="giscus.strict"
|
||||||
|
:reactions-enabled="giscus.reactionsEnabled"
|
||||||
|
:emit-metadata="giscus.emitMetadata"
|
||||||
|
:input-position="giscus.inputPosition"
|
||||||
|
:theme="'/css/giscus.css'"
|
||||||
|
lang="zh-CN"
|
||||||
|
class="giscus"
|
||||||
|
/>
|
||||||
<div v-else id="giscus-container" class="giscus" />
|
<div v-else id="giscus-container" class="giscus" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -21,10 +32,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { definePageMeta } from "#imports";
|
import { definePageMeta } from "#imports";
|
||||||
import { onMounted, shallowRef, markRaw } from "vue";
|
import { onMounted, shallowRef, markRaw } from "vue";
|
||||||
|
import type { Component } from "vue";
|
||||||
import siteConfig from "~/config/siteConfig";
|
import siteConfig from "~/config/siteConfig";
|
||||||
|
|
||||||
const giscus = siteConfig.comments.giscus || {};
|
const giscus = siteConfig.comments.giscus || {};
|
||||||
const GiscusComponent = shallowRef(null as any);
|
const GiscusComponent = shallowRef<Component | null>(null);
|
||||||
|
|
||||||
async function tryUseOfficialComponent() {
|
async function tryUseOfficialComponent() {
|
||||||
try {
|
try {
|
||||||
@@ -38,12 +50,14 @@ async function tryUseOfficialComponent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 如果没有配置 giscus,显示提示(由模板处理)
|
// 如果没有配置 giscus,显示提示(由模板处理)
|
||||||
if (!siteConfig.comments.enable) return;
|
if (!siteConfig.comments.enable) return;
|
||||||
if (!giscus || !giscus.repo) return;
|
if (!giscus || !giscus.repo) return;
|
||||||
const ok = await tryUseOfficialComponent();
|
const ok = await tryUseOfficialComponent();
|
||||||
|
if (!ok) {
|
||||||
|
console.error("Failed to load Giscus component.");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
import { defineNuxtPlugin } from "#app";
|
import { defineNuxtPlugin } from "#app";
|
||||||
import siteConfig from "~/config/siteConfig";
|
import siteConfig from "~/config/siteConfig";
|
||||||
|
|
||||||
|
type NeteaseMiniPlayerGlobal = {
|
||||||
|
init?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NeteaseWindow = Window & {
|
||||||
|
NeteaseMiniPlayer?: NeteaseMiniPlayerGlobal;
|
||||||
|
__NETEASE_MUSIC_CONFIG__?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
export default defineNuxtPlugin(() => {
|
export default defineNuxtPlugin(() => {
|
||||||
if (import.meta.server) return;
|
if (import.meta.server) return;
|
||||||
|
|
||||||
@@ -35,7 +44,7 @@ export default defineNuxtPlugin(() => {
|
|||||||
const ensureScript = () =>
|
const ensureScript = () =>
|
||||||
new Promise<void>((resolve) => {
|
new Promise<void>((resolve) => {
|
||||||
// 检查全局对象是否已存在,表示脚本已加载
|
// 检查全局对象是否已存在,表示脚本已加载
|
||||||
const anyWin = window as any;
|
const anyWin = window as NeteaseWindow;
|
||||||
if (anyWin.NeteaseMiniPlayer) {
|
if (anyWin.NeteaseMiniPlayer) {
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
@@ -62,7 +71,7 @@ export default defineNuxtPlugin(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const initPlayer = () => {
|
const initPlayer = () => {
|
||||||
const anyWin = window as any;
|
const anyWin = window as NeteaseWindow;
|
||||||
|
|
||||||
// 将 siteConfig 的音乐配置传递给全局 window 对象
|
// 将 siteConfig 的音乐配置传递给全局 window 对象
|
||||||
if (!anyWin.__NETEASE_MUSIC_CONFIG__) {
|
if (!anyWin.__NETEASE_MUSIC_CONFIG__) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { Router } from "vue-router";
|
|||||||
import siteConfig from "~/config/siteConfig";
|
import siteConfig from "~/config/siteConfig";
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
if (!process.client) return;
|
if (!import.meta.client) return;
|
||||||
if (!siteConfig.umami?.enable) return;
|
if (!siteConfig.umami?.enable) return;
|
||||||
|
|
||||||
// 跳过在 localhost 环境下加载 Umami
|
// 跳过在 localhost 环境下加载 Umami
|
||||||
|
|||||||
6
eslint.config.mjs
Normal file
6
eslint.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// @ts-check
|
||||||
|
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||||
|
|
||||||
|
export default withNuxt(
|
||||||
|
// Your custom configs here
|
||||||
|
)
|
||||||
@@ -5,17 +5,30 @@ import tailwindcss from "@tailwindcss/vite";
|
|||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: "2025-12-12",
|
compatibilityDate: "2025-12-12",
|
||||||
srcDir: "app/",
|
srcDir: "app/",
|
||||||
|
modules: ["@nuxt/image", "@nuxt/eslint"],
|
||||||
|
|
||||||
// 禁用 Vue Router 的非关键警告
|
// 禁用 Vue Router 的非关键警告
|
||||||
vue: {
|
vue: {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
isCustomElement: (tag) => tag.startsWith("ion-"),
|
isCustomElement: (tag) => tag.startsWith("ion-"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Tailwind CSS 集成
|
// Tailwind CSS 集成
|
||||||
css: ["~/styles.global.css"],
|
css: ["~/styles.global.css"],
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()],
|
plugins: [tailwindcss()],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
routeRules: {
|
||||||
|
"/": { prerender: true },
|
||||||
|
"/about": { isr: 3600 },
|
||||||
|
"/sites": { prerender: true },
|
||||||
|
"/projects": { prerender: true },
|
||||||
|
"/friends": { prerender: true },
|
||||||
|
},
|
||||||
|
|
||||||
app: {
|
app: {
|
||||||
head: {
|
head: {
|
||||||
title: siteConfig.siteMeta.title,
|
title: siteConfig.siteMeta.title,
|
||||||
@@ -45,6 +58,7 @@ export default defineNuxtConfig({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
nitro: {
|
nitro: {
|
||||||
prerender: {
|
prerender: {
|
||||||
crawlLinks: true,
|
crawlLinks: true,
|
||||||
@@ -52,6 +66,7 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
minify: true,
|
minify: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
smtpHost: process.env.SMTP_HOST ?? "",
|
smtpHost: process.env.SMTP_HOST ?? "",
|
||||||
smtpPort: Number(process.env.SMTP_PORT ?? 465),
|
smtpPort: Number(process.env.SMTP_PORT ?? 465),
|
||||||
|
|||||||
@@ -6,11 +6,16 @@
|
|||||||
"dev": "nuxt dev",
|
"dev": "nuxt dev",
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview"
|
"preview": "nuxt preview",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint . --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@giscus/vue": "^3.1.1",
|
"@giscus/vue": "^3.1.1",
|
||||||
"@jaseeey/vue-umami-plugin": "^1.4.0",
|
"@jaseeey/vue-umami-plugin": "^1.4.0",
|
||||||
|
"@nuxt/eslint": "1.12.1",
|
||||||
|
"@nuxt/image": "2.0.0",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
"nodemailer": "^7.0.11",
|
"nodemailer": "^7.0.11",
|
||||||
"nuxt": "^4.2.2",
|
"nuxt": "^4.2.2",
|
||||||
"vite-tsconfig-paths": "^6.0.1"
|
"vite-tsconfig-paths": "^6.0.1"
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
onlyBuiltDependencies:
|
onlyBuiltDependencies:
|
||||||
- esbuild
|
- esbuild
|
||||||
|
- sharp@0.34.5
|
||||||
|
|||||||
Reference in New Issue
Block a user