@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/configs/_common/.gitignore.template +49 -23
  3. package/dist/index.js +1 -1
  4. package/package.json +1 -1
  5. package/templates/react-app/{.env → .env.template} +2 -3
  6. package/templates/react-app/README.md +89 -8
  7. package/templates/react-app/config/app.router.json +13 -13
  8. package/templates/react-app/config/common.ts +8 -0
  9. package/templates/react-app/config/feapi.mock.json +8 -0
  10. package/templates/react-app/config/i18n.ts +3 -1
  11. package/templates/react-app/config/{Identifier.Error.ts → identifier/Error.ts} +7 -0
  12. package/templates/react-app/config/{Identifier.I18n.ts → identifier/I18n.ts} +321 -3
  13. package/templates/react-app/index.html +1 -1
  14. package/templates/react-app/package.json +3 -1
  15. package/templates/react-app/public/locales/en/common.json +55 -8
  16. package/templates/react-app/public/locales/zh/common.json +55 -8
  17. package/templates/react-app/public/router-root/logo.svg +1 -0
  18. package/templates/react-app/src/App.tsx +6 -3
  19. package/templates/react-app/src/base/cases/AppConfig.ts +1 -1
  20. package/templates/react-app/src/base/cases/PublicAssetsPath.ts +17 -0
  21. package/templates/react-app/src/base/port/LoginInterface.ts +8 -0
  22. package/templates/react-app/src/base/port/StoreInterface.ts +58 -0
  23. package/templates/react-app/src/base/services/I18nService.ts +15 -9
  24. package/templates/react-app/src/{uikit/controllers/RouterController.ts → base/services/RouteService.ts} +12 -12
  25. package/templates/react-app/src/{uikit/controllers/UserController.ts → base/services/UserService.ts} +29 -13
  26. package/templates/react-app/src/core/bootstrap.ts +1 -1
  27. package/templates/react-app/src/core/registers/RegisterCommon.ts +11 -1
  28. package/templates/react-app/src/core/registers/RegisterControllers.ts +3 -12
  29. package/templates/react-app/src/pages/auth/Layout.tsx +14 -6
  30. package/templates/react-app/src/pages/auth/Login.tsx +50 -29
  31. package/templates/react-app/src/pages/auth/Register.tsx +238 -1
  32. package/templates/react-app/src/pages/base/About.tsx +1 -1
  33. package/templates/react-app/src/pages/base/ErrorIdentifier.tsx +2 -2
  34. package/templates/react-app/src/pages/base/Executor.tsx +4 -4
  35. package/templates/react-app/src/pages/base/Home.tsx +1 -1
  36. package/templates/react-app/src/pages/base/JSONStorage.tsx +3 -3
  37. package/templates/react-app/src/pages/base/Request.tsx +4 -4
  38. package/templates/react-app/src/pages/base/components/BaseHeader.tsx +8 -2
  39. package/templates/react-app/src/styles/css/page.css +3 -3
  40. package/templates/react-app/src/styles/css/themes/_default.css +3 -3
  41. package/templates/react-app/src/styles/css/themes/dark.css +3 -3
  42. package/templates/react-app/src/styles/css/themes/pink.css +3 -3
  43. package/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +7 -7
  44. package/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +3 -3
  45. package/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +1 -1
  46. package/templates/react-app/src/uikit/controllers/ExecutorController.ts +6 -3
  47. package/templates/react-app/src/uikit/controllers/JSONStorageController.ts +6 -3
  48. package/templates/react-app/src/uikit/controllers/RequestController.ts +3 -4
  49. package/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +15 -0
  50. package/templates/react-app/src/uikit/hooks/useStore.ts +12 -0
  51. package/templates/react-app/src/uikit/providers/BaseRouteProvider.tsx +7 -1
  52. package/templates/react-app/src/uikit/providers/ProcessProvider.tsx +7 -7
  53. 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
- return <div>Register</div>;
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,5 +1,5 @@
1
1
  import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
2
- import * as i18nKeys from '@config/Identifier.I18n';
2
+ import * as i18nKeys from '@config/Identifier/I18n';
3
3
  import {
4
4
  Button,
5
5
  Tooltip,
@@ -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.Error';
4
- import * as i18nKeys from '@config/Identifier.I18n';
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 { useSliceStore } from '@qlover/slice-store-react';
8
- import * as i18nKeys from '@config/Identifier.I18n';
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 = useSliceStore(
27
+ const requestTimeout = useStore(
28
28
  jSONStorageController,
29
29
  jSONStorageController.selector.requestTimeout
30
30
  );
31
31
 
32
- const helloState = useSliceStore(
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.I18n';
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 { useSliceStore } from '@qlover/slice-store-react';
5
+ import { useStore } from '@/uikit/hooks/useStore';
6
6
  import { Button, Input } from 'antd';
7
- import * as i18nKeys from '@config/Identifier.I18n';
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 = useSliceStore(jsonStorageController);
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 { useSliceStore } from '@qlover/slice-store-react';
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.I18n';
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 = useSliceStore(requestController);
29
- const jsonStorageControllerState = useSliceStore(IOC(JSONStorageController));
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 src="/logo.svg" alt="logo" className="h-8 w-auto" />
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(--color-text-primary));
6
- --color-text-secondary: rgba(var(--color-text-secondary));
7
- --color-text-tertiary: rgba(var(--color-text-tertiary));
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
- --color-text-primary: 15 23 42; /* slate-900 */
11
- --color-text-secondary: 71 85 105; /* slate-600 */
12
- --color-text-tertiary: 100 116 139; /* slate-500 */
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
- --color-text-primary: 255 255 255;
11
- --color-text-secondary: 148 163 184; /* slate-400 */
12
- --color-text-tertiary: 100 116 139; /* slate-500 */
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
- --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 */
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 { useSliceStore } from '@qlover/slice-store-react';
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 = useSliceStore(i18nService, i18nService.selector.loading);
15
+ const loading = useStore(i18nService, i18nService.selector.loading);
16
16
  const { lng } = useParams<{ lng: I18nServiceLocale }>();
17
- const currentPath = window.location.pathname;
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
- const newPath = currentPath.replace(`/${lng}`, `/${newLang}`);
37
- navigate(newPath);
36
+ navigate(pathname.replace(`/${lng}`, `/${newLang}`));
37
+
38
38
  i18nService.changeLoading(false);
39
39
  },
40
- [lng, currentPath, navigate, i18nService]
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 { useSliceStore } from '@qlover/slice-store-react';
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.I18n';
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 } = useSliceStore(themeService);
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.Error';
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 { SliceStore } from '@qlover/slice-store-react';
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 SliceStore<ExecutorControllerState> {
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 { SliceStore } from '@qlover/slice-store-react';
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 SliceStore<JSONStoragePageState> {
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 { inject } from 'inversify';
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 SliceStore<RequestControllerState> {
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 '../contexts/BaseRouteContext';
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 { RouterController } from '../controllers/RouterController';
9
- import { UserController } from '../controllers/UserController';
10
- import { useSliceStore } from '@qlover/slice-store-react';
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 userController = IOC(UserController);
18
+ const userService = IOC(UserService);
19
19
  const pageProcesser = IOC(ProcesserService);
20
- const success = useSliceStore(userController, (state) => state.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(RouterController).setDependencies({ navigate });
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 (!userController.isAuthenticated()) {
36
+ if (!userService.isAuthenticated()) {
37
37
  return <Navigate to="/login" />;
38
38
  }
39
39