فهرست منبع

refactor(架构调整): 稳植与二次更改element-plus build tools等功能

gemercheung 2 سال پیش
والد
کامیت
c0c00f36eb
100فایلهای تغییر یافته به همراه5464 افزوده شده و 0 حذف شده
  1. 0 0
      docs/.vitepress/config.mts
  2. 10 0
      docs/.vitepress/config/analytics.ts
  3. 1 0
      docs/.vitepress/config/features.ts
  4. 59 0
      docs/.vitepress/config/head.ts
  5. 7 0
      docs/.vitepress/config/index.ts
  6. 23 0
      docs/.vitepress/config/nav.ts
  7. 49 0
      docs/.vitepress/config/plugins.ts
  8. 41 0
      docs/.vitepress/config/sidebars.ts
  9. 30 0
      docs/.vitepress/vitepress/components/common/vp-link.vue
  10. 30 0
      docs/.vitepress/vitepress/components/common/vp-markdown.vue
  11. 15 0
      docs/.vitepress/vitepress/components/common/vp-switch.vue
  12. 49 0
      docs/.vitepress/vitepress/components/common/vp-theme-toggler.vue
  13. 28 0
      docs/.vitepress/vitepress/components/demo/vp-example.vue
  14. 27 0
      docs/.vitepress/vitepress/components/demo/vp-source-code.vue
  15. 3 0
      docs/.vitepress/vitepress/components/dev/DeprecatedTag.vue
  16. 11 0
      docs/.vitepress/vitepress/components/dev/VersionTag.vue
  17. 28 0
      docs/.vitepress/vitepress/components/doc-content/vp-edit-link.vue
  18. 47 0
      docs/.vitepress/vitepress/components/doc-content/vp-last-updated-at.vue
  19. 35 0
      docs/.vitepress/vitepress/components/doc-content/vp-page-footer.vue
  20. 93 0
      docs/.vitepress/vitepress/components/doc-content/vp-page-nav.vue
  21. 55 0
      docs/.vitepress/vitepress/components/doc-content/vp-table-of-content.vue
  22. 40 0
      docs/.vitepress/vitepress/components/full-screen/vp-menu-link.vue
  23. 23 0
      docs/.vitepress/vitepress/components/full-screen/vp-menu.vue
  24. 24 0
      docs/.vitepress/vitepress/components/full-screen/vp-theme-toggler.vue
  25. 80 0
      docs/.vitepress/vitepress/components/full-screen/vp-translation.vue
  26. 32 0
      docs/.vitepress/vitepress/components/globals/contributors.vue
  27. 58 0
      docs/.vitepress/vitepress/components/globals/design-guide.vue
  28. 46 0
      docs/.vitepress/vitepress/components/globals/design/consistency-svg.vue
  29. 76 0
      docs/.vitepress/vitepress/components/globals/design/controllability-svg.vue
  30. 61 0
      docs/.vitepress/vitepress/components/globals/design/efficiency-svg.vue
  31. 40 0
      docs/.vitepress/vitepress/components/globals/design/feedback-svg.vue
  32. 304 0
      docs/.vitepress/vitepress/components/globals/icons-categories.json
  33. 141 0
      docs/.vitepress/vitepress/components/globals/icons.vue
  34. 33 0
      docs/.vitepress/vitepress/components/globals/main-color.vue
  35. 131 0
      docs/.vitepress/vitepress/components/globals/neutral-color.vue
  36. 381 0
      docs/.vitepress/vitepress/components/globals/parallax-home.vue
  37. 122 0
      docs/.vitepress/vitepress/components/globals/resource.vue
  38. 121 0
      docs/.vitepress/vitepress/components/globals/resources/axure-components-svg.vue
  39. 71 0
      docs/.vitepress/vitepress/components/globals/resources/figma-template-svg.vue
  40. 73 0
      docs/.vitepress/vitepress/components/globals/resources/sketch-template-svg.vue
  41. 35 0
      docs/.vitepress/vitepress/components/globals/secondary-colors.vue
  42. 110 0
      docs/.vitepress/vitepress/components/globals/vp-changelog.vue
  43. 153 0
      docs/.vitepress/vitepress/components/globals/vp-footer.vue
  44. 164 0
      docs/.vitepress/vitepress/components/home/home-cards.vue
  45. 75 0
      docs/.vitepress/vitepress/components/home/home-sponsors.vue
  46. 47 0
      docs/.vitepress/vitepress/components/home/sponsor-list.vue
  47. 61 0
      docs/.vitepress/vitepress/components/home/svg/component-svg.vue
  48. 45 0
      docs/.vitepress/vitepress/components/home/svg/guide-svg.vue
  49. 70 0
      docs/.vitepress/vitepress/components/home/svg/left-bottom-layer-svg.vue
  50. 39 0
      docs/.vitepress/vitepress/components/home/svg/left-layer-svg.vue
  51. 121 0
      docs/.vitepress/vitepress/components/home/svg/people-svg.vue
  52. 33 0
      docs/.vitepress/vitepress/components/home/svg/resource-svg.vue
  53. 46 0
      docs/.vitepress/vitepress/components/home/svg/right-layer-svg.vue
  54. 104 0
      docs/.vitepress/vitepress/components/home/svg/screen-svg.vue
  55. 8 0
      docs/.vitepress/vitepress/components/icons/back-to-top.vue
  56. 8 0
      docs/.vitepress/vitepress/components/icons/codepen.vue
  57. 8 0
      docs/.vitepress/vitepress/components/icons/dark.vue
  58. 8 0
      docs/.vitepress/vitepress/components/icons/element-plus-logo.vue
  59. 15 0
      docs/.vitepress/vitepress/components/icons/element-plus-text-logo.vue
  60. 5 0
      docs/.vitepress/vitepress/components/icons/expand.vue
  61. 8 0
      docs/.vitepress/vitepress/components/icons/light.vue
  62. 15 0
      docs/.vitepress/vitepress/components/icons/playground.vue
  63. 8 0
      docs/.vitepress/vitepress/components/icons/toggle-button.vue
  64. 39 0
      docs/.vitepress/vitepress/components/nav/l1-categories.vue
  65. 64 0
      docs/.vitepress/vitepress/components/nav/l2-categories.vue
  66. 34 0
      docs/.vitepress/vitepress/components/nav/l3-categories.vue
  67. 25 0
      docs/.vitepress/vitepress/components/nav/top-navigation-example.vue
  68. 13 0
      docs/.vitepress/vitepress/components/navbar/vp-hamburger.vue
  69. 67 0
      docs/.vitepress/vitepress/components/navbar/vp-menu-link.vue
  70. 12 0
      docs/.vitepress/vitepress/components/navbar/vp-menu.vue
  71. 204 0
      docs/.vitepress/vitepress/components/navbar/vp-search.vue
  72. 23 0
      docs/.vitepress/vitepress/components/navbar/vp-social-link.vue
  73. 19 0
      docs/.vitepress/vitepress/components/navbar/vp-social-links.vue
  74. 27 0
      docs/.vitepress/vitepress/components/navbar/vp-theme-toggler.vue
  75. 58 0
      docs/.vitepress/vitepress/components/navbar/vp-translation.vue
  76. 68 0
      docs/.vitepress/vitepress/components/sidebar/vp-sidebar-link.vue
  77. 23 0
      docs/.vitepress/vitepress/components/sponsors/right-logo-small-list.vue
  78. 40 0
      docs/.vitepress/vitepress/components/sponsors/right-richtext-list.vue
  79. 23 0
      docs/.vitepress/vitepress/components/sponsors/sponsors-button.vue
  80. 31 0
      docs/.vitepress/vitepress/components/subnav/toggle-sidebar-btn.vue
  81. 128 0
      docs/.vitepress/vitepress/components/vp-app.vue
  82. 48 0
      docs/.vitepress/vitepress/components/vp-content.vue
  83. 180 0
      docs/.vitepress/vitepress/components/vp-demo.vue
  84. 19 0
      docs/.vitepress/vitepress/components/vp-doc-content.vue
  85. 16 0
      docs/.vitepress/vitepress/components/vp-hero-content.vue
  86. 61 0
      docs/.vitepress/vitepress/components/vp-nav-full.vue
  87. 28 0
      docs/.vitepress/vitepress/components/vp-nav.vue
  88. 64 0
      docs/.vitepress/vitepress/components/vp-navbar.vue
  89. 30 0
      docs/.vitepress/vitepress/components/vp-not-found.vue
  90. 11 0
      docs/.vitepress/vitepress/components/vp-overlay.vue
  91. 58 0
      docs/.vitepress/vitepress/components/vp-reload-prompt.vue
  92. 28 0
      docs/.vitepress/vitepress/components/vp-sidebar.vue
  93. 66 0
      docs/.vitepress/vitepress/components/vp-sponsor-large.vue
  94. 56 0
      docs/.vitepress/vitepress/components/vp-sponsor-small.vue
  95. 30 0
      docs/.vitepress/vitepress/components/vp-sponsors.vue
  96. 18 0
      docs/.vitepress/vitepress/components/vp-subnav.vue
  97. 97 0
      docs/.vitepress/vitepress/composables/active-bar.ts
  98. 65 0
      docs/.vitepress/vitepress/composables/back-top.ts
  99. 7 0
      docs/.vitepress/vitepress/composables/dark.ts
  100. 0 0
      docs/.vitepress/vitepress/composables/edit-link.ts

docs/config.mts → docs/.vitepress/config.mts


+ 10 - 0
docs/.vitepress/config/analytics.ts

@@ -0,0 +1,10 @@
+export const sendEvent = (action: string, label: string, value?: any, category?: string): void => {
+    const gtag = (window as any).gtag
+    if (gtag) {
+        gtag('event', action, {
+            event_label: label,
+            event_value: value,
+            event_category: category,
+        })
+    }
+}

+ 1 - 0
docs/.vitepress/config/features.ts

@@ -0,0 +1 @@
+export const features = {}

+ 59 - 0
docs/.vitepress/config/head.ts

@@ -0,0 +1,59 @@
+// import fs from 'fs'
+// import path from 'path'
+// import { vpRoot } from '@element-plus/build-utils'
+// import { languages } from '../utils/lang'
+
+import type { HeadConfig } from 'vitepress'
+
+export const head: HeadConfig[] = [
+    [
+        'link',
+        {
+            rel: 'icon',
+            href: '/images/element-plus-logo-small.svg',
+            type: 'image/svg+xm',
+        },
+    ],
+    [
+        'link',
+        {
+            rel: 'apple-touch-icon',
+            href: '/apple-touch-icon.png',
+            sizes: '180x180',
+        },
+    ],
+    [
+        'link',
+        {
+            rel: 'mask-icon',
+            href: '/safari-pinned-tab.svg',
+            color: '#5bbad5',
+        },
+    ],
+    [
+        'meta',
+        {
+            name: 'theme-color',
+            content: '#ffffff',
+        },
+    ],
+    [
+        'meta',
+        {
+            name: 'msapplication-TileColor',
+            content: '#409eff',
+        },
+    ],
+    [
+        'meta',
+        {
+            name: 'msapplication-config',
+            content: '/browserconfig.xml',
+        },
+    ],
+]
+// head.push([
+//   'script',
+//   {},
+//   fs.readFileSync(path.resolve(vpRoot, 'dark-mode.js'), 'utf-8'),
+// ])

+ 7 - 0
docs/.vitepress/config/index.ts

@@ -0,0 +1,7 @@
+export * from './analytics'
+export * from './features'
+export * from './head'
+export * from './nav'
+export * from './plugins'
+export * from './sidebars'
+// export * from './sponsors'

+ 23 - 0
docs/.vitepress/config/nav.ts

@@ -0,0 +1,23 @@
+import { ensureLang } from '../utils/lang'
+import navLocale from '../i18n/pages/sidebar.json'
+
+// Mapping the first sub link to the nav link to avoid 404 error.
+
+function getNav() {
+    return Object.fromEntries(
+        Object.entries(navLocale).map(([lang, locales]) => {
+            const item: {
+                link: string
+                text: string
+                activeMatch?: string
+            }[] = Object.values(locales).map(item => ({
+                ...item,
+                link: `${ensureLang(lang)}${item.link}`,
+            }))
+
+            return [lang, item]
+        })
+    )
+}
+
+export const nav = getNav()

+ 49 - 0
docs/.vitepress/config/plugins.ts

@@ -0,0 +1,49 @@
+import path from 'path'
+import fs from 'fs'
+import MarkdownIt from 'markdown-it'
+import mdContainer from 'markdown-it-container'
+import { docRoot } from '@element-plus/build-utils'
+import externalLinkIcon from '../plugins/external-link-icon'
+import tableWrapper from '../plugins/table-wrapper'
+import { highlight } from '../utils/highlight'
+import type Token from 'markdown-it/lib/token'
+import type Renderer from 'markdown-it/lib/renderer'
+
+const localMd = MarkdownIt()
+
+interface ContainerOpts {
+    marker?: string | undefined
+    validate?(params: string): boolean
+    render?(tokens: Token[], index: number, options: any, env: any, self: Renderer): string
+}
+
+export const mdPlugin = (md: MarkdownIt) => {
+    md.use(externalLinkIcon)
+    md.use(tableWrapper)
+    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 /* means the tag is opening */) {
+                const description = m && m.length > 1 ? m[1] : ''
+                const sourceFileToken = tokens[idx + 2]
+                let source = ''
+                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}`)
+
+                return `<Demo :demos="demos" source="${encodeURIComponent(highlight(source, 'vue'))}" path="${sourceFile}" raw-source="${encodeURIComponent(source)}" description="${encodeURIComponent(
+                    localMd.render(description)
+                )}">`
+            } else {
+                return '</Demo>'
+            }
+        },
+    } as ContainerOpts)
+}

+ 41 - 0
docs/.vitepress/config/sidebars.ts

@@ -0,0 +1,41 @@
+import { ensureLang } from '../utils/lang'
+import guideLocale from '../i18n/pages/guide.json'
+import componentLocale from '../i18n/pages/component.json'
+
+function getGuideSidebar() {
+    return Object.fromEntries(Object.entries(guideLocale).map(([lang, val]) => [lang, Object.values(val).map(item => mapPrefix(item, lang))]))
+}
+
+function getComponentsSideBar() {
+    return Object.fromEntries(Object.entries(componentLocale).map(([lang, val]) => [lang, Object.values(val).map(item => mapPrefix(item, lang, '/component'))]))
+}
+
+// return sidebar with language configs.
+// this might create duplicated data but the overhead is ignorable
+const getSidebars = () => {
+    return {
+        '/guide/': getGuideSidebar(),
+        '/component/': getComponentsSideBar(),
+    }
+}
+
+type Item = {
+    text: string
+    children?: Item[]
+    link?: string
+}
+
+function mapPrefix(item: Item, lang: string, prefix = '') {
+    if (item.children && item.children.length > 0) {
+        return {
+            ...item,
+            children: item.children.map(child => mapPrefix(child, lang, prefix)),
+        }
+    }
+    return {
+        ...item,
+        link: `${ensureLang(lang)}${prefix}${item.link}`,
+    }
+}
+
+export const sidebars = getSidebars()

+ 30 - 0
docs/.vitepress/vitepress/components/common/vp-link.vue

@@ -0,0 +1,30 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const props = defineProps<{
+    href?: string
+    noIcon?: boolean
+}>()
+
+const isExternal = computed(() => props.href && /^[a-z]+:/i.test(props.href))
+</script>
+
+<template>
+    <component :is="href ? 'a' : 'span'" class="link-item" :class="{ link: href }" :href="href" :target="isExternal ? '_blank' : undefined" :rel="isExternal ? 'noopener noreferrer' : undefined">
+        <slot></slot>
+        <ElIcon v-if="isExternal && !noIcon">
+            <i-ri-external-link-line class="link-icon" />
+        </ElIcon>
+    </component>
+</template>
+
+<style scoped>
+.link-item {
+    display: flex;
+    align-items: center;
+}
+
+.el-icon {
+    margin-left: 4px;
+}
+</style>

+ 30 - 0
docs/.vitepress/vitepress/components/common/vp-markdown.vue

