index_bak.html 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>LED控制面板</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. }
  13. body {
  14. font-family: 'Arial', sans-serif;
  15. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  16. min-height: 100vh;
  17. padding: 20px;
  18. }
  19. .container {
  20. max-width: 800px;
  21. margin: 0 auto;
  22. background: white;
  23. border-radius: 15px;
  24. box-shadow: 0 10px 30px rgba(0,0,0,0.3);
  25. overflow: hidden;
  26. }
  27. .header {
  28. background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
  29. color: white;
  30. padding: 30px;
  31. text-align: center;
  32. }
  33. .header h1 {
  34. font-size: 2.5em;
  35. margin-bottom: 10px;
  36. }
  37. .header p {
  38. font-size: 1.1em;
  39. opacity: 0.9;
  40. }
  41. .content {
  42. padding: 30px;
  43. }
  44. .control-group {
  45. margin-bottom: 25px;
  46. padding: 20px;
  47. background: #f8f9fa;
  48. border-radius: 10px;
  49. border-left: 4px solid #4ecdc4;
  50. }
  51. .control-group h3 {
  52. color: #333;
  53. margin-bottom: 15px;
  54. font-size: 1.3em;
  55. }
  56. .button-group {
  57. display: flex;
  58. gap: 10px;
  59. flex-wrap: wrap;
  60. }
  61. .btn {
  62. padding: 12px 24px;
  63. border: none;
  64. border-radius: 25px;
  65. cursor: pointer;
  66. font-size: 14px;
  67. font-weight: bold;
  68. transition: all 0.3s ease;
  69. text-transform: uppercase;
  70. letter-spacing: 1px;
  71. }
  72. .btn-primary {
  73. background: linear-gradient(45deg, #4ecdc4, #44a08d);
  74. color: white;
  75. }
  76. .btn-primary:hover {
  77. transform: translateY(-2px);
  78. box-shadow: 0 5px 15px rgba(78, 205, 196, 0.4);
  79. }
  80. .btn-danger {
  81. background: linear-gradient(45deg, #ff6b6b, #ee5a52);
  82. color: white;
  83. }
  84. .btn-danger:hover {
  85. transform: translateY(-2px);
  86. box-shadow: 0 5px 15px rgba(255, 107, 107, 0.4);
  87. }
  88. .btn-warning {
  89. background: linear-gradient(45deg, #feca57, #ff9ff3);
  90. color: white;
  91. }
  92. .btn-warning:hover {
  93. transform: translateY(-2px);
  94. box-shadow: 0 5px 15px rgba(254, 202, 87, 0.4);
  95. }
  96. .slider-container {
  97. display: flex;
  98. align-items: center;
  99. gap: 15px;
  100. margin: 15px 0;
  101. }
  102. .slider {
  103. flex: 1;
  104. height: 8px;
  105. border-radius: 5px;
  106. background: #ddd;
  107. outline: none;
  108. -webkit-appearance: none;
  109. }
  110. .slider::-webkit-slider-thumb {
  111. -webkit-appearance: none;
  112. appearance: none;
  113. width: 20px;
  114. height: 20px;
  115. border-radius: 50%;
  116. background: #4ecdc4;
  117. cursor: pointer;
  118. }
  119. .slider::-moz-range-thumb {
  120. width: 20px;
  121. height: 20px;
  122. border-radius: 50%;
  123. background: #4ecdc4;
  124. cursor: pointer;
  125. border: none;
  126. }
  127. .color-picker {
  128. width: 50px;
  129. height: 50px;
  130. border: none;
  131. border-radius: 50%;
  132. cursor: pointer;
  133. margin: 0 10px;
  134. }
  135. .status-display {
  136. background: #e8f5e8;
  137. border: 1px solid #4caf50;
  138. border-radius: 10px;
  139. padding: 15px;
  140. margin-top: 20px;
  141. }
  142. .status-display h4 {
  143. color: #2e7d32;
  144. margin-bottom: 10px;
  145. }
  146. .segment-controls {
  147. display: grid;
  148. grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  149. gap: 15px;
  150. margin-top: 15px;
  151. }
  152. .segment-item {
  153. background: white;
  154. padding: 15px;
  155. border-radius: 8px;
  156. border: 1px solid #ddd;
  157. text-align: center;
  158. }
  159. .segment-item h4 {
  160. color: #333;
  161. margin-bottom: 10px;
  162. }
  163. .loading {
  164. display: none;
  165. text-align: center;
  166. color: #666;
  167. font-style: italic;
  168. }
  169. .error {
  170. background: #ffebee;
  171. color: #c62828;
  172. padding: 10px;
  173. border-radius: 5px;
  174. margin: 10px 0;
  175. display: none;
  176. }
  177. .success {
  178. background: #e8f5e8;
  179. color: #2e7d32;
  180. padding: 10px;
  181. border-radius: 5px;
  182. margin: 10px 0;
  183. display: none;
  184. }
  185. </style>
  186. </head>
  187. <body>
  188. <div class="container">
  189. <div class="header">
  190. <h1>🎨 LED控制面板</h1>
  191. <p>通过Web界面控制您的LED灯带与Kodi播放</p>
  192. </div>
  193. <div class="content">
  194. <!-- 展品灯效控制 -->
  195. <div class="control-group">
  196. <h3>🎭 展品灯效控制</h3>
  197. <div style="margin-bottom: 15px;">
  198. <label for="exhibitId">展品ID (0开始):</label>
  199. <input type="number" id="exhibitId" min="0" value="0" style="margin-left: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 5px; width: 80px;">
  200. </div>
  201. <div class="button-group">
  202. <button class="btn btn-primary" onclick="startEffect()">启动灯效</button>
  203. <button class="btn btn-danger" onclick="stopEffect()">停止灯效</button>
  204. <button class="btn btn-warning" onclick="getStatus()">刷新状态</button>
  205. </div>
  206. </div>
  207. <!-- Kodi 播放控制 -->
  208. <div class="control-group" style="border-left-color:#ff6b6b;">
  209. <h3>🎬 Kodi 播放控制</h3>
  210. <div style="margin-bottom: 15px;">
  211. <label for="videoId">视频ID (0开始):</label>
  212. <input type="number" id="videoId" min="0" value="0" style="margin-left: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 5px; width: 80px;">
  213. </div>
  214. <div class="button-group">
  215. <button class="btn btn-primary" onclick="startKodi()">开始/切换播放</button>
  216. <button class="btn btn-warning" onclick="getKodiStatus()">刷新Kodi状态</button>
  217. </div>
  218. <div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #ddd;">
  219. <h4 style="color: #666; margin-bottom: 10px; font-size: 1.1em;">🔧 Kodi 系统控制</h4>
  220. <div class="button-group">
  221. <button class="btn btn-primary" onclick="revokeIndividualState()">撤销独立状态</button>
  222. <button class="btn btn-primary" onclick="startAllKodiApps()">启动所有Kodi应用</button>
  223. </div>
  224. </div>
  225. <div class="status-display" id="kodiStatusDisplay" style="margin-top:15px;">
  226. <h4>📊 Kodi 状态</h4>
  227. <div id="kodiStatusContent">点击"刷新Kodi状态"查看</div>
  228. </div>
  229. </div>
  230. <!-- 图片播放控制 -->
  231. <div class="control-group" style="border-left-color:#feca57;">
  232. <h3>🖼️ 图片播放控制</h3>
  233. <div style="margin-bottom: 15px;">
  234. <label for="kodiClientIndexImage">Kodi客户端索引 (0开始):</label>
  235. <input type="number" id="kodiClientIndexImage" min="0" value="0" style="margin-left: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 5px; width: 80px;">
  236. </div>
  237. <div style="margin-bottom: 15px;">
  238. <label>方式一:上传图片文件</label>
  239. <input type="file" id="imageFileInput" accept="image/*" style="margin-top: 10px; padding: 8px; width: 100%; border: 1px solid #ddd; border-radius: 5px;">
  240. </div>
  241. <div style="margin-bottom: 15px;">
  242. <label>方式二:输入图片URL</label>
  243. <input type="text" id="imageUrlInput" placeholder="请输入图片URL,例如: http://example.com/image.jpg" style="margin-top: 10px; padding: 8px; width: 100%; border: 1px solid #ddd; border-radius: 5px;">
  244. </div>
  245. <div class="button-group">
  246. <button class="btn btn-primary" onclick="playImage()">播放图片</button>
  247. </div>
  248. </div>
  249. <!-- RTSP视频播放控制 -->
  250. <div class="control-group" style="border-left-color:#667eea;">
  251. <h3>📹 RTSP视频播放控制</h3>
  252. <div style="margin-bottom: 15px;">
  253. <label for="kodiClientIndexRtsp">Kodi客户端索引 (0开始):</label>
  254. <input type="number" id="kodiClientIndexRtsp" min="0" value="0" style="margin-left: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 5px; width: 80px;">
  255. </div>
  256. <div style="margin-bottom: 15px;">
  257. <label for="rtspUrlInput">RTSP视频流URL:</label>
  258. <input type="text" id="rtspUrlInput" placeholder="请输入RTSP URL,例如: rtsp://example.com/stream" style="margin-top: 10px; padding: 8px; width: 100%; border: 1px solid #ddd; border-radius: 5px;">
  259. </div>
  260. <div style="margin-bottom: 15px;">
  261. <label for="rtspVolume">音量 (0-100):</label>
  262. <input type="number" id="rtspVolume" min="0" max="100" value="0" style="margin-left: 10px; padding: 8px; border: 1px solid #ddd; border-radius: 5px; width: 80px;">
  263. </div>
  264. <div class="button-group">
  265. <button class="btn btn-primary" onclick="playRtsp()">播放RTSP流</button>
  266. </div>
  267. </div>
  268. <!-- 灯效说明 -->
  269. <div class="control-group">
  270. <h3>📖 灯效说明</h3>
  271. <div style="background: #f0f8ff; padding: 15px; border-radius: 8px; border-left: 4px solid #4ecdc4;">
  272. <p><strong>灯效流程:</strong></p>
  273. <ol style="margin: 10px 0; padding-left: 20px;">
  274. <li>选择展品ID,点击"启动灯效"</li>
  275. <li>指定展品将显示白色呼吸灯效,其他展品保持静止</li>
  276. <li>10秒后,所有展品将随机选择以下灯效之一:</li>
  277. <ul style="margin: 5px 0; padding-left: 20px;">
  278. <li>随机波浪效果</li>
  279. <li>随机闪烁效果</li>
  280. <li>随机呼吸效果</li>
  281. </ul>
  282. <li>所有灯效都使用白色</li>
  283. </ol>
  284. </div>
  285. </div>
  286. <!-- 状态显示 -->
  287. <div class="status-display" id="statusDisplay">
  288. <h4>📊 当前状态</h4>
  289. <div id="statusContent">点击"刷新状态"查看当前LED状态</div>
  290. </div>
  291. <!-- 消息显示 -->
  292. <div class="error" id="errorMessage"></div>
  293. <div class="success" id="successMessage"></div>
  294. <div class="loading" id="loadingMessage">正在处理请求...</div>
  295. </div>
  296. </div>
  297. <script>
  298. // 全局变量
  299. let currentStatus = null;
  300. let currentKodiStatus = null;
  301. // 页面加载完成后初始化
  302. document.addEventListener('DOMContentLoaded', function() {
  303. getStatus();
  304. getKodiStatus();
  305. });
  306. // 显示消息
  307. function showMessage(message, type = 'success') {
  308. const errorDiv = document.getElementById('errorMessage');
  309. const successDiv = document.getElementById('successMessage');
  310. errorDiv.style.display = 'none';
  311. successDiv.style.display = 'none';
  312. if (type === 'error') {
  313. errorDiv.textContent = message;
  314. errorDiv.style.display = 'block';
  315. } else {
  316. successDiv.textContent = message;
  317. successDiv.style.display = 'block';
  318. }
  319. setTimeout(() => {
  320. errorDiv.style.display = 'none';
  321. successDiv.style.display = 'none';
  322. }, 3000);
  323. }
  324. // 显示加载状态
  325. function showLoading(show = true) {
  326. document.getElementById('loadingMessage').style.display = show ? 'block' : 'none';
  327. }
  328. // 获取LED状态
  329. async function getStatus() {
  330. try {
  331. showLoading(true);
  332. const response = await fetch('/api/led/status');
  333. const result = await response.json();
  334. if (result.success) {
  335. currentStatus = result.data;
  336. updateUI();
  337. showMessage('LED状态已刷新');
  338. } else {
  339. showMessage('获取LED状态失败: ' + result.message, 'error');
  340. }
  341. } catch (error) {
  342. showMessage('网络错误: ' + error.message, 'error');
  343. } finally {
  344. showLoading(false);
  345. }
  346. }
  347. // 更新UI显示
  348. function updateUI() {
  349. if (!currentStatus) return;
  350. const statusContent = document.getElementById('statusContent');
  351. statusContent.innerHTML = `
  352. <p><strong>运行状态:</strong> ${currentStatus.is_running ? '运行中' : '已停止'}</p>
  353. <p><strong>状态信息:</strong> ${currentStatus.message}</p>
  354. `;
  355. }
  356. // 启动灯效
  357. async function startEffect() {
  358. const exhibitId = parseInt(document.getElementById('exhibitId').value);
  359. if (isNaN(exhibitId) || exhibitId < 0) {
  360. showMessage('请输入有效的展品ID(大于等于0的整数)', 'error');
  361. return;
  362. }
  363. try {
  364. showLoading(true);
  365. const response = await fetch('/api/led/start', {
  366. method: 'POST',
  367. headers: { 'Content-Type': 'application/json' },
  368. body: JSON.stringify({ exhibit_id: exhibitId })
  369. });
  370. const result = await response.json();
  371. if (result.success) {
  372. showMessage(result.message);
  373. getStatus();
  374. } else {
  375. showMessage('操作失败: ' + result.message, 'error');
  376. }
  377. } catch (error) {
  378. showMessage('网络错误: ' + error.message, 'error');
  379. } finally {
  380. showLoading(false);
  381. }
  382. }
  383. // 停止灯效
  384. async function stopEffect() {
  385. try {
  386. showLoading(true);
  387. const response = await fetch('/api/led/stop', { method: 'POST', headers: { 'Content-Type': 'application/json' } });
  388. const result = await response.json();
  389. if (result.success) {
  390. showMessage(result.message);
  391. getStatus();
  392. } else {
  393. showMessage('操作失败: ' + result.message, 'error');
  394. }
  395. } catch (error) {
  396. showMessage('网络错误: ' + error.message, 'error');
  397. } finally {
  398. showLoading(false);
  399. }
  400. }
  401. // ===== Kodi 控制 =====
  402. async function getKodiStatus() {
  403. try {
  404. showLoading(true);
  405. const response = await fetch('/api/kodi/status');
  406. const result = await response.json();
  407. if (result.success) {
  408. currentKodiStatus = result.data;
  409. const el = document.getElementById('kodiStatusContent');
  410. el.innerHTML = `
  411. <p><strong>线程状态:</strong> ${currentKodiStatus.is_running ? '运行中' : '已停止'}</p>
  412. <p><strong>状态信息:</strong> ${currentKodiStatus.message}</p>
  413. `;
  414. showMessage('Kodi状态已刷新');
  415. } else {
  416. showMessage('获取Kodi状态失败: ' + result.message, 'error');
  417. }
  418. } catch (error) {
  419. showMessage('网络错误: ' + error.message, 'error');
  420. } finally {
  421. showLoading(false);
  422. }
  423. }
  424. async function startKodi() {
  425. const videoId = parseInt(document.getElementById('videoId').value);
  426. if (isNaN(videoId) || videoId < 0) {
  427. showMessage('请输入有效的视频ID(大于等于0的整数)', 'error');
  428. return;
  429. }
  430. try {
  431. showLoading(true);
  432. const response = await fetch('/api/kodi/start', {
  433. method: 'POST',
  434. headers: { 'Content-Type': 'application/json' },
  435. body: JSON.stringify({ video_id: videoId })
  436. });
  437. const result = await response.json();
  438. if (result.success) {
  439. showMessage(result.message);
  440. getKodiStatus();
  441. } else {
  442. showMessage('操作失败: ' + result.message, 'error');
  443. }
  444. } catch (error) {
  445. showMessage('网络错误: ' + error.message, 'error');
  446. } finally {
  447. showLoading(false);
  448. }
  449. }
  450. // ===== 图片播放控制 =====
  451. async function playImage() {
  452. const clientIndex = parseInt(document.getElementById('kodiClientIndexImage').value);
  453. if (isNaN(clientIndex) || clientIndex < 0) {
  454. showMessage('请输入有效的客户端索引(大于等于0的整数)', 'error');
  455. return;
  456. }
  457. const fileInput = document.getElementById('imageFileInput');
  458. const urlInput = document.getElementById('imageUrlInput').value.trim();
  459. // 检查是否选择了文件或输入了URL
  460. if (!fileInput.files || fileInput.files.length === 0) {
  461. if (!urlInput) {
  462. showMessage('请选择图片文件或输入图片URL', 'error');
  463. return;
  464. }
  465. }
  466. try {
  467. showLoading(true);
  468. let response;
  469. // 如果上传了文件,使用FormData
  470. if (fileInput.files && fileInput.files.length > 0) {
  471. const formData = new FormData();
  472. formData.append('file', fileInput.files[0]);
  473. formData.append('kodi_client_index', clientIndex);
  474. response = await fetch('/api/kodi/play_image', {
  475. method: 'POST',
  476. body: formData
  477. });
  478. } else {
  479. // 使用图片URL
  480. response = await fetch('/api/kodi/play_image', {
  481. method: 'POST',
  482. headers: { 'Content-Type': 'application/json' },
  483. body: JSON.stringify({
  484. image_url: urlInput,
  485. kodi_client_index: clientIndex
  486. })
  487. });
  488. }
  489. const result = await response.json();
  490. if (result.success) {
  491. showMessage(result.message);
  492. getKodiStatus();
  493. } else {
  494. showMessage('操作失败: ' + result.message, 'error');
  495. }
  496. } catch (error) {
  497. showMessage('网络错误: ' + error.message, 'error');
  498. } finally {
  499. showLoading(false);
  500. }
  501. }
  502. // ===== RTSP播放控制 =====
  503. async function playRtsp() {
  504. const clientIndex = parseInt(document.getElementById('kodiClientIndexRtsp').value);
  505. const rtspUrl = document.getElementById('rtspUrlInput').value.trim();
  506. const volume = parseInt(document.getElementById('rtspVolume').value);
  507. if (isNaN(clientIndex) || clientIndex < 0) {
  508. showMessage('请输入有效的客户端索引(大于等于0的整数)', 'error');
  509. return;
  510. }
  511. if (!rtspUrl) {
  512. showMessage('请输入RTSP视频流URL', 'error');
  513. return;
  514. }
  515. if (isNaN(volume) || volume < 0 || volume > 100) {
  516. showMessage('音量必须是0-100之间的整数', 'error');
  517. return;
  518. }
  519. try {
  520. showLoading(true);
  521. const response = await fetch('/api/kodi/play_rtsp', {
  522. method: 'POST',
  523. headers: { 'Content-Type': 'application/json' },
  524. body: JSON.stringify({
  525. rtsp_url: rtspUrl,
  526. kodi_client_index: clientIndex,
  527. volume: volume
  528. })
  529. });
  530. const result = await response.json();
  531. if (result.success) {
  532. showMessage(result.message);
  533. getKodiStatus();
  534. } else {
  535. showMessage('操作失败: ' + result.message, 'error');
  536. }
  537. } catch (error) {
  538. showMessage('网络错误: ' + error.message, 'error');
  539. } finally {
  540. showLoading(false);
  541. }
  542. }
  543. // ===== Kodi 系统控制 =====
  544. async function revokeIndividualState() {
  545. try {
  546. showLoading(true);
  547. const response = await fetch('/api/kodi/revoke_individual_state', {
  548. method: 'POST',
  549. headers: { 'Content-Type': 'application/json' }
  550. });
  551. const result = await response.json();
  552. if (result.success) {
  553. showMessage(result.message);
  554. getKodiStatus();
  555. } else {
  556. showMessage('操作失败: ' + result.message, 'error');
  557. }
  558. } catch (error) {
  559. showMessage('网络错误: ' + error.message, 'error');
  560. } finally {
  561. showLoading(false);
  562. }
  563. }
  564. async function startAllKodiApps() {
  565. try {
  566. showLoading(true);
  567. const response = await fetch('/api/kodi/start_all_apps', {
  568. method: 'POST',
  569. headers: { 'Content-Type': 'application/json' }
  570. });
  571. const result = await response.json();
  572. if (result.success) {
  573. showMessage(result.message);
  574. getKodiStatus();
  575. } else {
  576. showMessage('操作失败: ' + result.message, 'error');
  577. }
  578. } catch (error) {
  579. showMessage('网络错误: ' + error.message, 'error');
  580. } finally {
  581. showLoading(false);
  582. }
  583. }
  584. </script>
  585. </body>
  586. </html>