feat(theme): split the post cover field into cover and coverStyle (#512)

This commit is contained in:
pengzhanbo 2025-03-02 11:25:58 +08:00 committed by GitHub
parent d4e76e0b0b
commit 4227b8a91e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 66 additions and 62 deletions

View File

@ -56,21 +56,22 @@ tags:
### cover
- 类型: `string \| BlogPostCover`
- 类型: `string`
- 默认值: `''`
文章封面图。 封面图仅显示在 文章列表页。
当传入为 `string` 时,表示 封面图链接地址。仅支持 绝对路径 以及 远程图片地址。
仅支持 绝对路径 以及 远程图片地址。
当传入为 `BlogPostCover` 时,表示 封面图配置。
### coverStyle
- 类型: `BlogPostCoverStyle`
- 默认值: `null
封面图配置。
```ts
interface BlogPostCover {
/**
* 封面图链接地址,只能使用 绝对路径 以及 远程图片地址
*/
url: string
interface BlogPostCoverStyle {
/**
* 博客文章封面图的位置
*/

View File

@ -162,16 +162,12 @@ interface BlogOptions {
*
* @default 'right'
*/
postCover?: BlogPostCoverLayout | Omit<BlogPostCover, 'url'>
postCover?: BlogPostCoverLayout | BlogPostCoverStyle
}
type BlogPostCoverLayout = 'left' | 'right' | 'odd-left' | 'odd-right' | 'top'
interface BlogPostCover {
/**
* 封面图链接地址,只能使用 绝对路径 以及 远程图片地址
*/
url: string
interface BlogPostCoverStyle {
/**
* 博客文章封面图的位置
*/

View File

@ -128,7 +128,8 @@ tags:
| tags | `string[]` | `[]` | 文章标签 |
| sticky | `boolean \| number` | false | 是否置顶, 如果为数字,则数字越大,置顶越靠前 |
| draft | `boolean` | false | 是否为草稿,草稿文章不会被展示 |
| cover | `string \| BlogPostCover` | `''` | 文章封面 |
| cover | `string` | `''` | 文章封面 |
| coverStyle | `BlogPostCoverStyle` | `null` | 文章封面样式 |
| excerpt | `boolean \| string` | '' | 文章摘要,默认通过 `<!-- more -->` 注释生成, 传入字符串表示自定义内容,不再从正文提取 |
除了以上的字段,你还可以使用 [通用 frontmatter 配置](../config/frontmatter/basic.md) 中的字段,
@ -214,8 +215,8 @@ cover: /images/cover.jpg # [!code ++]
```md
---
title: 文章标题
cover: # [!code ++:5]
url: /images/cover.jpg
cover: /images/cover.jpg # [!code ++:5]
coverStyle:
layout: left
ratio: 16:9
width: 300
@ -228,7 +229,7 @@ cover: # [!code ++:5]
<VPPostItem
:post="{ path: '/article/ecxnxxd0/', title: '文章标题',
categoryList: [{id:'65f30c',sort:4,name:'教程'}], createTime: '2024/09/18 09:19:36',
lang:'zh-CN', excerpt:'', cover: { url: 'https://api.pengzhanbo.cn/wallpaper/bing', layout: 'left', ratio: '16:9', width: 300 } }"
lang:'zh-CN', excerpt:'', cover: 'https://api.pengzhanbo.cn/wallpaper/bing', coverStyle: { layout: 'left', ratio: '16:9', width: 300 } }"
:index="1"
/>
</div>
@ -238,8 +239,8 @@ cover: # [!code ++:5]
```md
---
title: 文章标题
cover: # [!code ++:6]
url: /images/cover.jpg
cover: /images/cover.jpg # [!code ++:6]
coverStyle:
layout: left
ratio: 16:9
width: 300
@ -253,8 +254,8 @@ cover: # [!code ++:6]
<VPPostItem
:post="{ path: '/article/ecxnxxd0/', title: '文章标题',
categoryList: [{id:'65f30c',sort:4,name:'教程'}], createTime: '2024/09/18 09:19:36',
lang:'zh-CN', excerpt:'',
cover: { url: 'https://api.pengzhanbo.cn/wallpaper/bing', layout: 'left', ratio: '16:9', width: 300, compact: true } }"
lang:'zh-CN', excerpt:'', cover: 'https://api.pengzhanbo.cn/wallpaper/bing',
coverStyle: { layout: 'left', ratio: '16:9', width: 300, compact: true } }"
:index="1"
/>
</div>
@ -267,8 +268,8 @@ cover: # [!code ++:6]
```md
---
title: 文章标题
cover: # [!code ++:5]
url: /images/cover.jpg
cover: /images/cover.jpg # [!code ++:5]
coverStyle:
layout: top
ratio: 16:9
width: 300
@ -281,8 +282,8 @@ cover: # [!code ++:5]
<VPPostItem
:post="{ path: '/article/ecxnxxd0/', title: '文章标题',
categoryList: [{id:'65f30c',sort:4,name:'教程'}], createTime: '2024/09/18 09:19:36',
lang:'zh-CN', excerpt:'',
cover: { url: 'https://api.pengzhanbo.cn/wallpaper/bing', layout: 'top', ratio: '16:9', width: 300 } }"
lang:'zh-CN', excerpt:'', cover: 'https://api.pengzhanbo.cn/wallpaper/bing',
coverStyle: { layout: 'top', ratio: '16:9', width: 300 } }"
:index="1"
/>
</div>
@ -318,11 +319,7 @@ export default defineUserConfig({
```ts
type BlogPostCoverLayout = 'left' | 'right' | 'odd-left' | 'odd-right' | 'top'
interface BlogPostCover {
/**
* 封面图链接地址,只能使用 绝对路径 以及 远程图片地址
*/
url: string
interface BlogPostCoverStyle {
/**
* 博客文章封面图的位置
*/
@ -359,22 +356,22 @@ interface BlogPostCover {
<VPPostItem
:post="{ path: '/article/ecxnxxd0/', title: '文章标题',
categoryList: [{id:'65f30c',sort:4,name:'教程'}], createTime: '2024/09/18 09:19:36',
lang:'zh-CN', excerpt:'',
cover: { url: 'https://api.pengzhanbo.cn/wallpaper/bing', layout: 'odd-left', ratio: '16:9', width: 300, compact: true } }"
lang:'zh-CN', excerpt:'', cover: 'https://api.pengzhanbo.cn/wallpaper/bing',
coverStyle: { layout: 'odd-left', ratio: '16:9', width: 300, compact: true } }"
:index="0"
/>
<VPPostItem
:post="{ path: '/article/ecxnxxd0/', title: '文章标题',
categoryList: [{id:'65f30c',sort:4,name:'教程'}], createTime: '2024/09/18 09:19:36',
lang:'zh-CN', excerpt:'',
cover: { url: 'https://api.pengzhanbo.cn/wallpaper/bing', layout: 'odd-left', ratio: '16:9', width: 300,compact: true } }"
lang:'zh-CN', excerpt:'', cover: 'https://api.pengzhanbo.cn/wallpaper/bing',
coverStyle: { layout: 'odd-left', ratio: '16:9', width: 300,compact: true } }"
:index="1"
/>
<VPPostItem
:post="{ path: '/article/ecxnxxd0/', title: '文章标题',
categoryList: [{id:'65f30c',sort:4,name:'教程'}], createTime: '2024/09/18 09:19:36',
lang:'zh-CN', excerpt:'',
cover: { url: 'https://api.pengzhanbo.cn/wallpaper/bing', layout: 'odd-left', ratio: '16:9', width: 300, compact: true } }"
lang:'zh-CN', excerpt:'', cover: 'https://api.pengzhanbo.cn/wallpaper/bing',
coverStyle: { layout: 'odd-left', ratio: '16:9', width: 300, compact: true } }"
:index="2"
/>
</div>
@ -477,7 +474,7 @@ config:
这导致存在了重复功能的页面,为此,你需要 [主题配置 > 博客配置](../config/主题配置.md#blog) 中,
**关闭自动生成博客文章列表页**
(还可以重新修改 分类页/标签页/归档页的链接地址)
(还可以重新修改 分类页/标签页/归档页的链接地址)
::: code-tabs
@tab .vuepress/config.ts

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { BlogPostCover, PlumeThemeBlogPostItem } from '../../../shared/index.js'
import type { BlogPostCoverStyle, PlumeThemeBlogPostItem } from '../../../shared/index.js'
import VPLink from '@theme/VPLink.vue'
import { useMediaQuery } from '@vueuse/core'
import { computed } from 'vue'
@ -11,6 +11,7 @@ const props = defineProps<{
}>()
const { blog } = useData()
const isMobile = useMediaQuery('(max-width: 496px)')
const colors = useTagColors()
const { categories: categoriesLink, tags: tagsLink } = useInternalLink()
@ -38,15 +39,14 @@ const tags = computed(() => {
}))
})
const cover = computed<BlogPostCover | null>(() => {
const cover = computed<BlogPostCoverStyle | null>(() => {
if (!props.post.cover)
return null
const opt = blog.value.postCover ?? 'right'
const options = typeof opt === 'string' ? { layout: opt } : opt
const cover = typeof props.post.cover === 'string' ? { url: props.post.cover } : props.post.cover
return { layout: 'right', ratio: '4:3', ...options, ...cover }
return { layout: 'right', ratio: '4:3', ...options, ...props.post.coverStyle }
})
const isMobile = useMediaQuery('(max-width: 496px)')
const coverLayout = computed(() => {
if (isMobile.value)
return 'top'
@ -58,11 +58,13 @@ const coverLayout = computed(() => {
return odd ? 'right' : 'left'
return layout
})
const coverCompact = computed(() => {
if (props.post.excerpt || coverLayout.value === 'top')
return false
return cover.value?.compact ?? false
})
const coverStyles = computed(() => {
if (!cover.value)
return null
@ -84,9 +86,15 @@ const coverStyles = computed(() => {
</script>
<template>
<div class="vp-blog-post-item" data-allow-mismatch :class="{ 'has-cover': cover, [coverLayout]: cover }">
<div v-if="cover" class="post-cover" data-allow-mismatch :class="{ compact: coverCompact }" :style="coverStyles">
<img :src="cover.url" :alt="post.title" loading="lazy">
<div
class="vp-blog-post-item" data-allow-mismatch
:class="{ 'has-cover': props.post.cover, [coverLayout]: cover }"
>
<div
v-if="props.post.cover" class="post-cover" data-allow-mismatch
:class="{ compact: coverCompact }" :style="coverStyles"
>
<img :src="props.post.cover" :alt="post.title" loading="lazy">
</div>
<div class="blog-post-item-content">
<h3>

View File

@ -19,10 +19,6 @@ const props = withDefaults(defineProps<Props>(), {
size: 'medium',
theme: 'brand',
text: '',
tag: undefined,
href: undefined,
target: undefined,
rel: undefined,
})
const router = useRouter()

View File

@ -79,6 +79,7 @@ const groups = computed(() => matter.value.groups || [])
}
.edit-link {
display: flex;
padding-left: 1rem;
margin-top: 64px;
}

View File

@ -11,7 +11,7 @@ import { removeLeadingSlash } from '@vuepress/helper'
import { createFilter } from 'create-filter'
import dayjs from 'dayjs'
import { resolveNotesOptions } from '../config/index.js'
import { normalizePath, perfLog, perfMark, resolveContent, writeTemp } from '../utils/index.js'
import { logger, normalizePath, perfLog, perfMark, resolveContent, writeTemp } from '../utils/index.js'
import { isEncryptPage } from './prepareEncrypt.js'
const HEADING_RE = /<h(\d)[^>]*>.*?<\/h\1>/gi
@ -75,6 +75,12 @@ export async function preparedBlogData(
lang: page.lang,
excerpt: '',
cover: page.data.frontmatter.cover,
coverStyle: page.data.frontmatter.coverStyle,
}
// FIXME validate post cover
if (typeof data.cover === 'object') {
logger.warn(`cover should be a path string, please use string instead. (${page.filePathRelative})`)
}
if (isEncryptPage(page, encrypt)) {

View File

@ -65,11 +65,7 @@ export type CopyrightLicense = LiteralUnion<KnownCopyrightLicense>
export type BlogPostCoverLayout = 'left' | 'right' | 'odd-left' | 'odd-right' | 'top'
export interface BlogPostCover {
/**
* 使
*/
url: string
export interface BlogPostCoverStyle {
/**
*
*/

View File

@ -1,4 +1,4 @@
import type { BlogPostCover, CopyrightLicense } from '../base.js'
import type { BlogPostCoverStyle, CopyrightLicense } from '../base.js'
import type { CopyrightOptions } from '../options/copyright.js'
import type { PlumeThemePageFrontmatter } from './page.js'
@ -33,7 +33,9 @@ export interface PlumeThemePostFrontmatter extends PlumeThemePageFrontmatter {
/**
*
*/
cover?: string | BlogPostCover
cover?: string
coverStyle?: BlogPostCoverStyle
/**
* string `<!-- more -->`

View File

@ -1,4 +1,4 @@
import type { BlogPostCover, BlogPostCoverLayout } from '../base.js'
import type { BlogPostCoverLayout, BlogPostCoverStyle } from '../base.js'
import type { PageCategoryData } from '../page-data.js'
export interface BlogOptions {
@ -114,5 +114,5 @@ export interface BlogOptions {
*
* @default 'right'
*/
postCover?: BlogPostCoverLayout | Omit<BlogPostCover, 'url'>
postCover?: BlogPostCoverLayout | BlogPostCoverStyle
}

View File

@ -1,5 +1,5 @@
import type { GitPluginPageData } from '@vuepress/plugin-git'
import type { BlogPostCover } from './base.js'
import type { BlogPostCoverStyle } from './base.js'
interface ReadingTime {
/** 分钟数 */
@ -32,7 +32,8 @@ export interface PlumeThemeBlogPostItem {
createTime: string
lang: string
encrypt?: boolean
cover?: string | BlogPostCover
cover?: string
coverStyle?: BlogPostCoverStyle
}
export type PlumeThemeBlogPostData = PlumeThemeBlogPostItem[]