1#[derive(Debug, Clone)]
12pub struct Theme {
13 pub color_primary: &'static str,
15 pub color_success: &'static str,
16 pub color_warning: &'static str,
17 pub color_danger: &'static str,
18 pub color_info: &'static str,
19
20 pub color_white: &'static str,
22 pub color_black: &'static str,
23
24 pub color_text_primary: &'static str,
26 pub color_text_regular: &'static str,
27 pub color_text_secondary: &'static str,
28 pub color_text_placeholder: &'static str,
29
30 pub border_color_base: &'static str,
32 pub border_color_light: &'static str,
33 pub border_color_lighter: &'static str,
34 pub border_color_extra_light: &'static str,
35
36 pub background_color_base: &'static str,
38
39 pub font_size_extra_large: &'static str,
41 pub font_size_large: &'static str,
42 pub font_size_medium: &'static str,
43 pub font_size_base: &'static str,
44 pub font_size_small: &'static str,
45 pub font_size_extra_small: &'static str,
46
47 pub border_radius_base: &'static str,
49 pub border_radius_small: &'static str,
50 pub border_radius_circle: &'static str,
51
52 pub padding_base: &'static str,
54
55 pub all_transition: &'static str,
57 pub fade_transition: &'static str,
58 pub border_transition_base: &'static str,
59 pub color_transition_base: &'static str,
60}
61
62impl Default for Theme {
64 fn default() -> Self {
65 Self {
66 color_primary: "#409EFF",
67 color_success: "#67C23A",
68 color_warning: "#E6A23C",
69 color_danger: "#F56C6C",
70 color_info: "#909399",
71 color_white: "#FFFFFF",
72 color_black: "#000000",
73 color_text_primary: "#303133",
74 color_text_regular: "#606266",
75 color_text_secondary: "#909399",
76 color_text_placeholder: "#C0C4CC",
77 border_color_base: "#DCDFE6",
78 border_color_light: "#E4E7ED",
79 border_color_lighter: "#EBEEF5",
80 border_color_extra_light: "#F2F6FC",
81 background_color_base: "#F5F7FA",
82 font_size_extra_large: "20px",
83 font_size_large: "18px",
84 font_size_medium: "16px",
85 font_size_base: "14px",
86 font_size_small: "13px",
87 font_size_extra_small: "12px",
88 border_radius_base: "4px",
89 border_radius_small: "2px",
90 border_radius_circle: "100%",
91 padding_base: "12px 20px",
92 all_transition: "all .3s cubic-bezier(.645,.045,.355,1)",
93 fade_transition: "opacity 300ms cubic-bezier(0.23, 1, 0.32, 1)",
94 border_transition_base: "border-color .2s cubic-bezier(.645,.045,.355,1)",
95 color_transition_base: "color .2s cubic-bezier(.645,.045,.355,1)",
96 }
97 }
98}
99
100pub struct ButtonStyles;
102
103impl ButtonStyles {
104 pub fn base(theme: &Theme) -> String {
105 format!(r#"
106/* === Button Base Styles === */
107.el-button {{
108 display: inline-block;
109 line-height: 1;
110 white-space: nowrap;
111 cursor: pointer;
112 background: {};
113 border: 1px solid {};
114 color: {};
115 -webkit-appearance: none;
116 text-align: center;
117 box-sizing: border-box;
118 outline: none;
119 margin: 0;
120 transition: {};
121 font-weight: 500;
122 user-select: none;
123 padding: 12px 20px;
124 font-size: {};
125 border-radius: {};
126 height: 40px;
127}}
128
129.el-button--primary {{
130 color: {};
131 background-color: {};
132 border-color: {};
133}}
134
135.el-button--success {{
136 color: {};
137 background-color: {};
138 border-color: {};
139}}
140
141.el-button--warning {{
142 color: {};
143 background-color: {};
144 border-color: {};
145}}
146
147.el-button--danger {{
148 color: {};
149 background-color: {};
150 border-color: {};
151}}
152
153.el-button--info {{
154 color: {};
155 background-color: {};
156 border-color: {};
157}}
158
159.el-button--large {{
160 height: 40px;
161 padding: 12px 20px;
162 font-size: {};
163}}
164
165.el-button--medium {{
166 height: 36px;
167 padding: 10px 20px;
168 font-size: {};
169}}
170
171.el-button--small {{
172 height: 32px;
173 padding: 9px 15px;
174 font-size: {};
175}}
176
177.el-button--mini {{
178 height: 28px;
179 padding: 7px 15px;
180 font-size: {};
181}}
182
183.el-button.is-disabled,
184.el-button.is-disabled:focus,
185.el-button.is-disabled:hover {{
186 color: {};
187 cursor: not-allowed;
188 background-image: none;
189 background-color: {};
190 border-color: {};
191}}
192
193.el-button.is-round {{
194 border-radius: 20px;
195 padding: 12px 23px;
196}}
197
198.el-button.is-circle {{
199 border-radius: 50%;
200 padding: 12px;
201}}
202 "#,
203 theme.color_white, theme.border_color_base, theme.color_text_regular, theme.all_transition,
204 theme.font_size_base, theme.border_radius_base, theme.color_white, theme.color_primary, theme.color_primary,
205 theme.color_white, theme.color_success, theme.color_success,
206 theme.color_white, theme.color_warning, theme.color_warning,
207 theme.color_white, theme.color_danger, theme.color_danger,
208 theme.color_white, theme.color_info, theme.color_info,
209 theme.font_size_large, theme.font_size_base, theme.font_size_small, theme.font_size_extra_small,
210 theme.color_text_placeholder, theme.color_white, theme.border_color_base
211 )
212 }
213}
214
215pub struct InputStyles;
217
218impl InputStyles {
219 pub fn base(theme: &Theme) -> String {
220 format!(r#"
221/* === Input Base Styles === */
222.el-input {{
223 position: relative;
224 font-size: {};
225 display: inline-block;
226 width: 100%;
227}}
228
229.el-input__inner {{
230 appearance: none;
231 background-color: {};
232 background-image: none;
233 border-radius: {};
234 border: 1px solid {};
235 box-sizing: border-box;
236 color: {};
237 display: inline-block;
238 font-size: inherit;
239 height: 40px;
240 line-height: 40px;
241 outline: none;
242 padding: 0 15px;
243 transition: {};
244 width: 100%;
245}}
246
247.el-input__inner:focus {{
248 outline: none;
249 border-color: {};
250}}
251
252.el-input--large .el-input__inner {{
253 height: 40px;
254 line-height: 40px;
255}}
256
257.el-input--medium .el-input__inner {{
258 height: 36px;
259 line-height: 36px;
260}}
261
262.el-input--small .el-input__inner {{
263 height: 32px;
264 line-height: 32px;
265}}
266
267.el-input--mini .el-input__inner {{
268 height: 28px;
269 line-height: 28px;
270}}
271
272.el-input.is-disabled .el-input__inner {{
273 background-color: {};
274 border-color: {};
275 color: {};
276 cursor: not-allowed;
277}}
278
279.el-input__wrapper {{
280 display: inline-flex;
281 flex-grow: 1;
282 align-items: center;
283 justify-content: center;
284 padding: 1px 11px;
285 background-color: {};
286 background-image: none;
287 border-radius: {};
288 border: 1px solid {};
289 cursor: text;
290 transition: {};
291 transform: translateZ(0);
292}}
293
294.el-input__prefix {{
295 display: inline-flex;
296 margin-right: 8px;
297 color: {};
298}}
299
300.el-input__suffix {{
301 display: inline-flex;
302 margin-left: 8px;
303 color: {};
304}}
305 "#,
306 theme.font_size_base,
307 theme.color_white,
308 theme.border_radius_base,
309 theme.border_color_base,
310 theme.color_text_regular,
311 theme.border_transition_base,
312 theme.color_primary,
313 theme.background_color_base,
314 theme.border_color_base,
315 theme.color_text_placeholder,
316 theme.color_white,
317 theme.border_radius_base,
318 theme.border_color_base,
319 theme.border_transition_base,
320 theme.color_text_placeholder,
321 theme.color_text_placeholder
322 )
323 }
324}
325
326pub struct AlertStyles;
328
329impl AlertStyles {
330 pub fn base(theme: &Theme) -> String {
331 format!(r#"
332/* === Alert Base Styles === */
333.el-alert {{
334 position: relative;
335 padding: 8px 16px;
336 margin-bottom: 16px;
337 border-radius: {};
338 overflow: hidden;
339 opacity: 1;
340 display: flex;
341 align-items: center;
342 transition: {};
343 font-size: {};
344 line-height: 1.4;
345}}
346
347.el-alert--success {{
348 background-color: #f0f9ff;
349 color: {};
350 border: 1px solid rgba(103, 194, 58, 0.2);
351}}
352
353.el-alert--warning {{
354 background-color: #fdf6ec;
355 color: {};
356 border: 1px solid rgba(230, 162, 60, 0.2);
357}}
358
359.el-alert--error {{
360 background-color: #fef0f0;
361 color: {};
362 border: 1px solid rgba(245, 108, 108, 0.2);
363}}
364
365.el-alert--info {{
366 background-color: #f4f4f5;
367 color: {};
368 border: 1px solid rgba(144, 147, 153, 0.2);
369}}
370
371.el-alert__title {{
372 font-size: {};
373 line-height: 20px;
374}}
375
376.el-alert__description {{
377 font-size: {};
378 color: #666;
379 margin-top: 4px;
380}}
381
382.el-alert__icon {{
383 display: inline-block;
384 font-size: 16px;
385 margin-right: 8px;
386}}
387
388.el-alert__close-btn {{
389 position: absolute;
390 top: 12px;
391 right: 15px;
392 cursor: pointer;
393 font-size: 12px;
394 color: #c0c4cc;
395 background: none;
396 border: none;
397 padding: 0;
398}}
399
400.el-alert__content {{
401 display: table-cell;
402 padding: 0 8px;
403}}
404 "#,
405 theme.border_radius_base,
406 theme.all_transition,
407 theme.font_size_base,
408 theme.color_success,
409 theme.color_warning,
410 theme.color_danger,
411 theme.color_info,
412 theme.font_size_base,
413 theme.font_size_small
414 )
415 }
416}
417
418pub struct FormStyles;
420
421impl FormStyles {
422 pub fn base(theme: &Theme) -> String {
423 format!(r#"
424/* === Form Base Styles === */
425.el-form {{
426 width: 100%;
427}}
428
429.el-form--horizontal {{
430 display: flex;
431 flex-wrap: wrap;
432}}
433
434.el-form--vertical {{
435 display: flex;
436 flex-direction: column;
437}}
438
439.el-form-item {{
440 margin-bottom: 22px;
441 display: flex;
442 align-items: flex-start;
443}}
444
445.el-form-item__label {{
446 line-height: 40px;
447 padding-right: 12px;
448 font-size: {};
449 color: {};
450 width: 120px;
451}}
452
453.el-form-item__content {{
454 line-height: 40px;
455 position: relative;
456 font-size: {};
457 flex: 1;
458}}
459
460.el-form-item.is-required .el-form-item__label:before {{
461 content: "*";
462 color: #f56c6c;
463 margin-right: 4px;
464}}
465
466.el-form-item__error {{
467 position: absolute;
468 top: 100%;
469 left: 0;
470 line-height: 1;
471 padding-top: 4px;
472 color: #f56c6c;
473 font-size: {};
474}}
475
476.el-form-item.is-error .el-input__inner {{
477 border-color: #f56c6c;
478}}
479
480.el-select {{
481 position: relative;
482 display: inline-block;
483 width: 100%;
484}}
485
486.el-select__wrapper {{
487 display: inline-flex;
488 align-items: center;
489 justify-content: space-between;
490 width: 100%;
491 padding: 0 15px;
492 height: 40px;
493 background-color: {};
494 border: 1px solid {};
495 border-radius: {};
496 cursor: pointer;
497 transition: {};
498}}
499
500.el-checkbox {{
501 display: inline-flex;
502 align-items: center;
503 cursor: pointer;
504 margin-right: 30px;
505 font-size: {};
506 color: {};
507}}
508
509.el-checkbox__input {{
510 position: relative;
511 display: inline-block;
512 width: 14px;
513 height: 14px;
514 margin-right: 8px;
515}}
516
517.el-radio {{
518 display: inline-flex;
519 align-items: center;
520 cursor: pointer;
521 margin-right: 30px;
522 font-size: {};
523 color: {};
524}}
525
526.el-radio__input {{
527 position: relative;
528 display: inline-block;
529 width: 14px;
530 height: 14px;
531 margin-right: 8px;
532}}
533 "#,
534 theme.font_size_base,
535 theme.color_text_regular,
536 theme.font_size_base,
537 theme.font_size_extra_small,
538 theme.color_white,
539 theme.border_color_base,
540 theme.border_radius_base,
541 theme.border_transition_base,
542 theme.font_size_base,
543 theme.color_text_regular,
544 theme.font_size_base,
545 theme.color_text_regular
546 )
547 }
548}
549
550pub struct CardStyles;
552
553impl CardStyles {
554 pub fn base(theme: &Theme) -> String {
555 format!(r#"
556/* === Card Base Styles === */
557.el-card {{
558 border-radius: {};
559 border: 1px solid {};
560 background-color: {};
561 overflow: hidden;
562 color: {};
563 transition: {};
564}}
565
566.el-card__header {{
567 padding: 18px 20px;
568 border-bottom: 1px solid {};
569 box-sizing: border-box;
570}}
571
572.el-card__body {{
573 padding: 20px;
574}}
575
576.el-card.is-hover-shadow:hover {{
577 box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
578}}
579
580.el-card.is-always-shadow {{
581 box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
582}}
583
584.el-panel {{
585 background: {};
586 border: 1px solid {};
587 border-radius: {};
588 margin-bottom: 16px;
589}}
590
591.el-panel__header {{
592 padding: 12px 15px;
593 border-bottom: 1px solid {};
594 font-weight: 500;
595 font-size: {};
596}}
597
598.el-panel__body {{
599 padding: 15px;
600}}
601
602.el-panel.is-collapsible .el-panel__header {{
603 cursor: pointer;
604}}
605
606.el-panel.is-collapsed .el-panel__body {{
607 display: none;
608}}
609
610.el-box {{
611 display: block;
612 box-sizing: border-box;
613}}
614 "#,
615 theme.border_radius_base,
616 theme.border_color_lighter,
617 theme.color_white,
618 theme.color_text_regular,
619 theme.all_transition,
620 theme.border_color_lighter,
621 theme.color_white,
622 theme.border_color_lighter,
623 theme.border_radius_base,
624 theme.border_color_lighter,
625 theme.font_size_base
626 )
627 }
628}
629
630pub struct TableStyles;
632
633impl TableStyles {
634 pub fn base(theme: &Theme) -> String {
635 format!(r#"
636/* === Table Base Styles === */
637.el-table {{
638 width: 100%;
639 border-collapse: collapse;
640 font-size: {};
641 color: {};
642 position: relative;
643}}
644
645.el-table__header {{
646 background-color: {};
647}}
648
649.el-table__header tr {{
650 background-color: {};
651}}
652
653.el-table__header th {{
654 text-align: left;
655 font-weight: 500;
656 background-color: {};
657 border-bottom: 1px solid {};
658 padding: 12px 0;
659 font-size: {};
660 color: {};
661}}
662
663.el-table__body tr {{
664 transition: {};
665}}
666
667.el-table__body tr:hover {{
668 background-color: {};
669}}
670
671.el-table__row--striped td {{
672 background-color: {};
673}}
674
675.el-table__cell {{
676 padding: 12px 10px;
677 border-bottom: 1px solid {};
678 min-width: 0;
679 box-sizing: border-box;
680 text-overflow: ellipsis;
681 vertical-align: middle;
682 position: relative;
683 text-align: left;
684}}
685
686.el-table--border {{
687 border: 1px solid {};
688}}
689
690.el-table--border td, .el-table--border th {{
691 border-right: 1px solid {};
692}}
693
694.el-data-list {{
695 width: 100%;
696}}
697
698.el-data-list__item {{
699 padding: 16px;
700 border-bottom: 1px solid {};
701 cursor: pointer;
702 transition: {};
703}}
704
705.el-data-list__item:hover {{
706 background-color: {};
707}}
708 "#,
709 theme.font_size_base,
710 theme.color_text_regular,
711 theme.background_color_base,
712 theme.background_color_base,
713 theme.background_color_base,
714 theme.border_color_lighter,
715 theme.font_size_base,
716 theme.color_text_primary,
717 theme.color_transition_base,
718 theme.background_color_base,
719 theme.background_color_base,
720 theme.border_color_lighter,
721 theme.border_color_lighter,
722 theme.border_color_lighter,
723 theme.border_color_lighter,
724 theme.color_transition_base,
725 theme.background_color_base
726 )
727 }
728}
729
730pub struct LayoutStyles;
732
733impl LayoutStyles {
734 pub fn base(theme: &Theme) -> String {
735 format!(r#"
736/* === Layout Base Styles === */
737.el-container {{
738 display: flex;
739 flex-direction: column;
740 min-height: 100vh;
741}}
742
743.el-header {{
744 padding: 0 20px;
745 box-sizing: border-box;
746 flex-shrink: 0;
747 background-color: {};
748}}
749
750.el-aside {{
751 overflow: auto;
752 box-sizing: border-box;
753 flex-shrink: 0;
754 background-color: {};
755}}
756
757.el-main {{
758 flex: 1;
759 overflow: auto;
760 padding: 20px;
761 box-sizing: border-box;
762 background-color: {};
763}}
764
765.el-footer {{
766 padding: 0 20px;
767 box-sizing: border-box;
768 flex-shrink: 0;
769 background-color: {};
770}}
771
772.el-row {{
773 position: relative;
774 box-sizing: border-box;
775 display: flex;
776 flex-wrap: wrap;
777}}
778
779.el-row::before, .el-row::after {{
780 display: table;
781 content: "";
782}}
783
784.el-col {{
785 position: relative;
786 box-sizing: border-box;
787}}
788
789.el-col-1 {{ width: 4.16666667%; }}
790.el-col-2 {{ width: 8.33333333%; }}
791.el-col-3 {{ width: 12.5%; }}
792.el-col-4 {{ width: 16.66666667%; }}
793.el-col-5 {{ width: 20.83333333%; }}
794.el-col-6 {{ width: 25%; }}
795.el-col-7 {{ width: 29.16666667%; }}
796.el-col-8 {{ width: 33.33333333%; }}
797.el-col-9 {{ width: 37.5%; }}
798.el-col-10 {{ width: 41.66666667%; }}
799.el-col-11 {{ width: 45.83333333%; }}
800.el-col-12 {{ width: 50%; }}
801.el-col-13 {{ width: 54.16666667%; }}
802.el-col-14 {{ width: 58.33333333%; }}
803.el-col-15 {{ width: 62.5%; }}
804.el-col-16 {{ width: 66.66666667%; }}
805.el-col-17 {{ width: 70.83333333%; }}
806.el-col-18 {{ width: 75%; }}
807.el-col-19 {{ width: 79.16666667%; }}
808.el-col-20 {{ width: 83.33333333%; }}
809.el-col-21 {{ width: 87.5%; }}
810.el-col-22 {{ width: 91.66666667%; }}
811.el-col-23 {{ width: 95.83333333%; }}
812.el-col-24 {{ width: 100%; }}
813 "#,
814 theme.color_white,
815 theme.background_color_base,
816 theme.background_color_base,
817 theme.color_white
818 )
819 }
820}
821
822pub struct CompleteCssBuilder {
824 theme: Theme,
825 styles: Vec<String>,
826}
827
828impl CompleteCssBuilder {
829 pub fn new() -> Self {
830 Self {
831 theme: Theme::default(),
832 styles: Vec::new(),
833 }
834 }
835
836 pub fn with_theme(mut self, theme: Theme) -> Self {
837 self.theme = theme;
838 self
839 }
840
841 pub fn with_reset_styles(mut self) -> Self {
842 let reset = r#"
843/* === CSS Reset === */
844*, *::before, *::after {
845 box-sizing: border-box;
846 margin: 0;
847 padding: 0;
848}
849
850html {
851 font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
852 font-size: 14px;
853 line-height: 1.42857143;
854 color: #606266;
855 background-color: #fff;
856}
857
858body {
859 font-family: inherit;
860 font-size: 14px;
861 line-height: 1.42857143;
862 color: #606266;
863}
864
865.hidden {
866 display: none !important;
867}
868 "#.to_string();
869 self.styles.push(reset);
870 self
871 }
872
873 pub fn with_button_styles(mut self) -> Self {
874 self.styles.push(ButtonStyles::base(&self.theme));
875 self
876 }
877
878 pub fn with_input_styles(mut self) -> Self {
879 self.styles.push(InputStyles::base(&self.theme));
880 self
881 }
882
883 pub fn with_alert_styles(mut self) -> Self {
884 self.styles.push(AlertStyles::base(&self.theme));
885 self
886 }
887
888 pub fn with_form_styles(mut self) -> Self {
889 self.styles.push(FormStyles::base(&self.theme));
890 self
891 }
892
893 pub fn with_card_styles(mut self) -> Self {
894 self.styles.push(CardStyles::base(&self.theme));
895 self
896 }
897
898 pub fn with_table_styles(mut self) -> Self {
899 self.styles.push(TableStyles::base(&self.theme));
900 self
901 }
902
903 pub fn with_layout_styles(mut self) -> Self {
904 self.styles.push(LayoutStyles::base(&self.theme));
905 self
906 }
907
908 pub fn with_utility_styles(mut self) -> Self {
909 let utilities = r#"
910/* === Utility Classes === */
911.text-c { text-align: center !important; }
912.text-l { text-align: left !important; }
913.text-r { text-align: right !important; }
914.w-100 { width: 100% !important; }
915.h-100 { height: 100% !important; }
916.m-0 { margin: 0 !important; }
917.p-0 { padding: 0 !important; }
918.d-flex { display: flex !important; }
919.flex-column { flex-direction: column !important; }
920.flex-wrap { flex-wrap: wrap !important; }
921.align-center { align-items: center !important; }
922.justify-center { justify-content: center !important; }
923 "#.to_string();
924 self.styles.push(utilities);
925 self
926 }
927
928 pub fn build(self) -> String {
929 self.styles.join("\n\n")
930 }
931
932 pub fn build_complete(self) -> String {
933 self
934 .with_reset_styles()
935 .with_button_styles()
936 .with_input_styles()
937 .with_alert_styles()
938 .with_form_styles()
939 .with_card_styles()
940 .with_table_styles()
941 .with_layout_styles()
942 .with_utility_styles()
943 .build()
944 }
945}
946
947pub struct CompleteStyleManager {
949 theme: Theme,
950}
951
952impl CompleteStyleManager {
953 pub fn new() -> Self {
954 Self {
955 theme: Theme::default(),
956 }
957 }
958
959 pub fn with_theme(mut self, theme: Theme) -> Self {
960 self.theme = theme;
961 self
962 }
963
964 pub fn generate_complete_styles(&self) -> String {
965 CompleteCssBuilder::new()
966 .with_theme(self.theme.clone())
967 .build_complete()
968 }
969
970 pub fn generate_styles_for_components(&self, components: &[&str]) -> String {
971 let mut builder = CompleteCssBuilder::new().with_theme(self.theme.clone());
972
973 for component in components {
974 match *component {
975 "button" => builder = builder.with_button_styles(),
976 "input" => builder = builder.with_input_styles(),
977 "alert" => builder = builder.with_alert_styles(),
978 "form" => builder = builder.with_form_styles(),
979 "card" => builder = builder.with_card_styles(),
980 "table" => builder = builder.with_table_styles(),
981 "layout" => builder = builder.with_layout_styles(),
982 _ => {},
983 }
984 }
985
986 builder
987 .with_reset_styles()
988 .with_utility_styles()
989 .build()
990 }
991}