console
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>随手记</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;font-family: -apple-system, system-ui}
body{background-color:#f0f5f2;padding:16px}
/*顶部绿色汇总卡片*/
.top-card{
background:linear-gradient(180deg,#248257,#1b7049);
border-radius:22px;
color:white;
text-align:center;
padding:30px 20px;
margin-bottom:24px;
}
.top-month{
font-size:17px;
opacity:0.9;
margin-bottom:8px;
}
.top-label{
font-size:18px;
opacity:0.9;
margin-bottom:8px;
}
.top-money{
font-size:64px;
font-weight:bold;
line-height:1;
margin-bottom:16px;
}
.top-row{
display:flex;
justify-content:center;
gap:24px;
font-size:16px;
opacity:0.92;
}
/* 支出收入按钮 */
.btn-wrap{
display:flex;
gap:20px;
margin-bottom:24px;
}
.btn-item{
text-align:center;
}
.btn-circle-red{
width:72px;height:72px;
border-radius:50%;
background:#f8d9d9;
color:#d64545;
border:none;
font-size:30px;
margin-bottom:6px;
}
.btn-circle-green{
width:72px;height:72px;
border-radius:50%;
background:#d9f0e4;
color:#248257;
border:none;
font-size:30px;
margin-bottom:6px;
}
.btn-text{
font-size:15px;color:#444;
}
/*每日结算区域*/
.section-title{
display:flex;
justify-content:space-between;
color:#555;
font-size:16px;
margin-bottom:12px;
}
.day-block{
background:#fff;
border-radius:18px;
padding:20px;
margin-bottom:16px;
box-shadow:0 2px 10px rgba(0,0,0,0.06);
}
.day-header{
display:flex;
justify-content:space-between;
margin-bottom:18px;
}
.day-date{
font-size:18px;font-weight:500;
color:#333;
}
.day-balance{
font-size:18px;font-weight:bold;color:#248257;
}
.day-sub{
color:#666;font-size:15px;margin-top:4px;
}
/*单条账单项目*/
.bill-item{
display:flex;
justify-content:space-between;
align-items:center;
padding:12px 0;
border-bottom:1px solid #eee;
}
.bill-item:last-child{border-bottom:none;}
.bill-left{
display:flex;
align-items:center;
gap:12px;
}
.bill-icon-red{
width:44px;height:44px;border-radius:12px;background:#f8d9d9;color:#d64545;
display:grid;place-items:center;font-size:20px;
}
.bill-icon-green{
width:44px;height:44px;border-radius:12px;background:#d9f0e4;color:#248257;
display:grid;place-items:center;font-size:20px;
}
.bill-info .note{font-size:17px;color:#222}
.bill-info .time{font-size:14px;color:#777;margin-top:3px}
.bill-right{
display:flex;align-items:center;gap:10px;
}
.money-plus{color:#248257;font-size:19px;font-weight:500}
.money-minus{color:#d64545;font-size:19px;font-weight:500}
.del-btn{
background:#e53935;color:white;border:none;border-radius:6px;padding:4px 8px;font-size:13px;
}
/*底部加号按钮*/
.add-float{
position:fixed;
left:50%;
transform:translateX(-50%);
bottom:32px;
width:70px;height:70px;
border-radius:50%;
background:#248257;
color:#fff;
border:none;
font-size:34px;
box-shadow:0 4px 12px rgba(36,130,87,0.35);
}
/*弹出录入面板*/
.pop-mask{
position:fixed;inset:0;background:rgba(0,0,0,0.4);
display:none;align-items:flex-end;
}
.pop-box{
width:100%;background:#fff;padding:24px;border-radius:20px 20px 0 0;
}
.pop-type-title{
text-align:center;font-size:20px;margin-bottom:16px;
}
.input-money{
width:100%;font-size:36px;text-align:right;border:none;outline:none;padding:10px 0;
border-bottom:1px solid #ddd;margin-bottom:16px;
}
.input-note{
width:100%;font-size:18px;border:1px solid #ddd;border-radius:10px;padding:12px;margin-bottom:18px;
}
.keyboard{
display:grid;grid-template-columns:repeat(3,1fr);gap:10px;
}
.key{
padding:16px;font-size:22px;border:none;border-radius:10px;background:#eee;
}
.key-save{background:#248257;color:#fff}
.key-cancel{background:#999;color:#fff}
</style>
</head>
<body>
<!--顶部绿色卡片-->
<div class="top-card">
<div class="top-month" id="topMonth">10月</div>
<div class="top-label">本月支出</div>
<div class="top-money" id="monthOut">0.00</div>
<div class="top-row">
<span>收入 ¥<span id="monthIn">0.00</span></span>
<span>结余 ¥<span id="monthBalance">0.00</span></span>
<span id="billCount">0笔</span>
</div>
</div>
<!--支出 收入圆形按钮-->
<div class="btn-wrap">
<div class="btn-item">
<button class="btn-circle-red" onclick="openPop('out')">+</button>
<div class="btn-text">支出</div>
</div>
<div class="btn-item">
<button class="btn-circle-green" onclick="openPop('in')">⤒</button>
<div class="btn-text">收入</div>
</div>
</div>
<div class="section-title">
<span>每日结算</span>
<span>今日支出 ¥<span id="todayOut">0.00</span></span>
</div>
<div id="listWrap"></div>
<!--底部悬浮加号-->
<button class="add-float" onclick="openPop('out')">+</button>
<!--弹窗录入面板-->
<div class="pop-mask" id="pop">
<div class="pop-box">
<div class="pop-type-title" id="popTitle">支出</div>
<input class="input-money" id="moneyInput" value="0">
<input class="input-note" id="noteInput" placeholder="备注(餐、工资等)">
<div class="keyboard">
<button class="key" onclick="inputNum('1')">1</button>
<button class="key" onclick="inputNum('2')">2</button>
<button class="key" onclick="inputNum('3')">3</button>
<button class="key" onclick="inputNum('4')">4</button>
<button class="key" onclick="inputNum('5')">5</button>
<button class="key" onclick="inputNum('6')">6</button>
<button class="key" onclick="inputNum('7')">7</button>
<button class="key" onclick="inputNum('8')">8</button>
<button class="key" onclick="inputNum('9')">9</button>
<button class="key" onclick="inputNum('.')">.</button>
<button class="key" onclick="inputNum('0')">0</button>
<button class="key key-cancel" onclick="closePop()">取消</button>
<button class="key" onclick="delOne()">⌫</button>
<button class="key key-save" onclick="saveBill()">保存</button>
</div>
</div>
</div>
<script>
let bills = JSON.parse(localStorage.getItem('billData')) || [];
let currentType = 'out';
render();
function openPop(type){
currentType = type;
document.getElementById('popTitle').innerText = type==='out'?'支出':'收入';
document.getElementById('moneyInput').value = '0';
document.getElementById('noteInput').value = '';
document.getElementById('pop').style.display='flex';
}
function closePop(){
document.getElementById('pop').style.display='none';
}
function inputNum(v){
let val = document.getElementById('moneyInput').value;
if(val === '0' && v!=='.') val = '';
val += v;
document.getElementById('moneyInput').value = val;
}
function delOne(){
let val = document.getElementById('moneyInput').value;
val = val.slice(0,-1);
if(val === '' || val === '.') val = '0';
document.getElementById('moneyInput').value = val;
}
function saveBill(){
let money = parseFloat(document.getElementById('moneyInput').value);
let note = document.getElementById('noteInput').value.trim();
if(isNaN(money) || money <=0) return alert('金额必须大于0');
let now = new Date();
bills.push({
id:Date.now(),
type:currentType,
money:money,
note: note,
time: now.toISOString(),
timeDisplay: `${now.getHours()}:${String(now.getMinutes()).padStart(2,'0')}`
});
saveStorage();
render();
closePop();
}
function deleteBill(id){
if(confirm('确定删除这条记录?')){
bills = bills.filter(item=>item.id!==id);
saveStorage();
render();
}
}
function saveStorage(){
localStorage.setItem('billData', JSON.stringify(bills));
}
function render(){
const nowDate = new Date();
const currentMonth = nowDate.getMonth()+1;
const currentYear = nowDate.getFullYear();
//本月统计
let monthIn=0,monthOut=0;
let todayOut=0;
bills.forEach(b=>{
let d = new Date(b.time);
//判断是否本月
if(d.getFullYear()===currentYear && d.getMonth()+1 === currentMonth){
if(b.type==='in') monthIn += b.money;
else monthOut += b.money;
}
//今日支出
if(d.toDateString() === nowDate.toDateString()){
if(b.type==='out') todayOut += b.money;
}
})
const monthBal = monthIn - monthOut;
const totalCount = bills.length;
//更新顶部绿色卡片
document.getElementById('topMonth').innerText = `${currentMonth}月`;
document.getElementById('monthOut').innerText = monthOut.toFixed(2);
document.getElementById('monthIn').innerText = monthIn.toFixed(2);
document.getElementById('monthBalance').innerText = monthBal.toFixed(2);
document.getElementById('billCount').innerText = `${totalCount}笔`;
document.getElementById('todayOut').innerText = todayOut.toFixed(2);
//按日期分组
const group = {};
bills.forEach(b=>{
const d = new Date(b.time);
const dateStr = `${d.getFullYear()}/${d.getMonth()+1}/${d.getDate()}`;
if(!group[dateStr]) group[dateStr] = {list:[],in:0,out:0};
group[dateStr].list.push(b);
if(b.type==='in') group[dateStr].in += b.money;
else group[dateStr].out += b.money;
})
let html = '';
//日期倒序,最新放上面
Object.keys(group).sort((a,b)=>new Date(b)-new Date(a)).forEach(date=>{
const g = group[date];
const dayBal = g.in - g.out;
html += `
<div class="day-block">
<div class="day-header">
<div>
<div class="day-date">${date}</div>
<div class="day-sub">支出¥${g.out.toFixed(2)} 收入¥${g.in.toFixed(2)}</div>
</div>
<div class="day-balance">结余 ${dayBal>=0?'+':''}${dayBal.toFixed(2)}</div>
</div>
`;
g.list.forEach(item=>{
const isIn = item.type === 'in';
const iconClass = isIn ? 'bill-icon-green' : 'bill-icon-red';
const moneyClass = isIn ? 'money-plus' : 'money-minus';
const sign = isIn ? '+' : '-';
html += `
<div class="bill-item">
<div class="bill-left">
<div class="${iconClass}">${isIn ? '⤒' : '+'}</div>
<div class="bill-info">
<div class="note">${item.note||(isIn?'收入':'支出')}</div>
<div class="time">${isIn?'收入':'支出'} · ${item.timeDisplay}</div>
</div>
</div>
<div class="bill-right">
<span class="${moneyClass}">${sign}${item.money.toFixed(2)}</span>
<button class="del-btn" onclick="deleteBill(${item.id})">删除</button>
</div>
</div>
`
})
html += '</div>'
})
document.getElementById('listWrap').innerHTML = html;
}
</script>
</body>
</html>