@qlover/create-app 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/package.json +3 -3
  3. package/templates/react-app/config/Identifier.I18n.ts +878 -0
  4. package/templates/react-app/config/app.router.json +7 -7
  5. package/templates/react-app/config/theme.json +7 -88
  6. package/templates/react-app/package.json +7 -3
  7. package/templates/react-app/postcss.config.js +1 -2
  8. package/templates/react-app/public/locales/en/common.json +118 -1
  9. package/templates/react-app/public/locales/zh/common.json +118 -1
  10. package/templates/react-app/src/App.tsx +14 -2
  11. package/templates/react-app/src/base/cases/RequestLogger.ts +1 -1
  12. package/templates/react-app/src/base/services/I18nService.ts +31 -3
  13. package/templates/react-app/src/base/services/ProcesserService.ts +0 -1
  14. package/templates/react-app/src/core/IOC.ts +12 -7
  15. package/templates/react-app/src/core/bootstrap.ts +42 -53
  16. package/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +14 -0
  17. package/templates/react-app/src/core/bootstraps/index.ts +36 -7
  18. package/templates/react-app/src/core/registers/RegisterApi.ts +2 -5
  19. package/templates/react-app/src/core/registers/RegisterCommon.ts +38 -29
  20. package/templates/react-app/src/core/registers/RegisterControllers.ts +5 -10
  21. package/templates/react-app/src/core/registers/RegisterGlobals.ts +13 -13
  22. package/templates/react-app/src/core/registers/index.ts +27 -12
  23. package/templates/react-app/src/main.tsx +1 -1
  24. package/templates/react-app/src/pages/404.tsx +1 -1
  25. package/templates/react-app/src/pages/500.tsx +1 -1
  26. package/templates/react-app/src/pages/auth/Login.tsx +128 -36
  27. package/templates/react-app/src/pages/base/About.tsx +5 -2
  28. package/templates/react-app/src/pages/base/ErrorIdentifier.tsx +38 -19
  29. package/templates/react-app/src/pages/base/Executor.tsx +447 -29
  30. package/templates/react-app/src/pages/base/Home.tsx +99 -93
  31. package/templates/react-app/src/pages/base/JSONStorage.tsx +47 -38
  32. package/templates/react-app/src/pages/base/Layout.tsx +5 -2
  33. package/templates/react-app/src/pages/base/Request.tsx +90 -208
  34. package/templates/react-app/src/pages/base/components/BaseHeader.tsx +13 -5
  35. package/templates/react-app/src/styles/css/page.css +11 -0
  36. package/templates/react-app/src/styles/css/tailwind.css +5 -0
  37. package/templates/react-app/src/styles/css/themes/_default.css +200 -0
  38. package/templates/react-app/src/styles/css/themes/dark.css +154 -0
  39. package/templates/react-app/src/styles/css/themes/index.css +3 -0
  40. package/templates/react-app/src/styles/css/themes/pink.css +160 -0
  41. package/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +56 -0
  42. package/templates/react-app/src/uikit/components/Loading.tsx +27 -21
  43. package/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +63 -13
  44. package/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +1 -1
  45. package/templates/react-app/src/uikit/controllers/UserController.ts +1 -1
  46. package/templates/react-app/tailwind.config.js +1 -15
  47. package/templates/react-app/vite.config.ts +7 -1
  48. package/templates/react-app/lib/tailwind/root10px.js +0 -178
  49. package/templates/react-app/lib/tailwind/theme-generator.js +0 -238
  50. package/templates/react-app/public/locales/en/about.json +0 -3
  51. package/templates/react-app/public/locales/en/executor.json +0 -6
  52. package/templates/react-app/public/locales/en/home.json +0 -10
  53. package/templates/react-app/public/locales/en/jsonStorage.json +0 -11
  54. package/templates/react-app/public/locales/en/login.json +0 -7
  55. package/templates/react-app/public/locales/en/request.json +0 -15
  56. package/templates/react-app/public/locales/zh/about.json +0 -3
  57. package/templates/react-app/public/locales/zh/executor.json +0 -6
  58. package/templates/react-app/public/locales/zh/home.json +0 -10
  59. package/templates/react-app/public/locales/zh/jsonStorage.json +0 -11
  60. package/templates/react-app/public/locales/zh/login.json +0 -7
  61. package/templates/react-app/public/locales/zh/request.json +0 -15
  62. package/templates/react-app/src/base/port/InversifyIocInterface.ts +0 -9
  63. package/templates/react-app/src/uikit/styles/css/page.css +0 -3
  64. package/templates/react-app/src/uikit/styles/css/tailwind.css +0 -3
  65. /package/templates/react-app/config/{ErrorIdentifier.ts → Identifier.Error.ts} +0 -0
  66. /package/templates/react-app/src/{uikit/styles → styles}/css/index.css +0 -0
