保姆級Vue3+Vite項目實戰多佈局(上)

語言: CN / TW / HK

寫在前面

本文為 Vue3+Vite 項目實戰系列教程文章第二篇,系列文章建議從頭觀看效果更佳,大家可關注 Vue3 實戰系列 防走失!點個贊再看有助於全文完整閲讀!

此係列文章主要使用到的主要技術站棧為 Vue3+Vite,那既然是 Vue3,狀態庫我們使用的是 Pinia 而不是 Vuex,在寫法上也肯定是以 CompositionAPI 為主而不是 OptionsAPI,組件庫方面我們使用的是 ArcoDesign (趕緊丟掉 ElementUI 吧!)。

上文 這是一份保姆級Vue3+Vite實戰教程 中我們主要介紹了 Vue3+Vite 項目的搭建以及項目上的一些配置,當然後續在開發過程中如果有需要也會陸陸續續的補充一些配置。

本文我們終於要開始寫代碼了,但其實依舊還是在為項目做準備,因為此文的核心是項目的多佈局實戰,先把頁面佈局搭建好,隨後再開始寫項目,這也是項目開發中必不可少的一環,當然重要的還是寫代碼過程中的細節以及小技巧,新同學可以跟着碼一遍,老同學可以快速閲讀一遍看有沒有什麼用得上的實戰小技巧。

👉🏻 項目 GitHub 地址

如果大家不想從頭來過可以直接下載截止到上文內容的代碼,👉🏻 toolsdog tag v0.0.1-dev

代碼拉下來之後,npm install || pnpm install 下載依賴,然後 npm run serve || pnpm serve 啟動,如果一切沒問題的話,當前項目運行起來是這樣的:

PS: 在開始寫代碼之前,你需要簡單看下 Vue3 官方文檔,去了解一下基礎 API,這很重要!!!

OK,接下來開始本文內容!

項目多佈局思路

你想要的多佈局是哪種?

我們平常所説的多佈局比較籠統,仔細分來其實有兩種需要多佈局的場景,大家可以自行匹配一下:

  • 項目有很多頁面,有些頁面是一樣的佈局,但還有些頁面是另外一種佈局,所以我們需要多種佈局提供給不同的頁面。
  • 項目有很多頁面,頁面都是統一的佈局,但是我們需要提供多種可以自由切換的佈局,讓用户在生產環境自己去選擇。

多頁面不同佈局

如果你只是需要在不同的頁面使用不同的佈局,那麼很簡單。

因為你只需要寫多個不同的佈局組件,然後使用二級路由通過指定父級路由的 component 就可以決定採用哪個佈局,如下:

假如我們有 2 個佈局:

```js // layout 1 Layout1.vue

// layout 2 Layout2.vue ```

頁面 page_a 想要使用 Layout1 佈局,頁面 page_b 想要使用 Layout2 佈局,那麼只需在配置路由時如下:

```js { routes: [ { path: '/layout1', name: 'Layout1', component: () => import('/Layout1.vue'), redirect: '/layout1/page_a', children: [ { path: 'page_a', name: 'PageA', component: () => import('/PageA.vue') },

    // ...
  ]
},
{
  path: '/layout2',
  name: 'Layout2',
  component: () => import('***/Layout2.vue'),
  redirect: '/layout2/page_b',
  children: [
    {
      path: 'page_b',
      name: 'PageB',
      component: () => import('***/PageB.vue')
    },

    // ...
  ]
}

] } ```

如上所示,我們只需要在根組件和佈局組件中寫上 <router-view /> 就 OK 了!

可動態切換的佈局

再來看可以動態切換的佈局,其實也很簡單,一般來説,我們使用 Vuecomponent 組件,通過 is 屬性去動態的渲染布局組件就可以了,如下:

```html

```

然後,我們直接在父路由中引入此頁面,就可以通過改變狀態來動態切換所有的子路由佈局了,如下:

