feat(theme): 新增 <LinkCard> 链接卡片组件

This commit is contained in:
pengzhanbo 2024-06-19 12:47:02 +08:00
parent 62e128a960
commit 126da9fb5f
5 changed files with 199 additions and 53 deletions

View File

@ -161,6 +161,38 @@ export default defineUserConfig({
这里是卡片内容。
</Card>
## 链接卡片
使用 `<LinkCard>` 组件在页面中显示链接卡片。
### Props
| 名称 | 类型 | 默认值 | 说明 |
| ----------- | --------------------------- | ------ | ---------------------------------------------------------------- |
| title | `string` | `''` | 标题 |
| icon | `string \| { svg: string }` | `''` | 显示在标题左侧的图标,支持 iconify 所有图标,也可以使用 图片链接 |
| href | `string` | `''` | 链接 |
| description | `string` | `''` | 详情 |
### 插槽
| 名称 | 说明 |
| ------- | ------------ |
| default | 卡片详情内容 |
| title | 自定义标题 |
**输入:**
```md
<LinkCard title="卡片标题" href="/" description="这里是卡片内容" />
<LinkCard icon="twemoji:astonished-face" title="卡片标题" href="/" />
```
**输出:**
<LinkCard title="卡片标题" href="/" description="这里是卡片内容" />
<LinkCard icon="twemoji:astonished-face" title="卡片标题" href="/" />
## 卡片排列容器
当你需要将多个卡片排列,可以使用 `<CardGrid>` 组件。在空间足够时,多个卡片会自动排列。
@ -176,6 +208,11 @@ export default defineUserConfig({
这里是卡片内容。
</Card>
</CardGrid>
<CardGrid>
<LinkCard title="卡片标题" href="/" />
<LinkCard icon="twemoji:astonished-face" title="卡片标题" href="/" />
</CardGrid>
```
**输出:**
@ -189,6 +226,11 @@ export default defineUserConfig({
</Card>
</CardGrid>
<CardGrid>
<LinkCard title="链接卡片" href="/" />
<LinkCard icon="twemoji:astonished-face" title="链接卡片" href="/" />
</CardGrid>
## 首页布局容器
自定义首页时,使用 `<HomeBox>`提供给 区域 的 包装容器。

View File

@ -4,17 +4,15 @@ import { computed } from 'vue'
const props = defineProps<{
title?: string
icon: string | { svg: string }
icon?: string | { svg: string }
}>()
const icon = computed<string | { svg: string }>(() => {
const icon = computed<string | { svg: string } | undefined>(() => {
if (props.icon?.[0] === '{') {
try {
return JSON.parse(icon) as { svg: string }
}
catch {
return props.icon
}
catch {}
}
return props.icon
})
@ -51,6 +49,10 @@ const icon = computed<string | { svg: string }>(() => {
box-shadow: var(--vp-shadow-2);
}
.vp-card-wrapper :deep(.vp-iconify) {
margin: 0;
}
.vp-card-wrapper .title {
display: flex;
gap: 8px;

View File

@ -0,0 +1,97 @@
<script setup lang="ts">
import VPLink from '@theme/VPLink.vue'
import VPIcon from '@theme/VPIcon.vue'
import { computed } from 'vue'
const props = defineProps<{
href: string
title: string
icon?: string | { svg: string }
description?: string
}>()
const icon = computed<string | { svg: string } | undefined>(() => {
if (props.icon?.[0] === '{') {
try {
return JSON.parse(icon) as { svg: string }
}
catch {}
}
return props.icon
})
</script>
<template>
<div class="vp-link-card">
<span class="body">
<VPLink :href="href" no-icon class="link">
<slot name="title">
<VPIcon v-if="icon" :name="icon" />
<span v-if="title" v-html="title" />
</slot>
</VPLink>
<slot>
<p v-if="description" v-html="description" />
</slot>
</span>
<span class="vpi-arrow-right" />
</div>
</template>
<style scoped>
.vp-link-card {
position: relative;
display: flex;
gap: 8px;
align-items: flex-start;
padding: 16px 20px;
margin: 16px 0;
background-color: transparent;
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), background-color var(--t-color);
}
.vp-link-card:hover {
background-color: var(--vp-c-bg-soft);
border-color: var(--vp-c-brand-2);
box-shadow: var(--vp-shadow-2);
}
.vp-link-card .body {
display: flex;
flex: 1;
flex-direction: column;
align-items: flex-start;
}
.vp-link-card .body > :last-child {
margin-bottom: 0;
}
.vp-link-card .link {
display: flex;
gap: 8px;
align-items: center;
font-size: 18px;
font-weight: 700;
color: var(--vp-c-text-1);
text-decoration: none;
}
.vp-link-card .link::before {
position: absolute;
inset: 0;
content: "";
}
.vp-link-card .link :deep(.vp-iconify) {
margin: 0;
}
.vpi-arrow-right {
margin-top: 2px;
font-size: 20px;
}
</style>

View File

@ -2,12 +2,8 @@ import './styles/index.css'
import { defineClientConfig } from 'vuepress/client'
import type { ClientConfig } from 'vuepress/client'
import { h } from '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 { globalComponents } from './globalComponents.js'
import Layout from './layouts/Layout.vue'
import NotFound from './layouts/NotFound.vue'
@ -15,51 +11,10 @@ export default defineClientConfig({
enhance({ app, router }) {
setupDarkMode(app)
enhanceScrollBehavior(router)
// global component
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')
if (SearchComponent)
return h(SearchComponent)
return null
})
app.component('PageComment', (props) => {
const CommentService = app.component('CommentService')
if (CommentService)
return h(CommentService, props)
return null
})
app.component('Icon', (props) => {
const Iconify = app.component('Iconify')
if (Iconify)
return h(Iconify, props)
return null
})
/** @deprecated */
app.component('HomeBox', VPHomeBox)
app.component('VPHomeBox', VPHomeBox)
globalComponents(app)
},
setup() {
setupWatermark()
},
layouts: {
Layout,
NotFound,
},
layouts: { Layout, NotFound },
}) as ClientConfig

View File

@ -0,0 +1,50 @@
import type { App } from 'vue'
import { h } from 'vue'
import VPHomeBox from '@theme/Home/VPHomeBox.vue'
import VPCard from '@theme/global/VPCard.vue'
import VPLinkCard from '@theme/global/VPLinkCard.vue'
import VPBadge from '@theme/global/VPBadge.vue'
import VPCardGrid from '@theme/global/VPCardGrid.vue'
export function globalComponents(app: App) {
app.component('Badge', VPBadge)
app.component('VPBadge', VPBadge)
app.component('VPCard', VPCard)
app.component('Card', VPCard)
app.component('VPCardGrid', VPCardGrid)
app.component('CardGrid', VPCardGrid)
app.component('VPLinkCard', VPLinkCard)
app.component('LinkCard', VPLinkCard)
app.component('DocSearch', () => {
const SearchComponent
= app.component('Docsearch') || app.component('SearchBox')
if (SearchComponent)
return h(SearchComponent)
return null
})
app.component('PageComment', (props) => {
const CommentService = app.component('CommentService')
if (CommentService)
return h(CommentService, props)
return null
})
app.component('Icon', (props) => {
const Iconify = app.component('Iconify')
if (Iconify)
return h(Iconify, props)
return null
})
/** @deprecated */
app.component('HomeBox', VPHomeBox)
app.component('VPHomeBox', VPHomeBox)
}