仿element风格Demo展示
大约 3 分钟
仿 element 风格 Demo 展示
最近使用 vuepress-theme-hope
搭建博客时,发现之前使用的 demo 展示出现了问题,控制台报错并且不展示组件了,赶紧去官网看了下,也是同样的问题,这里决定自己封装下
安装 markdown 增强插件
先安装用于增强 markdown 的插件
pnpm install markdown-it-container markdown-it
创建插件
在根目录下创建的 plugins 存放本地插件
这里仿照 vuepress2 生态系统中的 markdown 增强,通过插件检索 :::demo :::
,重新渲染成组件 PreviewComponents
// plugins/markdown/plugin-preview-demo/index.ts
import { Plugin } from 'vuepress';
import mdContainer from 'markdown-it-container';
export const previewDemoPlugin = (options = {}): Plugin => {
options = Object.assign({});
return (app) => {
return {
name: 'vuepress-plugin-preview-demo',
extendsMarkdown: (md) => {
md.use(mdContainer, 'demo', {
validate(params) {
return params.trim().match(/^demo\s*(.*)$/);
},
render(tokens, idx) {
if (tokens[idx].nesting === 1) {
return `
<PreviewComponents>
`;
}
return `</PreviewComponents>`;
},
});
},
};
};
};
编写预览组件
预览插件主要分两块
- 组件渲染区域
- 代码展示区域
这里组件渲染通过具名插槽动态插入,代码通过默认插槽渲染代码块,这样可以省去代码块高亮处理
预览组件样式则参考 element-plus 文档样式
<template>
<div class="example">
<div class="example-showcase">
<slot name="component"></slot>
</div>
<el-divider />
<div class="op-btns">
<el-tooltip
class="box-item"
content="复制代码"
effect="dark"
placement="bottom"
>
<i class="el-icon" style="font-size: 16px" @click="handleCopy">
<CopyCode />
</i>
</el-tooltip>
<el-tooltip
class="box-item"
:content="expandedTooltip"
effect="dark"
placement="bottom"
>
<i class="el-icon" style="font-size: 16px" @click="handleExpanded">
<ViewCode />
</i>
</el-tooltip>
</div>
<el-collapse-transition>
<div class="example-source-wrapper" v-show="isExpanded" ref="code">
<slot />
</div>
</el-collapse-transition>
<transition name="el-fade-in">
<div
class="example-float-control"
v-show="isExpanded"
@click="handleHidden"
>
<el-icon><CaretTop /></el-icon>
<span>隐藏源代码</span>
</div>
</transition>
</div>
</template>
<script lang="ts" setup>
import { computed, ref } from 'vue';
import { CaretTop } from '@element-plus/icons-vue';
import { CopyCode, ViewCode } from '@icons';
import { copyText } from '@utils';
const isExpanded = ref(false);
const expandedTooltip = computed(() => {
return isExpanded.value ? '隐藏源代码' : '查看源代码';
});
const code = ref<any>(null);
const handleCopy = () => {
const text = (code.value as HTMLElement).innerText;
copyText(text);
};
const handleExpanded = () => {
isExpanded.value = !isExpanded.value;
};
const handleHidden = () => {
isExpanded.value = false;
};
//
</script>
<style lang="scss" scoped>
.example {
border: 1px solid #dcdfe6;
border-radius: 4px;
.el-divider {
margin: 0;
}
.op-btns {
display: flex;
align-items: center;
justify-content: flex-end;
height: 24px;
padding: 8px;
.el-icon {
margin: 0 8px;
cursor: pointer;
color: #909399;
}
}
}
.example-showcase {
padding: 24px;
background: #fff;
}
.example-source-wrapper {
margin: -15px 0;
min-height: 30px;
}
.example-float-control {
position: sticky;
left: 0;
right: 0;
bottom: 0;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid #dcdcdc;
height: 44px;
background-color: #fff;
font-size: 14px;
color: #909399;
cursor: pointer;
&:hover {
color: #409eff;
}
span {
margin-left: 10px;
}
}
</style>
将组件注册
// src/.vuepress/client.ts
// 引入Demo预览组件,于本地插件中动态替换 ::: demo ::: 标签
import PreviewComponents from './components/PreviewComponents/index.vue';
export default defineClientConfig({
enhance({ app }) {
// ...
app.component('PreviewComponents', PreviewComponents);
},
});