temp: add resources
This commit is contained in:
parent
a5bde33678
commit
fd30741151
2
.npmrc
2
.npmrc
@ -1,2 +1,4 @@
|
|||||||
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||||
|
|
||||||
|
ignore-workspace-root-check=true
|
@ -14,6 +14,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^5.6.1",
|
"@ant-design/icons": "^5.6.1",
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
|
"@emotion/styled": "^11.14.0",
|
||||||
"@icon-park/react": "^1.4.2",
|
"@icon-park/react": "^1.4.2",
|
||||||
"@kevisual/codemirror": "^0.0.2",
|
"@kevisual/codemirror": "^0.0.2",
|
||||||
"@kevisual/container": "1.0.0",
|
"@kevisual/container": "1.0.0",
|
||||||
@ -21,6 +23,7 @@
|
|||||||
"@kevisual/system-ui": "^0.0.3",
|
"@kevisual/system-ui": "^0.0.3",
|
||||||
"@kevisual/ui": "^0.0.2",
|
"@kevisual/ui": "^0.0.2",
|
||||||
"@monaco-editor/react": "^4.7.0",
|
"@monaco-editor/react": "^4.7.0",
|
||||||
|
"@mui/material": "^6.4.7",
|
||||||
"@tailwindcss/vite": "^4.0.12",
|
"@tailwindcss/vite": "^4.0.12",
|
||||||
"@uiw/react-textarea-code-editor": "^3.1.0",
|
"@uiw/react-textarea-code-editor": "^3.1.0",
|
||||||
"@xyflow/react": "^12.4.4",
|
"@xyflow/react": "^12.4.4",
|
||||||
|
31
packages/components/package.json
Normal file
31
packages/components/package.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "@kevisual/center-components",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "center components",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"keywords": [],
|
||||||
|
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"@mui/material": "^6.4.7",
|
||||||
|
"react": "19.0.0",
|
||||||
|
"react-dom": "19.0.0"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.tsx",
|
||||||
|
"./theme": "./src/theme/index.tsx",
|
||||||
|
"./button": "./src/button/index.tsx"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"tailwind-merge": "^3.0.2"
|
||||||
|
}
|
||||||
|
}
|
5
packages/components/src/button/index.tsx
Normal file
5
packages/components/src/button/index.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import MuiButton, { ButtonProps } from '@mui/material/Button';
|
||||||
|
|
||||||
|
export const Button = (props: ButtonProps) => {
|
||||||
|
return <MuiButton {...props} />;
|
||||||
|
};
|
7
packages/components/src/clsx/index.ts
Normal file
7
packages/components/src/clsx/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import clsx, { ClassValue } from 'clsx';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export const clsxMerge = (...args: ClassValue[]) => {
|
||||||
|
return twMerge(clsx(...args));
|
||||||
|
};
|
||||||
|
export { clsx };
|
1
packages/components/src/index.tsx
Normal file
1
packages/components/src/index.tsx
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './theme';
|
64
packages/components/src/theme/index.tsx
Normal file
64
packages/components/src/theme/index.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { createTheme, ThemeOptions } from '@mui/material/styles';
|
||||||
|
import { useTheme as useMuiTheme, Theme } from '@mui/material/styles';
|
||||||
|
import { amber } from '@mui/material/colors';
|
||||||
|
export const themeOptions: ThemeOptions = {
|
||||||
|
palette: {
|
||||||
|
primary: {
|
||||||
|
main: '#ffc107', // amber[300]
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: '#ffa000', // amber[500]
|
||||||
|
},
|
||||||
|
divider: amber[200],
|
||||||
|
common: {
|
||||||
|
white: '#ffa000',
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
primary: amber[600],
|
||||||
|
secondary: amber[600],
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
default: '#ffffff', // 设置默认背景颜色
|
||||||
|
// paper: '#f5f5f5', // 设置纸张背景颜色
|
||||||
|
},
|
||||||
|
},
|
||||||
|
typography: {
|
||||||
|
// fontFamily: 'Roboto, sans-serif',
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MuiButtonBase: {
|
||||||
|
defaultProps: {
|
||||||
|
disableRipple: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MuiTextField: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
'& .MuiOutlinedInput-root': {
|
||||||
|
'& fieldset': {
|
||||||
|
borderColor: amber[300],
|
||||||
|
},
|
||||||
|
'&:hover fieldset': {
|
||||||
|
borderColor: amber[500],
|
||||||
|
},
|
||||||
|
'& .MuiInputBase-input': {
|
||||||
|
color: amber[600],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .MuiInputLabel-root': {
|
||||||
|
color: amber[600],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://bareynol.github.io/mui-theme-creator/
|
||||||
|
*/
|
||||||
|
export const theme = createTheme(themeOptions);
|
||||||
|
|
||||||
|
export const useTheme = () => {
|
||||||
|
return useMuiTheme<Theme>();
|
||||||
|
};
|
36
packages/components/tsconfig.json
Normal file
36
packages/components/tsconfig.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": [
|
||||||
|
"ES2020",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"baseUrl": "./",
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types",
|
||||||
|
"node_modules/@kevisual/types",
|
||||||
|
],
|
||||||
|
"paths": {},
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src",
|
||||||
|
"typings.d.ts",
|
||||||
|
]
|
||||||
|
}
|
27
packages/resources/.gitignore
vendored
Normal file
27
packages/resources/.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
tsconfig.app.tsbuildinfo
|
||||||
|
tsconfig.node.tsbuildinfo
|
32
packages/resources/index.html
Normal file
32
packages/resources/index.html
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>AI Apps</title>
|
||||||
|
<link rel="stylesheet" href="./src/assets/index.css">
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ai-root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="/system/lib/app.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="ai-root"></div>
|
||||||
|
<!-- <div id="ai-bot-root"></div> -->
|
||||||
|
</body>
|
||||||
|
<script src="./src/main.ts" type="module"></script>
|
||||||
|
|
||||||
|
</html>
|
40
packages/resources/package.json
Normal file
40
packages/resources/package.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "resources",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"@kevisual/center-components": "workspace:*",
|
||||||
|
"@kevisual/router": "^0.0.9",
|
||||||
|
"@kevisual/store": "^0.0.2",
|
||||||
|
"@mui/material": "^6.4.7",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/nprogress": "^0.2.3",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||||
|
"dayjs": "^1.11.13",
|
||||||
|
"immer": "^10.1.1",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"lucide-react": "^0.482.0",
|
||||||
|
"nanoid": "^5.1.3",
|
||||||
|
"nprogress": "^0.2.0",
|
||||||
|
"pretty-bytes": "^6.1.1",
|
||||||
|
"react": "19.0.0",
|
||||||
|
"react-dom": "19.0.0",
|
||||||
|
"react-dropzone": "^14.3.8",
|
||||||
|
"react-toastify": "^11.0.5",
|
||||||
|
"zustand": "^5.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@kevisual/types": "^0.0.6"
|
||||||
|
}
|
||||||
|
}
|
12
packages/resources/src/app.ts
Normal file
12
packages/resources/src/app.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import type { Page } from '@kevisual/store/page';
|
||||||
|
import type { QueryRouterServer } from '@kevisual/router';
|
||||||
|
import { basename } from './modules/basename';
|
||||||
|
export const page = useContextKey('page', () => {
|
||||||
|
return new window.Page({
|
||||||
|
basename,
|
||||||
|
}) as unknown as Page;
|
||||||
|
});
|
||||||
|
export const app = useContextKey('app', () => {
|
||||||
|
console.error('app not found');
|
||||||
|
return null as unknown as QueryRouterServer;
|
||||||
|
});
|
20
packages/resources/src/assets/index.css
Normal file
20
packages/resources/src/assets/index.css
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
@import 'tailwindcss';
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.test-loading {
|
||||||
|
@apply w-20 h-20 bg-gray-300 rounded-full animate-spin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
#ai-bot-root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: -100px;
|
||||||
|
z-index: 9999;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
50
packages/resources/src/main.ts
Normal file
50
packages/resources/src/main.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { page, app } from './app.ts';
|
||||||
|
import { basename } from './modules/basename.ts';
|
||||||
|
import './pages/main.tsx';
|
||||||
|
|
||||||
|
export const render = ({ renderRoot }) => {
|
||||||
|
renderRoot.innerHTML = `
|
||||||
|
<h1>Hello, World!</h1>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
console.log('basename', basename, page, app);
|
||||||
|
|
||||||
|
if (page) {
|
||||||
|
page.addPage('/', 'home');
|
||||||
|
page.subscribe('home', () => {
|
||||||
|
render({
|
||||||
|
renderRoot: document.getElementById('ai-root'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
page.addPage('', 'index');
|
||||||
|
page.subscribe('index', () => {
|
||||||
|
const root = document.getElementById('ai-root') as HTMLElement;
|
||||||
|
root.innerHTML = `
|
||||||
|
<h1>Hello, World!</h1>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app) {
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'app-template',
|
||||||
|
key: 'render',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
let { renderRoot } = ctx.query;
|
||||||
|
if (!renderRoot) {
|
||||||
|
ctx.throw(404, 'renderRoot is required');
|
||||||
|
}
|
||||||
|
if (typeof renderRoot === 'string') {
|
||||||
|
renderRoot = document.querySelector(renderRoot);
|
||||||
|
}
|
||||||
|
if (!renderRoot) {
|
||||||
|
ctx.throw(404, 'renderRoot not found');
|
||||||
|
}
|
||||||
|
render({
|
||||||
|
renderRoot,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
}
|
2
packages/resources/src/modules/basename.ts
Normal file
2
packages/resources/src/modules/basename.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
export const basename = DEV_SERVER ? '/' : BASE_NAME;
|
3
packages/resources/src/modules/query.ts
Normal file
3
packages/resources/src/modules/query.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { QueryClient } from '@kevisual/query';
|
||||||
|
|
||||||
|
export const query = new QueryClient();
|
39
packages/resources/src/pages/App.tsx
Normal file
39
packages/resources/src/pages/App.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { theme } from '@kevisual/center-components/theme';
|
||||||
|
import { ThemeProvider } from '@mui/material/styles';
|
||||||
|
import { Left } from './layout/Left';
|
||||||
|
import { Main } from './main/index';
|
||||||
|
import { ToastContainer } from 'react-toastify';
|
||||||
|
import { useSettingsStore } from './store/settings';
|
||||||
|
import { CircularProgress } from '@mui/material';
|
||||||
|
import { useResourceStore } from './store/resource';
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
const { init, mounted, settings } = useSettingsStore();
|
||||||
|
const { setPrefix, init: initResource } = useResourceStore();
|
||||||
|
useEffect(() => {
|
||||||
|
init();
|
||||||
|
initResource();
|
||||||
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
if (settings.prefix && mounted) {
|
||||||
|
setPrefix(settings.prefix);
|
||||||
|
}
|
||||||
|
}, [mounted, settings.prefix]);
|
||||||
|
if (!mounted) {
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<Left>
|
||||||
|
<Main />
|
||||||
|
</Left>
|
||||||
|
<ToastContainer />
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
};
|
92
packages/resources/src/pages/file/FileIcon.tsx
Normal file
92
packages/resources/src/pages/file/FileIcon.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { FileText, Image, File, Video, Sheet, FileArchive, FolderClosedIcon } from 'lucide-react';
|
||||||
|
import { SVGProps } from 'react';
|
||||||
|
|
||||||
|
export const PDFIcon = (props: SVGProps<SVGSVGElement>) => {
|
||||||
|
return (
|
||||||
|
<svg viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='1656' id='mx_n_1742047110347' width='32' height='32' {...props}>
|
||||||
|
<path
|
||||||
|
d='M645.071238 97.52381L828.952381 281.258667V414.47619h48.761905v390.095239h-48.761905v48.761904a73.142857 73.142857 0 0 1-73.142857 73.142857H268.190476a73.142857 73.142857 0 0 1-73.142857-73.142857v-48.761904H146.285714V414.47619h48.761905V170.666667a73.142857 73.142857 0 0 1 73.142857-73.142857h376.880762zM755.809524 804.571429H268.190476v48.761904h487.619048v-48.761904zM368.274286 520.94781H299.154286v174.445714h43.885714v-53.101714h10.971429a177.493333 177.493333 0 0 0 31.597714-2.852572 103.619048 103.619048 0 0 0 16.237714-4.827428c4.534857-2.048 8.777143-4.461714 12.726857-7.241143 7.168-5.412571 12.507429-12.141714 16.018286-20.187429 3.364571-8.484571 5.046857-17.334857 5.046857-26.550857a72.338286 72.338286 0 0 0-4.388571-23.698286 56.441905 56.441905 0 0 0-13.604572-19.968 62.025143 62.025143 0 0 0-23.917714-12.726857 116.784762 116.784762 0 0 0-25.453714-3.291428z m140.434285 0h-61.44v174.445714h61.44c6.875429-0.146286 13.750857-0.731429 20.626286-1.755429 6.144-1.024 12.141714-2.56 17.993143-4.608 5.266286-2.194286 10.313143-4.754286 15.140571-7.68 4.388571-3.218286 8.411429-6.875429 12.068572-10.971428 3.510857-4.388571 6.582857-9.069714 9.216-14.043429a110.689524 110.689524 0 0 0 6.144-16.896c2.194286-10.24 3.364571-20.626286 3.510857-31.158857a176.37181 176.37181 0 0 0-1.755429-21.284571 113.249524 113.249524 0 0 0-4.608-18.432 90.599619 90.599619 0 0 0-7.68-15.579429 78.214095 78.214095 0 0 0-10.532571-12.507429 77.994667 77.994667 0 0 0-13.604571-9.435428 93.42781 93.42781 0 0 0-16.457143-6.363429 139.702857 139.702857 0 0 0-30.061715-3.730285z m213.504 0h-116.736v174.445714h43.885715v-65.828572h63.414857v-34.011428h-63.414857v-39.497143h72.850285v-35.108571z m-206.482285 35.108571c4.973714 0.146286 9.654857 1.536 14.043428 4.169143 4.534857 3.072 8.265143 7.021714 11.190857 11.849143 3.218286 5.851429 5.485714 11.995429 6.802286 18.432 1.024 5.997714 1.609143 11.995429 1.755429 17.993143-0.146286 6.144-0.731429 12.214857-1.755429 18.212571a62.22019 62.22019 0 0 1-6.802286 18.212571c-2.925714 4.681143-6.656 8.557714-11.190857 11.629715a28.038095 28.038095 0 0 1-14.043428 3.730285h-24.576v-104.228571h24.576z m-151.625143 0c3.949714 0.146286 7.826286 0.877714 11.629714 2.194286 3.364571 1.316571 6.363429 3.218286 8.996572 5.705143 4.681143 5.12 7.021714 11.117714 7.021714 17.993142 0 7.314286-2.706286 13.385143-8.118857 18.212572a28.525714 28.525714 0 0 1-9.874286 5.485714 47.88419 47.88419 0 0 1-12.068571 1.536h-18.651429v-51.126857h21.065143z m217.307428-385.414095L268.190476 170.666667v243.809523h487.619048v-49.956571h-174.372572L581.412571 170.666667z m73.142858 39.740952v80.993524h81.042285l-81.042285-80.993524z'
|
||||||
|
fill='currentColor'></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FilePowerpoint = (props: SVGProps<SVGSVGElement>) => {
|
||||||
|
return (
|
||||||
|
<svg viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='3922' width='32' height='32' {...props}>
|
||||||
|
<path
|
||||||
|
d='M424 476c-4.4 0-8 3.6-8 8v276c0 4.4 3.6 8 8 8h32.5c4.4 0 8-3.6 8-8v-95.5h63.3c59.4 0 96.2-38.9 96.2-94.1 0-54.5-36.3-94.3-96-94.3H424z m150.6 94.3c0 43.4-26.5 54.3-71.2 54.3h-38.9V516.2h56.2c33.8 0 53.9 19.7 53.9 54.1z'
|
||||||
|
fill='currentColor'></path>
|
||||||
|
<path
|
||||||
|
d='M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326z m1.8 562H232V136h302v216c0 23.2 18.8 42 42 42h216v494z'
|
||||||
|
fill='currentColor'></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const getExtension = (name: string) => {
|
||||||
|
return name.split('.').pop();
|
||||||
|
};
|
||||||
|
export const getIcon = (name: string) => {
|
||||||
|
if (!name) {
|
||||||
|
return <FolderClosedIcon />;
|
||||||
|
}
|
||||||
|
const extension = getExtension(name);
|
||||||
|
switch (extension) {
|
||||||
|
case 'pdf':
|
||||||
|
return <PDFIcon className='w-6 h-6' />;
|
||||||
|
case 'jpg':
|
||||||
|
case 'jpeg':
|
||||||
|
case 'gif':
|
||||||
|
case 'png':
|
||||||
|
return <Image />;
|
||||||
|
case 'mp3':
|
||||||
|
case 'wav':
|
||||||
|
case 'ogg':
|
||||||
|
case 'm4a':
|
||||||
|
case 'aac':
|
||||||
|
case 'flac':
|
||||||
|
case 'wma':
|
||||||
|
case 'mp4':
|
||||||
|
return <Video />;
|
||||||
|
case 'doc':
|
||||||
|
case 'docx':
|
||||||
|
return <FileText />;
|
||||||
|
case 'ppt':
|
||||||
|
case 'pptx':
|
||||||
|
return <FilePowerpoint className='w-6 h-6' />;
|
||||||
|
case 'xls':
|
||||||
|
case 'xlsx':
|
||||||
|
return <Sheet />;
|
||||||
|
case 'zip':
|
||||||
|
case 'rar':
|
||||||
|
case '7z':
|
||||||
|
case 'tar':
|
||||||
|
case 'gz':
|
||||||
|
case 'bz2':
|
||||||
|
return <FileArchive />;
|
||||||
|
case 'txt':
|
||||||
|
case 'md':
|
||||||
|
case 'csv':
|
||||||
|
case 'json':
|
||||||
|
case 'xml':
|
||||||
|
case 'yaml':
|
||||||
|
case 'yml':
|
||||||
|
case 'toml':
|
||||||
|
case 'ini':
|
||||||
|
case 'conf':
|
||||||
|
case 'cfg':
|
||||||
|
case 'config':
|
||||||
|
case 'props':
|
||||||
|
case 'properties':
|
||||||
|
case 'log':
|
||||||
|
case 'sh':
|
||||||
|
case 'bash':
|
||||||
|
case 'zsh':
|
||||||
|
case 'fish':
|
||||||
|
case 'bat':
|
||||||
|
case 'cmd':
|
||||||
|
return <FileText />;
|
||||||
|
default:
|
||||||
|
return <File />;
|
||||||
|
}
|
||||||
|
};
|
50
packages/resources/src/pages/file/index.tsx
Normal file
50
packages/resources/src/pages/file/index.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useResourceStore } from '../store/resource';
|
||||||
|
import { useSettingsStore } from '../store/settings';
|
||||||
|
import { Box, Button, Card, CardContent, Typography, ButtonGroup, useTheme } from '@mui/material';
|
||||||
|
import { FileText, Image, File, Table, Grid } from 'lucide-react';
|
||||||
|
import { getIcon } from './FileIcon';
|
||||||
|
import { FileTable } from './list/FileTable';
|
||||||
|
import { FileCard } from './list/FileCard';
|
||||||
|
export const FileApp = () => {
|
||||||
|
const { list, getList, prefix, setListType, listType } = useResourceStore();
|
||||||
|
const { settings } = useSettingsStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getList();
|
||||||
|
}, []);
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<Box sx={{ padding: 2, backgroundColor: 'white', borderRadius: 2, marginTop: 4, boxShadow: '0 0 10px 0 rgba(0, 0, 0, 0.1)' }}>
|
||||||
|
<div className='flex items-center gap-3 mb-8'>
|
||||||
|
<FileText className='w-8 h-8 text-amber-600' />
|
||||||
|
<Typography
|
||||||
|
variant='h1'
|
||||||
|
className='text-amber-900'
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: '1.3rem', sm: '2rem' },
|
||||||
|
fontWeight: 'bold',
|
||||||
|
}}>
|
||||||
|
Resources
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<Box className='flex items-center gap-2 mb-4'>
|
||||||
|
<Typography variant='h5' sx={{ fontWeight: 'bold', color: theme.palette.primary.main }}>
|
||||||
|
<span className='mr-2' style={{ color: theme.palette.secondary.main }}>
|
||||||
|
Prefix:
|
||||||
|
</span>
|
||||||
|
{prefix}
|
||||||
|
</Typography>
|
||||||
|
<ButtonGroup className='ml-auto' variant='contained' color='primary' sx={{ color: 'white' }}>
|
||||||
|
<Button variant={listType === 'table' ? 'contained' : 'outlined'} onClick={() => setListType('table')}>
|
||||||
|
<Table />
|
||||||
|
</Button>
|
||||||
|
<Button variant={listType === 'card' ? 'contained' : 'outlined'} onClick={() => setListType('card')}>
|
||||||
|
<Grid />
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</Box>
|
||||||
|
<div>{listType === 'card' ? <FileCard /> : <FileTable />}</div>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
27
packages/resources/src/pages/file/list/FileCard.tsx
Normal file
27
packages/resources/src/pages/file/list/FileCard.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { useResourceStore } from '@/pages/store/resource';
|
||||||
|
import { Card, CardContent, Typography } from '@mui/material';
|
||||||
|
import { getIcon } from '../FileIcon';
|
||||||
|
|
||||||
|
export const FileCard = () => {
|
||||||
|
const { list, prefix } = useResourceStore();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{list.map((resource) => (
|
||||||
|
<Card key={resource.etag} style={{ margin: '10px' }}>
|
||||||
|
<CardContent>
|
||||||
|
<Typography
|
||||||
|
variant='h5'
|
||||||
|
component='div'
|
||||||
|
// className='flex items-center gap-2'
|
||||||
|
>
|
||||||
|
{getIcon(resource.name)}
|
||||||
|
{resource.name ? resource.name.replace(prefix, '') : resource.prefix?.replace(prefix, '')}
|
||||||
|
</Typography>
|
||||||
|
{resource.lastModified && <Typography color='text.secondary'>Last Modified: {resource.lastModified}</Typography>}
|
||||||
|
{resource.size > 0 && <Typography color='text.secondary'>Size: {resource.size} bytes</Typography>}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
52
packages/resources/src/pages/file/list/FileTable.tsx
Normal file
52
packages/resources/src/pages/file/list/FileTable.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { useResourceStore } from '@/pages/store/resource';
|
||||||
|
import { Button, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from '@mui/material';
|
||||||
|
import prettyBytes from 'pretty-bytes';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { getIcon } from '../FileIcon';
|
||||||
|
|
||||||
|
export const FileTable = () => {
|
||||||
|
const { list, prefix, download } = useResourceStore();
|
||||||
|
return (
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Name</TableCell>
|
||||||
|
<TableCell sx={{ minWidth: 100 }}>Size</TableCell>
|
||||||
|
<TableCell sx={{ minWidth: 100 }}>Last Modified</TableCell>
|
||||||
|
<TableCell sx={{ minWidth: 100 }}>Actions</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{list.map((row) => (
|
||||||
|
<TableRow key={row.name}>
|
||||||
|
<TableCell>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
{getIcon(row.name)}
|
||||||
|
{row.name ? row.name.replace(prefix, '') : row.prefix?.replace?.(prefix, '')}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{row.size ? prettyBytes(row.size) : ''}</TableCell>
|
||||||
|
<TableCell>{row.lastModified ? dayjs(row.lastModified).format('YYYY-MM-DD HH:mm:ss') : ''}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{!row.prefix ? (
|
||||||
|
<Button
|
||||||
|
variant='contained'
|
||||||
|
color='primary'
|
||||||
|
onClick={() => download(row)}
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
}}>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
);
|
||||||
|
};
|
112
packages/resources/src/pages/layout/Left.tsx
Normal file
112
packages/resources/src/pages/layout/Left.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import React, { Fragment, useEffect, useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
AppBar,
|
||||||
|
Box,
|
||||||
|
CssBaseline,
|
||||||
|
Divider,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemText,
|
||||||
|
Toolbar,
|
||||||
|
Typography,
|
||||||
|
Button,
|
||||||
|
useTheme,
|
||||||
|
ListItemIcon,
|
||||||
|
Tooltip,
|
||||||
|
Container,
|
||||||
|
} from '@mui/material';
|
||||||
|
import { ActiveMenu, useLayoutStore } from '../store/layout';
|
||||||
|
import { activeMenuList } from '../modules/MenuList';
|
||||||
|
import { amber } from '@mui/material/colors';
|
||||||
|
import { lighten, rgbToHex } from '@mui/material/styles';
|
||||||
|
import { SquareMenu } from 'lucide-react';
|
||||||
|
|
||||||
|
const drawerWidth = 240;
|
||||||
|
|
||||||
|
export type LeftProps = {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
};
|
||||||
|
export const Left = ({ children }: LeftProps) => {
|
||||||
|
const { setActiveMenu, init, showLabel, setShowLabel } = useLayoutStore();
|
||||||
|
const theme = useTheme();
|
||||||
|
// console.log(theme.palette.primary.main, rgbToHex(lighten(theme.palette.primary.main, 0.4)));
|
||||||
|
// console.log(theme.palette.divider);
|
||||||
|
useEffect(() => {
|
||||||
|
init();
|
||||||
|
}, []);
|
||||||
|
let list = useMemo(() => {
|
||||||
|
const _activeMenuList = [
|
||||||
|
{
|
||||||
|
label: '资源管理器',
|
||||||
|
value: 'menu',
|
||||||
|
icon: <SquareMenu />,
|
||||||
|
hasDivider: true,
|
||||||
|
},
|
||||||
|
...activeMenuList,
|
||||||
|
];
|
||||||
|
return _activeMenuList;
|
||||||
|
}, [activeMenuList]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', backgroundColor: amber[50] }}>
|
||||||
|
<Box component='nav' sx={{ flexShrink: { sm: 0 } }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flexShrink: 0,
|
||||||
|
'& .MuiDrawer-paper': { boxSizing: 'border-box' },
|
||||||
|
}}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
textAlign: 'center',
|
||||||
|
height: '100vh',
|
||||||
|
borderRight: 1,
|
||||||
|
borderColor: theme.palette.divider,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}>
|
||||||
|
<List sx={{ flexGrow: 1 }} key={list.length}>
|
||||||
|
{list.map((item) => (
|
||||||
|
<Fragment key={item.value}>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton
|
||||||
|
sx={{
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
width: showLabel ? '240px' : '56px',
|
||||||
|
minHeight: '48px',
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (item.value === 'menu') {
|
||||||
|
// 打开资源管理器
|
||||||
|
setShowLabel(!showLabel);
|
||||||
|
} else {
|
||||||
|
setActiveMenu(item.value as ActiveMenu);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<ListItemIcon sx={{ color: 'inherit' }}>
|
||||||
|
{!showLabel ? (
|
||||||
|
<Tooltip placement='right' title={item.label}>
|
||||||
|
{item.icon}
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
item.icon
|
||||||
|
)}
|
||||||
|
</ListItemIcon>
|
||||||
|
{showLabel && <ListItemText primary={item.label} />}
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
{item.hasDivider && <Divider />}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Container sx={{ flexGrow: 1 }}>{children}</Container>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
4
packages/resources/src/pages/main.tsx
Normal file
4
packages/resources/src/pages/main.tsx
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { App } from './App.tsx';
|
||||||
|
|
||||||
|
createRoot(document.getElementById('ai-root')!).render(<App />);
|
22
packages/resources/src/pages/main/index.tsx
Normal file
22
packages/resources/src/pages/main/index.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ActiveMenu, useLayoutStore } from '../store/layout';
|
||||||
|
import { Upload } from '../upload';
|
||||||
|
import { Settings } from '../settings';
|
||||||
|
import { FileApp } from '../file';
|
||||||
|
import { Statistic } from '../statistic';
|
||||||
|
|
||||||
|
export const Main = () => {
|
||||||
|
const { activeMenu } = useLayoutStore();
|
||||||
|
if (activeMenu === ActiveMenu.Upload) {
|
||||||
|
return <Upload />;
|
||||||
|
}
|
||||||
|
if (activeMenu === ActiveMenu.Setting) {
|
||||||
|
return <Settings />;
|
||||||
|
}
|
||||||
|
if (activeMenu === ActiveMenu.Resource) {
|
||||||
|
return <FileApp />;
|
||||||
|
}
|
||||||
|
if (activeMenu === ActiveMenu.Statistic) {
|
||||||
|
return <Statistic />;
|
||||||
|
}
|
||||||
|
return <div>{activeMenu}</div>;
|
||||||
|
};
|
21
packages/resources/src/pages/message/ToastLogin.tsx
Normal file
21
packages/resources/src/pages/message/ToastLogin.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
// Custom message component
|
||||||
|
const LoginMessage = () => {
|
||||||
|
const handleClick = () => {
|
||||||
|
const currentUrl = window.location.href;
|
||||||
|
const redirect = encodeURIComponent(currentUrl);
|
||||||
|
window.location.href = '/user/login?redirect=' + redirect;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='msg-container' onClick={handleClick} style={{ cursor: 'pointer' }}>
|
||||||
|
<p className='msg-title'>Please login</p>
|
||||||
|
<p className='msg-description'>Click here to go to the login page.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toastLogin = () => {
|
||||||
|
toast.info(<LoginMessage />);
|
||||||
|
};
|
5
packages/resources/src/pages/message/toastify.ts
Normal file
5
packages/resources/src/pages/message/toastify.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
export const toastify = (message: string, type: 'success' | 'error' | 'warning' | 'info') => {
|
||||||
|
toast(message, { type });
|
||||||
|
};
|
30
packages/resources/src/pages/modules/MenuList.tsx
Normal file
30
packages/resources/src/pages/modules/MenuList.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { ActiveMenu } from '../store/layout';
|
||||||
|
import { UploadIcon, FileText, Settings, BarChart } from 'lucide-react';
|
||||||
|
|
||||||
|
export const activeMenuList: {
|
||||||
|
label: string;
|
||||||
|
value: ActiveMenu;
|
||||||
|
icon: any;
|
||||||
|
hasDivider?: boolean;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
label: '上传',
|
||||||
|
value: ActiveMenu.Upload,
|
||||||
|
icon: <UploadIcon />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '资源管理',
|
||||||
|
value: ActiveMenu.Resource,
|
||||||
|
icon: <FileText />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '设置',
|
||||||
|
value: ActiveMenu.Setting,
|
||||||
|
icon: <Settings />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '统计',
|
||||||
|
value: ActiveMenu.Statistic,
|
||||||
|
icon: <BarChart />,
|
||||||
|
},
|
||||||
|
];
|
104
packages/resources/src/pages/settings/index.tsx
Normal file
104
packages/resources/src/pages/settings/index.tsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { useSettingsStore, Settings as SettingsType } from '@/pages/store/settings';
|
||||||
|
import { Box, Typography, TextField, useTheme, Button } from '@mui/material';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Settings as SettingsIcon } from 'lucide-react';
|
||||||
|
export const FormText = ({
|
||||||
|
label,
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
disabled,
|
||||||
|
focused,
|
||||||
|
}: {
|
||||||
|
label?: string;
|
||||||
|
name?: string;
|
||||||
|
value?: string;
|
||||||
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
focused?: boolean;
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
label={label}
|
||||||
|
variant='outlined'
|
||||||
|
id={label}
|
||||||
|
name={name || label}
|
||||||
|
value={value || ''}
|
||||||
|
onChange={onChange}
|
||||||
|
fullWidth
|
||||||
|
disabled={disabled}
|
||||||
|
focused={focused}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const Settings = () => {
|
||||||
|
const { settings, updateSettings } = useSettingsStore();
|
||||||
|
const [config, setConfig] = useState<SettingsType>({});
|
||||||
|
useEffect(() => {
|
||||||
|
setConfig(settings);
|
||||||
|
}, [settings]);
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
let { name, value } = e.target;
|
||||||
|
console.log(name, value, e.target);
|
||||||
|
name = name.toLowerCase();
|
||||||
|
setConfig({ ...config, [name]: value });
|
||||||
|
};
|
||||||
|
const theme = useTheme();
|
||||||
|
const handleSave = async () => {
|
||||||
|
updateSettings(config);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
padding: 2,
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: 2,
|
||||||
|
boxShadow: '0 0 10px 0 rgba(0, 0, 0, 0.1)',
|
||||||
|
width: '80%',
|
||||||
|
// maxWidth: '600px',
|
||||||
|
margin: '0 auto',
|
||||||
|
marginTop: 4,
|
||||||
|
[theme.breakpoints.down('sm')]: {
|
||||||
|
width: '90%',
|
||||||
|
},
|
||||||
|
}}>
|
||||||
|
<div className='flex items-center gap-3 mb-8'>
|
||||||
|
<SettingsIcon className='w-8 h-8 text-amber-600' />
|
||||||
|
<Typography
|
||||||
|
variant='h1'
|
||||||
|
className='text-amber-900'
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: '1.3rem', sm: '2rem' },
|
||||||
|
fontWeight: 'bold',
|
||||||
|
}}>
|
||||||
|
Upload Settings
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<form>
|
||||||
|
<Box mb={2}>
|
||||||
|
<FormText label='Key' value={config.key} onChange={handleChange} focused={true} />
|
||||||
|
</Box>
|
||||||
|
<Box mb={2}>
|
||||||
|
<FormText label='Version' value={config.version} onChange={handleChange} disabled={true} />
|
||||||
|
</Box>
|
||||||
|
<Box mb={2}>
|
||||||
|
<FormText label='Username' value={config.username} onChange={handleChange} disabled={true} />
|
||||||
|
</Box>
|
||||||
|
<Box mb={2}>
|
||||||
|
<FormText label='Prefix' value={config.prefix} onChange={handleChange} disabled={true} />
|
||||||
|
</Box>
|
||||||
|
<Box mb={2}>
|
||||||
|
<Button
|
||||||
|
variant='contained'
|
||||||
|
sx={{
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
onClick={handleSave}>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
32
packages/resources/src/pages/statistic/index.tsx
Normal file
32
packages/resources/src/pages/statistic/index.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useStatisticStore } from '../store/statistic';
|
||||||
|
import { Typography, Box, Card, CardContent } from '@mui/material';
|
||||||
|
import { BarChart } from 'lucide-react';
|
||||||
|
|
||||||
|
export const Statistic = () => {
|
||||||
|
const { statistic, getStatistic } = useStatisticStore();
|
||||||
|
useEffect(() => {
|
||||||
|
getStatistic();
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<Box sx={{ padding: 2, backgroundColor: 'white', borderRadius: 2, marginTop: 4, boxShadow: '0 0 10px 0 rgba(0, 0, 0, 0.1)' }}>
|
||||||
|
<div className='flex items-center gap-3 mb-8'>
|
||||||
|
<BarChart className='w-8 h-8 text-amber-600' />
|
||||||
|
<Typography
|
||||||
|
variant='h1'
|
||||||
|
className='text-amber-900'
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: '1.3rem', sm: '2rem' },
|
||||||
|
fontWeight: 'bold',
|
||||||
|
}}>
|
||||||
|
Statistic
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
<div>allFileSize: {parseFloat(statistic.sizeMb + '').toFixed(2)} MB</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
61
packages/resources/src/pages/store/layout.ts
Normal file
61
packages/resources/src/pages/store/layout.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
export enum ActiveMenu {
|
||||||
|
Upload = 'upload',
|
||||||
|
Resource = 'resource',
|
||||||
|
Setting = 'setting',
|
||||||
|
Statistic = 'statistic',
|
||||||
|
}
|
||||||
|
export const activeMenuList = [
|
||||||
|
{
|
||||||
|
label: '上传',
|
||||||
|
value: ActiveMenu.Upload,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '资源管理',
|
||||||
|
value: ActiveMenu.Resource,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '设置',
|
||||||
|
value: ActiveMenu.Setting,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '统计',
|
||||||
|
value: ActiveMenu.Statistic,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
interface LayoutStore {
|
||||||
|
layout: 'left' | 'right';
|
||||||
|
setLayout: (layout: 'left' | 'right') => void;
|
||||||
|
/**
|
||||||
|
* 当前激活的菜单,默认是upload
|
||||||
|
*/
|
||||||
|
activeMenu: ActiveMenu;
|
||||||
|
setActiveMenu: (activeMenu: ActiveMenu) => void;
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (open: boolean) => void;
|
||||||
|
showLabel: boolean;
|
||||||
|
setShowLabel: (showLabel: boolean) => void;
|
||||||
|
init: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLayoutStore = create<LayoutStore>((set) => ({
|
||||||
|
layout: 'left',
|
||||||
|
setLayout: (layout) => set({ layout }),
|
||||||
|
activeMenu: ActiveMenu.Upload,
|
||||||
|
setActiveMenu: (activeMenu) => set({ activeMenu }),
|
||||||
|
open: false,
|
||||||
|
setOpen: (open) => set({ open }),
|
||||||
|
showLabel: true,
|
||||||
|
setShowLabel: (showLabel) => {
|
||||||
|
localStorage.setItem('showLabel', showLabel.toString());
|
||||||
|
set({ showLabel });
|
||||||
|
},
|
||||||
|
init: () => {
|
||||||
|
const showLabel = localStorage.getItem('showLabel');
|
||||||
|
if (showLabel) {
|
||||||
|
set({ showLabel: showLabel === 'true' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
79
packages/resources/src/pages/store/resource.ts
Normal file
79
packages/resources/src/pages/store/resource.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { query } from '@/modules/query';
|
||||||
|
import { sortBy } from 'lodash-es';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
export type Resource = {
|
||||||
|
name: string;
|
||||||
|
lastModified: string;
|
||||||
|
etag: string;
|
||||||
|
metaData: Record<string, string>;
|
||||||
|
size: number;
|
||||||
|
prefix?: string;
|
||||||
|
};
|
||||||
|
interface ResourceStore {
|
||||||
|
resources: Resource[];
|
||||||
|
loading: boolean;
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
list: Resource[];
|
||||||
|
setList: (list: Resource[]) => void;
|
||||||
|
prefix: string;
|
||||||
|
setPrefix: (prefix: string) => void;
|
||||||
|
getList: () => Promise<void>;
|
||||||
|
download: (resource: Resource) => void;
|
||||||
|
listType: 'table' | 'card';
|
||||||
|
setListType: (listType: 'table' | 'card') => void;
|
||||||
|
init: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useResourceStore = create<ResourceStore>((set, get) => ({
|
||||||
|
resources: [],
|
||||||
|
loading: false,
|
||||||
|
setLoading: (loading: boolean) => set({ loading }),
|
||||||
|
list: [],
|
||||||
|
setList: (list: any[]) => set({ list }),
|
||||||
|
prefix: '',
|
||||||
|
setPrefix: (prefix: string) => set({ prefix }),
|
||||||
|
getList: async () => {
|
||||||
|
set({ loading: true });
|
||||||
|
const { prefix, getList } = get();
|
||||||
|
if (!prefix) {
|
||||||
|
setTimeout(() => {
|
||||||
|
getList();
|
||||||
|
}, 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'file',
|
||||||
|
key: 'list',
|
||||||
|
data: {
|
||||||
|
prefix,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
set({ loading: false });
|
||||||
|
console.log(res);
|
||||||
|
if (res.code === 200) {
|
||||||
|
const list = res.data;
|
||||||
|
const sortedList = sortBy(list, [(item) => !item.prefix]);
|
||||||
|
set({ list: sortedList });
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || 'Request failed');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
download: (resource: Resource) => {
|
||||||
|
const { prefix } = get();
|
||||||
|
const url = `${prefix}/${resource.name}`;
|
||||||
|
window.open(url, '_blank');
|
||||||
|
},
|
||||||
|
listType: 'table',
|
||||||
|
setListType: (listType: 'table' | 'card') => {
|
||||||
|
localStorage.setItem('listType', listType);
|
||||||
|
set({ listType });
|
||||||
|
},
|
||||||
|
init: () => {
|
||||||
|
const listType = localStorage.getItem('listType');
|
||||||
|
if (listType) {
|
||||||
|
set({ listType: listType as 'table' | 'card' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
65
packages/resources/src/pages/store/settings.ts
Normal file
65
packages/resources/src/pages/store/settings.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { query } from '@/modules/query';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
export type Settings = {
|
||||||
|
key?: string;
|
||||||
|
version?: string;
|
||||||
|
username?: string;
|
||||||
|
prefix?: string;
|
||||||
|
};
|
||||||
|
interface SettingsStore {
|
||||||
|
settings: Settings;
|
||||||
|
setSettings: (settings: Settings) => void;
|
||||||
|
querySettings: () => Promise<void>;
|
||||||
|
mounted: boolean;
|
||||||
|
setMounted: (mounted: boolean) => void;
|
||||||
|
init: () => Promise<void>;
|
||||||
|
updateSettings: (settings: Settings) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||||
|
settings: {},
|
||||||
|
setSettings: (settings) => set({ settings }),
|
||||||
|
mounted: false,
|
||||||
|
setMounted: (mounted) => set({ mounted }),
|
||||||
|
querySettings: async () => {
|
||||||
|
const settings = get().settings;
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'config',
|
||||||
|
key: 'getUploadConfig',
|
||||||
|
});
|
||||||
|
console.log(res);
|
||||||
|
if (res.code === 200) {
|
||||||
|
const config = res.data;
|
||||||
|
localStorage.setItem('upload-config', JSON.stringify(config));
|
||||||
|
set({ settings: config });
|
||||||
|
if (JSON.stringify(settings) !== JSON.stringify(config)) {
|
||||||
|
set({ mounted: false });
|
||||||
|
set({ mounted: true });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error(res.message || '获取配置失败');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateSettings: async (settings) => {
|
||||||
|
console.log('setinng', settings);
|
||||||
|
// const res = await query.post({
|
||||||
|
// path: 'config',
|
||||||
|
// key: 'updateUploadConfig',
|
||||||
|
// data: settings,
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
init: async () => {
|
||||||
|
const cacheConfig = localStorage.getItem('upload-config');
|
||||||
|
if (cacheConfig) {
|
||||||
|
try {
|
||||||
|
set({ settings: JSON.parse(cacheConfig), mounted: true });
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('配置文件损坏');
|
||||||
|
localStorage.removeItem('upload-config');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get().querySettings();
|
||||||
|
},
|
||||||
|
}));
|
31
packages/resources/src/pages/store/statistic.ts
Normal file
31
packages/resources/src/pages/store/statistic.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { query } from '@/modules/query';
|
||||||
|
|
||||||
|
type Statistic = {
|
||||||
|
list: any[];
|
||||||
|
total: number;
|
||||||
|
size: number;
|
||||||
|
sizeMb: number;
|
||||||
|
};
|
||||||
|
export interface StatisticStore {
|
||||||
|
statistic: Statistic;
|
||||||
|
setStatistic: (statistic: Statistic) => void;
|
||||||
|
getStatistic: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStatisticStore = create<StatisticStore>((set) => ({
|
||||||
|
statistic: {
|
||||||
|
list: [],
|
||||||
|
total: 0,
|
||||||
|
size: 0,
|
||||||
|
sizeMb: 0,
|
||||||
|
},
|
||||||
|
setStatistic: (statistic: Statistic) => set({ statistic }),
|
||||||
|
getStatistic: async () => {
|
||||||
|
const res = await query.post({
|
||||||
|
path: 'file',
|
||||||
|
key: 'me-all-file-stat',
|
||||||
|
});
|
||||||
|
set({ statistic: res.data });
|
||||||
|
},
|
||||||
|
}));
|
71
packages/resources/src/pages/upload/index.tsx
Normal file
71
packages/resources/src/pages/upload/index.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { Box, useTheme, Container, Typography } from '@mui/material';
|
||||||
|
import { useDropzone } from 'react-dropzone';
|
||||||
|
import { uploadFiles } from './utils/upload';
|
||||||
|
import { FileText, CloudUpload as UploadIcon } from 'lucide-react';
|
||||||
|
import { uploadFileChunked } from './utils/upload-chunk';
|
||||||
|
|
||||||
|
export const Upload = () => {
|
||||||
|
const onDrop = async (acceptedFiles) => {
|
||||||
|
console.log(acceptedFiles);
|
||||||
|
// Handle the files here
|
||||||
|
// const res = await uploadFiles(acceptedFiles, {});
|
||||||
|
if (acceptedFiles.length > 1) {
|
||||||
|
const res = await uploadFiles(acceptedFiles, {});
|
||||||
|
console.log('uploadFiles res', res);
|
||||||
|
} else if (acceptedFiles.length === 1) {
|
||||||
|
const res = await uploadFileChunked(acceptedFiles[0], {});
|
||||||
|
console.log('uploadFiles res', res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { getRootProps, getInputProps } = useDropzone({ onDrop });
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
padding: { xs: '16px', sm: '32px' },
|
||||||
|
borderRadius: '8px',
|
||||||
|
margin: { xs: '16px', sm: '32px' },
|
||||||
|
boxShadow: '0 0 10px 0 rgba(0, 0, 0, 0.1)',
|
||||||
|
}}>
|
||||||
|
<div className='flex items-center gap-3 mb-8'>
|
||||||
|
<FileText className='w-8 h-8 text-amber-600' />
|
||||||
|
<Typography
|
||||||
|
variant='h1'
|
||||||
|
className='text-amber-900'
|
||||||
|
sx={{
|
||||||
|
fontSize: { xs: '1.3rem', sm: '2rem' },
|
||||||
|
fontWeight: 'bold',
|
||||||
|
}}>
|
||||||
|
Resources Upload
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
<Box
|
||||||
|
{...getRootProps()}
|
||||||
|
sx={{
|
||||||
|
border: '2px dashed',
|
||||||
|
borderColor: theme.palette.secondary.main,
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: { xs: '16px', sm: '32px' },
|
||||||
|
margin: { xs: '16px', sm: '32px' },
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '16px',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: theme.palette.primary.main,
|
||||||
|
},
|
||||||
|
transition: 'border-color 200ms',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}>
|
||||||
|
<UploadIcon style={{ width: '48px', height: '48px', color: theme.palette.secondary.main }} />
|
||||||
|
<label style={{ cursor: 'pointer' }}>
|
||||||
|
<input type='file' style={{ display: 'none' }} {...getInputProps()} />
|
||||||
|
<Box sx={{ color: theme.palette.secondary.main, '&:hover': { color: theme.palette.primary.main } }}>Click to upload or drag and drop</Box>
|
||||||
|
</label>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
88
packages/resources/src/pages/upload/utils/upload-chunk.ts
Normal file
88
packages/resources/src/pages/upload/utils/upload-chunk.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import NProgress from 'nprogress';
|
||||||
|
import 'nprogress/nprogress.css';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import { toastLogin } from '@/pages/message/ToastLogin';
|
||||||
|
|
||||||
|
type ConvertOpts = {
|
||||||
|
appKey?: string;
|
||||||
|
version?: string;
|
||||||
|
username?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (!token) {
|
||||||
|
toastLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskId = nanoid();
|
||||||
|
const filename = file.name;
|
||||||
|
const load = toast.loading(`${filename} 上传中...`);
|
||||||
|
NProgress.start();
|
||||||
|
const eventSource = new EventSource('http://49.232.155.236:11015/api/s1/events?taskId=' + taskId);
|
||||||
|
// 监听服务器推送的进度更新
|
||||||
|
eventSource.onmessage = function (event) {
|
||||||
|
console.log('Progress update:', event.data);
|
||||||
|
const parseIfJson = (data: string) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const receivedData = parseIfJson(event.data);
|
||||||
|
if (typeof receivedData === 'string') return;
|
||||||
|
const progress = receivedData.progress;
|
||||||
|
const progressFixed = progress.toFixed(2);
|
||||||
|
console.log('progress', progress, progressFixed);
|
||||||
|
toast.update(load, { render: `${filename} \n上传中...${progressFixed}%`, isLoading: true, autoClose: false });
|
||||||
|
if (progress) {
|
||||||
|
NProgress.set(progress);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
eventSource.onerror = function (event) {
|
||||||
|
console.log('eventSource.onerror', event);
|
||||||
|
reject(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const chunkSize = 1 * 1024 * 1024; // 1MB
|
||||||
|
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||||
|
|
||||||
|
for (let currentChunk = 0; currentChunk < totalChunks; currentChunk++) {
|
||||||
|
const start = currentChunk * chunkSize;
|
||||||
|
const end = Math.min(start + chunkSize, file.size);
|
||||||
|
const chunk = file.slice(start, end);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', chunk, file.name);
|
||||||
|
formData.append('chunkIndex', currentChunk.toString());
|
||||||
|
formData.append('totalChunks', totalChunks.toString());
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/s1/resources/upload/chunk?taskId=' + taskId, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'task-id': taskId,
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
}).then((response) => response.json());
|
||||||
|
|
||||||
|
console.log(`Chunk ${currentChunk + 1}/${totalChunks} uploaded`, res);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error uploading chunk', error);
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('/api/s1/events/close?taskId=' + taskId);
|
||||||
|
eventSource.close();
|
||||||
|
NProgress.done();
|
||||||
|
toast.dismiss(load);
|
||||||
|
resolve({ message: 'All chunks uploaded successfully' });
|
||||||
|
});
|
||||||
|
};
|
73
packages/resources/src/pages/upload/utils/upload.ts
Normal file
73
packages/resources/src/pages/upload/utils/upload.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import NProgress from 'nprogress';
|
||||||
|
import 'nprogress/nprogress.css';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import { toastLogin } from '@/pages/message/ToastLogin';
|
||||||
|
|
||||||
|
type ConvertOpts = {
|
||||||
|
appKey?: string;
|
||||||
|
version?: string;
|
||||||
|
username?: string;
|
||||||
|
};
|
||||||
|
export const uploadFiles = async (files: File[], opts: ConvertOpts) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
formData.append('file', files[i], files[i].name);
|
||||||
|
}
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (!token) {
|
||||||
|
toastLogin();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const taskId = nanoid();
|
||||||
|
// 49.232.155.236:11015
|
||||||
|
// const eventSource = new EventSource('https://kevisual.silkyai.cn/api/s1/events?taskId=' + taskId);
|
||||||
|
// const eventSource = new EventSource('/api/s1/events?taskId=' + taskId);
|
||||||
|
const eventSource = new EventSource('http://49.232.155.236:11015/api/s1/events?taskId=' + taskId);
|
||||||
|
const load = toast.loading('上传中...');
|
||||||
|
NProgress.start();
|
||||||
|
eventSource.onopen = async function (event) {
|
||||||
|
console.log('eventSource.onopen', event);
|
||||||
|
const res = await fetch('/api/s1/resources/upload?taskId=' + taskId, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'task-id': taskId,
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
}).then((response) => response.json());
|
||||||
|
|
||||||
|
console.log('upload success', res);
|
||||||
|
fetch('/api/s1/events/close?taskId=' + taskId);
|
||||||
|
eventSource.close();
|
||||||
|
NProgress.done();
|
||||||
|
toast.dismiss(load);
|
||||||
|
resolve(res);
|
||||||
|
};
|
||||||
|
// 监听服务器推送的进度更新
|
||||||
|
eventSource.onmessage = function (event) {
|
||||||
|
console.log('Progress update:', event.data);
|
||||||
|
const parseIfJson = (data: string) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const receivedData = parseIfJson(event.data);
|
||||||
|
if (typeof receivedData === 'string') return;
|
||||||
|
const progress = receivedData.progress;
|
||||||
|
console.log('progress', progress);
|
||||||
|
toast.update(load, { render: `上传中...${progress}%`, isLoading: true, autoClose: false });
|
||||||
|
if (progress) {
|
||||||
|
NProgress.set(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
eventSource.onerror = function (event) {
|
||||||
|
console.log('eventSource.onerror', event);
|
||||||
|
reject(event);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
39
packages/resources/tsconfig.json
Normal file
39
packages/resources/tsconfig.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": [
|
||||||
|
"ES2020",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"baseUrl": "./",
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types",
|
||||||
|
"node_modules/@kevisual/types",
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src",
|
||||||
|
]
|
||||||
|
}
|
80
packages/resources/vite.config.mjs
Normal file
80
packages/resources/vite.config.mjs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import path from 'path';
|
||||||
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
|
import basicSsl from '@vitejs/plugin-basic-ssl';
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
const plugins = [basicSsl()];
|
||||||
|
// const plugins = [];
|
||||||
|
plugins.push(tailwindcss());
|
||||||
|
let proxy = {};
|
||||||
|
if (true) {
|
||||||
|
proxy = {
|
||||||
|
'/api': {
|
||||||
|
target: 'https://kevisual.silkyai.cn',
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||||
|
},
|
||||||
|
'/api/router': {
|
||||||
|
target: 'wss://kevisual.silkyai.cn',
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
rewriteWsOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||||
|
},
|
||||||
|
'/user/login': {
|
||||||
|
target: 'https://kevisual.silkyai.cn',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/user/, '/user'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), ...plugins],
|
||||||
|
|
||||||
|
css: {
|
||||||
|
postcss: {},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
DEV_SERVER: JSON.stringify(process.env.NODE_ENV === 'development'),
|
||||||
|
},
|
||||||
|
base: isDev ? '/' : '/root/resources/',
|
||||||
|
server: {
|
||||||
|
port: 6022,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
proxy: {
|
||||||
|
'/system/lib': {
|
||||||
|
target: 'https://kevisual.xiongxiao.me',
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:4005',
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||||
|
},
|
||||||
|
'/api/router': {
|
||||||
|
target: 'ws://localhost:4005',
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
rewriteWsOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||||
|
},
|
||||||
|
'/api/s1/events': {
|
||||||
|
target: 'https://kevisual.silkyai.cn',
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||||
|
},
|
||||||
|
...proxy,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
690
pnpm-lock.yaml
generated
690
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
packages:
|
||||||
|
- 'packages/*'
|
Loading…
x
Reference in New Issue
Block a user