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