---
# System prepended metadata

title: Vue.js相關

---

# Vue.js相關
就筆記，邊開發邊寫給自己看的怕之後忘記自己在幹嘛
目前就當作插件筆記，專案做一做發現技術限太雜亂了，後面統一改用element-plus
所以底下插件基本都捨棄了QQ
說不定之後會用到所以筆記先留著
## Vue.js
前端框架，這邊主要是講Vue3的模式
和傳統HTML+JS+CSS拆分的方式不同
Vue2的方式是在各個html引入script並且new Vue寫內容
Vue3的寫法比較是用切分畫面元件(Component)的方式開發

畫面設計我很廢，基本上就是figma大致設計後，靠套版跟手動排layout
然後用元件(Component)拆分的方式開發，畫面舉例就是
![image](https://hackmd.io/_uploads/rJChOKRIT.png)

![image](https://hackmd.io/_uploads/rJdiOY0Up.png)
把畫面拆成多個元件，個別開發組裝，至於粒度要切多小看專案規劃跟個人
https://book.vue.tw/CH2/2-1-components.html  
可以看這，我覺得講蠻清楚的

元件結構是固定的
包含三個區塊，下面一個一個講
```vue=
<Template>
//寫html標籤
</Template>

<Script>
//寫vue內容
</Script>

<Style>
//寫css
</Style>
```
### Template標籤
拿來放基本html標籤，id/class之類的方式都相同
![image](https://hackmd.io/_uploads/HJywht08p.png)
或是<font color="#f00">放其他元件</font>(像這邊是用slot標籤做插槽的方式)，之間有父子元件的關係
![image](https://hackmd.io/_uploads/r1y3nYCUa.png)

![image](https://hackmd.io/_uploads/SyWL0tAU6.png)
其中<font color="#f00"><HeaderLogo /></font>也是另一個元件
![image](https://hackmd.io/_uploads/HJNiAFRU6.png)

![image](https://hackmd.io/_uploads/ByV0AFRLa.png)
如果要使用其他元件
就要用import的方式先==引入元件==(在[Script標籤](###Script標籤)內)

也可以用<font color="#f00">{{propertiesName}}</font>類似放入變數的方式
![image](https://hackmd.io/_uploads/Sk2--cA8p.png)
但一樣需要在[Script標籤](###Script標籤)內設定該變數(data)

Template內也可用vue的指令（Directives）
常用有這幾個
#### v-on
用來做事件觸發
<button <font color="#f00">v-on:click</font>="methodName">
可以簡寫為<font color="#f00">@click</font>
像這樣![image](https://hackmd.io/_uploads/ByoBQc0Ia.png)
#### v-if、v-else-if、v-else
就用來判斷，後面可以放變數(data)或method(但要返回boolean)
```vue=
<p v-if="isVisible">This is visible</p>
<p v-else>This is hidden</p>
```
#### v-for
就迴圈沒什麼好說的，不懂就問gpt
#### v-model
我都是用來綁input標籤，要在script標籤內綁定data(可以直接綁物件)
這樣就不用像傳統方式還要用id取值，雙向綁定可以直接拿變數來用(關於[data](####data))
```vue=
<template>
<input v-model="formData.username" class="inputArea" type="text" placeholder="Enter your username">
<input v-model="formData.password" class="inputArea" type="password"
                            placeholder="Enter your password">
</template>

<script>
export default {
    data() {
            return {
                formData: {
                    username: '',
                    password: '',
                },
            };
        },
    }
</script>
```
#### v-bind
用來綁定標籤屬性，一樣是綁data
```vue=
<a v-bind:href="url">Link</a>
//或簡寫成以下
<a :href="url">Link</a>
```

==v-還有很多種，這邊只提比較熟悉的，後續有用到會再補上==
### Script標籤
用來寫該元件JS的地方，主要是寫Vue組件的多個選項(data、methods...)
<font color="#f00">注意:Vue2和Vue3選項(export default)寫法不同</font>
外層寫法固定都是
```vue=
<script>
import 變數名稱 from '其他組件路徑(絕對/相對)';
export default{
    //各個選項
}
</script>
```
如果要在template中放入其他元件，就需要在這邊import，並且在[components](####components)掛出該變數

說明export default的部分，
包含多種選項
<font color="#f00">注意版本差別</font>
==Vue2:==
[data](####data):初始化數據
[computed](####computed):可以掛載並依照數據變動做實時計算變化
[methods](####methods):建立方法
[props](####props):接收父元件傳來的參數
[mounted](####mounted):數據初始化後依照需求做更動
[components](####components):數據初始化後依照需求做更動
~~其他我不熟悉就不說了XD~~
==Vue3:==
Vue3之後把Vue2 components以外的選項都整合到[setup](###Vue3-setup)內

關於執行順序流程先看看GPT的解釋
![image](https://hackmd.io/_uploads/Syh1dhCUT.png)
其內容主要就是用來導出建立的選項給template模板使用
如下
```vue=
<template>
  <div>
    <p>{{ message }}</p>
    <p>{{ computedMessage }}</p>
    <p>{{ greet() }}</p>
    <p>{{ customProperty }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello from data!'
    };
  },
  computed: {
    computedMessage() {
      return 'Hello from computed!';
    }
  },
  methods: {
    greet() {
      return 'Hello from method!';
    }
  },
  props: {
    customProperty: String
  }
};
</script>
```
可供給模板使用的基本上就是這幾種
接下來說明各種選項作用
#### data
主要是初始化用的，也可以用來設預設值
```vue=
<script>
    data() {
        return {
            status: null,
            info: '',
            formData: {
                username: '',
                password: '',
            },
            isChecked: false,
        };
    },
</script>
```
就當作是初始化建立變數吧，這樣就可以在模板中使用return內的變數
要注意的是，return內可以儲存多個，用','隔開
並且可以儲存物件
還有就是如果要在<script></script>內其他區塊使用該變數要用<font color="#f00">this.變數名</font>不然會抓不到
但Vue3棄用了this，包含[全局變數取法](###Vue3-全局變數取法)都不同

#### mounted
component元件都是先初始化data才跑其他選項
經過data初始化變數後，如果今天data資料是要從api或外部獲得
就會在mounted再賦值給變數，
還有就是需要對DOM元素做處理時會放在mounted
因為mounted主要運行時機是在整個頁面DOM和組件都渲染加載完成後才運行
```vue=
<template>
  <BasicBody>
    <p>{{ user }}</p>
  </BasicBody>
</template>
<script>
export default {
  data() {
    return {
      user: null,
    }
  },
  mounted() {
  // 访问或修改 DOM 元素
  this.$refs.myElement.innerText = 'Updated text';
}
};
</script>
```

#### computed
```vue=
<template>
  <div>
    <p>原始值: {{ originalValue }}</p>
    <p>计算属性: {{ computedValue }}</p>
    <button @click="updateOriginalValue">更新原始值</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      originalValue: 5,
    };
  },
  computed: {
    // 计算属性的 getter
    computedValue() {
      // 这里的计算会基于 originalValue 进行，只有 originalValue 变化时才会重新计算
      return this.originalValue * 2;
    },
  },
  methods: {
    updateOriginalValue() {
      // 更新原始值，触发 computedValue 重新计算
      this.originalValue += 1;
    },
  },
};
</script>
```
當originalValue變動時，computed掛載的computedValue 也會自動計算
當然是也可以另外存變數用事件處理並放上去就是了

#### methods
```vue=
export default {
    data() {
        return {
            isChecked: false,
        };
    },
    methods: {
        forgetPassword() {
            console.log('forgetPassword!');
        },
        check() {
            this.isChecked = !this.isChecked;
        }
    }
}
```
就建立方法==注意要使用this.變數名==才能使用組件內變數
可以給事件使用
```vue=
<button @click="check">Click me</button>
```

#### props
用於父元件傳值給子元件，用<font color="#f00">:變數</font>綁==dataName==
```vue=
//父元件.vue
<template>
  <div>
    <MyComponent :message="parentMessage" :user="parentUser" />
  </div>
</template>

<script>
import MyComponent from './MyComponent.vue';

export default {
  data() {
    return {
      parentMessage: 'Hello from parent!',
      parentUser: { name: 'John' }
    };
  },
  components: {
    MyComponent
  }
};
</script>
```
子元件用props接值
```vue=
<template>
  <div>
    <p>{{ message }}</p>
    <p>{{ user.name }}</p>
  </div>
</template>
<script>
    props: ['message', 'user']
</script>
```
並且可以直接使用<font color="#f00">{{ }}</font>給模板用
接值也可以指定型別和詳細校驗，預設值
```vue=
props: {
  message: String,
  user: {
    type: Object,
    required: true,
    default: () => ({})
  }
}
```
要注意這邊傳值只侷限父子元件如果要跨或是同層要使用[其他方式](###Vuex)

#### components
主要就是用來==在該元件內引入其他元件==
import元件之後，一定要放入這邊才能使用引入的元件
![image](https://hackmd.io/_uploads/rkPoX20L6.png)
在想放的位置放上<變數名稱/ >
![image](https://hackmd.io/_uploads/SJRB3sRU6.png)
像這樣，元件就會被引入作為子元件
除此之外還有一種使用方式是[全局component](###入口(main.js))
可以繼續往下看會提到

### Vue3-setup
vue3之後把上面的選項都整合到這邊了
基本上除了components之外就只要寫setup了
重點就是==不管是變數還是方法都寫在setup內，並且用return給模板使用==
==computed和mounted之類的預先計算加載也都寫在setup內==
以下說明
#### data
```vue=
import { ref } from 'vue';

export default {
  setup() {
    const message = ref('Hello Vue!');
    const count = ref(0);

    return {
      message,
      count,
    };
  },
};
```

#### Methods
```vue=
import { ref } from 'vue';

export default {
  setup() {
    const greet = () => {
      console.log('Greetings!');
    };

    return {
      greet,
    };
  },
};
```

#### Computed 
```vue=
import { computed, ref } from 'vue';

export default {
  setup() {
    const message = ref('Hello Vue!');
    const reversedMessage = computed(() => message.value.split('').reverse().join(''));

    return {
      message,
      reversedMessage,
    };
  },
};
```

#### onMounted (mounted)
==重點是從vue import onMounted方法==
```vue=
//重點是從vue import
import { onMounted } from 'vue';

setup() {
  onMounted(() => {
    console.log('Component is mounted');
  });
}
```

#### props
直接把props當參數傳入
```vue=
export default {
  props: {
    message: String,
  },
  //直接把props當參數傳入
  setup(props) {
    //使用傳入props
    console.log(props.message);
  },
};
```

### Vue3-setup語法糖**
看完上面Vue2改Vue3的寫法後
有更進階的寫法，不在區分不同選項，`<script>`標籤加上==setup==，
直接建立變數或是import就可以直接在模板中使用和呼叫內容，連export default都省了
使用ref之類的vue函式也不用另外引入
`<script setup>`寫法就會自動引入
至於props，因為不用寫setup(props)所以引入不到prop參數
這邊用新函式defineProps拿props的參數
範例如下
```vue=
<template>
  <div>
    <p>{{ message }}</p>
    <button @click="changeMessage">Change Message</button>
    <ChildComponent />
    <p>{{ propsVal }}</p>
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue';
const message = ref('Hello from setup');
const changeMessage = () => {
  message.value = 'New Message';
};
const propsVal = defineProps(['propValue']);
</script>
```

### Style標籤
就寫CSS，沒什麼特別的，一樣是使用id / class寫
唯一要注意就是scope
```vue=
<template>
    <div id='body'>
        <slot></slot>
    </div>
</template>

<style scoped>
#body {
    display: flex;
    flex: 50%;
}
</style>
```
vue因為元件的特性，會有多層或是共用的關係
所以style的部分是大家共用的會互相影響，不只是該元件本身
如果不想影響到其他元件，就要在style標籤加上`<style`<font color="#f00">scoped</font>`></style>`
限制只有該元件本身套用

## Vue專案結構和入口
### 結構介紹
說明完內容結構，來講入口和加載方式
建專案方式網路上都有這邊不細說
![image](https://hackmd.io/_uploads/ByrqHa0U6.png)
這是建立專案預設結構，這邊只提自己有用到的
==assets==放靜態資源(圖片)
==components==是最主要的，放各個元件(component)
==App.vue==是Vue應用程式的根節點
==main.js==專案入口處，初始化 Vue 應用程式
==package.json==裡面包含該專案所有插件版號和專案相關資訊，像這邊自己指定了port號
如果clone別人專案，可以進入專案package後用<font color="#f00">npm install</font>會自動捕捉這裡安裝所有需求插件
![image](https://hackmd.io/_uploads/SyC1wTRLa.png =50%x)

==vue.config.js==配置vue cli相關(devServer、webpack)這邊是配置了proxy
![image](https://hackmd.io/_uploads/ry1IuTCUp.png =50%x)

### 入口(main.js)
主要是從==main.js==開始
![image](https://hackmd.io/_uploads/SJSQYTRLp.png)
基本就是這樣，從vue中引入createApp並且把App.vue這個元件的根結點拿來加載初始化頁面
最後會加載進public/index.html的#app
![image](https://hackmd.io/_uploads/SJz2Ka08p.png)
如果今天有用到各種vue插件
就是要在==main.js==引入並使用，如下
![image](https://hackmd.io/_uploads/HyDm9aAUa.png)
app.use(插件變數名)
再來前面在說[components](####components)標籤時有提到，全局引入的方式
就是在main.js引入需要的component
並且用app.component(key, component);放入全局component
後續就可以在任何元件內隨意呼叫<key/ >
![image](https://hackmd.io/_uploads/rJG-26AIT.png)
呼叫
![image](https://hackmd.io/_uploads/ry343T0LT.png)
像這邊使用就不用在該元件內另外import


### Vue3-全局變數取法
Vue3中棄用this，所以沒版法用`this.$route`或是`this.$axios`的方法拿到main.js配置的全局組件
取用方式變成先從vue中引用getCurrentInstance方法，拿到全局的組件集合
```vue=
//固定寫法是這樣
const { proxy } = getCurrentInstance();//獲取全局組件
```
後續就用==proxy取代this==
```vue=
info.value = proxy.$route.query.message;
proxy.$router.push({ name: 'home' });
const response = await proxy.$axios.post(config.api.client.login, formData.value);
```


## 發起請求(Axios)
### 一般配置
其實axios不是vue的一部分，發起請求也有很多方式，只是這邊是選擇用axios，也沒什麼原因，因為我只會用這個，也可以選擇用fetchApi或其他方式，看這
https://eyesofkids.gitbooks.io/javascript-start-from-es6/content/part4/ajax_fetch.html

以下針對axios說明
先npm安裝
```bash=
npm install axios
```
並隨意創建.js文件(這邊命名文件用Axios.js做範例)，
寫入import axios from 'axios';
並且創建Axios之後export導出
```javascript=
import axios from "axios";
const instance = axios.create({
    baseURL: '',
    timeout: 10000,
    headers: {
        'Content-Type': 'application/json',
    },
})

export default instance;
```
這邊會獨立創建Axios.js原因是因為後續還有可能配置axios相關配置，
就統一文件不要和別的插件混再一起
再來把Axios.js引入[main.js](###入口(main.js))
給全域使用，後續就直接使用
==this.$axios==(Vue2取用方式)就好
==[Vue3取用方式](###Vue3-全局變數取法)==
![image](https://hackmd.io/_uploads/Sy7BH0RUa.png =70%x)
呼叫方式(任何component都可)
![image](https://hackmd.io/_uploads/ByeqrAAUp.png =90%x)
### Axios攔截器(interceptors)
配置關於請求攔截器，類似Filter，針對請求和響應結果做處理
以下是Axios完整配置，主要是針對用戶Token做處理
詳細針對需求自己配置，用不到不配也行
```javascript=
import axios from "axios";
import router from './RouterConfig'
const instance = axios.create({
    baseURL: '',
    timeout: 10000,
    headers: {
        'Content-Type': 'application/json',
    },
})

instance.interceptors.request.use(
    (config) => {
        const matchedRoute = router.router.find(route => config.url.includes(route.path));
        if(matchedRoute.meta && matchedRoute.meta.requiresAuth && matchedRoute.meta.requiresAuth === true){
            const token = localStorage.getItem('token');
            if(token){
                config.headers['Authorization'] = `Bearer ${token}`;
            }
            const refreshToken = localStorage.getItem('refreshToken');
            if(refreshToken){
                config.headers['X-Refresh-Token'] = refreshToken;
            }
        }
        return config;
    }
)

instance.interceptors.response.use(
    (response) => {
        const token = response.headers['authorization'];
        if(token){
            localStorage.setItem('token', token.replace('Bearer ', ''))
        }
        console.log(response);
        const refreshToken = response.headers['x-refresh-token'];
        if(refreshToken){
            localStorage.setItem('refreshToken', refreshToken)
        }
        return response;
    }
);

export default instance;
```

## vue-router
### router介紹
Vue和傳統的網頁模式切換頁面不同，屬於[單頁應用(SPA)](https://zh.wikipedia.org/zh-tw/%E5%8D%95%E9%A1%B5%E5%BA%94%E7%94%A8)
簡單來說就是只使用一個頁面，透過刷新頁面內容(components元件)來實際運行
vue-router是官方的路由器，用來協助切換路由和畫面上的各個元件，如果要切換都是透過router

### router配置
先按裝
```bash=
npm install vue-router
```
這邊一樣用獨立文件的方式引入
創建任意名稱.js(這邊用RouterConfig.js為範例)
引入vue-router
```javascript=
import { createRouter, createWebHistory } from 'vue-router';

//這邊是把相關路徑存成物件變數
const router = [
    {
        path: '/home',
        name: 'home',
        components: {
            //這邊使用() => import(component)的方式做懶加載
            //也可以在最上面import再使用變數import
            header: () => import('../components/header/HalfHeader.vue'),
            body: () => import('../components/body/HomePage.vue'),
        },
        meta: { requiresAuth: true },
    },
]

//創建Router，放入createWebHistory()和routes(路徑配置)
const r = createRouter({
    history: createWebHistory(),//儲存歷史紀錄用，可以使用this.$router.back()回上一頁
    routes: router,//配置路由
})

//這邊是配置在每次路由導向前會做什麼事，可不做看需求
//這邊做的是驗證所有元件如果有配置權限需求就必須登入，未登入則導向到'/login'登入頁
r.beforeEach((to, from, next) => {
    const requiresAuth = to.matched.some(router => router.meta.requiresAuth) === true;
    const isNotAuthenticated = !isAuthenticated();
    if ((requiresAuth && isNotAuthenticated)) {
        next('/login');
    } else {
        next();
    }
});

//不重要，只是驗證是否登入
function isAuthenticated() {
    const user = localStorage.getItem('user');
    return user !== null && user !== undefined;
}

//最後export上面創建的創建Router變數，如果需要導出多個變數可以用{p1,p2}
//使用方式是import此元件並用變數.導出名稱(在這邊是r和router)使用，可以看main.js範例
export default {r, router};
```
其中routes內配置所有components和對應路徑屬性
主要有四個屬性
```vue=
{
        path: '/home',
        name: 'home',
        components: {
            header: () => import('../components/header/HalfHeader.vue'),
            body: () => import('../components/body/HomePage.vue'),
        },
        meta: { requiresAuth: true },
    },
```
==path==:路徑，url會顯示該路徑，<font color="#f00">轉跳可以靠path導向</font>
==name==:該路由節點名稱，<font color="#f00">轉跳可以靠name導向</font>
==components==:配置要加載的元件，可以拆分為一個頁面多個元件，分別命名
==meta==:設定路由的變數，可以做到方便區分路由權限/是否需驗證之類的事

想像routes內每個路徑是一個頁面，設定的component會被投射到APP.vue這個根結點
像這邊是配置每個頁面拆分為兩個component(header和body)
如果沒有要拆分，直接寫==components:對應元件== 就好
以下是APP.vue
```vue=
<template>
  <div id="container">
    <router-view name="header"/>
    <router-view name="body"/>
  </div>
</template>

```
name要對應到components內的自訂名稱，
如果沒有拆分就直接寫<router-view>就會把對應元件投射到這
==配置完router後記得要讓main.js使用這個元件，不然後面調用不到==
![image](https://hackmd.io/_uploads/rJItWk1D6.png)
這邊.use(用router.r匹配到導出的屬性名稱r)

### 透過router轉跳頁面
<font color="#f00">要先配置完router並且配置給main.js</font>
主要使用有兩種方式
==在template中可以配置==
```vue=
//匹配路由的name
<router-link :to="{ name: 'home' }">
    <img src="@/assets/icon/svg/loginPage/Register.svg">
</router-link>
//匹配路由的path
<router-link to="/home">
    <img src="@/assets/icon/svg/loginPage/Register.svg">
</router-link>
```
元件初始化掛載渲染後，就會變成`<a>`標籤導向到路由指向的路徑，點擊就會重新渲染頁面

==在事件中配置==
```vue=
methods: {
    doReset() {
        this.$router.push({ name: "messagePage" });
    }
}
```
用
==this.$router==(Vue2)
[Vue3取法](###Vue3-全局變數取法)
調用配置的路由，用.push的方式導向頁面，可以[用query攜帶參數](###query)
這邊只能匹配路由對應的name

## 非props傳參
上面有提到，父子元件間可以依靠props傳參，非父子元件的話接下來說明
詳細可看這
https://hackmd.io/@CynthiaChuang/Vue-Router-Passing-Props-to-Route-Components
傳參方式有很多種，這邊只提有用到的，==Vuex是官方推薦大型專案使用==
### query
透過[router.push](###透過router轉跳頁面)攜帶參數並轉跳
==這邊的參數會顯示在url上類似Get請求==
```vue=
methods: {
    doReset() {
            this.$router.push({ name: "messagePage" , query: {myData:'yourDataHere'},
      });
    }
}
```
並且路由修改
```javascript=
const routes = [
  {
    path: '',
    name: '',
    component: '',
    props: true, // 開用 props 接收
  },
];

//或是配在路徑上
const routes = [
  {
    path: '/home:myData',
    name: '',
    component: '',
  },
];

```

接收方式
```vue=
mounted() {
//query後字段命名要相同名稱(data)
  const receivedData = this.$route.query.myData;
}
```
### params
~~原先有，類似上面query的傳參方式，目前是已廢棄無法使用~~
https://github.com/vuejs/router/blob/main/packages/router/CHANGELOG.md#important-note
看4.1.4
![image](https://hackmd.io/_uploads/r1RzByyDp.png)

### Vuex
因為不想讓參數顯示在url上，params又被廢棄，最後非父子元件傳參都使用這個方式
Vuex 是 Vue.js 官方的狀態管理庫，使用範圍蠻廣的，我是直接拿他當本地緩存或是==用來傳參==

安裝插件
```bash=
npm install vuex
```
配置，建立任意名稱.js(這邊用Store.js作為範例)，後續一樣要配置給main.js
```javascript=
import { createStore } from 'vuex';

export default createStore({
    state: {
        dataName: '',
    },
    mutations: {
        setDataName(state, data) {
            state.dataName = data;
        },
    },
    actions: {
        setDataName({ commit }, data) {
            commit('setDataName', data);
        },
    },
});
```
==state==:配置變數名稱，要配置了才能儲存在store內
==mutations==:就是更動state的方法，<font color="#f00">主要mutations是同步的</font>
==actions==:就是更動state的方法，<font color="#f00">主要actions是非同步的</font>
依照場景不同選擇使用
還有其他核心，這邊只提有用到的，詳細可以看  
https://vuex.vuejs.org/zh/guide/#%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84-store

配置完後引入==main.js==
![image](https://hackmd.io/_uploads/r1K__kkP6.png)

後續使用方式
```vue=
<template>
  <div id="app">
      //使用就是呼叫$store.state.屬姓名
    <p>Count: {{ $store.state.dataName }}</p>
    <button @click="increment">Increment</button>
    <button @click="asyncIncrement">Async Increment</button>
  </div>
</template>

<script>
export default {
  methods: {
    increment() {
        //用mutations更改內容
      this.$store.commit('setDataName');
    },
    asyncIncrement() {
        //用actions更改內容
      this.$store.dispatch('setDataName');
    },
  },
};
</script>
```

## Vee-Validate==4==(表單驗證)
版本間差異蠻大，這邊只說4.X.X
官方文檔:
https://vee-validate.logaretm.com/v4/guide/global-validators
一個針對Vue.js的表單驗證套件，定義好表單驗證的語系(i18n)、規則後
可以用類似建立一般form表單的方式指定表單Type和指定驗證規則
如果發生錯誤，可以用類似label的方式直接在指定位置放入對應欄位的標籤，自動生成錯誤信息
從必填、長度到正則(Regex)都能驗證，也可以自訂名稱和規則
![image](https://hackmd.io/_uploads/rJ9T0izPa.png)
先安裝插件
```shell=
#插件本人
npm install vee-validate

#驗證規則
npm install @vee-validate/rules

#i18n庫(用不用看個人, 如果沒有語系需求可以直接自訂返回的錯誤信息)
npm install @vee-validate/i18n
```

在來就是配置使用vee-validate，有兩種方式(全域/局部)大同小異
我是直接配全域，這樣之後要用就直接使用就好，這邊也不提局部需要就網上查
首先老樣子，開一個新檔案.js(這邊範例為ValidateConfig.js)
```javascript=
// 引入VeeValidate 元件跟功能
import {
    Field, Form, ErrorMessage, defineRule, configure,
} from 'vee-validate';
// 引入VeeValidate 的驗證規則
import * as AllRules from '@vee-validate/rules';
// 引入VeeValidate 的 i18n 功能
import { localize, setLocale } from '@vee-validate/i18n';

//這部分作用是找到使用者瀏覽器預設語系
const getUserLocale = () => {
    let userLanguage = navigator.language || navigator.userLanguage;
    let userLocale;
    if (userLanguage.startsWith('zh')) {
        userLocale = 'zh_TW';
    } else if (userLanguage.startsWith('en')) {
        userLocale = 'en';
    } else if (userLanguage.startsWith('es')) {
        userLocale = 'es';
    } else if (userLanguage.startsWith('fr')) {
        userLocale = 'fr';
    } else {
        userLocale = 'en';
    }
    return userLocale;
};
let userLocale = getUserLocale();

//動態利用使用者語系載入需要的i18n語言包
import(`@vee-validate/i18n/dist/locale/${userLocale}.json`)
    .then((locale) => {
        configure({
            //設定使用語系對應default errorMessage
            generateMessage: localize({ [userLocale]: locale.default}),
            validateOnInput: true,
        })
        setLocale(userLocale)
    })
    .catch(error => {
        console.error('Failed to load language file:', error);
    });

//把所有規則包的驗證規則都放進defineRule(就當作是驗證器吧，自定義驗證規則也是寫在這裡面)
//這邊就是把預設的驗證規則都寫入
Object.keys(AllRules).forEach((rule) => {
    defineRule(rule, AllRules[rule]);
});

//主要就是靠這三個部分，
//Form就是Form、
//Field為input欄位、
//ErrorMessage則是驗證錯誤時，錯誤信息顯示的位置(預設為<span>)
export default { Field, Form, ErrorMessage };
```
然後是自定義驗證規則的方式
在==任何==想自定義的地方(component就是寫在`<script>`內)，
或是寫在全域都行(像這邊就是寫在ValidateConfig.js)
```vue=
//引入defineRule
import { defineRule } from 'vee-validate';

defineRule('自定義規則名稱', (value) => {
    if(value ><= '驗證規則' || 'regex'.test(value)){
        //驗證通過return true;
    }
    
    //如果return false就是會使用預設語言包的defalseMessage當錯誤信息
    return false;
    
    //如果return String就是把返回字串當錯誤信息
    return "somethings wrong"
})
```
可以簡化
```javascript=
const createValidationRule = (regex, errorMessage) => (value) => regex.test(value) || errorMessage;
// 自定義規則(規則名稱, 驗證規則(包含錯誤返回信息))
defineRule('atLeastOneLowercase', createValidationRule(/.*[a-z].*/, 'Password must contain at least one lowercase letter.'));
defineRule('atLeastOneUppercase', createValidationRule(/.*[A-Z].*/, 'Password must contain at least one uppercase letter.'));
defineRule('atLeastOneNumber', createValidationRule(/.*\d.*/, 'Password must contain at least one number.'));
defineRule('noSpecialChars', createValidationRule(/^[^\s!@#$%^&*()_+={}[\]:;<>,.?~\\/-]+$/, 'Password must not contain special characters.'));
```
然後defineRule的規則是主要吃全預訂定的規則(寫在ValidateConfig.js內的)
如果是寫在個別component就只能在該component內使用
==如果規則名稱同名會互相覆蓋只留最後的==

以上就完成配置的部分
配置完後這邊是放入main.js成全域元件
![image](https://hackmd.io/_uploads/H1uhKhGvT.png)
![image](https://hackmd.io/_uploads/SyYpKnGwa.png)
後續使用就是直接`<VForm><VField><ErrorMessage>`
    
實際使用
```vue=
//建立form表單, @submit綁表單，會自動驗證所有規則通過才會提交
<VForm @submit="handleSubmit">

    //建立input欄位，基本屬性一樣可以使用placeholder之類的屬性
    //一定要指定name, rules是欄位的驗證規則，放入規則名稱(自定義或是預設規則)，用'|'隔開
    //規則驗證是按照順序，放前面的會先驗證所以提示字會以較前面的為優先
    <VField name="username" rules="required|min:6|max:20" />
    
    //如果驗證出錯，就會在這顯示errorMessage，要靠name綁定input欄位的name
    //as後面可以放要使用什麼標籤包裝，預設是<span>
    //像這邊設定最終結果就是<p>errorMessage</p>
    <ErrorMessage name="username" as="p"/>
    
    //就submit
    <button type="submit" />
</VForm>             
```

這裡只簡單使用，還有更多詳細方式去看官方文檔

## Bootstrap
結合Vue3使用，原先是要使用Bootstrap vue，但不知道為什麼會顯示版本問題
後面就沒解了直接改用原生Bootstrap

一樣先安裝插件(bootstrap和popperjs)
```shell=
npm install bootstrap @popperjs/core
```

然後在main.js引入
```javascript=
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
import 'bootstrap';
```

接下來就可以直接在元件中使用相關標籤屬性
```vue=
<template>
    <button v-bind:data-bs-toggle="'tooltip'" v-bind:title="'This is a tooltip'" data-bs-placement="bottom">Hover me</button>
</template>

<script setup>
import { onMounted} from 'vue';
import { Tooltip } from 'bootstrap'
//手動初始化Tooltip，不然有可能遇到bootstrap標籤的title屬性和原生html的title屬性衝突
onMounted(() => {
  new Tooltip(document.body, {
      selector: "[data-bs-toggle='tooltip']",
    })
});
</script>
```
效果如下
![image](https://hackmd.io/_uploads/S1-6WpDv6.png)

## vue-chartjs(折線圖、圓餅圖、統計圖表)
僅簡單說明
詳細看官方文檔
https://vue-chart-3.netlify.app/guide/#introduction
這邊是用來做折線圖
先裝插件，vue-chartjs是依賴於chartj所以都要裝
```shell=
npm install vue-chartjs chart.js
```

然後配置預設的折線圖元件(這邊以LineChart.vue為範例)
```vue=
<template>
  <Line :data="data" :options="options" />
</template>

<script>
import { Line } from "vue-chartjs";
//導入所有模塊
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend,
} from "chart.js";

ChartJS.register(
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend
);

export default {
  components: { Line },
  props: ["data", "options"],
};
</script>
```
創建完後，後續使用方式如下
```vue=
<template>
    <div style="flex: 3; display: flex;">
      <div class="shadowBlock lineChart">
      //重點是底下<LineChart>
        <LineChart :data="data1" :options="chartOptions" />
      </div>
    </div>
</template>
<script>
//引用剛剛配置的元件
import LineChart from "@/components/body/LineChart.vue";
//要統計的資料內容
const data1 = {
  labels: getLabels(),//X軸內容, 這邊是直接用當前日期回推30天
  datasets: [
    {
      label: "Assets growing",//圖樣標題
      backgroundColor: "#f87979",//點顏色
      data: generateRandomData(30),//資料內容
    },
  ],
};
//設定
const chartOptions = {
  responsive: true,//自動調整大小以適應其容器的尺寸變化
  maintainAspectRatio: false,//允許圖表在改變大小時不維持原始的寬高比
};
</script>
```

顯示結果如下
![image](https://hackmd.io/_uploads/By9BUz_Pa.png)

## vue3-loading-overlay(讀取中轉圈)
![image](https://hackmd.io/_uploads/SJKRPz2Pa.png)
呼叫api讀取時可以擋畫面
在main.js加載，放全域
```javascript=
import Loading from 'vue3-loading-overlay'
import 'vue3-loading-overlay/dist/vue3-loading-overlay.css'// Import stylesheet

app.component('VLoading', Loading)
```

後續使用
![image](https://hackmd.io/_uploads/Bye4dznwp.png)

```vue=
<template>
    //擺在template任何位置
    <VLoading :active="isLoading"></VLoading>
</template>
```
重點是 ==:active="isLoading"== 
在需要加載讀取的地方設置boolean true
要關閉就是false
![image](https://hackmd.io/_uploads/Hkgh_M3wa.png)

![image](https://hackmd.io/_uploads/rkvRuGhPp.png)
簡單使用
