Vue3 + Vite使用Vitepress编辑基于Element-plus再次封装的Vue3基础组件库文档弃用vitepress-theme-demoblock,使用自己封装的Demo组件

最终效果

使用方式

在这里插入图片描述

页面效果

在这里插入图片描述

一、需求

vitepress-theme-demoblock插件在编辑md文件时,不支持TSX模式,也无法import其他依赖,并且展开隐藏的vue代码都在md文件中,不够优雅,且每个md文件太长。至此弃用此拆件,改用自己封装demo组件

二、plugins配置:在docs/.vitepress下新建config文件夹

1、在config文件夹新建global.ts文件,代码如下

import { resolve } from 'path'
// 项目目录
export const projRoot = resolve(__dirname, '..', '..', '..')
// 项目名称
export const docsDirName = 'docs'
// 文档库目录
export const docRoot = resolve(projRoot, docsDirName)

2、在docs/.vitepress下新建utils文件夹,并新建highlight.ts文件,代码如下

# 依赖安装
pnpm add chalk escape-html prismjs consola -D
// 代码高亮
import chalk from 'chalk'
// @ts-ignore
import escapeHtml from 'escape-html'
// @ts-ignore
import prism from 'prismjs'
import consola from 'consola'

// prism is listed as actual dep so it's ok to require
// eslint-disable-next-line @typescript-eslint/no-var-requires
// const loadLanguages = require('prismjs/components/index')
import loadLanguages from 'prismjs/components/index'
// required to make embedded highlighting work...
loadLanguages(['markup', 'css', 'javascript'])

function wrap(code: string, lang: string): string {
  if (lang === 'text') {
    code = escapeHtml(code)
  }
  return `<pre v-pre><code>${code}</code></pre>`
}

export const highlight = (str: string, lang: string) => {
  if (!lang) {
    return wrap(str, 'text')
  }
  lang = lang.toLowerCase()
  const rawLang = lang
  if (lang === 'vue' || lang === 'html') {
    lang = 'markup'
  }
  if (lang === 'md') {
    lang = 'markdown'
  }
  if (lang === 'ts') {
    lang = 'typescript'
  }
  if (lang === 'py') {
    lang = 'python'
  }
  if (!prism.languages[lang]) {
    try {
      loadLanguages([lang])
    } catch {
      // eslint-disable-next-line no-console
      consola.warn(
        chalk.yellow(
          `[vitepress] Syntax highlight for language "${lang}" is not supported.`
        )
      )
    }
  }
  if (prism.languages[lang]) {
    const code = prism.highlight(str, prism.languages[lang], lang)
    return wrap(code, rawLang)
  }
  return wrap(str, 'text')
}

3、在config文件夹新建plugins.ts文件,代码如下

# 依赖安装
pnpm add markdown-it markdown-it-container -D
import path from 'path'
import fs from 'fs'
// @ts-ignore
import MarkdownIt from 'markdown-it'
// @ts-ignore
import mdContainer from 'markdown-it-container'
// @ts-ignore
import type Token from 'markdown-it/lib/token'
import { highlight } from '../utils/highlight'
import { docRoot } from './global'
const localMd = MarkdownIt()

interface ContainerOpts {
  marker?: string | undefined
  validate?(params: string): boolean
  render?(tokens: Token[], index: number): string
}
export const mdPlugin = (md: MarkdownIt) => {
  md.use(mdContainer, 'demo', {
    validate(params) {
      return !!params.trim().match(/^demo\s*(.*)$/)
    },
    render(tokens, idx) {
      const m = tokens[idx].info.trim().match(/^demo\s+(.*)$/)
      if (tokens[idx].nesting === 1) {
        const description = m && m.length > 1 ? m[1] : ''
        const sourceFileToken = tokens[idx + 2]
        let source = ''
        // demo文件名称
        const sourceFile = sourceFileToken.children?.[0].content ?? ''
        if (sourceFileToken.type === 'inline') {
          // 读取示列代码文件
          source = fs.readFileSync(
            path.resolve(docRoot, 'examples', `${sourceFile}.vue`),
            'utf-8'
          )
        }
        if (!source) throw new Error(`Incorrect source file: ${sourceFile}`)
        // opening tag
        return `<Demo
                        source="${encodeURIComponent(highlight(source, 'vue'))}"
                        path="${sourceFile}"
                        raw-source="${encodeURIComponent(source)}"
                        description="${encodeURIComponent(
                          localMd.render(description)
                        )}">`
      } else {
        // closing tag
        return '</Demo>\n'
      }
    },
  } as ContainerOpts)
}

三、在config.ts中配置markdown,新增如下代码:

import { mdPlugin } from './config/plugins'
export default defineConfig({
  markdown: {
    config: (md) => mdPlugin(md),
  },
})