@@ -0,0 +1,30 @@
+<script lang="ts" setup>
+import { computed } from 'vue'
+import MarkdownIt from 'markdown-it'
+
+const md = new MarkdownIt()
+
+const props = defineProps({
+    content: { type: String, required: true },
+})
+
+const attr = 'rel="noreferrer noopenner" target="_blank"'
+
+const parsed = computed(() => {
+    // Note this is relatively arbitrary so that this could be buggy.
+    return md
+        .render(props.content)
+        .replace(/#([0-9]+) by/g, `<a href="https://github.com/element-plus/element-plus/pull/$1" ${attr}>#$1</a> by`)
+        .replace(/@([A-Za-z0-9_-]+)/g, `<a href="https://github.com/$1" ${attr}>@$1</a>`)
+})
+</script>
+
+<template>
+    <div class="markdown-wrapper" v-html="parsed"></div>
+</template>
+
+<style>
+.markdown-wrapper h3 {
+    margin-top: 1rem;
+}
+</style>

+ 15 - 0
docs/.vitepress/vitepress/components/common/vp-switch.vue

@@ -0,0 +1,15 @@
+<script setup lang="ts">
+// for now el-switch does not support customized icon in the dot
+// we will implement a simple version of el-switch then update the switch
+// component for this feature
+</script>
+
+<template>
+    <div class="switch" role="switch">
+        <div class="switch__action">
+            <div class="switch__icon">
+                <slot></slot>
+            </div>
+        </div>
+    </div>
+</template>

+ 49 - 0
docs/.vitepress/vitepress/components/common/vp-theme-toggler.vue

@@ -0,0 +1,49 @@
+<script setup lang="ts">
+import DarkIcon from '../icons/dark.vue'
+import LightIcon from '../icons/light.vue'
+import VPSwitch from './vp-switch.vue'
+</script>
+
+<template>
+    <VPSwitch>
+        <ElIcon :size="13">
+            <DarkIcon class="dark-icon" />
+            <LightIcon class="light-icon" />
+        </ElIcon>
+    </VPSwitch>
+</template>
+
+<style lang="scss" scoped>
+.el-icon {
+    cursor: pointer;
+}
+
+.dark-icon,
+.light-icon {
+    transition: color var(--el-transition-duration), opacity var(--el-transition-duration);
+}
+
+.light-icon {
+    opacity: 1;
+    position: absolute;
+    top: 0;
+    left: 0;
+}
+
+.dark-icon {
+    opacity: 0;
+    position: absolute;
+    top: 0;
+    left: 0;
+}
+
+@at-root .dark {
+    .dark-icon {
+        opacity: 1;
+    }
+
+    .light-icon {
+        opacity: 0;
+    }
+}
+</style>

+ 28 - 0
docs/.vitepress/vitepress/components/demo/vp-example.vue

@@ -0,0 +1,28 @@
+<script setup lang="ts">
+defineProps({
+    file: {
+        type: String,
+        required: true,
+    },
+    demo: {
+        type: Object,
+        required: true,
+    },
+})
+</script>
+
+<template>
+    <div class="example-showcase">
+        <ClientOnly>
+            <component :is="demo" v-if="demo" v-bind="$attrs" />
+        </ClientOnly>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.example-showcase {
+    padding: 1.5rem;
+    margin: 0.5px;
+    background-color: var(--bg-color);
+}
+</style>

+ 27 - 0
docs/.vitepress/vitepress/components/demo/vp-source-code.vue

@@ -0,0 +1,27 @@
+<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>
+    </div>
+</template>
+
+<style scoped lang="scss">
+.language-vue {
+    margin: 0;
+    border-radius: 0;
+}
+</style>

+ 3 - 0
docs/.vitepress/vitepress/components/dev/DeprecatedTag.vue

@@ -0,0 +1,3 @@
+<template>
+    <el-tag size="small" type="warning" effect="plain" hit round>deprecated</el-tag>
+</template>

+ 11 - 0
docs/.vitepress/vitepress/components/dev/VersionTag.vue

@@ -0,0 +1,11 @@
+<script lang="ts" setup>
+defineProps<{
+    version: string
+}>()
+</script>
+
+<template>
+    <el-tag size="small" effect="plain" hit round>
+        {{ version }}
+    </el-tag>
+</template>

+ 28 - 0
docs/.vitepress/vitepress/components/doc-content/vp-edit-link.vue

@@ -0,0 +1,28 @@
+<script setup lang="ts">
+import { useEditLink } from '../../composables/edit-link'
+
+const { url, text } = useEditLink()
+</script>
+
+<template>
+    <div class="edit-link">
+        <a v-if="url" class="link text-sm" :href="url" target="_blank" rel="noopener noreferrer">
+            {{ text }}
+            <ElIcon :size="16" style="vertical-align: text-top; line-height: 24px">
+                <i-ri-external-link-line />
+            </ElIcon>
+        </a>
+    </div>
+</template>
+
+<style scoped>
+.link {
+    display: inline-block;
+    font-weight: 500;
+}
+
+.link:hover {
+    text-decoration: none;
+    color: var(--brand-color);
+}
+</style>

+ 47 - 0
docs/.vitepress/vitepress/components/doc-content/vp-last-updated-at.vue

@@ -0,0 +1,47 @@
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue'
+import { useData } from 'vitepress'
+import { useLang } from '../../composables/lang'
+import localeData from '../../../i18n/component/last-update-at.json'
+
+const { page } = useData()
+const lang = useLang()
+
+const prefix = computed(() => {
+    return localeData[lang.value].title
+})
+
+const datetime = ref('')
+onMounted(() => {
+    datetime.value = new Date(page.value.lastUpdated).toLocaleString(lang.value)
+})
+</script>
+
+<template>
+    <p class="last-updated text-sm">
+        <span class="prefix">{{ prefix }}:</span>
+        <span class="datetime">{{ datetime }}</span>
+    </p>
+</template>
+
+<style scoped lang="scss">
+@use '../../styles/mixins' as *;
+
+.last-updated {
+    display: inline-block;
+    margin: 0;
+    line-height: 1.4;
+    color: var(--text-color-light);
+
+    .prefix {
+        display: inline-block;
+        font-weight: 500;
+    }
+
+    .datetime {
+        display: inline-block;
+        margin-left: 6px;
+        font-weight: 400;
+    }
+}
+</style>

+ 35 - 0
docs/.vitepress/vitepress/components/doc-content/vp-page-footer.vue

@@ -0,0 +1,35 @@
+<script setup lang="ts">
+import VPEditLink from './vp-edit-link.vue'
+</script>
+
+<template>
+    <footer class="page-footer">
+        <div class="edit">
+            <VPEditLink />
+        </div>
+    </footer>
+</template>
+
+<style scoped lang="scss">
+@use '../../styles/mixins' as *;
+.page-footer {
+    padding-top: 1rem;
+    padding-bottom: 1rem;
+    overflow: auto;
+
+    .updated {
+        padding-top: 4px;
+    }
+}
+
+@include respond-to('lg') {
+    .page-footer {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+    }
+    .updated {
+        padding-top: 0;
+    }
+}
+</style>

+ 93 - 0
docs/.vitepress/vitepress/components/doc-content/vp-page-nav.vue

@@ -0,0 +1,93 @@
+<script setup lang="ts">
+import { withBase } from 'vitepress'
+import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
+import { usePageNav } from '../../composables/page-nav'
+
+const { hasLinks, prev, next } = usePageNav()
+</script>
+
+<template>
+    <div v-if="hasLinks" class="next-and-prev-link">
+        <div class="container">
+            <div class="prev">
+                <a v-if="prev" class="link" :href="withBase(prev.link)">
+                    <ElIcon class="mr-1">
+                        <ArrowLeft />
+                    </ElIcon>
+                    <span class="text">{{ prev.text }}</span>
+                </a>
+            </div>
+            <div class="next">
+                <a v-if="next" class="link" :href="withBase(next.link)">
+                    <span class="text">{{ next.text }}</span>
+                    <ElIcon class="ml-1">
+                        <ArrowRight />
+                    </ElIcon>
+                </a>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style scoped>
+.next-and-prev-link {
+    padding-top: 1rem;
+}
+
+.container {
+    display: flex;
+    justify-content: space-between;
+    border-top: 1px solid var(--border-color);
+    padding-top: 1rem;
+}
+
+.prev,
+.next {
+    display: flex;
+    flex-shrink: 0;
+    width: 50%;
+}
+
+.prev {
+    justify-content: flex-start;
+    padding-right: 12px;
+}
+
+.next {
+    justify-content: flex-end;
+    padding-left: 12px;
+}
+
+.link {
+    display: inline-flex;
+    justify-content: center;
+    align-items: center;
+
+    max-width: 100%;
+    height: 24px;
+    font-size: 14px;
+    font-weight: 500;
+}
+
+.text {
+    display: inline-flex;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.el-icon {
+    display: inline-flex;
+    flex-shrink: 0;
+    font-size: 12px;
+    color: var(--text-color);
+    transform: translateY(1px);
+}
+
+.icon-prev {
+    margin-right: 8px;
+}
+.icon-next {
+    margin-left: 8px;
+}
+</style>

+ 55 - 0
docs/.vitepress/vitepress/components/doc-content/vp-table-of-content.vue

@@ -0,0 +1,55 @@
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import { useToc } from '../../composables/use-toc'
+import { useActiveSidebarLinks } from '../../composables/active-bar'
+
+import sponsorLocale from '../../../i18n/component/sponsor.json'
+import { useLang } from '../../composables/lang'
+import SponsorsButton from '../sponsors/sponsors-button.vue'
+import SponsorRightTextList from '../sponsors/right-richtext-list.vue'
+import SponsorRightLogoSmallList from '../sponsors/right-logo-small-list.vue'
+// import SponsorLarge from '../vp-sponsor-large.vue'
+
+const headers = useToc()
+const marker = ref()
+const container = ref()
+useActiveSidebarLinks(container, marker)
+const lang = useLang()
+const sponsor = computed(() => sponsorLocale[lang.value])
+</script>
+
+<template>
+    <aside ref="container" class="toc-wrapper">
+        <nav class="toc-content">
+            <h3 class="toc-content__heading">Contents</h3>
+            <ul class="toc-items">
+                <li v-for="{ link, text, children } in headers" :key="link" class="toc-item">
+                    <a class="toc-link" :href="link" :title="text">{{ text }}</a>
+                    <ul v-if="children">
+                        <li v-for="{ link: childLink, text: childText } in children" :key="childLink" class="toc-item">
+                            <a class="toc-link subitem" :href="childLink" :title="text">{{ childText }}</a>
+                        </li>
+                    </ul>
+                </li>
+            </ul>
+            <div ref="marker" class="toc-marker"></div>
+            <!-- <SponsorLarge
+        class="mt-8 toc-ads flex flex-col"
+        item-style="width: 180px; height: 55px;"
+      /> -->
+            <p class="text-14px font-300 color-$text-color-secondary">
+                {{ sponsor.sponsoredBy }}
+            </p>
+            <sponsors-button class="sponsors-button mt-4 w-100%" />
+            <sponsor-right-logo-small-list />
+            <sponsor-right-text-list />
+        </nav>
+    </aside>
+</template>
+<style scoped lang="scss">
+.sponsors-button:deep {
+    button {
+        width: 100%;
+    }
+}
+</style>

+ 40 - 0
docs/.vitepress/vitepress/components/full-screen/vp-menu-link.vue

@@ -0,0 +1,40 @@
+<script lang="ts" setup>
+import VPLink from '../common/vp-link.vue'
+
+import type { Link } from '../../types'
+
+defineProps<{
+    item: Link
+}>()
+</script>
+
+<template>
+    <VPLink
+        :class="{
+            'is-menu-link': true,
+        }"
+        :href="item.link"
+        :no-icon="true"
+    >
+        {{ item.text }}
+    </VPLink>
+</template>
+
+<style scoped lang="scss">
+.is-menu-link {
+    display: block;
+    font-size: 13px;
+    font-weight: 500;
+    line-height: 24px;
+    color: var(--text-color);
+    transition: color var(--el-transition-duration);
+
+    &.active {
+        border-bottom: 2px solid var(--brand-color);
+    }
+
+    &:hover {
+        color: var(--brand-color);
+    }
+}
+</style>

+ 23 - 0
docs/.vitepress/vitepress/components/full-screen/vp-menu.vue

@@ -0,0 +1,23 @@
+<script setup lang="ts">
+import { useNav } from '../../composables/nav'
+import VPMenuLink from './vp-menu-link.vue'
+
+const navs = useNav()
+
+defineEmits(['close'])
+</script>
+
+<template>
+    <nav v-if="navs" class="full-screen-menu">
+        <div v-for="(item, key) in navs" :key="key" class="full-screen-menu__item">
+            <VPMenuLink :item="item" @click="$emit('close')" />
+        </div>
+    </nav>
+</template>
+
+<style lang="scss" scoped>
+.full-screen-menu__item {
+    padding: 12px 0;
+    border-bottom: 1px solid var(--border-color);
+}
+</style>

+ 24 - 0
docs/.vitepress/vitepress/components/full-screen/vp-theme-toggler.vue

@@ -0,0 +1,24 @@
+<script setup lang="ts">
+import CommonThemeToggler from '../common/vp-theme-toggler.vue'
+import { toggleDark } from '../../composables/dark'
+</script>
+
+<template>
+    <div class="full-screen-theme-toggler">
+        <span>Theme</span>
+        <CommonThemeToggler @click="toggleDark()" />
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.full-screen-theme-toggler {
+    display: flex;
+    padding: 12px 14px;
+    align-items: center;
+    justify-content: space-between;
+    margin-top: 16px;
+    font-size: 13px;
+    background-color: var(--bg-color-soft);
+    border-radius: 8px;
+}
+</style>

+ 80 - 0
docs/.vitepress/vitepress/components/full-screen/vp-translation.vue

@@ -0,0 +1,80 @@
+<script setup lang="ts">
+import { useToggle } from '@vueuse/core'
+import VPLink from '../common/vp-link.vue'
+import { useTranslation } from '../../composables/translation'
+import ExpandIcon from '../icons/expand.vue'
+
+const emit = defineEmits(['close'])
+
+const { languageMap, langs, lang, switchLang, helpTranslate } = useTranslation()
+
+const [show, toggle] = useToggle()
+
+const onSwitchLang = (lang: string) => {
+    switchLang(lang)
+    emit('close')
+}
+</script>
+
+<template>
+    <div class="full-screen-translation">
+        <ElButton style="width: 100%; color: var(--text-color)" text @click="toggle">
+            <div class="translation-toggler">
+                <span> Translations </span>
+                <ElIcon :size="14">
+                    <ExpandIcon class="toggle-icon" :class="{ expanded: show }" />
+                </ElIcon>
+            </div>
+        </ElButton>
+        <div v-show="show" class="translation-items">
+            <p v-for="l in langs" :key="l" :class="{ active: l === lang }" class="translation-item" @click="onSwitchLang(l)">
+                {{ languageMap[l] }}
+            </p>
+            <p class="translation-item">
+                <VPLink :href="`/${lang}/guide/translation`">
+                    {{ helpTranslate }}
+                </VPLink>
+            </p>
+        </div>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.full-screen-translation {
+    border-bottom: 1px solid var(--border-color);
+}
+.translation-toggler {
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    line-height: 24px;
+    .toggle-icon {
+        transition: transform var(--el-transition-duration);
+        transform: rotate(180deg);
+
+        &.expanded {
+            transform: rotate(0deg);
+        }
+    }
+}
+
+.translation-items {
+    padding-bottom: 12px;
+    .translation-item {
+        cursor: pointer;
+        margin: 0;
+        font-size: 14px;
+        line-height: 32px;
+
+        &.active {
+            font-weight: 500;
+            color: var(--brand-color);
+        }
+
+        .link-item {
+            font-weight: 500;
+        }
+    }
+}
+</style>

+ 32 - 0
docs/.vitepress/vitepress/components/globals/contributors.vue

@@ -0,0 +1,32 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+import _contributors from '@element-plus/metadata/dist/contributors.json'
+import VpLink from '../common/vp-link.vue'
+
+const props = defineProps<{ id: string }>()
+
+const contributors = computed(() => _contributors[props.id]?.filter(c => c.login !== 'renovate[bot]'))
+</script>
+
+<template>
+    <div class="mb-4">
+        <div class="flex flex-wrap gap-4 pt-2">
+            <div v-for="c of contributors" :key="c.hash">
+                <vp-link :href="`https://github.com/${c.login}`" class="flex gap-2 items-center link" no-icon>
+                    <img :src="c.avatar" class="w-8 h-8 rounded-full" />
+                    {{ c.name }}
+                </vp-link>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.link {
+    color: var(--text-color-light);
+
+    &:hover {
+        color: var(--brand-color);
+    }
+}
+</style>

+ 58 - 0
docs/.vitepress/vitepress/components/globals/design-guide.vue

@@ -0,0 +1,58 @@
+<template>
+    <div class="guide-design">
+        <div class="el-row cards" style="margin-left: -7px; margin-right: -7px">
+            <div class="el-col el-col-24 el-col-xs-12 el-col-sm-6 is-guttered">
+                <div class="card">
+                    <consistency-svg m="4" w="20" alt="Consistency" />
+                    <p>Consistency</p>
+                </div>
+            </div>
+            <div class="el-col el-col-24 el-col-xs-12 el-col-sm-6 is-guttered">
+                <div class="card">
+                    <feedback-svg m="4" w="20" alt="Feedback" />
+                    <p>Feedback</p>
+                </div>
+            </div>
+            <div class="el-col el-col-24 el-col-xs-12 el-col-sm-6 is-guttered">
+                <div class="card">
+                    <efficiency-svg m="4" w="20" alt="Efficiency" />
+                    <p>Efficiency</p>
+                </div>
+            </div>
+            <div class="el-col el-col-24 el-col-xs-12 el-col-sm-6 is-guttered">
+                <div class="card">
+                    <controllability-svg m="4" w="20" alt="Controllability" />
+                    <p>Controllability</p>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style scoped lang="scss">
+.el-col {
+    padding: 0 7px;
+}
+.card {
+    background: var(--el-fill-color-lighter);
+    height: 204px;
+    text-align: center;
+
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+
+    img {
+        margin: 1rem;
+        width: 5rem;
+        height: 5rem;
+    }
+}
+
+@media screen and (max-width: 767px) {
+    .el-col {
+        padding-bottom: 8px;
+    }
+}
+</style>

+ 46 - 0
docs/.vitepress/vitepress/components/globals/design/consistency-svg.vue

@@ -0,0 +1,46 @@
+<template>
+    <!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+    <svg
+        id="图层_1"
+        version="1.1"
+        xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        x="0px"
+        y="0px"
+        viewBox="0 0 79 79"
+        style="enable-background: new 0 0 79 79"
+        xml:space="preserve"
+    >
+        <circle class="st0" cx="39.5" cy="39.5" r="39.5" />
+        <path class="st1" d="M23.2,42c-0.5,0-1,0.4-1,1v17.8c0,0.5,0.4,1,1,1h3.5c0.5,0,1-0.4,1-1V43c0-0.5-0.4-1-1-1H23.2z" />
+        <path class="st1" d="M32.6,42c-0.5,0-1,0.4-1,1v17.8c0,0.5,0.4,1,1,1H36c0.5,0,1-0.4,1-1V43c0-0.5-0.4-1-1-1H32.6z" />
+        <path class="st1" d="M42,43c0-0.5,0.4-1,1-1h3.5c0.5,0,1,0.4,1,1v17.8c0,0.5-0.4,1-1,1H43c-0.5,0-1-0.4-1-1V43z" />
+        <path class="st1" d="M52.3,42c-0.5,0-1,0.4-1,1v17.8c0,0.5,0.4,1,1,1h3.5c0.5,0,1-0.4,1-1V43c0-0.5-0.4-1-1-1H52.3z" />
+        <path class="st2" d="M22.2,43c0-0.5,0.4-1,1-1h3.5c0.5,0,1,0.4,1,1v15.8c0,0.5-0.4,1-1,1h-3.5c-0.5,0-1-0.4-1-1V43z" />
+        <path class="st2" d="M31.6,43c0-0.5,0.4-1,1-1H36c0.5,0,1,0.4,1,1v15.8c0,0.5-0.4,1-1,1h-3.5c-0.5,0-1-0.4-1-1V43z" />
+        <path class="st2" d="M42,43c0-0.5,0.4-1,1-1h3.5c0.5,0,1,0.4,1,1v15.8c0,0.5-0.4,1-1,1H43c-0.5,0-1-0.4-1-1V43z" />
+        <path class="st2" d="M51.3,43c0-0.5,0.4-1,1-1h3.5c0.5,0,1,0.4,1,1v15.8c0,0.5-0.4,1-1,1h-3.5c-0.5,0-1-0.4-1-1V43z" />
+        <path class="st3" d="M57.3,28.1l-5.9-5.9v4.9H22.7c-0.5,0-1,0.4-1,1c0,0.5,0.4,1,1,1h28.6v4.9L57.3,28.1z" />
+    </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+    fill: #eff5fd;
+}
+.st1 {
+    fill: #0077ce;
+}
+.st2 {
+    fill: #20a0ff;
+}
+.st3 {
+    fill: #7383bf;
+}
+
+.dark {
+    .st0 {
+        fill: #36393d;
+    }
+}
+</style>

+ 76 - 0
docs/.vitepress/vitepress/components/globals/design/controllability-svg.vue

@@ -0,0 +1,76 @@
+<template>
+    <svg
+        id="图层_1"
+        version="1.1"
+        xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        x="0px"
+        y="0px"
+        viewBox="0 0 79 79"
+        style="enable-background: new 0 0 79 79"
+        xml:space="preserve"
+    >
+        <circle class="st0" cx="39.5" cy="39.5" r="39.5" />
+        <g>
+            <defs>
+                <rect id="SVGID_1_" x="15.8" y="17.3" width="47.4" height="43.9" />
+            </defs>
+            <clipPath id="SVGID_00000103965616648291865560000002216192073450902938_">
+                <use xlink:href="#SVGID_1_" style="overflow: visible" />
+            </clipPath>
+            <g style="clip-path: url(#SVGID_00000103965616648291865560000002216192073450902938_)">
+                <path
+                    class="st2"
+                    d="M57.3,23.2L57.3,23.2c0.5,0,1,0.4,1,1v36c0,0.5-0.4,1-1,1l0,0c-0.5,0-1-0.4-1-1v-36
+        C56.3,23.6,56.7,23.2,57.3,23.2z"
+                />
+                <ellipse class="st3" cx="57.3" cy="24.7" rx="5.9" ry="5.9" />
+                <ellipse class="st4" cx="57.3" cy="23.2" rx="5.9" ry="5.9" />
+                <path
+                    class="st2"
+                    d="M21.7,17.3L21.7,17.3c0.5,0,1,0.4,1,1v36c0,0.5-0.4,1-1,1h0c-0.5,0-1-0.4-1-1v-36
+        C20.7,17.7,21.2,17.3,21.7,17.3z"
+                />
+                <ellipse class="st3" cx="21.7" cy="55.3" rx="5.9" ry="5.9" />
+                <ellipse class="st4" cx="21.7" cy="53.8" rx="5.9" ry="5.9" />
+                <path
+                    class="st2"
+                    d="M39.5,17.3L39.5,17.3c0.5,0,1,0.4,1,1v42c0,0.5-0.4,1-1,1l0,0c-0.5,0-1-0.4-1-1v-42
+        C38.5,17.7,39,17.3,39.5,17.3z"
+                />
+                <ellipse class="st5" cx="39.5" cy="39" rx="5.9" ry="5.9" />
+                <ellipse class="st6" cx="39.5" cy="37.5" rx="5.9" ry="5.9" />
+            </g>
+        </g>
+    </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+    fill: #eff5fd;
+}
+.st1 {
+    clip-path: url(#SVGID_00000172405038201111576250000009038874290854515645_);
+}
+.st2 {
+    fill: #afcaf1;
+}
+.st3 {
+    fill: #afb6bb;
+}
+.st4 {
+    fill: #e7eced;
+}
+.st5 {
+    fill: #0077ce;
+}
+.st6 {
+    fill: #20a0ff;
+}
+
+.dark {
+    .st0 {
+        fill: #36393d;
+    }
+}
+</style>

+ 61 - 0
docs/.vitepress/vitepress/components/globals/design/efficiency-svg.vue

@@ -0,0 +1,61 @@
+<template>
+    <svg
+        id="图层_1"
+        version="1.1"
+        xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        x="0px"
+        y="0px"
+        viewBox="0 0 79 79"
+        style="enable-background: new 0 0 79 79"
+        xml:space="preserve"
+    >
+        <circle class="st0" cx="39.5" cy="39.5" r="39.5" />
+        <path
+            class="st1"
+            d="M37,16.8c-1.6,0-3,1.3-3,3V22c-3,0.9-5.7,2.5-7.9,4.5l-2-1.1c-1.4-0.8-3.2-0.3-4,1.1l-2.5,4.3
+      c-0.8,1.4-0.3,3.2,1.1,4l2,1.1c-0.4,1.5-0.5,3-0.5,4.5c0,1.6,0.2,3.1,0.5,4.5l-2,1.1c-1.4,0.8-1.9,2.6-1.1,4l2.5,4.3
+      c0.8,1.4,2.6,1.9,4,1.1l2-1.1c2.2,2.1,4.9,3.7,7.9,4.5v2.3c0,1.6,1.3,3,3,3h4.9c1.6,0,3-1.3,3-3V59c3-0.9,5.7-2.5,7.9-4.5l2,1.1
+      c1.4,0.8,3.2,0.3,4-1.1l2.5-4.3c0.8-1.4,0.3-3.2-1.1-4l-2-1.1c0.4-1.5,0.5-3,0.5-4.5c0-1.6-0.2-3.1-0.5-4.5l2-1.1
+      c1.4-0.8,1.9-2.6,1.1-4l-2.5-4.3c-0.8-1.4-2.6-1.9-4-1.1l-2,1.1c-2.2-2.1-4.9-3.7-7.9-4.5v-2.3c0-1.6-1.3-3-3-3H37z"
+        />
+        <path
+            class="st2"
+            d="M37,14.8c-1.6,0-3,1.3-3,3V20c-3,0.9-5.7,2.5-7.9,4.5l-2-1.1c-1.4-0.8-3.2-0.3-4,1.1l-2.5,4.3
+      c-0.8,1.4-0.3,3.2,1.1,4l2,1.1c-0.4,1.5-0.5,3-0.5,4.5s0.2,3.1,0.5,4.5l-2,1.1c-1.4,0.8-1.9,2.6-1.1,4l2.5,4.3
+      c0.8,1.4,2.6,1.9,4,1.1l2-1.1c2.2,2.1,4.9,3.7,7.9,4.5v2.3c0,1.6,1.3,3,3,3h4.9c1.6,0,3-1.3,3-3V57c3-0.9,5.7-2.5,7.9-4.5l2,1.1
+      c1.4,0.8,3.2,0.3,4-1.1l2.5-4.3c0.8-1.4,0.3-3.2-1.1-4l-2-1.1c0.4-1.5,0.5-3,0.5-4.5s-0.2-3.1-0.5-4.5l2-1.1c1.4-0.8,1.9-2.6,1.1-4
+      l-2.5-4.3c-0.8-1.4-2.6-1.9-4-1.1l-2,1.1c-2.2-2.1-4.9-3.7-7.9-4.5v-2.3c0-1.6-1.3-3-3-3H37z"
+        />
+        <ellipse transform="matrix(1 -2.392332e-03 2.392332e-03 1 -9.202202e-02 9.445851e-02)" class="st3" cx="39.4" cy="38.5" rx="15.3" ry="15.3" />
+        <path class="st4" d="M38.5,30.6c0-0.5,0.4-1,1-1c0.5,0,1,0.4,1,1v10.9h-2V30.6z" />
+        <path class="st5" d="M47.3,39.5c0.5,0,1,0.4,1,1c0,0.5-0.4,1-1,1h-8.9v-2H47.3z" />
+    </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+    fill: #eff5fd;
+}
+.st1 {
+    fill: #afb6bb;
+}
+.st2 {
+    fill: #e7eced;
+}
+.st3 {
+    fill: #ffffff;
+}
+.st4 {
+    fill: #0077ce;
+}
+.st5 {
+    fill: #20a0ff;
+}
+
+.dark {
+    .st0 {
+        fill: #36393d;
+    }
+}
+</style>

+ 40 - 0
docs/.vitepress/vitepress/components/globals/design/feedback-svg.vue

@@ -0,0 +1,40 @@
+<template>
+    <svg
+        id="图层_1"
+        version="1.1"
+        xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        x="0px"
+        y="0px"
+        viewBox="0 0 79 79"
+        style="enable-background: new 0 0 79 79"
+        xml:space="preserve"
+    >
+        <circle class="st0" cx="39.5" cy="39.5" r="39.5" />
+        <path class="st1" d="M19.8,26.2c0-3.3,2.7-5.9,5.9-5.9h27.7c3.3,0,5.9,2.7,5.9,5.9v23.7c0,3.3-2.7,5.9-5.9,5.9H25.9l-6.2,6.4V26.2z" />
+        <path class="st2" d="M19.8,24.2c0-3.3,2.7-5.9,5.9-5.9h27.7c3.3,0,5.9,2.7,5.9,5.9v23.7c0,3.3-2.7,5.9-5.9,5.9H25.9l-6.2,6.4V24.2z" />
+        <path class="st3" d="M37.5,35.5c0-1.1,0.9-2,2-2s2,0.9,2,2v7.9c0,1.1-0.9,2-2,2s-2-0.9-2-2V35.5z" />
+        <path class="st3" d="M37.5,29.1c0-1.1,0.9-2,2-2s2,0.9,2,2c0,1.1-0.9,2-2,2S37.5,30.2,37.5,29.1z" />
+    </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+    fill: #eff5fd;
+}
+.st1 {
+    fill: #0077ce;
+}
+.st2 {
+    fill: #20a0ff;
+}
+.st3 {
+    fill: #ffffff;
+}
+
+.dark {
+    .st0 {
+        fill: #36393d;
+    }
+}
+</style>

+ 304 - 0
docs/.vitepress/vitepress/components/globals/icons-categories.json