```js { routes: [ { path: '/', component: () => import('/SwitchLayout.vue'), redirect: '/page_a', children: [ { path: 'page_a', name: 'PageA', component: () => import('/PageA.vue') },

    // ...
  ]
},

} ```

PS: Vue 內置的component 組件在 Vue2is 也可以通過組件名稱切換, Vue3 中只能通過組件實例切換!

OK,到此本文就結束了,謝謝大家觀看!!!🤨

準備工作

結束是不可能結束的,多佈局本身其實很簡單,更重要的其實還是我們寫的過程中遇到的一些技術點和細節,那接下來我們開始安排!!!

咱們先寫一個可以動態切換的佈局,首先,在項目 src 目錄下創建一個佈局文件夾 layout

接下來我們在 src/layout 文件下創建一個可切換佈局的入口組件 SwitchIndex.vue,內容和上面所寫的差不多,如下:

```html

```

component 組件我們暫且註釋,因為目前還沒有佈局組件。

接下來我們創建兩個佈局組件,由於我們要把這兩種佈局的選擇權交給用户,所以我們在 layout 文件夾下新建一個 switch 文件夾,把可以切換的這兩個佈局組件放到裏面統一管理下。

創建可切換的默認佈局文件:layout/switch/DefaultLayout.vue

```html

```

創建可切換的邊欄佈局文件:layout/switch/SidebarLayout.vue

```html

```

這兩個佈局的預期如下:

其實就是兩種很普通很常見的佈局,一種是有側邊欄的 SidebarLayout( 下文叫它邊欄佈局)、一種無側邊欄的 DefaultLayout(下文叫它默認佈局),大家先了解下要寫的樣子即可。

OK,接下來我們先完善兩種佈局然後再寫動態切換。

默認佈局組件 DefaultLayout

上面我們已經創建好了 DefaultLayout 組件,那先來把它用上。

修改一下 DefaultLayout 組件,如下:

```html

```

然後直接在 SwitchIndex 組件引入使用這個佈局,上文中我們雖然配置了組件自動引入,但是並沒有配置 layout 目錄,所以 layout 文件夾下的組件是不會被自動引入的,那我們還需要現在 vite.config.js 配置文件中把 layout 目錄加上,如下:

```js export default defineConfig(({ mode }) => { return { // ...

plugins: [
  // ...

  Components({
    // 新增 'src/layout' 目錄配置
    dirs: ['src/components/', 'src/view/', 'src/layout'],
    include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
    resolvers: [
      ArcoResolver({
        sideEffect: true
      }),
      VueUseComponentsResolver(),
      VueUseDirectiveResolver(),
      IconsResolver({
        prefix: 'icon',
        customCollections: ['user', 'home']
      })
    ]
  }),
]

} }) ```

OK,然後我們就可以直接在 SwitchIndex 組件中使用 DefaultLayout 佈局組件了,我們寫的組件是匿名組件,默認組件名即文件名,如下:

```html

```

然後,我們需要修改下路由文件 router/index.js ,把 SwitchIndex 組件作為一級路由組件,那此路由下的所有子路由就都可以使用我們的佈局了:

js routes: [ { path: '/', name: 'Layout', component: () => import('@/layout/SwitchIndex.vue'), redirect: '/', children: [ { path: '/', name: 'HomePage', meta: { title: 'TOOLSDOG' }, component: () => import('@/views/HomePage.vue') } ] } ]

保存看頁面:

如果你的運行效果也同上,那就已經用上了佈局組件,到此都是 OK 的,接下來就可以調整佈局 UI 樣式了!

碼一下頁面佈局

上面也説了,我們的默認佈局其實很簡單,就是很普通的上中下三分佈局。

上文我們已經裝好了 ArcoDesign,同樣也配置了其組件自動引入,這裏我們直接使用 ArcoDesignlayout 佈局組件做一個常規的上中下三分佈局即可,需要注意的是,我們給 Navbar 導航部分加了一個固釘組件 a-affix,用於固定在頁面頂部。

PS: ArcoDesign 組件均以子母 a 開頭。

