wordpress_blog

This is a dynamic to static website.

Vue3 複習03

指令語法全介紹 | 操作畫面超容易

指令觀念介紹

綁定內容於畫面上 v-text

// HTML
<div id="app">
  <h3>字串 v-text 與 <code v-pre>{{}}</code> (Mustache)</h3>
  <p>{{ name }}在{{ position }}吃早餐</p>
  <p><strong v-text="name"></strong>在<span v-text="position"></span>吃早餐</p>
  <input type="text" v-model="name">

  <hr>
  <h3>原始字串 v-html</h3>
  <div v-html="rawHtml"></div>
  <p><a href="https://v3.cn.vuejs.org/api/directives.html#v-html">注意事項</a></p>

  <hr>
  <h3>單次綁定 v-once</h3>
  <p v-once>{{ name }}在{{ position }}吃早餐</p>
  <input type="text" v-model="name">

  <hr>
  <h3>進階技巧:表達式</h3>
  <p>樣板字面值: {{ `${name}在${position}吃早餐` }}</p>
  <p>反轉字串: {{ text.split('').reverse().join('') }}</p>
  <p>綁定 methods: {{ say('杰倫') }}</p>
  <p>JS 運算: {{ 1 + 1 }}</p>

  <hr>
  <h3>進階技巧:顯示資料狀態</h3>
  <p>顯示目前的陣列內容 {{ products }}</p>

  <hr>
  <h3>顯示 <span v-pre>{{ }}</span></h3>
  <p v-pre>這段文字不會被轉譯:{{ name }}在{{ position }}吃早餐</p>
</div>
// JS
const App = {
  name: 'Hexschool Component',
  data() {
    return {
      name: '小明',
      position: '早餐店',
      text: '小明在早餐店吃早餐',
      rawHtml: '<p>小明在早餐店吃早餐</p>',
      products: [
        {
          name: '蛋餅',
          price: 30,
          vegan: false
        },
        {
          name: '飯糰',
          price: 35,
          vegan: false
        },
        {
          name: '小籠包',
          price: 60,
          vegan: false
        },
        {
          name: '蘿蔔糕',
          price: 30,
          vegan: true
        }
      ],
      selected: [
        {
          name: '蛋餅',
          price: 30,
          vegan: false
        },
        {
          name: '飯糰',
          price: 35,
          vegan: false
        },
      ]
    }
  },
  methods: {
    say(name) {
      return `${name}在${this.position}吃早餐`
    }
  },
  computed: {
    total() {
      const total = this.selected.reduce((curr, next) => {
        return curr + next.price;
      }, 0);
      console.log(total);
      return total;
    }
  },
  created() {

  }
};

Vue.createApp(App).mount('#app');

多筆資料渲染 v-for

// HTML
<div id="app">
  <h3>呈現多筆資料於畫面上 v-for</h3>
  <p>菜單</p>
  <ul>
    <li v-for="(item, key) in products" >
      {{ key + 1 }} - {{ item.name }} / {{ item.price }}
    </li>
  </ul>

  <h3>物件回圈</h3>
  <ul>
    <li v-for="(item, key) in productsObj">
      {{ key }} - {{ item.name }} / {{ item.price }}元
    </li>
  </ul>

  <h3>純數值回圈</h3>
  <ul>
    <li v-for="i in 5">{{ i }}</li>
  </ul>

  <h3>v-for 與 key</h3>
  <p>菜單</p>
  <ul>
    <li v-for="(item, key) in products" v-bind:key="item.name">
      {{ key }} - {{ item.name}} / {{ item.price }} 元
      <input type="text">
    </li>
  </ul>
  <p>說明:有相同父元素的子元素必須有獨特的 key。重複的 key 會造成渲染錯誤。</p>
  <button type="button" v-on:click="reverseArray">反轉資料內容</button>

  <hr>
  <h3>進階技巧:在 template 標籤使用 v-for</h3>
  <table class="table">
    <tbody>
      <template v-for="(item, i) in products" v-bind:key="item.name">
        <tr>
          <th rowspan="2">{{ i + 1 }}</th>
          <td colspan="2">
            {{ item.name }}
          </td>
        </tr>
        <tr>
          <td>
            {{ item.price }}元
          </td>
          <td>
            <!-- 三元運算子 -->
            <!-- 變數 ? true : false -->
            {{ item.vegan ? "素食" : "不可素食" }}
          </td>
        </tr>
      </template>
    </tbody>
  </table>
  <p><a href="https://cn.vuejs.org/guide/essentials/list.html#v-for-on-template">參考介紹</a></p>

  <h3>補充說明:v-for 與元件</h3>
  <ul>
    <list-item :item="item" v-for="(item, key) in products" :key="item.name + 2"></list-item>
  </ul>
</div>
// JS
const App = {
  data() {
    return {
      name: '小明',
      products: [
        {
          name: '蛋餅',
          price: 30,
          vegan: false
        },
        {
          name: '飯糰',
          price: 35,
          vegan: false
        },
        {
          name: '小籠包',
          price: 60,
          vegan: false
        },
        {
          name: '蘿蔔糕',
          price: 30,
          vegan: true
        },
      ],
      productsObj: {
        chineseOmelette: {
          name: '蛋餅',
          price: 30,
          vegan: false
        },
        riceBall: {
          name: '飯糰',
          price: 35,
          vegan: false
        },
        soupDumpling: {
          name: '小籠包',
          price: 60,
          vegan: false
        },
        radishCake: {
          name: '蘿蔔糕',
          price: 30,
          vegan: true
        }
      },
    }
  },
  methods: {
    reverseArray: function () {
      this.products.reverse();
    },
  },
};

Vue.createApp(App)
  .component('list-item', {
    template: `
      <li>
        {{ item.name}} / {{ item.price }} 元
      </li>
    `,
    props: ['item']
  }).mount('#app');

條件判斷 v-if