@@ -0,0 +1,304 @@
+{
+    "categories": [
+        {
+            "name": "System",
+            "items": [
+                "Plus",
+                "Minus",
+                "CirclePlus",
+                "Search",
+                "Female",
+                "Male",
+                "Aim",
+                "House",
+                "FullScreen",
+                "Loading",
+                "Link",
+                "Service",
+                "Pointer",
+                "Star",
+                "Notification",
+                "Connection",
+                "ChatDotRound",
+                "Setting",
+                "Clock",
+                "Position",
+                "Discount",
+                "Odometer",
+                "ChatSquare",
+                "ChatRound",
+                "ChatLineRound",
+                "ChatLineSquare",
+                "ChatDotSquare",
+                "View",
+                "Hide",
+                "Unlock",
+                "Lock",
+                "RefreshRight",
+                "RefreshLeft",
+                "Refresh",
+                "Bell",
+                "MuteNotification",
+                "User",
+                "Check",
+                "CircleCheck",
+                "Warning",
+                "CircleClose",
+                "Close",
+                "PieChart",
+                "More",
+                "Compass",
+                "Filter",
+                "Switch",
+                "Select",
+                "SemiSelect",
+                "CloseBold",
+                "EditPen",
+                "Edit",
+                "Message",
+                "MessageBox",
+                "TurnOff",
+                "Finished",
+                "Delete",
+                "Crop",
+                "SwitchButton",
+                "Operation",
+                "Open",
+                "Remove",
+                "ZoomOut",
+                "ZoomIn",
+                "InfoFilled",
+                "CircleCheckFilled",
+                "SuccessFilled",
+                "WarningFilled",
+                "CircleCloseFilled",
+                "QuestionFilled",
+                "WarnTriangleFilled",
+                "UserFilled",
+                "MoreFilled",
+                "Tools",
+                "HomeFilled",
+                "Menu",
+                "UploadFilled",
+                "Avatar",
+                "HelpFilled",
+                "Share",
+                "StarFilled",
+                "Comment",
+                "Histogram",
+                "Grid",
+                "Promotion",
+                "DeleteFilled",
+                "RemoveFilled",
+                "CirclePlusFilled"
+            ]
+        },
+        {
+            "name": "Arrow",
+            "items": [
+                "ArrowLeft",
+                "ArrowUp",
+                "ArrowRight",
+                "ArrowDown",
+                "ArrowLeftBold",
+                "ArrowUpBold",
+                "ArrowRightBold",
+                "ArrowDownBold",
+                "DArrowRight",
+                "DArrowLeft",
+                "Download",
+                "Upload",
+                "Top",
+                "Bottom",
+                "Back",
+                "Right",
+                "TopRight",
+                "TopLeft",
+                "BottomRight",
+                "BottomLeft",
+                "Sort",
+                "SortUp",
+                "SortDown",
+                "Rank",
+                "CaretLeft",
+                "CaretTop",
+                "CaretRight",
+                "CaretBottom",
+                "DCaret",
+                "Expand",
+                "Fold"
+            ]
+        },
+        {
+            "name": "Document",
+            "items": [
+                "DocumentAdd",
+                "Document",
+                "Notebook",
+                "Tickets",
+                "Memo",
+                "Collection",
+                "Postcard",
+                "ScaleToOriginal",
+                "SetUp",
+                "DocumentDelete",
+                "DocumentChecked",
+                "DataBoard",
+                "DataAnalysis",
+                "CopyDocument",
+                "FolderChecked",
+                "Files",
+                "Folder",
+                "FolderDelete",
+                "FolderRemove",
+                "FolderOpened",
+                "DocumentCopy",
+                "DocumentRemove",
+                "FolderAdd",
+                "FirstAidKit",
+                "Reading",
+                "DataLine",
+                "Management",
+                "Checked",
+                "Ticket",
+                "Failed",
+                "TrendCharts",
+                "List"
+            ]
+        },
+        {
+            "name": "Media",
+            "items": [
+                "Microphone",
+                "Mute",
+                "Mic",
+                "VideoPause",
+                "VideoCamera",
+                "VideoPlay",
+                "Headset",
+                "Monitor",
+                "Film",
+                "Camera",
+                "Picture",
+                "PictureRounded",
+                "Iphone",
+                "Cellphone",
+                "VideoCameraFilled",
+                "PictureFilled",
+                "Platform",
+                "CameraFilled",
+                "BellFilled"
+            ]
+        },
+        {
+            "name": "Traffic",
+            "items": ["Location", "LocationInformation", "DeleteLocation", "Coordinate", "Bicycle", "OfficeBuilding", "School", "Guide", "AddLocation", "MapLocation", "Place", "LocationFilled", "Van"]
+        },
+        {
+            "name": "Food",
+            "items": [
+                "Watermelon",
+                "Pear",
+                "NoSmoking",
+                "Smoking",
+                "Mug",
+                "GobletSquareFull",
+                "GobletFull",
+                "KnifeFork",
+                "Sugar",
+                "Bowl",
+                "MilkTea",
+                "Lollipop",
+                "Coffee",
+                "Chicken",
+                "Dish",
+                "IceTea",
+                "ColdDrink",
+                "CoffeeCup",
+                "DishDot",
+                "IceDrink",
+                "IceCream",
+                "Dessert",
+                "IceCreamSquare",
+                "ForkSpoon",
+                "IceCreamRound",
+                "Food",
+                "HotWater",
+                "Grape",
+                "Fries",
+                "Apple",
+                "Burger",
+                "Goblet",
+                "GobletSquare",
+                "Orange",
+                "Cherry"
+            ]
+        },
+        {
+            "name": "Items",
+            "items": [
+                "Printer",
+                "Calendar",
+                "CreditCard",
+                "Box",
+                "Money",
+                "Refrigerator",
+                "Cpu",
+                "Football",
+                "Brush",
+                "Suitcase",
+                "SuitcaseLine",
+                "Umbrella",
+                "AlarmClock",
+                "Medal",
+                "GoldMedal",
+                "Present",
+                "Mouse",
+                "Watch",
+                "QuartzWatch",
+                "Magnet",
+                "Help",
+                "Soccer",
+                "ToiletPaper",
+                "ReadingLamp",
+                "Paperclip",
+                "MagicStick",
+                "Basketball",
+                "Baseball",
+                "Coin",
+                "Goods",
+                "Sell",
+                "SoldOut",
+                "Key",
+                "ShoppingCart",
+                "ShoppingCartFull",
+                "ShoppingTrolley",
+                "Phone",
+                "Scissor",
+                "Handbag",
+                "ShoppingBag",
+                "Trophy",
+                "TrophyBase",
+                "Stopwatch",
+                "Timer",
+                "CollectionTag",
+                "Discount",
+                "TakeawayBox",
+                "PriceTag",
+                "Wallet",
+                "Opportunity",
+                "PhoneFilled",
+                "WalletFilled",
+                "GoodsFilled",
+                "Flag",
+                "BrushFilled",
+                "Briefcase",
+                "Stamp"
+            ]
+        },
+        {
+            "name": "Weather",
+            "items": ["Sunrise", "Sunny", "Ship", "MostlyCloudy", "PartlyCloudy", "Sunset", "Sunrise", "Drizzling", "Pouring", "Cloudy", "Moon", "MoonNight", "Lightning"]
+        }
+    ]
+}

+ 141 - 0
docs/.vitepress/vitepress/components/globals/icons.vue

@@ -0,0 +1,141 @@
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import clipboardCopy from 'clipboard-copy'
+import { ElMessage } from 'element-plus'
+import * as Icons from '@element-plus/icons-vue'
+import { useLang } from '../../composables/lang'
+import localeData from '../../../i18n/component/icons.json'
+import IconCategories from './icons-categories.json'
+import type { DefineComponent } from 'vue'
+
+type CategoriesItem = {
+    name: string
+    icons: DefineComponent[]
+}
+
+const lang = useLang()
+const locale = computed(() => localeData[lang.value])
+const copyIcon = ref(true)
+
+const copyContent = async content => {
+    try {
+        await clipboardCopy(content)
+
+        ElMessage({
+            showClose: true,
+            message: locale.value['copy-success'],
+            type: 'success',
+        })
+    } catch {
+        ElMessage({
+            showClose: true,
+            message: locale.value['copy-error'],
+            type: 'error',
+        })
+    }
+}
+
+const copySvgIcon = async (name, refs) => {
+    if (copyIcon.value) {
+        await copyContent(`<el-icon><${name} /></el-icon>`)
+    } else {
+        const content = refs[name]?.[0].querySelector('svg')?.outerHTML ?? ''
+        await copyContent(content)
+    }
+}
+
+const categories = ref<CategoriesItem[]>([])
+const iconMap = new Map(Object.entries(Icons))
+
+IconCategories.categories.forEach(o => {
+    const result: CategoriesItem = {
+        name: o.name,
+        icons: [],
+    }
+    o.items.forEach(i => {
+        const icon = iconMap.get(i)
+        if (icon) {
+            result.icons.push(icon)
+            iconMap.delete(i)
+        }
+    })
+    categories.value.push(result)
+})
+
+categories.value.push({ name: 'Other', icons: Array.from(iconMap.values()) })
+</script>
+
+<template>
+    <div style="text-align: right">
+        <el-switch v-model="copyIcon" active-text="Copy icon code" inactive-text="Copy SVG content" />
+    </div>
+    <div v-for="item in categories" :key="item.name" class="demo-icon-item">
+        <div class="demo-icon-title">{{ item.name }}</div>
+        <ul class="demo-icon-list">
+            <li v-for="component in item.icons" :key="component.name" :ref="component.name" class="icon-item" @click="copySvgIcon(component.name, $refs)">
+                <span class="demo-svg-icon">
+                    <ElIcon :size="20">
+                        <component :is="component" />
+                    </ElIcon>
+                    <span class="icon-name">{{ component.name }}</span>
+                </span>
+            </li>
+        </ul>
+    </div>
+</template>
+
+<style scoped lang="scss">
+.demo-icon {
+    &-item {
+        margin-top: 24px;
+        &:first-child {
+            margin-top: 0;
+        }
+    }
+    &-title {
+        font-weight: 400;
+        font-size: 18px;
+        line-height: 26px;
+    }
+    &-list {
+        overflow: hidden;
+        list-style: none;
+        padding: 0 !important;
+        border-top: 1px solid var(--el-border-color);
+        border-left: 1px solid var(--el-border-color);
+        border-radius: 4px;
+        display: grid;
+        grid-template-columns: repeat(7, 1fr);
+
+        .icon-item {
+            text-align: center;
+            color: var(--el-text-color-regular);
+            height: 90px;
+            font-size: 13px;
+            border-right: 1px solid var(--el-border-color);
+            border-bottom: 1px solid var(--el-border-color);
+            transition: background-color var(--el-transition-duration);
+            &:hover {
+                background-color: var(--el-border-color-extra-light);
+                .el-icon {
+                    color: var(--brand-color-light);
+                }
+                color: var(--brand-color-light);
+            }
+
+            .demo-svg-icon {
+                display: flex;
+                flex-direction: column;
+                align-items: center;
+                justify-content: center;
+                height: 100%;
+                cursor: pointer;
+
+                .icon-name {
+                    margin-top: 8px;
+                }
+            }
+        }
+    }
+}
+</style>

+ 33 - 0
docs/.vitepress/vitepress/components/globals/main-color.vue

@@ -0,0 +1,33 @@
+<script lang="ts" setup>
+import { useCssVar } from '@vueuse/core'
+import { useCopyColor } from '../../utils'
+
+const primary = useCssVar('--el-color-primary')
+const colorLevel = [3, 5, 7, 8, 9].map(i => `light-${i}`)
+colorLevel.unshift('dark-2')
+
+const { copyColor } = useCopyColor()
+</script>
+
+<template>
+    <el-row :gutter="12">
+        <el-col :span="10" :xs="{ span: 12 }">
+            <div class="demo-color-box" :style="{ background: primary }">
+                Brand Color
+                <div class="value" text="xs">{{ primary.toUpperCase() }}</div>
+                <div class="bg-color-sub" :style="{ background: primary }">
+                    <div
+                        v-for="level in colorLevel"
+                        :key="level"
+                        class="bg-blue-sub-item cursor-pointer hover:shadow"
+                        :style="{
+                            width: `${100 / 6}%`,
+                            background: 'var(--el-color-primary-' + level + ')',
+                        }"
+                        @click="copyColor('primary-' + level)"
+                    ></div>
+                </div>
+            </div>
+        </el-col>
+    </el-row>
+</template>

+ 131 - 0
docs/.vitepress/vitepress/components/globals/neutral-color.vue

@@ -0,0 +1,131 @@
+<template>
+    <el-row :gutter="12">
+        <el-col :span="6" :xs="{ span: 12 }">
+            <div class="demo-color-box-group">
+                <div
+                    v-for="(text, i) in textColors"
+                    :key="i"
+                    class="demo-color-box demo-color-box-other"
+                    :style="{
+                        color: 'var(--el-bg-color)',
+                        background: text.var.value,
+                    }"
+                >
+                    {{ text.name }}
+                    <div class="value" text="xs">
+                        {{ text.var.value.toUpperCase() }}
+                    </div>
+                </div>
+            </div>
+        </el-col>
+
+        <el-col :span="6" :xs="{ span: 12 }">
+            <div class="demo-color-box-group">
+                <div v-for="(border, i) in borderColors" :key="i" class="demo-color-box demo-color-box-other demo-color-box-lite" :style="{ background: border.var.value }">
+                    {{ border.name }}
+                    <div class="value" text="xs">
+                        {{ border.var.value.toUpperCase() }}
+                    </div>
+                </div>
+            </div>
+        </el-col>
+
+        <el-col :span="6" :xs="{ span: 12 }">
+            <div class="demo-color-box-group">
+                <div
+                    v-for="(fill, i) in fillColors"
+                    :key="i"
+                    class="demo-color-box demo-color-box-other demo-color-box-lite"
+                    :style="{
+                        background: fill.var.value,
+                        border: `1px solid ${fill.name === 'Blank Fill' ? 'var(--el-border-color-light)' : 'transparent'}`,
+                    }"
+                >
+                    {{ fill.name }}
+                    <div class="value" text="xs">
+                        {{ fill.var.value.toUpperCase() }}
+                    </div>
+                </div>
+            </div>
+        </el-col>
+
+        <el-col :span="6" :xs="{ span: 12 }">
+            <div class="demo-color-box-group">
+                <div class="demo-color-box demo-color-box-other" :style="{ background: black }">
+                    Basic Black
+                    <div class="value" text="xs">{{ black }}</div>
+                </div>
+                <div
+                    class="demo-color-box demo-color-box-other"
+                    :style="{
+                        background: white,
+                        color: '#303133',
+                        border: '1px solid #eee',
+                    }"
+                >
+                    Basic White
+                    <div class="value" text="xs">{{ white }}</div>
+                </div>
+                <div class="demo-color-box demo-color-box-other demo-color-box-lite bg-transparent">
+                    Transparent
+                    <div class="value" text="xs">Transparent</div>
+                </div>
+
+                <div
+                    v-for="(bg, i) in backgroundColors"
+                    :key="i"
+                    class="demo-color-box demo-color-box-other demo-color-box-lite"
+                    :style="{
+                        background: bg.var.value,
+                        border: '1px solid ' + (!isDark || bg.name === 'Base Background' ? 'var(--el-border-color-light)' : 'transparent'),
+                    }"
+                >
+                    {{ bg.name }}
+                    <div class="value" text="xs">
+                        {{ bg.var.value.toUpperCase() }}
+                    </div>
+                </div>
+            </div>
+        </el-col>
+    </el-row>
+</template>
+
+<script lang="ts" setup>
+import { isDark } from '~/composables/dark'
+import { getCssVarName, getCssVarValue } from '~/utils/colors'
+
+const backgroundTypes = ['page', '', 'overlay']
+const backgroundColors = backgroundTypes.map(type => {
+    return {
+        name: type ? `${type[0].toUpperCase() + type.slice(1)} Background` : 'Base Background',
+        var: getCssVarValue(getCssVarName('bg-color', type)),
+    }
+})
+
+const borderTypes = ['darker', 'dark', '', 'light', 'lighter', 'extra-light']
+const borderColors = borderTypes.map(type => {
+    return {
+        name: type ? `${type[0].toUpperCase() + type.slice(1)} Border` : 'Base Border',
+        var: getCssVarValue(getCssVarName('border-color', type)),
+    }
+})
+
+const fillTypes = ['darker', 'dark', '', 'light', 'lighter', 'extra-light', 'blank']
+const fillColors = fillTypes.map(type => {
+    return {
+        name: type ? `${type[0].toUpperCase() + type.slice(1)} Fill` : 'Base Fill',
+        var: getCssVarValue(getCssVarName('fill-color', type)),
+    }
+})
+
+const textTypes = ['primary', 'regular', 'secondary', 'placeholder', 'disabled']
+const textColors = textTypes.map(type => {
+    return {
+        name: `${type[0].toUpperCase() + type.slice(1)} Text`,
+        var: getCssVarValue(getCssVarName('text-color', type)),
+    }
+})
+
+const black = '#000000'
+const white = '#FFFFFF'
+</script>

+ 381 - 0
docs/.vitepress/vitepress/components/globals/parallax-home.vue

@@ -0,0 +1,381 @@
+<script setup lang="ts">
+import { computed, reactive, ref } from 'vue'
+import { useEventListener, useParallax, useThrottleFn } from '@vueuse/core'
+import { useLang } from '../../composables/lang'
+import homeLocale from '../../../i18n/pages/home.json'
+import HomeSponsors from '../home/home-sponsors.vue'
+import HomeCards from '../home/home-cards.vue'
+import HomeFooter from './vp-footer.vue'
+import type { CSSProperties } from 'vue'
+const target = ref<HTMLElement | null>(null)
+const parallax = reactive(useParallax(target))
+const jumbotronRedOffset = ref(0)
+const jumbotronRef = ref<HTMLElement | null>(null)
+const lang = useLang()
+const homeLang = computed(() => homeLocale[lang.value])
+
+function jumpTo(path: string) {
+    // vitepress has not router
+    location.href = `/${lang.value}/${path}`
+}
+
+const containerStyle: CSSProperties = {
+    display: 'flex',
+    flexDirection: 'column',
+    justifyContent: 'center',
+    alignItems: 'center',
+    transition: '.3s ease-out all',
+
+    position: 'relative',
+
+    perspective: '300px',
+}
+
+const cardStyle = computed(() => ({
+    height: '30rem',
+    width: '100%',
+    transition: '.3s ease-out all',
+    transform: `rotateX(${parallax.roll}deg) rotateY(${parallax.tilt}deg)`,
+}))
+
+const layerBase: CSSProperties = {
+    position: 'absolute',
+    width: '100%',
+    height: '100%',
+    transition: '.3s ease-out all',
+}
+
+const screenLayer = computed(() => ({
+    ...layerBase,
+    width: '80%',
+    height: '80%',
+    transform: `translateX(${parallax.tilt * 10 + 80}px) translateY(${parallax.roll * 10 + 50}px)`,
+}))
+
+const peopleLayer = computed(() => ({
+    ...layerBase,
+    width: '30%',
+    height: '30%',
+    right: 0,
+    bottom: 0,
+    transform: `translateX(${parallax.tilt * 25 + 25}px) translateY(${parallax.roll * 25}px) scale(1)`,
+}))
+
+// center layer
+const leftLayer = computed(() => ({
+    ...layerBase,
+    width: '20%',
+    height: '20%',
+    transform: `translateX(${parallax.tilt * 12 + 205}px) translateY(${parallax.roll * 12 + 210}px)`,
+}))
+
+const leftBottomLayer = computed(() => ({
+    ...layerBase,
+    width: '30%',
+    height: '30%',
+    left: 0,
+    bottom: 0,
+    transform: `translateX(${parallax.tilt * 30 - 10}px) translateY(${parallax.roll * 30}px)`,
+}))
+
+const rightLayer = computed(() => ({
+    ...layerBase,
+    width: '33%',
+    height: '33%',
+    top: 0,
+    right: 0,
+    transform: `translateX(${parallax.tilt * 25 + 5}px) translateY(${parallax.roll * 25}px)`,
+}))
+
+const handleScroll = useThrottleFn(() => {
+    const ele = jumbotronRef.value
+    if (ele) {
+        const rect = ele.getBoundingClientRect()
+        const eleHeight = ele.clientHeight
+        let calHeight = (180 - rect.top) * 2
+        if (calHeight < 0) calHeight = 0
+        if (calHeight > eleHeight) calHeight = eleHeight
+        jumbotronRedOffset.value = calHeight
+    }
+}, 10)
+
+useEventListener(window, 'scroll', handleScroll)
+</script>
+
+<template>
+    <div ref="target" class="home-page">
+        <div class="banner" text="center">
+            <div class="banner-desc" m="t-4">
+                <h1>{{ homeLang['title'] }}</h1>
+                <p m="t-2">{{ homeLang['title_sub'] }}</p>
+            </div>
+        </div>
+        <div ref="jumbotronRef" class="jumbotron">
+            <div class="parallax-container" :style="containerStyle">
+                <div :style="cardStyle">
+                    <screen-svg :style="screenLayer" alt="banner" />
+                    <people-svg :style="peopleLayer" alt="banner" class="cursor-pointer" @click="jumpTo('guide/quickstart.html')" />
+                    <left-layer-svg :style="leftLayer" alt="banner" />
+                    <left-bottom-layer-svg :style="leftBottomLayer" alt="banner" />
+                    <right-layer-svg :style="rightLayer" alt="banner" />
+                </div>
+            </div>
+        </div>
+        <img src="/images/theme-index-blue.png" alt="banner" class="mobile-banner" />
+        <HomeSponsors />
+        <HomeCards />
+    </div>
+    <HomeFooter :is-home="true" />
+</template>
+
+<style lang="scss">
+@use '../../styles/mixins' as *;
+
+.home-page {
+    .mobile-banner {
+        display: none;
+    }
+
+    .banner-dot h1 span {
+        position: relative;
+        &::after {
+            content: '';
+            position: absolute;
+            right: -12px;
+            bottom: 8px;
+            background: var(--el-color-primary);
+            height: 8px;
+            width: 8px;
+            border-radius: 100%;
+        }
+    }
+    .banner-desc {
+        h1 {
+            font-size: 34px;
+            margin: 0;
+            line-height: 48px;
+            color: var(--text-color);
+        }
+
+        p {
+            font-size: 18px;
+            color: var(--text-color-light);
+        }
+    }
+
+    .count-down {
+        .cd-main {
+            background: #f1f6fe;
+            border-radius: 10px;
+            width: 50%;
+            margin: 60px auto 120px;
+            padding: 30px 0;
+            font-size: 24px;
+            color: #666;
+            text-align: center;
+            font-weight: 600;
+        }
+        .cd-date {
+            font-size: 28px;
+        }
+        .cd-time {
+            display: flex;
+            justify-content: space-between;
+            width: 80%;
+            margin: 10px auto 0;
+        }
+        .cd-num {
+            color: var(--el-color-primary);
+            font-size: 78px;
+            font-weight: bold;
+        }
+        .cd-num span {
+            width: 50%;
+            display: inline-block;
+        }
+        .cd-str {
+            font-size: 22px;
+            margin-top: -5px;
+        }
+    }
+
+    .jumbotron {
+        width: 800px;
+        margin: 20px auto;
+        position: relative;
+
+        img {
+            width: 100%;
+        }
+
+        .parallax-container {
+            width: 800px;
+        }
+    }
+
+    @media screen and (max-width: 959px) {
+        .jumbotron {
+            display: none !important;
+        }
+
+        .mobile-banner {
+            display: inline-block;
+        }
+    }
+
+    @media (max-width: 768px) {
+        .jumbotron {
+            width: 50%;
+            display: flex;
+            margin: auto;
+            justify-content: center;
+            align-items: center;
+
+            .parallax-container {
+                width: 100%;
+            }
+        }
+    }
+
+    @media (max-width: 768px) {
+        .banner-desc {
+            padding-top: 0px;
+        }
+        .cards {
+            li {
+                width: 80%;
+                margin: 0 auto 20px;
+                float: none;
+            }
+            .card {
+                height: auto;
+                padding-bottom: 54px;
+            }
+        }
+        .banner-stars {
+            display: none;
+        }
+        .banner-desc {
+            h1 {
+                font-size: 22px;
+            }
+            #line2 {
+                display: none;
+            }
+            h2 {
+                font-size: 32px;
+            }
+            p {
+                width: auto;
+            }
+        }
+        .banner-dot h1 span {
+            &::after {
+                right: -8px;
+                bottom: 2px;
+                height: 6px;
+                width: 6px;
+            }
+        }
+        .count-down {
+            .cd-main {
+                width: 90%;
+                margin: 40px auto 40px;
+                padding: 20px 0;
+            }
+            .cd-date {
+                font-size: 22px;
+            }
+            .cd-num {
+                font-size: 38px;
+            }
+            .cd-str {
+                font-size: 12px;
+                margin-top: 0px;
+            }
+        }
+        .sponsors-list {
+            display: flex;
+            flex-direction: column;
+            align-content: center;
+            .sponsor {
+                justify-content: left;
+            }
+        }
+    }
+    .theme-intro-b {
+        position: fixed;
+        left: 0;
+        right: 0;
+        top: 0;
+        bottom: 0;
+        z-index: 200;
+        .intro-banner {
+            position: absolute;
+        }
+        img {
+            width: 300px;
+        }
+        .title {
+            position: absolute;
+            top: 0;
+            bottom: 0;
+            left: 0;
+            right: 0;
+            color: #fff;
+            text-align: center;
+            font-weight: bold;
+            font-size: 20px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            p {
+                padding: 0;
+                margin: 10px 0;
+            }
+        }
+    }
+    .theme-intro-a {
+        position: fixed;
+        left: 0;
+        right: 0;
+        top: 0;
+        bottom: 0;
+        z-index: 200;
+        .mask {
+            position: fixed;
+            left: 0;
+            right: 0;
+            top: 0;
+            bottom: 0;
+            background: #000;
+            opacity: 0.5;
+        }
+        .intro-banner {
+            top: 50%;
+            left: 50%;
+            position: fixed;
+            transform: translate(-50%, -50%);
+            box-sizing: border-box;
+            text-align: center;
+            z-index: 100;
+            img {
+                width: 100%;
+            }
+            .intro-text {
+                position: absolute;
+                top: 50%;
+                left: 0;
+                right: 0;
+                p {
+                    padding: 0;
+                    margin: 0;
+                    font-size: 48px;
+                    font-weight: bold;
+                    color: #fff;
+                }
+            }
+        }
+    }
+}
+</style>