修改 DefaultLayout 組件,如下:

```html

```

接着我們簡單調整一下樣式。

注意,CSS 這裏我們接上文的配置,使用的是原子化 CSS 引擎(叫框架也行哈) UnoCSS ,不太瞭解的可以看下文檔教程,如果你也跟着上文配置了它並下載了 UnoCSS for VSCode 插件,那就跟着我寫,配合插件提供懸浮提示原生樣式,跟着我這邊寫一寫其實就會了,很簡單,畢竟常用的 CSS 樣式也就那些,語法記住就 OK 了,當然,你如果不習慣或者沒有配置此項,也無所謂,因為都是些基礎 CSS 樣式,直接用 CSS 寫一下也可以,咱就是圖個省事兒 ~

還有一點,由於我們想保證風格統一,還有就是後面想搞一下黑白模式切換,對於一些顏色、字體、尺寸方面,我這邊直接全使用了 ArcoDesign 拋出的 CSS 變量,沒有自己去自定義一套基礎變量,沒錯,同樣也是圖省事兒 ~

其實目前正經的 UI 庫都會拋出其統一設計的顏色變量(您要是説 ElementUI,OK,當我沒説),那對於我們這個項目來説,ArcoDesign 自身的這些變量已經夠了,如果你是真的寫正式項目,建議遵循自家 UI 的設計風格,搞一套自己的基礎 CSS 變量來用(對一些有主題、顏色切換需求的同學來説,沒有此需求寫死顏色即可),同時如果項目中使用了開源 UI 庫,你還需要定製一下 UI 庫樣式以匹配自家 UI 設計風格,目前正經的開源 UI 庫對定製主題這塊那都沒得説,清晰明瞭且 Easy (您要是還提 ElementUI,再次當我沒説,苦 ElementUI 良久 😄)

那説了這麼多,我們先來看下 ArcoDesign 的顏色變量吧!👉🏻 ArcoDesign CSS變量傳送們

如上,我們直接使用對應的 CSS 變量即可,這樣後期我們處理黑白模式時,直接可以用 UI 庫自帶的黑暗模式。

OK,我們簡單的寫下佈局樣式

```html

```

如上,我們給 Navbar 一個下邊框以及 58px 高度,給 Footer 一個上邊框,同時,我們給 Navbar、Content、Footer 加了不同級別的背景顏色(AcroDesign 背景色 CSS 變量),最後我們為了讓 Footer 首頁不顯示出來,給 a-layout-content 組件加了一個最小高度,使用視口高度 100vh 減去 Navbar 的高度就是該組件的最小高度了!

再説一次:相信大家無論用沒用過 UnoCSS 都可以看懂寫的樣式是啥意思,@applyUnoCSSstyle 標籤中的寫法,那在 HTML 標籤中 class 屬性中可以直接去寫 UnoCSS 樣式,如上面我們在 a-layout-content 標籤中寫的那樣,如果裝了插件,懸浮上去會有原生樣式的提示,如下:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/19f1fca99b614d3ab5df5783043b32e0~tplv-k3u1fbpfcp-zoom-1.image

一切都沒問題的話,我們的項目保存運行就如下所示了(Footer 需要滾動查看)

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/85283dc8cfa6462daeecd263bd6a9fc9~tplv-k3u1fbpfcp-zoom-1.image

導航組件 Navbar

簡單的佈局組件做好了,接下來我們慢慢填充佈局內容,先來做 Navbar 組件。

上文我們已經看了我畫的草圖,好吧,再看一遍

我們想要實現的兩種佈局都有導航欄,唯一的區別就是菜單的位置,所以我們這裏把導航欄中的各個元素單獨拆分作為獨立的組件,使用插槽的方式在 Navbar 組件去使用,Navbar 組件相當於導航欄的一個佈局組件。這樣導航欄組件在哪種佈局中都是可用的,避免重複代碼。

好,開始做了,在 src/layout 文件夾下新建 components 文件夾存放佈局相關的公共組件。

