<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>江苏省OPO可视化调度平台 - 协调员管理 V6</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* ==================== 1. 设计系统定义 (V6) ==================== */
:root {
--primary-blue: #007aff;
--tech-cyan: #00f0ff;
--tech-orange: #ff9f0a;
--tech-bg: #050b16;
--panel-bg: rgba(16, 36, 68, 0.95);
--border-color: rgba(64, 116, 180, 0.4);
--text-main: #ffffff;
--text-sub: #8fb4d9;
/* 状态色 */
--st-online: #30d158;
--st-task: #ff9f0a;
--st-offline: #8e8e93;
}
body {
margin: 0; padding: 0;
background-color: var(--tech-bg);
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
color: var(--text-main);
height: 100vh; width: 100vw;
overflow: hidden;
display: flex;
}
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 2px; }
::-webkit-scrollbar-track { background: transparent; }
/* ==================== 2. 地图层 ==================== */
.map-viewport {
flex: 1; position: relative;
background-image: radial-gradient(circle at 50% 50%, #112035 0%, #02050a 100%),
url('https://api.mapbox.com/styles/v1/mapbox/dark-v10/static/118.78,32.3,7.8,0/1600x1200?access_token=pk.eyJ1IjoiZGVtb3VzZXIiLCJhIjoiY2x4eH..."');
background-size: cover; background-position: center;
overflow: hidden;
transition: all 0.5s ease;
}
.trajectory-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; opacity: 0; transition: opacity 0.5s; z-index: 1;}
.trajectory-layer.active { opacity: 1; }
path.trace-line { fill: none; stroke: var(--tech-cyan); stroke-width: 2; stroke-dasharray: 10; stroke-dashoffset: 100; animation: dash 20s linear infinite; filter: drop-shadow(0 0 4px var(--tech-cyan)); }
@keyframes dash { to { stroke-dashoffset: 0; } }
.coord-marker { position: absolute; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; cursor: pointer; z-index: 10; transition: all 0.3s; }
.coord-avatar-box { width: 40px; height: 40px; background: #004e92; border-radius: 50%; border: 2px solid var(--st-offline); box-shadow: 0 0 10px rgba(0,0,0,0.8); display: flex; justify-content: center; align-items: center; font-size: 18px; font-weight: bold; color: #fff; position: relative; z-index: 2; }
.coord-marker.online .coord-avatar-box { border-color: var(--st-online); box-shadow: 0 0 15px rgba(48, 209, 88, 0.6); }
.coord-marker.task .coord-avatar-box { border-color: var(--st-task); box-shadow: 0 0 15px rgba(255, 159, 10, 0.6); }
.coord-marker.active { z-index: 50; transform: translate(-50%, -60%) scale(1.2); }
.coord-label { margin-top: 6px; background: rgba(0, 0, 0, 0.7); border: 1px solid rgba(255,255,255,0.2); backdrop-filter: blur(4px); padding: 2px 8px; border-radius: 12px; font-size: 11px; color: #fff; white-space: nowrap; display: flex; align-items: center; gap: 4px; }
.coord-dot { width: 6px; height: 6px; border-radius: 50%; background: #888; }
.online .coord-dot { background: var(--st-online); }
.task .coord-dot { background: var(--st-task); }
/* ==================== 3. 面板通用 ==================== */
.panel {
width: 400px; height: 100vh;
background: var(--panel-bg);
border-left: 1px solid var(--border-color);
border-right: 1px solid var(--border-color);
display: flex; flex-direction: column;
backdrop-filter: blur(12px);
z-index: 30; flex-shrink: 0;
box-shadow: 0 0 40px rgba(0,0,0,0.6);
}
.panel-left {
position: absolute; left: 0; top: 0;
transform: translateX(-100%);
transition: transform 0.4s cubic-bezier(0.25, 1, 0.5, 1);
border-right: 1px solid var(--border-color); border-left: none;
}
.panel-left.active { transform: translateX(0); }
.p-header {
height: 56px; flex-shrink: 0;
display: flex; align-items: center; padding: 0 20px;
background: linear-gradient(90deg, rgba(0,122,255,0.15), transparent);
border-bottom: 1px solid var(--border-color);
}
.header-deco { width: 4px; height: 18px; background: var(--tech-cyan); margin-right: 12px; box-shadow: 0 0 8px var(--tech-cyan); }
.p-title { font-size: 18px; font-weight: bold; color: #fff; letter-spacing: 1px; flex: 1; text-align: left; }
.p-content { flex: 1; overflow-y: auto; padding: 20px; }
/* ==================== 4. 左侧面板 (Detail V5) ==================== */
.info-card { display: flex; gap: 15px; margin-bottom: 25px; padding-bottom: 20px; border-bottom: 1px solid rgba(255,255,255,0.1); }
.info-avatar { width: 64px; height: 64px; background: #004e92; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-size: 24px; font-weight: bold; border: 2px solid rgba(255,255,255,0.2); flex-shrink: 0; }
.info-details { flex: 1; display: flex; flex-direction: column; justify-content: center; gap: 6px; }
.info-name-row { display: flex; align-items: center; gap: 10px; }
.info-name { font-size: 20px; font-weight: bold; color: #fff; }
.info-gender { font-size: 12px; background: rgba(255,255,255,0.1); padding: 1px 6px; border-radius: 4px; color: #ccc; }
.info-sub { font-size: 13px; color: var(--text-sub); display: flex; gap: 15px; }
.perf-container { display: flex; justify-content: space-between; margin-bottom: 30px; padding: 0 10px; }
.perf-item { text-align: center; }
.perf-ring { width: 50px; height: 50px; border-radius: 50%; border: 4px solid #333; display: flex; justify-content: center; align-items: center; font-size: 12px; font-weight: bold; margin: 0 auto 8px; position: relative; }
.perf-lbl { font-size: 12px; color: var(--text-sub); }
.section-title { font-size: 15px; font-weight: bold; color: var(--tech-cyan); border-left: 3px solid var(--tech-cyan); padding-left: 10px; margin-bottom: 12px; }
.task-card-v4 { background: rgba(30, 41, 59, 0.6); border: 1px solid rgba(255, 159, 10, 0.3); border-radius: 6px; padding: 15px; margin-bottom: 25px; }
.task-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-size: 13px; }
.task-row:last-child { margin-bottom: 0; }
.tk-label { color: #888; }
.tk-val { color: #fff; font-weight: 500; text-align: right; }
.tk-val.cyan { color: var(--tech-cyan); font-family: monospace; letter-spacing: 1px; }
.tk-val.orange { color: var(--tech-orange); font-weight: bold; }
.loc-box { margin-bottom: 25px; }
.loc-content { display: flex; align-items: center; gap: 10px; color: #fff; font-size: 14px; margin-top: 5px; }
.loc-icon { color: #ef4444; font-size: 16px; }
.comm-box { margin-bottom: 25px; background: rgba(0,0,0,0.2); padding: 15px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.05); }
.comm-screen { height: 40px; display: flex; align-items: center; justify-content: center; font-family: monospace; font-size: 14px; color: #888; margin-bottom: 10px; background: rgba(0,0,0,0.3); border-radius: 4px; }
.btn-call { width: 100%; padding: 10px; background: rgba(48, 209, 88, 0.15); border: 1px solid var(--st-online); color: var(--st-online); border-radius: 4px; font-weight: bold; cursor: pointer; transition: 0.2s; display: flex; justify-content: center; align-items: center; gap: 8px; }
.btn-call:hover { background: var(--st-online); color: #000; }
.btn-call.hangup { background: rgba(239, 68, 68, 0.2); border-color: #ef4444; color: #ef4444; }
.btn-call.hangup:hover { background: #ef4444; color: #fff; }
.trace-card-v4 { background: rgba(0,0,0,0.3); border-radius: 6px; padding: 15px; border: 1px solid rgba(255,255,255,0.05); }
.trace-header { display: flex; gap: 10px; margin-bottom: 15px; }
.trace-sel { background: #0f172a; border: 1px solid #334155; color: #fff; padding: 4px 8px; border-radius: 4px; font-size: 12px; outline: none; }
.trace-time-display { text-align: center; font-size: 20px; font-weight: bold; color: var(--tech-cyan); margin-bottom: 10px; font-family: Impact, monospace; }
.trace-slider { height: 4px; background: #334155; position: relative; margin-bottom: 8px; cursor: pointer; }
.ts-fill { height: 100%; background: var(--tech-cyan); width: 0%; position: absolute; left: 0; }
.ts-knob { width: 14px; height: 14px; background: #fff; border-radius: 50%; position: absolute; left: 0%; top: -5px; transform: translateX(-50%); box-shadow: 0 0 5px rgba(0,0,0,0.5); }
.ts-labels { display: flex; justify-content: space-between; font-size: 10px; color: #64748b; margin-bottom: 15px; font-family: monospace; }
.trace-controls { display: flex; justify-content: center; gap: 20px; align-items: center; margin-bottom: 15px; }
.tc-btn { color: #94a3b8; cursor: pointer; font-size: 14px; }
.tc-play { width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--tech-cyan); display: flex; justify-content: center; align-items: center; color: var(--tech-cyan); cursor: pointer; transition: 0.2s; }
.tc-play:hover { background: var(--tech-cyan); color: #000; }
.trace-logs { border-top: 1px solid rgba(255,255,255,0.1); padding-top: 10px; max-height: 120px; overflow-y: auto; }
.log-item { font-size: 12px; margin-bottom: 6px; color: #94a3b8; }
.log-time { color: var(--tech-cyan); font-weight: bold; margin-right: 6px; }
/* ==================== 5. 右侧面板 (List V6) ==================== */
.kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; padding: 12px 10px; border-bottom: 1px solid rgba(255,255,255,0.1); }
.kpi-box { background: rgba(255,255,255,0.05); border: 1px solid transparent; border-radius: 4px; padding: 8px 0; text-align: center; cursor: pointer; transition: 0.2s; }
.kpi-box:hover { background: rgba(255,255,255,0.1); }
.kpi-box.active { background: rgba(0,122,255,0.25); border-color: var(--primary-blue); box-shadow: inset 0 0 10px rgba(0,122,255,0.3); }
.kpi-val { font-family: Impact; font-size: 20px; display: block; margin-bottom: 2px; color:#fff; }
.kpi-name { font-size: 11px; color: var(--text-sub); }
.search-bar { padding: 12px; display: flex; gap: 8px; border-bottom: 1px solid rgba(255,255,255,0.05); }
.s-select { background: rgba(0,0,0,0.3); color: #fff; border: 1px solid rgba(255,255,255,0.2); padding: 5px; font-size: 12px; border-radius: 4px; }
.s-input { flex: 1; background: rgba(0,0,0,0.3); color: #fff; border: 1px solid rgba(255,255,255,0.2); padding: 5px 10px; font-size: 12px; border-radius: 4px; }
/* V6 List Card Improvements */
.list-card-v6 {
background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.05);
border-radius: 6px; margin-bottom: 8px; padding: 12px; cursor: pointer; transition: 0.2s;
}
.list-card-v6:hover { background: rgba(255,255,255,0.08); }
.list-card-v6.active { border-color: var(--tech-cyan); background: rgba(0, 240, 255, 0.08); }
.l6-row { display: flex; align-items: center; margin-bottom: 6px; }
.l6-avatar { width: 30px; height: 30px; background: #004e92; border-radius: 50%; display: flex; justify-content: center; align-items: center; font-size: 13px; font-weight: bold; margin-right: 10px; border: 1px solid rgba(255,255,255,0.3); flex-shrink: 0; }
/* Name & Identity Section */
.l6-name-group { flex: 1; display: flex; align-items: center; gap: 8px; overflow: hidden; }
.l6-name { font-size: 14px; font-weight: bold; color: #fff; white-space: nowrap; }
.l6-tags-wrapper { display: flex; gap: 4px; overflow: hidden; }
/* Identity Tag Style (Restored from V3) */
.l6-id-tag { font-size: 10px; padding: 0 4px; border-radius: 2px; border: 1px solid rgba(255,255,255,0.15); color: #ccc; background: rgba(255,255,255,0.05); white-space: nowrap; }
.l6-status-tag { font-size: 10px; padding: 1px 4px; border-radius: 2px; margin-left: auto; flex-shrink: 0; }
.tag-online { background: rgba(48, 209, 88, 0.2); color: var(--st-online); }
.tag-task { background: rgba(255, 159, 10, 0.2); color: var(--st-task); }
.tag-off { background: rgba(255,255,255,0.1); color: #888; }
.l6-info-row { font-size: 11px; color: #aaa; display: flex; align-items: center; margin-top: 4px; }
.l6-icon { width: 14px; text-align: center; margin-right: 4px; }
.l6-val { color: #ddd; }
.val-task { color: var(--st-task); }
</style>
</head>
<body>
<!-- 1. 左侧详情面板 (Same as V5) -->
<div class="panel panel-left" id="panelLeft">
<div class="p-header">
<div class="header-deco"></div>
<div class="p-title">协调员详情</div>
<i class="fas fa-times cursor-pointer text-gray-400 hover:text-white" onclick="closeLeftPanel()"></i>
</div>
<div class="p-content">
<div class="info-card">
<div class="info-avatar" id="d-avatar">--</div>
<div class="info-details">
<div class="info-name-row"><span class="info-name" id="d-name">--</span><span class="info-gender" id="d-gender">--</span></div>
<div class="info-sub"><span><i class="fas fa-phone mr-1"></i><span id="d-phone">--</span></span><span><i class="fas fa-map-marker-alt mr-1"></i><span id="d-region">--</span></span></div>
</div>
</div>
<div class="perf-container">
<div class="perf-item"><div class="perf-ring" style="border-color:var(--st-online); color:var(--st-online)">95%</div><span class="perf-lbl">及时性</span></div>
<div class="perf-item"><div class="perf-ring" style="border-color:#00f0ff; color:#00f0ff">100%</div><span class="perf-lbl">合规性</span></div>
<div class="perf-item"><div class="perf-ring" style="border-color:#ff9f0a; color:#ff9f0a">75%</div><span class="perf-lbl">转化率</span></div>
</div>
<div class="section-title">当前任务状态</div>
<div class="task-card-v4" id="d-task-box"></div>
<div class="section-title">当前实时位置</div>
<div class="loc-box"><div class="loc-content"><i class="fas fa-map-marker-alt loc-icon"></i><span id="d-location-text">--</span></div></div>
<div class="comm-box">
<div class="comm-screen" id="comm-screen">待机中</div>
<button class="btn-call" id="btn-call" onclick="toggleCall()"><i class="fas fa-phone-alt"></i> 呼叫协调员</button>
</div>
<div class="section-title"><i class="fas fa-map mr-2"></i>轨迹回放</div>
<div class="trace-card-v4">
<div class="trace-header"><select class="trace-sel flex-1"><option>今天 (2023-11-27)</option></select><select class="trace-sel w-20"><option>1x</option></select></div>
<div class="trace-time-display" id="trace-clock">08:00:00</div>
<div class="trace-slider"><div class="ts-fill" id="ts-fill"></div><div class="ts-knob" id="ts-knob"></div></div>
<div class="ts-labels"><span>00:00</span><span>12:00</span><span>23:59</span></div>
<div class="trace-controls"><i class="fas fa-step-backward tc-btn"></i><div class="tc-play" onclick="playTrace()"><i class="fas fa-play" id="play-icon"></i></div><i class="fas fa-step-forward tc-btn"></i></div>
<div class="trace-logs" id="trace-logs"></div>
</div>
</div>
</div>
<!-- 2. 中央地图 -->
<div class="map-viewport" id="mapView">
<svg class="trajectory-layer" id="trajLayer"><path class="trace-line" d="M 600 400 Q 650 350 700 420 T 800 380" /></svg>
<div id="marker-container"></div>
</div>
<!-- 3. 右侧列表面板 -->
<div class="panel">
<div class="p-header"><div class="header-deco"></div><div class="p-title">协调员管理</div></div>
<div class="kpi-grid">
<div class="kpi-box active" onclick="filter('all', this)"><span class="kpi-val">12</span><span class="kpi-name">总人数</span></div>
<div class="kpi-box" onclick="filter('online', this)"><span class="kpi-val" style="color:var(--st-online)">6</span><span class="kpi-name">在线</span></div>
<div class="kpi-box" onclick="filter('task', this)"><span class="kpi-val" style="color:var(--st-task)">4</span><span class="kpi-name">任务中</span></div>
<div class="kpi-box" onclick="filter('offline', this)"><span class="kpi-val" style="color:var(--st-offline)">2</span><span class="kpi-name">离线</span></div>
</div>
<div class="search-bar"><select class="s-select"><option>区域</option><option>南京</option><option>徐州</option></select><input type="text" class="s-input" placeholder="输入姓名..."></div>
<div class="p-content" id="list-container"></div>
</div>
<script>
// ==================== 数据 (Updated for V6 with Tags) ====================
const users = [
{
id: 'u1', name: '王强', sex: '男', region: '南京', phone: '13812345678',
tags: ['ICU背景', '资深', '金牌'], // V6: 新增标签数据
status: 'task',
pos: { x: 45, y: 40, loc: '南京市鼓楼医院 ICU' },
task: { type: '器官转运', code: 'T-JS099', start: '10:00', duration: '2h 50m' },
logs: [{ time: '08:30', msg: '打卡上班 @南京办事处' }, { time: '09:00', msg: '任务开始:器官转运 T-JS099' }]
},
{
id: 'u2', name: '李晓雯', sex: '女', region: '南京', phone: '13900001111',
tags: ['心理咨询师', '社工'], // V6: 新增标签数据
status: 'online',
pos: { x: 50, y: 55, loc: '南京办事处' },
task: null,
logs: [{ time: '08:50', msg: '打卡上班 @南京办事处' }]
},
{
id: 'u3', name: '赵刚', sex: '男', region: '徐州', phone: '15066667777',
tags: ['苏北片区'], // V6: 新增标签数据
status: 'offline',
pos: { x: 20, y: 30, loc: '徐州医科大附院 (最后位置)' },
task: null,
logs: [{ time: '08:30', msg: '打卡上班 @徐州办事处' }, { time: '17:30', msg: '打卡下班' }]
}
];
let currentUserId = null;
function init() {
renderList('all');
renderMap();
}
// ==================== 右侧列表 (V6: 增加身份标签) ====================
function renderList(filterType) {
const container = document.getElementById('list-container');
container.innerHTML = '';
const filtered = users.filter(u => filterType === 'all' || u.status === filterType);
filtered.forEach(u => {
const card = document.createElement('div');
card.className = `list-card-v6`;
card.id = `card-${u.id}`;
card.onclick = () => selectUser(u.id);
// 状态标签
let statusTagHTML = '';
if(u.status === 'task') statusTagHTML = '<span class="l6-status-tag tag-task">任务中</span>';
else if(u.status === 'online') statusTagHTML = '<span class="l6-status-tag tag-online">在线</span>';
else statusTagHTML = '<span class="l6-status-tag tag-off">离线</span>';
// 身份标签 (V6 新增)
const identityTagsHTML = u.tags.map(t => `<span class="l6-id-tag">${t}</span>`).join('');
// 任务详情
let taskRowHTML = '';
if(u.status === 'task') {
taskRowHTML = `<div class="l6-info-row"><i class="fas fa-briefcase l6-icon val-task"></i> <span class="l6-val val-task">${u.task.type} (${u.task.duration})</span></div>`;
} else {
taskRowHTML = `<div class="l6-info-row"><i class="fas fa-coffee l6-icon"></i> <span class="l6-val" style="color:#666">无进行中任务</span></div>`;
}
// 位置
let locRowHTML = `<div class="l6-info-row"><i class="fas fa-map-marker-alt l6-icon"></i> <span class="l6-val">${u.pos.loc}</span></div>`;
card.innerHTML = `
<div class="l6-row">
<div class="l6-avatar">${u.name.charAt(0)}</div>
<div class="l6-name-group">
<span class="l6-name">${u.name}</span>
<div class="l6-tags-wrapper">${identityTagsHTML}</div>
</div>
${statusTagHTML}
</div>
<div style="margin-top:8px; padding-top:8px; border-top:1px dashed rgba(255,255,255,0.1)">
${locRowHTML}
${taskRowHTML}
</div>
`;
container.appendChild(card);
});
}
// ==================== 左侧详情与交互 (Same as V5) ====================
function selectUser(uid) {
currentUserId = uid;
const u = users.find(user => user.id === uid);
document.getElementById('d-avatar').innerText = u.name.charAt(0);
document.getElementById('d-name').innerText = u.name;
document.getElementById('d-gender').innerText = u.sex;
document.getElementById('d-phone').innerText = u.phone;
document.getElementById('d-region').innerText = u.region;
const taskBox = document.getElementById('d-task-box');
if(u.status === 'task') {
taskBox.innerHTML = `
<div class="task-row"><span class="tk-label">任务类型:</span><span class="tk-val">${u.task.type}</span></div>
<div class="task-row"><span class="tk-label">登记编号:</span><span class="tk-val cyan">${u.task.code}</span></div>
<div class="task-row"><span class="tk-label">开始时间:</span><span class="tk-val">${u.task.start}</span></div>
<div class="task-row"><span class="tk-label">已持续:</span><span class="tk-val orange">${u.task.duration}</span></div>
`;
} else {
taskBox.innerHTML = `<div class="text-center text-xs text-gray-500 py-4">无进行中任务</div>`;
}
document.getElementById('d-location-text').innerText = u.pos.loc;
const logBox = document.getElementById('trace-logs');
logBox.innerHTML = u.logs.map(l => `<div class="log-item"><span class="log-time">${l.time}</span> ${l.msg}</div>`).join('');
document.querySelectorAll('.list-card-v6').forEach(c => c.classList.remove('active'));
document.getElementById(`card-${uid}`).classList.add('active');
document.getElementById('panelLeft').classList.add('active');
document.getElementById('mapView').style.transform = 'scale(1.05)';
resetCall(); stopTrace(); renderMap(uid);
}
function closeLeftPanel() {
document.getElementById('panelLeft').classList.remove('active');
document.getElementById('mapView').style.transform = 'scale(1)';
document.querySelectorAll('.active').forEach(e => e.classList.remove('active'));
renderMap(); resetCall(); stopTrace();
}
function filter(type, el) {
document.querySelectorAll('.kpi-box').forEach(k => k.classList.remove('active'));
el.classList.add('active');
renderList(type);
}
function renderMap(activeId = null) {
const con = document.getElementById('marker-container');
con.innerHTML = '';
users.forEach(u => {
const isActive = u.id === activeId ? 'active' : '';
const el = document.createElement('div');
el.className = `coord-marker ${u.status} ${isActive}`;
el.style.left = u.pos.x + '%'; el.style.top = u.pos.y + '%';
el.onclick = () => selectUser(u.id);
el.innerHTML = `<div class="coord-avatar-box">${u.name.charAt(0)}</div><div class="coord-label"><div class="coord-dot"></div> ${u.name}</div>`;
con.appendChild(el);
});
}
// Call Logic
let callTimer = null; let callSeconds = 0;
function toggleCall() {
const btn = document.getElementById('btn-call');
const screen = document.getElementById('comm-screen');
if (btn.classList.contains('hangup')) { resetCall(); } else {
btn.innerHTML = '<i class="fas fa-phone-slash"></i> 取消拨号'; btn.classList.add('hangup');
screen.innerText = "拨号中..."; screen.style.color = "#00f0ff";
setTimeout(() => {
if(btn.classList.contains('hangup')) {
screen.style.color = "#30d158"; btn.innerHTML = '<i class="fas fa-phone-slash"></i> 挂断';
callSeconds = 0; screen.innerText = "通话中 00:00";
callTimer = setInterval(() => {
callSeconds++;
const m = Math.floor(callSeconds / 60).toString().padStart(2, '0');
const s = (callSeconds % 60).toString().padStart(2, '0');
screen.innerText = `通话中 ${m}:${s}`;
}, 1000);
}
}, 2000);
}
}
function resetCall() {
clearInterval(callTimer);
const btn = document.getElementById('btn-call'); const screen = document.getElementById('comm-screen');
btn.classList.remove('hangup'); btn.innerHTML = '<i class="fas fa-phone-alt"></i> 呼叫协调员';
screen.innerText = "待机中"; screen.style.color = "#888";
}
// Trace Logic
let traceAnim = null; let isPlaying = false; let progress = 0;
function playTrace() {
const icon = document.getElementById('play-icon'); const layer = document.getElementById('trajLayer');
if (isPlaying) { stopTrace(); } else {
isPlaying = true; icon.className = "fas fa-pause"; layer.classList.add('active');
traceAnim = setInterval(() => {
progress += 0.5; if (progress > 100) progress = 0;
updateTraceUI(progress);
}, 50);
}
}
function stopTrace() {
isPlaying = false; clearInterval(traceAnim);
const icon = document.getElementById('play-icon'); if(icon) icon.className = "fas fa-play";
document.getElementById('trajLayer').classList.remove('active');
}
function updateTraceUI(pct) {
document.getElementById('ts-fill').style.width = pct + '%'; document.getElementById('ts-knob').style.left = pct + '%';
const totalMins = 960 * (pct / 100);
const h = 8 + Math.floor(totalMins / 60); const m = Math.floor(totalMins % 60);
document.getElementById('trace-clock').innerText = `${h.toString().padStart(2,'0')}:${m.toString().padStart(2,'0')}:00`;
}
init();
</script>
</body>
</html>
index.html
style.css
index.js
index.html