@@ -0,0 +1,154 @@
1
+ /* theme variables - for tailwind */
2
+ @layer base {
3
+ [data-theme='dark'] {
4
+ /* 基础背景色 */
5
+ --color-bg-base: 15 23 42; /* slate-900 */
6
+ --color-bg-secondary: 30 41 59; /* slate-800 */
7
+ --color-bg-elevated: 51 65 85; /* slate-700 */
8
+
9
+ /* 文字颜色 */
10
+ --color-text-primary: 255 255 255;
11
+ --color-text-secondary: 148 163 184; /* slate-400 */
12
+ --color-text-tertiary: 100 116 139; /* slate-500 */
13
+
14
+ /* 边框颜色 */
15
+ --color-border: 51 65 85; /* slate-700 */
16
+
17
+ /* 品牌色 */
18
+ --color-brand: 37 99 235; /* blue-600 */
19
+ --color-brand-hover: 59 130 246; /* blue-500 */
20
+ }
21
+ }
22
+
23
+ /* custom variables - for antd and custom css */
24
+ [data-theme='dark'],
25
+ [data-theme='dark'] .fe-theme {
26
+ /* Antd 主色调相关变量 - 紫色主题 */
27
+ --fe-color-primary: #9333ea; /* purple-600 */
28
+ --fe-color-primary-hover: #a855f7; /* purple-500 */
29
+ --fe-color-primary-active: #7e22ce; /* purple-700 */
30
+ --fe-color-primary-bg: rgba(147, 51, 234, 0.1); /* purple-600 with 0.1 opacity */
31
+ --fe-color-primary-border: #9333ea; /* purple-600 */
32
+ --fe-color-primary-text: #a855f7; /* purple-500 - 文字用亮一点的紫色 */
33
+ --fe-color-primary-text-hover: #c084fc; /* purple-400 */
34
+ --fe-color-primary-text-active: #9333ea; /* purple-600 */
35
+ --fe-color-primary-deprecated-bg: #9333ea; /* purple-600 */
36
+ --fe-color-primary-deprecated-border: #9333ea; /* purple-600 */
37
+
38
+ /* Antd 基础变量 */
39
+ --fe-color-bg-container: rgb(30 41 59);
40
+ --fe-color-bg-elevated: rgb(51 65 85);
41
+ --fe-color-text: rgba(255, 255, 255, 0.85);
42
+ --fe-color-text-secondary: rgba(255, 255, 255, 0.45);
43
+ --fe-color-text-tertiary: rgba(255, 255, 255, 0.35);
44
+ --fe-color-text-quaternary: rgba(255, 255, 255, 0.15);
45
+ --fe-color-text-placeholder: rgba(255, 255, 255, 0.25);
46
+ --fe-color-border: rgb(51 65 85);
47
+
48
+ /* Antd Input 组件变量 */
49
+ &.ant-input-css-var {
50
+ /* Input 交互状态 */
51
+ --fe-input-hover-border-color: #4096ff;
52
+ --fe-input-active-border-color: #1677ff;
53
+ --fe-input-active-shadow: 0 0 0 2px rgba(5, 145, 255, 0.2);
54
+ --fe-input-hover-bg: rgb(51 65 85);
55
+ --fe-input-active-bg: rgb(51 65 85);
56
+ --fe-input-addon-bg: rgba(255, 255, 255, 0.02);
57
+ }
58
+
59
+ /* 登录页特定样式 */
60
+ --login-card-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
61
+ --login-input-bg: var(--fe-color-bg-container);
62
+ --login-input-border: var(--fe-color-border);
63
+ --login-button-text: rgb(255 255 255);
64
+ --login-social-button-bg: var(--fe-color-bg-container);
65
+ --login-social-button-border: var(--fe-color-border);
66
+
67
+ /* Antd Button 组件变量 */
68
+ &.ant-btn {
69
+ /* 紫色主题 */
70
+ --fe-button-primary-color: #fff;
71
+ --fe-button-primary-bg: #8b5cf6; /* violet-500 */
72
+ --fe-button-primary-hover-bg: #7c3aed; /* violet-600 */
73
+ --fe-button-primary-active-bg: #6d28d9; /* violet-700 */
74
+ --fe-button-primary-shadow: 0 2px 0 rgba(109, 40, 217, 0.2);
75
+
76
+ /* 默认按钮 */
77
+ --fe-button-default-color: rgba(255, 255, 255, 0.85);
78
+ --fe-button-default-bg: rgb(30 41 59); /* slate-800 */
79
+ --fe-button-default-border-color: rgb(51 65 85); /* slate-700 */
80
+ --fe-button-default-hover-bg: rgb(30 41 59);
81
+ --fe-button-default-hover-color: #8b5cf6;
82
+ --fe-button-default-hover-border-color: #8b5cf6;
83
+ --fe-button-default-active-bg: rgb(30 41 59);
84
+ --fe-button-default-active-color: #6d28d9;
85
+ --fe-button-default-active-border-color: #6d28d9;
86
+ --fe-button-default-shadow: 0 2px 0 rgba(0, 0, 0, 0.1);
87
+
88
+ /* 其他状态 */
89
+ --fe-button-text-hover-bg: rgba(255, 255, 255, 0.08);
90
+ --fe-button-border-color-disabled: rgb(51 65 85);
91
+ }
92
+
93
+ /* Antd 图标相关变量 */
94
+ --fe-color-icon: rgba(255, 255, 255, 0.45); /* 暗色主题下的图标颜色 */
95
+ --fe-color-icon-hover: rgba(255, 255, 255, 0.85); /* 暗色主题下图标悬停颜色 */
96
+ --fe-color-icon-active: var(--fe-color-primary); /* 使用主题紫色 */
97
+ --fe-color-icon-disabled: rgba(255, 255, 255, 0.25); /* 暗色主题下禁用状态 */
98
+
99
+ /* Antd Select 组件变量 */
100
+ &.ant-select-css-var {
101
+ --fe-select-internal_fixed_item_margin: 2px;
102
+ --fe-select-z-index-popup: 1050;
103
+ --fe-select-option-selected-color: rgba(255, 255, 255, 0.88);
104
+ --fe-select-option-selected-font-weight: 600;
105
+ --fe-select-option-selected-bg: var(--fe-color-primary-bg);
106
+ --fe-select-option-active-bg: rgba(255, 255, 255, 0.08);
107
+ --fe-select-option-padding: 5px 12px;
108
+ --fe-select-option-font-size: 14px;
109
+ --fe-select-option-line-height: 1.5714285714285714;
110
+ --fe-select-option-height: 32px;
111
+ --fe-select-selector-bg: var(--fe-color-bg-container);
112
+ --fe-select-clear-bg: var(--fe-color-bg-container);
113
+ --fe-select-single-item-height-lg: 40px;
114
+ --fe-select-multiple-item-bg: rgba(255, 255, 255, 0.08);
115
+ --fe-select-multiple-item-border-color: transparent;
116
+ --fe-select-multiple-item-height: 24px;
117
+ --fe-select-multiple-item-height-sm: 16px;
118
+ --fe-select-multiple-item-height-lg: 32px;
119
+ --fe-select-multiple-selector-bg-disabled: rgba(255, 255, 255, 0.08);
120
+ --fe-select-multiple-item-color-disabled: rgba(255, 255, 255, 0.25);
121
+ --fe-select-multiple-item-border-color-disabled: transparent;
122
+ --fe-select-show-arrow-padding-inline-end: 18px;
123
+ --fe-select-hover-border-color: var(--fe-color-primary);
124
+ --fe-select-active-border-color: var(--fe-color-primary-active);
125
+ --fe-select-active-outline-color: var(--fe-color-primary-bg);
126
+ --fe-select-select-affix-padding: 4px;
127
+ }
128
+
129
+ /* Antd Tag 组件变量 */
130
+ &.ant-tag-css-var {
131
+ --fe-tag-default-bg: var(--fe-color-bg-container);
132
+ --fe-tag-default-color: var(--fe-color-text);
133
+ --fe-tag-default-border-color: var(--fe-color-border);
134
+ --fe-tag-font-size: 12px;
135
+ --fe-tag-line-height: 20px;
136
+ --fe-tag-height: 22px;
137
+ --fe-tag-padding-horizontal: 7px;
138
+ --fe-tag-margin: 0 8px 0 0;
139
+ --fe-tag-border-radius: 4px;
140
+ }
141
+
142
+ /* Antd Progress 组件变量 */
143
+ &.ant-progress-css-var {
144
+ --fe-progress-default-color: var(--fe-color-primary);
145
+ --fe-progress-remaining-color: var(--fe-color-bg-elevated);
146
+ --fe-progress-text-color: var(--fe-color-text);
147
+ --fe-progress-line-font-size: 14px;
148
+ --fe-progress-circle-font-size: 14px;
149
+ --fe-progress-circle-text-color: var(--fe-color-text);
150
+ --fe-progress-circle-trail-color: var(--fe-color-bg-elevated);
151
+ --fe-progress-circle-stroke-width: 6px;
152
+ --fe-progress-line-stroke-width: 8px;
153
+ }
154
+ }
@@ -0,0 +1,3 @@
1
+ @import './_default.css';
2
+ @import './dark.css';
3
+ @import './pink.css';
@@ -0,0 +1,160 @@
1
+ /* theme variables - for tailwind */
2
+ @layer base {
3
+ [data-theme='pink'] {
4
+ /* 基础背景色 */
5
+ --color-bg-base: 255 241 242; /* rose-50 */
6
+ --color-bg-secondary: 255 228 230; /* rose-100 */
7
+ --color-bg-elevated: 254 205 211; /* rose-200 */
8
+
9
+ /* 文字颜色 */
10
+ --color-text-primary: 190 18 60; /* rose-700 */
11
+ --color-text-secondary: 225 29 72; /* rose-600 */
12
+ --color-text-tertiary: 244 63 94; /* rose-500 */
13
+
14
+ /* 边框颜色 */
15
+ --color-border: 254 205 211; /* rose-200 */
16
+
17
+ /* 品牌色 */
18
+ --color-brand: 225 29 72; /* rose-600 */
19
+ --color-brand-hover: 244 63 94; /* rose-500 */
20
+ }
21
+ }
22
+
23
+ /* custom variables - for antd and custom css */
24
+ [data-theme='pink'],
25
+ [data-theme='pink'] .fe-theme {
26
+ /* Antd 主色调相关变量 - 玫瑰色主题 */
27
+ --fe-color-primary: #f472b6; /* pink-400 */
28
+ --fe-color-primary-hover: #ec4899; /* pink-500 */
29
+ --fe-color-primary-active: #db2777; /* pink-600 */
30
+ --fe-color-primary-bg: rgba(244, 114, 182, 0.1); /* pink-400 with 0.1 opacity */
31
+ --fe-color-primary-border: #f472b6; /* pink-400 */
32
+ --fe-color-primary-text: #f472b6; /* pink-400 */
33
+ --fe-color-primary-text-hover: #ec4899; /* pink-500 */
34
+ --fe-color-primary-text-active: #db2777; /* pink-600 */
35
+ --fe-color-primary-deprecated-bg: #f472b6; /* pink-400 */
36
+ --fe-color-primary-deprecated-border: #f472b6; /* pink-400 */
37
+
38
+ /* Antd 基础变量 */
39
+ --ant-color-bg-container: rgb(255 241 242);
40
+ --ant-color-bg-elevated: rgb(254 205 211);
41
+ --ant-color-text: rgba(190 18 60 / 0.85);
42
+ --ant-color-text-secondary: rgba(190 18 60 / 0.45);
43
+ --ant-color-text-tertiary: rgba(190 18 60 / 0.35);
44
+ --ant-color-text-quaternary: rgba(190 18 60 / 0.15);
45
+ --ant-color-text-placeholder: rgba(190 18 60 / 0.25);
46
+ --ant-color-border: rgb(254 205 211);
47
+
48
+ /* Antd Input 组件变量 */
49
+ &.ant-input-css-var {
50
+ /* Input 背景色 */
51
+ --fe-input-bg: var(--ant-color-bg-container);
52
+ --fe-input-hover-bg: var(--ant-color-bg-elevated);
53
+ --fe-input-active-bg: var(--ant-color-bg-elevated);
54
+ --fe-input-addon-bg: rgba(190 18 60, 0.02);
55
+
56
+ /* Input 边框颜色 */
57
+ --fe-input-border-color: var(--ant-color-border);
58
+ --fe-input-hover-border-color: rgb(var(--color-brand));
59
+ --fe-input-active-border-color: rgb(var(--color-brand));
60
+
61
+ /* Input 阴影效果 */
62
+ --fe-input-active-shadow: 0 0 0 2px rgba(var(--color-brand), 0.1);
63
+ }
64
+
65
+ /* 登录页特定样式 */
66
+ --login-card-shadow: 0 4px 12px rgba(244 63 94 / 0.15);
67
+ --login-input-bg: var(--ant-color-bg-container);
68
+ --login-input-border: var(--ant-color-border);
69
+ --login-button-text: rgb(255 255 255);
70
+ --login-social-button-bg: var(--ant-color-bg-container);
71
+ --login-social-button-border: var(--ant-color-border);
72
+
73
+ /* Antd Button 组件变量 */
74
+ &.ant-btn {
75
+ /* 玫瑰色主题 */
76
+ --fe-button-primary-color: #fff;
77
+ --fe-button-primary-bg: #f472b6; /* pink-400 */
78
+ --fe-button-primary-hover-bg: #ec4899; /* pink-500 */
79
+ --fe-button-primary-active-bg: #db2777; /* pink-600 */
80
+ --fe-button-primary-shadow: 0 2px 0 rgba(219, 39, 119, 0.15);
81
+
82
+ /* 默认按钮 */
83
+ --fe-button-default-color: rgb(190 18 60);
84
+ --fe-button-default-bg: rgb(255 241 242);
85
+ --fe-button-default-border-color: rgb(254 205 211);
86
+ --fe-button-default-hover-bg: rgb(255 241 242);
87
+ --fe-button-default-hover-color: #f472b6;
88
+ --fe-button-default-hover-border-color: #f472b6;
89
+ --fe-button-default-active-bg: rgb(255 241 242);
90
+ --fe-button-default-active-color: #db2777;
91
+ --fe-button-default-active-border-color: #db2777;
92
+ --fe-button-default-shadow: 0 2px 0 rgba(244, 114, 182, 0.1);
93
+
94
+ /* 其他状态 */
95
+ --fe-button-text-hover-bg: rgba(244, 114, 182, 0.08);
96
+ --fe-button-border-color-disabled: rgb(254 205 211);
97
+ }
98
+
99
+ /* Antd 图标相关变量 */
100
+ --fe-color-icon: rgba(190, 18, 60, 0.45); /* 粉色主题下的图标颜色 */
101
+ --fe-color-icon-hover: rgba(190, 18, 60, 0.85); /* 粉色主题下图标悬停颜色 */
102
+ --fe-color-icon-active: var(--fe-color-primary); /* 使用主题粉色 */
103
+ --fe-color-icon-disabled: rgba(190, 18, 60, 0.25); /* 粉色主题下禁用状态 */
104
+
105
+ /* Antd Select 组件变量 */
106
+ &.ant-select-css-var {
107
+ --fe-select-internal_fixed_item_margin: 2px;
108
+ --fe-select-z-index-popup: 1050;
109
+ --fe-select-option-selected-color: rgba(190, 18, 60, 0.88);
110
+ --fe-select-option-selected-font-weight: 600;
111
+ --fe-select-option-selected-bg: var(--fe-color-primary-bg);
112
+ --fe-select-option-active-bg: rgba(190, 18, 60, 0.04);
113
+ --fe-select-option-padding: 5px 12px;
114
+ --fe-select-option-font-size: 14px;
115
+ --fe-select-option-line-height: 1.5714285714285714;
116
+ --fe-select-option-height: 32px;
117
+ --fe-select-selector-bg: var(--fe-color-bg-container);
118
+ --fe-select-clear-bg: var(--fe-color-bg-container);
119
+ --fe-select-single-item-height-lg: 40px;
120
+ --fe-select-multiple-item-bg: rgba(190, 18, 60, 0.06);
121
+ --fe-select-multiple-item-border-color: transparent;
122
+ --fe-select-multiple-item-height: 24px;
123
+ --fe-select-multiple-item-height-sm: 16px;
124
+ --fe-select-multiple-item-height-lg: 32px;
125
+ --fe-select-multiple-selector-bg-disabled: rgba(190, 18, 60, 0.04);
126
+ --fe-select-multiple-item-color-disabled: rgba(190, 18, 60, 0.25);
127
+ --fe-select-multiple-item-border-color-disabled: transparent;
128
+ --fe-select-show-arrow-padding-inline-end: 18px;
129
+ --fe-select-hover-border-color: var(--fe-color-primary);
130
+ --fe-select-active-border-color: var(--fe-color-primary-active);
131
+ --fe-select-active-outline-color: var(--fe-color-primary-bg);
132
+ --fe-select-select-affix-padding: 4px;
133
+ }
134
+
135
+ /* Antd Tag 组件变量 */
136
+ &.ant-tag-css-var {
137
+ --fe-tag-default-bg: var(--fe-color-bg-container);
138
+ --fe-tag-default-color: var(--fe-color-text);
139
+ --fe-tag-default-border-color: var(--fe-color-border);
140
+ --fe-tag-font-size: 12px;
141
+ --fe-tag-line-height: 20px;
142
+ --fe-tag-height: 22px;
143
+ --fe-tag-padding-horizontal: 7px;
144
+ --fe-tag-margin: 0 8px 0 0;
145
+ --fe-tag-border-radius: 4px;
146
+ }
147
+
148
+ /* Antd Progress 组件变量 */
149
+ &.ant-progress-css-var {
150
+ --fe-progress-default-color: var(--fe-color-primary);
151
+ --fe-progress-remaining-color: var(--fe-color-bg-elevated);
152
+ --fe-progress-text-color: var(--fe-color-text);
153
+ --fe-progress-line-font-size: 14px;
154
+ --fe-progress-circle-font-size: 14px;
155
+ --fe-progress-circle-text-color: var(--fe-color-text);
156
+ --fe-progress-circle-trail-color: var(--fe-color-bg-elevated);
157
+ --fe-progress-circle-stroke-width: 6px;
158
+ --fe-progress-line-stroke-width: 8px;
159
+ }
160
+ }
@@ -0,0 +1,56 @@
1
+ import { Select } from 'antd';
2
+ import { GlobalOutlined } from '@ant-design/icons';
3
+ import { useNavigate, useParams } from 'react-router-dom';
4
+ import i18nConfig from '@config/i18n';
5
+ import { IOC } from '@/core/IOC';
6
+ import { I18nService, I18nServiceLocale } from '@/base/services/I18nService';
7
+ import { useCallback } from 'react';
8
+ import { useSliceStore } from '@qlover/slice-store-react';
9
+
10
+ const { supportedLngs } = i18nConfig;
11
+
12
+ export default function LanguageSwitcher() {
13
+ const navigate = useNavigate();
14
+ const i18nService = IOC(I18nService);
15
+ const loading = useSliceStore(i18nService, i18nService.selector.loading);
16
+ const { lng } = useParams<{ lng: I18nServiceLocale }>();
17
+ const currentPath = window.location.pathname;
18
+
19
+ const languageOptions = supportedLngs.map((lang) => ({
20
+ key: lang,
21
+ value: lang,
22
+ label: (
23
+ <div className="flex items-center gap-2">
24
+ <GlobalOutlined />
25
+ <span>{lang.toUpperCase()}</span>
26
+ </div>
27
+ )
28
+ }));
29
+
30
+ const handleLanguageChange = useCallback(
31
+ async (newLang: I18nServiceLocale) => {
32
+ i18nService.changeLoading(true);
33
+ // Change i18n language
34
+ await i18nService.changeLanguage(newLang);
35
+ // Update URL path
36
+ const newPath = currentPath.replace(`/${lng}`, `/${newLang}`);
37
+ navigate(newPath);
38
+ i18nService.changeLoading(false);
39
+ },
40
+ [lng, currentPath, navigate, i18nService]
41
+ );
42
+
43
+ return (
44
+ <div className="flex items-center gap-2">
45
+ <Select
46
+ loading={loading}
47
+ disabled={loading}
48
+ value={lng}
49
+ onChange={handleLanguageChange}
50
+ options={languageOptions}
51
+ style={{ width: 100 }}
52
+ className="min-w-24 max-w-full"
53
+ />
54
+ </div>
55
+ );
56
+ }
@@ -1,3 +1,5 @@
1
+ import clsx from 'clsx';
2
+
1
3
  /**
2
4
  * Loading component
3
5
  *
@@ -14,28 +16,32 @@
14
16
  export function Loading({ fullscreen = false }: { fullscreen?: boolean }) {
15
17
  return (
16
18
  <div
17
- className={`flex justify-center items-center ${fullscreen ? 'fixed inset-0 bg-white bg-opacity-80 z-50' : 'relative'}`}
19
+ className={clsx('flex justify-center items-center', {
20
+ 'fixed inset-0 backdrop-blur-sm z-50': fullscreen,
21
+ relative: !fullscreen
22
+ })}
23
+ style={{
24
+ backgroundColor: fullscreen
25
+ ? 'var(--fe-color-bg-container)'
26
+ : 'transparent'
27
+ }}
18
28
  >
19
- <svg
20
- className="animate-spin h-8 w-8 text-gray-500"
21
- xmlns="http://www.w3.org/2000/svg"
22
- fill="none"
23
- viewBox="0 0 24 24"
24
- >
25
- <circle
26
- className="opacity-25"
27
- cx="12"
28
- cy="12"
29
- r="10"
30
- stroke="currentColor"
31
- strokeWidth="4"
32
- ></circle>
33
- <path
34
- className="opacity-75"
35
- fill="currentColor"
36
- d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
37
- ></path>
38
- </svg>
29
+ <div className="flex space-x-3">
30
+ {[0, 1, 2].map((i) => (
31
+ <div
32
+ key={i}
33
+ className={clsx(
34
+ 'w-3 h-3 rounded-full',
35
+ 'bg-[var(--fe-color-primary)]',
36
+ 'animate-[bounce_1s_ease-in-out_infinite]'
37
+ )}
38
+ style={{
39
+ animationDelay: `${i * 0.2}s`,
40
+ opacity: 0.6
41
+ }}
42
+ />
43
+ ))}
44
+ </div>
39
45
  </div>
40
46
  );
41
47
  }
@@ -2,6 +2,37 @@ import { IOC } from '@/core/IOC';
2
2
  import { ThemeService } from '@qlover/corekit-bridge';
3
3
  import { useSliceStore } from '@qlover/slice-store-react';
4
4
  import { useTranslation } from 'react-i18next';
5
+ import { Select } from 'antd';
6
+ import {
7
+ BulbOutlined,
8
+ BulbFilled,
9
+ HeartFilled,
10
+ HeartOutlined
11
+ } from '@ant-design/icons';
12
+ import clsx from 'clsx';
13
+ import { useMemo } from 'react';
14
+ import * as i18nKeys from '@config/Identifier.I18n';
15
+
16
+ const colorMap: Record<
17
+ string,
18
+ { i18nkey: string; colors: string[]; icons: React.ElementType[] }
19
+ > = {
20
+ default: {
21
+ i18nkey: i18nKeys.HEADER_THEME_DEFAULT,
22
+ colors: ['text-text', 'text-text-secondary'],
23
+ icons: [BulbFilled, BulbOutlined]
24
+ },
25
+ dark: {
26
+ i18nkey: i18nKeys.HEADER_THEME_DARK,
27
+ colors: ['text-[#9333ea]', 'text-[#a855f7]'],
28
+ icons: [BulbFilled, BulbOutlined]
29
+ },
30
+ pink: {
31
+ i18nkey: i18nKeys.HEADER_THEME_PINK,
32
+ colors: ['text-[#f472b6]', 'text-[#ec4899]'],
33
+ icons: [HeartFilled, HeartOutlined]
34
+ }
35
+ };
5
36
 
6
37
  export default function ThemeSwitcher() {
7
38
  const themeService = IOC(ThemeService);
@@ -9,22 +40,41 @@ export default function ThemeSwitcher() {
9
40
  const themes = themeService.getSupportedThemes();
10
41
  const { t } = useTranslation('common');
11
42
 
43
+ const themeOptions = useMemo(() => {
44
+ return themes.map((themeName) => {
45
+ const { i18nkey, colors, icons } =
46
+ colorMap[themeName] || colorMap.default;
47
+ const [currentColor, normalColor] = colors;
48
+ const [CurrentIcon, NormalIcon] = icons;
49
+ const isSelf = theme === themeName;
50
+
51
+ return {
52
+ key: themeName + i18nkey,
53
+ value: themeName,
54
+ label: (
55
+ <div
56
+ className={clsx(
57
+ 'flex items-center gap-2',
58
+ isSelf ? currentColor : normalColor
59
+ )}
60
+ >
61
+ {isSelf ? <CurrentIcon /> : <NormalIcon />}
62
+ <span>{t(i18nkey)}</span>
63
+ </div>
64
+ )
65
+ };
66
+ });
67
+ }, [theme, themes, t]);
68
+
12
69
  return (
13
70
  <div className="flex items-center gap-2">
14
- <label className="text-black" htmlFor="theme-select">
15
- {t('header.theme.label')}
16
- </label>
17
- <select
18
- id="theme-select"
71
+ <Select
19
72
  value={theme}
20
- onChange={(e) => themeService.changeTheme(e.target.value)}
21
- >
22
- {themes.map((theme) => (
23
- <option key={theme} value={theme}>
24
- {theme}
25
- </option>
26
- ))}
27
- </select>
73
+ onChange={(value) => themeService.changeTheme(value)}
74
+ options={themeOptions}
75
+ style={{ width: 120 }}
76
+ className="min-w-40 max-w-full"
77
+ />
28
78
  </div>
29
79
  );
30
80
  }
@@ -5,7 +5,7 @@ import { RouteMeta } from '@/base/types/Page';
5
5
  import { createContext } from 'react';
6
6
  import merge from 'lodash/merge';
7
7
  import i18nConfig from '@config/i18n';
8
- import { WITHIN_PAGE_PROVIDER } from '@config/ErrorIdentifier';
8
+ import { WITHIN_PAGE_PROVIDER } from '@config/Identifier.Error';
9
9
 
10
10
  const { defaultNS } = i18nConfig;
11
11
 
@@ -10,7 +10,7 @@ import { IOCIdentifier } from '@/core/IOC';
10
10
  import { LoginInterface } from '@/base/port/LoginInterface';
11
11
  import { UserApi } from '@/base/apis/userApi/UserApi';
12
12
  import { AppError } from '@/base/cases/appError/AppError';
13
- import { LOCAL_NO_USER_TOKEN } from '@config/ErrorIdentifier';
13
+ import { LOCAL_NO_USER_TOKEN } from '@config/Identifier.Error';
14
14
  import { SliceStore } from '@qlover/slice-store-react';
15
15
 
16
16
  class UserControllerState {
@@ -1,18 +1,4 @@
1
- import root10px from './lib/tailwind/root10px';
2
- import themeCreate from './lib/tailwind/theme-generator';
3
- import themeConfig from './config/theme.json';
4
-
5
- const theme = themeCreate(themeConfig.override);
6
-
7
- /** @type {import('tailwindcss').Config} */
8
1
  export default {
9
2
  darkMode: 'class',
10
- content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
11
- theme: {
12
- extend: {
13
- ...root10px.themes,
14
- colors: theme.colors
15
- }
16
- },
17
- plugins: [root10px.plugin, theme.plugin]
3
+ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}']
18
4
  };
@@ -6,10 +6,12 @@ import tsconfigPaths from 'vite-tsconfig-paths';
6
6
  import envConfig from '@qlover/corekit-bridge/vite-env-config/index';
7
7
  import ts2Locales from '@qlover/corekit-bridge/vite-ts-to-locales/index';
8
8
  import i18nConfig from './config/i18n';
9
+ import tailwindcss from '@tailwindcss/vite';
9
10
 
10
11
  // https://vite.dev/config/
11
12
  export default defineConfig({
12
13
  plugins: [
14
+ tailwindcss(),
13
15
  envConfig({
14
16
  envPops: true,
15
17
  envPrefix,
@@ -24,7 +26,11 @@ export default defineConfig({
24
26
  locales: i18nConfig.supportedLngs as unknown as string[],
25
27
  options: [
26
28
  {
27
- source: './config/ErrorIdentifier.ts',
29
+ source: './config/Identifier.Error.ts',
30
+ target: './public/locales/{{lng}}/common.json'
31
+ },
32
+ {
33
+ source: './config/Identifier.I18n.ts',
28
34
  target: './public/locales/{{lng}}/common.json'
29
35
  }
30
36
  ]