src/layout/components 文件夾下創建 Navbar.vue 文件,內容如下:

```html ```

如上,我們給 Navbar 組件做了三個具名插槽,採用左中右這種結構並使用 flex 佈局將中間的插槽撐滿,同時我們也將默認插槽放在了中間的插槽位置,這樣默認會往佈局中間填充內容。

注意,導航區域的高度在佈局組件中已經固定寫死 58px 了,導航組件這裏我沒有設置高度,讓它自己撐滿就行了。因為在任何佈局下,導航欄高度是相同的。

我們在 DefaultLayout 佈局組件中的 a-layout-header 標籤中使用一下導航條組件,同樣無需引入直接使用,如下:

```html

<!-- 默認插槽和center插槽,默認插槽可不加template直接寫內容,作用同center插槽 -->
<template #center></template>

<!-- right插槽 -->
<template #right></template>

```

由於插槽中沒有寫內容,所以頁面上沒有東西,導航條殼子搞好了,接下來我們開始填充內容。

左側插槽我們寫一個 Logo 組件,中間插槽就是導航菜單 Menu 組件了,右側插槽則是一些頁面小功能組件,暫定為 Github 組件(用來跳轉 Github 的)、做佈局切換的 SwitchLayout 組件、切換模式的 SwitchMode 組件,暫時就這些。

OK,接下來我們依次寫一下這些組件。

Logo 組件

src/layout/components 文件夾下新建 Logo.vue 文件,寫入如下內容:

```html

```

然後把 Logo 組件填充到我們 DefaultLayout 組件下 Navbar 組件的 左側插槽中即可:

html <Navbar> <template #left> <Logo /> </template> </Navbar>

運行如下:

OK,解釋下 Logo 組件,其實就是一個圖標加上一個路由標題。

樣式我就不解釋了,跟着一塊寫的同學有什麼不知道的樣式就用編譯器鼠標懸浮看一下原生 CSS 是什麼即可,老同學應該都能大致看懂啥樣式。

那關於 logo 我們直接在 iconify 圖標庫中找了一個圖標用,我們這裏用的是 ri:hammer-fill 圖標,關於 icon 的配置以及使用這塊上文已經説過了,不瞭解的請看上文,下文中再使用該庫圖標我就直接寫個圖標名不再解釋了哈。另外,點擊 logo 會跳轉首頁。

標題呢,我們直接用 VuewatchEffect 方法監聽了當前路由 meta 對象中的 title 屬性並賦值給響應式變量 title ,這樣後面我們每次跳轉到某個功能頁面時, Logo 旁邊的文字信息以及瀏覽器 Tab 頁籤都會變成該頁面路由中配置的 title 信息。

useRoute 方法是 Vue3 組合式 API,它返回一個當前頁面路由的響應式對象,同樣 Vue 的核心 API 我們都做了自動引入,所以這裏沒有引入。

watchEffect 也是 Vue3API,該方法會立即運行一個函數,同時響應式地追蹤其依賴,並在依賴更改時重新執行。簡單來説就是隻要該回調中有響應式數據,這些響應式數據的依賴發生改變時就會重新執行此回調,默認會立即執行一次。那在這個場景下就比 watch 好用多了。

那響應式變量 title 是怎麼來的呢?代碼中我們使用了 useTitle 方法,同樣沒有引入,它不是 VueAPI,其實,它是 VueUse 庫中的一個方法,VueUse useTitle 傳送門,在上文我們已經給 VueUse 這個庫的方法做了自動引入,所以可以直接用,該方法會返回一個響應式變量,這個響應式變量在改變時會自動改變我們的網頁標題,注意這裏的標題指的是瀏覽器 Tab 標籤中的標題,如下:

