test-marker.html 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  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>3D 地形标记点测试</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. }
  13. body {
  14. font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  15. background: #1a1a2e;
  16. color: #eee;
  17. min-height: 100vh;
  18. }
  19. .container {
  20. display: flex;
  21. height: 100vh;
  22. }
  23. .sidebar {
  24. width: 320px;
  25. background: #16213e;
  26. padding: 20px;
  27. display: flex;
  28. flex-direction: column;
  29. gap: 16px;
  30. border-right: 1px solid #0f3460;
  31. overflow-y: auto;
  32. }
  33. .iframe-wrapper {
  34. flex: 1;
  35. background: #000;
  36. }
  37. iframe {
  38. width: 100%;
  39. height: 100%;
  40. border: none;
  41. }
  42. h2 {
  43. font-size: 18px;
  44. color: #00d9ff;
  45. margin-bottom: 8px;
  46. }
  47. .section {
  48. background: #1a1a2e;
  49. border-radius: 8px;
  50. padding: 16px;
  51. }
  52. .section h3 {
  53. font-size: 14px;
  54. color: #00d9ff;
  55. margin-bottom: 12px;
  56. text-transform: uppercase;
  57. letter-spacing: 1px;
  58. }
  59. .btn {
  60. display: block;
  61. width: 100%;
  62. padding: 10px 16px;
  63. margin-bottom: 8px;
  64. border: none;
  65. border-radius: 6px;
  66. cursor: pointer;
  67. font-size: 13px;
  68. font-weight: 500;
  69. transition: all 0.2s;
  70. }
  71. .btn-primary {
  72. background: #00d9ff;
  73. color: #1a1a2e;
  74. }
  75. .btn-primary:hover {
  76. background: #00b8d9;
  77. }
  78. .btn-success {
  79. background: #00c853;
  80. color: #fff;
  81. }
  82. .btn-success:hover {
  83. background: #00a043;
  84. }
  85. .btn-danger {
  86. background: #ff5252;
  87. color: #fff;
  88. }
  89. .btn-danger:hover {
  90. background: #d32f2f;
  91. }
  92. .btn-warning {
  93. background: #ffab00;
  94. color: #1a1a2e;
  95. }
  96. .btn-warning:hover {
  97. background: #ff8f00;
  98. }
  99. .btn-secondary {
  100. background: #5c6bc0;
  101. color: #fff;
  102. }
  103. .btn-secondary:hover {
  104. background: #3f51b5;
  105. }
  106. .form-group {
  107. margin-bottom: 12px;
  108. }
  109. .form-group label {
  110. display: block;
  111. font-size: 12px;
  112. color: #888;
  113. margin-bottom: 4px;
  114. }
  115. .form-group input {
  116. width: 100%;
  117. padding: 8px 12px;
  118. border: 1px solid #0f3460;
  119. border-radius: 4px;
  120. background: #0f3460;
  121. color: #eee;
  122. font-size: 13px;
  123. }
  124. .form-group input:focus {
  125. outline: none;
  126. border-color: #00d9ff;
  127. }
  128. .log-section {
  129. flex: 1;
  130. min-height: 200px;
  131. }
  132. .log-container {
  133. background: #0a0a15;
  134. border-radius: 4px;
  135. padding: 10px;
  136. height: 200px;
  137. overflow-y: auto;
  138. font-family: 'Monaco', 'Menlo', monospace;
  139. font-size: 11px;
  140. }
  141. .log-entry {
  142. padding: 4px 0;
  143. border-bottom: 1px solid #1a1a2e;
  144. word-break: break-all;
  145. }
  146. .log-entry.send {
  147. color: #00c853;
  148. }
  149. .log-entry.receive {
  150. color: #00d9ff;
  151. }
  152. .log-entry.click {
  153. color: #ffab00;
  154. }
  155. .log-entry.error {
  156. color: #ff5252;
  157. }
  158. .color-picker {
  159. display: flex;
  160. gap: 8px;
  161. flex-wrap: wrap;
  162. }
  163. .color-btn {
  164. width: 28px;
  165. height: 28px;
  166. border-radius: 4px;
  167. border: 2px solid transparent;
  168. cursor: pointer;
  169. }
  170. .color-btn.active {
  171. border-color: #fff;
  172. }
  173. </style>
  174. </head>
  175. <body>
  176. <div class="container">
  177. <div class="sidebar">
  178. <div class="section">
  179. <h2>3D 地形标记测试</h2>
  180. <p style="font-size: 12px; color: #888;">通过 iframe 消息控制地形标记点</p>
  181. </div>
  182. <div class="section">
  183. <h3>添加标记点</h3>
  184. <div class="form-group">
  185. <label>经度 (lon)</label>
  186. <input type="number" id="lon" value="113.725" step="0.001">
  187. </div>
  188. <div class="form-group">
  189. <label>纬度 (lat)</label>
  190. <input type="number" id="lat" value="24.570" step="0.001">
  191. </div>
  192. <div class="form-group">
  193. <label>高度 (alt)</label>
  194. <input type="number" id="alt" value="100" step="10">
  195. </div>
  196. <div class="form-group">
  197. <label>标题</label>
  198. <input type="text" id="title" value="测试点">
  199. </div>
  200. <div class="form-group">
  201. <label>类型</label>
  202. <select id="markerType" onchange="onTypeChange()" style="width: 100%; padding: 8px 12px; border: 1px solid #0f3460; border-radius: 4px; background: #0f3460; color: #eee; font-size: 13px;">
  203. <option value="">普通标记</option>
  204. <option value="video">视频 (video)</option>
  205. <option value="html">HTML (html)</option>
  206. <option value="device">设备 (device)</option>
  207. </select>
  208. </div>
  209. <!-- 通用字段 -->
  210. <div class="form-group">
  211. <label>内容</label>
  212. <input type="text" id="content" value="这是一个测试标记点">
  213. </div>
  214. <!-- video 字段 -->
  215. <div class="type-fields" id="videoFields" style="display:none">
  216. <div class="form-group">
  217. <label>视频 URL (videoUrl)</label>
  218. <input type="text" id="videoUrl" value="https://www.w3schools.com/html/mov_bbb.mp4">
  219. </div>
  220. <div class="form-group">
  221. <label>iframe URL (iframeUrl,优先级低于 videoUrl)</label>
  222. <input type="text" id="iframeUrl" value="">
  223. </div>
  224. </div>
  225. <!-- html 字段 -->
  226. <div class="type-fields" id="htmlFields" style="display:none">
  227. <div class="form-group">
  228. <label>HTML 内容 (htmlContent)</label>
  229. <input type="text" id="htmlContent" value="<div style='color:#22c55e'>自定义 HTML</div>">
  230. </div>
  231. <div class="form-group">
  232. <label>HTML 页面 URL (htmlUrl,优先级低于 htmlContent)</label>
  233. <input type="text" id="htmlUrl" value="">
  234. </div>
  235. </div>
  236. <!-- device 字段 -->
  237. <div class="type-fields" id="deviceFields" style="display:none">
  238. <div class="form-group">
  239. <label>状态 (status)</label>
  240. <input type="text" id="status" value="正常">
  241. </div>
  242. <div class="form-group">
  243. <label>指标 (metrics JSON)</label>
  244. <input type="text" id="metrics" value='[{"label":"温度","value":24,"unit":"°C"},{"label":"湿度","value":62,"unit":"%"}]'>
  245. </div>
  246. </div>
  247. <div class="form-group">
  248. <label>颜色</label>
  249. <div class="color-picker">
  250. <div class="color-btn active" style="background: #ff4444" data-color="#ff4444"></div>
  251. <div class="color-btn" style="background: #44ff88" data-color="#44ff88"></div>
  252. <div class="color-btn" style="background: #4488ff" data-color="#4488ff"></div>
  253. <div class="color-btn" style="background: #ffaa44" data-color="#ffaa44"></div>
  254. <div class="color-btn" style="background: #aa44ff" data-color="#aa44ff"></div>
  255. <div class="color-btn" style="background: #ffffff" data-color="#ffffff"></div>
  256. </div>
  257. </div>
  258. <button class="btn btn-primary" onclick="addMarker()">添加标记点</button>
  259. <button class="btn btn-success" onclick="addRandomMarker()">添加随机标记</button>
  260. </div>
  261. <div class="section">
  262. <h3>标记点管理</h3>
  263. <button class="btn btn-warning" onclick="flyToMarker()">飞行到标记点</button>
  264. <button class="btn btn-danger" onclick="clearMarkers()">清空所有标记</button>
  265. </div>
  266. <div class="section log-section">
  267. <h3>消息日志</h3>
  268. <button class="btn btn-secondary" onclick="clearLog()">清空日志</button>
  269. <div class="log-container" id="logContainer"></div>
  270. </div>
  271. </div>
  272. <div class="iframe-wrapper">
  273. <iframe id="terrainFrame" src="http://192.168.8.48:5173" allow="fullscreen"></iframe>
  274. </div>
  275. </div>
  276. <script>
  277. const iframe = document.getElementById('terrainFrame')
  278. let selectedColor = '#ff4444'
  279. // 颜色选择
  280. document.querySelectorAll('.color-btn').forEach(btn => {
  281. btn.addEventListener('click', () => {
  282. document.querySelectorAll('.color-btn').forEach(b => b.classList.remove('active'))
  283. btn.classList.add('active')
  284. selectedColor = btn.dataset.color
  285. })
  286. })
  287. // 类型切换
  288. function onTypeChange() {
  289. const type = document.getElementById('markerType').value
  290. document.querySelectorAll('.type-fields').forEach(el => el.style.display = 'none')
  291. if (type) document.getElementById(type + 'Fields').style.display = 'block'
  292. }
  293. // 添加标记点
  294. function addMarker() {
  295. const markerType = document.getElementById('markerType').value
  296. const data = {
  297. type: 'addMarker',
  298. lon: parseFloat(document.getElementById('lon').value),
  299. lat: parseFloat(document.getElementById('lat').value),
  300. alt: parseFloat(document.getElementById('alt').value),
  301. title: document.getElementById('title').value,
  302. content: document.getElementById('content').value,
  303. color: selectedColor,
  304. markerType: markerType
  305. }
  306. if (markerType === 'video') {
  307. const videoUrl = document.getElementById('videoUrl').value.trim()
  308. const iframeUrl = document.getElementById('iframeUrl').value.trim()
  309. if (videoUrl) data.videoUrl = videoUrl
  310. if (iframeUrl) data.iframeUrl = iframeUrl
  311. } else if (markerType === 'html') {
  312. const htmlContent = document.getElementById('htmlContent').value.trim()
  313. const htmlUrl = document.getElementById('htmlUrl').value.trim()
  314. if (htmlContent) data.htmlContent = htmlContent
  315. if (htmlUrl) data.htmlUrl = htmlUrl
  316. } else if (markerType === 'device') {
  317. data.status = document.getElementById('status').value
  318. try {
  319. const metricsStr = document.getElementById('metrics').value.trim()
  320. if (metricsStr) data.metrics = JSON.parse(metricsStr)
  321. } catch (e) {
  322. log('error', '指标 JSON 解析失败: ' + e.message)
  323. return
  324. }
  325. }
  326. iframe.contentWindow.postMessage(data, '*')
  327. log('send', `添加标记 [${markerType || '默认'}]: ${data.title} (${data.lon}, ${data.lat})`)
  328. }
  329. // 添加随机标记
  330. function addRandomMarker() {
  331. const lon = 113.720 + Math.random() * 0.015
  332. const lat = 24.565 + Math.random() * 0.015
  333. const colors = ['#ff4444', '#44ff88', '#4488ff', '#ffaa44', '#aa44ff']
  334. const types = ['video', 'device', 'html', '']
  335. const type = types[Math.floor(Math.random() * types.length)]
  336. const titles = {
  337. video: '视频监控',
  338. device: '设备监测',
  339. html: 'HTML面板',
  340. '': '普通标记'
  341. }
  342. const data = {
  343. type: 'addMarker',
  344. lon: lon,
  345. lat: lat,
  346. alt: Math.floor(Math.random() * 200),
  347. title: titles[type] + ' #' + Math.floor(Math.random() * 100),
  348. content: `随机标记点 #${Math.floor(Math.random() * 1000)}`,
  349. color: colors[Math.floor(Math.random() * colors.length)],
  350. markerType: type
  351. }
  352. if (type === 'video') {
  353. data.videoUrl = 'https://www.w3schools.com/html/mov_bbb.mp4'
  354. } else if (type === 'html') {
  355. data.htmlContent = '<div style="color:#22c55e;font-weight:600">随机 HTML 内容</div>'
  356. } else if (type === 'device') {
  357. data.status = Math.random() > 0.5 ? '正常' : '警告'
  358. data.metrics = [
  359. { label: '温度', value: Math.floor(Math.random() * 40), unit: '°C' },
  360. { label: '湿度', value: Math.floor(Math.random() * 100), unit: '%' }
  361. ]
  362. }
  363. iframe.contentWindow.postMessage(data, '*')
  364. log('send', `添加随机标记 [${type || '默认'}]: ${data.title} (${data.lon.toFixed(4)}, ${data.lat.toFixed(4)})`)
  365. }
  366. // 飞行到标记点
  367. function flyToMarker() {
  368. const data = {
  369. type: 'flyTo',
  370. lon: parseFloat(document.getElementById('lon').value),
  371. lat: parseFloat(document.getElementById('lat').value)
  372. }
  373. iframe.contentWindow.postMessage(data, '*')
  374. log('send', `飞行到: (${data.lon}, ${data.lat})`)
  375. }
  376. // 清空所有标记
  377. function clearMarkers() {
  378. iframe.contentWindow.postMessage({ type: 'clearMarkers' }, '*')
  379. log('send', '清空所有标记')
  380. }
  381. // 日志
  382. function log(type, message) {
  383. const container = document.getElementById('logContainer')
  384. const entry = document.createElement('div')
  385. entry.className = `log-entry ${type}`
  386. const time = new Date().toLocaleTimeString()
  387. entry.textContent = `[${time}] ${message}`
  388. container.insertBefore(entry, container.firstChild)
  389. }
  390. // 清空日志
  391. function clearLog() {
  392. document.getElementById('logContainer').innerHTML = ''
  393. }
  394. // 监听来自 iframe 的消息
  395. window.addEventListener('message', (event) => {
  396. const data = event.data
  397. if (data.type === 'markerClick') {
  398. log('click', `点击标记 [${data.markerType || '默认'}]: ${data.title} (${data.lon}, ${data.lat})`)
  399. }
  400. })
  401. </script>
  402. </body>
  403. </html>