2025-01-02 旅行分帳應用程式-步驟04

2025-01-02 旅行分帳應用程式-步驟04

接下來我們來實作點選「➕ 新增支出款項」後,跳出一個 Modal,讓你可以輸入:

  • ✏️ 支出說明(如:買門票、租車費)
  • 💰 金額

✅ 第一步:新增 Modal 區塊

<template> 最下面(例如在 </div> 上面),加入這段:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 新增支出 Modal -->
<div v-if="showAddExpense" class="modal-overlay" @click.self="showAddExpense = false">
<div class="modal">
<h3>新增支出款項</h3>
<input v-model="expenseDescription" placeholder="支出說明(如:午餐)" />
<input v-model.number="expenseAmount" type="number" placeholder="金額(如:500)" />

<div class="modal-buttons">
<button class="btn confirm" @click="addExpense">確認</button>
<button class="btn cancel" @click="showAddExpense = false">取消</button>
</div>
</div>
</div>

✅ 第二步:在 <script setup> 中加入對應的變數和方法

請補充在 setup 的區塊中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const expenseDescription = ref('')
const expenseAmount = ref(0)

function addExpense() {
const member = members.value[selectedMemberIndex.value]
if (member && expenseDescription.value.trim() && expenseAmount.value > 0) {
// 暫時只顯示 console 訊息,之後可以加到 member 的支出列表中
console.log(`為 ${member.name} 新增支出: ${expenseDescription.value}, 金額 $${expenseAmount.value}`)

// 清空輸入欄位
expenseDescription.value = ''
expenseAmount.value = 0
showAddExpense.value = false
} else {
alert('請輸入有效的說明與金額')
}
}

✅ 第三步:加入基本樣式(放到 <style scoped> 中)

在最下面加入這段:

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
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
}

.modal {
background: white;
padding: 20px;
border-radius: 10px;
width: 90%;
max-width: 400px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}

.modal h3 {
margin-bottom: 10px;
}

.modal input {
display: block;
width: 100%;
padding: 8px;
margin-bottom: 12px;
font-size: 16px;
border-radius: 5px;
border: 1px solid #ccc;
}

.modal-buttons {
display: flex;
justify-content: flex-end;
gap: 10px;
}

🧪 測試流程

  1. 加一個成員 ➕
  2. 點選成員(會出現「新增支出款項」按鈕)
  3. 點按鈕 → 跳出 modal
  4. 輸入「說明」和「金額」
  5. 點「確認」會 console.log(...),之後我們可以再加到 member 資料中

需要我幫你接下來「支出紀錄存進成員的資料中」嗎?還是你想先玩一下 Modal 的部分 😄?

非常棒的想法!我們可以讓「支出項目」欄位:

  1. ✅ 先從下拉選單選基本項目(例如:餐飲、住宿、門票…)
  2. 📝 如果使用者輸入新項目,就自動加入到下拉選單中,未來可以選用

🔧 我們會這樣設計:

👇 預設基本支出項目

1
2
const expenseCategories = ref(['餐飲', '住宿', '門票', '交通', '其他'])
const selectedCategory = ref('')

💡 在 Modal 裡改成可選又可輸入的欄位

你可以把這段 input 替換成:

1
2
3
4
5
6
7
8
9
<label>支出項目:</label>
<input
list="category-options"
v-model="selectedCategory"
placeholder="輸入或選擇項目"
/>
<datalist id="category-options">
<option v-for="cat in expenseCategories" :key="cat" :value="cat" />
</datalist>

✅ 修改 addExpense() 加入新項目到列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function addExpense() {
const member = members.value[selectedMemberIndex.value]
const trimmedCategory = selectedCategory.value.trim()

if (member && trimmedCategory && expenseAmount.value > 0) {
console.log(`為 ${member.name} 新增支出: ${trimmedCategory}, 金額 $${expenseAmount.value}`)

// ✅ 如果這項目不在預設清單中,就加入
if (!expenseCategories.value.includes(trimmedCategory)) {
expenseCategories.value.push(trimmedCategory)
}

// 清空
selectedCategory.value = ''
expenseAmount.value = 0
showAddExpense.value = false
} else {
alert('請輸入有效的項目與金額')
}
}

✅ 預期效果

操作 結果
輸入「飲料」 → 沒在列表 ✔ 自動加入下次可選
選擇「住宿」→ ✔ 直接使用現有項目
每次支出都可複用過去打過的類別

要不要我幫你把這一段整合進你的整體 Modal 程式碼中呢?或者你想先試試? 😄

好的,我將這個需求整合進你的 Modal 程式碼中,讓你可以選擇或輸入新的支出項目。

更新後的程式碼

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
<template>
<div class="app-container">
<!-- 上方: 標題 -->
<div class="header">
<h1>旅遊分帳應用程式</h1>
</div>

<!-- 中間: 成員清單 -->
<div class="member-list" ref="memberListRef">
<h2>分帳成員</h2>
<ul>
<li v-for="(member, index) in members"
:key="index" @click="selectMember(index)"
:class="{ selected: selectedMemberIndex === index }">
{{ member.name }} <span class="owe">應付 $0</span>
<!-- 如果這位成員是被選中的,就顯示新增支出按鈕 -->
<div v-if="selectedMemberIndex === index" class="add-expense-section">
<button class="btn small-btn" @click.stop="showAddExpense = true">➕ 新增支出款項</button>
</div>
</li>
</ul>
</div>

