# 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) 簡單使用