到目前為止,我們已經完成:
Day 1:用「流程控制」把點餐步驟拆開(飲料 → 甜度 → 冰量),並以 v-if、:disabled
負責 UX 節奏。
Day 2:用 事件驅動(@click)把使用者的「送出」行為寫進系統狀態(orders),完成第一個資料落袋的流程。
但現實世界從不會就此滿足 。使用者/老闆下一句常常是:
「我要知道這筆是誰點的,還想加備註(少冰、3 點拿
)!」
這就引出今天 Day 3 的主角:v-model
。我們將把「輸入框 ↔ 狀態」打通,讓姓名、備註和點餐內容一起被儲存。
明天教完bind之後,會補充為什麼我不單單用@change
就好了?
今天的學習路徑圖一樣奉上
如同前言提到的,因為我們是需要統計點餐人的各品項
好不容易統計完畢了~
可是卻沒有紀錄是誰點的? 如果要幾點拿? 在哪裡? 我們負責統計的秘書也很難整理資料
這時候就有新的user story出現惹!!
角色 | 故事 | 驗收條件 |
---|---|---|
點餐者 | 作為使用者,我想輸入「姓名」與「備註」,並在送出時與飲料選擇一起被保存,讓統計者能分辨誰點了什麼。 | 送出後:1. 訂單被加入 orders ,含 { name, note, drink, sweetness, ice } ;2. 表單清空;3. 清單顯示姓名與內容。 |
建立在昨天的基礎上
我們勢必多兩個欄位點餐者姓名、備註
但是姓名是必填欄位
接者我們就把資料面跟行為都補充吧!!
條列出來就會顯示以下的表格可以參照
面向 | 需求描述 |
---|---|
功能 | 在 Day 2 的基礎上,新增輸入欄位:點餐者姓名(必填)、備註(選填),送單時一併寫入。 |
使用者 | 點餐者(仍以單一角色為主)。 |
資料 | 每筆訂單包含 { name, note?, drink, sweetness, ice } 。 |
狀態 | 新增 name 、note ;orders 陣列元素結構擴充。 |
行為 | 送出前需驗證:name 與三個選項皆完成;成功後 push、清空含 name/note 。 |
畫面 | 顯示姓名輸入框(必要時提示必填)、備註輸入框、與更新後的訂單清單(包含姓名)。 |
接者我們再把時序圖畫出來:
這是以下的功能時序圖,基於前兩天的功能
我們的程式越來越越龐大了
但是不用擔心~ 我們可是有vue魔法呢!
下面HL的就是我們今天要學習的重點,也是新添加的功能
接者一樣我們需要有輸入表單
這時候可以去找官方document或是google
也是十之八九會告訴你用v-model搞定~
這時候你可能會問我不能用onchange或是bind處理嗎?
沒關係先別急~人別這麼猴急我們後續再說
需求 | Vue 技術與重點 |
---|---|
姓名、備註輸入 | v-model :input/textarea 與狀態雙向同步(可搭配 .trim 、.lazy 修飾符) |
表單驗證 | computed 或直接在 addOrder 內檢查 name && drink && sweetness && ice |
寫入清單 | orders.value.push({...}) ;元素結構擴充含 name 、note |
清空狀態 | 將 name/note/drink/sweetness/ice 設為空字串 |
顯示清單 | v-for 迭代 orders ,多顯示 name 與 note |
今天要介紹的新朋友就是v-model拉
這邊我覺得官方寫得很簡單了
就直接拿官方的範例用瞜
<script setup>
import { ref } from 'vue'
const message = ref('')
</script>
<template>
<p>Message is: {{ message }}</p>
<input v-model="message" placeholder="edit me" />
</template>
📃 tips這邊有兩個v-model的小技巧分享給大家~蠻好用的
今天你只要在v-model後面新增.trim的修飾詞
即可自動幫你處理莫名其妙的空格問題
例如有些使用者可能是輸入 :
空格空格ALICE => 這樣就會造成輸入的值不太對存到後面server很麻煩
這時候有些小修飾詞加上去可以避免掉要自己處理字串空格的麻煩
但要注意的是這邊是處理字首前方
跟最後方
後面的空格喔!! 中間的空格不算
使用者輸入過程中,變更值時就會修剪空白(Vue 3 的行為是每次 input 事件都處理)。
<template>
<input v-model.trim="name" placeholder="輸入名字" />
<p>儲存的值:'{{ name }}'</p>
</template>
<script setup>
import { ref } from 'vue'
const name = ref('')
</script>
有時候使用者在思考,或是打字過程但是還不想這麼快顯示未打完
的東西
其實這個方式也可以做到避免未輸入完就去call api執行避免效能浪費
使用者 離開輸入框 (blur)
這個有v-model + debounce的用法~ 不過我這邊還不想講先埋耿
後續我串接後端的時候會詳細說明使用情境喔!!
當然你也可以同時使用 v-model.lazy.trim="message"
達成去掉空格跟延遲輸入的功能
有了使用情境根具備的vue技巧後
我們就可以開始操刀了
這時候可以先把流程圖畫出來~
方便自己寫code梳理邏輯
其實今天沒有很難
只有新增兩個data跟綁定v-model輸入框框而已
我們期待的UI大概會這樣長
直接go完成我們的程式碼
我們只要新增兩個欄位是姓名跟備註即可
新增 v-if="!name"
判斷是否填姓名
<!-- DrinkOrdersDay3.vue -->
<template>
<h2>飲料點單(Day 3:加入姓名/備註 + 送單)</h2>
<!-- Day 3 新增:點餐者姓名(必填)與備註(選填) -->
<div>
<label>
姓名(必填)
<input type="text" v-model.trim="name" placeholder="請輸入你的名字" />
</label>
<p v-if="!name">⚠️ 尚未填寫姓名</p>
</div>
<div>
<label>
備註(選填)
<textarea v-model.trim="note" placeholder="例如:三點拿、少冰"></textarea>
</label>
</div>
<!-- 步驟 1:飲料 -->
<fieldset>
<legend>步驟 1:選擇飲料</legend>
<label>
<input type="radio" name="drink" value="紅茶" @change="onDrinkChange('紅茶')" /> 紅茶
</label>
<label>
<input type="radio" name="drink" value="綠茶" @change="onDrinkChange('綠茶')" /> 綠茶
</label>
<p v-if="!drink">⚠️ 尚未選取飲料</p>
<p v-else>✅ 已選:{{ drink }}</p>
</fieldset>
<!-- 步驟 2:甜度(只在選好飲料後顯示) -->
<fieldset v-if="drink">
<legend>步驟 2:選擇甜度</legend>
<label>
<input type="radio" name="sweetness" value="正常甜" @change="onSweetnessChange('正常甜')" /> 正常甜
</label>
<label>
<input type="radio" name="sweetness" value="去糖" @change="onSweetnessChange('去糖')" /> 去糖
</label>
<p v-if="!sweetness">⚠️ 尚未選擇甜度</p>
<p v-else>✅ 已選:{{ sweetness }}</p>
</fieldset>
<!-- 步驟 3:冰量(只在選好甜度後顯示) -->
<fieldset v-if="drink && sweetness">
<legend>步驟 3:選擇冰量</legend>
<label>
<input type="radio" name="ice" value="正常冰" @change="onIceChange('正常冰')" /> 正常冰
</label>
<label>
<input type="radio" name="ice" value="去冰" @change="onIceChange('去冰')" /> 去冰
</label>
<p v-if="!ice">⚠️ 尚未選擇冰量</p>
<p v-else>✅ 已選:{{ ice }}</p>
</fieldset>
<!-- 送出按鈕:需姓名 + 三選皆完成 -->
<button :disabled="!canSubmit" @click="addOrder">送出</button>
<!-- 簡短摘要(可選) -->
<p v-if="canSubmit">
你的選擇:{{ drink }} / {{ ice }} / {{ sweetness }}({{ name }}<span v-if="note">,備註:{{ note }}</span>)
</p>
<!-- 已送出的訂單清單 -->
<section v-if="orders.length">
<h3>目前已送出的訂單</h3>
<ul>
<li v-for="(o, i) in orders" :key="i">
{{ i + 1 }}. {{ o.name }}:{{ o.drink }} / {{ o.ice }} / {{ o.sweetness }}
<span v-if="o.note">(備註:{{ o.note }})</span>
</li>
</ul>
</section>
</template>
<script setup>
import { ref, computed } from 'vue'
// Day 3 新增:姓名、備註
const name = ref('')
const note = ref('')
// Day 1/2:三個選項(維持 @change,不用 v-model)
const drink = ref('')
const sweetness = ref('')
const ice = ref('')
// Day 2:訂單清單
const orders = ref([])
// 事件處理(選項)
function onDrinkChange(v) { drink.value = v }
function onSweetnessChange(v) { sweetness.value = v }
function onIceChange(v) { ice.value = v }
// 送出條件:姓名 + 三選皆完成
const canSubmit = computed(() =>
name.value && drink.value && sweetness.value && ice.value
)
// Day 2 + Day 3:送出一筆訂單,寫入姓名/備註
function addOrder() {
if (!canSubmit.value) return
orders.value.push({
name: name.value,
note: note.value,
drink: drink.value,
sweetness: sweetness.value,
ice: ice.value
})
// 清空,準備下一筆
name.value = ''
note.value = ''
drink.value = ''
sweetness.value = ''
ice.value = ''
}
</script>
我們來整理一下今天學的東西
需求回顧
v-if
/ :disabled
)和 Day 2 的事件驅動(@click
→ addOrder
)基礎上,技術關鍵
v-model
:把 <input>
與 <textarea>
的值與 Vue ref
變數雙向綁定。
.trim
(去除前後空白)、.lazy
(失焦後才同步)可依需求選用。@click
+ addOrder()
:沿用 Day 2 送單邏輯,但 push 的物件新增 name
與 note
。v-for
:在畫面上即時顯示包含姓名、備註的新訂單。:class
)」和 Day 5 的「清單統計」奠定了堅實基礎。