// HTML
<div id="app">
  <h3>v-if</h3>
  <p v-if="isFull">小明 飽了</p>
  <p v-else>小明 還沒吃飽</p>
  <!-- {{ isFull }} -->
  <button type="button" v-on:click="change('isFull')">狀態切換</button>

  <hr />
  <h3>v-else-if</h3>
  <nav class="nav nav-pills nav-fill">
    <a
      class="nav-link"
      href="#"
      v-bind:class="{ 'active': link === '小明' }"
      v-on:click="link = '小明'"
      >小明</a
    >
    <a
      class="nav-link"
      href="#"
      v-bind:class="{ 'active': link === '小美' }"
      v-on:click="link = '小美'"
      >小美</a
    >
    <a
      class="nav-link"
      href="#"
      v-bind:class="{ 'active': link === '杰倫' }"
      v-on:click="link = '杰倫'"
      >杰倫</a
    >
  </nav>
  <div>
    <!-- {{ link }} -->
    <div v-if="link === '小明'">小明吃早餐</div>
    <div v-else-if="link === '小美'">小美去百貨公司</div>
    <div v-else-if="link === '杰倫'">杰倫去幫助人</div>
  </div>

  <hr />
  <h3>注意事項:v-for 與 v-if 混用</h3>
  <ul>
    <template v-for="(item, key) in products" v-bind:key="item.name">
      <li v-if="item.price <= 35">{{ item.name}} / {{ item.price }} 元</li>
    </template>
  </ul>
  <p>
    參考說明:<a
      href="https://vue3js.cn/docs/zh/guide/conditional.html#v-if-%E4%B8%8E-v-for-%E4%B8%80%E8%B5%B7%E4%BD%BF%E7%94%A8"
      >https://vue3js.cn/docs/zh/guide/conditional.html#v-if-%E4%B8%8E-v-for-%E4%B8%80%E8%B5%B7%E4%BD%BF%E7%94%A8</a
    >
  </p>

  <hr />
  <h3>v-if 與 v-show</h3>
  <p v-show="isFull">小明 飽了</p>
  <p v-if="isFull">小明 飽了</p>
  <button type="button" v-on:click="change('isFull')">狀態切換</button>
</div>
// JS
const App = {
  data() {
    return {
      name: "小明",
      isFull: true,
      link: "小明",
      products: [
        {
          name: "蛋餅",
          price: 30,
          vegan: false,
        },
        {
          name: "飯糰",
          price: 35,
          vegan: false,
        },
        {
          name: "小籠包",
          price: 60,
          vegan: false,
        },
        {
          name: "蘿蔔糕",
          price: 30,
          vegan: true,
        },
      ],
      productsObj: {
        chineseOmelette: {
          name: "蛋餅",
          price: 30,
          vegan: false,
        },
        riceBall: {
          name: "飯糰",
          price: 35,
          vegan: false,
        },
        soupDumpling: {
          name: "小籠包",
          price: 60,
          vegan: false,
        },
        radishCake: {
          name: "蘿蔔糕",
          price: 30,
          vegan: true,
        },
      },
    };
  },
  methods: {
    change: function (key) {
      this[key] = !this[key];
    },
  },
};

Vue.createApp(App).mount("#app");

HTML 屬性綁定 v-bind

// HTML
<div id="app">
  <h3>綁定屬性 v-bind</h3>
  <p>{{ breakfastShop.name }}</p>
  <img
    v-bind:src="breakfastShop.imgUrl"
    class="square-img"
    v-bind:title="breakfastShop.name"
    alt=""
  />

  <h3>縮寫形式 <code>:</code></h3>
  <img
    :src="breakfastShop.imgUrl"
    class="square-img"
    :title="breakfastShop.name"
    alt=""
  />

  <hr />
  <h3>更多屬性綁定</h3>
  小明還想點餐:
  <form action="">
    <input type="text" value="我要吃蘿蔔糕" />
    <button type="submit" :disabled="isFull">送出</button>
  </form>

  <button type="button" v-on:click="change('isFull')">狀態切換</button>

  <hr />
  <h3>搭配 v-for</h3>
  <table class="table">
    <tbody>
      <tr v-for="item in products" :key="item.imgUrl">
        <td>
          <img :src="item.imgUrl" class="square-img" alt="" />
        </td>
        <td>{{ item.name }}</td>
        <td>{{ item.price }}元</td>
        <td>
          <div class="form-check">
            <input
              class="form-check-input"
              type="checkbox"
              value=""
              :id="item.name"
            />
            <label class="form-check-label" :for="item.name"> 我要這個 </label>
          </div>
        </td>
      </tr>
    </tbody>
  </table>

  <hr />
  <h3>表達式運用(串接)</h3>
  <p>{{ imageSize }}</p>
  <input type="range" min="100" max="1000" v-model="imageSize" />
  <br />
  <img :src="`${breakfastShop.resizeImg}&w=${imageSize}`" alt="" />
  <br />
  {{ `${breakfastShop.resizeImg}&w=${imageSize}` }}

  <hr />
  <h3>動態屬性綁定(注意大小寫)</h3>
  <button
    type="button"
    v-on:click="dynamic = dynamic === 'disabled' ? 'readonly':'disabled'"
  >
    切換為 {{ dynamic }}
  </button>
  <br />
  <input type="text" :[dynamic] :value="name" />

  <hr />
  <h3>預告:元件的資料傳遞亦是使用 v-bind</h3>
  <ul>
    <list-item
      :item="item"
      v-for="(item, key) in products"
      :key="item.name + 2"
    ></list-item>
  </ul>
</div>
// JS
const App = {
  data() {
    return {
      name: "小明",
      isFull: false,
      link: "小明",
      imageSize: 200,
      dynamic: "disabled",
      breakfastShop: {
        name: "奇蹟早餐",
        imgUrl:
          "https://images.unsplash.com/photo-1600182610361-4b4d664e07b9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=80",
        resizeImg:
          "https://images.unsplash.com/photo-1600182610361-4b4d664e07b9?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&q=80",
      },
      products: [
        {
          name: "蛋餅",
          price: 30,
          vegan: false,
          imgUrl:
            "https://images.unsplash.com/photo-1607278967323-8766a3a501e6?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=80",
        },
        {
          name: "飯糰",
          price: 35,
          vegan: false,
          imgUrl:
            "https://images.unsplash.com/photo-1603245460565-5a7b42a6a6f4?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=80",
        },
        {
          name: "小籠包",
          price: 60,
          vegan: false,
          imgUrl:
            "https://images.unsplash.com/photo-1595424265370-3e02d3e6c10c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=80",
        },
      ],
      productsObj: {
        chineseOmelette: {
          name: "蛋餅",
          price: 30,
          vegan: false,
        },
        riceBall: {
          name: "飯糰",
          price: 35,
          vegan: false,
        },
        soupDumpling: {
          name: "小籠包",
          price: 60,
          vegan: false,
        },
        radishCake: {
          name: "蘿蔔糕",
          price: 30,
          vegan: true,
        },
      },
    };
  },
  methods: {
    change: function (key) {
      this[key] = !this[key];
    },
  },
};