四、vp-demo组件封装:在docs/.vitepress下新建vitepress文件夹

1、vitepress下新建components文件夹在新建vp-demo文件夹

1、在vp-demo文件夹下新建vp-example.vue文件(渲染.vue文件),代码如下:

<template>
  <ClientOnly>
    <div class="example-component">
      <component
        :is="dynamicComponent"
        v-if="dynamicComponent"
        v-bind="$attrs"
      />
      <div v-else class="example-component--spin">
        <div></div>
        <div></div>
      </div>
    </div>
  </ClientOnly>
</template>

<script lang="ts" setup>
import { onBeforeMount, shallowRef } from 'vue'
const props: any = defineProps<{
  path?: string
}>()
// 创建一个跟踪自身 .value 变化的 ref,但不会使其值也变成响应式的。
let dynamicComponent = shallowRef(null)
onBeforeMount(() => {
  // 匹配到的文件默认是懒加载的,通过动态导入实现,并会在构建时分离为独立的 chunk。如果你倾向于直接引入所有的模块(例如依赖于这些模块中的副作用首先被应用),你可以传入 { eager: true } 作为第二个参数:
  const modules = import.meta.glob(`../../../../examples/*/*.vue`, {
    eager: true,
  })
  // 动态加载示列组件
  for (const modulesKey in modules) {
    const module = modules[modulesKey]
    // 找到example的组件,并加载
    if (modulesKey.split('.vue')[0].endsWith(props.path)) {
      dynamicComponent.value = module.default
    }
  }
})
</script>

<style lang="scss" scoped>
// loading动画
@keyframes lds-ripple {
  0% {
    top: 18px;
    left: 18px;
    width: 0;
    height: 0;
    opacity: 0;
  }
  4.9% {
    top: 18px;
    left: 18px;
    width: 0;
    height: 0;
    opacity: 0;
  }
  5% {
    top: 18px;
    left: 18px;
    width: 0;
    height: 0;
    opacity: 1;
  }
  100% {
    top: 0px;
    left: 0px;
    width: 36px;
    height: 36px;
    opacity: 0;
  }
}
.example-component {
  // min-height: 86px;
  // padding: 1.5rem;
  &--spin {
    width: 36px;
    height: 36px;
    display: inline-block;
    position: relative;
    > div {
      position: absolute;
      border: 4px solid var(--theme-light);
      opacity: 1;
      border-radius: 50%;
      animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
    }

    div:nth-child(2) {
      animation-delay: -0.5s;
    }
  }
}
</style>

2、在vp-demo文件夹下新建vp-source-code.vue文件(vue源码展开隐藏文件),代码如下:

<script setup lang="ts">
import { computed } from 'vue'

const props = defineProps({
  source: {
    type: String,
    required: true,
  },
})

const decoded = computed(() => {
  return decodeURIComponent(props.source)
})
</script>

<template>
  <div class="example-source-wrapper">
    <div class="example-source language-vue" v-html="decoded" />
  </div>
</template>

<style scoped lang="scss">
.language-vue {
  margin: 0 !important;
  border-radius: 0;
}
</style>

3、在vp-demo文件夹下新建index.vue文件,代码如下:

<template>
  <ClientOnly>
    <p text="sm" v-html="decodedDescription" />
    <div class="example">
      <Example :path="path" />
      <ElDivider class="m-0" />
      <div class="op-btns">
        <ElTooltip content="复制代码" :show-arrow="false">
          <ElIcon :size="16" class="op-btn" @click="copyCode">
            <CopyDocument />
          </ElIcon>
        </ElTooltip>
        <ElTooltip content="查看源代码" :show-arrow="false">
          <ElIcon :size="16" class="op-btn" @click="toggleSourceVisible()">
            <View />
          </ElIcon>
        </ElTooltip>
      </div>
      <ElCollapseTransition>
        <SourceCode v-show="sourceVisible" :source="source" />
      </ElCollapseTransition>
      <Transition name="el-fade-in-linear">
        <div
          v-show="sourceVisible"
          class="example-float-control"
          @click="toggleSourceVisible(false)"
        >
          <ElIcon :size="16">
            <CaretTop />
          </ElIcon>
          <span>隐藏源代码</span>
        </div>
      </Transition>
    </div>
  </ClientOnly>
</template>

<script lang="ts" setup>
import { computed } from 'vue'
import { ElMessage } from 'element-plus'
import { useClipboard, useToggle } from '@vueuse/core'

import Example from './vp-example.vue'
import SourceCode from './vp-source-code.vue'

const props = defineProps<{
  rawSource: string // 源码
  source: string
  path: string
  description?: string
}>()

const { copy, isSupported } = useClipboard({
  source: decodeURIComponent(props.rawSource),
  read: false,
})

