feat(theme): 新增 <Card> 卡片组件

This commit is contained in:
pengzhanbo 2024-06-19 11:12:46 +08:00
parent bea5c8b524
commit 62e128a960
11 changed files with 517 additions and 194 deletions

View File

@ -33,9 +33,10 @@
"editor.codeActionsOnSave": {
"source.fixAll.stylelint": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.markdownlint": "explicit",
"source.organizeImports": "never"
},
"editor.formatOnPaste": true,
"editor.formatOnPaste": false,
"eslint.validate": [
"javascript",
"javascriptreact",

View File

@ -4,6 +4,9 @@ author: pengzhanbo
icon: fluent:markdown-20-filled
createTime: 2024/03/05 11:10:39
permalink: /guide/markdown/basic/
tags:
- 指南
- markdown
---
::: note

View File

@ -4,6 +4,9 @@ author: pengzhanbo
icon: fluent-mdl2:auto-enhance-on
createTime: 2024/03/05 23:29:07
permalink: /guide/markdown/extensions/
tags:
- 指南
- markdown
---
## 标题锚点

View File

@ -5,203 +5,97 @@ icon: ic:outline-auto-fix-high
outline: 2
createTime: 2024/03/05 16:27:59
permalink: /guide/markdown/advance/
tags:
- 指南
- markdown
---
## iconify 图标
## 卡片
在 Markdown 文件中使用 [iconify](https://iconify.design/) 的图标。 主题虽然提供了
[`<Iconify />`](/guide/features/component/#iconify) 组件来支持在 markdown 中使用图标,
但是它需要从远程加载图标,可能速度比较慢。
对于想要突出显示的内容,可以将其放在 卡片容器 `::: card` 中。
为此,主题提供了另一种可选的方式,以更简单的方式,在 Markdown 中使用图标,并且将 图标资源编译到
本地静态资源中。
### 配置
该功能默认不启用,你需要在 `theme` 配置中启用。
::: code-tabs
@tab .vuepress/config.ts
```ts
export default defineUserConfig({
theme: plumeTheme({
plugins: {
markdownPower: {
icons: true,
},
}
})
})
```
:::
同时,该功能还需要你额外安装 `@iconify/json` 依赖。
::: code-tabs
@tab pnpm
```sh
pnpm add @iconify/json
```
@tab yarn
```sh
yarn add @iconify/json
```
@tab npm
```sh
npm install @iconify/json
```
:::
如果需要在空间足够时并排显示多个卡片,可以使用 `card-grid` 容器,包裹 多个卡片。
### 语法
```md
:[collect:name]:
<!-- 单个卡片 -->
::: card title="标题" icon="twemoji:astonished-face"
这里是卡片内容。
:::
<!-- 多个卡片 -->
:::: card-grid
::: card title="卡片标题 1" icon="twemoji:astonished-face"
这里是卡片内容。
:::
::: card title="卡片标题 2" icon="twemoji:astonished-face"
这里是卡片内容。
:::
::::
```
设置图标大小和颜色
```md
:[collect:name size]:
:[collect:name /color]:
:[collect:name size/color]:
```
`iconify` 拥有非常多的图标,这些图标被分组为不同的 `collect`,每个 `collect` 都有自己的
图标。
你可以从 <https://icon-sets.iconify.design/> 中获取 collect 和 name。
卡片 支持 可选的 `title``icon`。 其中, icon 支持传入 图片链接,也可以传入 iconify 图标名。
### 示例
输入:
**输入:**
```md
:[ion:logo-markdown]:
::: card title="卡片标题" icon="twemoji:astonished-face"
这里是卡片内容。
:::
```
输出:
**输出:**
:[ion:logo-markdown]:
::: card title="卡片标题" icon="twemoji:astonished-face"
该语法为行内语法,因此,你可以在同一行中跟其他 markdown 语法 一起使用。
输入:
```md
github: :[tdesign:logo-github-filled]:
修改颜色::[tdesign:logo-github-filled /#f00]:
修改大小::[tdesign:logo-github-filled 36px]:
修改大小颜色::[tdesign:logo-github-filled 36px/#f00]:
```
输出:
github: :[tdesign:logo-github-filled]:
修改颜色::[tdesign:logo-github-filled /#f00]:
修改大小::[tdesign:logo-github-filled 36px]:
修改大小颜色::[tdesign:logo-github-filled 36px/#f00]:
## “隐秘”文本
有时候,你不想直接把文本内容毫无保留的展示出来,想要保留一些 隐秘性,
可能是为了引起读者的好奇心,也可能纯粹是故意增加点阅读障碍,让文章变得更加有趣。
为了满足这种小小的心思,主题提供了一个 **“隐秘”文本** 的有趣小功能。它看起来像这样:
:::demo-wrapper
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
力量!于是,!!我在床上翻了个身!!
这里是卡片内容。
:::
读者不能直接阅读到完整的内容,部分的内容被 “遮住”,需要鼠标悬停到内容上,才能看到被遮住的内容。
### 配置
该功能默认不启用,你需要在 `theme` 配置中启用。
::: code-tabs
@tab .vuepress/config.ts
```ts
export default defineUserConfig({
theme: plumeTheme({
plugins: {
markdownPower: {
plot: true,
},
}
})
})
```
:::
`markdownPower.plot` 支持传入 `boolean | PlotOptions` 类型
```ts
interface PlotOptions {
/**
* 是否启用 `!! !!` markdown (该标记为非标准标记,脱离插件将不生效)
* 如果设置为 false 则表示不启用该标记,只能使用 <Plot /> 组件
* @default true
*/
tag?: boolean
/**
* 遮罩层颜色
*/
mask?: string | { light: string, dark: string }
/**
* 文本颜色
*/
color?: string | { light: string, dark: string }
/**
* 触发方式
*
* @default 'hover'
*/
trigger?: 'hover' | 'click'
}
```
### 语法
**输入:**
```md
!!需要隐秘的内容!!
```
:::: card-grid
::: card title="卡片标题 1" icon="twemoji:astonished-face"
如果不想使用 非标准的 `!! !!` 标记语法,你可以将 `plot.tag` 设置为 `false`
然后使用 [`<Plot />`](/guide/features/component/#plot) 组件替代。
### 示例
输入:
```md
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
力量!于是,!!我在床上翻了个身!!
```
输出:
:::demo-wrapper
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
力量!于是,!!我在床上翻了个身!!
这里是卡片内容。
:::
## 步骤容器
::: card title="卡片标题 2" icon="twemoji:astonished-face"
在 Markdown 中支持 步骤容器。
这里是卡片内容。
:::
::::
```
**输出:**
:::: card-grid
::: card title="卡片标题 1" icon="twemoji:astonished-face"
这里是卡片内容。
:::
::: card title="卡片标题 2" icon="twemoji:astonished-face"
这里是卡片内容。
:::
::::
::: info
如果你更喜欢通过组件的方式使用 卡片,你可以查看 [卡片组件](/guide/features/component/#card) 。
:::
## 步骤
有时候,你需要将内容 划分为递进的步骤展示,你可以使用 `steps` 容器 实现。
@ -372,6 +266,96 @@ corepack use pnpm@8
:::
## “隐秘”文本
有时候,你不想直接把文本内容毫无保留的展示出来,想要保留一些 隐秘性,
可能是为了引起读者的好奇心,也可能纯粹是故意增加点阅读障碍,让文章变得更加有趣。
为了满足这种小小的心思,主题提供了一个 **“隐秘”文本** 的有趣小功能。它看起来像这样:
:::demo-wrapper
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
力量!于是,!!我在床上翻了个身!!
:::
读者不能直接阅读到完整的内容,部分的内容被 “遮住”,需要鼠标悬停到内容上,才能看到被遮住的内容。
### 配置
该功能默认不启用,你需要在 `theme` 配置中启用。
::: code-tabs
@tab .vuepress/config.ts
```ts
export default defineUserConfig({
theme: plumeTheme({
plugins: {
markdownPower: {
plot: true,
},
}
})
})
```
:::
`markdownPower.plot` 支持传入 `boolean | PlotOptions` 类型
```ts
interface PlotOptions {
/**
* 是否启用 `!! !!` markdown (该标记为非标准标记,脱离插件将不生效)
* 如果设置为 false 则表示不启用该标记,只能使用 <Plot /> 组件
* @default true
*/
tag?: boolean
/**
* 遮罩层颜色
*/
mask?: string | { light: string, dark: string }
/**
* 文本颜色
*/
color?: string | { light: string, dark: string }
/**
* 触发方式
*
* @default 'hover'
*/
trigger?: 'hover' | 'click'
}
```
### 语法
```md
!!需要隐秘的内容!!
```
如果不想使用 非标准的 `!! !!` 标记语法,你可以将 `plot.tag` 设置为 `false`
然后使用 [`<Plot />`](/guide/features/component/#plot) 组件替代。
### 示例
输入:
```md
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
力量!于是,!!我在床上翻了个身!!
```
输出:
:::demo-wrapper
你知道吗, !!鲁迅!! 曾说过:“ !!我没说过这句话!!! ” 令我醍醐灌顶,深受启发,浑身迸发出无可匹敌的
力量!于是,!!我在床上翻了个身!!
:::
## 示例容器
有时候,你可能需要在 内容中补充一些 示例,但期望能与 其它内容 分隔开来呈现。
@ -460,6 +444,108 @@ corepack use pnpm@8
:::
## iconify 图标
在 Markdown 文件中使用 [iconify](https://iconify.design/) 的图标。 主题虽然提供了
[`<Iconify />`](/guide/features/component/#iconify) 组件来支持在 markdown 中使用图标,
但是它需要从远程加载图标,可能速度比较慢。
为此,主题提供了另一种可选的方式,以更简单的方式,在 Markdown 中使用图标,并且将 图标资源编译到
本地静态资源中。
### 配置
该功能默认不启用,你需要在 `theme` 配置中启用。
::: code-tabs
@tab .vuepress/config.ts
```ts
export default defineUserConfig({
theme: plumeTheme({
plugins: {
markdownPower: {
icons: true,
},
}
})
})
```
:::
同时,该功能还需要你额外安装 `@iconify/json` 依赖。
::: code-tabs
@tab pnpm
```sh
pnpm add @iconify/json
```
@tab yarn
```sh
yarn add @iconify/json
```
@tab npm
```sh
npm install @iconify/json
```
:::
### 语法
```md
:[collect:name]:
```
设置图标大小和颜色
```md
:[collect:name size]:
:[collect:name /color]:
:[collect:name size/color]:
```
`iconify` 拥有非常多的图标,这些图标被分组为不同的 `collect`,每个 `collect` 都有自己的
图标。
你可以从 <https://icon-sets.iconify.design/> 中获取 collect 和 name。
### 示例
输入:
```md
:[ion:logo-markdown]:
```
输出:
:[ion:logo-markdown]:
该语法为行内语法,因此,你可以在同一行中跟其他 markdown 语法 一起使用。
输入:
```md
github: :[tdesign:logo-github-filled]:
修改颜色::[tdesign:logo-github-filled /#f00]:
修改大小::[tdesign:logo-github-filled 36px]:
修改大小颜色::[tdesign:logo-github-filled 36px/#f00]:
```
输出:
github: :[tdesign:logo-github-filled]:
修改颜色::[tdesign:logo-github-filled /#f00]:
修改大小::[tdesign:logo-github-filled 36px]:
修改大小颜色::[tdesign:logo-github-filled 36px/#f00]:
## can I use
此功能默认不启用,你可以在配置文件中启用它。

View File

@ -9,11 +9,15 @@ permalink: /guide/features/component/
## 概述
VuePress 支持在 Markdown 文件中使用 组件。
主题提供了一些具有通用性的组件,可以在任何地方使用。
## `<Badge />` <Badge type="tip" text="badge" />
## 徽章 <Badge type="tip" text="badge" />
标签,用于在页面中增加一些提示信息。
使用 `<Badge>` 组件来显示 行内信息,如状态或标签。
将你想显示的内容传递给 `<Badge>` 组件的 `text` 属性。
### Props
@ -38,37 +42,37 @@ permalink: /guide/features/component/
- VuePress - <Badge type="warning" text="v2" />
- VuePress - <Badge type="danger" text="v2" />
## `<Iconify />`
## 图标
支持 iconify 所有图标,支持通过 icon name 加载图标。
支持 iconify 所有图标,通过 icon name 加载图标。
可在 [iconify search](https://icon-sets.iconify.design/) 搜索图标使用。
### Props
| 名称 | 类型 | 默认值 | 说明 |
| ----- | ------ | ---------------- | ---------- |
| name | string | `''` | icon name |
| color | string | `'currentcolor'` | icon color |
| size | string | `'1em'` | icon size |
| 名称 | 类型 | 默认值 | 说明 |
| ----- | ------ | ---------------- | -------- |
| name | string | `''` | 图标名 |
| color | string | `'currentcolor'` | 图标颜色 |
| size | string | `'1em'` | 图标大小 |
**输入:**
```md
- home - <Iconify name="material-symbols:home" color="currentColor" size="1em" />
- vscode - <Iconify name="skill-icons:vscode-dark" size="2em" />
- twitter - <Iconify name="skill-icons:twitter" size="2em" />
- home - <Icon name="material-symbols:home" color="currentColor" size="1em" />
- vscode - <Icon name="skill-icons:vscode-dark" size="2em" />
- twitter - <Icon name="skill-icons:twitter" size="2em" />
```
**输出:**
- home - <Iconify name="material-symbols:home" color="currentColor" size="1em" />
- vscode - <Iconify name="skill-icons:vscode-dark" size="2em" />
- twitter - <Iconify name="skill-icons:twitter" size="2em" />
- home - <Icon name="material-symbols:home" color="currentColor" size="1em" />
- vscode - <Icon name="skill-icons:vscode-dark" size="2em" />
- twitter - <Icon name="skill-icons:twitter" size="2em" />
## `<Plot />`
## “隐秘”文本
[“隐秘”文本](/guide/markdown/advance/#隐秘-文本) 组件
使用 `<Plot>` 组件显示 [“隐秘”文本](/guide/markdown/advance/#隐秘-文本) ,能够更灵活的控制行为
该组件默认不启用,你需要在 theme 配置中启用。
@ -109,11 +113,85 @@ export default defineUserConfig({
- 鼠标悬停 - <Plot>悬停时可见</Plot>
- 点击 - <Plot trigger="click">点击时可见</Plot>
## `<HomeBox />`
## 卡片
- 类型:`Component`
使用 `<Card>` 组件在页面中显示卡片。
自定义首页时,提供给 区域 的 包装容器。
也可以使用 markdown [卡片容器](/guide/markdown/advance/#卡片) 语法,替代 `<Card>` 组件。
### Props
| 名称 | 类型 | 默认值 | 说明 |
| ----- | --------------------------- | ------ | ---------------------------------------------------------------- |
| title | `string` | `''` | 标题 |
| icon | `string \| { svg: string }` | `''` | 显示在标题左侧的图标,支持 iconify 所有图标,也可以使用 图片链接 |
### 插槽
| 名称 | 说明 |
| ------- | ---------- |
| default | 卡片内容 |
| title | 自定义标题 |
**输入:**
```md
<Card title="卡片标题" icon="twemoji:astonished-face">
这里是卡片内容。
</Card>
<Card>
<template #title>
<p style="color: red">卡片标题</p>
</template>
这里是卡片内容。
</Card>
```
**输出:**
<Card title="卡片标题" icon="twemoji:astonished-face">
这里是卡片内容。
</Card>
<Card>
<template #title>
<p style="color: red;margin:0">卡片标题</p>
</template>
这里是卡片内容。
</Card>
## 卡片排列容器
当你需要将多个卡片排列,可以使用 `<CardGrid>` 组件。在空间足够时,多个卡片会自动排列。
**输入:**
```md
<CardGrid>
<Card title="卡片标题" icon="twemoji:astonished-face">
这里是卡片内容。
</Card>
<Card title="卡片标题" icon="twemoji:astonished-face">
这里是卡片内容。
</Card>
</CardGrid>
```
**输出:**
<CardGrid>
<Card title="卡片标题" icon="twemoji:astonished-face">
这里是卡片内容。
</Card>
<Card title="卡片标题" icon="twemoji:astonished-face">
这里是卡片内容。
</Card>
</CardGrid>
## 首页布局容器
自定义首页时,使用 `<HomeBox>`提供给 区域 的 包装容器。
### Props

View File

@ -0,0 +1,72 @@
<script setup lang="ts">
import VPIcon from '@theme/VPIcon.vue'
import { computed } from 'vue'
const props = defineProps<{
title?: string
icon: string | { svg: string }
}>()
const icon = computed<string | { svg: string }>(() => {
if (props.icon?.[0] === '{') {
try {
return JSON.parse(icon) as { svg: string }
}
catch {
return props.icon
}
}
return props.icon
})
</script>
<template>
<article class="vp-card-wrapper">
<slot name="title">
<p v-if="title || icon" class="title">
<VPIcon v-if="icon" :name="icon" />
<span v-if="title" v-html="title" />
</p>
</slot>
<div class="body">
<slot />
</div>
</article>
</template>
<style scoped>
.vp-card-wrapper {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px 20px;
margin: 16px 0;
border: solid 1px var(--vp-c-divider);
border-radius: 8px;
box-shadow: var(--vp-shadow-1);
transition: border-color var(--t-color), box-shadow var(--t-color);
}
.vp-card-wrapper:hover {
box-shadow: var(--vp-shadow-2);
}
.vp-card-wrapper .title {
display: flex;
gap: 8px;
align-items: center;
margin: 0;
font-size: 20px;
font-weight: 700;
color: var(--vp-c-text-1);
transition: color var(--t-color);
}
.vp-card-wrapper .body :first-child {
margin-top: 0;
}
.vp-card-wrapper .body :last-child {
margin-bottom: 0;
}
</style>

View File

@ -0,0 +1,23 @@
<template>
<div class="vp-card-grid">
<slot />
</div>
</template>
<style scoped>
.vp-card-grid {
display: grid;
gap: 16px 20px;
margin: 16px 0;
}
.vp-card-grid > * {
margin: 0 !important;
}
@media (min-width: 768px) {
.vp-card-grid {
grid-template-columns: 1fr 1fr;
}
}
</style>

View File

@ -3,11 +3,13 @@ import './styles/index.css'
import { defineClientConfig } from 'vuepress/client'
import type { ClientConfig } from 'vuepress/client'
import { h } from 'vue'
import VPBadge from './components/global/VPBadge.vue'
import VPHomeBox from '@theme/Home/VPHomeBox.vue'
import VPCard from '@theme/global/VPCard.vue'
import VPBadge from '@theme/global/VPBadge.vue'
import VPCardGrid from '@theme/global/VPCardGrid.vue'
import { enhanceScrollBehavior, setupDarkMode, setupWatermark } from './composables/index.js'
import Layout from './layouts/Layout.vue'
import NotFound from './layouts/NotFound.vue'
import VPHomeBox from './components/Home/VPHomeBox.vue'
export default defineClientConfig({
enhance({ app, router }) {
@ -18,6 +20,12 @@ export default defineClientConfig({
app.component('Badge', VPBadge)
app.component('VPBadge', VPBadge) // alias
app.component('VPCard', VPCard)
app.component('Card', VPCard)
app.component('VPCardGrid', VPCardGrid)
app.component('CardGrid', VPCardGrid)
app.component('DocSearch', () => {
const SearchComponent
= app.component('Docsearch') || app.component('SearchBox')

View File

@ -11,6 +11,9 @@ export { default as Layout } from './layouts/Layout.vue'
export { default as NotFound } from './layouts/NotFound.vue'
export { default as VPBadge } from './components/global/VPBadge.vue'
export { default as VPCard } from './components/global/VPCard.vue'
export { default as VPCardGrid } from './components/global/VPCardGrid.vue'
export { default as VPImage } from './components/VPImage.vue'
export { default as VPButton } from './components/VPButton.vue'
export { default as VPLink } from './components/VPLink.vue'

View File

@ -1,4 +1,4 @@
import { addViteConfig, addViteOptimizeDepsInclude, addViteSsrNoExternal } from '@vuepress/helper'
import { addViteConfig, addViteOptimizeDepsExclude, addViteOptimizeDepsInclude, addViteSsrNoExternal } from '@vuepress/helper'
import type { App } from 'vuepress'
export function extendsBundlerOptions(bundlerOptions: any, app: App): void {
@ -9,6 +9,7 @@ export function extendsBundlerOptions(bundlerOptions: any, app: App): void {
})
addViteOptimizeDepsInclude(bundlerOptions, app, '@vueuse/core', true)
addViteOptimizeDepsExclude(bundlerOptions, app, '@theme')
addViteSsrNoExternal(bundlerOptions, app, [
'@vuepress/helper',

View File

@ -39,6 +39,15 @@ export const customContainerPlugins: Plugin[] = [
return '</div></div>\n'
},
}),
/**
* :::steps
* 1. 1
* xxx
* 2. 2
* xxx
* 3. ...
* :::
*/
containerPlugin({
type: 'steps',
before() {
@ -48,6 +57,42 @@ export const customContainerPlugins: Plugin[] = [
return '</div>'
},
}),
/**
* ::: card title="xxx" icon="xxx"
* xxx
* :::
*/
containerPlugin({
type: 'card',
before(info) {
const title = resolveAttr(info, 'title')
const icon = resolveAttr(info, 'icon')
return `<VPCard${title ? ` title="${title}"` : ''}${icon ? ` icon="${icon}"` : ''}>`
},
after() {
return '</VPCard>'
},
}),
/**
* :::: card-grid
* ::: card
* xxx
* :::
* ::: card
* xxx
* :::
* ::::
*/
containerPlugin({
type: 'card-grid',
before() {
return '<VPCardGrid>'
},
after() {
return '</VPCardGrid>'
},
}),
]
/**