Files
HIS-GUI/demo/index.html
2026-03-30 10:48:18 +08:00

541 lines
24 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.
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: var(--font-mono, monospace); }
.his-shell {
background: var(--color-background-primary);
border: 0.5px solid var(--color-border-tertiary);
border-radius: var(--border-radius-lg);
overflow: hidden;
font-size: 13px;
}
.his-titlebar {
background: var(--color-background-secondary);
border-bottom: 0.5px solid var(--color-border-tertiary);
padding: 8px 16px;
display: flex;
align-items: center;
gap: 12px;
}
.dot { width: 10px; height: 10px; border-radius: 50%; }
.dot-r { background: #E24B4A; }
.dot-y { background: #EF9F27; }
.dot-g { background: #639922; }
.his-title { font-size: 12px; color: var(--color-text-secondary); margin-left: 4px; }
.his-nav {
display: flex;
gap: 0;
border-bottom: 0.5px solid var(--color-border-tertiary);
background: var(--color-background-secondary);
}
.nav-tab {
padding: 8px 16px;
font-size: 12px;
cursor: pointer;
color: var(--color-text-secondary);
border-bottom: 2px solid transparent;
transition: all 0.15s;
font-family: var(--font-sans);
background: none;
border-top: none;
border-left: none;
border-right: none;
}
.nav-tab:hover { color: var(--color-text-primary); background: var(--color-background-primary); }
.nav-tab.active {
color: var(--color-text-primary);
border-bottom: 2px solid var(--color-text-primary);
background: var(--color-background-primary);
}
.his-body { display: flex; height: 420px; }
.his-sidebar {
width: 180px;
border-right: 0.5px solid var(--color-border-tertiary);
overflow-y: auto;
background: var(--color-background-secondary);
padding: 8px 0;
flex-shrink: 0;
}
.sidebar-section {
padding: 4px 12px 2px;
font-size: 10px;
color: var(--color-text-tertiary);
letter-spacing: 0.06em;
font-family: var(--font-sans);
text-transform: uppercase;
margin-top: 6px;
}
.sidebar-item {
padding: 5px 14px;
font-size: 12px;
cursor: pointer;
color: var(--color-text-secondary);
display: flex;
align-items: center;
gap: 8px;
transition: all 0.1s;
font-family: var(--font-sans);
border-radius: 0;
}
.sidebar-item:hover { background: var(--color-background-primary); color: var(--color-text-primary); }
.sidebar-item.active { background: var(--color-background-primary); color: var(--color-text-primary); font-weight: 500; }
.sidebar-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.his-main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
.his-terminal {
flex: 1;
overflow-y: auto;
padding: 12px 16px;
background: var(--color-background-primary);
font-family: var(--font-mono, monospace);
font-size: 12.5px;
line-height: 1.65;
}
.line-out { color: var(--color-text-primary); }
.line-err { color: var(--color-text-danger); }
.line-ok { color: var(--color-text-success); }
.line-dim { color: var(--color-text-tertiary); }
.line-hdr { color: var(--color-text-info); font-weight: 500; }
.line-prompt { color: var(--color-text-secondary); }
.line-cmd { color: var(--color-text-primary); }
.his-input-row {
display: flex;
align-items: center;
padding: 8px 12px;
border-top: 0.5px solid var(--color-border-tertiary);
gap: 8px;
background: var(--color-background-secondary);
}
.his-prompt-label { color: var(--color-text-tertiary); font-size: 12px; white-space: nowrap; }
.his-input {
flex: 1;
background: none;
border: none;
outline: none;
font-size: 12.5px;
font-family: var(--font-mono, monospace);
color: var(--color-text-primary);
}
.his-run {
font-size: 11px;
padding: 4px 10px;
border-radius: var(--border-radius-md);
background: none;
border: 0.5px solid var(--color-border-secondary);
color: var(--color-text-secondary);
cursor: pointer;
font-family: var(--font-sans);
}
.his-run:hover { background: var(--color-background-primary); color: var(--color-text-primary); }
.quick-btn-row {
display: flex;
flex-wrap: wrap;
gap: 5px;
padding: 8px 12px;
border-top: 0.5px solid var(--color-border-tertiary);
background: var(--color-background-secondary);
}
.qbtn {
font-size: 11px;
padding: 3px 8px;
border-radius: var(--border-radius-md);
background: none;
border: 0.5px solid var(--color-border-tertiary);
color: var(--color-text-secondary);
cursor: pointer;
font-family: var(--font-mono, monospace);
}
.qbtn:hover { background: var(--color-background-primary); color: var(--color-text-primary); border-color: var(--color-border-secondary); }
.stats-panel {
padding: 16px;
font-family: var(--font-sans);
overflow-y: auto;
flex: 1;
}
.stats-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 16px; }
.stat-card {
background: var(--color-background-secondary);
border-radius: var(--border-radius-md);
padding: 12px;
}
.stat-label { font-size: 11px; color: var(--color-text-secondary); margin-bottom: 4px; }
.stat-value { font-size: 22px; font-weight: 500; color: var(--color-text-primary); }
.stat-sub { font-size: 11px; color: var(--color-text-tertiary); margin-top: 2px; }
.table-wrap { border: 0.5px solid var(--color-border-tertiary); border-radius: var(--border-radius-md); overflow: hidden; }
.his-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.his-table th {
text-align: left;
padding: 7px 12px;
background: var(--color-background-secondary);
color: var(--color-text-secondary);
font-weight: 500;
border-bottom: 0.5px solid var(--color-border-tertiary);
}
.his-table td {
padding: 7px 12px;
border-bottom: 0.5px solid var(--color-border-tertiary);
color: var(--color-text-primary);
}
.his-table tr:last-child td { border-bottom: none; }
.his-table tr:hover td { background: var(--color-background-secondary); }
.badge {
display: inline-block;
font-size: 10px;
padding: 2px 7px;
border-radius: 99px;
}
.badge-ok { background: var(--color-background-success); color: var(--color-text-success); }
.badge-warn { background: var(--color-background-warning); color: var(--color-text-warning); }
.badge-err { background: var(--color-background-danger); color: var(--color-text-danger); }
.badge-info { background: var(--color-background-info); color: var(--color-text-info); }
.ward-panel { padding: 16px; font-family: var(--font-sans); overflow-y: auto; flex: 1; }
.ward-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; margin-bottom: 16px; }
.bed-cell {
border: 0.5px solid var(--color-border-tertiary);
border-radius: var(--border-radius-md);
padding: 8px;
text-align: center;
cursor: pointer;
transition: all 0.12s;
}
.bed-cell:hover { border-color: var(--color-border-secondary); }
.bed-cell.occupied { background: var(--color-background-info); border-color: var(--color-border-info); }
.bed-cell.empty { background: var(--color-background-secondary); }
.bed-num { font-size: 11px; font-weight: 500; color: var(--color-text-secondary); }
.bed-cell.occupied .bed-num { color: var(--color-text-info); }
.bed-name { font-size: 10px; color: var(--color-text-tertiary); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.section-label { font-size: 11px; color: var(--color-text-secondary); margin-bottom: 8px; font-weight: 500; }
.ward-row { display: flex; gap: 8px; margin-bottom: 8px; align-items: center; }
.ward-name { font-size: 12px; width: 80px; color: var(--color-text-secondary); }
.bed-mini-row { display: flex; gap: 4px; flex-wrap: wrap; }
.bed-mini { width: 16px; height: 16px; border-radius: 3px; border: 0.5px solid var(--color-border-tertiary); background: var(--color-background-secondary); }
.bed-mini.occ { background: var(--color-background-info); border-color: var(--color-border-info); }
</style>
<div class="his-shell">
<div class="his-titlebar">
<div class="dot dot-r"></div>
<div class="dot dot-y"></div>
<div class="dot dot-g"></div>
<span class="his-title">HIS — 轻量级医疗信息管理系统 v1.0 | 演示模式</span>
</div>
<div class="his-nav">
<button class="nav-tab active" onclick="switchTab('cli')">命令行</button>
<button class="nav-tab" onclick="switchTab('patients')">患者管理</button>
<button class="nav-tab" onclick="switchTab('wards')">病房管理</button>
<button class="nav-tab" onclick="switchTab('report')">统计报表</button>
</div>
<!-- CLI Tab -->
<div id="tab-cli" class="his-body">
<div class="his-sidebar">
<div class="sidebar-section">常用命令</div>
<div class="sidebar-item" onclick="fillCmd('help')"><div class="sidebar-dot" style="background:#639922"></div>help</div>
<div class="sidebar-item" onclick="fillCmd('list patient')"><div class="sidebar-dot" style="background:#378ADD"></div>list patient</div>
<div class="sidebar-item" onclick="fillCmd('find patient --name 张')"><div class="sidebar-dot" style="background:#378ADD"></div>find patient</div>
<div class="sidebar-item" onclick="fillCmd('add patient')"><div class="sidebar-dot" style="background:#1D9E75"></div>add patient</div>
<div class="sidebar-item" onclick="fillCmd('admit P001')"><div class="sidebar-dot" style="background:#EF9F27"></div>admit</div>
<div class="sidebar-item" onclick="fillCmd('discharge P003')"><div class="sidebar-dot" style="background:#E24B4A"></div>discharge</div>
<div class="sidebar-section">药房</div>
<div class="sidebar-item" onclick="fillCmd('dispense M001 --qty 3')"><div class="sidebar-dot" style="background:#7F77DD"></div>dispense</div>
<div class="sidebar-item" onclick="fillCmd('stock M001')"><div class="sidebar-dot" style="background:#7F77DD"></div>stock</div>
<div class="sidebar-section">报表</div>
<div class="sidebar-item" onclick="fillCmd('report doctor')"><div class="sidebar-dot" style="background:#D85A30"></div>report doctor</div>
<div class="sidebar-item" onclick="fillCmd('report revenue')"><div class="sidebar-dot" style="background:#D85A30"></div>report revenue</div>
</div>
<div class="his-main">
<div class="his-terminal" id="terminal"></div>
<div class="quick-btn-row">
<button class="qbtn" onclick="runCmd('help')">help</button>
<button class="qbtn" onclick="runCmd('list patient')">list patient</button>
<button class="qbtn" onclick="runCmd('find patient --name 张三')">find --name 张三</button>
<button class="qbtn" onclick="runCmd('report revenue')">report revenue</button>
<button class="qbtn" onclick="runCmd('admit P001')">admit P001</button>
<button class="qbtn" onclick="runCmd('dispense M001 --qty 99')">dispense 99测试异常</button>
</div>
<div class="his-input-row">
<span class="his-prompt-label">his &gt;</span>
<input class="his-input" id="cmd-input" placeholder="输入命令..." onkeydown="if(event.key==='Enter') runCmd()" />
<button class="his-run" onclick="runCmd()">执行 ↵</button>
</div>
</div>
</div>
<!-- Patients Tab -->
<div id="tab-patients" class="his-body" style="display:none;">
<div class="his-main">
<div class="stats-panel">
<div class="stats-grid">
<div class="stat-card"><div class="stat-label">门诊患者</div><div class="stat-value">87</div><div class="stat-sub">容量 100</div></div>
<div class="stat-card"><div class="stat-label">住院患者</div><div class="stat-value">23</div><div class="stat-sub">容量 30</div></div>
<div class="stat-card"><div class="stat-label">今日挂号</div><div class="stat-value">14</div><div class="stat-sub">较昨日 +3</div></div>
</div>
<div class="table-wrap">
<table class="his-table">
<thead><tr><th>患者ID</th><th>姓名</th><th>年龄</th><th>科室</th><th>状态</th><th>主治医生</th></tr></thead>
<tbody id="patient-tbody"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Wards Tab -->
<div id="tab-wards" class="his-body" style="display:none;">
<div class="his-main">
<div class="ward-panel">
<div class="stats-grid" style="grid-template-columns:repeat(3,1fr);margin-bottom:16px;">
<div class="stat-card"><div class="stat-label">总床位</div><div class="stat-value">60</div><div class="stat-sub">3个病房</div></div>
<div class="stat-card"><div class="stat-label">已占用</div><div class="stat-value">23</div><div class="stat-sub">使用率 38%</div></div>
<div class="stat-card"><div class="stat-label">空余</div><div class="stat-value">37</div><div class="stat-sub">可立即入住</div></div>
</div>
<div class="section-label">内科病房20床</div>
<div class="bed-mini-row" id="ward-nei" style="margin-bottom:14px;"></div>
<div class="section-label">外科病房20床</div>
<div class="bed-mini-row" id="ward-wai" style="margin-bottom:14px;"></div>
<div class="section-label">特护病房20床</div>
<div class="bed-mini-row" id="ward-te" style="margin-bottom:14px;"></div>
<div style="display:flex;align-items:center;gap:12px;font-size:11px;color:var(--color-text-secondary);margin-top:8px;">
<div style="display:flex;align-items:center;gap:4px;"><div class="bed-mini occ"></div>已占用</div>
<div style="display:flex;align-items:center;gap:4px;"><div class="bed-mini"></div>空余</div>
</div>
</div>
</div>
</div>
<!-- Report Tab -->
<div id="tab-report" class="his-body" style="display:none;">
<div class="his-main">
<div class="stats-panel">
<div class="stats-grid" style="grid-template-columns:repeat(2,1fr);margin-bottom:16px;">
<div class="stat-card"><div class="stat-label">本月营业额</div><div class="stat-value">¥128,400</div><div class="stat-sub">检查+药品+住院</div></div>
<div class="stat-card"><div class="stat-label">药品出库</div><div class="stat-value">312</div><div class="stat-sub">本月处方次数</div></div>
</div>
<div class="section-label" style="margin-bottom:8px;">医生工作量排行</div>
<div class="table-wrap" style="margin-bottom:14px;">
<table class="his-table">
<thead><tr><th>医生</th><th>科室</th><th>职称</th><th>本月诊疗</th><th>住院管理</th></tr></thead>
<tbody>
<tr><td>李建国</td><td>内科</td><td>主任医师</td><td>89</td><td>12</td></tr>
<tr><td>王秀梅</td><td>外科</td><td>副主任医师</td><td>74</td><td>8</td></tr>
<tr><td>张伟</td><td>内科</td><td>主治医师</td><td>67</td><td>5</td></tr>
<tr><td>刘芳</td><td>儿科</td><td>主治医师</td><td>61</td><td>3</td></tr>
</tbody>
</table>
</div>
<div class="section-label" style="margin-bottom:8px;">药品库存预警</div>
<div class="table-wrap">
<table class="his-table">
<thead><tr><th>药品ID</th><th>通用名</th><th>商品名</th><th>库存</th><th>状态</th></tr></thead>
<tbody>
<tr><td>M003</td><td>阿莫西林</td><td>阿莫仙</td><td>8</td><td><span class="badge badge-err">库存紧张</span></td></tr>
<tr><td>M007</td><td>布洛芬</td><td>芬必得</td><td>31</td><td><span class="badge badge-ok">正常</span></td></tr>
<tr><td>M011</td><td>氯化钠注射液</td><td>生理盐水</td><td>15</td><td><span class="badge badge-warn">偏低</span></td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script>
const patients = [
{id:'P001',name:'张三',age:34,dept:'内科',status:'门诊',doctor:'李建国'},
{id:'P002',name:'张三',age:52,dept:'外科',status:'住院',doctor:'王秀梅'},
{id:'P003',name:'李梅',age:28,dept:'儿科',status:'门诊',doctor:'刘芳'},
{id:'P004',name:'王建',age:67,dept:'内科',status:'住院',doctor:'李建国'},
{id:'P005',name:'陈晓燕',age:41,dept:'外科',status:'已出院',doctor:'王秀梅'},
{id:'P006',name:'赵磊',age:19,dept:'骨科',status:'门诊',doctor:'张伟'},
];
function statusBadge(s){
if(s==='住院') return `<span class="badge badge-info">${s}</span>`;
if(s==='门诊') return `<span class="badge badge-ok">${s}</span>`;
return `<span class="badge badge-warn">${s}</span>`;
}
function renderPatients(){
const tb = document.getElementById('patient-tbody');
tb.innerHTML = patients.map(p=>`<tr><td>${p.id}</td><td>${p.name}</td><td>${p.age}</td><td>${p.dept}</td><td>${statusBadge(p.status)}</td><td>${p.doctor}</td></tr>`).join('');
}
function renderWards(){
const occ = [1,2,4,6,8,9,11,14,15,16,17,19,3,5,7,10,12,13,18,20,22,24,27];
['nei','wai','te'].forEach((id,wi)=>{
const el = document.getElementById('ward-'+id);
el.innerHTML = Array.from({length:20},(_,i)=>{
const n = wi*20+i+1;
return `<div class="bed-mini ${occ.includes(n)?'occ':''}"></div>`;
}).join('');
});
}
const cmdHistory = [];
let histIdx = -1;
const termOutput = [
{cls:'line-hdr', text:'HIS 医疗管理系统 v1.0 [演示模式]'},
{cls:'line-dim', text:'加载数据: patients(87) doctors(20) wards(3) medicines(24)'},
{cls:'line-dim', text:'输入 help 查看可用命令。'},
{cls:'line-out', text:''},
];
function renderTerminal(){
const t = document.getElementById('terminal');
t.innerHTML = termOutput.map(l=>`<div class="${l.cls}">${l.text}</div>`).join('');
t.scrollTop = t.scrollHeight;
}
function append(cls, text){ termOutput.push({cls,text}); renderTerminal(); }
function fillCmd(cmd){ document.getElementById('cmd-input').value = cmd; document.getElementById('cmd-input').focus(); }
function runCmd(preset){
const input = document.getElementById('cmd-input');
const cmd = (preset !== undefined ? preset : input.value).trim();
if(!cmd) return;
if(!preset) input.value = '';
cmdHistory.unshift(cmd); histIdx = -1;
append('line-prompt', `his > ${cmd}`);
const parts = cmd.split(/\s+/);
const c = parts[0], sub = parts[1];
if(c === 'help'){
append('line-hdr', '可用命令:');
[
' list patient|doctor|medicine|ward',
' find patient --name <姓名> | --id <ID>',
' add patient|doctor|medicine',
' admit <PatientID> — 患者住院分配床位',
' discharge <PatientID> — 患者出院',
' dispense <MedID> --qty <数量>',
' stock <MedID> — 查询药品库存',
' report doctor|revenue|ward',
' help | exit',
].forEach(l=>append('line-out', l));
} else if(c==='list' && sub==='patient'){
append('line-hdr', 'PatientID 姓名 年龄 科室 状态');
append('line-dim', '─────────────────────────────────────────');
patients.forEach(p=>{
const st = p.status==='住院'?'\x1b[34m住院\x1b[0m':p.status;
append('line-out', `${p.id.padEnd(10)} ${p.name.padEnd(10)} ${String(p.age).padEnd(5)} ${p.dept.padEnd(7)} ${p.status}`);
});
append('line-ok', `${patients.length} 条记录(演示数据)`);
} else if(c==='find' && sub==='patient'){
const nm = (cmd.match(/--name\s+(\S+)/)||[])[1];
const id = (cmd.match(/--id\s+(\S+)/)||[])[1];
const res = patients.filter(p=>(nm && p.name.includes(nm))||(id && p.id===id));
if(!nm && !id){ append('line-err','[ERR] 需要 --name 或 --id 参数'); return; }
if(res.length===0){ append('line-err','[NOT FOUND] 无匹配患者'); return; }
append('line-hdr', `找到 ${res.length} 名患者:`);
res.forEach(p=>append('line-ok', ` ${p.id} ${p.name} ${p.age}${p.dept} ${p.status} 主治:${p.doctor}`));
} else if(c==='admit'){
const pid = parts[1];
if(!pid){ append('line-err','[ERR] 用法: admit <PatientID>'); return; }
const p = patients.find(x=>x.id===pid);
if(!p){ append('line-err',`[ERR] 患者 ${pid} 不存在`); return; }
if(p.status==='住院'){ append('line-err',`[ERR] 患者 ${p.name} 当前已在住院`); return; }
append('line-ok', `[OK] 正在为患者 ${p.name}(${pid}) 分配床位...`);
append('line-ok', `[OK] 分配成功: 内科病房 床位 A-07`);
p.status='住院';
} else if(c==='discharge'){
const pid = parts[1];
if(!pid){ append('line-err','[ERR] 用法: discharge <PatientID>'); return; }
const p = patients.find(x=>x.id===pid);
if(!p){ append('line-err',`[ERR] 患者 ${pid} 不存在`); return; }
if(p.status!=='住院'){ append('line-err',`[ERR] 患者 ${p.name} 当前未住院`); return; }
p.status='已出院';
append('line-ok', `[OK] 患者 ${p.name}(${pid}) 已办理出院,床位已释放`);
} else if(c==='dispense'){
const mid = parts[1];
const qty = parseInt((cmd.match(/--qty\s+(\d+)/)||[])[1]||'0');
if(!mid||!qty){ append('line-err','[ERR] 用法: dispense <MedID> --qty <数量>'); return; }
if(qty > 50){ append('line-err',`[ERR] 库存不足。当前库存: 12申请数量: ${qty},拒绝发药`); return; }
if(qty <= 0){ append('line-err','[ERR] 数量必须为正整数'); return; }
append('line-ok', `[OK] 发药成功: ${mid} × ${qty},剩余库存: ${12-qty}`);
} else if(c==='stock'){
const mid = parts[1];
if(!mid){ append('line-err','[ERR] 用法: stock <MedID>'); return; }
append('line-out', `药品ID: ${mid}`);
append('line-out', '通用名: 阿莫西林 | 商品名: 阿莫仙 | 别名: AMX');
append('line-out', '当前库存: 12 | 单价: ¥3.50 | 关联科室: 内科, 儿科');
append('line-warn'?'line-err':'line-err', '[WARN] 库存低于警戒线(20),建议补货');
} else if(c==='report'){
if(sub==='doctor'){
append('line-hdr','医生工作量报表');
append('line-dim', '────────────────────────────────');
[['李建国','内科','主任','89次诊疗 12住院'],['王秀梅','外科','副主任','74次诊疗 8住院'],
['张伟','内科','主治','67次诊疗 5住院'],['刘芳','儿科','主治','61次诊疗 3住院']]
.forEach(r=>append('line-out',` ${r[0].padEnd(6)} ${r[1].padEnd(5)} ${r[2].padEnd(5)} ${r[3]}`));
} else if(sub==='revenue'){
append('line-hdr','营业额报表(本月)');
append('line-out',' 检查费用: ¥48,200');
append('line-out',' 药品收入: ¥31,600');
append('line-out',' 住院费用: ¥48,600');
append('line-dim',' ─────────────────');
append('line-ok', ' 合计: ¥128,400');
} else if(sub==='ward'){
append('line-hdr','床位使用率报表');
append('line-out',' 内科病房: 14/20 (70%)');
append('line-out',' 外科病房: 7/20 (35%)');
append('line-out',' 特护病房: 2/20 (10%)');
append('line-ok', ' 整体使用率: 38%');
} else {
append('line-err','[ERR] 未知报表类型。可用: doctor | revenue | ward');
}
} else if(c==='add'){
append('line-ok','[交互模式] 演示中省略输入步骤。实际系统将逐字段提示输入并校验。');
} else if(c==='exit'){
append('line-dim','[保存数据到文件...] patients.txt doctors.txt records.txt');
append('line-ok', '再见!');
} else if(cmd===''){
// nothing
} else {
append('line-err', `[ERR] 未知命令: ${c}。输入 help 查看帮助。`);
}
}
function switchTab(tab){
document.querySelectorAll('[id^="tab-"]').forEach(el=>el.style.display='none');
document.querySelectorAll('.nav-tab').forEach(el=>el.classList.remove('active'));
document.getElementById('tab-'+tab).style.display='flex';
const tabs = ['cli','patients','wards','report'];
document.querySelectorAll('.nav-tab')[tabs.indexOf(tab)].classList.add('active');
if(tab==='patients') renderPatients();
if(tab==='wards') renderWards();
}
document.getElementById('cmd-input').addEventListener('keydown', e=>{
if(e.key==='ArrowUp'){ histIdx = Math.min(histIdx+1, cmdHistory.length-1); e.target.value = cmdHistory[histIdx]||''; e.preventDefault(); }
if(e.key==='ArrowDown'){ histIdx = Math.max(histIdx-1, -1); e.target.value = histIdx>=0 ? cmdHistory[histIdx] : ''; e.preventDefault(); }
});
renderTerminal();
</script>