feat: 去掉antd
This commit is contained in:
parent
c206add7eb
commit
cfd263a1e7
14
package.json
14
package.json
@ -20,7 +20,7 @@
|
||||
"@kevisual/center-components": "workspace:*",
|
||||
"@kevisual/codemirror": "workspace:*",
|
||||
"@kevisual/container": "1.0.0",
|
||||
"@kevisual/query": "^0.0.8",
|
||||
"@kevisual/query": "^0.0.9",
|
||||
"@kevisual/resources": "workspace:*",
|
||||
"@kevisual/system-ui": "^0.0.3",
|
||||
"@kevisual/ui": "^0.0.2",
|
||||
@ -41,14 +41,14 @@
|
||||
"immer": "^10.1.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"marked": "^15.0.7",
|
||||
"nanoid": "^5.1.4",
|
||||
"nanoid": "^5.1.5",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"react-router": "^7.3.0",
|
||||
"react-router-dom": "^7.3.0",
|
||||
"react-router": "^7.4.0",
|
||||
"react-router-dom": "^7.4.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"vite-plugin-tsconfig-paths": "^1.4.1",
|
||||
"zustand": "^5.0.3"
|
||||
@ -61,7 +61,7 @@
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^22.13.10",
|
||||
"@types/path-browserify": "^1.0.3",
|
||||
"@types/react": "^19.0.11",
|
||||
"@types/react": "^19.0.12",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
@ -71,7 +71,7 @@
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^16.0.0",
|
||||
"lucide-react": "^0.482.0",
|
||||
"lucide-react": "^0.483.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"postcss-import": "^16.1.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
@ -80,7 +80,7 @@
|
||||
"tailwindcss": "^4.0.14",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.26.1",
|
||||
"typescript-eslint": "^8.27.0",
|
||||
"vite": "^6.2.2"
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
"@codemirror/basic-setup": "^0.20.0",
|
||||
"@codemirror/commands": "^6.8.0",
|
||||
"@codemirror/history": "^0.19.2",
|
||||
"@codemirror/lang-css": "^6.3.1",
|
||||
"@codemirror/lang-html": "^6.4.9",
|
||||
"@codemirror/lang-javascript": "^6.2.3",
|
||||
|
@ -5,10 +5,10 @@ import { html } from '@codemirror/lang-html';
|
||||
import { css } from '@codemirror/lang-css';
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import { yaml } from '@codemirror/lang-yaml';
|
||||
import { history } from '@codemirror/history';
|
||||
import { history } from '@codemirror/commands';
|
||||
import { vscodeLight } from '@uiw/codemirror-theme-vscode';
|
||||
import { formatKeymap } from './modules/keymap';
|
||||
import { Compartment, EditorState, Extension } from '@codemirror/state';
|
||||
import { Compartment, Extension } from '@codemirror/state';
|
||||
import { defaultKeymap } from '@codemirror/commands';
|
||||
import { autocompletion, Completion } from '@codemirror/autocomplete';
|
||||
import { getFileType } from './utils/get-file-type';
|
||||
@ -58,7 +58,7 @@ export class BaseEditor {
|
||||
vscodeLight,
|
||||
formatKeymap,
|
||||
keymap.of(defaultKeymap), //
|
||||
// history(),
|
||||
history(),
|
||||
];
|
||||
if (this.autoComplete?.open) {
|
||||
extensions.push(
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button } from '@mui/material';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import type { ModalFuncProps } from 'antd';
|
||||
export const Confirm = ({
|
||||
open,
|
||||
onClose,
|
||||
title,
|
||||
content,
|
||||
onConfirm,
|
||||
confirmText = '确认',
|
||||
okText = '确认',
|
||||
cancelText = '取消',
|
||||
}: {
|
||||
open: boolean;
|
||||
@ -15,11 +15,15 @@ export const Confirm = ({
|
||||
title: string;
|
||||
content: string;
|
||||
onConfirm?: () => void;
|
||||
confirmText?: string;
|
||||
okText?: string;
|
||||
cancelText?: string;
|
||||
}) => {
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} aria-labelledby='alert-dialog-title' aria-describedby='alert-dialog-description'>
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'>
|
||||
<DialogTitle id='alert-dialog-title' className='text-secondary min-w-[300px]'>
|
||||
{title}
|
||||
</DialogTitle>
|
||||
@ -31,7 +35,7 @@ export const Confirm = ({
|
||||
{cancelText || '取消'}
|
||||
</Button>
|
||||
<Button onClick={onConfirm} variant='contained' color='primary' autoFocus>
|
||||
{confirmText || '确认'}
|
||||
{okText || '确认'}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
@ -39,26 +43,48 @@ export const Confirm = ({
|
||||
};
|
||||
|
||||
type Fn = () => void;
|
||||
export const useConfirm = () => {
|
||||
export const useModal = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [title, setTitle] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
const fns = useRef<{
|
||||
onConfirm: Fn;
|
||||
onCancel: Fn;
|
||||
confirmText: string;
|
||||
okText: string;
|
||||
cancelText: string;
|
||||
}>({
|
||||
onConfirm: () => {},
|
||||
onCancel: () => {},
|
||||
confirmText: '确认',
|
||||
okText: '确认',
|
||||
cancelText: '取消',
|
||||
});
|
||||
return {
|
||||
contextHolder: (
|
||||
const modal = {
|
||||
confirm: (props: ModalFuncProps) => {
|
||||
setOpen(true);
|
||||
setTitle(props.title as string);
|
||||
setContent(props.content as string);
|
||||
fns.current.onConfirm = async () => {
|
||||
const isClose = await props.onOk?.();
|
||||
if (!isClose) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
fns.current.onCancel = async () => {
|
||||
await props.onCancel?.();
|
||||
setOpen(false);
|
||||
};
|
||||
fns.current.okText = props.okText as string;
|
||||
fns.current.cancelText = props.cancelText as string;
|
||||
},
|
||||
cancel: () => {
|
||||
setOpen(false);
|
||||
fns.current.onCancel();
|
||||
},
|
||||
};
|
||||
const contextHolder = (
|
||||
<Confirm
|
||||
open={open}
|
||||
confirmText={fns.current.confirmText}
|
||||
okText={fns.current.okText}
|
||||
cancelText={fns.current.cancelText}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
@ -68,24 +94,6 @@ export const useConfirm = () => {
|
||||
content={content}
|
||||
onConfirm={fns.current.onConfirm}
|
||||
/>
|
||||
),
|
||||
confirm: (
|
||||
title: string,
|
||||
content: string,
|
||||
opts?: {
|
||||
onConfirm: () => void;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
onCancel?: () => void;
|
||||
},
|
||||
) => {
|
||||
setOpen(true);
|
||||
setTitle(title);
|
||||
setContent(content);
|
||||
fns.current.onConfirm = opts?.onConfirm || (() => {});
|
||||
fns.current.onCancel = opts?.onCancel || (() => {});
|
||||
fns.current.confirmText = opts?.confirmText || '确认';
|
||||
fns.current.cancelText = opts?.cancelText || '取消';
|
||||
},
|
||||
};
|
||||
);
|
||||
return [modal, contextHolder] as [typeof modal, React.ReactNode];
|
||||
};
|
||||
|
47
packages/components/src/select/TagsInput.tsx
Normal file
47
packages/components/src/select/TagsInput.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import Autocomplete from '@mui/material/Autocomplete';
|
||||
import { TextField, Chip } from '@mui/material';
|
||||
|
||||
type TagsInputProps = {
|
||||
value: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
};
|
||||
export const TagsInput = ({ value, onChange, placeholder = 'Add a tag', label = 'Tags' }: TagsInputProps) => {
|
||||
const [tags, setTags] = useState<string[]>(value);
|
||||
useEffect(() => {
|
||||
setTags(value);
|
||||
}, [value]);
|
||||
const randomid = () => {
|
||||
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
};
|
||||
return (
|
||||
<Autocomplete
|
||||
multiple
|
||||
freeSolo
|
||||
options={[]}
|
||||
value={tags}
|
||||
onChange={(event, newValue) => {
|
||||
// setTags(newValue as string[]);
|
||||
onChange(newValue as string[]);
|
||||
}}
|
||||
renderTags={(value: string[], getTagProps) => {
|
||||
const id = randomid();
|
||||
const com = value.map((option: string, index: number) => (
|
||||
<Chip
|
||||
variant='outlined'
|
||||
sx={{
|
||||
borderColor: 'primary.main',
|
||||
}}
|
||||
label={option}
|
||||
{...getTagProps({ index })}
|
||||
key={`${id}-${index}`}
|
||||
/>
|
||||
));
|
||||
return <Fragment key={id}>{com}</Fragment>;
|
||||
}}
|
||||
renderInput={(params) => <TextField {...params} variant='outlined' label={label} placeholder={placeholder} />}
|
||||
/>
|
||||
);
|
||||
};
|
18
packages/components/src/select/index.tsx
Normal file
18
packages/components/src/select/index.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { MenuItem, Select as MuiSelect, SelectProps as MuiSelectProps } from '@mui/material';
|
||||
|
||||
type SelectProps = {
|
||||
options?: { label: string; value: string }[];
|
||||
} & MuiSelectProps;
|
||||
|
||||
export const Select = (props: SelectProps) => {
|
||||
const { options, ...rest } = props;
|
||||
return (
|
||||
<MuiSelect {...rest}>
|
||||
{options?.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MuiSelect>
|
||||
);
|
||||
};
|
@ -58,7 +58,7 @@ export const themeOptions: ThemeOptions = {
|
||||
// paper: '#f5f5f5', // 设置纸张背景颜色
|
||||
},
|
||||
error: {
|
||||
main: red[500],
|
||||
main: red[500], // 设置错误颜色 "#f44336"
|
||||
},
|
||||
},
|
||||
shadows: generateShadows('rgba(255, 193, 7, 0.2)'),
|
||||
@ -94,6 +94,14 @@ export const themeOptions: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
MuiTextField: {
|
||||
defaultProps: {
|
||||
fullWidth: true,
|
||||
slotProps: {
|
||||
inputLabel: {
|
||||
shrink: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
'& .MuiOutlinedInput-root': {
|
||||
@ -145,6 +153,29 @@ export const themeOptions: ThemeOptions = {
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiFormControlLabel: {
|
||||
defaultProps: {
|
||||
labelPlacement: 'top',
|
||||
sx: {
|
||||
alignItems: 'flex-start',
|
||||
'& .MuiFormControlLabel-label': {
|
||||
textAlign: 'left',
|
||||
width: '100%',
|
||||
},
|
||||
'& .MuiFormControlLabel-root': {
|
||||
width: '100%',
|
||||
},
|
||||
'& .MuiInputBase-root': {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
color: amber[600],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { useResourceStore } from '@kevisual/resources/pages/store/resource';
|
||||
import { useResourceFileStore } from '@kevisual/resources/pages/store/resource-file';
|
||||
import { Box, Divider, Drawer, Tab, Tabs } from '@mui/material';
|
||||
import { Box, Button, Divider, Drawer, Tab, Tabs } from '@mui/material';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { QuickValues, QuickTabs } from './QuickTabs';
|
||||
import { Delete, Trash } from 'lucide-react';
|
||||
import { InitProvider } from '../../App';
|
||||
|
||||
export const FileDrawer = () => {
|
||||
const { prefix } = useResourceStore();
|
||||
const { resource, openDrawer, setOpenDrawer } = useResourceFileStore();
|
||||
const { prefix, getList } = useResourceStore();
|
||||
const { resource, openDrawer, setOpenDrawer, deleteFile } = useResourceFileStore();
|
||||
const [tab, setTab] = useState<string>(QuickValues[0]);
|
||||
const quickCom = useMemo(() => {
|
||||
return QuickTabs.find((item) => item.value === tab)?.component;
|
||||
@ -31,10 +33,26 @@ export const FileDrawer = () => {
|
||||
style={{
|
||||
zIndex: 1000,
|
||||
}}>
|
||||
<div className='p-4 w-[400px] max-w-[90%] overflow-hidden h-full sm:w-[600px]'>
|
||||
<div className='p-4 overflow-hidden h-full sm:w-[600px]'>
|
||||
<div style={{ height: '140px' }}>
|
||||
<h2 className='text-2xl font-bold truncate py-2 pb-6 '>
|
||||
{resource?.name ? resource.name.replace(prefix, '') : resource?.prefix?.replace(prefix, '')}
|
||||
<h2 className='text-2xl font-bold py-2 pb-6 flex '>
|
||||
<div className='grow truncate'>{resource?.name ? resource.name.replace(prefix, '') : resource?.prefix?.replace(prefix, '')}</div>
|
||||
<Button
|
||||
sx={{ ml: 2 }}
|
||||
color='primary'
|
||||
size='small'
|
||||
onClick={() => {
|
||||
if (resource) {
|
||||
deleteFile(resource, {
|
||||
onSuccess: () => {
|
||||
getList();
|
||||
setOpenDrawer(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<Trash />
|
||||
</Button>
|
||||
</h2>
|
||||
<Divider />
|
||||
<Box sx={{ borderBottom: 1, mt: 2, borderColor: 'divider' }}>
|
||||
@ -58,3 +76,11 @@ export const FileDrawer = () => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const FileDrawerApp = () => {
|
||||
return (
|
||||
<InitProvider>
|
||||
<FileDrawer />
|
||||
</InitProvider>
|
||||
);
|
||||
};
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { useResourceFileStore } from '@kevisual/resources/pages/store/resource-file';
|
||||
import { FormControlLabel, Box, TextField, Button, IconButton, ButtonGroup, Tooltip, Select, MenuItem, Typography, FormGroup } from '@mui/material';
|
||||
import { FormControlLabel, Box, ButtonGroup, Tooltip, Typography } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { Info, Plus, Save, Share, Shuffle, Trash } from 'lucide-react';
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { create } from 'zustand';
|
||||
import { uniq } from 'lodash-es';
|
||||
import { DatePicker } from './DatePicker';
|
||||
import { SelectPicker } from './SelectPicker';
|
||||
import dayjs from 'dayjs';
|
||||
import { DialogKey } from './DialogKey';
|
||||
import { keysTips, KeyParse } from '../../modules/key-parse';
|
||||
import { KeyShareSelect, KeyTextField } from '../../modules/PermissionManager';
|
||||
@ -139,7 +139,7 @@ export const MetaForm = () => {
|
||||
} else {
|
||||
_formData[key] = value;
|
||||
}
|
||||
setFormData(_formData);
|
||||
setFormData({ ..._formData });
|
||||
};
|
||||
const deleteMeta = (key: string) => {
|
||||
setKeys(keys.filter((item) => item !== key));
|
||||
@ -166,13 +166,14 @@ export const MetaForm = () => {
|
||||
<Box className='sticky top-0 z-10 pointer-events-none'>
|
||||
<div className='flex justify-end mr-20'>
|
||||
<div className=' pointer-events-auto '>
|
||||
<ButtonGroup className='bg-white' variant='contained' sx={{ color: 'white' }}>
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
sx={{
|
||||
mt: 1,
|
||||
backgroundColor: 'primary.main',
|
||||
}}>
|
||||
{btnList.map((item) => {
|
||||
const icon = (
|
||||
<IconButton color='secondary' onClick={item.onClick}>
|
||||
{item.icon}
|
||||
</IconButton>
|
||||
);
|
||||
const icon = <IconButton onClick={item.onClick}>{item.icon}</IconButton>;
|
||||
if (item.tooltip) {
|
||||
return (
|
||||
<Tooltip key={item.key} title={item.tooltip} placement='top' arrow>
|
||||
@ -212,7 +213,14 @@ export const MetaForm = () => {
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<IconButton color='error' onClick={() => deleteMeta(key)}>
|
||||
<IconButton
|
||||
variant='text'
|
||||
sx={{
|
||||
color: 'primay.main',
|
||||
}}
|
||||
color='error'
|
||||
size='small'
|
||||
onClick={() => deleteMeta(key)}>
|
||||
<Trash />
|
||||
</IconButton>
|
||||
</div>
|
||||
@ -241,5 +249,3 @@ export const MetaForm = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
@ -6,12 +6,12 @@ import { getIcon } from '../FileIcon';
|
||||
import { Download, Trash } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
import { useResourceFileStore } from '@kevisual/resources/pages/store/resource-file';
|
||||
import { useConfirm } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
|
||||
export const FileTable = () => {
|
||||
const { list, prefix, download, onOpenPrefix, deleteFile } = useResourceStore();
|
||||
const { setOpenDrawer, setPrefix } = useResourceFileStore();
|
||||
const { confirm, contextHolder } = useConfirm();
|
||||
const { list, prefix, download, onOpenPrefix, getList } = useResourceStore();
|
||||
const { setOpenDrawer, setPrefix, deleteFile } = useResourceFileStore();
|
||||
const [modal, contextHolder] = useModal();
|
||||
return (
|
||||
<>
|
||||
{contextHolder}
|
||||
@ -95,9 +95,15 @@ export const FileTable = () => {
|
||||
className='ml-2!'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
confirm('删除文件', '确定删除该文件吗?', {
|
||||
onConfirm: () => {
|
||||
deleteFile(row);
|
||||
modal.confirm({
|
||||
title: '删除文件',
|
||||
content: '确定删除该文件吗?',
|
||||
onOk: () => {
|
||||
deleteFile(row, {
|
||||
onSuccess: () => {
|
||||
getList();
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}}
|
||||
|
@ -4,6 +4,8 @@ import { FormControlLabel, TextField, Select, MenuItem, FormGroup, Tooltip } fro
|
||||
import { DatePicker } from '../draw/modules/DatePicker';
|
||||
import { SelectPicker } from '../draw/modules/SelectPicker';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export const KeyShareSelect = ({ name, value, onChange }: { name: string; value: string; onChange?: (value: string) => void }) => {
|
||||
return (
|
||||
<Select
|
||||
@ -34,8 +36,7 @@ export const KeyTextField = ({ name, value, onChange }: { name: string; value: s
|
||||
variant='outlined'
|
||||
size='small'
|
||||
name={name}
|
||||
defaultValue={value}
|
||||
// value={formData[key] || ''}
|
||||
value={value}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
sx={{
|
||||
width: '100%',
|
||||
@ -48,8 +49,10 @@ export const KeyTextField = ({ name, value, onChange }: { name: string; value: s
|
||||
type PermissionManagerProps = {
|
||||
value: Record<string, any>;
|
||||
onChange: (value: Record<string, any>) => void;
|
||||
className?: string;
|
||||
};
|
||||
export const PermissionManager = ({ value, onChange }: PermissionManagerProps) => {
|
||||
export const PermissionManager = ({ value, onChange, className }: PermissionManagerProps) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const [formData, setFormData] = useState<any>(value);
|
||||
const [keys, setKeys] = useState<any>([]);
|
||||
useEffect(() => {
|
||||
@ -80,26 +83,19 @@ export const PermissionManager = ({ value, onChange }: PermissionManagerProps) =
|
||||
onChange(KeyParse.stringify(newFormData));
|
||||
}
|
||||
};
|
||||
const tips = getTips('share', i18n.language);
|
||||
return (
|
||||
<form className='w-[400px] flex flex-col gap-2'>
|
||||
<form className={clsx('flex flex-col gap-2', className)}>
|
||||
<FormControlLabel
|
||||
labelPlacement='top'
|
||||
control={<KeyShareSelect name='share' value={formData?.share} onChange={(value) => onChangeValue('share', value)} />}
|
||||
label={
|
||||
<div className='flex items-center gap-1'>
|
||||
Share
|
||||
<Tooltip title={getTips('share')}>
|
||||
{t('Share')}
|
||||
<Tooltip title={tips}>
|
||||
<HelpCircle size={16} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
'& .MuiFormControlLabel-label': {
|
||||
textAlign: 'left',
|
||||
width: '100%',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{keys.map((item: any) => {
|
||||
let control: React.ReactNode | null = null;
|
||||
|
@ -1,6 +1,13 @@
|
||||
import dayjs from 'dayjs';
|
||||
export const getTips = (key: string) => {
|
||||
return keysTips.find((item) => item.key === key)?.tips;
|
||||
export const getTips = (key: string, lang?: string) => {
|
||||
const tip = keysTips.find((item) => item.key === key);
|
||||
if (tip) {
|
||||
if (lang === 'en') {
|
||||
return tip.enTips;
|
||||
}
|
||||
return tip.tips;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
export const keysTips = [
|
||||
{
|
||||
@ -10,26 +17,35 @@ export const keysTips = [
|
||||
2. 设置受保护需要登录后访问
|
||||
3. 设置私有只有自己可以访问。\n
|
||||
受保护可以设置密码,设置访问的用户名。切换共享状态后,需要重新设置密码和用户名。 不设置,默认是只能自己访问。`,
|
||||
enTips: `1. Set public to directly access
|
||||
2. Set protected to access after login
|
||||
3. Set private to access only yourself.
|
||||
Protected can set a password and set the username for access. After switching the shared state, you need to reset the password and username. If not set, it defaults to only being accessible to yourself.`,
|
||||
},
|
||||
{
|
||||
key: 'content-type',
|
||||
tips: `内容类型,设置文件的内容类型。默认不要修改。`,
|
||||
enTips: `Content type, set the content type of the file. Default do not modify.`,
|
||||
},
|
||||
{
|
||||
key: 'app-source',
|
||||
tips: `应用来源,上传方式。默认不要修改。`,
|
||||
enTips: `App source, upload method. Default do not modify.`,
|
||||
},
|
||||
{
|
||||
key: 'cache-control',
|
||||
tips: `缓存控制,设置文件的缓存控制。默认不要修改。`,
|
||||
enTips: `Cache control, set the cache control of the file. Default do not modify.`,
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
tips: `密码,设置文件的密码。不设置默认是所有人都可以访问。`,
|
||||
enTips: `Password, set the password of the file. If not set, it defaults to everyone can access.`,
|
||||
},
|
||||
{
|
||||
key: 'usernames',
|
||||
tips: `用户名,设置文件的用户名。不设置默认是所有人都可以访问。`,
|
||||
enTips: `Username, set the username of the file. If not set, it defaults to everyone can access.`,
|
||||
parse: (value: string) => {
|
||||
if (!value) {
|
||||
return [];
|
||||
@ -46,6 +62,7 @@ export const keysTips = [
|
||||
{
|
||||
key: 'expiration-time',
|
||||
tips: `过期时间,设置文件的过期时间。不设置默认是永久。`,
|
||||
enTips: `Expiration time, set the expiration time of the file. If not set, it defaults to permanent.`,
|
||||
parse: (value: Date) => {
|
||||
if (!value) {
|
||||
return null;
|
||||
|
@ -10,8 +10,15 @@ interface ResourceFileStore {
|
||||
setOpenDrawer: (openDrawer: boolean) => void;
|
||||
prefix: string;
|
||||
setPrefix: (prefix: string, replace?: string) => void;
|
||||
/**
|
||||
* 需要先设置prefix
|
||||
* @returns
|
||||
*/
|
||||
getStatFile: () => Promise<any>;
|
||||
updateMeta: (metadata: any) => Promise<any>;
|
||||
deleteFile: (resource: Resource, opts?: { onSuccess?: (res: any) => void }) => Promise<void>;
|
||||
once: ((data: any) => any) | null;
|
||||
setOnce: (data: any) => void;
|
||||
}
|
||||
|
||||
export const useResourceFileStore = create<ResourceFileStore>((set, get) => ({
|
||||
@ -45,10 +52,35 @@ export const useResourceFileStore = create<ResourceFileStore>((set, get) => ({
|
||||
},
|
||||
});
|
||||
if (res.code === 200) {
|
||||
// set({ resource: { ...res.data, name: resource?.name } });
|
||||
toast.success('Update metadata success');
|
||||
getStatFile();
|
||||
} else {
|
||||
toast.error(res.message || '更新元数据失败');
|
||||
}
|
||||
},
|
||||
deleteFile: async (resource: Resource, opts?: { onSuccess?: (res: any) => void }) => {
|
||||
const { once, setOnce } = get();
|
||||
const name = resource.name;
|
||||
if (!name) {
|
||||
toast.error('Resource is not a file');
|
||||
return;
|
||||
}
|
||||
const res = await query.post({
|
||||
path: 'file',
|
||||
key: 'delete',
|
||||
data: {
|
||||
prefix: name,
|
||||
},
|
||||
});
|
||||
if (res.code === 200) {
|
||||
toast.success('Delete file success');
|
||||
opts?.onSuccess?.(res);
|
||||
once?.(res);
|
||||
setOnce(null);
|
||||
} else {
|
||||
toast.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
once: null,
|
||||
setOnce: (data: any) => set({ once: data }),
|
||||
}));
|
||||
|
@ -3,30 +3,67 @@ 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 UploadButton = (props: { prefix?: string; onUpload?: (res: any) => void; hasDirectory?: boolean; uploadDirectory?: boolean }) => {
|
||||
import { filterFiles } from './utils/filter-files';
|
||||
|
||||
type UploadButtonProps = {
|
||||
/**
|
||||
* 前缀
|
||||
*/
|
||||
directory?: string;
|
||||
/**
|
||||
* 应用key
|
||||
*/
|
||||
appKey?: string;
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
username?: string;
|
||||
/**
|
||||
* 上传回调
|
||||
*/
|
||||
onUpload?: (res: any) => void;
|
||||
/**
|
||||
* 是否上传文件夹
|
||||
*/
|
||||
uploadDirectory?: boolean;
|
||||
/**
|
||||
* 是否只显示图标
|
||||
*/
|
||||
onlyIcon?: boolean;
|
||||
/**
|
||||
* 上传图标
|
||||
*/
|
||||
icon?: React.ReactNode;
|
||||
};
|
||||
/**
|
||||
* 上传按钮
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export const UploadButton = (props: UploadButtonProps) => {
|
||||
const { onlyIcon = false, icon } = props;
|
||||
const { appKey, version, username, directory } = props;
|
||||
const onDrop = async (acceptedFiles) => {
|
||||
console.log(acceptedFiles);
|
||||
acceptedFiles = filterFiles(acceptedFiles);
|
||||
if (acceptedFiles.length > 1) {
|
||||
const res = await uploadFiles(acceptedFiles, { directory: props.prefix });
|
||||
const res = await uploadFiles(acceptedFiles, { directory, appKey, version, username });
|
||||
console.log('uploadFiles res', res);
|
||||
props.onUpload?.(res);
|
||||
} else if (acceptedFiles.length === 1) {
|
||||
const res = await uploadFileChunked(acceptedFiles[0], { directory: props.prefix });
|
||||
const res = await uploadFileChunked(acceptedFiles[0], { directory, appKey, version, username });
|
||||
console.log('uploadFiles res', res);
|
||||
props.onUpload?.(res);
|
||||
}
|
||||
};
|
||||
const { getRootProps, getInputProps } = useDropzone({ onDrop });
|
||||
return (
|
||||
<Box {...getRootProps()}>
|
||||
<Button
|
||||
color='primary'
|
||||
sx={{
|
||||
minWidth: 'unset',
|
||||
padding: '2px',
|
||||
}}>
|
||||
<UploadIcon />
|
||||
</Button>
|
||||
const uploadCom = (
|
||||
<div {...getRootProps()}>
|
||||
{icon || <UploadIcon />}
|
||||
<input
|
||||
type='file'
|
||||
style={{ display: 'none' }}
|
||||
@ -35,12 +72,27 @@ export const UploadButton = (props: { prefix?: string; onUpload?: (res: any) =>
|
||||
webkitdirectory={props.uploadDirectory ? 'true' : undefined}
|
||||
mozdirectory={props.uploadDirectory ? 'true' : undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
if (onlyIcon) {
|
||||
return uploadCom;
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<Button
|
||||
color='primary'
|
||||
sx={{
|
||||
minWidth: 'unset',
|
||||
padding: '2px',
|
||||
}}>
|
||||
{uploadCom}
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export const Upload = ({ uploadDirectory = false }: { uploadDirectory?: boolean }) => {
|
||||
const onDrop = async (acceptedFiles) => {
|
||||
console.log(acceptedFiles);
|
||||
acceptedFiles = filterFiles(acceptedFiles);
|
||||
if (acceptedFiles.length > 1) {
|
||||
const res = await uploadFiles(acceptedFiles, {});
|
||||
console.log('uploadFiles res', res);
|
||||
|
@ -21,12 +21,8 @@ const getFileType = (extension: string) => {
|
||||
return 'image/gif';
|
||||
case 'svg':
|
||||
return 'image/svg+xml';
|
||||
case 'ico':
|
||||
return 'image/x-icon';
|
||||
case 'webp':
|
||||
return 'image/webp';
|
||||
case 'gif':
|
||||
return 'image/gif';
|
||||
case 'ico':
|
||||
return 'image/x-icon';
|
||||
default:
|
||||
|
23
packages/resources/src/pages/upload/utils/filter-files.ts
Normal file
23
packages/resources/src/pages/upload/utils/filter-files.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 过滤文件, 过滤 .DS_Store, node_modules, 以.开头的文件, 过滤 __开头的文件
|
||||
* @param files
|
||||
* @returns
|
||||
*/
|
||||
export const filterFiles = (files: File[]) => {
|
||||
files = files.filter((file) => {
|
||||
if (file.webkitRelativePath.startsWith('__MACOSX')) {
|
||||
return false;
|
||||
}
|
||||
// 过滤node_modules
|
||||
if (file.webkitRelativePath.includes('node_modules')) {
|
||||
return false;
|
||||
}
|
||||
// 过滤文件 .DS_Store
|
||||
if (file.name === '.DS_Store') {
|
||||
return false;
|
||||
}
|
||||
// 过滤以.开头的文件
|
||||
return !file.name.startsWith('.');
|
||||
});
|
||||
return files;
|
||||
};
|
@ -18,6 +18,7 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
console.log('uploadFileChunked token', token);
|
||||
toastLogin();
|
||||
return;
|
||||
}
|
||||
@ -33,6 +34,7 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
searchParams.set('public', 'true');
|
||||
}
|
||||
const eventSource = new EventSource('/api/s1/events?' + searchParams.toString());
|
||||
let isError = false;
|
||||
// 监听服务器推送的进度更新
|
||||
eventSource.onmessage = function (event) {
|
||||
console.log('Progress update:', event.data);
|
||||
@ -55,6 +57,7 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
};
|
||||
eventSource.onerror = function (event) {
|
||||
console.log('eventSource.onerror', event);
|
||||
isError = true;
|
||||
reject(event);
|
||||
};
|
||||
|
||||
@ -91,6 +94,15 @@ export const uploadFileChunked = async (file: File, opts: ConvertOpts) => {
|
||||
},
|
||||
}).then((response) => response.json());
|
||||
fetch('/api/s1/events/close?taskId=' + taskId);
|
||||
if (res?.code !== 200) {
|
||||
toast.error('上传失败');
|
||||
isError = true;
|
||||
NProgress.done();
|
||||
eventSource.close();
|
||||
toast.dismiss(load);
|
||||
reject(new Error(res?.message || '上传失败'));
|
||||
return;
|
||||
}
|
||||
if (isLast) {
|
||||
NProgress.done();
|
||||
eventSource.close();
|
||||
|
@ -3,7 +3,6 @@ import 'nprogress/nprogress.css';
|
||||
import { toast } from 'react-toastify';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { toastLogin } from '@kevisual/resources/pages/message/ToastLogin';
|
||||
|
||||
type ConvertOpts = {
|
||||
appKey?: string;
|
||||
version?: string;
|
||||
@ -39,6 +38,7 @@ export const uploadFiles = async (files: File[], opts: ConvertOpts) => {
|
||||
}
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
console.log('uploadFiles token', token);
|
||||
toastLogin();
|
||||
return;
|
||||
}
|
||||
|
583
pnpm-lock.yaml
generated
583
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -41,5 +41,13 @@
|
||||
"User List": "User List",
|
||||
"Switch to Org": "Switch to Org",
|
||||
"Login": "Login",
|
||||
"uploadDirectory": "Upload Directory"
|
||||
"uploadDirectory": "Upload Directory",
|
||||
"refresh": "Refresh",
|
||||
"upload": "Upload",
|
||||
"app": {
|
||||
"domain": "Domain",
|
||||
"version": "Version",
|
||||
"runtime": "Can run environment"
|
||||
},
|
||||
"Share": "Share"
|
||||
}
|
@ -41,5 +41,13 @@
|
||||
"User List": "用户列表",
|
||||
"Switch to Org": "切换组织",
|
||||
"Login": "登录",
|
||||
"uploadDirectory": "上传文件夹"
|
||||
"uploadDirectory": "上传文件夹",
|
||||
"refresh": "刷新",
|
||||
"upload": "上传",
|
||||
"app": {
|
||||
"domain": "访问域名",
|
||||
"version": "版本",
|
||||
"runtime": "可以运行的环境"
|
||||
},
|
||||
"Share": "分享"
|
||||
}
|
19
src/App.tsx
19
src/App.tsx
@ -10,19 +10,30 @@ import { Redirect } from './modules/Redirect';
|
||||
import { CustomThemeProvider } from '@kevisual/center-components/theme/index.tsx';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import 'dayjs/locale/en';
|
||||
import zhCN from 'antd/locale/zh_CN';
|
||||
import enUS from 'antd/locale/en_US';
|
||||
import ConfigProvider from 'antd/es/config-provider';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const AntProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const theme = useTheme();
|
||||
const primaryColor = theme.palette.primary.main;
|
||||
const secondaryColor = theme.palette.secondary.main;
|
||||
const { i18n } = useTranslation();
|
||||
const [locale, setLocale] = useState(zhCN);
|
||||
useEffect(() => {
|
||||
if (i18n.language === 'en') {
|
||||
setLocale(enUS);
|
||||
} else {
|
||||
setLocale(zhCN);
|
||||
}
|
||||
}, [i18n.language]);
|
||||
return (
|
||||
<ConfigProvider
|
||||
locale={zhCN}
|
||||
locale={locale}
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: primaryColor,
|
||||
@ -43,7 +54,7 @@ const AntProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
},
|
||||
Tooltip: {
|
||||
zIndexPopupBase: 2000,
|
||||
}
|
||||
},
|
||||
},
|
||||
}}>
|
||||
{children}
|
||||
|
@ -57,8 +57,9 @@ h3 {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.cm-editor {
|
||||
@apply h-full;
|
||||
}
|
||||
#for-message {
|
||||
z-index: 9999 !important;
|
||||
}
|
@ -1,3 +1,2 @@
|
||||
import { message } from '@kevisual/system-ui/dist/message';
|
||||
|
||||
export { message };
|
@ -2,8 +2,14 @@ import { useNavigation, useParams } from 'react-router';
|
||||
import { useAppVersionStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Form, Input, Modal, Tooltip } from 'antd';
|
||||
import { CloudUploadOutlined, DeleteOutlined, EditOutlined, FileOutlined, LeftOutlined, LinkOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import CloudUploadOutlined from '@ant-design/icons/CloudUploadOutlined';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import FileOutlined from '@ant-design/icons/FileOutlined';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
import LinkOutlined from '@ant-design/icons/LinkOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { Tooltip } from '@mui/material';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { FileUpload } from '../modules/FileUpload';
|
||||
import clsx from 'clsx';
|
||||
@ -13,9 +19,13 @@ import { Button } from '@mui/material';
|
||||
import { Dialog, DialogContent, DialogTitle, ButtonGroup } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { TextField } from '@mui/material';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
const FormModal = () => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const { control, handleSubmit, reset } = useForm();
|
||||
const containerStore = useAppVersionStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -26,62 +36,56 @@ const FormModal = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const open = containerStore.showEdit;
|
||||
if (open) {
|
||||
if (open) {
|
||||
const isNull = isObjectNull(containerStore.formData);
|
||||
if (isNull) {
|
||||
form.setFieldsValue({});
|
||||
} else form.setFieldsValue(containerStore.formData);
|
||||
reset({});
|
||||
} else {
|
||||
reset(containerStore.formData);
|
||||
}
|
||||
}
|
||||
}, [containerStore.showEdit]);
|
||||
|
||||
const onFinish = async (values: any) => {
|
||||
containerStore.updateData(values);
|
||||
const pickValues = pick(values, ['id', 'key', 'version']);
|
||||
containerStore.updateData(pickValues);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
containerStore.setShowEdit(false);
|
||||
form.resetFields();
|
||||
reset();
|
||||
};
|
||||
|
||||
const isEdit = containerStore.formData.id;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={isEdit ? 'Edit' : 'Add'}
|
||||
<Dialog
|
||||
open={containerStore.showEdit}
|
||||
onClose={() => containerStore.setShowEdit(false)}
|
||||
destroyOnClose
|
||||
footer={false}
|
||||
width={800}
|
||||
onCancel={onClose}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
sx={{
|
||||
'& .MuiDialog-paper': {
|
||||
width: '800px',
|
||||
},
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='key' label='key'>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item name='version' label='version'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<DialogTitle>{isEdit ? 'Edit' : 'Add'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<form className='flex flex-col gap-6' onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller name='key' control={control} defaultValue='' render={({ field }) => <TextField label='key' {...field} disabled />} />
|
||||
<Controller name='version' control={control} defaultValue='' render={({ field }) => <TextField label='version' {...field} />} />
|
||||
<div>
|
||||
<Button type='submit' variant='contained' color='primary'>
|
||||
{t('submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' onClick={onClose}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@ -105,7 +109,7 @@ export const AppVersionList = () => {
|
||||
}),
|
||||
);
|
||||
const navigate = useNewNavigate();
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
const [modal, contextHolder] = useModal();
|
||||
const [isUpload, setIsUpload] = useState(false);
|
||||
useEffect(() => {
|
||||
// fetch app version list
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useUserAppStore } from '../store';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Form, Input, Modal, Select, Switch } from 'antd';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import LinkOutlined from '@ant-design/icons/LinkOutlined';
|
||||
@ -9,26 +10,43 @@ import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import UnorderedListOutlined from '@ant-design/icons/UnorderedListOutlined';
|
||||
import CodeOutlined from '@ant-design/icons/CodeOutlined';
|
||||
import ShareAltOutlined from '@ant-design/icons/ShareAltOutlined';
|
||||
|
||||
import { FormControlLabel, Switch } from '@mui/material';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { DialogActions, Tooltip } from '@mui/material';
|
||||
import { marked } from 'marked';
|
||||
import clsx from 'clsx';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { Select } from '@kevisual/center-components/select/index.tsx';
|
||||
import { iText } from '@kevisual/resources/index.ts';
|
||||
import { PermissionManager } from '@kevisual/resources/pages/file/modules/PermissionManager.tsx';
|
||||
import { Button } from '@mui/material';
|
||||
import { message } from '@/modules/message';
|
||||
import { Dialog, DialogContent, DialogTitle, ButtonGroup } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TextField, InputAdornment } from '@mui/material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const defaultValues = {
|
||||
id: '',
|
||||
title: '',
|
||||
domain: '',
|
||||
key: '',
|
||||
description: '',
|
||||
proxy: true,
|
||||
status: 'running',
|
||||
};
|
||||
const { control, handleSubmit, reset } = useForm({
|
||||
defaultValues,
|
||||
});
|
||||
const containerStore = useUserAppStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
showEdit: state.showEdit,
|
||||
setShowEdit: state.setShowEdit,
|
||||
formData: state.formData,
|
||||
userApp: state.userApp,
|
||||
updateData: state.updateData,
|
||||
};
|
||||
}),
|
||||
@ -36,121 +54,113 @@ const FormModal = () => {
|
||||
useEffect(() => {
|
||||
const open = containerStore.showEdit;
|
||||
if (open) {
|
||||
if (open) {
|
||||
const isNull = isObjectNull(containerStore.formData);
|
||||
const isNull = isObjectNull(containerStore.userApp);
|
||||
console.log('isNull', containerStore.userApp);
|
||||
if (isNull) {
|
||||
form.setFieldsValue({});
|
||||
} else form.setFieldsValue(containerStore.formData);
|
||||
reset(defaultValues);
|
||||
} else {
|
||||
reset(containerStore.userApp);
|
||||
}
|
||||
}
|
||||
}, [containerStore.showEdit]);
|
||||
}, [containerStore.showEdit, containerStore.userApp]);
|
||||
const onFinish = async (values: any) => {
|
||||
containerStore.updateData(values);
|
||||
const pickValues = pick(values, ['id', 'title', 'domain', 'key', 'description', 'proxy', 'status']);
|
||||
containerStore.updateData(pickValues);
|
||||
};
|
||||
const onClose = () => {
|
||||
containerStore.setShowEdit(false);
|
||||
form.resetFields();
|
||||
reset();
|
||||
};
|
||||
const isEdit = containerStore.formData.id;
|
||||
const isEdit = containerStore?.userApp?.id;
|
||||
return (
|
||||
<Dialog
|
||||
title={isEdit ? 'Edit' : 'Add'}
|
||||
open={containerStore.showEdit}
|
||||
onClose={() => containerStore.setShowEdit(false)}
|
||||
sx={{
|
||||
'& .MuiDialog-paper': {
|
||||
width: '800px',
|
||||
width: '1000px',
|
||||
},
|
||||
}}>
|
||||
<DialogTitle>{isEdit ? 'Edit' : 'Add'}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
initialValues={{
|
||||
proxy: true,
|
||||
}}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='title' label='title'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='domain' label='domain' tooltip='域名自定义绑定'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='key' label='key'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item name='proxy' label='proxy' tooltip='设置为true,则后端直接代理请求minio服务进行转发,不会缓存下载到服务器。'>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item name='status' label='status'>
|
||||
<form className='flex flex-col gap-4 pt-2' onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller name='title' control={control} render={({ field }) => <TextField {...field} label='title' fullWidth />} />
|
||||
<Controller
|
||||
name='domain'
|
||||
control={control}
|
||||
render={({ field }) => <TextField {...field} label='domain' variant='outlined' helperText='域名自定义绑定' />}
|
||||
/>
|
||||
<Controller name='key' control={control} render={({ field }) => <TextField {...field} label='key' fullWidth />} />
|
||||
<Controller name='description' control={control} render={({ field }) => <TextField {...field} label='description' multiline rows={4} fullWidth />} />
|
||||
<Controller name='proxy' control={control} render={({ field }) => <Switch {...field} checked={field.value} />} />
|
||||
<Controller
|
||||
name='status'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
sx={{
|
||||
width: '100%',
|
||||
}}
|
||||
options={[
|
||||
{ label: 'Running', value: 'running' },
|
||||
{ label: 'Stop', value: 'stop' },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
)}
|
||||
/>
|
||||
<div>
|
||||
<Button type='submit'>提交</Button>
|
||||
<Button className='ml-2' type='reset' onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
const ShareModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [permission, setPermission] = useState<any>(null);
|
||||
const [runtime, setRuntime] = useState<string[]>([]);
|
||||
const containerStore = useUserAppStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
showEdit: state.showShareEdit,
|
||||
setShowEdit: state.setShowShareEdit,
|
||||
formData: state.formData,
|
||||
updateData: state.updateData,
|
||||
userApp: state.userApp,
|
||||
};
|
||||
}),
|
||||
);
|
||||
useEffect(() => {
|
||||
const open = containerStore.showEdit;
|
||||
if (open) {
|
||||
// form.setFieldsValue(containerStore.formData);
|
||||
const permission = containerStore.formData?.data?.permission || {};
|
||||
const permission = containerStore.userApp?.data?.permission || {};
|
||||
const runtime = containerStore.userApp?.data?.runtime || [];
|
||||
if (isObjectNull(permission)) {
|
||||
setPermission(null);
|
||||
} else {
|
||||
setPermission(permission);
|
||||
}
|
||||
setRuntime(runtime);
|
||||
}
|
||||
}, [containerStore.showEdit]);
|
||||
}, [containerStore.showEdit, containerStore.userApp]);
|
||||
const onFinish = async () => {
|
||||
const values = {
|
||||
...containerStore.formData,
|
||||
id: containerStore.userApp.id,
|
||||
data: {
|
||||
permission,
|
||||
runtime,
|
||||
},
|
||||
};
|
||||
containerStore.updateData(values);
|
||||
};
|
||||
const onClose = () => {
|
||||
containerStore.setShowEdit(false);
|
||||
form.resetFields();
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
console.log('runtime', runtime);
|
||||
return (
|
||||
<Dialog
|
||||
open={containerStore.showEdit}
|
||||
@ -159,27 +169,52 @@ const ShareModal = () => {
|
||||
}}>
|
||||
<DialogTitle>{iText.share.title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<div className='flex flex-col gap-2 w-[400px] '>
|
||||
<PermissionManager
|
||||
value={permission}
|
||||
onChange={(value) => {
|
||||
setPermission(value);
|
||||
}}
|
||||
/>
|
||||
<FormControlLabel
|
||||
label={t('app.runtime')}
|
||||
control={
|
||||
<Select
|
||||
multiple
|
||||
size='small'
|
||||
value={runtime}
|
||||
onChange={(e) => {
|
||||
setRuntime(e.target.value as string[]);
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
label: 'Node',
|
||||
value: 'node',
|
||||
},
|
||||
{
|
||||
label: 'Browser',
|
||||
value: 'browser',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button type='submit' variant='contained' onClick={onFinish}>
|
||||
提交
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' type='reset' onClick={onClose}>
|
||||
取消
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export const List = () => {
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
const [modal, contextHolder] = useModal();
|
||||
const { t } = useTranslation();
|
||||
const userAppStore = useUserAppStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -190,6 +225,7 @@ export const List = () => {
|
||||
setFormData: state.setFormData,
|
||||
deleteData: state.deleteData,
|
||||
setShowShareEdit: state.setShowShareEdit,
|
||||
getUserApp: state.getUserApp,
|
||||
};
|
||||
}),
|
||||
);
|
||||
@ -206,6 +242,7 @@ export const List = () => {
|
||||
padding: '8px',
|
||||
}}
|
||||
onClick={() => {
|
||||
userAppStore.setFormData({});
|
||||
userAppStore.setShowEdit(true);
|
||||
}}>
|
||||
<PlusOutlined />
|
||||
@ -242,8 +279,14 @@ export const List = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{item.domain && <div className='text-xs'>访问域名: {item.domain}</div>}
|
||||
<div className='text-xs'>version: {item.version}</div>
|
||||
{item.domain && (
|
||||
<div className='text-xs'>
|
||||
{t('app.domain')}: {item.domain}
|
||||
</div>
|
||||
)}
|
||||
<div className='text-xs'>
|
||||
{t('app.version')}: {item.version}
|
||||
</div>
|
||||
<div className={clsx('text-sm border border-gray-200 p-2 max-h-[140px] scrollbar my-1', !hasDescription && 'hidden')}>
|
||||
<div dangerouslySetInnerHTML={{ __html: content }}></div>
|
||||
</div>
|
||||
@ -273,6 +316,7 @@ export const List = () => {
|
||||
<Tooltip title={iText.share.tips}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
userAppStore.getUserApp(item.id);
|
||||
userAppStore.setFormData(item);
|
||||
userAppStore.setShowShareEdit(true);
|
||||
}}>
|
||||
@ -294,7 +338,7 @@ export const List = () => {
|
||||
if (DEV_SERVER) {
|
||||
baseUri = 'http://localhost:3005';
|
||||
}
|
||||
const link = new URL(`/${item.user}/${item.key}`, baseUri);
|
||||
const link = new URL(`/${item.user}/${item.key}/`, baseUri);
|
||||
window.open(link.toString(), '_blank');
|
||||
} else {
|
||||
message.error('The app is not running');
|
||||
|
@ -14,6 +14,9 @@ type UserAppStore = {
|
||||
deleteData: (id: string) => Promise<void>;
|
||||
showShareEdit: boolean;
|
||||
setShowShareEdit: (showShareEdit: boolean) => void;
|
||||
userApp: any;
|
||||
setUserApp: (userApp: any) => void;
|
||||
getUserApp: (id: string) => Promise<void>;
|
||||
};
|
||||
export const useUserAppStore = create<UserAppStore>((set, get) => {
|
||||
return {
|
||||
@ -69,5 +72,20 @@ export const useUserAppStore = create<UserAppStore>((set, get) => {
|
||||
},
|
||||
showShareEdit: false,
|
||||
setShowShareEdit: (showShareEdit) => set({ showShareEdit }),
|
||||
userApp: {},
|
||||
setUserApp: (userApp) => set({ userApp }),
|
||||
getUserApp: async (id) => {
|
||||
set({ userApp: null });
|
||||
const res = await query.post({
|
||||
path: 'user-app',
|
||||
key: 'get',
|
||||
id,
|
||||
});
|
||||
if (res.code === 200) {
|
||||
set({ userApp: res.data });
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { Input, Modal, Select } from 'antd';
|
||||
import { Fragment, Suspense, useEffect, useState } from 'react';
|
||||
import { TextArea } from '../components/TextArea';
|
||||
import { useContainerStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Form } from 'antd';
|
||||
// import copy from 'copy-to-clipboard';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { message } from '@/modules/message';
|
||||
@ -19,10 +16,16 @@ import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@/components/card/CardBlank';
|
||||
import { Settings } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { TextField } from '@mui/material';
|
||||
import { pick } from 'lodash-es';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TagsInput } from '@kevisual/center-components/select/TagsInput.tsx';
|
||||
const DrawEdit = React.lazy(() => import('../module/DrawEdit'));
|
||||
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const { control, handleSubmit, reset, setValue } = useForm();
|
||||
const containerStore = useContainerStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -33,70 +36,75 @@ const FormModal = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const open = containerStore.showEdit;
|
||||
if (open) {
|
||||
if (open) {
|
||||
const isNull = isObjectNull(containerStore.formData);
|
||||
if (isNull) {
|
||||
form.setFieldsValue({});
|
||||
} else form.setFieldsValue(containerStore.formData);
|
||||
reset({});
|
||||
} else {
|
||||
Object.keys(containerStore.formData).forEach((key) => {
|
||||
setValue(key, containerStore.formData[key]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [containerStore.showEdit]);
|
||||
const onFinish = async (values: any) => {
|
||||
containerStore.updateData(values);
|
||||
return () => {
|
||||
reset({});
|
||||
};
|
||||
}, [containerStore.showEdit]);
|
||||
|
||||
const onFinish = async (values: any) => {
|
||||
const pickValues = pick(values, ['id', 'title', 'description', 'tags', 'code']);
|
||||
containerStore.updateData(pickValues);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
containerStore.setShowEdit(false);
|
||||
form.resetFields();
|
||||
reset();
|
||||
};
|
||||
|
||||
const isEdit = containerStore.formData.id;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Dialog open={containerStore.showEdit} onClose={() => containerStore.setShowEdit(false)}>
|
||||
<Dialog open={containerStore.showEdit} onClose={onClose}>
|
||||
<DialogTitle>{isEdit ? 'Edit' : 'Add'}</DialogTitle>
|
||||
<DialogContent sx={{ padding: '20px', minWidth: '600px' }}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
<form className='flex flex-col gap-6 pt-2' onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller name='title' control={control} defaultValue='' render={({ field }) => <TextField {...field} label='Title' fullWidth />} />
|
||||
<Controller
|
||||
name='description'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
render={({ field }) => <TextField {...field} label='Description' multiline rows={4} fullWidth />}
|
||||
/>
|
||||
<Controller
|
||||
name='tags'
|
||||
control={control}
|
||||
defaultValue={[]}
|
||||
render={({ field }) => {
|
||||
return <TagsInput key={'tags'} label='Tags' placeholder='添加标签' value={field.value} onChange={(value) => field.onChange(value)} />;
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='title' label='title'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item name='tags' label='tags'>
|
||||
<Select mode='tags' />
|
||||
</Form.Item>
|
||||
/>
|
||||
{!isEdit && (
|
||||
<Form.Item name='code' label='code'>
|
||||
<TextArea />
|
||||
</Form.Item>
|
||||
<Controller name='code' control={control} defaultValue='' render={({ field }) => <TextField {...field} label='Code' multiline fullWidth />} />
|
||||
)}
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<div>
|
||||
<Button variant='contained' type='submit'>
|
||||
提交
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' onClick={onClose}>
|
||||
取消
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
const PublishFormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const { control, handleSubmit, reset, setValue, getValues } = useForm();
|
||||
const { t } = useTranslation();
|
||||
const containerStore = useContainerStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -107,20 +115,25 @@ const PublishFormModal = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const open = containerStore.showEdit;
|
||||
if (open) {
|
||||
if (open) {
|
||||
const isNull = isObjectNull(containerStore.formData);
|
||||
if (isNull) {
|
||||
form.setFieldsValue({});
|
||||
} else form.setFieldsValue(containerStore.formData);
|
||||
reset({});
|
||||
} else {
|
||||
Object.keys(containerStore.formData).forEach((key) => {
|
||||
setValue(key, containerStore.formData[key]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [containerStore.showEdit]);
|
||||
|
||||
const onFinish = async () => {
|
||||
const values = form.getFieldsValue();
|
||||
const containerRes = await containerStore.updateData(values, { closePublish: false });
|
||||
const values = getValues();
|
||||
const pickValues = pick(values, ['id', 'publish.key', 'publish.version', 'publish.fileName', 'publish.description']);
|
||||
const containerRes = await containerStore.updateData(pickValues, { closePublish: false });
|
||||
if (containerRes.code === 200) {
|
||||
const code = containerRes.data?.code || '-';
|
||||
const fileName = values['publish']?.['fileName'];
|
||||
@ -139,12 +152,11 @@ const PublishFormModal = () => {
|
||||
const key = values['publish']['key'];
|
||||
const version = values['publish']['version'];
|
||||
const file = toFile(code, directoryAndName.name);
|
||||
const res = await uploadFileChunked(file, {
|
||||
const res = (await uploadFileChunked(file, {
|
||||
appKey: key,
|
||||
version,
|
||||
directory: directoryAndName.directory,
|
||||
});
|
||||
// @ts-ignore
|
||||
})) as any;
|
||||
if (res.code === 200) {
|
||||
message.success('upload success');
|
||||
} else {
|
||||
@ -152,66 +164,67 @@ const PublishFormModal = () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onUpdate = async () => {
|
||||
const values = form.getFieldsValue();
|
||||
const values = getValues();
|
||||
containerStore.updateData(values);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
containerStore.setShowEdit(false);
|
||||
form.resetFields();
|
||||
reset();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={containerStore.showEdit} onClose={() => containerStore.setShowEdit(false)}>
|
||||
<Dialog open={containerStore.showEdit} onClose={onClose}>
|
||||
<DialogTitle>Publish</DialogTitle>
|
||||
<DialogContent sx={{ padding: '10px', minWidth: '600px' }}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 6,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 18,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'key']} label='App key' required>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'version']} label='App Version' required>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'fileName']} label='Filename' tooltip='可以是文件夹格式,比如(directory/a.name)' required>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name={['publish', 'description']} label='Description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<form className='flex flex-col gap-6 pt-2' onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller
|
||||
name='publish.key'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
render={({ field }) => <TextField {...field} label='App key' required fullWidth />}
|
||||
/>
|
||||
<Controller
|
||||
name='publish.version'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
render={({ field }) => <TextField {...field} label='App Version' required fullWidth />}
|
||||
/>
|
||||
<Controller
|
||||
name='publish.fileName'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
render={({ field }) => <TextField {...field} label='Filename' required fullWidth />}
|
||||
/>
|
||||
<Controller
|
||||
name='publish.description'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
render={({ field }) => <TextField {...field} label='Description' multiline rows={4} fullWidth />}
|
||||
/>
|
||||
<div className='flex gap-3'>
|
||||
<Tooltip
|
||||
placement='top'
|
||||
title='根据文件名和code的字符串的内容,自动生成文件。并保存。如果是其他文件类型,转成base64上传。比如图片以类似data:image/jpeg;开头'>
|
||||
<Button variant='contained' color='primary' type='submit'>
|
||||
上传
|
||||
{t('Upload')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Button variant='contained' onClick={onUpdate}>
|
||||
保存
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button onClick={onClose}>取消</Button>
|
||||
<Button onClick={onClose}>{t('Cancel')}</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export const ContainerList = () => {
|
||||
const navicate = useNewNavigate();
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
const [modal, contextHolder] = useModal();
|
||||
const containerStore = useContainerStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -271,7 +284,7 @@ export const ContainerList = () => {
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
{item.title || '-'}
|
||||
<div className='font-thin card-key ml-3'>{item.tags ? item.tags.join(', ') : ''}</div>
|
||||
<div className='font-thin card-key ml-3'>{item.tags ? item.tags?.join?.(', ') : ''}</div>
|
||||
</div>
|
||||
<div className='font-light text-xs mt-2'>{item.description ? item.description : '-'}</div>
|
||||
</div>
|
||||
|
@ -16,4 +16,3 @@ export const App = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export * from './module/Select';
|
||||
|
@ -1,39 +0,0 @@
|
||||
import { query } from '@/modules';
|
||||
import { Select as AntSelect, SelectProps } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { message } from '@/modules/message';
|
||||
export const Select = (props: SelectProps) => {
|
||||
const [options, setOptions] = useState<{ value: string; id: string }[]>([]);
|
||||
useEffect(() => {
|
||||
fetch();
|
||||
}, []);
|
||||
const fetch = async () => {
|
||||
const res = await query.post({
|
||||
path: 'container',
|
||||
key: 'list',
|
||||
});
|
||||
if (res.code !== 200) {
|
||||
message.error(res.message || '获取容器列表失败');
|
||||
return;
|
||||
}
|
||||
const data = res.data || [];
|
||||
setOptions(
|
||||
data.map((item: any) => {
|
||||
return {
|
||||
label: item.title,
|
||||
value: item.id,
|
||||
};
|
||||
}),
|
||||
);
|
||||
};
|
||||
return (
|
||||
<AntSelect
|
||||
{...props}
|
||||
options={options}
|
||||
// onChange={(e) => {
|
||||
// const labelValue = options.find((item) => item.value === e);
|
||||
// props.onChange?.(e, options);
|
||||
// }}
|
||||
/>
|
||||
);
|
||||
};
|
@ -4,13 +4,17 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import path from 'path-browserify';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
import clsx from 'clsx';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import FileOutlined from '@ant-design/icons/FileOutlined';
|
||||
import FolderOutlined from '@ant-design/icons/FolderOutlined';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { render, unmount } from '@kevisual/resources/pages/Bootstrap.tsx';
|
||||
import UploadOutlined from '@ant-design/icons/lib/icons/UploadOutlined';
|
||||
import { Tooltip } from '@mui/material';
|
||||
import { useResourceFileStore } from '@kevisual/resources/pages/store/resource-file.ts';
|
||||
import { FileDrawerApp } from '@kevisual/resources/pages/file/draw/FileDrawer.tsx';
|
||||
import { RefreshCw, Upload } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { UploadButton } from '@kevisual/resources/pages/upload/index.tsx';
|
||||
export const CardPath = ({ children }: any) => {
|
||||
const userAppStore = useFileStore(
|
||||
useShallow((state) => {
|
||||
@ -32,10 +36,18 @@ export const CardPath = ({ children }: any) => {
|
||||
userAppStore.setPath(prefix.replace('root/', '') + '/');
|
||||
userAppStore.getList();
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [usrname, appKey, version] = paths;
|
||||
const onUloadFinish = (res: any) => {
|
||||
console.log(res);
|
||||
userAppStore.getList();
|
||||
};
|
||||
return (
|
||||
<div className='border border-gray-200 rounded'>
|
||||
<div className='p-2'>
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex justify-between'>
|
||||
<div className='flex gap-2 py-2'>
|
||||
<div>Path: </div>
|
||||
<div className='flex'>
|
||||
@ -56,6 +68,32 @@ export const CardPath = ({ children }: any) => {
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<Tooltip title={t('refresh')} placement='bottom'>
|
||||
<IconButton
|
||||
color='primary'
|
||||
onClick={() => {
|
||||
userAppStore.getList();
|
||||
}}>
|
||||
<RefreshCw />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{version && (
|
||||
<>
|
||||
<Tooltip title={t('uploadDirectory')} placement='bottom'>
|
||||
<IconButton color='primary'>
|
||||
<UploadButton onlyIcon uploadDirectory icon={<Upload />} appKey={appKey} version={version} username={usrname} onUpload={onUloadFinish} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('upload')} placement='bottom'>
|
||||
<IconButton color='primary'>
|
||||
<UploadButton onlyIcon appKey={appKey} version={version} username={usrname} onUpload={onUloadFinish} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className=''>{children}</div>
|
||||
@ -65,6 +103,17 @@ export const CardPath = ({ children }: any) => {
|
||||
export const List = () => {
|
||||
const [tab, setTab] = useState<'folder' | 'upload'>('folder');
|
||||
const uploadRef = useRef<HTMLDivElement>(null);
|
||||
const { setOpenDrawer, setPrefix, setResource, getStatFile, setOnce } = useResourceFileStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
setOpenDrawer: state.setOpenDrawer,
|
||||
setPrefix: state.setPrefix,
|
||||
setResource: state.setResource,
|
||||
getStatFile: state.getStatFile,
|
||||
setOnce: state.setOnce,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const userAppStore = useFileStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -79,6 +128,9 @@ export const List = () => {
|
||||
);
|
||||
useEffect(() => {
|
||||
userAppStore.getList();
|
||||
return () => {
|
||||
setOnce(null);
|
||||
};
|
||||
}, []);
|
||||
const onDirectoryClick = (prefix: string) => {
|
||||
userAppStore.setPath(prefix);
|
||||
@ -114,7 +166,15 @@ export const List = () => {
|
||||
className=' border-t border-gray-200 flex gap-2 p-2'
|
||||
key={index}
|
||||
onClick={() => {
|
||||
userAppStore.getFile(item.name);
|
||||
setPrefix(item.name);
|
||||
setResource(item);
|
||||
getStatFile();
|
||||
setOpenDrawer(true);
|
||||
setOnce((data: any) => {
|
||||
// console.log(data);
|
||||
userAppStore.getList();
|
||||
});
|
||||
console.log(item);
|
||||
}}>
|
||||
<div>
|
||||
<FileOutlined />
|
||||
@ -126,9 +186,7 @@ export const List = () => {
|
||||
})}
|
||||
</div>
|
||||
</CardPath>
|
||||
<div>
|
||||
<pre>{!isObjectNull(userAppStore.file) && JSON.stringify(userAppStore.file, null, 2)}</pre>
|
||||
</div>
|
||||
<FileDrawerApp />
|
||||
</>
|
||||
);
|
||||
}, [userAppStore.list, userAppStore.path]);
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { Input, Modal } from 'antd';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { useOrgStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Form } from 'antd';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Tooltip, Button, ButtonGroup, Dialog, DialogTitle, DialogContent } from '@mui/material';
|
||||
@ -18,10 +16,14 @@ import clsx from 'clsx';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@/components/card/CardBlank';
|
||||
import { useLayoutStore } from '@/modules/layout/store';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { TextField } from '@mui/material';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
const { control, handleSubmit, reset } = useForm();
|
||||
const userStore = useOrgStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -33,47 +35,50 @@ const FormModal = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const open = userStore.showEdit;
|
||||
if (open) {
|
||||
const isNull = isObjectNull(userStore.formData);
|
||||
if (isNull) {
|
||||
form.setFieldsValue({});
|
||||
} else form.setFieldsValue(userStore.formData);
|
||||
reset({});
|
||||
} else reset(userStore.formData);
|
||||
}
|
||||
}, [userStore.showEdit, userStore.formData]);
|
||||
const onFinish = async (values: any) => {
|
||||
userStore.updateData(values);
|
||||
return () => {
|
||||
reset({});
|
||||
};
|
||||
}, [userStore.showEdit, userStore.formData, reset]);
|
||||
|
||||
const onFinish = async (values: any) => {
|
||||
const pickValues = pick(values, ['id', 'username', 'description']);
|
||||
userStore.updateData(pickValues);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
userStore.setShowEdit(false);
|
||||
form.setFieldsValue({});
|
||||
reset({});
|
||||
userStore.setFormData({});
|
||||
};
|
||||
|
||||
const isEdit = userStore.formData.id;
|
||||
|
||||
return (
|
||||
<Dialog open={userStore.showEdit} onClose={() => userStore.setShowEdit(false)}>
|
||||
<DialogTitle>{isEdit ? 'Edit' : 'Add'}</DialogTitle>
|
||||
<DialogContent sx={{ padding: '20px', minWidth: '600px' }}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='username' label='username'>
|
||||
<Input disabled={isEdit} />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<form onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller
|
||||
name='username'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
render={({ field }) => <TextField {...field} label='username' disabled={isEdit} fullWidth margin='normal' />}
|
||||
/>
|
||||
<Controller
|
||||
name='description'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
render={({ field }) => <TextField {...field} label='description' multiline rows={4} fullWidth margin='normal' />}
|
||||
/>
|
||||
<div className='flex gap-2'>
|
||||
<Button variant='contained' type='submit'>
|
||||
{t('Submit')}
|
||||
@ -82,8 +87,7 @@ const FormModal = () => {
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
@ -111,9 +115,7 @@ export const List = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
const [codeEdit, setCodeEdit] = useState(false);
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
const [code, setCode] = useState('');
|
||||
const [modal, contextHolder] = useModal();
|
||||
useEffect(() => {
|
||||
userStore.getList();
|
||||
}, []);
|
||||
@ -143,9 +145,7 @@ export const List = () => {
|
||||
className='flex text-sm gap flex-col w-[400px] max-h-[400px] bg-white p-4 rounded-lg'
|
||||
key={item.id}
|
||||
onClick={() => {
|
||||
setCode(item.code);
|
||||
userStore.setFormData(item);
|
||||
setCodeEdit(true);
|
||||
// userStore.setFormData(item);
|
||||
}}>
|
||||
<div className='px-4 cursor-pointer'>
|
||||
<div
|
||||
@ -168,7 +168,6 @@ export const List = () => {
|
||||
onClick={(e) => {
|
||||
userStore.setFormData(item);
|
||||
userStore.setShowEdit(true);
|
||||
setCodeEdit(false);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<EditOutlined />
|
||||
@ -214,35 +213,6 @@ export const List = () => {
|
||||
<CardBlank className='w-[400px]' />
|
||||
</div>
|
||||
</div>
|
||||
<div className={clsx('bg-gray-100 border-gray-200 border-bg-slate-300 w-[600px] shark-0', !codeEdit && 'hidden', 'hidden')}>
|
||||
<div className='bg-white p-2'>
|
||||
<div className='mt-2 ml-2 flex gap-2'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setCodeEdit(false);
|
||||
userStore.setFormData({});
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
userStore.updateData({ ...userStore.formData, code });
|
||||
}}>
|
||||
<SaveOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[94%] p-2 rounded-2 shadow-xs'>
|
||||
<Input.TextArea
|
||||
value={code}
|
||||
onChange={(value) => {
|
||||
// setCode(value);
|
||||
}}
|
||||
className='h-full max-h-full scrollbar'
|
||||
style={{ overflow: 'auto' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormModal />
|
||||
{contextHolder}
|
||||
|
@ -2,22 +2,24 @@ import { useShallow } from 'zustand/react/shallow';
|
||||
import { useOrgStore } from '../store';
|
||||
import { useParams } from 'react-router';
|
||||
import { useEffect } from 'react';
|
||||
import { Input, Modal, Select } from 'antd';
|
||||
import { message } from '@/modules/message';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import { Tooltip, Button, ButtonGroup, Dialog, DialogTitle, DialogContent } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { Form } from 'antd';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx';
|
||||
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 [form] = Form.useForm();
|
||||
const { control, handleSubmit, reset } = useForm();
|
||||
const userStore = useOrgStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -29,17 +31,21 @@ const FormModal = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const open = userStore.showEdit;
|
||||
if (open) {
|
||||
const isNull = isObjectNull(userStore.formData);
|
||||
if (isNull) {
|
||||
form.setFieldsValue({});
|
||||
} else form.setFieldsValue(userStore.formData);
|
||||
reset({});
|
||||
} else reset(userStore.formData);
|
||||
}
|
||||
}, [userStore.showEdit, userStore.formData]);
|
||||
return () => {
|
||||
reset({});
|
||||
};
|
||||
}, [userStore.showEdit, userStore.formData, reset]);
|
||||
|
||||
const onFinish = async (values: any) => {
|
||||
//
|
||||
console.log(values);
|
||||
const username = values.username;
|
||||
const role = values.role;
|
||||
@ -47,47 +53,46 @@ const FormModal = () => {
|
||||
message.error('username is required');
|
||||
return;
|
||||
}
|
||||
userStore.addUser({ username: username, role });
|
||||
const res = await userStore.addUser({ username: username, role });
|
||||
if (res?.code === 200) {
|
||||
userStore.setShowEdit(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
userStore.setShowEdit(false);
|
||||
userStore.setFormData({});
|
||||
};
|
||||
|
||||
const isEdit = userStore.formData.id;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Dialog open={userStore.showEdit} onClose={() => userStore.setShowEdit(false)}>
|
||||
<DialogTitle>{isEdit ? 'Edit' : 'Add'}</DialogTitle>
|
||||
<DialogContent sx={{ padding: '20px', minWidth: '600px' }}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='username' label='username'>
|
||||
<Input disabled={isEdit} />
|
||||
</Form.Item>
|
||||
<Form.Item name='role' label='role'>
|
||||
<form className='flex flex-col gap-6' onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller
|
||||
name='username'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
render={({ field }) => <TextField {...field} label='username' disabled={isEdit} fullWidth margin='normal' />}
|
||||
/>
|
||||
<Controller
|
||||
name='role'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
options={[
|
||||
{
|
||||
label: 'admin',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: 'member',
|
||||
value: 'member',
|
||||
},
|
||||
]}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
{ label: 'admin', value: 'admin' },
|
||||
{ label: 'user', value: 'user' },
|
||||
]}
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className='flex gap-2'>
|
||||
<Button variant='contained' type='submit'>
|
||||
{t('Submit')}
|
||||
@ -96,8 +101,7 @@ const FormModal = () => {
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
@ -106,7 +110,7 @@ const FormModal = () => {
|
||||
export const UserList = () => {
|
||||
const param = useParams();
|
||||
const navicate = useNewNavigate();
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
const [modal, contextHolder] = useModal();
|
||||
const { t } = useTranslation();
|
||||
const orgStore = useOrgStore(
|
||||
useShallow((state) => {
|
||||
@ -173,8 +177,18 @@ export const UserList = () => {
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
{/* <Tooltip title='edit'>
|
||||
<Button
|
||||
disabled={isOwner}
|
||||
onClick={() => {
|
||||
orgStore.setUserFormData(item);
|
||||
orgStore.setShowUserEdit(true);
|
||||
}}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
</Tooltip> */}
|
||||
<Tooltip title='delete'>
|
||||
<IconButton
|
||||
<Button
|
||||
disabled={isOwner}
|
||||
onClick={() => {
|
||||
modal.confirm({
|
||||
@ -186,7 +200,7 @@ export const UserList = () => {
|
||||
});
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</IconButton>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
@ -22,7 +22,7 @@ type OrgStore = {
|
||||
orgId: string;
|
||||
setOrgId: (orgId: string) => void;
|
||||
getOrg: () => Promise<any>;
|
||||
addUser: (data: { userId?: string; username?: string; role?: string }) => Promise<void>;
|
||||
addUser: (data: { userId?: string; username?: string; role?: string }) => Promise<any>;
|
||||
removeUser: (userId: string) => Promise<void>;
|
||||
};
|
||||
export const useOrgStore = create<OrgStore>((set, get) => {
|
||||
@ -113,6 +113,7 @@ export const useOrgStore = create<OrgStore>((set, get) => {
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
return res
|
||||
},
|
||||
removeUser: async (userId: string) => {
|
||||
const { orgId } = get();
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { Input, Modal } from 'antd';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { useUserStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { Form } from 'antd';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import SaveOutlined from '@ant-design/icons/SaveOutlined';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
@ -14,8 +12,13 @@ import { CardBlank } from '@kevisual/center-components/card/CardBlank.tsx';
|
||||
import { Dialog, ButtonGroup, Button, DialogContent, DialogTitle } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { TextField } from '@mui/material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { pick } from 'lodash-es';
|
||||
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const { control, handleSubmit, reset } = useForm();
|
||||
const userStore = useUserStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -27,25 +30,34 @@ const FormModal = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const open = userStore.showEdit;
|
||||
if (open) {
|
||||
const isNull = isObjectNull(userStore.formData);
|
||||
if (isNull) {
|
||||
form.setFieldsValue({});
|
||||
} else form.setFieldsValue(userStore.formData);
|
||||
reset({});
|
||||
} else reset(userStore.formData);
|
||||
}
|
||||
}, [userStore.showEdit]);
|
||||
const onFinish = async (values: any) => {
|
||||
userStore.updateData(values);
|
||||
return () => {
|
||||
reset({});
|
||||
};
|
||||
}, [userStore.showEdit]);
|
||||
|
||||
const onFinish = (values: any) => {
|
||||
const pickValues = pick(values, ['id', 'username', 'description']);
|
||||
userStore.updateData(pickValues);
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
userStore.setShowEdit(false);
|
||||
form.setFieldsValue({});
|
||||
reset({});
|
||||
userStore.setFormData({});
|
||||
};
|
||||
|
||||
const isEdit = userStore.formData.id;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={userStore.showEdit}
|
||||
@ -57,39 +69,30 @@ const FormModal = () => {
|
||||
}}>
|
||||
<DialogTitle>{isEdit ? t('Edit') : t('Add')}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='username' label='username'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='description' label='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<form onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller name='username' control={control} defaultValue='' render={({ field }) => <TextField {...field} label='username' fullWidth />} />
|
||||
<Controller
|
||||
name='description'
|
||||
control={control}
|
||||
defaultValue=''
|
||||
render={({ field }) => <TextField {...field} label='description' multiline rows={4} fullWidth />}
|
||||
/>
|
||||
<div>
|
||||
<Button type='submit' variant='contained'>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' type='reset' onClick={onClose}>
|
||||
<Button className='ml-2' type='button' onClick={onClose}>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export const List = () => {
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
const [modal, contextHolder] = useModal();
|
||||
const userStore = useUserStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@ -104,8 +107,6 @@ export const List = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
const [codeEdit, setCodeEdit] = useState(false);
|
||||
const [code, setCode] = useState('');
|
||||
useEffect(() => {
|
||||
userStore.getList();
|
||||
}, []);
|
||||
@ -132,9 +133,7 @@ export const List = () => {
|
||||
className='flex text-sm gap flex-col w-[400px] max-h-[400px] bg-white p-4 rounded-lg'
|
||||
key={item.id}
|
||||
onClick={() => {
|
||||
setCode(item.code);
|
||||
userStore.setFormData(item);
|
||||
setCodeEdit(true);
|
||||
// userStore.setFormData(item);
|
||||
}}>
|
||||
<div className='px-4 cursor-pointer'>
|
||||
<div
|
||||
@ -156,7 +155,6 @@ export const List = () => {
|
||||
onClick={(e) => {
|
||||
userStore.setFormData(item);
|
||||
userStore.setShowEdit(true);
|
||||
setCodeEdit(false);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<EditOutlined />
|
||||
@ -188,36 +186,6 @@ export const List = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={clsx('bg-gray-100 border-l-gray-200 border-bg-slate-300 w-[600px] shark-0', !codeEdit && 'hidden', 'hidden')}>
|
||||
<div className='bg-white p-2'>
|
||||
<div className='mt-2 ml-2 flex gap-2'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setCodeEdit(false);
|
||||
userStore.setFormData({});
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
console.log('save', userStore.formData);
|
||||
userStore.updateData({ ...userStore.formData, code });
|
||||
}}>
|
||||
<SaveOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[94%] p-2 rounded-2 shadow-xs'>
|
||||
<Input.TextArea
|
||||
value={code}
|
||||
onChange={(value) => {
|
||||
// setCode(value);
|
||||
}}
|
||||
className='h-full max-h-full scrollbar'
|
||||
style={{ overflow: 'auto' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormModal />
|
||||
{contextHolder}
|
||||
|
@ -32,5 +32,6 @@
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"packages/**/*"
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user