Vue.createApp(App)
  .component("list-item", {
    template: `
    <li>
      {{ item.name}} / {{ item.price }} 元
    </li>
  `,
    props: ["item"],
  })
  .mount("#app");
// CSS
.square-img {
  width: 100px;
  height: 100px;
  object-fit: cover;
}

HTML 樣式綁定

// HTML
<div id="app">
  <h2>切換 Class</h2>
  <h3>物件寫法</h3>
  <!-- key 值是對應 className,物件的值則是判斷式 -->
  <div class="box" :class="{ rotate: isTransform, 'bg-danger': boxColor }"></div>
  <hr>
  <button class="btn btn-outline-primary" v-on:click="change('isTransform')">選轉物件</button>
  <button class="btn btn-outline-primary ms-1" v-on:click="change('boxColor')">切換色彩</button>

  <hr class="mt-4">
  <h3>物件寫法 2</h5>
  <div class="box" :class="objectClass"></div>

  <hr>
  <h4>陣列寫法</h4>
  <button class="btn" :class="['btn-primary', 'disabled']">請操作本元件</button>
  <button class="btn" :class="arrayClass">請操作本元件</button>
  <button type="button"
    class="btn btn-outline-primary"
    v-on:click="addClass(['btn-primary', 'disabled'])">為陣列加入 Class</button>

  <hr>
  <h2>行內樣式</h2>
  <h4>綁定行內樣式</h4>
  <!-- key 會帶入 style 的屬性 -->
  <!-- 值則是帶入 Style 相對應的值 -->
  <div class="box" :style="{ backgroundColor: 'red' }"></div>
  <div class="box" :style="styleObject"></div>
  <div class="box" :style="[styleObject, styleObject2]"></div>
</div>
// JS
const App = {
  data() {
    return {
      isTransform: true,
      boxColor: false,
      objectClass: {
        rotate: true,
        'bg-danger': true
      },

      // Array 操作
      arrayClass: [''],

      // 行內樣式
      // 使用駝峰式命名
      styleObject: {
        backgroundColor: 'red',
        borderWidth: '5px'
      },
      styleObject2: {
        boxShadow: '3px 3px 5px rgba(0, 0, 0, 0.16)'
      },
      styleObject3: {
        userSelect: 'none'
      }
    };
  },
  methods: {
    change: function (key) {
      this[key] = !this[key];
    },
    addClass(arr) {
      this.arrayClass.push(...arr);
    }
  },
};

Vue.createApp(App).mount('#app');
// CSS
.box {
  background-color: var(--bs-light);
  border: 1px solid var(--bs-gray);
  width: 80px;
  height: 80px;
}
.box {
  transition: all .5s;
}
.box.rotate {
  transform: rotate(45deg)
}

資料雙向綁定 v-model

// HTML
<div id="app">
  <h3>input</h3>
  <input type="text" class="form-control" v-model="name" />
  {{ name }}

  <hr />
  <h3>textarea</h3>
  <textarea cols="30" rows="3" class="form-control" v-model="text"></textarea>
  {{ text }}

  <hr />
  <h3>checkbox 單選框</h3>
  <p>小明,你是吃飽沒?</p>
  <p>{{ checkAnswer }}</p>
  <p>{{ checkAnswer ? '吃飽了' : '還沒'}}</p>
  <div class="form-check">
    <input
      type="checkbox"
      class="form-check-input"
      id="check1"
      v-model="checkAnswer"
    />
    <label class="form-check-label" for="check1">小明回覆</label>
  </div>

  <hr />
  <h3>checkbox 單選延伸</h3>
  <p>小明,你是吃飽沒?</p>
  <p>{{ checkAnswer2 }}</p>
  <div class="form-check">
    <input
      type="checkbox"
      v-model="checkAnswer2"
      true-value="吃飽了"
      false-value="還沒"
      class="form-check-input"
      id="check2"
    />
    <label class="form-check-label" for="check2">小明回覆</label>
  </div>

  <hr />
  <h3>checkbox 複選框</h3>
  <p>你還要吃什麼?</p>
  <p>{{ checkAnswer3.join(' ') }}</p>
  <div class="form-check">
    <input
      type="checkbox"
      class="form-check-input"
      id="check3"
      value="蛋餅"
      v-model="checkAnswer3"
    />
    <label class="form-check-label" for="check3">蛋餅</label>
  </div>
  <div class="form-check">
    <input
      type="checkbox"
      class="form-check-input"
      id="check4"
      value="蘿蔔糕"
      v-model="checkAnswer3"
    />
    <label class="form-check-label" for="check4">蘿蔔糕</label>
  </div>
  <div class="form-check">
    <input
      type="checkbox"
      class="form-check-input"
      id="check5"
      value="豆漿"
      v-model="checkAnswer3"
    />
    <label class="form-check-label" for="check5">豆漿</label>
  </div>

  <hr />
  <h3>radio 單選框</h3>
  <p>你還要吃什麼?</p>
  <p>{{ radioAnswer }}</p>
  <div class="form-check">
    <input
      type="radio"
      v-model="radioAnswer"
      class="form-check-input"
      id="radio1"
      value="蛋餅"
    />
    <label class="form-check-label" for="radio1">蛋餅</label>
  </div>
  <div class="form-check">
    <input
      type="radio"
      v-model="radioAnswer"
      class="form-check-input"
      id="radio2"
      value="蘿蔔糕"
    />
    <label class="form-check-label" for="radio2">蘿蔔糕</label>
  </div>
  <div class="form-check">
    <input
      type="radio"
      v-model="radioAnswer"
      class="form-check-input"
      id="radio3"
      value="豆漿"
    />
    <label class="form-check-label" for="radio3">豆漿</label>
  </div>

  <hr />
  <h3>select 單選</h3>
  <p>你還要吃什麼?</p>
  <p>{{ selectAnswer }}</p>
  <select class="form-select" v-model="selectAnswer">
    <option value="" disabled>說吧,你要吃什麼?</option>
    <!-- <option value="1234">12345</option> -->
    <option :value="item.name" v-for="item in products" :key="item.name">
      {{ item.name }} / {{ item.price }} 元
    </option>
  </select>

  <hr />
  <h3>select 多選</h3>
  <p>你還要吃什麼?</p>
  <p>{{ selectAnswer2 }}</p>
  <select class="form-select" multiple v-model="selectAnswer2">
    <option selected disabled value="">說吧,你要吃什麼?</option>
    <option :value="item.name" v-for="item in products" :key="item.name">
      {{item.name}} / {{item.price}} 元
    </option>
  </select>
