@qlover/create-app 0.0.1
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 +10 -0
- package/bin/create-app.js +28 -0
- package/dist/cjs/index.d.ts +67 -0
- package/dist/cjs/index.js +1 -0
- package/dist/es/index.d.ts +67 -0
- package/dist/es/index.js +1 -0
- package/package.json +60 -0
- package/templates/fe-react/.env +3 -0
- package/templates/fe-react/README.md +50 -0
- package/templates/fe-react/config/app.common.ts +52 -0
- package/templates/fe-react/config/app.router.json +150 -0
- package/templates/fe-react/config/feapi.mock.json +14 -0
- package/templates/fe-react/config/i18n.ts +21 -0
- package/templates/fe-react/config/theme.json +90 -0
- package/templates/fe-react/eslint.config.js +31 -0
- package/templates/fe-react/index.html +13 -0
- package/templates/fe-react/lib/fe-react-controller/FeController.ts +15 -0
- package/templates/fe-react/lib/fe-react-controller/index.ts +2 -0
- package/templates/fe-react/lib/fe-react-controller/useController.ts +71 -0
- package/templates/fe-react/lib/fe-react-theme/ThemeController.ts +40 -0
- package/templates/fe-react/lib/fe-react-theme/ThemeStateGetter.ts +53 -0
- package/templates/fe-react/lib/fe-react-theme/index.ts +3 -0
- package/templates/fe-react/lib/fe-react-theme/tw-generator.js +239 -0
- package/templates/fe-react/lib/fe-react-theme/type.ts +21 -0
- package/templates/fe-react/lib/openAiApi/OpenAIAuthPlugin.ts +29 -0
- package/templates/fe-react/lib/openAiApi/OpenAIClient.ts +51 -0
- package/templates/fe-react/lib/openAiApi/StreamProcessor.ts +81 -0
- package/templates/fe-react/lib/openAiApi/index.ts +3 -0
- package/templates/fe-react/lib/request-common-plugin/index.ts +169 -0
- package/templates/fe-react/lib/tw-root10px/index.css +4 -0
- package/templates/fe-react/lib/tw-root10px/index.js +178 -0
- package/templates/fe-react/package.json +49 -0
- package/templates/fe-react/postcss.config.js +6 -0
- package/templates/fe-react/public/locales/en/about.json +3 -0
- package/templates/fe-react/public/locales/en/common.json +6 -0
- package/templates/fe-react/public/locales/en/executor.json +6 -0
- package/templates/fe-react/public/locales/en/home.json +10 -0
- package/templates/fe-react/public/locales/en/jsonStorage.json +11 -0
- package/templates/fe-react/public/locales/en/login.json +7 -0
- package/templates/fe-react/public/locales/en/request.json +15 -0
- package/templates/fe-react/public/locales/zh/about.json +3 -0
- package/templates/fe-react/public/locales/zh/common.json +7 -0
- package/templates/fe-react/public/locales/zh/executor.json +7 -0
- package/templates/fe-react/public/locales/zh/home.json +10 -0
- package/templates/fe-react/public/locales/zh/jsonStorage.json +11 -0
- package/templates/fe-react/public/locales/zh/login.json +8 -0
- package/templates/fe-react/public/locales/zh/request.json +15 -0
- package/templates/fe-react/public/logo.svg +1 -0
- package/templates/fe-react/src/App.tsx +20 -0
- package/templates/fe-react/src/assets/react.svg +1 -0
- package/templates/fe-react/src/components/Loading.tsx +41 -0
- package/templates/fe-react/src/components/LocaleLink.tsx +42 -0
- package/templates/fe-react/src/components/ProcessProvider.tsx +41 -0
- package/templates/fe-react/src/components/ThemeSwitcher.tsx +29 -0
- package/templates/fe-react/src/containers/context/BaseRouteContext.ts +27 -0
- package/templates/fe-react/src/containers/context/BaseRouteProvider.tsx +11 -0
- package/templates/fe-react/src/containers/globals.ts +31 -0
- package/templates/fe-react/src/containers/index.ts +71 -0
- package/templates/fe-react/src/hooks/useLanguageGuard.ts +25 -0
- package/templates/fe-react/src/hooks/useStrictEffect.ts +29 -0
- package/templates/fe-react/src/main.tsx +15 -0
- package/templates/fe-react/src/pages/404.tsx +14 -0
- package/templates/fe-react/src/pages/500.tsx +14 -0
- package/templates/fe-react/src/pages/auth/Layout.tsx +14 -0
- package/templates/fe-react/src/pages/auth/Login.tsx +62 -0
- package/templates/fe-react/src/pages/auth/Register.tsx +3 -0
- package/templates/fe-react/src/pages/base/About.tsx +12 -0
- package/templates/fe-react/src/pages/base/Executor.tsx +38 -0
- package/templates/fe-react/src/pages/base/Home.tsx +78 -0
- package/templates/fe-react/src/pages/base/JSONStorage.tsx +124 -0
- package/templates/fe-react/src/pages/base/Layout.tsx +17 -0
- package/templates/fe-react/src/pages/base/RedirectPathname.tsx +15 -0
- package/templates/fe-react/src/pages/base/Request.tsx +91 -0
- package/templates/fe-react/src/pages/base/components/BaseHeader.tsx +19 -0
- package/templates/fe-react/src/pages/index.tsx +108 -0
- package/templates/fe-react/src/services/controllers/ExecutorController.ts +56 -0
- package/templates/fe-react/src/services/controllers/JSONStorageController.ts +42 -0
- package/templates/fe-react/src/services/controllers/RequestController.ts +105 -0
- package/templates/fe-react/src/services/controllers/RouterController.ts +90 -0
- package/templates/fe-react/src/services/controllers/UserController.ts +146 -0
- package/templates/fe-react/src/services/feApi/FeApi.ts +51 -0
- package/templates/fe-react/src/services/feApi/FeApiMockPlugin.ts +42 -0
- package/templates/fe-react/src/services/feApi/FeApiType.ts +55 -0
- package/templates/fe-react/src/services/feApi/index.ts +2 -0
- package/templates/fe-react/src/services/i18n/index.ts +50 -0
- package/templates/fe-react/src/services/pageProcesser/PageProcesser.ts +29 -0
- package/templates/fe-react/src/services/pageProcesser/index.ts +1 -0
- package/templates/fe-react/src/styles/css/index.css +2 -0
- package/templates/fe-react/src/styles/css/page.css +3 -0
- package/templates/fe-react/src/styles/css/tailwind.css +3 -0
- package/templates/fe-react/src/types/Page.ts +49 -0
- package/templates/fe-react/src/types/UIDependenciesInterface.ts +31 -0
- package/templates/fe-react/src/types/global.d.ts +7 -0
- package/templates/fe-react/src/utils/RequestLogger.ts +34 -0
- package/templates/fe-react/src/utils/datetime.ts +25 -0
- package/templates/fe-react/src/utils/thread.ts +3 -0
- package/templates/fe-react/src/vite-env.d.ts +1 -0
- package/templates/fe-react/tailwind.config.js +18 -0
- package/templates/fe-react/tsconfig.app.json +29 -0
- package/templates/fe-react/tsconfig.json +7 -0
- package/templates/fe-react/tsconfig.node.json +22 -0
- package/templates/fe-react/vite.config.ts +32 -0
- package/templates/pack-app/.editorconfig +23 -0
- package/templates/pack-app/.env +5 -0
- package/templates/pack-app/.env.template +6 -0
- package/templates/pack-app/.gitattributes +2 -0
- package/templates/pack-app/.github/workflows/general-check.yml +50 -0
- package/templates/pack-app/.github/workflows/release.yml.template +110 -0
- package/templates/pack-app/.prettierignore +5 -0
- package/templates/pack-app/.prettierrc.js +3 -0
- package/templates/pack-app/CHANGELOG.md +0 -0
- package/templates/pack-app/README.md +1 -0
- package/templates/pack-app/eslint.config.js +77 -0
- package/templates/pack-app/fe-config.json +10 -0
- package/templates/pack-app/jest.config.js +31 -0
- package/templates/pack-app/package.json +66 -0
- package/templates/pack-app/packages/browser/__tests__/calc.test.ts +9 -0
- package/templates/pack-app/packages/browser/package.json +11 -0
- package/templates/pack-app/packages/browser/rollup.config.js +70 -0
- package/templates/pack-app/packages/browser/src/calc.ts +3 -0
- package/templates/pack-app/packages/browser/src/index.ts +1 -0
- package/templates/pack-app/packages/browser/tsconfig.json +15 -0
- package/templates/pack-app/packages/node/__tests__/readJson.test.ts +25 -0
- package/templates/pack-app/packages/node/package.json +11 -0
- package/templates/pack-app/packages/node/rollup.config.js +89 -0
- package/templates/pack-app/packages/node/src/index.ts +7 -0
- package/templates/pack-app/packages/node/src/readJson.ts +6 -0
- package/templates/pack-app/packages/node/tsconfig.json +17 -0
- package/templates/pack-app/packages/react-vite-lib/README.md +50 -0
- package/templates/pack-app/packages/react-vite-lib/__tests__/Sum.test.ts +9 -0
- package/templates/pack-app/packages/react-vite-lib/__tests__/Text.test.tsx +11 -0
- package/templates/pack-app/packages/react-vite-lib/eslint.config.js +28 -0
- package/templates/pack-app/packages/react-vite-lib/index.html +13 -0
- package/templates/pack-app/packages/react-vite-lib/package.json +30 -0
- package/templates/pack-app/packages/react-vite-lib/public/vite.svg +1 -0
- package/templates/pack-app/packages/react-vite-lib/src/calc.ts +3 -0
- package/templates/pack-app/packages/react-vite-lib/src/commponents/Text.tsx +7 -0
- package/templates/pack-app/packages/react-vite-lib/src/index.ts +2 -0
- package/templates/pack-app/packages/react-vite-lib/src/vite-env.d.ts +1 -0
- package/templates/pack-app/packages/react-vite-lib/tsconfig.json +25 -0
- package/templates/pack-app/packages/react-vite-lib/vite.config.ts +24 -0
- package/templates/pack-app/pnpm-workspace.yaml +2 -0
- package/templates/pack-app/tsconfig.json +9 -0
- package/templates/pack-app/tsconfig.test.json +10 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import BaseHeader from './components/BaseHeader';
|
|
2
|
+
import { Outlet } from 'react-router-dom';
|
|
3
|
+
import { ProcessProvider } from '@/components/ProcessProvider';
|
|
4
|
+
|
|
5
|
+
export default function Layout() {
|
|
6
|
+
return (
|
|
7
|
+
<ProcessProvider>
|
|
8
|
+
<div data-testid="basic-layout" className="text-base">
|
|
9
|
+
<BaseHeader />
|
|
10
|
+
|
|
11
|
+
<div className="text-black bg-white">
|
|
12
|
+
<Outlet />
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
</ProcessProvider>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
const RedirectToDefault = () => {
|
|
5
|
+
const navigate = useNavigate();
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
// Redirect to the default language path
|
|
9
|
+
navigate('/en', { replace: true });
|
|
10
|
+
}, [navigate]);
|
|
11
|
+
|
|
12
|
+
return null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default RedirectToDefault;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { jsonStorageController, requestController } from '@/containers';
|
|
2
|
+
import { useControllerState } from '@lib/fe-react-controller';
|
|
3
|
+
import { useBaseRoutePage } from '../../containers/context/BaseRouteContext';
|
|
4
|
+
|
|
5
|
+
export default function Request() {
|
|
6
|
+
const requestControllerState = useControllerState(requestController);
|
|
7
|
+
const jsonStorageControllerState = useControllerState(jsonStorageController);
|
|
8
|
+
const { t } = useBaseRoutePage();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="min-h-screen bg-gray-100 py-6 flex flex-col sm:py-12">
|
|
12
|
+
<div className="bg-white shadow-lg rounded-lg px-8 py-6">
|
|
13
|
+
{t('requestTimeout')}: {jsonStorageControllerState.requestTimeout}
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div className="bg-white shadow-lg rounded-lg px-8 py-6">
|
|
17
|
+
<button
|
|
18
|
+
className="bg-blue-500 text-white px-4 py-2 rounded-md"
|
|
19
|
+
onClick={requestController.onHello}
|
|
20
|
+
>
|
|
21
|
+
{requestControllerState.helloState.loading ? 'Loading...' : 'Hello'}
|
|
22
|
+
</button>
|
|
23
|
+
<div className="mt-4">
|
|
24
|
+
{t('helloResult')}:{' '}
|
|
25
|
+
{JSON.stringify(requestControllerState.helloState.result)}
|
|
26
|
+
</div>
|
|
27
|
+
<div className="mt-4">
|
|
28
|
+
{t('helloError')}:{' '}
|
|
29
|
+
{JSON.stringify(requestControllerState.helloState.error)}
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div className="bg-white shadow-lg rounded-lg px-8 py-6">
|
|
34
|
+
<button
|
|
35
|
+
className="bg-blue-500 text-white px-4 py-2 rounded-md"
|
|
36
|
+
onClick={requestController.onIpInfo}
|
|
37
|
+
>
|
|
38
|
+
{requestControllerState.ipInfoState.loading
|
|
39
|
+
? t('loading')
|
|
40
|
+
: t('ipInfo')}
|
|
41
|
+
</button>
|
|
42
|
+
<div className="mt-4">
|
|
43
|
+
{t('ipInfoResult')}:{' '}
|
|
44
|
+
{JSON.stringify(requestControllerState.ipInfoState.result)}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<div className="bg-white shadow-lg rounded-lg px-8 py-6">
|
|
49
|
+
<button
|
|
50
|
+
className="bg-blue-500 text-white px-4 py-2 rounded-md"
|
|
51
|
+
onClick={requestController.onRandomUser}
|
|
52
|
+
>
|
|
53
|
+
{requestControllerState.randomUserState.loading
|
|
54
|
+
? t('loading')
|
|
55
|
+
: t('randomUser')}
|
|
56
|
+
</button>
|
|
57
|
+
<div className="mt-4">
|
|
58
|
+
{t('randomUserResult')}:{' '}
|
|
59
|
+
{JSON.stringify(requestControllerState.randomUserState.result)}
|
|
60
|
+
</div>
|
|
61
|
+
<div className="mt-4">
|
|
62
|
+
{t('randomUserError')}:{' '}
|
|
63
|
+
{JSON.stringify(requestControllerState.randomUserState.error)}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div className="bg-white shadow-lg rounded-lg px-8 py-6">
|
|
68
|
+
<button
|
|
69
|
+
className="bg-blue-500 text-white px-4 py-2 rounded-md"
|
|
70
|
+
onClick={requestController.onTriggerAbortRequest}
|
|
71
|
+
>
|
|
72
|
+
{requestControllerState.abortState.loading
|
|
73
|
+
? t('stopAbortRequest')
|
|
74
|
+
: t('triggerAbortRequest')}
|
|
75
|
+
<span className="text-xs">
|
|
76
|
+
{requestControllerState.abortState.loading ? t('loading') : ''}
|
|
77
|
+
</span>
|
|
78
|
+
</button>
|
|
79
|
+
|
|
80
|
+
<div className="mt-4">
|
|
81
|
+
{t('abortRequestResult')}:{' '}
|
|
82
|
+
{JSON.stringify(requestControllerState.abortState.result)}
|
|
83
|
+
</div>
|
|
84
|
+
<div className="mt-4">
|
|
85
|
+
{t('abortRequestError')}:{' '}
|
|
86
|
+
{JSON.stringify(requestControllerState.abortState.error)}
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import ThemeSwitcher from '@/components/ThemeSwitcher';
|
|
2
|
+
import LocaleLink from '@/components/LocaleLink';
|
|
3
|
+
|
|
4
|
+
export default function BaseHeader() {
|
|
5
|
+
return (
|
|
6
|
+
<header className="h-10 bg-white sticky top-0 z-50">
|
|
7
|
+
<div className="flex items-center justify-between h-full px-2">
|
|
8
|
+
<div className="flex items-center">
|
|
9
|
+
<LocaleLink href="/">
|
|
10
|
+
<img src="/logo.svg" alt="logo" />
|
|
11
|
+
</LocaleLink>
|
|
12
|
+
</div>
|
|
13
|
+
<div className="flex items-center">
|
|
14
|
+
<ThemeSwitcher />
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
</header>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { lazy, Suspense } from 'react';
|
|
2
|
+
import isString from 'lodash/isString';
|
|
3
|
+
import { LoadProps, PagesMaps, RouteCategory, RouteType } from '@/types/Page';
|
|
4
|
+
import { Loading } from '@/components/Loading';
|
|
5
|
+
import BaseRouteProvider from '../containers/context/BaseRouteProvider';
|
|
6
|
+
import NotFound from './404';
|
|
7
|
+
import NotFound500 from './500';
|
|
8
|
+
|
|
9
|
+
const staticComponentsMaps: Record<string, () => React.ComponentType<unknown>> =
|
|
10
|
+
{
|
|
11
|
+
'404': () => NotFound as React.ComponentType<unknown>,
|
|
12
|
+
'500': () => NotFound500 as React.ComponentType<unknown>
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const getRealComponents = () => {
|
|
16
|
+
return import.meta.glob('./*/**/*.tsx');
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const getLazyComponentMaps = () => {
|
|
20
|
+
const modules = getRealComponents();
|
|
21
|
+
|
|
22
|
+
const pagesMaps: PagesMaps = {};
|
|
23
|
+
|
|
24
|
+
for (const path in modules) {
|
|
25
|
+
const componentName = path.replace(/^\.\/(.*)\.tsx$/, '$1');
|
|
26
|
+
|
|
27
|
+
pagesMaps[componentName] = () =>
|
|
28
|
+
lazy(
|
|
29
|
+
modules[path] as () => Promise<{
|
|
30
|
+
default: React.ComponentType<unknown>;
|
|
31
|
+
}>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return pagesMaps;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// 懒加载组件
|
|
39
|
+
const lazyLoad = ({ pagesMaps, componentPath, route, Provider }: LoadProps) => {
|
|
40
|
+
// first try static
|
|
41
|
+
let loadedComponent = staticComponentsMaps[componentPath];
|
|
42
|
+
if (!loadedComponent) {
|
|
43
|
+
loadedComponent = pagesMaps[componentPath];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!loadedComponent) {
|
|
47
|
+
console.warn(`Route ${componentPath} not found`);
|
|
48
|
+
return <NotFound route={componentPath} />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const Component = loadedComponent();
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Suspense fallback={<Loading fullscreen />}>
|
|
55
|
+
{Provider ? (
|
|
56
|
+
<Provider {...route.meta}>
|
|
57
|
+
<Component />
|
|
58
|
+
</Provider>
|
|
59
|
+
) : (
|
|
60
|
+
<Component />
|
|
61
|
+
)}
|
|
62
|
+
</Suspense>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// 转换单个路由
|
|
67
|
+
const transformRoute = (
|
|
68
|
+
route: RouteType,
|
|
69
|
+
options: Pick<LoadProps, 'pagesMaps' | 'Provider'>
|
|
70
|
+
): RouteType => {
|
|
71
|
+
const result: RouteType = {
|
|
72
|
+
...route,
|
|
73
|
+
path: route.path
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (isString(route.element)) {
|
|
77
|
+
result.element = lazyLoad({
|
|
78
|
+
...options,
|
|
79
|
+
componentPath: route.element,
|
|
80
|
+
route
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (route.children) {
|
|
85
|
+
result.children = route.children.map((child) =>
|
|
86
|
+
transformRoute(child, options)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export function createFeReactRoutes(routes: RouteType[]) {
|
|
94
|
+
const pagesMaps = getLazyComponentMaps();
|
|
95
|
+
|
|
96
|
+
return routes.map((route) =>
|
|
97
|
+
transformRoute(route, { pagesMaps, Provider: BaseRouteProvider })
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function filterRoutesByCategory(
|
|
102
|
+
routes: RouteType[],
|
|
103
|
+
category: RouteCategory[]
|
|
104
|
+
): RouteType[] {
|
|
105
|
+
return routes.filter((route) =>
|
|
106
|
+
route.meta?.category ? category.includes(route.meta?.category) : true
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { FeController } from '@lib/fe-react-controller';
|
|
2
|
+
import { FeApi } from '@/services/feApi';
|
|
3
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
4
|
+
import {
|
|
5
|
+
ExecutorPlugin,
|
|
6
|
+
RequestAdapterResponse
|
|
7
|
+
} from 'packages/fe-utils/interface';
|
|
8
|
+
import { RequestAdapterFetchConfig } from '@qlover/fe-utils';
|
|
9
|
+
|
|
10
|
+
function createDefaultState() {
|
|
11
|
+
return {
|
|
12
|
+
helloState: {
|
|
13
|
+
loading: false,
|
|
14
|
+
result: null as unknown,
|
|
15
|
+
error: null as unknown
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type ExecutorControllerState = ReturnType<typeof createDefaultState>;
|
|
21
|
+
|
|
22
|
+
const TestPlugin: ExecutorPlugin<RequestAdapterFetchConfig> = {
|
|
23
|
+
pluginName: 'test',
|
|
24
|
+
async onSuccess({ parameters, returnValue }) {
|
|
25
|
+
if (
|
|
26
|
+
parameters.responseType === 'text' &&
|
|
27
|
+
(returnValue as RequestAdapterResponse).data instanceof Response
|
|
28
|
+
) {
|
|
29
|
+
(returnValue as RequestAdapterResponse<unknown, string>).data = await (
|
|
30
|
+
returnValue as RequestAdapterResponse<unknown, Response>
|
|
31
|
+
).data.text();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export class ExecutorController extends FeController<ExecutorControllerState> {
|
|
37
|
+
private feApi: FeApi;
|
|
38
|
+
|
|
39
|
+
constructor(feApi: FeApi) {
|
|
40
|
+
super(createDefaultState());
|
|
41
|
+
|
|
42
|
+
this.feApi = cloneDeep(feApi);
|
|
43
|
+
|
|
44
|
+
this.feApi.usePlugin(TestPlugin);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
onTestPlugins = async () => {
|
|
48
|
+
console.log('onTestPlugins');
|
|
49
|
+
|
|
50
|
+
const res = await this.feApi.get('/api/v1/executor/test', {
|
|
51
|
+
responseType: 'text'
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
console.log('res', res);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { FeController } from '@lib/fe-react-controller';
|
|
2
|
+
import { JSONStorage } from '@qlover/fe-utils';
|
|
3
|
+
import { random } from 'lodash';
|
|
4
|
+
|
|
5
|
+
interface JSONStoragePageState {
|
|
6
|
+
testKey1?: number;
|
|
7
|
+
testKey2?: number;
|
|
8
|
+
expireTime: number;
|
|
9
|
+
requestTimeout: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class JSONStorageController extends FeController<JSONStoragePageState> {
|
|
13
|
+
constructor(private storage: JSONStorage) {
|
|
14
|
+
super({
|
|
15
|
+
testKey1: storage.getItem('testKey1') ?? 0,
|
|
16
|
+
testKey2: storage.getItem('testKey2') ?? 0,
|
|
17
|
+
expireTime: 5000,
|
|
18
|
+
requestTimeout: storage.getItem('requestTimeout') ?? 5000
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
changeRandomTestKey1 = (): void => {
|
|
23
|
+
const value = random(100, 9000);
|
|
24
|
+
this.storage.setItem('testKey1', value);
|
|
25
|
+
this.emit({ ...this.state, testKey1: value });
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
onChangeRandomTestKey2 = (): void => {
|
|
29
|
+
const value = random(100, 9000);
|
|
30
|
+
this.storage.setItem('testKey2', value, Date.now() + this.state.expireTime);
|
|
31
|
+
this.emit({ ...this.state, testKey2: value });
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
changeExpireTime = (expireTime: number): void => {
|
|
35
|
+
this.emit({ ...this.state, expireTime });
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
changeRequestTimeout = (requestTimeout: number): void => {
|
|
39
|
+
this.storage.setItem('requestTimeout', requestTimeout);
|
|
40
|
+
this.emit({ ...this.state, requestTimeout });
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { OpenAIClient } from '@lib/openAiApi/OpenAIClient';
|
|
2
|
+
import { FeController } from '@lib/fe-react-controller';
|
|
3
|
+
import { FeApi } from '@/services/feApi';
|
|
4
|
+
import { logger } from '@/containers/globals';
|
|
5
|
+
|
|
6
|
+
function createDefaultState() {
|
|
7
|
+
return {
|
|
8
|
+
helloState: {
|
|
9
|
+
loading: false,
|
|
10
|
+
result: null as unknown,
|
|
11
|
+
error: null as unknown
|
|
12
|
+
},
|
|
13
|
+
ipInfoState: {
|
|
14
|
+
loading: false,
|
|
15
|
+
result: null as unknown,
|
|
16
|
+
error: null as unknown
|
|
17
|
+
},
|
|
18
|
+
randomUserState: {
|
|
19
|
+
loading: false,
|
|
20
|
+
result: null as unknown,
|
|
21
|
+
error: null as unknown
|
|
22
|
+
},
|
|
23
|
+
abortState: {
|
|
24
|
+
loading: false,
|
|
25
|
+
result: null as unknown,
|
|
26
|
+
error: null as unknown
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type RequestControllerState = ReturnType<typeof createDefaultState>;
|
|
32
|
+
|
|
33
|
+
export class RequestController extends FeController<RequestControllerState> {
|
|
34
|
+
constructor(
|
|
35
|
+
private readonly aiApi: OpenAIClient,
|
|
36
|
+
private readonly feApi: FeApi
|
|
37
|
+
) {
|
|
38
|
+
super(createDefaultState());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onHello = async () => {
|
|
42
|
+
this.setState({ helloState: { loading: true, result: '', error: null } });
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const result = await this.aiApi.completion({
|
|
46
|
+
messages: [{ role: 'user', content: 'Hello, world!' }]
|
|
47
|
+
});
|
|
48
|
+
this.setState({ helloState: { loading: false, result, error: null } });
|
|
49
|
+
} catch (error) {
|
|
50
|
+
logger.error(error);
|
|
51
|
+
this.setState({ helloState: { loading: false, result: null, error } });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
onIpInfo = async () => {
|
|
56
|
+
this.setState({
|
|
57
|
+
ipInfoState: { loading: true, result: null, error: null }
|
|
58
|
+
});
|
|
59
|
+
try {
|
|
60
|
+
const result = await this.feApi.getIpInfo();
|
|
61
|
+
this.setState({ ipInfoState: { loading: false, result, error: null } });
|
|
62
|
+
} catch (error) {
|
|
63
|
+
logger.error(error);
|
|
64
|
+
this.setState({ ipInfoState: { loading: false, result: null, error } });
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
onRandomUser = async () => {
|
|
69
|
+
this.setState({
|
|
70
|
+
randomUserState: { loading: true, result: null, error: null }
|
|
71
|
+
});
|
|
72
|
+
try {
|
|
73
|
+
const result = await this.feApi.getRandomUser();
|
|
74
|
+
this.setState({
|
|
75
|
+
randomUserState: { loading: false, result, error: null }
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
logger.error(error);
|
|
79
|
+
this.setState({
|
|
80
|
+
randomUserState: { loading: false, result: null, error }
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
onTriggerAbortRequest = async () => {
|
|
86
|
+
if (this.state.abortState.loading) {
|
|
87
|
+
this.stopAbortRequest();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.setState({ abortState: { loading: true, result: null, error: null } });
|
|
92
|
+
try {
|
|
93
|
+
await this.feApi.request({
|
|
94
|
+
method: 'GET',
|
|
95
|
+
url: 'https://api.example.com/users'
|
|
96
|
+
});
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.setState({ abortState: { loading: false, result: null, error } });
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
stopAbortRequest = async () => {
|
|
103
|
+
this.feApi.stop({ method: 'GET', url: 'https://api.example.com/users' });
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { I18nService } from '@/services/i18n';
|
|
2
|
+
import { RouteConfig, RouteType } from '@/types/Page';
|
|
3
|
+
import { UIDependenciesInterface } from '@/types/UIDependenciesInterface';
|
|
4
|
+
import { i18nConfig } from '@config/i18n';
|
|
5
|
+
import { Logger } from '@qlover/fe-utils';
|
|
6
|
+
import { NavigateFunction, NavigateOptions } from 'react-router-dom';
|
|
7
|
+
|
|
8
|
+
export type RouterControllerDependencies = {
|
|
9
|
+
location: globalThis.Location;
|
|
10
|
+
navigate: NavigateFunction;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type RouterControllerOptions = {
|
|
14
|
+
config: RouteConfig;
|
|
15
|
+
logger: Logger;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type RouterControllerState = {
|
|
19
|
+
routes: RouteType[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class RouterController
|
|
23
|
+
implements UIDependenciesInterface<RouterControllerDependencies>
|
|
24
|
+
{
|
|
25
|
+
/**
|
|
26
|
+
* @override
|
|
27
|
+
*/
|
|
28
|
+
dependencies?: RouterControllerDependencies;
|
|
29
|
+
|
|
30
|
+
state: RouterControllerState;
|
|
31
|
+
|
|
32
|
+
constructor(private options: RouterControllerOptions) {
|
|
33
|
+
this.state = {
|
|
34
|
+
routes: options.config.routes
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get logger(): Logger {
|
|
39
|
+
return this.options.logger;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get navigate(): NavigateFunction | undefined {
|
|
43
|
+
const navigate = this.dependencies?.navigate;
|
|
44
|
+
|
|
45
|
+
if (!navigate) {
|
|
46
|
+
this.logger.debug('navigate is not set');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return navigate;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @override
|
|
54
|
+
*/
|
|
55
|
+
setDependencies(dependencies: Partial<RouterControllerDependencies>): void {
|
|
56
|
+
this.dependencies = Object.assign(
|
|
57
|
+
this.dependencies || {},
|
|
58
|
+
dependencies
|
|
59
|
+
) as RouterControllerDependencies;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getRoutes(): RouteType[] {
|
|
63
|
+
return this.state.routes;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
changeRoutes(routes: RouteType[]): void {
|
|
67
|
+
this.state.routes = routes;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
goto(path: string, options?: NavigateOptions): void {
|
|
71
|
+
console.trace('path');
|
|
72
|
+
|
|
73
|
+
const tryLng = path.split('/')[0];
|
|
74
|
+
if (!I18nService.isValidLanguage(tryLng)) {
|
|
75
|
+
path = i18nConfig.fallbackLng + path.replace(tryLng, '');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.navigate?.(path, options);
|
|
79
|
+
|
|
80
|
+
this.navigate?.(path, options);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
gotoLogin(): void {
|
|
84
|
+
this.goto('/login', { replace: true });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
replaceToHome(): void {
|
|
88
|
+
this.goto('/', { replace: true });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { ExecutorPlugin, JSONStorage } from '@qlover/fe-utils';
|
|
2
|
+
import { sleep } from '@/utils/thread';
|
|
3
|
+
import { FeController } from '@lib/fe-react-controller';
|
|
4
|
+
import { FeApi, } from '@/services/feApi';
|
|
5
|
+
import { FeApiGetUserInfo, FeApiLogin } from '@/services/feApi/FeApiType';
|
|
6
|
+
import { adjustExpirationTime } from '@/utils/datetime';
|
|
7
|
+
import { RouterController } from './RouterController';
|
|
8
|
+
|
|
9
|
+
export interface UserControllerState {
|
|
10
|
+
success: boolean;
|
|
11
|
+
userInfo: FeApiGetUserInfo['response']['data'];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface UserControllerOptions {
|
|
15
|
+
/**
|
|
16
|
+
* @default `month`
|
|
17
|
+
*/
|
|
18
|
+
expiresIn?: number | 'day' | 'week' | 'month' | 'year';
|
|
19
|
+
storageKey: string;
|
|
20
|
+
storage: JSONStorage;
|
|
21
|
+
feApi: FeApi;
|
|
22
|
+
routerController: RouterController;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface LoginInterface {
|
|
26
|
+
login(
|
|
27
|
+
params: FeApiLogin['request']
|
|
28
|
+
): Promise<FeApiGetUserInfo['response']['data']>;
|
|
29
|
+
logout(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function restoreUserSession(options: UserControllerOptions): {
|
|
33
|
+
state: UserControllerState;
|
|
34
|
+
token: string;
|
|
35
|
+
} {
|
|
36
|
+
const { storageKey, storage } = options;
|
|
37
|
+
const token = storage.getItem(storageKey, '') as string;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
state: {
|
|
41
|
+
success: !!token,
|
|
42
|
+
userInfo: {
|
|
43
|
+
name: '',
|
|
44
|
+
email: '',
|
|
45
|
+
picture: ''
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
token
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class UserController
|
|
53
|
+
extends FeController<UserControllerState>
|
|
54
|
+
implements ExecutorPlugin, LoginInterface
|
|
55
|
+
{
|
|
56
|
+
readonly pluginName = 'UserController';
|
|
57
|
+
|
|
58
|
+
private token = '';
|
|
59
|
+
|
|
60
|
+
constructor(private options: UserControllerOptions) {
|
|
61
|
+
const restoreSession = restoreUserSession(options);
|
|
62
|
+
super(restoreSession.state);
|
|
63
|
+
|
|
64
|
+
this.token = restoreSession.token;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
selectorSuccess = (state: UserControllerState): boolean => state.success;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @override
|
|
71
|
+
*/
|
|
72
|
+
async onBefore(): Promise<void> {
|
|
73
|
+
await sleep(1000);
|
|
74
|
+
|
|
75
|
+
if (!this.token) {
|
|
76
|
+
throw new Error('User not logged in');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const userInfo = await this.options.feApi.getUserInfo();
|
|
80
|
+
|
|
81
|
+
this.setState({
|
|
82
|
+
success: true,
|
|
83
|
+
userInfo: userInfo.data
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @override
|
|
89
|
+
*/
|
|
90
|
+
async onError(): Promise<void> {
|
|
91
|
+
this.logout();
|
|
92
|
+
|
|
93
|
+
this.options.routerController.gotoLogin();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @override
|
|
98
|
+
*/
|
|
99
|
+
async login(
|
|
100
|
+
params: FeApiLogin['request']
|
|
101
|
+
): Promise<FeApiGetUserInfo['response']['data']> {
|
|
102
|
+
const { feApi } = this.options;
|
|
103
|
+
|
|
104
|
+
const result = await feApi.login(params);
|
|
105
|
+
|
|
106
|
+
this.setToken(result.data.token);
|
|
107
|
+
|
|
108
|
+
const userInfo = await feApi.getUserInfo();
|
|
109
|
+
|
|
110
|
+
this.setState({
|
|
111
|
+
success: true,
|
|
112
|
+
userInfo: userInfo.data
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return userInfo.data;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @override
|
|
120
|
+
*/
|
|
121
|
+
logout(): void {
|
|
122
|
+
this.reset();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
setToken(token: string): void {
|
|
126
|
+
this.token = token;
|
|
127
|
+
|
|
128
|
+
const { storageKey, storage } = this.options;
|
|
129
|
+
|
|
130
|
+
storage.setItem(
|
|
131
|
+
storageKey,
|
|
132
|
+
this.token,
|
|
133
|
+
adjustExpirationTime(Date.now(), this.options.expiresIn ?? 'month')
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
reset(): void {
|
|
138
|
+
this.token = '';
|
|
139
|
+
this.options.storage.removeItem(this.options.storageKey);
|
|
140
|
+
this.setState({ success: false });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
isAuthenticated(): boolean {
|
|
144
|
+
return this.state.success;
|
|
145
|
+
}
|
|
146
|
+
}
|