perf: 优化首页组件结构

This commit is contained in:
pengzhanbo 2024-03-04 03:06:14 +08:00
parent 2be308b262
commit 83bae10344
11 changed files with 179 additions and 239 deletions

View File

@ -4,7 +4,7 @@ config:
-
type: hero
full: true
background: filter
background: filter-blur
hero:
name: Theme Plume
tagline: Vuepress Next Theme

View File

@ -19,6 +19,12 @@ const components: Record<string, Component<any, any, any>> = {
'custom': HomeCustom,
}
const DEFAULT_HERO = {
name: 'Theme Plume',
tagline: 'VuePress Next Theme',
text: '一个简约的,功能丰富的 vuepress 文档&博客 主题',
}
const matter = usePageFrontmatter<PlumeThemeHomeFrontmatter>()
const config = computed(() => {
@ -26,15 +32,22 @@ const config = computed(() => {
if (config && config.length)
return config
// @deprecated
//
if (matter.value.banner) {
return [{
type: 'banner',
banner: matter.value.banner,
bannerMask: matter.value.bannerMask,
hero: matter.value.hero ?? DEFAULT_HERO,
}]
}
return [{
type: 'hero',
full: true,
background: 'filter',
hero: matter.value.hero ?? {
name: 'Theme Plume',
tagline: 'VuePress Next Theme',
text: '一个简约的,功能丰富的 vuepress 文档&博客 主题',
},
hero: matter.value.hero ?? DEFAULT_HERO,
}]
})

View File

@ -6,7 +6,7 @@ import type { PlumeThemeHomeBanner, PlumeThemeHomeFrontmatter } from '../../../s
import { useDarkMode } from '../../composables/darkMode.js'
import VButton from '../VButton.vue'
const props = defineProps<PlumeThemeHomeBanner & { onlyOnce: boolean }>()
const props = defineProps<PlumeThemeHomeBanner>()
const DEFAULT_BANNER = 'http://file.mo7.cc/api/public/bz'
@ -49,6 +49,7 @@ const actions = computed(() => props.hero?.actions ?? matter.value.hero?.actions
<p v-if="text" class="hero-text">
{{ text }}
</p>
<div v-if="actions.length" class="actions">
<div v-for="action in actions" :key="action.link" class="action">
<VButton
@ -134,11 +135,7 @@ const actions = computed(() => props.hero?.actions ?? matter.value.hero?.actions
font-size: 16px;
font-weight: 500;
color: var(--vp-c-text-hero-text);
/* padding: 6px 20px; */
border-radius: 5px;
/* background-color: rgba(0, 0, 0, 0.25); */
}
@media (min-width: 960px) {

View File

@ -1,59 +1,13 @@
<script setup lang="ts">
import { Content, withBase } from 'vuepress/client'
import { computed } from 'vue'
import { isLinkHttp } from 'vuepress/shared'
import { Content } from 'vuepress/client'
import type { PlumeThemeHomeCustom } from '../../../shared/index.js'
import { useDarkMode } from '../../composables/index.js'
import HomeBox from './HomeBox.vue'
const props = defineProps<PlumeThemeHomeCustom & { onlyOnce?: boolean }>()
const isDark = useDarkMode()
const styles = computed(() => {
if (!props.backgroundImage)
return null
const image = typeof props.backgroundImage === 'string' ? props.backgroundImage : (props.backgroundImage[isDark.value ? 'dark' : 'light'] ?? props.backgroundImage.light)
const link = isLinkHttp(image) ? props.backgroundImage : withBase(image)
return {
'background-image': `url(${link})`,
'background-size': 'cover',
'background-position': 'center',
'background-repeat': 'no-repeat',
'background-attachment': props.backgroundAttachment || '',
}
})
const props = defineProps<PlumeThemeHomeCustom>()
</script>
<template>
<div class="home-custom" :style="styles">
<div class="container">
<Content class="plume-content" />
</div>
</div>
<HomeBox class="home-custom" v-bind="props">
<Content class="plume-content" />
</HomeBox>
</template>
<style scoped>
.home-custom {
position: relative;
padding: 24px;
}
@media (min-width: 640px) {
.home-custom {
padding: 32px 48px;
}
}
@media (min-width: 960px) {
.home-custom {
padding: 48px;
}
}
.container {
max-width: 1152px;
margin: 0 auto;
}
</style>

View File

@ -2,10 +2,9 @@
import { computed } from 'vue'
import type { PlumeThemeHomeFeatures } from '../../../shared/index.js'
import HomeFeature from './HomeFeature.vue'
import HomeBox from './HomeBox.vue'
const props = defineProps<{
onlyOnce?: boolean
} & PlumeThemeHomeFeatures>()
const props = defineProps<PlumeThemeHomeFeatures>()
const grid = computed(() => {
const length = props.features?.length
@ -30,56 +29,40 @@ const grid = computed(() => {
</script>
<template>
<div v-if="features" class="home-features">
<div class="container">
<h2 v-if="title" class="title" v-html="title" />
<p v-if="description" class="description" v-html="description" />
<div class="items">
<div
v-for="feature in features"
:key="feature.title"
class="item"
:class="[grid]"
>
<HomeFeature
:icon="feature.icon"
:title="feature.title"
:details="feature.details"
:link="feature.link"
:link-text="feature.linkText"
:rel="feature.rel"
:target="feature.target"
/>
</div>
<HomeBox
v-if="features"
class="home-features"
:type="type"
:background-image="backgroundImage"
:background-attachment="backgroundAttachment"
:full="full"
>
<h2 v-if="title" class="title" v-html="title" />
<p v-if="description" class="description" v-html="description" />
<div class="items">
<div
v-for="feature in features"
:key="feature.title"
class="item"
:class="[grid]"
>
<HomeFeature
:icon="feature.icon"
:title="feature.title"
:details="feature.details"
:link="feature.link"
:link-text="feature.linkText"
:rel="feature.rel"
:target="feature.target"
/>
</div>
</div>
</div>
</HomeBox>
</template>
<style scoped>
.home-features {
position: relative;
padding: 24px;
}
@media (min-width: 640px) {
.home-features {
padding: 24px 48px 48px;
}
}
@media (min-width: 960px) {
.home-features {
padding: 48px 64px 64px;
}
}
.container {
max-width: 1152px;
margin: 0 auto;
}
.container .title {
.title {
margin-bottom: 20px;
font-size: 20px;
font-weight: 900;
@ -88,7 +71,7 @@ const grid = computed(() => {
transition: color var(--t-color);
}
.container .description {
.description {
margin-bottom: 20px;
font-size: 16px;
line-height: 1.7;
@ -98,17 +81,17 @@ const grid = computed(() => {
}
@media (min-width: 768px) {
.container .title {
.title {
font-size: 24px;
}
.container .description {
.description {
font-size: 18px;
}
}
@media (min-width: 960px) {
.container .title {
.title {
font-size: 28px;
}
}

View File

@ -3,18 +3,33 @@ import { usePageFrontmatter, withBase } from 'vuepress/client'
import { isLinkHttp } from 'vuepress/shared'
import { computed } from 'vue'
import VButton from '../VButton.vue'
import { useDarkMode } from '../../composables/index.js'
import type { PlumeThemeHomeFrontmatter, PlumeThemeHomeHero } from '../../../shared/index.js'
const props = defineProps<PlumeThemeHomeHero & { onlyOnce: boolean }>()
const props = defineProps<PlumeThemeHomeHero>()
const matter = usePageFrontmatter<PlumeThemeHomeFrontmatter>()
const isDark = useDarkMode()
const background = computed(() => {
const background = props.background !== 'filter' ? props.background : ''
const link = background ? isLinkHttp(background) ? background : withBase(background) : ''
return link
? { 'background-image': `url(${link})` }
: null
const heroBackground = computed(() => {
if (props.background === 'filter-blur')
return null
const image = props.backgroundImage
? typeof props.backgroundImage === 'string'
? props.backgroundImage
: (props.backgroundImage[isDark.value ? 'dark' : 'light'] ?? props.backgroundImage.light)
: ''
const background = image || props.background
if (!background)
return null
const link = isLinkHttp(background) ? background : withBase(background)
return {
'background-image': `url(${link})`,
'background-attachment': props.backgroundAttachment || '',
'--vp-hero-bg-filter': props.filter,
}
})
const hero = computed(() => props.hero ?? matter.value.hero ?? {})
@ -23,9 +38,9 @@ const actions = computed(() => hero.value.actions ?? [])
<template>
<div class="home-hero" :class="{ full: props.full, once: props.onlyOnce }">
<div v-if="background" class="home-hero-bg" :style="background" />
<div v-if="heroBackground" class="home-hero-bg" :style="heroBackground" />
<div v-if="props.background === 'filter'" class="bg-filter">
<div v-if="background === 'filter-blur'" class="bg-filter">
<div class="g g-1" />
<div class="g g-2" />
<div class="g g-3" />
@ -36,16 +51,12 @@ const actions = computed(() => hero.value.actions ?? [])
<h1 v-if="hero.name" class="hero-name" v-html="hero.name" />
<p v-if="hero.tagline" class="hero-tagline" v-html="hero.tagline" />
<p v-if="hero.text" class="hero-text" v-html="hero.text" />
<div v-if="actions.length" class="actions">
<div class="action">
<VButton
v-for="action in actions"
:key="action.link"
tag="a"
size="medium"
:theme="action.theme"
:text="action.text"
:href="action.link"
v-for="action in actions" :key="action.link" tag="a" size="medium" :theme="action.theme"
:text="action.text" :href="action.link"
/>
</div>
</div>
@ -68,6 +79,17 @@ const actions = computed(() => hero.value.actions ?? [])
height: calc(100vh - var(--vp-nav-height) - var(--vp-footer-height, 0px));
}
.home-hero-bg {
position: absolute;
z-index: 0;
width: 100%;
height: 100%;
filter: var(--vp-hero-bg-filter);
background-repeat: no-repeat;
background-position: center;
background-size: cover;
}
.container {
position: relative;
z-index: 1;

View File

@ -3,8 +3,9 @@ import { computed } from 'vue'
import type { PlumeThemeHomeProfile } from '../../../shared/index.js'
import VImage from '../VImage.vue'
import { useThemeLocaleData } from '../../composables/index.js'
import HomeBox from './HomeBox.vue'
const props = defineProps<PlumeThemeHomeProfile & { onlyOnce?: boolean }>()
const props = defineProps<PlumeThemeHomeProfile>()
const theme = useThemeLocaleData()
@ -21,66 +22,53 @@ const profile = computed(() => {
</script>
<template>
<div class="home-profile">
<div class="container">
<VImage v-if="profile.avatar" :image="profile.avatar" :class="{ circle: profile.circle }" />
<h3 v-if="profile.name">
{{ profile.name }}
</h3>
<p v-if="profile.description">
{{ profile.description }}
</p>
</div>
</div>
<HomeBox
class="home-profile"
:type="type"
:background-image="backgroundImage"
:background-attachment="backgroundAttachment"
:full="full"
>
<VImage v-if="profile.avatar" :image="profile.avatar" :class="{ circle: profile.circle }" />
<h3 v-if="profile.name">
{{ profile.name }}
</h3>
<p v-if="profile.description">
{{ profile.description }}
</p>
</HomeBox>
</template>
<style scoped>
.home-profile {
position: relative;
padding: 24px;
}
@media (min-width: 640px) {
.home-profile {
padding: 32px 48px;
}
}
@media (min-width: 960px) {
.home-profile {
padding: 48px 64px;
}
}
.container {
max-width: 1152px;
margin: 0 auto;
.home-profile :deep(.container) {
overflow: hidden;
}
.container :deep(img) {
.home-profile :deep(img) {
float: left;
width: 64px;
margin-right: 24px;
}
.container :deep(img.circle) {
.home-profile :deep(img.circle) {
border-radius: 50%;
}
@media (min-width: 960px) {
.container :deep(img) {
.home-profile :deep(img) {
width: 96px;
}
}
.container :deep(h3) {
.home-profile :deep(h3) {
margin-bottom: 12px;
font-size: 20px;
font-weight: 500;
}
.container :deep(p) {
.home-profile :deep(p) {
font-size: 16px;
font-weight: 400;
line-height: 1.5;

View File

@ -1,14 +1,10 @@
<script setup lang="ts">
import { computed } from 'vue'
import { isLinkHttp } from 'vuepress/shared'
import { withBase } from 'vuepress/client'
import type { PlumeThemeHomeTextImage } from '../../../shared/index.js'
import VImage from '../VImage.vue'
import { useDarkMode } from '../../composables/index.js'
import HomeBox from './HomeBox.vue'
const props = defineProps<PlumeThemeHomeTextImage & { onlyOnce?: boolean }>()
const isDark = useDarkMode()
const props = defineProps<PlumeThemeHomeTextImage>()
const maxWidth = computed(() => {
const width = props.width
@ -18,70 +14,45 @@ const maxWidth = computed(() => {
return width
})
const styles = computed(() => {
if (!props.backgroundImage)
return null
const image = typeof props.backgroundImage === 'string' ? props.backgroundImage : (props.backgroundImage[isDark.value ? 'dark' : 'light'] ?? props.backgroundImage.light)
const link = isLinkHttp(image) ? props.backgroundImage : withBase(image)
return {
'background-image': `url(${link})`,
'background-size': 'cover',
'background-position': 'center',
'background-repeat': 'no-repeat',
'background-attachment': props.backgroundAttachment || '',
}
})
</script>
<template>
<div class="home-text-image" :style="styles">
<div class="container" :class="{ reverse: type === 'text-image' }">
<div class="content-image">
<VImage :image="image" :style="{ maxWidth }" />
</div>
<div class="content-text plume-content">
<section>
<h2 v-if="title" class="title">
{{ title }}
</h2>
<p v-if="description" class="description" v-html="description" />
<ul v-if="list && list.length" class="list">
<li v-for="(item, index) in list" :key="index">
<template v-if="typeof item === 'object'">
<h3 v-if="item.title" v-html="item.title" />
<p v-if="item.description" v-html="item.description" />
</template>
<p v-else v-html="item" />
</li>
</ul>
</section>
</div>
<HomeBox
class="home-text-image"
:type="type"
:background-image="backgroundImage"
:background-attachment="backgroundAttachment"
:full="full"
:container-class="{ reverse: type === 'text-image' }"
>
<div class="content-image">
<VImage :image="image" :style="{ maxWidth }" />
</div>
</div>
<div class="content-text plume-content">
<section>
<h2 v-if="title" class="title">
{{ title }}
</h2>
<p v-if="description" class="description" v-html="description" />
<ul v-if="list && list.length" class="list">
<li v-for="(item, index) in list" :key="index">
<template v-if="typeof item === 'object'">
<h3 v-if="item.title" v-html="item.title" />
<p v-if="item.description" v-html="item.description" />
</template>
<p v-else v-html="item" />
</li>
</ul>
</section>
</div>
</HomeBox>
</template>
<style scoped>
.home-text-image {
position: relative;
padding: 24px;
}
@media (min-width: 640px) {
.home-text-image {
padding: 32px 48px;
}
}
@media (min-width: 960px) {
.home-text-image {
padding: 48px 64px;
}
}
.container {
.home-text-image :deep(.container) {
display: flex;
flex-direction: column;
gap: 24px;
@ -92,11 +63,11 @@ const styles = computed(() => {
}
@media (min-width: 960px) {
.container {
.home-text-image :deep(.container) {
flex-direction: row;
}
.container.reverse {
.home-text-image :deep(.container.reverse) {
flex-direction: row-reverse;
}
}
@ -152,7 +123,7 @@ const styles = computed(() => {
}
@media (min-width: 960px) {
.container {
.home-text-image :deep(.container) {
gap: 48px;
}
@ -161,7 +132,7 @@ const styles = computed(() => {
margin: 0 96px;
}
.container .content-text {
.content-text {
display: flex;
justify-content: center;
max-width: 80%;

View File

@ -8,6 +8,7 @@ import ExternalLinkIcon from './components/global/ExternalLinkIcon.vue'
import { setupDarkMode, useScrollPromise } from './composables/index.js'
import Layout from './layouts/Layout.vue'
import NotFound from './layouts/NotFound.vue'
import HomeBox from './components/Home/HomeBox.vue'
export default defineClientConfig({
enhance({ app, router }) {
@ -43,6 +44,8 @@ export default defineClientConfig({
return null
})
app.component('HomeBox', HomeBox)
// handle scrollBehavior with transition
const scrollBehavior = router.options.scrollBehavior!
router.options.scrollBehavior = async (...args) => {

View File

@ -4,3 +4,9 @@ export { default as plumeClientConfig } from './config.js'
export { default as Layout } from './layouts/Layout.vue'
export { default as NotFound } from './layouts/NotFound.vue'
export {
useDarkMode,
useThemeData,
useThemeLocaleData,
} from './composables/index.js'

View File

@ -23,9 +23,13 @@ export interface PlumeThemeHeroAction {
export interface PlumeHomeConfigBase {
type: 'banner' | 'hero' | 'text-image' | 'image-text' | 'features' | 'profile' | 'custom'
full?: boolean
backgroundImage?: string | { light: string, dark: string }
backgroundAttachment?: 'fixed' | 'local'
onlyOnce?: boolean
}
export interface PlumeThemeHomeBanner extends PlumeHomeConfigBase {
export interface PlumeThemeHomeBanner extends Pick<PlumeHomeConfigBase, 'type' | 'onlyOnce' | 'full'> {
type: 'banner'
banner?: string
bannerMask?: number | { light?: number, dark?: number }
@ -36,7 +40,8 @@ export interface PlumeThemeHomeHero extends PlumeHomeConfigBase {
type: 'hero'
hero: PlumeThemeHero
full?: boolean
background?: 'filter' | (string & { zz_IGNORE?: never })
background?: 'filter-blur' | (string & { zz_IGNORE?: never })
filter?: string
}
export interface PlumeThemeHomeTextImage extends PlumeHomeConfigBase {
@ -46,8 +51,6 @@ export interface PlumeThemeHomeTextImage extends PlumeHomeConfigBase {
title?: string
description?: string
list: (string | { title?: string, description?: string })[]
backgroundImage?: string | { light: string, dark: string }
backgroundAttachment?: 'fixed' | 'local'
}
export interface PlumeThemeHomeFeatures extends PlumeHomeConfigBase {