使用Vue.js與Vite設計購物網站-1
作者:許薰尹
精誠資訊/恆逸教育訓練中心資深講師
這篇文章將介紹如何使用Vue.js與Vite設計一個簡易的購物網站,並整合了多個工具與插件,包括 Vue Router、Pinia、Element Plus 等,逐步說明從專案建立到功能實現的過程。
我們將從使用 Vite 建立專案開始,說明如何安裝 Vue Router 和 Pinia 插件,並介紹基於 Vue 3 的 UI 框架Element Plus,透過它提供豐富的組件如按鈕,結合其樣式檔案,提升網站視覺效果與開發效率。在狀態管理方面,使用 Pinia 取代傳統的 Vuex,提供輕量且直觀的全域狀態管理,以管理購物車商品。
組件UI設計部分,使用 Element Plus 的卡片元件展示商品圖片、名稱、價格。同時使用側邊欄顯示購物車商品,並透過 Element Plus 的對話視窗提升使用者的互動體驗。
建立專案
要使用Vite建立專案,可直接在作業系統命令提示字元,或開啟Visual Studio Code的「Terminal」視窗下指令,參考以下語法:
1 | npm init vue@latest |
輸入上述指令之後,將會自動安裝並執行Vue官方Vue project scaffolding 工具:「create-vue」,接下來會出現一些對話,以讓你命名專案,並詢問是否加裝插件(plugin),在此我們選取加裝「Router (SPA development) 」與「Pinia」插件,請參考下圖所示:
按下鍵盤「Enter」鍵之後稍等一下,專案就建立完成了,此時可以看到以下畫面:
依照命令提示字元視窗最後印出的指示,我們在命令提示視窗輸入以下指令,使用「cd」切換到「my-shopping」專案所在資料夾:
1 | cd my-shopping |
在命令提示視窗輸入以下指令,安裝相依插件:
1 | npm install |
在命令提示視窗輸入以下指令,啟動開發階段的網站伺服器,並執行該網站首頁:
1 | npm run dev |
開發階段的網站伺服器會動態賦予網站一個埠號,以本例來說,接聽在「5173」埠,請參考下圖所示:
開啟瀏覽器,根據上個步驟,輸入以下網址與埠號「http://localhost:5173/」就可以看到範本網站。
要停止執行網站,只要在命令提示字元輸入「CTRL+C」組合鍵即可。
Element Plus 插件
Element Plus 是一個以Vue 3為基礎的UI 框架,提供了一組豐富的 UI 組件,能夠幫助開發者快速構建現代化的網站應用程式。Element Plus 是 Element UI 的升級版,專為 Vue 3 設計,並且完全使用 TypeScript 編寫,提供了更好的型別支援和開發體驗。更詳細的說明請參閱官網「https://element-plus.org/en-US/」的說明。
Element Plus 提供了多種常用的 UI 組件,如按鈕、表單、表格、對話框、通知等,這些組件都經過精心設計和美化,具有良好的可用性和一致的外觀風格。開發者可以通過簡單的配置和叫用API,快速將這些組件加到自己的網站應用程式之中,從而提高開發效率。
此外,Element Plus 還提供了豐富的客製化功能,開發者可以根據需求自行定義組件的樣式,以滿足不同項目的設計要求。
安裝「Element Plus」插件
在Visual Studio Code「Terminal」視窗中,切換目前工作路徑到專案資料夾,輸入以下指令,安裝「npm i element-plus」插件:
npm i element-plus
在專案「main.js」檔案之中加入以下「import」程式碼,「from」關鍵字後方指明要匯入「element-plus」插件,並且引入其樣式檔案 「element-plus/dist/index.css」。最後使用「createApp」函式建立的 Vue 應用程式實例 app之「app.use」方法將Element Plus 整合到應用程式中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import { createApp } from 'vue' import { createPinia } from 'pinia' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue' import router from './router' const app = createApp( App ) app.use( createPinia() ) app.use( ElementPlus ) app.use( router ) app.mount( '#app' ) |
使用Pinia管理狀態
「Pinia」是一個用於 Vue.js 應用程式的狀態管理程式庫。它的設計目的是提供一個簡單且直觀的方式來管理應用程式中的全域狀態,讓所有的組件共享。「Pinia」的靈感來自「Vuex」,但它更加輕量化且易於使用。以下是「Pinia」的一些主要用途:
- 集中管理狀態:「Pinia」允許你在一個集中式的Store中管理應用程式的狀態,這樣可以避免在不同組件之間傳遞資料的麻煩。
- 響應式狀態:「Pinia」的狀態是響應式的,這意味著當狀態改變時,所有相依該狀態的組件都會自動更新。
- 模組化:「Pinia」支援模組化,你可以將狀態分割成多個模組,每個模組負責管理應用程式的一部分狀態。這樣可以使程式碼更加結構化和易於維護,以及重複使用。
- 開發者工具支援:「Pinia」���「Vue Devtools」工具完美整合,提供了強大的開發者工具支援,使得測試和檢查狀態變得更加容易。
- 簡單的 API:「Pinia」提供了一個簡單且直觀的 API,使得學習和使用變得非常容易。你只需要定義狀態、行為和「getters」,就可以開始使用 Pinia 來管理你的應用程式狀態。
建立Pinia Store儲存產品資料
在專案「src」-「stores」資料夾建立「products.js」檔案,在檔案中加入以下程式碼,用來儲存產品資料:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | import { defineStore } from 'pinia' export const useProductStore = defineStore( 'products' , { state : () => ( { products : [ { id : 1, name : 'iPhone 15 Pro' , price : 34900, description : '最新iPhone,搭載A17 Pro晶片' , }, { id : 2, name : 'MacBook Air M2' , price : 39900, description : '搭載M2晶片的輕薄筆電' , }, { id : 3, name : 'iPad Air' , price : 19900, description : '功能強大的平板電腦' , }, { id : 4, name : 'AirPods Pro' , price : 7990, description : '主動降噪無線耳機' , }, { id : 5, name : 'iPad mini' , price : 16900, description : '輕巧可攜的8.3吋平板' , }, { id : 6, name : 'Apple Watch Series 9' , price : 14900, description : '智能手錶,健康監測' , }, { id : 7, name : 'AirPods Max' , price : 18900, description : '頂級無線耳罩式耳機' , }, { id : 8, name : 'Mac Studio' , price : 59900, description : '強大的桌上型電腦' , }, { id : 9, name : 'Studio Display' , price : 54900, description : '27吋5K視網膜顯示器' , }, { id : 10, name : 'Magic Keyboard' , price : 3990, description : '無線藍牙鍵盤' , } ] }), getters: { getAllProducts: ( state ) => state.products, getProductById: ( state ) => ( id ) => state.products.find( p => p.id === id ) } }) |
這段程式碼使用了Vue.js 的狀態管理庫「Pinia」插件,定義一個名為「useProductStore」的「store」用來儲存產品狀態資訊。
範例程式先從「Pinia」中匯入了「defineStore」函式,這個函式用來定義一個新的 store。接著,使用「defineStore」定義了一個名為「useProductStore」的 store,並給它指定了一個名稱「products」。
在這個 store 中,定義了一個「state」函式,這個函式回傳一個包含產品資訊的物件陣列。每個產品物件包含「id」、「name」、「price」、「description」和「image」等屬性,這些屬性描述了產品的基本資訊。
此外,這個 store 還定義了兩個「getters」。第一個「getter」是「getAllProducts」,它回傳所有的產品資訊。第二個「getter」是「getProductById」,它接受一個「id」作為參數,並回傳id 相符的產品資訊。
建立Pinia Store儲存購物車資料
在「stores」資料夾建立一個「cart.js」檔案,加入以下程式碼定義了一個名為「useCartStore」的 Pinia store,用於管理購物車的狀態、計算屬性和操作方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | import { defineStore } from 'pinia' export const useCartStore = defineStore( 'cart' , { state : () => ( { items : [] }), getters : { cartItems : ( state ) => state.items, total : ( state ) => state.items.reduce( ( sum, item ) => sum + item.price * item.quantity, 0 ), itemCount : ( state ) => state.items.reduce( ( count, item ) => count + item.quantity, 0 ) }, Actions : { addToCart( product ) { const existingItem = this .items.find( item => item.id === product.id ) if ( existingItem ) { existingItem.quantity++ } else { this .items.push( { id : product.id, name : product.name, price : product.price, image : product.image, quantity : 1 } ) } }, removeFromCart( productid ) { const index = this .items.findIndex( item => item.id === productid ) if ( index > -1 ) { this .items.splice( index, 1 ) } }, updateQuantity( productId, quantity ) { const item = this .items.find( item => item.id === productid ) if ( item ) { if ( quantity > 0 ) { item.quantity = quantity } else { this .removeFromCart( productid ) } } }, clearCart() { this .items = [] } } }) |
程式說明如下,首先,「state」函式回傳一個物件其「items」屬性初始狀態是一個空的陣列,這個陣列用來存放購物車中的商品。
接著,「getters」中定義了以下計算屬性:
- 「cartItems」回傳回 state 中的 items 屬性,包含購物車中的所有商品。
- 「total」 計算購物車中所有商品的總價。使用「reduce」方法將「items」陣列中每個項目的價格乘以數量,並將結果加到「sum」中,最終回傳總價。
- 「itemCount」計算購物車中所有商品的總數量。使用「reduce」方法將「items」陣列中項目的數量加到「count」中,最終回傳總數量。
「actions」定義了一些操作方法:
- 「addToCart(product)」 方法用於將商品加入購物車。如果商品已經存在,則增加其數量;如果不存在,則將其新增到購物車中。
- 「removeFromCart(productId)」 方法用於從購物車中移除指定的商品。它首先找到商品在「items」陣列中的索引,然後使用 splice 方法將其移除。
- 「updateQuantity(productId, quantity) 」方法用於更新購物車中指定商品的數量。如果數量大於零,則更新數量;如果數量小於或等於零,則將商品從購物車中移除。
- 「clearCart()」 方法用於清空購物車,將 items 陣列清空。
設計組件顯示產品資料
在「Components」資料夾加入「ProductCard.vue」組件,在組件中加入以下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | <template> <!-- 使用 Element Plus 的卡片組件 --> <el-card class = "product-card" :body-style= "{ padding: '0px' }" > <!-- 顯示產品圖片,並註冊error事件處理圖片載入錯誤 --> <img :src= "imageUrl" class = "image" @error= "handleImageError" /> <!-- 產品資訊區域 --> <div class = "product-info" > <!-- 顯示產品名稱 --> <h3> {{ product.name }} </h3> <!-- 顯示產品價格,並格式化為千分位 --> <p class = "price" > NT$ {{ product.price.toLocaleString() }} </p> <!-- 顯示產品描述 --> <p class = "description" > {{ product.description }} </p> <!-- 加入購物車按鈕, 並註冊click事件將產品加入購物車--> <div class = "button-container" > <el-button type= "primary" @click= "addToCart" > 加入購物車 </el-button> </div> </div> </el-card> </template> <script setup> import { ref , onMounted } from 'vue' // 匯入 Vue 的 ref 和 onMounted 函式 import { useCartStore } from '../stores/cart' // 匯入購物車的 store import { ElMessage } from 'element-plus' // 匯入 Element Plus 的ElMessage組件 const imageUrl = ref ( '' ) // 定義圖片 URL 的 ref // 定義組件的 props const props = defineProps( { product : { type: Object, required: true } } ) const cartStore = useCartStore() // 使用購物車的 store // 在組件載入時初始化圖片 onMounted( () => { imageUrl.value = props.product.image // 設置圖片 URL 為產品的圖片 } ) // 處理圖片載入錯誤 const handleImageError = () => { // 使用 Picsum Photos 生成隨機圖片,固定尺寸 400x300 const randomId = Math.floor( Math.random() * 1000 ) imageUrl.value = `https: //picsum.photos/400/300?random=${randomId}` // 設置隨機圖片 URL } // 新增產品到購物車 const addToCart = () => { cartStore.addToCart( props.product ) // 叫用 store 的方法將產品加到購物車 ElMessage.success( '已加入購物車' ) // 顯示成功消息 } </script> <style scoped> .product-card { width: 300px; margin: 10px; transition: transform 0.3s; display: flex; flex-direction: column; justify-content: space-between; height: 100%; } .product-card:hover { transform: translateY(-5px); } .image { width: 100%; height: 200px; object -fit: cover; display: block; } .product-info { padding: 14px; flex-grow: 1; display: flex; flex-direction: column; justify-content: space-between; } .product-info h3 { margin: 0; font-size: 18px; margin-bottom: 8px; } .price { font-size: 24px; color: #FF5722; font-weight: bold; margin: 8px 0; } .description { color: #666; font-size: 14px; margin: 8px 0; flex-grow: 1; } .button-container { text-align: right; margin-top: 12px; } .el-button { background-color: #409EFF; border-color: #409EFF; color: #fff; } .el-button:hover { background-color: #66b1ff; border-color: #66b1ff; } </style> |
使用vue-router設計路由
「vue-router」是 Vue.js 的官方路由管理器,用於建立單一網頁應用程式(SPA)。它允許開發者定義應用程式中的路由,並將這些路由對應到組件。以下是 vue-router 的一些主要特點:
- 動態路由:可以根據 URL 動態地載入不同的組件。
- 巢狀式路由:支持在路由中建立巢狀式的子路由,這對於建立複雜的應用程式非常有用。
- 路由參數:可以在路由中傳遞參數,這使得 URL 更加靈活和有意義。
- 歷史模式和Hash模式:支持援HTML5 的歷史模式和Hash模式。
修改「router」資料夾中的「index.js」檔案程式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import { createRouter, createWebHistory } from 'vue-router' import ProductList from '../views/ProductList.vue' const router = createRouter( { history : createWebHistory( import.meta.env.BASE_URL ), routes : [ { path : '/' , name : 'home' , component : ProductList } ] } ) export default router |
從 vue-router 模組中匯入「createRouter」和「createWebHistory」這兩個函式。「createRouter」用於建立路由實例,而「createWebHistory」用於設定路由使用歷史模式,使應用程式可以使用瀏覽器的History API 來管理路由。
接著,從 「../views/ProductList.vue」路徑匯入「ProductList」組件。這個組件將會作為路由對應的組件。
然後,使用「createRouter」函式來建立一個路由實例(Instance)。這個實例包含兩個主要屬性:「history」和「routes」。「history」屬性使用「createWebHistory」函式來設定,並傳入「import.meta.env.BASE_URL」作為基礎 URL。「routes」屬性是一個陣列,定義了應用程式中的路由。每個路由是一個物件,包含「path」、「name」和「component」這三個屬性。
在這個程式中,只有設定一個路由,路徑為 「/」,名稱為「home」對應的組件是「ProductList」。這意味著當使用者存取根路徑 「/」時,應用程式會顯示「ProductList」組件的內容。
最後,將這個路由實例匯出,這樣可以在應用程式的其他部分使用這個路由設定。
設計版面配置
「App.vue」是專案中的根組件,主要負責定義頁面的版面配置,修改其程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | <template> <!-- el-container 元件,包含頁面主要版面配置 --> <el-container class = "layout-container" > <!-- el-header 元件,包含頁面標題和購物車按鈕 --> <el-header> <div class = "header-content" > <h1> Vue 商城 </h1> <!-- 購物車資訊,點擊後顯示購物車側邊欄 --> <div class = "cart-info" @click= "showCart = true" > <!-- el-badge 元件,顯示購物車商品數量 --> <el-badge :value= "cartItemCount" class = "cart-badge" > <!-- 購物車按鈕 --> <el-button type= "primary" > 購物車 </el-button> </el-badge> </div> </div> </el-header> <!-- el-main 元件,包含主要內容 --> <el-main> <!-- 路由視圖,顯示對應路由的組件 --> <router-view> </router-view> </el-main> <!-- 購物車側邊欄 --> <el-drawer v-model= "showCart" title= "購物車" direction= "rtl" size= "400px" > <!-- 當購物車為空時顯示的訊息 --> <div v- if = "cartItems.length === 0" class = "empty-cart" > 購物車是空的 </div> <div v- else > <!-- 購物車商品列表 --> <div class = "cart-items" > <!-- 購物車商品 --> <div v- for = "item in cartItems" :key= "item.id" class = "cart-item" > <!-- 商品圖片 --> <img :src= "item.image" class = "cart-item-image" /> <div class = "cart-item-info" > <!-- 商品名稱 --> <h4> {{ item.name }} </h4> <!-- 商品價格 --> <p> NT$ {{ item.price.toLocaleString() }} </p> <!-- 商品數量控制 --> <div class = "quantity-control" > <!-- 數量輸入方塊 --> <el-input-number v-model= "item.quantity" :min= "1" @change= "( value ) => updateQuantity( item.id, value )" /> <!-- 刪除商品按鈕 --> <el-button type= "danger" @click= "removeFromCart(item.id)" > 刪除</el-button> </div> </div> </div> </div> <!-- 購物車底部 --> <div class = "cart-footer" > <!-- 總計金額 --> <div class = "total" > 總計: NT$ {{ total.toLocaleString() }} </div> <!-- 購物車操作按鈕 --> <div class = "cart-actions" > <!-- 清空購物車按鈕 --> <el-button type= "danger" @click= "confirmClearCart" > 清空購物車 </el-button> <!-- 結帳按鈕 --> <el-button type= "primary" size= "large" @click= "handleCheckout" > 結帳 </el-button> </div> </div> </div> </el-drawer> <!-- el-footer 元件,包含頁腳內容 --> <el-footer class = "footer" > <div class = "footer-content" > © {{ new Date().getFullYear() }} Vue 商城. <div class = "footer-text" > All Rights Reserved. </div> </div> </el-footer> </el-container> </template> <script setup> // 引入 Vue 的 ref 函式 import { ref } from 'vue' // 引入 Pinia 的 storeToRefs 函式 import { storeToRefs } from 'pinia' // 引入購物車 store import { useCartStore } from './stores/cart' // 引入 Element Plus 的ElMessageBox和ElMessage組件 import { ElMessageBox, ElMessage } from 'element-plus' // 定義 showCart 變數,用於控制購物車側邊欄的顯示 const showCart = ref ( false ) // 使用購物車 store const cartStore = useCartStore() // 從購物車 store 中取得 cartItems, total 和 cartItemCount const { cartItems, total, itemCount: cartItemCount } = storeToRefs(cartStore) // 從購物車 store 中取得 updateQuantity, removeFromCart 和 clearCart 方法 const { updateQuantity, removeFromCart, clearCart } = cartStore // 確認清空購物車的函式 const confirmClearCart = () => { ElMessageBox.confirm( '確定要清空購物車嗎?此操作無法復原。' , '警告' , { confirmButtonText: '確定' , cancelButtonText: '取消' , type: 'warning' , } ).then(() => { // 清空購物車 clearCart() // 顯示成功訊息 ElMessage.success( '購物車已清空' ) }). catch (() => { // 顯示取消操作訊息 ElMessage.info( '已取消操作' ) }) } // 處理結帳的函式 const handleCheckout = () => { ElMessageBox.alert( '結帳完成' , '訊息' , { confirmButtonText: '確定' , callback: () => { // 隱藏購物車側邊欄 showCart.value = false // 清空購物車 clearCart() } }) } </script> <style scoped> .layout-container { min-height: 100vh; } .header-content { max-width: 1200px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; padding: 0 20px; height: 100%; } .header-content h1 { color: white; margin: 0; font-size: 24px; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); } .el-header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); position: fixed ; width: 100%; z-index: 100; transition: all 0.3s ease; height: 60px; line-height: 60px; } .el-header .el-button { background: rgba(255, 255, 255, 0.15); border: 1px solid rgba(255, 255, 255, 0.3); color: white; } .el-header .el-button:hover { background: rgba(255, 255, 255, 0.25); transform: translateY(-2px); } .cart-info { transition: all 0.3s ease; } .cart-info:hover { transform: translateY(-2px); } .el-badge__content { background-color: #ff4949; border: 2px solid #764ba2; } .el-main { margin-top: 60px; background-color: #f5f7fa; } .cart-items { padding: 20px; } .cart-item { display: flex; margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #eee; } .cart-item-image { width: 100px; height: 100px; object -fit: cover; margin-right: 15px; } .cart-item-info { flex: 1; } .cart-item-info h4 { margin: 0 0 10px 0; } .quantity-control { display: flex; align-items: center; gap: 10px; margin-top: 10px; } .cart-footer { position: absolute; bottom: 0; left: 0; right: 0; padding: 20px; background-color: #fff; box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1); } .total { font-size: 20px; font-weight: bold; margin-bottom: 15px; } .empty-cart { text-align: center; padding: 40px; color: #909399; font-size: 16px; } .cart-actions { display: flex; gap: 10px; justify-content: flex-end; } .cart-actions .el-button { flex: 1; } .footer { background-color: #f8f9fa; padding: 20px 0; text-align: center; margin-top: auto; } .footer-content { max-width: 1200px; margin: 0 auto; color: #666; } .footer-text { margin-top: 8px; font-size: 14px; color: #999; } </style> |
測試
根據本文的說明使用「npm run dev」執行這個網站,首頁將會顯示產品清單,你可以在文字方塊輸入篩選條件,過濾感興趣的產品資料,當資料過多時,可以使用下方的換頁按鈕切換到其它頁面來顯示,請參考下圖所示:
當點選任一產品的「加入購物車」按鈕,便可將此產品加到購物車,頁面上方將會顯示一個「已加入購物車」的通知訊息,請參考下圖所示:
當你點選網頁右上方的「購物車」按鈕,便可看到購物車以側邊欄的方式顯示,其中包含購物車目前的產品,請參考下圖所示:
0 意見:
張貼留言