</div>
// JS
const App = {
  data() {
    return {
      name: "小明",
      text: "一段文字敘述",
      checkAnswer: false,
      checkAnswer2: "",
      checkAnswer3: [],
      radioAnswer: "蛋餅",
      selectAnswer: "",
      selectAnswer2: [],
      products: [
        {
          name: "蛋餅",
          price: 30,
          vegan: false,
        },
        {
          name: "飯糰",
          price: 35,
          vegan: false,
        },
        {
          name: "小籠包",
          price: 60,
          vegan: false,
        },
        {
          name: "蘿蔔糕",
          price: 30,
          vegan: true,
        },
      ],
      productsObj: {
        chineseOmelette: {
          name: "蛋餅",
          price: 30,
          vegan: false,
        },
        riceBall: {
          name: "飯糰",
          price: 35,
          vegan: false,
        },
        soupDumpling: {
          name: "小籠包",
          price: 60,
          vegan: false,
        },
        radishCake: {
          name: "蘿蔔糕",
          price: 30,
          vegan: true,
        },
      },
    };
  },
  methods: {
    reverseArray: function () {
      this.products.reverse();
    },
  },
};

Vue.createApp(App)
  .component("list-item", {
    template: `
    <li>
      {{ item.name}} / {{ item.price }} 元
    </li>
  `,
    props: ["item"],
  })
  .mount("#app");

v-model 修飾符

// HTML
<div id="app">
  <h3>修飾符</h3>
  <h4 class="mt-3">延遲 Lazy</h4>
  {{ lazyMsg }}
  <input type="text" class="form-control" v-model.lazy="lazyMsg" />
  <h4 class="mt-3">純數值 Number</h4>
  {{ numberMsg }}{{ typeof numberMsg }}
  <input type="number" class="form-control" v-model.number="numberMsg" />
  <h4 class="mt-3">修剪 Trim</h4>
  這是一段{{ trimMsg }}緊黏的文字
  <input type="text" class="form-control" v-model.trim="trimMsg" />
</div>
// JS
const App = {
  data() {
    return {
      lazyMsg: "",
      numberMsg: "",
      trimMsg: "",
    };
  },
};

Vue.createApp(App).mount("#app");

事件觸發 v-on

// HTML
<div id="app">
  <h3>觸發事件 與 縮寫*</h3>
  <div class="box" :class="{ rotate: isTransform }"></div>
  <hr />
  <!-- v-on:click 縮寫寫法 @click -->
  <button class="btn btn-outline-primary" @click="changeClass">選轉物件</button>
  <br />
  <h3>帶入參數*</h3>
  <div class="box" :class="{ rotate: isTransform }"></div>
  <hr />
  <button class="btn btn-outline-primary" v-on:click="change('isTransform')">
    選轉物件
  </button>

  <hr />
  <h3>原生 DOM 事件*</h3>
  <!-- https://developer.mozilla.org/en-US/docs/Web/Events -->
  <h4>input change 事件</h4>
  <input type="text" @change="onChange" />
  <br /><br />
  <h4>form submit 事件</h4>
  <form @submit.prevent="submitForm">
    <input type="text" v-model="name" />
    <button>送出表單</button>
  </form>
  <hr />
  <h3>動態事件 []</h3>
  <input type="text" @[event]="dynamicEvent" />
  <input type="text" v-model="event" />

  <hr />
  <h3>動態物件方法 {}</h3>
  <!-- 此方法無法傳入參數 -->
  <button class="box" @="{ mousedown: down, mouseup: up }"></button>
</div>
// JS
const App = {
  data() {
    return {
      name: "小明",
      isTransform: true,
      num: 10,
      event: "click",
    };
  },
  methods: {
    changeClass() {
      this.isTransform = !this.isTransform;
    },
    change(key) {
      this[key] = !this[key];
    },
    onChange(evt) {
      console.log("change 事件");
      console.log(evt);
    },
    submitForm() {
      console.log("表單已送出", `name 為 ${this.name}`);
    },
    dynamicEvent() {
      console.log("這是一個動態事件", this.event);
    },
    down() {
      console.log("按下");
    },
    up() {
      console.log("放開");
    },
  },
};

Vue.createApp(App).mount("#app");
// CSS
.box {
  background-color: var(--bs-light);
  border: 1px solid var(--bs-gray);
  width: 80px;
  height: 80px;
}
.box {
  transition: all 0.5s;
}
.box.rotate {
  transform: rotate(45deg);
}

v-on 修飾符

