|
|
发表于 2025-6-12 15:50:47
|
显示全部楼层
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>AI赋能MQTT</title>
- <style>
- :root {
- --primary-color: #00a8ff;
- --secondary-color: #192a56;
- --accent-color: #0097e6;
- --success-color: #44bd32;
- --warning-color: #e1b12c;
- --danger-color: #c23616;
- --text-color: #dcdde1;
- --bg-color: #2f3640;
- }
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- }
- body {
- background: var(--bg-color);
- color: var(--text-color);
- min-height: 100vh;
- padding: 20px;
- }
- .container {
- max-width: 1200px;
- margin: 0 auto;
- background: rgba(0, 0, 0, 0.3);
- border-radius: 15px;
- padding: 20px;
- box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
- }
- .title {
- text-align: center;
- font-size: 2.5em;
- color: var(--primary-color);
- margin-bottom: 30px;
- text-shadow: 0 0 10px rgba(0, 168, 255, 0.5);
- cursor: pointer;
- transition: all 0.3s ease;
- user-select: none;
- }
- .title:hover {
- transform: scale(1.05);
- }
- .tabs {
- margin-bottom: 20px;
- }
- .tab-buttons {
- display: flex;
- gap: 10px;
- margin-bottom: 20px;
- }
- .tab-button {
- padding: 10px 20px;
- background: var(--secondary-color);
- border: none;
- border-radius: 5px;
- color: var(--text-color);
- cursor: pointer;
- transition: all 0.3s ease;
- }
- .tab-button.active {
- background: var(--primary-color);
- }
- .tab-content {
- display: none;
- animation: fadeIn 0.3s ease;
- }
- .tab-content.active {
- display: block;
- }
- .config-section {
- margin-bottom: 20px;
- }
- .form-row {
- display: flex;
- gap: 20px;
- margin-bottom: 15px;
- align-items: center;
- }
- .form-group {
- flex: 1;
- }
- .form-group label {
- display: block;
- margin-bottom: 5px;
- color: var(--primary-color);
- }
- .form-control {
- width: 100%;
- padding: 8px 12px;
- border: 1px solid var(--secondary-color);
- border-radius: 5px;
- background: rgba(0, 0, 0, 0.2);
- color: var(--text-color);
- transition: all 0.3s ease;
- }
- .form-control:focus {
- outline: none;
- border-color: var(--primary-color);
- box-shadow: 0 0 5px var(--primary-color);
- }
- .btn {
- padding: 10px 20px;
- border: none;
- border-radius: 5px;
- cursor: pointer;
- transition: all 0.3s ease;
- background: var(--primary-color);
- color: white;
- font-weight: bold;
- }
- .btn:hover {
- transform: scale(1.05);
- box-shadow: 0 0 10px rgba(0, 168, 255, 0.5);
- }
- .control-panel {
- display: grid;
- grid-template-areas:
- ". forward ."
- "left stop right"
- ". backward ."
- "functions functions functions";
- grid-template-columns: repeat(3, 100px);
- gap: 10px;
- justify-content: center;
- margin: 20px auto;
- padding: 20px;
- background: rgba(0, 0, 0, 0.3);
- border-radius: 15px;
- box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
- }
- .function-panel {
- grid-area: functions;
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
- gap: 10px;
- margin-top: 20px;
- padding-top: 20px;
- border-top: 1px solid var(--secondary-color);
- }
- .control-btn {
- padding: 15px;
- border: none;
- border-radius: 50%;
- cursor: pointer;
- transition: all 0.3s ease;
- background: var(--secondary-color);
- color: var(--text-color);
- font-weight: bold;
- position: relative;
- overflow: hidden;
- width: 100px;
- height: 100px;
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.3),
- inset 0 0 15px rgba(255, 255, 255, 0.1);
- }
- .control-btn::before {
- content: '';
- position: absolute;
- top: -50%;
- left: -50%;
- width: 200%;
- height: 200%;
- background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 70%);
- transform: scale(0);
- transition: transform 0.3s ease-out;
- }
- .control-btn:hover::before {
- transform: scale(1);
- }
- .control-btn:active {
- transform: scale(0.95);
- box-shadow: 0 0 5px rgba(0, 0, 0, 0.3),
- inset 0 0 20px rgba(0, 0, 0, 0.5);
- }
- .control-btn:hover {
- background: var(--primary-color);
- box-shadow: 0 0 20px var(--primary-color),
- inset 0 0 15px rgba(255, 255, 255, 0.2);
- }
- .control-btn[data-command="前进"] { grid-area: forward; }
- .control-btn[data-command="后退"] { grid-area: backward; }
- .control-btn[data-command="左转"] { grid-area: left; }
- .control-btn[data-command="右转"] { grid-area: right; }
- .control-btn[data-command="停止"] {
- grid-area: stop;
- background: var(--danger-color);
- }
- .control-btn[data-command="停止"]:hover {
- background: #ff3f34;
- }
- .function-btn {
- padding: 12px;
- border: none;
- border-radius: 10px;
- cursor: pointer;
- transition: all 0.3s ease;
- background: var(--secondary-color);
- color: var(--text-color);
- font-weight: bold;
- position: relative;
- overflow: hidden;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
- }
- .function-btn:hover {
- background: var(--primary-color);
- transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(0, 168, 255, 0.3);
- }
- .function-btn:active {
- transform: translateY(0);
- }
- .function-btn[data-command="开灯"],
- .function-btn[data-command="关灯"] {
- background: var(--warning-color);
- }
- .function-btn[data-command="红灯"] { background: #c23616; }
- .function-btn[data-command="绿灯"] { background: #44bd32; }
- .function-btn[data-command="黄灯"] { background: #e1b12c; }
- .function-btn[data-command="喇叭"] { background: #8c7ae6; }
- @keyframes pulse {
- 0% { transform: scale(1); }
- 50% { transform: scale(1.05); }
- 100% { transform: scale(1); }
- }
- .control-btn:active::after {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- width: 10px;
- height: 10px;
- background: var(--primary-color);
- border-radius: 50%;
- transform: translate(-50%, -50%);
- animation: pulse 0.5s ease-out;
- }
- .result-section {
- margin-top: 20px;
- }
- .result-box {
- width: 100%;
- min-height: 100px;
- padding: 10px;
- background: rgba(0, 0, 0, 0.2);
- border: 1px solid var(--secondary-color);
- border-radius: 5px;
- color: var(--text-color);
- margin-bottom: 10px;
- font-family: monospace;
- }
- @keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
- }
- .arrow {
- font-size: 2em;
- font-style: normal;
- display: block;
- margin-bottom: 5px;
- text-shadow: 0 0 10px rgba(0, 168, 255, 0.5);
- }
- .control-btn span {
- font-size: 0.9em;
- opacity: 0.8;
- }
- @keyframes glow {
- 0% { box-shadow: 0 0 5px var(--primary-color); }
- 50% { box-shadow: 0 0 20px var(--primary-color), 0 0 30px var(--primary-color); }
- 100% { box-shadow: 0 0 5px var(--primary-color); }
- }
- .control-btn:active .arrow {
- transform: scale(0.8);
- transition: transform 0.1s ease;
- }
- .function-btn {
- position: relative;
- overflow: hidden;
- }
- .function-btn::after {
- content: '';
- position: absolute;
- top: -50%;
- left: -50%;
- width: 200%;
- height: 200%;
- background: linear-gradient(
- transparent,
- rgba(255, 255, 255, 0.1),
- transparent
- );
- transform: rotate(45deg);
- transition: transform 0.5s;
- transform: translateX(-100%);
- }
- .function-btn:hover::after {
- transform: translateX(100%);
- }
- .control-panel {
- animation: slideIn 0.5s ease-out;
- }
- @keyframes slideIn {
- from {
- transform: translateY(20px);
- opacity: 0;
- }
- to {
- transform: translateY(0);
- opacity: 1;
- }
- }
- .function-panel {
- animation: fadeInUp 0.5s ease-out;
- }
- @keyframes fadeInUp {
- from {
- transform: translateY(10px);
- opacity: 0;
- }
- to {
- transform: translateY(0);
- opacity: 1;
- }
- }
- .control-btn[data-command="停止"] .arrow {
- color: #ff3f34;
- animation: pulse 2s infinite;
- }
- .function-btn:active {
- transform: scale(0.95);
- box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.3);
- }
- /* 状态面板样式 */
- .status-panel {
- background: rgba(0, 0, 0, 0.3);
- border-radius: 10px;
- padding: 15px;
- margin: 20px auto;
- max-width: 400px;
- display: flex;
- justify-content: space-between;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
- }
- .status-indicator {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .status-label {
- color: var(--primary-color);
- font-weight: bold;
- }
- .status-value {
- background: rgba(0, 0, 0, 0.2);
- padding: 5px 10px;
- border-radius: 5px;
- min-width: 80px;
- text-align: center;
- }
- /* 键盘按键样式 */
- kbd {
- background-color: var(--secondary-color);
- border-radius: 3px;
- border: 1px solid var(--primary-color);
- box-shadow: 0 1px 1px rgba(0, 0, 0, .2);
- color: var(--text-color);
- display: inline-block;
- font-size: 0.85em;
- font-weight: 700;
- line-height: 1;
- padding: 2px 6px;
- margin: 0 2px;
- }
- /* 连接状态指示器 */
- #connectionStatus.connected {
- color: var(--success-color);
- animation: pulse 2s infinite;
- }
- #connectionStatus.disconnected {
- color: var(--danger-color);
- }
- #connectionStatus.pending {
- color: var(--warning-color);
- }
- /* 当前命令高亮 */
- #currentCommand.active {
- color: var(--primary-color);
- font-weight: bold;
- }
- /* 为不同类型的按钮添加特殊效果 */
- .function-btn[data-command="开灯"]:hover,
- .function-btn[data-command="关灯"]:hover {
- animation: glow 1.5s infinite;
- }
- .function-btn[data-command="喇叭"]:hover {
- animation: shake 0.5s infinite;
- }
- @keyframes shake {
- 0%, 100% { transform: translateX(0); }
- 25% { transform: translateX(-2px); }
- 75% { transform: translateX(2px); }
- }
- /* 添加响应式调整 */
- /* 状态指示灯样式 */
- .status-light {
- position: absolute;
- top: 10px;
- right: 10px;
- width: 30px;
- height: 30px;
- border-radius: 50%;
- background: rgba(0, 0, 0, 0.3);
- padding: 5px;
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
- }
- .light-indicator {
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background-color: #2f3542;
- transition: all 0.3s ease;
- box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.5);
- }
- .light-indicator.active {
- box-shadow:
- 0 0 10px currentColor,
- 0 0 20px currentColor,
- inset 0 0 5px rgba(255, 255, 255, 0.5);
- animation: pulseLight 1s infinite;
- }
- @keyframes pulseLight {
- 0% {
- transform: scale(0.95);
- opacity: 0.8;
- }
- 50% {
- transform: scale(1.05);
- opacity: 1;
- }
- 100% {
- transform: scale(0.95);
- opacity: 0.8;
- }
- }
- /* 命令执行动画 */
- @keyframes commandExecute {
- 0% {
- transform: scale(1);
- opacity: 1;
- }
- 50% {
- transform: scale(1.2);
- opacity: 0.5;
- }
- 100% {
- transform: scale(1);
- opacity: 1;
- }
- }
- .executing-command {
- animation: commandExecute 0.5s ease-out;
- }
- /* 响应式调整 */
- @media (max-width: 768px) {
- .control-panel {
- grid-template-columns: repeat(3, 80px);
- gap: 8px;
- padding: 15px;
- }
- .control-btn {
- width: 80px;
- height: 80px;
- }
- .arrow {
- font-size: 1.5em;
- }
- .control-btn span {
- font-size: 0.8em;
- }
- .status-light {
- width: 20px;
- height: 20px;
- top: 5px;
- right: 5px;
- }
- .function-panel {
- grid-template-columns: repeat(3, 1fr);
- }
- .status-panel {
- flex-direction: column;
- gap: 10px;
- align-items: center;
- }
- }
- /* 暗色主题优化 */
- @media (prefers-color-scheme: dark) {
- .status-light {
- background: rgba(255, 255, 255, 0.1);
- }
- .light-indicator {
- background-color: #1e272e;
- }
- }
- .switch {
- position: relative;
- display: inline-block;
- width: 60px;
- height: 34px;
- }
- .switch input {
- opacity: 0;
- width: 0;
- height: 0;
- }
- .slider {
- position: absolute;
- cursor: pointer;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: var(--secondary-color);
- transition: .4s;
- border-radius: 34px;
- }
- .slider:before {
- position: absolute;
- content: "";
- height: 26px;
- width: 26px;
- left: 4px;
- bottom: 4px;
- background-color: white;
- transition: .4s;
- border-radius: 50%;
- }
- input:checked + .slider {
- background-color: var(--primary-color);
- }
- input:checked + .slider:before {
- transform: translateX(26px);
- }
- .modal {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.5);
- justify-content: center;
- align-items: center;
- }
- .modal-content {
- background: var(--bg-color);
- padding: 20px;
- border-radius: 10px;
- width: 300px;
- }
- .close {
- float: right;
- cursor: pointer;
- color: var(--text-color);
- }
- </style>
- </head>
- <body>
- <div class="container">
- <h1 class="title" id="mainTitle">AI赋能MQTT</h1>
-
- <div class="tabs">
- <div class="tab-buttons">
- <button class="tab-button active" data-tab="config">配置参数</button>
- <button class="tab-button" data-tab="control">遥控面板</button>
- </div>
- <div class="tab-content active" id="config">
- <div class="config-section">
- <div class="form-row">
- <label class="switch">
- <input type="checkbox" id="configToggle">
- <span class="slider"></span>
- </label>
- <span>显示/隐藏配置</span>
- </div>
- <div id="configParams">
- <div class="form-row">
- <div class="form-group">
- <label>服务器</label>
- <input type="text" class="form-control" id="server" value="i.37ck.cn">
- </div>
- <div class="form-group">
- <label>端口</label>
- <input type="text" class="form-control" id="port" value="88">
- </div>
- <div class="form-group">
- <label>操作</label>
- <input type="text" class="form-control" id="operation" value="publish">
- </div>
- </div>
- <div class="form-row">
- <div class="form-group">
- <label>用户名</label>
- <input type="text" class="form-control" id="username" value="37ck">
- </div>
- <div class="form-group">
- <label>密码</label>
- <input type="password" class="form-control" id="password" value="20200808">
- </div>
- </div>
- </div>
- <div class="form-row">
- <div class="form-group">
- <label>主题</label>
- <select class="form-control" id="topic">
- <option value="37ck/37创客">37ck/37创客</option>
- <option value="37ck/莘村中学">37ck/莘村中学</option>
- <option value="37ck/富教小学">37ck/富教小学</option>
- <option value="37ck/文田中学">37ck/文田中学</option>
- </select>
- </div>
- <div class="form-group">
- <label>指令</label>
- <select class="form-control" id="command">
- <option value="在线">在线</option>
- <option value="放学啦">放学啦</option>
- <option value="下课啦">下课啦</option>
- <option value="到安全等待区">到安全等待区</option>
- <option value="正赶往等待车位">正赶往等待车位</option>
- </select>
- </div>
- </div>
- <div class="form-row">
- <button class="btn" id="importConfig">导入配置</button>
- <button class="btn" id="exportConfig">导出配置</button>
- <button class="btn" id="sendBtn">发送请求</button>
- </div>
- </div>
- </div>
- <div class="tab-content" id="control">
- <h3 style="text-align: center; margin-bottom: 20px; color: var(--primary-color);">遥控操作面板</h3>
- <div class="control-panel">
- <button class="control-btn" data-command="前进">
- <i class="arrow">↑</i>
- <span>前进</span>
- </button>
- <button class="control-btn" data-command="左转">
- <i class="arrow">←</i>
- <span>左转</span>
- </button>
- <button class="control-btn" data-command="停止">
- <i class="arrow">■</i>
- <span>停止</span>
- </button>
- <button class="control-btn" data-command="右转">
- <i class="arrow">→</i>
- <span>右转</span>
- </button>
- <button class="control-btn" data-command="后退">
- <i class="arrow">↓</i>
- <span>后退</span>
- </button>
-
- <div class="function-panel">
- <button class="function-btn" data-command="开灯">开灯</button>
- <button class="function-btn" data-command="关灯">关灯</button>
- <button class="function-btn" data-command="红灯">红灯</button>
- <button class="function-btn" data-command="绿灯">绿灯</button>
- <button class="function-btn" data-command="黄灯">黄灯</button>
- <button class="function-btn" data-command="喇叭">喇叭</button>
- </div>
- </div>
-
- <div class="status-panel">
- <div class="status-indicator">
- <span class="status-label">状态:</span>
- <span class="status-value" id="connectionStatus">就绪</span>
- </div>
- <div class="status-indicator">
- <span class="status-label">当前命令:</span>
- <span class="status-value" id="currentCommand">无</span>
- </div>
- </div>
-
- <div style="text-align: center; margin-top: 20px; font-size: 0.9em; color: var(--text-color);">
- <p>点击按钮或使用键盘方向键控制</p>
- <p><kbd>↑</kbd> 前进, <kbd>↓</kbd> 后退, <kbd>←</kbd> 左转, <kbd>→</kbd> 右转, <kbd>空格</kbd> 停止</p>
- </div>
- </div>
- </div>
- <div class="result-section">
- <div class="form-group">
- <label>发送内容</label>
- <div class="result-box" id="requestUrl"></div>
- </div>
- <div class="form-group">
- <label>返回内容</label>
- <div class="result-box" id="response"></div>
- </div>
- </div>
- </div>
- <div class="modal" id="titleModal">
- <div class="modal-content">
- <span class="close">×</span>
- <div class="form-group">
- <label>修改标题</label>
- <input type="text" class="form-control" id="newTitle">
- </div>
- <button class="btn" id="saveTitle">保存</button>
- </div>
- </div>
- <audio id="clickSound" src="data:audio/mpeg;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjI5LjEwMAAAAAAAAAAAAAAA//OEAAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAAEAAABIADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDV1dXV1dXV1dXV1dXV1dXV1dXV1dXV1dXV6urq6urq6urq6urq6urq6urq6urq6urq6v////////////////////////////////8AAAAATGF2YzU4LjU0AAAAAAAAAAAAAAAAJAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN//MUZAMAAAGkAAAAAAAAA0gAAAAARTMu//MUZAYAAAGkAAAAAAAAA0gAAAAARTMz//MUZAkAAAGkAAAAAAAAA0gAAAAARTMz//MUZAwAAAGkAAAAAAAAA0gAAAAARTMz//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN//MUZAAAAAGkAAAAAAAAA0gAAAAATEFN" preload="auto"></audio>
- <script>
- // 音效播放函数
- function playClickSound() {
- const sound = document.getElementById('clickSound');
- sound.currentTime = 0;
- sound.play().catch(e => console.log('Audio play failed:', e));
- }
- // 标题编辑功能
- const mainTitle = document.getElementById('mainTitle');
- const titleModal = document.getElementById('titleModal');
- const newTitleInput = document.getElementById('newTitle');
- const saveTitleBtn = document.getElementById('saveTitle');
- const closeBtn = document.querySelector('.close');
- mainTitle.addEventListener('dblclick', () => {
- titleModal.style.display = 'flex';
- newTitleInput.value = mainTitle.textContent;
- playClickSound();
- });
- closeBtn.addEventListener('click', () => {
- titleModal.style.display = 'none';
- playClickSound();
- });
- saveTitleBtn.addEventListener('click', () => {
- mainTitle.textContent = newTitleInput.value;
- titleModal.style.display = 'none';
- playClickSound();
- });
- // 标签页切换
- const tabButtons = document.querySelectorAll('.tab-button');
- const tabContents = document.querySelectorAll('.tab-content');
- tabButtons.forEach(button => {
- button.addEventListener('click', () => {
- playClickSound();
- const tabId = button.getAttribute('data-tab');
-
- tabButtons.forEach(btn => btn.classList.remove('active'));
- tabContents.forEach(content => content.classList.remove('active'));
-
- button.classList.add('active');
- document.getElementById(tabId).classList.add('active');
- });
- });
- // 配置参数显示/隐藏
- const configToggle = document.getElementById('configToggle');
- const configParams = document.getElementById('configParams');
- configToggle.addEventListener('change', () => {
- playClickSound();
- configParams.style.display = configToggle.checked ? 'block' : 'none';
- });
- configParams.style.display = 'none'; // 默认隐藏
- // 配置导入导出
- const importConfigBtn = document.getElementById('importConfig');
- const exportConfigBtn = document.getElementById('exportConfig');
- function getConfig() {
- return {
- server: document.getElementById('server').value,
- port: document.getElementById('port').value,
- operation: document.getElementById('operation').value,
- username: document.getElementById('username').value,
- password: document.getElementById('password').value,
- topic: document.getElementById('topic').value,
- command: document.getElementById('command').value
- };
- }
- function setConfig(config) {
- document.getElementById('server').value = config.server || '';
- document.getElementById('port').value = config.port || '';
- document.getElementById('operation').value = config.operation || '';
- document.getElementById('username').value = config.username || '';
- document.getElementById('password').value = config.password || '';
- document.getElementById('topic').value = config.topic || '';
- document.getElementById('command').value = config.command || '';
- }
- exportConfigBtn.addEventListener('click', () => {
- playClickSound();
- const config = getConfig();
- const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = 'mqtt_config.json';
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- URL.revokeObjectURL(url);
- });
- importConfigBtn.addEventListener('click', () => {
- playClickSound();
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'application/json';
- input.onchange = e => {
- const file = e.target.files[0];
- const reader = new FileReader();
- reader.onload = event => {
- try {
- const config = JSON.parse(event.target.result);
- setConfig(config);
- } catch (err) {
- alert('配置文件格式错误!');
- }
- };
- reader.readAsText(file);
- };
- input.click();
- });
- // 状态管理
- const connectionStatus = document.getElementById('connectionStatus');
- const currentCommand = document.getElementById('currentCommand');
-
- function updateStatus(status) {
- connectionStatus.textContent = status;
- connectionStatus.className = '';
-
- switch(status.toLowerCase()) {
- case '已连接':
- connectionStatus.classList.add('connected');
- break;
- case '断开':
- connectionStatus.classList.add('disconnected');
- break;
- case '连接中':
- case '发送中':
- connectionStatus.classList.add('pending');
- break;
- default:
- break;
- }
- }
-
- function updateCurrentCommand(command) {
- currentCommand.textContent = command || '无';
- if (command && command !== '无') {
- currentCommand.classList.add('active');
- setTimeout(() => {
- currentCommand.classList.remove('active');
- }, 2000);
- } else {
- currentCommand.classList.remove('active');
- }
- }
- // 发送请求函数
- function sendRequest(command) {
- const config = getConfig();
- const url = `http://${config.server}:${config.port}/${config.operation}?topic=${encodeURIComponent(config.topic)}&msg=${encodeURIComponent(command)}&iname=${encodeURIComponent(config.username)}&ipwd=${encodeURIComponent(config.password)}`;
-
- document.getElementById('requestUrl').textContent = url;
- updateStatus('发送中');
- updateCurrentCommand(command);
- fetch(url)
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP error! Status: ${response.status}`);
- }
- updateStatus('已连接');
- return response.text();
- })
- .then(data => {
- document.getElementById('response').textContent = data;
-
- // 添加视觉反馈
- const responseBox = document.getElementById('response');
- responseBox.style.backgroundColor = 'rgba(0, 168, 255, 0.1)';
- setTimeout(() => {
- responseBox.style.backgroundColor = '';
- }, 500);
- })
- .catch(error => {
- document.getElementById('response').textContent = '请求失败:' + error.message;
- updateStatus('断开');
-
- // 添加错误视觉反馈
- const responseBox = document.getElementById('response');
- responseBox.style.backgroundColor = 'rgba(194, 54, 22, 0.1)';
- setTimeout(() => {
- responseBox.style.backgroundColor = '';
- }, 500);
- });
- }
- // 发送按钮事件
- document.getElementById('sendBtn').addEventListener('click', () => {
- playClickSound();
- const command = document.getElementById('command').value;
- sendRequest(command);
- });
- // 遥控按钮事件和键盘控制
- const controlButtons = document.querySelectorAll('.control-btn, .function-btn');
- const keyCommandMap = {
- 'ArrowUp': '前进',
- 'ArrowDown': '后退',
- 'ArrowLeft': '左转',
- 'ArrowRight': '右转',
- ' ': '停止', // 空格键
- 'KeyL': '开灯',
- 'KeyK': '关灯',
- 'KeyR': '红灯',
- 'KeyG': '绿灯',
- 'KeyY': '黄灯',
- 'KeyH': '喇叭'
- };
- let activeButtons = new Set();
- // 添加虚拟遥控器状态指示灯
- const statusLight = document.createElement('div');
- statusLight.className = 'status-light';
- statusLight.innerHTML = `
- <div class="light-indicator"></div>
- `;
- document.querySelector('.control-panel').appendChild(statusLight);
- // 更新按钮激活函数
- function activateButton(command) {
- const button = document.querySelector(`[data-command="${command}"]`);
- if (button && !activeButtons.has(command)) {
- activeButtons.add(command);
- button.style.animation = 'glow 1s infinite';
-
- // 更新状态指示灯
- const lightIndicator = document.querySelector('.light-indicator');
- lightIndicator.className = 'light-indicator active';
-
- // 根据命令类型设置不同颜色
- if (command === '前进' || command === '后退' || command === '左转' || command === '右转') {
- lightIndicator.style.backgroundColor = '#00a8ff'; // 蓝色
- } else if (command === '停止') {
- lightIndicator.style.backgroundColor = '#ff3f34'; // 红色
- } else if (command.includes('灯')) {
- lightIndicator.style.backgroundColor = '#ffa502'; // 黄色
- } else {
- lightIndicator.style.backgroundColor = '#7bed9f'; // 绿色
- }
-
- playClickSound();
- debouncedSendRequest(command);
- }
- }
- function deactivateButton(command) {
- const button = document.querySelector(`[data-command="${command}"]`);
- if (button) {
- activeButtons.delete(command);
- button.style.animation = '';
-
- // 如果没有活动按钮,重置状态指示灯
- if (activeButtons.size === 0) {
- const lightIndicator = document.querySelector('.light-indicator');
- lightIndicator.className = 'light-indicator';
- }
- }
- }
- // 防抖动函数
- function debounce(func, wait) {
- let timeout;
- return function executedFunction(...args) {
- const later = () => {
- clearTimeout(timeout);
- func(...args);
- };
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- };
- }
- // 节流函数
- function throttle(func, limit) {
- let inThrottle;
- return function(...args) {
- if (!inThrottle) {
- func.apply(this, args);
- inThrottle = true;
- setTimeout(() => inThrottle = false, limit);
- }
- }
- }
- // 防抖动的请求发送
- const debouncedSendRequest = debounce((command) => {
- sendRequest(command);
- }, 300);
- // 节流的按钮激活
- const throttledActivateButton = throttle((command) => {
- const button = document.querySelector(`[data-command="${command}"]`);
- if (button && !activeButtons.has(command)) {
- activeButtons.add(command);
- button.style.animation = 'glow 1s infinite';
- playClickSound();
- debouncedSendRequest(command);
- }
- }, 200);
- // 键盘控制
- document.addEventListener('keydown', (e) => {
- if (document.activeElement.tagName === 'INPUT') return; // 如果焦点在输入框中,不处理键盘事件
-
- const command = keyCommandMap[e.key] || keyCommandMap[e.code];
- if (command) {
- e.preventDefault();
- throttledActivateButton(command);
- }
- });
- document.addEventListener('keyup', (e) => {
- const command = keyCommandMap[e.key] || keyCommandMap[e.code];
- if (command) {
- deactivateButton(command);
- }
- });
- // 触摸事件处理优化
- let touchStartTime;
- let longPressTimer;
-
- function handleTouchStart(e, command) {
- e.preventDefault();
- touchStartTime = Date.now();
- longPressTimer = setTimeout(() => {
- throttledActivateButton(command);
- }, 500);
- }
- function handleTouchEnd(e, command) {
- e.preventDefault();
- clearTimeout(longPressTimer);
- const touchDuration = Date.now() - touchStartTime;
-
- if (touchDuration < 500) {
- // 短按
- throttledActivateButton(command);
- }
- deactivateButton(command);
- }
- // 更新控制按钮事件监听
- controlButtons.forEach(button => {
- const command = button.getAttribute('data-command');
- // 触摸事件
- button.addEventListener('touchstart', (e) => handleTouchStart(e, command));
- button.addEventListener('touchend', (e) => handleTouchEnd(e, command));
- button.addEventListener('touchcancel', () => {
- clearTimeout(longPressTimer);
- deactivateButton(command);
- });
- // 鼠标事件
- button.addEventListener('mousedown', () => throttledActivateButton(command));
- button.addEventListener('mouseup', () => deactivateButton(command));
- button.addEventListener('mouseleave', () => deactivateButton(command));
- });
- // 遥控按钮事件
- controlButtons.forEach(button => {
- let pressTimer;
- const command = button.getAttribute('data-command');
- button.addEventListener('mousedown', () => {
- activateButton(command);
- // 长按处理
- pressTimer = setTimeout(() => {
- button.classList.add('long-press');
- }, 500);
- });
- button.addEventListener('mouseup', () => {
- deactivateButton(command);
- clearTimeout(pressTimer);
- button.classList.remove('long-press');
- });
- button.addEventListener('mouseleave', () => {
- deactivateButton(command);
- clearTimeout(pressTimer);
- button.classList.remove('long-press');
- });
- // 触摸设备支持
- button.addEventListener('touchstart', (e) => {
- e.preventDefault();
- activateButton(command);
- });
- button.addEventListener('touchend', () => {
- deactivateButton(command);
- });
- });
- // 添加按钮反馈效果
- function addButtonFeedback(button) {
- button.addEventListener('mousedown', () => {
- button.style.transform = 'scale(0.95)';
- });
- button.addEventListener('mouseup', () => {
- button.style.transform = '';
- });
- button.addEventListener('mouseleave', () => {
- button.style.transform = '';
- });
- }
- // 为所有按钮添加反馈效果
- document.querySelectorAll('.btn, .control-btn, .function-btn').forEach(addButtonFeedback);
- // 添加工具提示显示快捷键
- function addTooltip(button, command) {
- const keyBinding = Object.entries(keyCommandMap).find(([_, cmd]) => cmd === command);
- if (keyBinding) {
- const key = keyBinding[0].replace('Arrow', '↑↓←→').replace('Key', '');
- button.title = `${command} (${key})`;
- }
- }
- controlButtons.forEach(button => {
- const command = button.getAttribute('data-command');
- addTooltip(button, command);
- });
- // 点击其他区域关闭标题编辑模态框
- window.addEventListener('click', (e) => {
- if (e.target === titleModal) {
- titleModal.style.display = 'none';
- }
- });
- // 按ESC键关闭标题编辑模态框
- document.addEventListener('keydown', (e) => {
- if (e.key === 'Escape' && titleModal.style.display === 'flex') {
- titleModal.style.display = 'none';
- }
- });
- </script>
- </body>
- </html>
复制代码 |
|