iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 12
0
自我挑戰組

網頁前端框架 Vue 3 從頭開始(重新挑戰)系列 第 12

vue3 Composition API 學習手冊-12 實作 多層次動態選單

  • 分享至 

  • xImage
  •  

在經歷了前幾篇文章的分享後,這篇文章想要整合一下前面所提到的進度,來製作一個整合性的範例,幫助大家停下腳步想想,除了每個項目分別的作用之外,有沒有什麼事可以透過這些項目的結合而產生的網頁功能,在這邊示範一個多層次的動態選單,藉以驗收學習上的成果。

下方範例有兩個下拉選單,第一個選單選擇的是Frameworks的類型,而依據類型選擇的不同,第二個選單的內容也會跟著變化,範例檔

大家可以想想看這個範例如果交到你手上,你會用什麼樣的方式來製作,建議先思考讓心中有個雛形之後,再來看看我們這邊的作法:

<div id="app">
    <h3>Please select your favorite framework:</h3>
    <p>
        Type:
        <select v-model="state.frameworksIdx">
            <option v-for="(item, index) in state.frameworks" :value="index">
                {{item.type}}
            </option>
        </select>
    </p>
    <p>
        Name:
        <select v-model="state.contentsIdx">
            <option v-for="(item, index) in pickContents" :value="index">
                {{item.name}}
            </option>
        </select>
    </p>
    <div class="noteText">
        <p>
            你選擇的框架類型是:{{ state.frameworks[state.frameworksIdx].type }} - 
            {{ state.frameworks[state.frameworksIdx].contents[state.contentsIdx].name }}
        </p>
        <p>官方網站:{{ state.frameworks[state.frameworksIdx].contents[state.contentsIdx].website }}</p>
    </div>
</div>
<script>
const { reactive, computed, watch } = Vue;
const app = {
    setup(){
        const state = reactive({
            frameworksIdx: 0, // 記錄第一層選單的被選取項目
            contentsIdx: 0, // 記錄第二層選單的被選取項目
            frameworks : [
                {
                    type: 'Frontend',
                    contents: [
                        { name: 'Vue', website: 'https://vuejs.org/' },
                        { name: 'React', website: 'https://reactjs.org/' },
                        { name: 'Angular', website: 'https://angular.io/' },
                    ],
                },
                {
                    type: 'Backend',
                    contents: [
                        { name: 'Laravel', website: 'https://laravel.com/' },
                        { name: 'CakePHP', website: 'https://cakephp.org/' },
                        { name: 'Django', website: 'https://www.djangoproject.com/' },
                        { name: 'Ruby on Rails', website: 'https://rubyonrails.org/' },
                    ],
                },
            ],
        })
        const pickContents = computed(() => {
            return state.frameworks[state.frameworksIdx].contents;
        })
        watch(() => state.frameworksIdx, (value) =>{
            state.contentsIdx = 0;
        })
        return { state, pickContents };
    }
}
const myVue = Vue.createApp(app).mount("#app");
</script>

稍微針對上面的範例進行一些分析:

HTML:

  • 第一個下拉選單
    • select透過v-model綁定frameworksIdx,去記錄目前被選取的項目Index(0,1,2....)
    • option透過v-for綁定frameworks數據,去渲染清單內容,value設定成index,內容顯示frameworks中的type
  • 第二個下拉選單
    • Select透過v-model綁定contentsIdx,去記錄目前被選取的項目Index(0,1,2….)
    • option透過v-for綁定contents數據,去渲染清單內容,value設定成index,內容顯示contents中的name
      Javascript:
  • 第2~24行是數據的部分
    • frameworksIdx記錄第一層選單的被選取項目
    • contentsIdx記錄第二層選單的被選取項目
    • frameworks是所有的資料內容
  • 第28~32行是computed的部分,主要是依據第一層選單值,傳回第二層選單應該要呈現的內容
  • 第33~37行是watch的部分,主要是去偵測第一層選單若改變,自動將第二層選單改選為第一個選項

剛好本次這個範例也應用到之前所提到多篇文章的內容,大家也可以看看是否能更換成別的做法,接下來再做一個三層式的動態選單,內容方向做了一些調整,模擬服飾店網站的選單,透過性別的選取後決定品項,透過品項的選擇,進而顯示不同產品,下面讓大家來看看成果,也順便找找有什麼可以優化的內容,範例檔

