[上課筆記][20250224] 恆逸 Vue3

my lesson note

老師範例的檔案路徑
\\10.0.1.2\share
帳:user
密:111


瀏覽器開發者工具先點擊Vue頁籤>Component>點擊console頁籤即可在主控台下指令$vm.data.msg(屬性)取得vm的資料

Options API在網頁中引用"vue.global.js"和"vue.esm-browser.js"這兩個有什麼差異?
https://grok.com/share/bGVnYWN5_98b56147-028c-4874-95f6-405af0215981

Options API與Composition API兩者有何差異?我該使用哪種?
https://grok.com/share/bGVnYWN5_1f16d4c4-f748-4d08-9102-b1f705f6c681

從jQuery事件中呼叫Vue的方法&取得Vue的資料屬性
https://grok.com/share/bGVnYWN5_6f0a37a9-8f17-4bce-b5a7-ad1f41cc703d

計算屬性的簡單範例

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <title>Document</title>
</head>

<body>
  <div id="app">
 
    <input type="text" v-model="msgUpper" />
    <p>{{ msg }}</p>
    <p>{{ msgUpper }}</p>
  </div>
  <script type="importmap">
    {
      "imports": {
        "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
      }
    }
  </script>

  <script type="module">
    import { createApp } from 'vue'
    createApp({
      data() {
        return {
          msg: "Hello World ! ",
        };
      },
      computed: {
        msgUpper: {
          get  () {
            return this.msg.toUpperCase()
          },
          set  (newvalue) {
            this.msg = newvalue.toLowerCase()
          }
        },
      },
    }).mount("#app");
  </script>
</body>

</html>

偵聽器(Watcher) 監看資料屬性是否變動,簡單範例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <title>Document</title>
</head>

<body>
  <div id="app">
    <input type="text" v-model="msg"/>   <br/>
    <p v-text="msg"></p>
  </div>
  <script type="importmap">
    {
      "imports": {
        "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
      }
    }
  </script>

  <script type="module">
    import { createApp } from 'vue'
    const vue_vm = createApp({
      data() {
        return { 
          msg: "",
        };
      },
      watch: {
        msg: function (msg) {
          console.log("監看msg值:"+msg);
        },

      },
      methods: { 
      }, 
    }).mount("#app");
  </script>
</body>

</html> 

在HTML DOM使用$event傳遞觸發Vue JS事件的DOM物件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <a href="https://vuejs.org/" @click="showMsg" target="_blank">  Go To Vue.js  </a>
      <br/>
      <a href="https://vuejs.org/" @click="showMsg($event)" target="_blank">  Go To Vue.js  </a>

    </div>
  <script type="importmap">
    {
      "imports": {
        "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
      }
    }
  </script>

  <script type="module">
    import { createApp } from 'vue'   
      createApp({
        data() {
          return {};
        },
        methods: {
          showMsg: function (event) {
            alert("No redirect !");
            event.preventDefault();
            console.log(event.target);
          },
        },
      }).mount("#app");
    </script>
  </body>
</html>

Vue3 與 Bootstrap-select 整合示例
https://grok.com/share/bGVnYWN5_81cf68da-5e16-4c4c-9178-f6e386cdb747

