feat: 添加i18n,美化界面
This commit is contained in:
@@ -2,14 +2,19 @@ import { useNavigation, useParams } from 'react-router';
|
||||
import { useAppVersionStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Button, Form, Input, Modal, Space, Tooltip } from 'antd';
|
||||
import { Form, Input, Modal, Tooltip } from 'antd';
|
||||
import { CloudUploadOutlined, DeleteOutlined, EditOutlined, FileOutlined, LeftOutlined, LinkOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { FileUpload } from '../modules/FileUpload';
|
||||
import clsx from 'clsx';
|
||||
import { message } from '@/modules/message';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
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';
|
||||
const FormModal = () => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const containerStore = useAppVersionStore(
|
||||
useShallow((state) => {
|
||||
@@ -68,11 +73,11 @@ const FormModal = () => {
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
Submit
|
||||
<Button type='submit' variant='contained' color='primary'>
|
||||
{t('submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' htmlType='reset' onClick={onClose}>
|
||||
Cancel
|
||||
<Button className='ml-2' onClick={onClose}>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
@@ -115,24 +120,25 @@ export const AppVersionList = () => {
|
||||
return (
|
||||
<div className='w-full h-full flex bg-slate-100'>
|
||||
<div className='p-2 bg-white'>
|
||||
<Button
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
versionStore.setFormData({ key: appKey });
|
||||
versionStore.setShowEdit(true);
|
||||
}}
|
||||
icon={<PlusOutlined />}
|
||||
/>
|
||||
}}>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<div className='grow h-full relative'>
|
||||
<div className='absolute top-2 left-4'>
|
||||
<Tooltip title='返回' placement='bottom'>
|
||||
<Button
|
||||
<IconButton
|
||||
variant='contained'
|
||||
onClick={() => {
|
||||
navigate('/app/edit/list');
|
||||
}}
|
||||
icon={<LeftOutlined />}
|
||||
/>
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -144,7 +150,7 @@ export const AppVersionList = () => {
|
||||
const color = isPublish ? 'bg-green-500' : '';
|
||||
const isRunning = item.status === 'running';
|
||||
return (
|
||||
<div className='card border-t w-[300px]' key={index}>
|
||||
<div className='card w-[300px]' key={index}>
|
||||
<div className={'flex items-center justify-between'}>
|
||||
{item.version}
|
||||
|
||||
@@ -153,7 +159,10 @@ export const AppVersionList = () => {
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='mt-4'>
|
||||
<Space.Compact>
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
{/* <Button
|
||||
onClick={() => {
|
||||
versionStore.setFormData(item);
|
||||
@@ -172,21 +181,20 @@ export const AppVersionList = () => {
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<DeleteOutlined />}
|
||||
/>
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='使用当前版本,发布为此版本'>
|
||||
<Button
|
||||
icon={<CloudUploadOutlined />}
|
||||
onClick={() => {
|
||||
versionStore.publishVersion({ id: item.id });
|
||||
}}
|
||||
/>
|
||||
}}>
|
||||
<CloudUploadOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title={'To Test App'}>
|
||||
<Button
|
||||
icon={<LinkOutlined />}
|
||||
onClick={() => {
|
||||
if (isRunning) {
|
||||
const link = new URL(`/test/${item.id}`, location.origin);
|
||||
@@ -194,18 +202,20 @@ export const AppVersionList = () => {
|
||||
} else {
|
||||
message.error('The app is not running');
|
||||
}
|
||||
}}></Button>
|
||||
}}>
|
||||
<LinkOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='文件管理'>
|
||||
<Button
|
||||
icon={<FileOutlined />}
|
||||
onClick={() => {
|
||||
versionStore.setFormData(item);
|
||||
setIsUpload(true);
|
||||
}}
|
||||
/>
|
||||
}}>
|
||||
<FileOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -220,12 +230,12 @@ export const AppVersionList = () => {
|
||||
<div className='bg-white p-2 w-[600px] h-full flex flex-col'>
|
||||
<div className='header flex items-center gap-2'>
|
||||
<Tooltip title='返回'>
|
||||
<Button
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setIsUpload(false);
|
||||
}}
|
||||
icon={<LeftOutlined />}
|
||||
/>
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<div className='font-bold'>{versionStore.key}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Button } from 'antd';
|
||||
import { Button } from '@mui/material';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useAppVersionStore } from '../store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { message } from '@/modules/message';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export type FileType = {
|
||||
name: string;
|
||||
size: number;
|
||||
@@ -12,6 +13,7 @@ export type FileType = {
|
||||
|
||||
export const FileUpload = () => {
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
const { t } = useTranslation();
|
||||
const appVersionStore = useAppVersionStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@@ -27,11 +29,21 @@ export const FileUpload = () => {
|
||||
// webkitRelativePath
|
||||
let files = Array.from(e.target.files) as any[];
|
||||
console.log(files);
|
||||
if (files.length === 0) {
|
||||
message.error('请选择文件');
|
||||
return;
|
||||
}
|
||||
|
||||
// 过滤 文件 .DS_Store
|
||||
files = files.filter((file) => {
|
||||
if (file.webkitRelativePath.startsWith('__MACOSX')) {
|
||||
return false;
|
||||
}
|
||||
// 过滤node_modules
|
||||
if (file.webkitRelativePath.includes('node_modules')) {
|
||||
return false;
|
||||
}
|
||||
// 过滤以.开头的文件
|
||||
return !file.name.startsWith('.');
|
||||
});
|
||||
if (files.length === 0) {
|
||||
@@ -89,7 +101,7 @@ export const FileUpload = () => {
|
||||
}
|
||||
ref.current!.click();
|
||||
}}>
|
||||
上传文件夹
|
||||
{t('uploadDirectory')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,30 +1,26 @@
|
||||
import { Input, Modal, Select, Space, Switch } from 'antd';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
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 copy from 'copy-to-clipboard';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
import { message } from '@/modules/message';
|
||||
import { Dialog, DialogTitle, DialogContent, Tooltip, Button, ButtonGroup } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { getDirectoryAndName, toFile, uploadFileChunked } from '@kevisual/resources/index.ts';
|
||||
import {
|
||||
EditOutlined,
|
||||
SettingOutlined,
|
||||
LinkOutlined,
|
||||
SaveOutlined,
|
||||
DeleteOutlined,
|
||||
LeftOutlined,
|
||||
MessageOutlined,
|
||||
PlusOutlined,
|
||||
DashboardOutlined,
|
||||
CloudUploadOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import clsx from 'clsx';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import CloudUploadOutlined from '@ant-design/icons/CloudUploadOutlined';
|
||||
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@/components/card/CardBlank';
|
||||
import { Settings } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
const DrawEdit = React.lazy(() => import('../module/DrawEdit'));
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const containerStore = useContainerStore(
|
||||
@@ -81,9 +77,11 @@ const FormModal = () => {
|
||||
<Form.Item name='tags' label='tags'>
|
||||
<Select mode='tags' />
|
||||
</Form.Item>
|
||||
<Form.Item name='code' label='code'>
|
||||
<TextArea />
|
||||
</Form.Item>
|
||||
{!isEdit && (
|
||||
<Form.Item name='code' label='code'>
|
||||
<TextArea />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button variant='contained' type='submit'>
|
||||
提交
|
||||
@@ -122,10 +120,9 @@ const PublishFormModal = () => {
|
||||
}, [containerStore.showEdit]);
|
||||
const onFinish = async () => {
|
||||
const values = form.getFieldsValue();
|
||||
const success = await containerStore.updateData(values, { closePublish: false });
|
||||
if (success) {
|
||||
const formData = containerStore.formData;
|
||||
const code = formData.code;
|
||||
const containerRes = await containerStore.updateData(values, { closePublish: false });
|
||||
if (containerRes.code === 200) {
|
||||
const code = containerRes.data?.code || '-';
|
||||
const fileName = values['publish']?.['fileName'];
|
||||
let directoryAndName: ReturnType<typeof getDirectoryAndName> | null = null;
|
||||
try {
|
||||
@@ -142,7 +139,6 @@ const PublishFormModal = () => {
|
||||
const key = values['publish']['key'];
|
||||
const version = values['publish']['version'];
|
||||
const file = toFile(code, directoryAndName.name);
|
||||
console.log('key', key, version, directoryAndName.directory, directoryAndName.name);
|
||||
const res = await uploadFileChunked(file, {
|
||||
appKey: key,
|
||||
version,
|
||||
@@ -167,15 +163,15 @@ const PublishFormModal = () => {
|
||||
return (
|
||||
<Dialog open={containerStore.showEdit} onClose={() => containerStore.setShowEdit(false)}>
|
||||
<DialogTitle>Publish</DialogTitle>
|
||||
<DialogContent sx={{ padding: '20px', minWidth: '600px' }}>
|
||||
<DialogContent sx={{ padding: '10px', minWidth: '600px' }}>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
span: 6,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 20,
|
||||
span: 18,
|
||||
}}>
|
||||
<Form.Item name='id' hidden>
|
||||
<Input />
|
||||
@@ -229,12 +225,14 @@ export const ContainerList = () => {
|
||||
setShowPublish: state.setShowPublish,
|
||||
updateData: state.updateData,
|
||||
formData: state.formData,
|
||||
getOne: state.getOne,
|
||||
setOpenDrawEdit: state.setOpenDrawEdit,
|
||||
openDrawEdit: state.openDrawEdit,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
const [codeEdit, setCodeEdit] = useState(false);
|
||||
const [code, setCode] = useState('');
|
||||
// const [codeEdit, setCodeEdit] = useState(false);
|
||||
useEffect(() => {
|
||||
containerStore.getList();
|
||||
}, []);
|
||||
@@ -248,7 +246,7 @@ export const ContainerList = () => {
|
||||
<div className='w-full h-full flex '>
|
||||
<div className='p-2 flex flex-col gap-2'>
|
||||
<Tooltip title='添加'>
|
||||
<IconButton variant='contained' onClick={onAdd} sx={{ padding: '8px' }}>
|
||||
<IconButton variant='contained' onClick={onAdd}>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
@@ -264,56 +262,47 @@ export const ContainerList = () => {
|
||||
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);
|
||||
containerStore.setFormData(item);
|
||||
setCodeEdit(true);
|
||||
// containerStore.setFormData(item);
|
||||
}}>
|
||||
<div className='px-4 cursor-pointer'>
|
||||
<div
|
||||
className='font-bold flex items-center'
|
||||
onClick={(e) => {
|
||||
copy(item.code);
|
||||
e.stopPropagation();
|
||||
message.success('copy code success');
|
||||
}}>
|
||||
{item.title || '-'}
|
||||
<div className='font-thin card-key ml-3 text-xs'>{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>
|
||||
<div className='w-full text-xs'>
|
||||
<TextArea className='max-h-[240px] scrollbar' value={item.code} readonly />
|
||||
</div>
|
||||
|
||||
<div className='flex mt-2 '>
|
||||
<ButtonGroup variant='contained' color='primary'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
// containerStore.publishData(item);
|
||||
}}>
|
||||
<SettingOutlined />
|
||||
</Button>
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
<Tooltip title='编辑代码'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
containerStore.getOne(item.id);
|
||||
containerStore.setFormData(item);
|
||||
containerStore.setOpenDrawEdit(true);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<Settings size={16} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='编辑'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
containerStore.setFormData(item);
|
||||
containerStore.setShowEdit(true);
|
||||
setCodeEdit(false);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{/* <Tooltip title='预览'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
// navicate('/container/preview/' + item.id);
|
||||
window.open('/container/preview/' + item.id);
|
||||
e.stopPropagation();
|
||||
}}>
|
||||
<LinkOutlined />
|
||||
</Button>
|
||||
</Tooltip> */}
|
||||
<Tooltip title='发布到 user app当中'>
|
||||
<Tooltip title='添加到 user app当中'>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
// containerStore.publishData(item);
|
||||
@@ -355,42 +344,12 @@ export const ContainerList = () => {
|
||||
<CardBlank className='w-[400px]' />
|
||||
</div>
|
||||
</div>
|
||||
<div className={clsx('bg-gray-100 border-l-gray-200 border-bg-slate-300 w-[600px] shark-0', !codeEdit && 'hidden')}>
|
||||
<div className='bg-white p-2'>
|
||||
<div className='mt-2 ml-2 flex gap-2'>
|
||||
<Tooltip title='返回'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setCodeEdit(false);
|
||||
containerStore.setFormData({});
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='保存'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
containerStore.updateData({ ...containerStore.formData, code });
|
||||
}}>
|
||||
<SaveOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[94%] p-2 rounded-2 shadow-xs'>
|
||||
<TextArea
|
||||
value={code}
|
||||
onChange={(value) => {
|
||||
setCode(value);
|
||||
}}
|
||||
className='h-full max-h-full scrollbar'
|
||||
style={{ overflow: 'auto' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FormModal />
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<DrawEdit />
|
||||
</Suspense>
|
||||
<PublishFormModal />
|
||||
<FormModal />
|
||||
{contextHolder}
|
||||
</div>
|
||||
);
|
||||
|
||||
103
src/pages/container/module/DrawEdit.tsx
Normal file
103
src/pages/container/module/DrawEdit.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { BaseEditor } from '@kevisual/codemirror/editor/editor.ts';
|
||||
import { Drawer } from '@mui/material';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { useContainerStore } from '../store';
|
||||
import { Tooltip } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { LeftOutlined, SaveOutlined } from '@ant-design/icons';
|
||||
|
||||
export const DrawEdit = () => {
|
||||
const editorElRef = useRef<HTMLDivElement>(null);
|
||||
const editorRef = useRef<BaseEditor>(null);
|
||||
const containerStore = useContainerStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
openDrawEdit: state.openDrawEdit,
|
||||
setOpenDrawEdit: state.setOpenDrawEdit,
|
||||
data: state.data,
|
||||
updateData: state.updateData,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const { openDrawEdit, setOpenDrawEdit } = containerStore;
|
||||
const [mount, setMount] = useState(false);
|
||||
useEffect(() => {
|
||||
if (openDrawEdit && editorElRef.current && !mount) {
|
||||
editorRef.current = new BaseEditor();
|
||||
editorRef.current.createEditor(editorElRef.current);
|
||||
setMount(true);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.destroyEditor();
|
||||
}
|
||||
};
|
||||
}, [openDrawEdit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (openDrawEdit && containerStore.data?.id && mount) {
|
||||
const editor = editorRef.current;
|
||||
const formData = containerStore.data;
|
||||
const fileType = editor?.getFileType(formData.title);
|
||||
const language = editor?.language;
|
||||
if (fileType && fileType !== language) {
|
||||
editor?.setLanguage(fileType, editorElRef.current!);
|
||||
} else {
|
||||
editor?.resetEditor(editorElRef.current!);
|
||||
}
|
||||
editor?.setContent(formData.code || '');
|
||||
}
|
||||
}, [openDrawEdit, containerStore.data?.id, mount]);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={openDrawEdit}
|
||||
ModalProps={{
|
||||
keepMounted: true, // 保持挂载
|
||||
hideBackdrop: true, // 移除mask
|
||||
disableEnforceFocus: true, // 允许操作背景内容
|
||||
disableAutoFocus: true, // 防止自动聚焦
|
||||
}}
|
||||
anchor='right'>
|
||||
<div className='bg-secondary w-[600px] h-[48px]'>
|
||||
<div className='text-white flex p-2'>
|
||||
<div className='ml-2 flex gap-2'>
|
||||
<Tooltip title='返回'>
|
||||
<IconButton
|
||||
sx={{
|
||||
'&:hover': {
|
||||
color: 'primary.main',
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
setOpenDrawEdit(false);
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title='保存'>
|
||||
<IconButton
|
||||
sx={{
|
||||
'&:hover': {
|
||||
color: 'primary.main',
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
const code = editorRef.current?.getContent();
|
||||
containerStore.updateData({ id: containerStore.data.id, code }, { refresh: false });
|
||||
}}>
|
||||
<SaveOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='flex-1 ml-2 flex items-center'>{containerStore.data?.title}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='border-primary' style={{ height: 'calc(100% - 50px)' }} ref={editorElRef}></div>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DrawEdit;
|
||||
@@ -12,8 +12,13 @@ type ContainerStore = {
|
||||
setLoading: (loading: boolean) => void;
|
||||
list: any[];
|
||||
getList: () => Promise<void>;
|
||||
updateData: (data: any, opts?: { closePublish?: boolean; closeEdit?: boolean }) => Promise<Boolean>;
|
||||
updateData: (data: any, opts?: { closePublish?: boolean; closeEdit?: boolean, refresh?: boolean }) => Promise<any>;
|
||||
deleteData: (id: string) => Promise<void>;
|
||||
openDrawEdit: boolean;
|
||||
setOpenDrawEdit: (openDrawEdit: boolean) => void;
|
||||
getOne: (id: string) => Promise<void>;
|
||||
data: any;
|
||||
setData: (data: any) => void;
|
||||
};
|
||||
export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
return {
|
||||
@@ -43,6 +48,7 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
const { getList } = get();
|
||||
const closePublish = opts?.closePublish ?? true;
|
||||
const closeEdit = opts?.closeEdit ?? true;
|
||||
const refresh = opts?.refresh ?? true;
|
||||
const res = await query.post({
|
||||
path: 'container',
|
||||
key: 'update',
|
||||
@@ -51,7 +57,9 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
if (res.code === 200) {
|
||||
message.success('Success');
|
||||
set({ formData: res.data });
|
||||
getList();
|
||||
if (refresh) {
|
||||
getList();
|
||||
}
|
||||
if (closePublish) {
|
||||
set({ showPublish: false });
|
||||
}
|
||||
@@ -61,7 +69,7 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
return res.code === 200;
|
||||
return res;
|
||||
},
|
||||
deleteData: async (id) => {
|
||||
const { getList } = get();
|
||||
@@ -77,5 +85,23 @@ export const useContainerStore = create<ContainerStore>((set, get) => {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
openDrawEdit: false,
|
||||
setOpenDrawEdit: (openDrawEdit) => set({ openDrawEdit }),
|
||||
getOne: async (id) => {
|
||||
set({ loading: true, data: {} });
|
||||
const res = await query.post({
|
||||
path: 'container',
|
||||
key: 'get',
|
||||
id,
|
||||
});
|
||||
set({ loading: false });
|
||||
if (res.code === 200) {
|
||||
set({ data: res.data });
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
},
|
||||
data: {},
|
||||
setData: (data) => set({ data }),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
import clsx from 'clsx';
|
||||
import { useNewNavigate } from '@/modules';
|
||||
const serverList = ['container', 'map'];
|
||||
const serverPath = [
|
||||
{
|
||||
path: 'container',
|
||||
links: ['edit/list', 'preview/:id', 'edit/:id'],
|
||||
},
|
||||
{
|
||||
path: 'app',
|
||||
links: ['edit/list', ':app/version/list'],
|
||||
},
|
||||
{
|
||||
path: 'file',
|
||||
links: ['edit/list'],
|
||||
},
|
||||
{
|
||||
path: 'map',
|
||||
links: ['/'],
|
||||
},
|
||||
{
|
||||
path: 'org',
|
||||
links: ['edit/list'],
|
||||
},
|
||||
];
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ServerPath = () => {
|
||||
const navigate = useNewNavigate();
|
||||
const { t } = useTranslation();
|
||||
const serverPath = [
|
||||
{
|
||||
path: 'container',
|
||||
links: ['edit/list', 'preview/:id', 'edit/:id'],
|
||||
},
|
||||
{
|
||||
path: 'app',
|
||||
links: ['edit/list', ':app/version/list'],
|
||||
},
|
||||
{
|
||||
path: 'file',
|
||||
links: ['edit/list'],
|
||||
},
|
||||
{
|
||||
path: 'map',
|
||||
links: ['/'],
|
||||
},
|
||||
{
|
||||
path: 'org',
|
||||
links: ['edit/list'],
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className='p-2 w-full h-full bg-gray-200'>
|
||||
<h1 className='p-4 w-1/2 m-auto h1'>Site Map</h1>
|
||||
<div className='p-2 w-full h-full bg-gray-200 text-primary'>
|
||||
<h1 className='p-4 w-1/2 m-auto h1'>{t('Site Map')}</h1>
|
||||
<div className='w-1/2 m-auto bg-white p-4 border rounded-md shadow-md min-w-[700px] max-h-[80vh] overflow-auto scrollbar'>
|
||||
<div className='flex flex-col w-full'>
|
||||
{serverPath.map((item) => {
|
||||
@@ -42,7 +44,6 @@ const ServerPath = () => {
|
||||
if (hasId) {
|
||||
return;
|
||||
}
|
||||
console.log('link', link);
|
||||
if (link === '/') {
|
||||
navigate(`/${item.path}`);
|
||||
return;
|
||||
@@ -69,18 +70,3 @@ const ServerPath = () => {
|
||||
);
|
||||
};
|
||||
export const App = ServerPath;
|
||||
export const ServerList = () => {
|
||||
return (
|
||||
<div className='p-2 w-full h-full bg-gray-200'>
|
||||
<div className='flex flex-col w-1/2 m-auto bg-white p-4 border rounded-md shadow-md'>
|
||||
{serverList.map((item) => {
|
||||
return (
|
||||
<div key={item} className='flex flex-col'>
|
||||
<div>{item}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import { Button, Input, Modal, Table, Tooltip } from 'antd';
|
||||
import { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
import { message } from '@/modules/message';
|
||||
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 {
|
||||
EditOutlined,
|
||||
SettingOutlined,
|
||||
LinkOutlined,
|
||||
SaveOutlined,
|
||||
DeleteOutlined,
|
||||
LeftOutlined,
|
||||
PlusOutlined,
|
||||
SwapOutlined,
|
||||
UnorderedListOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Tooltip, Button, ButtonGroup, Dialog, DialogTitle, DialogContent } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import SaveOutlined from '@ant-design/icons/SaveOutlined';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import SwapOutlined from '@ant-design/icons/SwapOutlined';
|
||||
import UnorderedListOutlined from '@ant-design/icons/UnorderedListOutlined';
|
||||
import clsx from 'clsx';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@/components/card/CardBlank';
|
||||
@@ -23,6 +21,7 @@ import { useLayoutStore } from '@/modules/layout/store';
|
||||
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
const userStore = useOrgStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@@ -53,42 +52,40 @@ const FormModal = () => {
|
||||
};
|
||||
const isEdit = userStore.formData.id;
|
||||
return (
|
||||
<Modal
|
||||
title={isEdit ? 'Edit' : 'Add'}
|
||||
open={userStore.showEdit}
|
||||
onClose={() => userStore.setShowEdit(false)}
|
||||
destroyOnClose
|
||||
footer={false}
|
||||
width={800}
|
||||
onCancel={onClose}>
|
||||
<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}>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
Submit
|
||||
</Button>
|
||||
<Button className='ml-2' htmlType='reset' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<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}>
|
||||
<div className='flex gap-2'>
|
||||
<Button variant='contained' type='submit'>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' onClick={onClose}>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
export const List = () => {
|
||||
@@ -125,10 +122,15 @@ export const List = () => {
|
||||
userStore.setFormData({});
|
||||
userStore.setShowEdit(true);
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className='w-full h-full flex'>
|
||||
<div className='p-2'>
|
||||
<Button onClick={onAdd} icon={<PlusOutlined />}></Button>
|
||||
<Tooltip title={t('Add Org')}>
|
||||
<IconButton onClick={onAdd} variant='contained'>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='flex grow overflow-hidden h-full'>
|
||||
<div className='grow overflow-auto scrollbar bg-gray-100'>
|
||||
@@ -157,24 +159,29 @@ export const List = () => {
|
||||
<div className='font-light text-xs mt-2'>{item.description ? item.description : '-'}</div>
|
||||
</div>
|
||||
<div className='flex mt-2 '>
|
||||
<Button.Group>
|
||||
<Tooltip title='Edit'>
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
<Tooltip title={t('Edit')}>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
userStore.setFormData(item);
|
||||
userStore.setShowEdit(true);
|
||||
setCodeEdit(false);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<EditOutlined />}></Button>
|
||||
}}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip title='User List'>
|
||||
<Tooltip title={t('User List')}>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
navicate(`/org/edit/user/${item.id}`);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<UnorderedListOutlined />}></Button>
|
||||
}}>
|
||||
<UnorderedListOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
@@ -186,16 +193,18 @@ export const List = () => {
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<DeleteOutlined />}></Button>
|
||||
<Tooltip title='Switch to Org'>
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
<Tooltip title={t('Switch to Org')}>
|
||||
<Button
|
||||
icon={<SwapOutlined />}
|
||||
onClick={() => {
|
||||
layoutStore.switchOrg(item.username);
|
||||
}}></Button>
|
||||
}}>
|
||||
<SwapOutlined />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Button.Group>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
@@ -212,14 +221,15 @@ export const List = () => {
|
||||
onClick={() => {
|
||||
setCodeEdit(false);
|
||||
userStore.setFormData({});
|
||||
}}
|
||||
icon={<LeftOutlined />}></Button>
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
console.log('save', userStore.formData);
|
||||
userStore.updateData({ ...userStore.formData, code });
|
||||
}}
|
||||
icon={<SaveOutlined />}></Button>
|
||||
}}>
|
||||
<SaveOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[94%] p-2 rounded-2 shadow-xs'>
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useOrgStore } from '../store';
|
||||
import { useNavigation, useParams } from 'react-router';
|
||||
import { useParams } from 'react-router';
|
||||
import { useEffect } from 'react';
|
||||
import { Button, Input, Modal, Select, Space, Tooltip } from 'antd';
|
||||
import { Input, Modal, Select } from 'antd';
|
||||
import { message } from '@/modules/message';
|
||||
import { DeleteOutlined, EditOutlined, LeftOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
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';
|
||||
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
@@ -48,53 +54,52 @@ const FormModal = () => {
|
||||
userStore.setFormData({});
|
||||
};
|
||||
const isEdit = userStore.formData.id;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Modal
|
||||
title={isEdit ? 'Edit' : 'Add'}
|
||||
open={userStore.showEdit}
|
||||
onClose={() => userStore.setShowEdit(false)}
|
||||
destroyOnClose
|
||||
footer={false}
|
||||
width={800}
|
||||
onCancel={onClose}>
|
||||
<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'>
|
||||
<Select
|
||||
options={[
|
||||
{
|
||||
label: 'admin',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: 'member',
|
||||
value: 'member',
|
||||
},
|
||||
]}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
Submit
|
||||
</Button>
|
||||
<Button className='ml-2' htmlType='reset' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<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'>
|
||||
<Select
|
||||
options={[
|
||||
{
|
||||
label: 'admin',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: 'member',
|
||||
value: 'member',
|
||||
},
|
||||
]}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<div className='flex gap-2'>
|
||||
<Button variant='contained' type='submit'>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' onClick={onClose}>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -102,7 +107,7 @@ export const UserList = () => {
|
||||
const param = useParams();
|
||||
const navicate = useNewNavigate();
|
||||
const [modal, contextHolder] = Modal.useModal();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const orgStore = useOrgStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@@ -128,56 +133,50 @@ export const UserList = () => {
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<div className='w-full h-full bg-gray-200 flex'>
|
||||
<div className='w-full h-full bg-gray-100 flex text-primary'>
|
||||
<div className='p-2 bg-white mr-2'>
|
||||
<Button
|
||||
icon={<PlusOutlined />}
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
orgStore.setUserFormData({});
|
||||
orgStore.setShowUserEdit(true);
|
||||
}}></Button>
|
||||
}}>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className='p-4 pt-12 grow relative'>
|
||||
<div className='absolute top-2'>
|
||||
<Tooltip title='返回'>
|
||||
<Button
|
||||
<Tooltip title={t('Back')}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
navicate(-1);
|
||||
}}
|
||||
icon={<LeftOutlined />}></Button>
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='p-4 bg-white rounded-lg border border-gray-200 shadow-md h-full'>
|
||||
<div className='p-4 bg-white rounded-lg border shadow-md h-full'>
|
||||
<div className='flex gap-4'>
|
||||
{orgStore.users.map((item) => {
|
||||
const isOwner = item.role === 'owner';
|
||||
return (
|
||||
<div key={item.id} className='card w-[300px] border-t border-gray-200 justify-between p-2 border-b'>
|
||||
<div key={item.id} className='card w-[300px] justify-between p-2 '>
|
||||
<div
|
||||
className='card-title capitalize truncate cursor-pointer'
|
||||
onClick={() => {
|
||||
copy(item.username);
|
||||
}}>
|
||||
username: {item.username}
|
||||
{t('Username')}: {item.username}
|
||||
</div>
|
||||
<div className='flex gap-2 capitalize'>{item.role || '-'}</div>
|
||||
<div className='mt-2'>
|
||||
<Space.Compact>
|
||||
{/* <Tooltip title='Edit'>
|
||||
<Button
|
||||
icon={<EditOutlined />}
|
||||
disabled={isOwner}
|
||||
onClick={() => {
|
||||
orgStore.setShowEdit(true);
|
||||
orgStore.setUserFormData(item);
|
||||
}}></Button>
|
||||
</Tooltip> */}
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
<Tooltip title='delete'>
|
||||
<Button
|
||||
icon={<DeleteOutlined />}
|
||||
<IconButton
|
||||
disabled={isOwner}
|
||||
onClick={() => {
|
||||
// o-NDO62XGeyEQoz_Sytz-1UUB7kw
|
||||
modal.confirm({
|
||||
title: 'Delete',
|
||||
content: 'Are you sure?',
|
||||
@@ -185,9 +184,11 @@ export const UserList = () => {
|
||||
orgStore.removeUser(item.id);
|
||||
},
|
||||
});
|
||||
}}></Button>
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Space.Compact>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -96,7 +96,6 @@ export const useOrgStore = create<OrgStore>((set, get) => {
|
||||
if (res.code === 200) {
|
||||
const { org, users } = res.data || {};
|
||||
set({ org, users });
|
||||
message.success('load success');
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { Button, Input, Modal, Space } from 'antd';
|
||||
import { Fragment, useEffect, useMemo, useState } from 'react';
|
||||
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 { useNewNavigate } from '@/modules';
|
||||
import EditOutlined from '@ant-design/icons/EditOutlined';
|
||||
import SettingOutlined from '@ant-design/icons/SettingOutlined';
|
||||
import LinkOutlined from '@ant-design/icons/LinkOutlined';
|
||||
import SaveOutlined from '@ant-design/icons/SaveOutlined';
|
||||
import DeleteOutlined from '@ant-design/icons/DeleteOutlined';
|
||||
import LeftOutlined from '@ant-design/icons/LeftOutlined';
|
||||
@@ -14,8 +11,9 @@ import PlusOutlined from '@ant-design/icons/PlusOutlined';
|
||||
import clsx from 'clsx';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { CardBlank } from '@kevisual/center-components/card/CardBlank.tsx';
|
||||
import { message } from '@/modules/message';
|
||||
import { Dialog } from '@mui/material';
|
||||
import { Dialog, ButtonGroup, Button, DialogContent, DialogTitle } from '@mui/material';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
const FormModal = () => {
|
||||
const [form] = Form.useForm();
|
||||
const userStore = useUserStore(
|
||||
@@ -47,9 +45,9 @@ const FormModal = () => {
|
||||
userStore.setFormData({});
|
||||
};
|
||||
const isEdit = userStore.formData.id;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Dialog
|
||||
title={isEdit ? 'Edit' : 'Add'}
|
||||
open={userStore.showEdit}
|
||||
onClose={() => userStore.setShowEdit(false)}
|
||||
sx={{
|
||||
@@ -57,33 +55,36 @@ const FormModal = () => {
|
||||
width: '800px',
|
||||
},
|
||||
}}>
|
||||
<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}>
|
||||
<Button type='primary' htmlType='submit'>
|
||||
Submit
|
||||
</Button>
|
||||
<Button className='ml-2' htmlType='reset' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<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}>
|
||||
<Button type='submit' variant='contained'>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
<Button className='ml-2' type='reset' onClick={onClose}>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -116,7 +117,9 @@ export const List = () => {
|
||||
return (
|
||||
<div className='w-full h-full flex'>
|
||||
<div className='p-2'>
|
||||
<Button onClick={onAdd} icon={<PlusOutlined />}></Button>
|
||||
<IconButton onClick={onAdd} variant='contained'>
|
||||
<PlusOutlined />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className='flex grow overflow-hidden h-full'>
|
||||
<div className='grow overflow-auto scrollbar bg-gray-100'>
|
||||
@@ -145,15 +148,19 @@ export const List = () => {
|
||||
<div className='font-light text-xs mt-2'>{item.description ? item.description : '-'}</div>
|
||||
</div>
|
||||
<div className='flex mt-2 '>
|
||||
<Space.Compact>
|
||||
<ButtonGroup
|
||||
variant='contained'
|
||||
color='primary'
|
||||
sx={{ color: 'white', '& .MuiButton-root': { color: 'white', minWidth: '32px', width: '32px', height: '32px', padding: '6px' } }}>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
userStore.setFormData(item);
|
||||
userStore.setShowEdit(true);
|
||||
setCodeEdit(false);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<EditOutlined />}></Button>
|
||||
}}>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
modal.confirm({
|
||||
@@ -164,9 +171,10 @@ export const List = () => {
|
||||
},
|
||||
});
|
||||
e.stopPropagation();
|
||||
}}
|
||||
icon={<DeleteOutlined />}></Button>
|
||||
</Space.Compact>
|
||||
}}>
|
||||
<DeleteOutlined />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
@@ -187,14 +195,16 @@ export const List = () => {
|
||||
onClick={() => {
|
||||
setCodeEdit(false);
|
||||
userStore.setFormData({});
|
||||
}}
|
||||
icon={<LeftOutlined />}></Button>
|
||||
}}>
|
||||
<LeftOutlined />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
console.log('save', userStore.formData);
|
||||
userStore.updateData({ ...userStore.formData, code });
|
||||
}}
|
||||
icon={<SaveOutlined />}></Button>
|
||||
}}>
|
||||
<SaveOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='h-[94%] p-2 rounded-2 shadow-xs'>
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
import { Button, Form, Input } from 'antd';
|
||||
import { TextField } from '@mui/material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { Button } from '@mui/material';
|
||||
import { useUserStore } from '../store';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { useLayoutStore } from '@/modules/layout/store';
|
||||
import { AvatarUpload } from '../module/AvatarUpload';
|
||||
import UploadOutlined from '@ant-design/icons/UploadOutlined';
|
||||
import PandaPNG from '@/assets/panda.png';
|
||||
import { FileUpload } from '../module/FileUpload';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const Profile = () => {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
const { control, handleSubmit, setValue, reset, formState, getValues } = useForm({
|
||||
defaultValues: {
|
||||
username: '',
|
||||
avatar: '',
|
||||
description: '',
|
||||
},
|
||||
});
|
||||
const ref = useRef<any>(null);
|
||||
const userStore = useUserStore(
|
||||
useShallow((state) => {
|
||||
@@ -32,21 +42,29 @@ export const Profile = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
// const avatar = layoutStore.me?.avatar;
|
||||
useEffect(() => {
|
||||
const fromData = layoutStore.me;
|
||||
if (isObjectNull(fromData)) {
|
||||
form.setFieldsValue({});
|
||||
reset({
|
||||
username: '',
|
||||
avatar: '',
|
||||
description: '',
|
||||
});
|
||||
} else {
|
||||
form.setFieldsValue(fromData);
|
||||
reset({
|
||||
username: fromData.username,
|
||||
avatar: fromData.avatar || '',
|
||||
description: fromData.description || '',
|
||||
});
|
||||
}
|
||||
setAvatar(fromData.avatar || '');
|
||||
}, [layoutStore.me]);
|
||||
}, [layoutStore.me, setValue]);
|
||||
const onChange = (path: string) => {
|
||||
let url = '/resources/' + path;
|
||||
console.log('path', url);
|
||||
form.setFieldsValue({ avatar: url });
|
||||
setAvatar(url + '?t=' + new Date().getTime());
|
||||
const url = path + '?t=' + new Date().getTime();
|
||||
setAvatar(url);
|
||||
setValue('avatar', url);
|
||||
const values = getValues();
|
||||
onFinish(values);
|
||||
};
|
||||
const onFinish = async (values) => {
|
||||
const newMe = await userStore.updateSelf(values);
|
||||
@@ -55,48 +73,51 @@ export const Profile = () => {
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className='w-full h-full bg-gray-200 p-4'>
|
||||
<div className='border shadow-lg p-4 bg-white rounded-lg'>
|
||||
<div className='text-2xl'>Profile</div>
|
||||
<div className='text-sm text-gray-500'>Edit your profile</div>
|
||||
<div className='w-full h-full bg-amber-50 p-4 text-primary'>
|
||||
<div className=' shadow-lg p-4 bg-white rounded-lg'>
|
||||
<div className='text-2xl'>{t('Profile')}</div>
|
||||
<div className='text-sm text-secondary'>{t('Edit your profile')}</div>
|
||||
<div className='flex gap-4'>
|
||||
<div className='w-[600px] p-4 border mt-2 '>
|
||||
<div className='w-full my-4'>
|
||||
{avatar && <img className='w-20 h-20 mx-auto rounded-full' src={avatar} alt='avatar' />}
|
||||
{!avatar && <img className='w-20 h-20 mx-auto rounded-full' src={PandaPNG} alt='avatar' />}
|
||||
</div>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 4,
|
||||
}}>
|
||||
<Form.Item label='Name' name='username'>
|
||||
<Input className='w-full border rounded-lg p-2' disabled />
|
||||
</Form.Item>
|
||||
<Form.Item label='Avatar' name='avatar'>
|
||||
<Input
|
||||
addonAfter={
|
||||
<div>
|
||||
<UploadOutlined
|
||||
onClick={() => {
|
||||
ref.current?.open?.();
|
||||
}}
|
||||
/>
|
||||
<FileUpload ref={ref} onChange={onChange} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label='Description' name='description'>
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label=' ' colon={false}>
|
||||
<Button className='' htmlType='submit'>
|
||||
保存
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<form onSubmit={handleSubmit(onFinish)} className='flex flex-col gap-4'>
|
||||
<Controller
|
||||
name='username'
|
||||
control={control}
|
||||
render={({ field }) => <TextField {...field} label={t('Name')} className='w-full border rounded-lg p-2' disabled />}
|
||||
/>
|
||||
<Controller
|
||||
name='avatar'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
label={t('Avatar')}
|
||||
slotProps={{
|
||||
input: {
|
||||
endAdornment: (
|
||||
<div>
|
||||
<UploadOutlined
|
||||
onClick={() => {
|
||||
ref.current?.open?.();
|
||||
}}
|
||||
/>
|
||||
<FileUpload ref={ref} onChange={onChange} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller name='description' control={control} render={({ field }) => <TextField {...field} label={t('Description')} multiline rows={4} />} />
|
||||
<Button className='' type='submit' variant='contained'>
|
||||
{t('Save')}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Button, Form, Input } from 'antd';
|
||||
import { useLoginStore } from '../store/login';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { useEffect } from 'react';
|
||||
import { isObjectNull } from '@/utils/is-null';
|
||||
import { LockOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import LockOutlined from '@ant-design/icons/LockOutlined';
|
||||
import UserOutlined from '@ant-design/icons/UserOutlined';
|
||||
import { Button } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TextField, InputAdornment } from '@mui/material';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
export const Login = () => {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
const { control, handleSubmit } = useForm();
|
||||
const loginStore = useLoginStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
@@ -15,50 +18,68 @@ export const Login = () => {
|
||||
};
|
||||
}),
|
||||
);
|
||||
useEffect(() => {
|
||||
const isNull = isObjectNull(loginStore.formData);
|
||||
if (isNull) {
|
||||
form.setFieldsValue({});
|
||||
} else {
|
||||
form.setFieldsValue(loginStore.formData);
|
||||
}
|
||||
}, [loginStore.formData]);
|
||||
|
||||
const onFinish = (values: any) => {
|
||||
loginStore.setFormData(values);
|
||||
loginStore.login();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='bg-slate-200 text-slate-900 w-full h-full overflow-hidden'>
|
||||
<div className='bg-gray-100 text-primary w-full h-full overflow-hidden'>
|
||||
<div className='w-full h-full absolute top-[10%] xl:top-[15%] 2xl:top-[18%] 3xl:top-[20%] '>
|
||||
<div className='w-[400px] mx-auto'>
|
||||
<h1 className='mb-4 tracking-widest text-center'>Login</h1>
|
||||
<h1 className='mb-4 tracking-widest text-center'>{t('Login')}</h1>
|
||||
<div className='card border-t-2 border-gray-200 pt-8 px-8'>
|
||||
<Form
|
||||
className='mt-2'
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
labelCol={{
|
||||
span: 6,
|
||||
}}>
|
||||
<Form.Item label='' name='username'>
|
||||
<Input addonBefore={<UserOutlined />} placeholder='Username' />
|
||||
</Form.Item>
|
||||
<Form.Item label='' name='password'>
|
||||
<Input type='password' addonBefore={<LockOutlined />} placeholder='Password' />
|
||||
</Form.Item>
|
||||
<Form.Item label='' colon={false}>
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
type='primary'
|
||||
htmlType='submit'
|
||||
style={{
|
||||
background: '#84d5e8',
|
||||
}}>
|
||||
Login
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<form className='mt-2 flex flex-col gap-8' onSubmit={handleSubmit(onFinish)}>
|
||||
<Controller
|
||||
name='username'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
label={t('Username')}
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position='start'>
|
||||
<UserOutlined className='text-primary!' />
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name='password'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
type='password'
|
||||
label={t('Password')}
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
slotProps={{
|
||||
input: {
|
||||
startAdornment: (
|
||||
<InputAdornment position='start'>
|
||||
<LockOutlined className='text-primary!' />
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className='flex gap-2'>
|
||||
<Button type='submit' variant='contained'>
|
||||
{t('Login')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { Flex, Upload } from 'antd';
|
||||
import { message } from '@/modules/message';
|
||||
import type { GetProp, UploadProps } from 'antd';
|
||||
|
||||
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
||||
|
||||
const getBase64 = (img: FileType, callback: (url: string) => void) => {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener('load', () => callback(reader.result as string));
|
||||
reader.readAsDataURL(img);
|
||||
};
|
||||
|
||||
const beforeUpload = (file: FileType) => {
|
||||
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
|
||||
if (!isJpgOrPng) {
|
||||
message.error('You can only upload JPG/PNG file!');
|
||||
}
|
||||
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||
if (!isLt2M) {
|
||||
message.error('Image must smaller than 2MB!');
|
||||
}
|
||||
return isJpgOrPng && isLt2M;
|
||||
};
|
||||
|
||||
export const AvatarUpload = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [imageUrl, setImageUrl] = useState<string>();
|
||||
|
||||
const handleChange: UploadProps['onChange'] = (info) => {
|
||||
if (info.file.status === 'uploading') {
|
||||
setLoading(true);
|
||||
return;
|
||||
}
|
||||
if (info.file.status === 'done') {
|
||||
// Get this url from response in real world.
|
||||
getBase64(info.file.originFileObj as FileType, (url) => {
|
||||
setLoading(false);
|
||||
setImageUrl(url);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const uploadButton = (
|
||||
<button style={{ border: 0, background: 'none' }} type='button'>
|
||||
{loading ? <LoadingOutlined /> : <PlusOutlined />}
|
||||
<div style={{ marginTop: 8 }}>Upload</div>
|
||||
</button>
|
||||
);
|
||||
const onAciton = async (file) => {
|
||||
console.log('file', file);
|
||||
return '';
|
||||
};
|
||||
const customAction = (file) => {
|
||||
console.log('file', file);
|
||||
};
|
||||
return (
|
||||
<Flex gap='middle' wrap>
|
||||
<Upload
|
||||
name='avatar'
|
||||
listType='picture-circle'
|
||||
className='avatar-uploader'
|
||||
multiple={false}
|
||||
showUploadList={false}
|
||||
action={onAciton}
|
||||
customRequest={customAction}
|
||||
beforeUpload={beforeUpload}
|
||||
onChange={handleChange}>
|
||||
{imageUrl ? <img src={imageUrl} alt='avatar' style={{ width: '100%' }} /> : uploadButton}
|
||||
</Upload>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import { message } from '@/modules/message';
|
||||
import { useImperativeHandle, useRef, forwardRef } from 'react';
|
||||
import type { GetProp, UploadProps } from 'antd';
|
||||
type FileTypeOrg = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
||||
import { uploadFileChunked } from '@kevisual/resources/index.ts';
|
||||
|
||||
export type FileType = {
|
||||
name: string;
|
||||
@@ -10,7 +9,7 @@ export type FileType = {
|
||||
webkitRelativePath: string; // 包含name
|
||||
};
|
||||
|
||||
const beforeUpload = (file: FileTypeOrg) => {
|
||||
const beforeUpload = (file: any) => {
|
||||
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
|
||||
if (!isJpgOrPng) {
|
||||
message.error('You can only upload JPG/PNG file!');
|
||||
@@ -33,22 +32,15 @@ export const FileUpload = forwardRef<any, Props>((props, ref) => {
|
||||
console.log(e.target.files);
|
||||
const file = e.target.files[0];
|
||||
const endType = file.name.split('.').pop();
|
||||
const formData = new FormData();
|
||||
formData.append('file', file, `avatar.${endType}`); // 保留文件夹路径
|
||||
const res = await fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
body: formData, //
|
||||
headers: {
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token'),
|
||||
},
|
||||
}).then((res) => res.json());
|
||||
const filename = `avatar.${endType}`;
|
||||
const res = (await uploadFileChunked(file, {
|
||||
isPublic: true,
|
||||
filename,
|
||||
})) as any;
|
||||
console.log('res', res);
|
||||
if (res?.code === 200) {
|
||||
console.log('res', res);
|
||||
//
|
||||
const [file] = res.data;
|
||||
const { path } = file || {};
|
||||
props?.onChange?.(path);
|
||||
//
|
||||
const resource = res.data?.resource;
|
||||
props?.onChange?.(resource);
|
||||
} else {
|
||||
message.error(res.message || 'Request failed');
|
||||
}
|
||||
|
||||
@@ -53,13 +53,11 @@ export const useUserStore = create<UserStore>((set, get) => {
|
||||
}
|
||||
},
|
||||
updateSelf: async (data) => {
|
||||
const loaded = message.loading('Action in progress..', 0);
|
||||
const res = await query.post({
|
||||
path: 'user',
|
||||
key: 'updateSelf',
|
||||
data,
|
||||
});
|
||||
loaded();
|
||||
if (res.code === 200) {
|
||||
message.success('Success');
|
||||
set({ formData: res.data });
|
||||
|
||||
Reference in New Issue
Block a user