如上圖,既然已經拿圖標當了 logo,那一不做二不休,把 Tab 籤中的 ico 圖標也換了吧,就是上圖中默認的 Vue 圖標,再次去 iconify 圖標庫在線網站中找到我們使用的 logo 圖標,下載個 png 下來,然後找個免費在線的圖片 pngico 格式網站轉一下格式(百度、谷歌找),把轉換後的文件名改成 favicon.ico,替換掉項目根目錄下的 public/favicon.ico 文件即可,然後我們瀏覽器中的 Tab ico 圖片就換好了,如下:

OK,到此 Logo 組件就搞好了。

Github 跳轉小組件

Github 跳轉組件之前我們需要在 config/index.js 文件中配置一下 GitHub Url 地址,方便日後我們在項目中使用或統一修改,Config 配置文件具體內容看文章開頭的代碼或者看上文配置講解。

config/index.js 文件的 configSource 對象中新增一個 github 屬性,屬性值寫上我們的項目地址,如下:

```js const configSource = { // ...

github: 'https://github.com/isboyjc/toolsdog' } ```

Github 跳轉組件很簡單,就是字面意思,我們搞一個圖標放上去,然後能夠點擊打開一個新標籤跳轉到項目的 GitHub 地址就行了。在 src/layout/components 文件夾下新建 Github.vue 文件,寫入如下內容:

```html

```

GitHub 的圖標我們用的 iconify 圖標庫中 mdi:github 圖標,這個就沒啥需要説的了,什麼?你問我為啥不直接用 a 標籤或者 UI 庫的 Link 組件?答案是這個組件庫按鈕懸浮的交互很好看~

接着我們去使用一下,把 Github 組件填充到默認佈局 DefaultLayout 組件下 Navbar 組件的右側插槽中即可:

html <Navbar> <template #right> <Github /> </template> </Navbar>

運行如下:

SwitchLayout 組件、SwitchMode 組件我們得放到後面再説,接下來我們來寫導航菜單組件。

菜單組件 Menu

由於我們目前只有一個路由,就是首頁,還沒有其他正八經兒的功能頁面,所以,這裏我們要先寫一個路由頁面。

那佈局寫完之後我們第一個功能應該是要寫正則可視化校驗功能,這裏我們就提前給它把路由以及頁面定義好吧!

首先,在 src/views 文件夾下新建 RegularPage.vue 文件作為正則校驗頁面組件:

```html

```

接着我們要配置一下路由,注意,由於現在寫的頁面路由它同時還是個菜單,所以我們把這些可以作為菜單的路由單獨寫一個路由文件,這樣我們後期可以直接可以導出當作菜單項配置用。

src/router 文件夾下新建 menuRouter.js 文件,導出一個菜單路由數組,如下:

js export const menuRouter = []

src/router/index.js 中使用一下:

```js import { createRouter, createWebHistory } from 'vue-router' // 導入菜單路由 import { menuRouter } from './menuRouter'

const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', name: 'Layout', component: () => import('@/layout/index.vue'), redirect: '/', children: [ { path: '/', name: 'HomePage', meta: { title: 'TOOLSDOG' }, component: () => import('@/views/HomePage.vue') }, // 使用菜單路由 ...menuRouter ] } ] })

export default router ```

OK,接下來我們配置菜單路由數組,由於我們將來可能會寫到很多不同種類的功能,所以我們使用多級路由的方式給這些頁面做個分類,正則可視化校驗屬於開發工具類,所以我們給它一個 devtools 的父級路由,另外,在菜單路由中,每個父級菜單我們給他在 meta 對象中添加一個 icon 屬性,然後導入一個圖片組件作為對應 icon 的值,這樣做的目的是將來要在導航菜單中給每個分類的菜單都加個圖標。

OK,修改 menuRouter.js 文件如下:

```js import IconMaterialSymbolsCodeBlocksOutline from '~icons/material-symbols/code-blocks-outline'

export const menuRouter = [ { path: 'devtools', name: 'DevTools', meta: { title: '開發工具', icon: markRaw(IconMaterialSymbolsCodeBlocksOutline) }, redirect: { name: 'RegularPage' }, children: [ { path: 'regular', name: 'RegularPage', meta: { title: '正則在線校驗' }, component: () => import('@/views/RegularPage.vue') } ] } ] ```

