[Vue.js] 連動下拉選單 範例程式碼 (後端ASP.net Core)

Vue.js cascading dropdownlist

前言

隨手記錄Vue.js連動下拉選單實現作法

我太懶只寫兩層,第三層依同樣邏輯推理就可以

如果是在資料編輯的表單畫面,網頁一載入時要先帶出用戶輸入過的值,而第二層以後的下拉選單,我到現在仍猶豫這時該交由前端發Ajax處理,或Server端先處理完再回傳前端

選擇障礙發作XD

實作

後端版本 ASP.net Core 2.1

Controller

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Newtonsoft.Json;

namespace WebApplication1VueMenu.Controllers
{

    public class Option
    {
        public int id { get; set; }
        public int pid { get; set; } //父層id,頂層為0
        public string text { get; set; } 

    }
    public class HomeController : Controller
    {
        private List<Option> InitOptions()
        {
            List<Option> options = new List<Option>();
            //第一層選單
            options.Add(new Option() { id = 1, pid=0, text = "A" });
            options.Add(new Option() { id = 2, pid=0, text = "B" });
            options.Add(new Option() { id = 3, pid=0, text = "C" });

            //第二層選單
            options.Add(new Option() { id = 4, pid = 1, text = "a" });
            options.Add(new Option() { id = 5, pid = 1, text = "aa" });
            options.Add(new Option() { id = 6, pid = 1, text = "aaa" });

            options.Add(new Option() { id = 7, pid = 2, text = "b" });
            options.Add(new Option() { id = 8, pid = 2, text = "bb" });
            options.Add(new Option() { id = 9, pid = 2, text = "bbb" });

            options.Add(new Option() { id = 10, pid = 3, text = "c" });
            options.Add(new Option() { id = 11, pid = 3, text = "cc" });
            options.Add(new Option() { id = 12, pid = 3, text = "ccc" });
            //End 第二層選單

            return options;

        }
         
        /// <summary>
        /// 新增資料表單
        /// </summary>
        /// <returns></returns>
        public IActionResult Add()
        {
            List<SelectListItem> options =  this.InitOptions()
                                           .Where(m=>m.pid==0)
                                           .Select(m=>new SelectListItem() { Value = m.id.ToString() , Text = m.text  }).ToList();
            options.Insert(0,new SelectListItem() {  Value="-1",Text="請選擇"  });//Selected=true由前端v-model控制,後端不處理
            ViewBag.parents = options;
            return View();
        }
        /// <summary>
        /// 編輯資料表單 (假裝用戶之前選擇了 父menu id=2 text=B, 子menu id=9 text=bbb ,這個值來自DB抓出來)
        /// </summary>
        /// <returns></returns>
        public IActionResult Edit()
        {
            string userPid = "2";//這個值來自DB抓出來
            ViewBag.parentid = Convert.ToInt32(userPid);
            string userCid = "9";//這個值來自DB抓出來
            ViewBag.childrenid = Convert.ToInt32(userCid);




            //第一層選單
            List<SelectListItem> options_parent = this.InitOptions()
                                                 .Where(m => m.pid == 0)
                                                 .Select(m => new SelectListItem() { Value = m.id.ToString(), Text = m.text  }).ToList();
            //Selected由前端v-model控制,後端不處理,就算後端指定Selected,仍然會被前端v-model蓋掉XD
            options_parent.Insert(0, new SelectListItem() { Value = "-1", Text = "請選擇"  });

            //第二層選單
            //↓這一段....以前我都在網頁第一次載入時,前端JS發出Ajax處理,現在覺得第一次網頁載入時
            //應該先交由Server處理完DOM元素再回給前端,比較不會有網頁剛載入就破版(or DOM元素動來動去)問題
            List<SelectListItem> options_children = this.InitOptions()
                .Where(m=>m.pid==Convert.ToInt32(userPid))
                .Select(m => new SelectListItem()
                { Value = m.id.ToString(), Text = m.text  }).ToList();//Selected 後端不處理,由前端v-model控制
            options_children.Insert(0, new SelectListItem() { Value = "-1", Text = "請選擇" });




            ViewBag.parents = options_parent;
            //序列化為Json陣列,讓Vue.js存取
            ViewBag.childrens = JsonConvert.SerializeObject(options_children);
            return View();
        }

        //父menu發出Ajax要資料
        public IActionResult QueryChildren(int parentid)
        {
            List<SelectListItem> options = new List<SelectListItem>();
            if ( parentid !=  -1 )
            {

                options = this.InitOptions()
                              .Where(m=>m.pid==parentid)
                              .Select(m => new SelectListItem() { Value = m.id.ToString(), Text = m.text }).ToList();
                options.Insert(0, new SelectListItem() { Value = "-1", Text = "請選擇" });
                 
            }

            return Content(JsonConvert.SerializeObject(options),"application/json");//統一使用JsonConvert.SerializeObject,維持物件屬性的首字大寫
        }
    }
}