+ 122 - 0
docs/.vitepress/vitepress/components/globals/resource.vue

@@ -0,0 +1,122 @@
+<script lang="ts" setup>
+import { computed } from 'vue'
+import { isClient } from '@vueuse/core'
+import { useLang } from '../../composables/lang'
+import resourceLocale from '../../../i18n/pages/resource.json'
+import { sendEvent } from '../../../config/analytics'
+const mirrorUrl = 'element-plus.gitee.io'
+const isMirrorUrl = () => {
+    if (!isClient) return
+    return window.location.hostname === mirrorUrl
+}
+const resourceUrl = {
+    github: {
+        sketch: 'https://github.com/ElementUI/Resources/raw/master/Element_Plus_Design_System_2022_1.0_Beta.zip',
+        axure: 'https://github.com/ElementUI/Resources/raw/master/Element_Components_v2.1.0.rplib',
+    },
+    gitee: {
+        sketch: 'https://gitee.com/element-plus/resources/raw/master/Element_Plus_Design_System_2022_1.0_Beta.zip',
+        axure: 'https://gitee.com/element-plus/resources/raw/master/Element_Components_v2.1.0.rplib',
+    },
+}[isMirrorUrl() ? 'gitee' : 'github']
+
+const lang = useLang()
+const resourceLang = computed(() => resourceLocale[lang.value])
+const onClick = (item: string) => {
+    sendEvent('resource_download', item)
+}
+</script>
+
+<template>
+    <div class="page-resource">
+        <h1>{{ resourceLang.title }}</h1>
+        <p>{{ resourceLang.lineOne }}</p>
+        <p v-html="resourceLang.lineTwo"></p>
+        <div class="flex flex-wrap justify-center mt-32px">
+            <div class="inline-flex w-full md:w-1/3" p="2" pl-0>
+                <el-card class="card" shadow="hover">
+                    <axure-components-svg w="30" alt="axure" />
+                    <h3>{{ resourceLang.axure }}</h3>
+                    <p>
+                        {{ resourceLang.axureIntro }}
+                    </p>
+                    <a target="_blank" :href="resourceUrl.axure" @click="onClick('axure')">
+                        <el-button type="primary">{{ resourceLang.download }}</el-button>
+                    </a>
+                </el-card>
+            </div>
+            <div class="inline-flex w-full md:w-1/3" p="2">
+                <el-card class="card" shadow="hover">
+                    <sketch-template-svg w="30" alt="Sketch" />
+                    <h3>{{ resourceLang.sketch }}</h3>
+                    <p>
+                        {{ resourceLang.sketchIntro }}
+                    </p>
+                    <a target="_blank" :href="resourceUrl.sketch" @click="onClick('sketch')">
+                        <el-button type="primary">{{ resourceLang.download }}</el-button>
+                    </a>
+                </el-card>
+            </div>
+            <div class="inline-flex w-full md:w-1/3" p="2">
+                <el-card class="card" shadow="hover">
+                    <figma-template-svg w="30" alt="Figma" />
+                    <h3>{{ resourceLang.figma }}</h3>
+                    <p>
+                        {{ resourceLang.figmaIntro }}
+                    </p>
+                    <a href="https://www.figma.com/community/file/1021254029764378306" target="_blank" @click="onClick('figma')">
+                        <el-button type="primary">{{ resourceLang.download }}</el-button>
+                    </a>
+                </el-card>
+            </div>
+        </div>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.page-resource {
+    box-sizing: border-box;
+    padding: 0 40px;
+
+    h1 {
+        color: var(--text-color);
+        margin-bottom: 24px;
+    }
+    p {
+        color: var(--text-color-light);
+        line-height: 24px;
+        margin: 0;
+        &:last-of-type {
+            margin-top: 8px;
+        }
+    }
+}
+
+.card {
+    text-align: center;
+    padding: 32px 0;
+
+    img {
+        margin: auto;
+        margin-bottom: 16px;
+        height: 87px;
+    }
+
+    h3 {
+        margin: 10px;
+        font-size: 18px;
+        font-weight: normal;
+    }
+
+    p {
+        font-size: 14px;
+        color: #99a9bf;
+        padding: 0 30px;
+        margin: 0;
+        word-break: break-word;
+        line-height: 1.8;
+        min-height: 75px;
+        margin-bottom: 16px;
+    }
+}
+</style>

+ 121 - 0
docs/.vitepress/vitepress/components/globals/resources/axure-components-svg.vue

@@ -0,0 +1,121 @@
+<template>
+    <svg
+        id="图层_1"
+        version="1.1"
+        xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        x="0px"
+        y="0px"
+        viewBox="0 0 120 120"
+        style="enable-background: new 0 0 120 120"
+        xml:space="preserve"
+    >
+        <path class="st0" d="M8,16h104c1.7,0,3,1.3,3,3v82c0,1.7-1.3,3-3,3H8c-1.7,0-3-1.3-3-3V19C5,17.3,6.3,16,8,16z" />
+        <path
+            class="st1"
+            d="M13,22c0,1.1-0.9,2-2,2c-1.1,0-2-0.9-2-2s0.9-2,2-2C12.1,20,13,20.9,13,22z M19,22c0,1.1-0.9,2-2,2s-2-0.9-2-2
+    s0.9-2,2-2S19,20.9,19,22z M23,24c1.1,0,2-0.9,2-2s-0.9-2-2-2s-2,0.9-2,2S21.9,24,23,24z"
+        />
+        <path class="st2" d="M12,28h96c1.7,0,3,1.3,3,3v66c0,1.7-1.3,3-3,3H12c-1.7,0-3-1.3-3-3V31C9,29.3,10.3,28,12,28z" />
+        <path class="st3" d="M39,34h42c1.1,0,2,0.9,2,2v22c0,1.1-0.9,2-2,2H39c-1.1,0-2-0.9-2-2V36C37,34.9,37.9,34,39,34z" />
+        <path class="st4" d="M47,38h26c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H47c-1.1,0-2-0.9-2-2l0,0C45,38.9,45.9,38,47,38z" />
+        <path class="st4" d="M54,52h12c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H54c-1.1,0-2-0.9-2-2l0,0C52,52.9,52.9,52,54,52z" />
+        <path class="st4" d="M43,45h34c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H43c-1.1,0-2-0.9-2-2l0,0C41,45.9,41.9,45,43,45z" />
+        <path class="st5" d="M17,75h22c1.1,0,2,0.9,2,2v15c0,1.1-0.9,2-2,2H17c-1.1,0-2-0.9-2-2V77C15,75.9,15.9,75,17,75z" />
+        <path class="st6" d="M21,79h14c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H21c-1.1,0-2-0.9-2-2l0,0C19,79.9,19.9,79,21,79z" />
+        <path class="st6" d="M24,86h8c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2h-8c-1.1,0-2-0.9-2-2l0,0C22,86.9,22.9,86,24,86z" />
+        <path class="st7" d="M49,75h22c1.1,0,2,0.9,2,2v15c0,1.1-0.9,2-2,2H49c-1.1,0-2-0.9-2-2V77C47,75.9,47.9,75,49,75z" />
+        <path class="st8" d="M53,79h14c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H53c-1.1,0-2-0.9-2-2l0,0C51,79.9,51.9,79,53,79z" />
+        <path class="st8" d="M56,86h8c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2h-8c-1.1,0-2-0.9-2-2l0,0C54,86.9,54.9,86,56,86z" />
+        <path class="st9" d="M81,75h22c1.1,0,2,0.9,2,2v15c0,1.1-0.9,2-2,2H81c-1.1,0-2-0.9-2-2V77C79,75.9,79.9,75,81,75z" />
+        <path class="st10" d="M85,79h14c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H85c-1.1,0-2-0.9-2-2l0,0C83,79.9,83.9,79,85,79z" />
+        <path class="st10" d="M88,86h8c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2h-8c-1.1,0-2-0.9-2-2l0,0C86,86.9,86.9,86,88,86z" />
+        <path class="st11" d="M58,60h4v5h30c1.1,0,2,0.9,2,2v2v6h-4v-6H62v6h-4v-6H30v6h-4v-6v-2c0-1.1,0.9-2,2-2h30V60z" />
+    </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+    fill: #f2f8fe;
+}
+.st1 {
+    fill-rule: evenodd;
+    clip-rule: evenodd;
+    fill: #ddeafc;
+}
+.st2 {
+    fill: #ffffff;
+}
+.st3 {
+    fill: #20a0ff;
+}
+.st4 {
+    fill: #0d89e5;
+}
+.st5 {
+    fill: #80a8e1;
+}
+.st6 {
+    fill: #5289d6;
+}
+.st7 {
+    fill: #ffd6d2;
+}
+.st8 {
+    fill: #f8a3a4;
+}
+.st9 {
+    fill: #dbedff;
+}
+.st10 {
+    fill: #a2c6eb;
+}
+.st11 {
+    fill-rule: evenodd;
+    clip-rule: evenodd;
+    fill: #deebfd;
+}
+
+.dark {
+    .st0 {
+        fill: #272829;
+    }
+    .st1 {
+        fill-rule: evenodd;
+        clip-rule: evenodd;
+        fill: #494c52;
+    }
+    .st2 {
+        fill: #33383d;
+    }
+    .st3 {
+        fill: #20a0ff;
+    }
+    .st4 {
+        fill: #0d89e5;
+    }
+    .st5 {
+        fill: #80a8e1;
+    }
+    .st6 {
+        fill: #5289d6;
+    }
+    .st7 {
+        fill: #ffd6d2;
+    }
+    .st8 {
+        fill: #f8a3a4;
+    }
+    .st9 {
+        fill: #5d6874;
+    }
+    .st10 {
+        fill: #a2c6eb;
+    }
+    .st11 {
+        fill-rule: evenodd;
+        clip-rule: evenodd;
+        fill: #3e4652;
+    }
+}
+</style>

+ 71 - 0
docs/.vitepress/vitepress/components/globals/resources/figma-template-svg.vue

@@ -0,0 +1,71 @@
+<template>
+    <!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+    <svg
+        id="图层_1"
+        version="1.1"
+        xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        x="0px"
+        y="0px"
+        viewBox="0 0 120 120"
+        style="enable-background: new 0 0 120 120"
+        xml:space="preserve"
+    >
+        <path class="st0" d="M7,14h106c1.1,0,2,0.9,2,2v68c0,1.1-0.9,2-2,2H7c-1.1,0-2-0.9-2-2V16C5,14.9,5.9,14,7,14z" />
+        <path class="st1" d="M60,46c0-3.3,2.7-6,6-6l0,0c3.3,0,6,2.7,6,6l0,0c0,3.3-2.7,6-6,6l0,0C62.7,52,60,49.3,60,46L60,46z" />
+        <path class="st2" d="M48,58c0-3.3,2.7-6,6-6h6v6c0,3.3-2.7,6-6,6l0,0C50.7,64,48,61.3,48,58L48,58z" />
+        <path class="st3" d="M60,28v12h6c3.3,0,6-2.7,6-6l0,0c0-3.3-2.7-6-6-6H60z" />
+        <path class="st4" d="M48,34c0,3.3,2.7,6,6,6h6V28h-6C50.7,28,48,30.7,48,34L48,34z" />
+        <path class="st5" d="M48,46c0,3.3,2.7,6,6,6h6V40h-6C50.7,40,48,42.7,48,46L48,46z" />
+        <path class="st6" d="M5,74h110v10c0,1.1-0.9,2-2,2H7c-1.1,0-2-0.9-2-2V74z" />
+        <circle class="st7" cx="60" cy="80" r="2" />
+        <path
+            class="st8"
+            d="M46.8,86L38,103.1c-0.7,1.3,0.3,2.9,1.8,2.9H60V86H46.8z M73.2,86l8.7,17.1c0.7,1.3-0.3,2.9-1.8,2.9H60V86H73.2
+	z"
+        />
+    </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+    fill: #f2f8fe;
+}
+.st1 {
+    fill: #1abcfe;
+}
+.st2 {
+    fill: #0acf83;
+}
+.st3 {
+    fill: #ff7262;
+}
+.st4 {
+    fill: #f24e1e;
+}
+.st5 {
+    fill: #a259ff;
+}
+.st6 {
+    fill: #20a0ff;
+}
+.st7 {
+    fill: #ffffff;
+}
+.st8 {
+    fill-rule: evenodd;
+    clip-rule: evenodd;
+    fill: #deecf9;
+}
+
+.dark {
+    .st0 {
+        fill: #272829;
+    }
+    .st8 {
+        fill-rule: evenodd;
+        clip-rule: evenodd;
+        fill: #373f48;
+    }
+}
+</style>

+ 73 - 0
docs/.vitepress/vitepress/components/globals/resources/sketch-template-svg.vue

@@ -0,0 +1,73 @@
+<template>
+    <svg
+        id="图层_1"
+        version="1.1"
+        xmlns="http://www.w3.org/2000/svg"
+        xmlns:xlink="http://www.w3.org/1999/xlink"
+        x="0px"
+        y="0px"
+        viewBox="0 0 120 120"
+        style="enable-background: new 0 0 120 120"
+        xml:space="preserve"
+    >
+        <path class="st0" d="M13,15h48c3.3,0,6,2.7,6,6v33c0,3.3-2.7,6-6,6H13c-3.3,0-6-2.7-6-6V21C7,17.7,9.7,15,13,15z" />
+        <path class="st0" d="M81,15h20c3.3,0,6,2.7,6,6v33c0,3.3-2.7,6-6,6H81c-3.3,0-6-2.7-6-6V21C75,17.7,77.7,15,81,15z" />
+        <path
+            class="st1"
+            d="M12.5,15h49c3,0,5.5,2.5,5.5,5.5l0,0c0,3-2.5,5.5-5.5,5.5h-49c-3,0-5.5-2.5-5.5-5.5l0,0C7,17.5,9.5,15,12.5,15z
+      "
+        />
+        <path
+            class="st2"
+            d="M80.5,15h21c3,0,5.5,2.5,5.5,5.5l0,0c0,3-2.5,5.5-5.5,5.5h-21c-3,0-5.5-2.5-5.5-5.5l0,0
+      C75,17.5,77.5,15,80.5,15z"
+        />
+        <path class="st0" d="M90,68c3.3,0,6,2.7,6,6v28c0,2.2-1.8,4-4,4H13c-3.3,0-6-2.7-6-6V74c0-3.3,2.7-6,6-6H90z" />
+        <path
+            class="st2"
+            d="M18,73.5v27c0,3-2.5,5.5-5.5,5.5l0,0c-3,0-5.5-2.5-5.5-5.5v-27c0-3,2.5-5.5,5.5-5.5l0,0
+      C15.5,68,18,70.5,18,73.5z"
+        />
+        <path class="st3" d="M84.3,78.7L79.5,68h13.1L84.3,78.7z" />
+        <path class="st4" d="M102.1,78.7H84.2L92.6,68L102.1,78.7z" />
+        <path class="st5" d="M70,78.7L79.5,68l4.8,10.7H70z" />
+        <path class="st6" d="M92.6,68l9.5,10.7l4.8-10.7H92.6z" />
+        <path class="st5" d="M106.8,68l8.3,10.7h-13.1L106.8,68z" />
+        <path class="st7" d="M93.8,106l21.4-27.3h-13.1L93.8,106z" />
+        <path class="st3" d="M70,78.7L93.8,106l-9.5-27.3H70z" />
+        <path class="st5" d="M93.8,106l8.3-27.3H84.2L93.8,106z" />
+    </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+    fill: #f2f8fe;
+}
+.st1 {
+    fill: #ffd6d2;
+}
+.st2 {
+    fill: #20a0ff;
+}
+.st3 {
+    fill: #6496dc;
+}
+.st4 {
+    fill: #afc8ea;
+}
+.st5 {
+    fill: #80a8e1;
+}
+.st6 {
+    fill: #93b8ee;
+}
+.st7 {
+    fill: #afc8eb;
+}
+
+.dark {
+    .st0 {
+        fill: #3e444a;
+    }
+}
+</style>

+ 35 - 0
docs/.vitepress/vitepress/components/globals/secondary-colors.vue

@@ -0,0 +1,35 @@
+<script lang="ts" setup>
+import { getColorValue, useCopyColor } from '../../utils'
+
+const colorsType = ['success', 'warning', 'danger', 'info']
+
+const colorLevel = [3, 5, 7, 8, 9].map(item => `light-${item}`)
+colorLevel.unshift('dark-2')
+
+const { copyColor } = useCopyColor()
+</script>
+
+<template>
+    <el-row :gutter="12">
+        <el-col v-for="(type, i) in colorsType" :key="i" :span="6" :xs="{ span: 12 }">
+            <div class="demo-color-box" :style="{ background: getColorValue(type) }">
+                {{ type.charAt(0).toUpperCase() + type.slice(1) }}
+                <div class="value" text="xs">
+                    {{ getColorValue(type).toUpperCase() }}
+                </div>
+                <div class="bg-color-sub">
+                    <div
+                        v-for="(level, key) in colorLevel"
+                        :key="key"
+                        class="bg-secondary-sub-item transition cursor-pointer hover:shadow"
+                        :style="{
+                            width: `${100 / 6}%`,
+                            background: `var(--el-color-${type}-` + level + ')',
+                        }"
+                        @click="copyColor(type + '-' + level)"
+                    ></div>
+                </div>
+            </div>
+        </el-col>
+    </el-row>
+</template>

+ 110 - 0
docs/.vitepress/vitepress/components/globals/vp-changelog.vue

@@ -0,0 +1,110 @@
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+import axios from 'axios'
+import VPLink from '../common/vp-link.vue'
+import VPMarkdown from '../common/vp-markdown.vue'
+import { useLang } from '../../composables/lang'
+import { useLocale } from '../../composables/locale'
+import changelogLocale from '../../../i18n/component/changelog.json'
+
+interface Release {
+    id: number
+    name: string
+}
+
+const loading = ref(true)
+const releases = ref<Release[]>([])
+const currentRelease = ref()
+const changelog = useLocale(changelogLocale)
+const lang = useLang()
+
+const onVersionChange = val => {
+    const _releases = releases.value
+    currentRelease.value = _releases[_releases.findIndex(r => r.name === val)]
+}
+
+onMounted(async () => {
+    try {
+        const { data } = await axios.get<Release[]>('https://api.github.com/repos/element-plus/element-plus/releases')
+        releases.value = data
+        currentRelease.value = data[0]
+    } catch {
+        releases.value = []
+        currentRelease.value = undefined
+        // do something
+    } finally {
+        loading.value = false
+    }
+})
+</script>
+
+<template>
+    <div class="changelogs">
+        <ClientOnly>
+            <ElSkeleton :loading="loading">
+                <div class="changelog-versions">
+                    <p>{{ changelog['select-version'] }}:</p>
+                    <ElSelect :model-value="currentRelease.name" :placeholder="changelog['select-version']" style="min-width: 200px" @change="onVersionChange">
+                        <ElOption v-for="release in releases" :key="release.id" :value="release.name">
+                            {{ release.name }}
+                        </ElOption>
+                    </ElSelect>
+                </div>
+                <ElCard v-if="currentRelease">
+                    <template #header>
+                        <div class="changelog-header">
+                            <div class="changelog-meta">
+                                <p class="changelog-by">
+                                    {{ changelog['published-by'] }}
+                                    <strong>{{ currentRelease.author.login }}</strong>
+                                </p>
+                                <p class="changelog-date">
+                                    {{ new Date(currentRelease.published_at).toLocaleString(lang) }}
+                                </p>
+                            </div>
+                            <div class="operators">
+                                <VPLink :href="currentRelease.html_url">
+                                    {{ changelog['open-link'] }}
+                                </VPLink>
+                            </div>
+                        </div>
+                    </template>
+                    <div>
+                        <VPMarkdown :content="currentRelease.body" />
+                    </div>
+                </ElCard>
+            </ElSkeleton>
+        </ClientOnly>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.changelog-versions {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 1rem;
+
+    p {
+        margin-right: 2rem;
+    }
+}
+.changelog-header {
+    display: flex;
+    align-items: flex-start;
+    justify-content: space-between;
+
+    .changelog-meta {
+        display: flex;
+        flex: 1;
+        flex-direction: column;
+        p {
+            margin: 0;
+        }
+    }
+
+    .link-item {
+        line-height: 1.7;
+    }
+}
+</style>

+ 153 - 0
docs/.vitepress/vitepress/components/globals/vp-footer.vue

