From 27ad9b6901f2ed18aa2e7277aa445c1198354bf7 Mon Sep 17 00:00:00 2001 From: OP Date: Mon, 29 Jun 2026 00:10:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=81=A2=E5=A4=8D=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E6=A0=8F=E5=AE=8C=E6=95=B4=E5=8A=9F=E8=83=BD=EF=BC=88=E9=80=9A?= =?UTF-8?q?=E7=9F=A5/=E5=AF=BC=E5=87=BA/=E5=AF=BC=E5=85=A5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 两个页面 header 统一添加:通知铃铛、导出按钮、导入按钮、用户信息 - 添加通知相关 JS 函数到 analysis.html - 添加导出/导入相关 JS 函数到 analysis.html - 导航栏布局:Logo + 标题 | 首页/分析 | 用户名+通知+导出+导入+管理+退出 --- frontend/analysis.html | 84 ++++++++++++++++++++++++++++++++++++++++-- frontend/index.html | 61 ++++++++++++++++++++++++++++-- 2 files changed, 139 insertions(+), 6 deletions(-) diff --git a/frontend/analysis.html b/frontend/analysis.html index d6f4762..63d9af4 100644 --- a/frontend/analysis.html +++ b/frontend/analysis.html @@ -19,13 +19,43 @@ body{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","PingFang SC" .nav-tab{padding:7px 20px;border-radius:6px;font-size:13px;font-weight:500;color:var(--text2);text-decoration:none;transition:all .2s;cursor:pointer;white-space:nowrap} .nav-tab:hover{color:var(--text);background:rgba(255,255,255,.5)} .nav-tab.active{background:#fff;color:var(--text);box-shadow:0 1px 4px rgba(0,0,0,.08)} -.header-right{display:flex;align-items:center;gap:6px;flex-shrink:0} -.header-right a{color:var(--text2);text-decoration:none;font-size:12px;padding:6px 14px;border-radius:var(--radius-xs);transition:all .2s;white-space:nowrap} -.header-right a:hover{background:rgba(0,0,0,.04);color:var(--text)} +.header-right a,.hdr-btn{color:var(--text2);text-decoration:none;font-size:12px;padding:6px 14px;border-radius:var(--radius-xs);transition:all .2s;background:transparent;border:none;cursor:pointer;font-family:inherit} +.header-right a:hover,.hdr-btn:hover{background:rgba(0,0,0,.04);color:var(--text)} .header-right .admin-link{color:var(--blue);font-weight:500} .header-right .logout-link{color:var(--red)} .user-info{color:var(--text);font-size:13px;font-weight:500;cursor:pointer;padding:6px 14px;border-radius:var(--radius-xs);transition:all .2s;background:rgba(0,0,0,.02)} .user-info:hover{background:rgba(0,0,0,.06)} +.notif-wrap{position:relative} +.notif-bell{cursor:pointer;padding:6px 8px;border-radius:var(--radius-xs);transition:all .2s;color:var(--text2)} +.notif-bell:hover{background:rgba(0,0,0,.04);color:var(--text)} +.notif-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;background:var(--red);color:#fff;border-radius:8px;font-size:10px;font-weight:600;display:flex;align-items:center;justify-content:center;padding:0 4px} +.notif-dropdown{display:none;position:absolute;right:0;top:calc(100% + 8px);background:rgba(255,255,255,.96);backdrop-filter:blur(24px);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 12px 48px rgba(0,0,0,.15);width:360px;max-height:480px;overflow:hidden;z-index:60} +.notif-dropdown.show{display:block} +.notif-header{padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center} +.notif-header h4{font-size:14px;font-weight:600;color:var(--text)} +.notif-header .mark-all{font-size:12px;color:var(--blue);cursor:pointer;background:none;border:none;font-family:inherit} +.notif-header .mark-all:hover{text-decoration:underline} +.notif-list{overflow-y:auto;max-height:380px} +.notif-item{padding:12px 16px;border-bottom:1px solid var(--border);cursor:pointer;transition:background .15s;display:flex;gap:12px;align-items:flex-start} +.notif-item:hover{background:rgba(0,0,0,.02)} +.notif-item.unread{background:rgba(0,113,227,.03)} +.notif-item .notif-icon{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0} +.notif-icon.repayment_due{background:rgba(0,113,227,.08)} +.notif-icon.repayment_overdue{background:rgba(255,59,48,.08)} +.notif-icon.system{background:rgba(175,82,222,.08)} +.notif-icon.account{background:rgba(52,199,89,.08)} +.notif-item .notif-content{flex:1;min-width:0} +.notif-item .notif-title{font-size:13px;font-weight:500;color:var(--text);margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.notif-item .notif-msg{font-size:12px;color:var(--text3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.notif-item .notif-time{font-size:11px;color:var(--text3);white-space:nowrap} +.notif-empty{text-align:center;padding:32px;color:var(--text3);font-size:13px} +.export-wrap{position:relative} +.export-menu{display:none;position:absolute;right:0;top:calc(100% + 4px);background:rgba(255,255,255,.95);backdrop-filter:blur(20px);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:0 8px 32px rgba(0,0,0,.12);min-width:160px;z-index:50;overflow:hidden} +.export-menu.show{display:block} +.export-menu a{display:block;padding:10px 18px;font-size:13px;color:var(--text);text-decoration:none;transition:background .15s} +.export-menu a:hover{background:rgba(0,0,0,.04)} +.user-info{color:var(--text);font-size:13px;font-weight:500;cursor:pointer;padding:6px 14px;border-radius:var(--radius-xs);transition:all .2s;background:rgba(0,0,0,.02)} +.user-info:hover{background:rgba(0,0,0,.06)} .container{max-width:1120px;margin:0 auto;padding:24px} .stats-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:24px} .stat-card{background:var(--card);border-radius:var(--radius-sm);padding:20px;text-align:center;border:1px solid var(--border)} @@ -53,6 +83,32 @@ body{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","PingFang SC" 分析
+ +
+
+ + +
+
+

通知

+
暂无通知
+
+
+
+ + +
+
+ + +
退出
@@ -166,6 +222,28 @@ function renderMonth(){ new Chart($('chartMonth'),{type:'bar',data:{labels:months.map(m=>m.replace('-','年')+'月'),datasets:[{label:'应还',data:months.map(m=>map[m].total),backgroundColor:'rgba(0,113,227,.3)',borderColor:'#0071e3',borderWidth:1},{label:'已还',data:months.map(m=>map[m].paid),backgroundColor:'rgba(52,199,89,.5)',borderColor:'#34c759',borderWidth:1}]},options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{position:'bottom',labels:{padding:12,font:{size:12}}}},scales:{y:{beginAtZero:true,ticks:{callback:v=>'¥'+v.toLocaleString()}},x:{ticks:{font:{size:10}}}}}}); } + +function escapeHtml(s){if(!s)return'';const d=document.createElement('div');d.textContent=s;return d.innerHTML;} +function notifTypeIcon(type){const map={repayment_due:'📅',repayment_overdue:'⚠️',system:'📢',account:'👤'};return map[type]||'📩';} +function timeAgo(dt){const diff=(Date.now()-new Date(dt).getTime())/1000;if(diff<60)return '刚刚';if(diff<3600)return Math.floor(diff/60)+'分钟前';if(diff<86400)return Math.floor(diff/3600)+'小时前';return Math.floor(diff/86400)+'天前';} + +function toggleNotifDropdown(e){e.stopPropagation();const dd=$('notifDropdown');dd.classList.toggle('show');if(dd.classList.contains('show'))loadNotifications();} +document.addEventListener('click',function(){$('notifDropdown')?.classList.remove('show');$('exportMenu')?.classList.remove('show');$('importMenu')?.classList.remove('show');}); + +async function loadNotifications(){const res=await apiFetch('/notifications?size=20');if(!res||!res.ok)return;const data=await res.json();renderNotifBadge(data.unread);renderNotifList(data.items);} +function renderNotifBadge(unread){const badge=$('notifBadge');if(unread>0){badge.style.display='';badge.textContent=unread>99?'99+':unread;}else{badge.style.display='none';}} +function renderNotifList(items){const el=$('notifList');if(!items.length){el.innerHTML='
暂无通知
';return;}el.innerHTML=items.map(n=>`
${notifTypeIcon(n.type)}
${escapeHtml(n.title)}
${escapeHtml(n.message)}
${timeAgo(n.created_at)}
`).join('');} +async function readNotif(id){await apiFetch('/notifications/'+id+'/read',{method:'PUT'});loadNotifications();} + +function toggleExport(e){e.stopPropagation();$('exportMenu').classList.toggle('show');} +function toggleImport(e){e.stopPropagation();$('importMenu').classList.toggle('show');} +function download(filename,content,mime){const blob=new Blob(['\uFEFF'+content],{type:mime});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=filename;a.click();} +function exportCSV(e){e.preventDefault();if(!loans.length)return;let csv='借款人,借款日期,借款金额,年利率(%),总利息,分期数,还款方式,已还期数,状态\n';loans.forEach(l=>{const ti=l.schedule.reduce((s,r)=>s+r.it,0);const paid=l.paid.length+'/'+l.periods;const status=l.paid.length>=l.periods?'已还清':'还款中';csv+=`${escapeHtml(l.name)},${l.date},${l.amount},${l.rate},${ti.toFixed(2)},${l.periods},${l.method==='e'?'等额本息':l.method==='p'?'等额本金':'先息后本'},${paid},${status}\n`;});download('债务统计_'+new Date().toISOString().slice(0,10)+'.csv',csv,'text/csv;charset=utf-8');} +function exportJSON(e){e.preventDefault();if(!loans.length)return;const data=loans.map(l=>({借款人:l.name,借款日期:l.date,借款金额:l.amount,年利率:l.rate,分期数:l.periods,还款方式:l.method==='e'?'等额本息':l.method==='p'?'等额本金':'先息后本',已还期数:l.paid.length,状态:l.paid.length>=l.periods?'已还清':'还款中'}));download('债务统计_'+new Date().toISOString().slice(0,10)+'.json',JSON.stringify(data,null,2),'application/json;charset=utf-8');} +function exportHTML(e){e.preventDefault();if(!loans.length)return;let h=`债务统计

债务统计报表

`;loans.forEach(l=>{h+=``;});h+=`
借款人金额利率期数
${escapeHtml(l.name)}¥${l.amount.toLocaleString()}${l.rate}%${l.paid.length}/${l.periods}
`;download('债务统计_'+new Date().toISOString().slice(0,10)+'.html',h,'text/html;charset=utf-8');} +function downloadTemplate(e){e.preventDefault();$('importMenu').classList.remove('show');let csv='借款人,借款日期,借款金额,年利率(%),总利息,分期数,还款方式\n';csv+='张三,2026-01-01,100000,5.7,5198.99,24,等额本息\n';download('导入模板.csv',csv,'text/csv;charset=utf-8');} +function showImportModal(e){e.preventDefault();$('importMenu').classList.remove('show');} + checkAuth().then(ok=>{if(ok)loadLoans();}); diff --git a/frontend/index.html b/frontend/index.html index ff1e381..1b33b64 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -36,13 +36,43 @@ body{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","SF Pro Text" .nav-tab{padding:7px 20px;border-radius:6px;font-size:13px;font-weight:500;color:var(--text2);text-decoration:none;transition:all .2s;cursor:pointer;white-space:nowrap} .nav-tab:hover{color:var(--text);background:rgba(255,255,255,.5)} .nav-tab.active{background:#fff;color:var(--text);box-shadow:0 1px 4px rgba(0,0,0,.08)} -.header-right{display:flex;align-items:center;gap:6px;flex-shrink:0} -.header-right a{color:var(--text2);text-decoration:none;font-size:12px;padding:6px 14px;border-radius:var(--radius-xs);transition:all .2s;white-space:nowrap} -.header-right a:hover{background:rgba(0,0,0,.04);color:var(--text)} +.header-right a,.hdr-btn{color:var(--text2);text-decoration:none;font-size:12px;padding:6px 14px;border-radius:var(--radius-xs);transition:all .2s;background:transparent;border:none;cursor:pointer;font-family:inherit} +.header-right a:hover,.hdr-btn:hover{background:rgba(0,0,0,.04);color:var(--text)} .header-right .admin-link{color:var(--blue);font-weight:500} .header-right .logout-link{color:var(--red)} .user-info{color:var(--text);font-size:13px;font-weight:500;cursor:pointer;padding:6px 14px;border-radius:var(--radius-xs);transition:all .2s;background:rgba(0,0,0,.02)} .user-info:hover{background:rgba(0,0,0,.06)} +.notif-wrap{position:relative} +.notif-bell{cursor:pointer;padding:6px 8px;border-radius:var(--radius-xs);transition:all .2s;color:var(--text2)} +.notif-bell:hover{background:rgba(0,0,0,.04);color:var(--text)} +.notif-badge{position:absolute;top:2px;right:2px;min-width:16px;height:16px;background:var(--red);color:#fff;border-radius:8px;font-size:10px;font-weight:600;display:flex;align-items:center;justify-content:center;padding:0 4px} +.notif-dropdown{display:none;position:absolute;right:0;top:calc(100% + 8px);background:rgba(255,255,255,.96);backdrop-filter:blur(24px);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 12px 48px rgba(0,0,0,.15);width:360px;max-height:480px;overflow:hidden;z-index:60} +.notif-dropdown.show{display:block} +.notif-header{padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center} +.notif-header h4{font-size:14px;font-weight:600;color:var(--text)} +.notif-header .mark-all{font-size:12px;color:var(--blue);cursor:pointer;background:none;border:none;font-family:inherit} +.notif-header .mark-all:hover{text-decoration:underline} +.notif-list{overflow-y:auto;max-height:380px} +.notif-item{padding:12px 16px;border-bottom:1px solid var(--border);cursor:pointer;transition:background .15s;display:flex;gap:12px;align-items:flex-start} +.notif-item:hover{background:rgba(0,0,0,.02)} +.notif-item.unread{background:rgba(0,113,227,.03)} +.notif-item .notif-icon{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0} +.notif-icon.repayment_due{background:rgba(0,113,227,.08)} +.notif-icon.repayment_overdue{background:rgba(255,59,48,.08)} +.notif-icon.system{background:rgba(175,82,222,.08)} +.notif-icon.account{background:rgba(52,199,89,.08)} +.notif-item .notif-content{flex:1;min-width:0} +.notif-item .notif-title{font-size:13px;font-weight:500;color:var(--text);margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.notif-item .notif-msg{font-size:12px;color:var(--text3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.notif-item .notif-time{font-size:11px;color:var(--text3);white-space:nowrap} +.notif-empty{text-align:center;padding:32px;color:var(--text3);font-size:13px} +.export-wrap{position:relative} +.export-menu{display:none;position:absolute;right:0;top:calc(100% + 4px);background:rgba(255,255,255,.95);backdrop-filter:blur(20px);border:1px solid var(--border);border-radius:var(--radius-sm);box-shadow:0 8px 32px rgba(0,0,0,.12);min-width:160px;z-index:50;overflow:hidden} +.export-menu.show{display:block} +.export-menu a{display:block;padding:10px 18px;font-size:13px;color:var(--text);text-decoration:none;transition:background .15s} +.export-menu a:hover{background:rgba(0,0,0,.04)} +.user-info{color:var(--text);font-size:13px;font-weight:500;cursor:pointer;padding:6px 14px;border-radius:var(--radius-xs);transition:all .2s;background:rgba(0,0,0,.02)} +.user-info:hover{background:rgba(0,0,0,.06)} /* MAIN */ .main{max-width:1120px;margin:0 auto;padding:24px;display:grid;grid-template-columns:320px 1fr;gap:20px} @@ -337,6 +367,31 @@ body{font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","SF Pro Text"
+
+
+ + +
+
+

通知

+
暂无通知
+
+
+
+ + +
+
+ + +
退出