如上,我們如果想要訪問此頁面,只需要訪問 /devtools/regular 路由即可,那可能有些人注意到該配置中的父級路由的重定向中我們使用的是 name 來做的重定向,這裏不用 path 是為了更安全,這個安全指的是由於我們單獨抽離出了這個菜單路由模塊,雖然目前是在把它引入並寫在了 / 路由下,但是將來萬一改變了一級路由,那整體的 path 都會改變,而使用 name 字段重定向就不存在這個問題,我們只需要注意下各個路由的 name 字段配置不重複即可。

注意,我們上面手動引入了 iconify 圖標庫中的圖標,可能有人會問不是做了 iconify 的自動引入嗎?為什麼還要手動去引入?其實,組件的自動引入是靠解析識別組件模板中引入的組件再做的匹配,而這裏我們沒有在組件模板中使用,而是在 JS 中直接使用的,包括我們做項目經常會做的菜單配置,都是隻存一個圖標名,它是靠我們在運行時通過圖標名去匹配組件,這是一個運行時動態的過程,開發時是做不了自動引入的,這類情況我們需要手動引入一下。

還有一個大家可能發現了,在寫入圖標組件時,我們使用了 Vue3markRaw 方法,markRaw 方法會標記一個對象,使其不能成為一個響應式對象,因為後面我們會將整個菜單路由數據作為一個響應式對象傳入菜單組件渲染,那如果我們在這個數據中存在 Vue 組件,將會造成一些不必要的性能開銷,所以這裏我們直接使用 markRaw 對象給它標記下,使該對象不會被遞歸解析成響應式對象即可。

接下來在瀏覽器訪問下 /devtools/regular 路由,看看效果,同下即沒問題:

已經有菜單了數據了,我們去寫菜單 Menu 組件。先理一下思路,通常組件庫中會有 Menu 組件,當然 ArcoDesign 也不例外,我們可以直接拿過來封裝一層去使用。封裝什麼呢?雖然我們目前只有一個路由,但是我們在應該要考慮到多級的情況,那其實解決辦法就是做一個可以無限遞歸的菜單組件。

OK,在寫菜單組件之前,路由菜單數據還需要處理下,我們寫個遞歸方法拼接一下每個菜單的完整路由,並把每個路由菜單中的 meta 對象壓平到菜單裏,方便我們後面使用,還是在 src/router 文件夾下的 menuRouter.js 文件,新增一個 menuRouterFormat 方法處理菜單數據並將處理後的數據導出,如下:

```js export const menuRouter = [ // ... ]

/* * @description 菜單路由數組 format * @param { Array } router 路由數組 * @param { String } parentPath 父級路由 path * @return { Array } / export const menuRouterFormat = (router, parentPath) => { return router.map(item => { // 拼接路由,例:'devtools' -> '/devtools' 'regular' -> '/devtools/regular' item.path = parentPath ? ${parentPath}/${item.path} : /${item.path}

// 存在 children 屬性,且 children 數組長度大於 0,開始遞歸
if (item.children && item.children.length > 0) {
  item.children = menuRouterFormat(item.children, item.path)
}

return Object.assign({}, item, item.meta || {})

}) }

// 解析後 路由菜單列表 export const menuRouterFormatList = menuRouterFormat([...menuRouter]) ```

src/layout/components 文件夾下新建 Menu/index.vue 文件:

```html

```

簡單説一下上面代碼,我們先導入了之前 menuRouter.js 中的菜單解析後的數據 menuRouterFormatList 對菜單數據進行了一個初始化。

再來看模板,我們用到了 arcoDesign 組件庫的 a-menu 組件。

  • accordion 開啟手風琴效果。
  • mode 屬性是設置菜單模式(水平或垂直),我們給它設置成水平即 horizontal
  • menuItemClick 子菜單點擊時觸發,該回調參數為 key
  • selected-keys 選中的菜單項 key 數組。
  • auto-open-selected 默認展開選中的菜單。

