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的方法