1.1 模仿Vue官网首页 创建首页和文档页

[toc]


创建首页和文档页

创建src/view目录以及文件Docs.vue Home.vue

开始创建官网

  • Home.vue
    • TopNav: 左边是 logo,右边是 menu
    • Banner: 文件介绍 + 开始按钮
  • Docs.vue
    • TopNav: 复用
    • Content: 左边是 aside,右边是 main
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
src
 ┣ assets
 ┃ ┗ logo.png
 ┣ components
 ┃ ┗ TopNav.vue
 ┣ styles
 ┃ ┗ index.scss
 ┣ views
 ┃ ┣ Docs.vue
 ┃ ┗ Home.vue
 ┣ App.vue
 ┣ env.d.ts
 ┗ main.ts

确保所有组件都有<script>标签,否则在VSCode中可能不能识别引入路径


封装 Topnav


完善样式

完善 Home.vue 样式

H1 + H2 + 两个链接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<template>
  <div class="banner">
    <h1>Vueel 3 demo</h1>
    <h2>一个基于 Vue 3 的UI组件库</h2>
    <p class="actions">
      <a href="https://www.github.com/xmasuhai">GitHub</a> |
      <router-link to="/docs">开始</router-link>
    </p>
  </div>
</template>

<script lang="ts">
import TopNav from '@/components/TopNav.vue';

export default {
  name: 'Home',
  components: {
    TopNav
  }
};
</script>

<style lang="scss" scoped>
@use "sass:math";

$one-line-height: 28px;
.banner {
  padding: 100px 0;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  background: darkolivegreen;

  > .actions {
    padding: 8px 0;

    a {
      margin: 0 8px;
      background: #fff;
      display: inline-block;
      height: $one-line-height;
      line-height: $one-line-height;
      border-radius: math.div($one-line-height, 2);
      padding: 0 8px;
    }
  }
}
</style>

完善 Docs.vue 样式

边栏 + 主内容

主做手机,兼容PC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<template>
  <div class="layout">
    <div class="content">
      <aside></aside>
      <main></main>
    </div>
  </div>
</template>

<script setup lang="ts">
import {inject, Ref} from 'vue';

let asideVisible = inject<Ref<boolean>>('asideVisible');
</script>

<script lang="ts">
import TopNav from '@/components/TopNav.vue';

export default {
  name: 'Docs',
  components: {TopNav}
};
</script>

<style lang="scss" scoped>
.layout {
  display: flex;
  flex-direction: column;
  height: 100vh;

  > .content {
    display: flex;
    flex-grow: 1;
    padding-top: 60px;
    padding-left: 156px;
    @media (max-width: 500px) {
      padding-left: 0;
    }

    > aside {
      flex-shrink: 0;
      position: fixed;
      top: 0;
      left: 0;
      background: cornflowerblue;
      width: 150px;
      padding: 70px 16px 16px;

    }

    > main {
      flex-grow: 1;
      padding: 16px;
      background: lightgreen;
      overflow: auto;
    }
  }

}

</style>


点击切换 aside 边栏

点击一次显示,再点击一次隐藏,使用 provide 和 inject 实现切换功能

  • menuVisible重命名为asideVisible
  • 使用一个变量asideVisible,表示边栏是否被显示
  • 变量asideVisible不可放在Topnav.vue组件中,因为Docs.vue不能访问变量
  • 变量asideVisible不可放在Docs.vue组件中,因为Docs.vue隐藏时不能访问变量
  • 变量asideVisible必须放在App.vue组件中
    • 并且通过provide/inject标记下变量asideVisible是可以被子组件访问的

点击切换 aside 边栏


App.vue中提供控制变量

使用 单文件组件<script setup>

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<script setup lang="ts">
import {provide, ref} from 'vue';

const asideVisible = ref(false);
provide('xxx', asideVisible);
</script>

<script lang="ts">
export default {
  name: 'App'
};
</script>

<template>
  <router-view></router-view>
</template>

  • <script setup>是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖
  • 里面的代码会被编译成组件 setup() 函数的内容
  • ref 值在模板中使用的时候会自动解包处值 value
  • ref 值在其他处中使用的时候需要手动解包refValueXXX.value

