| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>工位配置 - 恒达视觉监控平台</title>
- <link rel="icon" href="/logo.png" type="image/png">
- <style>
- *{margin:0;padding:0;box-sizing:border-box;border-radius:0 !important}
- body{font-family:system-ui,sans-serif;background:#12192e;color:#eee;overflow:hidden}
- .app{display:flex;height:100vh}
- .sidebar{width:200px;background:#0e1426;border-right:1px solid #0f3460;display:flex;flex-direction:column;flex-shrink:0}
- .logo{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid #0f3460}
- .logo img{height:28px;flex-shrink:0}
- .logo .t{font-size:14px;color:#e94560;font-weight:bold;white-space:nowrap}
- .nav{padding-top:8px;flex:1}
- .nav-item{display:flex;align-items:center;gap:12px;padding:13px 18px;color:#8892a8;cursor:pointer;font-size:14px;border-left:3px solid transparent;white-space:nowrap;user-select:none}
- .nav-item:hover{background:#182242;color:#ccc}
- .nav-item.active{color:#e94560;border-left-color:#e94560;background:#182242;font-weight:bold}
- .nav-item .ico{width:20px;text-align:center;flex-shrink:0;font-weight:bold}
- .nav-label{transition:none}
- .side-foot{border-top:1px solid #0f3460;padding:12px 14px;display:flex;flex-direction:column;gap:10px}
- .collapse-btn{background:none;border:1px solid #0f3460;color:#8892a8;padding:5px;cursor:pointer;font-size:12px;width:100%}
- .collapse-btn:hover{color:#e94560;border-color:#e94560}
- .content{flex:1;overflow:auto;padding:16px}
- .card{background:#141d36;border:1px solid #0f3460;padding:16px;margin-bottom:16px}
- .card h3{color:#e94560;margin-bottom:12px;font-size:15px}
- .form-group{margin-bottom:12px}
- .form-group label{display:block;font-size:13px;color:#aaa;margin-bottom:4px}
- .form-group input,.form-group select{width:100%;padding:8px;border:1px solid #0f3460;background:#0e1426;color:#eee;font-size:13px}
- .form-row{display:flex;gap:12px}
- .form-row .form-group{flex:1}
- .btn{padding:8px 16px;border:none;cursor:pointer;font-size:13px;font-weight:bold}
- .btn-primary{background:#e94560;color:#fff}
- .btn-primary:hover{background:#c0392b}
- .btn-success{background:#27ae60;color:#fff}
- .btn-danger{background:#e74c3c;color:#fff}
- .btn-outline{background:transparent;border:1px solid #555;color:#aaa}
- .btn-outline:hover{border-color:#e94560;color:#e94560}
- .btn-row{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap}
- .bucket-item{background:#0e1426;border:1px solid #0f3460;padding:12px;margin-bottom:10px}
- .bucket-item .header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
- .bucket-item .title{font-weight:bold;color:#3498db;font-size:14px}
- .bucket-item .actions{display:flex;gap:6px}
- .bucket-item .actions button{padding:4px 10px;font-size:12px}
- .bucket-item .fields{display:grid;grid-template-columns:1fr 1fr;gap:8px;font-size:12px;color:#888}
- .bucket-item .fields span{word-break:break-all}
- .edit-form{background:#182242;border:1px solid #3498db;padding:12px;margin-top:10px}
- .edit-form .form-group{margin-bottom:8px}
- body.rail .sidebar{width:60px}
- body.rail .logo .t,body.rail .nav-label,body.rail .side-foot .nav-label{display:none}
- </style>
- </head>
- <body>
- <div class="app">
- <aside class="sidebar" id="sidebar">
- <div class="logo"><img src="/logo.png" alt=""><span class="t">恒达视觉监控平台</span></div>
- <nav class="nav">
- <a class="nav-item" href="/" style="text-decoration:none"><span class="ico">监</span><span class="nav-label">实时监控</span></a>
- <a class="nav-item" href="/#fence" style="text-decoration:none"><span class="ico">围</span><span class="nav-label">电子围栏</span></a>
- <a class="nav-item" href="/#alarm" style="text-decoration:none"><span class="ico">警</span><span class="nav-label">报警历史</span></a>
- <a class="nav-item" href="/#action" style="text-decoration:none"><span class="ico">动</span><span class="nav-label">动作记录</span></a>
- <a class="nav-item" href="/#relay" style="text-decoration:none"><span class="ico">继</span><span class="nav-label">继电器控制</span></a>
- <a class="nav-item" href="/#channel" style="text-decoration:none"><span class="ico">通</span><span class="nav-label">通道管理</span></a>
- <a class="nav-item" href="/#system" style="text-decoration:none"><span class="ico">系</span><span class="nav-label">系统设置</span></a>
- <div style="border-top:1px solid #0f3460;margin:8px 12px"></div>
- <a class="nav-item" href="/preview.html" style="text-decoration:none"><span class="ico">工</span><span class="nav-label">工位预览</span></a>
- <a class="nav-item active" href="/config.html" style="text-decoration:none"><span class="ico">设</span><span class="nav-label">工位配置</span></a>
- </nav>
- <div class="side-foot">
- <button class="collapse-btn" onclick="document.body.classList.toggle('rail')">« 收起菜单</button>
- </div>
- </aside>
- <main class="content">
- <div class="card">
- <h3>通讯配置</h3>
- <div class="form-row">
- <div class="form-group">
- <label>模块类型</label>
- <select id="moduleType" onchange="onModuleTypeChange()">
- <option value="RFID">RFID读卡器</option>
- <option value="Modbus">Modbus TCP (HD3200)</option>
- </select>
- </div>
- <div class="form-group">
- <label>空闲超时(秒)</label>
- <input type="number" id="idleTimeout" min="1" max="300" value="5">
- </div>
- </div>
- <div id="rfidConfig">
- <div class="form-row">
- <div class="form-group"><label>RFID IP</label><input type="text" id="rfidIp" placeholder="192.168.100.113"></div>
- <div class="form-group"><label>端口</label><input type="number" id="rfidPort" min="1" max="65535" value="60000"></div>
- </div>
- <div class="form-row">
- <div class="form-group"><label>稳定时间(秒)</label><input type="number" id="rfidStabilityTime" min="1" max="60" value="5"></div>
- <div class="form-group"><label>稳定次数</label><input type="number" id="rfidStabilityCount" min="1" max="10" value="3"></div>
- </div>
- </div>
- <div id="modbusConfig" style="display:none">
- <div class="form-row">
- <div class="form-group"><label>Modbus IP</label><input type="text" id="modbusIp" placeholder="192.168.1.200"></div>
- <div class="form-group"><label>端口</label><input type="number" id="modbusPort" min="1" max="65535" value="502"></div>
- </div>
- <div class="form-row">
- <div class="form-group"><label>轮询间隔(ms)</label><input type="number" id="modbusFrequency" min="50" max="5000" value="100"></div>
- <div class="form-group"><label>稳定次数</label><input type="number" id="modbusStabilityCount" min="1" max="10" value="3"></div>
- </div>
- </div>
- <div class="btn-row">
- <button class="btn btn-primary" onclick="saveModuleConfig()">保存通讯配置</button>
- </div>
- </div>
- <div class="card">
- <h3>工位管理</h3>
- <div id="bucketList"></div>
- <div class="btn-row">
- <button class="btn btn-primary" onclick="addBucket()">+ 添加工位</button>
- </div>
- </div>
- </main>
- </div>
- <script>
- let buckets = [];
- async function loadConfig() {
- try {
- const [modRes, bucketRes] = await Promise.all([
- fetch('/api/module/config'),
- fetch('/api/buckets')
- ]);
- const modData = await modRes.json();
- const bucketData = await bucketRes.json();
- document.getElementById('moduleType').value = modData.module_type || 'RFID';
- document.getElementById('idleTimeout').value = modData.idle_timeout || 5;
- onModuleTypeChange();
- if (modData.rfid) {
- document.getElementById('rfidIp').value = modData.rfid.ip || '';
- document.getElementById('rfidPort').value = modData.rfid.port || 60000;
- document.getElementById('rfidStabilityTime').value = modData.rfid.stability_time || 5;
- document.getElementById('rfidStabilityCount').value = modData.rfid.stability_count || 3;
- }
- if (modData.modbus) {
- document.getElementById('modbusIp').value = modData.modbus.ip || '';
- document.getElementById('modbusPort').value = modData.modbus.port || 502;
- document.getElementById('modbusFrequency').value = modData.modbus.frequency || 100;
- document.getElementById('modbusStabilityCount').value = modData.modbus.stability_count || 3;
- }
- buckets = bucketData.buckets || [];
- renderBuckets();
- } catch (e) { console.error('加载配置失败:', e); }
- }
- function onModuleTypeChange() {
- const type = document.getElementById('moduleType').value;
- document.getElementById('rfidConfig').style.display = type === 'RFID' ? 'block' : 'none';
- document.getElementById('modbusConfig').style.display = type === 'Modbus' ? 'block' : 'none';
- }
- function renderBuckets() {
- const el = document.getElementById('bucketList');
- if (buckets.length === 0) {
- el.innerHTML = '<div style="color:#888;font-size:13px;padding:20px;text-align:center">暂无工位,请添加</div>';
- return;
- }
- el.innerHTML = buckets.map(b => `
- <div class="bucket-item" id="bucket-${b.id}">
- <div class="header">
- <span class="title">${b.description}</span>
- <div class="actions">
- <button class="btn btn-outline" onclick="editBucket(${b.id})">编辑</button>
- <button class="btn btn-danger" onclick="deleteBucket(${b.id})">删除</button>
- </div>
- </div>
- <div class="fields">
- <div><label>标识:</label> <span>${b.code_x}</span></div>
- <div><label>垂直工位:</label> <span>${b.code_y || '无'}</span></div>
- <div><label>左摄像头:</label> <span>${b.left_camera ? b.left_camera.url : '未配置'}</span></div>
- <div><label>右摄像头:</label> <span>${b.right_camera ? b.right_camera.url : '未配置'}</span></div>
- </div>
- <div id="edit-${b.id}"></div>
- </div>
- `).join('');
- }
- function addBucket() {
- const maxId = buckets.reduce((max, b) => Math.max(max, b.id), 0);
- const newBucket = {
- id: maxId + 1,
- description: `${maxId + 1}号工位`,
- code_x: String(maxId + 1).padStart(8, '0'),
- code_y: '',
- left_camera: { url: '', topic: '' },
- right_camera: { url: '', topic: '' }
- };
- buckets.push(newBucket);
- renderBuckets();
- editBucket(newBucket.id);
- }
- function editBucket(id) {
- const b = buckets.find(x => x.id === id);
- if (!b) return;
- document.getElementById(`edit-${id}`).innerHTML = `
- <div class="edit-form">
- <div class="form-row">
- <div class="form-group"><label>工位名称</label><input type="text" id="e-desc-${id}" value="${b.description}"></div>
- <div class="form-group"><label>标识(卡号/点号)</label><input type="text" id="e-codex-${id}" value="${b.code_x}"></div>
- </div>
- <div class="form-group"><label>垂直工位标识(可选)</label><input type="text" id="e-codey-${id}" value="${b.code_y || ''}"></div>
- <div class="form-group"><label>左摄像头RTSP</label><input type="text" id="e-left-${id}" value="${b.left_camera ? b.left_camera.url : ''}" placeholder="rtsp://user:pass@ip:554/stream"></div>
- <div class="form-group"><label>右摄像头RTSP</label><input type="text" id="e-right-${id}" value="${b.right_camera ? b.right_camera.url : ''}" placeholder="rtsp://user:pass@ip:554/stream"></div>
- <div class="btn-row">
- <button class="btn btn-success" onclick="saveBucket(${id})">保存</button>
- <button class="btn btn-outline" onclick="cancelEdit(${id})">取消</button>
- </div>
- </div>
- `;
- }
- function cancelEdit(id) { document.getElementById(`edit-${id}`).innerHTML = ''; }
- async function saveBucket(id) {
- const b = buckets.find(x => x.id === id);
- if (!b) return;
- b.description = document.getElementById(`e-desc-${id}`).value;
- b.code_x = document.getElementById(`e-codex-${id}`).value;
- b.code_y = document.getElementById(`e-codey-${id}`).value;
- b.left_camera = { url: document.getElementById(`e-left-${id}`).value, topic: '' };
- b.right_camera = { url: document.getElementById(`e-right-${id}`).value, topic: '' };
- try {
- await fetch('/api/buckets', {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({ buckets })
- });
- renderBuckets();
- alert('保存成功');
- } catch (e) { alert('保存失败: ' + e.message); }
- }
- async function deleteBucket(id) {
- if (!confirm('确定删除此工位?')) return;
- buckets = buckets.filter(b => b.id !== id);
- try {
- await fetch('/api/buckets', {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify({ buckets })
- });
- renderBuckets();
- } catch (e) { alert('删除失败: ' + e.message); }
- }
- async function saveModuleConfig() {
- const moduleType = document.getElementById('moduleType').value;
- const idleTimeout = parseInt(document.getElementById('idleTimeout').value) || 5;
- const config = { module_type: moduleType, idle_timeout: idleTimeout };
- if (moduleType === 'RFID') {
- config.rfid = {
- ip: document.getElementById('rfidIp').value,
- port: parseInt(document.getElementById('rfidPort').value) || 60000,
- stability_time: parseInt(document.getElementById('rfidStabilityTime').value) || 5,
- stability_count: parseInt(document.getElementById('rfidStabilityCount').value) || 3
- };
- } else {
- config.modbus = {
- ip: document.getElementById('modbusIp').value,
- port: parseInt(document.getElementById('modbusPort').value) || 502,
- frequency: parseInt(document.getElementById('modbusFrequency').value) || 100,
- stability_count: parseInt(document.getElementById('modbusStabilityCount').value) || 3
- };
- }
- try {
- await fetch('/api/module/config', {
- method: 'POST',
- headers: {'Content-Type': 'application/json'},
- body: JSON.stringify(config)
- });
- alert('通讯配置已保存');
- } catch (e) { alert('保存失败: ' + e.message); }
- }
- loadConfig();
- </script>
- </body>
- </html>
|