const [sourceVisible, toggleSourceVisible] = useToggle(false)

const decodedDescription = computed(() =>
  decodeURIComponent(props.description!)
)

const copyCode = async () => {
  if (!isSupported) {
    ElMessage.error('复制失败')
  }
  try {
    await copy()
    ElMessage.success('已复制')
  } catch (e: any) {
    ElMessage.error(e.message)
  }
}
</script>
<style lang="scss" scoped>
.example {
  border: 1px solid var(--border-color);
  border-radius: var(--el-border-radius-base);
  .m-0 {
    margin: 0;
  }
  .op-btns {
    padding: 0.5rem;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    height: 2.5rem;

    .el-icon {
      &:hover {
        color: var(--text-color);
      }
    }

    .op-btn {
      margin: 0 0.5rem;
      cursor: pointer;
      color: var(--text-color-lighter);
      transition: 0.2s;

      &.github a {
        transition: 0.2s;
        color: var(--text-color-lighter);

        &:hover {
          color: var(--text-color);
        }
      }
    }
  }

  &-float-control {
    display: flex;
    align-items: center;
    justify-content: center;
    border-top: 1px solid var(--border-color);
    height: 44px;
    box-sizing: border-box;
    background-color: var(--bg-color, #fff);
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    margin-top: -1px;
    color: var(--el-text-color-secondary);
    cursor: pointer;
    position: sticky;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 10;
    span {
      font-size: 14px;
      margin-left: 10px;
    }

    &:hover {
      color: var(--el-color-primary);
    }
  }
}
</style>

2、vitepress下新建style文件夹

1、新建css-vars.scss文件(变量配置),代码如下

@use './mixins' as *;

// css variables
:root {
  // layouts
  --vp-screen-max-width: 1376px;
  --vp-c-brand: #646cff;
  --vp-c-brand-light: #747bff;
  --vp-c-brand-lighter: #9499ff;
  --vp-c-brand-lightest: #bcc0ff;
  --vp-c-brand-dark: #535bf2;
  --vp-c-brand-darker: #454ce1;
  // colors
  --text-color: var(--el-text-color-primary);
  --text-color-light: var(--el-text-color-regular);
  --text-color-lighter: var(--el-text-color-secondary);

  --brand-color: var(--el-color-primary);
  --brand-color-light: var(--el-color-primary-light-1);
  --bg-brand-color: var(--el-color-primary-light-9);

  --bg-color: var(--el-bg-color);
  --bg-color-rgb: 255, 255, 255;
  --bg-color-soft: #fafafa;
  --bg-color-mute: #f2f2f2;
  --border-color: var(--el-border-color);
  --border-color-light: var(--el-border-color-lighter);

  --font-family: var(--el-font-family);
  --font-family-mono: 'JetBrains Mono', source-code-pro, Menlo, Monaco, Consolas,
    'Courier New', monospace;

  // info
  --success-color: var(--el-color-success);
  --warning-color: var(--el-color-warning);
  --danger-color: var(--el-color-danger);

  // header vars
  --header-height: 55px;
  --nav-height: 55px; // alias of --header-height

  /* Screen Size */
  --vp-screen-max-width: 1362px;

  @include respond-to('xxl') {
    --vp-sidebar-width-small: 234px;
  }

  @include respond-to('max') {
    --vp-screen-max-width: 1482px;
    --vp-sidebar-width-small: 290px;
  }

  // sidebar
  --vp-sidebar-width-mobile: 320px;
  --vp-sidebar-width-small: 266px;

  --sidebar-width-sm: 16rem;
  --sidebar-width-xs: 20rem;
  --content-min-width: 16rem;
  --content-max-width: 48rem;

  --nav-z-index: 11;
  --sub-nav-z-index: 11;
  --sidebar-z-index: 40;
  --overlay-z-index: 30;
  // --dropdown-z-index: 22;

  // code block vars
  --code-line-height: 1.4;
  --code-font-size: var(--el-font-size-base);
  --code-bg-color: var(--el-fill-color-light);
  --code-text-color: var(--text-color);
  --code-font-family: var(--font-family-mono);

  // tip block
  --block-tip-bg-color: rgba(var(--el-color-primary-rgb), 0.1);
  --block-warning-bg-color: rgba(var(--el-color-danger-rgb), 0.1);

  // link
  --link-active-bg-color: rgba(var(--el-color-primary-rgb), 0.1);
}

.dark {
  --bg-color-rgb: 0, 0, 0;
  --bg-color-soft: #242424;
  --bg-color-mute: #2c2c2c;
}

2、新建code.css文件(展开隐藏代码样式),代码如下

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
  color: var(--prism-comment);
  font-style: var(--prism-comment-style);
}

.token.namespace {
  color: var(--prism-namespace);
}