@@ -0,0 +1,153 @@
+<script lang="ts" setup>
+import { computed } from 'vue'
+import { useLang } from '../../composables/lang'
+import homeLocale from '../../../i18n/pages/home.json'
+
+defineProps<{
+    isHome?: boolean
+}>()
+
+const lang = useLang()
+const homeLang = computed(() => homeLocale[lang.value])
+</script>
+
+<template>
+    <footer class="footer" :class="{ 'is-home': isHome }">
+        <div class="footer-main">
+            <h4>{{ homeLang['10'] }}</h4>
+            <a href="https://github.com/element-plus/element-plus" class="footer-main-link" target="_blank">
+                {{ homeLang['11'] }}
+            </a>
+            <a href="https://element-plus.gitee.io/zh-CN/" class="footer-main-link" target="_blank">
+                {{ homeLang['china_mirror'] }}
+            </a>
+            <a href="https://github.com/element-plus/element-plus/releases" class="footer-main-link" target="_blank">
+                {{ homeLang['12'] }}
+            </a>
+            <a href="https://element.eleme.io/" class="footer-main-link" target="_blank">
+                {{ homeLang['13'] }}
+            </a>
+        </div>
+
+        <div class="footer-main">
+            <h4>{{ homeLang['19'] }}</h4>
+            <a href="https://discord.link/ElementPlus" class="footer-main-link" target="_blank">
+                {{ homeLang['discord'] }}
+            </a>
+            <a href="https://github.com/element-plus/element-plus/issues" class="footer-main-link" target="_blank">
+                {{ homeLang['16'] }}
+            </a>
+            <a href="https://github.com/element-plus/element-plus/blob/dev/.github/CONTRIBUTING.en-US.md" class="footer-main-link" target="_blank">
+                {{ homeLang['17'] }}
+            </a>
+            <a href="https://segmentfault.com/t/element-plus" class="footer-main-link" target="_blank">
+                {{ homeLang['18'] }}
+            </a>
+        </div>
+    </footer>
+</template>
+
+<style lang="scss">
+.dark .footer {
+    background-color: var(--el-fill-color-lighter);
+}
+.footer {
+    background-color: #f5f7fa;
+    box-sizing: border-box;
+    padding: 42px 64px 64px;
+    // height: 340px;
+
+    &.is-home {
+        background-color: var(--bg-color);
+        max-width: 900px;
+        margin: 0 auto;
+        padding: 40px 19px;
+    }
+
+    .container {
+        box-sizing: border-box;
+        width: auto;
+    }
+
+    .footer-main {
+        font-size: 0;
+        display: inline-block;
+        vertical-align: top;
+        margin-right: 130px;
+
+        h4 {
+            font-size: 18px;
+            line-height: 1;
+            margin: 0 0 15px 0;
+            font-weight: 400;
+            color: var(--el-text-color-primary);
+        }
+
+        .footer-main-link {
+            display: block;
+            margin: 0;
+            line-height: 2;
+            font-size: 14px;
+            color: var(--text-color-light);
+
+            &:hover {
+                color: var(--text-color);
+            }
+        }
+    }
+
+    .footer-social {
+        float: right;
+        text-align: right;
+
+        .footer-social-title {
+            color: var(--text-color-light);
+            font-size: 18px;
+            line-height: 1;
+            margin: 0 0 20px 0;
+            padding: 0;
+            font-weight: bold;
+        }
+
+        .ep-icon-github {
+            transition: 0.3s;
+            display: inline-block;
+            line-height: 32px;
+            text-align: center;
+            color: #c8d6e8;
+            background-color: transparent;
+            font-size: 32px;
+            vertical-align: middle;
+            margin-right: 20px;
+            &:hover {
+                transform: scale(1.2);
+                color: #8d99ab;
+            }
+        }
+
+        .doc-icon-gitter {
+            margin-right: 0;
+        }
+    }
+}
+
+@media (max-width: 1140px) {
+    .footer {
+        height: auto;
+    }
+}
+
+@media (max-width: 1000px) {
+    .footer-social {
+        display: none;
+    }
+}
+
+@media (max-width: 768px) {
+    .footer {
+        .footer-main {
+            margin-bottom: 30px;
+        }
+    }
+}
+</style>

+ 164 - 0
docs/.vitepress/vitepress/components/home/home-cards.vue

@@ -0,0 +1,164 @@
+<script lang="ts" setup>
+import { computed } from 'vue'
+import { useLang } from '../../composables/lang'
+import homeLocale from '../../../i18n/pages/home.json'
+const lang = useLang()
+
+const homeLang = computed(() => homeLocale[lang.value])
+</script>
+
+<template>
+    <div class="cards">
+        <ul class="container">
+            <li>
+                <div class="card">
+                    <guide-svg w="40" m="y-12" />
+                    <h3>{{ homeLang['3'] }}</h3>
+                    <p>{{ homeLang['4'] }}</p>
+                    <a :href="`/${lang}/guide/design.html`">{{ homeLang['5'] }}</a>
+                </div>
+            </li>
+            <li>
+                <div class="card">
+                    <component-svg w="40" m="y-12" />
+                    <h3>{{ homeLang['6'] }}</h3>
+                    <p>{{ homeLang['7'] }}</p>
+                    <a :href="`/${lang}/component/layout.html`">
+                        {{ homeLang['5'] }}
+                    </a>
+                </div>
+            </li>
+            <li>
+                <div class="card">
+                    <resource-svg w="40" m="y-12" />
+                    <h3>{{ homeLang['8'] }}</h3>
+                    <p>{{ homeLang['9'] }}</p>
+                    <a :href="`/${lang}/resource/index.html`"> {{ homeLang['5'] }} </a>
+                </div>
+            </li>
+        </ul>
+    </div>
+</template>
+
+<style lang="scss">
+.home-page {
+    .cards {
+        margin: 0 auto 110px;
+        max-width: 900px;
+
+        .container {
+            padding: 0;
+            margin: 0 -11px;
+            width: auto;
+            &::before,
+            &::after {
+                display: table;
+                content: '';
+            }
+            &::after {
+                clear: both;
+            }
+        }
+
+        li {
+            width: 33.3%;
+            padding: 0 19px;
+            box-sizing: border-box;
+            float: left;
+            list-style: none;
+        }
+
+        img {
+            width: 160px;
+            height: 120px;
+        }
+    }
+    .card {
+        height: 430px;
+        width: 100%;
+        background: var(--bg-color);
+        border: 1px solid var(--border-color);
+        border-radius: 5px;
+        box-sizing: border-box;
+        text-align: center;
+        position: relative;
+        transition: all 0.3s ease-in-out;
+        bottom: 0;
+
+        &:hover {
+            box-shadow: 0px 12px 32px 4px rgba(237, 239, 245, 0.24), 0px 8px 20px rgba(237, 239, 245, 0.48);
+        }
+
+        img {
+            margin: 48px auto;
+        }
+        h3 {
+            margin: 0;
+            font-size: 18px;
+            color: var(--el-text-color-primary);
+            font-weight: normal;
+        }
+        p {
+            font-size: 14px;
+            color: #99a9bf;
+            padding: 0 25px;
+            line-height: 20px;
+        }
+        a {
+            height: 53px;
+            line-height: 52px;
+            font-size: 14px;
+            color: var(--brand-color);
+            text-align: center;
+            border: 0;
+            border-top: 1px solid var(--border-color);
+            padding: 0;
+            cursor: pointer;
+            width: 100%;
+            position: absolute;
+            bottom: 0;
+            left: 0;
+            background-color: var(--bg-color);
+            border-radius: 0 0 5px 5px;
+            transition: all 0.3s;
+            text-decoration: none;
+            display: block;
+
+            &:hover {
+                color: #fff;
+                background: var(--brand-color);
+            }
+        }
+        &:hover {
+            bottom: 6px;
+            // box-shadow: 0 6px 18px 0 rgba(232, 237, 250, 0.5);
+        }
+    }
+    @media (max-width: 1140px) {
+        .cards {
+            width: 100%;
+            .container {
+                width: 100%;
+                margin: 0;
+            }
+        }
+        .banner .container {
+            width: 100%;
+            box-sizing: border-box;
+        }
+        .banner img {
+            right: 0;
+        }
+    }
+}
+
+.dark {
+    .home-page {
+        .card {
+            &:hover {
+                box-shadow: 0px 12px 32px 4px rgba(9, 11, 16, 0.24), 0px 8px 20px rgba(9, 11, 16, 0.48);
+            }
+        }
+    }
+}
+</style>

+ 75 - 0
docs/.vitepress/vitepress/components/home/home-sponsors.vue

@@ -0,0 +1,75 @@
+<script lang="ts" setup>
+import { goldSponsors, platinumSponsors } from '../../../config/sponsors'
+
+import SponsorsButton from '../sponsors/sponsors-button.vue'
+import SponsorList from './sponsor-list.vue'
+</script>
+
+<template>
+    <div class="sponsors-container" m="auto">
+        <SponsorList :sponsors="platinumSponsors" sponsor-type="platinumSponsor" />
+        <SponsorList :sponsors="goldSponsors" sponsor-type="goldSponsor" />
+
+        <sponsors-button round />
+    </div>
+</template>
+
+<style lang="scss">
+.home-page {
+    .sponsors-container {
+        margin-top: 72px;
+        .join {
+            text-align: center;
+            margin: 0 0 52px 0;
+        }
+    }
+
+    .sponsor-list {
+        --min-width: 100%;
+        grid-template-columns: repeat(auto-fit, minmax(var(--min-width), 320px));
+        justify-content: center;
+
+        &.platinum {
+            --min-width: 220px;
+        }
+        &.gold {
+            --min-width: 140px;
+
+            @media (max-width: 720px) {
+                --min-width: 160px;
+            }
+        }
+    }
+
+    .sponsor {
+        margin: 0 20px 10px;
+        height: calc(var(--min-width) / 2);
+        align-items: center;
+        // for dark mode
+        // background-color: var(--bg-color-soft);
+
+        .name {
+            font-weight: bold;
+            color: var(--text-color);
+            font-size: 14px;
+        }
+
+        img {
+            margin-right: 16px;
+        }
+
+        div {
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+        }
+
+        p {
+            margin: 0;
+            line-height: 1.8;
+            color: var(--text-color-light);
+            font-size: 12px;
+        }
+    }
+}
+</style>

+ 47 - 0
docs/.vitepress/vitepress/components/home/sponsor-list.vue

@@ -0,0 +1,47 @@
+<script lang="ts" setup>
+import { computed } from 'vue'
+import { isDark } from '../../composables/dark'
+import { useLang } from '../../composables/lang'
+import sponsorLocale from '../../../i18n/component/sponsor.json'
+import { sendEvent } from '../../../config/analytics'
+const onItemClick = (item: any) => {
+    sendEvent('sp_click', item.name, 'index')
+}
+defineProps({
+    sponsors: Array,
+    sponsorType: String,
+})
+
+const lang = useLang()
+const sponsorLang = computed(() => sponsorLocale[lang.value])
+
+const langZhCN = 'zh-CN'
+
+const getSponsorName = sponsor => {
+    if (lang.value === langZhCN) {
+        return sponsor.name_cn || sponsor.name
+    }
+    return sponsor.name
+}
+const getSponsorSlogan = sponsor => {
+    if (lang.value === langZhCN) {
+        return sponsor.slogan_cn || sponsor.slogan
+    }
+    return sponsor.slogan
+}
+</script>
+
+<template>
+    <h2 class="text-center mb-4 text-xl">{{ sponsorLang[sponsorType] }}</h2>
+    <div class="grid gap-1 sponsor-list platinum">
+        <a v-for="(sponsor, i) in sponsors" :key="i" :class="['sponsor flex px-4 rounded-md', sponsor.className]" :href="sponsor.url" target="_blank" @click="onItemClick(sponsor)">
+            <img :class="sponsor.isDark && isDark ? 'filter invert' : ''" width="45" :src="sponsor.img" :alt="sponsor.name" />
+            <div>
+                <p>
+                    <span class="name">{{ getSponsorName(sponsor) }}</span>
+                </p>
+                <p>{{ getSponsorSlogan(sponsor) }}</p>
+            </div>
+        </a>
+    </div>
+</template>

+ 61 - 0
docs/.vitepress/vitepress/components/home/svg/component-svg.vue

@@ -0,0 +1,61 @@
+<template>
+    <svg class="component-svg" width="160" height="120" viewBox="0 0 160 120" fill="none" xmlns="http://www.w3.org/2000/svg">
+        <circle cx="80" cy="60" r="60" fill="var(--component-c-0)" />
+        <path d="M37 23C37 20.7909 38.7909 19 41 19H101C103.209 19 105 20.7909 105 23V97C105 99.2091 103.209 101 101 101H45C40.5817 101 37 97.4183 37 93V23Z" fill="var(--component-c-1)" />
+        <path d="M112.048 91H49.0484C49.8484 95.4 46.715 99.5 45.0484 101H107.548C114.348 100.6 113.382 94.1667 112.048 91Z" fill="var(--component-c-2)" />
+        <path d="M121.5 5.5L114.883 19.2063C114.791 19.3981 115.022 19.5824 115.188 19.4494L127 10L121.5 5.5Z" fill="#FFACAD" />
+        <path
+            d="M121.68 21.3628L115.49 27.0342C115.145 27.3502 115.385 27.9246 115.852 27.9022L124.153 27.5051C124.496 27.4887 124.721 27.1389 124.593 26.8199L122.482 21.5456C122.352 21.2211 121.938 21.1266 121.68 21.3628Z"
+            fill="#FFD6D2"
+        />
+        <path d="M37 22C37 19.7909 38.7909 18 41 18H101C103.209 18 105 19.7909 105 22V32H37V22Z" fill="#0077CE" />
+        <path d="M37 22C37 19.7909 38.7909 18 41 18H101C103.209 18 105 19.7909 105 22V30H37V22Z" fill="#20A0FF" />
+        <circle cx="44" cy="25" r="2" fill="#0077CE" />
+        <circle cx="51" cy="25" r="2" fill="#0077CE" />
+        <circle cx="60" cy="25" r="2" fill="#0077CE" />
+        <rect x="45" y="38" width="14" height="4" fill="var(--component-c-3)" />
+        <rect x="45" y="55" width="14" height="4" fill="var(--component-c-3)" />
+        <rect x="45" y="45" width="50" height="4" fill="var(--component-c-4)" />
+        <rect x="45" y="62" width="50" height="4" fill="var(--component-c-4)" />
+        <rect x="61" y="75" width="20" height="6" rx="3" fill="#20A0FF" />
+        <path
+            fill-rule="evenodd"
+            clip-rule="evenodd"
+            d="M87 42C85.8954 42 85 42.8954 85 44V69C85 70.1046 85.8954 71 87 71H96.6154L98 75.5H100.5L99.15 71H132C133.105 71 134 70.1046 134 69V44C134 42.8954 133.105 42 132 42H87Z"
+            fill="var(--component-c-5)"
+        />
+        <path
+            fill-rule="evenodd"
+            clip-rule="evenodd"
+            d="M89 42C87.8954 42 87 42.8954 87 44V69C87 70.1046 87.8954 71 89 71H98.6154L100 75.5L104.154 71H134C135.105 71 136 70.1046 136 69V44C136 42.8954 135.105 42 134 42H89Z"
+            fill="var(--component-c-6)"
+        />
+        <path
+            d="M103.391 61.7402L94.2305 57.1895V56.4473L103.391 51.3984V53.0879L96.584 56.75V56.7988L103.391 60.0605V61.7402ZM114.943 47.873L107.688 65.5098H105.9L113.156 47.873H114.943ZM126.789 57.1699L117.629 61.7207V60.041L124.445 56.8086V56.7402L117.629 53.0781V51.3887L126.789 56.4277V57.1699Z"
+            fill="var(--component-c-7)"
+        />
+    </svg>
+</template>
+
+<style scoped lang="scss">
+.component-svg {
+    --component-c-0: #eff5fd;
+    --component-c-1: white;
+    --component-c-2: #d9edfe;
+    --component-c-3: var(--component-c-2);
+    --component-c-4: var(--component-c-2);
+    --component-c-5: #6496dc;
+    --component-c-6: #80a8e1;
+    --component-c-7: #dff2ff;
+}
+.dark .component-svg {
+    --component-c-0: #272b31;
+    --component-c-1: #273751;
+    --component-c-2: #394c68;
+    --component-c-3: #20a0ff;
+    --component-c-4: #384692;
+    --component-c-5: #263346;
+    --component-c-6: #3a5a88;
+    --component-c-7: white;
+}
+</style>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 45 - 0
docs/.vitepress/vitepress/components/home/svg/guide-svg.vue


+ 70 - 0
docs/.vitepress/vitepress/components/home/svg/left-bottom-layer-svg.vue

@@ -0,0 +1,70 @@
+<template>
+    <svg class="left-bottom-layer" width="468" height="258" viewBox="0 0 468 258" fill="none" xmlns="http://www.w3.org/2000/svg">
+        <g filter="url(#filter0_dd_11237_126801)">
+            <path
+                d="M442.314 16.9005H24.9315C22.4792 16.9005 20.4912 18.8885 20.4912 21.3408V228.922C20.4912 231.375 22.4792 233.363 24.9315 233.363H442.314C444.766 233.363 446.754 231.375 446.754 228.922V21.3408C446.754 18.8885 444.766 16.9005 442.314 16.9005Z"
+                fill="var(--lbl-c-0)"
+            />
+        </g>
+        <path
+            d="M256.934 107.925C261.839 107.925 265.815 103.949 265.815 99.0449C265.815 94.1404 261.839 90.1644 256.934 90.1644C252.03 90.1644 248.054 94.1404 248.054 99.0449C248.054 103.949 252.03 107.925 256.934 107.925Z"
+            fill="#3F85ED"
+        />
+        <path
+            d="M256.934 162.318C261.839 162.318 265.815 158.342 265.815 153.437C265.815 148.533 261.839 144.557 256.934 144.557C252.03 144.557 248.054 148.533 248.054 153.437C248.054 158.342 252.03 162.318 256.934 162.318Z"
+            fill="var(--lbl-c-1)"
+        />
+        <path
+            fill-rule="evenodd"
+            clip-rule="evenodd"
+            d="M128.722 203.39C171.943 203.39 206.981 168.352 206.981 125.131C206.981 81.9099 171.943 46.8721 128.722 46.8721C85.5007 46.8721 50.4629 81.9099 50.4629 125.131C50.4629 168.352 85.5007 203.39 128.722 203.39ZM128.722 177.304C157.536 177.304 180.894 153.945 180.894 125.131C180.894 96.3171 157.536 72.9585 128.722 72.9585C99.9077 72.9585 76.5492 96.3171 76.5492 125.131C76.5492 153.945 99.9077 177.304 128.722 177.304Z"
+            fill="#3F85ED"
+        />
+        <path
+            fill-rule="evenodd"
+            clip-rule="evenodd"
+            d="M50.4629 125.131H76.5492C76.5492 96.3171 99.9077 72.9585 128.722 72.9585V46.8721C85.5007 46.8721 50.4629 81.9099 50.4629 125.131Z"
+            fill="var(--lbl-c-2)"
+        />
+        <path
+            d="M403.461 88.4994H291.345C285.521 88.4994 280.8 93.2208 280.8 99.0449C280.8 104.869 285.521 109.59 291.345 109.59H403.461C409.285 109.59 414.007 104.869 414.007 99.0449C414.007 93.2208 409.285 88.4994 403.461 88.4994Z"
+            fill="var(--lbl-c-3)"
+        />
+        <path
+            d="M403.461 142.892H291.345C285.521 142.892 280.8 147.614 280.8 153.438C280.8 159.262 285.521 163.984 291.345 163.984H403.461C409.285 163.984 414.007 159.262 414.007 153.438C414.007 147.614 409.285 142.892 403.461 142.892Z"
+            fill="var(--lbl-c-3)"
+        />
+        <defs>
+            <filter id="filter0_dd_11237_126801" x="2.73027" y="3.57981" width="461.785" height="251.984" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+                <feFlood flood-opacity="0" result="BackgroundImageFix" />
+                <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
+                <feOffset dy="4.44024" />
+                <feGaussianBlur stdDeviation="8.88047" />
+                <feColorMatrix type="matrix" values="0 0 0 0 0.039009 0 0 0 0 0.13338 0 0 0 0 0.232469 0 0 0 0.08 0" />
+                <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_11237_126801" />
+                <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
+                <feOffset />
+                <feGaussianBlur stdDeviation="2.22012" />
+                <feColorMatrix type="matrix" values="0 0 0 0 0.0392157 0 0 0 0 0.133333 0 0 0 0 0.231373 0 0 0 0.04 0" />
+                <feBlend mode="normal" in2="effect1_dropShadow_11237_126801" result="effect2_dropShadow_11237_126801" />
+                <feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_11237_126801" result="shape" />
+            </filter>
+        </defs>
+    </svg>
+</template>
+
+<style scoped>
+.left-bottom-layer {
+    --lbl-c-0: white;
+    --lbl-c-1: #c7dbf8;
+    --lbl-c-2: #98bdf2;
+    --lbl-c-3: #e4efff;
+}
+
+.dark .left-bottom-layer {
+    --lbl-c-0: #1e2835;
+    --lbl-c-1: #58677a;
+    --lbl-c-2: #2f5790;
+    --lbl-c-3: #444f5d;
+}
+</style>

+ 39 - 0
docs/.vitepress/vitepress/components/home/svg/left-layer-svg.vue

@@ -0,0 +1,39 @@
+<template>
+    <svg class="left-layer-svg" width="262" height="173" viewBox="0 0 262 173" fill="none" xmlns="http://www.w3.org/2000/svg">
+        <g clip-path="url(#clip0_11237_126796)">
+            <path
+                fill-rule="evenodd"
+                clip-rule="evenodd"
+                d="M5.66289 1.92664C3.21061 1.92664 1.22266 3.9146 1.22266 6.36687V155.114C1.22266 157.567 3.21061 159.555 5.66289 159.555H173.559L186.602 172.32L199.09 159.555H256.536C258.988 159.555 260.976 157.567 260.976 155.114V6.36688C260.976 3.9146 258.988 1.92664 256.536 1.92664H5.66289Z"
+                fill="var(--ll-c-0)"
+            />
+            <path
+                d="M80.8689 23.0177H38.1317C32.1543 23.0177 27.3086 27.8633 27.3086 33.8407C27.3086 39.8182 32.1543 44.6638 38.1317 44.6638H80.8689C86.8463 44.6638 91.692 39.8182 91.692 33.8407C91.692 27.8633 86.8463 23.0177 80.8689 23.0177Z"
+                fill="#C7DCFB"
+            />
+            <path
+                d="M230.449 64.0898H31.7488C29.2966 64.0898 27.3086 66.0778 27.3086 68.5301V81.2957C27.3086 83.748 29.2966 85.7359 31.7488 85.7359H230.449C232.902 85.7359 234.89 83.748 234.89 81.2957V68.5301C234.89 66.0778 232.902 64.0898 230.449 64.0898Z"
+                fill="#A2C5F9"
+            />
+            <path
+                d="M230.449 103.497H31.7488C29.2966 103.497 27.3086 105.485 27.3086 107.938V120.703C27.3086 123.156 29.2966 125.144 31.7488 125.144H230.449C232.902 125.144 234.89 123.156 234.89 120.703V107.938C234.89 105.485 232.902 103.497 230.449 103.497Z"
+                fill="#A2C5F9"
+            />
+        </g>
+        <defs>
+            <clipPath id="clip0_11237_126796">
+                <rect width="261" height="172" fill="white" transform="translate(0.491211 0.941406)" />
+            </clipPath>
+        </defs>
+    </svg>
+</template>
+
+<style scoped>
+.left-layer-svg {
+    --ll-c-0: #5c97ee;
+}
+
+.dark .left-layer-svg {
+    --ll-c-0: #4372b6;
+}
+</style>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 121 - 0
docs/.vitepress/vitepress/components/home/svg/people-svg.vue


