fix: 修复导出列顺序,用途移到最后一列
- CSV/JSON/HTML 导出:用途列移到最后 - 下载模板示例数据同步更新 - 导入时正确对应各列数据
This commit is contained in:
@@ -335,8 +335,8 @@ async function readNotif(id){await apiFetch('/notifications/'+id+'/read',{method
|
|||||||
function toggleExport(e){e.stopPropagation();$('exportMenu').classList.toggle('show');}
|
function toggleExport(e){e.stopPropagation();$('exportMenu').classList.toggle('show');}
|
||||||
function toggleImport(e){e.stopPropagation();$('importMenu').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 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 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 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?'已还清':'还款中',用途:l.purpose||''}));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=`<html><head><meta charset="utf-8"><title>债务统计</title></head><body><h2>债务统计报表</h2><table border="1"><tr><th>借款人</th><th>金额</th><th>利率</th><th>期数</th></tr>`;loans.forEach(l=>{h+=`<tr><td>${escapeHtml(l.name)}</td><td>¥${l.amount.toLocaleString()}</td><td>${l.rate}%</td><td>${l.paid.length}/${l.periods}</td></tr>`;});h+=`</table>
|
function exportHTML(e){e.preventDefault();if(!loans.length)return;let h=`<html><head><meta charset="utf-8"><title>债务统计</title></head><body><h2>债务统计报表</h2><table border="1"><tr><th>借款人</th><th>金额</th><th>利率</th><th>期数</th></tr>`;loans.forEach(l=>{h+=`<tr><td>${escapeHtml(l.name)}</td><td>¥${l.amount.toLocaleString()}</td><td>${l.rate}%</td><td>${l.paid.length}/${l.periods}</td></tr>`;});h+=`</table>
|
||||||
<div class="modal-mask" id="changeNickModal"><div class="modal"><h3>修改昵称</h3><div class="form-row"><label>新昵称</label><input type="text" id="newNick" placeholder="请输入新昵称"></div><div id="nickError" class="error-msg" style="display:none"></div><div id="nickSuccess" class="success-msg" style="display:none"></div><div class="btn-row"><button class="btn btn-ghost" onclick="closeChangeNick()">取消</button><button class="btn btn-main" onclick="doChangeNick()">确认修改</button></div></div></div>
|
<div class="modal-mask" id="changeNickModal"><div class="modal"><h3>修改昵称</h3><div class="form-row"><label>新昵称</label><input type="text" id="newNick" placeholder="请输入新昵称"></div><div id="nickError" class="error-msg" style="display:none"></div><div id="nickSuccess" class="success-msg" style="display:none"></div><div class="btn-row"><button class="btn btn-ghost" onclick="closeChangeNick()">取消</button><button class="btn btn-main" onclick="doChangeNick()">确认修改</button></div></div></div>
|
||||||
<div class="modal-mask" id="changePwdModal"><div class="modal"><h3>修改密码</h3><div class="form-row"><label>原密码</label><input type="password" id="oldPwd" placeholder="请输入原密码"></div><div class="form-row"><label>新密码</label><input type="password" id="newPwd" placeholder="请输入新密码"></div><div class="form-row"><label>确认新密码</label><input type="password" id="newPwd2" placeholder="请再次输入新密码"></div><div id="pwdError" class="error-msg" style="display:none"></div><div id="pwdSuccess" class="success-msg" style="display:none"></div><div class="btn-row"><button class="btn btn-ghost" onclick="closeChangePwd()">取消</button><button class="btn btn-main" onclick="doChangePwd()">确认修改</button></div></div></div>
|
<div class="modal-mask" id="changePwdModal"><div class="modal"><h3>修改密码</h3><div class="form-row"><label>原密码</label><input type="password" id="oldPwd" placeholder="请输入原密码"></div><div class="form-row"><label>新密码</label><input type="password" id="newPwd" placeholder="请输入新密码"></div><div class="form-row"><label>确认新密码</label><input type="password" id="newPwd2" placeholder="请再次输入新密码"></div><div id="pwdError" class="error-msg" style="display:none"></div><div id="pwdSuccess" class="success-msg" style="display:none"></div><div class="btn-row"><button class="btn btn-ghost" onclick="closeChangePwd()">取消</button><button class="btn btn-main" onclick="doChangePwd()">确认修改</button></div></div></div>
|
||||||
|
|||||||
@@ -692,8 +692,8 @@ function toggleMonth(ym){if(openMonths.has(ym))openMonths.delete(ym);else openMo
|
|||||||
function toggleExport(e){e.stopPropagation();$('exportMenu').classList.toggle('show');}
|
function toggleExport(e){e.stopPropagation();$('exportMenu').classList.toggle('show');}
|
||||||
document.addEventListener('click',function(){$('exportMenu').classList.remove('show');$('importMenu').classList.remove('show');$('userMenu').classList.remove('show');});
|
document.addEventListener('click',function(){$('exportMenu').classList.remove('show');$('importMenu').classList.remove('show');$('userMenu').classList.remove('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 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 showToast('暂无数据可导出','info');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},${methodLabel(l.method)},${escapeHtml(l.purpose||'')},${paid},${status}\n`;});download('债务统计_'+today()+'.csv',csv,'text/csv;charset=utf-8');}
|
function exportCSV(e){e.preventDefault();if(!loans.length)return showToast('暂无数据可导出','info');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},${methodLabel(l.method)},${paid},${status},${escapeHtml(l.purpose||'')}\n`;});download('债务统计_'+today()+'.csv',csv,'text/csv;charset=utf-8');}
|
||||||
function exportJSON(e){e.preventDefault();if(!loans.length)return showToast('暂无数据可导出','info');const data=loans.map(l=>({债权人:l.name,借款日期:l.date,借款金额:l.amount,年化率:l.rate,分期数:l.periods,还款方式:methodLabel(l.method),用途:l.purpose||'',已还期数:l.paid.length,状态:l.paid.length>=l.periods?'已还清':'还款中',还款计划:l.schedule.map(r=>({期数:r.p,日期:r.date,还款额:r.pay,本金:r.pr,利息:r.it}))}));download('债务统计_'+today()+'.json',JSON.stringify(data,null,2),'application/json;charset=utf-8');}
|
function exportJSON(e){e.preventDefault();if(!loans.length)return showToast('暂无数据可导出','info');const data=loans.map(l=>({债权人:l.name,借款日期:l.date,借款金额:l.amount,年化率:l.rate,分期数:l.periods,还款方式:methodLabel(l.method),已还期数:l.paid.length,状态:l.paid.length>=l.periods?'已还清':'还款中',用途:l.purpose||'',还款计划:l.schedule.map(r=>({期数:r.p,日期:r.date,还款额:r.pay,本金:r.pr,利息:r.it}))}));download('债务统计_'+today()+'.json',JSON.stringify(data,null,2),'application/json;charset=utf-8');}
|
||||||
function exportHTML(e){e.preventDefault();if(!loans.length)return showToast('暂无数据可导出','info');let h=`<html><head><meta charset="utf-8"><title>债务统计</title><style>body{font-family:"Microsoft YaHei",sans-serif;padding:20px}h2{color:#1d1d1f}table{border-collapse:collapse;width:100%;font-size:13px;margin-bottom:20px}th{background:#0071e3;color:#fff;padding:8px 12px;text-align:left}td{padding:6px 12px;border-bottom:1px solid #d2d2d7}tr:nth-child(even){background:#f5f5f7}.done{color:#34c759;font-weight:600}
|
function exportHTML(e){e.preventDefault();if(!loans.length)return showToast('暂无数据可导出','info');let h=`<html><head><meta charset="utf-8"><title>债务统计</title><style>body{font-family:"Microsoft YaHei",sans-serif;padding:20px}h2{color:#1d1d1f}table{border-collapse:collapse;width:100%;font-size:13px;margin-bottom:20px}th{background:#0071e3;color:#fff;padding:8px 12px;text-align:left}td{padding:6px 12px;border-bottom:1px solid #d2d2d7}tr:nth-child(even){background:#f5f5f7}.done{color:#34c759;font-weight:600}
|
||||||
.profile-section{padding:4px 0}
|
.profile-section{padding:4px 0}
|
||||||
.profile-section .form-row{margin-bottom:14px}
|
.profile-section .form-row{margin-bottom:14px}
|
||||||
@@ -701,7 +701,7 @@ function exportHTML(e){e.preventDefault();if(!loans.length)return showToast('暂
|
|||||||
.profile-section .form-row input{width:100%;padding:10px 14px;border:1px solid #d2d2d7;border-radius:var(--radius-xs);font-size:14px;outline:none;transition:all .2s}
|
.profile-section .form-row input{width:100%;padding:10px 14px;border:1px solid #d2d2d7;border-radius:var(--radius-xs);font-size:14px;outline:none;transition:all .2s}
|
||||||
.profile-section .form-row input:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(0,113,227,.12)}
|
.profile-section .form-row input:focus{border-color:var(--blue);box-shadow:0 0 0 3px rgba(0,113,227,.12)}
|
||||||
.profile-section .form-row input:disabled{background:#f5f5f7;color:var(--text3)}
|
.profile-section .form-row input:disabled{background:#f5f5f7;color:var(--text3)}
|
||||||
</style></head><body><h2>债务统计报表 - ${today()}</h2>`;loans.forEach(l=>{const ti=l.schedule.reduce((s,r)=>s+r.it,0);const st=l.paid.length>=l.periods;h+=`<h3>${escapeHtml(l.name)} - ${l.date} - ¥${fmt(l.amount)}${st?' <span class="done">已还清</span>':''}</h3><p>年化率 ${l.rate}% · ${methodLabel(l.method)} · ${l.periods}期 · 总利息 ¥${ti.toFixed(2)}</p><table><tr><th>期数</th><th>日期</th><th>还款额</th><th>本金</th><th>利息</th><th>剩余本金</th><th>用途</th><th>状态</th></tr>`;l.schedule.forEach(r=>{h+=`<tr><td>${r.p}</td><td>${r.date}</td><td>¥${fmt(r.pay)}</td><td>¥${fmt(r.pr)}</td><td>¥${fmt(r.it)}</td><td>¥${fmt(r.rp)}</td><td>${l.paid.includes(r.p)?'✓ 已还':'待还'}</td></tr>`;});h+=`</table>`;});h+=`
|
</style></head><body><h2>债务统计报表 - ${today()}</h2>`;loans.forEach(l=>{const ti=l.schedule.reduce((s,r)=>s+r.it,0);const st=l.paid.length>=l.periods;h+=`<h3>${escapeHtml(l.name)} - ${l.date} - ¥${fmt(l.amount)}${st?' <span class="done">已还清</span>':''}</h3><p>年化率 ${l.rate}% · ${methodLabel(l.method)} · ${l.periods}期 · 总利息 ¥${ti.toFixed(2)}</p><table><tr><th>期数</th><th>日期</th><th>还款额</th><th>本金</th><th>利息</th><th>剩余本金</th><th>状态</th><th>用途</th></tr>`;l.schedule.forEach(r=>{h+=`<tr><td>${r.p}</td><td>${r.date}</td><td>¥${fmt(r.pay)}</td><td>¥${fmt(r.pr)}</td><td>¥${fmt(r.it)}</td><td>¥${fmt(r.rp)}</td><td>${l.paid.includes(r.p)?'✓ 已还':'待还'}</td></tr>`;});h+=`</table>`;});h+=`
|
||||||
<div class="modal-mask" id="profileModal">
|
<div class="modal-mask" id="profileModal">
|
||||||
<div class="modal" style="max-width:480px">
|
<div class="modal" style="max-width:480px">
|
||||||
<h3>个人中心</h3>
|
<h3>个人中心</h3>
|
||||||
@@ -733,7 +733,7 @@ function toggleImport(e){e.stopPropagation();$('importMenu').classList.toggle('s
|
|||||||
document.addEventListener('click',function(){$('importMenu').classList.remove('show');});
|
document.addEventListener('click',function(){$('importMenu').classList.remove('show');});
|
||||||
function showImportModal(e){e.preventDefault();$('importMenu').classList.remove('show');$('importFile').value='';$('importError').style.display='none';$('importSuccess').style.display='none';$('importModal').classList.add('show');}
|
function showImportModal(e){e.preventDefault();$('importMenu').classList.remove('show');$('importFile').value='';$('importError').style.display='none';$('importSuccess').style.display='none';$('importModal').classList.add('show');}
|
||||||
function closeImportModal(){$('importModal').classList.remove('show');}
|
function closeImportModal(){$('importModal').classList.remove('show');}
|
||||||
function downloadTemplate(e){e.preventDefault();$('importMenu').classList.remove('show');let csv='债权人,借款日期,借款金额,年化率(%),总利息,分期数,还款方式,用途,已还期数,状态\n';csv+='张三,2026-01-01,100000,5.7,5198.99,24,等额本息,装修,0/24,还款中\n';csv+='李四,2026-03-15,50000,8,1200.00,12,先息后本,教育,0/12,还款中\n';csv+='王五,2026-06-01,30000,0,0.00,6,等额本息,,0/6,还款中\n';download('导入模板.csv',csv,'text/csv;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,等额本息,0/24,还款中,装修\n';csv+='李四,2026-03-15,50000,8,1200.00,12,先息后本,0/12,还款中,教育\n';csv+='王五,2026-06-01,30000,0,0.00,6,等额本息,0/6,还款中,\n';download('导入模板.csv',csv,'text/csv;charset=utf-8');}
|
||||||
async function doImport(){const file=$('importFile').files[0];if(!file){$('importError').textContent='请选择文件';$('importError').style.display='';return;}$('importError').style.display='none';$('importSuccess').style.display='none';const text=await file.text();const lines=text.trim().split('\n');if(lines.length<2){$('importError').textContent='文件为空或格式错误';$('importError').style.display='';return;}const header=lines[0].split(',').map(s=>s.trim());const nameIdx=header.indexOf('债权人'),dateIdx=header.indexOf('借款日期'),amtIdx=header.indexOf('借款金额'),rateIdx=header.indexOf('年利率(%)'),periodsIdx=header.indexOf('分期数'),methodIdx=header.indexOf('还款方式'),paidIdx=header.indexOf('已还期数');if(nameIdx===-1||dateIdx===-1||amtIdx===-1||rateIdx===-1||periodsIdx===-1||methodIdx===-1){$('importError').textContent='缺少必需列:债权人,借款日期,借款金额,年化率(%),分期数,还款方式';$('importError').style.display='';return;}const methodMap={'等额本息':'e','等额本金':'p','先息后本':'i','e':'e','p':'p','i':'i'};let imported=0,failed=0;for(let i=1;i<lines.length;i++){const line=lines[i].trim();if(!line)continue;const cols=line.split(',').map(s=>s.trim());if(cols.length<6)continue;const name=cols[nameIdx],date=cols[dateIdx],amount=parseFloat(cols[amtIdx]),rate=parseFloat(cols[rateIdx]),periods=parseInt(cols[periodsIdx]),method=methodMap[cols[methodIdx]];if(!name||!date||isNaN(amount)||isNaN(rate)||isNaN(periods)||!method){failed++;continue;}const schedule=calcSchedule(amount,rate,periods,method,date);let paid=[];if(paidIdx>=0&&cols[paidIdx]){const paidStr=cols[paidIdx].trim();const m=paidStr.match(/^(\d+)\//);if(m){const paidCount=parseInt(m[1]);for(let p=1;p<=Math.min(paidCount,periods);p++)paid.push(p);}}const purpose=purposeIdx>=0?cols[purposeIdx]||'':'';const loan={name,date,amount,rate,periods,method,purpose,schedule,paid};const saved=await saveLoanToServer(loan);if(saved)loans.push(saved);else failed++;imported++;}if(imported>0){$('importSuccess').style.display='';$('importSuccess').textContent=`成功导入 ${imported} 条${failed?`,失败 ${failed} 条`:''}`;renderAll();}else{$('importError').textContent='导入失败,请检查文件格式';$('importError').style.display='';}}
|
async function doImport(){const file=$('importFile').files[0];if(!file){$('importError').textContent='请选择文件';$('importError').style.display='';return;}$('importError').style.display='none';$('importSuccess').style.display='none';const text=await file.text();const lines=text.trim().split('\n');if(lines.length<2){$('importError').textContent='文件为空或格式错误';$('importError').style.display='';return;}const header=lines[0].split(',').map(s=>s.trim());const nameIdx=header.indexOf('债权人'),dateIdx=header.indexOf('借款日期'),amtIdx=header.indexOf('借款金额'),rateIdx=header.indexOf('年利率(%)'),periodsIdx=header.indexOf('分期数'),methodIdx=header.indexOf('还款方式'),paidIdx=header.indexOf('已还期数');if(nameIdx===-1||dateIdx===-1||amtIdx===-1||rateIdx===-1||periodsIdx===-1||methodIdx===-1){$('importError').textContent='缺少必需列:债权人,借款日期,借款金额,年化率(%),分期数,还款方式';$('importError').style.display='';return;}const methodMap={'等额本息':'e','等额本金':'p','先息后本':'i','e':'e','p':'p','i':'i'};let imported=0,failed=0;for(let i=1;i<lines.length;i++){const line=lines[i].trim();if(!line)continue;const cols=line.split(',').map(s=>s.trim());if(cols.length<6)continue;const name=cols[nameIdx],date=cols[dateIdx],amount=parseFloat(cols[amtIdx]),rate=parseFloat(cols[rateIdx]),periods=parseInt(cols[periodsIdx]),method=methodMap[cols[methodIdx]];if(!name||!date||isNaN(amount)||isNaN(rate)||isNaN(periods)||!method){failed++;continue;}const schedule=calcSchedule(amount,rate,periods,method,date);let paid=[];if(paidIdx>=0&&cols[paidIdx]){const paidStr=cols[paidIdx].trim();const m=paidStr.match(/^(\d+)\//);if(m){const paidCount=parseInt(m[1]);for(let p=1;p<=Math.min(paidCount,periods);p++)paid.push(p);}}const purpose=purposeIdx>=0?cols[purposeIdx]||'':'';const loan={name,date,amount,rate,periods,method,purpose,schedule,paid};const saved=await saveLoanToServer(loan);if(saved)loans.push(saved);else failed++;imported++;}if(imported>0){$('importSuccess').style.display='';$('importSuccess').textContent=`成功导入 ${imported} 条${failed?`,失败 ${failed} 条`:''}`;renderAll();}else{$('importError').textContent='导入失败,请检查文件格式';$('importError').style.display='';}}
|
||||||
function toggleUserMenu(e){e.stopPropagation();$('userMenu').classList.toggle('show');}
|
function toggleUserMenu(e){e.stopPropagation();$('userMenu').classList.toggle('show');}
|
||||||
document.addEventListener('click',function(){$('userMenu').classList.remove('show');});
|
document.addEventListener('click',function(){$('userMenu').classList.remove('show');});
|
||||||
|
|||||||
Reference in New Issue
Block a user