在子孙组件中插入使用

TopNav.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<template>
  <div class="top-nav">
    <div class="logo">LOGO</div>
    <ul class="menu">
      <li>菜单1</li>
      <li>菜单2</li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import {inject, Ref} from 'vue';

const asideVisible = inject<Ref<boolean>>('xxx');
console.log('topNav 获取的 asideVisible 为:', asideVisible?.value);
</script>

<script lang="ts">
export default {
  name: 'TopNav'
};
</script>

<style lang="scss" scoped>
.top-nav {
  position: relative;
  z-index: 10;
  background: dodgerblue;
  display: flex;
  padding: 16px;

  > .logo {
    max-width: 6em;
    margin-right: auto;
  }

  > .menu {
    display: flex;
    white-space: nowrap;
    flex-wrap: nowrap;

    > li {
      margin: 0 1em;
    }
  }
}
</style>

  • 注意:需要提供接收的类型const asideVisible = inject<Ref<boolean>>('xxx');
    • Ref<boolean> 内部类型也需要

Docs.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<template>
  <top-nav></top-nav>
  <div class="content">
    <aside>
      <h2>组件列表</h2>
      <ol>
        <li>
          <router-link to="/doc/swich">Switch 组件</router-link>
        </li>
        <li>
          <router-link to="/doc/button">Button 组件</router-link>
        </li>
        <li>
          <router-link to="/doc/dialog">Dialog 组件</router-link>
        </li>
        <li>
          <router-link to="/doc/tabs">Tabs 组件</router-link>
        </li>
      </ol>
    </aside>
    <main>内容</main>
  </div>
</template>

<script setup lang="ts">
import {inject, Ref} from 'vue';

const asideVisible = inject<Ref<boolean>>('xxx');
console.log('docs 获取的 asideVisible 为:', asideVisible?.value);
</script>

<script lang="ts">
import TopNav from '@/components/TopNav.vue';

export default {
  name: 'Docs',
  components: {TopNav}
};
</script>

<style lang="scss" scoped>
aside {
  background: cornflowerblue;
  width: 150px;
  padding: 70px 16px 16px;
  position: fixed;
  top: 0;
  left: 0;

  > h2 {
    margin-bottom: 4px;
  }

  > ol {
    > li {
      padding: 4px 0;
    }
  }
}
</style>

  • 分别在控制台打印出
    • console.log('topNav 获取的 asideVisible 为:', asideVisible?.value);
    • console.log('docs 获取的 asideVisible 为:', asideVisible?.value);
  • 查看在子组件中是否获取到 asideVisible
  • 注意:获取的是asideVisible?.value,解包RefImpl后的值

添加点击切换事件

TopNav.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<template>
  <div class="top-nav">
    <div class="logo" @click="toggleMenu">LOGO</div>
    <ul class="menu">
      <li>菜单1</li>
      <li>菜单2</li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import {inject, Ref} from 'vue';

const asideVisible = inject<Ref<boolean>>('xxx');
const toggleMenu = () => {
  asideVisible!.value = !asideVisible?.value;
};
</script>
...

Docs.vue

  • 添加条件渲染<aside v-if="asideVisible">...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
  <top-nav></top-nav>
  <div class="content">
    <aside v-if="asideVisible">
      <h2>组件列表</h2>
      <ol>
        <li>
          <router-link to="/doc/swich">Switch 组件</router-link>
        </li>
        <li>
          <router-link to="/doc/button">Button 组件</router-link>
        </li>
        <li>
          <router-link to="/doc/dialog">Dialog 组件</router-link>
        </li>
        <li>
          <router-link to="/doc/tabs">Tabs 组件</router-link>
        </li>
      </ol>
    </aside>
    <main>内容</main>
  </div>
</template>

<script setup lang="ts">
import {inject, Ref} from 'vue';

const asideVisible = inject<Ref<boolean>>('xxx');
</script>

<script lang="ts">
import TopNav from '@/components/TopNav.vue';

export default {
  name: 'Docs',
  components: {TopNav}
};
</script>
...