.token.interpolation {
  color: var(--prism-interpolation);
}

.token.string {
  color: var(--prism-string);
}

.token.punctuation {
  color: var(--prism-punctuation);
}

.token.operator {
  color: var(--prism-operator);
}

.token.keyword.module,
.token.keyword.control-flow {
  color: var(--prism-keyword-control);
}

.token.url,
.token.symbol,
.token.inserted {
  color: var(--prism-symbol);
}

.token.constant {
  color: var(--prism-constant);
}

.token.string.url {
  text-decoration: var(--prism-url-decoration);
}

.token.boolean,
.language-json .token.boolean {
  color: var(--prism-boolean);
}

.token.number,
.language-json .token.number {
  color: var(--prism-number);
}

.token.variable {
  color: var(--prism-variable);
}

.token.keyword {
  color: var(--prism-keyword);
}

.token.atrule,
.token.attr-value,
.token.selector {
  color: var(--prism-selector);
}

.token.function {
  color: var(--prism-function);
}

.token.deleted {
  color: var(--prism-deleted);
}

.token.important,
.token.bold {
  font-weight: 700;
}

.token.italic {
  font-style: italic;
}

.token.class-name {
  color: var(--prism-class);
}

.token.tag,
.token.builtin {
  color: var(--prism-builtin);
}

.token.attr-name,
.token.property,
.token.entity {
  color: var(--prism-property);
}

.language-json .token.property {
  color: var(--prism-json-property);
}

.token.regex {
  color: var(--prism-regex);
}

.token.decorator,
.token.annotation {
  color: var(--prism-decorator);
}

.line-numbers .line-numbers-rows {
  border-right-color: var(--prism-line-number);
}

.line-numbers-rows > span:before {
  color: var(--prism-line-number-gutter);
}

.line-highlight {
  background: var(--prism-line-highlight-background);
}

[class*='language-']:before {
  position: absolute;
  top: 0.4em;
  right: 0.8em;
  z-index: 2;
  opacity: var(--prism-marker-opacity);
  font-size: var(--prism-marker-font-size);
  color: var(--prism-marker-color);
}

[class~='language-html']:before,
[class~='language-markup']:before {
  content: 'html';
}

[class~='language-md']:before,
[class~='language-markdown']:before {
  content: 'md';
}

[class~='language-css']:before {
  content: 'css';
}

[class~='language-sass']:before {
  content: 'sass';
}

[class~='language-scss']:before {
  content: 'scss';
}

[class~='language-less']:before {
  content: 'less';
}

[class~='language-stylus']:before {
  content: 'styl';
}

[class~='language-js']:before,
[class~='language-typescript']:before {
  content: 'js';
}

[class~='language-ts']:before,
[class~='language-typescript']:before {
  content: 'ts';
}

[class~='language-json']:before {
  content: 'json';
}

[class~='language-rb']:before,
[class~='language-ruby']:before {
  content: 'rb';
}

[class~='language-py']:before,
[class~='language-python']:before {
  content: 'py';
}

[class~='language-sh']:before,
[class~='language-bash']:before {
  content: 'sh';
}

[class~='language-php']:before {
  content: 'php';
}

[class~='language-go']:before {
  content: 'go';
}

[class~='language-rust']:before {
  content: 'rust';
}

[class~='language-java']:before {
  content: 'java';
}

[class~='language-c']:before {
  content: 'c';
}

[class~='language-yaml']:before {
  content: 'yaml';
}

[class~='language-vue']:before {
  content: 'vue';
}

[class~='language-dockerfile']:before {
  content: 'dockerfile';
}

[class*='language-']:before {
  font-family: var(--code-font-family);
}

span[class~='language-css']:before {
  content: '';
}

:root {
  --el-fill-color-light: #f5f7fa;
  --el-fill-color-lighter: #fafafa;
  --prism-marker-opacity: 0.6;
  --prism-marker-color: var(--code-text-color);
  --prism-line-height: var(--code-line-height);
}

html:not(.dark) {
  --prism-builtin: #3182bd;
  --prism-comment: #848486;
  --prism-deleted: #3182bd;
  --prism-function: #6196cc;
  --prism-boolean: #c25205;
  --prism-number: #c25205;
  --prism-property: #717c11;
  --prism-punctuation: #a8a9cc;
  --prism-keyword: #c792ea;
  --prism-variable: #0b8235;
  --prism-url-decoration: #67cdcc;
  --prism-symbol: green;
  --prism-selector: #0b8235;
}