// HTML
<div id="app">
  <h3>修飾符</h3>
  <h4>按鍵修飾符*</h4>
  <ul>
    <li>keyAlias - 只當事件是從特定鍵觸發時才觸發。</li>
    <li>
      別名修飾 - .enter, .tab, .delete, .esc, .space, .up, .down, .left, .right
    </li>
    <li>
      修飾符來實現僅在按下相應按鍵時才觸發鼠標或鍵盤事件的監聽器 - .ctrl, .alt,
      .shift, .meta
    </li>
  </ul>

  <h6 class="mt-3">別名修飾</h6>
  <input
    type="text"
    class="form-control"
    v-model="text"
    @keyup.enter="trigger('enter')"
  />

  <h6 class="mt-3">相應按鍵時才觸發的監聽器</h6>
  <input
    type="text"
    class="form-control"
    v-model="text"
    @keyup.shift.enter="trigger('shift + Enter')"
  />

  <h6 class="mt-3">特定鍵</h6>
  <input
    type="text"
    class="form-control"
    v-model="text"
    @keyup.h="trigger('h')"
  />
  <hr />
  <h4>滑鼠修飾符</h4>
  <ul>
    <li>.left 只當點擊鼠標左鍵時觸發。</li>
    <li>.right 只當點擊鼠標右鍵時觸發。</li>
    <li>.middle 只當點擊鼠標中鍵時觸發。</li>
  </ul>
  <h6 class="mt-3">滑鼠修飾符</h6>
  <div class="p-3 bg-primary">
    <span class="box" @click.right="trigger('right button')"> </span>
  </div>

  <hr />
  <h4>事件修飾符</h4>
  <ul>
    <li>.stop - 調用 event.stopPropagation()。</li>
    <li><strong>.prevent - 調用 event.preventDefault()。</strong></li>
    <li>.capture - 添加事件偵聽器時使用 capture 模式。</li>
    <li>.self - 只當事件是從偵聽器綁定的元素本身觸發時才觸發回調。</li>
    <li>.once - 只觸發一次回調。</li>
  </ul>
  <a href="https://www.google.com/" @click.prevent="trigger('prevent')"
    >加入 Prevent</a
  >

  <hr />
  <h6>預設情境</h6>
  <a href="https://javascript.info/bubbling-and-capturing">冒泡事件參考文章</a>
  <div class="p-3 bg-primary" @click="trigger('div')">
    <span
      class="box d-flex align-items-center justify-content-center"
      @click="trigger('box')"
    >
      <button class="btn btn-outline-secondary" @click="trigger('button')">
        按我
      </button>
    </span>
  </div>

  <h6 class="mt-3">stopPropagation (防止向外尋找)</h6>
  <div class="p-3 bg-primary" @click="trigger('div')">
    <span
      class="box d-flex align-items-center justify-content-center"
      @click.stop="trigger('box')"
    >
      <button class="btn btn-outline-secondary" @click="trigger('button')">
        按我
      </button>
    </span>
  </div>

  <h6 class="mt-3">事件偵聽器時使用 capture 模式 (事件改為由外而內)</h6>
  <div class="p-3 bg-primary" @click.capture="trigger('div')">
    <span
      class="box d-flex align-items-center justify-content-center"
      @click.capture="trigger('box')"
    >
      <button
        class="btn btn-outline-secondary"
        @click.capture="trigger('button')"
      >
        按我
      </button>
    </span>
  </div>

  <h6 class="mt-3">事件偵聽器時使用 self 模式 (只會觸發自己範圍內的)</h6>
  <div class="p-3 bg-primary" @click.self="trigger('div')">
    <span
      class="box d-flex align-items-center justify-content-center"
      @click.self="trigger('box')"
    >
      <button class="btn btn-outline-secondary" @click.self="trigger('button')">
        按我
      </button>
    </span>
  </div>
  <hr />
  <h3 class="mt-3">事件偵聽器只觸發一次 once</h3>
  <div class="p-3 bg-primary" @click.once="trigger('div')">
    <span
      class="box d-flex align-items-center justify-content-center"
      @click.once="trigger('box')"
    >
      <button class="btn btn-outline-secondary" @click.once="trigger('button')">
        按我
      </button>
    </span>
  </div>
  <hr />
</div>
// JS
const App = {
  data() {
    return {
      text: "小明",
      isTransform: true,
      num: 10,
      event: "click",
    };
  },
  methods: {
    trigger: function (name) {
      console.log(name, "此事件被觸發了");
    },
  },
};
Vue.createApp(App).mount("#app");
// CSS
.box {
  display: block;
  background-color: var(--bs-light);
  border: 1px solid var(--bs-gray);
  width: 80px;
  height: 80px;
}
.box {
  transition: all 0.5s;
}
.box.rotate {
  transform: rotate(45deg);
}

v-on DOM 事件處理技巧

// HTML
<div id="app">
  <h3>原生 DOM 事件</h3>
  <a href="https://www.w3schools.com/jsref/dom_obj_event.asp">參考:DOM 事件</a>
  <div class="box" :class="{ rotate: isTransform }"></div>
  <hr />
  <!-- 當沒有參數時,預設第一個則是 dom 事件參數 -->
  <button class="btn btn-outline-primary" @click="changeClass">選轉物件</button>
  <button class="btn btn-outline-primary" @keyup.enter="changeClass">
    按鈕事件
  </button>
  <!-- 當如果有參數時,則可以使用 $event -->
  <button
    class="btn btn-outline-primary"
    @click="changeClassWithEvent('這段是自訂參數', $event)"
  >
    自訂參數
  </button>
  <br />
  <br />
  <h3>取得原生 input 數值</h3>
  <input type="text" @change="inputEvent" />
  <br /><br />
  <h3>監聽按鍵事件</h3>
  <input type="text" @keyup="keyboardEvent" />
</div>
// JS
const App = {
  data() {
    return {
      text: "小明",
      isTransform: true,
      num: 10,
    };
  },
  methods: {
    changeClass: function (e) {
      console.log("dom", e);
      this.isTransform = !this.isTransform;
    },
    changeClassWithEvent(parameter, e) {
      console.log(parameter, e);
    },
    inputEvent(e) {
      console.log(e.target.value);
    },
    keyboardEvent(e) {
      console.dir(e.keyCode);
      if (e.keyCode === 13) {
        alert("你按下了 Enter");
      } else if (e.keyCode === 32) {
        alert("你按下了 空白鍵");
      }
    },
  },
};
Vue.createApp(App).mount("#app");
// CSS
.box {
  display: block;
  background-color: var(--bs-light);
  border: 1px solid var(--bs-gray);
  width: 80px;
  height: 80px;
}
.box {
  transition: all 0.5s;
}
.box.rotate {
  transform: rotate(45deg);
}

指令章節作業 – 簡單版

  1. 試著拆解流程
    • 左邊的清單完成
    • 選擇品項
    • 訂單列表
    • 調整品項細節
  2. 先觀察解答
  3. 第二次製作,就完全不要看解答

指令章節作業 – 簡單版實作示範

  1. 先準備左邊的品項呈現
  2. 準備暫存資料
  3. 暫存資料轉為訂單內容
  4. 計算總金額
  5. 客製化選項
