feat: add app and config
This commit is contained in:
parent
063837350b
commit
1d97ff88b9
@ -1 +1,6 @@
|
|||||||
export * from './theme';
|
export * from './theme';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输入组件, 使用theme的defaultProps
|
||||||
|
*/
|
||||||
|
export * from './input/TextField';
|
||||||
|
23
packages/components/src/input/TextField.tsx
Normal file
23
packages/components/src/input/TextField.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { TextField as MuiTextField, TextFieldProps, Tooltip } from '@mui/material';
|
||||||
|
import { useTheme } from '../theme';
|
||||||
|
import { HelpCircle } from 'lucide-react';
|
||||||
|
|
||||||
|
export const TextField = (props: TextFieldProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const defaultProps = theme.components?.MuiTextField?.defaultProps;
|
||||||
|
return <MuiTextField {...defaultProps} {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TextFieldLabel = ({ children, tips, label }: { children?: React.ReactNode; tips?: string; label?: any }) => {
|
||||||
|
return (
|
||||||
|
<div className='flex items-center gap-1'>
|
||||||
|
{label}
|
||||||
|
{children}
|
||||||
|
{tips && (
|
||||||
|
<Tooltip title={tips}>
|
||||||
|
<HelpCircle size={16} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -6,9 +6,10 @@ type TagsInputProps = {
|
|||||||
value: string[];
|
value: string[];
|
||||||
onChange: (value: string[]) => void;
|
onChange: (value: string[]) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
label?: string;
|
label?: any;
|
||||||
|
showLabel?: boolean;
|
||||||
};
|
};
|
||||||
export const TagsInput = ({ value, onChange, placeholder = 'Add a tag', label = 'Tags' }: TagsInputProps) => {
|
export const TagsInput = ({ value, onChange, placeholder = '', label = '', showLabel = false }: TagsInputProps) => {
|
||||||
const [tags, setTags] = useState<string[]>(value);
|
const [tags, setTags] = useState<string[]>(value);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTags(value);
|
setTags(value);
|
||||||
@ -26,6 +27,9 @@ export const TagsInput = ({ value, onChange, placeholder = 'Add a tag', label =
|
|||||||
// setTags(newValue as string[]);
|
// setTags(newValue as string[]);
|
||||||
onChange(newValue as string[]);
|
onChange(newValue as string[]);
|
||||||
}}
|
}}
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
renderTags={(value: string[], getTagProps) => {
|
renderTags={(value: string[], getTagProps) => {
|
||||||
const id = randomid();
|
const id = randomid();
|
||||||
const com = value.map((option: string, index: number) => (
|
const com = value.map((option: string, index: number) => (
|
||||||
@ -33,6 +37,13 @@ export const TagsInput = ({ value, onChange, placeholder = 'Add a tag', label =
|
|||||||
variant='outlined'
|
variant='outlined'
|
||||||
sx={{
|
sx={{
|
||||||
borderColor: 'primary.main',
|
borderColor: 'primary.main',
|
||||||
|
borderRadius: '4px',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: 'primary.main',
|
||||||
|
},
|
||||||
|
'& .MuiChip-deleteIcon': {
|
||||||
|
color: 'secondary.main',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
label={option}
|
label={option}
|
||||||
{...getTagProps({ index })}
|
{...getTagProps({ index })}
|
||||||
@ -41,7 +52,7 @@ export const TagsInput = ({ value, onChange, placeholder = 'Add a tag', label =
|
|||||||
));
|
));
|
||||||
return <Fragment key={id}>{com}</Fragment>;
|
return <Fragment key={id}>{com}</Fragment>;
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => <TextField {...params} variant='outlined' label={label} placeholder={placeholder} />}
|
renderInput={(params) => <TextField {...params} label={showLabel ? label : ''} placeholder={placeholder} />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -35,19 +35,21 @@ const generateShadows = (color: string): Shadows => {
|
|||||||
`0px 13px 17px -8px ${color}`,
|
`0px 13px 17px -8px ${color}`,
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
const primaryMain = amber[300]; // #ffc107
|
||||||
|
const secondaryMain = amber[500]; // #ffa000
|
||||||
export const themeOptions: ThemeOptions = {
|
export const themeOptions: ThemeOptions = {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
// cssVariables: true,
|
// cssVariables: true,
|
||||||
palette: {
|
palette: {
|
||||||
primary: {
|
primary: {
|
||||||
main: '#ffc107', // amber[300]
|
main: primaryMain, // amber[300]
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: '#ffa000', // amber[500]
|
main: secondaryMain, // amber[500]
|
||||||
},
|
},
|
||||||
divider: amber[200],
|
divider: amber[200],
|
||||||
common: {
|
common: {
|
||||||
white: '#ffa000',
|
white: secondaryMain,
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
primary: amber[600],
|
primary: amber[600],
|
||||||
@ -78,7 +80,7 @@ export const themeOptions: ThemeOptions = {
|
|||||||
'&.MuiButton-contained': {
|
'&.MuiButton-contained': {
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
':hover': {
|
':hover': {
|
||||||
color: amber[500],
|
color: secondaryMain,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -96,6 +98,7 @@ export const themeOptions: ThemeOptions = {
|
|||||||
MuiTextField: {
|
MuiTextField: {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
|
size: 'small',
|
||||||
slotProps: {
|
slotProps: {
|
||||||
inputLabel: {
|
inputLabel: {
|
||||||
shrink: true,
|
shrink: true,
|
||||||
@ -121,6 +124,11 @@ export const themeOptions: ThemeOptions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
MuiAutocomplete: {
|
||||||
|
defaultProps: {
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
},
|
||||||
MuiSelect: {
|
MuiSelect: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
@ -156,7 +164,10 @@ export const themeOptions: ThemeOptions = {
|
|||||||
MuiFormControlLabel: {
|
MuiFormControlLabel: {
|
||||||
defaultProps: {
|
defaultProps: {
|
||||||
labelPlacement: 'top',
|
labelPlacement: 'top',
|
||||||
sx: {
|
},
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
color: amber[600],
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
'& .MuiFormControlLabel-label': {
|
'& .MuiFormControlLabel-label': {
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
@ -170,9 +181,18 @@ export const themeOptions: ThemeOptions = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
MuiMenuItem: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: {
|
root: {
|
||||||
color: amber[600],
|
'&.Mui-selected': {
|
||||||
|
backgroundColor: amber[500],
|
||||||
|
color: '#ffffff',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: amber[600],
|
||||||
|
color: '#ffffff',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -201,3 +221,6 @@ export const CustomThemeProvider = ({ children, themeOptions: customThemeOptions
|
|||||||
const theme = createTheme(customThemeOptions || themeOptions);
|
const theme = createTheme(customThemeOptions || themeOptions);
|
||||||
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
|
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: think
|
||||||
|
export const getComponentProps = () => {};
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
export { KeyParse, keysTips } from './pages/file/modules/key-parse';
|
export { KeyParse, keysTips } from './pages/file/modules/key-parse';
|
||||||
|
export { PermissionManager } from './pages/file/modules/PermissionManager.tsx';
|
||||||
|
export { PermissionModal, usePermissionModal } from './pages/file/modules/PermissionModal.tsx';
|
||||||
export { iText } from './i-text/index.ts';
|
export { iText } from './i-text/index.ts';
|
||||||
export * from './pages/upload/app';
|
export * from './pages/upload/app';
|
@ -1,6 +1,7 @@
|
|||||||
import ReactDatePicker from 'antd/es/date-picker';
|
import ReactDatePicker from 'antd/es/date-picker';
|
||||||
import { styled, useTheme } from '@mui/material';
|
import { styled, useTheme } from '@mui/material';
|
||||||
import 'antd/es/date-picker/style/index';
|
import 'antd/es/date-picker/style/index';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
interface DatePickerProps {
|
interface DatePickerProps {
|
||||||
value?: Date | null;
|
value?: Date | null;
|
||||||
onChange?: (date: Date | null) => void;
|
onChange?: (date: Date | null) => void;
|
||||||
@ -8,13 +9,14 @@ interface DatePickerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DatePickerCom = ({ value, onChange, className }: DatePickerProps) => {
|
export const DatePickerCom = ({ value, onChange, className }: DatePickerProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const primaryColor = theme.palette.primary.main;
|
const primaryColor = theme.palette.primary.main;
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<ReactDatePicker
|
<ReactDatePicker
|
||||||
placement='topLeft'
|
placement='topLeft'
|
||||||
placeholder='请选择日期'
|
placeholder={t('Select Date')}
|
||||||
value={value}
|
value={value}
|
||||||
showNow={false}
|
showNow={false}
|
||||||
// showTime={true}
|
// showTime={true}
|
||||||
|
@ -7,10 +7,10 @@ import { toast } from 'react-toastify';
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { uniq } from 'lodash-es';
|
import { uniq } from 'lodash-es';
|
||||||
import { DatePicker } from './DatePicker';
|
import { DatePicker } from './DatePicker';
|
||||||
import { SelectPicker } from './SelectPicker';
|
|
||||||
import { DialogKey } from './DialogKey';
|
import { DialogKey } from './DialogKey';
|
||||||
import { keysTips, KeyParse } from '../../modules/key-parse';
|
import { keysTips, KeyParse } from '../../modules/key-parse';
|
||||||
import { KeyShareSelect, KeyTextField } from '../../modules/PermissionManager';
|
import { KeyShareSelect, KeyTextField } from '../../modules/PermissionManager';
|
||||||
|
import { TagsInput } from '@kevisual/center-components/select/TagsInput.tsx';
|
||||||
export const setShareKeysOperate = (value: 'public' | 'protected' | 'private') => {
|
export const setShareKeysOperate = (value: 'public' | 'protected' | 'private') => {
|
||||||
const keys = ['password', 'usernames', 'expiration-time'];
|
const keys = ['password', 'usernames', 'expiration-time'];
|
||||||
const deleteKeys = keys.map((item) => {
|
const deleteKeys = keys.map((item) => {
|
||||||
@ -187,7 +187,7 @@ export const MetaForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
<form>
|
<form className='flex flex-col gap-1 pb-4'>
|
||||||
{keys.map((key) => {
|
{keys.map((key) => {
|
||||||
let control: React.ReactNode | null = null;
|
let control: React.ReactNode | null = null;
|
||||||
if (key === 'share') {
|
if (key === 'share') {
|
||||||
@ -195,7 +195,7 @@ export const MetaForm = () => {
|
|||||||
} else if (key === 'expiration-time') {
|
} else if (key === 'expiration-time') {
|
||||||
control = <DatePicker value={formData[key]} onChange={(date) => handleFormDataChange(key, date)} />;
|
control = <DatePicker value={formData[key]} onChange={(date) => handleFormDataChange(key, date)} />;
|
||||||
} else if (key === 'usernames') {
|
} else if (key === 'usernames') {
|
||||||
control = <SelectPicker value={formData[key] || []} onChange={(value) => handleFormDataChange(key, value)} />;
|
control = <TagsInput value={formData[key] || []} showLabel={false} onChange={(value) => handleFormDataChange(key, value)} />;
|
||||||
} else {
|
} else {
|
||||||
control = <KeyTextField name={key} value={formData[key] || ''} onChange={(value) => handleFormDataChange(key, value)} />;
|
control = <KeyTextField name={key} value={formData[key] || ''} onChange={(value) => handleFormDataChange(key, value)} />;
|
||||||
}
|
}
|
||||||
@ -226,23 +226,7 @@ export const MetaForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (
|
return <FormControlLabel key={key} labelPlacement={'top'} label={<Label />} control={control} />;
|
||||||
<div key={key} className='flex flex-col gap-2'>
|
|
||||||
<FormControlLabel
|
|
||||||
key={key}
|
|
||||||
label={<Label />}
|
|
||||||
labelPlacement='top'
|
|
||||||
control={control}
|
|
||||||
sx={{
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
'& .MuiFormControlLabel-label': {
|
|
||||||
textAlign: 'left',
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</form>
|
</form>
|
||||||
<DialogKey onAdd={addMetaKey} />
|
<DialogKey onAdd={addMetaKey} />
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
import { styled } from '@mui/material';
|
|
||||||
import Select from 'antd/es/select';
|
|
||||||
import 'antd/es/select/style/index';
|
|
||||||
|
|
||||||
interface SelectPickerProps {
|
|
||||||
value: string[];
|
|
||||||
onChange: (value: string[]) => void;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const SelectPickerCom = ({ value, onChange, className }: SelectPickerProps) => {
|
|
||||||
return <Select className={className} style={{ width: '100%' }} popupClassName='hidden' showSearch={false} mode='tags' value={value} onChange={onChange} />;
|
|
||||||
};
|
|
||||||
export const SelectPicker = styled(SelectPickerCom)(({ theme }) => ({
|
|
||||||
|
|
||||||
'& .ant-select-selection-item': {
|
|
||||||
backgroundColor: 'var(--color-primary) !important',
|
|
||||||
color: 'white !important',
|
|
||||||
},
|
|
||||||
'& svg': {
|
|
||||||
color: 'white !important',
|
|
||||||
},
|
|
||||||
'& svg:hover': {
|
|
||||||
color: '#ccc !important',
|
|
||||||
},
|
|
||||||
'& .ant-select-arrow': {
|
|
||||||
// color: 'var(--color-primary) !important',
|
|
||||||
display: 'none !important',
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,12 +1,13 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { KeyParse, getTips } from './key-parse';
|
import { KeyParse, getTips } from './key-parse';
|
||||||
import { FormControlLabel, TextField, Select, MenuItem, FormGroup, Tooltip } from '@mui/material';
|
import { FormControlLabel, TextField, Select, MenuItem, Tooltip } from '@mui/material';
|
||||||
import { DatePicker } from '../draw/modules/DatePicker';
|
import { DatePicker } from '../draw/modules/DatePicker';
|
||||||
import { SelectPicker } from '../draw/modules/SelectPicker';
|
import { TagsInput } from '@kevisual/center-components/select/TagsInput.tsx';
|
||||||
import { HelpCircle } from 'lucide-react';
|
import { HelpCircle } from 'lucide-react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
export const KeyShareSelect = ({ name, value, onChange }: { name: string; value: string; onChange?: (value: string) => void }) => {
|
export const KeyShareSelect = ({ name, value, onChange }: { name: string; value: string; onChange?: (value: string) => void }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
variant='outlined'
|
variant='outlined'
|
||||||
@ -18,14 +19,14 @@ export const KeyShareSelect = ({ name, value, onChange }: { name: string; value:
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
marginBottom: '16px',
|
marginBottom: '16px',
|
||||||
}}>
|
}}>
|
||||||
<MenuItem value='public' title='公开'>
|
<MenuItem value='public' title={t('Public')}>
|
||||||
公开
|
{t('Public')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem value='protected' title='受保护'>
|
<MenuItem value='protected' title={t('Protected')}>
|
||||||
受保护
|
{t('Protected')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem value='private' title='私有'>
|
<MenuItem value='private' title={t('Private')}>
|
||||||
私有
|
{t('Private')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
@ -85,8 +86,9 @@ export const PermissionManager = ({ value, onChange, className }: PermissionMana
|
|||||||
};
|
};
|
||||||
const tips = getTips('share', i18n.language);
|
const tips = getTips('share', i18n.language);
|
||||||
return (
|
return (
|
||||||
<form className={clsx('flex flex-col gap-2', className)}>
|
<form className={clsx('flex flex-col w-full gap-2', className)}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
labelPlacement='top'
|
||||||
control={<KeyShareSelect name='share' value={formData?.share} onChange={(value) => onChangeValue('share', value)} />}
|
control={<KeyShareSelect name='share' value={formData?.share} onChange={(value) => onChangeValue('share', value)} />}
|
||||||
label={
|
label={
|
||||||
<div className='flex items-center gap-1'>
|
<div className='flex items-center gap-1'>
|
||||||
@ -99,38 +101,26 @@ export const PermissionManager = ({ value, onChange, className }: PermissionMana
|
|||||||
/>
|
/>
|
||||||
{keys.map((item: any) => {
|
{keys.map((item: any) => {
|
||||||
let control: React.ReactNode | null = null;
|
let control: React.ReactNode | null = null;
|
||||||
|
const tips = getTips(item);
|
||||||
|
|
||||||
|
const label = (
|
||||||
|
<div className='flex items-center gap-1'>
|
||||||
|
{t(item)}
|
||||||
|
{tips && (
|
||||||
|
<Tooltip title={tips}>
|
||||||
|
<HelpCircle size={16} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
if (item === 'expiration-time') {
|
if (item === 'expiration-time') {
|
||||||
control = <DatePicker value={formData[item] || ''} onChange={(date) => onChangeValue(item, date)} />;
|
control = <DatePicker className='mt-1' value={formData[item] || ''} onChange={(date) => onChangeValue(item, date)} />;
|
||||||
} else if (item === 'usernames') {
|
} else if (item === 'usernames') {
|
||||||
control = <SelectPicker value={formData[item] || []} onChange={(value) => onChangeValue(item, value)} />;
|
control = <TagsInput value={formData[item] || []} onChange={(value) => onChangeValue(item, value)} />;
|
||||||
} else {
|
} else {
|
||||||
control = <KeyTextField name={item} value={formData[item] || ''} onChange={(value) => onChangeValue(item, value)} />;
|
control = <KeyTextField name={item} value={formData[item] || ''} onChange={(value) => onChangeValue(item, value)} />;
|
||||||
}
|
}
|
||||||
const tips = getTips(item);
|
return <FormControlLabel key={item} labelPlacement='top' control={control} label={label} />;
|
||||||
return (
|
|
||||||
<FormControlLabel
|
|
||||||
labelPlacement='top'
|
|
||||||
key={item}
|
|
||||||
control={control}
|
|
||||||
label={
|
|
||||||
<div className='flex items-center gap-1'>
|
|
||||||
{item}
|
|
||||||
{tips && (
|
|
||||||
<Tooltip title={tips}>
|
|
||||||
<HelpCircle size={16} />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
sx={{
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
'& .MuiFormControlLabel-label': {
|
|
||||||
textAlign: 'left',
|
|
||||||
width: '100%',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PermissionManager } from './PermissionManager';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
type PermissionModalProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
value: Record<string, any>;
|
||||||
|
onChange: (value: Record<string, any>) => void;
|
||||||
|
onSave: () => void;
|
||||||
|
};
|
||||||
|
export const PermissionModal = ({ open, onClose, value, onChange, onSave }: PermissionModalProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onClose={onClose}>
|
||||||
|
<DialogTitle>{t('app.share')}</DialogTitle>
|
||||||
|
<DialogContent sx={{ width: '400px' }}>
|
||||||
|
<PermissionManager value={value} onChange={onChange} />
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={onClose}>{t('Cancel')}</Button>
|
||||||
|
<Button color='primary' onClick={onSave}>
|
||||||
|
{t('Submit')}
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type UsePermissionModalProps = {
|
||||||
|
/**
|
||||||
|
* 保存回调
|
||||||
|
*/
|
||||||
|
onSave: (values: Record<string, any>) => Promise<boolean>;
|
||||||
|
};
|
||||||
|
export const usePermissionModal = ({ onSave }: UsePermissionModalProps) => {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [formData, setFormData] = useState<Record<string, any>>({});
|
||||||
|
const onChange = (value: Record<string, any>) => {
|
||||||
|
setFormData(value);
|
||||||
|
};
|
||||||
|
const _onSave = async () => {
|
||||||
|
const res = await onSave(formData);
|
||||||
|
if (res) {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const contextHolder = <PermissionModal open={open} onClose={() => setOpen(false)} value={formData} onChange={onChange} onSave={_onSave} />;
|
||||||
|
return {
|
||||||
|
open,
|
||||||
|
setOpen: (open: boolean, fromData?: any) => {
|
||||||
|
setOpen(open);
|
||||||
|
if (open) {
|
||||||
|
setFormData(fromData ?? {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setFormData: (values: any) => {
|
||||||
|
setFormData({ ...formData, ...values });
|
||||||
|
},
|
||||||
|
contextHolder,
|
||||||
|
};
|
||||||
|
};
|
@ -47,7 +47,28 @@
|
|||||||
"app": {
|
"app": {
|
||||||
"domain": "Domain",
|
"domain": "Domain",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"runtime": "Can run environment"
|
"runtime": "Can run environment",
|
||||||
|
"share": "Share Config"
|
||||||
},
|
},
|
||||||
"Share": "Share"
|
"Share": "Share",
|
||||||
|
"Switch To User": "Switch To User",
|
||||||
|
"Language changed to": "Language changed to",
|
||||||
|
"en": "English",
|
||||||
|
"zh": "Chinese",
|
||||||
|
"Config": "Config",
|
||||||
|
"Tags": "Tags",
|
||||||
|
"Add a tag": "Add a tag",
|
||||||
|
"Tags Input": "Tags Input",
|
||||||
|
"Public": "Public",
|
||||||
|
"Protected": "Protected",
|
||||||
|
"Private": "Private",
|
||||||
|
"Input User Name": "Input User Name",
|
||||||
|
"Select Date": "Select Date",
|
||||||
|
"Permission": "Permission Manager",
|
||||||
|
"password": "Password",
|
||||||
|
"expiration-time": "Expiration Time",
|
||||||
|
"usernames": "Usernames",
|
||||||
|
"content-type": "Content Type",
|
||||||
|
"app-source": "App Source",
|
||||||
|
"app-version": "App Version"
|
||||||
}
|
}
|
@ -47,7 +47,28 @@
|
|||||||
"app": {
|
"app": {
|
||||||
"domain": "访问域名",
|
"domain": "访问域名",
|
||||||
"version": "版本",
|
"version": "版本",
|
||||||
"runtime": "可以运行的环境"
|
"runtime": "可以运行的环境",
|
||||||
|
"share": "共享配置"
|
||||||
},
|
},
|
||||||
"Share": "分享"
|
"Share": "分享",
|
||||||
|
"Switch To User": "切换到用户",
|
||||||
|
"Language changed to": "语言已切换到",
|
||||||
|
"en": "英语",
|
||||||
|
"zh": "中文",
|
||||||
|
"Config": "配置",
|
||||||
|
"Tags": "标签",
|
||||||
|
"Add a tag": "添加一个标签",
|
||||||
|
"Tags Input": "标签输入",
|
||||||
|
"Public": "公开",
|
||||||
|
"Protected": "受保护",
|
||||||
|
"Private": "私有",
|
||||||
|
"Input User Name": "输入用户名称",
|
||||||
|
"Select Date": "选择日期",
|
||||||
|
"Permission": "权限管理",
|
||||||
|
"password": "密码",
|
||||||
|
"expiration-time": "过期时间",
|
||||||
|
"usernames": "用户名",
|
||||||
|
"content-type": "内容类型",
|
||||||
|
"app-source": "应用来源",
|
||||||
|
"app-version": "应用版本"
|
||||||
}
|
}
|
@ -85,6 +85,7 @@ export const App = () => {
|
|||||||
</Router>
|
</Router>
|
||||||
</div>
|
</div>
|
||||||
</AntProvider>
|
</AntProvider>
|
||||||
|
<div id='for-drawer'></div>
|
||||||
<div id='for-modal'></div>
|
<div id='for-modal'></div>
|
||||||
<ToastContainer />
|
<ToastContainer />
|
||||||
</CustomThemeProvider>
|
</CustomThemeProvider>
|
||||||
|
@ -3,13 +3,13 @@ import { useLayoutStore } from './store';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Menu, MenuItem, Tooltip } from '@mui/material';
|
import { Menu, MenuItem, Tooltip } from '@mui/material';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
|
||||||
import { message } from '@/modules/message';
|
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 { useMemo } from 'react';
|
||||||
import { query } from '../query';
|
import { query, queryLogin } from '../query';
|
||||||
import { useNewNavigate } from '../navicate';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@ -27,17 +27,17 @@ export const LayoutUser = () => {
|
|||||||
const meun = [
|
const meun = [
|
||||||
{
|
{
|
||||||
title: t('Your profile'),
|
title: t('Your profile'),
|
||||||
icon: <HomeOutlined />,
|
icon: <SquareUser size={16} />,
|
||||||
link: '/user/profile',
|
link: '/user/profile',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Your orgs'),
|
title: t('Your orgs'),
|
||||||
icon: <DashboardOutlined />,
|
icon: <SwitcherOutlined />,
|
||||||
link: '/org/edit/list',
|
link: '/org/edit/list',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('Site Map'),
|
title: t('Site Map'),
|
||||||
icon: <HomeOutlined />,
|
icon: <Map size={16} />,
|
||||||
link: '/map',
|
link: '/map',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -110,12 +110,17 @@ export const LayoutUser = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className='flex items-center p-4 hover:bg-secondary hover:text-white cursor-pointer'
|
className='flex items-center p-4 hover:bg-secondary hover:text-white cursor-pointer'
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
query.removeToken();
|
const res = await queryLogin.logout();
|
||||||
window.open('/user/login', '_self');
|
// console.log(res);
|
||||||
|
if (res.success) {
|
||||||
|
window.open('/user/login', '_self');
|
||||||
|
} else {
|
||||||
|
message.error(res.message || 'Logout failed');
|
||||||
|
}
|
||||||
}}>
|
}}>
|
||||||
<div className='mr-4'>
|
<div className='mr-4'>
|
||||||
<LogoutOutlined />
|
<LogOut size={16} />
|
||||||
</div>
|
</div>
|
||||||
<div>{t('Login Out')}</div>
|
<div>{t('Login Out')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,8 +3,13 @@ import { useLayoutStore } from './store';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import { message } from '@/modules/message';
|
import { message } from '@/modules/message';
|
||||||
import { AppstoreOutlined, CodeOutlined, FolderOutlined, HomeOutlined, SmileOutlined, SwitcherOutlined } from '@ant-design/icons';
|
import HomeOutlined from '@ant-design/icons/HomeOutlined';
|
||||||
import { X } from 'lucide-react';
|
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 { useNewNavigate } from '../navicate';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -36,6 +41,7 @@ export const LayoutMenu = () => {
|
|||||||
icon: <SwitcherOutlined />,
|
icon: <SwitcherOutlined />,
|
||||||
link: '/org/edit/list',
|
link: '/org/edit/list',
|
||||||
},
|
},
|
||||||
|
{ title: t('Config'), icon: <Settings size={16} />, link: '/config/edit/list' },
|
||||||
{
|
{
|
||||||
title: t('About'),
|
title: t('About'),
|
||||||
icon: <SmileOutlined />,
|
icon: <SmileOutlined />,
|
||||||
@ -72,7 +78,7 @@ export const LayoutMenu = () => {
|
|||||||
}
|
}
|
||||||
setOpen(false);
|
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>{item.title}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -14,6 +14,8 @@ import i18n from 'i18next';
|
|||||||
|
|
||||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||||
import { Languages } from 'lucide-react';
|
import { Languages } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
type LayoutMainProps = {
|
type LayoutMainProps = {
|
||||||
title?: React.ReactNode;
|
title?: React.ReactNode;
|
||||||
@ -64,10 +66,11 @@ export const LayoutMain = (props: LayoutMainProps) => {
|
|||||||
|
|
||||||
const changeLanguage = (lng: string) => {
|
const changeLanguage = (lng: string) => {
|
||||||
i18n.changeLanguage(lng);
|
i18n.changeLanguage(lng);
|
||||||
|
toast.success(t('Language changed to') + ' ' + t(lng));
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
const currentLanguage = i18n.language;
|
const currentLanguage = i18n.language;
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className='flex w-full h-full flex-col relative'>
|
<div className='flex w-full h-full flex-col relative'>
|
||||||
<LayoutMenu />
|
<LayoutMenu />
|
||||||
@ -89,10 +92,27 @@ export const LayoutMain = (props: LayoutMainProps) => {
|
|||||||
<div>
|
<div>
|
||||||
<Tooltip title={currentLanguage === 'en' ? 'English' : 'Chinese'}>
|
<Tooltip title={currentLanguage === 'en' ? 'English' : 'Chinese'}>
|
||||||
<IconButton onClick={handleClick} variant='contained'>
|
<IconButton onClick={handleClick} variant='contained'>
|
||||||
<Languages />
|
<Languages size={16} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</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')}>
|
<MenuItem selected={currentLanguage === 'en'} onClick={() => changeLanguage('en')}>
|
||||||
English
|
English
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -103,13 +123,13 @@ export const LayoutMain = (props: LayoutMainProps) => {
|
|||||||
</div>
|
</div>
|
||||||
{menuStore.me?.type === 'org' && (
|
{menuStore.me?.type === 'org' && (
|
||||||
<div>
|
<div>
|
||||||
<Tooltip title='Switch To User'>
|
<Tooltip title={t('Switch To User')}>
|
||||||
<Button
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
menuStore.switchOrg('', 'user');
|
menuStore.switchOrg('', 'user');
|
||||||
}}>
|
}}>
|
||||||
<SwapOutlined />
|
<SwapOutlined />
|
||||||
</Button>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -33,15 +33,6 @@ export const request = query.post;
|
|||||||
|
|
||||||
export const ws = query.qws?.ws;
|
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(() => {
|
query.qws.listenConnect(() => {
|
||||||
console.log('Connected to WebSocket server');
|
console.log('Connected to WebSocket server');
|
||||||
});
|
});
|
||||||
|
@ -167,7 +167,7 @@ const ShareModal = () => {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
containerStore.setShowEdit(false);
|
containerStore.setShowEdit(false);
|
||||||
}}>
|
}}>
|
||||||
<DialogTitle>{iText.share.title}</DialogTitle>
|
<DialogTitle>{t('app.share')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div className='flex flex-col gap-2 w-[400px] '>
|
<div className='flex flex-col gap-2 w-[400px] '>
|
||||||
<PermissionManager
|
<PermissionManager
|
||||||
@ -178,6 +178,12 @@ const ShareModal = () => {
|
|||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
label={t('app.runtime')}
|
label={t('app.runtime')}
|
||||||
|
labelPlacement='top'
|
||||||
|
sx={{
|
||||||
|
'& .MuiFormControlLabel-label': {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
}}
|
||||||
control={
|
control={
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { JSX, useEffect, useState } from 'react';
|
||||||
import { useConfigStore } from '../store/config';
|
import { useConfigStore } from '../store/config';
|
||||||
import { CardBlank } from '@/components/card';
|
import { CardBlank } from '@/components/card';
|
||||||
import { Button, ButtonGroup, Divider, Drawer, Tab, Tabs, Tooltip } from '@mui/material';
|
import { Button, ButtonGroup, Divider, Drawer, FormControlLabel, Tab, Tabs, Tooltip, useTheme } from '@mui/material';
|
||||||
import { Edit, Plus, Save, Trash, X } from 'lucide-react';
|
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 { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||||
import { useForm, Controller } from 'react-hook-form';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
@ -13,7 +16,8 @@ import { load, dump } from 'js-yaml';
|
|||||||
import CodeEditor from '@uiw/react-textarea-code-editor';
|
import CodeEditor from '@uiw/react-textarea-code-editor';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { isEmpty, pick } from 'lodash-es';
|
import { isEmpty, pick } from 'lodash-es';
|
||||||
|
import { usePermissionModal } from '@kevisual/resources/index.ts';
|
||||||
|
import React from 'react';
|
||||||
type DataYamlEditProps = {
|
type DataYamlEditProps = {
|
||||||
onSave: (data: any) => Promise<void>;
|
onSave: (data: any) => Promise<void>;
|
||||||
type?: 'yaml' | 'json';
|
type?: 'yaml' | 'json';
|
||||||
@ -42,7 +46,6 @@ export const DataYamlEdit = ({ onSave, type }: DataYamlEditProps) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [formData]);
|
}, [formData]);
|
||||||
console.log(formData);
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
let data: any = {};
|
let data: any = {};
|
||||||
try {
|
try {
|
||||||
@ -109,14 +112,16 @@ export const DrawerEdit = () => {
|
|||||||
await updateData({ ...values, id: formData.id }, { refresh: true });
|
await updateData({ ...values, id: formData.id }, { refresh: true });
|
||||||
};
|
};
|
||||||
const onSubmit = (data) => {
|
const onSubmit = (data) => {
|
||||||
console.log('Form Data:', data);
|
|
||||||
const pickValue = pick(data, ['title', 'key', 'description']);
|
const pickValue = pick(data, ['title', 'key', 'description']);
|
||||||
onSave(pickValue);
|
onSave(pickValue);
|
||||||
};
|
};
|
||||||
|
const theme = useTheme();
|
||||||
|
const defaultProps = theme.components?.MuiTextField?.defaultProps;
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
open={showEdit}
|
open={showEdit}
|
||||||
anchor='right'
|
anchor='right'
|
||||||
|
container={document.getElementById('for-drawer')}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
paper: {
|
paper: {
|
||||||
sx: {
|
sx: {
|
||||||
@ -139,22 +144,21 @@ export const DrawerEdit = () => {
|
|||||||
<Tab label='JSON Config' value='json' />
|
<Tab label='JSON Config' value='json' />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{tab === 'base' && (
|
{tab === 'base' && (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} className='w-full p-2'>
|
<form onSubmit={handleSubmit(onSubmit)} className='w-full p-2 flex flex-col gap-6'>
|
||||||
<Controller
|
<Controller control={control} name='title' render={({ field }) => <TextField {...field} label='Title' />} />
|
||||||
name='title'
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => <TextField {...field} label='Title' variant='outlined' fullWidth margin='normal' />}
|
|
||||||
/>
|
|
||||||
<Controller
|
<Controller
|
||||||
name='key'
|
name='key'
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => <TextField {...field} label='Key' variant='outlined' fullWidth margin='normal' />}
|
render={({ field }) => (
|
||||||
/>
|
<TextField
|
||||||
<Controller
|
{...field}
|
||||||
name='description'
|
label={
|
||||||
control={control}
|
<TextFieldLabel label='Key' tips='Key is the unique identifier for the configuration. if set and id is none will change data by key;' />
|
||||||
render={({ field }) => <TextField {...field} label='Description' variant='outlined' fullWidth margin='normal' multiline rows={4} />}
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Controller name='description' control={control} render={({ field }) => <TextField {...field} label='Description' multiline rows={4} />} />
|
||||||
<Button type='submit' variant='contained' color='primary'>
|
<Button type='submit' variant='contained' color='primary'>
|
||||||
{t('Submit')}
|
{t('Submit')}
|
||||||
</Button>
|
</Button>
|
||||||
@ -174,14 +178,36 @@ export const DrawerEdit = () => {
|
|||||||
</Drawer>
|
</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 = () => {
|
export const List = () => {
|
||||||
const { list, getConfig, setShowEdit, setFormData, deleteConfig } = useConfigStore();
|
const { list, getConfig, setShowEdit, setFormData, deleteConfig, updateData, formData } = useConfigStore();
|
||||||
const [modal, contextHolder] = useModal();
|
const [modal, contextHolder] = useModal();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getConfig();
|
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);
|
console.log(list);
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className='w-full h-full flex bg-gray-100'>
|
<div className='w-full h-full flex bg-gray-100'>
|
||||||
<div className='h-full bg-white'>
|
<div className='h-full bg-white'>
|
||||||
@ -191,17 +217,26 @@ export const List = () => {
|
|||||||
setShowEdit(true);
|
setShowEdit(true);
|
||||||
setFormData({});
|
setFormData({});
|
||||||
}}>
|
}}>
|
||||||
<Plus />
|
<Plus size={16} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
</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='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) => (
|
{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-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'>
|
<div className='card-footer flex justify-end'>
|
||||||
<ButtonGroup variant='contained'>
|
<ButtonGroup variant='contained'>
|
||||||
<Button
|
<Button
|
||||||
@ -211,6 +246,15 @@ export const List = () => {
|
|||||||
}}>
|
}}>
|
||||||
<Edit />
|
<Edit />
|
||||||
</Button>
|
</Button>
|
||||||
|
<Tooltip title={t('Permission')}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setFormData(item);
|
||||||
|
setOpen(true, item.data?.permission);
|
||||||
|
}}>
|
||||||
|
<ShareAltOutlined style={{ fontSize: 20 }} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
@ -233,6 +277,7 @@ export const List = () => {
|
|||||||
</div>
|
</div>
|
||||||
<DrawerEdit />
|
<DrawerEdit />
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
|
{contextHolderPermission}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@ import { query } from '@/modules/query';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { QueryConfig } from '@kevisual/query-config';
|
import { QueryConfig } from '@kevisual/query-config';
|
||||||
|
|
||||||
export const queryConfig = new QueryConfig({ query });
|
export const queryConfig = new QueryConfig({ query: query as any });
|
||||||
|
|
||||||
interface ConfigStore {
|
interface ConfigStore {
|
||||||
list: any[];
|
list: any[];
|
||||||
|
@ -83,7 +83,7 @@ const FormModal = () => {
|
|||||||
control={control}
|
control={control}
|
||||||
defaultValue={[]}
|
defaultValue={[]}
|
||||||
render={({ field }) => {
|
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 && (
|
{!isEdit && (
|
||||||
|
@ -16,7 +16,6 @@ import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
|||||||
import { TextField } from '@mui/material';
|
import { TextField } from '@mui/material';
|
||||||
import { Select } from '@kevisual/center-components/select/index.tsx';
|
import { Select } from '@kevisual/center-components/select/index.tsx';
|
||||||
import { useForm, Controller } from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
|
||||||
|
|
||||||
const FormModal = () => {
|
const FormModal = () => {
|
||||||
const { control, handleSubmit, reset } = useForm();
|
const { control, handleSubmit, reset } = useForm();
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 24f091ac792342ea295496cf8ff49f6c79ab47a6
|
Subproject commit 9ba45c37c2d642e7ae0a0856a29ba3f3cd61d2c9
|
@ -32,13 +32,7 @@ if (true) {
|
|||||||
target: backend,
|
target: backend,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
ws: true,
|
ws: true,
|
||||||
rewrite: (path: any) => path.replace(/^\/api/, '/api'),
|
cookieDomainRewrite: 'localhost',
|
||||||
},
|
|
||||||
'/api/router': {
|
|
||||||
target: backendWss,
|
|
||||||
changeOrigin: true,
|
|
||||||
ws: true,
|
|
||||||
rewriteWsOrigin: true,
|
|
||||||
rewrite: (path: any) => path.replace(/^\/api/, '/api'),
|
rewrite: (path: any) => path.replace(/^\/api/, '/api'),
|
||||||
},
|
},
|
||||||
'/user/login': {
|
'/user/login': {
|
||||||
@ -81,13 +75,6 @@ export default defineConfig({
|
|||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
||||||
},
|
},
|
||||||
'/api/router': {
|
|
||||||
target: 'wss://localhost:4005',
|
|
||||||
changeOrigin: true,
|
|
||||||
ws: true,
|
|
||||||
rewriteWsOrigin: true,
|
|
||||||
rewrite: (path) => path.replace(/^\/api/, '/api'),
|
|
||||||
},
|
|
||||||
...proxy,
|
...proxy,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user