html.dark {
  --prism-scheme: dark;
  --prism-foreground: #a6accd;
  --prism-background: #181818;
  --prism-comment: #758575;
  --prism-string: #c3e88d;
  --prism-literal: #429988;
  --prism-keyword: #89ddff;
  --prism-boolean: #6394bf;
  --prism-number: #6394bf;
  --prism-variable: #c2b36e;
  --prism-function: #82aaff;
  --prism-deleted: #bc6066;
  --prism-class: #54b1bf;
  --prism-builtin: var(--el-color-primary-light-3);
  --prism-property: #c792ea;
  --prism-namespace: #db889a;
  --prism-punctuation: #89ddff;
  --prism-decorator: #bd8f8f;
  --prism-regex: #ab5e3f;
  --prism-json-property: #6b8b9e;
  --prism-line-number: #888888;
  --prism-line-number-gutter: #eeeeee;
  --prism-line-highlight-background: #444444;
  --prism-selection-background: #444444;
  --prism-inline-background: #2d2d2d;
}

code {
  margin: 0;
  border-radius: 4px;
  padding: 0.15rem 0.5rem;
  font-family: var(--code-font-family);
  font-size: var(--code-font-size);
  color: var(--code-text-color);
  line-height: var(--code-line-height);
  background-color: var(--code-bg-color);
}

pre code {
  background-color: transparent;
}

a > code {
  color: var(--code-link-color);
}

code .token.deleted {
  color: #ec5975;
}

code .token.inserted {
  color: var(--c-brand);
}

div[class*='language-'] {
  position: relative;
  margin: 0;
  background-color: var(--code-bg-color);
  overflow-x: auto;
}

li > div[class*='language-'] {
  border-radius: 6px 0 0 6px;
  margin: 0;
}

@media (min-width: 420px) {
  div[class*='language-'] {
    margin: 0;
    border-radius: 6px;
  }

  li > div[class*='language-'] {
    margin: 0;
    border-radius: 6px;
  }
}

[class*='language-'] pre,
[class*='language-'] code {
  text-align: left;
  white-space: pre;
  word-spacing: normal;
  word-break: normal;
  word-wrap: normal;
  tab-size: 4;
  hyphens: none;
}

[class*='language-'] pre {
  position: relative;
  z-index: 1;
  margin: 0;
  padding: 0.25rem;
  background: transparent;
  overflow-x: auto;
}

[class*='language-'] code {
  padding: 0;
  line-height: var(--code-line-height);
  font-size: var(--code-font-size);
  color: var(--code-text-color);
}

.highlight-lines {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  padding: 1.25rem 0;
  width: 100%;
  line-height: var(--code-line-height);
  font-family: var(--code-font-family);
  font-size: var(--code-font-size);
  user-select: none;
  overflow: hidden;
}

.highlight-lines .highlighted {
  background-color: #000000a8;
}

div[class*='language-'].line-numbers-mode {
  padding-left: 3.5rem;
}

.line-numbers-wrapper {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: 3;
  border-right: 1px solid var(--el-overlay-color-lighter);
  padding: 1.25rem 0;
  width: 3.5rem;
  text-align: center;
  line-height: var(--code-line-height);
  font-family: var(--code-font-family);
  font-size: var(--code-font-size);
  color: #888;
}

.navbar-menu {
  display: none;
}

@media screen and (min-width: 768px) {
  .navbar-menu {
    display: flex;
  }
}

.navbar-wrapper {
  position: relative;
  border-bottom: 1px solid var(--border-color);
  height: var(--header-height);
  padding: 0 12px 0 24px;
  background-image: radial-gradient(transparent 1px, var(--bg-color) 1px);
  background-size: 4px 4px;
  backdrop-filter: saturate(50%) blur(4px);
  -webkit-backdrop-filter: saturate(50%) blur(4px);
  top: 0;
}

@media screen and (min-width: 768px) {
  .navbar-wrapper {
    padding: 0 12px 0 32px;
  }
}

@media screen and (min-width: 1280px) {
  .navbar-wrapper {
    padding: 0 32px;
  }
}

.navbar-wrapper .header-container {
  display: flex;
  justify-content: space-between;
  margin: 0 auto;
}

.navbar-wrapper .header-container .content {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  flex-grow: 1;
}

@media screen and (min-width: 1440px) {
  .navbar-wrapper .header-container {
    max-width: calc(var(--vp-screen-max-width));
  }
}

@media screen and (min-width: 1680px) {
  .navbar-wrapper .header-container {
    max-width: calc(var(--vp-screen-max-width));
  }
}

@media screen and (min-width: 768px) {
  .navbar-wrapper .content {
    padding-top: 1px;
  }
}

.navbar {
  top: 0;
  left: 0;
  position: relative;
  z-index: var(--nav-z-index);
}

@media screen and (min-width: 960px) {
  .navbar {
    position: sticky;
    width: 100%;
  }
}

.menu + .appearance {
  margin-left: 8px;
}

.menu + .social-links {
  margin-left: 12px;
}

.appearance + .social-links {
  margin-left: 12px;
}