+ 33 - 0
docs/.vitepress/vitepress/components/home/svg/resource-svg.vue

@@ -0,0 +1,33 @@
+<template>
+    <svg class="resource-svg" width="160" height="120" viewBox="0 0 160 120" fill="none" xmlns="http://www.w3.org/2000/svg">
+        <circle cx="80" cy="60" r="60" fill="var(--resource-c-0)" />
+        <rect x="32" y="14" width="96" height="73" rx="4" fill="var(--resource-c-1)" />
+        <rect x="38" y="20" width="84" height="56" fill="var(--resource-c-2)" />
+        <rect x="78" y="31" width="14" height="14" fill="var(--resource-c-3)" />
+        <rect x="98" y="31" width="14" height="14" fill="#FF9EA4" />
+        <rect x="78" y="51" width="14" height="14" fill="#FFCFC7" />
+        <rect x="57" y="51" width="14" height="14" fill="var(--resource-c-3)" />
+        <path d="M127.966 82H32.0339L25 95H136L127.966 82Z" fill="#20A0FF" />
+        <path d="M25 95H136V97C136 98.1046 135.105 99 134 99H27C25.8954 99 25 98.1046 25 97V95Z" fill="#0077CE" />
+        <circle cx="37" cy="41" r="15" fill="#20A0FF" />
+        <path d="M41 35H33L29 39L37 48L45 39L41 35Z" fill="white" />
+        <rect x="6" y="39" width="12" height="4" rx="2" fill="#FFCFC7" />
+        <rect x="11.8955" y="22" width="12" height="4" rx="2" transform="rotate(28.2865 11.8955 22)" fill="#FF9EA4" />
+        <rect x="23.4775" y="11" width="12" height="4" rx="2" transform="rotate(60.3794 23.4775 11)" fill="#FFCFC7" />
+    </svg>
+</template>
+
+<style scoped lang="scss">
+.resource-svg {
+    --resource-c-0: #eff5fd;
+    --resource-c-1: #d9edfe;
+    --resource-c-2: white;
+    --resource-c-3: #eff5fd;
+}
+.dark .resource-svg {
+    --resource-c-0: #272b31;
+    --resource-c-1: #3b6a92;
+    --resource-c-2: #273751;
+    --resource-c-3: #3a5a88;
+}
+</style>

+ 46 - 0
docs/.vitepress/vitepress/components/home/svg/right-layer-svg.vue

@@ -0,0 +1,46 @@
+<template>
+    <svg class="right-layer" width="474" height="328" viewBox="0 0 474 328" fill="none" xmlns="http://www.w3.org/2000/svg">
+        <path
+            fill-rule="evenodd"
+            clip-rule="evenodd"
+            d="M25.1434 17.0806C22.6911 17.0806 20.7031 19.0686 20.7031 21.5209V285.714C20.7031 288.167 22.6911 290.155 25.1434 290.155H155.576L168.619 302.92L181.107 290.155H448.076C450.529 290.155 452.517 288.167 452.517 285.714V21.5209C452.517 19.0686 450.529 17.0806 448.076 17.0806H25.1434Z"
+            fill="var(--rl-c-0)"
+        />
+        <path
+            d="M203.307 211.895H143.364C131.409 211.895 121.718 221.745 121.718 233.895V233.895C121.718 246.046 131.409 255.895 143.364 255.895H203.307C215.261 255.895 224.953 246.046 224.953 233.895V233.895C224.953 221.745 215.261 211.895 203.307 211.895Z"
+            fill="var(--rl-c-1)"
+        />
+        <path
+            d="M329.299 211.895H269.356C257.401 211.895 247.71 221.745 247.71 233.895V233.895C247.71 246.046 257.401 255.895 269.356 255.895H329.299C341.254 255.895 350.945 246.046 350.945 233.895V233.895C350.945 221.745 341.254 211.895 329.299 211.895Z"
+            fill="#3F85ED"
+        />
+        <path
+            d="M409.223 108.105H63.9949C61.5426 108.105 59.5547 110.093 59.5547 112.546V125.311C59.5547 127.764 61.5426 129.752 63.9949 129.752H409.223C411.676 129.752 413.664 127.764 413.664 125.311V112.546C413.664 110.093 411.676 108.105 409.223 108.105Z"
+            fill="var(--rl-c-1)"
+        />
+        <path
+            d="M353.166 149.177H120.054C117.601 149.177 115.613 151.165 115.613 153.618V166.383C115.613 168.836 117.601 170.823 120.054 170.823H353.166C355.618 170.823 357.606 168.836 357.606 166.383V153.618C357.606 151.165 355.618 149.177 353.166 149.177Z"
+            fill="var(--rl-c-1)"
+        />
+        <path
+            d="M318.754 49.8273H156.685C154.233 49.8273 152.245 51.8034 152.245 54.2411V77.4135C152.245 79.8511 154.233 81.8273 156.685 81.8273H318.754C321.206 81.8273 323.194 79.8511 323.194 77.4135V54.2411C323.194 51.8034 321.206 49.8273 318.754 49.8273Z"
+            fill="#91BEFF"
+        />
+    </svg>
+</template>
+
+<style scoped>
+.right-layer {
+    --rl-c-0: white;
+    --rl-c-1: #e4efff;
+
+    filter: drop-shadow(0px 0px 6px rgba(10, 34, 59, 0.04)) drop-shadow(0px 6px 20px rgba(10, 34, 59, 0.08));
+}
+
+.dark .right-layer {
+    --rl-c-0: #1e2835;
+    --rl-c-1: #444f5d;
+
+    filter: drop-shadow(0px 0px 6px rgba(6, 15, 26, 0.24)) drop-shadow(0px 6px 20px rgba(6, 15, 26, 0.48));
+}
+</style>

+ 104 - 0
docs/.vitepress/vitepress/components/home/svg/screen-svg.vue

@@ -0,0 +1,104 @@
+<template>
+    <svg class="screen-svg" width="1163" height="728" viewBox="0 0 1163 728" fill="none" xmlns="http://www.w3.org/2000/svg">
+        <path d="M1152 0H8C3.58172 0 0 3.58172 0 8V720C0 724.418 3.58173 728 8 728H1152C1156.42 728 1160 724.418 1160 720V8C1160 3.58172 1156.42 0 1152 0Z" fill="var(--screen-c-0)" />
+        <path d="M1152 0H8C3.58172 0 0 3.58172 0 8V64H1160V8C1160 3.58172 1156.42 0 1152 0Z" fill="#3F85ED" />
+        <path d="M56 40C60.4183 40 64 36.4183 64 32C64 27.5817 60.4183 24 56 24C51.5817 24 48 27.5817 48 32C48 36.4183 51.5817 40 56 40Z" fill="#FD873B" />
+        <path d="M88 40C92.4183 40 96 36.4183 96 32C96 27.5817 92.4183 24 88 24C83.5817 24 80 27.5817 80 32C80 36.4183 83.5817 40 88 40Z" fill="#FFCD8A" />
+        <path d="M120 40C124.418 40 128 36.4183 128 32C128 27.5817 124.418 24 120 24C115.582 24 112 27.5817 112 32C112 36.4183 115.582 40 120 40Z" fill="#91DD9D" />
+        <path
+            d="M503.394 16H200.606C191.435 16 184 23.1634 184 32C184 40.8366 191.435 48 200.606 48H503.394C512.565 48 520 40.8366 520 32C520 23.1634 512.565 16 503.394 16Z"
+            fill="var(--screen-c-1)"
+        />
+        <path
+            d="M291.571 112H52.4286C49.9827 112 48 113.989 48 116.442V675.558C48 678.011 49.9827 680 52.4286 680H291.571C294.017 680 296 678.011 296 675.558V116.442C296 113.989 294.017 112 291.571 112Z"
+            fill="var(--screen-c-2)"
+        />
+        <path
+            d="M251.52 144H92.48C90.0058 144 88 145.976 88 148.414V171.586C88 174.024 90.0058 176 92.48 176H251.52C253.994 176 256 174.024 256 171.586V148.414C256 145.976 253.994 144 251.52 144Z"
+            fill="var(--screen-c-3)"
+        />
+        <path
+            d="M251.52 200H92.48C90.0058 200 88 201.976 88 204.414V227.586C88 230.024 90.0058 232 92.48 232H251.52C253.994 232 256 230.024 256 227.586V204.414C256 201.976 253.994 200 251.52 200Z"
+            fill="var(--screen-c-3)"
+        />
+        <path
+            d="M251.52 256H92.48C90.0058 256 88 257.976 88 260.414V283.586C88 286.024 90.0058 288 92.48 288H251.52C253.994 288 256 286.024 256 283.586V260.414C256 257.976 253.994 256 251.52 256Z"
+            fill="var(--screen-c-3)"
+        />
+        <path
+            d="M251.52 312H92.48C90.0058 312 88 313.976 88 316.414V339.586C88 342.024 90.0058 344 92.48 344H251.52C253.994 344 256 342.024 256 339.586V316.414C256 313.976 253.994 312 251.52 312Z"
+            fill="var(--screen-c-3)"
+        />
+        <path
+            d="M251.52 368H92.48C90.0058 368 88 369.976 88 372.414V395.586C88 398.024 90.0058 400 92.48 400H251.52C253.994 400 256 398.024 256 395.586V372.414C256 369.976 253.994 368 251.52 368Z"
+            fill="var(--screen-c-3)"
+        />
+        <path
+            d="M1107.55 112H336.448C333.991 112 332 114.075 332 116.635V253.365C332 255.925 333.991 258 336.448 258H1107.55C1110.01 258 1112 255.925 1112 253.365V116.635C1112 114.075 1110.01 112 1107.55 112Z"
+            fill="var(--screen-c-2)"
+        />
+        <path
+            d="M751.574 144H474.426C471.981 144 470 145.976 470 148.414V171.586C470 174.024 471.981 176 474.426 176H751.574C754.019 176 756 174.024 756 171.586V148.414C756 145.976 754.019 144 751.574 144Z"
+            fill="var(--screen-c-5)"
+        />
+        <path
+            d="M919.516 194H474.484C472.008 194 470 195.976 470 198.414V221.586C470 224.024 472.008 226 474.484 226H919.516C921.992 226 924 224.024 924 221.586V198.414C924 195.976 921.992 194 919.516 194Z"
+            fill="var(--screen-c-6)"
+        />
+        <path
+            d="M1067.39 194H952.614C950.066 194 948 195.976 948 198.414V221.586C948 224.024 950.066 226 952.614 226H1067.39C1069.93 226 1072 224.024 1072 221.586V198.414C1072 195.976 1069.93 194 1067.39 194Z"
+            fill="var(--screen-c-6)"
+        />
+        <path
+            d="M441.568 144H368.432C365.984 144 364 145.984 364 148.432V221.568C364 224.016 365.984 226 368.432 226H441.568C444.016 226 446 224.016 446 221.568V148.432C446 145.984 444.016 144 441.568 144Z"
+            fill="#3F85ED"
+        />
+        <path
+            d="M1107 282H335C332.791 282 331 283.791 331 286V676C331 678.209 332.791 680 335 680H1107C1109.21 680 1111 678.209 1111 676V286C1111 283.791 1109.21 282 1107 282Z"
+            fill="var(--screen-c-2)"
+        />
+        <path d="M1015.7 672.14V444.023" stroke="var(--screen-c-4)" stroke-width="8.88047" stroke-linecap="round" />
+        <path d="M817.559 672.14V444.023" stroke="var(--screen-c-4)" stroke-width="8.88047" stroke-linecap="round" />
+        <path d="M619.413 672.14V444.023" stroke="var(--screen-c-4)" stroke-width="8.88047" stroke-linecap="round" />
+        <path d="M421.268 672.14V444.023" stroke="var(--screen-c-4)" stroke-width="8.88047" stroke-linecap="round" />
+        <path
+            d="M354.386 544.206C369.65 570.57 382.138 592.494 419.047 591.384C461.229 590.115 469.25 538.656 519.23 538.656C579.173 538.656 628.571 613.585 708.495 602.484C805.903 588.955 804.515 362.434 846.975 362.434C896.928 362.434 846.975 555.307 949.933 575.01C997.09 584.035 1069.26 554.844 1089.24 538.656"
+            stroke="var(--screen-c-4)"
+            stroke-width="8.88047"
+            stroke-linecap="round"
+        />
+        <path
+            d="M358.271 504.799C390.186 524.503 408.224 525.058 426.54 522.005C454.569 517.333 488.148 465.114 528.666 459.564C579.451 452.607 605.26 490.258 663.538 480.655C718.399 471.616 760.113 367.707 843.09 380.472C920.794 392.427 913.58 530.608 954.374 540.043C977.13 545.307 989.063 516.732 1021.53 521.45C1047.51 525.224 1075.28 560.764 1085.91 578.063"
+            stroke="#75ADFF"
+            stroke-width="8.88047"
+            stroke-linecap="round"
+        />
+        <path
+            d="M421 533C426.523 533 431 528.523 431 523C431 517.477 426.523 513 421 513C415.477 513 411 517.477 411 523C411 528.523 415.477 533 421 533Z"
+            fill="#E4EFFF"
+            stroke="#75ADFF"
+            stroke-width="4.99526"
+        />
+    </svg>
+</template>
+
+<style scoped>
+.screen-svg {
+    --screen-c-0: #e4efff;
+    --screen-c-1: #c7deff;
+    --screen-c-2: white;
+    --screen-c-3: #eff5ff;
+    --screen-c-4: var(--screen-c-3);
+    --screen-c-5: #bdd8ff;
+    --screen-c-6: #eff5ff;
+}
+.dark .screen-svg {
+    --screen-c-0: #0d1014;
+    --screen-c-1: #7fabee;
+    --screen-c-2: #14181e;
+    --screen-c-3: #303c4b;
+    --screen-c-4: #1d242c;
+    --screen-c-5: #5f7ca5;
+    --screen-c-6: #313c4b;
+}
+</style>

+ 8 - 0
docs/.vitepress/vitepress/components/icons/back-to-top.vue

@@ -0,0 +1,8 @@
+<template>
+    <svg viewBox="0 0 512 512">
+        <path
+            d="M256 48C141.13 48 48 141.13 48 256s93.13 208 208 208s208-93.13 208-208S370.87 48 256 48zm91.36 212.65a16 16 0 0 1-22.63.09L272 208.42V342a16 16 0 0 1-32 0V208.42l-52.73 52.32A16 16 0 1 1 164.73 238l80-79.39a16 16 0 0 1 22.54 0l80 79.39a16 16 0 0 1 .09 22.65z"
+            fill="currentColor"
+        />
+    </svg>
+</template>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 8 - 0
docs/.vitepress/vitepress/components/icons/codepen.vue


+ 8 - 0
docs/.vitepress/vitepress/components/icons/dark.vue

@@ -0,0 +1,8 @@
+<template>
+    <svg viewBox="0 0 24 24">
+        <path
+            d="M11.01 3.05C6.51 3.54 3 7.36 3 12a9 9 0 0 0 9 9c4.63 0 8.45-3.5 8.95-8c.09-.79-.78-1.42-1.54-.95A5.403 5.403 0 0 1 11.1 7.5c0-1.06.31-2.06.84-2.89c.45-.67-.04-1.63-.93-1.56z"
+            fill="currentColor"
+        />
+    </svg>
+</template>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 8 - 0
docs/.vitepress/vitepress/components/icons/element-plus-logo.vue


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 15 - 0
docs/.vitepress/vitepress/components/icons/element-plus-text-logo.vue


+ 5 - 0
docs/.vitepress/vitepress/components/icons/expand.vue

@@ -0,0 +1,5 @@
+<template>
+    <svg viewBox="0 0 24 24">
+        <path d="M11.29 8.71L6.7 13.3a.996.996 0 1 0 1.41 1.41L12 10.83l3.88 3.88a.996.996 0 1 0 1.41-1.41L12.7 8.71a.996.996 0 0 0-1.41 0z" fill="currentColor" />
+    </svg>
+</template>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 8 - 0
docs/.vitepress/vitepress/components/icons/light.vue


+ 15 - 0
docs/.vitepress/vitepress/components/icons/playground.vue

@@ -0,0 +1,15 @@
+<template>
+    <svg version="1.1" x="0px" y="0px" viewBox="0 0 44 44" xml:space="preserve">
+        <path
+            fill="#409eff"
+            class="st0"
+            d="M37.4,32.4c0,1.6-0.8,1.9-0.8,1.9L21.5,43c-0.5,0.2-1,0.2-1.5,0
+	c0,0-14.8-8.6-15.3-9c-0.3-0.2-0.5-0.6-0.6-1V15.2c0-0.8,1-1.3,1-1.3l14.8-8.5c0.6-0.3,1.2-0.3,1.8,0l14.5,8.4
+	c0.8,0.3,1.3,1.2,1.2,2.1L37.4,32.4L37.4,32.4z M31.5,15.4L21.4,9.5c-0.4-0.2-1-0.2-1.4,0L8.3,16.1c0,0-0.8,0.5-0.8,1.1
+	s0,13.9,0,13.9c0,0.3,0.2,0.6,0.4,0.8c0.4,0.3,12,7,12,7c0.4,0.2,0.8,0.2,1.2,0c0.7-0.4,11.8-6.8,11.8-6.8s0.7-0.3,0.7-1.5v-3.5
+	l-13,7.9v-3c0.1-0.8,0.4-1.5,1-2.1L33.2,23c0.3-0.4,0.5-0.9,0.5-1.5v-3.1l-13.1,7.9v-3.2c0-0.7,0.3-1.4,0.8-1.8L31.5,15.4z
+	 M41.1,4.2c0-0.2-0.1-0.4-0.4-0.4c0,0,0,0,0,0H38V1.1c0-0.2-0.3-0.3-0.5-0.2L36,1.1c-0.2,0-0.3,0.1-0.3,0.2v2.5H33
+	c-0.2,0-0.3,0.2-0.4,0.4v2h3V9c0,0.2,0.3,0.3,0.5,0.2L37.7,9c0.2,0,0.3-0.1,0.3-0.2V6.1h3.1L41.1,4.2z"
+        />
+    </svg>
+</template>

+ 8 - 0
docs/.vitepress/vitepress/components/icons/toggle-button.vue

@@ -0,0 +1,8 @@
+<template>
+    <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+        <rect x="2" y="7" width="11" height="2" fill="#606266" />
+        <rect x="2" y="11" width="14" height="2" fill="#606266" />
+        <rect x="2" y="15" width="8" height="2" fill="#606266" />
+        <rect x="2" y="3" width="16" height="2" fill="#606266" />
+    </svg>
+</template>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 39 - 0
docs/.vitepress/vitepress/components/nav/l1-categories.vue


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 64 - 0
docs/.vitepress/vitepress/components/nav/l2-categories.vue


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 34 - 0
docs/.vitepress/vitepress/components/nav/l3-categories.vue


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 25 - 0
docs/.vitepress/vitepress/components/nav/top-navigation-example.vue


+ 13 - 0
docs/.vitepress/vitepress/components/navbar/vp-hamburger.vue

@@ -0,0 +1,13 @@
+<script setup lang="ts">
+defineProps<{
+    active: boolean
+}>()
+</script>
+
+<template>
+    <div :class="{ 'menu-hamburger': true, active }" role="button">
+        <span class="hamburger-1"></span>
+        <span class="hamburger-2"></span>
+        <span class="hamburger-3"></span>
+    </div>
+</template>

+ 67 - 0
docs/.vitepress/vitepress/components/navbar/vp-menu-link.vue

@@ -0,0 +1,67 @@
+<script lang="ts" setup>
+import { useRoute } from 'vitepress'
+import { useStorage } from '@vueuse/core'
+import VPLink from '../common/vp-link.vue'
+import { isActiveLink } from '../../utils'
+
+import type { Link } from '../../types'
+const USER_VISITED_NEW_RESOURCE_PAGE = 'USER_VISITED_NEW_RESOURCE_PAGE'
+defineProps<{
+    item: Link
+}>()
+
+const route = useRoute()
+const isVisited = useStorage<boolean | string>(USER_VISITED_NEW_RESOURCE_PAGE, false)
+const isNewPage = (item: Link) => item.activeMatch === '/some_fake_path/'
+
+const onNavClick = (item: Link) => {
+    if (isNewPage(item) && !isVisited.value) {
+        isVisited.value = Date.now().toString()
+    }
+}
+</script>
+
+<template>
+    <VPLink
+        :class="{
+            'is-menu-link': true,
+            active: isActiveLink(route, item.activeMatch || item.link, !!item.activeMatch),
+        }"
+        :href="item.link"
+        :no-icon="true"
+        @click="onNavClick(item)"
+    >
+        <el-badge v-if="isNewPage(item) && !isVisited" is-dot class="badge"> {{ item.text }}</el-badge>
+        <template v-else> {{ item.text }}</template>
+    </VPLink>
+</template>
+
+<style scoped lang="scss">
+.is-menu-link {
+    display: block;
+    padding: 0 12px;
+    line-height: calc(var(--nav-height) - 3px);
+    font-size: 14px;
+    font-weight: 500;
+    color: var(--text-color);
+    transition: color var(--el-transition-duration);
+    border-bottom: 2px solid transparent;
+
+    &.active {
+        border-bottom-color: var(--brand-color);
+    }
+
+    &:hover {
+        color: var(--brand-color);
+    }
+
+    .badge {
+        display: inline;
+        vertical-align: unset;
+    }
+
+    .badge:deep(.is-dot) {
+        right: 0;
+    }
+}
+</style>

+ 12 - 0
docs/.vitepress/vitepress/components/navbar/vp-menu.vue

@@ -0,0 +1,12 @@
+<script setup lang="ts">
+import { useNav } from '../../composables/nav'
+import VPMenuLink from './vp-menu-link.vue'
+
+const navs = useNav()
+</script>
+
+<template>
+    <nav v-if="navs" class="navbar-menu">
+        <VPMenuLink v-for="(item, key) in navs" :key="key" :item="item" />
+    </nav>
+</template>

+ 204 - 0
docs/.vitepress/vitepress/components/navbar/vp-search.vue

