assets 下新增 all.scss
assets 下新增 helpers資料夾 下新增 _variables.scss(_不會被辨識到)
並將原本的BS內 variables.scss 資料貼至 _variables.scss 內
2-3 自定義匯入
assets > all.scss
@import "~bootstrap/scss/functions";
@import "./helpers/variables";
@import "~bootstrap/scss/mixins";
@import "~bootstrap/scss/bootstrap";
App.vue
<style lang="scss">
@import "./assets/all";
</style>
多嘗試幾次
6. 製作登入頁面
(1)安裝axios
https://www.npmjs.com/package/vue-axios
npm install --save axios vue-axios
(2)全域匯入
main.js
import axios from 'axios'
import VueAxios from 'vue-axios'
app.use(VueAxios, axios);
(3)製作登入頁面(製作元件)
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E8%AA%B2%E7%A8%8B%E9%83%A8%E5%88%86%E6%A8%A1%E6%9D%BF
View 新增 Login.vue
(4)綑綁路由(設定路由)
Router > index.js
path: "/login",
name: "Login",
component: () =>
import("../views/Login.vue"),
(5)對應API寫JS
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E7%99%BB%E5%85%A5%E5%8F%8A%E9%A9%97%E8%AD%89
登入成功顯示範例
"uid": "XX4VbV87lRRBXKhZKT7YX6zhsuO2",
"token": "xxx"
"expired": "1234567890"
7. 登入與 Cookie
https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie
https://github.com/axios/axios#global-axios-defaults
document.cookie = "doSomethingOnlyOnce(名稱自訂)=true(值);
expires(到期日)=Fri,31 Dec 9999 23:59:59 GMT; SameSite=None; Secure";
目標:將token存進Cookie
const {token, expired} = res.data //token、expired = data內的token、expired
console.log(token ,expired)
document.cookie = `hexToken=${token}; expired=${new Date(expired)}`
8.確認是否維持登入狀態
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E7%99%BB%E5%85%A5%E5%8F%8A%E9%A9%97%E8%AD%89
(1)從cookie拿出token (需上步驟先看到Cookie內有token)
https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie
1-1 製作元素頁面
Dashboard.vue
created() {
const token = document.cookie.replace(
// hexToken cookie名稱
/(?:(?:^|.*;\s*)hexToken\s*=\s*([^;]*).*$)|^.*$/,
console.log(token);
1-2 綑綁路由
index.js
path: "/dashboard",
name: "Dashboard",
component: () =>
import("../views/Dashboard.vue"),
(2)token放入Headers
https://github.com/axios/axios
Dashboard.vue
this.$http.default.headers.common["Authorization"] = token;
(3)登入驗證
Dashboard.vue
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E7%99%BB%E5%85%A5%E5%8F%8A%E9%A9%97%E8%AD%89
const api = `${process.env.VUE_APP_API}api/user/check`;
console.log(api);
this.$http.post(api, this.user).then((res) => {
console.log(res);
如果把cookie的hexToken刪除
(4)轉址 $router.push
Login.vue如果登入成功轉Dashboard.vue頁面
this.$http.post(api, this.user).then((res) => {
// 如果登入成功轉跳Dashboard.vue頁面
if (res.data.success) {
this.$router.push("/dashboard");
如果登入失敗Dashboard.vue會轉回Login.vue
if (!res.data.success) {
this.$router.push("/login");
9. 調整巢狀路由並且加入 Navbar
(1)製作畫面元件 Products.vue(新增元件)
(2)Dashboard.vue增加Bs Navbar
(3)Dashboard.vue調整巢狀路由
顯示App.vue
<router-view />
(4)App.vue 調整巢狀路由
<router-link />
(5)綑綁路由
index.js
path: "/dashboard",
name: "Dashboard",
component: () => import("../views/Dashboard.vue"),
// 多個,陣列
children: [
path: "products",
component: () => import("../views/Products.vue"),
(6)調整 login.vue 登入成功轉跳 products.vue
this.$router.push("/dashboard/products");
(7)調整 Dashboard.vue Navbar
把Navbar拆出來做成一個小元件
7-1. 新增 components > Navbar.vue
並把Dashboard.vue 的 BS Navbar 剪下貼上
7-2 Dashboard.vue下註冊該 Navbar.vue 元件 並放入
<Navber></Navber>
import Navber from "../components/Navbar.vue";
export default {
// 註冊元件
components: {
Navber
(8)製作登出
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E7%99%BB%E5%85%A5%E5%8F%8A%E9%A9%97%E8%AD%89
Navbar.vue
<a class="nav-link" href="#" v-on:click.prevent="logout">登出</a>
methods: {
logout() {
// 製作登出方法
const api = `${process.env.VUE_APP_API}logout`;
console.log(api);
// axios post(api, 欲傳送的值)方法方法
this.$http.post(api, this.user).then((res) => {
if (res.data.success) {
console.log(res);
// 轉跳登入頁面
this.$router.push("/login");
成功,F12檢查
data: {success: true, message: '已登出'}
10. 加入產品列表
(1)Dashboard.vue 調整面板
<!-- 讓版面不要太貼齊周邊 BS樣式 -->
<div class="container-fluid">
<router-view />
(2)Products.vue 製作表格
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E8%AA%B2%E7%A8%8B%E9%83%A8%E5%88%86%E6%A8%A1%E6%9D%BF
(3)Products.vue 製作元件 取得商品列表
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E7%AE%A1%E7%90%86%E6%8E%A7%E5%88%B6%E5%8F%B0-%5B%E9%9C%80%E9%A9%97%E8%AD%89%5D
11. 增加 Bootstrap Modal
目標:新增Bs Modal 彈跳視窗
https://bootstrap.hexschool.com/docs/4.2/components/modal/
BS的Modal,可以用JS呼叫出來使用~
(1)新增元件 放入BS樣式 及 方法
components > ProductModal.vue
(2)將ProductModal.vue 元件放入 Products.vue
<ProductModal></ProductModal>
import ProductModal from "../components/ProductModal.vue";
components: {
// 區域註冊元件
ProductModal,
Modal元件已經放入,剩下寫判斷式讓按按鈕後 Modal跳出
(3)判斷式 + 按鈕
Products.vue
<!-- 使用元件 ref似id 之後可以用this.$refs抓 -->
<ProductModal ref="poductModal"></ProductModal>
<button
v-on:click="$refs.poductModal.showModal()"
class="btn btn-primary"
type="button"
增加一個產品
</button>
12. 透過彈出視窗新增品項 - (新增)
目標:彈出視窗的元件,輸入完資料後,資料推進產品列表元件內
API 取得商品列表
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E7%AE%A1%E7%90%86%E6%8E%A7%E5%88%B6%E5%8F%B0-%5B%E9%9C%80%E9%A9%97%E8%AD%89%5D#%E5%95%86%E5%93%81%E5%BB%BA%E7%AB%8B
(1)修改ProductModal.vue 彈跳視窗樣式,方便輸入值(保留ref)
模板 增加 Bootstrap Modal
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E8%AA%B2%E7%A8%8B%E9%83%A8%E5%88%86%E6%A8%A1%E6%9D%BF
(2)修改products.vue 彈跳視窗獨立方法
openModal() {
this.tempProduct = {};
const productComponent = this.$refs.productModal;
productComponent.showModal();
(3)修改ProductModal.vue 新增倉庫接收外層(products.vue)輸入的資料
(4)products.vue 新增方法接收ProductModal.vue送進的值(item)
emit內傳外
ProductModal.vue
emit送出去
v-on:click="$emit('update-product',tempProduct)"
type="button" class="btn btn-primary">確認</button>
products.vue
<ProductModal
v-on:update-product="updateProduct"
ref="productModal">
</ProductModal>
updateProduct(item) {
// 從內層(ProductModal.vue)接收資料, 送進tempProduct倉庫
this.tempProduct = item;
(5)products.vue 並送值到資料庫(使用axios)
this.$http.post(api, { data: this.tempProduct })
.then((res)=>{
// then 成功
console.log(res);
// 執行 ProductModal.vue 的 hideModal()
productComponent.hideModal();
// 執行
this.getProducts();
title(String)
category(String)
unit(String)
origin_price(Number)
price(Number) 為必填欄位
13. 產品資料更新 - (編輯)
API 修改產品
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E7%AE%A1%E7%90%86%E6%8E%A7%E5%88%B6%E5%8F%B0-%5B%E9%9C%80%E9%A9%97%E8%AD%89%5D#%E4%BF%AE%E6%94%B9%E7%94%A2%E5%93%81
products.vue
(1)增加 是否為新增商品判斷 倉庫
data() {
return {
isNew: false,
(2)點擊新增按鈕時,帶入參數 True
true = 點此按鈕時要新增商品
v-on:click="openModal(true)"
(3)點擊編輯按鈕時,帶入參數 false
false = 點此按鈕時非新增商品 = 編輯商品
item = v-if="item.is_enabled" 的 item
v-on:click="openModal(false,item)"
有夠方便。
(4)在跳窗的方法openModal帶入參數 及 增加判斷式 (渲染、顯示)
此時判斷 新增商品 or 編輯商品
openModal(isNew, item) {
// if isNew = true
if(isNew){
// 新增商品
this.tempProduct ={};
}else{
// 編輯商品
// ... 展開舊的item
this.tempProduct ={...item};
(5)將資料寫進後端API
API 修改產品
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E7%AE%A1%E7%90%86%E6%8E%A7%E5%88%B6%E5%8F%B0-%5B%E9%9C%80%E9%A9%97%E8%AD%89%5D#%E4%BF%AE%E6%94%B9%E7%94%A2%E5%93%81
updateProduct(item) {
// 從內層(ProductModal.vue)接收資料, 送進tempProduct倉庫
this.tempProduct = item;
// 新增
// API使用方式 取得商品列表
let api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/product`;
let httpMethod = "post";
// 編輯
if (!this.isNew) {
api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/product/${item.id}`;
httpMethod = "put";
// this.$refs.productModal = html 內 <ProductModal ref="productModal"></ProductModal>
const productComponent = this.$refs.productModal;
this.$http[httpMethod](api, { data: this.tempProduct }).then((res) => {
// then 成功
console.log(res);
// 執行 ProductModal.vue 的 hideModal()
productComponent.hideModal();
// 執行 取得列表資料
this.getProducts();
為何使用[]?
如果 $http.httpMethod(xxxx) 會被判定成function
14. 透過 API 上傳圖片
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E7%AE%A1%E7%90%86%E6%8E%A7%E5%88%B6%E5%8F%B0-%5B%E9%9C%80%E9%A9%97%E8%AD%89%5D#%E4%B8%8A%E5%82%B3%E5%9C%96%E7%89%87
目標:點擊新增產品內上傳圖片,可上傳圖片進資料庫
取出檔案,並改成FormData格式
ProductModal.vue
(1)input改變時,呼叫該方法
v-on:change="uploadfile"
(2)並給該input id 好取值
ref="fileInput"
(3)製作uploadfile方法,取出檔案,並改成FormData格式
抓files[0]
![https://ithelp.ithome.com.tw/upload/images/20211115/20137684nviwLLUWkX.png](https://ithelp.ithome.com.tw/upload/images/20211115/20137684nviwLLUWkX.png)
uploadfile() {
// 抓取檔案
// this.$refs.fileInput = ref="fileInput"
const uploadfile = this.$refs.fileInput.files[0];
console.dir(uploadfile);
// 製作成FormData格式
const formData = new FormData(); //Js
formData.append("file-to-upload", uploadfile); // "file-to-upload" API接收格式
(4)上傳資料庫
uploadfile() {
//API收圖片 表單傳送 action
const url = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/upload`;
// axios post(api, 欲傳送的值)方法方法
this.$http.post(url, formData).then((response) => {
console.log(response.data);
if (response.data.success) {
this.tempProduct.imageUrl = response.data.imageUrl;
15. 刪除商品
DelModal.vue
16. 使用 mixin 整合 元件內相同程式碼
(1)src > 新增mixins 資料夾 > 新增 modalMixin.js
(2)把重複的資料放進去
(3)放到需要用到的元件內
import modalMixin from "@/mixins/modalMixin";
mixins: [modalMixin],
17. 加入讀取的視覺效果(vue3套件效果)
https://www.npmjs.com/package/vue3-loading-overlay
(1)安裝套件
npm install vue3-loading-overlay
(2)main.js 匯入 並 全域註冊
// 讀取動畫套件
// Import component
import Loading from "vue3-loading-overlay";
// Import stylesheet
import "vue3-loading-overlay/dist/vue3-loading-overlay.css";
// 全域註冊
app.component('Loading', Loading)
(3)供各個網頁使用(登入、註冊、新增、編輯、刪除產品..)
Products.vue、Login.vue
active = true才會轉圈圈
<Loading :active="isLoading"></Loading>
data() {
return {
// 判斷是否要loading轉圈圈 倉庫
isLoading: false,
getProdcts() {
const api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/products`;
// 撈到資料前有loding
this.isLoading = true;
console.log(api);
this.$http.get(api).then((res) => {
// 撈到資料loding圈圈關閉
this.isLoading = false;
if (res.data.success) {
18.加入錯誤的訊息回饋 (錯誤通知) mitt 跨元件溝通(平行、父子層)
目標:編輯或新增商品錯誤時,跳出錯誤訊息(BS樣式 吐司)通知
Toast分好幾個放的優點:可以產生多次、獨立的生命周期等...
(1)安裝 mitt
npm i mitt
(2)為了讓所有的元件可以使用mitt
src > 新增methods資料夾 > 新增 emitter.js檔案
(3)匯入mitt
emitter.js
// 匯入
import mitt from "mitt";
const emitter = mitt();
// 使用方式:emitter
export default emitter;
(4)在最外層Dashboard.vue引用
讓內層都可以引用外層功能 provide
import emitter from '@/methods/emitter';
provide() {
return {
emitter,
// http://localhost:8080/#/dashboard/products
path: "products",
component: () => import("../views/Products.vue"),
(5)製作吐司 與 使用 emitter
19. 加入分頁切換 (製作分頁元件)
API 取得商品列表
https://github.com/hexschool/vue3-course-api-wiki/wiki/%E7%AE%A1%E7%90%86%E6%8E%A7%E5%88%B6%E5%8F%B0-%5B%E9%9C%80%E9%A9%97%E8%AD%89%5D#%E5%8F%96%E5%BE%97%E5%95%86%E5%93%81%E5%88%97%E8%A1%A8
(1)API改分頁模式(/?page=${page}),並檢查res
Products.vue
getProducts( page = 1 ) {
// 3.使用API抓值 取得商品列表
const api = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/products/?page=${page}`;
this.$http.get(api).then((res) => {
if (res.data.success) {
console.log("res", res);
(2)製作分頁元件 Pagination.vue
components > Pagination.vue
(3)上下頁製作
20. 套用全域的千分號 ($1,000)
(1)製作 千分號 方法
src > methods > 新增filters.js
export function currency(num) {
const n = parseInt(num, 10);
return `${n.toFixed(0).replace(/./g, (c, i, a) => (i && c !== '.' && ((a.length - i) % 3 === 0) ? `, ${c}`.replace(/\s/g, '') : c))}`;
(2)匯入欲使用的區域
Products.vue
import { currency } from "../methods/filters.js";
methods: {
// 千分號 方法引用
currency,
(3)使用
Products.vue
<td class="text-right">{{ currency(item.origin_price) }}</td>
<td class="text-right">{{ currency(item.price) }}</td>
全域方法 globalProperties ****
https://v3.cn.vuejs.org/api/application-config.html#globalproperties
// foo 自定義 通常 = function才好用 如 下例
app.config.globalProperties.foo = 'bar'
app.component('child-component', {
mounted() {
console.log(this.foo) // 'bar'
(1)製作 千分號 方法
src > methods > 新增filters.js
export function currency(num) {
const n = parseInt(num, 10);
return `${n.toFixed(0).replace(/./g, (c, i, a) => (i && c !== '.' && ((a.length - i) % 3 === 0) ? `, ${c}`.replace(/\s/g, '') : c))}`;
(2)全域匯入
main.js
import { currency } from "./methods/filters.js";
// 全域定義 方法
// https://v3.cn.vuejs.org/api/application-config.html#globalproperties
app.config.globalProperties.$filters = {
// 轉千位數 及 時間方法
currency,
(3)選擇任一地方使用
Products.vue
<td class="text-right">{{ $filters.currency(item.price) }}</td>
21.中場加速 製作 訂單資料 及 優惠卷
src/methods/pushMessageState.js 錯誤訊息吐司 判斷式 給Coupons.vue & Orders.vue
Products.vue 內 吐司錯誤訊息程式碼 會因為點擊越來越多,故拆開 製作成另一方法
因此 methods > pushMessageState.js
並 全域匯入 給 Coupons.vue & Orders.vue 用
邏輯:因為直接用全域方法(main.js)只會有全域一個倉庫(提升效能),但區域方法就會到處都有倉庫
src/views/Orders.vue 訂單頁面
src/views/Coupons.vue 優惠頁面
src/components/orderModal.vue
src/components/CouponModal.vue
src/components/Navbar.vue 連結樣板
src/router/index.js 連結製作
src/main.js 全域匯入pushMessageState = $httpMessageState
生命週期 mounted() vs created() 使用時機
mounted()
html標籤跑完後執行
寫在mounted就是防止抓不到dom元素 或抓不到html標籤的問題產生
ex:Bootstrap Modal、吐司等等..
created()用在一開始要抓值時
ex: API資料
props vs emit vs mitt
(1) props 外傳內
Products.vue(外)
import DelModal from "@/components/DelModal.vue";
// 口決:前內後外
<DelModal :item="tempProduct"/>
data() {
return {
tempProduct: {},
DelModal.vue(內)
props: {
item: {},
data() {
return {
modal: "",
(2) emit 內傳外
Products.vue(外)
import DelModal from "@/components/DelModal.vue";
// 口決:前內後外
<DelModal @del-item="delProduct" />
delProduct() {
const url = `${process.env.VUE_APP_API}api/${process.env.VUE_APP_PATH}/admin/product/${this.tempProduct.id}`;
this.isLoading = true;
this.$http.delete(url).then((response) => {
this.isLoading = false;
console.log(response.data);
const delComponent = this.$refs.delModal;
delComponent.hideModal();
this.getProdcts();
DelModal.vue(內)
@click="$emit('del-item')"
(3) mitt 平行、跨元件溝通(沒有誰包誰、父子層)
emitter.js
// 匯入
import mitt from "mitt";
const emitter = mitt();
// 使用方式:emitter
export default emitter;
Dashboard.vue(父)
import emitter from "@/methods/emitter";
provide() {
return {
emitter,
ToastMessages.vue(子)
// 引用emitter (因 Dashboard.vue provide )
inject: ["emitter"],
父、子層區分
index.js
path: "/dashboard",
name: "Dashboard",
component: () => import("../views/Dashboard.vue"),
// 多個,陣列
children: [
// http://localhost:8080/#/dashboard/products
path: "products",
component: () => import("../views/Products.vue"),
split() 轉陣列
let myApple = '123T456';
let arr = myApple.split("T");
consolog(arr) // [123,456]
let myApple2 = '1,2,3,4';
let arr2 = myApple.split(",");
consolog(arr) // [1,2,3,4]
$http 是 axios的方法