feat: add query-login
This commit is contained in:
238
src/pages/config/edit/List.tsx
Normal file
238
src/pages/config/edit/List.tsx
Normal file
@@ -0,0 +1,238 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useConfigStore } from '../store/config';
|
||||
import { CardBlank } from '@/components/card';
|
||||
import { Button, ButtonGroup, Divider, Drawer, Tab, Tabs, Tooltip } from '@mui/material';
|
||||
import { Edit, Plus, Save, Trash, X } from 'lucide-react';
|
||||
import { useModal } from '@kevisual/center-components/modal/Confirm.tsx';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { TextField } from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IconButton } from '@kevisual/center-components/button/index.tsx';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { load, dump } from 'js-yaml';
|
||||
import CodeEditor from '@uiw/react-textarea-code-editor';
|
||||
import { toast } from 'react-toastify';
|
||||
import { isEmpty, pick } from 'lodash-es';
|
||||
|
||||
type DataYamlEditProps = {
|
||||
onSave: (data: any) => Promise<void>;
|
||||
type?: 'yaml' | 'json';
|
||||
};
|
||||
export const DataYamlEdit = ({ onSave, type }: DataYamlEditProps) => {
|
||||
const { formData } = useConfigStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
formData: state.formData,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const [yaml, setYaml] = useState(formData.data);
|
||||
useEffect(() => {
|
||||
const _fromData = formData.data || {};
|
||||
if (isEmpty(_fromData)) {
|
||||
setYaml('');
|
||||
return;
|
||||
} else {
|
||||
if (type === 'yaml') {
|
||||
const data = dump(_fromData);
|
||||
setYaml(data);
|
||||
} else {
|
||||
const data = JSON.stringify(_fromData, null, 2);
|
||||
setYaml(data);
|
||||
}
|
||||
}
|
||||
}, [formData]);
|
||||
console.log(formData);
|
||||
const handleSave = () => {
|
||||
let data: any = {};
|
||||
try {
|
||||
if (type === 'yaml') {
|
||||
data = load(yaml);
|
||||
} else {
|
||||
data = JSON.parse(yaml);
|
||||
}
|
||||
onSave({ data });
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
const errorMessage = error.message.toString();
|
||||
toast.error(errorMessage || '解析失败,请检查格式');
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<Tooltip title='保存'>
|
||||
<IconButton sx={{ width: 24, height: 24, padding: '8px' }} onClick={handleSave}>
|
||||
<Save />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<div className='text-sm'>{type === 'yaml' ? 'Yaml' : 'Json'} 配置</div>
|
||||
</div>
|
||||
<CodeEditor value={yaml} onChange={(e) => setYaml(e.target.value)} className='w-full h-full grow' language={type === 'yaml' ? 'yaml' : 'json'} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const DrawerEdit = () => {
|
||||
const { t } = useTranslation();
|
||||
const { showEdit, setShowEdit, formData, updateData } = useConfigStore(
|
||||
useShallow((state) => {
|
||||
return {
|
||||
showEdit: state.showEdit,
|
||||
setShowEdit: state.setShowEdit,
|
||||
formData: state.formData,
|
||||
updateData: state.updateData,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const [tab, setTab] = useState<'base' | 'yaml' | 'json'>('base');
|
||||
|
||||
const { control, handleSubmit, reset } = useForm({
|
||||
defaultValues: {
|
||||
title: formData.title || '',
|
||||
description: formData.description || '',
|
||||
key: formData.key || '',
|
||||
},
|
||||
});
|
||||
useEffect(() => {
|
||||
if (showEdit) {
|
||||
const _formData = {
|
||||
id: formData.id || '',
|
||||
title: formData.title || '',
|
||||
description: formData.description || '',
|
||||
key: formData.key || '',
|
||||
};
|
||||
reset(_formData);
|
||||
}
|
||||
}, [showEdit, formData]);
|
||||
const isEdit = !!formData?.id;
|
||||
const onSave = async (values: any) => {
|
||||
await updateData({ ...values, id: formData.id }, { refresh: true });
|
||||
};
|
||||
const onSubmit = (data) => {
|
||||
console.log('Form Data:', data);
|
||||
const pickValue = pick(data, ['title', 'key', 'description']);
|
||||
onSave(pickValue);
|
||||
};
|
||||
return (
|
||||
<Drawer
|
||||
open={showEdit}
|
||||
anchor='right'
|
||||
slotProps={{
|
||||
paper: {
|
||||
sx: {
|
||||
width: '50%',
|
||||
height: '100%',
|
||||
},
|
||||
},
|
||||
}}>
|
||||
<div className='h-full bg-white rounded-lg p-2 w-full overflow-hidden'>
|
||||
<div className='text-2xl font-bold px-2 py-4 flex gap-2 items-center'>
|
||||
<IconButton onClick={() => setShowEdit(false)} size='small'>
|
||||
<X />
|
||||
</IconButton>
|
||||
<div>{formData.title}</div>
|
||||
</div>
|
||||
<Divider />
|
||||
<Tabs value={tab} onChange={(_, value) => setTab(value)}>
|
||||
<Tab label='基本信息' value='base' />
|
||||
<Tab label='Yaml Config' value='yaml' />
|
||||
<Tab label='JSON Config' value='json' />
|
||||
</Tabs>
|
||||
{tab === 'base' && (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='w-full p-2'>
|
||||
<Controller
|
||||
name='title'
|
||||
control={control}
|
||||
render={({ field }) => <TextField {...field} label='Title' variant='outlined' fullWidth margin='normal' />}
|
||||
/>
|
||||
<Controller
|
||||
name='key'
|
||||
control={control}
|
||||
render={({ field }) => <TextField {...field} label='Key' variant='outlined' fullWidth margin='normal' />}
|
||||
/>
|
||||
<Controller
|
||||
name='description'
|
||||
control={control}
|
||||
render={({ field }) => <TextField {...field} label='Description' variant='outlined' fullWidth margin='normal' multiline rows={4} />}
|
||||
/>
|
||||
<Button type='submit' variant='contained' color='primary'>
|
||||
{t('Submit')}
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
{tab === 'yaml' && (
|
||||
<div className='w-full flex flex-col gap-2 px-4 py-2' style={{ height: 'calc(100% - 120px)' }}>
|
||||
<DataYamlEdit onSave={onSave} type='yaml' />
|
||||
</div>
|
||||
)}
|
||||
{tab === 'json' && (
|
||||
<div className='w-full flex flex-col gap-2 px-4 py-2' style={{ height: 'calc(100% - 120px)' }}>
|
||||
<DataYamlEdit onSave={onSave} type='json' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
|
||||
export const List = () => {
|
||||
const { list, getConfig, setShowEdit, setFormData, deleteConfig } = useConfigStore();
|
||||
const [modal, contextHolder] = useModal();
|
||||
useEffect(() => {
|
||||
getConfig();
|
||||
}, []);
|
||||
console.log(list);
|
||||
return (
|
||||
<div className='w-full h-full flex bg-gray-100'>
|
||||
<div className='h-full bg-white'>
|
||||
<div className='p-2'>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setShowEdit(true);
|
||||
setFormData({});
|
||||
}}>
|
||||
<Plus />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className=' grow p-4'>
|
||||
<div className='w-full h-full bg-white rounded-lg p-2 scrollbar '>
|
||||
<div className='flex flex-wrap gap-2'>
|
||||
{list.map((item) => (
|
||||
<div className='card w-[300px]' key={item.id}>
|
||||
<div className='card-title flex font-bold justify-between'>{item.title}</div>
|
||||
<div className='card-content'>{item.description}</div>
|
||||
<div className='card-footer flex justify-end'>
|
||||
<ButtonGroup variant='contained'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setShowEdit(true);
|
||||
setFormData(item);
|
||||
}}>
|
||||
<Edit />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
modal.confirm({
|
||||
title: 'Delete',
|
||||
content: 'Are you sure delete this data?',
|
||||
onOk: () => {
|
||||
deleteConfig(item.id);
|
||||
},
|
||||
});
|
||||
}}>
|
||||
<Trash />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<CardBlank />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DrawerEdit />
|
||||
{contextHolder}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
14
src/pages/config/index.tsx
Normal file
14
src/pages/config/index.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { LayoutMain } from '@/modules/layout';
|
||||
import { List } from './edit/List.tsx';
|
||||
import { Redirect } from '@/modules/Redirect';
|
||||
export const App = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route element={<LayoutMain title='Config' />}>
|
||||
<Route path='/' element={<Redirect to='/config/edit/list' />}></Route>
|
||||
<Route path='edit/list' element={<List />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
53
src/pages/config/store/config.ts
Normal file
53
src/pages/config/store/config.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { create } from 'zustand';
|
||||
import { query } from '@/modules/query';
|
||||
import { toast } from 'react-toastify';
|
||||
import { QueryConfig } from '@kevisual/query-config';
|
||||
|
||||
export const queryConfig = new QueryConfig({ query });
|
||||
|
||||
interface ConfigStore {
|
||||
list: any[];
|
||||
getConfig: () => Promise<void>;
|
||||
updateData: (data: any, opts?: { refresh?: boolean }) => Promise<any>;
|
||||
showEdit: boolean;
|
||||
setShowEdit: (showEdit: boolean) => void;
|
||||
formData: any;
|
||||
setFormData: (formData: any) => void;
|
||||
deleteConfig: (id: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useConfigStore = create<ConfigStore>((set, get) => ({
|
||||
list: [],
|
||||
getConfig: async () => {
|
||||
const res = await queryConfig.listConfig();
|
||||
if (res.code === 200) {
|
||||
set({ list: res.data?.list || [] });
|
||||
}
|
||||
},
|
||||
updateData: async (data: any, opts?: { refresh?: boolean }) => {
|
||||
const res = await queryConfig.updateConfig(data);
|
||||
if (res.code === 200) {
|
||||
get().setFormData(res.data);
|
||||
if (opts?.refresh ?? true) {
|
||||
get().getConfig();
|
||||
}
|
||||
toast.success('保存成功');
|
||||
} else {
|
||||
toast.error('保存失败');
|
||||
}
|
||||
return res;
|
||||
},
|
||||
showEdit: false,
|
||||
setShowEdit: (showEdit: boolean) => set({ showEdit }),
|
||||
formData: {},
|
||||
setFormData: (formData: any) => set({ formData }),
|
||||
deleteConfig: async (id: string) => {
|
||||
const res = await queryConfig.deleteConfig(id);
|
||||
if (res.code === 200) {
|
||||
get().getConfig();
|
||||
toast.success('删除成功');
|
||||
} else {
|
||||
toast.error('删除失败');
|
||||
}
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user