home2.html 65 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280
  1. 
  2. <!DOCTYPE html>
  3. <html lang="zh-CN">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>油气回收在线监控系统</title>
  8. <script src="https://cdn.tailwindcss.com"></script>
  9. <script src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"></script>
  10. <link href="https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700&f[]=jetbrains-mono@400,700&display=swap" rel="stylesheet">
  11. <script>
  12. tailwind.config = {
  13. theme: {
  14. extend: {
  15. fontFamily: {
  16. sans: ['Satoshi', 'sans-serif'],
  17. mono: ['JetBrains Mono', 'monospace'],
  18. },
  19. colors: {
  20. system: {
  21. bg: '#f8fafc', // Light theme background
  22. card: '#ffffff', // Light theme card
  23. border: '#e2e8f0', // Light theme border
  24. normal: '#10b981', // Emerald 500
  25. warning: '#f59e0b', // Amber 500
  26. alarm: '#ef4444', // Red 500
  27. text: '#0f172a', // Dark text for light bg
  28. muted: '#64748b' // Muted text
  29. }
  30. },
  31. animation: {
  32. 'flow-up': 'flow-up 1s linear infinite',
  33. 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
  34. 'spin-slow': 'spin 3s linear infinite',
  35. },
  36. keyframes: {
  37. 'flow-up': {
  38. '0%': { transform: 'translateY(10px)', opacity: '0' },
  39. '50%': { opacity: '1' },
  40. '100%': { transform: 'translateY(-10px)', opacity: '0' }
  41. }
  42. }
  43. }
  44. }
  45. }
  46. </script>
  47. <style>
  48. /* Custom Scrollbar for light dashboard feel */
  49. ::-webkit-scrollbar {
  50. width: 6px;
  51. height: 6px;
  52. }
  53. ::-webkit-scrollbar-track {
  54. background: #f1f5f9;
  55. }
  56. ::-webkit-scrollbar-thumb {
  57. background: #cbd5e1;
  58. border-radius: 3px;
  59. }
  60. ::-webkit-scrollbar-thumb:hover {
  61. background: #94a3b8;
  62. }
  63. /* Glowing effect for status indicators - adjusted for light mode */
  64. .glow-normal {
  65. box-shadow: 0 0 12px rgba(16, 185, 129, 0.3);
  66. }
  67. .glow-warning {
  68. box-shadow: 0 0 12px rgba(245, 158, 11, 0.4);
  69. }
  70. .glow-alarm {
  71. box-shadow: 0 0 16px rgba(239, 68, 68, 0.4);
  72. }
  73. </style>
  74. <meta name="preview-version" content="6b5723dc-a31d-45f5-8dff-da5335066aea" />
  75. <meta name="preview-timestamp" content="2026-03-04T05:55:35.649Z" />
  76. <style>
  77. html::-webkit-scrollbar, body::-webkit-scrollbar, *::-webkit-scrollbar {
  78. display: none !important;
  79. width: 0 !important;
  80. height: 0 !important;
  81. }
  82. html, body, * {
  83. -ms-overflow-style: none !important;
  84. scrollbar-width: none !important;
  85. }
  86. </style>
  87. <style>
  88. body:not(.sd-ready) {
  89. opacity: 0 !important;
  90. }
  91. body.sd-ready {
  92. opacity: 1 !important;
  93. transition: opacity 0.1s ease-in;
  94. }
  95. sd-component {
  96. display: block;
  97. }
  98. </style>
  99. <script type="module">
  100. (function() {
  101. document.addEventListener('wheel', function(e) {
  102. if (e.ctrlKey) e.preventDefault();
  103. }, { passive: false });
  104. })();
  105. </script>
  106. <script type="module">
  107. // SuperDesign Runtime
  108. console.log('[SuperDesign] Preview loaded for versionId:', "6b5723dc-a31d-45f5-8dff-da5335066aea");
  109. window.__SUPERDESIGN_PREVIEW__ = {
  110. versionId: "6b5723dc-a31d-45f5-8dff-da5335066aea",
  111. timestamp: "2026-03-04T05:55:35.649Z",
  112. _modernScreenshotModule: null,
  113. sendMessage: function(type, data) {
  114. if (window.parent) {
  115. window.parent.postMessage({
  116. source: 'superdesign-preview',
  117. type: type,
  118. data: data,
  119. versionId: this.versionId
  120. }, '*');
  121. }
  122. },
  123. ready: function() {
  124. this.sendMessage('ready', { timestamp: Date.now() });
  125. },
  126. _captureScreenshotInternal: async function() {
  127. if (!this._modernScreenshotModule) {
  128. console.log('[SuperDesign] Loading modern-screenshot library...');
  129. this._modernScreenshotModule = await import('https://esm.sh/modern-screenshot@4.6.6');
  130. }
  131. var modernScreenshot = this._modernScreenshotModule;
  132. if (!modernScreenshot || !modernScreenshot.domToCanvas) {
  133. throw new Error('modern-screenshot library not loaded');
  134. }
  135. var scrollX = window.scrollX || window.pageXOffset;
  136. var scrollY = window.scrollY || window.pageYOffset;
  137. var viewportWidth = window.innerWidth;
  138. var viewportHeight = window.innerHeight;
  139. var documentWidth = Math.max(
  140. document.documentElement.scrollWidth,
  141. document.documentElement.offsetWidth,
  142. document.documentElement.clientWidth,
  143. document.body.scrollWidth,
  144. document.body.offsetWidth
  145. );
  146. var documentHeight = Math.max(
  147. document.documentElement.scrollHeight,
  148. document.documentElement.offsetHeight,
  149. document.documentElement.clientHeight,
  150. document.body.scrollHeight,
  151. document.body.offsetHeight
  152. );
  153. var isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
  154. var pixelRatio = isMobile ? 1 : (window.devicePixelRatio || 1);
  155. var dataUrl;
  156. if (isMobile) {
  157. var wrapper = document.createElement('div');
  158. wrapper.style.cssText = 'position:fixed;top:0;left:0;width:' + viewportWidth + 'px;height:' + viewportHeight + 'px;overflow:hidden;z-index:999999;pointer-events:none;';
  159. var clone = document.body.cloneNode(true);
  160. clone.style.cssText = 'position:absolute;top:' + (-scrollY) + 'px;left:' + (-scrollX) + 'px;margin:0;width:' + documentWidth + 'px;';
  161. wrapper.appendChild(clone);
  162. document.body.appendChild(wrapper);
  163. try {
  164. var viewportCanvas = await modernScreenshot.domToCanvas(wrapper, {
  165. scale: 1, backgroundColor: '#ffffff',
  166. width: viewportWidth, height: viewportHeight,
  167. });
  168. dataUrl = viewportCanvas.toDataURL('image/png', 0.5);
  169. } finally {
  170. document.body.removeChild(wrapper);
  171. }
  172. } else {
  173. var fullCanvas = await modernScreenshot.domToCanvas(document.documentElement, {
  174. scale: pixelRatio, backgroundColor: '#ffffff',
  175. width: documentWidth, height: documentHeight,
  176. });
  177. var viewportCanvas = document.createElement('canvas');
  178. viewportCanvas.width = viewportWidth * pixelRatio;
  179. viewportCanvas.height = viewportHeight * pixelRatio;
  180. var ctx = viewportCanvas.getContext('2d');
  181. if (!ctx) throw new Error('Failed to get canvas context');
  182. ctx.drawImage(
  183. fullCanvas,
  184. scrollX * pixelRatio, scrollY * pixelRatio,
  185. viewportWidth * pixelRatio, viewportHeight * pixelRatio,
  186. 0, 0, viewportWidth * pixelRatio, viewportHeight * pixelRatio
  187. );
  188. dataUrl = viewportCanvas.toDataURL('image/png', 1.0);
  189. }
  190. if (!dataUrl || !dataUrl.startsWith('data:image/png;base64,') || dataUrl.length < 100) {
  191. throw new Error('Invalid screenshot data');
  192. }
  193. return {
  194. dataUrl: dataUrl,
  195. viewportWidth: viewportWidth,
  196. viewportHeight: viewportHeight,
  197. pixelRatio: pixelRatio
  198. };
  199. },
  200. autoCapture: async function() {
  201. if (window === window.parent) return;
  202. try {
  203. var result = await this._captureScreenshotInternal();
  204. this.sendMessage('auto-screenshot', {
  205. dataUrl: result.dataUrl,
  206. timestamp: Date.now(),
  207. viewportWidth: result.viewportWidth,
  208. viewportHeight: result.viewportHeight,
  209. pixelRatio: result.pixelRatio,
  210. });
  211. } catch (error) {
  212. console.error('[SuperDesign] Auto-capture failed:', error);
  213. }
  214. },
  215. captureScreenshot: async function(requestId, autoCaptureId) {
  216. try {
  217. this._currentRequestId = requestId;
  218. this._currentAutoCaptureId = autoCaptureId;
  219. var result = await this._captureScreenshotInternal();
  220. this.sendMessage('screenshot-captured', {
  221. dataUrl: result.dataUrl,
  222. timestamp: Date.now(),
  223. viewportWidth: result.viewportWidth,
  224. viewportHeight: result.viewportHeight,
  225. pixelRatio: result.pixelRatio,
  226. requestId: this._currentRequestId,
  227. autoCaptureId: this._currentAutoCaptureId
  228. });
  229. return result.dataUrl;
  230. } catch (error) {
  231. console.error('[SuperDesign] Screenshot capture failed:', error);
  232. this.sendMessage('screenshot-error', {
  233. error: error.message || String(error),
  234. timestamp: Date.now(),
  235. requestId: this._currentRequestId,
  236. autoCaptureId: this._currentAutoCaptureId
  237. });
  238. throw error;
  239. }
  240. }
  241. };
  242. // Font loading helpers
  243. window.__SUPERDESIGN_PREVIEW__.loadedFonts = new Set();
  244. window.__SUPERDESIGN_PREVIEW__.loadGoogleFont = function(fontFamily) {
  245. if (!fontFamily || this.loadedFonts.has(fontFamily)) return;
  246. if (fontFamily.startsWith('Custom-')) return;
  247. var systemFonts = ['system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI',
  248. 'Roboto', 'Helvetica', 'Arial', 'sans-serif', 'Georgia', 'Times New Roman',
  249. 'Times', 'serif', 'Menlo', 'Monaco', 'Consolas', 'Courier New', 'monospace'];
  250. if (systemFonts.some(function(sf) { return fontFamily.toLowerCase().includes(sf.toLowerCase()); })) return;
  251. if (!document.querySelector('link[href*="fonts.googleapis.com"]')) {
  252. var preconnect1 = document.createElement('link');
  253. preconnect1.rel = 'preconnect';
  254. preconnect1.href = 'https://fonts.googleapis.com';
  255. document.head.appendChild(preconnect1);
  256. var preconnect2 = document.createElement('link');
  257. preconnect2.rel = 'preconnect';
  258. preconnect2.href = 'https://fonts.gstatic.com';
  259. preconnect2.crossOrigin = 'anonymous';
  260. document.head.appendChild(preconnect2);
  261. }
  262. var linkId = 'google-font-' + fontFamily.replace(/\\s+/g, '-').toLowerCase();
  263. if (!document.getElementById(linkId)) {
  264. var link = document.createElement('link');
  265. link.id = linkId;
  266. link.rel = 'stylesheet';
  267. link.href = 'https://fonts.googleapis.com/css2?family=' + encodeURIComponent(fontFamily) + ':wght@400;500;600;700&display=swap';
  268. link.onerror = function() { console.error('[SuperDesign] Failed to load Google Font:', fontFamily); };
  269. document.head.appendChild(link);
  270. }
  271. this.loadedFonts.add(fontFamily);
  272. };
  273. window.__SUPERDESIGN_PREVIEW__.loadedCustomFonts = new Set();
  274. window.__SUPERDESIGN_PREVIEW__.loadCustomFont = function(fontInfo) {
  275. if (!fontInfo || !fontInfo.family || !fontInfo.url) return;
  276. if (this.loadedCustomFonts.has(fontInfo.family)) return;
  277. var styleId = 'custom-font-' + fontInfo.family.replace(/[^a-zA-Z0-9]/g, '-');
  278. if (document.getElementById(styleId)) return;
  279. var style = document.createElement('style');
  280. style.id = styleId;
  281. style.textContent = '@font-face { ' +
  282. 'font-family: "' + fontInfo.family + '"; ' +
  283. 'src: url("' + fontInfo.url + '") format("' + (fontInfo.format || 'woff2') + '"); ' +
  284. 'font-weight: 100 900; font-style: normal; font-display: swap; }';
  285. document.head.appendChild(style);
  286. this.loadedCustomFonts.add(fontInfo.family);
  287. };
  288. // Theme sync
  289. window.__SUPERDESIGN_PREVIEW__.applyTheme = function(payload, isDark) {
  290. var root = document.documentElement;
  291. var colorVars = isDark ? payload.dark : payload.light;
  292. for (var name in colorVars) {
  293. if (colorVars.hasOwnProperty(name)) {
  294. root.style.setProperty(name, colorVars[name]);
  295. }
  296. }
  297. if (payload.customFonts && Array.isArray(payload.customFonts)) {
  298. payload.customFonts.forEach(function(fontInfo) {
  299. window.__SUPERDESIGN_PREVIEW__.loadCustomFont(fontInfo);
  300. });
  301. }
  302. if (payload.typography) {
  303. var t = payload.typography;
  304. if (t.fontSans && !t.fontSans.startsWith('Custom-')) window.__SUPERDESIGN_PREVIEW__.loadGoogleFont(t.fontSans);
  305. if (t.fontSerif && !t.fontSerif.startsWith('Custom-')) window.__SUPERDESIGN_PREVIEW__.loadGoogleFont(t.fontSerif);
  306. if (t.fontMono && !t.fontMono.startsWith('Custom-')) window.__SUPERDESIGN_PREVIEW__.loadGoogleFont(t.fontMono);
  307. var formatFontValue = function(ff) {
  308. if (!ff) return ff;
  309. var first = ff.split(',')[0].trim().replace(/^["']|["']$/g, '');
  310. return first.startsWith('var(') ? first : '"' + first + '"';
  311. };
  312. root.style.setProperty('--font-sans', formatFontValue(t.fontSans));
  313. root.style.setProperty('--font-serif', formatFontValue(t.fontSerif));
  314. root.style.setProperty('--font-mono', formatFontValue(t.fontMono));
  315. root.style.setProperty('--font-size', t.fontSize);
  316. root.style.setProperty('--line-height', t.lineHeight);
  317. root.style.setProperty('--letter-spacing-body', t.letterSpacing);
  318. root.style.setProperty('--letter-spacing-heading', t.headingLetterSpacing);
  319. root.style.setProperty('--font-weight-regular', t.fontWeightRegular);
  320. root.style.setProperty('--font-weight-bold', t.fontWeightBold);
  321. }
  322. if (payload.effects) {
  323. root.style.setProperty('--radius', payload.effects.radius);
  324. root.style.setProperty('--shadow', payload.effects.shadow);
  325. }
  326. if (isDark) { root.classList.add('dark'); } else { root.classList.remove('dark'); }
  327. };
  328. // Message listener
  329. window.addEventListener('message', function(event) {
  330. if (event.data && event.data.type === 'request-screenshot') {
  331. if (window.__SUPERDESIGN_PREVIEW__) {
  332. window.__SUPERDESIGN_PREVIEW__.captureScreenshot(event.data.requestId, event.data.autoCaptureId);
  333. }
  334. }
  335. if (event.data && event.data.type === 'superdesign-theme-update') {
  336. if (window.__SUPERDESIGN_PREVIEW__ && event.data.payload) {
  337. var isDark = event.data.isDark || false;
  338. window.__SUPERDESIGN_PREVIEW__.applyTheme(event.data.payload, isDark);
  339. window.__SUPERDESIGN_PREVIEW__.sendMessage('theme-applied', { timestamp: Date.now(), isDark: isDark });
  340. }
  341. }
  342. if (event.data && event.data.type === 'navigate-to-route') {
  343. var route = event.data.route;
  344. if (route) {
  345. if (route === '/') {
  346. if (window.location.hash) {
  347. history.pushState(null, '', window.location.pathname + window.location.search);
  348. window.dispatchEvent(new HashChangeEvent('hashchange'));
  349. }
  350. } else {
  351. var newHash = '#' + route;
  352. if (window.location.hash !== newHash) {
  353. window.location.hash = route;
  354. }
  355. }
  356. }
  357. }
  358. });
  359. // Page lifecycle
  360. if (document.readyState === 'loading') {
  361. document.addEventListener('DOMContentLoaded', function() { window.__SUPERDESIGN_PREVIEW__.ready(); });
  362. } else {
  363. window.__SUPERDESIGN_PREVIEW__.ready();
  364. }
  365. function onPageFullyLoaded() {
  366. requestAnimationFrame(function() {
  367. requestAnimationFrame(function() {
  368. setTimeout(function() {
  369. window.__SUPERDESIGN_PREVIEW__.sendMessage('loaded', { timestamp: Date.now() });
  370. }, 1500);
  371. // Auto-capture disabled for this preview type
  372. });
  373. });
  374. }
  375. if (document.readyState === 'complete') {
  376. onPageFullyLoaded();
  377. } else {
  378. window.addEventListener('load', onPageFullyLoaded);
  379. }
  380. </script>
  381. <script src="https://cdn.jsdelivr.net/npm/petite-vue@0.4.1/dist/petite-vue.iife.js"></script>
  382. </head>
  383. <body>
  384. <!-- Root Container -->
  385. <div class="min-h-screen bg-system-bg text-system-text font-sans flex flex-col overflow-hidden selection:bg-system-normal/20">
  386. <!-- 1. Header Section -->
  387. <header class="h-16 bg-white/90 backdrop-blur-md border-b border-system-border flex items-center justify-between px-6 z-20 shrink-0 shadow-sm">
  388. <div class="flex items-center gap-4">
  389. <div class="w-10 h-10 rounded-lg bg-gradient-to-br from-system-normal to-emerald-600 flex items-center justify-center shadow-md shadow-system-normal/20">
  390. <iconify-icon icon="mdi:leaf" class="text-2xl text-white"></iconify-icon>
  391. </div>
  392. <h1 class="text-xl tracking-wide font-bold text-slate-800">
  393. 油气回收在线监控系统
  394. </h1>
  395. </div>
  396. <!-- Global System Status Summary -->
  397. <div class="flex items-center gap-6 bg-slate-50 px-5 py-2 rounded-full border border-slate-200">
  398. <div class="flex items-center gap-2">
  399. <span class="relative flex h-3 w-3">
  400. <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-system-normal opacity-75"></span>
  401. <span class="relative inline-flex rounded-full h-3 w-3 bg-system-normal"></span>
  402. </span>
  403. <span class="text-sm text-slate-600">正常: <span class="font-mono font-bold text-system-normal">88</span></span>
  404. </div>
  405. <div class="w-px h-4 bg-slate-300"></div>
  406. <div class="flex items-center gap-2">
  407. <span class="relative flex h-3 w-3">
  408. <span class="relative inline-flex rounded-full h-3 w-3 bg-system-warning"></span>
  409. </span>
  410. <span class="text-sm text-slate-600">预警: <span class="font-mono font-bold text-system-warning">2</span></span>
  411. </div>
  412. <div class="w-px h-4 bg-slate-300"></div>
  413. <div class="flex items-center gap-2">
  414. <span class="relative flex h-3 w-3">
  415. <span class="animate-pulse absolute inline-flex h-full w-full rounded-full bg-system-alarm opacity-75"></span>
  416. <span class="relative inline-flex rounded-full h-3 w-3 bg-system-alarm"></span>
  417. </span>
  418. <span class="text-sm text-slate-600">报警: <span class="font-mono font-bold text-system-alarm">2</span></span>
  419. </div>
  420. </div>
  421. <!-- Clock & User -->
  422. <div class="flex items-center gap-6">
  423. <div class="flex flex-col items-end">
  424. <span class="font-mono text-lg font-bold text-slate-800 tracking-wider">14:32:45</span>
  425. <span class="text-xs text-system-muted font-mono">2023-10-27 星期五</span>
  426. </div>
  427. <button class="w-10 h-10 rounded-full bg-slate-50 border border-slate-200 flex items-center justify-center hover:bg-slate-100 transition-colors">
  428. <iconify-icon icon="mdi:account-cog" class="text-xl text-slate-600"></iconify-icon>
  429. </button>
  430. </div>
  431. </header>
  432. <!-- 2. Top Status Bar (Key Metrics & Remote Platforms) -->
  433. <section class="px-6 py-4 grid grid-cols-12 gap-4 shrink-0 z-10">
  434. <!-- Key Metrics (Left 8 columns) -->
  435. <div class="col-span-8 grid grid-cols-4 gap-4">
  436. <!-- Tank Pressure: Normal -->
  437. <div class="bg-system-card rounded-xl border border-system-border shadow-sm p-4 flex items-center gap-4 relative overflow-hidden">
  438. <div class="absolute top-0 right-0 w-16 h-16 bg-system-normal/10 rounded-bl-full"></div>
  439. <div class="w-12 h-12 rounded-full bg-system-normal/10 flex items-center justify-center border border-system-normal/20 shrink-0">
  440. <iconify-icon icon="mdi:gauge" class="text-2xl text-system-normal"></iconify-icon>
  441. </div>
  442. <div class="flex-1">
  443. <div class="text-xs text-system-muted mb-1">油罐压力</div>
  444. <div class="flex items-baseline gap-1">
  445. <span class="text-2xl font-mono font-bold text-slate-800">1.25</span>
  446. <span class="text-xs text-system-muted font-mono">kPa</span>
  447. </div>
  448. </div>
  449. <div class="flex flex-col items-end">
  450. <iconify-icon icon="mdi:check-circle" class="text-system-normal"></iconify-icon>
  451. <span class="text-[10px] text-system-normal mt-1">正常</span>
  452. </div>
  453. </div>
  454. <!-- Vapor Concentration: Warning -->
  455. <div class="bg-system-card rounded-xl border border-system-warning/30 shadow-sm p-4 flex items-center gap-4 relative overflow-hidden">
  456. <div class="absolute top-0 right-0 w-16 h-16 bg-system-warning/10 rounded-bl-full"></div>
  457. <div class="w-12 h-12 rounded-full bg-system-warning/10 flex items-center justify-center border border-system-warning/30 shrink-0">
  458. <iconify-icon icon="mdi:water-percent" class="text-2xl text-system-warning"></iconify-icon>
  459. </div>
  460. <div class="flex-1">
  461. <div class="text-xs text-system-muted mb-1">油气浓度</div>
  462. <div class="flex items-baseline gap-1">
  463. <span class="text-2xl font-mono font-bold text-system-warning">18.5</span>
  464. <span class="text-xs text-system-muted font-mono">g/m³</span>
  465. </div>
  466. </div>
  467. <div class="flex flex-col items-end">
  468. <iconify-icon icon="mdi:alert-outline" class="text-system-warning animate-pulse"></iconify-icon>
  469. <span class="text-[10px] text-system-warning mt-1">偏高</span>
  470. </div>
  471. </div>
  472. <!-- Pipeline Pressure: Normal -->
  473. <div class="bg-system-card rounded-xl border border-system-border shadow-sm p-4 flex items-center gap-4 relative overflow-hidden">
  474. <div class="absolute top-0 right-0 w-16 h-16 bg-system-normal/10 rounded-bl-full"></div>
  475. <div class="w-12 h-12 rounded-full bg-system-normal/10 flex items-center justify-center border border-system-normal/20 shrink-0">
  476. <iconify-icon icon="mdi:pipe" class="text-2xl text-system-normal"></iconify-icon>
  477. </div>
  478. <div class="flex-1">
  479. <div class="text-xs text-system-muted mb-1">管线压力</div>
  480. <div class="flex items-baseline gap-1">
  481. <span class="text-2xl font-mono font-bold text-slate-800">3.80</span>
  482. <span class="text-xs text-system-muted font-mono">kPa</span>
  483. </div>
  484. </div>
  485. <div class="flex flex-col items-end">
  486. <iconify-icon icon="mdi:check-circle" class="text-system-normal"></iconify-icon>
  487. <span class="text-[10px] text-system-normal mt-1">正常</span>
  488. </div>
  489. </div>
  490. <!-- Tank Temperature: Alarm -->
  491. <div class="bg-system-card rounded-xl border border-system-alarm/40 shadow-sm p-4 flex items-center gap-4 relative overflow-hidden glow-alarm">
  492. <div class="absolute top-0 right-0 w-16 h-16 bg-system-alarm/10 rounded-bl-full"></div>
  493. <div class="w-12 h-12 rounded-full bg-system-alarm/10 flex items-center justify-center border border-system-alarm/30 shrink-0">
  494. <iconify-icon icon="mdi:thermometer-alert" class="text-2xl text-system-alarm"></iconify-icon>
  495. </div>
  496. <div class="flex-1">
  497. <div class="text-xs text-system-muted mb-1">油罐温度</div>
  498. <div class="flex items-baseline gap-1">
  499. <span class="text-2xl font-mono font-bold text-system-alarm">42.5</span>
  500. <span class="text-xs text-system-muted font-mono">°C</span>
  501. </div>
  502. </div>
  503. <div class="flex flex-col items-end">
  504. <iconify-icon icon="mdi:close-octagon" class="text-system-alarm animate-bounce"></iconify-icon>
  505. <span class="text-[10px] text-system-alarm mt-1 font-bold">超限</span>
  506. </div>
  507. </div>
  508. </div>
  509. <!-- Remote Platform Status (Right 4 columns) -->
  510. <div class="col-span-4 bg-system-card rounded-xl border border-system-border shadow-sm p-4 flex flex-col justify-between">
  511. <div class="text-sm font-medium text-slate-700 flex items-center gap-2 mb-2">
  512. <iconify-icon icon="mdi:cloud-upload-outline" class="text-slate-500"></iconify-icon>
  513. 数据远传状态
  514. </div>
  515. <div class="grid grid-cols-2 gap-2 h-full">
  516. <div class="bg-slate-50 border border-slate-100 rounded px-3 py-2 flex items-center justify-between">
  517. <span class="text-xs text-slate-600">市环保局</span>
  518. <div class="flex items-center gap-1.5">
  519. <iconify-icon icon="mdi:sync" class="text-system-normal text-xs animate-spin-slow"></iconify-icon>
  520. <span class="text-[10px] text-system-normal">上传中</span>
  521. </div>
  522. </div>
  523. <div class="bg-slate-50 border border-slate-100 rounded px-3 py-2 flex items-center justify-between">
  524. <span class="text-xs text-slate-600">省质监局</span>
  525. <div class="flex items-center gap-1.5">
  526. <span class="w-2 h-2 rounded-full bg-system-normal"></span>
  527. <span class="text-[10px] text-slate-500">已同步</span>
  528. </div>
  529. </div>
  530. <div class="bg-slate-50 border border-slate-100 rounded px-3 py-2 flex items-center justify-between">
  531. <span class="text-xs text-slate-600">集团总部</span>
  532. <div class="flex items-center gap-1.5">
  533. <iconify-icon icon="mdi:sync" class="text-system-normal text-xs animate-spin-slow"></iconify-icon>
  534. <span class="text-[10px] text-system-normal">上传中</span>
  535. </div>
  536. </div>
  537. <div class="bg-orange-50/50 border border-system-warning/30 rounded px-3 py-2 flex items-center justify-between">
  538. <span class="text-xs text-slate-600">应急管理</span>
  539. <div class="flex items-center gap-1.5">
  540. <span class="w-2 h-2 rounded-full bg-system-warning animate-pulse"></span>
  541. <span class="text-[10px] text-system-warning">网络延迟</span>
  542. </div>
  543. </div>
  544. </div>
  545. </div>
  546. </section>
  547. <!-- 3. Main Nozzle Grid Section -->
  548. <main class="flex-1 overflow-y-auto px-6 pb-24">
  549. <!-- Dense Grid Layout -->
  550. <div class="grid grid-cols-5 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10 2xl:grid-cols-12 gap-1.5">
  551. <!-- Card 01: Normal Fueling (Green Icon/Border, Green Tag) -->
  552. <article class="group relative bg-system-card border border-system-normal shadow-sm rounded-lg p-2 flex flex-col gap-1 transition-all duration-300">
  553. <div class="flex justify-between items-center">
  554. <span class="text-sm font-mono font-bold text-slate-800">01#</span>
  555. <span class="text-[10px] px-1 py-0.5 rounded bg-slate-100 text-slate-600 border border-slate-200">92#</span>
  556. </div>
  557. <div class="flex-1 flex justify-center items-center py-1.5">
  558. <div class="relative w-12 h-12 rounded-full border-2 border-system-normal flex justify-center items-center bg-system-normal/10 glow-normal">
  559. <iconify-icon icon="mdi:fuel" class="text-lg text-system-normal"></iconify-icon>
  560. <div class="absolute -right-2 top-1/2 -translate-y-1/2 flex flex-col gap-0.5">
  561. <iconify-icon icon="mdi:chevron-up" class="text-[8px] text-system-normal animate-flow-up" style="animation-delay: 0s"></iconify-icon>
  562. <iconify-icon icon="mdi:chevron-up" class="text-[8px] text-system-normal animate-flow-up" style="animation-delay: 0.3s"></iconify-icon>
  563. </div>
  564. </div>
  565. </div>
  566. <div class="flex justify-between items-end mt-1 h-5">
  567. <div class="flex flex-col leading-tight">
  568. <span class="text-[9px] text-system-muted">气液比</span>
  569. <span class="text-xs font-mono text-system-normal font-bold">1.05</span>
  570. </div>
  571. <span class="text-[9px] text-system-normal bg-system-normal/10 px-1 py-0.5 rounded border border-system-normal/20">加油中</span>
  572. </div>
  573. </article>
  574. <!-- Card 02: Normal Idle (Green Icon/Border, Gray Tag) -->
  575. <article class="group relative bg-system-card border border-system-normal/60 hover:border-system-normal shadow-sm rounded-lg p-2 flex flex-col gap-1 transition-all duration-300 opacity-90">
  576. <div class="flex justify-between items-center">
  577. <span class="text-sm font-mono font-bold text-slate-800">02#</span>
  578. <span class="text-[10px] px-1 py-0.5 rounded bg-slate-100 text-slate-600 border border-slate-200">92#</span>
  579. </div>
  580. <div class="flex-1 flex justify-center items-center py-1.5">
  581. <div class="relative w-12 h-12 rounded-full border-2 border-system-normal flex justify-center items-center bg-slate-50">
  582. <iconify-icon icon="mdi:fuel" class="text-lg text-system-normal"></iconify-icon>
  583. </div>
  584. </div>
  585. <div class="flex justify-between items-end mt-1 h-5">
  586. <div class="flex flex-col leading-tight">
  587. <span class="text-[9px] text-system-muted">气液比</span>
  588. <span class="text-xs font-mono text-slate-500 font-bold">--</span>
  589. </div>
  590. <span class="text-[9px] text-slate-500 bg-slate-100 px-1 py-0.5 rounded border border-slate-200">空闲</span>
  591. </div>
  592. </article>
  593. <!-- Card 03: Normal Idle -->
  594. <article class="group relative bg-system-card border border-system-normal/60 hover:border-system-normal shadow-sm rounded-lg p-2 flex flex-col gap-1 transition-all duration-300 opacity-90">
  595. <div class="flex justify-between items-center">
  596. <span class="text-sm font-mono font-bold text-slate-800">03#</span>
  597. <span class="text-[10px] px-1 py-0.5 rounded bg-slate-100 text-slate-600 border border-slate-200">95#</span>
  598. </div>
  599. <div class="flex-1 flex justify-center items-center py-1.5">
  600. <div class="relative w-12 h-12 rounded-full border-2 border-system-normal flex justify-center items-center bg-slate-50">
  601. <iconify-icon icon="mdi:fuel" class="text-lg text-system-normal"></iconify-icon>
  602. </div>
  603. </div>
  604. <div class="flex justify-between items-end mt-1 h-5">
  605. <div class="flex flex-col leading-tight">
  606. <span class="text-[9px] text-system-muted">气液比</span>
  607. <span class="text-xs font-mono text-slate-500 font-bold">--</span>
  608. </div>
  609. <span class="text-[9px] text-slate-500 bg-slate-100 px-1 py-0.5 rounded border border-slate-200">空闲</span>
  610. </div>
  611. </article>
  612. <!-- Card 04: Offline (Gray Icon/Border, Gray Tag) -->
  613. <article class="group relative bg-system-card border border-slate-300 hover:border-slate-400 shadow-sm rounded-lg p-2 flex flex-col gap-1 transition-all duration-300 opacity-75">
  614. <div class="flex justify-between items-center">
  615. <span class="text-sm font-mono font-bold text-slate-600">04#</span>
  616. <span class="text-[10px] px-1 py-0.5 rounded bg-slate-100 text-slate-400 border border-slate-200">95#</span>
  617. </div>
  618. <div class="flex-1 flex justify-center items-center py-1.5">
  619. <div class="relative w-12 h-12 rounded-full border-2 border-slate-300 flex justify-center items-center bg-slate-50">
  620. <iconify-icon icon="mdi:fuel" class="text-lg text-slate-400"></iconify-icon>
  621. </div>
  622. </div>
  623. <div class="flex justify-between items-end mt-1 h-5">
  624. <div class="flex flex-col leading-tight">
  625. <span class="text-[9px] text-slate-400">气液比</span>
  626. <span class="text-xs font-mono text-slate-400 font-bold">--</span>
  627. </div>
  628. <span class="text-[9px] text-slate-500 bg-slate-100 px-1 py-0.5 rounded border border-slate-200">离线</span>
  629. </div>
  630. </article>
  631. <!-- Card 05: WARNING (Yellow Color Only, No Text Tag) -->
  632. <article class="group relative bg-system-card border-2 border-system-warning shadow-sm rounded-lg p-2 flex flex-col gap-1 transition-all duration-300 bg-orange-50/30">
  633. <div class="flex justify-between items-center">
  634. <span class="text-sm font-mono font-bold text-slate-800">05#</span>
  635. <span class="text-[10px] px-1 py-0.5 rounded bg-orange-100 text-system-warning border border-orange-200">98#</span>
  636. </div>
  637. <div class="flex-1 flex justify-center items-center py-1.5">
  638. <div class="relative w-12 h-12 rounded-full border-2 border-system-warning flex justify-center items-center bg-system-warning/10 glow-warning">
  639. <iconify-icon icon="mdi:fuel" class="text-lg text-system-warning"></iconify-icon>
  640. </div>
  641. </div>
  642. <div class="flex justify-between items-end mt-1 h-5">
  643. <div class="flex flex-col leading-tight">
  644. <span class="text-[9px] text-system-muted">气液比</span>
  645. <span class="text-xs font-mono text-system-warning font-bold">1.28</span>
  646. </div>
  647. <!-- Deliberately left empty, warning relies entirely on border/icon color -->
  648. </div>
  649. </article>
  650. <!-- Card 06: ALARM (Red Color Only, No Text Tag) -->
  651. <article class="group relative bg-system-card border-2 border-system-alarm shadow-sm rounded-lg p-2 flex flex-col gap-1 transition-all duration-300 bg-red-50/30">
  652. <div class="flex justify-between items-center">
  653. <span class="text-sm font-mono font-bold text-slate-800">06#</span>
  654. <span class="text-[10px] px-1 py-0.5 rounded bg-red-100 text-system-alarm border border-red-200">92#</span>
  655. </div>
  656. <div class="flex-1 flex justify-center items-center py-1.5">
  657. <div class="relative w-12 h-12 rounded-full border-2 border-system-alarm flex justify-center items-center bg-system-alarm/10 glow-alarm">
  658. <iconify-icon icon="mdi:fuel" class="text-lg text-system-alarm"></iconify-icon>
  659. <div class="absolute -top-1 -right-1 w-3 h-3 bg-system-alarm rounded-full flex items-center justify-center animate-bounce shadow-md">
  660. </div>
  661. </div>
  662. </div>
  663. <div class="flex justify-between items-end mt-1 h-5">
  664. <div class="flex flex-col leading-tight">
  665. <span class="text-[9px] text-system-muted">气液比</span>
  666. <span class="text-xs font-mono text-system-alarm font-bold">0.00</span>
  667. </div>
  668. <!-- Deliberately left empty, alarm relies entirely on border/icon color -->
  669. </div>
  670. </article>
  671. <!-- Generate remaining cards -->
  672. <script>
  673. const fuelTypes = ['92#', '95#', '98#', '0#'];
  674. for(let i=7; i<=32; i++) {
  675. let num = i < 10 ? '0'+i : i;
  676. let fuel = fuelTypes[Math.floor(Math.random() * fuelTypes.length)];
  677. let rand = Math.random();
  678. if(i === 15) {
  679. // Warning (Yellow, No Text Tag)
  680. document.write(`
  681. <article class="group relative bg-system-card border-2 border-system-warning shadow-sm rounded-lg p-2 flex flex-col gap-1 transition-all duration-300 bg-orange-50/30">
  682. <div class="flex justify-between items-center">
  683. <span class="text-sm font-mono font-bold text-slate-800">${num}#</span>
  684. <span class="text-[10px] px-1 py-0.5 rounded bg-orange-100 text-system-warning border border-orange-200">${fuel}</span>
  685. </div>
  686. <div class="flex-1 flex justify-center items-center py-1.5">
  687. <div class="relative w-12 h-12 rounded-full border-2 border-system-warning flex justify-center items-center bg-system-warning/10 glow-warning">
  688. <iconify-icon icon="mdi:fuel" class="text-lg text-system-warning"></iconify-icon>
  689. <div class="absolute -right-2 top-1/2 -translate-y-1/2 flex flex-col gap-0.5">
  690. <iconify-icon icon="mdi:chevron-up" class="text-[8px] text-system-warning animate-flow-up" style="animation-delay: 0s"></iconify-icon>
  691. <iconify-icon icon="mdi:chevron-up" class="text-[8px] text-system-warning animate-flow-up" style="animation-delay: 0.3s"></iconify-icon>
  692. </div>
  693. </div>
  694. </div>
  695. <div class="flex justify-between items-end mt-1 h-5">
  696. <div class="flex flex-col leading-tight">
  697. <span class="text-[9px] text-system-muted">气液比</span>
  698. <span class="text-xs font-mono text-system-warning font-bold">0.85</span>
  699. </div>
  700. </div>
  701. </article>
  702. `);
  703. } else if(i === 22) {
  704. // Alarm (Red, No Text Tag)
  705. document.write(`
  706. <article class="group relative bg-system-card border-2 border-system-alarm shadow-sm rounded-lg p-2 flex flex-col gap-1 transition-all duration-300 bg-red-50/30">
  707. <div class="flex justify-between items-center">
  708. <span class="text-sm font-mono font-bold text-slate-800">${num}#</span>
  709. <span class="text-[10px] px-1 py-0.5 rounded bg-red-100 text-system-alarm border border-red-200">${fuel}</span>
  710. </div>
  711. <div class="flex-1 flex justify-center items-center py-1.5">
  712. <div class="relative w-12 h-12 rounded-full border-2 border-system-alarm flex justify-center items-center bg-system-alarm/10 glow-alarm">
  713. <iconify-icon icon="mdi:fuel" class="text-lg text-system-alarm"></iconify-icon>
  714. </div>
  715. </div>
  716. <div class="flex justify-between items-end mt-1 h-5">
  717. <div class="flex flex-col leading-tight">
  718. <span class="text-[9px] text-system-muted">气液比</span>
  719. <span class="text-xs font-mono text-system-alarm font-bold">2.50</span>
  720. </div>
  721. </div>
  722. </article>
  723. `);
  724. } else if (rand > 0.85) {
  725. // Offline (Gray, Gray Tag)
  726. document.write(`
  727. <article class="group relative bg-system-card border border-slate-300 hover:border-slate-400 shadow-sm rounded-lg p-2 flex flex-col gap-1 transition-all duration-300 opacity-75">
  728. <div class="flex justify-between items-center">
  729. <span class="text-sm font-mono font-bold text-slate-600">${num}#</span>
  730. <span class="text-[10px] px-1 py-0.5 rounded bg-slate-100 text-slate-400 border border-slate-200">${fuel}</span>
  731. </div>
  732. <div class="flex-1 flex justify-center items-center py-1.5">
  733. <div class="relative w-12 h-12 rounded-full border-2 border-slate-300 flex justify-center items-center bg-slate-50">
  734. <iconify-icon icon="mdi:fuel" class="text-lg text-slate-400"></iconify-icon>
  735. </div>
  736. </div>
  737. <div class="flex justify-between items-end mt-1 h-5">
  738. <div class="flex flex-col leading-tight">
  739. <span class="text-[9px] text-slate-400">气液比</span>
  740. <span class="text-xs font-mono text-slate-400 font-bold">--</span>
  741. </div>
  742. <span class="text-[9px] text-slate-500 bg-slate-100 px-1 py-0.5 rounded border border-slate-200">离线</span>
  743. </div>
  744. </article>
  745. `);
  746. } else if (rand > 0.4) {
  747. // Fueling (Green border/icon, Green Tag)
  748. let al = (1.0 + Math.random() * 0.15).toFixed(2);
  749. document.write(`
  750. <article class="group relative bg-system-card border border-system-normal shadow-sm rounded-lg p-2 flex flex-col gap-1 transition-all duration-300">
  751. <div class="flex justify-between items-center">
  752. <span class="text-sm font-mono font-bold text-slate-800">${num}#</span>
  753. <span class="text-[10px] px-1 py-0.5 rounded bg-slate-100 text-slate-600 border border-slate-200">${fuel}</span>
  754. </div>
  755. <div class="flex-1 flex justify-center items-center py-1.5">
  756. <div class="relative w-12 h-12 rounded-full border-2 border-system-normal flex justify-center items-center bg-system-normal/10 glow-normal">
  757. <iconify-icon icon="mdi:fuel" class="text-lg text-system-normal"></iconify-icon>
  758. <div class="absolute -right-2 top-1/2 -translate-y-1/2 flex flex-col gap-0.5">
  759. <iconify-icon icon="mdi:chevron-up" class="text-[8px] text-system-normal animate-flow-up" style="animation-delay: 0s"></iconify-icon>
  760. <iconify-icon icon="mdi:chevron-up" class="text-[8px] text-system-normal animate-flow-up" style="animation-delay: 0.3s"></iconify-icon>
  761. </div>
  762. </div>
  763. </div>
  764. <div class="flex justify-between items-end mt-1 h-5">
  765. <div class="flex flex-col leading-tight">
  766. <span class="text-[9px] text-system-muted">气液比</span>
  767. <span class="text-xs font-mono text-system-normal font-bold">${al}</span>
  768. </div>
  769. <span class="text-[9px] text-system-normal bg-system-normal/10 px-1 py-0.5 rounded border border-system-normal/20">加油中</span>
  770. </div>
  771. </article>
  772. `);
  773. } else {
  774. // Idle (Green border/icon, Gray Tag)
  775. document.write(`
  776. <article class="group relative bg-system-card border border-system-normal/60 hover:border-system-normal shadow-sm rounded-lg p-2 flex flex-col gap-1 transition-all duration-300 opacity-90">
  777. <div class="flex justify-between items-center">
  778. <span class="text-sm font-mono font-bold text-slate-800">${num}#</span>
  779. <span class="text-[10px] px-1 py-0.5 rounded bg-slate-100 text-slate-600 border border-slate-200">${fuel}</span>
  780. </div>
  781. <div class="flex-1 flex justify-center items-center py-1.5">
  782. <div class="relative w-12 h-12 rounded-full border-2 border-system-normal flex justify-center items-center bg-slate-50">
  783. <iconify-icon icon="mdi:fuel" class="text-lg text-system-normal"></iconify-icon>
  784. </div>
  785. </div>
  786. <div class="flex justify-between items-end mt-1 h-5">
  787. <div class="flex flex-col leading-tight">
  788. <span class="text-[9px] text-system-muted">气液比</span>
  789. <span class="text-xs font-mono text-slate-500 font-bold">--</span>
  790. </div>
  791. <span class="text-[9px] text-slate-500 bg-slate-100 px-1 py-0.5 rounded border border-slate-200">空闲</span>
  792. </div>
  793. </article>
  794. `);
  795. }
  796. }
  797. </script>
  798. </div>
  799. </main>
  800. <!-- 4. Pagination Control (Fixed Bottom) -->
  801. <footer class="fixed bottom-0 w-full h-14 bg-white/95 backdrop-blur-md border-t border-system-border px-6 flex items-center justify-between z-40 shadow-[0_-2px_10px_rgba(0,0,0,0.02)]">
  802. <!-- Legend -->
  803. <div class="flex items-center gap-4 text-xs">
  804. <span class="text-slate-500">状态图例:</span>
  805. <div class="flex items-center gap-1.5">
  806. <span class="w-2.5 h-2.5 rounded-full bg-system-normal glow-normal border border-system-normal/20"></span>
  807. <span class="text-slate-600">正常(加油/空闲)</span>
  808. </div>
  809. <div class="flex items-center gap-1.5">
  810. <span class="w-2.5 h-2.5 rounded-full bg-slate-300 border border-slate-400"></span>
  811. <span class="text-slate-600">离线</span>
  812. </div>
  813. <div class="flex items-center gap-1.5">
  814. <span class="w-2.5 h-2.5 rounded-full bg-system-warning glow-warning"></span>
  815. <span class="text-slate-600">预警</span>
  816. </div>
  817. <div class="flex items-center gap-1.5">
  818. <span class="w-2.5 h-2.5 rounded-full bg-system-alarm glow-alarm"></span>
  819. <span class="text-slate-600">报警</span>
  820. </div>
  821. </div>
  822. <!-- Pagination Center -->
  823. <div class="flex items-center gap-6">
  824. <span class="text-sm text-slate-500">第 <span class="text-slate-800 font-bold">1</span> 页 / 共 3 页 (96条)</span>
  825. <div class="flex items-center gap-2">
  826. <button class="px-3 py-1 rounded bg-white border border-slate-200 text-slate-400 transition-colors disabled:opacity-50 text-sm" disabled>
  827. <iconify-icon icon="mdi:chevron-left" class="inline-block align-middle"></iconify-icon> 上一页
  828. </button>
  829. <!-- Page 1: Current, has Alarm (Red) -->
  830. <button class="w-7 h-7 rounded flex items-center justify-center bg-system-alarm text-white font-bold shadow-sm transition-all">
  831. 1
  832. </button>
  833. <!-- Page 2: Has Warning (Yellow) -->
  834. <button class="w-7 h-7 rounded flex items-center justify-center bg-orange-50 text-system-warning border border-system-warning/30 hover:bg-orange-100 transition-all relative" title="该页存在预警项">
  835. 2
  836. <div class="absolute -top-1 -right-1 w-2 h-2 bg-system-warning rounded-full"></div>
  837. </button>
  838. <!-- Page 3: All Normal (Green) -->
  839. <button class="w-7 h-7 rounded flex items-center justify-center bg-emerald-50 text-emerald-700 border border-emerald-200 hover:bg-emerald-100 transition-all" title="该页全正常">
  840. 3
  841. </button>
  842. <button class="px-3 py-1 rounded bg-white border border-slate-200 text-slate-600 hover:text-slate-800 hover:bg-slate-50 transition-colors text-sm">
  843. 下一页 <iconify-icon icon="mdi:chevron-right" class="inline-block align-middle"></iconify-icon>
  844. </button>
  845. </div>
  846. </div>
  847. <!-- Quick Jump -->
  848. <div class="flex items-center gap-2 text-sm">
  849. <span class="text-slate-500">跳转至</span>
  850. <select class="bg-white border border-slate-300 text-slate-700 text-sm rounded focus:ring-system-normal focus:border-system-normal block px-2 py-1 outline-none">
  851. <option>1</option>
  852. <option>2</option>
  853. <option>3</option>
  854. </select>
  855. <span class="text-slate-500">页</span>
  856. </div>
  857. </footer>
  858. </div>
  859. <script defer type="module">
  860. window.addEventListener('error', function(event) {
  861. var msg = event.message || '';
  862. var name = (event.error && event.error.name) || msg.split(':')[0] || '';
  863. if (name === 'HierarchyRequestError' || msg.includes("Failed to execute 'appendChild' on 'Node'")) return;
  864. if (window.__SUPERDESIGN_PREVIEW__) {
  865. var err = event.error;
  866. window.__SUPERDESIGN_PREVIEW__.sendMessage('error', {
  867. message: event.message, filename: event.filename,
  868. lineno: event.lineno, colno: event.colno,
  869. name: (err && err.name) || name || 'Error',
  870. stack: (err && err.stack) || null
  871. });
  872. }
  873. });
  874. window.addEventListener('unhandledrejection', function(event) {
  875. var reason = event.reason;
  876. var msg = (reason && reason.message) || (typeof reason === 'string' ? reason : '');
  877. var name = (reason && reason.name) || 'UnhandledRejection';
  878. if (name === 'HierarchyRequestError' || msg.includes("Failed to execute 'appendChild' on 'Node'")) return;
  879. if (window.__SUPERDESIGN_PREVIEW__) {
  880. window.__SUPERDESIGN_PREVIEW__.sendMessage('unhandled-rejection', {
  881. reason: (reason && reason.message) || String(reason),
  882. name: name,
  883. stack: (reason && reason.stack) || null
  884. });
  885. }
  886. });
  887. </script>
  888. <script defer type="module">import '/__visual-edit-bridge/iframe-runtime.mjs?v=e62d2259';</script>
  889. <script defer type="module">
  890. (function() {
  891. 'use strict';
  892. var MAX_RENDER_DEPTH = 5;
  893. function showPage() {
  894. document.body.classList.add('sd-ready');
  895. }
  896. var componentRegistry = new Map();
  897. var instanceRegistry = new Map();
  898. var API_BASE = "https://api.superdesign.dev";
  899. var DRAFT_ID = "6b5723dc-a31d-45f5-8dff-da5335066aea";
  900. function buildDefaultProps(definition) {
  901. var defaultProps = {};
  902. var props = definition.props;
  903. if (!props || !Array.isArray(props)) return defaultProps;
  904. for (var i = 0; i < props.length; i++) {
  905. var prop = props[i];
  906. if (prop.defaultValue === undefined) continue;
  907. var path = prop.name.split('.');
  908. var current = defaultProps;
  909. for (var j = 0; j < path.length - 1; j++) {
  910. var key = path[j];
  911. if (!(key in current) || typeof current[key] !== 'object' || current[key] === null) {
  912. current[key] = {};
  913. }
  914. current = current[key];
  915. }
  916. current[path[path.length - 1]] = prop.defaultValue;
  917. }
  918. return defaultProps;
  919. }
  920. function deepMerge(target, source) {
  921. var result = Object.assign({}, target);
  922. for (var key in source) {
  923. if (!source.hasOwnProperty(key)) continue;
  924. if (
  925. source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) &&
  926. result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])
  927. ) {
  928. result[key] = deepMerge(result[key], source[key]);
  929. } else {
  930. result[key] = source[key];
  931. }
  932. }
  933. return result;
  934. }
  935. function registerComponent(id, definition) {
  936. componentRegistry.set(id, definition);
  937. console.log('[SDComponent] Registered component:', id);
  938. }
  939. function renderComponent(element, depth) {
  940. depth = depth || 0;
  941. var componentId = element.getAttribute('componentid') || element.getAttribute('ref');
  942. var instanceId = element.getAttribute('instance');
  943. var propsAttr = element.getAttribute('props');
  944. if (!componentId) {
  945. console.warn('[SDComponent] Missing componentId attribute on sd-component');
  946. return;
  947. }
  948. if (depth >= MAX_RENDER_DEPTH) {
  949. console.warn('[SDComponent] Max render depth reached for:', componentId);
  950. element.setAttribute('data-error', 'max-depth');
  951. return;
  952. }
  953. var definition = componentRegistry.get(componentId);
  954. if (!definition) {
  955. console.warn('[SDComponent] Component not found:', componentId);
  956. element.setAttribute('data-pending', 'true');
  957. return;
  958. }
  959. var instanceProps = {};
  960. if (propsAttr) {
  961. try { instanceProps = JSON.parse(propsAttr); }
  962. catch (e) { console.warn('[SDComponent] Invalid props JSON:', propsAttr); }
  963. }
  964. var defaultProps = buildDefaultProps(definition);
  965. var finalProps = deepMerge(defaultProps, instanceProps);
  966. element.innerHTML = definition.htmlContent;
  967. if (window.PetiteVue) {
  968. finalProps.$emit = function(eventName, payload) {
  969. if (payload && typeof payload === 'object' && payload.href) {
  970. window.location.href = payload.href;
  971. return;
  972. }
  973. var customEvent = new CustomEvent('sd-' + eventName, {
  974. detail: payload, bubbles: true, composed: true,
  975. });
  976. element.dispatchEvent(customEvent);
  977. };
  978. window.PetiteVue.createApp(finalProps).mount(element);
  979. } else {
  980. console.warn('[SDComponent] Petite-Vue not loaded, template syntax will not be resolved');
  981. }
  982. instanceRegistry.set(instanceId, {
  983. componentId: componentId,
  984. element: element,
  985. props: finalProps,
  986. version: definition.version,
  987. });
  988. element.removeAttribute('data-pending');
  989. element.setAttribute('data-rendered', 'true');
  990. element.setAttribute('data-version', String(definition.version));
  991. console.log('[SDComponent] Rendered:', componentId, instanceId);
  992. // Render any nested sd-component children that were just injected
  993. var nestedElements = element.querySelectorAll('sd-component[componentid], sd-component[ref]');
  994. if (nestedElements.length > 0) {
  995. var missingIds = [];
  996. nestedElements.forEach(function(nested) {
  997. var nestedId = nested.getAttribute('componentid') || nested.getAttribute('ref');
  998. if (nestedId && !componentRegistry.has(nestedId)) {
  999. missingIds.push(nestedId);
  1000. }
  1001. });
  1002. if (missingIds.length > 0) {
  1003. // Fetch missing nested components, then render
  1004. loadComponentsByIds(missingIds).then(function() {
  1005. nestedElements.forEach(function(nested) {
  1006. if (!nested.getAttribute('data-rendered')) {
  1007. renderComponent(nested, depth + 1);
  1008. }
  1009. });
  1010. });
  1011. } else {
  1012. // All nested definitions already loaded — render immediately
  1013. nestedElements.forEach(function(nested) {
  1014. if (!nested.getAttribute('data-rendered')) {
  1015. renderComponent(nested, depth + 1);
  1016. }
  1017. });
  1018. }
  1019. }
  1020. }
  1021. function updateComponentInstances(componentId) {
  1022. var elements = document.querySelectorAll('sd-component[componentid="' + componentId + '"], sd-component[ref="' + componentId + '"]');
  1023. elements.forEach(function(el) { renderComponent(el); });
  1024. }
  1025. function initializeComponents() {
  1026. var elements = document.querySelectorAll('sd-component[componentid], sd-component[ref]');
  1027. elements.forEach(function(el) {
  1028. if (!el.getAttribute('data-rendered')) { renderComponent(el); }
  1029. });
  1030. }
  1031. async function loadComponentsByIds(ids) {
  1032. if (!ids || ids.length === 0) return;
  1033. // Deduplicate and filter already-loaded
  1034. var uniqueIds = ids.filter(function(id, i, arr) {
  1035. return arr.indexOf(id) === i && !componentRegistry.has(id);
  1036. });
  1037. if (uniqueIds.length === 0) return;
  1038. try {
  1039. console.log('[SDComponent] Batch loading components:', uniqueIds);
  1040. var response = await fetch(API_BASE + '/v1/design-drafts/components/batch', {
  1041. method: 'POST',
  1042. headers: { 'Content-Type': 'application/json' },
  1043. body: JSON.stringify({ componentIds: uniqueIds }),
  1044. });
  1045. if (!response.ok) throw new Error('Batch load failed: ' + response.status);
  1046. var components = await response.json();
  1047. console.log('[SDComponent] Batch loaded', components.length, 'components');
  1048. components.forEach(function(comp) { registerComponent(comp.id, comp); });
  1049. } catch (error) {
  1050. console.error('[SDComponent] Failed to batch load components:', error);
  1051. }
  1052. }
  1053. async function loadComponentsForDraft() {
  1054. if (!DRAFT_ID) {
  1055. console.warn('[SDComponent] No draft ID configured');
  1056. showPage();
  1057. return;
  1058. }
  1059. try {
  1060. console.log('[SDComponent] Loading components for draft:', DRAFT_ID);
  1061. var response = await fetch(API_BASE + '/v1/design-drafts/' + DRAFT_ID + '/components', {
  1062. method: 'GET',
  1063. headers: { 'Content-Type': 'application/json' },
  1064. });
  1065. if (!response.ok) throw new Error('Failed to load components: ' + response.status);
  1066. var components = await response.json();
  1067. console.log('[SDComponent] Loaded', components.length, 'components');
  1068. components.forEach(function(comp) { registerComponent(comp.id, comp); });
  1069. initializeComponents();
  1070. showPage();
  1071. } catch (error) {
  1072. console.error('[SDComponent] Failed to load components:', error);
  1073. showPage();
  1074. }
  1075. }
  1076. function findReferencedComponents() {
  1077. var elements = document.querySelectorAll('sd-component[componentid], sd-component[ref]');
  1078. var ids = new Set();
  1079. elements.forEach(function(el) {
  1080. var ref = el.getAttribute('componentid') || el.getAttribute('ref');
  1081. if (ref) ids.add(ref);
  1082. });
  1083. return Array.from(ids);
  1084. }
  1085. window.__SD_COMPONENTS__ = {
  1086. register: registerComponent,
  1087. render: renderComponent,
  1088. update: updateComponentInstances,
  1089. loadForDraft: loadComponentsForDraft,
  1090. loadByIds: loadComponentsByIds,
  1091. init: initializeComponents,
  1092. findRefs: findReferencedComponents,
  1093. getRegistry: function() { return componentRegistry; },
  1094. getInstances: function() { return instanceRegistry; },
  1095. getDraftId: function() { return DRAFT_ID; },
  1096. };
  1097. window.addEventListener('message', function(event) {
  1098. if (event.data && event.data.type === 'SD_COMPONENT_UPDATE') {
  1099. var cId = event.data.componentId;
  1100. var def = event.data.definition;
  1101. if (cId && def) { registerComponent(cId, def); updateComponentInstances(cId); }
  1102. }
  1103. if (event.data && event.data.type === 'SD_COMPONENTS_REGISTER') {
  1104. var comps = event.data.components;
  1105. if (Array.isArray(comps)) {
  1106. comps.forEach(function(c) { registerComponent(c.id, c); });
  1107. initializeComponents();
  1108. }
  1109. }
  1110. });
  1111. function bootComponents() {
  1112. // Pre-register components from __SD_COMPONENT_PRELOAD__ (injected by backend for standalone previews)
  1113. if (window.__SD_COMPONENT_PRELOAD__ && Array.isArray(window.__SD_COMPONENT_PRELOAD__)) {
  1114. console.log('[SDComponent] Pre-registering', window.__SD_COMPONENT_PRELOAD__.length, 'preloaded components');
  1115. window.__SD_COMPONENT_PRELOAD__.forEach(function(comp) {
  1116. registerComponent(comp.id, comp);
  1117. });
  1118. }
  1119. var refs = findReferencedComponents();
  1120. var unloaded = refs.filter(function(id) { return !componentRegistry.has(id); });
  1121. if (unloaded.length > 0) {
  1122. if (DRAFT_ID) {
  1123. loadComponentsForDraft();
  1124. } else {
  1125. // No draft ID — try batch loading the missing IDs directly
  1126. loadComponentsByIds(unloaded).then(function() {
  1127. initializeComponents();
  1128. showPage();
  1129. });
  1130. }
  1131. } else if (refs.length > 0) {
  1132. // All components already registered (e.g. from preload) — render immediately
  1133. initializeComponents();
  1134. showPage();
  1135. } else {
  1136. showPage();
  1137. }
  1138. }
  1139. if (document.readyState === 'loading') {
  1140. document.addEventListener('DOMContentLoaded', bootComponents);
  1141. } else {
  1142. bootComponents();
  1143. }
  1144. var observer = new MutationObserver(function(mutations) {
  1145. var hasNew = false;
  1146. mutations.forEach(function(m) {
  1147. m.addedNodes.forEach(function(node) {
  1148. if (node.nodeType === 1) {
  1149. if (node.tagName === 'SD-COMPONENT') { hasNew = true; }
  1150. else if (node.querySelector) {
  1151. if (node.querySelectorAll('sd-component[componentid], sd-component[ref]').length > 0) hasNew = true;
  1152. }
  1153. }
  1154. });
  1155. });
  1156. if (hasNew) {
  1157. var refs = findReferencedComponents();
  1158. var unloaded = refs.filter(function(id) { return !componentRegistry.has(id); });
  1159. if (unloaded.length > 0) {
  1160. loadComponentsByIds(unloaded).then(function() {
  1161. initializeComponents();
  1162. });
  1163. } else {
  1164. initializeComponents();
  1165. }
  1166. }
  1167. });
  1168. observer.observe(document.body, { childList: true, subtree: true });
  1169. console.log('[SDComponent] Runtime initialized');
  1170. })();
  1171. </script>
  1172. </body>
  1173. </html>