Merge pull request #143 from pengzhanbo/image-layout

Image Card
This commit is contained in:
pengzhanbo 2024-08-18 10:19:51 +08:00 committed by GitHub
commit 3a07d55e75
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 1140 additions and 725 deletions

View File

@ -2,6 +2,7 @@ import * as path from 'node:path'
import { type UserConfig, defineUserConfig } from 'vuepress'
import { viteBundler } from '@vuepress/bundler-vite'
import { addViteOptimizeDepsInclude, addViteSsrExternal } from '@vuepress/helper'
import { peerDependencies } from '../package.json'
import { theme } from './theme.js'
export default defineUserConfig({
@ -27,6 +28,10 @@ export default defineUserConfig({
addViteSsrExternal(bundlerOptions, app, '@simonwep/pickr')
},
define: {
__VUEPRESS_VERSION__: peerDependencies.vuepress,
},
bundler: viteBundler(),
theme,

View File

@ -16,8 +16,10 @@ const { lightColors, darkColors, css, reset } = useThemeColors()
<h4>{{ name }}</h4>
<section v-for="color in group" :key="color.key" class="theme-color">
<ColorPick v-model="color.value" />
<h5>{{ color.name }}</h5>
<span class="desc">{{ color.desc }}</span>
<div>
<h5>{{ color.name }}</h5>
<span class="desc">{{ color.desc }}</span>
</div>
</section>
</div>
</div>
@ -27,8 +29,10 @@ const { lightColors, darkColors, css, reset } = useThemeColors()
<h4>{{ name }}</h4>
<section v-for="color in group" :key="color.key" class="theme-color">
<ColorPick v-model="color.value" />
<h5>{{ color.name }}</h5>
<span class="desc">{{ color.desc }}</span>
<div>
<h5>{{ color.name }}</h5>
<span class="desc">{{ color.desc }}</span>
</div>
</section>
</div>
</div>

View File

@ -0,0 +1,70 @@
---
title: 照片类作品示例
author: Plume Theme
createTime: 2024/08/17 14:30:23
permalink: /article/30995vcd/
---
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([])
onMounted(async () => {
const res = await fetch('https://api.pengzhanbo.cn/wallpaper/bing/list/zh/').then((res) => res.json())
list.value = res.map(item => ({
title: item.title,
image: item.url,
author: item.copyright.replace('© ', '').split('/')?.[0].trim(),
description: item.description,
date: item.ssd.replace(/(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})/, (_, y, m, d, h, mm) => `${y}/${m}/${d} ${h}:${mm}`),
href: item.url
}))
})
</script>
## 单列照片
```md :no-line-numbers
<ImageCard v-for="item in list" :key="item.image" v-bind="item" />
```
<ImageCard v-for="item in list" :key="item.image" v-bind="item" />
## 双列照片
```md :no-line-numbers
<CardGrid cols="2">
<ImageCard v-for="item in list" :key="item.image" v-bind="item" />
</CardGrid>
```
<CardGrid cols="2">
<ImageCard v-for="item in list" :key="item.image" v-bind="item" />
</CardGrid>
## 三列照片
```md :no-line-numbers
<CardGrid cols="3">
<ImageCard v-for="item in list" :key="item.image" v-bind="item" />
</CardGrid>
```
<CardGrid cols="3">
<ImageCard v-for="item in list" :key="item.image" v-bind="item" />
</CardGrid>
## 不同尺寸设备适配
调整窗口大小以观察效果
```md :no-line-numbers
<CardGrid :cols="{ sm: 2, md: 3, lg: 3 }">
<ImageCard v-for="item in list" :key="item.image" v-bind="item" />
</CardGrid>
```
<CardGrid :cols="{ sm: 2, md: 3, lg: 3 }">
<ImageCard v-for="item in list" :key="item.image" v-bind="item" />
</CardGrid>

View File

@ -28,7 +28,7 @@ VuePress 支持在 Markdown 文件中使用 组件。
**输入:**
```md
```md :no-line-numbers
- VuePress - <Badge type="info" text="v2" />
- VuePress - <Badge type="tip" text="v2" />
- VuePress - <Badge type="warning" text="v2" />
@ -58,7 +58,7 @@ VuePress 支持在 Markdown 文件中使用 组件。
**输入:**
```md
```md :no-line-numbers
- 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" />
@ -79,7 +79,7 @@ VuePress 支持在 Markdown 文件中使用 组件。
::: code-tabs
@tab .vuepress/config.ts
```ts
```ts :no-line-numbers
export default defineUserConfig({
theme: plumeTheme({
plugins: {
@ -103,7 +103,7 @@ export default defineUserConfig({
**输入:**
```md
```md :no-line-numbers
- 鼠标悬停 - <Plot>悬停时可见</Plot>
- 点击 - <Plot trigger="click">点击时可见</Plot>
```
@ -135,7 +135,7 @@ export default defineUserConfig({
**输入:**
```md
```md :no-line-numbers
<Card title="卡片标题" icon="twemoji:astonished-face">
这里是卡片内容。
</Card>
@ -183,7 +183,7 @@ export default defineUserConfig({
**输入:**
```md
```md :no-line-numbers
<LinkCard title="卡片标题" href="/" description="这里是卡片内容" />
<LinkCard icon="twemoji:astonished-face" title="卡片标题" href="/" />
```
@ -193,13 +193,109 @@ export default defineUserConfig({
<LinkCard title="卡片标题" href="/" description="这里是卡片内容" />
<LinkCard icon="twemoji:astonished-face" title="卡片标题" href="/" />
## 图片卡片
使用 `<ImageCard>` 组件在页面中显示图片卡片。
图片卡片 有别于 markdown 的 普通插入图片方式,它展示与图片相关的更多信息,包括标题、描述、作者、链接等。
适用于如 摄影作品、设计作品、宣传海报 等场景。
### Props
| 名称 | 类型 | 默认值 | 说明 |
| ----------- | -------------------------- | ------ | --------------------------------------- |
| image | `string` | `''` | 必填,图片链接 |
| title | `string` | `''` | 可选,标题 (展示其它信息需要依赖此属性) |
| description | `string` | `''` | 可选,描述 |
| author | `string` | `''` | 可选,作者 |
| href | `string` | `''` | 可选,链接 |
| date | `string \| Date \| number` | `''` | 可选,日期 |
**输入:**
```md :no-line-numbers
<ImageCard
image="https://cn.bing.com/th?id=OHR.AlfanzinaLighthouse_ZH-CN9704515669_1920x1080.webp"
title="阿尔凡齐纳灯塔,阿尔加维,葡萄牙"
description="今天照片中的灯塔位于葡萄牙南部海岸阿尔加维的卡沃埃罗。阿尔凡齐纳灯塔建于1919年照耀着大海帮助船只在该地区周围危险的水域航行。这座灯塔是著名的旅游胜地同时也是该地区与海洋紧密联系的象征。如果你有幸住在灯塔附近那么本周末就是拜访灯塔的最佳时机。"
href="/"
author="Andreas Kunz"
date="2024/08/16"
/>
```
**输出:**
<ImageCard
image="https://cn.bing.com/th?id=OHR.AlfanzinaLighthouse_ZH-CN9704515669_1920x1080.webp"
title="阿尔凡齐纳灯塔,阿尔加维,葡萄牙"
description="今天照片中的灯塔位于葡萄牙南部海岸阿尔加维的卡沃埃罗。阿尔凡齐纳灯塔建于1919年照耀着大海帮助船只在该地区周围危险的水域航行。这座灯塔是著名的旅游胜地同时也是该地区与海洋紧密联系的象征。如果你有幸住在灯塔附近那么本周末就是拜访灯塔的最佳时机。"
href="/"
author="Andreas Kunz"
date="2024/08/16"
/>
还可以放到 `<CardGrid>` 组件中。
**输入:**
```md :no-line-numbers
<CardGrid>
<ImageCard
image="https://cn.bing.com/th?id=OHR.AlfanzinaLighthouse_ZH-CN9704515669_1920x1080.webp"
title="阿尔凡齐纳灯塔,阿尔加维,葡萄牙"
description="..."
href="/"
author="Andreas Kunz"
date="2024/08/16"
/>
<ImageCard
image="https://cn.bing.com/th?id=OHR.AlfanzinaLighthouse_ZH-CN9704515669_1920x1080.webp"
title="阿尔凡齐纳灯塔,阿尔加维,葡萄牙"
description="..."
href="/"
author="Andreas Kunz"
date="2024/08/16"
/>
</CardGrid>
```
**输出:**
<CardGrid>
<ImageCard
image="https://cn.bing.com/th?id=OHR.AlfanzinaLighthouse_ZH-CN9704515669_1920x1080.webp"
title="阿尔凡齐纳灯塔,阿尔加维,葡萄牙"
description="今天照片中的灯塔位于葡萄牙南部海岸阿尔加维的卡沃埃罗。阿尔凡齐纳灯塔建于1919年照耀着大海帮助船只在该地区周围危险的水域航行。这座灯塔是著名的旅游胜地同时也是该地区与海洋紧密联系的象征。如果你有幸住在灯塔附近那么本周末就是拜访灯塔的最佳时机。"
href="/"
author="Andreas Kunz"
date="2024/08/16"
/>
<ImageCard
image="https://cn.bing.com/th?id=OHR.AlfanzinaLighthouse_ZH-CN9704515669_1920x1080.webp"
title="阿尔凡齐纳灯塔,阿尔加维,葡萄牙"
description="今天照片中的灯塔位于葡萄牙南部海岸阿尔加维的卡沃埃罗。阿尔凡齐纳灯塔建于1919年照耀着大海帮助船只在该地区周围危险的水域航行。这座灯塔是著名的旅游胜地同时也是该地区与海洋紧密联系的象征。如果你有幸住在灯塔附近那么本周末就是拜访灯塔的最佳时机。"
href="/"
author="Andreas Kunz"
date="2024/08/16"
/>
</CardGrid>
[查看 照片类作品示例](../../../../1.示例/照片类作品示例.md)
## 卡片排列容器
当你需要将多个卡片排列,可以使用 `<CardGrid>` 组件。在空间足够时,多个卡片会自动排列。
### Props
| 名称 | 类型 | 默认值 | 说明 |
| :--- | :----------------------------------------------- | :----- | :------------- |
| cols | `number \| Record<'sm' \| 'md' \| 'lg', number>` | `2` | 卡片排列列数。 |
**输入:**
```md
```md :no-line-numbers
<CardGrid>
<Card title="卡片标题" icon="twemoji:astonished-face">
这里是卡片内容。

View File

@ -9,6 +9,10 @@ tags:
- 快速开始
---
<script setup>
const vuepressVersion = __VUEPRESS_VERSION__
</script>
## 依赖环境
- [Node.js v18.20.0+](https://nodejs.org/)
@ -94,7 +98,7 @@ tags:
:::
:::warning
主题当前版本 已适配至 `vuepress@2.0.0-rc.14`,你应该安装这个版本的 VuePress。
主题当前版本 已适配至 <code>vuepress@{{ vuepressVersion }}</code>,你应该安装这个版本的 VuePress。
高于或低于这个版本,可能会存在潜在的兼容性问题。
:::
@ -160,7 +164,7 @@ tags:
:::
:::warning
无论是否需要使用 **多语言** ,你都应该为 VuePress 配置 正确 `lang` 选项值。
无论是否需要使用 __多语言__ ,你都应该为 VuePress 配置 正确 `lang` 选项值。
主题需要根据 `lang` 选项来确定语言环境文本。
:::

View File

@ -22,13 +22,13 @@ tags:
示例:
```
``` :no-line-numbers
{sourceDir}/
├─ notes/
├─ typescript/
│ ├─ foo.md
├─ rust/
│ ├─ foo.md
├─ typescript/
│ │ └─ foo.md
└─ rust/
│ └─ foo.md
```
其中,`typescript``rust` 为目录名,各自独立保存与之相关的 markdown 文件。

View File

@ -12,7 +12,7 @@
"vuepress": "2.0.0-rc.14"
},
"dependencies": {
"@iconify/json": "^2.2.235",
"@iconify/json": "^2.2.238",
"@simonwep/pickr": "^1.9.1",
"@vuepress/bundler-vite": "2.0.0-rc.14",
"chart.js": "^4.4.3",
@ -20,7 +20,7 @@
"flowchart.ts": "^3.0.0",
"http-server": "^14.1.1",
"mermaid": "^10.9.1",
"vue": "^3.4.37",
"vue": "^3.4.38",
"vuepress-theme-plume": "workspace:*"
},
"devDependencies": {

View File

@ -3,7 +3,7 @@
"type": "module",
"version": "1.0.0-rc.86",
"private": true,
"packageManager": "pnpm@9.7.0",
"packageManager": "pnpm@9.7.1",
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
"license": "MIT",
"keywords": [
@ -44,17 +44,17 @@
"@types/lodash.merge": "^4.6.9",
"@types/node": "20.12.10",
"@types/webpack-env": "^1.18.5",
"bumpp": "^9.4.2",
"bumpp": "^9.5.1",
"commitizen": "^4.3.0",
"conventional-changelog-cli": "^5.0.0",
"cpx2": "^7.0.1",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^9.8.0",
"eslint": "^9.9.0",
"husky": "^9.1.4",
"lint-staged": "^15.2.8",
"lint-staged": "^15.2.9",
"rimraf": "^6.0.1",
"stylelint": "^16.8.1",
"tsconfig-vuepress": "^4.5.0",
"stylelint": "^16.8.2",
"tsconfig-vuepress": "^5.0.0",
"tsup": "^8.2.4",
"typescript": "^5.5.4",
"wait-on": "^7.2.0"

View File

@ -40,7 +40,7 @@
"vuepress": "2.0.0-rc.14"
},
"dependencies": {
"vue": "^3.4.37"
"vue": "^3.4.38"
},
"publishConfig": {
"access": "public"

View File

@ -41,13 +41,13 @@
},
"dependencies": {
"@vuepress/helper": "2.0.0-rc.40",
"@vueuse/core": "^10.11.1",
"@vueuse/core": "^11.0.0",
"markdown-it-container": "^4.0.0",
"nanoid": "^5.0.7",
"shiki": "^1.12.1",
"tm-grammars": "^1.16.3",
"tm-themes": "^1.6.2",
"vue": "^3.4.37"
"shiki": "^1.14.1",
"tm-grammars": "^1.17.2",
"tm-themes": "^1.7.1",
"vue": "^3.4.38"
},
"devDependencies": {
"@types/markdown-it": "^14.1.2"

View File

@ -41,14 +41,14 @@
},
"dependencies": {
"@vuepress/helper": "2.0.0-rc.40",
"@vueuse/core": "^10.11.1",
"@vueuse/integrations": "^10.11.1",
"@vueuse/core": "^11.0.0",
"@vueuse/integrations": "^11.0.0",
"chokidar": "^3.6.0",
"focus-trap": "^7.5.4",
"mark.js": "^8.11.1",
"minisearch": "^7.1.0",
"p-map": "^7.0.2",
"vue": "^3.4.37"
"vue": "^3.4.38"
},
"publishConfig": {
"access": "public"

View File

@ -36,17 +36,17 @@
"vuepress": "2.0.0-rc.14"
},
"dependencies": {
"@shikijs/transformers": "^1.12.1",
"@shikijs/twoslash": "^1.12.1",
"@shikijs/transformers": "^1.14.1",
"@shikijs/twoslash": "^1.14.1",
"@types/hast": "^3.0.4",
"@vuepress/helper": "2.0.0-rc.40",
"@vueuse/core": "^10.11.1",
"@vueuse/core": "^11.0.0",
"floating-vue": "^5.2.2",
"mdast-util-from-markdown": "^2.0.1",
"mdast-util-gfm": "^3.0.0",
"mdast-util-to-hast": "^13.2.0",
"nanoid": "^5.0.7",
"shiki": "^1.12.1",
"shiki": "^1.14.1",
"twoslash": "^0.2.9",
"twoslash-vue": "^0.2.9"
},

1349
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -91,24 +91,24 @@
"@vuepress/plugin-seo": "2.0.0-rc.40",
"@vuepress/plugin-sitemap": "2.0.0-rc.40",
"@vuepress/plugin-watermark": "2.0.0-rc.40",
"@vueuse/core": "^10.11.1",
"@vueuse/core": "^11.0.0",
"bcrypt-ts": "^5.0.2",
"chokidar": "^3.6.0",
"create-filter": "^1.1.0",
"date-fns": "^3.6.0",
"esbuild": "^0.23.0",
"esbuild": "^0.23.1",
"fast-glob": "^3.3.2",
"gray-matter": "^4.0.3",
"json2yaml": "^1.1.0",
"katex": "^0.16.11",
"local-pkg": "^0.5.0",
"nanoid": "^5.0.7",
"vue": "^3.4.37",
"vue": "^3.4.38",
"vue-router": "^4.4.3",
"vuepress-plugin-md-enhance": "2.0.0-rc.52",
"vuepress-plugin-md-power": "workspace:*"
},
"devDependencies": {
"@iconify/json": "^2.2.235"
"@iconify/json": "^2.2.238"
}
}

View File

@ -86,7 +86,6 @@ watchPostEffect(() => {
.vp-navbar.screen-open {
background-color: var(--vp-nav-bg-color);
border-bottom: 1px solid var(--vp-c-divider);
transition: none;
}
.vp-navbar:not(.home) {

View File

@ -26,7 +26,15 @@ const hasComments = computed(() => {
return page.value.frontmatter.comments !== false && isPageDecrypted.value
})
const enableAside = computed(() => hasAside.value && headers.value.length)
const enableAside = computed(() => {
if (!hasAside.value)
return false
if (isBlogPost.value)
return headers.value.length > 0
return true
})
const pageName = computed(() =>
route.path.replace(/[./]+/g, '_').replace(/_html$/, ''),

View File

@ -1,5 +1,41 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useMediaQuery } from '@vueuse/core'
const props = defineProps<{
cols?: string | number | { sm?: number, md?: number, lg?: number }
}>()
const md = useMediaQuery('(min-width: 768px)')
const lg = useMediaQuery('(min-width: 960px)')
const cols = computed(() => {
const reset = { sm: 1, md: 2, lg: 2 }
if (!props.cols)
return reset
if (typeof props.cols === 'number' || typeof props.cols === 'string') {
const cols = Number(props.cols)
return { sm: cols, md: cols, lg: cols }
}
return { ...reset, ...props.cols }
})
const repeat = computed(() => {
if (lg.value)
return cols.value.lg
else if (md.value)
return cols.value.md
else
return cols.value.sm
})
</script>
<template>
<div class="vp-card-grid">
<div
class="vp-card-grid" :class="[`cols-${repeat}`]" :style="{
gridTemplateColumns: `repeat(${repeat}, 1fr)`,
}"
>
<slot />
</div>
</template>
@ -14,10 +50,4 @@
.vp-card-grid > * {
margin: 0 !important;
}
@media (min-width: 768px) {
.vp-card-grid {
grid-template-columns: 1fr 1fr;
}
}
</style>

View File

@ -0,0 +1,191 @@
<script setup lang="ts">
import { computed } from 'vue'
import { usePageLang } from 'vuepress/client'
const props = defineProps<{
image: string
title?: string
description?: string
href?: string
author?: string
date?: string | Date | number
}>()
const lang = usePageLang()
const title = computed(() => {
if (props.title)
return props.title
const image = props.image || ''
const dirs = image.split('/')
return dirs[dirs.length - 1]
})
const date = computed(() => {
if (!props.date)
return ''
const date = props.date instanceof Date ? props.date : new Date(props.date)
const intl = new Intl.DateTimeFormat(
lang.value,
{ year: 'numeric', month: 'short', day: 'numeric' },
)
return intl.format(date)
})
</script>
<template>
<div class="vp-image-card">
<div class="image-container">
<img :src="image" :alt="title" loading="lazy">
<div class="image-info">
<h3 class="title">
<a v-if="href" :href="href" target="_blank" rel="noopener noreferrer">{{ title }}</a>
<span v-else>{{ title }}</span>
</h3>
<p v-if="author || date" class="copyright">
<span v-if="author">{{ author }}</span>
<span v-if="author && date"> | </span>
<span v-if="date">{{ date }}</span>
</p>
<p v-if="description" class="description">
{{ description }}
</p>
</div>
</div>
</div>
</template>
<style scoped>
.vp-image-card {
margin: 16px 0;
box-shadow: var(--vp-shadow-2);
transition: var(--t-color);
transition-property: box-shadow;
}
.vp-image-card:hover {
box-shadow: var(--vp-shadow-4);
}
.vp-image-card .image-container {
position: relative;
overflow: hidden;
font-size: 0;
line-height: 1;
border-radius: 8px;
}
.image-info {
position: absolute;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
width: 100%;
max-height: 100%;
padding: 16px 20px 0;
overflow-y: hidden;
font-size: 14px;
color: var(--vp-c-white);
background-color: rgba(0, 0, 0, 0.5);
border-top-left-radius: 8px;
border-top-right-radius: 8px;
transition: transform var(--t-color);
transform: translateY(calc(100% - 60px));
}
:where(.vp-card-grid.cols-3) .image-info {
padding: 8px 8px 0;
font-size: 12px;
transform: translateY(calc(100% - 36px));
}
@media (max-width: 767px) {
:where(.vp-card-grid.cols-2) .image-info {
padding: 8px 8px 0;
font-size: 12px;
transform: translateY(calc(100% - 36px));
}
}
.image-info:hover {
transform: translateY(0);
}
.image-info .title {
min-height: 28px;
margin: 0 0 16px;
overflow: hidden;
font-size: 18px;
text-overflow: ellipsis;
white-space: nowrap;
}
:where(.vp-card-grid.cols-3) .image-info .title {
min-height: 20px;
margin: 0 0 8px;
font-size: 14px;
line-height: 20px;
}
@media (max-width: 767px) {
:where(.vp-card-grid.cols-2) .image-info .title {
min-height: 20px;
margin: 0 0 8px;
font-size: 14px;
line-height: 20px;
}
}
.image-info .title a {
color: inherit;
text-decoration: none;
}
.image-info p {
margin: 0;
line-height: 24px;
color: var(--vp-c-white);
}
:where(.vp-card-grid.cols-3) .image-info p {
line-height: 20px;
}
@media (max-width: 767px) {
:where(.vp-card-grid.cols-2) .image-info p {
line-height: 20px;
}
}
.image-info p:last-child {
margin-bottom: 16px;
}
.image-info .copyright {
display: flex;
gap: 4px;
align-items: center;
justify-content: flex-end;
}
.image-info .copyright span:first-child {
flex: 1;
overflow: hidden;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
}
.image-info .description {
flex: 1;
height: 1px;
overflow-y: auto;
}
.image-info .description::-webkit-scrollbar {
width: 0;
height: 0;
}
</style>

View File

@ -5,6 +5,7 @@ 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'
import VPImageCard from '@theme/global/VPImageCard.vue'
import VPIcon from '@theme/VPIcon.vue'
export function globalComponents(app: App) {
@ -20,6 +21,9 @@ export function globalComponents(app: App) {
app.component('VPLinkCard', VPLinkCard)
app.component('LinkCard', VPLinkCard)
app.component('VPImageCard', VPImageCard)
app.component('ImageCard', VPImageCard)
app.component('DocSearch', () => {
const SearchComponent
= app.component('Docsearch') || app.component('SearchBox')
@ -40,7 +44,6 @@ export function globalComponents(app: App) {
app.component('Icon', VPIcon)
app.component('VPIcon', VPIcon)
/** @deprecated */
app.component('HomeBox', VPHomeBox)
app.component('VPHomeBox', VPHomeBox)
}