拆完頁面後,我們來解決選單項目位置的問題,原先的做法是每個頁面去手動變更class,讓當前的頁面選單會有高亮反白的顯示,這個動作如果每個頁面都要做就太累了,所以我們要讓選單自動記得現在誰被點了,然後被點選的項目應該維持反白的狀態。
Vue本身有提供 emit 這個機制來讓子層資料可以傳遞到父層去,這個機制在組件層級太多時會很繁瑣,所以我們希望有一個可以全域管理狀態的機制,在之前大多是使用Vuex,近期Vue官方推薦使用Pinia。
安裝pinia
npm install pinia
resources/js/app.js 中掛載pinia
import './bootstrap';
import '../css/app.css';
import { createApp, h } from 'vue';
import { createPinia } from 'pinia'; //引入pinia模組
import { createInertiaApp } from '@inertiajs/inertia-vue3';
import { InertiaProgress } from '@inertiajs/progress';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
const pinia = createPinia() //建立pinia物件
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
setup({ el, app, props, plugin }) {
return createApp({ render: () => h(app, props) })
.use(plugin)
.use(ZiggyVue, Ziggy)
.use(pinia) //掛載pinia進vue app
.mount(el);
},
});
InertiaProgress.init({ color: '#4B5563' });
建立一個資料夾來存放所有的狀態檔案,我個人習慣把所有的狀態分門別類不同的檔案做管理,而pinia允許狀態物件間相互引用,所以不需要把狀態都寫在一個檔案中。
建立一個選單專用的狀態管理檔
import { defineStore } from "pinia";
export const menuStore=defineStore('menuStore',{
state:()=>({ }),
getters:{ },
actions:{ },
})
為了之後的擴充方便,我希望在增加選單項目時,選單項目可以自己在狀態管理中註冊自己,之後選單組件會自己去監測每個選單項目的變化,並通知每個項目更新自己的狀態:
resources/js/Stores/MenuStore.js
import { defineStore } from "pinia";
export const menuStore=defineStore('menuStore',{
state:()=>({
selected:{}, //以物件的方式來儲存狀態
}),
actions:{
setItem(item){
//把項目的KEY值取出成為一個陣列
let items=Object.keys(this.selected)
//如果陣列中沒有這個項目,就加入
if(items.indexOf(item)<0){
this.selected[item]=false;
}
},
select(item){
//把選單項目全部設為false
Object.keys(this.selected).forEach(key=>{
this.selected[key]=false
})
//把點選的項目設為true
this.selected[item]=true
}
},
})
resources/js/QuizComponents/MenuItem.vue
<script setup>
import { Link } from "@inertiajs/inertia-vue3";
import { storeToRefs } from 'pinia'
import { menuStore } from '@/Stores/MenuStore.js';
import { ref, onMounted ,watch} from 'vue';
//onSelected是外部傳入的一個值,型態為布林值,預設為false
defineProps({
href: String,
onSelected:{type:Boolean,default:false}
});
const store=menuStore();
const {selected}=storeToRefs(store)
const {setItem,select} = store
const itemText=ref(); //設定一個變數來代表DOM物件
onMounted(()=>{
//當單一選單項目被掛載到畫面上時,去狀態管理器中註冊一下自己
//註冊的方式是把項目文字傳過去
store.setItem(itemText.value.innerText)
});
</script>
<template>
//在項目本身建立一個click事件,觸發時去通知狀態管理那個項目被點選了
//class的部份綁定傳入的onSelected屬性,進行不同class的切換
<Link
:href="href"
class="block my-2 py-2 text-center text-xl"
:class="{'hover:bg-slate-50 hover:text-slate-900':!onSelected,
'bg-slate-50 text-slate-900':onSelected}"
@click="select(itemText.innerText)"
>
<span ref="itemText"><slot /></span>
</Link>
</template>
resources/js/Layouts/BackstageLeftMenu.vue
<script setup>
import { storeToRefs } from 'pinia'
import { menuStore } from '@/Stores/MenuStore.js';
import MenuItem from "@/QuizComponents/MenuItem.vue";
const store=menuStore();
const { selected } = storeToRefs(store)
</script>
<template>
<!--左側選單-->
<div
class="w-48 max-w-96 bg-black text-white py-8 px-2 shadow-lg
h-[calc(100vh_-_138px)]">
<!--把每個項目的狀態傳進去-->
<MenuItem :href="route('backstage.banks')"
:onSelected="selected['管理題庫']">管理題庫</MenuItem>
<MenuItem :href="route('backstage.quizzes')"
:onSelected="selected['管理試卷']">管理試卷</MenuItem>
<MenuItem :href="route('backstage.tests')"
:onSelected="selected['管理測驗']">管理測驗</MenuItem>
<MenuItem :href="route('backstage.groups')"
:onSelected="selected['管理群組']">管理群組</MenuItem>
</div>
</template>
以上就是Pinia的狀態管理的使用方式,基本上就是當它是個全域變數來看,所以連帶的也要注意在某些情境下,要記得先還原狀態,否則會影響到其它的組件狀態。
今天就先這樣了,我們總算是把一個全端開發的流程建立起來了,之後的每個功能,大多都是像這樣,在不同的檔案間來回穿梭,看起來好像很簡單,但開發專案難的不是在於這些套件或框架的使用,而是專案本身的商業邏輯如何和這些套件及框架整合在一起。