mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
feat(theme): 新增 <LinkCard> 链接卡片组件
This commit is contained in:
parent
62e128a960
commit
126da9fb5f
@ -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>`提供给 区域 的 包装容器。
|
||||
|
||||
@ -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;
|
||||
|
||||
97
theme/src/client/components/global/VPLinkCard.vue
Normal file
97
theme/src/client/components/global/VPLinkCard.vue
Normal 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>
|
||||
@ -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
|
||||
|
||||
50
theme/src/client/globalComponents.ts
Normal file
50
theme/src/client/globalComponents.ts
Normal 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)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user