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

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