// HTML
<div id="app">
  <div class="container gx-2">
    <div class="row gx-3 bg-light py-3">
      <!-- 1. 先準備左邊的品項呈現 -->
      <!-- 2-2. 觸發點擊事件 -->
      <!-- 2-3. v-bind:class 樣式綁定 -->
      <!-- key 值是對應 className,物件的值則是判斷式 -->
      <div class="col-md-4">
        <div class="list-group">
          <a
            href="#"
            class="list-group-item list-group-item-action"
            v-for="item in products"
            :key="item.name"
            @click.prevent="selectProduct(item)"
            :class="{ 'active': itemSelected.name === item.name }"
          >
            <h6 class="card-title mb-1">{{ item.name }}</h6>
            <div class="d-flex align-items-center justify-content-between">
              <p class="mb-0"><small>{{ item.engName }}</small></p>
              <p class="mb-0"><small>NT$ {{ item.price }}</small></p>
            </div>
          </a>
        </div>
      </div>
      <div class="col-md-8">
        <div class="card mb-2">
          <!-- 2-4. 條件判斷 v-if -->
          <div
            v-if="!itemSelected.name"
            class="position-absolute text-white d-flex align-items-center justify-content-center active"
            style="
              top: 0;
              bottom: 0;
              left: 0;
              right: 0;
              background-color: rgba(0, 0, 0, 0.65);
              z-index: 100;
            "
          >
            請先選擇飲品
          </div>
          <!-- 5. 客製化選項 -->
          <!-- 5-1. 資料雙向綁定 v-model  -->
          <div class="card-body px-4">
            <div class="mb-3">
              <label for="productNum" class="form-label">數量</label>
              <input
                type="number"
                class="form-control"
                id="productNum"
                v-model="itemSelected.count"
                placeholder="數量"
                min="0"
              />
            </div>
            <div class="mb-3">
              <label for="productIce" class="form-label d-block">冰塊*</label>
              <div
                class="form-check form-check-inline"
                v-for="(ice, key) in iceType"
                :key="'ice' + key"
              >
                <input
                  class="form-check-input"
                  name="iceType"
                  type="radio"
                  :id="'ice' + key"
                  :value="ice"
                  v-model="itemSelected.ice"
                />
                <label class="form-check-label" :for="'ice' + key"
                  >{{ ice }}</label
                >
              </div>
            </div>
            <div class="mb-3">
              <label for="productSugar" class="form-label d-block">甜度*</label>
              <div
                class="form-check form-check-inline"
                v-for="(sugar, key) in sugarType"
                :key="'sugar' + key"
              >
                <input
                  class="form-check-input"
                  name="sugarType"
                  type="radio"
                  :id="'sugar' + key"
                  :value="sugar"
                  v-model="itemSelected.sugar"
                />
                <label class="form-check-label" :for="'sugar' + key"
                  >{{ sugar }}</label
                >
              </div>
            </div>
            <!-- 5-6. 製作加料是相對複雜的部份 -->
            <!-- 5-7. v-for -->
            <div class="mb-3">
              <label for="productTopping" class="form-label d-block"
                >加料</label
              >
              <div
                class="form-check form-check-inline"
                v-for="(topping, key) in toppingsType"
                :key="'topping' + key"
              >
                <!-- 5-9. 把加料的值取出來 -->
                <!-- 5-10. 資料雙向綁定 v-model -->
                <input
                  class="form-check-input"
                  type="checkbox"
                  :id="'topping' + key"
                  :value="topping"
                  v-model="itemSelected.toppings"
                />
                <label class="form-check-label" :for="'topping' + key"
                  >{{ topping }}</label
                >
              </div>
            </div>
            <div class="mb-3">
              <label for="productNotice" class="form-label">備註</label>
              <textarea
                class="form-control"
                id="productNotice"
                rows="2"
                v-model="itemSelected.notice"
              ></textarea>
            </div>
            <div class="d-flex gap-2">
              <!-- 5-5. 觸發取消清空 -->
              <button
                class="btn btn-outline-primary w-100"
                @click.prevent="resetOrder"
                type="button"
              >
                取消
              </button>
              <!-- 3-2. 觸發加到購物車 -->
              <button
                class="btn btn-primary w-100"
                @click.prevent="addToOrder(itemSelected)"
                type="button"
                :disabled="!itemSelected.name"
              >
                加入
              </button>
            </div>
          </div>
        </div>
        <div class="card" v-if="orderList.length > 0">
          <div class="card-body">
            <table class="table">
              <thead>
                <tr>
                  <th scope="col">品項</th>
                  <th scope="col">冰塊</th>
                  <th scope="col">甜度</th>
                  <th scope="col">加料</th>
                  <th scope="col">單價</th>
                  <th scope="col">數量</th>
                  <th scope="col">小計</th>
                </tr>
              </thead>
              <tbody>
                <!-- 3-4. 呈現在列表畫面上 -->
                <!-- 5-11. 把加料呈現在列表畫面上,並轉成字串 -->
                <!-- 5-13. 調整加上加料後的單價 -->
                <tr v-for="(item, key) in orderList" :key="'order' + key">
                  <th scope="row">
                    {{ item.name }}<br />
                    <small
                      class="text-muted fw-normal"
                      v-if="item.notice !== ''"
                      >備註:{{ item.notice }}</small
                    >
                  </th>
                  <td>{{ item.ice }}</td>
                  <td>{{ item.sugar }}</td>
                  <td>{{ item.toppings.toString() }}</td>
                  <td>{{ item.price + item.toppings.length * 10 }}</td>
                  <td>{{ item.count }}</td>
                  <td class="text-end">{{ item.total }}</td>
                </tr>
              </tbody>
            </table>
            <p class="text-end">共 NT$ {{ orderTotal }} 元</p>
            <button
              class="btn btn-sm btn-secondary w-100"
              :disabled="orderList.length === 0"
              @click="resetOrder"
            >
              重新選擇
            </button>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
// JS
const App = {
  data() {
    return {
      // 2. 準備暫存資料
      itemSelected: {},
      // 3-3. 購物車列表
      orderList: [],
      // 4-3. 訂單總價
      orderTotal: 0,
      iceType: ["正常冰", "少冰", "微冰", "去冰", "熱"],
      sugarType: ["全糖", "七分", "半糖", "三分", "無糖"],
      toppingsType: ["珍珠", "粉條", "小粉圓", "椰果", "芋頭"],
      products: [
        {
          name: "珍珠鮮奶茶",
          engName: "Pearl Milk Tea",
          price: 60,
        },
        {
          name: "鮮奶茶",
          engName: "Milk Tea",
          price: 50,
        },
        {
          name: "古意冬瓜茶",
          engName: "Winter Melon Drink",
          price: 30,
        },
        {
          name: "蜜香紅茶",
          engName: "Black Tea",
          price: 30,
        },
        {
          name: "包種青茶",
          engName: "Pouchong tea",
          price: 35,
        },
        {
          name: "檸檬烏龍",
          engName: "Lemon Oolong Tea",
          price: 55,
        },
        {
          name: "薑母茶",
          engName: "Ginger Tea",
          price: 55,
        },
        {
          name: "青草茶",
          engName: "Herbal Tea",
          price: 35,
        },
        {
          name: "金桔檸檬",
          engName: "Kumquat Lemonade",
          price: 40,
        },
        {
          name: "柳澄青茶",
          engName: "Orange Mountain Tea",
          price: 45,
        },
      ],
    };
  },
  methods: {
    // 2-1. 選擇品項
    selectProduct(product) {
      this.itemSelected = {
        // 透過解構方式避免影響到原始資料
        ...product,
        // 3-5. 加上預設值
        count: 1,
        // 5-8. 加入加料項目
        toppings: [],
        ice: "正常冰",
        sugar: "全糖",
        notice: "",
      };
    },
    // 3-1. 加到購物車
    addToOrder(product) {
      // 4. 計算總金額
      // 4-1. 重新整理訂單所需要的內容
      const order = {
        // 透過解構方式
        ...product,
        // 小計
        // 5-12. 加料
        total: (product.price + product.toppings.length * 10) * product.count,
      };
      // 4-2. product → order
      this.orderList.push(order);
      // 4-5. 計算當前的總價
      this.countTotal();
      // 5-4. 使用 resetOrder 方法加入後清空
      this.resetOrder();
    },
    // 4-4. 計算總價
    countTotal() {
      // 4-6. 每次計算先清空總價
      this.orderTotal = 0;
      this.orderList.forEach((item) => {
        this.orderTotal += item.total;
      });
    },
    // 5-3. 清空方法
    resetOrder() {
      // 5-2. 加入後清空
      this.itemSelected = {};
    },
  },
};