這塊還是建議看下組件庫文檔哈。

子菜單點擊方法中我們直接使用 router.push 傳入 key 跳轉路由即可。那對於 selectedKeys ,我們直接用計算屬性 computed 返回了當前路由對象 routepath 屬性值組成的數組,這樣每次路由改變該方法就會被觸發,selectedKeys 數組值就會響應式的改變。key 值即子菜單的唯一標識,下面我們寫子菜單組件時會將每個子菜單的 key 設置為菜單對應的路由 path

上面我們用到了一個還沒有創建的 MenuItem 組件,它其實就是我們的子菜單組件,接下來我們還是在 src/layout/components/Menu 文件夾下新建 MenuItem.vue 文件,內容如下:

```html

```

子菜單組件 MenuItem 如上,首先,它是一個完全受控組件,就是字面意思,這個組件完全依靠父組件傳入的數據,它只做渲染使用,所以叫完全受控組件。

我們在 Menu 組件中遍歷了菜單數據,並給每個子菜單組件傳入了一個 menu 屬性即對應的菜單信息對象。

在子菜單中,我們使用 defineProps 定義了一個 menu 屬性,Vue3 有個 toRefs 方法可以將響應式對象轉換成普通對象,但是這個普通對象中的屬性會變成一個個響應式屬性。正常情況下我們需要使用 props.menu 才能調用父組件傳入的屬性並且該屬性是單向傳遞,也就是我們不能在子組件中修改它,但是我們可以使用 Vue3toRefs 方法將整個 props 對象解構,解構出的每個屬性值都是響應式的對象,並且在子組件中直接修改該值可以同步父組件。我們這裏使用 toRefs 只是為了方便,並沒有在子組件去修改父組件傳入的值,這裏簡單提一下可以這樣做,算是一個小技巧吧,不然我們想要在子組件修改父組件傳入的值,只能先定義一個響應式數據接收傳入的值,再在該響應值改變時,emit 出去一個事件,那在 Vue3 中使用 emit 還需要使用 defineEmits 去定義一下事件名,還是比較麻煩的,不理解沒關係,後面有案例會用到這種方法,到時候還會説!!!

OK,接着説,我們拿到了父組件中傳入的 menu 對象,接下來看組件模板,首先我們在模板中校驗了一下傳入的 menu 對象中是否存在 children 屬性。

如果不存在 children 屬性,那它就是一個菜單,我們直接使用 a-menu-item 組件寫入子菜單並把唯一標識 key 設置為 menu 對象的 path 屬性即可(菜單路由),該組件有兩個插槽,默認插槽我們寫入菜單標題 menu.title ,接着校驗了一下傳入的 menu 對象中有沒有 icon 屬性,有的話就在 icon 插槽中使用 component 直接渲染 icon 組件(這裏傳入的 icon 屬性值必須為組件對象,其實目前我們只是在帶有子級的菜單傳入了 icon 組件)。

如果存在 children 屬性,那它就是一個帶子級的菜單項,我們使用 a-sub-menu 組件去渲染它,和之前不一樣的是,帶子級的菜單組件 a-sub-menu 我們直接把菜單標題傳入該組件的 title 屬性就可以了,icon 還是一樣的操作,劃重點,那既然是帶子級的菜單,我們還需要把它的子級再渲染出來,那它的子級可能還有子級(無限套娃),這裏我們只需要遞歸的調用一下組件自身就可以無限的渲染直到沒有子級數據。那 Vue3 的遞歸組件(組件自身調用自身),寫法和 Vue2 其實也沒太大差別,我們這裏由於配置了自動引入,所以都不需要引入,直接像在外部調用該組件一樣,直接調用自己就行,這裏其實涉及到匿名組件和具名組件的問題,我們這個組件是個匿名組件,但是在匿名狀態下,我們可以直接將該組件文件名作為組件名調用即可,那如果組件文件名是 index.vueVue 會自動將上層的文件夾名作為組件名調用,注意,僅僅是調用而已,其他的都操作不了,想要通過組件名操作一個組件,還是得具名,Vue3 組合式 API 組件的具名方法下文有案例會提到,這裏不多説。如上代碼,還是像父組件 Menu 中調用 MenuItem 組件一樣調用自己調自己就行了,到此我們的菜單組件就寫好了。

