@qlover/create-app 0.4.1 → 0.4.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.
- package/CHANGELOG.md +54 -0
- package/configs/_common/.gitignore.template +49 -23
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/react-app/{.env → .env.template} +2 -3
- package/templates/react-app/README.md +89 -8
- package/templates/react-app/config/app.router.json +13 -13
- package/templates/react-app/config/common.ts +8 -0
- package/templates/react-app/config/feapi.mock.json +8 -0
- package/templates/react-app/config/i18n.ts +3 -1
- package/templates/react-app/config/{Identifier.Error.ts → identifier/Error.ts} +7 -0
- package/templates/react-app/config/{Identifier.I18n.ts → identifier/I18n.ts} +321 -3
- package/templates/react-app/index.html +1 -1
- package/templates/react-app/package.json +3 -1
- package/templates/react-app/public/locales/en/common.json +55 -8
- package/templates/react-app/public/locales/zh/common.json +55 -8
- package/templates/react-app/public/router-root/logo.svg +1 -0
- package/templates/react-app/src/App.tsx +6 -3
- package/templates/react-app/src/base/cases/AppConfig.ts +1 -1
- package/templates/react-app/src/base/cases/PublicAssetsPath.ts +17 -0
- package/templates/react-app/src/base/port/LoginInterface.ts +8 -0
- package/templates/react-app/src/base/port/StoreInterface.ts +58 -0
- package/templates/react-app/src/base/services/I18nService.ts +15 -9
- package/templates/react-app/src/{uikit/controllers/RouterController.ts → base/services/RouteService.ts} +12 -12
- package/templates/react-app/src/{uikit/controllers/UserController.ts → base/services/UserService.ts} +29 -13
- package/templates/react-app/src/core/bootstrap.ts +1 -1
- package/templates/react-app/src/core/registers/RegisterCommon.ts +11 -1
- package/templates/react-app/src/core/registers/RegisterControllers.ts +3 -12
- package/templates/react-app/src/pages/auth/Layout.tsx +14 -6
- package/templates/react-app/src/pages/auth/Login.tsx +50 -29
- package/templates/react-app/src/pages/auth/Register.tsx +238 -1
- package/templates/react-app/src/pages/base/About.tsx +1 -1
- package/templates/react-app/src/pages/base/ErrorIdentifier.tsx +2 -2
- package/templates/react-app/src/pages/base/Executor.tsx +4 -4
- package/templates/react-app/src/pages/base/Home.tsx +1 -1
- package/templates/react-app/src/pages/base/JSONStorage.tsx +3 -3
- package/templates/react-app/src/pages/base/Request.tsx +4 -4
- package/templates/react-app/src/pages/base/components/BaseHeader.tsx +8 -2
- package/templates/react-app/src/styles/css/page.css +3 -3
- package/templates/react-app/src/styles/css/themes/_default.css +3 -3
- package/templates/react-app/src/styles/css/themes/dark.css +3 -3
- package/templates/react-app/src/styles/css/themes/pink.css +3 -3
- package/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +7 -7
- package/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +3 -3
- package/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +1 -1
- package/templates/react-app/src/uikit/controllers/ExecutorController.ts +6 -3
- package/templates/react-app/src/uikit/controllers/JSONStorageController.ts +6 -3
- package/templates/react-app/src/uikit/controllers/RequestController.ts +3 -4
- package/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +15 -0
- package/templates/react-app/src/uikit/hooks/useStore.ts +12 -0
- package/templates/react-app/src/uikit/providers/BaseRouteProvider.tsx +7 -1
- package/templates/react-app/src/uikit/providers/ProcessProvider.tsx +7 -7
- package/templates/react-app/vite.config.ts +20 -11
|
@@ -1,3 +1,240 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { Form, Input, Button, Checkbox } from 'antd';
|
|
3
|
+
import { UserOutlined, LockOutlined, MailOutlined } from '@ant-design/icons';
|
|
4
|
+
import { IOC } from '@/core/IOC';
|
|
5
|
+
import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
|
|
6
|
+
import { RouteService } from '@/base/services/RouteService';
|
|
7
|
+
import { UserService } from '@/base/services/UserService';
|
|
8
|
+
import { useStore } from '@/uikit/hooks/useStore';
|
|
9
|
+
import * as i18nKeys from '@config/Identifier/I18n';
|
|
10
|
+
import type { RegisterFormData } from '@/base/port/LoginInterface';
|
|
11
|
+
|
|
1
12
|
export default function Register() {
|
|
2
|
-
|
|
13
|
+
const { t } = useBaseRoutePage();
|
|
14
|
+
const userService = IOC(UserService);
|
|
15
|
+
const AppConfig = IOC('AppConfig');
|
|
16
|
+
useStore(userService);
|
|
17
|
+
const [loading, setLoading] = useState(false);
|
|
18
|
+
const [form] = Form.useForm();
|
|
19
|
+
|
|
20
|
+
const handleRegister = async (values: RegisterFormData) => {
|
|
21
|
+
try {
|
|
22
|
+
setLoading(true);
|
|
23
|
+
await userService.register(values);
|
|
24
|
+
IOC(RouteService).replaceToHome();
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(error);
|
|
27
|
+
} finally {
|
|
28
|
+
setLoading(false);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const handleLoginClick = (e: React.MouseEvent) => {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
IOC(RouteService).gotoLogin();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="flex min-h-screen text-xs1 bg-primary">
|
|
39
|
+
{/* Left side - Brand section */}
|
|
40
|
+
<div className="hidden lg:flex lg:w-1/2 bg-secondary p-12 flex-col">
|
|
41
|
+
<div className="flex items-center gap-3 mb-12">
|
|
42
|
+
<div className="w-10 h-10 bg-brand rounded-lg"></div>
|
|
43
|
+
<span className="text-2xl font-semibold text-text">
|
|
44
|
+
{AppConfig.appName}
|
|
45
|
+
</span>
|
|
46
|
+
</div>
|
|
47
|
+
<h1 className="text-4xl font-bold text-text mb-4">
|
|
48
|
+
{t(i18nKeys.REGISTER_TITLE)}
|
|
49
|
+
</h1>
|
|
50
|
+
<p className="text-text-secondary text-lg mb-8">
|
|
51
|
+
{t(i18nKeys.REGISTER_SUBTITLE)}
|
|
52
|
+
</p>
|
|
53
|
+
<div className="space-y-4">
|
|
54
|
+
<FeatureItem
|
|
55
|
+
icon="🎯"
|
|
56
|
+
text={t(i18nKeys.REGISTER_FEATURE_PERSONALIZED)}
|
|
57
|
+
/>
|
|
58
|
+
<FeatureItem icon="👨🏫" text={t(i18nKeys.REGISTER_FEATURE_SUPPORT)} />
|
|
59
|
+
<FeatureItem
|
|
60
|
+
icon="👥"
|
|
61
|
+
text={t(i18nKeys.REGISTER_FEATURE_COMMUNITY)}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
{/* Right side - Registration form */}
|
|
67
|
+
<div className="w-full lg:w-1/2 p-8 sm:p-12 flex items-center justify-center">
|
|
68
|
+
<div className="w-full max-w-[420px]">
|
|
69
|
+
<h2 className="text-2xl font-semibold mb-2 text-text">
|
|
70
|
+
{t(i18nKeys.REGISTER_TITLE)}
|
|
71
|
+
</h2>
|
|
72
|
+
<p className="text-text-secondary mb-8">
|
|
73
|
+
{t(i18nKeys.REGISTER_SUBTITLE)}
|
|
74
|
+
</p>
|
|
75
|
+
|
|
76
|
+
<Form
|
|
77
|
+
form={form}
|
|
78
|
+
name="register"
|
|
79
|
+
onFinish={handleRegister}
|
|
80
|
+
layout="vertical"
|
|
81
|
+
className="space-y-4"
|
|
82
|
+
>
|
|
83
|
+
<Form.Item
|
|
84
|
+
name="username"
|
|
85
|
+
rules={[
|
|
86
|
+
{
|
|
87
|
+
required: true,
|
|
88
|
+
message: t(i18nKeys.REGISTER_USERNAME_REQUIRED)
|
|
89
|
+
}
|
|
90
|
+
]}
|
|
91
|
+
>
|
|
92
|
+
<Input
|
|
93
|
+
prefix={<UserOutlined className="text-text-tertiary" />}
|
|
94
|
+
placeholder={t(i18nKeys.REGISTER_USERNAME)}
|
|
95
|
+
className="h-12 text-base bg-secondary border-border"
|
|
96
|
+
/>
|
|
97
|
+
</Form.Item>
|
|
98
|
+
|
|
99
|
+
<Form.Item
|
|
100
|
+
name="email"
|
|
101
|
+
rules={[
|
|
102
|
+
{
|
|
103
|
+
required: true,
|
|
104
|
+
message: t(i18nKeys.REGISTER_EMAIL_REQUIRED)
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: 'email',
|
|
108
|
+
message: t(i18nKeys.REGISTER_EMAIL_REQUIRED)
|
|
109
|
+
}
|
|
110
|
+
]}
|
|
111
|
+
>
|
|
112
|
+
<Input
|
|
113
|
+
prefix={<MailOutlined className="text-text-tertiary" />}
|
|
114
|
+
placeholder={t(i18nKeys.REGISTER_EMAIL)}
|
|
115
|
+
className="h-12 text-base bg-secondary border-border"
|
|
116
|
+
/>
|
|
117
|
+
</Form.Item>
|
|
118
|
+
|
|
119
|
+
<Form.Item
|
|
120
|
+
name="password"
|
|
121
|
+
rules={[
|
|
122
|
+
{
|
|
123
|
+
required: true,
|
|
124
|
+
message: t(i18nKeys.REGISTER_PASSWORD_REQUIRED)
|
|
125
|
+
}
|
|
126
|
+
]}
|
|
127
|
+
>
|
|
128
|
+
<Input.Password
|
|
129
|
+
prefix={<LockOutlined />}
|
|
130
|
+
placeholder={t(i18nKeys.REGISTER_PASSWORD)}
|
|
131
|
+
className="h-12 text-base"
|
|
132
|
+
/>
|
|
133
|
+
</Form.Item>
|
|
134
|
+
|
|
135
|
+
<Form.Item
|
|
136
|
+
name="confirmPassword"
|
|
137
|
+
dependencies={['password']}
|
|
138
|
+
rules={[
|
|
139
|
+
{
|
|
140
|
+
required: true,
|
|
141
|
+
message: t(i18nKeys.REGISTER_CONFIRM_PASSWORD_REQUIRED)
|
|
142
|
+
},
|
|
143
|
+
({ getFieldValue }) => ({
|
|
144
|
+
validator(_, value) {
|
|
145
|
+
if (!value || getFieldValue('password') === value) {
|
|
146
|
+
return Promise.resolve();
|
|
147
|
+
}
|
|
148
|
+
return Promise.reject(
|
|
149
|
+
t(i18nKeys.REGISTER_PASSWORD_MISMATCH)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
]}
|
|
154
|
+
>
|
|
155
|
+
<Input.Password
|
|
156
|
+
prefix={<LockOutlined />}
|
|
157
|
+
placeholder={t(i18nKeys.REGISTER_CONFIRM_PASSWORD)}
|
|
158
|
+
className="h-12 text-base"
|
|
159
|
+
/>
|
|
160
|
+
</Form.Item>
|
|
161
|
+
|
|
162
|
+
<Form.Item
|
|
163
|
+
name="agreeToTerms"
|
|
164
|
+
valuePropName="checked"
|
|
165
|
+
rules={[
|
|
166
|
+
{
|
|
167
|
+
validator: (_, value) =>
|
|
168
|
+
value
|
|
169
|
+
? Promise.resolve()
|
|
170
|
+
: Promise.reject(
|
|
171
|
+
new Error(t(i18nKeys.REGISTER_TERMS_REQUIRED))
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
]}
|
|
175
|
+
>
|
|
176
|
+
<Checkbox>
|
|
177
|
+
<span className="text-text-secondary">
|
|
178
|
+
{t(i18nKeys.REGISTER_TERMS_PREFIX)}{' '}
|
|
179
|
+
<a
|
|
180
|
+
href="#"
|
|
181
|
+
className="text-brand hover:text-brand-hover"
|
|
182
|
+
target="_blank"
|
|
183
|
+
rel="noopener noreferrer"
|
|
184
|
+
>
|
|
185
|
+
{t(i18nKeys.REGISTER_TERMS_LINK)}
|
|
186
|
+
</a>{' '}
|
|
187
|
+
{t(i18nKeys.REGISTER_TERMS_AND)}{' '}
|
|
188
|
+
<a
|
|
189
|
+
href="#"
|
|
190
|
+
className="text-brand hover:text-brand-hover"
|
|
191
|
+
target="_blank"
|
|
192
|
+
rel="noopener noreferrer"
|
|
193
|
+
>
|
|
194
|
+
{t(i18nKeys.REGISTER_PRIVACY_LINK)}
|
|
195
|
+
</a>
|
|
196
|
+
</span>
|
|
197
|
+
</Checkbox>
|
|
198
|
+
</Form.Item>
|
|
199
|
+
|
|
200
|
+
<Form.Item>
|
|
201
|
+
<Button
|
|
202
|
+
type="primary"
|
|
203
|
+
htmlType="submit"
|
|
204
|
+
loading={loading}
|
|
205
|
+
className="w-full h-12 text-base"
|
|
206
|
+
>
|
|
207
|
+
{t(i18nKeys.REGISTER_BUTTON)}
|
|
208
|
+
</Button>
|
|
209
|
+
</Form.Item>
|
|
210
|
+
|
|
211
|
+
<div className="text-center mt-6">
|
|
212
|
+
<span className="text-text-tertiary">
|
|
213
|
+
{t(i18nKeys.REGISTER_HAVE_ACCOUNT)}{' '}
|
|
214
|
+
</span>
|
|
215
|
+
<a
|
|
216
|
+
href="#"
|
|
217
|
+
className="text-brand hover:text-brand-hover"
|
|
218
|
+
onClick={handleLoginClick}
|
|
219
|
+
>
|
|
220
|
+
{t(i18nKeys.REGISTER_LOGIN_LINK)}
|
|
221
|
+
</a>
|
|
222
|
+
</div>
|
|
223
|
+
</Form>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Helper component for feature items
|
|
231
|
+
function FeatureItem({ icon, text }: { icon: string; text: string }) {
|
|
232
|
+
return (
|
|
233
|
+
<div className="flex items-center gap-3 text-text">
|
|
234
|
+
<div className="w-8 h-8 bg-elevated rounded-lg flex items-center justify-center">
|
|
235
|
+
{icon}
|
|
236
|
+
</div>
|
|
237
|
+
<span>{text}</span>
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
3
240
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Button } from 'antd';
|
|
2
2
|
import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
|
|
3
|
-
import * as ErrorIdentifierList from '@config/Identifier
|
|
4
|
-
import * as i18nKeys from '@config/Identifier
|
|
3
|
+
import * as ErrorIdentifierList from '@config/Identifier/Error';
|
|
4
|
+
import * as i18nKeys from '@config/Identifier/I18n';
|
|
5
5
|
|
|
6
6
|
export default function ErrorIdentifier() {
|
|
7
7
|
const { t } = useBaseRoutePage();
|
|
@@ -4,8 +4,8 @@ import { useState, useEffect } from 'react';
|
|
|
4
4
|
import { IOC } from '@/core/IOC';
|
|
5
5
|
import { JSONStorageController } from '@/uikit/controllers/JSONStorageController';
|
|
6
6
|
import { ExecutorController } from '@/uikit/controllers/ExecutorController';
|
|
7
|
-
import {
|
|
8
|
-
import * as i18nKeys from '@config/Identifier
|
|
7
|
+
import { useStore } from '@/uikit/hooks/useStore';
|
|
8
|
+
import * as i18nKeys from '@config/Identifier/I18n';
|
|
9
9
|
|
|
10
10
|
interface Task {
|
|
11
11
|
id: string;
|
|
@@ -24,12 +24,12 @@ export default function Executor() {
|
|
|
24
24
|
const { t } = useBaseRoutePage();
|
|
25
25
|
const executorController = IOC(ExecutorController);
|
|
26
26
|
const jSONStorageController = IOC(JSONStorageController);
|
|
27
|
-
const requestTimeout =
|
|
27
|
+
const requestTimeout = useStore(
|
|
28
28
|
jSONStorageController,
|
|
29
29
|
jSONStorageController.selector.requestTimeout
|
|
30
30
|
);
|
|
31
31
|
|
|
32
|
-
const helloState =
|
|
32
|
+
const helloState = useStore(
|
|
33
33
|
executorController,
|
|
34
34
|
executorController.selector.helloState
|
|
35
35
|
);
|
|
@@ -2,7 +2,7 @@ import { Button } from 'antd';
|
|
|
2
2
|
import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
|
|
3
3
|
import LocaleLink from '@/uikit/components/LocaleLink';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
|
-
import * as i18nKeys from '@config/Identifier
|
|
5
|
+
import * as i18nKeys from '@config/Identifier/I18n';
|
|
6
6
|
|
|
7
7
|
export default function Home() {
|
|
8
8
|
const { t } = useBaseRoutePage();
|
|
@@ -2,13 +2,13 @@ import { IOC } from '@/core/IOC';
|
|
|
2
2
|
import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
|
|
3
3
|
import template from 'lodash/template';
|
|
4
4
|
import { JSONStorageController } from '@/uikit/controllers/JSONStorageController';
|
|
5
|
-
import {
|
|
5
|
+
import { useStore } from '@/uikit/hooks/useStore';
|
|
6
6
|
import { Button, Input } from 'antd';
|
|
7
|
-
import * as i18nKeys from '@config/Identifier
|
|
7
|
+
import * as i18nKeys from '@config/Identifier/I18n';
|
|
8
8
|
|
|
9
9
|
export default function JSONStorage() {
|
|
10
10
|
const jsonStorageController = IOC(JSONStorageController);
|
|
11
|
-
const controllerState =
|
|
11
|
+
const controllerState = useStore(jsonStorageController);
|
|
12
12
|
const { t } = useBaseRoutePage();
|
|
13
13
|
|
|
14
14
|
return (
|
|
@@ -3,10 +3,10 @@ import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
|
|
|
3
3
|
import { JSONStorageController } from '@/uikit/controllers/JSONStorageController';
|
|
4
4
|
import { RequestController } from '@/uikit/controllers/RequestController';
|
|
5
5
|
import { useMemo } from 'react';
|
|
6
|
-
import {
|
|
6
|
+
import { useStore } from '@/uikit/hooks/useStore';
|
|
7
7
|
import { Button } from 'antd';
|
|
8
8
|
import { LoadingOutlined } from '@ant-design/icons';
|
|
9
|
-
import * as i18nKeys from '@config/Identifier
|
|
9
|
+
import * as i18nKeys from '@config/Identifier/I18n';
|
|
10
10
|
|
|
11
11
|
function JSONValue({ value }: { value: unknown }) {
|
|
12
12
|
const output = useMemo(() => {
|
|
@@ -25,8 +25,8 @@ function JSONValue({ value }: { value: unknown }) {
|
|
|
25
25
|
|
|
26
26
|
export default function Request() {
|
|
27
27
|
const requestController = IOC(RequestController);
|
|
28
|
-
const requestControllerState =
|
|
29
|
-
const jsonStorageControllerState =
|
|
28
|
+
const requestControllerState = useStore(requestController);
|
|
29
|
+
const jsonStorageControllerState = useStore(IOC(JSONStorageController));
|
|
30
30
|
const { t } = useBaseRoutePage();
|
|
31
31
|
|
|
32
32
|
return (
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import ThemeSwitcher from '@/uikit/components/ThemeSwitcher';
|
|
2
2
|
import LocaleLink from '@/uikit/components/LocaleLink';
|
|
3
3
|
import LanguageSwitcher from '@/uikit/components/LanguageSwitcher';
|
|
4
|
+
import { PublicAssetsPath } from '@/base/cases/PublicAssetsPath';
|
|
5
|
+
import { IOC } from '@/core/IOC';
|
|
4
6
|
|
|
5
7
|
export default function BaseHeader() {
|
|
6
8
|
return (
|
|
@@ -11,9 +13,13 @@ export default function BaseHeader() {
|
|
|
11
13
|
href="/"
|
|
12
14
|
className="flex items-center hover:opacity-80 transition-opacity"
|
|
13
15
|
>
|
|
14
|
-
<img
|
|
16
|
+
<img
|
|
17
|
+
src={IOC(PublicAssetsPath).getPath('/logo.svg')}
|
|
18
|
+
alt="logo"
|
|
19
|
+
className="h-8 w-auto"
|
|
20
|
+
/>
|
|
15
21
|
<span className="ml-2 text-lg font-semibold text-text">
|
|
16
|
-
{'appName
|
|
22
|
+
{IOC('AppConfig').appName}
|
|
17
23
|
</span>
|
|
18
24
|
</LocaleLink>
|
|
19
25
|
</div>
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
--color-primary: rgba(var(--color-bg-base));
|
|
3
3
|
--color-secondary: rgba(var(--color-bg-secondary));
|
|
4
4
|
--color-elevated: rgba(var(--color-bg-elevated));
|
|
5
|
-
--color-text: rgba(var(--
|
|
6
|
-
--color-text-secondary: rgba(var(--
|
|
7
|
-
--color-text-tertiary: rgba(var(--
|
|
5
|
+
--color-text: rgba(var(--text-primary));
|
|
6
|
+
--color-text-secondary: rgba(var(--text-secondary));
|
|
7
|
+
--color-text-tertiary: rgba(var(--text-tertiary));
|
|
8
8
|
--color-border: rgba(var(--color-border));
|
|
9
9
|
--color-brand: rgba(var(--color-brand));
|
|
10
10
|
--color-brand-hover: rgba(var(--color-brand-hover));
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
--color-bg-elevated: 248 250 252; /* slate-50 */
|
|
8
8
|
|
|
9
9
|
/* 文字颜色 */
|
|
10
|
-
--
|
|
11
|
-
--
|
|
12
|
-
--
|
|
10
|
+
--text-primary: 15 23 42; /* slate-900 */
|
|
11
|
+
--text-secondary: 71 85 105; /* slate-600 */
|
|
12
|
+
--text-tertiary: 100 116 139; /* slate-500 */
|
|
13
13
|
|
|
14
14
|
/* 边框颜色 */
|
|
15
15
|
--color-border: 226 232 240; /* slate-200 */
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
--color-bg-elevated: 51 65 85; /* slate-700 */
|
|
8
8
|
|
|
9
9
|
/* 文字颜色 */
|
|
10
|
-
--
|
|
11
|
-
--
|
|
12
|
-
--
|
|
10
|
+
--text-primary: 255 255 255;
|
|
11
|
+
--text-secondary: 148 163 184; /* slate-400 */
|
|
12
|
+
--text-tertiary: 100 116 139; /* slate-500 */
|
|
13
13
|
|
|
14
14
|
/* 边框颜色 */
|
|
15
15
|
--color-border: 51 65 85; /* slate-700 */
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
--color-bg-elevated: 254 205 211; /* rose-200 */
|
|
8
8
|
|
|
9
9
|
/* 文字颜色 */
|
|
10
|
-
--
|
|
11
|
-
--
|
|
12
|
-
--
|
|
10
|
+
--text-primary: 190 18 60; /* rose-700 */
|
|
11
|
+
--text-secondary: 225 29 72; /* rose-600 */
|
|
12
|
+
--text-tertiary: 244 63 94; /* rose-500 */
|
|
13
13
|
|
|
14
14
|
/* 边框颜色 */
|
|
15
15
|
--color-border: 254 205 211; /* rose-200 */
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { Select } from 'antd';
|
|
2
2
|
import { GlobalOutlined } from '@ant-design/icons';
|
|
3
|
-
import { useNavigate, useParams } from 'react-router-dom';
|
|
3
|
+
import { useLocation, useNavigate, useParams } from 'react-router-dom';
|
|
4
4
|
import i18nConfig from '@config/i18n';
|
|
5
5
|
import { IOC } from '@/core/IOC';
|
|
6
6
|
import { I18nService, I18nServiceLocale } from '@/base/services/I18nService';
|
|
7
7
|
import { useCallback } from 'react';
|
|
8
|
-
import {
|
|
8
|
+
import { useStore } from '@/uikit/hooks/useStore';
|
|
9
9
|
|
|
10
10
|
const { supportedLngs } = i18nConfig;
|
|
11
11
|
|
|
12
12
|
export default function LanguageSwitcher() {
|
|
13
13
|
const navigate = useNavigate();
|
|
14
14
|
const i18nService = IOC(I18nService);
|
|
15
|
-
const loading =
|
|
15
|
+
const loading = useStore(i18nService, i18nService.selector.loading);
|
|
16
16
|
const { lng } = useParams<{ lng: I18nServiceLocale }>();
|
|
17
|
-
const
|
|
17
|
+
const { pathname } = useLocation();
|
|
18
18
|
|
|
19
19
|
const languageOptions = supportedLngs.map((lang) => ({
|
|
20
20
|
key: lang,
|
|
@@ -33,11 +33,11 @@ export default function LanguageSwitcher() {
|
|
|
33
33
|
// Change i18n language
|
|
34
34
|
await i18nService.changeLanguage(newLang);
|
|
35
35
|
// Update URL path
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
navigate(pathname.replace(`/${lng}`, `/${newLang}`));
|
|
37
|
+
|
|
38
38
|
i18nService.changeLoading(false);
|
|
39
39
|
},
|
|
40
|
-
[lng,
|
|
40
|
+
[lng, pathname, navigate, i18nService]
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
return (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IOC } from '@/core/IOC';
|
|
2
2
|
import { ThemeService } from '@qlover/corekit-bridge';
|
|
3
|
-
import {
|
|
3
|
+
import { useStore } from '@/uikit/hooks/useStore';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { Select } from 'antd';
|
|
6
6
|
import {
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from '@ant-design/icons';
|
|
12
12
|
import clsx from 'clsx';
|
|
13
13
|
import { useMemo } from 'react';
|
|
14
|
-
import * as i18nKeys from '@config/Identifier
|
|
14
|
+
import * as i18nKeys from '@config/Identifier/I18n';
|
|
15
15
|
|
|
16
16
|
const colorMap: Record<
|
|
17
17
|
string,
|
|
@@ -36,7 +36,7 @@ const colorMap: Record<
|
|
|
36
36
|
|
|
37
37
|
export default function ThemeSwitcher() {
|
|
38
38
|
const themeService = IOC(ThemeService);
|
|
39
|
-
const { theme } =
|
|
39
|
+
const { theme } = useStore(themeService);
|
|
40
40
|
const themes = themeService.getSupportedThemes();
|
|
41
41
|
const { t } = useTranslation('common');
|
|
42
42
|
|
|
@@ -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/Identifier
|
|
8
|
+
import { WITHIN_PAGE_PROVIDER } from '@config/Identifier/Error';
|
|
9
9
|
|
|
10
10
|
const { defaultNS } = i18nConfig;
|
|
11
11
|
|
|
@@ -6,9 +6,12 @@ import {
|
|
|
6
6
|
RequestAdapterFetchConfig
|
|
7
7
|
} from '@qlover/fe-corekit';
|
|
8
8
|
import { inject, injectable } from 'inversify';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
StoreInterface,
|
|
11
|
+
StoreStateInterface
|
|
12
|
+
} from '@/base/port/StoreInterface';
|
|
10
13
|
|
|
11
|
-
class ExecutorControllerState {
|
|
14
|
+
class ExecutorControllerState implements StoreStateInterface {
|
|
12
15
|
helloState = {
|
|
13
16
|
loading: false,
|
|
14
17
|
result: null as Record<string, unknown> | null,
|
|
@@ -31,7 +34,7 @@ const TestPlugin: ExecutorPlugin<RequestAdapterFetchConfig> = {
|
|
|
31
34
|
};
|
|
32
35
|
|
|
33
36
|
@injectable()
|
|
34
|
-
export class ExecutorController extends
|
|
37
|
+
export class ExecutorController extends StoreInterface<ExecutorControllerState> {
|
|
35
38
|
selector = {
|
|
36
39
|
helloState: (state: ExecutorControllerState) => state.helloState
|
|
37
40
|
};
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { JSONStorage } from '@qlover/fe-corekit';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
StoreInterface,
|
|
4
|
+
StoreStateInterface
|
|
5
|
+
} from '@/base/port/StoreInterface';
|
|
3
6
|
import { random } from 'lodash';
|
|
4
7
|
|
|
5
|
-
interface JSONStoragePageState {
|
|
8
|
+
interface JSONStoragePageState extends StoreStateInterface {
|
|
6
9
|
testKey1?: number;
|
|
7
10
|
testKey2?: number;
|
|
8
11
|
expireTime: number;
|
|
9
12
|
requestTimeout: number;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
export class JSONStorageController extends
|
|
15
|
+
export class JSONStorageController extends StoreInterface<JSONStoragePageState> {
|
|
13
16
|
selector = {
|
|
14
17
|
requestTimeout: (state: JSONStoragePageState) => state.requestTimeout
|
|
15
18
|
};
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import { inject, injectable } from 'inversify';
|
|
1
2
|
import { FeApi } from '@/base/apis/feApi/FeApi';
|
|
2
3
|
import { logger } from '@/core/globals';
|
|
3
4
|
import { UserApi } from '@/base/apis/userApi/UserApi';
|
|
4
5
|
import { aiHello } from '@/base/apis/AiApi';
|
|
5
|
-
import {
|
|
6
|
-
import { injectable } from 'inversify';
|
|
7
|
-
import { SliceStore } from '@qlover/slice-store-react';
|
|
6
|
+
import { StoreInterface } from '@/base/port/StoreInterface';
|
|
8
7
|
|
|
9
8
|
function createDefaultState() {
|
|
10
9
|
return {
|
|
@@ -39,7 +38,7 @@ function createDefaultState() {
|
|
|
39
38
|
export type RequestControllerState = ReturnType<typeof createDefaultState>;
|
|
40
39
|
|
|
41
40
|
@injectable()
|
|
42
|
-
export class RequestController extends
|
|
41
|
+
export class RequestController extends StoreInterface<RequestControllerState> {
|
|
43
42
|
constructor(
|
|
44
43
|
@inject(FeApi) private readonly feApi: FeApi,
|
|
45
44
|
@inject(UserApi) private readonly userApi: UserApi
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useDocumentTitle(title: string) {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
if (!title) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const prevTitle = document.title;
|
|
10
|
+
document.title = title;
|
|
11
|
+
return () => {
|
|
12
|
+
document.title = prevTitle;
|
|
13
|
+
};
|
|
14
|
+
}, [title]);
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StoreInterface,
|
|
3
|
+
StoreStateInterface
|
|
4
|
+
} from '@/base/port/StoreInterface';
|
|
5
|
+
import { useSliceStore } from '@qlover/slice-store-react';
|
|
6
|
+
|
|
7
|
+
export function useStore<
|
|
8
|
+
C extends StoreInterface<StoreStateInterface>,
|
|
9
|
+
State = C['state']
|
|
10
|
+
>(store: C, selector?: (state: C['state']) => State): State {
|
|
11
|
+
return useSliceStore(store, selector);
|
|
12
|
+
}
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { PropsWithChildren } from 'react';
|
|
2
2
|
import { RouteMeta } from '@/base/types/Page';
|
|
3
|
-
import { BaseRoutePageContext } from '
|
|
3
|
+
import { BaseRoutePageContext } from '@/uikit/contexts/BaseRouteContext';
|
|
4
|
+
import { useDocumentTitle } from '@/uikit/hooks/useDocumentTitle';
|
|
5
|
+
import { IOC } from '@/core/IOC';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
4
7
|
|
|
5
8
|
export default function BaseRouteProvider(props: PropsWithChildren<RouteMeta>) {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
useDocumentTitle(props.title ? t(props.title) : IOC('AppConfig').appName);
|
|
11
|
+
|
|
6
12
|
return (
|
|
7
13
|
<BaseRoutePageContext.Provider value={props}>
|
|
8
14
|
{props.children}
|
|
@@ -5,9 +5,9 @@ import { useStrictEffect } from '@/uikit/hooks/useStrictEffect';
|
|
|
5
5
|
import { ProcesserService } from '@/base/services/ProcesserService';
|
|
6
6
|
import { Navigate, useNavigate } from 'react-router-dom';
|
|
7
7
|
import { Loading } from '@/uikit/components/Loading';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { RouteService } from '../../base/services/RouteService';
|
|
9
|
+
import { UserService } from '../../base/services/UserService';
|
|
10
|
+
import { useStore } from '@/uikit/hooks/useStore';
|
|
11
11
|
|
|
12
12
|
const PageProcesserContext = createContext<ProcesserService>(
|
|
13
13
|
IOC(ProcesserService)
|
|
@@ -15,9 +15,9 @@ const PageProcesserContext = createContext<ProcesserService>(
|
|
|
15
15
|
|
|
16
16
|
export function ProcessProvider({ children }: { children: React.ReactNode }) {
|
|
17
17
|
useLanguageGuard();
|
|
18
|
-
const
|
|
18
|
+
const userService = IOC(UserService);
|
|
19
19
|
const pageProcesser = IOC(ProcesserService);
|
|
20
|
-
const success =
|
|
20
|
+
const success = useStore(userService, (state) => state.success);
|
|
21
21
|
|
|
22
22
|
const navigate = useNavigate();
|
|
23
23
|
|
|
@@ -26,14 +26,14 @@ export function ProcessProvider({ children }: { children: React.ReactNode }) {
|
|
|
26
26
|
}, []);
|
|
27
27
|
|
|
28
28
|
useEffect(() => {
|
|
29
|
-
IOC(
|
|
29
|
+
IOC(RouteService).setDependencies({ navigate });
|
|
30
30
|
}, [navigate]);
|
|
31
31
|
|
|
32
32
|
if (!success) {
|
|
33
33
|
return <Loading fullscreen />;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
if (!
|
|
36
|
+
if (!userService.isAuthenticated()) {
|
|
37
37
|
return <Navigate to="/login" />;
|
|
38
38
|
}
|
|
39
39
|
|