@@ -0,0 +1,204 @@
+<script setup lang="ts">
+import '@docsearch/css'
+import { getCurrentInstance, onMounted, watch } from 'vue'
+import { useRoute, useRouter } from 'vitepress'
+import docsearch from '@docsearch/js'
+import { isClient } from '@vueuse/core'
+// import { useLang } from '../../composables/lang'
+// import type { DefaultTheme } from '../config'
+import type { DocSearchHit } from '@docsearch/react/dist/esm/types'
+
+const props = defineProps<{
+    options: any
+    multilang?: boolean
+}>()
+
+const vm = getCurrentInstance()
+const route = useRoute()
+const router = useRouter()
+
+watch(
+    () => props.options,
+    value => {
+        update(value)
+    }
+)
+
+onMounted(() => {
+    initialize(props.options)
+})
+
+function isSpecialClick(event: MouseEvent) {
+    return event.button === 1 || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey
+}
+
+function getRelativePath(absoluteUrl: string) {
+    const { pathname, hash } = new URL(absoluteUrl)
+
+    return pathname + hash
+}
+
+function update(options: any) {
+    if (vm && vm.vnode.el) {
+        vm.vnode.el.innerHTML = '<div class="algolia-search-box" id="docsearch"></div>'
+        initialize(options)
+    }
+}
+
+// const lang = useLang()
+
+function initialize(userOptions: any) {
+    // if the user has multiple locales, the search results should be filtered
+    // based on the language
+    // const facetFilters = props.multilang ? [`language:${lang.value}`] : []
+
+    docsearch(
+        Object.assign({}, userOptions, {
+            container: '#docsearch',
+            indexName: 'element-plus',
+            // searchParameters: Object.assign({}, userOptions.searchParameters, {
+            //   // pass a custom lang facetFilter to allow multiple language search
+            //   // https://github.com/algolia/docsearch-configs/pull/3942
+            //   facetFilters: facetFilters.concat(
+            //     userOptions.searchParameters?.facetFilters || []
+            //   ),
+            // }),
+
+            navigator: {
+                navigate: ({ suggestionUrl }: { suggestionUrl: string }) => {
+                    if (!isClient) return
+
+                    const { pathname: hitPathname } = new URL(window.location.origin + suggestionUrl)
+
+                    // Router doesn't handle same-page navigation so we use the native
+                    // browser location API for anchor navigation
+                    if (route.path === hitPathname) {
+                        window.location.assign(window.location.origin + suggestionUrl)
+                    } else {
+                        router.go(suggestionUrl)
+                    }
+                },
+            },
+
+            transformItems: (items: DocSearchHit[]) => {
+                return items.map(item => {
+                    return Object.assign({}, item, {
+                        url: getRelativePath(item.url),
+                    })
+                })
+            },
+
+            hitComponent: ({ hit, children }: { hit: DocSearchHit; children: any }) => {
+                const relativeHit = hit.url.startsWith('http') ? getRelativePath(hit.url as string) : hit.url
+
+                return {
+                    type: 'a',
+                    ref: undefined,
+                    constructor: undefined,
+                    key: undefined,
+                    props: {
+                        href: hit.url,
+                        onClick: (event: MouseEvent) => {
+                            if (isSpecialClick(event)) {
+                                return
+                            }
+
+                            // we rely on the native link scrolling when user is already on
+                            // the right anchor because Router doesn't support duplicated
+                            // history entries
+                            if (route.path === relativeHit) {
+                                return
+                            }
+
+                            // if the hits goes to another page, we prevent the native link
+                            // behavior to leverage the Router loading feature
+                            if (route.path !== relativeHit) {
+                                event.preventDefault()
+                            }
+
+                            router.go(relativeHit)
+                        },
+                        children,
+                    },
+                    __v: null,
+                }
+            },
+        })
+    )
+}
+</script>
+
+<template>
+    <div id="docsearch" class="algolia-search-box"></div>
+</template>
+
+<style lang="scss">
+@use '../../styles/mixins' as *;
+.algolia-search-box {
+    // display: flex;
+    // align-items: center;
+    // line-height: var(--header-height);
+    // padding-left: 0.5rem;
+    // padding-top: 1px;
+    // margin-right: 12px;
+    // .search-box-placeholder,
+    // .search-box-key {
+    //   display: flex;
+    // }
+
+    @include respond-to('md') {
+        min-width: 176.3px;
+    }
+}
+
+.DocSearch {
+    --docsearch-primary-color: var(--brand-color);
+    --docsearch-key-gradient: rgba(125, 125, 125, 0.1);
+    // --docsearch-key-shadow: rgba(125, 125, 125, 0.3);
+    --docsearch-footer-height: 44px;
+    --docsearch-footer-background: var(--bg-color);
+    --docsearch-footer-shadow: 0 -1px 0 0 #e0e3e8, 0 -3px 6px 0 rgba(69, 98, 155, 0.12);
+    --docsearch-searchbox-background: rgba(var(--bg-color-rgb), 0.8);
+    --docsearch-searchbox-focus-background: var(--bg-color-mute);
+    --docsearch-muted-color: var(--text-color-lighter);
+    --docsearch-text-color: var(--text-color-light);
+    --docsearch-modal-background: var(--bg-color-soft);
+    --docsearch-modal-shadow: var(--el-box-shadow);
+
+    transition: background-color var(--el-transition-duration-fast);
+
+    &.DocSearch-Container {
+        z-index: 20000;
+    }
+
+    &.DocSearch-Button {
+        margin-right: 8px;
+    }
+
+    @media (max-width: 749px) {
+        &.DocSearch-Button {
+            margin: 0 12px;
+            padding: 0;
+        }
+    }
+
+    .dark & {
+        --docsearch-text-color: var(--text-color-light);
+        --docsearch-key-shadow: none;
+        --docsearch-modal-shadow: none;
+        --docsearch-footer-shadow: none;
+        // --docsearch-searchbox-focus-background: var(--bg-color-mute);
+        .DocSearch-Button {
+            .DocSearch-Button-Key {
+                box-shadow: unset;
+            }
+        }
+    }
+
+    background-color: transparent;
+
+    @include respond-to('md') {
+        background-color: var(--docsearch-searchbox-background);
+    }
+}
+</style>

+ 23 - 0
docs/.vitepress/vitepress/components/navbar/vp-social-link.vue

@@ -0,0 +1,23 @@
+<script setup lang="ts">
+import type { Component } from 'vue'
+
+defineProps<{
+    icon: Component
+    link: string
+    text: string
+}>()
+</script>
+
+<template>
+    <a :href="link" :title="text" target="_blank" rel="noreferrer noopener" class="social-link">
+        <ElIcon v-if="icon" :size="24">
+            <component :is="icon" />
+        </ElIcon>
+    </a>
+</template>
+
+<style scoped lang="scss">
+.social-link {
+    color: var(--text-color);
+}
+</style>

+ 19 - 0
docs/.vitepress/vitepress/components/navbar/vp-social-links.vue

@@ -0,0 +1,19 @@
+<script setup lang="ts">
+import { useSocialLinks } from '../../composables/social-links'
+import VPSocialLink from './vp-social-link.vue'
+
+const links = useSocialLinks()
+</script>
+
+<template>
+    <div class="social-links">
+        <VPSocialLink v-for="link in links" :key="link.text" v-bind="link" />
+    </div>
+</template>
+
+<style scoped lang="scss">
+.social-links {
+    height: 24px;
+    padding: 0 12px;
+}
+</style>

+ 27 - 0
docs/.vitepress/vitepress/components/navbar/vp-theme-toggler.vue

@@ -0,0 +1,27 @@
+<script setup lang="ts">
+import { toggleDark } from '../../composables/dark'
+import CommonThemeToggler from '../common/vp-theme-toggler.vue'
+</script>
+
+<template>
+    <div class="theme-toggler-content">
+        <CommonThemeToggler @click="() => toggleDark()" />
+    </div>
+</template>
+
+<style scoped lang="scss">
+@use '../../styles/mixins' as *;
+.theme-toggler-content {
+    @include with-bg;
+    background-color: transparent;
+    display: none;
+    border-radius: 50%;
+    height: 24px;
+    padding: 0 12px;
+
+    @include respond-to('md') {
+        display: flex;
+        align-items: center;
+    }
+}
+</style>

+ 58 - 0
docs/.vitepress/vitepress/components/navbar/vp-translation.vue

@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import VPLink from '../common/vp-link.vue'
+import { useTranslation } from '../../composables/translation'
+
+const { switchLang, languageMap, langs, lang, helpTranslate } = useTranslation()
+</script>
+
+<template>
+    <div class="translation-container">
+        <ClientOnly>
+            <ElPopover :show-arrow="false" trigger="hover" popper-class="translation-popup">
+                <template #reference>
+                    <ElIcon :size="24">
+                        <i-ri-translate-2 />
+                    </ElIcon>
+                </template>
+                <div v-for="l in langs" :key="l" :class="{ language: true, selected: l === lang }" @click="switchLang(l)">
+                    {{ languageMap[l] }}
+                </div>
+                <div class="language">
+                    <VPLink :href="`/${lang}/guide/translation`">
+                        {{ helpTranslate }}
+                    </VPLink>
+                </div>
+            </ElPopover>
+        </ClientOnly>
+    </div>
+</template>
+
+<style lang="scss" scoped>
+@use '../../styles/mixins' as *;
+.translation-container {
+    display: none;
+    height: 24px;
+    padding: 0 12px;
+
+    @include respond-to('md') {
+        display: block;
+    }
+
+    @at-root .translation-popup.el-popper {
+        box-shadow: var(--el-box-shadow);
+
+        .language {
+            cursor: pointer;
+            padding: 0 16px;
+            line-height: 28px;
+            &.selected {
+                color: var(--brand-color);
+            }
+
+            .link-item {
+                font-weight: 500;
+            }
+        }
+    }
+}
+</style>

+ 68 - 0
docs/.vitepress/vitepress/components/sidebar/vp-sidebar-link.vue

@@ -0,0 +1,68 @@
+<script lang="ts" setup>
+import { useRoute } from 'vitepress'
+import { isActive } from '../../utils'
+
+import type { Link } from '../../types'
+
+defineProps<{
+    item: Link
+}>()
+
+defineEmits(['close'])
+
+const route = useRoute()
+</script>
+
+<template>
+    <a
+        :class="{
+            link: true,
+            active: isActive(route, item.link),
+            'flex items-center': item.promotion,
+        }"
+        :href="item.link"
+        @click="$emit('close')"
+    >
+        <p class="link-text">{{ item.text }}</p>
+        <VersionTag v-if="item.promotion" class="ml-2" :version="item.promotion" />
+    </a>
+</template>
+
+<style scoped lang="scss">
+.link:not(.flex) {
+    display: block;
+}
+
+.link {
+    padding: 10px 16px;
+    line-height: 1.5;
+    font-size: 0.9rem;
+    border-radius: 8px;
+
+    .link-text {
+        margin: 0;
+    }
+}
+
+.link:hover .link-text {
+    color: var(--brand-color);
+    transition: color 0.25s;
+}
+
+.link.active {
+    background-color: var(--link-active-bg-color);
+    .link-text {
+        font-weight: 600;
+        color: var(--brand-color);
+        transition: color 0.25s;
+    }
+}
+
+.link-text {
+    line-height: 20px;
+    font-size: 13px;
+    font-weight: 500;
+    color: var(--text-color-light);
+    transition: color 0.5s;
+}
+</style>

+ 23 - 0
docs/.vitepress/vitepress/components/sponsors/right-logo-small-list.vue

@@ -0,0 +1,23 @@
+<script setup lang="ts">
+import { rightLogoSmallSponsors } from '../../../config/sponsors'
+import { sendEvent } from '../../../config/analytics'
+import { isDark } from '../../composables/dark'
+const onItemClick = (item: any) => {
+    sendEvent('sp_click', item.name, 'right_logo_small')
+}
+</script>
+
+<template>
+    <div class="m-t-16px flex flex-wrap justify-between">
+        <template v-for="item in rightLogoSmallSponsors.concat([{} as any])" :key="item.name">
+            <div v-if="!item.url" :class="[isDark && '!bg-#262729 color-$text-color-placeholder', 'flex bg-#F9F9F9 rd-0px h-40px w-89px justify-center items-center']">
+                <div class="color-#ddd text-13px cursor-default">Your logo</div>
+            </div>
+            <a v-else :href="item.url" :title="`${item.name_cn || item.name} - ${item.slogan_cn || item.slogan}`" target="_blank" @click="onItemClick(item)">
+                <div :class="[isDark && '!bg-#262729', 'flex m-b-2px bg-#F9F9F9 rd-0px h-40px w-89px justify-center items-center']">
+                    <img :src="item.imgL" :alt="item.name" />
+                </div>
+            </a>
+        </template>
+    </div>
+</template>

+ 40 - 0
docs/.vitepress/vitepress/components/sponsors/right-richtext-list.vue

@@ -0,0 +1,40 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+import { rightRichTextSponsors } from '../../../config/sponsors'
+import { sendEvent } from '../../../config/analytics'
+import { useLang } from '../../composables/lang'
+import { isDark } from '../../composables/dark'
+const lang = useLang()
+const langZhCN = 'zh-CN'
+const isZhCn = computed(() => lang.value === langZhCN)
+const onItemClick = (item: any) => {
+    sendEvent('sp_click', item.name, 'right_richtext_list')
+}
+</script>
+
+<template>
+    <div class="m-t-16px">
+        <a
+            v-for="item in rightRichTextSponsors"
+            :key="item.name"
+            :href="item.url"
+            :title="`${item.name_cn || item.name} - ${item.slogan_cn || item.slogan}`"
+            target="_blank"
+            @click="onItemClick(item)"
+        >
+            <div :class="[isDark && '!bg-#262729', 'flex bg-#F9F9F9 pl-12px pr-12px pt-16px pb-16px rd-4px']">
+                <div class="w-32px m-r-8px h-56px">
+                    <img class="mt-2px rd-4px w-32px h-32px" :src="item.img" :alt="item.name" />
+                </div>
+                <div class="flex-1 h-56px">
+                    <div :class="['color-#303133 font-400 text-13px', isDark && '!color-#E5E9F3']">
+                        {{ isZhCn ? item.name_cn || item.name : item.name }}
+                    </div>
+                    <div :class="['m-t-2px font-400 text-12px color-#909399', isDark && '!color-#A3A6AD']">
+                        {{ isZhCn ? item.slogan_cn || item.slogan : item.slogan }}
+                    </div>
+                </div>
+            </div>
+        </a>
+    </div>
+</template>

+ 23 - 0
docs/.vitepress/vitepress/components/sponsors/sponsors-button.vue

@@ -0,0 +1,23 @@
+<template>
+    <div class="join">
+        <el-tooltip placement="top" :hide-after="1000" :offset="20">
+            <template #content>
+                {{ homeLang['21'] }}
+                <a href="mailto:element-plus@outlook.com" target="_blank"> &nbsp;element-plus@outlook.com </a>
+            </template>
+            <a href="mailto:element-plus@outlook.com" target="_blank">
+                <el-button style="overflow: hidden" :round="round">{{ homeLang['20'] }}</el-button>
+            </a>
+        </el-tooltip>
+    </div>
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue'
+import { useLang } from '../../composables/lang'
+import homeLocale from '../../../i18n/pages/home.json'
+const lang = useLang()
+const homeLang = computed(() => homeLocale[lang.value])
+
+defineProps<{ round?: boolean }>()
+</script>

+ 31 - 0
docs/.vitepress/vitepress/components/subnav/toggle-sidebar-btn.vue

@@ -0,0 +1,31 @@
+<script lang="ts" setup>
+import ToggleButton from '../icons/toggle-button.vue'
+</script>
+
+<template>
+    <div class="sidebar-button flex items-center">
+        <ElIcon :size="20" class="mr-2">
+            <ToggleButton />
+        </ElIcon>
+        <span class="leading-6">Menu</span>
+    </div>
+</template>
+
+<style>
+.sidebar-button {
+    cursor: pointer;
+    color: var(--text-color);
+}
+
+.sidebar-button .icon {
+    display: block;
+    width: 1.25rem;
+    height: 1.25rem;
+}
+
+/* @media screen and (max-width: 1044px) {
+  .sidebar-button {
+    display: block;
+  }
+} */
+</style>

+ 128 - 0
docs/.vitepress/vitepress/components/vp-app.vue

@@ -0,0 +1,128 @@
+<script setup lang="ts">
+import { onMounted } from 'vue'
+import { ElMessageBox } from 'element-plus'
+import nprogress from 'nprogress'
+import dayjs from 'dayjs'
+import { isClient, useStorage, useToggle } from '@vueuse/core'
+import { useSidebar } from '../composables/sidebar'
+import { useToggleWidgets } from '../composables/toggle-widgets'
+import { useLang } from '../composables/lang'
+import { breakpoints } from '../constant'
+import VPOverlay from './vp-overlay.vue'
+import VPNav from './vp-nav.vue'
+import VPSubNav from './vp-subnav.vue'
+import VPSidebar from './vp-sidebar.vue'
+import VPContent from './vp-content.vue'
+import VPSponsors from './vp-sponsors.vue'
+
+const USER_PREFER_GITHUB_PAGE = 'USER_PREFER_GITHUB_PAGE'
+const [isSidebarOpen, toggleSidebar] = useToggle(false)
+const { hasSidebar } = useSidebar()
+const lang = useLang()
+
+const mirrorUrl = 'element-plus.gitee.io'
+const isMirrorUrl = () => {
+    if (!isClient) return
+    return window.location.hostname === mirrorUrl
+}
+
+useToggleWidgets(isSidebarOpen, () => {
+    if (!isClient) return
+    if (window.outerWidth >= breakpoints.lg) {
+        toggleSidebar(false)
+    }
+})
+
+const userPrefer = useStorage<boolean | string>(USER_PREFER_GITHUB_PAGE, null)
+
+onMounted(async () => {
+    if (!isClient) return
+    window.addEventListener(
+        'click',
+        e => {
+            const link = (e.target as HTMLElement).closest('a')
+            if (!link) return
+
+            const { protocol, hostname, pathname, target } = link
+            const currentUrl = window.location
+            const extMatch = pathname.match(/\.\w+$/)
+            // only intercept inbound links
+            if (
+                !e.ctrlKey &&
+                !e.shiftKey &&
+                !e.altKey &&
+                !e.metaKey &&
+                target !== `_blank` &&
+                protocol === currentUrl.protocol &&
+                hostname === currentUrl.hostname &&
+                !(extMatch && extMatch[0] !== '.html')
+            ) {
+                e.preventDefault()
+                if (pathname !== currentUrl.pathname) {
+                    nprogress.start()
+                }
+            }
+        },
+        { capture: true }
+    )
+
+    if (lang.value === 'zh-CN') {
+        if (isMirrorUrl()) return
+
+        if (userPrefer.value) {
+            // no alert in the next 90 days
+            if (dayjs.unix(Number(userPrefer.value)).add(90, 'day').diff(dayjs(), 'day', true) > 0) return
+        }
+        try {
+            await ElMessageBox.confirm('建议大陆用户访问部署在国内的站点,是否跳转?', '提示', {
+                confirmButtonText: '跳转',
+                cancelButtonText: '取消',
+            })
+            const toLang = '/zh-CN/'
+            location.href = `https://element-plus.gitee.io${toLang}${location.pathname.slice(toLang.length)}`
+        } catch {
+            userPrefer.value = String(dayjs().unix())
+        }
+    }
+    // unregister sw
+    navigator?.serviceWorker?.getRegistrations().then(registrations => {
+        for (const registration of registrations) {
+            registration.unregister()
+        }
+    })
+})
+</script>
+
+<template>
+    <div class="App">
+        <VPOverlay class="overlay" :show="isSidebarOpen" @click="toggleSidebar(false)" />
+        <VPNav />
+        <VPSubNav v-if="hasSidebar" @open-menu="toggleSidebar(true)" />
+        <VPSidebar :open="isSidebarOpen" @close="toggleSidebar(false)">
+            <template #top>
+                <VPSponsors />
+            </template>
+            <template #bottom>
+                <slot name="sidebar-bottom"></slot>
+            </template>
+        </VPSidebar>
+        <VPContent :is-sidebar-open="isSidebarOpen">
+            <template #content-top>
+                <slot name="content-top"></slot>
+            </template>
+            <template #content-bottom>
+                <slot name="content-bottom"></slot>
+            </template>
+            <template #aside-top>
+                <slot name="aside-top"></slot>
+            </template>
+            <template #aside-mid>
+                <slot name="aside-mid"></slot>
+            </template>
+            <template #aside-bottom>
+                <slot name="aside-bottom"></slot>
+            </template>
+        </VPContent>
+        <Debug />
+    </div>
+</template>

+ 48 - 0
docs/.vitepress/vitepress/components/vp-content.vue

@@ -0,0 +1,48 @@
+<script setup lang="ts">
+import { computed, nextTick, onUpdated, ref, watch } from 'vue'
+import nprogress from 'nprogress'
+import { useData, useRoute } from 'vitepress'
+import { useSidebar } from '../composables/sidebar'
+import VPHeroContent from './vp-hero-content.vue'
+import VPDocContent from './vp-doc-content.vue'
+import VPNotFound from './vp-not-found.vue'
+import VPFooter from './globals/vp-footer.vue'
+
+const { frontmatter } = useData()
+const route = useRoute()
+const isNotFound = computed(() => route.component === VPNotFound)
+const isHeroPost = computed(() => frontmatter.value.page === true)
+const { hasSidebar } = useSidebar()
+
+const props = defineProps<{ isSidebarOpen: boolean }>()
+
+const shouldUpdateProgress = ref(true)
+
+watch(
+    () => props.isSidebarOpen,
+    val => {
+        // delay the flag update since watch is called before onUpdated
+        nextTick(() => {
+            shouldUpdateProgress.value = !val
+        })
+    }
+)
+
+onUpdated(() => {
+    if (shouldUpdateProgress.value) {
+        nprogress.done()
+    }
+})
+</script>
+
+<template>
+    <main :class="{ 'page-content': true, 'has-sidebar': hasSidebar }">
+        <VPNotFound v-if="isNotFound" />
+        <VPHeroContent v-else-if="isHeroPost" />
+        <VPDocContent v-else>
+            <template #content-top><slot name="content-top"></slot></template>
+            <template #content-bottom><slot name="content-bottom"></slot></template>
+        </VPDocContent>
+        <VPFooter v-if="!isHeroPost" />
+    </main>
+</template>

+ 180 - 0
docs/.vitepress/vitepress/components/vp-demo.vue