<div id="app">
    <h3>Please make your decision:</h3>
    <p>
        Gender:
        <select v-model="state.genderIdx">
            <option v-for="(item, index) in state.clothes" :value="index">
                {{item.gender}}
            </option>
        </select>
    </p>
    <p>
        Type:
        <select v-model="state.partIdx">
            <option v-for="(item, index) in pickTypes" :value="index">
                {{item.part}}
            </option>
        </select>
    </p>
    <p>
        Product:
        <select v-model="state.itemIdx">
            <option v-for="(item, index) in pickParts" :value="index">
                {{item.product}}
            </option>
        </select>
    </p>
</div>
<script>
const { reactive, computed, watch } = Vue;
const app = {
    setup(){
        const state = reactive({
            genderIdx: 0, // 記錄第一層選單的被選取項目
            partIdx: 0, // 記錄第二層選單的被選取項目
            itemIdx: 0, // 記錄第三層選單的被選取項目
            clothes : [
            {
                gender: "男",
                types: [
                    {
                        part: "上衣類",
                        contents: [
                            { product: "短袖/背心" },
                            { product: "長袖" },
                            { product: "立領/高領" },
                            { product: "針織衫" },
                            { product: "休閒襯衫" },
                            { product: "商務襯衫" },
                            { product: "法蘭絨系列" },
                            { product: "厚棉系列" },
                        ],
                    },
                    {
                        part: "外套類",
                        contents: [
                            { product: "休閒外套" },
                            { product: "Fleece系列" },
                            { product: "極輕羽絨" },
                            { product: "極暖羽絨" },
                        ],
                    },
                    {
                        part: "下身類",
                        contents: [
                            { product: "短/七分褲" },
                            { product: "九分/束口褲" },
                            { product: "休閒長褲" },
                            { product: "牛仔褲" },
                            { product: "保暖褲" },
                        ],
                    },
                    {
                        part: "家居服",
                        contents: [
                            { product: "家居套裝" },
                            { product: "家居褲" },
                            { product: "家居毯" },
                        ],
                    },
                ],
            },
            {
                gender: "女",
                types: [
                    {
                        part: "上衣類",
                        contents: [
                            { product: "印花短T" },
                            { product: "印花長T" },
                            { product: "短袖/背心" },
                            { product: "七分/長袖" },
                            { product: "長版上衣" },
                            { product: "針織衫" },
                            { product: "polo衫" },
                            { product: "Pima棉" },
                        ],
                    },
                    {
                        part: "外套類",
                        contents: [
                            { product: "休閒外套" },
                            { product: "Fleece系列" },
                            { product: "極輕羽絨" },
                            { product: "極暖羽絨" },
                        ],
                    },
                    {
                        part: "下身類",
                        contents: [
                            { product: "休閒短褲" },
                            { product: "七/九分褲" },
                            { product: "牛仔系列" },
                            { product: "寬褲系列" },
                            { product: "休閒長褲" },
                            { product: "裙子" },
                            { product: "連身/吊帶褲" },
                            { product: "緊身褲" },
                            { product: "裙子" },
                        ],
                    },
                    {
                        part: "洋裝",
                        contents: [
                            { product: "洋裝" },
                            { product: "家居褲" },
                            { product: "吊帶裙" },
                        ],
                    },
                ],
            },
            ],
        })
        const pickTypes = computed(() => {
            return state.clothes[state.genderIdx].types;
        })
        const pickParts = computed(() => {
            return state.clothes[state.genderIdx].types[state.partIdx].contents;
        })
        watch(() => state.genderIdx, (value) =>{
            state.partIdx = 0;
        })
        watch(() => state.genderIdx, (value) =>{
            state.itemIdx = 0;
        })
        return { state, pickTypes, pickParts };
    }
}
const myVue = Vue.createApp(app).mount("#app");
</script>

大部分的程式邏輯跟兩層式選單的相差不大,但可以看到Javascript從第4行~第104行並非是邏輯運算,幾乎都是在描述資料的部分,當然其中第5~7行是配合邏輯運算紀錄使用,但除此之外,純描述資料的部分就佔約100行,當然這僅僅是示範案例,真正網站的資料恐怕還會超過這個量,但通常這樣的資料,可能不是寫死在前端而是透過後端資料庫或其他方式進行提供,所以未來我們的案例也會有這個方向的考量。


上一篇
vue3 Composition API 學習手冊-11 監聽器
下一篇
vue3 Composition API 學習手冊-13 生命週期
系列文
網頁前端框架 Vue 3 從頭開始(重新挑戰)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言