JXCT Soil Sensor 7-in-1 v3.4.9 (June 2025)
Professional IoT soil monitoring system with ESP32, Modbus RTU, MQTT, and advanced compensation algorithms
Загрузка...
Поиск...
Не найдено
jxct_ui_system.cpp
См. документацию.
1#include "jxct_ui_system.h"
2
3// 🎨 ЕДИНЫЙ CSS ДЛЯ ВСЕХ СТРАНИЦ
4const char* getUnifiedCSS()
5{
6 static const char css[] = R"(
7/* === JXCT UI DESIGN SYSTEM v2.3.1 === */
8* { box-sizing: border-box; }
9
10body {
11 font-family: Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
12 margin: 0;
13 padding: 20px;
14 background: #f5f5f5;
15 color: #333;
16 font-size: 16px;
17 line-height: 1.5;
18}
19
20.container {
21 max-width: 1000px;
22 margin: 0 auto;
23 background: white;
24 border-radius: 6px;
25 box-shadow: 0 2px 10px rgba(0,0,0,0.1);
26 padding: 30px;
27}
28
29/* === ТИПОГРАФИКА === */
30h1 {
31 color: #333;
32 font-size: 22px;
33 margin: 0 0 20px 0;
34 font-weight: 600;
35}
36
37h2 {
38 color: #333;
39 font-size: 18px;
40 margin: 20px 0 12px 0;
41 font-weight: 500;
42 border-bottom: 2px solid #4CAF50;
43 padding-bottom: 6px;
44}
45
46/* === НАВИГАЦИЯ === */
47.nav {
48 margin-bottom: 30px;
49 padding: 15px 0;
50 border-bottom: 1px solid #ddd;
51}
52
53.nav a {
54 display: inline-block;
55 margin-right: 10px;
56 text-decoration: none;
57 color: #4CAF50;
58 font-weight: 600;
59 padding: 8px 12px;
60 border-radius: 6px;
61 transition: 0.2s ease;
62}
63
64.nav a:hover {
65 background: #4CAF50;
66 color: white;
67 transform: translateY(-1px);
68}
69
70/* === СЕКЦИИ === */
71.section {
72 margin-bottom: 25px;
73 padding: 15px;
74 border: 1px solid #ddd;
75 border-radius: 6px;
76 background: #fafafa;
77}
78
79/* === ФОРМЫ === */
80.form-group {
81 margin-bottom: 20px;
82}
83
84label {
85 display: block;
86 margin-bottom: 6px;
87 font-weight: 600;
88 color: #333;
89}
90
91input[type=text], input[type=password], input[type=number], input[type=email], input[type=file], select, textarea {
92 width: 100%;
93 padding: 10px;
94 border: 2px solid #ddd;
95 border-radius: 6px;
96 font-size: 16px;
97 transition: 0.2s ease;
98}
99
100input:focus, select:focus, textarea:focus {
101 outline: none;
102 border-color: #4CAF50;
103 box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.1);
104}
105
106/* === КНОПКИ (ЕДИНАЯ СИСТЕМА) === */
107.btn {
108 display: inline-block;
109 padding: 8px 16px;
110 border: none;
111 border-radius: 6px;
112 font-size: 14px;
113 font-weight: 500;
114 cursor: pointer;
115 text-decoration: none;
116 transition: 0.3s ease;
117 margin-right: 10px;
118 margin-bottom: 10px;
119 min-width: 120px;
120 text-align: center;
121}
122
123.btn:hover {
124 transform: translateY(-2px);
125 box-shadow: 0 4px 12px rgba(0,0,0,0.15);
126}
127
128.btn-primary {
129 background: #4CAF50;
130 color: white;
131}
132
133.btn-primary:hover {
134 background: #45a049;
135}
136
137.btn-secondary {
138 background: #2196F3;
139 color: white;
140}
141
142.btn-secondary:hover {
143 background: #0b7dda;
144}
145
146.btn-danger {
147 background: #F44336;
148 color: white;
149}
150
151.btn-danger:hover {
152 background: #d32f2f;
153}
154
155.btn-outline {
156 background: transparent;
157 color: #4CAF50;
158 border: 2px solid #4CAF50;
159}
160
161.btn-outline:hover {
162 background: #4CAF50;
163 color: white;
164}
165
166/* === СООБЩЕНИЯ === */
167.msg {
168 padding: 15px 20px;
169 margin-bottom: 20px;
170 border-radius: 6px;
171 font-weight: 500;
172 border-left: 4px solid;
173}
174
175.msg-success {
176 background: #e8f5e8;
177 color: #2e7d32;
178 border-left-color: #4CAF50;
179}
180
181.msg-error {
182 background: #ffebee;
183 color: #c62828;
184 border-left-color: #F44336;
185}
186
187.msg-warning {
188 background: #fff8e1;
189 color: #f57c00;
190 border-left-color: #FFC107;
191}
192
193.msg-info {
194 background: #e3f2fd;
195 color: #1565c0;
196 border-left-color: #2196F3;
197}
198
199/* === ВСПОМОГАТЕЛЬНЫЕ ЭЛЕМЕНТЫ === */
200.help {
201 color: #666;
202 font-size: 14px;
203 margin-top: 5px;
204 font-style: italic;
205}
206
207.status-dot {
208 display: inline-block;
209 width: 12px;
210 height: 12px;
211 border-radius: 50%;
212 margin-right: 8px;
213 vertical-align: middle;
214}
215
216.dot-ok { background: #4CAF50; }
217.dot-warn { background: #FFC107; }
218.dot-err { background: #F44336; }
219.dot-off { background: #bbb; }
220
221/* === ЛОАДЕР === */
222.loader {
223 border: 3px solid #f3f3f3;
224 border-top: 3px solid #4CAF50;
225 border-radius: 50%;
226 width: 20px;
227 height: 20px;
228 animation: spin 1s linear infinite;
229 display: inline-block;
230 margin-right: 10px;
231}
232
233@keyframes spin {
234 0% { transform: rotate(0deg); }
235 100% { transform: rotate(360deg); }
236}
237
238/* === TOAST УВЕДОМЛЕНИЯ === */
239.toast {
240 position: fixed;
241 top: 20px;
242 right: 20px;
243 padding: 15px 25px;
244 border-radius: 6px;
245 color: white;
246 font-weight: 600;
247 z-index: 9999;
248 opacity: 0;
249 transform: translateX(100%);
250 transition: all 0.3s ease;
251}
252
253.toast.show {
254 opacity: 1;
255 transform: translateX(0);
256}
257
258
259
260/* === МОБИЛЬНАЯ АДАПТАЦИЯ === */
261@media (max-width: 768px) {
262 body { padding: 10px; }
263 .container { padding: 20px; margin: 5px; }
264 h1 { font-size: 20px; }
265 h2 { font-size: 16px; }
266 .nav a {
267 display: block;
268 margin: 5px 0;
269 text-align: center;
270 }
271 .btn {
272 width: 100%;
273 margin-right: 0;
274 margin-bottom: 15px;
275 }
276 .section { padding: 12px; }
277 .form-group { margin-bottom: 15px; }
278
279
280}
281 )";
282 return css;
283}
284
285// 🎯 ГЕНЕРАЦИЯ HTML КНОПОК
286String generateButton(ButtonType type, const char* icon, const char* text, const char* action)
287{
288 String cssClass = "btn ";
289
290 switch (type)
291 {
293 cssClass += "btn-primary";
294 break;
296 cssClass += "btn-secondary";
297 break;
299 cssClass += "btn-danger";
300 break;
302 cssClass += "btn-primary";
303 break;
305 cssClass += "btn-outline";
306 break;
307 }
308
309 String html = "<button type='submit' class='" + cssClass + "'";
310 if (strlen(action) > 0)
311 {
312 html = "<button type='button' class='" + cssClass + "' onclick=\"" + String(action) + "\"";
313 }
314 html += ">" + String(icon) + " " + String(text) + "</button>";
315
316 return html;
317}
318
319// 🍞 TOAST УВЕДОМЛЕНИЯ
320const char* getToastHTML()
321{
322 return R"(
323<script>
324function showToast(message, type) {
325 const toast = document.createElement('div');
326 toast.className = 'toast';
327 toast.textContent = message;
328
329 const colors = {
330 'success': '#4CAF50',
331 'error': '#F44336',
332 'warning': '#FFC107',
333 'info': '#2196F3'
334 };
335
336 toast.style.background = colors[type] || colors['info'];
337 document.body.appendChild(toast);
338
339 setTimeout(() => toast.classList.add('show'), 100);
340 setTimeout(() => {
341 toast.classList.remove('show');
342 setTimeout(() => document.body.removeChild(toast), 300);
343 }, 3000);
344}
345
346// Показать toast при загрузке если есть сообщение
347window.addEventListener('load', function() {
348 const urlParams = new URLSearchParams(window.location.search);
349 const msg = urlParams.get('msg');
350 const type = urlParams.get('type') || 'info';
351 if (msg) {
352 showToast(decodeURIComponent(msg), type);
353 }
354});
355</script>
356 )";
357}
358
359// ⌛ ЛОАДЕР
360const char* getLoaderHTML()
361{
362 return "<div class='loader'></div>";
363}
const char * getUnifiedCSS()
Определения jxct_ui_system.cpp:4
const char * getLoaderHTML()
Определения jxct_ui_system.cpp:360
String generateButton(ButtonType type, const char *icon, const char *text, const char *action)
Определения jxct_ui_system.cpp:286
const char * getToastHTML()
Определения jxct_ui_system.cpp:320
ButtonType
Определения jxct_ui_system.h:64
@ SECONDARY
Определения jxct_ui_system.h:66
@ PRIMARY
Определения jxct_ui_system.h:65
@ OUTLINE
Определения jxct_ui_system.h:69
@ DANGER
Определения jxct_ui_system.h:67
@ SUCCESS
Определения jxct_ui_system.h:68