* {
  scrollbar-color: var(--el-scrollbar-bg-color) var(--el-fill-color-light);
}

::-webkit-scrollbar {
  width: 6px;
}

::-webkit-scrollbar:horizontal {
  height: 6px;
}

::-webkit-scrollbar-track {
  border-radius: 10px;
}

::-webkit-scrollbar-thumb {
  background-color: #0003;
  border-radius: 10px;
  transition: all 0.2s ease-in-out;
}

::-webkit-scrollbar-thumb:hover {
  cursor: pointer;
  background-color: #0000004d;
}

.dark ::-webkit-scrollbar-thumb {
  background-color: #fff3;
}

.dark ::-webkit-scrollbar-thumb:hover {
  background-color: #fff6;
}

.sub-nav {
  border-bottom: 1px solid var(--border-color);
  background-color: var(--bg-color);
  color: var(--text-color);
  transition: border-color var(--el-transition-duration),
    background-color var(--el-transition-duration-fast);
  position: sticky;
  top: 0;
  left: 0;
  width: 100%;
  display: flex;
  padding: 0 32px;
  justify-content: space-between;
  z-index: var(--sub-nav-z-index);
  overflow: hidden;
}

@media (max-width: 767px) {
  .sub-nav {
    padding: 0 24px;
  }
}

.sub-nav .go-back-top {
  transform: translateY(100%);
  opacity: 0;
}

.sub-nav .go-back-top.show {
  transform: translateY(0);
  opacity: 1;
}

@media screen and (min-width: 960px) {
  .sub-nav {
    display: none !important;
  }
}

.overlay {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: rgba(0, 0, 0, 0.6);
  transition: opacity 0.5s;
  z-index: var(--overlay-z-index);
}

.sidebar {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  z-index: var(--sidebar-z-index);
  width: var(--sidebar-width-xs);
  background-color: var(--bg-color);
  padding: 48px 32px 0;
  overflow-y: auto;
  transform: translate(-100%);
  transition: background-color var(--el-transition-duration-fast), opacity 0.25s,
    transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
}

.sidebar.open {
  transform: translate(0);
}

.sidebar .sidebar-groups {
  padding: 0 0 5rem;
}

.sidebar .sidebar-groups .sidebar-group__title {
  font-size: 1rem;
  font-weight: 700;
  margin-bottom: 8px;
  line-height: 24px;
}

.sidebar .sidebar-groups .sidebar-group + .sidebar-group {
  padding-top: 24px;
}

@media (max-width: 767px) {
  .sidebar {
    width: calc(var(--vp-sidebar-width-mobile) - 14px);
  }
}

@media screen and (min-width: 768px) {
  .sidebar {
    width: calc(var(--vp-sidebar-width-small));
  }
}

@media screen and (min-width: 960px) {
  .sidebar {
    top: var(--header-height);
    transform: translate(0);
  }
}

@media screen and (min-width: 1440px) {
  .sidebar {
    padding: 48px 32px 96px calc((100vw - var(--vp-screen-max-width)) / 2);
    width: calc(
      (100vw - var(--vp-screen-max-width)) / 2 + var(--vp-sidebar-width-small)
    );
  }
}

@media screen and (min-width: 1680px) {
  .sidebar {
    padding: 48px 48px 96px calc((100vw - var(--vp-screen-max-width)) / 2);
    width: calc(
      (100vw - var(--vp-screen-max-width)) / 2 + var(--vp-sidebar-width-small)
    );
  }
}

.toc-wrapper {
  display: none;
  padding-left: 64px;
}

.toc-wrapper .toc-content {
  position: sticky;
  top: calc(var(--header-height) + 32px);
  margin-top: 0;
  padding: 4px 8px 4px 12px;
  margin-bottom: 32px;
  width: 200px;
}

.toc-wrapper .toc-content .toc-marker {
  opacity: 0;
  position: absolute;
  background-color: var(--brand-color);
  border-radius: 4px;
  width: 4px;
  height: 14px;
  top: 30px;
  left: 0;
  z-index: 0;
  transition: top 0.25s cubic-bezier(0, 1, 0.5, 1), opacity 0.25s,
    background-color 0.5s;
}

.toc-wrapper .toc-content__heading {
  font-size: 12px;
  color: var(--text-color-light);
  font-weight: 600;
  text-transform: uppercase;
  margin-top: 0;
}

.toc-wrapper .toc-content .toc-items {
  list-style: none;
  padding: 0;
  margin: 12px 0 0;
  line-height: 1.2;
}