小结

  • 父组件提供变量provide('xxx', asideVisible);
  • 子孙组件获取变量const asideVisible = inject<Ref<boolean>>('xxx');
  • 切换逻辑
    • 一个子组件中添加切换事件
      • <div class="logo" @click="toggleMenu">LOGO</div>
      • const toggleMenu = () => {asideVisible!.value = !asideVisible?.value;};
    • 另一个子组件中按变量进行 条件渲染
      • <aside v-if="asideVisible">

改造TopNav.vue 添加切换按钮

手机页面上的切换按钮 汉堡按钮

  • 当页面窄到 500px 时,隐藏侧边栏,显示菜单按钮
  • 使用 @media (max-width: 500px) {...}

初始时判断页面宽度


路由间切换

点击组件,点击组件显示相应的文档

使用嵌套路由main.ts

每次切换路由时,都将组件列表菜单关闭(PC端不需要关闭)

  • main.ts中使用路由钩子router.afterEach(() => {})
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import {createApp} from 'vue';
import App from '@/App.vue';
import '@/styles/index.scss';
import {createWebHashHistory, createRouter} from 'vue-router';
import Home from '@/views/Home.vue';
import Docs from '@/views/Docs.vue';
import SwitchDemo from '@/components/SwitchDemo.vue';

const history = createWebHashHistory();
const router = createRouter({
  history,
  routes: [
    {path: '/', component: Home},
    {
      path: '/docs', component: Docs,
      children: [
        {path: 'switch', component: SwitchDemo}
      ]
    },
  ]
});

router.afterEach(() => {
  console.log('路由切换了');
});

const app = createApp(App);
app.use(router);
app.mount('#app');

  • 问题是现在需要访问的变量asideVisibleApp.vue
  • 如果将钩子放到App.vue中,就无法访问router

单独封装一个路由文件router.ts, 导出router接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import {createWebHashHistory, createRouter} from 'vue-router';
import Home from '@/views/Home.vue';
import Docs from '@/views/Docs.vue';
import SwitchDemo from '@/components/SwitchDemo.vue';

const history = createWebHashHistory();
export const router = createRouter({
  history,
  routes: [
    {path: '/', component: Home},
    {
      path: '/docs', component: Docs,
      children: [
        {path: 'switch', component: SwitchDemo}
      ]
    },
  ]
});

main.ts中引入router

1
2
3
4
5
6
7
8
9
import {createApp} from 'vue';
import App from '@/App.vue';
import '@/styles/index.scss';
import {router} from '@/router';

const app = createApp(App);
app.use(router);
app.mount('#app');

App.vue中使用router钩子afterEach

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script setup lang="ts">
import {provide, ref} from 'vue';
import {router} from '@/router';

const width = document.documentElement.clientWidth;
const asideVisible = ref(width > 500);
provide('asideVisible', asideVisible);

router.afterEach(() => {
  asideVisible.value = false;
});

</script>

<script lang="ts">
export default {
  name: 'App'
};
</script>

<template>
  <router-view></router-view>
</template>

  • 注意在模板<template>外使用数据需要.valueasideVisible.value = false;

小结

  • 封装路由router.ts
  • main.ts中引入router
  • App.vue中引入router,使用钩子router.afterEach(()=>{})去实现逻辑
  • 完善PC逻辑,PC不许要隐藏逻辑,加上视口宽度判断

基本完成官网

补全其他组件路由

router.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import {createWebHashHistory, createRouter} from 'vue-router';
import Home from '@/views/Home.vue';
import Docs from '@/views/Docs.vue';
import SwitchDemo from '@/components/SwitchDemo.vue';
import ButtonDemo from '@/components/ButtonDemo.vue';
import DialogDemo from '@/components/DialogDemo.vue';
import TabsDemo from '@/components/TabsDemo.vue';

const history = createWebHashHistory();
export const router = createRouter({
  history,
  routes: [
    {path: '/', component: Home},
    {
      path: '/docs', component: Docs,
      children: [
        {path: '', component: DocsDemo},
        {path: 'switch', component: SwitchDemo},
        {path: 'button', component: ButtonDemo},
        {path: 'dialog', component: DialogDemo},
        {path: 'tabs', component: TabsDemo},
      ]
    },
  ]
});



参考