Vue.createApp(App).mount("#app");

指令章節作業

  • 先寫出流程,一個一個拆解
    1. 列表
    2. 表單
    3. 加入購物車
  • 請同時撰寫筆記,製作第二次,僅參考自己筆記完成
// HTML
<div id="app">
  <div class="container py-5 gx-2">
    <h2 class="text-center">指令章節作業</h2>
    <div class="row gx-3 bg-light py-3">
      <div class="col-md-4">
        <div class="list-group">
          <a
            href="#"
            class="list-group-item list-group-item-action"
            v-for="item in products"
            :key="item.name"
            @click.prevent="selectProduct(item)"
            :class="{ 'active': itemSelected.name === item.name }"
          >
            <h6 class="card-title mb-1">{{ item.name }}</h6>
            <div class="d-flex align-items-center justify-content-between">
              <p class="mb-0"><small>{{ item.engName }}</small></p>
              <p class="mb-0"><small>NT$ {{ item.price }}</small></p>
            </div>
          </a>
        </div>
      </div>
      <div class="col-md-8">
        <div class="card mb-2">
          <!-- 條件判斷 v-if -->
          <div
            v-if="!itemSelected.hasOwnProperty('name')"
            class="position-absolute text-white d-flex align-items-center justify-content-center"
            style="
              top: 0;
              bottom: 0;
              left: 0;
              right: 0;
              background-color: rgba(0, 0, 0, 0.65);
              z-index: 100;
            "
          >
            請先選擇飲品
          </div>
          <div class="card-body px-4">
            <div class="mb-3">
              <label for="productNum" class="form-label">數量</label>
              <input
                type="number"
                class="form-control"
                id="productNum"
                v-model="itemSelected.count"
                placeholder="數量"
                min="0"
              />
            </div>
            <div class="mb-3">
              <label for="productIce" class="form-label d-block">冰塊*</label>
              <div
                class="form-check form-check-inline"
                v-for="(ice, key) in iceType"
                :key="'ice' + key"
              >
                <!-- HTML 屬性綁定 :disabled -->
                <input
                  class="form-check-input"
                  name="iceType"
                  type="radio"
                  :id="'ice' + key"
                  :value="ice"
                  v-model="itemSelected.ice"
                  :disabled="!itemSelected.hasOwnProperty('defaults') || (itemSelected.defaults.ice !== '' && itemSelected.defaults.ice !== ice)"
                />
                <label class="form-check-label" :for="'ice' + key"
                  >{{ ice }}</label
                >
              </div>
            </div>
            <div class="mb-3">
              <label for="productSugar" class="form-label d-block">甜度*</label>
              <div
                class="form-check form-check-inline"
                v-for="(sugar, key) in sugarType"
                :key="'sugar' + key"
              >
                <!-- HTML 屬性綁定 :disabled -->
                <input
                  class="form-check-input"
                  name="sugarType"
                  type="radio"
                  :id="'sugar' + key"
                  :value="sugar"
                  v-model="itemSelected.sugar"
                  :disabled="!itemSelected.hasOwnProperty('defaults') || (itemSelected.defaults.sugar !== '' && itemSelected.defaults.sugar !== sugar)"
                />
                <label class="form-check-label" :for="'sugar' + key"
                  >{{ sugar }}</label
                >
              </div>
            </div>
            <div class="mb-3">
              <label for="productTopping" class="form-label d-block"
                >加料</label
              >
              <div
                class="form-check form-check-inline"
                v-for="(topping, key) in toppingsType"
                :key="'topping' + key"
              >
                <!-- HTML 屬性綁定 :disabled -->
                <input
                  class="form-check-input"
                  type="checkbox"
                  :id="'topping' + key"
                  :value="topping"
                  v-model="itemSelected.toppings"
                  :disabled="!itemSelected.hasOwnProperty('defaults') || (itemSelected.defaults.toppings.includes(topping))"
                />
                <label class="form-check-label" :for="'topping' + key"
                  >{{ topping }}</label
                >
              </div>
            </div>
            <div class="mb-3">
              <label for="productNotice" class="form-label">備註</label>
              <textarea
                class="form-control"
                id="productNotice"
                rows="2"
                v-model="itemSelected.notice"
              ></textarea>
            </div>
            <div class="d-flex gap-2">
              <button
                class="btn btn-outline-primary w-100"
                @click.prevent="resetOrder"
                type="button"
              >
                取消
              </button>
              <!-- HTML 屬性綁定 :disabled -->
              <button
                class="btn btn-primary w-100"
                @click.prevent="addToOrder(itemSelected)"
                :disabled="!itemSelected.hasOwnProperty('name')"
                type="button"
              >
                加入
              </button>
            </div>
          </div>
        </div>
        <!-- 條件判斷 v-if -->
        <div class="card" v-if="orderList.length > 0">
          <div class="card-body">
            <table class="table">
              <thead>
                <tr>
                  <th scope="col">品項</th>
                  <th scope="col">冰塊</th>
                  <th scope="col">甜度</th>
                  <th scope="col">加料</th>
                  <th scope="col">單價</th>
                  <th scope="col">數量</th>
                  <th scope="col">小計</th>
                </tr>
              </thead>
              <tbody>
                <!-- 多筆資料渲染 v-for -->
                <tr v-for="(item, key) in orderList" :key="'order' + key">
                  <th scope="row">
                    {{ item.name }}<br />
                    <!-- 條件判斷 v-if -->
                    <small
                      v-if="item.notice !== ''"
                      class="text-muted fw-normal"
                      >備註:{{ item.notice }}</small
                    >
                  </th>
                  <td>{{ item.ice }}</td>
                  <td>{{ item.sugar }}</td>
                  <td>{{ item.toppings.toString() }}</td>
                  <td>{{ item.price + item.toppings.length * 10 }}</td>
                  <td>{{ item.count }}</td>
                  <td class="text-end">{{ item.total }}</td>
                </tr>
              </tbody>
            </table>
            <p class="text-end">共 NT$ {{ orderTotal }} 元</p>
            <!-- HTML 屬性綁定 :disabled -->
            <!-- 事件觸發 v-on generateOrder() -->
            <button
              class="btn btn-sm btn-primary w-100"
              :disabled="orderList.length === 0"
              @click.prevent="generateOrder(orderList, orderTotal)"
            >
              產生訂單
            </button>
          </div>
        </div>
      </div>
    </div>
  </div>
  <!-- 產生訂單 -->
  <!-- 條件判斷 v-if -->
  <div class="bg-light p-3 mt-3" v-if="checkedOrder.orders.length > 0">
    <div class="bg-white p-3 d-flex flex-column" style="min-height: 450px">
      <table class="table">
        <thead>
          <tr>
            <th scope="col">品項</th>
            <th scope="col">冰塊</th>
            <th scope="col">甜度</th>
            <th scope="col">加料</th>
            <th scope="col">單價</th>
            <th scope="col">數量</th>
            <th scope="col">小計</th>
          </tr>
        </thead>
        <tbody>
          <!-- 多筆資料渲染 v-for -->
          <tr v-for="(item, key) in checkedOrder.orders" :key="'order' + key">
            <th scope="row">
              {{ item.name }}<br />
              <!-- 條件判斷 v-if -->
              <small v-if="item.notice !== ''" class="text-muted fw-normal"
                >備註:{{ item.notice }}</small
              >
            </th>
            <td>{{ item.ice }}</td>
            <td>{{ item.sugar }}</td>
            <td>{{ item.toppings.toString() }}</td>
            <td>{{ item.price + item.toppings.length * 10 }}</td>
            <td>{{ item.count }}</td>
            <td class="text-end">{{ item.total }}</td>
          </tr>
        </tbody>
      </table>
      <p class="mt-3 mb-1">訂單成立時間:{{ checkedOrder.time }}</p>
      <p class="mb-1">餐點數: {{ checkedOrder.orders.length }}</p>
      <p class="mb-1">付款狀態:未付款</p>
      <p class="text-end mt-auto">共 NT$ {{ checkedOrder.total }} 元</p>
    </div>
  </div>