View

Add.cshtml

 

<!DOCTYPE html> 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Add</title>
</head>
<body>
    <form>
        <div id="app">
            第一層:
            @Html.DropDownList("parent",(List<SelectListItem>)ViewBag.parents, new Dictionary<string, object>()
             { { "v-model.number", "parentid" }, { "v-on:change", "ParetnChange" } }   )
            <br />
            第二層: 
            <select name="children" v-model.number="childrenid">
                <option v-for="(c,index) in childrens" v-if="childrens.length > 0" v-bind:value="c.Value">
                    <!--留意 c 物件屬性有區分大小寫-->
                    <!--省下 v-bind:selected 單向綁定,由v-model控制-->
                    {{c.Text}}
                </option>
            </select>
            <br />
            <button type="button" v-on:click.prevent="MySubmit">送出</button>
            <hr />
            <div v-if="showResult!==''">
                送出資料↓<br />
                {{showResult}}
            </div>
        </div>
    </form>
    <!-- 引用jQuery -->
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <!--引用Vue.js -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>
    <script type="text/javascript">
        //取得第二層<option>資料的Ajax Url
        let AjaxUrl = "@Url.Action("QueryChildren","Home")";
    </script>






    <!--javascript,通常在.js檔裡-->
    <script type="text/javascript">
        let app = new Vue({
            el: "#app",
            data: {
                parentid: -1,//預設「請選擇」
                childrenid:-1,//預設「請選擇」
                childrens: [],
                showResult:"" //顯示送出的資料(Json格式)
            },
            methods: {
                ParetnChange: function ($event) {
                    let vm = this; 
                    if (vm.parentid === -1)
                    {
                        vm.childrenid = -1;//子menu回到請選擇
                        vm.childrens = [];
                        return;
                    }

                        $.ajax({
                            url: AjaxUrl,
                            data: { parentid: vm.parentid },
                            method: "get",
                            success: function (arry)
                            {
                                vm.childrens = arry;//變更第二層選單資料 
                                vm.childrenid = -1;//第二層寫死綁定「請選擇」
                            }
                        });
                },
                MySubmit: function ($event) {
                    let vm = this;
                    vm.showResult = JSON.stringify( { parentid: vm.parentid, childrenid: vm.childrenid });
                }
            }
        });
    </script>
</body>
</html>

Edit.cshtml

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Edit</title>
</head>
<body>
    <form>
        <div id="app">
            第一層:
            @Html.DropDownList("parent",(List<SelectListItem>)ViewBag.parents, new Dictionary<string, object>()
             { { "v-model.number", "parentid" }, { "v-on:change", "ParetnChange" } }   )
            <br />
            第二層:
            <select name="children" v-model.number="childrenid">
                <option v-for="(c,index) in childrens" v-if="childrens.length > 0" v-bind:value="c.Value">
                    <!--留意 c 物件屬性有區分大小寫-->
                    <!--省下單向綁定v-bind:selected,由v-model控制-->
                    {{c.Text}}
                </option>
            </select>
            <br />
            <button type="button" v-on:click.prevent="MySubmit">送出</button>
            <hr />
            <div v-if="showResult!==''">
                送出資料↓<br />
                {{showResult}}
            </div>
        </div>
    </form>
    <!-- 引用jQuery -->
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <!--引用Vue.js -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>
    <script type="text/javascript">
        //取得第二層<option>資料的Ajax Url
        let AjaxUrl = "@Url.Action("QueryChildren","Home")";

        let parentid =@ViewBag.parentid;//user在DB裡第一層選單的值 
        let childrenid =  @ViewBag.childrenid; //user在DB裡第二層選單的值
        let childrensArry = @Html.Raw(ViewBag.childrens);
    </script>






    <!--javascript,通常在.js檔裡-->
    <script type="text/javascript">
        let app = new Vue({
            el: "#app",
            data: {
                parentid: parentid,
                childrenid: childrenid,
                childrens: childrensArry,
                showResult:"" //顯示送出的資料(Json格式)
            },
            methods: {
                ParetnChange: function ($event) {
                    let vm = this;
                    if (vm.parentid === -1)
                    {//清掉第二層選單
                        vm.childrenid = -1;
                        vm.childrens = [];
                        return;
                    }

                        $.ajax({
                            url: AjaxUrl,
                            data: { parentid: vm.parentid },
                            method: "get",
                            success: function (arry)
                            {
                                vm.childrens = arry;//變更第二層選單資料
                                vm.childrenid = -1;//第二層寫死綁定「請選擇」
                            }
                        });
                },
                MySubmit: function ($event) {
                    let vm = this;
                    vm.showResult = JSON.stringify( { parentid: vm.parentid, childrenid: vm.childrenid });
                }
            }
        });
    </script>
</body>
</html>

線上Demo:https://jsfiddle.net/ShadowKao/k89m3wau/