| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>展厅控制</title>
- <style>
- :root {
- --primary-color: #4ecdc4;
- --secondary-color: #ff6b6b;
- --accent-color: #feca57;
- --dark-text: #333;
- --light-text: #fff;
- --bg-gradient: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
- --card-bg: #ffffff;
- }
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
- background: var(--bg-gradient);
- min-height: 100vh;
- padding: 20px;
- color: var(--dark-text);
- }
- .container {
- max-width: 1000px;
- margin: 0 auto;
- }
- .header {
- text-align: center;
- margin-bottom: 30px;
- color: #2c3e50;
- padding: 20px;
- background: rgba(255, 255, 255, 0.8);
- border-radius: 15px;
- box-shadow: 0 4px 6px rgba(0,0,0,0.05);
- }
- .header h1 {
- font-size: 2.2em;
- margin-bottom: 10px;
- color: #1a252f;
- }
- .main-section {
- background: var(--card-bg);
- border-radius: 15px;
- padding: 25px;
- margin-bottom: 30px;
- box-shadow: 0 10px 20px rgba(0,0,0,0.1);
- }
- .section-header {
- border-bottom: 2px solid #eee;
- padding-bottom: 15px;
- margin-bottom: 20px;
- display: flex;
- align-items: center;
- }
- .section-header h2 {
- font-size: 1.5em;
- color: #2c3e50;
- border-left: 5px solid var(--primary-color);
- padding-left: 15px;
- }
- .sub-section {
- background: #f8f9fa;
- border-radius: 10px;
- padding: 20px;
- margin-bottom: 20px;
- border: 1px solid #e9ecef;
- }
- .sub-section h3 {
- font-size: 1.2em;
- margin-bottom: 15px;
- color: #555;
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .control-row {
- display: flex;
- flex-wrap: wrap;
- gap: 15px;
- align-items: center;
- margin-bottom: 15px;
- }
- .control-group {
- flex: 1;
- min-width: 200px;
- }
- label {
- display: block;
- margin-bottom: 5px;
- font-weight: 600;
- font-size: 0.9em;
- color: #666;
- }
- input[type="text"],
- input[type="number"],
- select {
- width: 100%;
- padding: 10px;
- border: 1px solid #ddd;
- border-radius: 6px;
- font-size: 14px;
- transition: border-color 0.3s;
- }
- input[type="text"]:focus,
- input[type="number"]:focus,
- select:focus {
- border-color: var(--primary-color);
- outline: none;
- }
- input[type="file"] {
- padding: 8px 0;
- }
- .btn {
- padding: 10px 20px;
- border: none;
- border-radius: 6px;
- cursor: pointer;
- font-size: 14px;
- font-weight: 600;
- transition: all 0.2s;
- color: white;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- }
- .btn:active {
- transform: translateY(1px);
- box-shadow: none;
- }
- .btn-primary { background-color: var(--primary-color); }
- .btn-primary:hover { background-color: #3dbdb4; }
- .btn-secondary { background-color: var(--secondary-color); }
- .btn-secondary:hover { background-color: #ff5252; }
- .btn-warning { background-color: var(--accent-color); color: #333; }
- .btn-warning:hover { background-color: #fdbf38; }
- .btn-info { background-color: #667eea; }
- .btn-info:hover { background-color: #5a6fd6; }
- .status-display {
- background: #e8f5e8;
- border: 1px solid #c8e6c9;
- color: #2e7d32;
- padding: 15px;
- border-radius: 8px;
- font-size: 0.9em;
- margin-top: 15px;
- }
- .status-display p { margin: 5px 0; }
- .message-box {
- position: fixed;
- top: 20px;
- right: 20px;
- padding: 15px 25px;
- border-radius: 8px;
- color: white;
- display: none;
- z-index: 3000;
- box-shadow: 0 4px 12px rgba(0,0,0,0.15);
- animation: slideIn 0.3s ease-out;
- }
- @keyframes slideIn {
- from { transform: translateX(100%); opacity: 0; }
- to { transform: translateX(0); opacity: 1; }
- }
- .msg-success { background-color: #4caf50; }
- .msg-error { background-color: #f44336; }
- .loading-overlay {
- position: fixed;
- top: 0; left: 0; right: 0; bottom: 0;
- background: rgba(255,255,255,0.7);
- display: none;
- justify-content: center;
- align-items: center;
- z-index: 4000;
- font-size: 1.2em;
- color: #555;
- }
- .helper-text {
- font-size: 0.85em;
- color: #888;
- margin-top: 5px;
- }
- /* 电视网格布局 */
- .tv-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
- gap: 20px;
- margin-bottom: 20px;
- }
- .tv-card {
- background: white;
- border-radius: 12px;
- padding: 20px;
- text-align: center;
- border: 1px solid #eee;
- transition: transform 0.2s, box-shadow 0.2s;
- cursor: pointer;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
- .tv-card:hover {
- transform: translateY(-5px);
- box-shadow: 0 10px 20px rgba(0,0,0,0.1);
- border-color: var(--primary-color);
- }
- .tv-icon {
- font-size: 3em;
- margin-bottom: 10px;
- }
- .tv-name {
- font-weight: bold;
- color: #2c3e50;
- margin-bottom: 5px;
- }
- .tv-index {
- font-size: 0.8em;
- color: #999;
- margin-bottom: 10px;
- }
- /* Modal 弹窗样式 */
- .modal {
- display: none;
- position: fixed;
- top: 0; left: 0; width: 100%; height: 100%;
- background-color: rgba(0,0,0,0.5);
- z-index: 2000;
- justify-content: center;
- align-items: center;
- }
- .modal-content {
- background-color: white;
- padding: 30px;
- border-radius: 15px;
- width: 90%;
- max-width: 500px;
- position: relative;
- box-shadow: 0 5px 30px rgba(0,0,0,0.3);
- max-height: 90vh;
- overflow-y: auto;
- }
- .modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- border-bottom: 1px solid #eee;
- padding-bottom: 10px;
- }
- .modal-title {
- font-size: 1.4em;
- color: #2c3e50;
- font-weight: bold;
- }
- .close-btn {
- font-size: 1.5em;
- color: #aaa;
- cursor: pointer;
- line-height: 1;
- }
- .close-btn:hover {
- color: #333;
- }
- .config-section {
- background: #f8f9fa;
- padding: 15px;
- border-radius: 8px;
- margin-bottom: 15px;
- border: 1px solid #eee;
- }
- .config-title {
- font-weight: bold;
- margin-bottom: 10px;
- color: #555;
- display: flex;
- align-items: center;
- gap: 5px;
- }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="header" style="position: relative;">
- <h1>展厅控制</h1>
- <p>统一管理展厅电视与灯光设备</p>
- <a href="/logout" style="position: absolute; right: 20px; top: 20px; text-decoration: none; color: #ff6b6b; font-weight: bold; border: 1px solid #ff6b6b; padding: 5px 15px; border-radius: 5px; transition: all 0.2s;" onmouseover="this.style.background='#ff6b6b';this.style.color='white'" onmouseout="this.style.background='transparent';this.style.color='#ff6b6b'">
- 退出登录
- </a>
- </div>
- <!-- 模块一:控制 Kodi 所安装的电视 -->
- <div class="main-section">
- <div class="section-header">
- <h2>📺 电视控制 (Kodi)</h2>
- </div>
- <!-- 1. 控制单个电视 -->
- <div class="sub-section">
- <h3>👤 控制单个电视</h3>
- <p style="margin-bottom: 15px; color: #666; font-size: 0.9em;">点击电视图标进行详细配置</p>
- <div id="tv-grid-container" class="tv-grid">
- <!-- 动态生成电视图标 -->
- <div style="grid-column: 1 / -1; text-align: center; padding: 20px;">
- 加载中...
- </div>
- </div>
- </div>
- <!-- 2. 控制所有电视 -->
- <div class="sub-section">
- <h3>👥 控制所有电视</h3>
-
- <div style="background: white; padding: 15px; border-radius: 8px; border: 1px solid #eee; margin-bottom: 15px;">
- <h4 style="margin-bottom: 15px; color: #ff6b6b;">同步播放控制</h4>
- <div class="control-row">
- <div class="control-group">
- <label for="videoSelect">选择视频 (Video ID)</label>
- <select id="videoSelect">
- <option value="">加载中...</option>
- </select>
- </div>
- <div class="control-group" style="flex: 2; display: flex; align-items: flex-end;">
- <button class="btn btn-secondary" style="width: 100%;" onclick="startKodi()">同步播放视频</button>
- </div>
- </div>
-
- <div style="background: white; padding: 15px; border-radius: 8px; border: 1px solid #eee; margin-bottom: 15px;">
- <h4 style="margin-bottom: 15px; color: #4ecdc4;">音量控制</h4>
- <div class="control-row">
- <div class="control-group" style="flex: 1;">
- <label for="globalVolume">全局音量 (0-100)</label>
- <input type="number" id="globalVolume" min="0" max="100" value="65">
- </div>
- <div class="control-group" style="flex: 2; display: flex; align-items: flex-end;">
- <button class="btn btn-info" style="width: 100%;" onclick="setGlobalVolume()">设置音量</button>
- </div>
- </div>
- </div>
- </div>
- <div style="background: white; padding: 15px; border-radius: 8px; border: 1px solid #eee;">
- <h4 style="margin-bottom: 15px; color: #333;">系统与模式控制</h4>
- <div class="control-row" style="gap: 15px;">
- <button class="btn btn-primary" style="flex: 1;" onclick="startAllKodiApps()">启动所有电视Kodi应用</button>
- <button class="btn btn-warning" style="flex: 1;" onclick="revokeIndividualState()">撤销独立控制 (恢复同步)</button>
- </div>
- <div class="control-row" style="gap: 15px;">
- <button class="btn btn-secondary" style="flex: 1;" onclick="turnOnAllTvs()">唤醒所有电视</button>
- <button class="btn" style="flex: 1; background-color: #34495e; color: white;" onclick="turnOffAllTvs()">息屏所有电视</button>
- </div>
- </div>
-
- <div class="status-display" id="kodiStatusDisplay">
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
- <strong>📊 Kodi 系统状态</strong>
- <button class="btn btn-warning" style="padding: 5px 10px; font-size: 12px;" onclick="getKodiStatus()">刷新</button>
- </div>
- <div id="kodiStatusContent">加载中...</div>
- </div>
- </div>
- </div>
- <!-- 模块三:门禁控制 -->
- <div class="main-section">
- <div class="section-header">
- <h2>🚪 办公楼大门控制</h2>
- </div>
-
- <div class="sub-section" style="background: white;">
- <!-- 远程开门 -->
- <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid #eee; margin-bottom: 15px;">
- <h4 style="margin-bottom: 15px; color: #2ecc71;">🔓 远程开门</h4>
- <div class="control-row">
- <div class="control-group" style="flex: 2;">
- <p style="color: #666; margin-bottom: 0; line-height: 38px;">控制办公楼大门 (ID: 1)</p>
- </div>
- <div class="control-group" style="flex: 0 0 auto; display: flex; align-items: flex-end;">
- <button class="btn btn-primary" onclick="remoteOpenDoor()">远程开门</button>
- </div>
- </div>
- </div>
- <!-- 模式控制 -->
- <div style="background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid #eee;">
- <h4 style="margin-bottom: 15px; color: #e74c3c;">⚙️ 模式设置</h4>
- <div class="control-row">
- <div class="control-group">
- <label for="doorControlWay">控制模式</label>
- <select id="doorControlWay">
- <option value="0">在线 (普通模式)</option>
- <option value="1">常开 (保持开启)</option>
- <option value="2">常闭 (保持关闭)</option>
- </select>
- </div>
- <div class="control-group" style="flex: 0 0 auto; display: flex; align-items: flex-end;">
- <button class="btn btn-warning" onclick="setDoorMode()">应用设置</button>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 模块二:展品灯座控制 -->
- <div class="main-section">
- <div class="section-header">
- <h2>💡 展品灯座控制</h2>
- </div>
- <div class="sub-section" style="background: white;">
- <div class="control-row">
- <div class="control-group">
- <label for="exhibitId">选择展品</label>
- <select id="exhibitId">
- {% if led_segments %}
- {% for segment in led_segments %}
- <option value="{{ segment.id }}">{{ segment.name }} (ID: {{ segment.id }})</option>
- {% endfor %}
- {% else %}
- <option value="0">未找到配置 (默认ID: 0)</option>
- {% endif %}
- </select>
- </div>
- <div class="control-group" style="flex: 2; display: flex; align-items: flex-end; gap: 10px;">
- <button class="btn btn-primary" onclick="startEffect()">启动灯效</button>
- <button class="btn btn-secondary" onclick="stopEffect()">停止灯效</button>
- <button class="btn btn-warning" onclick="getStatus()">刷新状态</button>
- </div>
- </div>
- <div class="status-display" id="ledStatusDisplay">
- <div id="statusContent">点击"刷新状态"查看当前LED状态</div>
- </div>
- <div style="margin-top: 20px; padding: 15px; background: #f0f8ff; border-radius: 8px; border-left: 4px solid #4ecdc4;">
- <p><strong>📖 灯效说明:</strong> 指定展品将显示白色呼吸灯效,其他展品保持静止。10秒后,所有展品将随机播放波浪、闪烁或呼吸效果。</p>
- </div>
- </div>
- </div>
- </div>
- <!-- 弹窗 (Modal) -->
- <div id="tvConfigModal" class="modal">
- <div class="modal-content">
- <div class="modal-header">
- <div class="modal-title">
- 📺 <span id="modalTvName">电视配置</span>
- </div>
- <span class="close-btn" onclick="closeTvModal()">×</span>
- </div>
-
- <!-- 电源控制 -->
- <div class="config-section">
- <div class="config-title" style="color: #ff6b6b;">🔌 电源控制</div>
- <div class="control-row" style="margin-bottom: 0;">
- <button class="btn btn-secondary" style="flex: 1;" onclick="turnOnCurrentTv()">唤醒</button>
- <button class="btn" style="flex: 1; background-color: #34495e; color: white;" onclick="turnOffCurrentTv()">息屏</button>
- </div>
- </div>
- <!-- 图片播放 -->
- <div class="config-section">
- <div class="config-title" style="color: #4ecdc4;">🖼️ 图片播放</div>
- <div class="control-group" style="margin-bottom: 10px;">
- <label>方式一:上传图片</label>
- <input type="file" id="modalImageFile" accept="image/*">
- </div>
- <div class="control-group" style="margin-bottom: 10px;">
- <label>方式二:图片URL</label>
- <input type="text" id="modalImageUrl" placeholder="http://example.com/image.jpg">
- </div>
- <button class="btn btn-primary" style="width: 100%;" onclick="playImageCurrentTv()">播放图片</button>
- </div>
- <!-- RTSP播放 -->
- <div class="config-section">
- <div class="config-title" style="color: #667eea;">📹 RTSP视频流</div>
- <div class="control-group" style="margin-bottom: 10px;">
- <label>RTSP URL</label>
- <input type="text" id="modalRtspUrl" placeholder="rtsp://example.com/stream">
- </div>
- <div class="control-group" style="margin-bottom: 10px;">
- <label>音量 (0-100)</label>
- <input type="number" id="modalRtspVolume" min="0" max="100" value="0">
- </div>
- <button class="btn btn-info" style="width: 100%;" onclick="playRtspCurrentTv()">播放RTSP流</button>
- </div>
- </div>
- </div>
- <!-- 消息提示与遮罩 -->
- <div id="messageBox" class="message-box"></div>
- <div id="loadingOverlay" class="loading-overlay">
- <div><span style="font-size: 2em;">⏳</span><br>处理中...</div>
- </div>
- <script>
- // 全局变量
- let currentStatus = null;
- let currentKodiStatus = null;
- let activeTvIndex = null; // 当前正在配置的电视Index
- document.addEventListener('DOMContentLoaded', function() {
- getStatus();
- getKodiStatus();
- getKodiClients(); // 加载Kodi客户端列表并生成卡片
- getVideoList(); // 加载视频列表
- // 点击Modal外部关闭
- window.onclick = function(event) {
- const modal = document.getElementById('tvConfigModal');
- if (event.target == modal) {
- closeTvModal();
- }
- }
- });
- // 消息提示函数
- function showMessage(message, type = 'success') {
- const msgBox = document.getElementById('messageBox');
- msgBox.textContent = message;
- msgBox.className = 'message-box ' + (type === 'success' ? 'msg-success' : 'msg-error');
- msgBox.style.display = 'block';
-
- // 重新触发动画
- msgBox.style.animation = 'none';
- msgBox.offsetHeight; /* trigger reflow */
- msgBox.style.animation = null;
- setTimeout(() => {
- msgBox.style.display = 'none';
- }, 3000);
- }
- function showLoading(show = true) {
- document.getElementById('loadingOverlay').style.display = show ? 'flex' : 'none';
- }
- // ===== 展品灯效控制 =====
- async function getStatus() {
- try {
- showLoading(true);
- const response = await fetch('/api/led/status');
- const result = await response.json();
-
- if (result.success) {
- currentStatus = result.data;
- document.getElementById('statusContent').innerHTML = `
- <p><strong>运行状态:</strong> ${currentStatus.is_running ? '运行中' : '已停止'}</p>
- <p><strong>信息:</strong> ${currentStatus.message}</p>
- `;
- showMessage('LED状态已刷新');
- } else {
- showMessage('获取LED状态失败: ' + result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- async function startEffect() {
- const exhibitId = parseInt(document.getElementById('exhibitId').value);
- if (isNaN(exhibitId) || exhibitId < 0) {
- showMessage('请输入有效的展品ID', 'error');
- return;
- }
- try {
- showLoading(true);
- const response = await fetch('/api/led/start', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ exhibit_id: exhibitId })
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getStatus();
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- async function stopEffect() {
- try {
- showLoading(true);
- const response = await fetch('/api/led/stop', { method: 'POST', headers: { 'Content-Type': 'application/json' } });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getStatus();
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- // ===== Kodi 控制 =====
- async function getKodiStatus() {
- try {
- // showLoading(true); // 状态刷新不强制显示全屏遮罩,体验更好
- const response = await fetch('/api/kodi/status');
- const result = await response.json();
- if (result.success) {
- currentKodiStatus = result.data;
- document.getElementById('kodiStatusContent').innerHTML = `
- <p><strong>线程状态:</strong> ${currentKodiStatus.is_running ? '运行中' : '已停止'}</p>
- <p><strong>信息:</strong> ${currentKodiStatus.message}</p>
- `;
- } else {
- document.getElementById('kodiStatusContent').innerHTML = `<span style="color:red">获取失败: ${result.message}</span>`;
- }
- } catch (error) {
- document.getElementById('kodiStatusContent').innerHTML = `<span style="color:red">网络错误</span>`;
- }
- }
- async function getKodiClients() {
- const container = document.getElementById('tv-grid-container');
- if (!container) return;
-
- try {
- const response = await fetch('/api/kodi/clients');
- const result = await response.json();
-
- if (result.success && result.data.length > 0) {
- container.innerHTML = ''; // 清空
- result.data.forEach(client => {
- const index = client.index;
- const name = client.name;
-
- // 生成电视大图标卡片
- const cardDiv = document.createElement('div');
- cardDiv.className = 'tv-card';
- cardDiv.onclick = () => openTvModal(index, name);
-
- cardDiv.innerHTML = `
- <div class="tv-icon">📺</div>
- <div class="tv-name">${name}</div>
- <div class="tv-index">ID: ${index}</div>
- <button class="btn btn-primary" style="padding: 5px 15px; font-size: 12px; margin-top: 5px;">
- ⚙️ 配置
- </button>
- `;
- container.appendChild(cardDiv);
- });
- } else {
- container.innerHTML = '<div style="grid-column: 1 / -1; text-align: center; color: #666;">未找到 Kodi 客户端</div>';
- showMessage('未找到 Kodi 客户端', 'error');
- }
- } catch (error) {
- console.error(error);
- container.innerHTML = '<div style="grid-column: 1 / -1; text-align: center; color: red;">加载失败</div>';
- showMessage('加载 Kodi 客户端列表失败', 'error');
- }
- }
- async function getVideoList() {
- const selectEl = document.getElementById('videoSelect');
- try {
- const response = await fetch('/api/kodi/videos');
- const result = await response.json();
-
- if (result.success && result.data.length > 0) {
- selectEl.innerHTML = ''; // 清空
- result.data.forEach(video => {
- const option = document.createElement('option');
- option.value = video.id;
- option.textContent = `${video.name} (ID: ${video.id})`;
- selectEl.appendChild(option);
- });
- } else {
- selectEl.innerHTML = '<option value="">未找到视频</option>';
- showMessage('未找到视频列表', 'error');
- }
- } catch (error) {
- console.error(error);
- selectEl.innerHTML = '<option value="">加载失败</option>';
- showMessage('加载视频列表失败', 'error');
- }
- }
- async function startKodi() {
- const videoId = parseInt(document.getElementById('videoSelect').value);
- // const volume = parseInt(document.getElementById('syncVolume').value); // 移除了同步播放时的音量设置
-
- if (isNaN(videoId) || videoId < 0) {
- showMessage('请选择有效的视频', 'error');
- return;
- }
-
- // if (isNaN(volume) || volume < 0 || volume > 100) {
- // showMessage('音量必须是 0-100 之间的整数', 'error');
- // return;
- // }
- try {
- showLoading(true);
- const response = await fetch('/api/kodi/start', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ video_id: videoId }) // 移除了 volume
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getKodiStatus();
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- async function setGlobalVolume() {
- const volume = parseInt(document.getElementById('globalVolume').value);
- if (isNaN(volume) || volume < 0 || volume > 100) {
- showMessage('音量必须是 0-100 之间的整数', 'error');
- return;
- }
- try {
- showLoading(true);
- const response = await fetch('/api/kodi/set_volume', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ volume: volume })
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- async function revokeIndividualState() {
- try {
- showLoading(true);
- const response = await fetch('/api/kodi/revoke_individual_state', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' }
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getKodiStatus();
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- async function startAllKodiApps() {
- try {
- showLoading(true);
- const response = await fetch('/api/kodi/start_all_apps', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' }
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- getKodiStatus();
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- // ===== Modal 弹窗控制逻辑 =====
- function openTvModal(index, name) {
- activeTvIndex = index;
- document.getElementById('modalTvName').textContent = `${name} (Index: ${index})`;
-
- // 重置输入框
- document.getElementById('modalImageFile').value = '';
- document.getElementById('modalImageUrl').value = '';
- document.getElementById('modalRtspUrl').value = '';
- document.getElementById('modalRtspVolume').value = '0';
-
- document.getElementById('tvConfigModal').style.display = 'flex';
- }
- function closeTvModal() {
- document.getElementById('tvConfigModal').style.display = 'none';
- activeTvIndex = null;
- }
- // ===== 电视电源控制 (单台 - Modal内) =====
- async function turnOnCurrentTv() {
- if (activeTvIndex === null) return;
- try {
- showLoading(true);
- const response = await fetch('/api/mitv/turn_on', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ kodi_id: activeTvIndex })
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- async function turnOffCurrentTv() {
- if (activeTvIndex === null) return;
- try {
- showLoading(true);
- const response = await fetch('/api/mitv/turn_off', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ kodi_id: activeTvIndex })
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- // ===== 电视控制 (图片/RTSP - Modal内) =====
- async function playImageCurrentTv() {
- if (activeTvIndex === null) return;
- const fileInput = document.getElementById('modalImageFile');
- const urlInput = document.getElementById('modalImageUrl').value.trim();
- if ((!fileInput.files || fileInput.files.length === 0) && !urlInput) {
- showMessage('请选择图片文件或输入图片URL', 'error');
- return;
- }
- try {
- showLoading(true);
- let response;
-
- if (fileInput.files && fileInput.files.length > 0) {
- const formData = new FormData();
- formData.append('file', fileInput.files[0]);
- formData.append('kodi_client_index', activeTvIndex);
-
- response = await fetch('/api/kodi/play_image', {
- method: 'POST',
- body: formData
- });
- } else {
- response = await fetch('/api/kodi/play_image', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- image_url: urlInput,
- kodi_client_index: activeTvIndex
- })
- });
- }
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- // 关闭弹窗可能比较好,也可能用户想继续操作,暂时不关
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- async function playRtspCurrentTv() {
- if (activeTvIndex === null) return;
- const rtspUrl = document.getElementById('modalRtspUrl').value.trim();
- const volume = parseInt(document.getElementById('modalRtspVolume').value);
- if (!rtspUrl) {
- showMessage('请输入RTSP视频流URL', 'error');
- return;
- }
- if (isNaN(volume) || volume < 0 || volume > 100) {
- showMessage('音量必须是0-100之间的整数', 'error');
- return;
- }
- try {
- showLoading(true);
- const response = await fetch('/api/kodi/play_rtsp', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- rtsp_url: rtspUrl,
- kodi_client_index: activeTvIndex,
- volume: volume
- })
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- // ===== 全局电视电源控制 =====
- async function turnOnAllTvs() {
- if (!confirm('确定要唤醒所有电视吗?')) return;
- try {
- showLoading(true);
- const response = await fetch('/api/mitv/turn_on_all', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' }
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- async function turnOffAllTvs() {
- if (!confirm('确定要息屏所有电视吗?')) return;
- try {
- showLoading(true);
- const response = await fetch('/api/mitv/turn_off_all', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' }
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- // ===== 门禁控制 =====
- async function remoteOpenDoor() {
- const doorId = 1; // 默认ID为1
- const payload = { door_id: doorId };
- try {
- showLoading(true);
- const response = await fetch('/api/door/open', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload)
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- async function setDoorMode() {
- const controlWay = parseInt(document.getElementById('doorControlWay').value);
-
- const payload = { control_way: controlWay };
- const modeName = controlWay === 0 ? '在线' : (controlWay === 1 ? '常开' : '常闭');
- if (!confirm(`确定要将门禁设置为"${modeName}"模式吗?`)) return;
- try {
- showLoading(true);
- const response = await fetch('/api/door/control', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload)
- });
- const result = await response.json();
- if (result.success) {
- showMessage(result.message);
- } else {
- showMessage(result.message, 'error');
- }
- } catch (error) {
- showMessage('网络错误: ' + error.message, 'error');
- } finally {
- showLoading(false);
- }
- }
- </script>
- </body>
- </html>
|