Menu 組件中還寫了一些樣式,其實就是簡單調整一下組件庫中菜單組件的樣式,具體樣式這裏就不多説了,因為會的能看懂,不會的自己跟着實踐一下就懂了(太佔篇幅)。

OK,寫完了使用一下看看效果,把 Menu 組件填充到默認佈局 DefaultLayout 組件下 Navbar 組件的中間插槽或者默認插槽中即可:

```html

<!-- 默認插槽和center插槽,默認插槽可不加template直接寫內容,作用同center插槽 -->
<template #center>
  <Menu />
</template>

<!-- ... -->

```

看下頁面效果:

到此默認佈局的導航組件就寫的差不多了,下面來寫下頁尾組件!

頁尾組件 Footer

頁尾區域我們在佈局組件中沒有設置高度,因為頁尾的高度不固定,可能隨時會在頁尾加個內容啥的,所以就讓它隨組件內容高度自由撐開吧。。

由於頁尾需要展示一些個人信息,所以我們統一把這些數據都放在 config/index.js 中的基礎配置對象裏,Config 文件內容配置以及使用上文已經説過了,不再描述,這裏的數據沒什麼重要的,不需要脱敏,如需脱敏,可以配合 env.local 環境變量配置文件去做,env.local 環境變量配置文件默認會被 git 忽略,寫入該環境變量文件,並在 config 文件中引入環境變量即可,關於環境變量的配置也請看上文或者直接看文檔 👉🏻 Vite 中 env 配置文檔

```js // ...

const configSource = { // ...

// 個人配置 me: { name: 'isboyjc', // 公眾號 gzhName: '不正經的前端', gzhUrl: 'http://qiniuimages.isboyjc.com/picgo/202210030159449.jpeg', // github github: 'https://github.com/isboyjc' } } ```

我們在 src/components 文件夾下新建 Footer.vue 文件,Footer 組件比較簡單,暫時也沒寫太多內容,這裏我就不會多描述了,直接看代碼吧。

```html

```

在佈局文件中使用一下:

DefaultLayout 默認佈局組件中的 a-layout-footer 組件標籤中使用一下 Footer 組件,同樣無需引入直接使用,如下:

```html

```

保存後瀏覽器中看看效果吧:

默認佈局到此告一段落,目前首頁是我們上文留下的示例代碼,有點醜,我們稍微修改一下,讓它有個網站的樣子。

首頁修改 HomePage

打開 src/views/HomePage.vue 文件,清空當前內容,寫入下面代碼:

```html ```

不再細説了,因為沒啥東西,還是那個 logo 圖標放上去,加了點樣式,給它放到頁面中間就行了,暫時先這樣,保存查看效果如下:

太長了,看下文吧!

寫在最後

由於此文內容篇幅比較長,為了閲讀體驗,邊欄佈局以及動態切換這些內容我放在下文裏了,由於本文描述核心內容並未結束,所以沒有給代碼打 Tag,大家有精力可以直接點擊下文鏈接接着看,兩文應該會一塊發佈,項目分支代碼隨時會變動,不建議直接看哈,下文結束代碼會打一個 Tag 發佈供大家下載查看當前版本代碼,就這樣。

保姆級 Vue3+Vite 項目實戰多佈局(下)

謝閲,如有錯誤請評論糾正,有什麼疑問或者不理解的地方都可以私信諮詢我,由於不經常寫實戰文章,也為了不同程度同學都可以看下去,文章可能稍微有些囉嗦,見諒,再次歡迎關注專欄 Vue3實戰系列 ,點贊關注不迷路,回見!

本文為稀土掘金技術社區首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!