feat: add app and config

This commit is contained in:
2025-03-23 02:59:41 +08:00
parent 063837350b
commit 1d97ff88b9
24 changed files with 351 additions and 176 deletions

View File

@@ -85,6 +85,7 @@ export const App = () => {
</Router>
</div>
</AntProvider>
<div id='for-drawer'></div>
<div id='for-modal'></div>
<ToastContainer />
</CustomThemeProvider>

View File

@@ -3,13 +3,13 @@ import { useLayoutStore } from './store';
import clsx from 'clsx';
import { Menu, MenuItem, Tooltip } from '@mui/material';
import { Button } from '@mui/material';
import { IconButton } from '@kevisual/center-components/button/index.tsx';
import { message } from '@/modules/message';
import { DashboardOutlined, HomeOutlined, LogoutOutlined, SmileOutlined } from '@ant-design/icons';
import SmileOutlined from '@ant-design/icons/SmileOutlined';
import SwitcherOutlined from '@ant-design/icons/SwitcherOutlined';
import { useMemo } from 'react';
import { query } from '../query';
import { query, queryLogin } from '../query';
import { useNewNavigate } from '../navicate';
import { Users, X } from 'lucide-react';
import { LogOut, Map, SquareUser, Users, X } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import React from 'react';
@@ -27,17 +27,17 @@ export const LayoutUser = () => {
const meun = [
{
title: t('Your profile'),
icon: <HomeOutlined />,
icon: <SquareUser size={16} />,
link: '/user/profile',
},
{
title: t('Your orgs'),
icon: <DashboardOutlined />,
icon: <SwitcherOutlined />,
link: '/org/edit/list',
},
{
title: t('Site Map'),
icon: <HomeOutlined />,
icon: <Map size={16} />,
link: '/map',
},
];
@@ -110,12 +110,17 @@ export const LayoutUser = () => {
</div>
<div
className='flex items-center p-4 hover:bg-secondary hover:text-white cursor-pointer'
onClick={() => {
query.removeToken();
window.open('/user/login', '_self');
onClick={async () => {
const res = await queryLogin.logout();
// console.log(res);
if (res.success) {
window.open('/user/login', '_self');
} else {
message.error(res.message || 'Logout failed');
}
}}>
<div className='mr-4'>
<LogoutOutlined />
<LogOut size={16} />
</div>
<div>{t('Login Out')}</div>
</div>

View File

@@ -3,8 +3,13 @@ import { useLayoutStore } from './store';
import clsx from 'clsx';
import { Button } from '@mui/material';
import { message } from '@/modules/message';
import { AppstoreOutlined, CodeOutlined, FolderOutlined, HomeOutlined, SmileOutlined, SwitcherOutlined } from '@ant-design/icons';
import { X } from 'lucide-react';
import HomeOutlined from '@ant-design/icons/HomeOutlined';
import AppstoreOutlined from '@ant-design/icons/AppstoreOutlined';
import FolderOutlined from '@ant-design/icons/FolderOutlined';
import CodeOutlined from '@ant-design/icons/CodeOutlined';
import SwitcherOutlined from '@ant-design/icons/SwitcherOutlined';
import SmileOutlined from '@ant-design/icons/SmileOutlined';
import { X, Settings } from 'lucide-react';
import { useNewNavigate } from '../navicate';
import { useTranslation } from 'react-i18next';
@@ -36,6 +41,7 @@ export const LayoutMenu = () => {
icon: <SwitcherOutlined />,
link: '/org/edit/list',
},
{ title: t('Config'), icon: <Settings size={16} />, link: '/config/edit/list' },
{
title: t('About'),
icon: <SmileOutlined />,
@@ -72,7 +78,7 @@ export const LayoutMenu = () => {
}
setOpen(false);
}}>
<div className='w-6 h-6'>{item.icon}</div>
<div className='w-6 h-6 flex items-center justify-center'>{item.icon}</div>
<div>{item.title}</div>
</div>
);

View File

@@ -14,6 +14,8 @@ import i18n from 'i18next';
import { IconButton } from '@kevisual/center-components/button/index.tsx';
import { Languages } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { toast } from 'react-toastify';
type LayoutMainProps = {
title?: React.ReactNode;
@@ -64,10 +66,11 @@ export const LayoutMain = (props: LayoutMainProps) => {
const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
toast.success(t('Language changed to') + ' ' + t(lng));
handleClose();
};
const currentLanguage = i18n.language;
const { t } = useTranslation();
return (
<div className='flex w-full h-full flex-col relative'>
<LayoutMenu />
@@ -89,10 +92,27 @@ export const LayoutMain = (props: LayoutMainProps) => {
<div>
<Tooltip title={currentLanguage === 'en' ? 'English' : 'Chinese'}>
<IconButton onClick={handleClick} variant='contained'>
<Languages />
<Languages size={16} />
</IconButton>
</Tooltip>
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleClose}>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: -2,
horizontal: 52,
}}
sx={{
'& .MuiButtonBase-root.Mui-selected': {
backgroundColor: 'primary.main',
color: 'white',
},
}}>
<MenuItem selected={currentLanguage === 'en'} onClick={() => changeLanguage('en')}>
English
</MenuItem>
@@ -103,13 +123,13 @@ export const LayoutMain = (props: LayoutMainProps) => {
</div>
{menuStore.me?.type === 'org' && (
<div>
<Tooltip title='Switch To User'>
<Button
<Tooltip title={t('Switch To User')}>
<IconButton
onClick={() => {
menuStore.switchOrg('', 'user');
}}>
<SwapOutlined />
</Button>
</IconButton>
</Tooltip>
</div>
)}

View File

@@ -33,15 +33,6 @@ export const request = query.post;
export const ws = query.qws?.ws;
// 当连接成功时
// query.qws.ws.onopen = () => {
// console.log('Connected to WebSocket server');
// };
// // 处理 WebSocket 关闭
// query.qws.ws.onclose = () => {
// console.log('Disconnected from WebSocket server');
// };
query.qws.listenConnect(() => {
console.log('Connected to WebSocket server');
});

View File

@@ -167,7 +167,7 @@ const ShareModal = () => {
onClose={() => {
containerStore.setShowEdit(false);
}}>
<DialogTitle>{iText.share.title}</DialogTitle>
<DialogTitle>{t('app.share')}</DialogTitle>
<DialogContent>
<div className='flex flex-col gap-2 w-[400px] '>
<PermissionManager
@@ -178,6 +178,12 @@ const ShareModal = () => {
/>
<FormControlLabel
label={t('app.runtime')}
labelPlacement='top'
sx={{
'& .MuiFormControlLabel-label': {
width: '100%',
},
}}
control={
<Select
multiple

View File

@@ -1,11 +1,14 @@
import { useEffect, useState } from 'react';
import { JSX, useEffect, useState } from 'react';
import { useConfigStore } from '../store/config';
import { CardBlank } from '@/components/card';
import { Button, ButtonGroup, Divider, Drawer, Tab, Tabs, Tooltip } from '@mui/material';
import { Edit, Plus, Save, Trash, X } from 'lucide-react';
import { Button, ButtonGroup, Divider, Drawer, FormControlLabel, Tab, Tabs, Tooltip, useTheme } from '@mui/material';
import { Edit, Plus, Save, Share, Trash, X } from 'lucide-react';
import ShareAltOutlined from '@ant-design/icons/ShareAltOutlined';
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
import { useForm, Controller } from 'react-hook-form';
import { TextField } from '@mui/material';
import { useController } from 'react-hook-form';
import { TextField, TextFieldLabel } from '@kevisual/center-components/input/TextField.tsx';
import { useTranslation } from 'react-i18next';
import { IconButton } from '@kevisual/center-components/button/index.tsx';
import { useShallow } from 'zustand/shallow';
@@ -13,7 +16,8 @@ import { load, dump } from 'js-yaml';
import CodeEditor from '@uiw/react-textarea-code-editor';
import { toast } from 'react-toastify';
import { isEmpty, pick } from 'lodash-es';
import { usePermissionModal } from '@kevisual/resources/index.ts';
import React from 'react';
type DataYamlEditProps = {
onSave: (data: any) => Promise<void>;
type?: 'yaml' | 'json';
@@ -42,7 +46,6 @@ export const DataYamlEdit = ({ onSave, type }: DataYamlEditProps) => {
}
}
}, [formData]);
console.log(formData);
const handleSave = () => {
let data: any = {};
try {
@@ -109,14 +112,16 @@ export const DrawerEdit = () => {
await updateData({ ...values, id: formData.id }, { refresh: true });
};
const onSubmit = (data) => {
console.log('Form Data:', data);
const pickValue = pick(data, ['title', 'key', 'description']);
onSave(pickValue);
};
const theme = useTheme();
const defaultProps = theme.components?.MuiTextField?.defaultProps;
return (
<Drawer
open={showEdit}
anchor='right'
container={document.getElementById('for-drawer')}
slotProps={{
paper: {
sx: {
@@ -139,22 +144,21 @@ export const DrawerEdit = () => {
<Tab label='JSON Config' value='json' />
</Tabs>
{tab === 'base' && (
<form onSubmit={handleSubmit(onSubmit)} className='w-full p-2'>
<Controller
name='title'
control={control}
render={({ field }) => <TextField {...field} label='Title' variant='outlined' fullWidth margin='normal' />}
/>
<form onSubmit={handleSubmit(onSubmit)} className='w-full p-2 flex flex-col gap-6'>
<Controller control={control} name='title' render={({ field }) => <TextField {...field} label='Title' />} />
<Controller
name='key'
control={control}
render={({ field }) => <TextField {...field} label='Key' variant='outlined' fullWidth margin='normal' />}
/>
<Controller
name='description'
control={control}
render={({ field }) => <TextField {...field} label='Description' variant='outlined' fullWidth margin='normal' multiline rows={4} />}
render={({ field }) => (
<TextField
{...field}
label={
<TextFieldLabel label='Key' tips='Key is the unique identifier for the configuration. if set and id is none will change data by key;' />
}
/>
)}
/>
<Controller name='description' control={control} render={({ field }) => <TextField {...field} label='Description' multiline rows={4} />} />
<Button type='submit' variant='contained' color='primary'>
{t('Submit')}
</Button>
@@ -174,14 +178,36 @@ export const DrawerEdit = () => {
</Drawer>
);
};
export const MyController = React.forwardRef(
({ control, name, Component, componentProps }: { control: any; name: string; Component: (props: any) => JSX.Element; componentProps: any }, ref) => {
const theme = useTheme();
const defaultProps = theme.components?.MuiTextField?.defaultProps;
console.log(defaultProps, 'defaultProps');
const { field } = useController({ control, name });
return <Component {...field} {...componentProps} ref={ref} />;
},
);
export const List = () => {
const { list, getConfig, setShowEdit, setFormData, deleteConfig } = useConfigStore();
const { list, getConfig, setShowEdit, setFormData, deleteConfig, updateData, formData } = useConfigStore();
const [modal, contextHolder] = useModal();
useEffect(() => {
getConfig();
}, []);
const { setOpen, contextHolder: contextHolderPermission } = usePermissionModal({
onSave: async (values) => {
const permission = values;
console.log(permission, formData.id);
if (permission && formData.id) {
const res = await updateData({ data: { permission }, id: formData.id }, { refresh: true });
console.log(res);
return res.code === 200;
}
await new Promise((resolve) => setTimeout(resolve, 1000));
return true;
},
});
console.log(list);
const { t } = useTranslation();
return (
<div className='w-full h-full flex bg-gray-100'>
<div className='h-full bg-white'>
@@ -191,17 +217,26 @@ export const List = () => {
setShowEdit(true);
setFormData({});
}}>
<Plus />
<Plus size={16} />
</IconButton>
</div>
</div>
<div className=' grow p-4'>
<div className=' grow p-4 '>
<div className='w-full h-full bg-white rounded-lg p-2 scrollbar '>
<div className='flex flex-wrap gap-2'>
<div className='flex flex-wrap gap-2 '>
{list.map((item) => (
<div className='card w-[300px]' key={item.id}>
<div className='card w-[300px] ' key={item.id}>
<div className='card-title flex font-bold justify-between'>{item.title}</div>
<div className='card-content'>{item.description}</div>
<div className='card-content'>
<div className='flex gap-2'>
<div>{t('Description')}:</div>
<div>{item.description}</div>
</div>
<div className='flex gap-2'>
<div>{t('Key')}:</div>
<div>{item.key}</div>
</div>
</div>
<div className='card-footer flex justify-end'>
<ButtonGroup variant='contained'>
<Button
@@ -211,6 +246,15 @@ export const List = () => {
}}>
<Edit />
</Button>
<Tooltip title={t('Permission')}>
<Button
onClick={() => {
setFormData(item);
setOpen(true, item.data?.permission);
}}>
<ShareAltOutlined style={{ fontSize: 20 }} />
</Button>
</Tooltip>
<Button
onClick={() => {
modal.confirm({
@@ -233,6 +277,7 @@ export const List = () => {
</div>
<DrawerEdit />
{contextHolder}
{contextHolderPermission}
</div>
);
};

View File

@@ -3,7 +3,7 @@ import { query } from '@/modules/query';
import { toast } from 'react-toastify';
import { QueryConfig } from '@kevisual/query-config';
export const queryConfig = new QueryConfig({ query });
export const queryConfig = new QueryConfig({ query: query as any });
interface ConfigStore {
list: any[];

View File

@@ -83,7 +83,7 @@ const FormModal = () => {
control={control}
defaultValue={[]}
render={({ field }) => {
return <TagsInput key={'tags'} label='Tags' placeholder='添加标签' value={field.value} onChange={(value) => field.onChange(value)} />;
return <TagsInput key={'tags'} showLabel label={t('Tags')} value={field.value} onChange={(value) => field.onChange(value)} />;
}}
/>
{!isEdit && (

View File

@@ -16,7 +16,6 @@ import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
import { TextField } from '@mui/material';
import { Select } from '@kevisual/center-components/select/index.tsx';
import { useForm, Controller } from 'react-hook-form';
import EditOutlined from '@ant-design/icons/EditOutlined';
const FormModal = () => {
const { control, handleSubmit, reset } = useForm();