303 lines
16 KiB
HTML
303 lines
16 KiB
HTML
<!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>h(m)</th>
|
||
<th rowspan="3">备注</th>
|
||
</tr>
|
||
<tr>
|
||
<th>上丝</th><th>下丝</th><th>视距d(m)</th>
|
||
<th>上丝</th><th>下丝</th><th>视距d(m)</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>
|