2025-04-09-JavaScript 系列九:第6課 ── 學習用外部狀態管理 vue 元件
JavaScript 系列九:第6課 ── 學習用外部狀態管理 vue 元件
課程目標
學習用外部狀態管理 vue 元件
課程內容
前面的課程中,元件的狀態很單純,都是匯入之後直接使用
但是有些元件匯入之後,一開始在畫面上是不顯示的
比方說視窗類型的元件:這種元件需要額外有一個狀態,來管理它是否顯示
課後作業
這一課作業,請先看一下 Dialog 視窗元件的用法
https://quasar.dev/vue-components/dialog
請使用官方的元件試玩工具:Vue SFC Playground
https://sfc.vuejs.org/
請建立 MyDialog.vue 元件,並支援下列功能
v-model 能夠管理視窗是否顯示
預設的 slot 能夠放入視窗內容
完成之後,請在 App.vue 之中匯入此元件,並用多個範例證明,有完成上述規格
做出以上功能,你就完成這次的課程目標了!
好的 👍 我幫你把這一課的作業拆解成「小步驟」來完成,並給你完整範例程式碼。
🎯 學習目標
- 建立一個 MyDialog.vue 元件
- 讓它支援
v-model
控制「顯示/隱藏」
- 允許在 default slot 放入任意內容
- 在
App.vue
中測試多個 MyDialog
🛠️ Step 1:建立 MyDialog.vue
這個元件要支援 v-model
,所以我們要使用 defineProps
+ defineEmits
搭配 雙向綁定語法。
📂 MyDialog.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
| <template> <div v-if="modelValue" class="dialog-backdrop"> <div class="dialog"> <!-- slot:顯示外部傳入的內容 --> <slot></slot> <div class="actions"> <button @click="closeDialog">關閉</button> </div> </div> </div> </template>
<script setup> const props = defineProps({ modelValue: { type: Boolean, default: false } })
const emit = defineEmits(['update:modelValue'])
// 關閉對話框 function closeDialog() { emit('update:modelValue', false) } </script>
<style scoped> .dialog-backdrop { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; }
.dialog { background: #fff; padding: 20px; border-radius: 12px; min-width: 300px; max-width: 500px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); }
.actions { margin-top: 20px; text-align: right; } </style>
|
🛠️ Step 2:在 App.vue 測試
建立多個 MyDialog,並用不同內容確認 slot 與 v-model 功能。
📂 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
| <template> <h2>🎯 MyDialog 測試</h2>
<button @click="showDialog1 = true">開啟對話框1</button> <button @click="showDialog2 = true">開啟對話框2</button>
<!-- 測試 Dialog 1 --> <MyDialog v-model="showDialog1"> <h3>對話框 1</h3> <p>這是第一個對話框的內容。</p> </MyDialog>
<!-- 測試 Dialog 2 --> <MyDialog v-model="showDialog2"> <h3>對話框 2</h3> <p>這裡放的是另一個內容。</p> <p>slot 讓我們能自由放入 HTML!</p> </MyDialog> </template>
<script setup> import { ref } from 'vue' import MyDialog from './MyDialog.vue'
const showDialog1 = ref(false) const showDialog2 = ref(false) </script>
|
✅ 功能驗證
- 預設不顯示
- 點擊按鈕 → 打開對話框
- 點擊「關閉」 →
v-model
更新,對話框隱藏
- slot 的內容可以不同
這樣就完成了 「用外部狀態管理元件」 的作業 🎉
懂了 👍
你不要「測試用的例子」,而是想要一些 實際日常會用到的對話框場景。
我幫你設計幾個常見的對話框情境,全部用 MyDialog
來示範:
📂 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
| <template> <h2>📌 常見的對話框範例</h2>
<!-- 登入對話框 --> <button @click="showLogin = true">登入</button> <MyDialog v-model="showLogin"> <h3>🔑 使用者登入</h3> <label> 帳號: <input type="text" placeholder="輸入帳號" /> </label> <br /> <label> 密碼: <input type="password" placeholder="輸入密碼" /> </label> <div class="actions"> <button @click="showLogin = false">取消</button> <button @click="handleLogin">登入</button> </div> </MyDialog>
<!-- 確認刪除對話框 --> <button @click="showDelete = true">刪除資料</button> <MyDialog v-model="showDelete"> <h3>⚠️ 確認刪除</h3> <p>你確定要刪除這筆資料嗎?此動作無法復原。</p> <div class="actions"> <button @click="showDelete = false">取消</button> <button class="danger" @click="confirmDelete">確定刪除</button> </div> </MyDialog>
<!-- 表單輸入對話框 --> <button @click="showForm = true">新增聯絡人</button> <MyDialog v-model="showForm"> <h3>👤 新增聯絡人</h3> <label> 姓名: <input type="text" v-model="newContact.name" /> </label> <br /> <label> 電話: <input type="text" v-model="newContact.phone" /> </label> <div class="actions"> <button @click="showForm = false">取消</button> <button @click="saveContact">儲存</button> </div> </MyDialog> </template>
<script setup> import { ref } from 'vue' import MyDialog from './MyDialog.vue'
const showLogin = ref(false) const showDelete = ref(false) const showForm = ref(false)
const newContact = ref({ name: '', phone: '' })
function handleLogin() { alert('登入成功!(這裡可以改成實際 API 呼叫)') showLogin.value = false }
function confirmDelete() { alert('資料已刪除!') showDelete.value = false }
function saveContact() { alert(`已新增聯絡人:${newContact.value.name} / ${newContact.value.phone}`) newContact.value = { name: '', phone: '' } showForm.value = false } </script>
<style scoped> .actions { margin-top: 20px; text-align: right; }
button { margin: 4px; padding: 6px 12px; border-radius: 6px; border: 1px solid #ccc; cursor: pointer; }
button.danger { background: #e74c3c; color: white; border: none; } </style>
|
✨ 提供的例子
- 登入對話框:帳號 + 密碼輸入
- 確認刪除對話框:刪除前提醒
- 表單輸入對話框:新增聯絡人
這三種是日常開發中最常用的對話框場景。
好問題 👌
我們之前有把 MyDialog.vue 寫好,但我剛剛只放了 App.vue 的日常例子,沒有再把 MyDialog.vue
重複給你。
我幫你整理完整專案結構,這樣你就清楚知道 MyDialog.vue 要放在哪裡。
📂 專案結構
1 2 3
| src/ ├─ App.vue ← 使用 MyDialog 的地方 └─ MyDialog.vue ← 對話框元件
|
📄 MyDialog.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
| <template> <div v-if="modelValue" class="dialog-backdrop"> <div class="dialog"> <!-- slot:顯示外部傳入的內容 --> <slot></slot> </div> </div> </template>
<script setup> const props = defineProps({ modelValue: { type: Boolean, default: false } })
const emit = defineEmits(['update:modelValue'])
// 讓外部可以呼叫:<MyDialog v-model="xxx"> 來控制顯示 function closeDialog() { emit('update:modelValue', false) } </script>
<style scoped> .dialog-backdrop { position: fixed; inset: 0; /* top, right, bottom, left = 0 */ background: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; }
.dialog { background: #fff; padding: 20px; border-radius: 12px; min-width: 300px; max-width: 500px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); } </style>
|
📄 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
| <template> <h2>📌 常見的對話框範例</h2>
<!-- 登入對話框 --> <button @click="showLogin = true">登入</button> <MyDialog v-model="showLogin"> <h3>🔑 使用者登入</h3> <label> 帳號: <input type="text" placeholder="輸入帳號" /> </label> <br /> <label> 密碼: <input type="password" placeholder="輸入密碼" /> </label> <div class="actions"> <button @click="showLogin = false">取消</button> <button @click="handleLogin">登入</button> </div> </MyDialog>
<!-- 確認刪除對話框 --> <button @click="showDelete = true">刪除資料</button> <MyDialog v-model="showDelete"> <h3>⚠️ 確認刪除</h3> <p>你確定要刪除這筆資料嗎?此動作無法復原。</p> <div class="actions"> <button @click="showDelete = false">取消</button> <button class="danger" @click="confirmDelete">確定刪除</button> </div> </MyDialog>
<!-- 表單輸入對話框 --> <button @click="showForm = true">新增聯絡人</button> <MyDialog v-model="showForm"> <h3>👤 新增聯絡人</h3> <label> 姓名: <input type="text" v-model="newContact.name" /> </label> <br /> <label> 電話: <input type="text" v-model="newContact.phone" /> </label> <div class="actions"> <button @click="showForm = false">取消</button> <button @click="saveContact">儲存</button> </div> </MyDialog> </template>
<script setup> import { ref } from 'vue' import MyDialog from './MyDialog.vue'
const showLogin = ref(false) const showDelete = ref(false) const showForm = ref(false)
const newContact = ref({ name: '', phone: '' })
function handleLogin() { alert('登入成功!(這裡可以改成實際 API 呼叫)') showLogin.value = false }
function confirmDelete() { alert('資料已刪除!') showDelete.value = false }
function saveContact() { alert(`已新增聯絡人:${newContact.value.name} / ${newContact.value.phone}`) newContact.value = { name: '', phone: '' } showForm.value = false } </script>
<style scoped> .actions { margin-top: 20px; text-align: right; }
button { margin: 4px; padding: 6px 12px; border-radius: 6px; border: 1px solid #ccc; cursor: pointer; }
button.danger { background: #e74c3c; color: white; border: none; } </style>
|
👉 這樣你有 MyDialog.vue(元件本體) + App.vue(日常例子)。
要不要我再幫你把 MyDialog.vue
加上「右上角關閉按鈕 (X)」?這樣會更像實際專案常用的 Dialog。
這個問題問得很好 👍
按鈕顏色沒有「絕對規定」,但大部分 UI 設計(像 Material Design、Bootstrap、Ant Design、Quasar 等框架)都有一些「常見的配色習慣」:
🔑 常見按鈕顏色建議
1. 登入 / 確認 / 儲存
👉 因為「藍色」給人穩定、信任的感覺,
👉 「綠色」則表示成功、完成。
2. 刪除 / 危險動作
👉 紅色能立即讓使用者警覺「這個動作不可逆」。
3. 新增 / 建立
➕ Add / Create
常見顏色:綠色 (Green)
例子:
- #4caf50(Material Design Green 500)
- Bootstrap
.btn-success
👉 綠色象徵「新增、成長、正向」。
4. 取消 / 關閉
❌ Secondary / Neutral
常見顏色:灰色 (Gray)
例子:
- #6c757d(Bootstrap
.btn-secondary
)
- Material Design「Cancel」多用灰色文字 + 輕量按鈕
👉 灰色代表「不強調、不會造成危險」。
🎨 小總結表
動作 |
常見顏色 |
登入 / 確認 / 儲存 |
藍色 / 綠色 |
刪除 |
紅色 |
新增 |
綠色 |
取消 / 關閉 |
灰色 |
要不要我幫你把 App.vue 的範例按鈕,套上這些「符合業界習慣的顏色」?
好的 👍 我幫你把你的 Lesson6.vue
中按鈕顏色修改成 符合業界習慣的配色,並標註用途:
修改後的 Lesson6.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> <h2>JavaScript 系列九:第6課 ── 學習用外部狀態管理 vue 元件</h2>
<!-- 登入對話框 --> <button class="primary" @click="showLogin = true">登入</button> <MyDialog v-model="showLogin"> <h3>🔑 使用者登入</h3> <label> 帳號: <input type="text" placeholder="輸入帳號" /> </label> <br /> <label> 密碼: <input type="password" placeholder="輸入密碼" /> </label> <div class="actions"> <button class="secondary" @click="showLogin = false">取消</button> <button class="primary" @click="handleLogin">登入</button> </div> </MyDialog>
<!-- 確認刪除對話框 --> <button class="danger" @click="showDelete = true">刪除資料</button> <MyDialog v-model="showDelete"> <h3>⚠️ 確認刪除</h3> <p>你確定要刪除這筆資料嗎?此動作無法復原。</p> <div class="actions"> <button class="secondary" @click="showDelete = false">取消</button> <button class="danger" @click="confirmDelete">確定刪除</button> </div> </MyDialog>
<!-- 表單輸入對話框 --> <button class="success" @click="showForm = true">新增聯絡人</button> <MyDialog v-model="showForm"> <h3>👤 新增聯絡人</h3> <label> 姓名: <input type="text" v-model="newContact.name" /> </label> <br /> <label> 電話: <input type="text" v-model="newContact.phone" /> </label> <div class="actions"> <button class="secondary" @click="showForm = false">取消</button> <button class="success" @click="saveContact">儲存</button> </div> </MyDialog> </template>
<script setup> import { ref } from 'vue' import MyDialog from './Lesson6_components/MyDialog.vue'
const showLogin = ref(false) const showDelete = ref(false) const showForm = ref(false)
const newContact = ref({ name: '', phone: '' })
function handleLogin() { alert('登入成功!(這裡可以改成實際 API 呼叫)') showLogin.value = false }
function confirmDelete() { alert('資料已刪除!') showDelete.value = false }
function saveContact() { alert(`已新增聯絡人:${newContact.value.name} / ${newContact.value.phone}`) newContact.value = { name: '', phone: '' } showForm.value = false } </script>
<style scoped> .actions { margin-top: 20px; text-align: right; }
button { margin: 4px; padding: 6px 12px; border-radius: 6px; border: 1px solid #ccc; cursor: pointer; }
/* 主要動作:登入 / 儲存 */ button.primary { background-color: #1976d2; /* 藍色 */ color: white; border: none; }
/* 成功 / 新增 */ button.success { background-color: #28a745; /* 綠色 */ color: white; border: none; }
/* 危險 / 刪除 */ button.danger { background-color: #e74c3c; /* 紅色 */ color: white; border: none; }
/* 次要 / 取消 */ button.secondary { background-color: #6c757d; /* 灰色 */ color: white; border: none; }
h2 { color: rgb(149, 44, 95); } </style>
|
🎨 套用規則說明
- primary → 藍色:登入 / 確認
- success → 綠色:新增 / 儲存
- danger → 紅色:刪除
- secondary → 灰色:取消