<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <title>Vue 3 with Bootstrap-select Example</title>
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Bootstrap-select CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap-select@1.14.0-beta3/dist/css/bootstrap-select.min.css" rel="stylesheet">
</head>
<body>
    <div id="app" class="container mt-5">
        <h2>選擇項目</h2>
        <!-- 搜尋框:data-live-search="true"  -->
        <select class="selectpicker" 
                v-model="selectedItemValue" 
                data-live-search="false"  
                title="請選擇一個項目"
                @change="handleSelection">
            <option v-for="item in items" 
                    :key="item.id" 
                    :value="item.id" v-text="item.name"> 
            </option>
        </select>
        
        <div class="mt-3" v-if="selectedItemValue">
            getSelectedItemName: {{ getSelectedItemName }} <br/>
            selectedItemValue: {{selectedItemValue}}
        </div>
    </div>
    <!-- jQuery (bootstrap-select 依賴) -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <!-- Vue 3 -->
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> 
    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <!-- Bootstrap-select JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap-select@1.14.0-beta3/dist/js/bootstrap-select.min.js"></script>

    <script>
        const { createApp } = Vue;

        createApp({
            data() {
                return {
                    items: [],
                    selectedItemValue: null
                }
            },
            computed: {
                getSelectedItemName() {
                    const item = this.items.find(item => item.id === this.selectedItemValue);
                    return item ? item.name : '';
                }
            },
            methods: {
                fetchItems() {
                    // 使用 Promise 替代 async/await
                    fetch('https://jsonplaceholder.typicode.com/users')
                        .then(response => response.json())
                        .then(data => {
                            // 轉換資料格式
                            this.items = data.map(user => ({//集合轉型
                                id: user.id,
                                name: user.name
                            }));
                            // 更新 bootstrap-select
                            this.$nextTick(() => {
                                $('.selectpicker').selectpicker('refresh');
                            });
                        })
                        .catch(error => {
                            console.error('獲取資料失敗:', error);
                        });
                },
                handleSelection() {
                    console.log('選擇的項目ID:', this.selectedItemValue);
                }
            },
            mounted() {
                // Vue 掛載後 Ajax 獲取資料
                this.fetchItems();
            },
            updated() {
                console.log("Vue updated");
                // 當 DOM 更新後重新刷新 selectpicker
                //$('.selectpicker').selectpicker('refresh');
            }
        }).mount('#app');
    </script>
</body>
</html>

子元件的props從父元件接收資料,基本用法

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <!-- 預設沒v-bind給字串值 -->
      <welcome-component message="Hello"></welcome-component> 
      <!-- 給JS值 -->
      <welcome-component :message="'bind Hi'" :age="1+2"></welcome-component>
    </div>
    <script type="importmap">
    {
      "imports": {
        "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
      }
    }
  </script>

  <script type="module">
    import { createApp } from 'vue'
      let welcome = {
        /*VS Code的es6-string-html 擴充套件幫忙高亮html字串*/
        template: /*html*/`<h1> {{ message }} : {{ age }}</h1>`,
        props: ["message","age"]
      };
      var app = createApp({
        components: {
          "welcome-component" : welcome
        },
      });
      app.mount("#app");
    </script>
  </body>
</html>

父元件透過子元件的props傳遞資料,子元件透過$emit傳遞事件給父元件

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <title>Document</title>
</head>

<body>
  <div id="app">
    <input type="text" v-model="name">
    <welcome-component v-bind:user-name="name" v-on:sayhello="onsayhello" v-on:inputchange="childinputchange" >
    </welcome-component>
  </div>
  <script type="importmap">
    {
      "imports": {
        "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
      }
    }
  </script>

  <script type="module">
    import { createApp } from 'vue'
    let welcome = {
      /*子元件的input v-model不可直接修改唯讀的props userName*/
      template: /*html*/`<div>子元件:
                    <label> {{labelText}} </label>
                    <input type="text" v-model="inputText" />
                    <button v-on:click="$emit('sayhello', inputText)">
                  sayhello
              </button>
                    </div>`,
      props: ["userName"],
      data: function () {
        return {
          labelText: " Hello ",
          inputText: this.userName,/*預設值,只執行一次,讀取父元件傳來的userName*/
        };
      },
      watch: {
        userName : function(newValue,oldValue) {
          console.log(`userName newValue:${newValue},userName oldValue:${oldValue}`);
          this.inputText = newValue;
        },
        inputText: function(newValue,oldValue){
          console.log(`inputText newValue:${newValue},inputText oldValue:${oldValue}`);
          this.$emit('inputchange',newValue);

        }
      },
    };

    var app = createApp({
      data() {
        return {
          name: "mary",
        };
      },
      components: {
        "welcome-component": welcome,
      },
      methods: {
        onsayhello: function ( msg ) {
         alert(msg );
        },
        childinputchange:function(childInputText)
        {
          this.name = childInputText;
        }
      },
    });
    app.mount("#app");
  </script>