.toc-wrapper .toc-content .toc-items .toc-item {
  margin-top: 10px;
  font-size: 11px;
  color: var(--text-color-lighter);
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
  color: inherit;
}
.toc-wrapper .toc-content .toc-items .toc-item .toc-link {
  position: relative;
  color: var(--text-color-lighter);
  transition: color var(--el-transition-duration);
}
.toc-wrapper .toc-content .toc-items .toc-item .toc-link.active {
  color: var(--brand-color);
}
.toc-wrapper .toc-content .toc-items .toc-item.subitem {
  padding-left: 1rem;
}

@media screen and (min-width: 1440px) {
  .toc-wrapper {
    display: block;
  }
}
@media screen and (min-width: 1680px) {
  .toc-wrapper {
    padding-left: 96px;
    display: block;
  }
}
.doc-content-wrapper {
  --vp-content-width: 800px;
  padding: 32px 24px 96px;
}
.doc-content-wrapper .doc-content-container .doc-content {
  width: 100%;
}
@media screen and (min-width: 1440px) {
  .doc-content-wrapper .doc-content-container {
    width: var(--vp-content-width);
  }
}
@media screen and (min-width: 768px) {
  .doc-content-wrapper {
    padding: 48px 32px 42px;
  }
}

@media screen and (min-width: 960px) {
  .doc-content-wrapper {
    padding: 64px 64px 42px;
  }
}
@media screen and (min-width: 1440px) {
  .doc-content-wrapper {
    padding: 64px 0 42px 64px;
    display: flex;
  }
}
@media screen and (min-width: 1680px) {
  .doc-content-wrapper {
    padding: 64px 0 42px 96px;
    display: flex;
  }
}
.hero-content {
  padding: 40px 40px 0;
}
@media (max-width: 768px) {
  .hero-content {
    padding: 30px 10px 0;
  }
}
.vp-tag {
  --vp-tag-color: var(--el-color-primary);
  --vp-tag-border-color: var(--el-color-primary);
  display: inline-block;
  padding: 0 7px;
  border-radius: 10px;
  border: 1px solid var(--vp-tag-border-color);
  font-size: var(--el-font-size-extra-small);
  color: var(--vp-tag-color);
  line-height: 18px;
  white-space: nowrap;
}
.vp-tag.beta {
  --vp-tag-color: var(--el-color-danger);
  --vp-tag-border-color: var(--el-color-danger);
}
.vp-tag.deprecated {
  --vp-tag-color: var(--el-color-warning);
  --vp-tag-border-color: var(--el-color-warning);
}
.vp-tag.a11y {
  --vp-tag-color: var(--purple-color);
  --vp-tag-border-color: var(--purple-color-light);
}
.page-content {
  outline: none;
}
@media screen and (min-width: 960px) {
  .page-content {
    padding-top: var(--nav-height);
  }
  .page-content.has-sidebar {
    padding-left: calc(var(--sidebar-width-sm) + 10px);
  }
}
@media screen and (min-width: 960px) and (min-width: 1280px) {
  .page-content.has-sidebar {
    padding-left: calc(var(--vp-sidebar-width-small) - 6px);
  }
}
@media screen and (min-width: 960px) and (min-width: 1440px) {
  .page-content.has-sidebar {
    padding-left: calc(
      (100% - var(--vp-screen-max-width)) / 2 + var(--vp-sidebar-width-small)
    );
  }
}
.page-content .doc-content a {
  display: inline-flex;
  align-items: center;
}
.page-content .doc-content a.vp-link {
  white-space: nowrap;
  word-break: keep-all;
}
.page-content .doc-content a .link-icon {
  margin-left: 0.25rem;
  height: 1em;
  width: 1em;
}
.container {
  max-width: 100%;
}
@media (min-width: 640px) {
  .container {
    max-width: 640px;
  }
}

@media (min-width: 768px) {
  .container {
    max-width: 768px;
  }
}

@media (min-width: 1024px) {
  .container {
    max-width: 1024px;
  }
}
@media (min-width: 1280px) {
  .container {
    max-width: 1280px;
  }
}
@media (min-width: 1536px) {
  .container {
    max-width: 1536px;
  }
}
.\!visible {
  visibility: visible !important;
}
.visible {
  visibility: visible;
}
.invisible {
  visibility: hidden;
}
.absolute {
  position: absolute;
}
.fixed,
[fixed=''] {
  position: fixed;
}
.relative {
  position: relative;
}
.sticky {
  position: sticky;
}

3、新建vars.scss、mixins.scss、index文件,代码如下

// vars.scss
$breakpoint-max: 1680px !default;
$breakpoint-xxl: 1440px !default;
$breakpoint-xlg: 1280px !default;
$breakpoint-lg: 960px !default;
$breakpoint-md: 768px !default;
$breakpoint-sm: 480px !default;

$scrollbar-size: 6px;
// mixins.scss	
@use 'sass:map';

@use './vars' as *;