</div>
// JS
const app = {
  // 資料 (函式)
  data() {
    return {
      itemSelected: {},
      orderList: [],
      orderTotal: 0,
      // 確認訂單
      checkedOrder: {
        time: "",
        total: 0,
        orders: [],
      },
      iceType: ["正常冰", "少冰", "微冰", "去冰", "熱"],
      sugarType: ["全糖", "七分", "半糖", "三分", "無糖"],
      toppingsType: ["珍珠", "粉條", "小粉圓", "椰果", "芋頭"],
      products: [
        {
          name: "珍珠鮮奶茶",
          engName: "Pearl Milk Tea",
          price: 60,
          defaults: {
            toppings: ["珍珠"],
            sugar: "",
            ice: "",
          },
        },
        {
          name: "椰果鮮奶茶",
          engName: "Coconut Milk Tea",
          price: 60,
          defaults: {
            toppings: ["椰果"],
            sugar: "",
            ice: "",
          },
        },
        {
          name: "鮮奶茶",
          engName: "Milk Tea",
          price: 50,
          defaults: {
            toppings: [""],
            sugar: "",
            ice: "",
          },
        },
        {
          name: "古意冬瓜茶 (糖固定)",
          engName: "Winter Melon Drink",
          price: 30,
          defaults: {
            toppings: [""],
            sugar: "全糖",
            ice: "",
          },
        },
        {
          name: "蜜香紅茶",
          engName: "Black Tea",
          price: 30,
          defaults: {
            toppings: [""],
            sugar: "",
            ice: "",
          },
        },
        {
          name: "包種青茶",
          engName: "Pouchong tea",
          price: 35,
          defaults: {
            toppings: [""],
            sugar: "",
            ice: "",
          },
        },
        {
          name: "檸檬烏龍",
          engName: "Lemon Oolong Tea",
          price: 55,
          defaults: {
            toppings: [""],
            sugar: "",
            ice: "",
          },
        },
        {
          name: "薑母茶 (熱飲)",
          engName: "Ginger Tea",
          price: 55,
          defaults: {
            toppings: [""],
            sugar: "",
            ice: "熱",
          },
        },
        {
          name: "青草茶",
          engName: "Herbal Tea",
          price: 35,
          defaults: {
            toppings: [""],
            sugar: "",
            ice: "",
          },
        },
        {
          name: "金桔檸檬",
          engName: "Kumquat Lemonade",
          price: 40,
          defaults: {
            toppings: [""],
            sugar: "",
            ice: "",
          },
        },
        {
          name: "柳澄青茶",
          engName: "Orange Mountain Tea",
          price: 45,
          defaults: {
            toppings: [""],
            sugar: "",
            ice: "",
          },
        },
      ],
    };
  },
  // 生命週期 (函式)
  created() {
    console.log(this);
  },
  // 方法 (物件)
  methods: {
    // 選擇飲品
    selectProduct(product) {
      this.itemSelected = {
        ...product,
        count: 1,
        toppings: [],
        // 三元運算子
        ice: product.defaults.ice !== "" ? product.defaults.ice : "正常冰",
        sugar:
          product.defaults.sugar !== "" ? product.defaults.sugar : "全糖",
        notice: "",
      };
    },
    // 加入品項
    addToOrder(product) {
      const order = {
        ...product,
        total: (product.price + product.toppings.length * 10) * product.count,
      };
      this.orderList.push(order);
      this.countTotal();
      this.resetOrder();
    },
    // 計算總價
    countTotal() {
      // reduce 將陣列化為單一值
      this.orderTotal = this.orderList.reduce(
        (acc, obj) => acc + obj.total,
        0
      );
    },
    resetOrder() {
      this.itemSelected = {};
    },
    // 產生訂單
    generateOrder(orders, total) {
      const date = new Date().toLocaleString();
      this.checkedOrder.time = date;
      this.checkedOrder.orders = orders;
      this.checkedOrder.total = total;
      this.orderList = [];
      this.resetOrder();
    },
  },
};

Vue.createApp(app).mount("#app");