@@ -0,0 +1,180 @@
+<script setup lang="ts">
+import { computed, getCurrentInstance, toRef } from 'vue'
+import { isClient, useClipboard, useToggle } from '@vueuse/core'
+import { CaretTop } from '@element-plus/icons-vue'
+import { useLang } from '../composables/lang'
+import { useSourceCode } from '../composables/source-code'
+import { usePlayground } from '../composables/use-playground'
+
+import demoBlockLocale from '../../i18n/component/demo-block.json'
+
+import Example from './demo/vp-example.vue'
+import SourceCode from './demo/vp-source-code.vue'
+
+const props = defineProps<{
+    demos: object
+    source: string
+    path: string
+    rawSource: string
+    description?: string
+}>()
+
+const vm = getCurrentInstance()!
+
+const { copy, isSupported } = useClipboard({
+    source: decodeURIComponent(props.rawSource),
+    read: false,
+})
+
+const [sourceVisible, toggleSourceVisible] = useToggle()
+const lang = useLang()
+const demoSourceUrl = useSourceCode(toRef(props, 'path'))
+
+const formatPathDemos = computed(() => {
+    const demos = {}
+
+    Object.keys(props.demos).forEach(key => {
+        demos[key.replace('../../examples/', '').replace('.vue', '')] = props.demos[key].default
+    })
+
+    return demos
+})
+
+const locale = computed(() => demoBlockLocale[lang.value])
+const decodedDescription = computed(() => decodeURIComponent(props.description!))
+
+const onPlaygroundClick = () => {
+    const { link } = usePlayground(props.rawSource)
+    if (!isClient) return
+    window.open(link)
+}
+
+const copyCode = async () => {
+    const { $message } = vm.appContext.config.globalProperties
+    if (!isSupported) {
+        $message.error(locale.value['copy-error'])
+    }
+    try {
+        await copy()
+        $message.success(locale.value['copy-success'])
+    } catch (e: any) {
+        $message.error(e.message)
+    }
+}
+</script>
+
+<template>
+    <ClientOnly>
+        <!-- danger here DO NOT USE INLINE SCRIPT TAG -->
+        <p text="sm" v-html="decodedDescription"></p>
+
+        <div class="example">
+            <Example :file="path" :demo="formatPathDemos[path]" />
+
+            <ElDivider class="m-0" />
+
+            <div class="op-btns">
+                <ElTooltip :content="locale['edit-in-editor']" :show-arrow="false">
+                    <ElIcon :size="16" class="op-btn">
+                        <i-ri-flask-line @click="onPlaygroundClick" />
+                    </ElIcon>
+                </ElTooltip>
+                <ElTooltip :content="locale['edit-on-github']" :show-arrow="false">
+                    <ElIcon :size="16" class="op-btn github" style="color: var(--text-color-light)">
+                        <a :href="demoSourceUrl" rel="noreferrer noopener" target="_blank">
+                            <i-ri-github-line />
+                        </a>
+                    </ElIcon>
+                </ElTooltip>
+                <ElTooltip :content="locale['copy-code']" :show-arrow="false">
+                    <ElIcon :size="16" class="op-btn" @click="copyCode">
+                        <i-ri-file-copy-line />
+                    </ElIcon>
+                </ElTooltip>
+                <ElTooltip :content="locale['view-source']" :show-arrow="false">
+                    <ElIcon :size="16" class="op-btn" @click="toggleSourceVisible()">
+                        <i-ri-code-line />
+                    </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>{{ locale['hide-source'] }}</span>
+                </div>
+            </Transition>
+        </div>
+    </ClientOnly>
+</template>
+
+<style scoped lang="scss">
+.example {
+    border: 1px solid var(--border-color);
+    border-radius: var(--el-border-radius-base);
+
+    .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>

+ 19 - 0
docs/.vitepress/vitepress/components/vp-doc-content.vue

@@ -0,0 +1,19 @@
+<script lang="ts" setup>
+import { useData } from 'vitepress'
+import VPPageFooter from './doc-content/vp-page-footer.vue'
+import VPPageNav from './doc-content/vp-page-nav.vue'
+import VPTableOfContent from './doc-content/vp-table-of-content.vue'
+
+const { page } = useData()
+</script>
+
+<template>
+    <div class="doc-content-wrapper">
+        <div class="doc-content-container">
+            <Content class="doc-content" />
+            <VPPageFooter />
+            <VPPageNav />
+        </div>
+        <VPTableOfContent v-if="page.headers" />
+    </div>
+</template>

+ 16 - 0
docs/.vitepress/vitepress/components/vp-hero-content.vue

@@ -0,0 +1,16 @@
+<template>
+    <div class="hero-content">
+        <Content />
+    </div>
+    <el-divider style="margin-bottom: 0" />
+    <div class="text-center py-6 text-xs">
+        <p class="mb-1">
+            Released under the
+            <a href="https://opensource.org/licenses/MIT" target="_blank" rel="noopener noreferer">MIT License</a>.
+        </p>
+        <p class="mt-1">
+            Made with ❤️ by
+            <a href="https://github.com/element-plus" target="_blank" rel="noopener noreferer">Element Plus</a>
+        </p>
+    </div>
+</template>

+ 61 - 0
docs/.vitepress/vitepress/components/vp-nav-full.vue

@@ -0,0 +1,61 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+import { useLockScreen } from '../composables/lock-screen'
+import VPFullScreenMenu from './full-screen/vp-menu.vue'
+import VPFullScreenTranslation from './full-screen/vp-translation.vue'
+import VPFullScreenThemeToggler from './full-screen/vp-theme-toggler.vue'
+
+defineProps<{
+    fullScreen: boolean
+}>()
+
+const { lock, cleanup } = useLockScreen()
+const fullscreen = ref()
+</script>
+
+<template>
+    <Transition name="el-fade-in" @enter="lock" @after-leave="cleanup">
+        <div v-if="fullScreen" ref="fullscreen">
+            <div class="full-screen-container">
+                <VPFullScreenMenu @close="$emit('close')" />
+                <VPFullScreenTranslation @close="$emit('close')" />
+                <VPFullScreenThemeToggler />
+            </div>
+        </div>
+    </Transition>
+</template>
+
+<style lang="scss" scoped>
+.full-screen {
+    position: fixed;
+    top: var(--nav-height);
+    right: 0;
+    bottom: 0;
+    left: 0;
+    padding: 0 32px;
+    width: 100%;
+    background-color: var(--bg-color);
+    transition: background-color 0.5s;
+    overflow-y: auto;
+
+    &.el-fade-in-enter-active,
+    &.el-fade-in-leave-active {
+        .full-screen-container {
+            transition: transform var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);
+        }
+    }
+
+    &.el-fade-in-enter-from,
+    &.el-fade-in-leave-to {
+        .full-screen-container {
+            transform: translateY(-8px);
+        }
+    }
+
+    .full-screen-container {
+        margin: 0 auto;
+        padding: 24px 0 96px;
+        max-width: 18rem;
+    }
+}
+</style>

+ 28 - 0
docs/.vitepress/vitepress/components/vp-nav.vue

@@ -0,0 +1,28 @@
+<script setup lang="ts">
+import { isClient } from '@vueuse/core'
+
+import { useSidebar } from '../composables/sidebar'
+import { useFullScreen } from '../composables/fullscreen'
+import { useToggleWidgets } from '../composables/toggle-widgets'
+import { breakpoints } from '../constant'
+import VpNavbar from './vp-navbar.vue'
+import VpNavFull from './vp-nav-full.vue'
+
+const { hasSidebar } = useSidebar()
+const { toggleFullScreen, isFullScreen } = useFullScreen()
+const close = () => toggleFullScreen(false)
+
+useToggleWidgets(isFullScreen, () => {
+    if (!isClient) return
+    if (window.outerWidth >= breakpoints.md) {
+        close()
+    }
+})
+</script>
+
+<template>
+    <header :class="{ navbar: true, 'has-sidebar': hasSidebar }">
+        <VpNavbar :full-screen="isFullScreen" @toggle="toggleFullScreen" />
+        <VpNavFull :full-screen="isFullScreen" class="full-screen" @close="close" />
+    </header>
+</template>

+ 64 - 0
docs/.vitepress/vitepress/components/vp-navbar.vue

@@ -0,0 +1,64 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+import { inBrowser, useData } from 'vitepress'
+
+import VPNavbarSearch from './navbar/vp-search.vue'
+import VPNavbarMenu from './navbar/vp-menu.vue'
+import VPNavbarThemeToggler from './navbar/vp-theme-toggler.vue'
+import VPNavbarTranslation from './navbar/vp-translation.vue'
+import VPNavbarSocialLinks from './navbar/vp-social-links.vue'
+import VPNavbarHamburger from './navbar/vp-hamburger.vue'
+
+defineProps<{
+    fullScreen: boolean
+}>()
+
+defineEmits(['toggle'])
+
+const { theme, page } = useData()
+
+const currentLink = computed(() => {
+    if (!inBrowser) {
+        return `/${page.value?.frontmatter?.lang || ''}/`
+    }
+    const existLangIndex = theme.value.langs.findIndex(lang => window?.location?.pathname.startsWith(`/${lang}`))
+
+    return existLangIndex === -1 ? '/' : `/${theme.value.langs[existLangIndex]}/`
+})
+</script>
+
+<template>
+    <div class="navbar-wrapper">
+        <div class="header-container">
+            <div class="logo-container">
+                <a :href="currentLink">
+                    <img class="logo" src="/images/element-plus-logo.svg" alt="Element Plus Logo" />
+                </a>
+            </div>
+            <div class="content">
+                <VPNavbarSearch class="search" :options="theme.agolia" multilang />
+                <VPNavbarMenu class="menu" />
+                <VPNavbarThemeToggler class="theme-toggler" />
+                <VPNavbarTranslation class="translation" />
+                <VPNavbarSocialLinks class="social-links" />
+                <VPNavbarHamburger :active="fullScreen" class="hamburger" @click="$emit('toggle')" />
+            </div>
+        </div>
+    </div>
+</template>
+
+<style scoped lang="scss">
+.logo-container {
+    display: flex;
+    align-items: center;
+    height: var(--header-height);
+    > a {
+        height: 28px;
+        width: 128px;
+    }
+    .logo {
+        position: relative;
+        height: 100%;
+    }
+}
+</style>

+ 30 - 0
docs/.vitepress/vitepress/components/vp-not-found.vue

@@ -0,0 +1,30 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+import { isClient } from '@vueuse/core'
+import { useLang } from '../composables/lang'
+import localeData from '../../i18n/pages/not-found.json'
+
+const lang = useLang()
+
+const locale = computed(() => localeData[lang.value])
+
+const goHome = () => {
+    if (!isClient) return
+    window.location.href = `/${lang.value}/`
+}
+</script>
+
+<template>
+    <el-result icon="error" :title="locale.title" :sub-title="locale.desc">
+        <template #extra>
+            <el-button @click="goHome">{{ locale['button-title'] }}</el-button>
+        </template>
+    </el-result>
+</template>
+
+<style scoped>
+.el-result {
+    height: 100vh;
+    width: 100vw;
+}
+</style>

+ 11 - 0
docs/.vitepress/vitepress/components/vp-overlay.vue

@@ -0,0 +1,11 @@
+<script setup lang="ts">
+defineProps<{
+    show: boolean
+}>()
+</script>
+
+<template>
+    <Transition name="el-fade-in">
+        <div v-if="show"></div>
+    </Transition>
+</template>

+ 58 - 0
docs/.vitepress/vitepress/components/vp-reload-prompt.vue

@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import { computed, watch } from 'vue'
+import { useStorage } from '@vueuse/core'
+import { useRegisterSW } from 'virtual:pwa-register/vue'
+import { useLang } from '../composables/lang'
+import pwaLocale from '../../i18n/component/pwa.json'
+
+const lang = useLang()
+const locale = computed(() => pwaLocale[lang.value])
+const { needRefresh, updateServiceWorker } = useRegisterSW()
+const alwaysRefresh = useStorage('PWA_Always_Refresh', false)
+
+watch(needRefresh, value => {
+    value && alwaysRefresh.value && updateServiceWorker()
+})
+</script>
+
+<template>
+    <transition name="pwa-popup">
+        <el-card v-if="!alwaysRefresh && needRefresh" class="pwa-card" role="alert">
+            <p class="pwa-card-text">{{ locale.message }}</p>
+            <el-button type="primary" plain @click="updateServiceWorker()">
+                {{ locale.refresh }}
+            </el-button>
+            <el-button plain @click="alwaysRefresh = true">
+                {{ locale['always-refresh'] }}
+            </el-button>
+            <el-button plain @click="needRefresh = false">
+                {{ locale.close }}
+            </el-button>
+        </el-card>
+    </transition>
+</template>
+
+<style scoped>
+.pwa-card {
+    position: fixed;
+    right: 1em;
+    bottom: 1em;
+    z-index: 3000;
+    text-align: center;
+}
+
+.pwa-card .pwa-card-text {
+    margin: 0 0 1em;
+}
+
+.pwa-popup-enter-active,
+.pwa-popup-leave-active {
+    transition: var(--el-transition-md-fade);
+}
+
+.pwa-popup-enter,
+.pwa-popup-leave-to {
+    opacity: 0;
+    transform: translate(0, 50%) scale(0.5);
+}
+</style>

+ 28 - 0
docs/.vitepress/vitepress/components/vp-sidebar.vue

@@ -0,0 +1,28 @@
+<script lang="ts" setup>
+import { useSidebar } from '../composables/sidebar'
+
+import VPSidebarLink from './sidebar/vp-sidebar-link.vue'
+
+defineProps<{ open: boolean }>()
+defineEmits(['close'])
+
+// const isHome = useIsHome()
+const { sidebars, hasSidebar } = useSidebar()
+</script>
+
+<template>
+    <el-scrollbar v-if="hasSidebar" :class="{ sidebar: true, open }">
+        <aside>
+            <slot name="top"></slot>
+            <div class="sidebar-groups">
+                <section v-for="(item, key) of sidebars" :key="key" class="sidebar-group">
+                    <p class="sidebar-group__title">
+                        {{ item.text }}
+                    </p>
+                    <VPSidebarLink v-for="(child, childKey) in item.children" :key="childKey" :item="child" @close="$emit('close')" />
+                </section>
+            </div>
+            <slot name="bottom"></slot>
+        </aside>
+    </el-scrollbar>
+</template>

+ 66 - 0
docs/.vitepress/vitepress/components/vp-sponsor-large.vue

@@ -0,0 +1,66 @@
+<script setup lang="ts">
+import { leftCustomImgSponsors } from '../../config/sponsors'
+import { sendEvent } from '../../config/analytics'
+
+defineProps({
+    itemClass: String,
+    itemStyle: [String, Object, Array],
+})
+
+const onItemClick = (item: any) => {
+    sendEvent('sp_click', item.name, 'left_custom_img')
+}
+</script>
+
+<template>
+    <div>
+        <a
+            v-for="item in leftCustomImgSponsors"
+            :key="item.name"
+            :href="item.url"
+            :title="`${item.name_cn || item.name} - ${item.slogan_cn || item.slogan}`"
+            :class="['sponsor-item inline-flex', itemClass]"
+            :style="itemStyle"
+            target="_blank"
+            @click="onItemClick(item)"
+        >
+            <img :src="item.banner_img" :alt="item.name" />
+        </a>
+    </div>
+</template>
+
+<style scoped lang="scss">
+@use '../styles/mixins.scss' as *;
+
+.sponsor-item {
+    margin-bottom: 8px;
+    height: 60px;
+    width: 196px;
+
+    @include respond-to('max') {
+        width: 236px;
+        height: 72px;
+    }
+
+    @media (max-width: 767px) {
+        width: 236px;
+        height: 72px;
+    }
+
+    img {
+        border-radius: 8px;
+        overflow: hidden;
+        height: 100%;
+        width: 100%;
+    }
+}
+
+@media (max-width: 768px) {
+    .sponsor-item {
+        img {
+            border-radius: 4px;
+            min-height: 45px;
+        }
+    }
+}
+</style>

+ 56 - 0
docs/.vitepress/vitepress/components/vp-sponsor-small.vue

@@ -0,0 +1,56 @@
+<script setup lang="ts">
+import { isDark } from '../composables/dark'
+import { leftLogoSponsors } from '../../config/sponsors'
+import { sendEvent } from '../../config/analytics'
+const onItemClick = (item: any) => {
+    sendEvent('sp_click', item.name, 'left_small_img')
+}
+</script>
+
+<template>
+    <div>
+        <a
+            v-for="item in leftLogoSponsors"
+            :key="item.name"
+            :class="['sponsor-item inline-flex items-center', item.isDark && isDark ? 'filter invert' : '']"
+            :href="item.url"
+            :title="`${item.name_cn || item.name} - ${item.slogan_cn || item.slogan}`"
+            target="_blank"
+            @click="onItemClick(item)"
+        >
+            <img :src="item.img" :alt="item.name" />
+        </a>
+    </div>
+</template>
+
+<style scoped lang="scss">
+@use '../styles/mixins' as *;
+div {
+    display: flex;
+    align-items: center;
+    .sponsor-item {
+        margin-right: 4px;
+        height: 36px;
+        width: 36px;
+
+        @include respond-to('max') {
+            height: 44px;
+            width: 44px;
+        }
+
+        @media (max-width: 767px) {
+            width: 44px;
+            height: 44px;
+        }
+
+        img {
+            height: 100%;
+            width: 100%;
+        }
+    }
+
+    @include respond-to('xs') {
+        width: 196px;
+    }
+}
+</style>

+ 30 - 0
docs/.vitepress/vitepress/components/vp-sponsors.vue

@@ -0,0 +1,30 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+import sponsorLocale from '../../i18n/component/sponsor.json'
+import { useLang } from '../composables/lang'
+import VPSponsorSmall from './vp-sponsor-small.vue'
+import VPSponsorLarge from './vp-sponsor-large.vue'
+
+const lang = useLang()
+const sponsor = computed(() => sponsorLocale[lang.value])
+</script>
+
+<template>
+    <div class="page-content">
+        <p class="title">{{ sponsor.sponsoredBy }}</p>
+        <VPSponsorLarge />
+        <VPSponsorSmall />
+    </div>
+</template>
+
+<style lang="scss" scoped>
+.page-content {
+    padding-bottom: 10px;
+    padding-top: 0;
+    .title {
+        color: var(--text-color-secondary);
+        font-weight: 300;
+        font-size: 14px;
+    }
+}
+</style>

+ 18 - 0
docs/.vitepress/vitepress/components/vp-subnav.vue

@@ -0,0 +1,18 @@
+<script setup lang="ts">
+import { useSidebar } from '../composables/sidebar'
+import { useBackTop } from '../composables/back-top'
+import ToggleSidebarBtn from './subnav/toggle-sidebar-btn.vue'
+defineEmits(['open-menu'])
+
+const { hasSidebar } = useSidebar()
+const { shouldShow, scrollToTop } = useBackTop()
+</script>
+
+<template>
+    <div class="sub-nav py-3 flex items-center">
+        <ToggleSidebarBtn v-if="hasSidebar" @click="$emit('open-menu')" />
+        <Transition name="shifting">
+            <ElLink :class="{ 'go-back-top': true, show: shouldShow }" :underline="false" class="height-5" size="small" @click.prevent.stop="scrollToTop">{{ 'Back to top' }}</ElLink>
+        </Transition>
+    </div>
+</template>

+ 97 - 0
docs/.vitepress/vitepress/composables/active-bar.ts

@@ -0,0 +1,97 @@
+import { onMounted, onUnmounted, onUpdated } from 'vue'
+import { isClient } from '@vueuse/core'
+import { throttleAndDebounce } from '../utils'
+
+import type { Ref } from 'vue'
+
+export function useActiveSidebarLinks(container: Ref<HTMLElement>, marker: Ref<HTMLElement>) {
+    if (!isClient) return
+
+    const onScroll = throttleAndDebounce(setActiveLink, 150)
+    function setActiveLink() {
+        const sidebarLinks = getSidebarLinks()
+        const anchors = getAnchors(sidebarLinks)
+        // Cancel the processing of the anchor point being forced to be the last one in the storefront
+        // if (
+        //   anchors.length &&
+        //   scrollDom &&
+        //   scrollDom.scrollTop + scrollDom.clientHeight === scrollDom.scrollHeight
+        // ) {
+        //   activateLink(anchors[anchors.length - 1].hash)
+        //   return
+        // }
+        for (let i = 0; i < anchors.length; i++) {
+            const anchor = anchors[i]
+            const nextAnchor = anchors[i + 1]
+            const [isActive, hash] = isAnchorActive(i, anchor, nextAnchor)
+            if (isActive) {
+                history.replaceState(null, document.title, hash ? (hash as string) : ' ')
+                activateLink(hash as string)
+                return
+            }
+        }
+    }
+
+    let prevActiveLink: HTMLAnchorElement | null = null
+
+    function activateLink(hash: string) {
+        deactiveLink(prevActiveLink)
+
+        const activeLink = (prevActiveLink = hash == null ? null : (container.value.querySelector(`.toc-item a[href="${decodeURIComponent(hash)}"]`) as HTMLAnchorElement))
+        if (activeLink) {
+            activeLink.classList.add('active')
+            marker.value.style.opacity = '1'
+            marker.value.style.top = `${activeLink.offsetTop}px`
+        } else {
+            marker.value.style.opacity = '0'
+            marker.value.style.top = '33px'
+        }
+    }
+
+    function deactiveLink(link: HTMLElement | null) {
+        link && link.classList.remove('active')
+    }
+
+    onMounted(() => {
+        window.requestAnimationFrame(setActiveLink)
+        window.addEventListener('scroll', onScroll)
+    })
+
+    onUpdated(() => {
+        activateLink(location.hash)
+    })
+
+    onUnmounted(() => {
+        window.removeEventListener('scroll', onScroll)
+    })
+}
+function getSidebarLinks() {
+    return Array.from(document.querySelectorAll('.toc-content .toc-link')) as HTMLAnchorElement[]
+}
+function getAnchors(sidebarLinks: HTMLAnchorElement[]) {
+    return (Array.from(document.querySelectorAll('.doc-content .header-anchor')) as HTMLAnchorElement[]).filter(anchor => sidebarLinks.some(sidebarLink => sidebarLink.hash === anchor.hash))
+}
+function getPageOffset() {
+    return (document.querySelector('.navbar') as HTMLElement).offsetHeight
+}
+function getAnchorTop(anchor: HTMLAnchorElement) {
+    const pageOffset = getPageOffset()
+    try {
+        return anchor.parentElement!.offsetTop - pageOffset - 15
+    } catch {
+        return 0
+    }
+}
+function isAnchorActive(index: number, anchor: HTMLAnchorElement, nextAnchor: HTMLAnchorElement) {
+    const scrollTop = window.scrollY
+    if (index === 0 && scrollTop === 0) {
+        return [true, null]
+    }
+    if (scrollTop < getAnchorTop(anchor)) {
+        return [false, null]
+    }
+    if (!nextAnchor || scrollTop < getAnchorTop(nextAnchor)) {
+        return [true, decodeURIComponent(anchor.hash)]
+    }
+    return [false, null]
+}

+ 65 - 0
docs/.vitepress/vitepress/composables/back-top.ts

@@ -0,0 +1,65 @@
+import { onBeforeUnmount, onMounted, ref } from 'vue'
+import { isClient } from '@vueuse/core'
+import { throttleAndDebounce } from '../utils'
+
+const threshold = 960
+
+const cubic = (value: number): number => value ** 3
+const easeInOutCubic = (value: number): number => (value < 0.5 ? cubic(value * 2) / 2 : 1 - cubic((1 - value) * 2) / 2)
+
+export const useBackTop = (offset = 200) => {
+    const shouldShow = ref(false)
+    const throttleResize = throttleAndDebounce(onResize, 300)
+    const throttleScroll = throttleAndDebounce(onScroll, 160)
+
+    onMounted(() => {
+        if (!isClient) return
+        onResize()
+        onScroll()
+        window.addEventListener('resize', throttleResize)
+    })
+
+    onBeforeUnmount(() => {
+        if (!isClient) return
+        window.removeEventListener('resize', throttleResize)
+        window.removeEventListener('scroll', throttleScroll)
+    })
+
+    const scrollToTop = () => {
+        const beginTime = Date.now()
+        const beginValue = document.documentElement.scrollTop
+        const rAF = window.requestAnimationFrame
+        const frameFunc = () => {
+            const progress = (Date.now() - beginTime) / 500
+            if (progress < 1) {
+                document.documentElement.scrollTop = beginValue * (1 - easeInOutCubic(progress))
+                rAF(frameFunc)
+            } else {
+                document.documentElement.scrollTop = 0
+            }
+        }
+        rAF(frameFunc)
+    }
+
+    function onResize() {
+        if (!isClient) return
+
+        const { clientWidth } = document.body
+
+        if (clientWidth < threshold) {
+            window.addEventListener('scroll', throttleScroll)
+        } else {
+            window.removeEventListener('scroll', throttleScroll)
+        }
+    }
+
+    function onScroll() {
+        if (!isClient) return
+        shouldShow.value = document.documentElement.scrollTop > offset
+    }
+
+    return {
+        shouldShow,
+        scrollToTop,
+    }
+}

+ 7 - 0
docs/.vitepress/vitepress/composables/dark.ts

@@ -0,0 +1,7 @@
+import { useDark, useToggle } from '@vueuse/core'
+
+export const isDark = useDark({
+    storageKey: 'el-theme-appearance',
+})
+
+export const toggleDark = useToggle(isDark)

+ 0 - 0
docs/.vitepress/vitepress/composables/edit-link.ts


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است