</body>

</html>

父元件透過物件傳遞參考給子元件,在子元件中編輯 傳遞過來的物件屬性會有嚮應式連動效果,子元件不必像上述一樣使用watch來監聽

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  <title>Document</title>
</head>

<body>
  <div id="app">
    <input type="text" v-model="user.name">
    <welcome-component v-bind:user="user" v-on:sayhello="onsayhello"  >
    </welcome-component>
  </div>
  <script type="importmap">
    {
      "imports": {
        "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
      }
    }
  </script>

  <script type="module">
    import { createApp } from 'vue'
    let welcome = {
      /*子元件的input v-model不可直接修改唯讀的props參考*/
      template: /*html*/`<div>子元件: 
                    <input type="text" v-model="user.name" />
                    <button v-on:click="$emit('sayhello', user)">
                        sayhello
                    </button>
                    </div>`,
                    props: {
                               user : Object         
                           },
      data: function () {
        return {  
        };
      },
      watch: { 
      },
    };

    var app = createApp({
      data() {
        return {
          user:{    name:"myName"   }
        };
      },
      components: {
        "welcome-component": welcome,
      },
      methods: {
        onsayhello: function (user) {
          alert(`this.user.name:${this.user.name},user.name:${user.name}`);
        } 
      },
    });
    app.mount("#app");
  </script>
</body>

</html>

父組件和子組件的輸入框連動嚮應式,組合式API(Composition API)的寫法

<!-- ChildComponent.vue -->
<template>
    <div>
      <h2>子組件</h2>
      <input v-model="childValue" v-on:input="childTextInput" placeholder="在子組件中輸入" />
    </div>
  </template>
   


<script>
import { ref,watch} from "vue";
 
export default {
  props: {
    modelValue: String
  },
  setup(props,context) {  
    let childValue = ref(props.modelValue); 

    const childTextInput=()=>   
    { 
      //呼叫emit函式,把childValue往外傳出去
      context.emit('update-model-value', childValue.value);
    };


    //當父組件的 modelValue 變化時,子組件的 childValue 會同步更新。
    watch(() => props.modelValue,(newVal)=>{ 
     childValue.value=newVal;
    });
  
 
    // 回傳要暴露給模板的變數和函數
    return {
      childValue ,childTextInput
    };
  },
  
};
</script>
<!-- ParentComponent.vue -->
<template>
    <div>
      <h1>父組件</h1>
      <input v-model="parentValue"  placeholder="在父組件中輸入" /> 
    </div>
    <div>
        <h1>子組件</h1>
        <child-component :model-value="parentValue" v-on:update-model-value="updateParentValue" ></child-component>
    </div>
  </template>
  
  <script setup>
  import { ref } from 'vue'; 
  import ChildComponent from './ChildComponent.vue';
   
  let parentValue = ref("parentValue");

  
const updateParentValue = (childValue)=>{ 
    parentValue.value = childValue; 
};  

  </script>

Bootstrap 5 Alert 整合 Vue 3 (使用子元件)
https://grok.com/share/bGVnYWN5_b256d2bf-6d2e-474d-8000-5cdd5d82fe1e

ASP.NET MVC 整合 Vite建立的Vue專案
https://grok.com/share/bGVnYWN5_4e62978a-cbdc-47fe-8c93-621f328a022d

Vue3 Composition API 介紹
https://grok.com/share/bGVnYWN5_90603467-94cf-44a1-88a0-7c7e4071c1d1

