diff --git a/client/shared/model/config.ts b/client/shared/model/config.ts
index 5c7173f9..6356f35f 100644
--- a/client/shared/model/config.ts
+++ b/client/shared/model/config.ts
@@ -20,6 +20,11 @@ export interface GlobalConfig {
* 服务器名
*/
serverName?: string;
+
+ /**
+ * 服务器入口背景图
+ */
+ serverEntryImage?: string;
}
export function getGlobalConfig(): GlobalConfig {
diff --git a/client/web/src/routes/Entry/index.tsx b/client/web/src/routes/Entry/index.tsx
index 5e4980f9..15207b71 100644
--- a/client/web/src/routes/Entry/index.tsx
+++ b/client/web/src/routes/Entry/index.tsx
@@ -8,10 +8,16 @@ import { RegisterView } from './RegisterView';
import { useRecordMeasure } from '@/utils/measure-helper';
import { GuestView } from './GuestView';
import { ForgetPasswordView } from './ForgetPasswordView';
+import { Helmet } from 'react-helmet';
+import { parseUrlStr, useGlobalConfigStore } from 'tailchat-shared';
const EntryRoute = React.memo(() => {
useRecordMeasure('appEntryRenderStart');
+ const serverEntryImage = useGlobalConfigStore(
+ (state) => state.serverEntryImage
+ );
+
return (
{
+ {serverEntryImage && (
+
+
+
+ )}
+
);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3fb9f915..7570cbe3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1007,6 +1007,7 @@ importers:
specifiers:
'@fastify/busboy': ^1.1.0
'@mui/icons-material': ^5.11.0
+ '@mui/lab': 5.0.0-alpha.122
'@mui/material': ^5.11.3
'@remix-run/dev': ^1.9.0
'@remix-run/express': ^1.9.0
@@ -1048,6 +1049,7 @@ importers:
dependencies:
'@fastify/busboy': 1.1.0
'@mui/icons-material': 5.11.0_oiuuhmk4wjjpe4qb2sby7usney
+ '@mui/lab': 5.0.0-alpha.122_g64p43h6yqqjvdymvb55np57cu
'@mui/material': 5.11.3_ib3m5ricvtkl2cll7qpr2f6lvq
'@remix-run/express': 1.9.0_cwk4saenierp7pa7l5cpbeswge
'@remix-run/node': 1.9.0_biqbaboplfbrettd7655fr4n2y
@@ -8847,7 +8849,31 @@ packages:
'@babel/runtime': 7.21.0
'@emotion/is-prop-valid': 1.2.0
'@mui/types': 7.2.3_@types+react@18.0.26
- '@mui/utils': 5.11.2_react@18.2.0
+ '@mui/utils': 5.11.12_react@18.2.0
+ '@popperjs/core': 2.11.6
+ '@types/react': 18.0.26
+ clsx: 1.2.1
+ prop-types: 15.8.1
+ react: 18.2.0
+ react-dom: 18.2.0_react@18.2.0
+ react-is: 18.2.0
+ dev: false
+
+ /@mui/base/5.0.0-alpha.120_ib3m5ricvtkl2cll7qpr2f6lvq:
+ resolution: {integrity: sha512-UoIXLjbl8ghK7OSD1dYzHIj79sx9v5S2J7vYeuhxUS0QR0FwGZ3WLHd31TQ2CT2faPX/AXsHQeFn93wKSnjPUQ==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@types/react': ^17.0.0 || ^18.0.0
+ react: ^17.0.0 || ^18.0.0
+ react-dom: ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.21.0
+ '@emotion/is-prop-valid': 1.2.0
+ '@mui/types': 7.2.3_@types+react@18.0.26
+ '@mui/utils': 5.11.12_react@18.2.0
'@popperjs/core': 2.11.6
'@types/react': 18.0.26
clsx: 1.2.1
@@ -8878,6 +8904,38 @@ packages:
react: 18.2.0
dev: false
+ /@mui/lab/5.0.0-alpha.122_g64p43h6yqqjvdymvb55np57cu:
+ resolution: {integrity: sha512-rJyu9llUWAluUQgDEmN0WrpcFxeTdJgu+XYriJtp/MchdvKl/qVTlx+vhnIhqas2bySj5N1VQnkI6qOvfXiYvQ==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@emotion/react': ^11.5.0
+ '@emotion/styled': ^11.3.0
+ '@mui/material': ^5.0.0
+ '@types/react': ^17.0.0 || ^18.0.0
+ react: ^17.0.0 || ^18.0.0
+ react-dom: ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@emotion/react':
+ optional: true
+ '@emotion/styled':
+ optional: true
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.21.0
+ '@mui/base': 5.0.0-alpha.120_ib3m5ricvtkl2cll7qpr2f6lvq
+ '@mui/material': 5.11.3_ib3m5ricvtkl2cll7qpr2f6lvq
+ '@mui/system': 5.11.12_kzbn2opkn2327fwg5yzwzya5o4
+ '@mui/types': 7.2.3_@types+react@18.0.26
+ '@mui/utils': 5.11.12_react@18.2.0
+ '@types/react': 18.0.26
+ clsx: 1.2.1
+ prop-types: 15.8.1
+ react: 18.2.0
+ react-dom: 18.2.0_react@18.2.0
+ react-is: 18.2.0
+ dev: false
+
/@mui/material/5.11.3_af5ln35zuaotaffazii6n6bke4:
resolution: {integrity: sha512-Oz+rMFiMtxzzDLUxKyyj4mSxF9ShmsBoJ9qvglXCYqklgSrEl1R/Z4hfPZ+2pWd5CriO8U/0CFHr4DksrlTiCw==}
engines: {node: '>=12.0.0'}
@@ -8984,8 +9042,8 @@ packages:
react-transition-group: 4.4.5_biqbaboplfbrettd7655fr4n2y
dev: false
- /@mui/private-theming/5.11.2_kzbn2opkn2327fwg5yzwzya5o4:
- resolution: {integrity: sha512-qZwMaqRFPwlYmqwVKblKBGKtIjJRAj3nsvX93pOmatsXyorW7N/0IPE/swPgz1VwChXhHO75DwBEx8tB+aRMNg==}
+ /@mui/private-theming/5.11.12_kzbn2opkn2327fwg5yzwzya5o4:
+ resolution: {integrity: sha512-hnJ0svNI1TPeWZ18E6DvES8PB4NyMLwal6EyXf69rTrYqT6wZPLjB+HiCYfSOCqU/fwArhupSqIIkQpDs8CkAw==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0
@@ -8995,7 +9053,7 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.21.0
- '@mui/utils': 5.11.2_react@18.2.0
+ '@mui/utils': 5.11.12_react@18.2.0
'@types/react': 18.0.26
prop-types: 15.8.1
react: 18.2.0
@@ -9040,8 +9098,30 @@ packages:
react: 18.2.0
dev: false
- /@mui/styled-engine/5.11.0_react@18.2.0:
- resolution: {integrity: sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==}
+ /@mui/styled-engine/5.11.11_hfzxdiydbrbhhfpkwuv3jhvwmq:
+ resolution: {integrity: sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@emotion/react': ^11.4.1
+ '@emotion/styled': ^11.3.0
+ react: ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@emotion/react':
+ optional: true
+ '@emotion/styled':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.21.0
+ '@emotion/cache': 11.10.5
+ '@emotion/react': 11.10.4_kzbn2opkn2327fwg5yzwzya5o4
+ '@emotion/styled': 11.10.4_5edjfi7y5ucoavvp3vhupkkowq
+ csstype: 3.1.1
+ prop-types: 15.8.1
+ react: 18.2.0
+ dev: false
+
+ /@mui/styled-engine/5.11.11_react@18.2.0:
+ resolution: {integrity: sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==}
engines: {node: '>=12.0.0'}
peerDependencies:
'@emotion/react': ^11.4.1
@@ -9060,6 +9140,34 @@ packages:
react: 18.2.0
dev: false
+ /@mui/system/5.11.12_kzbn2opkn2327fwg5yzwzya5o4:
+ resolution: {integrity: sha512-sYjsXkiwKpZDC3aS6O/6KTjji0jGINLQcrD5EJ5NTkIDiLf19I4HJhnufgKqlTWNfoDBlRohuTf3TzfM06c4ug==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ '@emotion/react': ^11.5.0
+ '@emotion/styled': ^11.3.0
+ '@types/react': ^17.0.0 || ^18.0.0
+ react: ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@emotion/react':
+ optional: true
+ '@emotion/styled':
+ optional: true
+ '@types/react':
+ optional: true
+ dependencies:
+ '@babel/runtime': 7.21.0
+ '@mui/private-theming': 5.11.12_kzbn2opkn2327fwg5yzwzya5o4
+ '@mui/styled-engine': 5.11.11_react@18.2.0
+ '@mui/types': 7.2.3_@types+react@18.0.26
+ '@mui/utils': 5.11.12_react@18.2.0
+ '@types/react': 18.0.26
+ clsx: 1.2.1
+ csstype: 3.1.1
+ prop-types: 15.8.1
+ react: 18.2.0
+ dev: false
+
/@mui/system/5.11.2_4mv32nu4vciambuqqzuu4gtvj4:
resolution: {integrity: sha512-PPkYhrcP2MkhscX6SauIl0wPgra0w1LGPtll+hIKc2Z2JbGRSrUCFif93kxejB7I1cAoCay9jWW4mnNhsOqF/g==}
engines: {node: '>=12.0.0'}
@@ -9107,10 +9215,10 @@ packages:
optional: true
dependencies:
'@babel/runtime': 7.21.0
- '@mui/private-theming': 5.11.2_kzbn2opkn2327fwg5yzwzya5o4
- '@mui/styled-engine': 5.11.0_react@18.2.0
+ '@mui/private-theming': 5.11.12_kzbn2opkn2327fwg5yzwzya5o4
+ '@mui/styled-engine': 5.11.11_react@18.2.0
'@mui/types': 7.2.3_@types+react@18.0.26
- '@mui/utils': 5.11.2_react@18.2.0
+ '@mui/utils': 5.11.12_react@18.2.0
'@types/react': 18.0.26
clsx: 1.2.1
csstype: 3.1.1
@@ -9137,10 +9245,10 @@ packages:
'@babel/runtime': 7.21.0
'@emotion/react': 11.10.4_kzbn2opkn2327fwg5yzwzya5o4
'@emotion/styled': 11.10.4_5edjfi7y5ucoavvp3vhupkkowq
- '@mui/private-theming': 5.11.2_kzbn2opkn2327fwg5yzwzya5o4
- '@mui/styled-engine': 5.11.0_hfzxdiydbrbhhfpkwuv3jhvwmq
+ '@mui/private-theming': 5.11.12_kzbn2opkn2327fwg5yzwzya5o4
+ '@mui/styled-engine': 5.11.11_hfzxdiydbrbhhfpkwuv3jhvwmq
'@mui/types': 7.2.3_@types+react@18.0.26
- '@mui/utils': 5.11.2_react@18.2.0
+ '@mui/utils': 5.11.12_react@18.2.0
'@types/react': 18.0.26
clsx: 1.2.1
csstype: 3.1.1
@@ -9170,6 +9278,20 @@ packages:
'@types/react': 18.0.26
dev: false
+ /@mui/utils/5.11.12_react@18.2.0:
+ resolution: {integrity: sha512-5vH9B/v8pzkpEPO2HvGM54ToXV6cFdAn8UrvdN8TMEEwpn/ycW0jLiyBcgUlPsQ+xha7hqXCPQYHaYFDIcwaiw==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ react: ^17.0.0 || ^18.0.0
+ dependencies:
+ '@babel/runtime': 7.21.0
+ '@types/prop-types': 15.7.5
+ '@types/react-is': 17.0.3
+ prop-types: 15.8.1
+ react: 18.2.0
+ react-is: 18.2.0
+ dev: false
+
/@mui/utils/5.11.2_react@18.2.0:
resolution: {integrity: sha512-AyizuHHlGdAtH5hOOXBW3kriuIwUIKUIgg0P7LzMvzf6jPhoQbENYqY6zJqfoZ7fAWMNNYT8mgN5EftNGzwE2w==}
engines: {node: '>=12.0.0'}
@@ -14657,7 +14779,7 @@ packages:
/@types/react-is/17.0.3:
resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==}
dependencies:
- '@types/react': 17.0.53
+ '@types/react': 18.0.26
dev: false
/@types/react-mentions/4.1.8:
@@ -14696,7 +14818,7 @@ packages:
/@types/react-transition-group/4.4.5:
resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==}
dependencies:
- '@types/react': 18.0.20
+ '@types/react': 18.0.26
/@types/react-virtualized-auto-sizer/1.0.1:
resolution: {integrity: sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong==}
diff --git a/server/admin/app/ra/components/Image.tsx b/server/admin/app/ra/components/Image.tsx
index e75cec46..59ad6642 100644
--- a/server/admin/app/ra/components/Image.tsx
+++ b/server/admin/app/ra/components/Image.tsx
@@ -1,7 +1,9 @@
-import React from 'react';
+import React, { ImgHTMLAttributes } from 'react';
import { parseUrlStr } from '../utils';
-export const Image: React.FC<{ src: string }> = React.memo((props) => {
- return
;
-});
+export const Image: React.FC> = React.memo(
+ (props) => {
+ return
;
+ }
+);
Image.displayName = 'Image';
diff --git a/server/admin/app/ra/i18n/custom.ts b/server/admin/app/ra/i18n/custom.ts
index bf8bedb2..5dba6d5a 100644
--- a/server/admin/app/ra/i18n/custom.ts
+++ b/server/admin/app/ra/i18n/custom.ts
@@ -10,6 +10,7 @@ export const englishCustom = {
errorOccurred: 'some errors occurred',
operateSuccess: 'Operate Success',
operateFailed: 'Operate Failed',
+ upload: 'Upload',
},
menu: {
network: 'Tailchat Network',
@@ -87,6 +88,7 @@ export const chineseCustom = {
errorOccurred: '发生了一些错误',
operateSuccess: '操作成功',
operateFailed: '操作失败',
+ upload: '上传',
},
menu: {
network: 'Tailchat 网络',
diff --git a/server/admin/app/ra/routes/system/index.tsx b/server/admin/app/ra/routes/system/index.tsx
index a949e075..dfb6c25c 100644
--- a/server/admin/app/ra/routes/system/index.tsx
+++ b/server/admin/app/ra/routes/system/index.tsx
@@ -2,10 +2,12 @@ import React, { PropsWithChildren } from 'react';
import { request } from '../../request';
import { useRequest } from 'ahooks';
import { CircularProgress, Box, Grid, Input } from '@mui/material';
-import { useTranslate, useDelete, useNotify } from 'react-admin';
+import { useTranslate, useNotify } from 'react-admin';
import DoneIcon from '@mui/icons-material/Done';
import ClearIcon from '@mui/icons-material/Clear';
import { useEditValue } from '../../utils/hooks';
+import { Image } from '../../components/Image';
+import LoadingButton from '@mui/lab/LoadingButton';
const SystemItem: React.FC<
PropsWithChildren<{
@@ -13,9 +15,9 @@ const SystemItem: React.FC<
}>
> = React.memo((props) => {
return (
-
+
- {props.label}
+ {props.label}:
{props.children}
@@ -35,6 +37,7 @@ export const SystemConfig: React.FC = React.memo(() => {
data: config,
loading,
error,
+ refresh,
} = useRequest(async () => {
const { data } = await request.get('/config/client');
@@ -53,6 +56,7 @@ export const SystemConfig: React.FC = React.memo(() => {
key: 'serverName',
value: val,
});
+ refresh();
notify('custom.common.operateSuccess', {
type: 'info',
});
@@ -64,6 +68,49 @@ export const SystemConfig: React.FC = React.memo(() => {
}
);
+ const {
+ loading: loadingServerEntryImage,
+ run: handleUploadServerEntryImage,
+ } = useRequest(
+ async (file: File) => {
+ try {
+ const formdata = new FormData();
+ formdata.append('file', file);
+
+ const { data } = await request.put('/file/upload', formdata, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ });
+
+ const fileInfo = data.files[0];
+
+ if (!fileInfo) {
+ throw new Error('not get file');
+ }
+
+ const url = fileInfo.url;
+ await request.patch('/config/client', {
+ key: 'serverEntryImage',
+ value: url,
+ });
+ refresh();
+
+ notify('custom.common.operateSuccess', {
+ type: 'info',
+ });
+ } catch (err) {
+ console.log(err);
+ notify('custom.common.operateFailed', {
+ type: 'info',
+ });
+ }
+ },
+ {
+ manual: true,
+ }
+ );
+
if (loading) {
return ;
}
@@ -100,6 +147,38 @@ export const SystemConfig: React.FC = React.memo(() => {
placeholder="Tailchat"
/>
+
+
+
+
+ {translate('custom.common.upload')}
+ {
+ const file = e.target.files[0];
+ if (file) {
+ handleUploadServerEntryImage(file);
+ }
+ }}
+ />
+
+
+
+ {config?.serverEntryImage && (
+
+ )}
+
+
+
);
});
diff --git a/server/admin/app/ra/utils/index.ts b/server/admin/app/ra/utils/index.ts
index 12bae4e7..bfb26b37 100644
--- a/server/admin/app/ra/utils/index.ts
+++ b/server/admin/app/ra/utils/index.ts
@@ -4,5 +4,10 @@
* @returns 解析后的url
*/
export function parseUrlStr(originUrl: string): string {
- return String(originUrl).replace('{BACKEND}', window.location.origin);
+ return String(originUrl).replace(
+ '{BACKEND}',
+ process.env.NODE_ENV === 'development'
+ ? 'http://localhost:11000'
+ : window.location.origin
+ );
}
diff --git a/server/admin/app/server/router/api.ts b/server/admin/app/server/router/api.ts
index 6131d4a4..685a1281 100644
--- a/server/admin/app/server/router/api.ts
+++ b/server/admin/app/server/router/api.ts
@@ -5,7 +5,7 @@ import { callBrokerAction } from '../broker';
import { adminAuth, auth, authSecret } from '../middleware/auth';
import { configRouter } from './config';
import { networkRouter } from './network';
-import { fileRouter } from './upload';
+import { fileRouter } from './file';
const router = Router();
diff --git a/server/admin/app/server/router/upload.ts b/server/admin/app/server/router/file.ts
similarity index 100%
rename from server/admin/app/server/router/upload.ts
rename to server/admin/app/server/router/file.ts
diff --git a/server/admin/package.json b/server/admin/package.json
index efeefdff..a6657a3f 100644
--- a/server/admin/package.json
+++ b/server/admin/package.json
@@ -13,6 +13,7 @@
"dependencies": {
"@fastify/busboy": "^1.1.0",
"@mui/icons-material": "^5.11.0",
+ "@mui/lab": "5.0.0-alpha.122",
"@mui/material": "^5.11.3",
"@remix-run/express": "^1.9.0",
"@remix-run/node": "^1.9.0",