Files
biss 6eed2e357f
Vercel Deploy / deploy (push) Failing after 5m47s
tfyg
2026-06-29 06:35:12 +08:00

303 lines
16 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>四等水准测量记录表</title>
<style>
:root{--border:#222;--thin:#555;--bg:#f6f7fb;--blue:#1f6feb;--red:#d1242f}
*{box-sizing:border-box}
body{margin:0;padding:18px;background:var(--bg);font-family:"Microsoft YaHei",SimSun,Arial,sans-serif;color:#111}
.toolbar{position:sticky;top:0;z-index:10;background:#fff;border:1px solid #ddd;border-radius:10px;padding:10px;margin-bottom:12px;box-shadow:0 2px 10px #0001;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
button,.file-label{border:1px solid #bbb;background:#fff;border-radius:7px;padding:7px 10px;cursor:pointer;font-size:14px}
button.primary{background:var(--blue);border-color:var(--blue);color:#fff}
button.danger{color:var(--red);border-color:#e6b8bd}
button:hover,.file-label:hover{filter:brightness(.97)}
input[type=file]{display:none}
.tip{font-size:13px;color:#555;margin-left:auto}
.page{width:1120px;max-width:100%;margin:auto;background:#fff;padding:18px 18px 28px;border:1px solid #ddd;box-shadow:0 2px 16px #0001}
.meta{display:grid;grid-template-columns:1fr 1fr 2fr;gap:16px;margin:0 0 8px;font-size:15px;align-items:end}
.meta input{border:none;border-bottom:1px solid #777;outline:none;padding:3px 6px;font-size:15px;width:190px}
h1{text-align:center;font-size:22px;letter-spacing:2px;margin:0 0 10px}
table{width:100%;border-collapse:collapse;table-layout:fixed;background:#fff}
th,td{border:1px solid var(--border);text-align:center;vertical-align:middle;padding:0;height:30px;font-size:13px;line-height:1.15}
th{font-weight:600;background:#fafafa}
.thin td,.thin th{border-color:var(--thin)}
input,select{width:100%;height:100%;border:none;outline:none;text-align:center;font:inherit;background:transparent;padding:3px}
input.calc{background:#f8fbff;color:#0b5cab;font-weight:600}
input.warn{background:#fff3cd;color:#8a5a00}
.v{writing-mode:vertical-rl;text-orientation:upright;letter-spacing:2px}
.narrow{font-size:12px}
.station-start td{border-top-width:2px}
.group-end td{border-bottom-width:2px}
.dircell{display:flex;align-items:center;height:100%}
.dir-fixed{min-width:34px;border-right:1px solid #ccc;height:100%;display:flex;align-items:center;justify-content:center;background:#fbfbfb;font-weight:600}
.dircell input{text-align:left;padding-left:6px}
.right-note{width:84px}
.summary{margin-top:10px;display:grid;grid-template-columns:repeat(5,1fr);gap:8px;font-size:14px}
.box{border:1px solid #ccc;border-radius:8px;padding:8px;background:#fafafa}
.box b{display:block;margin-bottom:4px}
@media print{body{background:#fff;padding:0}.toolbar{display:none}.page{box-shadow:none;border:none;width:100%;padding:10mm}button{display:none}th,td{height:25px;font-size:11px}.summary{page-break-inside:avoid}}
</style>
</head>
<body>
<div class="toolbar">
<button class="primary" onclick="addStation()">+新增一个测站</button>
<button onclick="addStations(5)">+新增5个测站</button>
<button onclick="exportJSON()">导出JSON</button>
<button onclick="exportCSV()">导出CSV</button>
<label class="file-label" for="importFile">导入JSON/CSV</label><input id="importFile" type="file" accept=".json,.csv,text/csv,application/json" onchange="importFile(event)">
<button onclick="window.print()">打印/另存PDF</button>
<button class="danger" onclick="clearAll()">清空</button>
<span class="tip">输入上下丝、黑面、红面、K值后自动计算;“后/前/后-前”后面的文字可直接填写。</span>
</div>
<div class="page">
<div class="meta">
<div>观测员:<input id="observer" /></div>
<div>记录员:<input id="recorder" /></div>
<h1>四等水准测量记录表</h1>
</div>
<table id="recordTable">
<colgroup>
<col style="width:42px"><col style="width:46px">
<col style="width:88px"><col style="width:88px"><col style="width:82px">
<col style="width:88px"><col style="width:88px"><col style="width:82px">
<col style="width:98px"><col style="width:84px"><col style="width:84px"><col style="width:86px"><col style="width:96px"><col style="width:82px">
</colgroup>
<thead>
<tr>
<th rowspan="3" class="v">测站编号</th>
<th rowspan="3" class="v">转点</th>
<th colspan="3">后尺</th>
<th colspan="3">前尺</th>
<th rowspan="3">方向及<br>尺号</th>
<th colspan="2">标尺读数<br>m</th>
<th rowspan="3">黑+K-红<br>mm</th>
<th rowspan="3">高差中数<br>hm</th>
<th rowspan="3">备注</th>
</tr>
<tr>
<th>上丝</th><th>下丝</th><th>视距dm</th>
<th>上丝</th><th>下丝</th><th>视距dm</th>
<th>黑面</th><th>红面</th>
</tr>
<tr>
<th colspan="3" class="narrow">自动:d =(上丝-下丝)×100</th>
<th colspan="3" class="narrow">后-前视距差自动计算</th>
<th colspan="2" class="narrow">可输入至 0.001 m</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
<div class="summary">
<div class="box"><b>测站数</b><span id="sumStations">0</span></div>
<div class="box"><b>Σ后视距(m</b><span id="sumBackD">0.00</span></div>
<div class="box"><b>Σ前视距(m</b><span id="sumFrontD">0.00</span></div>
<div class="box"><b>Σ视距差(m</b><span id="sumDiffD">0.00</span></div>
<div class="box"><b>Σ高差中数(m</b><span id="sumH">0.0000</span></div>
</div>
</div>
<script>
const tbody = document.getElementById('tbody');
let stationCount = 0;
function n(v){ const x = parseFloat(String(v).trim()); return Number.isFinite(x) ? x : null; }
function fmt(v, d=3){ return Number.isFinite(v) ? v.toFixed(d) : ''; }
function mm(v){ return Number.isFinite(v) ? Math.round(v).toString() : ''; }
function q(root, sel){ return root.querySelector(sel); }
function input(cls='', extra=''){
return `<input class="${cls}" ${extra} oninput="recalc()">`;
}
function calcInput(cls=''){
return `<input class="calc ${cls}" readonly>`;
}
function addStation(data={}){
stationCount++;
const sid = 'S' + Date.now() + '_' + Math.random().toString(16).slice(2);
const tr1 = document.createElement('tr');
const tr2 = document.createElement('tr');
const tr3 = document.createElement('tr');
tr1.className = 'station station-start'; tr2.className = 'station'; tr3.className = 'station group-end';
[tr1,tr2,tr3].forEach(tr => tr.dataset.sid = sid);
tr1.innerHTML = `
<td rowspan="3"><input class="stationNo" value="${data.stationNo ?? stationCount}" oninput="recalc()"></td>
<td rowspan="3"><input class="turnPoint" value="${data.turnPoint ?? ''}" oninput="recalc()"></td>
<td>${input('backUpper', `value="${data.backUpper ?? ''}"`)}</td>
<td>${input('backLower', `value="${data.backLower ?? ''}"`)}</td>
<td>${calcInput('backD')}</td>
<td>${input('frontUpper', `value="${data.frontUpper ?? ''}"`)}</td>
<td>${input('frontLower', `value="${data.frontLower ?? ''}"`)}</td>
<td>${calcInput('frontD')}</td>
<td><div class="dircell"><span class="dir-fixed">后</span><input class="dirBack" value="${data.dirBack ?? ''}" placeholder="尺号/说明"></div></td>
<td>${input('backBlack', `value="${data.backBlack ?? ''}"`)}</td>
<td>${input('backRed', `value="${data.backRed ?? ''}"`)}</td>
<td>${input('kBack', `value="${data.kBack ?? ''}" placeholder="K")}</td>
<td rowspan="3">${calcInput('hAvg')}</td>
<td rowspan="3"><input class="note" value="${data.note ?? ''}" oninput="recalc()"></td>`;
tr2.innerHTML = `
<td colspan="3" class="narrow">后视距自动</td>
<td colspan="3" class="narrow">前视距自动</td>
<td><div class="dircell"><span class="dir-fixed">前</span><input class="dirFront" value="${data.dirFront ?? ''}" placeholder="尺号/说明"></div></td>
<td>${input('frontBlack', `value="${data.frontBlack ?? ''}"`)}</td>
<td>${input('frontRed', `value="${data.frontRed ?? ''}"`)}</td>
<td>${input('kFront', `value="${data.kFront ?? ''}" placeholder="K")}</td>`;
tr3.innerHTML = `
<td colspan="2" class="narrow">视距差</td>
<td>${calcInput('diffD')}</td>
<td colspan="2" class="narrow">累计视距差</td>
<td>${calcInput('cumDiffD')}</td>
<td><div class="dircell"><span class="dir-fixed">后-前</span><input class="dirDiff" value="${data.dirDiff ?? ''}" placeholder="可写说明"></div></td>
<td>${calcInput('hBlack')}</td>
<td>${calcInput('hRed')}</td>
<td>${calcInput('checkDiff')}</td>`;
tbody.append(tr1,tr2,tr3);
recalc();
}
function addStations(k){ for(let i=0;i<k;i++) addStation(); }
function stationGroups(){
const rows = [...tbody.querySelectorAll('tr.station-start')];
return rows.map(start => {
const sid = start.dataset.sid;
const all = [...tbody.querySelectorAll(`tr[data-sid="${sid}"]`)];
return {sid, rows: all, r1: all[0], r2: all[1], r3: all[2]};
});
}
function recalc(){
let cumDiff = 0, sumBackD=0, sumFrontD=0, sumH=0, validH=0;
for (const g of stationGroups()){
const bu=n(q(g.r1,'.backUpper')?.value), bl=n(q(g.r1,'.backLower')?.value);
const fu=n(q(g.r1,'.frontUpper')?.value), fl=n(q(g.r1,'.frontLower')?.value);
const backD = (bu!==null && bl!==null) ? (bu-bl)*100 : null;
const frontD = (fu!==null && fl!==null) ? (fu-fl)*100 : null;
q(g.r1,'.backD').value = fmt(backD,2);
q(g.r1,'.frontD').value = fmt(frontD,2);
if(backD!==null) sumBackD += backD;
if(frontD!==null) sumFrontD += frontD;
const diffD = (backD!==null && frontD!==null) ? backD-frontD : null;
if(diffD!==null) cumDiff += diffD;
q(g.r3,'.diffD').value = fmt(diffD,2);
q(g.r3,'.cumDiffD').value = fmt(cumDiff,2);
const bb=n(q(g.r1,'.backBlack')?.value), br=n(q(g.r1,'.backRed')?.value), kb=n(q(g.r1,'.kBack')?.value);
const fb=n(q(g.r2,'.frontBlack')?.value), fr=n(q(g.r2,'.frontRed')?.value), kf=n(q(g.r2,'.kFront')?.value);
// 单尺校核:黑面读数 + K - 红面读数,K通常填mm;黑/红读数填m。
const backCheck = (bb!==null && br!==null && kb!==null) ? (bb*1000 + kb - br*1000) : null;
const frontCheck = (fb!==null && fr!==null && kf!==null) ? (fb*1000 + kf - fr*1000) : null;
q(g.r1,'.kBack').classList.toggle('warn', backCheck!==null && Math.abs(backCheck)>3);
q(g.r2,'.kFront').classList.toggle('warn', frontCheck!==null && Math.abs(frontCheck)>3);
// 为节省版面,K列输入框仍保留K值;后-前行显示两尺校核差。
const hBlack = (bb!==null && fb!==null) ? bb - fb : null;
const hRed = (br!==null && fr!==null && kb!==null && kf!==null) ? (br - fr + (kf-kb)/1000) : null;
const hAvg = (hBlack!==null && hRed!==null) ? (hBlack + hRed)/2 : (hBlack!==null ? hBlack : null);
const checkDiff = (hBlack!==null && hRed!==null) ? (hBlack-hRed)*1000 : null;
q(g.r3,'.hBlack').value = fmt(hBlack,4);
q(g.r3,'.hRed').value = fmt(hRed,4);
q(g.r3,'.checkDiff').value = mm(checkDiff);
q(g.r1,'.hAvg').value = fmt(hAvg,4);
if(hAvg!==null){ sumH += hAvg; validH++; }
}
document.getElementById('sumStations').textContent = stationGroups().length;
document.getElementById('sumBackD').textContent = sumBackD.toFixed(2);
document.getElementById('sumFrontD').textContent = sumFrontD.toFixed(2);
document.getElementById('sumDiffD').textContent = (sumBackD-sumFrontD).toFixed(2);
document.getElementById('sumH').textContent = sumH.toFixed(4);
saveLocal();
}
function collect(){
return {
observer: document.getElementById('observer').value,
recorder: document.getElementById('recorder').value,
stations: stationGroups().map(g => ({
stationNo:q(g.r1,'.stationNo').value, turnPoint:q(g.r1,'.turnPoint').value,
backUpper:q(g.r1,'.backUpper').value, backLower:q(g.r1,'.backLower').value,
frontUpper:q(g.r1,'.frontUpper').value, frontLower:q(g.r1,'.frontLower').value,
dirBack:q(g.r1,'.dirBack').value, dirFront:q(g.r2,'.dirFront').value, dirDiff:q(g.r3,'.dirDiff').value,
backBlack:q(g.r1,'.backBlack').value, backRed:q(g.r1,'.backRed').value, kBack:q(g.r1,'.kBack').value,
frontBlack:q(g.r2,'.frontBlack').value, frontRed:q(g.r2,'.frontRed').value, kFront:q(g.r2,'.kFront').value,
note:q(g.r1,'.note').value
}))
};
}
function load(data){
tbody.innerHTML=''; stationCount=0;
document.getElementById('observer').value = data.observer ?? '';
document.getElementById('recorder').value = data.recorder ?? '';
(data.stations && data.stations.length ? data.stations : Array.from({length:8},()=>({}))).forEach(addStation);
recalc();
}
function download(name, text, type){
const a = document.createElement('a');
a.href = URL.createObjectURL(new Blob([text],{type})); a.download = name; a.click(); URL.revokeObjectURL(a.href);
}
function exportJSON(){ download('四等水准测量记录表.json', JSON.stringify(collect(),null,2), 'application/json;charset=utf-8'); }
function exportCSV(){
const rows = [['测站编号','转点','后上丝','后下丝','后视距','前上丝','前下丝','前视距','后尺号说明','前尺号说明','后-前说明','后黑','后红','后K','前黑','前红','前K','黑面高差','红面高差','高差中数','备注']];
for(const g of stationGroups()) rows.push([
q(g.r1,'.stationNo').value,q(g.r1,'.turnPoint').value,q(g.r1,'.backUpper').value,q(g.r1,'.backLower').value,q(g.r1,'.backD').value,
q(g.r1,'.frontUpper').value,q(g.r1,'.frontLower').value,q(g.r1,'.frontD').value,q(g.r1,'.dirBack').value,q(g.r2,'.dirFront').value,q(g.r3,'.dirDiff').value,
q(g.r1,'.backBlack').value,q(g.r1,'.backRed').value,q(g.r1,'.kBack').value,q(g.r2,'.frontBlack').value,q(g.r2,'.frontRed').value,q(g.r2,'.kFront').value,
q(g.r3,'.hBlack').value,q(g.r3,'.hRed').value,q(g.r1,'.hAvg').value,q(g.r1,'.note').value
]);
const csv = rows.map(r => r.map(v => '"'+String(v??'').replaceAll('"','""')+'"').join(',')).join('\n');
download('四等水准测量记录表.csv', '\ufeff'+csv, 'text/csv;charset=utf-8');
}
async function importFile(e){
const file = e.target.files[0]; if(!file) return;
const text = await file.text();
try{
if(file.name.toLowerCase().endsWith('.json')) load(JSON.parse(text));
else importCSV(text);
}catch(err){ alert('导入失败:' + err.message); }
e.target.value='';
}
function parseCSVLine(line){
const out=[]; let cur='', inQ=false;
for(let i=0;i<line.length;i++){
const c=line[i];
if(c==='"' && line[i+1]==='"'){cur+='"'; i++;}
else if(c==='"') inQ=!inQ;
else if(c===',' && !inQ){out.push(cur); cur='';}
else cur+=c;
}
out.push(cur); return out;
}
function importCSV(text){
const lines = text.replace(/^\ufeff/,'').split(/\r?\n/).filter(Boolean);
const data={observer:'',recorder:'',stations:[]};
for(const line of lines.slice(1)){
const r=parseCSVLine(line);
data.stations.push({stationNo:r[0],turnPoint:r[1],backUpper:r[2],backLower:r[3],frontUpper:r[5],frontLower:r[6],dirBack:r[8],dirFront:r[9],dirDiff:r[10],backBlack:r[11],backRed:r[12],kBack:r[13],frontBlack:r[14],frontRed:r[15],kFront:r[16],note:r[20]});
}
load(data);
}
function saveLocal(){ localStorage.setItem('fourth_leveling_record', JSON.stringify(collect())); }
function loadLocal(){
const raw = localStorage.getItem('fourth_leveling_record');
if(raw){ try{ load(JSON.parse(raw)); return; }catch{} }
load({stations:Array.from({length:8},()=>({}))});
}
function clearAll(){ if(confirm('确定清空当前表格吗?')){ localStorage.removeItem('fourth_leveling_record'); load({stations:Array.from({length:8},()=>({}))}); } }
document.getElementById('observer').addEventListener('input', saveLocal);
document.getElementById('recorder').addEventListener('input', saveLocal);
loadLocal();
</script>
</body>
</html>