<scrip setup>改成setup()的寫法:setup()函式多了 export default 且須回傳資料給setup()函式,可以在ASP.net MVC的視圖中使用setup()函式寫法,<script setup>標籤則是Vue專案專用 
Vue3 組合式Composition API: setup vs <script setup>用法差異
https://grok.com/share/bGVnYWN5_f3d70e9c-7bc3-4148-b784-233ad7ab0a40

使用ref創建基本型別、null、undefined的嚮應式對象;陣列、物件用reactive創建嚮應式對象

 <template>
  <h1> Home </h1>
  <div>
    <p> Employee Name :{{ empName }} </p>
    <p> Age : {{ age }} </p> 
    <p> Is Married : {{ isMarried }} </p>
    <p> Array Value: {{ arry[0] }} </p> <!-- 顯示陣列的第一個值 -->
  </div>
  <div>
    <button @click="buttonClick" >Click me</button>
  </div>
</template> 
<script>
import { ref, reactive } from "vue";

export default {
  setup() {
    let empName = "shadow";
    let age = ref(39);//基本型別
    let isMarried = false;
     let arry = reactive([0,1]);//陣列、物件用reactive創建嚮應式對象
    const buttonClick = () => {
      arry[0]++;//這裡累加1效果 
      age.value++;
    };

    // 回傳要暴露給模板的變數和函數
    return {
      empName,
      age,
      isMarried,
      arry,
      buttonClick
    };
  }
};
</script>

Vue3: watch vs watchEffect
https://grok.com/share/bGVnYWN5_88ab721c-18f8-455d-ba9d-da1490843034

Vue3 defineProps函式,定義物件傳遞範例
https://grok.com/share/bGVnYWN5_511e97e3-cfe7-4405-ae2a-95d308943880

在 ASP.NET MVC 中使用 Vue 3 Router、<script type="importmap">引用vue.js、兩個子組件封裝成ES模組的寫法
https://grok.com/share/bGVnYWN5_e3c3e5c9-99c8-47d7-9164-be38155369bb

我的ASP.net MVC專案整合Vue3的起手式範本
Index.cshtml

<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <title>Vue 應用</title>
</head>
<body>
    <div id="vue-app">
        <div>
            <nav>
                <router-link to="/">首頁</router-link> |
                <router-link to="/about">關於</router-link>
            </nav>
            <router-view></router-view>
        </div>
    </div>

    <script type="text/x-template" id="home-template">
        <div>
            <h1>Home</h1>
            <p>{{ message }}</p>
        </div>
         
    </script>

    <script type="text/x-template" id="about-template">
        <div>
            <h1>About</h1>
            <p>{{ message }}</p>
        </div> 
    </script>

    <script type="importmap">
        {
            "imports": {
                "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js",
                "vue-router": "https://unpkg.com/vue-router@4/dist/vue-router.esm-browser.js",
                "@@vue/devtools-api": "https://unpkg.com/@@vue/devtools-api@6.5.0/lib/esm/index.js"
            }
        }
    </script>
    <script>
         
        const serverRoot = "@Url.Content("~/")";
    </script>
    <script type="module" src="~/Vue-js/app.js"></script>
</body>
</html>

app.js

import { createApp } from 'vue';
import { createRouter, createWebHashHistory } from 'vue-router';

import Home from  "./components/Home.js";  // 引入 Home 組件
import About from "./components/About.js";  // 引入 About 組件
  
// 定義路由
const routes = [
    { path: '/', component: Home },
    { path: '/about', component: About }
];

// 創建路由器實例
const router = createRouter({
    history: createWebHashHistory(),
    routes
});

// 創建 Vue 應用實例並使用路由器
const app = createApp({
    setup() {
        
    }
});
app.use(router);

// 掛載應用到 #vue-app
app.mount('#vue-app');

Home.js

import { ref } from 'vue';


export default {
    setup() { 
        return { message: '歡迎來到首頁' };
    },
    template: '#home-template'
};

About.js

import { ref } from 'vue';

export default {
    setup() {
       
        return { message: '關於的頁面'   };
    },
    template: '#about-template'
};