成品:
成品網址
成品功能: 1.在最上方 yout balance 處顯示所剩餘額 2.在 income、expense 處顯示所以收入以及花費金額並呈現綠色以及紅色 3.History 的地方會記錄並顯示出花費狀態以及輸入的內容 ex 書本支出 100 元並且會有刪除品項的按鈕 ps.支出會顯示紅色 收入會顯示綠色 4.下方有新增各種行為的欄位並且有加入的 submit 按鈕
HTML 上 CSS 之前的 HTML:
程式碼: 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 <body > <h2 > Expense Tracker</h2 > <div class ="container" > <h4 > Your Balance</h4 > <h1 id ="balance" > $0.00</h1 > <div class ="inc-exp-container" > <div > <h4 > Income</h4 > <p id ="money-plus" class ="money plus" > $0.00</p > </div > <div > <h4 > Expense</h4 > <p id ="money-minus" class ="money minus" > -$0.00</p > </div > </div > <h3 > History</h3 > <ul id ="list" class ="list" > </ul > <h3 > Add new transaction</h3 > <form id ="form" > <div class ="form-control" > <label for ="text" > Text</label > <input type ="text" id ="text" placeholder ="Enter text..." /> </div > <div class ="form-control" > <label for ="amount" > Amount <br /> (negative - expense, positive - income) </label > <input type ="number" id ="amount" placeholder ="Enter amount..." /> </div > <button class ="btn" > Add transaction</button > </form > </div > <script src ="script.js" > </script > </body >
CSS: CSS 設置好的樣式
程式碼 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 @import url('https://fonts.googleapis.com/css?family=Lato&display=swap' ) /* 讓特定的物件在瀏覽器上變的比較明顯 */ :root { --box-shadow : 0 1px 3px rgba (0 , 0 , 0 , 0.12 ), 0 1px 2px rgba (0 , 0 , 0 , 0.24 ); } * { box-sizing : border-box; } body { background-color : #f7f7f7 ; display : flex; flex-direction : column; align-items : center; justify-content : center; min-height : 100vh ; margin : 0 ; font-family : 'Lato' , sans-serif; } .container { margin : 30px auto; width : 350px ; } h1 { letter-spacing : 1px ; margin : 0 ; } h3 { border-bottom : 1px solid #bbb ; padding-bottom : 10px ; margin : 40px 0 10px ; } h4 { margin : 0 ; text-transform : uppercase; } .inc-exp-container { background-color : #fff ; box-shadow : var (--box--shadow); padding : 20px ; display : flex; justify-content : space-between; margin : 20px 0 ; } .inc-exp-container >div { flex : 1 ; text-align : center; } .inc-exp-container >div :first -of-type { border-right : 1px solid #dedede ; } .money { font-size : 20px ; letter-spacing : 1px ; margin : 5px ; } .money .plus { color : #2ecc71 ; } .money .minus { color : #c0392b ; } label { display : inline-block; margin : 10px 0 ; } input [type='text' ] ,input [type='number' ] { border : 1px solid #dedede ; border-radius : 2px ; display : block; font-size : 16px ; padding : 10px ; width : 100% ; } .btn { cursor : pointer; background-color : #9c88ff ; box-shadow : var (--box--shadow); color : #fff ; border : 0 ; display : block; font-size : 16px ; margin : 10px 0 30px ; padding : 10px ; width : 100% ; } .btn :focus ,.delete-btn { outline : 0 ; } .list { list-style-type : none; padding : 0 ; margin-bottom : 40px ; } .list li { background-color : #fff ; box-shadow : var (--box--shadow); color : #333 ; display : flex; justify-content : space-between; position : relative; padding : 10px ; margin : 10px 0 ; } .list li .plus { border-right : 5px solid #2ecc71 ; } .list li .minus { border-right : 5px solid #c0392b ; } .delete-btn { cursor : pointer; background-color : #e74c3c ; border : 0 ; color : #fff ; font-size : 20px ; line-height : 20px ; padding : 2px 5px ; position : absolute; top : 50% ; left : 0% ; transform : translate (-100% , -50% ); opacity : 0 ; transition : opacity 0.3s ease; } .list li :hover .delete-btn { opacity : 1 ; }
小補充:
無
JS: 變數設置 要使用 JS 處理的地方基本上都要抓取:
1 2 3 4 5 6 7 const balance = document .getElementById("balance" );const money_plus = document .getElementById("money-plus" );const money_minus = document .getElementById("money-minus" );const list = document .getElementById("list" );const form = document .getElementById("form" );const text = document .getElementById("text" );const amount = document .getElementById("amount" );
functions: addTransactionDOM-把收入或是消費的新的 li 呈現到 History 上面(也就是 DOM 上面) 他會在 addTransaction function 被呼叫,因為只有當新的交易產生這個功能才會被需要
注意的點: 作者使用 onclick 在 delete-btn 上面並且使用 template string${}
放入動態的變數進去符合 id,這個用法只能用在字串中一般的 function 無法使用 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 function addTransactionDOM (transaction ) { const sign = transaction.amount < 0 ? "-" : "+" ; const item = document .createElement("li" ); item.classList.add(transaction.amount < 0 ? "minus" : "plus" ); item.innerHTML = ` ${transaction.text} <span>${sign} ${Math .abs( transaction.amount // 使用onlick事件並且使用動態參數`${} ` 包裹住transaction的id這樣一來參數就會是動態的 // 內容會隨著刪除的id不同而產生不同的array )} </span> <button class="delete-btn" onclick="removeTransaction(${ transaction.id } )">x</button> ` ; list.appendChild(item); }
尤其是 reduce 的寫法比較特別
1 reduce((acc, item ) => (acc += item), 0 )
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 function updateValues ( ) { const amounts = transactions.map((transaction ) => transaction.amount); const total = amounts.reduce((acc, item ) => (acc += item), 0 ).toFixed(2 ); const income = amounts .filter((item ) => item > 0 ) .reduce((acc, item ) => (acc += item), 0 ) .toFixed(2 ); const expense = ( amounts.filter((item ) => item < 0 ).reduce((acc, item ) => (acc += item), 0 ) * -1 ).toFixed(2 ); balance.innerText = `$${total} ` ; money_plus.innerText = `$${income} ` ; money_minus.innerText = `$${expense} ` ; }
submit 後觸發的方法:
加入新的交易資料 把加入的資料更新到 DOM(History) 更新 balance, income, expense 數字 加入資料進去 localstorage 2.3.4 必須呼叫其他函式來處理
最後把空格清空
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 function addTransaction (e ) { e.preventDefault(); if (text.value.trim() === "" || amount.value.trim() === "" ) { alert("Please add a text and amount" ); } else { const transaction = { id: generateID(), text: text.value, amount: +amount.value, }; transactions.push(transaction); addTransactionDOM(transaction); updateValues(); updateLocalStorage(); text.value = "" ; amount.value = "" ; } }
主要使用數學的方法來產生 id 給上面的 transaction
1 2 3 4 function generateID ( ) { return Math .floor(Math .random() * 100000000 ); }
設置鍵值對並且轉換成 JSON 格式推上去 localstorage 儲存
1 2 3 4 5 function updateLocalStorage ( ) { localStorage .setItem("transactions" , JSON .stringify(transactions)); }
使用 filter 去做篩選出”沒有參數 id 的 array”並且重新指派給 transactions(原本的資料庫) 就可以刪掉放入 removeTransaction 裡面的物件
1 2 3 4 5 6 7 8 9 10 11 12 function removeTransaction (id ) { transactions = transactions.filter((transaction ) => transaction.id !== id); updateLocalStorage(); init(); }
會更新所有檯面上數值以及 History 裡面的表格使用:
updateValues 更新 balance, income and expense 的數值 addTransactionDOM 更新 History 裡面的表格 1 2 3 4 5 6 7 8 function init ( ) { list.innerHTML = "" ; transactions.forEach(addTransactionDOM); updateValues(); }
1 form.addEventListener("submit" , addTransaction);
小補充:
Math.floor
函式會回傳小於等於所給數字的最大整數。
Math.random
回傳一個偽隨機小數 (pseudo-random),小數也稱浮點數; 介於 0 到 1 之間(包含 0,不包含 1) 。
toFixed()
選擇性輸入數值。顯示數值至多少個小數點,範圍由 0 到 20 之間,執行時或可支援非常大範圍的數值。如果無輸入會默認做 0。
完整程式碼: 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 const balance = document .getElementById("balance" );const money_plus = document .getElementById("money-plus" );const money_minus = document .getElementById("money-minus" );const list = document .getElementById("list" );const form = document .getElementById("form" );const text = document .getElementById("text" );const amount = document .getElementById("amount" );const localStorageTransactions = JSON .parse( localStorage .getItem("transactions" ) ); let transactions = localStorage .getItem("transactions" ) !== null ? localStorageTransactions : []; console .log(transactions);function addTransaction (e ) { e.preventDefault(); if (text.value.trim() === "" || amount.value.trim() === "" ) { alert("Please add a text and amount" ); } else { const transaction = { id: generateID(), text: text.value, amount: +amount.value, }; transactions.push(transaction); addTransactionDOM(transaction); updateValues(); updateLocalStorage(); text.value = "" ; amount.value = "" ; } } function generateID ( ) { return Math .floor(Math .random() * 100000000 ); } function addTransactionDOM (transaction ) { const sign = transaction.amount < 0 ? "-" : "+" ; const item = document .createElement("li" ); item.classList.add(transaction.amount < 0 ? "minus" : "plus" ); item.innerHTML = ` ${transaction.text} <span>${sign} ${Math .abs( transaction.amount // 使用onlick事件並且使用動態參數`` 包裹住transaction的id )} </span> <button class="delete-btn" onclick="removeTransaction(${ transaction.id } )">x</button> ` ; list.appendChild(item); } function updateValues ( ) { const amounts = transactions.map((transaction ) => transaction.amount); const total = amounts.reduce((acc, item ) => (acc += item), 0 ).toFixed(2 ); const income = amounts .filter((item ) => item > 0 ) .reduce((acc, item ) => (acc += item), 0 ) .toFixed(2 ); const expense = ( amounts.filter((item ) => item < 0 ).reduce((acc, item ) => (acc += item), 0 ) * -1 ).toFixed(2 ); balance.innerText = `$${total} ` ; money_plus.innerText = `$${income} ` ; money_minus.innerText = `$${expense} ` ; } function removeTransaction (id ) { transactions = transactions.filter((transaction ) => transaction.id !== id); updateLocalStorage(); init(); } function updateLocalStorage ( ) { localStorage .setItem("transactions" , JSON .stringify(transactions)); } function init ( ) { list.innerHTML = "" ; transactions.forEach(addTransactionDOM); updateValues(); } init(); form.addEventListener("submit" , addTransaction);