refactor: modify layout

This commit is contained in:
Jerry Fan
2023-11-13 18:00:09 +08:00
parent 6502c1e2e5
commit 71cee154e7
15 changed files with 329 additions and 57 deletions

View File

@@ -20,8 +20,11 @@ export default defineConfig({
publicPath: '/',
hash: true,
routes: [
{ path: '/login', component: '@/pages/login' },
{ path: '/', component: 'main' },
{
path: '/',
component: '@/layouts/GlobalLayout',
routes: [{ path: '/', component: 'main' }],
},
],
npmClient: 'yarn',

View File

@@ -1,3 +0,0 @@
declare module 'monaco-editor/esm/vs/basic-languages/sql/sql';
declare module 'monaco-editor/esm/vs/language/typescript/ts.worker.js';
declare module 'monaco-editor/esm/vs/editor/editor.worker.js';

View File

@@ -0,0 +1,64 @@
import { useState, useEffect } from 'react';
interface IProps {
/** 最大请求次数 */
maxAttempts?: number;
/** 请求间隔时间ms */
interval?: number;
/** 请求服务 */
loopService: (...rest) => Promise<boolean>;
}
export enum ServiceStatus {
PENDING = 'PENDING',
SUCCESS = 'SUCCESS',
FAILURE = 'FAILURE',
}
/**
* 轮询请求后端服务
*/
const usePollRequestService = ({ maxAttempts = 100, interval = 5000, loopService }: IProps) => {
const [serviceStatus, setServiceStatus] = useState<ServiceStatus>(ServiceStatus.PENDING);
const [attempts, setAttempts] = useState(0);
const [restart, setRestart] = useState(false);
useEffect(() => {
let intervalId: NodeJS.Timeout;
const serviceFn = async () => {
if (attempts >= maxAttempts) {
setServiceStatus(ServiceStatus.FAILURE);
clearInterval(intervalId);
return;
}
try {
setAttempts(attempts + 1);
await loopService();
setServiceStatus(ServiceStatus.SUCCESS);
clearInterval(intervalId);
} catch (error) {
// setAttempts(attempts + 1);
}
};
serviceFn();
if (serviceStatus !== ServiceStatus.SUCCESS) {
intervalId = setInterval(serviceFn, interval);
}
return () => clearInterval(intervalId);
}, [maxAttempts, interval, restart]);
// 新增加的重置函数
const restartPolling = () => {
setServiceStatus(ServiceStatus.PENDING);
setAttempts(0);
setRestart(!restart);
};
return { serviceStatus, restartPolling };
};
export default usePollRequestService;

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { addColorSchemeListener, colorSchemeListeners } from '@/layouts';
// import { addColorSchemeListener, colorSchemeListeners } from '@/layouts';
import { getOsTheme } from '@/utils';
import { ITheme } from '@/typings';
import { ThemeType, PrimaryColorType } from '@/constants';
@@ -37,10 +37,10 @@ export function useTheme<T = ITheme>(): [T, React.Dispatch<React.SetStateAction<
// const isDark = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]);
useEffect(() => {
const uuid = addColorSchemeListener(setAppTheme as any);
return () => {
delete colorSchemeListeners[uuid];
};
// const uuid = addColorSchemeListener(setAppTheme as any);
// return () => {
// delete colorSchemeListeners[uuid];
// };
}, []);
function handleAppThemeChange(theme: { backgroundColor: ThemeType; primaryColor: PrimaryColorType }) {
@@ -50,9 +50,9 @@ export function useTheme<T = ITheme>(): [T, React.Dispatch<React.SetStateAction<
? ThemeType.DarkDimmed
: ThemeType.Light;
}
Object.keys(colorSchemeListeners)?.forEach((t) => {
colorSchemeListeners[t]?.(theme);
});
// Object.keys(colorSchemeListeners)?.forEach((t) => {
// colorSchemeListeners[t]?.(theme);
// });
document.documentElement.setAttribute('theme', theme.backgroundColor);
setTheme(theme.backgroundColor);
document.documentElement.setAttribute('primary-color', theme.primaryColor);

View File

View File

@@ -0,0 +1,8 @@
import React from 'react';
import styles from './index.less';
function BasicLayout(props) {
return <div>{props.children}</div>;
}
export default BasicLayout;

View File

@@ -0,0 +1,92 @@
@import '@/styles/global.less';
@import '@/styles/var.less';
.loadingBox {
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
flex-direction: column;
.contact {
line-height: 32px;
display: flex;
align-items: center;
.icon {
cursor: pointer;
font-size: 32px;
margin-right: 20px;
}
}
}
.app {
width: 100vw;
height: 100vh;
}
:global {
#root {
height: 100%;
.codicon-symbol-text:before {
content: '\eb11';
width: 16px;
height: 16px;
}
.codicon-symbol-function:before {
content: '\ebb8';
width: 16px;
height: 16px;
// background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAKZJREFUOE+lkgEOgzAMA83LNl7G9rJtL2O6qkZZlRYGkSpacGzHdNLFmi72a0TwkLQEgRTbI3DzLOk9ctkjWCU9JUE0rBHBoXwi6BWk7tX6p7rgTDEOe1ZxFwkMIjgaPTtPwLc6FkLbeJlNAFaO8/MekZ9sMgICzNL/i6AltivGYb8JtED//zYbcqGJch7lnBEYtHcFyvee1d0LZPaMwFZPOTjUFEFfU0IlESizFzUAAAAASUVORK5CYII=");
}
.codicon-symbol-folder:before {
content: '\ebb7';
width: 16px;
height: 16px;
// background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAHxJREFUOE/Fk10OgCAMgz9Opp5MPZl4Mk0JS4AEJWK0L+OvZWzF0QkX+SMwA4ot8MAKeBPYgB1YWtjx3ABMJnAANm7UIHBeFWi9OT1XzcBqUYsSuXzCPwLqq0EtEtRaoZxrTb7JatAtkPrg+xqUVr7LQPuZlbs/0xMXBs4Jp5IvERhfiDgAAAAASUVORK5CYII=");
}
.codicon-symbol-field:before {
content: '\eb17';
width: 16px;
height: 16px;
// background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjk3MTcwMDQyMTI4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjkyNTIiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPjxwYXRoIGQ9Ik05NzkuMiAwSDQ0LjhhNDQuOCA0NC44IDAgMCAwLTQ0LjggNDQuOHY5MzQuNGE0NC44IDQ0LjggMCAwIDAgNDQuOCA0NC44aDkzNC40YTQ0LjggNDQuOCAwIDAgMCA0NC44LTQ0LjhWNDQuOGE0NC44IDQ0LjggMCAwIDAtNDQuOC00NC44ek05NjAgOTYwSDY0VjY0aDg5NnoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTMiPjwvcGF0aD48cGF0aCBkPSJNMjU2IDIzOS44MDhoMjI0djU0NC4zODRIMzg0djY0aDI1NnYtNjRINTQ0VjIzOS44MDhINzY4djEzNC43Mmg2NFYxNzUuODA4SDE5MnYxOTguNzJoNjRWMjM5LjgwOHoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTQiPjwvcGF0aD48L3N2Zz4=');
}
.codicon-symbol-unit:before {
content: '\ea70';
width: 16px;
height: 16px;
// background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjk3MTcwMDQyMTI4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjkyNTIiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPjxwYXRoIGQ9Ik05NzkuMiAwSDQ0LjhhNDQuOCA0NC44IDAgMCAwLTQ0LjggNDQuOHY5MzQuNGE0NC44IDQ0LjggMCAwIDAgNDQuOCA0NC44aDkzNC40YTQ0LjggNDQuOCAwIDAgMCA0NC44LTQ0LjhWNDQuOGE0NC44IDQ0LjggMCAwIDAtNDQuOC00NC44ek05NjAgOTYwSDY0VjY0aDg5NnoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTMiPjwvcGF0aD48cGF0aCBkPSJNMjU2IDIzOS44MDhoMjI0djU0NC4zODRIMzg0djY0aDI1NnYtNjRINTQ0VjIzOS44MDhINzY4djEzNC43Mmg2NFYxNzUuODA4SDE5MnYxOTguNzJoNjRWMjM5LjgwOHoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTQiPjwvcGF0aD48L3N2Zz4=');
}
.codicon-symbol-property:before {
content: '\eace';
width: 16px;
height: 16px;
// background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjk3MTcwMDQyMTI4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjkyNTIiIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPjxwYXRoIGQ9Ik05NzkuMiAwSDQ0LjhhNDQuOCA0NC44IDAgMCAwLTQ0LjggNDQuOHY5MzQuNGE0NC44IDQ0LjggMCAwIDAgNDQuOCA0NC44aDkzNC40YTQ0LjggNDQuOCAwIDAgMCA0NC44LTQ0LjhWNDQuOGE0NC44IDQ0LjggMCAwIDAtNDQuOC00NC44ek05NjAgOTYwSDY0VjY0aDg5NnoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTMiPjwvcGF0aD48cGF0aCBkPSJNMjU2IDIzOS44MDhoMjI0djU0NC4zODRIMzg0djY0aDI1NnYtNjRINTQ0VjIzOS44MDhINzY4djEzNC43Mmg2NFYxNzUuODA4SDE5MnYxOTguNzJoNjRWMjM5LjgwOHoiIGZpbGw9IiM1NzU4NjkiIHAtaWQ9IjkyNTQiPjwvcGF0aD48L3N2Zz4=');
}
}
}

View File

@@ -0,0 +1,66 @@
import React, { useEffect } from 'react';
import usePollRequestService, { ServiceStatus } from '@/hooks/usePollRequestService';
import i18n, { isEn } from '@/i18n';
import { Button, ConfigProvider, Spin, Tooltip } from 'antd';
import antdEnUS from 'antd/locale/en_US';
import antdZhCN from 'antd/locale/zh_CN';
import service from '@/service/misc';
import styles from './index.less';
import MyNotification from '@/components/MyNotification';
import { useTheme } from '@/hooks/useTheme';
import { getAntdThemeConfig } from '@/theme';
import useCopyFocusData from '@/hooks/useFocusData';
import { Outlet } from 'umi';
import init from '../init/init';
import { GithubOutlined, SyncOutlined, WechatOutlined } from '@ant-design/icons';
const GlobalLayout = () => {
const [appTheme] = useTheme();
const { serviceStatus, restartPolling } = usePollRequestService({
loopService: service.testService,
});
useCopyFocusData();
useEffect(() => {
init();
}, []);
// 等待状态页面
if (serviceStatus === ServiceStatus.PENDING) {
return <Spin className={styles.loadingBox} size="large" />;
}
// 错误状态页面
if (serviceStatus === ServiceStatus.FAILURE) {
return (
<div className={styles.loadingBox}>
<Button type="primary" onClick={restartPolling} style={{ marginBottom: 20 }}>
<SyncOutlined />
{i18n('common.text.tryToRestart')}
</Button>
<div className={styles.contact}>
{i18n('common.text.contactUs')}
<GithubOutlined className={styles.icon} onClick={() => window.open('https://github.com/chat2db/Chat2DB')} />
<Tooltip
placement="bottom"
title={<img style={{ width: 200, height: 200 }} src="https://sqlgpt.cn/_static/img/chat2db_wechat.png" />}
>
<WechatOutlined className={styles.icon} />
</Tooltip>
</div>
</div>
);
}
return (
<ConfigProvider locale={isEn ? antdEnUS : antdZhCN} theme={getAntdThemeConfig(appTheme)}>
<div className={styles.app}>
<Outlet />
</div>
<MyNotification />
</ConfigProvider>
);
};
export default GlobalLayout;

View File

@@ -4,45 +4,19 @@ import { Outlet } from 'umi';
import { ConfigProvider, Spin } from 'antd';
import { v4 as uuidv4 } from 'uuid';
import { getAntdThemeConfig } from '@/theme';
import { IVersionResponse } from '@/typings';
import miscService from '@/service/misc';
import antdEnUS from 'antd/locale/en_US';
import antdZhCN from 'antd/locale/zh_CN';
import { useTheme } from '@/hooks';
import { ThemeType, LangType, PrimaryColorType } from '@/constants/';
import styles from './index.less';
import { getLang, setLang } from '@/utils/localStorage';
import { clearOlderLocalStorage } from '@/utils';
import registerMessage from './init/registerMessage';
import registerNotification from './init/registerNotification';
import MyNotification from '@/components/MyNotification';
// import Iconfont from '@/components/Iconfont';
// import Setting from '@/blocks/Setting';
import indexedDB from '@/indexedDB';
import useCopyFocusData from '@/hooks/useFocusData';
declare global {
interface Window {
_Lang: string;
_APP_PORT: string;
_BUILD_TIME: string;
_BaseURL: string;
_AppThemePack: { [key in string]: string };
_appGatewayParams: IVersionResponse;
_notificationApi: any;
_indexedDB: any;
electronApi?: {
startServerForSpawn: () => void;
quitApp: () => void;
setBaseURL: (baseUrl: string) => void;
registerAppMenu: (data: any) => void;
};
}
const __APP_VERSION__: string;
const __BUILD_TIME__: string;
const __ENV__: string;
const __APP_PORT__: string;
}
import styles from './index.less';
const initConfig = () => {
registerMessage();
@@ -139,16 +113,7 @@ function AppContainer() {
};
}
// 初始化语言
function initLang() {
const lang = getLang();
if (!lang) {
setLang(LangType.EN_US);
document.documentElement.setAttribute('lang', LangType.EN_US);
const date = new Date('2030-12-30 12:30:00').toUTCString();
document.cookie = `CHAT2DB.LOCALE=${lang};Expires=${date}`;
}
}
useEffect(() => {
detectionService();
@@ -203,8 +168,7 @@ function AppContainer() {
{startSchedule === 2 && <Outlet />}
</div>
)}
{/* 全局的弹窗 */}
<MyNotification />
</div>
);
}

View File

@@ -0,0 +1,30 @@
import { clearOlderLocalStorage } from '@/utils';
import initIndexedDB from './initIndexedDB';
import registerElectronApi from './registerElectronApi';
import registerMessage from './registerMessage';
import registerNotification from './registerNotification';
import { getLang, setLang } from '@/utils/localStorage';
import { LangType } from '@/constants';
const init = () => {
clearOlderLocalStorage();
initLang();
initIndexedDB();
registerElectronApi();
registerMessage();
registerNotification();
};
// 初始化语言
const initLang = () => {
const lang = getLang();
if (!lang) {
setLang(LangType.EN_US);
document.documentElement.setAttribute('lang', LangType.EN_US);
const date = new Date('2030-12-30 12:30:00').toUTCString();
document.cookie = `CHAT2DB.LOCALE=${lang};Expires=${date}`;
}
};
export default init;

View File

@@ -0,0 +1,12 @@
import indexedDB from '@/indexedDB';
/** 初始化indexedDB */
const initIndexedDB = () => {
indexedDB.createDB('chat2db', 1).then((db) => {
window._indexedDB = {
chat2db: db,
};
});
};
export default initIndexedDB;

View File

@@ -0,0 +1,9 @@
// 注册Electron关闭时关闭服务
const registerElectronApi = () => {
window.electronApi?.registerAppMenu({
version: __APP_VERSION__,
});
window.electronApi?.setBaseURL?.(window._BaseURL);
};
export default registerElectronApi;

View File

@@ -1,5 +1,5 @@
import createRequest from "./base";
const testService = createRequest<void, void>('/api/system', { errorLevel: false });
const testService = createRequest<null, boolean>('/api/system', { errorLevel: false });
const systemStop = createRequest<void, void>('/api/system/stop', { errorLevel: false, method: 'post' });
const testApiSmooth = createRequest<void, void>('/api/system/get-version-a', { errorLevel: false, method: 'get' });

View File

@@ -3,6 +3,6 @@
"compilerOptions": {
"moduleResolution": "node",
"jsx": "react-jsx",
"noImplicitAny": false,
},
}
"noImplicitAny": false
}
}

View File

@@ -1,9 +1,36 @@
import 'umi/typings';
import { IVersionResponse } from '@/typings';
declare module 'monaco-editor/esm/vs/basic-languages/sql/sql';
declare module 'monaco-editor/esm/vs/language/typescript/ts.worker.js';
declare module 'monaco-editor/esm/vs/editor/editor.worker.js';
declare namespace NodeJS {
interface ProcessEnv {
readonly NODE_ENV: 'development' | 'production'
readonly UMI_ENV: string
readonly __ENV: string;
}
}
}
declare global {
interface Window {
_Lang: string;
_APP_PORT: string;
_BUILD_TIME: string;
_BaseURL: string;
_AppThemePack: { [key in string]: string };
_appGatewayParams: IVersionResponse;
_notificationApi: any;
_indexedDB: any;
electronApi?: {
startServerForSpawn: () => void;
quitApp: () => void;
setBaseURL: (baseUrl: string) => void;
registerAppMenu: (data: any) => void;
};
}
const __APP_VERSION__: string;
const __BUILD_TIME__: string;
const __ENV__: string;
const __APP_PORT__: string;
}