@mixin with-bg {
  background-color: var(--bg-color);
  color: var(--text-color);
  transition: border-color var(--el-transition-duration),
    background-color var(--el-transition-duration-fast);
}

@mixin with-border {
  border-bottom: 1px solid var(--border-color);
}

$breakpoints: (
  'sm': #{$breakpoint-sm},
  'md': #{$breakpoint-md},
  'lg': #{$breakpoint-lg},
  'xlg': #{$breakpoint-xlg},
  'xxl': #{$breakpoint-xxl},
  'max': #{$breakpoint-max},
) !default;

@mixin respond-to($breakpoint) {
  @if #{map.has-key($breakpoints, $breakpoints)} {
    @media screen and (min-width: #{map.get($breakpoints, $breakpoint)}) {
      @content;
    }
  }
}
// index.scss
@import "vars.scss";
@import "code.css";

2、vitepress下新建index.ts文件,代码如下:

import 'normalize.css'
import VPDemo from './components/vp-demo/index.vue'
import './style/css-vars.scss'
import './style/index.scss'
export { VPDemo }

五、注册vp-demo组件,在theme文件中新增引入注册

import { VPDemo } from '../vitepress'
export default {
  enhanceApp(ctx) {
   .....
    ctx.app.component('Demo', VPDemo)
  },
}

六、在docs文件夹下新增examples文件夹(命名你可以随意——别忘记要与vp-example.vue文件引入一致)

七、在docs文件夹下新增vite.config.ts、tsconfig.json文件,解决examples文件<script setup lang="tsx">报错

// vite.config.ts
import { defineConfig } from 'vite'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueSetupExtend from 'vite-plugin-vue-setup-extend' // 设置neme属性
export default defineConfig({
  plugins: [vueJsx(), vueSetupExtend()],
  server: {
    host: '0.0.0.0',
    port: 2222,
    open: true,
    https: false,
  },
})
// tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitAny": false,
    "skipLibCheck": true,
    "jsx": "preserve",
  }
}

八、完成以上配置,那么在md文件中如下编写即可实现vue文件渲染及源码展示与隐藏(以TSelect组件为例)

1、examples下新建TSelect文件夹在新建single.vue文件,代码如下:

<template>
  <t-layout-page>
    <t-select
      placeholder="请选择工序"
      v-model="selectVlaue"
      :optionSource="stepList"
      valueKey="label"
      @change="selectChange"
      width="200px"
    />
  </t-layout-page>
</template>
<script setup lang="ts" name="Single">
import { ref } from 'vue'
const selectVlaue = ref<any>()
const stepList = [
  { label: '开始' },
  { label: 'POSUI' },
  { label: '11' },
  { label: 'GX123' },
  { label: '烘干破碎' },
  { label: '车间仓库' },
  { label: 'ui3333' },
  { label: 'hhh333' },
]
const selectChange = (val: any) => {
  console.log('selectChange', val, selectVlaue.value)
}
</script>

2、components下新建TSelect文件夹在新建base.md文件,代码如下:

# TSelect 下拉选择组件

### 单选

:::demo
TSelect/single
:::

3、页面效果如下:

在这里插入图片描述

源码地址

gitHub组件地址

gitee码云组件地址

相关文章

基于ElementUi&Antd再次封装基础组件文档


vue3+ts基于Element-plus再次封装基础组件文档


vite+vue3+ts项目搭建之集成qiankun

  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
个人博客网站是一个用于展示个人博客内容的网站。其中,Vue3是一个流行的JavaScript框架,它提供了一种简洁和高效的方法来构建用户界面。Vue3采用了一些改进的特性,如响应性系统的重写、新的组合式API、更高效的虚拟DOM等。这些改进使得Vue3在性能和开发体验方面有了显著的提升。 Vite是一个新一代的构建工具,它专注于提供快速的冷启动和快速的开发体验。Vite基于ESM(ES模块)构建,通过利用现代浏览器原生的模块引入能力来消除了繁琐的打包步骤。Vite还支持热重载,可以在开发过程中实时更新页面内容,提高开发效率。 Pinia是一个专门为Vue3设计的状态管理。它提供了一种简单且可扩展的方式来管理应用程序中的状态。Pinia通过使用Vue3的响应式系统,能够高效地管理状态,并提供了丰富的API来处理状态的变化和逻辑。 Element Plus是一个基于Vue3的UI组件,它提供了一套丰富的、美观的界面组件,帮助开发者简化开发和设计工作。Element Plus内置了大量的常用组件,如按钮、表格、表单等,可以通过简单的配置和组合来构建复杂的界面。 综上所述,个人博客网站使用Vue3作为前端框架,通过Vite进行快速构建和开发,在状态管理方面选用Pinia,并使用Element Plus作为UI组件,这样可以提供更好的开发体验和用户界面效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wocwin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值