<!-- 下方: 新增成員按鈕 -->
<div class="bottom-section">
<input
v-model="newMemberName"
class="member-input"
placeholder="輸入成員姓名"
@keyup.enter="addMember"
/>
<button class="btn add-btn" @click="addMember">➕ 新增成員</button>
</div>

<!-- Modal: 新增支出款項 -->
<div v-if="showAddExpense" class="modal-backdrop">
<div class="modal">
<h3>新增支出</h3>
<label>支出項目:</label>
<input
list="category-options"
v-model="selectedCategory"
placeholder="輸入或選擇項目"
/>
<datalist id="category-options">
<option v-for="cat in expenseCategories" :key="cat" :value="cat" />
</datalist>

<label>金額:</label>
<input
v-model="expenseAmount"
type="number"
placeholder="輸入金額"
/>

<div class="modal-buttons">
<button class="btn confirm" @click="addExpense">確認</button>
<button class="btn cancel" @click="showAddExpense = false">取消</button>
</div>
</div>
</div>
</div>
</template>

<script setup>
import { ref, nextTick } from 'vue'

const members = ref([])
const newMemberName = ref('')
const memberListRef = ref(null) //拿到成員清單的 DOM
const selectedMemberIndex = ref(null) // 儲存目前選中的成員 index
const showAddExpense = ref(false)

// 預設的支出項目
const expenseCategories = ref(['餐飲', '住宿', '門票', '交通', '其他'])
const selectedCategory = ref('')
const expenseAmount = ref(0)

// 新增成員
function addMember() {
const name = newMemberName.value.trim()

if (name !== '' && !members.value.some(m => m.name === name)) {
members.value.push({ name })
newMemberName.value = ''

//新增完畢滾動到底
nextTick(() => {
const el = memberListRef.value
if (el) {
el.scrollTop = el.scrollHeight
}
})
}
}

// 選擇成員
function selectMember(index) {
// 點第二次同一位就取消選取
if (selectedMemberIndex.value === index) {
selectedMemberIndex.value = null
showAddExpense.value = false
} else {
selectedMemberIndex.value = index
showAddExpense.value = false
}
}

// 新增支出
function addExpense() {
const member = members.value[selectedMemberIndex.value]
const trimmedCategory = selectedCategory.value.trim()

if (member && trimmedCategory && expenseAmount.value > 0) {
console.log(`為 ${member.name} 新增支出: ${trimmedCategory}, 金額 $${expenseAmount.value}`)

// 如果這項目不在預設清單中,就加入
if (!expenseCategories.value.includes(trimmedCategory)) {
expenseCategories.value.push(trimmedCategory)
}

// 清空
selectedCategory.value = ''
expenseAmount.value = 0
showAddExpense.value = false
} else {
alert('請輸入有效的項目與金額')
}
}
</script>

<style scoped>
/* 設定整體樣式 */
.app-container {
max-width: 500px;
margin: auto;
font-family: Arial, sans-serif;
display: flex;
flex-direction: column; /* 讓區塊沿著垂直方向排列 */
height: 100vh; /* 讓畫面填滿整個視窗 */
justify-content: space-between; /* 讓畫面分成上中下三部分 */
}

h1 {
font-size: 2rem;
}

.header {
font-size: 1.5em;
font-weight: 600;
color: #5e3a4a;
text-align: center;
padding: 10px;
background-color: #f8e6e6;
border-bottom: 2px solid #d16a7b;
}

.member-list {
flex-grow: 1;
padding: 10px;
overflow-y: auto;
}

h2 {
color: #4a2c38;
}

ul {
list-style-type: none;
padding: 0;
}

li {
background: #f8f8f8;
margin: 5px 0;
padding: 8px;
border-radius: 5px;
display: flex;
justify-content: space-between;
align-items: center;
}

.owe {
color: red;
}

.bottom-section {
padding: 10px;
background-color: #f8e6e6;
border-top: 2px solid #d16a7b;
display: flex;
gap: 10px;
align-items: center;
justify-content: center;
}

.member-input {
flex: 1;
min-width: 200px;
padding: 10px;
font-size: 16px;
border-radius: 5px;
border: 1px solid #ccc;
}

.add-btn {
width: auto;
padding: 10px 20px;
}

.btn {
background-color: #d16a7b;
color: white;
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
width: 100%;
}

.btn:hover {
background-color: #b85967;
}

.selected {
background-color: #ffe6e6;
}

.small-btn {
margin-top: 8px;
font-size: 14px;
padding: 6px 10px;
}

/* Modal 相關樣式 */
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
justify-content: center;
align-items: center;
}

.modal {
background-color: white;
padding: 20px;
border-radius: 10px;
min-width: 300px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
text-align: center;
}

.modal input {
width: 80%;
padding: 8px;
margin: 10px 0;
border-radius: 5px;
border: 1px solid #ccc;
}

.modal-buttons {
display: flex;
justify-content: space-between;
}

.btn.confirm {
background-color: #4caf50;
}

.btn.cancel {
background-color: #f44336;
}
</style>

👇 更新後重點

  1. 選擇或輸入支出項目:現在有一個 datalist 可供選擇,並且支出項目會自動新增到列表中。
  2. 新增支出項目:在 addExpense 函數中,我們檢查是否為新項目,並將其加入到 expenseCategories 中。
  3. Modal 交互:支出項目、金額輸入後會關閉 Modal,並且新增支出。

現在你可以選擇或輸入新的支出項目,並且這些項目會被添加到下拉列表中,並且在將來再次使用時可以選擇。

試試看這個更新,應該會符合你的需求! 😄