[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>工程合同一体化管理平台 | 智能资金台账</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.sheetjs.com/xlsx-0.20.2/package/dist/xlsx.full.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; }
body { background: #f4f7ff; min-height: 100vh; }
/* 登录页美化 */
.login-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: linear-gradient(135deg, #0b1224, #1a294a);
display: flex; align-items: center; justify-content: center;
z-index: 2000; backdrop-filter: blur(6px);
}
.login-card {
background: rgba(255,255,255,0.97);
border-radius: 30px; padding: 50px 40px;
width: 440px; max-width: 92%;
box-shadow: 0 30px 60px rgba(0,0,0,0.25);
animation: fadeUp 0.5s ease forwards;
text-align: center;
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.login-logo {
width: 76px; height: 76px; border-radius: 50%;
background: linear-gradient(135deg, #2563eb, #3b82f6);
display: inline-flex; align-items: center; justify-content: center;
margin-bottom: 24px; box-shadow: 0 12px 24px rgba(37,99,235,0.3);
}
.login-logo i { font-size: 36px; color: white; }
.login-card h2 {
font-size: 26px; font-weight: 800;
background: linear-gradient(135deg, #1e3a8a, #2563eb);
-webkit-background-clip: text; background-clip: text; color: transparent;
margin-bottom: 8px;
}
.login-sub { color: #64748b; font-size: 14px; margin-bottom: 32px; }
.input-group-login {
position: relative; margin-bottom: 20px;
}
.input-group-login i {
position: absolute; left: 18px; top: 50%; transform: translateY(-50%);
color: #94a3b8; font-size: 18px;
}
.input-group-login input {
width: 100%; padding: 16px 16px 16px 54px;
border: 1px solid #e2e8f0; border-radius: 16px;
font-size: 15px; outline: none; transition: 0.2s;
}
.input-group-login input:focus {
border-color: #2563eb; box-shadow: 0 0 0 4px rgba(37,99,235,0.1);
}
.btn-login {
background: linear-gradient(135deg, #2563eb, #3b82f6);
border: none; width: 100%; padding: 16px; border-radius: 16px;
color: white; font-weight: 600; font-size: 15px;
cursor: pointer; transition: 0.2s; box-shadow: 0 8px 20px rgba(37,99,235,0.25);
}
.btn-login:hover { transform: translateY(-2px); box-shadow: 0 12px 24px rgba(37,99,235,0.3); }
.demo-hint {
background: #eff6ff; border-radius: 12px; padding: 12px;
margin-top: 24px; font-size: 12px; color: #1d4ed8; font-weight: 500;
}
/* 主容器 */
.app-container { max-width: 1600px; margin: 0 auto; padding: 28px 24px; display: none; }
.glass-header {
background: white; border-radius: 24px; padding: 22px 30px;
display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap;
margin-bottom: 28px; box-shadow: 0 8px 24px rgba(0,0,0,0.04);
}
.brand-area { display: flex; align-items: center; gap: 16px; }
.brand-icon {
width: 50px; height: 50px; border-radius: 50%;
background: linear-gradient(135deg, #2563eb, #3b82f6);
display: flex; align-items: center; justify-content: center;
}
.brand-icon i { font-size: 22px; color: white; }
.title h1 {
font-size: 24px; font-weight: 800;
background: linear-gradient(135deg, #1e3a8a, #2563eb);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.title p { color: #64748b; font-size: 13px; margin-top: 4px; }
.action-buttons { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
/* 按钮 */
.btn-primary {
background: linear-gradient(135deg, #2563eb, #3b82f6);
border: none; padding: 12px 20px; border-radius: 14px;
color: white; font-weight: 600; cursor: pointer;
display: inline-flex; align-items: center; gap: 8px;
transition: 0.2s; box-shadow: 0 4px 12px rgba(37,99,235,0.15);
}
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 16px rgba(37,99,235,0.2); }
.btn-outline {
background: white; border: 1px solid #cbd5e1;
padding: 11px 19px; border-radius: 14px;
cursor: pointer; font-weight: 500; color: #334155;
display: inline-flex; align-items: center; gap: 8px;
transition: 0.2s;
}
.btn-outline:hover { border-color: #2563eb; color: #2563eb; background: #eff6ff; }
/* 统计卡片 */
.stats-row {
display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 20px; margin-bottom: 28px;
}
.stat-card {
background: white; border-radius: 20px; padding: 24px;
border-left: 6px solid #2563eb;
box-shadow: 0 8px 16px rgba(0,0,0,0.03);
transition: 0.2s;
}
.stat-card:hover { transform: translateY(-4px); }
.stat-card span { font-size: 28px; font-weight: 800; display: block; margin-bottom: 6px; }
.stat-card .stat-label { font-size: 13px; color: #64748b; font-weight: 600; }
/* 筛选栏 */
.filter-bar {
background: white; border-radius: 20px; padding: 20px 24px;
display: flex; flex-wrap: wrap; align-items: flex-end; gap: 20px;
margin-bottom: 24px; box-shadow: 0 4px 12px rgba(0,0,0,0.03);
}
.filter-group { flex: 1; min-width: 180px; }
.filter-group label { font-size: 12px; font-weight: 600; color: #475569; margin-bottom: 8px; display: block; }
.filter-group select, .filter-group input {
width: 100%; padding: 12px 14px; border-radius: 12px;
border: 1px solid #e2e8f0; font-size: 14px; outline: none;
}
.filter-actions { display: flex; gap: 12px; align-items: center; }
.stat-badge {
background: #eff6ff; padding: 10px 16px; border-radius: 12px;
font-size: 13px; font-weight: 600; color: #1e40af;
}
/* 表格面板 */
.contracts-panel {
background: white; border-radius: 24px; overflow: hidden;
box-shadow: 0 8px 24px rgba(0,0,0,0.05);
}
.section-title {
padding: 20px 28px; font-weight: 700; font-size: 16px;
border-bottom: 1px solid #f1f5f9; background: #fafbfc;
display: flex; justify-content: space-between; align-items: center;
}
.resizable-table-wrapper { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; min-width: 1300px; }
th, td { padding: 16px 14px; vertical-align: middle; border-bottom: 1px solid #f1f5f9; }
th {
background: #f8fafc; font-weight: 700; color: #334155;
font-size: 13px; position: relative;
}
th.resizable { position: relative; }
.resize-handle {
position: absolute; right: 0; top: 0; bottom: 0;
width: 8px; background: transparent; cursor: col-resize;
}
.resize-handle:hover { background: #2563eb; opacity: 0.2; }
body.resizing { user-select: none; cursor: col-resize; }
tbody tr:hover { background: #f8fafc; }
/* 标签与状态 */
.expired-warning { background: #fffbeb !important; border-left: 4px solid #f59e0b; }
.warning-badge {
background: #fef3c7; color: #d97706; padding: 4px 10px;
border-radius: 12px; font-size: 12px; font-weight: 600;
}
.tag {
background: #f1f5f9; padding: 4px 10px; border-radius: 12px;
font-size: 12px; color: #475569; font-weight: 500;
}
.funds-dashboard {
background: #f8fafc; border-radius: 16px; padding: 10px 12px;
font-size: 13px; line-height: 1.5;
}
.progress-badge {
background: #dbeafe; color: #1d4ed8;
padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600;
}
.file-link {
background: #ecfdf5; color: #047857;
padding: 4px 10px; border-radius: 12px; font-size: 12px;
display: inline-flex; align-items: center; gap: 6px; margin: 2px;
cursor: pointer;
}
.actions i { margin: 0 6px; cursor: pointer; color: #64748b; font-size: 16px; transition: 0.2s; }
.actions i:hover { color: #2563eb; transform: scale(1.1); }
/* 弹窗 */
.modal {
display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.4); backdrop-filter: blur(4px);
align-items: center; justify-content: center; z-index: 1000;
}
.modal-content {
background: white; width: 90%; max-width: 1100px;
border-radius: 24px; padding: 32px; max-height: 85vh; overflow-y: auto;
box-shadow: 0 20px 40px rgba(0,0,0,0.15);
}
.form-row { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 20px; }
.form-group { flex: 1; min-width: 220px; display: flex; flex-direction: column; gap: 8px; }
.form-group label { font-weight: 600; font-size: 14px; color: #1e293b; }
.form-group input, .form-group textarea {
padding: 12px 14px; border: 1px solid #e2e8f0; border-radius: 12px;
font-size: 14px; outline: none;
}
.sub-table {
width: 100%; font-size: 13px; background: #f8fafc;
border-radius: 16px; margin-top: 12px;
}
.sub-table th, .sub-table td { padding: 10px 12px; border: 1px solid #e2e8f0; }
.bond-date-group {
background: #f8fafc; padding: 18px; border-radius: 16px; margin-bottom: 16px;
}
.attachment-item {
background: #f1f5f9; border-radius: 12px; padding: 6px 12px;
display: inline-flex; align-items: center; gap: 8px; margin: 4px; font-size: 13px;
}
.upload-area {
background: #f8fafc; border: 1px dashed #cbd5e1;
border-radius: 16px; padding: 20px; text-align: center; margin-top: 12px;
}
.flex-between { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px; }
.record-user { font-size: 12px; color: #64748b; }
.role-badge {
background: #d1fae5; color: #065f46;
padding: 6px 14px; border-radius: 12px; font-size: 13px; font-weight: 600;
}
footer {
text-align: center; margin-top: 32px;
font-size: 12px; color: #64748b;
}
</style>
</head>
<body>
<div class="app-container" id="appContainer">
<div class="glass-header">
<div class="brand-area">
<div class="brand-icon"><i class="fas fa-chalkboard-user"></i></div>
<div class="title">
<h1>工程合同智能管理</h1>
<p>全列拖拽调整宽度 · 甲方乙方筛选 · 资金台账实时统计</p>
</div>
</div>
<div class="action-buttons">
<button class="btn-primary" id="addContractBtn"><i class="fas fa-plus"></i> 新增合同</button>
<button class="btn-outline" id="importExcelBtn"><i class="fas fa-upload"></i> 导入Excel</button>
<button class="btn-outline" id="exportExcelBtn"><i class="fas fa-download"></i> 导出Excel</button>
<button class="btn-outline" id="manageUsersBtn"><i class="fas fa-users-gear"></i> 用户管理</button>
<button class="btn-outline" id="accountSettingsBtn"><i class="fas fa-user-cog"></i> 账号设置</button>
<button class="btn-outline" id="logoutBtn"><i class="fas fa-sign-out-alt"></i> 退出</button>
<span id="userRoleDisplay" class="role-badge"></span>
</div>
</div>
<div class="stats-row">
<div class="stat-card" style="border-left-color:#2563eb;"><span id="totalContracts">0</span><div class="stat-label">总合同数</div></div>
<div class="stat-card" style="border-left-color:#f97316;"><span id="totalInvoiceSum">¥0.00</span><div class="stat-label">总开票金额</div></div>
<div class="stat-card" style="border-left-color:#10b981;"><span id="totalPaymentSum">¥0.00</span><div class="stat-label">总回款金额</div></div>
<div class="stat-card" style="border-left-color:#ef4444;"><span id="totalUnpaidSum">¥0.00</span><div class="stat-label">未回款总额</div></div>
</div>
<div class="filter-bar">
<div class="filter-group"><label><i class="fas fa-building"></i> 甲方筛选</label><select id="filterPartyA"><option value="">全部甲方</option></select></div>
<div class="filter-group"><label><i class="fas fa-users"></i> 乙方筛选</label><select id="filterPartyB"><option value="">全部乙方</option></select></div>
<div class="filter-actions">
<button class="btn-outline" id="clearFilterBtn">清除筛选</button>
<div class="stat-badge" id="filterStats">筛选后: 0 合同 | 总金额 ¥0.00</div>
</div>
</div>
<div class="contracts-panel">
<div class="section-title">
<span><i class="fas fa-list-check"></i> 工程项目合同清单</span>
<span>所有列均可拖拽调整宽度</span>
</div>
<div class="resizable-table-wrapper">
<table id="contractTable">
<thead>
<tr id="headerRow">
<th class="resizable">合同名称<div class="resize-handle"></div></th>
<th class="resizable">合同编号<div class="resize-handle"></div></th>
<th class="resizable">甲方<div class="resize-handle"></div></th>
<th class="resizable">乙方<div class="resize-handle"></div></th>
<th class="resizable">合同金额(元)<div class="resize-handle"></div></th>
<th class="resizable">📊 资金台账 & 比例<div class="resize-handle"></div></th>
<th class="resizable">合同附件<div class="resize-handle"></div></th>
<th class="resizable">履约状态<div class="resize-handle"></div></th>
<th class="resizable">履约保证金(元)<div class="resize-handle"></div></th>
<th class="resizable">质保金(元)<div class="resize-handle"></div></th>
<th class="resizable">操作<div class="resize-handle"></div></th>
</tr>
</thead>
<tbody id="contractListBody"></tbody>
</table>
</div>
</div>
<footer>© 工程合同全景管理平台 - 所有功能完整可用</footer>
</div>
<!-- 合同弹窗 -->
<div id="contractModal" class="modal">
<div class="modal-content">
<h3 id="modalTitle">合同信息</h3>
<form id="contractForm">
<input type="hidden" id="contractId">
<div class="form-row">
<div class="form-group"><label>合同名称 *</label><input type="text" id="contractName" required></div>
<div class="form-group"><label>合同编号</label><input type="text" id="contractNo"></div>
</div>
<div class="form-row">
<div class="form-group"><label>甲方</label><input type="text" id="partyA"></div>
<div class="form-group"><label>乙方</label><input type="text" id="partyB"></div>
</div>
<div class="form-row">
<div class="form-group"><label>合同金额(元)</label><input type="number" id="contractAmount" step="0.01"></div>
</div>
<div class="bond-date-group">
<div style="font-weight:600;margin-bottom:10px;">履约保证金</div>
<div class="form-row">
<div class="form-group"><label>金额</label><input type="number" id="performanceBond" step="0.01"></div>
<div class="form-group"><label>交付日期</label><input type="date" id="perfBondDeliveryDate"></div>
<div class="form-group"><label>退回日期</label><input type="date" id="perfBondReturnDate"></div>
</div>
</div>
<div class="bond-date-group">
<div style="font-weight:600;margin-bottom:10px;">质保金</div>
<div class="form-row">
<div class="form-group"><label>金额</label><input type="number" id="warrantyBond" step="0.01"></div>
<div class="form-group"><label>交付日期</label><input type="date" id="warrantyDeliveryDate"></div>
<div class="form-group"><label>退回日期</label><input type="date" id="warrantyReturnDate"></div>
</div>
<div class="form-row">
<div class="form-group"><label>质保金到期日</label><input type="date" id="warrantyExpiryDate"></div>
</div>
</div>
<div class="form-group">
<label>合同附件</label>
<div id="attachmentsList"></div>
</div>
<div class="upload-area">
<i class="fas fa-paperclip"></i> 上传附件
<input type="file" id="fileUploadInput" multiple hidden>
</div>
<div style="display:flex;justify-content:flex-end;gap:12px;margin-top:24px;">
<button type="button" class="btn-outline" id="closeModalBtn">取消</button>
<button type="submit" class="btn-primary">保存合同</button>
</div>
</form>
</div>
</div>
<!-- 资金台账弹窗 -->
<div id="progressModal" class="modal">
<div class="modal-content" style="max-width:1200px;">
<h3><i class="fas fa-chart-line"></i> 资金台账 · <span id="progressContractName"></span></h3>
<div style="margin-bottom:20px;">
<div class="flex-between"><strong>验工计价</strong><button class="btn-primary" id="addWorkBtn">+ 新增计价</button></div>
<table class="sub-table" id="workTable">
<thead><tr><th>日期</th><th>金额</th><th>说明</th><th>附件</th><th>操作人</th><th>操作</th></tr></thead>
<tbody></tbody>
</table>
</div>
<div style="margin-bottom:20px;">
<div class="flex-between"><strong>开票记录</strong><button class="btn-primary" id="addInvoiceBtn">+ 新增开票</button></div>
<table class="sub-table" id="invoiceTable">
<thead><tr><th>日期</th><th>金额</th><th>备注</th><th>附件</th><th>操作人</th><th>操作</th></tr></thead>
<tbody></tbody>
</table>
</div>
<div style="margin-bottom:20px;">
<div class="flex-between"><strong>回款记录</strong><button class="btn-primary" id="addPaymentBtn">+ 新增回款</button></div>
<table class="sub-table" id="paymentTable">
<thead><tr><th>日期</th><th>金额</th><th>备注</th><th>附件</th><th>操作人</th><th>操作</th></tr></thead>
<tbody></tbody>
</table>
</div>
<div style="background:#f8fafc;border-radius:16px;padding:20px;margin-top:10px;">
<div class="flex-between"><span>📊 汇总</span><span>累计计价: <span id="sumWork">0.00</span> | 开票: <span id="sumInvoice">0.00</span> | 回款: <span id="sumPayment">0.00</span></span></div>
<div class="flex-between" style="margin-top:8px;"><span>未回款:</span><span id="unpaidBalance" style="font-weight:800;color:#ef4444;">0.00</span></div>
<div style="margin-top:8px;display:flex;gap:20px;flex-wrap:wrap;">
<span>验工/合同: <strong id="workContractRatio">0%</strong></span>
<span>开票/合同: <strong id="invoiceContractRatio">0%</strong></span>
<span>回款/开票: <strong id="paymentInvoiceRatio">0%</strong></span>
</div>
<div style="margin-top:6px;">合同总额: <span id="contractAmountDisplay"></span></div>
</div>
<div style="display:flex;justify-content:flex-end;gap:12px;margin-top:24px;">
<button class="btn-outline" id="closeProgressBtn">关闭</button>
</div>
</div>
</div>
<!-- 明细录入弹窗 -->
<div id="entryModal" class="modal">
<div class="modal-content">
<h3 id="entryTitle">登记明细</h3>
<div class="form-group"><label>日期</label><input type="date" id="entryDate"></div>
<div class="form-group"><label>金额(元)</label><input type="number" id="entryAmount" step="0.01"></div>
<div class="form-group"><label>备注</label><textarea id="entryRemark" rows="2"></textarea></div>
<div class="form-group"><label>附件上传</label><input type="file" id="entryAttachmentFile"></div>
<div style="display:flex;justify-content:flex-end;gap:12px;margin-top:24px;">
<button class="btn-outline" id="cancelEntryBtn">取消</button>
<button class="btn-primary" id="confirmEntryBtn">确认</button>
</div>
</div>
</div>
<!-- 用户管理 -->
<div id="userManageModal" class="modal">
<div class="modal-content" style="max-width:550px;">
<h3>用户管理</h3>
<div id="userListArea" style="margin-bottom:20px;"></div>
<div style="border-top:1px solid #f1f5f9;padding-top:20px;">
<h4>新增用户</h4>
<div class="form-row">
<div class="form-group"><label>用户名</label><input type="text" id="newUserName"></div>
<div class="form-group"><label>密码</label><input type="password" id="newUserPwd"></div>
</div>
<div style="display:flex;justify-content:flex-end;gap:12px;margin-top:20px;">
<button class="btn-outline" id="closeUserManageBtn">关闭</button>
<button class="btn-primary" id="createUserBtn">创建用户</button>
</div>
</div>
</div>
</div>
<!-- 账号设置 -->
<div id="accountModal" class="modal">
<div class="modal-content">
<h3>账号设置</h3>
<div id="accountErrorMsg" style="color:#ef4444;margin:10px 0;"></div>
<div class="form-group"><label>新用户名</label><input type="text" id="newUsername"></div>
<div class="form-group"><label>原密码</label><input type="password" id="oldPassword"></div>
<div class="form-group"><label>新密码</label><input type="password" id="newPassword"></div>
<div class="form-group"><label>确认新密码</label><input type="password" id="confirmNewPassword"></div>
<div style="display:flex;justify-content:flex-end;gap:12px;margin-top:24px;">
<button class="btn-outline" id="closeAccountBtn">取消</button>
<button class="btn-primary" id="saveAccountBtn">保存</button>
</div>
</div>
</div>
<script>
let appUsers = JSON.parse(localStorage.getItem('appUsers')) || [{ username:"admin", password:"123456", role:"admin" },{ username:"user", password:"123456", role:"general" }];
let currentUser = null;
let contracts = JSON.parse(localStorage.getItem('contractsData')) || [];
let currentFilters = { partyA: '', partyB: '' };
let currentAttachContractId = null;
let currentProgressContract = null;
let pendingEntryType = null;
function saveUsers() { localStorage.setItem('appUsers', JSON.stringify(appUsers)); }
function saveContracts() { localStorage.setItem('contractsData', JSON.stringify(contracts)); }
function isAdmin() { return currentUser && currentUser.role === "admin"; }
function escapeHtml(str) { if(!str) return ''; return str.replace(/[&<>]/g, m => ({ '&':'&','<':'<','>':'>' })[m]); }
function formatMoney(v) { return (v===undefined||v===null) ? "0.00" : Number(v).toLocaleString('en-US',{minimumFractionDigits:2}); }
function isWarrantyExpiringSoon(d) { if(!d) return false; const exp=new Date(d), today=new Date(); today.setHours(0,0,0,0); const diff=Math.ceil((exp-today)/(86400000)); return diff<=30 && diff>=0; }
function openAttachmentInBrowser(url,name){ const win=window.open(); if(win) win.document.write(`<html><head><title>${escapeHtml(name)}</title></head><body style="margin:0;"><iframe src="${url}" style="width:100%;height:100vh;border:none;"></iframe></body></html>`); else window.location.href=url; }
function getFilteredContracts() { return contracts.filter(c => (!currentFilters.partyA || c.partyA === currentFilters.partyA) && (!currentFilters.partyB || c.partyB === currentFilters.partyB)); }
function refreshGlobalStats() {
let totalInv=0,totalPay=0;
contracts.forEach(c=>{ totalInv+=c.invoiceList?.reduce((s,i)=>s+(i.amount||0),0)||0; totalPay+=c.paymentList?.reduce((s,i)=>s+(i.amount||0),0)||0; });
document.getElementById('totalContracts').innerText=contracts.length;
document.getElementById('totalInvoiceSum').innerHTML=`¥${formatMoney(totalInv)}`;
document.getElementById('totalPaymentSum').innerHTML=`¥${formatMoney(totalPay)}`;
document.getElementById('totalUnpaidSum').innerHTML=`¥${formatMoney(totalInv-totalPay)}`;
}
function renderContractTable(){
const filtered=getFilteredContracts();
const tbody=document.getElementById('contractListBody'); tbody.innerHTML='';
filtered.forEach(c=>{
const totalWork=c.workList?.reduce((s,i)=>s+(i.amount||0),0)||0, totalInvoice=c.invoiceList?.reduce((s,i)=>s+(i.amount||0),0)||0, totalPayment=c.paymentList?.reduce((s,i)=>s+(i.amount||0),0)||0;
const contractAmt=c.contractAmount||1, workRatio=(totalWork/contractAmt)*100, invRatio=(totalInvoice/contractAmt)*100, payInvRatio=totalInvoice>0?(totalPayment/totalInvoice)*100:0;
const fundsHtml=`<div class="funds-dashboard"><div>📈验工:¥${formatMoney(totalWork)} <span class="progress-badge">${workRatio.toFixed(1)}%</span></div><div>🧾开票:¥${formatMoney(totalInvoice)} <span class="progress-badge">${invRatio.toFixed(1)}%</span></div><div>💰回款:¥${formatMoney(totalPayment)} <span class="progress-badge">回款/开票 ${payInvRatio.toFixed(1)}%</span></div><div>未回款:¥${formatMoney(totalInvoice-totalPayment)}</div></div>`;
let attHtml='<span class="tag">无附件</span>';
if(c.attachments?.length) attHtml=c.attachments.map(a=>`<a class="file-link" data-openurl='${a.dataURL}' data-name='${escapeHtml(a.name)}'><i class="fas fa-eye"></i> ${a.name.length>12?a.name.slice(0,10)+'...':a.name}</a>`).join(' ');
const actionsHtml=isAdmin()?`<div class="actions"><i class="fas fa-chart-line" data-id="${c.id}" data-action="progress"></i><i class="fas fa-edit" data-id="${c.id}" data-action="edit"></i><i class="fas fa-trash-alt" data-id="${c.id}" data-action="delete"></i></div>`:`<div class="actions"><i class="fas fa-chart-line" data-id="${c.id}" data-action="progress"></i><span style="color:#8ba0ae;">只读</span></div>`;
const isExp=isWarrantyExpiringSoon(c.warrantyExpiryDate);
const tr=tbody.insertRow(); if(isExp) tr.classList.add('expired-warning');
tr.innerHTML=`<td>${escapeHtml(c.name)}</td><td>${escapeHtml(c.contractNo||'-')}</td><td>${escapeHtml(c.partyA||'-')}</td><td>${escapeHtml(c.partyB||'-')}</td><td>¥${formatMoney(c.contractAmount)}</td><td>${fundsHtml}</td><td>${attHtml}</td><td>${isExp?'<span class="warning-badge">质保金将到期</span>':'<span class="tag">履约中</span>'}</td><td>¥${formatMoney(c.performanceBond)}</td><td>¥${formatMoney(c.warrantyBond)}</td><td>${actionsHtml}</td>`;
});
document.querySelectorAll('[data-openurl]').forEach(el=>{ el.onclick=(e)=>{ e.stopPropagation(); openAttachmentInBrowser(el.getAttribute('data-openurl'),el.getAttribute('data-name')||'附件'); }; });
document.querySelectorAll('.actions i').forEach(icon=>{ const id=icon.getAttribute('data-id'), act=icon.getAttribute('data-action'); if(act==='edit') icon.onclick=()=>{ if(isAdmin()) openEditContract(id); else alert("无权限"); }; if(act==='delete') icon.onclick=()=>{ if(isAdmin()&&confirm('确认删除?')){ contracts=contracts.filter(c=>c.id!==id); saveContracts(); updateFilterOptions(); renderContractTable(); refreshGlobalStats();} else alert("无权限"); }; if(act==='progress') icon.onclick=()=>openProgressModal(id); });
const totalAmount=filtered.reduce((s,c)=>s+(c.contractAmount||0),0);
document.getElementById('filterStats').innerHTML=`筛选后: ${filtered.length} 合同 | 总金额 ¥${formatMoney(totalAmount)}`;
refreshGlobalStats();
}
function updateFilterOptions(){
const partyASet=new Set(contracts.map(c=>c.partyA).filter(v=>v&&v.trim()));
const partyBSet=new Set(contracts.map(c=>c.partyB).filter(v=>v&&v.trim()));
const partyASelect=document.getElementById('filterPartyA'), partyBSelect=document.getElementById('filterPartyB');
partyASelect.innerHTML='<option value="">全部甲方</option>'+Array.from(partyASet).sort().map(v=>`<option value="${escapeHtml(v)}">${escapeHtml(v)}</option>`).join('');
partyBSelect.innerHTML='<option value="">全部乙方</option>'+Array.from(partyBSet).sort().map(v=>`<option value="${escapeHtml(v)}">${escapeHtml(v)}</option>`).join('');
partyASelect.value=currentFilters.partyA; partyBSelect.value=currentFilters.partyB;
}
function initColumnResize(){
const handles = document.querySelectorAll('#headerRow th.resizable .resize-handle');
handles.forEach(handle => {
handle.addEventListener('mousedown', function(e) {
e.preventDefault();
const th = this.parentElement;
const startX = e.clientX;
const startWidth = th.offsetWidth;
const onMouseMove = (me) => {
const newWidth = startWidth + (me.clientX - startX);
if (newWidth > 60) { th.style.width = newWidth + 'px'; th.style.minWidth = newWidth + 'px'; }
};
const onMouseUp = () => {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
document.body.classList.remove('resizing');
};
document.body.classList.add('resizing');
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
});
}
function openProgressModal(id){
currentProgressContract = contracts.find(c => c.id === id);
if(!currentProgressContract) return;
document.getElementById('progressContractName').innerHTML = currentProgressContract.name;
document.getElementById('contractAmountDisplay').innerHTML = `¥${formatMoney(currentProgressContract.contractAmount)}`;
renderProgressTables();
updateProgressSummary();
document.getElementById('progressModal').style.display = 'flex';
}
function renderProgressTables(){
const wb = document.querySelector('#workTable tbody'); wb.innerHTML = '';
(currentProgressContract.workList || []).forEach((it,idx)=>{ wb.innerHTML += `<tr><td>${it.date||''}</td><td>${formatMoney(it.amount)}</td><td>${escapeHtml(it.desc||'')}</td><td>${renderMiniAttachments(it.attachments)}</td><td class="record-user">${escapeHtml(it.createdBy||'')}</td><td><i class="fas fa-trash" data-type="work" data-idx="${idx}"></i></td></tr>`; });
const ib = document.querySelector('#invoiceTable tbody'); ib.innerHTML = '';
(currentProgressContract.invoiceList || []).forEach((it,idx)=>{ ib.innerHTML += `<tr><td>${it.date||''}</td><td>${formatMoney(it.amount)}</td><td>${escapeHtml(it.remark||'')}</td><td>${renderMiniAttachments(it.attachments)}</td><td class="record-user">${escapeHtml(it.createdBy||'')}</td><td><i class="fas fa-trash" data-type="invoice" data-idx="${idx}"></i></td></tr>`; });
const pb = document.querySelector('#paymentTable tbody'); pb.innerHTML = '';
(currentProgressContract.paymentList || []).forEach((it,idx)=>{ pb.innerHTML += `<tr><td>${it.date||''}</td><td>${formatMoney(it.amount)}</td><td>${escapeHtml(it.remark||'')}</td><td>${renderMiniAttachments(it.attachments)}</td><td class="record-user">${escapeHtml(it.createdBy||'')}</td><td><i class="fas fa-trash" data-type="payment" data-idx="${idx}"></i></td></tr>`; });
bindProgressEvents();
}
function renderMiniAttachments(attachments){
if(!attachments?.length) return '<span class="tag">无</span>';
return attachments.map(att => `<a class="file-link" data-openurl='${att.dataURL}' data-name='${escapeHtml(att.name)}'><i class="fas fa-paperclip"></i> ${att.name.slice(0,8)}</a>`).join('');
}
function bindProgressEvents(){
document.querySelectorAll('#workTable .fa-trash, #invoiceTable .fa-trash, #paymentTable .fa-trash').forEach(icon=>{
icon.onclick = () => {
if(!isAdmin()){ alert("仅管理员可删除"); return; }
const type = icon.getAttribute('data-type'), idx = parseInt(icon.getAttribute('data-idx'));
if(type==='work') currentProgressContract.workList.splice(idx,1);
if(type==='invoice') currentProgressContract.invoiceList.splice(idx,1);
if(type==='payment') currentProgressContract.paymentList.splice(idx,1);
renderProgressTables(); updateProgressSummary(); renderContractTable(); saveContracts();
};
});
document.querySelectorAll('[data-openurl]').forEach(el=>{ el.onclick=(e)=>{ e.stopPropagation(); openAttachmentInBrowser(el.getAttribute('data-openurl'), el.getAttribute('data-name')||'附件'); }; });
}
function updateProgressSummary(){
const sw = currentProgressContract.workList?.reduce((s,i)=>s+(i.amount||0),0)||0;
const si = currentProgressContract.invoiceList?.reduce((s,i)=>s+(i.amount||0),0)||0;
const sp = currentProgressContract.paymentList?.reduce((s,i)=>s+(i.amount||0),0)||0;
const amt = currentProgressContract.contractAmount || 1;
document.getElementById('sumWork').innerText = formatMoney(sw);
document.getElementById('sumInvoice').innerText = formatMoney(si);
document.getElementById('sumPayment').innerText = formatMoney(sp);
document.getElementById('unpaidBalance').innerHTML = formatMoney(si-sp);
document.getElementById('workContractRatio').innerHTML = `${((sw/amt)*100).toFixed(1)}%`;
document.getElementById('invoiceContractRatio').innerHTML = `${((si/amt)*100).toFixed(1)}%`;
document.getElementById('paymentInvoiceRatio').innerHTML = `${(si>0?(sp/si)*100:0).toFixed(1)}%`;
}
function showEntryDialog(type, title){
pendingEntryType = type;
document.getElementById('entryTitle').innerHTML = title;
document.getElementById('entryDate').value = new Date().toISOString().slice(0,10);
document.getElementById('entryAmount').value = '';
document.getElementById('entryRemark').value = '';
document.getElementById('entryAttachmentFile').value = '';
document.getElementById('entryModal').style.display = 'flex';
}
function confirmEntry(){
const date = document.getElementById('entryDate').value, amount = parseFloat(document.getElementById('entryAmount').value), remark = document.getElementById('entryRemark').value;
if(!date || isNaN(amount) || amount<=0){ alert("请填写完整有效信息"); return; }
const fileInput = document.getElementById('entryAttachmentFile');
const process = (att) => {
const newItem = { date, amount, remark: remark||'', createdBy: currentUser?.username || 'unknown', createdAt: new Date().toISOString(), attachments: att ? [att] : [] };
if(pendingEntryType === 'work'){ if(!currentProgressContract.workList) currentProgressContract.workList = []; currentProgressContract.workList.push(newItem); }
else if(pendingEntryType === 'invoice'){ if(!currentProgressContract.invoiceList) currentProgressContract.invoiceList = []; currentProgressContract.invoiceList.push(newItem); }
else if(pendingEntryType === 'payment'){ if(!currentProgressContract.paymentList) currentProgressContract.paymentList = []; currentProgressContract.paymentList.push(newItem); }
renderProgressTables(); updateProgressSummary(); renderContractTable(); saveContracts(); closeEntryModal();
};
if(fileInput.files.length > 0){
const reader = new FileReader();
reader.onload = ev => process({ name: fileInput.files[0].name, dataURL: ev.target.result });
reader.readAsDataURL(fileInput.files[0]);
} else process(null);
}
function closeEntryModal(){ document.getElementById('entryModal').style.display = 'none'; pendingEntryType = null; }
function saveContract(e){ e.preventDefault(); if(!isAdmin()) return alert("无权限"); const id=document.getElementById('contractId').value; const name=document.getElementById('contractName').value.trim(); if(!name) return alert('合同名称必填'); const data={ name, contractNo:document.getElementById('contractNo').value, partyA:document.getElementById('partyA').value, partyB:document.getElementById('partyB').value, contractAmount:parseFloat(document.getElementById('contractAmount').value)||0, performanceBond:parseFloat(document.getElementById('performanceBond').value)||0, perfBondDeliveryDate:document.getElementById('perfBondDeliveryDate').value, perfBondReturnDate:document.getElementById('perfBondReturnDate').value, warrantyBond:parseFloat(document.getElementById('warrantyBond').value)||0, warrantyDeliveryDate:document.getElementById('warrantyDeliveryDate').value, warrantyReturnDate:document.getElementById('warrantyReturnDate').value, warrantyExpiryDate:document.getElementById('warrantyExpiryDate').value };
if(id){ const idx=contracts.findIndex(c=>c.id===id); if(idx!==-1) contracts[idx]={...contracts[idx], ...data}; }
else { contracts.push({ id:'c_'+Date.now()+'_'+Math.random().toString(36).substr(2,6), ...data, workList:[], invoiceList:[], paymentList:[], attachments:[] }); }
saveContracts(); updateFilterOptions(); renderContractTable(); closeContractModal();
}
function openEditContract(id){ if(!isAdmin()) return; const c=contracts.find(c=>c.id===id); if(c){ document.getElementById('contractId').value=c.id; document.getElementById('contractName').value=c.name; document.getElementById('contractNo').value=c.contractNo||''; document.getElementById('partyA').value=c.partyA||''; document.getElementById('partyB').value=c.partyB||''; document.getElementById('contractAmount').value=c.contractAmount||0; document.getElementById('performanceBond').value=c.performanceBond||0; document.getElementById('perfBondDeliveryDate').value=c.perfBondDeliveryDate||''; document.getElementById('perfBondReturnDate').value=c.perfBondReturnDate||''; document.getElementById('warrantyBond').value=c.warrantyBond||0; document.getElementById('warrantyDeliveryDate').value=c.warrantyDeliveryDate||''; document.getElementById('warrantyReturnDate').value=c.warrantyReturnDate||''; document.getElementById('warrantyExpiryDate').value=c.warrantyExpiryDate||''; currentAttachContractId=c.id; refreshAttachmentUI(c.id); document.getElementById('modalTitle').innerHTML='编辑合同'; document.getElementById('contractModal').style.display='flex'; } }
function addNewContract(){ if(!isAdmin()) return alert("无权限"); document.getElementById('contractForm').reset(); document.getElementById('contractId').value=''; document.getElementById('contractAmount').value='0'; document.getElementById('modalTitle').innerHTML='新增合同'; document.getElementById('attachmentsList').innerHTML='<span class="tag">暂无附件</span>'; currentAttachContractId=null; document.getElementById('contractModal').style.display='flex'; }
function closeContractModal(){ document.getElementById('contractModal').style.display='none'; }
function refreshAttachmentUI(contractId){ const container=document.getElementById('attachmentsList'); const contract=contracts.find(c=>c.id===contractId); if(contract?.attachments) container.innerHTML=contract.attachments.map((att,idx)=>`<div class="attachment-item">${att.name}<span><i class="fas fa-eye" data-openurl='${att.dataURL}' data-name='${escapeHtml(att.name)}'></i><i class="fas fa-trash-alt" data-delidx="${idx}" style="margin-left:6px;"></i></span></div>`).join(''); else container.innerHTML='<span class="tag">暂无附件</span>'; document.querySelectorAll('[data-openurl]').forEach(el=>{ el.onclick=()=>openAttachmentInBrowser(el.getAttribute('data-openurl'),'附件'); }); document.querySelectorAll('[data-delidx]').forEach(el=>{ el.onclick=()=>{ if(isAdmin()){ contract.attachments.splice(parseInt(el.getAttribute('data-delidx')),1); refreshAttachmentUI(contractId); renderContractTable(); saveContracts(); } else alert("无权限"); }; }); }
function exportToExcel(){ const data=contracts.map(c=>({ '合同名称':c.name,'合同编号':c.contractNo,'甲方':c.partyA,'乙方':c.partyB,'合同金额':c.contractAmount })); const ws=XLSX.utils.json_to_sheet(data); const wb=XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb,ws,'合同台账'); XLSX.writeFile(wb,`工程合同台账_${new Date().toISOString().slice(0,10)}.xlsx`); }
function importExcel(file){ if(!isAdmin()){ alert("仅管理员可导入"); return; } const reader=new FileReader(); reader.onload=function(e){ const wb=XLSX.read(new Uint8Array(e.target.result),{type:'array'}); const sheet=wb.Sheets[wb.SheetNames[0]]; const rows=XLSX.utils.sheet_to_json(sheet); rows.forEach(row=>{ contracts.push({ id:'c_imp_'+Date.now()+'_'+Math.random().toString(36).substr(2,5), name:row['合同名称']||'未命名', contractNo:row['合同编号']||'', partyA:row['甲方']||'', partyB:row['乙方']||'', contractAmount:parseFloat(row['合同金额'])||0, performanceBond:0, warrantyBond:0, warrantyExpiryDate:'', workList:[], invoiceList:[], paymentList:[], attachments:[] }); }); saveContracts(); updateFilterOptions(); renderContractTable(); alert(`导入成功 ${rows.length} 条`); }; reader.readAsArrayBuffer(file); }
function renderUserList(){ const container=document.getElementById('userListArea'); container.innerHTML=appUsers.map(u=>`<div style="padding:10px;border-bottom:1px solid #f1f5f9;"><strong>${escapeHtml(u.username)}</strong> (${u.role==='admin'?'管理员':'普通用户'}) ${u.role!=='admin'?`<button class="btn-outline" style="padding:6px 12px;margin-left:12px;" data-deluser="${u.username}">删除</button>`:''}</div>`).join(''); document.querySelectorAll('[data-deluser]').forEach(btn=>{ btn.onclick=()=>{ const uname=btn.getAttribute('data-deluser'); if(uname===currentUser.username){ alert("不能删除自己"); return; } appUsers=appUsers.filter(u=>u.username!==uname); saveUsers(); renderUserList(); }; }); }
function openUserManage(){ if(!isAdmin()){ alert("无权限"); return; } renderUserList(); document.getElementById('userManageModal').style.display='flex'; }
function createUser(){ const uname=document.getElementById('newUserName').value.trim(), pwd=document.getElementById('newUserPwd').value; if(!uname||!pwd) return alert("用户名/密码不能为空"); if(appUsers.some(u=>u.username===uname)) return alert("用户名已存在"); appUsers.push({ username:uname, password:pwd, role:"general" }); saveUsers(); renderUserList(); document.getElementById('newUserName').value=''; document.getElementById('newUserPwd').value=''; alert(`用户 ${uname} 创建成功`); }
function openAccountModal(){ document.getElementById('accountErrorMsg').innerText=''; document.getElementById('newUsername').value=''; document.getElementById('oldPassword').value=''; document.getElementById('newPassword').value=''; document.getElementById('confirmNewPassword').value=''; document.getElementById('accountModal').style.display='flex'; }
function saveAccountChanges(){ const newName=document.getElementById('newUsername').value.trim(), oldPwd=document.getElementById('oldPassword').value, newPwd=document.getElementById('newPassword').value, confirm=document.getElementById('confirmNewPassword').value, err=document.getElementById('accountErrorMsg'); const idx=appUsers.findIndex(u=>u.username===currentUser.username); if(idx===-1||appUsers[idx].password!==oldPwd){ err.innerText='原密码错误'; return; } if(newName && newName!==currentUser.username){ if(appUsers.some(u=>u.username===newName)){ err.innerText('用户名重复'); return; } currentUser.username=newName; appUsers[idx].username=newName; } if(newPwd){ if(newPwd!==confirm){ err.innerText='两次密码不一致'; return; } appUsers[idx].password=newPwd; } saveUsers(); localStorage.setItem('currentUser',JSON.stringify(currentUser)); alert('修改成功'); closeAccountModal(); }
function closeAccountModal(){ document.getElementById('accountModal').style.display='none'; }
function showLogin(){
const loginHtml = `
<div class="login-overlay" id="loginOverlay">
<div class="login-card">
<div class="login-logo"><i class="fas fa-hard-hat"></i></div>
<h2>工程合同智管平台</h2>
<div class="login-sub">合同管理 | 资金台账 | 权限控制</div>
<div class="input-group-login"><i class="fas fa-user"></i><input id="loginUsername" placeholder="请输入用户名"></div>
<div class="input-group-login"><i class="fas fa-lock"></i><input id="loginPassword" type="password" placeholder="请输入密码"></div>
<button class="btn-login" id="doLoginBtn"><i class="fas fa-sign-in-alt"></i> 登录系统</button>
<div class="demo-hint">管理员:admin / 123456 普通用户:user / 123456</div>
</div>
</div>`;
document.body.insertAdjacentHTML('beforeend', loginHtml);
document.getElementById('doLoginBtn').onclick = () => {
const uname = document.getElementById('loginUsername').value.trim();
const pwd = document.getElementById('loginPassword').value.trim();
const user = appUsers.find(u => u.username === uname && u.password === pwd);
if (user) {
currentUser = { username: user.username, role: user.role };
localStorage.setItem('currentUser', JSON.stringify(currentUser));
document.getElementById('loginOverlay').remove();
document.getElementById('appContainer').style.display = 'block';
document.getElementById('userRoleDisplay').innerText = currentUser.role === 'admin' ? '管理员' : '普通用户';
if(contracts.length === 0) {
contracts = [{ id:"c1", name:"裕丰花园住宅项目", contractNo:"GC-2023-001", partyA:"裕丰地产", partyB:"中建三局", contractAmount:15800000, performanceBond:120000, warrantyBond:80000, warrantyExpiryDate:"2026-06-15", workList:[], invoiceList:[], paymentList:[], attachments:[] }];
saveContracts();
}
updateFilterOptions();
renderContractTable();
initColumnResize();
} else {
alert("用户名或密码错误");
}
};
}
function logout(){ currentUser=null; localStorage.removeItem('currentUser'); document.getElementById('appContainer').style.display='none'; showLogin(); }
function initFilters(){
document.getElementById('filterPartyA').addEventListener('change',(e)=>{ currentFilters.partyA=e.target.value; renderContractTable(); });
document.getElementById('filterPartyB').addEventListener('change',(e)=>{ currentFilters.partyB=e.target.value; renderContractTable(); });
document.getElementById('clearFilterBtn').addEventListener('click',()=>{ currentFilters.partyA=''; currentFilters.partyB=''; updateFilterOptions(); renderContractTable(); });
}
window.onload = () => {
const savedUser = localStorage.getItem('currentUser');
if (savedUser) {
try {
const u = JSON.parse(savedUser);
const found = appUsers.find(au => au.username === u.username);
if (found) {
currentUser = found;
document.getElementById('appContainer').style.display = 'block';
document.getElementById('userRoleDisplay').innerText = currentUser.role === 'admin' ? '管理员' : '普通用户';
contracts = JSON.parse(localStorage.getItem('contractsData')) || [];
if(contracts.length === 0) {
contracts = [{ id:"c1", name:"裕丰花园住宅项目", contractNo:"GC-2023-001", partyA:"裕丰地产", partyB:"中建三局", contractAmount:15800000, performanceBond:120000, warrantyBond:80000, warrantyExpiryDate:"2026-06-15", workList:[], invoiceList:[], paymentList:[], attachments:[] }];
saveContracts();
}
updateFilterOptions();
initFilters();
renderContractTable();
initColumnResize();
} else {
showLogin();
}
} catch (e) {
showLogin();
}
} else {
showLogin();
}
document.getElementById('addContractBtn')?.addEventListener('click', addNewContract);
document.getElementById('closeModalBtn')?.addEventListener('click', closeContractModal);
document.getElementById('closeProgressBtn')?.addEventListener('click', () => document.getElementById('progressModal').style.display = 'none');
document.getElementById('exportExcelBtn')?.addEventListener('click', exportToExcel);
document.getElementById('importExcelBtn')?.addEventListener('click', () => document.getElementById('excelUploadInput').click());
const excelInput = document.createElement('input');
excelInput.type = 'file'; excelInput.id = 'excelUploadInput'; excelInput.accept = '.xlsx,.xls'; excelInput.hidden = true;
document.body.appendChild(excelInput);
excelInput.addEventListener('change', (e) => { if(e.target.files.length) importExcel(e.target.files[0]); e.target.value = ''; });
document.getElementById('logoutBtn')?.addEventListener('click', logout);
document.getElementById('accountSettingsBtn')?.addEventListener('click', openAccountModal);
document.getElementById('closeAccountBtn')?.addEventListener('click', closeAccountModal);
document.getElementById('saveAccountBtn')?.addEventListener('click', saveAccountChanges);
document.getElementById('manageUsersBtn')?.addEventListener('click', openUserManage);
document.getElementById('closeUserManageBtn')?.addEventListener('click', () => document.getElementById('userManageModal').style.display = 'none');
document.getElementById('createUserBtn')?.addEventListener('click', createUser);
document.getElementById('contractForm')?.addEventListener('submit', saveContract);
document.getElementById('fileUploadInput')?.addEventListener('change', async (e) => {
let targetId = currentAttachContractId || document.getElementById('contractId').value;
if(!targetId){ alert('请先保存合同'); return; }
if(!isAdmin()){ alert("仅管理员可上传"); return; }
for(let file of e.target.files){
const reader = new FileReader();
await new Promise(resolve => {
reader.onload = ev => {
const ct = contracts.find(c => c.id === targetId);
if(ct){ if(!ct.attachments) ct.attachments = []; ct.attachments.push({ name:file.name, dataURL:ev.target.result }); }
resolve();
};
reader.readAsDataURL(file);
});
}
e.target.value = '';
if(targetId) refreshAttachmentUI(targetId);
renderContractTable();
saveContracts();
});
document.getElementById('addWorkBtn')?.addEventListener('click', () => showEntryDialog('work','新增验工计价'));
document.getElementById('addInvoiceBtn')?.addEventListener('click', () => showEntryDialog('invoice','新增开票记录'));
document.getElementById('addPaymentBtn')?.addEventListener('click', () => showEntryDialog('payment','新增回款记录'));
document.getElementById('confirmEntryBtn')?.addEventListener('click', confirmEntry);
document.getElementById('cancelEntryBtn')?.addEventListener('click', closeEntryModal);
document.querySelector('.upload-area')?.addEventListener('click', () => document.getElementById('fileUploadInput').click());
};
</script>
</body>
</html>