mirror of
https://github.com/RhenCloud/Cloud-Home.git
synced 2026-03-10 06:32:40 +08:00
chore: 界面改进和问题修复
This commit is contained in:
81
app/app.vue
81
app/app.vue
@@ -1,78 +1,7 @@
|
||||
<template>
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-if="contact?.customHtml" v-html="customHeaderHtml" />
|
||||
<div class="app-shell" :style="backgroundStyle">
|
||||
<div class="background-overlay" :style="overlayStyle" />
|
||||
<button
|
||||
class="background-toggle"
|
||||
:title="hideComponents ? '显示内容' : '隐藏内容'"
|
||||
:class="{ active: hideComponents }"
|
||||
@click="hideComponents = !hideComponents"
|
||||
>
|
||||
<span class="toggle-icon">{{ hideComponents ? "👁️" : "🙈" }}</span>
|
||||
<span class="toggle-label">{{ hideComponents ? "显示" : "隐藏" }}</span>
|
||||
</button>
|
||||
<div class="content-stack">
|
||||
<Transition name="fade-down">
|
||||
<main v-if="!hideComponents" key="content" class="app-body">
|
||||
<NuxtPage />
|
||||
</main>
|
||||
</Transition>
|
||||
<Transition name="fade-up">
|
||||
<PageSwitcher v-if="!hideComponents" key="switcher" />
|
||||
</Transition>
|
||||
<Transition name="fade-down">
|
||||
<FooterSection v-if="!hideComponents" key="footer" :contact="contact" />
|
||||
</Transition>
|
||||
</div>
|
||||
<MusicPlayer />
|
||||
</div>
|
||||
<!-- <UApp> -->
|
||||
<NuxtLayout>
|
||||
<NuxtPage class="w-full" />
|
||||
</NuxtLayout>
|
||||
<!-- </UApp> -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, computed, ref } from "vue";
|
||||
import PageSwitcher from "~/components/PageSwitcher.vue";
|
||||
import FooterSection from "~/components/FooterSection.vue";
|
||||
import MusicPlayer from "~/components/MusicPlayer.vue";
|
||||
import siteConfig from "~/config/siteConfig";
|
||||
|
||||
const customHeaderHtml = siteConfig.header.customHtml;
|
||||
const contact = siteConfig.footer;
|
||||
const bg = siteConfig.appearance.background;
|
||||
const isMobile = ref(false);
|
||||
const hideComponents = ref(false);
|
||||
|
||||
const checkIfMobile = () => {
|
||||
isMobile.value = typeof window !== "undefined" && window.innerWidth <= 768;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkIfMobile();
|
||||
window.addEventListener("resize", checkIfMobile);
|
||||
});
|
||||
|
||||
const getBackgroundImage = () => {
|
||||
if (!bg.enable) return undefined;
|
||||
const image = isMobile.value && bg.mobileImage ? bg.mobileImage : bg.image;
|
||||
if (!image) return undefined;
|
||||
return image.startsWith("http") ? image : `/${image}`;
|
||||
};
|
||||
|
||||
const backgroundStyle = computed(() => ({ backgroundColor: "#0f1629" }));
|
||||
|
||||
const overlayStyle = computed(() => {
|
||||
const imageUrl = getBackgroundImage();
|
||||
if (!bg.enable || !imageUrl) return {};
|
||||
return {
|
||||
backgroundImage: `linear-gradient(${bg.overlay}, ${bg.overlay}), url('${imageUrl}')`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundAttachment: "fixed",
|
||||
filter: bg.blur ? `blur(${bg.blur}px)` : undefined,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- <style>
|
||||
@import "/css/netease-mini-player-v2.css";
|
||||
</style> -->
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
<h2 class="m-0 mb-1">个人简介</h2>
|
||||
<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-2 sm:gap-3.5">
|
||||
<article
|
||||
v-if="age"
|
||||
class="flex-1 min-w-35 flex items-center justify-between gap-2 bg-linear-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-linear-to-br hover:from-primary/6"
|
||||
class="flex-1 min-w-28 sm:min-w-35 flex items-center justify-between gap-2 bg-linear-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2 sm:p-2.5 sm:px-3.5 px-2.5 shadow-md-dark transition-all duration-300 hover:-translate-y-1 hover:border-primary/40 hover:shadow-lg-dark hover:bg-linear-to-br hover:from-primary/6"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xl leading-none">🎂</span>
|
||||
<h3 class="m-0 text-sm font-semibold text-white/90">年龄</h3>
|
||||
<span class="text-lg sm:text-xl leading-none">🎂</span>
|
||||
<h3 class="m-0 text-xs sm:text-sm font-semibold text-white/90">年龄</h3>
|
||||
</div>
|
||||
<p
|
||||
class="text-text-muted text-xs m-0 text-right whitespace-nowrap font-medium text-white/60"
|
||||
@@ -21,11 +21,11 @@
|
||||
|
||||
<article
|
||||
v-if="profile?.gender"
|
||||
class="flex-1 min-w-35 flex items-center justify-between gap-2 bg-linear-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"
|
||||
class="flex-1 min-w-28 sm:min-w-35 flex items-center justify-between gap-2 bg-linear-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2 sm:p-2.5 sm:px-3.5 px-2.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">
|
||||
<span class="text-xl leading-none">⚧️</span>
|
||||
<h3 class="m-0 text-sm font-semibold text-white/90">性别</h3>
|
||||
<span class="text-lg sm:text-xl leading-none">⚧️</span>
|
||||
<h3 class="m-0 text-xs sm:text-sm font-semibold text-white/90">性别</h3>
|
||||
</div>
|
||||
<p
|
||||
class="text-text-muted text-xs m-0 text-right whitespace-nowrap font-medium text-white/60"
|
||||
@@ -36,11 +36,11 @@
|
||||
|
||||
<article
|
||||
v-if="profile?.pronouns"
|
||||
class="flex-1 min-w-35 flex items-center justify-between gap-2 bg-linear-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"
|
||||
class="flex-1 min-w-28 sm:min-w-35 flex items-center justify-between gap-2 bg-linear-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2 sm:p-2.5 sm:px-3.5 px-2.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">
|
||||
<span class="text-xl leading-none">🗣️</span>
|
||||
<h3 class="m-0 text-sm font-semibold text-white/90">代词</h3>
|
||||
<span class="text-lg sm:text-xl leading-none">🗣️</span>
|
||||
<h3 class="m-0 text-xs sm:text-sm font-semibold text-white/90">代词</h3>
|
||||
</div>
|
||||
<p
|
||||
class="text-text-muted text-xs m-0 text-right whitespace-nowrap font-medium text-white/60"
|
||||
@@ -51,11 +51,11 @@
|
||||
|
||||
<article
|
||||
v-if="profile?.location"
|
||||
class="flex-1 min-w-35 flex items-center justify-between gap-2 bg-linear-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"
|
||||
class="flex-1 min-w-28 sm:min-w-35 flex items-center justify-between gap-2 bg-linear-to-br from-white/5 to-white/2 border border-white/10 rounded-2xl p-2 sm:p-2.5 sm:px-3.5 px-2.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">
|
||||
<span class="text-xl leading-none">📍</span>
|
||||
<h3 class="m-0 text-sm font-semibold text-white/90">地区</h3>
|
||||
<span class="text-lg sm:text-xl leading-none">📍</span>
|
||||
<h3 class="m-0 text-xs sm:text-sm font-semibold text-white/90">地区</h3>
|
||||
</div>
|
||||
<p
|
||||
class="text-text-muted text-xs m-0 text-right whitespace-nowrap font-medium text-white/60"
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<template>
|
||||
<section
|
||||
class="card main-section grid grid-cols-[120px_1fr] gap-4 items-center hover:shadow-lg-dark group"
|
||||
class="card main-section grid grid-cols-1 sm:grid-cols-[120px_1fr] gap-3 sm:gap-4 items-start sm:items-center hover:shadow-lg-dark group text-center sm:text-left"
|
||||
>
|
||||
<div class="relative">
|
||||
<div class="relative justify-self-center sm:justify-self-start">
|
||||
<div
|
||||
class="absolute inset-0 rounded-full bg-linear-to-br from-primary/30 to-accent/20 blur-xl group-hover:blur-2xl transition-all duration-300 opacity-0 group-hover:opacity-100"
|
||||
/>
|
||||
<NuxtImg
|
||||
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"
|
||||
class="relative w-24 h-24 sm:w-30 sm: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 class="overflow-hidden">
|
||||
<h1 class="text-2xl font-bold">{{ profile.name }}</h1>
|
||||
<h1 class="text-2xl font-bold leading-tight">{{ profile.name }}</h1>
|
||||
<p class="text-text-muted text-sm mt-1">{{ profile.title }}</p>
|
||||
<p class="mt-2 line-clamp-2">{{ profile.bio }}</p>
|
||||
<p class="mt-2 line-clamp-3 sm:line-clamp-2">{{ profile.bio }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<section class="card flex flex-col gap-2.5">
|
||||
<section class="card relative main-section flex flex-col gap-2.5">
|
||||
<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>
|
||||
<div class="relative">
|
||||
<button
|
||||
<!-- <button
|
||||
v-show="canScrollLeft"
|
||||
aria-label="向左滚动"
|
||||
class="scroll-arrow left flex items-center justify-center"
|
||||
@@ -25,26 +25,26 @@
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</button> -->
|
||||
|
||||
<div
|
||||
ref="container"
|
||||
class="flex flex-nowrap gap-2.5 overflow-x-auto social-links-scroll py-1"
|
||||
>
|
||||
<div ref="container" class="relative flex flex-wrap gap-2.5">
|
||||
<template v-for="link in links" :key="link.url">
|
||||
<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">
|
||||
<Icon v-if="iconFor(link).name" :name="iconFor(link).name" width="20" height="20" />
|
||||
<span
|
||||
v-if="iconFor(link)"
|
||||
class="inline-flex items-center justify-center w-5 h-5 shrink-0"
|
||||
>
|
||||
<Icon :name="iconFor(link).name" width="20" height="20" />
|
||||
</span>
|
||||
<span>{{ link.name }}</span>
|
||||
<span class="whitespace-nowrap">{{ link.name }}</span>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<button
|
||||
<!-- <button
|
||||
v-show="canScrollRight"
|
||||
aria-label="向右滚动"
|
||||
class="scroll-arrow right flex items-center justify-center"
|
||||
@@ -66,7 +66,7 @@
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</button> -->
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -82,8 +82,7 @@ defineProps({
|
||||
});
|
||||
|
||||
const container = ref(null);
|
||||
const canScrollLeft = ref(false);
|
||||
const canScrollRight = ref(false);
|
||||
const showOnlyIcons = ref(false);
|
||||
|
||||
const iconMap = {
|
||||
bilibili: "simple-icons:bilibili",
|
||||
@@ -118,24 +117,17 @@ const iconFor = (link) => {
|
||||
function updateScrollButtons() {
|
||||
const el = container.value;
|
||||
if (!el) return;
|
||||
canScrollLeft.value = el.scrollLeft > 0;
|
||||
canScrollRight.value = el.scrollWidth - el.clientWidth - el.scrollLeft > 1;
|
||||
// 检测是否需要换行(内容高度大于一行)
|
||||
const hasOverflow = el.scrollHeight > el.clientHeight + 5;
|
||||
showOnlyIcons.value = hasOverflow;
|
||||
}
|
||||
|
||||
function scrollByAmount(amount) {
|
||||
const el = container.value;
|
||||
if (!el) return;
|
||||
el.scrollBy({ left: amount, behavior: "smooth" });
|
||||
setTimeout(updateScrollButtons, 300);
|
||||
}
|
||||
|
||||
function scrollLeft() {
|
||||
scrollByAmount(-200);
|
||||
}
|
||||
|
||||
function scrollRight() {
|
||||
scrollByAmount(200);
|
||||
}
|
||||
// function scrollByAmount(amount) {
|
||||
// const el = container.value;
|
||||
// if (!el) return;
|
||||
// el.scrollBy({ left: amount, behavior: "smooth" });
|
||||
// setTimeout(updateScrollButtons, 300);
|
||||
// }
|
||||
|
||||
onMounted(() => {
|
||||
updateScrollButtons();
|
||||
|
||||
80
app/layouts/default.vue
Normal file
80
app/layouts/default.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="relative min-h-screen flex flex-col">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<div v-if="contact?.customHtml" v-html="customHeaderHtml" />
|
||||
<div class="app-shell" :style="backgroundStyle">
|
||||
<div class="background-overlay" :style="overlayStyle" />
|
||||
<button
|
||||
class="background-toggle"
|
||||
:title="hideComponents ? '显示内容' : '隐藏内容'"
|
||||
:class="{ active: hideComponents }"
|
||||
@click="hideComponents = !hideComponents"
|
||||
>
|
||||
<span class="toggle-icon">{{ hideComponents ? "👁️" : "🙈" }}</span>
|
||||
<span class="toggle-label">{{ hideComponents ? "显示" : "隐藏" }}</span>
|
||||
</button>
|
||||
<div class="content-stack">
|
||||
<Transition name="fade-down">
|
||||
<main v-if="!hideComponents" key="content" class="app-body">
|
||||
<NuxtPage />
|
||||
</main>
|
||||
</Transition>
|
||||
<Transition name="fade-up">
|
||||
<PageSwitcher v-if="!hideComponents" key="switcher" />
|
||||
</Transition>
|
||||
<Transition name="fade-down">
|
||||
<FooterSection v-if="!hideComponents" key="footer" :contact="contact" />
|
||||
</Transition>
|
||||
</div>
|
||||
<MusicPlayer />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
import PageSwitcher from "~/components/PageSwitcher.vue";
|
||||
import FooterSection from "~/components/FooterSection.vue";
|
||||
import MusicPlayer from "~/components/MusicPlayer.vue";
|
||||
import siteConfig from "~/config/siteConfig";
|
||||
|
||||
const customHeaderHtml = siteConfig.header.customHtml;
|
||||
const contact = siteConfig.footer;
|
||||
const bg = siteConfig.appearance.background;
|
||||
const isMobile = ref(false);
|
||||
const hideComponents = ref(false);
|
||||
|
||||
const checkIfMobile = () => {
|
||||
isMobile.value = typeof window !== "undefined" && window.innerWidth <= 768;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
checkIfMobile();
|
||||
window.addEventListener("resize", checkIfMobile);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", checkIfMobile);
|
||||
});
|
||||
|
||||
const getBackgroundImage = () => {
|
||||
if (!bg.enable) return undefined;
|
||||
const image = isMobile.value && bg.mobileImage ? bg.mobileImage : bg.image;
|
||||
if (!image) return undefined;
|
||||
return image.startsWith("http") ? image : `/${image}`;
|
||||
};
|
||||
|
||||
const backgroundStyle = computed(() => ({ backgroundColor: "#0f1629" }));
|
||||
|
||||
const overlayStyle = computed(() => {
|
||||
const imageUrl = getBackgroundImage();
|
||||
if (!bg.enable || !imageUrl) return {};
|
||||
return {
|
||||
backgroundImage: `linear-gradient(${bg.overlay}, ${bg.overlay}), url('${imageUrl}')`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundAttachment: "fixed",
|
||||
filter: bg.blur ? `blur(${bg.blur}px)` : undefined,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
@@ -161,15 +161,18 @@
|
||||
}
|
||||
|
||||
.page {
|
||||
/* width: 100%; */
|
||||
max-width: var(--site-content-max-width);
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem 3rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.main-section {
|
||||
width: 100%;
|
||||
max-width: var(--main-max-width);
|
||||
/* min-width: var(--main-min-width); */
|
||||
}
|
||||
@@ -258,6 +261,7 @@
|
||||
.netease-mini-player[data-position="bottom-right"] .playlist-container {
|
||||
position: fixed !important;
|
||||
bottom: calc(20px + 80px) !important;
|
||||
width: 45% !important;
|
||||
}
|
||||
|
||||
.muted {
|
||||
@@ -334,6 +338,15 @@
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.page {
|
||||
padding: 1.25rem 0.75rem 2rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* .card {
|
||||
padding: 1rem 1rem;
|
||||
} */
|
||||
|
||||
.background-toggle {
|
||||
right: 0.75rem;
|
||||
bottom: 0.75rem;
|
||||
|
||||
Reference in New Issue
Block a user