feat: 添加i18n,美化界面

This commit is contained in:
2025-03-20 02:29:01 +08:00
parent 27d9bdf54e
commit c206add7eb
56 changed files with 2743 additions and 928 deletions

View File

@@ -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>

View File

@@ -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>
);

View File

@@ -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>
);

View 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;

View File

@@ -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 }),
};
});

View File

@@ -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>
);
};

View File

@@ -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'>

View File

@@ -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>
);

View File

@@ -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');
}

View File

@@ -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'>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
);
};

View File

@@ -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');
}

View File

@@ -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 });